一、ribbon 服务消费者
ribbon 提供了负载均衡和重试功能, 它底层是使用 RestTemplate 进行 Rest api 调用
1.RestTemplate
RestTemplate 是SpringBoot提供的一个Rest远程调用工具
它的常用方法:
- getForObject() – 执行get请求
- postForObject() – 执行post请求
之前的系统结构是浏览器直接访问后台服务
后面我们通过一个Demo项目演示 Spring Cloud 远程调用
下面我们先不使用ribbon, 单独使用RestTemplate来执行远程调用
1.1 新建 sp06-ribbon 项目
1.2 修改pom文件
- eureka-client 中已经包含 ribbon 依赖
- 需要添加 sp01-commons 依赖
<?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.tedu</groupId> <artifactId>sp06-ribbon</artifactId> <version>0.0.1-SNAPSHOT</version> <name>sp06-ribbon</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <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-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>cn.tedu</groupId> <artifactId>sp01-commons</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
1.3 修改application.yml
spring: application: name: ribbon server: port: 3001 eureka: client: service-url: defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
1.4 主程序
-
创建
RestTemplate
实例RestTemplate
是用来调用其他微服务的工具类,封装了远程调用代码,提供了一组用于远程调用的模板方法,例如:getForObject()
、postForObject()
等
package cn.tedu.sp06; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableDiscoveryClient @SpringBootApplication public class Sp06RibbonApplication { //创建 RestTemplate 实例,并存入 spring 容器 @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Sp06RibbonApplication.class, args); } }
1.5 创建 RibbonController 类
package com.tedu.sp06.controller; import cn.tedu.sp01.pojo.Item; import cn.tedu.sp01.pojo.Order; import cn.tedu.sp01.pojo.User; import cn.tedu.web.util.JsonResult; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import java.util.List; @RestController @Slf4j public class RibbonController { @Autowired private RestTemplate rt; @GetMapping("/item-service/{orderId}") public JsonResult<List<Item>> getItems(@PathVariable String orderId) { // 调用远程的商品服务,获得订单的商品列表 // {1} - RestTemplate自己定义的一种站位符格式 return rt.getForObject("http://localhost:8001/{1}", JsonResult.class, orderId); //用 orderId 填充占位符 {1} } @PostMapping("/item-service/decreaseNumber") public JsonResult<?> decreaseNumber(@RequestBody List<Item> items) { return rt.postForObject( "http://localhost:8001/decreaseNumber", items, JsonResult.class); } @GetMapping("/user-service/{userId}") public JsonResult<User> getUser(@PathVariable Integer userId) { return rt.getForObject("http://localhost:8101/{1}", JsonResult.class, userId); } @GetMapping("/user-service/{userId}/score") // ?score=1000 public JsonResult<?> addScore(@PathVariable Integer userId, @RequestParam Integer score) { return rt.getForObject("http://localhost:8101/{1}/score?score={2}", JsonResult.class, userId, score); } @GetMapping("/order-service/{orderId}") public JsonResult<Order> getOrder(@PathVariable String orderId) { return rt.getForObject("http://localhost:8201/{1}", JsonResult.class, orderId); } @GetMapping("/order-service/") public JsonResult<?> addOrder() { return rt.getForObject("http://localhost:8201/",JsonResult.class); } }
1.6 启动服务,并访问测试
http://localhost:3001/item-service/35
http://localhost:3001/item-service/decreaseNumber
使用postman,POST发送以下格式数据:
[{"id":1, "name":"abc", "number":23},{"id":2, "name":"def", "number":11}]
http://localhost:3001/user-service/7
http://localhost:3001/user-service/7/score?score=100
2. ribbon 负载均衡和重试
2.1 Ribbon 负载均衡
2.1.1 添加 ribbon 依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
2.1.2 RestTemplate 设置 @LoadBalanced
@LoadBalanced
负载均衡注解,会对 RestTemplate
实例进行封装,创建动态代理对象,并切入(AOP)负载均衡代码,把请求分发到集群中的服务器
package cn.tedu.sp06; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableDiscoveryClient @SpringBootApplication public class Sp06RibbonApplication { @LoadBalanced //负载均衡注解 @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Sp06RibbonApplication.class, args); } }
2.1.3 访问路径设置为服务id
package cn.tedu.sp06.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import cn.tedu.sp01.pojo.Item; import cn.tedu.sp01.pojo.Order; import cn.tedu.sp01.pojo.User; import cn.tedu.web.util.JsonResult; @RestController public class RibbonController { @Autowired private RestTemplate rt; @GetMapping("/item-service/{orderId}") @HystrixCommand(fallbackMethod = "getItemsFB") public JsonResult<List<Item>> getItems(@PathVariable String orderId) { //调用远程的商品服务,获得订单的商品列表 //{1} - RestTemplate自己定义的一种站位符格式 @PostMapping("/item-service/decreaseNumber") public JsonResult<?> decreaseNumber(@RequestBody List<Item> items) { return rt.postForObject("http://item-service/decreaseNumber", items, JsonResult.class); } @GetMapping("/user-service/{userId}") public JsonResult<User> getUser(@PathVariable Integer userId) { return rt.getForObject("http://user-service/{1}", JsonResult.class, userId); } @GetMapping("/user-service/{userId}/score") // ?score=1000 public JsonResult<?> addScore(@PathVariable Integer userId, @RequestParam Integer score) { return rt.getForObject("http://user-service/{1}/score?score={2}", JsonResult.class, userId, score); } @GetMapping("/order-service/{orderId}") public JsonResult<Order> getOrder(@PathVariable String orderId) { return rt.getForObject("http://order-service/{1}", JsonResult.class, orderId); } @GetMapping("/order-service") public JsonResult<?> addOrder() { return rt.getForObject("http://order-service", JsonResult.class); }
2.1.4 访问测试
-
访问测试,ribbon 会把请求分发到 8001 和 8002 两个服务端口上
2.2 ribbon 重试
2.2.1 添加 ribbon 依赖
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
2.2.2 配置 application.yml
spring: application: name: ribbon server: port: 3001 #连接eureka eureka: client: service-url: defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka ribbon: #单台重试次数 MaxAutoRetries: 1 #更换服务器次数 MaxAutoRetriesNextServer: 2
2.2.3 主程序设置 RestTemplate 的请求工厂的超时属性
package com.tedu.sp06; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableCircuitBreaker//启用断路器 public class Sp06RibbonApplication { public static void main(String[] args) { SpringApplication.run(Sp06RibbonApplication.class, args); } //创建 RestTemplate 实例,并存入 spring 容器 @LoadBalanced//负载均衡注解,对 RestTemplate 进行封装,增强RestTemplate的功能 @Bean public RestTemplate getRestTemplate (){ SimpleClientHttpRequestFactory f=new SimpleClientHttpRequestFactory(); //两个超时参数默认-1,表示不启用 f.setConnectTimeout(1000); f.setReadTimeout(1000); return new RestTemplate(); } }
2.2.4 ItemController 添加延迟代码,以便测试 ribbon 的重试机制
package cn.tedu.sp02.item.controller; import cn.tedu.sp01.pojo.Item; import cn.tedu.sp01.service.ItemService; import cn.tedu.web.util.JsonResult; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Random; @RestController @Slf4j public class ItemController { @Autowired private ItemService itemService; // 为了后面测试集群多台服务器,这里注入端口号 @Value("${server.port}") private int port; @GetMapping("/{orderId}") public JsonResult<List<Item>> getItems(@PathVariable String orderId) throws InterruptedException { log.info("orderId="+orderId+", port="+port); //添加随机延迟代码 if (Math.random()<0.9){ //随机产生0到5000的随机延迟时长 long t=new Random().nextInt(5000); log.info("延迟:"+t); Thread.sleep(t); } // List<Item> items = itemService.getItems(orderId); return JsonResult.ok().msg("port="+port).data(items); } // 只处理post请求 // @RequestBody 注解,完整接收post请求协议体数据 @PostMapping("/decreaseNumber") public JsonResult<?> decreaseNumber(@RequestBody List<Item> items) { itemService.decreaseNumber(items); return JsonResult.ok().msg("减少商品库存成功"); } }
2.2.5 测试 ribbon 重试机制
-
通过 ribbon 访问 item-service,当超时,ribbon 会重试请求集群中其他服务器