SpringCloud系列教材 (七)- 视图微服务-Ribbon
SpringCloud系列教材 (七)- 视图微服务-Ribbon
步骤1:Ribbon 概念
步骤2:先运行,看到效果,再学习
步骤3:模仿和排错
步骤4:创建子项目
步骤5:pom.xml
步骤6:实体类
步骤7:Ribbon 客户端
步骤8:服务类
步骤9:控制器
步骤10:products.html
步骤11:启动类
步骤12:application.yml
步骤13:启动并访问
步骤14:调用图
步骤2:先运行,看到效果,再学习
步骤3:模仿和排错
步骤4:创建子项目
步骤5:pom.xml
步骤6:实体类
步骤7:Ribbon 客户端
步骤8:服务类
步骤9:控制器
步骤10:products.html
步骤11:启动类
步骤12:application.yml
步骤13:启动并访问
步骤14:调用图
步骤 1 : Ribbon 概念
接下来,我们就要访问前面注册好的数据微服务了。 springcloud 提供了两种方式,一种是 Ribbon,一种是 Feign。
Ribbon 是使用 restTemplate 进行调用,并进行客户端负载均衡。 什么是客户端负载均衡呢? 在前面 注册数据微服务 里,注册了8001和8002两个微服务, Ribbon 会从注册中心获知这个信息,然后由 Ribbon 这个客户端自己决定是调用哪个,这个就叫做客户端负载均衡。
Feign 是什么呢? Feign 是对 Ribbon的封装,调用起来更简单。。。
本知识点讲解如何实现 Ribbon 客户端。
Ribbon 是使用 restTemplate 进行调用,并进行客户端负载均衡。 什么是客户端负载均衡呢? 在前面 注册数据微服务 里,注册了8001和8002两个微服务, Ribbon 会从注册中心获知这个信息,然后由 Ribbon 这个客户端自己决定是调用哪个,这个就叫做客户端负载均衡。
Feign 是什么呢? Feign 是对 Ribbon的封装,调用起来更简单。。。
本知识点讲解如何实现 Ribbon 客户端。
步骤 2 : 先运行,看到效果,再学习
老规矩,先下载下载区(点击进入)的可运行项目,配置运行起来,确认可用之后,再学习做了哪些步骤以达到这样的效果。
1. 先启动 EurekaServerApplication
2. 然后启动两次 ProductDataServiceApplication, 分别输入 8001和8002.
3. 然后运行 ProductViewServiceRibbonApplication 以启动 微服务,然后访问地址:
http://127.0.0.1:8010/products
多刷新几遍,会发现这个端口有时候是 8001,有时候是8002. 从而观察到访问 数据服务集群,客户端负载均衡的效果。
1. 先启动 EurekaServerApplication
2. 然后启动两次 ProductDataServiceApplication, 分别输入 8001和8002.
3. 然后运行 ProductViewServiceRibbonApplication 以启动 微服务,然后访问地址:
http://127.0.0.1:8010/products
多刷新几遍,会发现这个端口有时候是 8001,有时候是8002. 从而观察到访问 数据服务集群,客户端负载均衡的效果。
步骤 3 : 模仿和排错
在确保可运行项目能够正确无误地运行之后,再严格照着教程的步骤,对代码模仿一遍。
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。
推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
模仿过程难免代码有出入,导致无法得到期望的运行结果,此时此刻通过比较正确答案 ( 可运行项目 ) 和自己的代码,来定位问题所在。
采用这种方式,学习有效果,排错有效率,可以较为明显地提升学习速度,跨过学习路上的各个槛。
推荐使用diffmerge软件,进行文件夹比较。把你自己做的项目文件夹,和我的可运行项目文件夹进行比较。
这个软件很牛逼的,可以知道文件夹里哪两个文件不对,并且很明显地标记出来
这里提供了绿色安装和使用教程:diffmerge 下载和使用教程
步骤 4 : 创建子项目
创建子项目 product-view-service-ribbon
步骤 5 : pom.xml
包含以下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 协议, 那么就无法演示重要的 微服务端调用了,所以站长这里特意没有用前后端分离,以便于大家观察和掌握微服务的彼此调用
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 > |
步骤 6 : 实体类
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; } } |
步骤 7 : Ribbon 客户端
Ribbon 客户端, 通过 restTemplate 访问 http://PRODUCT-DATA-SERVICE/products , 而 product-data-service 既不是域名也不是ip地址,而是 数据服务在 eureka 注册中心的名称。
注意看,这里只是指定了要访问的 微服务名称,但是并没有指定端口号到底是 8001, 还是 8002.
注意看,这里只是指定了要访问的 微服务名称,但是并没有指定端口号到底是 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 ); } } |
步骤 8 : 服务类
服务类,数据从 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(); } } |
步骤 9 : 控制器
控制器,把数据取出来放在 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" ; } } |
步骤 10 : products.html
遍历数据
<!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 > |
步骤 11 : 启动类
启动类, 注解多了个 @EnableDiscoveryClient, 表示用于发现eureka 注册中心的微服务。
还多了个 RestTemplate,就表示用 restTemplate 这个工具来做负载均衡
Ribbon 客户端 里就用到了这个 restTemplate.
还多了个 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(); } } |
步骤 12 : application.yml
配置类,指定了 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 |
步骤 13 : 启动并访问
运行 ProductViewServiceRibbonApplication 以启动 微服务,然后访问地址:
http://127.0.0.1:8010/products
多刷新几遍,会发现这个端口有时候是 8001,有时候是8002. 从而观察到访问 数据服务集群,客户端负载均衡的效果。
http://127.0.0.1:8010/products
多刷新几遍,会发现这个端口有时候是 8001,有时候是8002. 从而观察到访问 数据服务集群,客户端负载均衡的效果。
步骤 14 : 调用图
如图所示:
1. 首先数据微服务和视图微服务都被 eureka 管理起来了。
2. 数据服务是由两个实例的集群组成的,端口分别是 8001 , 8002
3. 视图微服务通过 注册中心调用微服务, 然后负载均衡到 8001 或者 8002 端口的应用上。
1. 首先数据微服务和视图微服务都被 eureka 管理起来了。
2. 数据服务是由两个实例的集群组成的,端口分别是 8001 , 8002
3. 视图微服务通过 注册中心调用微服务, 然后负载均衡到 8001 或者 8002 端口的应用上。