步骤 2 : 先运行,看到效果,再学习 步骤 3 : 模仿和排错 步骤 4 : 创建子项目 步骤 5 : pom.xml 步骤 6 : 实体类 步骤 7 : Ribbon 客户端 步骤 8 : 服务类 步骤 9 : 控制器 步骤 10 : products.html 步骤 11 : 启动类 步骤 12 : application.yml 步骤 13 : 启动并访问 步骤 14 : 调用图
接下来,我们就要访问前面注册好的数据微服务了。 springcloud 提供了两种方式,一种是 Ribbon,一种是 Feign。
Ribbon 是使用 restTemplate 进行调用,并进行客户端负载均衡。 什么是客户端负载均衡呢? 在前面 注册数据微服务 里,注册了8001和8002两个微服务, Ribbon 会从注册中心获知这个信息,然后由 Ribbon 这个客户端自己决定是调用哪个,这个就叫做客户端负载均衡。 Feign 是什么呢? Feign 是对 Ribbon的封装,调用起来更简单。。。 本知识点讲解如何实现 Ribbon 客户端。
老规矩,先下载右上角的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
1. 先启动 EurekaServerApplication 2. 然后启动两次 ProductDataServiceApplication, 分别输入 8001和8002. 3. 然后运行 ProductViewServiceRibbonApplication 以启动 微服务,然后访问地址: http://127.0.0.1:8010/products 多刷新几遍,会发现这个端口有时候是 8001,有时候是8002. 从而观察到访问 数据服务集群,客户端负载均衡的效果。
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。 采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。 推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。 这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来 这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
创建子项目 product-view-service-ribbon
包含以下jar:
spring-cloud-starter-netflix-eureka-client: eureka 客户端 spring-boot-starter-web: springmvc spring-boot-starter-thymeleaf: thymeleaf 做服务端渲染 有同学就会问了,为什么不用前后端分离呢? 干嘛要用 thymeleaf 做服务端渲染呢? 原因如下: 1. 使用前后端分离,站长多半会用 vue.js + axios.js来做,就像 springboot 天猫教程那样。 如果学习者没有这个基础,就会加重学习的负担。 2. 使用前后端分离,是走的 http 协议, 那么就无法演示重要的 微服务端调用了,所以站长这里特意没有用前后端分离,以便于大家观察和掌握微服务的彼此调用 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.how2j.springcloud</groupId>
<artifactId>springcloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>product-view-service-ribbon</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
</project>
package cn.how2j.springcloud.pojo;
public class Product {
private int id;
private String name;
private int price;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public Product() {
}
public Product(int id, String name, int price) {
super();
this.id = id;
this.name = name;
this.price = price;
}
}
Ribbon 客户端, 通过 restTemplate 访问 http://PRODUCT-DATA-SERVICE/products , 而 product-data-service 既不是域名也不是ip地址,而是 数据服务在 eureka 注册中心的名称。
注意看,这里只是指定了要访问的 微服务名称,但是并没有指定端口号到底是 8001, 还是 8002. package cn.how2j.springcloud.client;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import cn.how2j.springcloud.pojo.Product;
@Component
public class ProductClientRibbon {
@Autowired
RestTemplate restTemplate;
public List<Product> listProdcuts() {
return restTemplate.getForObject("http://PRODUCT-DATA-SERVICE/products",List.class);
}
}
服务类,数据从 ProductClientRibbon 中获取
package cn.how2j.springcloud.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import cn.how2j.springcloud.client.ProductClientRibbon;
import cn.how2j.springcloud.pojo.Product;
@Service
public class ProductService {
@Autowired ProductClientRibbon productClientRibbon;
public List<Product> listProducts(){
return productClientRibbon.listProdcuts();
}
}
package cn.how2j.springcloud.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import cn.how2j.springcloud.client.ProductClientRibbon; import cn.how2j.springcloud.pojo.Product; @Service public class ProductService { @Autowired ProductClientRibbon productClientRibbon; public List<Product> listProducts(){ return productClientRibbon.listProdcuts(); } }
控制器,把数据取出来放在 product.html 中
package cn.how2j.springcloud.web;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import cn.how2j.springcloud.pojo.Product;
import cn.how2j.springcloud.service.ProductService;
@Controller
public class ProductController {
@Autowired ProductService productService;
@RequestMapping("/products")
public Object products(Model m) {
List<Product> ps = productService.listProducts();
m.addAttribute("ps", ps);
return "products";
}
}
遍历数据
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>products</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style>
table {
border-collapse:collapse;
width:400px;
margin:20px auto;
}
td,th{
border:1px solid gray;
}
</style>
</head>
<body>
<div class="workingArea">
<table>
<thead>
<tr>
<th>id</th>
<th>产品名称</th>
<th>价格</th>
</tr>
</thead>
<tbody>
<tr th:each="p: ${ps}">
<td th:text="${p.id}"></td>
<td th:text="${p.name}"></td>
<td th:text="${p.price}"></td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
启动类, 注解多了个 @EnableDiscoveryClient, 表示用于发现eureka 注册中心的微服务。
还多了个 RestTemplate,就表示用 restTemplate 这个工具来做负载均衡 @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } Ribbon 客户端 里就用到了这个 restTemplate. package cn.how2j.springcloud;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.NetUtil;
import cn.hutool.core.util.NumberUtil;
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
public class ProductViewServiceRibbonApplication {
public static void main(String[] args) {
int port = 0;
int defaultPort = 8010;
Future<Integer> future = ThreadUtil.execAsync(() ->{
int p = 0;
System.out.println("请于5秒钟内输入端口号, 推荐 8010 超过5秒将默认使用 " + defaultPort);
Scanner scanner = new Scanner(System.in);
while(true) {
String strPort = scanner.nextLine();
if(!NumberUtil.isInteger(strPort)) {
System.err.println("只能是数字");
continue;
}
else {
p = Convert.toInt(strPort);
scanner.close();
break;
}
}
return p;
});
try{
port=future.get(5,TimeUnit.SECONDS);
}
catch (InterruptedException | ExecutionException | TimeoutException e){
port = defaultPort;
}
if(!NetUtil.isUsableLocalPort(port)) {
System.err.printf("端口%d被占用了,无法启动%n", port );
System.exit(1);
}
new SpringApplicationBuilder(ProductViewServiceRibbonApplication.class).properties("server.port=" + port).run(args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
配置类,指定了 eureka server 的地址,以及自己的名称。 另外是一些 thymeleaf 的默认配置。
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: product-view-service-ribbon
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
content-type: text/html
mode: HTML5
eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ spring: application: name: product-view-service-ribbon thymeleaf: cache: false prefix: classpath:/templates/ suffix: .html encoding: UTF-8 content-type: text/html mode: HTML5
运行 ProductViewServiceRibbonApplication 以启动 微服务,然后访问地址:
http://127.0.0.1:8010/products 多刷新几遍,会发现这个端口有时候是 8001,有时候是8002. 从而观察到访问 数据服务集群,客户端负载均衡的效果。
如图所示:
1. 首先数据微服务和视图微服务都被 eureka 管理起来了。 2. 数据服务是由两个实例的集群组成的,端口分别是 8001 , 8002 3. 视图微服务通过 注册中心调用微服务, 然后负载均衡到 8001 或者 8002 端口的应用上。
HOW2J公众号,关注后实时获知最新的教程和优惠活动,谢谢。
问答区域
2022-03-02
这里为什么说是RestTemplate做负载均衡?RestTemplate能做这个? 还有为什么是客户端负载均衡?不是服务端负载均衡吗?希望语言表述更加严谨一些
2022-01-06
用右上角站长给的项目,访问 http://localhost:8010/products 时报错了 QAQ...
回答已经提交成功,正在审核。 请于 我的回答 处查看回答记录,谢谢
2021-02-24
冗余内容
2021-02-22
No instances available for XXX
2020-07-26
(直接下载站长源程序运行)视图微服务调用数据微服务调用异常:org.springframework.web.client.HttpClientErrorException: 400 null
提问太多,页面渲染太慢,为了加快渲染速度,本页最多只显示几条提问。还有 32 条以前的提问,请 点击查看
提问之前请登陆
提问已经提交成功,正在审核。 请于 我的提问 处查看提问记录,谢谢
|