SpringCloud-第二部分

第九章、OpenFeign

OpenFeign是什么

官网解释:
https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-openfeign

Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。
它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡

SpringCloud-第二部分 - 图1

Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可

GitHub : https://github.com/spring-cloud/spring-cloud-openfeign

Feign能干什么
Feign旨在使编写Java Http客户端变得更容易。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。

Feign集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用

Feign和OpenFeign两者区别

Feign OpenFeign
Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端
Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务
OpenFeign是Spring Cloud 在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

org.springframework.cloud
spring-cloud-starter-feign

org.springframework.cloud
spring-cloud-starter-openfeign

OpenFeign使用步骤

接口+注解

微服务调用接口+@FeignClient

新建cloud-consumer-feign-order80

Feign在消费端使用

SpringCloud-第二部分 - 图2

pom:

  1. <!--openfeign-->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-openfeign</artifactId>
  5. </dependency>

yml:

  1. server:
  2. port: 80
  3. eureka:
  4. client:
  5. register-with-eureka: false
  6. service-url:
  7. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/

主启动: @EnableFeignClients @FeignClient

  1. @SpringBootApplication
  2. @EnableFeignClients
  3. public class OrderFeignMain80{
  4. public static void main(String[] args){
  5. SpringApplication.run(OrderFeignMain80.class,args);
  6. }
  7. }

业务类:

业务逻辑接口+@FeignClient配置调用provider服务

新建PaymentFeignService接口并新增注解@FeignClient

控制层Controller

service:

  1. @Component
  2. @FeignClient(value = "CLOUD-PAYMENT-SERVICE")
  3. public interface PaymentFeignService{
  4. @GetMapping(value = "/payment/get/{id}")
  5. CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
  6. }

controller:

  1. @RestController
  2. public class OrderFeignController{
  3. @Resource
  4. private PaymentFeignService paymentFeignService;
  5. @GetMapping(value = "/consumer/payment/get/{id}")
  6. public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
  7. return paymentFeignService.getPaymentById(id);
  8. }
  9. }

测试:

先启动2个eureka集群7001/7002

再启动2个微服务8001/8002

启动OpenFeign启动

http://localhost/consumer/payment/get/31

Feign自带负载均衡配置项(集成了ribbon)

小总结:

SpringCloud-第二部分 - 图3

OpenFeign超时控制

超时设置,故意设置超时演示出错情况:

服务提供方8001故意写暂停程序

  1. @RestController
  2. @Slf4j
  3. public class PaymentController
  4. {
  5. @Value("${server.port}")
  6. private String serverPort;
  7. @Resource
  8. private PaymentService paymentService;
  9. @Resource
  10. private DiscoveryClient discoveryClient;
  11. @PostMapping(value = "/payment/create")
  12. public CommonResult create(@RequestBody Payment payment){
  13. int result = paymentService.create(payment);
  14. log.info("*****插入操作返回结果:" + result);
  15. if(result > 0)
  16. {
  17. return new CommonResult(200,"插入成功,返回结果"+result+"\t 服务端口:"+serverPort,payment);
  18. }else{
  19. return new CommonResult(444,"插入失败",null);
  20. }
  21. }
  22. @GetMapping(value = "/payment/get/{id}")
  23. public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
  24. Payment payment = paymentService.getPaymentById(id);
  25. log.info("*****查询结果:{}",payment);
  26. if (payment != null) {
  27. return new CommonResult(200,"查询成功"+"\t 服务端口:"+serverPort,payment);
  28. }else{
  29. return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
  30. }
  31. }
  32. @GetMapping(value = "/payment/discovery")
  33. public Object discovery(){
  34. List<String> services = discoveryClient.getServices();
  35. for (String element : services) {
  36. System.out.println(element);
  37. }
  38. List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
  39. for (ServiceInstance element : instances) {
  40. System.out.println(element.getServiceId() + "\t" + element.getHost() + "\t" + element.getPort() + "\t" + element.getUri());
  41. }
  42. return this.discoveryClient;
  43. }
  44. @GetMapping(value = "/payment/lb")
  45. public String getPaymentLB(){
  46. System.out.println("*****lb from port: "+serverPort);
  47. return serverPort;
  48. }
  49. @GetMapping(value = "/payment/feign/timeout")
  50. public String paymentFeignTimeOut(){
  51. System.out.println("*****paymentFeignTimeOut from port: "+serverPort);
  52. //暂停几秒钟线程
  53. try {
  54. TimeUnit.SECONDS.sleep(3);
  55. } catch (InterruptedException e) {
  56. e.printStackTrace();
  57. }
  58. return serverPort;
  59. }
  60. }

服务消费方80添加超时方法PaymentFeignService

  1. @Component
  2. @FeignClient(value = "CLOUD-PAYMENT-SERVICE")
  3. public interface PaymentFeignService{
  4. @GetMapping(value = "/payment/get/{id}")
  5. CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
  6. @GetMapping(value = "/payment/feign/timeout")
  7. String paymentFeignTimeOut();
  8. }

服务消费方80添加超时方法OrderFeignController

  1. @RestController
  2. public class OrderFeignController{
  3. @Resource
  4. private PaymentFeignService paymentFeignService;
  5. @GetMapping(value = "/consumer/payment/get/{id}")
  6. public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
  7. return paymentFeignService.getPaymentById(id);
  8. }
  9. @GetMapping(value = "/consumer/payment/feign/timeout")
  10. public String paymentFeignTimeOut(){
  11. return paymentFeignService.paymentFeignTimeOut();
  12. }
  13. }

测试:

http://localhost/consumer/payment/feign/timeout

错误页面:

SpringCloud-第二部分 - 图4

OpenFeign默认等待1秒钟,超过后报错

是什么

默认Feign客户端只等待一秒钟,但是服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制。

yml文件中开启配置

OpenFeign默认支持Ribbon

SpringCloud-第二部分 - 图5

YML文件里需要开启OpenFeign客户端超时控制

  1. server:
  2. port: 80
  3. eureka:
  4. client:
  5. register-with-eureka: false
  6. service-url:
  7. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
  8. #设置feign客户端超时时间(OpenFeign默认支持ribbon)
  9. ribbon:
  10. #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  11. ReadTimeout: 5000
  12. #指的是建立连接后从服务器读取到可用资源所用的时间
  13. ConnectTimeout: 5000

OpenFeign日志打印功能

日志打印功能

是什么

Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。说白了就是对Feign接口的调用情况进行监控和输出

日志级别

  1. NONE:默认的,不显示任何日志;
  2. BASIC:仅记录请求方法、URL、响应状态码及执行时间;
  3. HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
  4. FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

配置日志bean

  1. @Configuration
  2. public class FeignConfig{
  3. @Bean
  4. Logger.Level feignLoggerLevel(){
  5. return Logger.Level.FULL;
  6. }
  7. }

YML文件里需要开启日志的Feign客户端

  1. server:
  2. port: 80
  3. eureka:
  4. client:
  5. register-with-eureka: false
  6. service-url:
  7. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
  8. #设置feign客户端超时时间
  9. #springCloud默认开启支持ribbon
  10. ribbon:
  11. #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  12. ReadTimeout: 5000
  13. #指的是建立连接后从服务器读取到可用资源所用的时间
  14. ConnectTimeout: 5000
  15. logging:
  16. level:
  17. # feign日志以什么级别监控哪个接口
  18. com.atguigu.springcloud.service.PaymentFeignService: debug

后台日志查看

SpringCloud-第二部分 - 图6

第十章、Hystrix断路器

概述

分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。

SpringCloud-第二部分 - 图7

服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.

  1. 对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。<br />
  2. 所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

豪猪哥是什么

  1. **Hystrix**是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
  2. “**断路器**”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

能干嘛

服务降级、服务熔断、接近实时的监控

官网资料

https://github.com/Netflix/Hystrix/wiki/How-To-Use

Hystrix官宣,停更进维

https://github.com/Netflix/Hystrix

SpringCloud-第二部分 - 图8

被动修复bugs

不再接受合并请求

不再发布新版本

Hystrix重要概念

服务降级

服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,fallback

哪些情况会出发降级

程序运行异常

超时

服务熔断触发服务降级

线程池/信号量打满也会导致服务降级

服务熔断

类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示

就是保险丝

服务的降级->进而熔断->恢复调用链路

服务限流

秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行

hystrix案例

构建

新建cloud-provider-hystrix-payment8001

POM

  1. <dependencies>
  2. <!--hystrix-->
  3. <dependency>
  4. <groupId>org.springframework.cloud</groupId>
  5. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  6. </dependency>
  7. <!--eureka client-->
  8. <dependency>
  9. <groupId>org.springframework.cloud</groupId>
  10. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  11. </dependency>
  12. <!--web-->
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-web</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-actuator</artifactId>
  20. </dependency>
  21. <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  22. <groupId>com.atguigu.springcloud</groupId>
  23. <artifactId>cloud-api-commons</artifactId>
  24. <version>${project.version}</version>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-devtools</artifactId>
  29. <scope>runtime</scope>
  30. <optional>true</optional>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.projectlombok</groupId>
  34. <artifactId>lombok</artifactId>
  35. <optional>true</optional>
  36. </dependency>
  37. <dependency>
  38. <groupId>org.springframework.boot</groupId>
  39. <artifactId>spring-boot-starter-test</artifactId>
  40. <scope>test</scope>
  41. </dependency>
  42. </dependencies>

YML

  1. server:
  2. port: 8001
  3. spring:
  4. application:
  5. name: cloud-provider-hystrix-payment
  6. eureka:
  7. client:
  8. register-with-eureka: true
  9. fetch-registry: true
  10. service-url:
  11. #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
  12. defaultZone: http://eureka7001.com:7001/eureka

主启动

  1. @SpringBootApplication
  2. @EnableEurekaClient //本服务启动后会自动注册进eureka服务中
  3. public class PaymentHystrixMain8001{
  4. public static void main(String[] args){
  5. SpringApplication.run(PaymentHystrixMain8001.class,args);
  6. }
  7. }

业务类

service

  1. @Service
  2. public class PaymentService
  3. {
  4. /**
  5. * 正常访问,一切OK
  6. * @param id
  7. * @return
  8. */
  9. public String paymentInfo_OK(Integer id){
  10. return "线程池:"+Thread.currentThread().getName()+"paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O";
  11. }
  12. /**
  13. * 超时访问,演示降级
  14. * @param id
  15. * @return
  16. */
  17. public String paymentInfo_TimeOut(Integer id){
  18. try {
  19. TimeUnit.SECONDS.sleep(3);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗费3秒";
  24. }
  25. }

controller

  1. @RestController
  2. @Slf4j
  3. public class PaymentController
  4. {
  5. @Autowired
  6. private PaymentService paymentService;
  7. @Value("${server.port}")
  8. private String serverPort;
  9. @GetMapping("/payment/hystrix/ok/{id}")
  10. public String paymentInfo_OK(@PathVariable("id") Integer id){
  11. String result = paymentService.paymentInfo_OK(id);
  12. log.info("****result: "+result);
  13. return result;
  14. }
  15. @GetMapping("/payment/hystrix/timeout/{id}")
  16. public String paymentInfo_TimeOut(@PathVariable("id") Integer id) throws InterruptedException{
  17. String result = paymentService.paymentInfo_TimeOut(id);
  18. log.info("****result: "+result);
  19. return result;
  20. }
  21. }

正常测试

启动eureka7001

启动cloud-provider-hystrix-payment8001

访问:success的方法—-> http://localhost:8001/payment/hystrix/ok/31

每次调用耗费5秒钟 —-> http://localhost:8001/payment/hystrix/timeout/31

上述module均OK —-> 以上述为根基平台,从正确->错误->降级熔断->恢复

高并发测试

前提: JMeter下载和安装

SpringCloud-第二部分 - 图9

环境变量配置

SpringCloud-第二部分 - 图10

SpringCloud-第二部分 - 图11

上述在非高并发情形下,还能勉强满足 but……

Jmeter压测测试: 开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务

SpringCloud-第二部分 - 图12

再来一个访问 : http://localhost:8001/payment/hystrix/ok/31

看演示结果 : 两个都在自己转圈圈

为什么会被卡死 : tomcat的默认的工作线程数被打满 了,没有多余的线程来分解压力和处理。

Jmeter压测结论

上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死

看热闹不嫌弃事大,80新建加入 —-> cloud-consumer-feign-hystrix-order80

新建 cloud-consumer-feign-hystrix-order80

POM

  1. <dependencies>
  2. <!--openfeign-->
  3. <dependency>
  4. <groupId>org.springframework.cloud</groupId>
  5. <artifactId>spring-cloud-starter-openfeign</artifactId>
  6. </dependency>
  7. <!--hystrix-->
  8. <dependency>
  9. <groupId>org.springframework.cloud</groupId>
  10. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  11. </dependency>
  12. <!--eureka client-->
  13. <dependency>
  14. <groupId>org.springframework.cloud</groupId>
  15. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  16. </dependency>
  17. <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  18. <dependency>
  19. <groupId>com.atguigu.springcloud</groupId>
  20. <artifactId>cloud-api-commons</artifactId>
  21. <version>${project.version}</version>
  22. </dependency>
  23. <!--web-->
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-web</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-actuator</artifactId>
  31. </dependency>
  32. <!--一般基础通用配置-->
  33. <dependency>
  34. <groupId>org.springframework.boot</groupId>
  35. <artifactId>spring-boot-devtools</artifactId>
  36. <scope>runtime</scope>
  37. <optional>true</optional>
  38. </dependency>
  39. <dependency>
  40. <groupId>org.projectlombok</groupId>
  41. <artifactId>lombok</artifactId>
  42. <optional>true</optional>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.springframework.boot</groupId>
  46. <artifactId>spring-boot-starter-test</artifactId>
  47. <scope>test</scope>
  48. </dependency>
  49. </dependencies>

YML

  1. server:
  2. port: 80
  3. eureka:
  4. client:
  5. register-with-eureka: false
  6. service-url:
  7. defaultZone: http://eureka7001.com:7001/eureka/

主启动

  1. @SpringBootApplication
  2. @EnableFeignClients
  3. public class OrderHystrixMain80{
  4. public static void main(String[] args){
  5. SpringApplication.run(OrderHystrixMain80.class,args);
  6. }
  7. }

业务类

PaymentHystrixService

  1. @Component
  2. @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
  3. public interface PaymentHystrixService{
  4. @GetMapping("/payment/hystrix/ok/{id}")
  5. String paymentInfo_OK(@PathVariable("id") Integer id);
  6. @GetMapping("/payment/hystrix/timeout/{id}")
  7. String paymentInfo_TimeOut(@PathVariable("id") Integer id);
  8. }

OrderHystirxController

  1. @RestController
  2. @Slf4j
  3. public class OrderHystirxController{
  4. @Resource
  5. private PaymentHystrixService paymentHystrixService;
  6. @GetMapping("/consumer/payment/hystrix/ok/{id}")
  7. public String paymentInfo_OK(@PathVariable("id") Integer id){
  8. String result = paymentHystrixService.paymentInfo_OK(id);
  9. return result;
  10. }
  11. @GetMapping("/consumer/payment/hystrix/timeout/{id}")
  12. public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
  13. String result = paymentHystrixService.paymentInfo_TimeOut(id);
  14. return result;
  15. }
  16. }

正常测试

http://localhost/consumer/payment/hystrix/ok/31

高并发测试

2W个线程压8001

消费端80微服务再去访问正常的Ok微服务8001地址

http://localhost/consumer/payment/hystrix/ok/32

消费者80 ===> 要么转圈圈等待;;要么消费端报超时错误

SpringCloud-第二部分 - 图13

故障现象和导致原因

8001同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕

80此时调用8001,客户端访问响应缓慢,转圈圈

上诉结论

正因为有上述故障或不佳表现才有我们的降级/容错/限流等技术诞生

如何解决?解决的要求

超时导致服务器变慢(转圈) =====> 超时不再等待

出错(宕机或程序运行出错) =====> 出错要有兜底

解决 ====>

对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级

对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级

对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

服务降级

降级配置

@HystrixCommand

8001先从自身找问题

设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback

8001fallback

业务类启用

  1. @Service
  2. public class PaymentService
  3. {
  4. /**
  5. * 正常访问,一切OK
  6. * @param id
  7. * @return
  8. */
  9. public String paymentInfo_OK(Integer id)
  10. {
  11. return "线程池:"+Thread.currentThread().getName()+"paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O";
  12. }
  13. /**
  14. * 超时访问,演示降级
  15. * @param id
  16. * @return
  17. */
  18. @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
  19. @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
  20. })
  21. public String paymentInfo_TimeOut(Integer id)
  22. {
  23. int second = 5;
  24. try { TimeUnit.SECONDS.sleep(second); } catch (InterruptedException e) { e.printStackTrace(); }
  25. return "线程池:"+Thread.currentThread().getName()+"paymentInfo_TimeOut,id: "+id+"\t"+"O(∩_∩)O,耗费秒: "+second;
  26. }
  27. public String paymentInfo_TimeOutHandler(Integer id){
  28. return "/(ㄒoㄒ)/调用支付接口超时或异常:\t"+ "\t当前线程池名字" + Thread.currentThread().getName();
  29. }
  30. }

@HystrixCommand报异常后如何处理

一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的 fallbackMethod调用类中的指定方法

图示

SpringCloud-第二部分 - 图14

上图故意制造两个异常:
1 int age = 10/0; 计算异常
2 我们能接受3秒钟,它运行5秒钟,超时异常。

当前服务不可用了,做服务降级,兜底的方案都是paymentInfo_TimeOutHandler

主启动类激活

添加新注解@EnableCircuitBreaker

80fallback

80订单微服务,也可以更好的保护自己,自己也依样画葫芦进行客户端降级保护

题外话,切记

我们自己配置过的热部署方式对java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务

YML

  1. server:
  2. port: 80
  3. eureka:
  4. client:
  5. register-with-eureka: false
  6. service-url:
  7. defaultZone: http://eureka7001.com:7001/eureka/
  8. feign:
  9. hystrix:
  10. enabled: true

主启动@EnableHystrix

  1. @SpringBootApplication
  2. @EnableFeignClients
  3. @EnableHystrix
  4. public class OrderHystrixMain80{
  5. public static void main(String[] args){
  6. SpringApplication.run(OrderHystrixMain80.class,args);
  7. }
  8. }

业务类

  1. @RestController
  2. @Slf4j
  3. public class PaymentHystirxController{
  4. @Resource
  5. private PaymentHystrixService paymentHystrixService;
  6. @GetMapping("/consumer/payment/hystrix/ok/{id}")
  7. public String paymentInfo_OK(@PathVariable("id") Integer id){
  8. String result = paymentHystrixService.paymentInfo_OK(id);
  9. return result;
  10. }
  11. @GetMapping("/consumer/payment/hystrix/timeout/{id}")
  12. @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
  13. @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
  14. })
  15. public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
  16. String result = paymentHystrixService.paymentInfo_TimeOut(id);
  17. return result;
  18. }
  19. public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
  20. return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
  21. }
  22. }

目前问题

每个业务方法对应一个兜底的方法,代码膨胀。统一和自定义的分开

解决问题

每个方法配置一个???膨胀

feign接口系列

@DefaultProperties(defaultFallback = “”)

SpringCloud-第二部分 - 图15

说明

  1. @DefaultProperties(defaultFallback = "")
  2. 11 每个方法配置一个服务降级方法,技术上可以,实际上傻X
  3. 1N 除了个别重要核心业务有专属,其它普通的可以通过@DefaultProperties(defaultFallback = "") 统一跳转到统一处理结果页面
  4. 通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量,O(∩_∩)O哈哈~

controller配置

  1. @RestController
  2. @Slf4j
  3. @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
  4. public class PaymentHystirxController{
  5. @Resource
  6. private PaymentHystrixService paymentHystrixService;
  7. @GetMapping("/consumer/payment/hystrix/ok/{id}")
  8. public String paymentInfo_OK(@PathVariable("id") Integer id){
  9. String result = paymentHystrixService.paymentInfo_OK(id);
  10. return result;
  11. }
  12. @GetMapping("/consumer/payment/hystrix/timeout/{id}")
  13. @HystrixCommand //加了@DefaultProperties属性注解,并且没有写具体方法名字,就用统一全局的
  14. public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
  15. String result = paymentHystrixService.paymentInfo_TimeOut(id);
  16. return result;
  17. }
  18. public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
  19. return "paymentTimeOutFallbackMethod,对方系统繁忙,请10秒钟后再次尝试/(ㄒoㄒ)/";
  20. }
  21. public String payment_Global_FallbackMethod(){
  22. return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
  23. }
  24. }

和业务逻辑混一起???混乱

服务降级,客户端去调用服务端,碰上服务端宕机或关闭

本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦

未来我们要面对的异常:运行、超时、宕机

再看我们的业务类PaymentController

SpringCloud-第二部分 - 图16

混合在一块 ,每个业务方法都要提供一个。

修改cloud-consumer-feign-hystrix-order80

根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理

PaymentFallbackService类实现PaymentFeignClientService接口

  1. @Component //必须加 //必须加 //必须加
  2. public class PaymentFallbackService implements PaymentFeignClientService{
  3. @Override
  4. public String getPaymentInfo(Integer id){
  5. return "服务调用失败,提示来自:cloud-consumer-feign-order80";
  6. }
  7. }

YML

  1. server:
  2. port: 80
  3. eureka:
  4. client:
  5. register-with-eureka: false
  6. service-url:
  7. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  8. #logging:
  9. # level:
  10. # # feign日志以什么级别监控哪个接口
  11. # com.atguigu.springcloud.service.PaymentFeignClientService: debug
  12. # 用于服务降级 在注解@FeignClient中添加fallbackFactory属性值
  13. feign:
  14. hystrix:
  15. enabled: true #在Feign中开启Hystrix

PaymentFeignClientService接口

  1. @Component
  2. @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
  3. public interface PaymentFeignClientService{
  4. @GetMapping("/payment/hystrix/{id}")
  5. public String getPaymentInfo(@PathVariable("id") Integer id);
  6. }

测试

单个eureka先启动7001

PaymentHystrixMain8001启动

正常访问测试 http://localhost/consumer/payment/hystrix/ok/31

故意关闭微服务8001

客户端自己调用提示

此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器

服务熔断

断路器 一句话就是家里的保险丝

熔断是什么

  1. 熔断机制概述
  2. 熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,
  3. 会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
  4. 当检测到该节点微服务调用响应正常后,恢复调用链路。
  5. Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,
  6. 当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand

大神论文 https://martinfowler.com/bliki/CircuitBreaker.html

实操

修改cloud-provider-hystrix-payment8001
PaymentService

  1. //=========服务熔断
  2. @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
  3. @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
  4. @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
  5. @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
  6. @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),
  7. })
  8. public String paymentCircuitBreaker(@PathVariable("id") Integer id){
  9. if(id < 0){
  10. throw new RuntimeException("******id 不能负数");
  11. }
  12. String serialNumber = IdUtil.simpleUUID();
  13. return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
  14. }
  15. public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
  16. return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
  17. }

why配置这些参数

SpringCloud-第二部分 - 图17

PaymentController

  1. @GetMapping("/payment/circuit/{id}")
  2. public String paymentCircuitBreaker(@PathVariable("id") Integer id){
  3. String result = paymentService.paymentCircuitBreaker(id);
  4. log.info("****result: "+result);
  5. return result;
  6. }

测试

自测cloud-provider-hystrix-payment8001

正确 http://localhost:8001/payment/circuit/31

错误 http://localhost:8001/payment/circuit/-31

一次正确一次错误trytry

重点测试 : 多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行

原理(小总结)

大神结论

SpringCloud-第二部分 - 图18

熔断类型

熔断打开

请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入半熔断状态

熔断关闭

熔断关闭不会对服务进行熔断

熔断半开

部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断

官网断路器流程图

SpringCloud-第二部分 - 图19

官网步骤

SpringCloud-第二部分 - 图20

断路器在什么情况下开始起作用

SpringCloud-第二部分 - 图21

涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。
1:快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。

2:请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。

3:错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。

断路器开启或者关闭的条件

当满足一定的阀值的时候(默认10秒内超过20个请求次数)

当失败率达到一定的时候(默认10秒内超过50%的请求失败)

到达以上阀值,断路器将会开启

当开启的时候,所有请求都不会进行转发

一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5

断路器打开之后

  1. 1:再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
  2. 2:原来的主逻辑要如何恢复呢?
  3. 对于这一问题,hystrix也为我们实现了自动恢复功能。
  4. 当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,
  5. 当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。

All配置

  1. //========================All
  2. @HystrixCommand(fallbackMethod = "str_fallbackMethod",
  3. groupKey = "strGroupCommand",
  4. commandKey = "strCommand",
  5. threadPoolKey = "strThreadPool",
  6. commandProperties = {
  7. // 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
  8. @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
  9. // 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
  10. @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
  11. // 配置命令执行的超时时间
  12. @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
  13. // 是否启用超时时间
  14. @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
  15. // 执行超时的时候是否中断
  16. @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
  17. // 执行被取消的时候是否中断
  18. @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
  19. // 允许回调方法执行的最大并发数
  20. @HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
  21. // 服务降级是否启用,是否执行回调函数
  22. @HystrixProperty(name = "fallback.enabled", value = "true"),
  23. // 是否启用断路器
  24. @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
  25. // 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,
  26. // 如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
  27. @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
  28. // 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过
  29. // circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50,
  30. // 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
  31. @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
  32. // 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,
  33. // 会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,
  34. // 如果成功就设置为 "关闭" 状态。
  35. @HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
  36. // 断路器强制打开
  37. @HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
  38. // 断路器强制关闭
  39. @HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
  40. // 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
  41. @HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
  42. // 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据
  43. // 设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
  44. // 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
  45. @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
  46. // 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
  47. @HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
  48. // 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
  49. @HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
  50. // 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
  51. @HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
  52. // 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
  53. // 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
  54. // 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
  55. @HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
  56. // 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
  57. @HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
  58. // 是否开启请求缓存
  59. @HystrixProperty(name = "requestCache.enabled", value = "true"),
  60. // HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
  61. @HystrixProperty(name = "requestLog.enabled", value = "true"),
  62. },
  63. threadPoolProperties = {
  64. // 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
  65. @HystrixProperty(name = "coreSize", value = "10"),
  66. // 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,
  67. // 否则将使用 LinkedBlockingQueue 实现的队列。
  68. @HystrixProperty(name = "maxQueueSize", value = "-1"),
  69. // 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
  70. // 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue
  71. // 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
  72. @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
  73. }
  74. )
  75. public String strConsumer(){
  76. return "hello 2020";
  77. }
  78. public String str_fallbackMethod(){
  79. return "*****fall back str_fallbackMethod";
  80. }

服务限流

后面高级篇讲解alibaba的Sentinel说明

hystrix工作流程

https://github.com/Netflix/Hystrix/wiki/How-it-Works

Hystrix工作流程

官网图例

SpringCloud-第二部分 - 图22

步骤说明

1 创建 HystrixCommand(用在依赖的服务返回单个操作结果的时候) 或 HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候) 对象。
2 命令执行。其中 HystrixComand 实现了下面前两种执行方式;而 HystrixObservableCommand 实现了后两种执行方式:execute():同步执行,从依赖的服务返回一个单一的结果对象, 或是在发生错误的时候抛出异常。queue():异步执行, 直接返回 一个Future对象, 其中包含了服务执行结束时要返回的单一结果对象。observe():返回 Observable 对象,它代表了操作的多个结果,它是一个 Hot Obserable(不论 “事件源” 是否有 “订阅者”,都会在创建后对事件进行发布,所以对于 Hot Observable 的每一个 “订阅者” 都有可能是从 “事件源” 的中途开始的,并可能只是看到了整个操作的局部过程)。toObservable(): 同样会返回 Observable 对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有 “订阅者” 的时候并不会发布事件,而是进行等待,直到有 “订阅者” 之后才发布事件,所以对于 Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)。
3 若当前命令的请求缓存功能是被启用的, 并且该命令缓存命中, 那么缓存的结果会立即以 Observable 对象的形式 返回。
4 检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到 fallback 处理逻辑(第 8 步);如果断路器是关闭的,检查是否有可用资源来执行命令(第 5 步)。
5 线程池/请求队列/信号量是否占满。如果命令依赖服务的专有线程池和请求队列,或者信号量(不使用线程池的时候)已经被占满, 那么 Hystrix 也不会执行命令, 而是转接到 fallback 处理逻辑(第8步)。
6 Hystrix 会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。HystrixCommand.run() :返回一个单一的结果,或者抛出异常。HystrixObservableCommand.construct(): 返回一个Observable 对象来发射多个结果,或通过 onError 发送错误通知。
7 Hystrix会将 “成功”、”失败”、”拒绝”、”超时” 等信息报告给断路器, 而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行 “熔断/短路”。
8 当命令执行失败的时候, Hystrix 会进入 fallback 尝试回退处理, 我们通常也称该操作为 “服务降级”。而能够引起服务降级处理的情况有下面几种:第4步: 当前命令处于”熔断/短路”状态,断路器是打开的时候。第5步: 当前命令的线程池、 请求队列或 者信号量被占满的时候。第6步:HystrixObservableCommand.construct() 或 HystrixCommand.run() 抛出异常的时候。
9 当Hystrix命令执行成功之后, 它会将处理结果直接返回或是以Observable 的形式返回。

tips:如果我们没有为命令实现降级逻辑或者在降级处理逻辑中抛出了异常, Hystrix 依然会返回一个 Observable 对象, 但是它不会发射任何结果数据, 而是通过 onError 方法通知命令立即中断请求,并通过onError()方法将引起命令失败的异常发送给调用者。

服务监控hystrixDashboard

概述

  1. 除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。

仪表盘9001

新建cloud-consumer-hystrix-dashboard9001

POM

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-actuator</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-devtools</artifactId>
  13. <scope>runtime</scope>
  14. <optional>true</optional>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.projectlombok</groupId>
  18. <artifactId>lombok</artifactId>
  19. <optional>true</optional>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-test</artifactId>
  24. <scope>test</scope>
  25. </dependency>
  26. </dependencies>

YML

  1. server:
  2. port: 9001

HystrixDashboardMain9001+新注解@EnableHystrixDashboard

  1. @SpringBootApplication
  2. @EnableHystrixDashboard
  3. public class HystrixDashboardMain9001{
  4. public static void main(String[] args){
  5. SpringApplication.run(MainApp9001.class,args);
  6. }
  7. }

所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置

  1. <!-- actuator监控信息完善 -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-actuator</artifactId>
  5. </dependency>

启动cloud-consumer-hystrix-dashboard9001该微服务后续将监控微服务8001

http://localhost:9001/hystrix

SpringCloud-第二部分 - 图23

断路器演示(服务监控hystrixDashboard)

修改cloud-provider-hystrix-payment8001

注意:新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径

  1. @SpringBootApplication
  2. @EnableEurekaClient //本服务启动后会自动注册进eureka服务中
  3. @EnableCircuitBreaker//对hystrixR熔断机制的支持
  4. public class MainAppHystrix8001
  5. {
  6. public static void main(String[] args){
  7. SpringApplication.run(MainAppHystrix8001.class,args);
  8. }
  9. /**
  10. *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
  11. *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
  12. *只要在自己的项目里配置上下面的servlet就可以了
  13. */
  14. @Bean
  15. public ServletRegistrationBean getServlet() {
  16. HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
  17. ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
  18. registrationBean.setLoadOnStartup(1);
  19. registrationBean.addUrlMappings("/hystrix.stream");
  20. registrationBean.setName("HystrixMetricsStreamServlet");
  21. return registrationBean;
  22. }
  23. }

Unable to connect to Command Metric Stream.

404

监控测试

启动1个eureka或者3个eureka集群均可

观察监控窗口

9001监控8001

SpringCloud-第二部分 - 图24

1:Delay:该参数用来控制服务器上轮询监控信息的延迟时间,默认为2000毫秒,可以通过配置该属性来降低客户端的网络和CPU消耗。

2:Title:该参数对应了头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,可以通过配置该信息来展示更合适的标题。

填写监控地址 http://localhost:8001/hystrix.stream

测试地址

http://localhost:8001/payment/circuit/31

http://localhost:8001/payment/circuit/-31

上述测试通过 ok

先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的。

监控结果,成功

SpringCloud-第二部分 - 图25

监控结果,失败

SpringCloud-第二部分 - 图26

如何看?

7色

1圈

实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。
该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。

1线

曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。

整图说明

SpringCloud-第二部分 - 图27

整图说明2

SpringCloud-第二部分 - 图28

搞懂一个才能看懂复杂的

SpringCloud-第二部分 - 图29

第十一章、zuul路由网关

概述简介

官网资料

https://github.com/Netflix/zuul/wiki/Getting-Started

https://cloud.spring.io/spring-cloud-static/spring-cloud-netflix/2.2.1.RELEASE/reference/html/#router-and-filter-zuul

是什么

Zuul是一种提供动态路由、监视、弹性、安全性等功能的边缘服务。
Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器。

SpringCloud-第二部分 - 图30

  1. API网关为微服务架构中的服务提供了统一的访问入口,客户端通过API网关访问相关服务。API网关的定义类似于设计模式中的门面模式,它相当于整个微服务架构中的门面,所有客户端的访问都通过它来进行路由及过滤。它实现了请求路由、负载均衡、校验过滤、服务容错、服务聚合等功能。

SpringCloud-第二部分 - 图31

SpringCloud-第二部分 - 图32

Zuul包含了如下最主要的功能:
代理+路由+过滤三大功能

能干嘛

路由 + 过滤 + 负载均衡

  1. 网关为入口,由网关与微服务进行交互,所以网关必须要实现负载均衡的功能;
  2. 网关会获取微服务注册中心里面的服务连接地址,再配合一些算法选择其中一个服务地址,进行处理业务。
  3. 这个属于客户端侧的负载均衡,由调用方去实现负载均衡逻辑。

SpringCloud-第二部分 - 图33

灰度发布(又称金丝雀发布)

起源是,矿井工人发现,金丝雀对瓦斯气体很敏感,矿工会在下井之前,先放一只金丝雀到井中,如果金丝雀不叫了,就代表瓦斯浓度高。

SpringCloud-第二部分 - 图34

在灰度发布开始后,先启动一个新版本应用,但是并不直接将流量切过来,而是测试人员对新版本进行线上测试,启动的这个新版本应用,就是我们的金丝雀。如果没有问题,那么可以将少量的用户流量导入到新版本上,然后再对新版本做运行状态观察,收集各种运行时数据,如果此时对新旧版本做各种数据对比,就是所谓的A/B测试。新版本没什么问题,那么逐步扩大范围、流量,把所有用户都迁移到新版本上面来。

路由基本配置

功能: 路由功能负责将外部请求转发到具体的服务实例上去,是实现统一访问入口的基础

新建Module模块cloud-zuul-gateway9527

POM

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.cloud</groupId>
  8. <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-actuator</artifactId>
  13. </dependency>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-devtools</artifactId>
  17. <scope>runtime</scope>
  18. <optional>true</optional>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.projectlombok</groupId>
  22. <artifactId>lombok</artifactId>
  23. <optional>true</optional>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-test</artifactId>
  28. <scope>test</scope>
  29. </dependency>
  30. </dependencies>

YML

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-zuul-gateway
  6. eureka:
  7. client:
  8. service-url:
  9. #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
  10. defaultZone: http://eureka7001.com:7001/eureka
  11. instance:
  12. instance-id: gateway-9527.com
  13. prefer-ip-address: true

hosts修改

127.0.0.1 myzuul.com

主启动类@EnableZuulProxy

  1. @SpringBootApplication
  2. @EnableZuulProxy
  3. public class Zuul_9527_StartSpringCloudApp{
  4. public static void main(String[] args){
  5. SpringApplication.run(Zuul_9527_StartSpringCloudApp.class, args);
  6. }
  7. }

启动

SpringCloud-第二部分 - 图35

  1. 三个eureka集群
  2. 一个服务提供类microservicecloud-provider-dept-8001
  3. 一个路由

测试

不用路由

http://localhost:8001/paymentInfo

启用路由

zuul映射配置+注册中心注册后对外暴露的服务名称+rest调用地址

http://myzuul.com:9527/cloud-provider-payment/paymentInfo

路由访问映射规则

工程microservicecloud-zuul-gateway-9527

代理名称

before
http://myzuul.com:9527/cloud-provider-payment/paymentInfo

  1. zuul:
  2. routes: # 路由映射配置
  3. mypayment.path: /mypayment/** #IE地址栏输入的路径
  4. mypayment.serviceId: cloud-provider-payment #注册进eureka服务器的地址

after
http://myzuul.com:9527/weixin/paymentInfo

此时问题

路由访问OK http://myzuul.com:9527/weixin/paymentInfo

原路径访问OK http://myzuul.com:9527/cloud-provider-payment/paymentInfo

如果不想使用默认的路由规则,可以添加以下配置来忽略默认路由配置

原有真实服务名忽略

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-zuul-gateway
  6. eureka:
  7. client:
  8. service-url:
  9. #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
  10. defaultZone: http://eureka7001.com:7001/eureka
  11. instance:
  12. instance-id: gateway-9527.com
  13. prefer-ip-address: true
  14. zuul:
  15. ignored-services: cloud-provider-payment
  16. routes: # 路由映射配置
  17. mypayment.serviceId: cloud-provider-payment
  18. mypayment.path: /weixin/**
  19. mysms.serviceId: cloud-provider-sms
  20. mysms.path: /mysms/**

上一步配置后,cloud-provider-payment就不行了

http://myzuul.com:9527/cloud-provider-payment/paymentInfo

单个具体,多个可以用”*”

  1. zuul:
  2. ignored-services: "*"
  3. routes:
  4. mydept.serviceId: microservicecloud-dept
  5. mydept.path: /mydept/**

路由转发和负载均衡功能

服务提供者SMS短信模块

建模块cloud-provider-sms8008

POM

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.cloud</groupId>
  8. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-actuator</artifactId>
  13. </dependency>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-devtools</artifactId>
  17. <scope>runtime</scope>
  18. <optional>true</optional>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.projectlombok</groupId>
  22. <artifactId>lombok</artifactId>
  23. <optional>true</optional>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-test</artifactId>
  28. <scope>test</scope>
  29. </dependency>
  30. </dependencies>

YML

  1. server:
  2. port: 8008
  3. ###服务名称(服务注册到eureka名称)
  4. spring:
  5. application:
  6. name: cloud-provider-sms
  7. eureka:
  8. client: #服务提供者provider注册进eureka服务列表内
  9. service-url:
  10. register-with-eureka: true
  11. fetch-registry: true
  12. defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
  13. #defaultZone: http://127.0.0.1:7001/eureka,http://127.0.0.1:7002/eureka
  14. #defaultZone: http://eureka7001.com:7001/eureka # eureka集群加@老本版

业务类

  1. @RestController
  2. public class SMSController{
  3. @Value("${server.port}")
  4. private String serverPort;
  5. @GetMapping("/sms")
  6. public String sms(){
  7. return "sms provider service: "+"\t"+serverPort;
  8. }
  9. }

主启动

  1. @SpringBootApplication
  2. @EnableEurekaClient
  3. public class MainAppSMS8008{
  4. public static void main(String[] args){
  5. SpringApplication.run(MainAppSMS8008.class,args);
  6. }
  7. }

启动8008并成功注册进eureka服务器上

SpringCloud-第二部分 - 图36

修改我们的zuul服务9527

修改YML,体现路由转发和负载均衡

YML

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-zuul-gateway
  6. eureka:
  7. client:
  8. service-url:
  9. #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
  10. defaultZone: http://eureka7001.com:7001/eureka
  11. instance:
  12. instance-id: gateway-9527.com
  13. prefer-ip-address: true
  14. zuul:
  15. #ignored-services: cloud-provider-payment
  16. routes: # 路由映射配置
  17. mypayment.serviceId: cloud-provider-payment
  18. mypayment.path: /weixin/**
  19. mysms.serviceId: cloud-provider-sms
  20. mysms.path: /mysms/**

由于Zuul自动集成了Ribbon和Hystrix,所以Zuul天生就有负载均衡和服务容错能力

测试

http://myzuul.com:9527/weixin/paymentInfo 负载均衡

http://myzuul.com:9527/mysms/sms 路由转发

微信服务找8001/8002

短信服务找8008

设置统一公共前缀

YML

  1. zuul:
  2. prefix: /atguigu
  3. ignored-services: "*"
  4. routes:
  5. mydept.serviceId: microservicecloud-dept
  6. mydept.path: /mydept/**

http://myzuul.com:9527/atguigu/weixin/paymentInfo

http://myzuul.com:9527/atguigu/mysms/sms

http://myzuul.com:9527/atguigu/cloud-provider-payment/paymentInfo

最后YML

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-zuul-gateway
  6. eureka:
  7. client:
  8. service-url:
  9. #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
  10. defaultZone: http://eureka7001.com:7001/eureka
  11. instance:
  12. instance-id: gateway-9527.com
  13. prefer-ip-address: true
  14. zuul:
  15. #ignored-services: cloud-provider-payment
  16. prefix: /atguigu
  17. routes: # 路由映射配置
  18. mypayment.serviceId: cloud-provider-payment
  19. mypayment.path: /weixin/**
  20. mysms.serviceId: cloud-provider-sms
  21. mysms.path: /mysms/**

查看路由信息

POM

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-actuator</artifactId>
  4. </dependency>

YML

  1. # 开启查看路由的端点
  2. management:
  3. endpoints:
  4. web:
  5. exposure:
  6. include: 'routes'

查看路由详细信息

http://localhost:9527/actuator/routes

过滤器

功能

过滤功能负责对请求过程进行额外的处理,是请求校验过滤及服务聚合的基础。

过滤器的生命周期

SpringCloud-第二部分 - 图37

ZuulFilter

过滤类型

  1. pre:在请求被路由到目标服务前执行,比如权限校验、打印日志等功能;
  2. routing:在请求被路由到目标服务时执行
  3. post:在请求被路由到目标服务后执行,比如给目标服务的响应添加头信息,收集统计数据等功能;
  4. error:请求在其他阶段发生错误时执行。

过滤顺序 数字小的先执行

过滤是否开启 shouldFilter方法为true走

执行逻辑 自己的业务逻辑

案例Case

前置过滤器,用于在请求路由到目标服务前打印请求日志

业务代码

  1. @Component
  2. @Slf4j
  3. public class PreLogFilter extends ZuulFilter{
  4. @Override
  5. public String filterType(){
  6. return "pre";
  7. }
  8. @Override
  9. public int filterOrder(){
  10. return 1;
  11. }
  12. @Override
  13. public boolean shouldFilter(){
  14. return true;
  15. }
  16. @Override
  17. public Object run() throws ZuulException{
  18. RequestContext requestContext = RequestContext.getCurrentContext();
  19. HttpServletRequest request = requestContext.getRequest();
  20. String host = request.getRemoteHost();
  21. String method = request.getMethod();
  22. String uri = request.getRequestURI();
  23. //log.info("=====> Remote host:{},method:{},uri:{}", host, method, uri);
  24. System.out.println("********"+new Date().getTime());
  25. return null;
  26. }
  27. }

测试

http://myzuul.com:9527/atguigu/mysms/sms

在调用8008之前会打印日志

开关,YML配置

  1. zuul:
  2. PreLogFilter:
  3. pre:
  4. disable: true

第十二章、Gateway

概述简介

官网:

上一代zuul 1.X https://github.com/Netflix/zuul/wiki

当前gateway https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/

概述:

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和 Project Reactor等技术。
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能, 例如:熔断、限流、重试等

SpringCloud-第二部分 - 图38

是什么:

Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关;
但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul,
那就是SpringCloud Gateway一句话:gateway是原zuul1.x版的替代

SpringCloud-第二部分 - 图39

  1. SpringCloud Gateway Spring Cloud 的一个全新项目,基于 Spring 5.0+Spring Boot 2.0 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
  2. SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.xReactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty
  3. Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

一句话:

SpringCloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架。

源码架构

SpringCloud-第二部分 - 图40

能干嘛

反向代理、鉴权、流量控制、熔断、日志监控…

微服务架构中网关在哪里

SpringCloud-第二部分 - 图41

有Zuul了怎么又出来了gateway

我们为什么选择Gateway?

1、neflix不太靠谱,zuul2.0一直跳票,迟迟不发布

  1. 一方面因为Zuul1.0已经进入了维护阶段,而且GatewaySpringCloud团队研发的,是亲儿子产品,值得信赖。
  2. 而且很多功能Zuul都没有用起来也非常的简单便捷。
  3. Gateway是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然Netflix早就发布了最新的 Zuul 2.x
  4. Spring Cloud 貌似没有整合计划。而且Netflix相关组件都宣布进入维护期;不知前景如何?
  5. 多方面综合考虑Gateway是很理想的网关选择。

2、SpringCloud Gateway具有如下特性

  1. Spring Cloud Gateway 具有如下特性:
  2. 基于Spring Framework 5, Project Reactor Spring Boot 2.0 进行构建;
  3. 动态路由:能够匹配任何请求属性;
  4. 可以对路由指定 Predicate(断言)和 Filter(过滤器);
  5. 集成Hystrix的断路器功能;
  6. 集成 Spring Cloud 服务发现功能;
  7. 易于编写的 Predicate(断言)和 Filter(过滤器);
  8. 请求限流功能;
  9. 支持路径重写。

3、SpringCloud Gateway 与 Zuul的区别

  1. Spring Cloud Gateway Zuul的区别
  2. SpringCloud Finchley 正式版之前,Spring Cloud 推荐的网关是 Netflix 提供的Zuul
  3. 1Zuul 1.x,是一个基于阻塞 I/ O API Gateway
  4. 2Zuul 1.x 基于Servlet 2. 5使用阻塞架构它不支持任何长连接(如 WebSocket) Zuul 的设计模式和Nginx较像,每次 I/ O 操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx C++ 实现,Zuul Java 实现,而 JVM 本身会有第一次加载较慢的情况,使得Zuul 的性能相对较差。
  5. 3Zuul 2.x理念更先进,想基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。 Zuul 2.x的性能较 Zuul 1.x 有较大提升。在性能方面,根据官方提供的基准测试, Spring Cloud Gateway RPS(每秒请求数)是Zuul 1. 6 倍。
  6. 4Spring Cloud Gateway 建立 Spring Framework 5 Project Reactor Spring Boot 2 之上, 使用非阻塞 API
  7. 5Spring Cloud Gateway 支持 WebSocket 并且与Spring紧密集成拥有更好的开发体验

Zuul1.x模型

  1. Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是传统的Servlet IO处理模型。
  2. 学过尚硅谷web中期课程都知道一个题目,Servlet的生命周期?servletservlet container进行生命周期管理。
  3. container启动时构造servlet对象并调用servlet init()进行初始化;
  4. container运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用service()。
  5. container关闭时调用servlet destory()销毁servlet

SpringCloud-第二部分 - 图42

上述模式的缺点:
servlet是一个简单的网络IO模型,当请求进入servlet container时,servlet container就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发(比如抽风用jemeter压),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。在一些简单业务场景下,不希望为每个request分配一个线程,只需要1个或几个线程就能应对极大并发的请求,这种业务场景下servlet模型没有优势

所以Zuul 1.X是基于servlet之上的一个阻塞式处理模型,即spring实现了处理所有request请求的一个servlet(DispatcherServlet)并由该servlet阻塞式处理处理。所以Springcloud Zuul无法摆脱servlet模型的弊端

GateWay模型

WebFlux是什么

SpringCloud-第二部分 - 图43

SpringCloud-第二部分 - 图44

https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-new-framework

说明

传统的Web框架,比如说:struts2,springmvc等都是基于Servlet API与Servlet容器基础之上运行的。
但是在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(Spring5必须让你使用java8)

Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。

三大核心概念

Route(路由)

路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由

Predicate(断言)

参考的是Java8的java.util.function.Predicate
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

Filter(过滤)

指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

总体

SpringCloud-第二部分 - 图45

web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
predicate就是我们的匹配条件;
而filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

Gateway工作流程

官网总结

SpringCloud-第二部分 - 图46

SpringCloud-第二部分 - 图47

  1. 客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler
  2. Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
  3. 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
  4. Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,
  5. 在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

核心逻辑

路由转发+执行过滤器链

入门配置

新建Module

cloud-gateway-gateway9527

POM

  1. <dependencies>
  2. <!--gateway-->
  3. <dependency>
  4. <groupId>org.springframework.cloud</groupId>
  5. <artifactId>spring-cloud-starter-gateway</artifactId>
  6. </dependency>
  7. <!--eureka-client-->
  8. <dependency>
  9. <groupId>org.springframework.cloud</groupId>
  10. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  11. </dependency>
  12. <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  13. <dependency>
  14. <groupId>com.atguigu.springcloud</groupId>
  15. <artifactId>cloud-api-commons</artifactId>
  16. <version>${project.version}</version>
  17. </dependency>
  18. <!--一般基础配置类-->
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-devtools</artifactId>
  22. <scope>runtime</scope>
  23. <optional>true</optional>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.projectlombok</groupId>
  27. <artifactId>lombok</artifactId>
  28. <optional>true</optional>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-test</artifactId>
  33. <scope>test</scope>
  34. </dependency>
  35. </dependencies>

YML

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-gateway
  6. eureka:
  7. instance:
  8. hostname: cloud-gateway-service
  9. client: #服务提供者provider注册进eureka服务列表内
  10. service-url:
  11. register-with-eureka: true
  12. fetch-registry: true
  13. defaultZone: http://eureka7001.com:7001/eureka

业务类

主启动类

  1. @SpringBootApplication
  2. @EnableEurekaClient
  3. public class GateWayMain9527{
  4. public static void main(String[] args){
  5. SpringApplication.run(GateWayMain9527.class,args);
  6. }
  7. }

9527网关如何做路由映射 ?

cloud-provider-payment8001看看controller的访问地址

get lb

我们目前不想暴露8001端口,希望在8001外面套一层9527

YML新增网关配置

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-gateway
  6. cloud:
  7. gateway:
  8. routes:
  9. - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  10. uri: http://localhost:8001 #匹配后提供服务的路由地址
  11. predicates:
  12. - Path=/payment/get/** # 断言,路径相匹配的进行路由
  13. - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  14. uri: http://localhost:8001 #匹配后提供服务的路由地址
  15. predicates:
  16. - Path=/payment/lb/** # 断言,路径相匹配的进行路由
  17. eureka:
  18. instance:
  19. hostname: cloud-gateway-service
  20. client: #服务提供者provider注册进eureka服务列表内
  21. service-url:
  22. register-with-eureka: true
  23. fetch-registry: true
  24. defaultZone: http://eureka7001.com:7001/eureka

测试

启动7001

启动8001

cloud-provider-payment8001

启动9527网关

访问说明

SpringCloud-第二部分 - 图48

添加网关前

http://localhost:8001/payment/get/31

添加网关后

http://localhost:9527/payment/get/31

YML配置说明

Gateway网关路由有两种配置方式:

在配置文件yml中配置 见前面的步骤

代码中注入RouteLocator的Bean

官网案例

SpringCloud-第二部分 - 图49

百度国内新闻网址,需要外网

http://news.baidu.com/guonei

自己写一个

百度新闻

业务需求 通过9527网关访问到外网的百度新闻网址

编码

cloud-gateway-gateway9527

业务实现 config

  1. @Configuration
  2. public class GateWayConfig
  3. {
  4. /**
  5. * 配置了一个id为route-name的路由规则,
  6. * 当访问地址 http://localhost:9527/guonei时会自动转发到地址:http://news.baidu.com/guonei
  7. * @param builder
  8. * @return
  9. */
  10. @Bean
  11. public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
  12. RouteLocatorBuilder.Builder routes = builder.routes();
  13. routes.route("path_route_atguigu", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
  14. return routes.build();
  15. }
  16. @Bean
  17. public RouteLocator customRouteLocator2(RouteLocatorBuilder builder){
  18. RouteLocatorBuilder.Builder routes = builder.routes();
  19. routes.route("path_route_atguigu2", r -> r.path("/guoji").uri("http://news.baidu.com/guoji")).build();
  20. return routes.build();
  21. }
  22. }

通过微服务名实现动态路由

默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

启动:

一个eureka7001 + 两个服务提供者8001/8002

POM

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  4. </dependency>

YML

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
  11. routes:
  12. - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  13. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  14. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  15. predicates:
  16. - Path=/payment/get/** # 断言,路径相匹配的进行路由
  17. - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  18. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  19. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  20. predicates:
  21. - Path=/payment/lb/** # 断言,路径相匹配的进行路由
  22. eureka:
  23. instance:
  24. hostname: cloud-gateway-service
  25. client: #服务提供者provider注册进eureka服务列表内
  26. service-url:
  27. register-with-eureka: true
  28. fetch-registry: true
  29. defaultZone: http://eureka7001.com:7001/eureka

需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。

lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri

测试

http://localhost:9527/payment/lb

8001/8002两个端口切换

Predicate的使用

是什么

启动我们的gateway9527

SpringCloud-第二部分 - 图50

Route Predicate Factories这个是什么?

SpringCloud-第二部分 - 图51

Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合

Spring Cloud Gateway 创建 Route 对象时, 使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。 Spring Cloud Gateway 包含许多内置的Route Predicate Factories。

所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。

常用的Route Predicate

SpringCloud-第二部分 - 图52

1、After Route Predicate

SpringCloud-第二部分 - 图53

我们的问题是:上述这个After好懂,这个时间串?

  1. public class ZonedDateTimeDemo
  2. {
  3. public static void main(String[] args){
  4. ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
  5. System.out.println(zbj);
  6. // ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York")); // 用指定时区获取当前时间
  7. // System.out.println(zny);
  8. }
  9. }

YML

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true #开启从注册中心动态创建路由的功能
  11. routes:
  12. - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  13. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  14. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  15. predicates:
  16. - Path=/payment/get/** # 断言,路径相匹配的进行路由
  17. - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  18. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  19. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  20. predicates:
  21. - Path=/payment/lb/** # 断言,路径相匹配的进行路由
  22. - After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  23. eureka:
  24. instance:
  25. hostname: cloud-gateway-service
  26. client: #服务提供者provider注册进eureka服务列表内
  27. service-url:
  28. register-with-eureka: true
  29. fetch-registry: true
  30. defaultZone: http://eureka7001.com:7001/eureka

2、Before Route Predicate

SpringCloud-第二部分 - 图54

YML

  1. - Between=2019-12-02T17:45:06.206+08:00[Asia/Shanghai],2019-12-02T18:59:06.206+08:00[Asia/Shanghai]
  2. server:
  3. port: 9527
  4. spring:
  5. application:
  6. name: cloud-gateway
  7. cloud:
  8. gateway:
  9. discovery:
  10. locator:
  11. enabled: true #开启从注册中心动态创建路由的功能
  12. routes:
  13. - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  14. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  15. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  16. predicates:
  17. - Path=/payment/get/** # 断言,路径相匹配的进行路由
  18. - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  19. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  20. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  21. predicates:
  22. - Path=/payment/lb/** # 断言,路径相匹配的进行路由
  23. #- After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  24. #- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  25. - Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
  26. eureka:
  27. instance:
  28. hostname: cloud-gateway-service
  29. client: #服务提供者provider注册进eureka服务列表内
  30. service-url:
  31. register-with-eureka: true
  32. fetch-registry: true
  33. defaultZone: http://eureka7001.com:7001/eureka
  34. #id:我们自定义的路由 ID,保持唯一
  35. ##uri:目标服务地址
  36. ##predicates:路由条件,Predicate接受一个输入参数返回一个布尔值。
  37. ## 该属性包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)

4、Cookie Route Predicate

SpringCloud-第二部分 - 图55

Cookie Route Predicate需要两个参数,一个是 Cookie name ,一个是正则表达式。
路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行

不带cookies访问

SpringCloud-第二部分 - 图56

curl http://localhost:9588/paymentInfo

curl http://localhost:9527/payment/lb

带上cookies访问

SpringCloud-第二部分 - 图57

curl http://localhost:9588/paymentInfo —cookie “username=zzyy”

curl http://localhost:9527/payment/lb —cookie “username=zzyy”

加入curl返回中文乱码

https://blog.csdn.net/leedee/article/details/82685636

YML

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true #开启从注册中心动态创建路由的功能
  11. routes:
  12. - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  13. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  14. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  15. predicates:
  16. - Path=/payment/get/** # 断言,路径相匹配的进行路由
  17. - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  18. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  19. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  20. predicates:
  21. - Path=/payment/lb/** # 断言,路径相匹配的进行路由
  22. - After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  23. #- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  24. #- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
  25. - Cookie=username,zzyy
  26. eureka:
  27. instance:
  28. hostname: cloud-gateway-service
  29. client: #服务提供者provider注册进eureka服务列表内
  30. service-url:
  31. register-with-eureka: true
  32. fetch-registry: true
  33. defaultZone: http://eureka7001.com:7001/eureka
  34. #id:我们自定义的路由 ID,保持唯一
  35. ##uri:目标服务地址
  36. ##predicates:路由条件,Predicate接受一个输入参数返回一个布尔值。
  37. ## 该属性包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)

5、Header Route Predicate

SpringCloud-第二部分 - 图58

两个参数:一个是属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。

YML

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true #开启从注册中心动态创建路由的功能
  11. routes:
  12. - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  13. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  14. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  15. predicates:
  16. - Path=/payment/get/** # 断言,路径相匹配的进行路由
  17. - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  18. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  19. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  20. predicates:
  21. - Path=/payment/lb/** # 断言,路径相匹配的进行路由
  22. - After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  23. #- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  24. #- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
  25. #- Cookie=username,zzyy
  26. - Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式

curl http://localhost:9588/paymentInfo -H “X-Request-Id:123”

SpringCloud-第二部分 - 图59

curl http://localhost:9527/payment/lb -H “X-Request-Id:123”

6、Host Route Predicate

SpringCloud-第二部分 - 图60

Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。
它通过参数中的主机地址作为匹配规则。

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true #开启从注册中心动态创建路由的功能
  11. routes:
  12. - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  13. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  14. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  15. predicates:
  16. - Path=/payment/get/** # 断言,路径相匹配的进行路由
  17. - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  18. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  19. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  20. predicates:
  21. - Path=/payment/lb/** # 断言,路径相匹配的进行路由
  22. - After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  23. #- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  24. #- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
  25. #- Cookie=username,zzyy
  26. #- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
  27. - Host=**.atguigu.com
  28. eureka:
  29. instance:
  30. hostname: cloud-gateway-service
  31. client: #服务提供者provider注册进eureka服务列表内
  32. service-url:
  33. register-with-eureka: true
  34. fetch-registry: true
  35. defaultZone: http://eureka7001.com:7001/eureka
  36. #id:我们自定义的路由 ID,保持唯一
  37. ##uri:目标服务地址
  38. ##predicates:路由条件,Predicate接受一个输入参数返回一个布尔值。
  39. ## 该属性包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)

SpringCloud-第二部分 - 图61

curl http://localhost:9588/paymentInfo -H “Host: www.atguigu.com”

curl http://localhost:9588/paymentInfo -H “Host: news.atguigu.com”

正确:curl http://localhost:9527/payment/lb -H “Host: www.atguigu.com”
正确:curl http://localhost:9527/payment/lb -H “Host: java.atguigu.com”
错误:curl http://localhost:9527/payment/lb -H “Host: java.atguigu.net”

7、Method Route Predicate

SpringCloud-第二部分 - 图62

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true #开启从注册中心动态创建路由的功能
  11. routes:
  12. - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  13. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  14. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  15. predicates:
  16. - Path=/payment/get/** # 断言,路径相匹配的进行路由
  17. - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  18. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  19. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  20. predicates:
  21. - Path=/payment/lb/** # 断言,路径相匹配的进行路由
  22. - After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  23. #- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  24. #- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
  25. #- Cookie=username,zzyy
  26. #- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
  27. #- Host=**.atguigu.com
  28. - Method=GET
  29. eureka:
  30. instance:
  31. hostname: cloud-gateway-service
  32. client: #服务提供者provider注册进eureka服务列表内
  33. service-url:
  34. register-with-eureka: true
  35. fetch-registry: true
  36. defaultZone: http://eureka7001.com:7001/eureka
  37. #id:我们自定义的路由 ID,保持唯一
  38. ##uri:目标服务地址
  39. ##predicates:路由条件,Predicate接受一个输入参数返回一个布尔值。
  40. ## 该属性包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)

8、Path Route Predicate

SpringCloud-第二部分 - 图63

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true #开启从注册中心动态创建路由的功能
  11. routes:
  12. - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  13. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  14. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  15. predicates:
  16. - Path=/payment/get/** # 断言,路径相匹配的进行路由
  17. - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  18. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  19. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  20. predicates:
  21. - Path=/payment/lb/** # 断言,路径相匹配的进行路由
  22. - After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  23. #- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  24. #- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
  25. #- Cookie=username,zzyy
  26. #- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
  27. #- Host=**.atguigu.com
  28. - Method=GET
  29. eureka:
  30. instance:
  31. hostname: cloud-gateway-service
  32. client: #服务提供者provider注册进eureka服务列表内
  33. service-url:
  34. register-with-eureka: true
  35. fetch-registry: true
  36. defaultZone: http://eureka7001.com:7001/eureka
  37. #id:我们自定义的路由 ID,保持唯一
  38. ##uri:目标服务地址
  39. ##predicates:路由条件,Predicate接受一个输入参数返回一个布尔值。
  40. ## 该属性包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)

9、Query Route Predicate

SpringCloud-第二部分 - 图64

支持传入两个参数,一个是属性名,一个为属性值,属性值可以是正则表达式。

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true #开启从注册中心动态创建路由的功能
  11. routes:
  12. - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  13. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  14. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  15. predicates:
  16. - Path=/payment/get/** # 断言,路径相匹配的进行路由
  17. - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  18. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  19. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  20. predicates:
  21. - Path=/payment/lb/** # 断言,路径相匹配的进行路由
  22. - After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  23. #- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  24. #- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
  25. #- Cookie=username,zzyy
  26. #- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
  27. #- Host=**.atguigu.com
  28. - Method=GET
  29. - Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
  30. eureka:
  31. instance:
  32. hostname: cloud-gateway-service
  33. client: #服务提供者provider注册进eureka服务列表内
  34. service-url:
  35. register-with-eureka: true
  36. fetch-registry: true
  37. defaultZone: http://eureka7001.com:7001/eureka
  38. #id:我们自定义的路由 ID,保持唯一
  39. ##uri:目标服务地址
  40. ##predicates:路由条件,Predicate接受一个输入参数返回一个布尔值。
  41. ## 该属性包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)

http://localhost:9527/payment/lb?username=31

http://localhost:9527/payment/lb?username=-31

10、小总结

ALL

  1. server:
  2. port: 9527
  3. spring:
  4. application:
  5. name: cloud-gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true #开启从注册中心动态创建路由的功能
  11. routes:
  12. - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  13. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  14. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  15. predicates:
  16. - Path=/payment/get/** # 断言,路径相匹配的进行路由
  17. - id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  18. # uri: http://localhost:8001 #匹配后提供服务的路由地址
  19. uri: lb://cloud-payment-service #匹配后提供服务的路由地址
  20. predicates:
  21. - Path=/payment/lb/** # 断言,路径相匹配的进行路由
  22. - After=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  23. #- Before=2020-02-05T15:10:03.685+08:00[Asia/Shanghai] # 断言,路径相匹配的进行路由
  24. #- Between=2020-02-02T17:45:06.206+08:00[Asia/Shanghai],2020-03-25T18:59:06.206+08:00[Asia/Shanghai]
  25. #- Cookie=username,zzyy
  26. #- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
  27. #- Host=**.atguigu.com
  28. - Method=GET
  29. - Query=username, \d+ # 要有参数名username并且值还要是整数才能路由
  30. eureka:
  31. instance:
  32. hostname: cloud-gateway-service
  33. client: #服务提供者provider注册进eureka服务列表内
  34. service-url:
  35. register-with-eureka: true
  36. fetch-registry: true
  37. defaultZone: http://eureka7001.com:7001/eureka
  38. #id:我们自定义的路由 ID,保持唯一
  39. ##uri:目标服务地址
  40. ##predicates:路由条件,Predicate接受一个输入参数返回一个布尔值。
  41. ## 该属性包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)

说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。

Filter的使用

是什么

SpringCloud-第二部分 - 图65

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。

Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生

Spring Cloud Gateway的Filter

生命周期,Only Two

pre post

种类,Only Two

GatewayFilter

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#the-addrequestparameter-gatewayfilter-factory
31种之多。。。。。

SpringCloud-第二部分 - 图66

GlobalFilter

SpringCloud-第二部分 - 图67

常用的GatewayFilter

AddRequestParameter

  1. server:
  2. port: 9588
  3. spring:
  4. application:
  5. name: cloud-gateway
  6. cloud:
  7. gateway:
  8. discovery:
  9. locator:
  10. enabled: true #开启从注册中心动态创建路由的功能
  11. lower-case-service-id: true #使用小写服务名,默认是大写
  12. routes:
  13. - id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
  14. uri: lb://cloud-provider-payment #匹配后的目标服务地址,供服务的路由地址
  15. #uri: http://localhost:8001 #匹配后提供服务的路由地址
  16. filters:
  17. - AddRequestParameter=X-Request-Id,1024 #过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Id值为1024
  18. predicates:
  19. - Path=/paymentInfo/** # 断言,路径相匹配的进行路由
  20. - Method=GET,POST
  21. eureka:
  22. instance:
  23. hostname: cloud-gateway-service
  24. client: #服务提供者provider注册进eureka服务列表内
  25. service-url:
  26. register-with-eureka: true
  27. fetch-registry: true
  28. defaultZone: http://eureka7001.com:7001/eureka

自定义过滤器

自定义全局GlobalFilter

两个主要接口介绍 implements GlobalFilter,Ordered

能干嘛

全局日志记录 统一网关鉴权

案例代码

  1. @Component //必须加,必须加,必须加
  2. public class MyLogGateWayFilter implements GlobalFilter,Ordered{
  3. @Override
  4. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
  5. System.out.println("time:"+new Date()+"\t 执行了自定义的全局过滤器: "+"MyLogGateWayFilter"+"hello");
  6. String uname = exchange.getRequest().getQueryParams().getFirst("uname");
  7. if (uname == null) {
  8. System.out.println("****用户名为null,无法登录");
  9. exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
  10. return exchange.getResponse().setComplete();
  11. }
  12. return chain.filter(exchange);
  13. }
  14. @Override
  15. public int getOrder(){
  16. return 0;
  17. }
  18. }

测试

启动

SpringCloud-第二部分 - 图68

正确

http://localhost:9527/payment/lb?uname=z3

错误

没有参数uname

http://localhost:9527/payment/lb

无法正常使用转发

第十三章、Config 分布式配置中心

概述

分布式系统面临的—-配置问题

  1. 微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。
  2. SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,上百个配置文件的管理....../(ㄒoㄒ)/~~

是什么

SpringCloud-第二部分 - 图69

  1. 是什么
  2. SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
  3. 怎么玩
  4. SpringCloud Config分为服务端和客户端两部分。
  5. 服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
  6. 客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容。

能干嘛

  1. 集中管理配置文件
  2. 不同环境不同配置,动态化的配置更新,分环境部署比如dev/test/prod/beta/release
  3. 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
  4. 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
  5. 将配置信息以REST接口的形式暴露 postcurl访问刷新均可......

与GitHub整合配置

由于SpringCloud Config默认使用Git来存储配置文件(也有其它方式,比如支持SVN和本地文件),但最推荐的还是Git,而且使用的是http/https访问的形式

官网 https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.1.RELEASE/reference/html/

SpringCloud-第二部分 - 图70

Config服务端配置与测试

用你自己的账号在GitHub上新建一个名为springcloud-config的新Repository

由上一步获得刚新建的git地址 git@github.com:zzyybs/springcloud-config.git

本地硬盘目录上新建git仓库并clone

SpringCloud-第二部分 - 图71

本地地址:D:\44\SpringCloud2020

git命令

git clone git@github.com:zzyybs/springcloud-config.git

此时在本地D盘符下D:\44\SpringCloud2020\springcloud-config

SpringCloud-第二部分 - 图72

表示多个环境的配置文件

保存格式必须为UTF-8

如果需要修改,此处模拟运维人员操作git和github

git add .

git commit -m “init yml”

git push origin master

新建Module模块cloud-config-center-3344 它即为Cloud的配置中心模块cloudConfig Center

POM

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-config-server</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.cloud</groupId>
  8. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-web</artifactId>
  13. </dependency>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-actuator</artifactId>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-devtools</artifactId>
  21. <scope>runtime</scope>
  22. <optional>true</optional>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.projectlombok</groupId>
  26. <artifactId>lombok</artifactId>
  27. <optional>true</optional>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-starter-test</artifactId>
  32. <scope>test</scope>
  33. </dependency>
  34. </dependencies>

YML

  1. server:
  2. port: 3344
  3. spring:
  4. application:
  5. name: cloud-config-center #注册进Eureka服务器的微服务名
  6. cloud:
  7. config:
  8. server:
  9. git:
  10. uri: git@github.com:zzyybs/springcloud-config.git #GitHub上面的git仓库名字
  11. ####搜索目录
  12. search-paths:
  13. - springcloud-config
  14. ####读取分支
  15. label: master
  16. #服务注册到eureka地址
  17. eureka:
  18. client:
  19. service-url:
  20. defaultZone: http://localhost:7001/eureka

主启动类 ConfigCenterMain3344

  1. @SpringBootApplication
  2. @EnableConfigServer
  3. public class ConfigCenterMain3344{
  4. public static void main(String[] args) {
  5. SpringApplication.run(ConfigCenterMain3344.class, args);
  6. }
  7. }

@EnableConfigServer

windows下修改hosts文件,增加映射

127.0.0.1 config-3344.com

测试通过Config微服务是否可以从GitHub上获取配置内容

启动微服务3344 http://config-3344.com:3344/master/config-dev.yml

配置读取规则

官网

SpringCloud-第二部分 - 图73

/{label}/{application}-{profile}.yml

master分支

http://config-3344.com:3344/master/config-dev.yml

http://config-3344.com:3344/master/config-test.yml

http://config-3344.com:3344/master/config-prod.yml

dev分支

http://config-3344.com:3344/dev/config-dev.yml

http://config-3344.com:3344/dev/config-test.yml

http://config-3344.com:3344/dev/config-prod.yml

/{application}-{profile}.yml

http://config-3344.com:3344/config-dev.yml

http://config-3344.com:3344/config-test.yml

http://config-3344.com:3344/config-prod.yml

http://config-3344.com:3344/config-xxxx.yml(不存在的配置)

/{application}/{profile}[/{label}]

http://config-3344.com:3344/config/dev/master

http://config-3344.com:3344/config/test/master

http://config-3344.com:3344/config/test/dev

重要配置细节总结

  1. /{name}-{profiles}.yml

/{label}-{name}-{profiles}.yml

label:分支(branch)
name :服务名
profiles:环境(dev/test/prod)

成功实现了用SpringCloud Config通过GitHub获取配置信息

Config客户端配置与测试

新建cloud-config-client-3355

POM

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-config</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.cloud</groupId>
  8. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-web</artifactId>
  13. </dependency>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-actuator</artifactId>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-devtools</artifactId>
  21. <scope>runtime</scope>
  22. <optional>true</optional>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.projectlombok</groupId>
  26. <artifactId>lombok</artifactId>
  27. <optional>true</optional>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-starter-test</artifactId>
  32. <scope>test</scope>
  33. </dependency>
  34. </dependencies>

bootstrap.yml

是什么

  1. applicaiton.yml是用户级的资源配置项
  2. bootstrap.yml是系统级的,优先级更加高
  3. Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的`Application Context`的父上下文。初始化的时候,`Bootstrap Context`负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的`Environment`
  4. `Bootstrap`属性有高优先级,默认情况下,它们不会被本地配置覆盖。 `Bootstrap context``Application Context`有着不同的约定,所以新增了一个`bootstrap.yml`文件,保证`Bootstrap Context``Application Context`配置的分离。
  5. 要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的,
  6. 因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml

内容

  1. server:
  2. port: 3355
  3. spring:
  4. application:
  5. name: config-client
  6. cloud:
  7. #Config客户端配置
  8. config:
  9. label: master #分支名称
  10. name: config #配置文件名称
  11. profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
  12. uri: http://localhost:3344 #配置中心地址k
  13. #服务注册到eureka地址
  14. eureka:
  15. client:
  16. service-url:
  17. defaultZone: http://localhost:7001/eureka

说明

SpringCloud-第二部分 - 图74

修改config-dev.yml配置并提交到GitHub中,比如加个变量age或者版本号version

主启动 ConfigClientMain3355

  1. @EnableEurekaClient
  2. @SpringBootApplication
  3. public class ConfigClientMain3355{
  4. public static void main(String[] args){
  5. SpringApplication.run(ConfigClientMain3355.class,args);
  6. }
  7. }

业务类

  1. @RestController
  2. public class ConfigClientController{
  3. @Value("${config.info}")
  4. private String configInfo;
  5. @GetMapping("/configInfo")
  6. public String getConfigInfo() {
  7. return configInfo;
  8. }
  9. }

测试

启动Config配置中心3344微服务并自测

http://config-3344.com:3344/master/config-prod.yml

http://config-3344.com:3344/master/config-dev.yml

启动3355作为Client准备访问

http://localhost:3355/configInfo

成功实现了客户端3355访问SpringCloud Config3344通过GitHub获取配置信息

问题随时而来,分布式配置的动态刷新问题

Linux运维修改GitHub上的配置文件内容做调整

刷新3344,发现ConfigServer配置中心立刻响应

刷新3355,发现ConfigClient客户端没有任何响应

3355没有变化除非自己重启或者重新加载

难到每次运维修改配置文件,客户端都需要重启??噩梦

Config客户端之动态刷新

避免每次更新配置都要重启客户端微服务3355

动态刷新步骤

修改3355模块

POM引入actuator监控

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-actuator</artifactId>
  4. </dependency>

修改YML,暴露监控端口

  1. server:
  2. port: 3355
  3. spring:
  4. application:
  5. name: config-client
  6. cloud:
  7. #Config客户端配置
  8. config:
  9. label: master #分支名称
  10. name: config #配置文件名称
  11. profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取
  12. uri: http://localhost:3344 #配置中心地址k
  13. #服务注册到eureka地址
  14. eureka:
  15. client:
  16. service-url:
  17. defaultZone: http://localhost:7001/eureka
  18. # 暴露监控端点
  19. management:
  20. endpoints:
  21. web:
  22. exposure:
  23. include: "*"

@RefreshScope业务类Controller修改

  1. @RestController
  2. @RefreshScope
  3. public class ConfigClientController{
  4. @Value("${config.info}")
  5. private String configInfo;
  6. @GetMapping("/configInfo")
  7. public String getConfigInfo() {
  8. return configInfo;
  9. }
  10. }

此时修改github—-> 3344 ——>3355

http://localhost:3355/configInfo

3355改变没有???

How

需要运维人员发送Post请求刷新3355

必须是POST请求

curl -X POST “http://localhost:3355/actuator/refresh

SpringCloud-第二部分 - 图75

http://localhost:3355/configInfo 成功实现了客户端3355刷新到最新配置内容 避免了服务重启

想想还有什么问题?

假如有多个微服务客户端3355/3366/3377。。。。。。

每个微服务都要执行一次post请求,手动刷新?

可否广播,一次通知,处处生效?

我们想大范围的自动刷新,求方法

第十四章、Bus消息总线

概述

上一讲解的加深和扩充,一言以蔽之

分布式自动刷新配置功能,Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新。

是什么

Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新。

SpringCloud-第二部分 - 图76

Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,它整合了Java的事件处理机制和消息中间件的功能。Spring Clud Bus目前支持RabbitMQ和Kafka。Bus支持两种消息代理:RabbitMQ 和 Kafka

能干嘛

Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道。

SpringCloud-第二部分 - 图77

为何被称为总线

什么是总线
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。

基本原理
ConfigClient实例都监听MQ中同一个topic(默认是springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到Topic中,这样其它监听同一Topic的服务就能得到通知,然后去更新自身的配置。

https://www.bilibili.com/video/av55976700?from=search&seid=15010075915728605208

RabbitMQ环境配置

安装Erlang,下载地址: http://erlang.org/download/otp_win64_21.3.exe

步骤

SpringCloud-第二部分 - 图78

SpringCloud-第二部分 - 图79

SpringCloud-第二部分 - 图80

SpringCloud-第二部分 - 图81

安装RabbitMQ,下载地址:

https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/rabbitmq-server-3.7.14.exe

步骤

SpringCloud-第二部分 - 图82

SpringCloud-第二部分 - 图83

进入RabbitMQ安装目录下的sbin目录

如例阳哥本机

D:\devSoft\RabbitMQ Server\rabbitmq_server-3.7.14\sbin

SpringCloud-第二部分 - 图84

输入以下命令启动管理功能

rabbitmq-plugins enable rabbitmq_management

SpringCloud-第二部分 - 图85

可视化插件

SpringCloud-第二部分 - 图86

访问地址查看是否安装成功:

SpringCloud-第二部分 - 图87

http://localhost:15672/

输入账号密码并登录:guest guest

Bus动态刷新全局广播

必须先具备良好的RabbitMQ环境先

演示广播效果,增加复杂度,再以3355为模板再制作一个3366

新建

cloud-config-client-3366

POM

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-config</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.cloud</groupId>
  8. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.boot</groupId>
  12. <artifactId>spring-boot-starter-web</artifactId>
  13. </dependency>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-actuator</artifactId>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-devtools</artifactId>
  21. <scope>runtime</scope>
  22. <optional>true</optional>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.projectlombok</groupId>
  26. <artifactId>lombok</artifactId>
  27. <optional>true</optional>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.springframework.boot</groupId>
  31. <artifactId>spring-boot-starter-test</artifactId>
  32. <scope>test</scope>
  33. </dependency>
  34. </dependencies>

YML

  1. server:
  2. port: 3366
  3. spring:
  4. application:
  5. name: config-client
  6. cloud:
  7. #Config客户端配置
  8. config:
  9. label: master #分支名称
  10. name: config #配置文件名称
  11. profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取http://config-3344.com:3344/master/config-dev.yml
  12. uri: http://localhost:3344 #配置中心地址
  13. #服务注册到eureka地址
  14. eureka:
  15. client:
  16. service-url:
  17. defaultZone: http://localhost:7001/eureka
  18. # 暴露监控端点
  19. management:
  20. endpoints:
  21. web:
  22. exposure:
  23. include: "*"

主启动

  1. @EnableEurekaClient
  2. @SpringBootApplication
  3. public class ConfigClientMain3366{
  4. public static void main(String[] args){
  5. SpringApplication.run(ConfigClientMain3366.class,args);
  6. }
  7. }

controller

  1. @RestController
  2. @RefreshScope
  3. public class ConfigClientController{
  4. @Value("${server.port}")
  5. private String serverPort;
  6. @Value("${config.info}")
  7. private String configInfo;
  8. @GetMapping("/configInfo")
  9. public String configInfo(){
  10. return "serverPort: "+serverPort+"\t\n\n configInfo: "+configInfo;
  11. }
  12. }

设计思想

1)利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置

SpringCloud-第二部分 - 图88

2)利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置

SpringCloud-第二部分 - 图89

图二的架构显然更加适合,图一不适合的原因如下

打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新的职责。

破坏了微服务各节点的对等性。

有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改

给cloud-config-center-3344配置中心服务端添加消息总线支持

POM

  1. <!--添加消息总线RabbitMQ支持-->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-bus-amqp</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-actuator</artifactId>
  9. </dependency>

YML

  1. port: 3344
  2. spring:
  3. application:
  4. name: cloud-config-center #注册进Eureka服务器的微服务名
  5. cloud:
  6. config:
  7. server:
  8. git:
  9. uri: git@github.com:zzyybs/springcloud-config.git #GitHub上面的git仓库名字
  10. ####搜索目录
  11. search-paths:
  12. - springcloud-config
  13. ####读取分支
  14. label: master
  15. #rabbitmq相关配置
  16. rabbitmq:
  17. host: localhost
  18. port: 5672
  19. username: guest
  20. password: guest
  21. #服务注册到eureka地址
  22. eureka:
  23. client:
  24. service-url:
  25. defaultZone: http://localhost:7001/eureka
  26. ##rabbitmq相关配置,暴露bus刷新配置的端点
  27. management:
  28. endpoints: #暴露bus刷新配置的端点
  29. web:
  30. exposure:
  31. include: 'bus-refresh'

给cloud-config-client-3355客户端添加消息总线支持

POM

  1. <!--添加消息总线RabbitMQ支持-->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-bus-amqp</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-actuator</artifactId>
  9. </dependency>

YML

  1. server:
  2. port: 3355
  3. spring:
  4. application:
  5. name: config-client
  6. cloud:
  7. #Config客户端配置
  8. config:
  9. label: master #分支名称
  10. name: config #配置文件名称
  11. profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取
  12. uri: http://localhost:3344 #配置中心地址k
  13. #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
  14. rabbitmq:
  15. host: localhost
  16. port: 5672
  17. username: guest
  18. password: guest
  19. #服务注册到eureka地址
  20. eureka:
  21. client:
  22. service-url:
  23. defaultZone: http://localhost:7001/eureka
  24. # 暴露监控端点
  25. management:
  26. endpoints:
  27. web:
  28. exposure:
  29. include: "*" # 'refresh'

给cloud-config-client-3366客户端添加消息总线支持

POM

  1. <!--添加消息总线RabbitMQ支持-->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-bus-amqp</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-actuator</artifactId>
  9. </dependency>

YML

  1. server:
  2. port: 3366
  3. spring:
  4. application:
  5. name: config-client
  6. cloud:
  7. #Config客户端配置
  8. config:
  9. label: master #分支名称
  10. name: config #配置文件名称
  11. profile: dev #读取后缀名称 上述3个综合:master分支上config-dev.yml的配置文件被读取
  12. uri: http://localhost:3344 #配置中心地址k
  13. #rabbitmq相关配置 15672是Web管理界面的端口;5672是MQ访问的端口
  14. rabbitmq:
  15. host: localhost
  16. port: 5672
  17. username: guest
  18. password: guest
  19. #服务注册到eureka地址
  20. eureka:
  21. client:
  22. service-url:
  23. defaultZone: http://localhost:7001/eureka
  24. # 暴露监控端点
  25. management:
  26. endpoints:
  27. web:
  28. exposure:
  29. include: "*" # 'refresh'

测试

运维工程师

修改Github上配置文件增加版本号

发送POST请求

SpringCloud-第二部分 - 图90

curl -X POST “http://localhost:3344/actuator/bus-refresh

一次发送,处处生效

配置中心

http://config-3344.com:3344/config-dev.yml

客户端

http://localhost:3355/configInfo

http://localhost:3366/configInfo

获取配置信息,发现都已经刷新了

“一次修改,广播通知,处处生效”

Bus动态刷新定点通知

不想全部通知,只想定点通知

只通知3355,不通知3366

简单一句话

指定具体某一个实例生效而不是全部

公式:http://localhost:配置中心的端口号/actuator/bus-refresh/{destination}

/bus/refresh请求不再发送到具体的服务实例上,而是发给config server并通过destination参数类指定需要更新配置的服务或实例

案例

我们这里以刷新运行在3355端口上的config-client为例,只通知3355,不通知3366

curl -X POST “http://localhost:3344/actuator/bus-refresh/config-client:3355

SpringCloud-第二部分 - 图91

通知总结All

SpringCloud-第二部分 - 图92

第十五章、Stream消息驱动

消息驱动概述

一句话

  1. 什么是SpringCloudStream
  2. 官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架。
  3. 应用程序通过 inputs 或者 outputs 来与 Spring Cloud Streambinder对象交互。
  4. 通过我们配置来binding(绑定) ,而 Spring Cloud Stream binder对象负责与消息中间件交互。
  5. 所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。
  6. 通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。
  7. Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。
  8. 目前仅支持RabbitMQKafka

屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型

官网

https://spring.io/projects/spring-cloud-stream#overview

Spring Cloud Stream是用于构建与共享消息传递系统连接的高度可伸缩的事件驱动微服务框架,该框架提供了一个灵活的编程模型,它建立在已经建立和熟悉的Spring熟语和最佳实践上,包括支持持久化的发布/订阅、消费组以及消息分区这三个核心概念

SpringCloud-第二部分 - 图93

https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/3.0.1.RELEASE/reference/html/

Spring Cloud Stream中文指导手册

https://m.wang1314.com/doc/webapp/topic/20971999.html

设计思想

标准MQ

SpringCloud-第二部分 - 图94

生产者/消费者之间靠消息媒介传递信息内容 Message

消息必须走特定的通道,消息通道MessageChannel

消息通道里的消息如何被消费呢,谁负责收发处理,消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅

为什么用Cloud Stream

  1. 比方说我们用到了RabbitMQKafka,由于这两个消息中间件的架构上的不同,像RabbitMQexchangekafkaTopicPartitions分区

SpringCloud-第二部分 - 图95

这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候springcloud Stream给我们提供了一种解耦合的方式。

stream凭什么可以统一底层差异?

  1. 在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,
  2. 由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性
  3. 通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。
  4. 通过向应用程序暴露统一的Channel通道,使得应用程序不需要再考虑各种不同的消息中间件实现。
  5. 通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。

SpringCloud-第二部分 - 图96

Binder

  1. 在没有绑定器这个概念的情况下,我们的SpringBoot应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性,通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。Stream对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq切换为kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程

SpringCloud-第二部分 - 图97

通过定义绑定器Binder作为中间层,实现了应用程序与消息中间件细节之间的隔离。

Binder可以生成Binding,Binding用来绑定消息容器的生产者和消费者,它有两种类型,INPUT和OUTPUT,INPUT对应于消费者,OUTPUT对应于生产者。

INPUT对应于消费者 OUTPUT对应于生产者

Stream中的消息通信方式遵循了发布-订阅模式

Topic主题进行广播,在RabbitMQ就是Exchange,在Kakfa中就是Topic

Spring Cloud Stream标准流程套路

SpringCloud-第二部分 - 图98

SpringCloud-第二部分 - 图99

Binder 很方便的连接中间件,屏蔽差异

Channel 通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置

Source和Sink 简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入。

编码API和常用注解

SpringCloud-第二部分 - 图100

SpringCloud-第二部分 - 图101

案例说明

RabbitMQ环境已经OK

工程中新建三个子模块

cloud-stream-rabbitmq-provider8801, 作为生产者进行发消息模块

cloud-stream-rabbitmq-consumer8802,作为消息接收模块

cloud-stream-rabbitmq-consumer8803 作为消息接收模块

消息驱动之生产者

新建Module

cloud-stream-rabbitmq-provider8801

POM

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-actuator</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.cloud</groupId>
  12. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  13. </dependency>
  14. <dependency>
  15. <groupId>org.springframework.cloud</groupId>
  16. <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
  17. </dependency>
  18. <!--基础配置-->
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-devtools</artifactId>
  22. <scope>runtime</scope>
  23. <optional>true</optional>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.projectlombok</groupId>
  27. <artifactId>lombok</artifactId>
  28. <optional>true</optional>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-test</artifactId>
  33. <scope>test</scope>
  34. </dependency>
  35. </dependencies>

YML

  1. server:
  2. port: 8801
  3. spring:
  4. application:
  5. name: cloud-stream-provider
  6. cloud:
  7. stream:
  8. binders: # 在此处配置要绑定的rabbitmq的服务信息;
  9. defaultRabbit: # 表示定义的名称,用于于binding整合
  10. type: rabbit # 消息组件类型
  11. environment: # 设置rabbitmq的相关的环境配置
  12. spring:
  13. rabbitmq:
  14. host: localhost
  15. port: 5672
  16. username: guest
  17. password: guest
  18. bindings: # 服务的整合处理
  19. output: # 这个名字是一个通道的名称
  20. destination: studyExchange # 表示要使用的Exchange名称定义
  21. content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
  22. binder: defaultRabbit # 设置要绑定的消息服务的具体设置
  23. eureka:
  24. client: # 客户端进行Eureka注册的配置
  25. service-url:
  26. defaultZone: http://localhost:7001/eureka
  27. instance:
  28. lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
  29. lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
  30. instance-id: send-8801.com # 在信息列表时显示主机名称
  31. prefer-ip-address: true # 访问的路径变为IP地址

主启动类StreamMQMain8801

  1. @SpringBootApplication
  2. public class StreamMQMain8801{
  3. public static void main(String[] args){
  4. SpringApplication.run(StreamMQMain8801.class,args);
  5. }
  6. }

业务类

发送消息接口

  1. public interface IMessageProvider{
  2. public String send() ;
  3. }

发送消息接口实现类

  1. @EnableBinding(Source.class) // 可以理解为是一个消息的发送管道的定义
  2. public class MessageProviderImpl implements IMessageProvider{
  3. @Resource
  4. private MessageChannel output; // 消息的发送管道[必须写成output不能写成outPut]
  5. @Override
  6. public String send(){
  7. String serial = UUID.randomUUID().toString();
  8. this.output.send(MessageBuilder.withPayload(serial).build()); // 创建并发送消息
  9. System.out.println("***serial: "+serial);
  10. return serial;
  11. }
  12. }

Controller

  1. @RestController
  2. public class SendMessageController{
  3. @Resource
  4. private IMessageProvider messageProvider;
  5. @GetMapping(value = "/sendMessage")
  6. public String sendMessage(){
  7. return messageProvider.send();
  8. }
  9. }

测试

启动7001eureka

启动rabbitmq

rabbitmq-plugins enable rabbitmq_management

http://localhost:15672/

启动8801

SpringCloud-第二部分 - 图102

访问

http://localhost:8801/sendMessage

消息驱动之消费者

新建Module

cloud-stream-rabbitmq-consumer8802

POM

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.cloud</groupId>
  8. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.cloud</groupId>
  12. <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
  13. </dependency>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-actuator</artifactId>
  17. </dependency>
  18. <!--基础配置-->
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-devtools</artifactId>
  22. <scope>runtime</scope>
  23. <optional>true</optional>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.projectlombok</groupId>
  27. <artifactId>lombok</artifactId>
  28. <optional>true</optional>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-test</artifactId>
  33. <scope>test</scope>
  34. </dependency>
  35. </dependencies>

YML

  1. server:
  2. port: 8802
  3. spring:
  4. application:
  5. name: cloud-stream-consumer
  6. cloud:
  7. stream:
  8. binders: # 在此处配置要绑定的rabbitmq的服务信息;
  9. defaultRabbit: # 表示定义的名称,用于于binding整合
  10. type: rabbit # 消息组件类型
  11. environment: # 设置rabbitmq的相关的环境配置
  12. rabbitmq:
  13. host: localhost
  14. port: 5672
  15. username: guest
  16. password: guest
  17. bindings: # 服务的整合处理
  18. input: # 这个名字是一个通道的名称
  19. destination: studyExchange # 表示要使用的Exchange名称定义
  20. content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
  21. binder: defaultRabbit # 设置要绑定的消息服务的具体设置
  22. eureka:
  23. client: # 客户端进行Eureka注册的配置
  24. service-url:
  25. defaultZone: http://localhost:7001/eureka
  26. instance:
  27. lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
  28. lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
  29. instance-id: receive-8802.com # 在信息列表时显示主机名称
  30. prefer-ip-address: true # 访问的路径变为IP地址

主启动类StreamMQMain8802

  1. @SpringBootApplication
  2. public class StreamMQMain8802{
  3. public static void main(String[] args){
  4. SpringApplication.run(StreamMQMain8802.class,args);
  5. }
  6. }

业务类

  1. @Component
  2. @EnableBinding(Sink.class)
  3. public class ReceiveMessageListener{
  4. @Value("${server.port}")
  5. private String serverPort;
  6. @StreamListener(Sink.INPUT)
  7. public void input(Message<String> message){
  8. System.out.println("消费者1号,------->接收到的消息:" + message.getPayload()+"\t port: "+serverPort);
  9. }
  10. }

测试8801发送8802接收消息 http://localhost:8801/sendMessage

分组消费与持久化

依照8802,clone出来一份运行8803

cloud-stream-rabbitmq-consumer8803

POM

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.cloud</groupId>
  8. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>org.springframework.cloud</groupId>
  12. <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
  13. </dependency>
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-actuator</artifactId>
  17. </dependency>
  18. <!--基础配置-->
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-devtools</artifactId>
  22. <scope>runtime</scope>
  23. <optional>true</optional>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.projectlombok</groupId>
  27. <artifactId>lombok</artifactId>
  28. <optional>true</optional>
  29. </dependency>
  30. <dependency>
  31. <groupId>org.springframework.boot</groupId>
  32. <artifactId>spring-boot-starter-test</artifactId>
  33. <scope>test</scope>
  34. </dependency>
  35. </dependencies>

YML

  1. server:
  2. port: 8803
  3. spring:
  4. application:
  5. name: cloud-stream-consumer
  6. cloud:
  7. stream:
  8. binders: # 在此处配置要绑定的rabbitmq的服务信息;
  9. defaultRabbit: # 表示定义的名称,用于于binding整合
  10. type: rabbit # 消息组件类型
  11. environment: # 设置rabbitmq的相关的环境配置
  12. spring:
  13. rabbitmq:
  14. host: localhost
  15. port: 5672
  16. username: guest
  17. password: guest
  18. bindings: # 服务的整合处理
  19. input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
  20. destination: studyExchange # 表示要使用的Exchange名称定义
  21. content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
  22. binder: defaultRabbit # 设置要绑定的消息服务的具体设置
  23. eureka:
  24. client: # 客户端进行Eureka注册的配置
  25. service-url:
  26. defaultZone: http://localhost:7001/eureka
  27. instance:
  28. lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
  29. lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
  30. instance-id: receive-8803.com # 在信息列表时显示主机名称
  31. prefer-ip-address: true # 访问的路径变为IP地址

主启动类

  1. @SpringBootApplication
  2. public class StreamMQMain8803{
  3. public static void main(String[] args){
  4. SpringApplication.run(StreamMQMain8803.class,args);
  5. }
  6. }

业务类

  1. @Component
  2. @EnableBinding(Sink.class)
  3. public class ReceiveMessageListener{
  4. @Value("${server.port}")
  5. private String serverPort;
  6. @StreamListener(Sink.INPUT)
  7. public void input(Message<String> message){
  8. System.out.println("消费者2号,------->接收到的消息:" + message.getPayload()+"\t port: "+serverPort);
  9. }
  10. }

启动

  1. RabbitMQ
  2. 7001 服务注册
  3. 8801 消息生产
  4. 8802 消息消费
  5. 8803 消息消费

运行后有两个问题

有重复消费问题 消息持久化问题

消费

目前是8802/8803同时都收到了,存在重复消费问题

http://localhost:8801/sendMessage

8802

SpringCloud-第二部分 - 图103

8803

SpringCloud-第二部分 - 图104

如何解决

分组和持久化属性group

生产实际案例

比如在如下场景中,订单系统我们做集群部署,都会从RabbitMQ中获取订单信息,
那如果一个订单同时被两个服务获取到,那么就会造成数据错误,我们得避免这种情况。
这时我们就可以使用Stream中的消息分组来解决

SpringCloud-第二部分 - 图105

注意在Stream中处于同一个group中的多个消费者是竞争关系,就能够保证消息只会被其中一个应用消费一次。
不同组是可以全面消费的(重复消费),
同一组内会发生竞争关系,只有其中一个可以消费。

分组

原理

微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。
不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。

8802/8803都变成不同组,group两个不同

group: atguiguA、atguiguB

8802修改YML

  1. server:
  2. port: 8802
  3. spring:
  4. application:
  5. name: cloud-stream-consumer
  6. cloud:
  7. stream:
  8. binders: # 在此处配置要绑定的rabbitmq的服务信息;
  9. defaultRabbit: # 表示定义的名称,用于于binding整合
  10. type: rabbit # 消息组件类型
  11. environment: # 设置rabbitmq的相关的环境配置
  12. spring:
  13. rabbitmq:
  14. host: localhost
  15. port: 5672
  16. username: guest
  17. password: guest
  18. bindings: # 服务的整合处理
  19. input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
  20. destination: studyExchange # 表示要使用的Exchange名称定义
  21. content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
  22. binder: defaultRabbit # 设置要绑定的消息服务的具体设置
  23. group: atguiguA
  24. eureka:
  25. client: # 客户端进行Eureka注册的配置
  26. service-url:
  27. defaultZone: http://localhost:7001/eureka
  28. instance:
  29. lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
  30. lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
  31. instance-id: receive-8802.com # 在信息列表时显示主机名称
  32. prefer-ip-address: true # 访问的路径变为IP地址

8803修改YML

  1. server:
  2. port: 8803
  3. spring:
  4. application:
  5. name: cloud-stream-consumer
  6. cloud:
  7. stream:
  8. binders: # 在此处配置要绑定的rabbitmq的服务信息;
  9. defaultRabbit: # 表示定义的名称,用于于binding整合
  10. type: rabbit # 消息组件类型
  11. environment: # 设置rabbitmq的相关的环境配置
  12. spring:
  13. rabbitmq:
  14. host: localhost
  15. port: 5672
  16. username: guest
  17. password: guest
  18. bindings: # 服务的整合处理
  19. input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
  20. destination: studyExchange # 表示要使用的Exchange名称定义
  21. content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
  22. binder: defaultRabbit # 设置要绑定的消息服务的具体设置
  23. group: atguiguB
  24. eureka:
  25. client: # 客户端进行Eureka注册的配置
  26. service-url:
  27. defaultZone: http://localhost:7001/eureka
  28. instance:
  29. lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
  30. lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
  31. instance-id: receive-8803.com # 在信息列表时显示主机名称
  32. prefer-ip-address: true # 访问的路径变为IP地址

我们自己配置

SpringCloud-第二部分 - 图106

分布式微服务应用为了实现高可用和负载均衡,实际上都会部署多个实例,本例阳哥启动了两个消费微服务(8802/8803)

多数情况,生产者发送消息给某个具体微服务时只希望被消费一次,按照上面我们启动两个应用的例子,虽然它们同属一个应用,
但是这个消息出现了被重复消费两次的情况。为了解决这个问题,在Spring Cloud Stream中提供了消费组的概念。

结论

还是重复消费

8802/8803实现了轮询分组,每次只有一个消费者
8801模块的发的消息只能被8802或8803其中一个接收到,这样避免了重复消费

8802/8803都变成相同组,group两个相同

group: atguiguA

8802修改YML

  1. server:
  2. port: 8802
  3. spring:
  4. application:
  5. name: cloud-stream-consumer
  6. cloud:
  7. stream:
  8. binders: # 在此处配置要绑定的rabbitmq的服务信息;
  9. defaultRabbit: # 表示定义的名称,用于于binding整合
  10. type: rabbit # 消息组件类型
  11. environment: # 设置rabbitmq的相关的环境配置
  12. rabbitmq:
  13. host: localhost
  14. port: 5672
  15. username: guest
  16. password: guest
  17. bindings: # 服务的整合处理
  18. input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
  19. destination: studyExchange # 表示要使用的Exchange名称定义
  20. content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
  21. binder: defaultRabbit # 设置要绑定的消息服务的具体设置
  22. group: atguiguA
  23. eureka:
  24. client: # 客户端进行Eureka注册的配置
  25. service-url:
  26. defaultZone: http://localhost:7001/eureka
  27. instance:
  28. lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
  29. lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
  30. instance-id: receive-8802.com # 在信息列表时显示主机名称
  31. prefer-ip-address: true # 访问的路径变为IP地址

8803修改YML

  1. server:
  2. port: 8803
  3. spring:
  4. application:
  5. name: cloud-stream-consumer
  6. cloud:
  7. stream:
  8. binders: # 在此处配置要绑定的rabbitmq的服务信息;
  9. defaultRabbit: # 表示定义的名称,用于于binding整合
  10. type: rabbit # 消息组件类型
  11. environment: # 设置rabbitmq的相关的环境配置
  12. spring:
  13. rabbitmq:
  14. host: localhost
  15. port: 5672
  16. username: guest
  17. password: guest
  18. bindings: # 服务的整合处理
  19. input: # 这个名字是一个通道的名称,在分析具体源代码的时候会进行说明
  20. destination: studyExchange # 表示要使用的Exchange名称定义
  21. content-type: application/json # 设置消息类型,本次为对象json,如果是文本则设置“text/plain”
  22. binder: defaultRabbit # 设置要绑定的消息服务的具体设置
  23. group: atguiguA
  24. eureka:
  25. client: # 客户端进行Eureka注册的配置
  26. service-url:
  27. defaultZone: http://localhost:7001/eureka
  28. instance:
  29. lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
  30. lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
  31. instance-id: receive-8803.com # 在信息列表时显示主机名称
  32. prefer-ip-address: true # 访问的路径变为IP地址

结论

同一个组的多个微服务实例,每次只会有一个拿到

持久化

通过上述,解决了重复消费问题,再看看持久化

停止8802/8803并去除掉8802的分组group: atguiguA,8803的分组group: atguiguA没有去掉

8801先发送4条消息到rabbitmq

先启动8802,无分组属性配置,后台没有打出来消息

SpringCloud-第二部分 - 图107

再启动8803,有分组属性配置,后台打出来了MQ上的消息

SpringCloud-第二部分 - 图108

第十六章、Sleuth 分布式请求链路跟踪

概述

为什么会出现这个技术?需要解决哪些问题?

问题

在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。

SpringCloud-第二部分 - 图109

SpringCloud-第二部分 - 图110

是什么

https://github.com/spring-cloud/spring-cloud-sleuth

Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案

在分布式系统中提供追踪解决方案并且兼容支持了zipkin

解决

SpringCloud-第二部分 - 图111

搭建链路监控步骤

1、zipkin

下载

SpringCloud从F版起已不需要自己构建Zipkin Server了,只需调用jar包即可

https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/

zipkin-server-2.12.9-exec.jar

运行jar

java -jar zipkin-server-2.12.9-exec.jar

SpringCloud-第二部分 - 图112

运行控制台

http://localhost:9411/zipkin/

术语

完整的调用链路

表示一请求链路,一条链路通过Trace Id唯一标识,Span标识发起的请求信息,各span通过parent id 关联起来

SpringCloud-第二部分 - 图113

SpringCloud-第二部分 - 图114

SpringCloud-第二部分 - 图115

名词解释

Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识

span:表示调用链路来源,通俗的理解span就是一次请求信息

2、服务提供者

cloud-provider-payment8001

POM

  1. <dependencies>
  2. <!--包含了sleuth+zipkin-->
  3. <dependency>
  4. <groupId>org.springframework.cloud</groupId>
  5. <artifactId>spring-cloud-starter-zipkin</artifactId>
  6. </dependency>
  7. <!--eureka-client-->
  8. <dependency>
  9. <groupId>org.springframework.cloud</groupId>
  10. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  11. </dependency>
  12. <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  13. <groupId>com.atguigu.springcloud</groupId>
  14. <artifactId>cloud-api-commons</artifactId>
  15. <version>${project.version}</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-web</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-actuator</artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.mybatis.spring.boot</groupId>
  27. <artifactId>mybatis-spring-boot-starter</artifactId>
  28. </dependency>
  29. <dependency>
  30. <groupId>com.alibaba</groupId>
  31. <artifactId>druid-spring-boot-starter</artifactId>
  32. <version>1.1.10</version>
  33. </dependency>
  34. <!--mysql-connector-java-->
  35. <dependency>
  36. <groupId>mysql</groupId>
  37. <artifactId>mysql-connector-java</artifactId>
  38. </dependency>
  39. <!--jdbc-->
  40. <dependency>
  41. <groupId>org.springframework.boot</groupId>
  42. <artifactId>spring-boot-starter-jdbc</artifactId>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.springframework.boot</groupId>
  46. <artifactId>spring-boot-devtools</artifactId>
  47. <scope>runtime</scope>
  48. <optional>true</optional>
  49. </dependency>
  50. <dependency>
  51. <groupId>org.projectlombok</groupId>
  52. <artifactId>lombok</artifactId>
  53. <optional>true</optional>
  54. </dependency>
  55. <dependency>
  56. <groupId>org.springframework.boot</groupId>
  57. <artifactId>spring-boot-starter-test</artifactId>
  58. <scope>test</scope>
  59. </dependency>
  60. </dependencies>

YML

  1. server:
  2. port: 8001
  3. spring:
  4. application:
  5. name: cloud-payment-service
  6. zipkin:
  7. base-url: http://localhost:9411
  8. sleuth:
  9. sampler:
  10. #采样率值介于 0 到 1 之间,1 则表示全部采集
  11. probability: 1
  12. datasource:
  13. type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
  14. driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
  15. url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
  16. username: root
  17. password: 123456
  18. eureka:
  19. client:
  20. #表示是否将自己注册进EurekaServer默认为true。
  21. register-with-eureka: true
  22. #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
  23. fetchRegistry: true
  24. service-url:
  25. #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
  26. defaultZone: http://localhost:7001/eureka # 单机版
  27. instance:
  28. instance-id: payment8001
  29. #访问路径可以显示IP地址
  30. prefer-ip-address: true
  31. #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
  32. lease-renewal-interval-in-seconds: 1
  33. #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
  34. lease-expiration-duration-in-seconds: 2
  35. mybatis:
  36. mapperLocations: classpath:mapper/*.xml
  37. type-aliases-package: com.atguigu.springcloud.entities # 所有Entity别名类所在包

业务类PaymentController

  1. @GetMapping("/payment/zipkin")
  2. public String paymentZipkin(){
  3. return "hi ,i'am paymentzipkin server fall back,welcome to atguigu,O(∩_∩)O哈哈~";
  4. }

3、服务消费者(调用方)

cloud-consumer-order80

POM

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.cloud</groupId>
  8. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  9. </dependency>
  10. <!--包含了sleuth+zipkin-->
  11. <dependency>
  12. <groupId>org.springframework.cloud</groupId>
  13. <artifactId>spring-cloud-starter-zipkin</artifactId>
  14. </dependency>
  15. <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  16. <groupId>com.atguigu.springcloud</groupId>
  17. <artifactId>cloud-api-commons</artifactId>
  18. <version>${project.version}</version>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-web</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-actuator</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-devtools</artifactId>
  31. <scope>runtime</scope>
  32. <optional>true</optional>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.projectlombok</groupId>
  36. <artifactId>lombok</artifactId>
  37. <optional>true</optional>
  38. </dependency>
  39. <dependency>
  40. <groupId>org.springframework.boot</groupId>
  41. <artifactId>spring-boot-starter-test</artifactId>
  42. <scope>test</scope>
  43. </dependency>
  44. </dependencies>

YML

  1. server:
  2. port: 80
  3. spring:
  4. application:
  5. name: cloud-order-service
  6. zipkin:
  7. base-url: http://localhost:9411
  8. sleuth:
  9. sampler:
  10. probability: 1
  11. eureka:
  12. client:
  13. #表示是否将自己注册进EurekaServer默认为true。
  14. register-with-eureka: true
  15. #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
  16. fetchRegistry: true
  17. service-url:
  18. #defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
  19. defaultZone: http://eureka7001.com:7001/eureka

业务类OrderController

  1. // ====================> zipkin+sleuth
  2. @GetMapping("/consumer/payment/zipkin")
  3. public String paymentZipkin(){
  4. String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/", String.class);
  5. return result;
  6. }

4、依次启动eureka7001/8001/80

80调用8001几次测试下

5、打开浏览器访问:http://localhost:9411

会出现以下界面

SpringCloud-第二部分 - 图116

查看

SpringCloud-第二部分 - 图117

查看依赖关系

SpringCloud-第二部分 - 图118

SpringCloud-第二部分 - 图119

原理

SpringCloud-第二部分 - 图120

第十七章、Alibaba 入门简介

为什么会出现SpringCloud alibaba

Spring Cloud Netflix项目进入维护模式

https://spring.io/blog/2018/12/12/spring-cloud-greenwich-rc1-available-now

SpringCloud-第二部分 - 图121

说明

SpringCloud-第二部分 - 图122

Spring Cloud Netflix Projects Entering Maintenance Mode

什么是维护模式

SpringCloud-第二部分 - 图123

将模块置于维护模式,意味着 Spring Cloud 团队将不会再向模块添加新功能。
我们将修复 block 级别的 bug 以及安全问题,我们也会考虑并审查社区的小型 pull request。

进入维护模式意味着什么呢?

进入维护模式意味着

Spring Cloud Netflix 将不再开发新的组件
我们都知道Spring Cloud 版本迭代算是比较快的,因而出现了很多重大ISSUE都还来不及Fix就又推另一个Release了。进入维护模式意思就是目前一直以后一段时间Spring Cloud Netflix提供的服务和功能就这么多了,不在开发新的组件和功能了。以后将以维护和Merge分支Full Request为主

新组件功能将以其他替代平代替的方式实现

SpringCloud-第二部分 - 图124

SpringCloud alibaba带来了什么

是什么

官网:
https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

诞生:
2018.10.31,Spring Cloud Alibaba 正式入驻了 Spring Cloud 官方孵化器,并在 Maven 中央库发布了第一个版本。

SpringCloud-第二部分 - 图125

能干嘛

  1. 服务限流降级:默认支持 ServletFeignRestTemplateDubbo RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
  2. 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
  3. 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
  4. 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
  5. 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
  6. 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Workerschedulerx-client)上执行。

去哪下

https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

怎么玩

SpringCloud-第二部分 - 图126

SpringCloud alibaba学习资料获取

官网

SpringCloud-第二部分 - 图127

Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

SpringCloud Alibaba进入了SpringCloud官方孵化器,而且毕业了

https://spring.io/projects/spring-cloud-alibaba#overview

英文

https://github.com/alibaba/spring-cloud-alibaba

https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html

中文

https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md

第十八章、Nacos服务注册和配置中心

Nacos简介

为什么叫Nacos

前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service。

是什么

一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

Nacos: Dynamic Naming and Configuration Service

Nacos就是注册中心 + 配置中心的组合 等价于 Nacos = Eureka+Config +Bus

能干嘛

替代Eureka做服务注册中心

替代Config做服务配置中心

去哪下

https://github.com/alibaba/Nacos

官网文档

https://nacos.io/zh-cn/index.html

https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_nacos_discovery

各种注册中心比较

SpringCloud-第二部分 - 图128

据说 Nacos 在阿里巴巴内部有超过 10 万的实例运行,已经过了类似双十一等各种大型流量的考验

安装并运行Nacos

本地Java8+Maven环境已经OK

先从官网下载Nacos

https://github.com/alibaba/nacos/releases

SpringCloud-第二部分 - 图129

SpringCloud-第二部分 - 图130

解压安装包,直接运行bin目录下的startup.cmd

startup.cmd -m standalone

命令运行成功后直接访问http://localhost:8848/nacos

默认账号密码都是nacos

结果页面

SpringCloud-第二部分 - 图131

Nacos作为服务注册中心演示

官网文档

https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_nacos_config

基于Nacos的服务提供者

新建Module

cloudalibaba-provider-payment9001

POM

父POM

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.atguigu.springcloud</groupId>
  6. <artifactId>mscloud</artifactId>
  7. <version>1.0-SNAPSHOT</version>
  8. <packaging>pom</packaging>
  9. <modules>
  10. <module>cloud-eureka-server7001</module>
  11. <module>cloud-eureka-server7002</module>
  12. <module>cloud-eureka-server7003</module>
  13. <module>cloud-provider-payment8001</module>
  14. <module>cloud-provider-payment8002</module>
  15. <module>cloud-providerzk-server8004</module>
  16. <module>cloud-providerzk-server8005</module>
  17. <module>cloud-consumer-order80</module>
  18. <module>cloud-consumerzk-order81</module>
  19. <module>cloud-providerconsul-payment8006</module>
  20. <module>cloud-consumerconsul-order82</module>
  21. <module>cloud-providerconsul-payment8007</module>
  22. <module>cloud-provider-payment8003</module>
  23. <module>cloud-consumer-feign-order80</module>
  24. <module>cloud-provider-hystrix-payment8001</module>
  25. <module>cloud-consumer-hystrix-dashboard9001</module>
  26. <module>cloud-zuul-gateway9527</module>
  27. <module>cloud-provider-sms8008</module>
  28. <module>mytest</module>
  29. <module>cloud-config-center-3344</module>
  30. <module>cloud-config-client-3355</module>
  31. <module>cloud-conumser-feign-order2001</module>
  32. <module>cloud-provider-hystrix-payment2701</module>
  33. <module>cloud-provider-hystrix-payment2702</module>
  34. <module>cloud-config-client-3366</module>
  35. <module>cloud-stream-rabbitmq-provider8801</module>
  36. <module>cloud-stream-rabbitmq-consumer8802</module>
  37. <module>cloud-gateway-gateway9588</module>
  38. <module>cloudalibaba-provider-payment9001</module>
  39. <module>cloudalibaba-provider-payment9002</module>
  40. </modules>
  41. <properties>
  42. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  43. <maven.compiler.source>1.8</maven.compiler.source>
  44. <maven.compiler.target>1.8</maven.compiler.target>
  45. <junit.version>4.12</junit.version>
  46. <log4j.version>1.2.17</log4j.version>
  47. <lombok.version>1.16.18</lombok.version>
  48. </properties>
  49. <dependencyManagement>
  50. <dependencies>
  51. <dependency>
  52. <groupId>org.springframework.boot</groupId>
  53. <artifactId>spring-boot-dependencies</artifactId>
  54. <version>2.1.9.RELEASE</version>
  55. <type>pom</type>
  56. <scope>import</scope>
  57. </dependency>
  58. <dependency>
  59. <groupId>org.springframework.cloud</groupId>
  60. <artifactId>spring-cloud-dependencies</artifactId>
  61. <version>Greenwich.SR3</version>
  62. <type>pom</type>
  63. <scope>import</scope>
  64. </dependency>
  65. <dependency>
  66. <groupId>com.alibaba.cloud</groupId>
  67. <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  68. <version>2.1.0.RELEASE</version>
  69. <type>pom</type>
  70. <scope>import</scope>
  71. </dependency>
  72. <dependency>
  73. <groupId>mysql</groupId>
  74. <artifactId>mysql-connector-java</artifactId>
  75. <version>5.1.47</version>
  76. </dependency>
  77. <dependency>
  78. <groupId>com.alibaba</groupId>
  79. <artifactId>druid</artifactId>
  80. <version>1.1.16</version>
  81. </dependency>
  82. <dependency>
  83. <groupId>org.mybatis.spring.boot</groupId>
  84. <artifactId>mybatis-spring-boot-starter</artifactId>
  85. <version>1.3.0</version>
  86. </dependency>
  87. <dependency>
  88. <groupId>junit</groupId>
  89. <artifactId>junit</artifactId>
  90. <version>${junit.version}</version>
  91. </dependency>
  92. <dependency>
  93. <groupId>log4j</groupId>
  94. <artifactId>log4j</artifactId>
  95. <version>${log4j.version}</version>
  96. </dependency>
  97. <dependency>
  98. <groupId>org.projectlombok</groupId>
  99. <artifactId>lombok</artifactId>
  100. <version>${lombok.version}</version>
  101. <optional>true</optional>
  102. </dependency>
  103. </dependencies>
  104. </dependencyManagement>
  105. <build>
  106. <plugins>
  107. <plugin>
  108. <groupId>org.springframework.boot</groupId>
  109. <artifactId>spring-boot-maven-plugin</artifactId>
  110. <configuration>
  111. <fork>true</fork>
  112. <addResources>true</addResources>
  113. </configuration>
  114. </plugin>
  115. </plugins>
  116. </build>
  117. </project>

本模块POM

  1. <dependencies>
  2. <!--SpringCloud ailibaba nacos -->
  3. <dependency>
  4. <groupId>com.alibaba.cloud</groupId>
  5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  6. </dependency>
  7. <!-- SpringBoot整合Web组件 -->
  8. <dependency>
  9. <groupId>org.springframework.boot</groupId>
  10. <artifactId>spring-boot-starter-web</artifactId>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework.boot</groupId>
  14. <artifactId>spring-boot-starter-actuator</artifactId>
  15. </dependency>
  16. <!--日常通用jar包配置-->
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-devtools</artifactId>
  20. <scope>runtime</scope>
  21. <optional>true</optional>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.projectlombok</groupId>
  25. <artifactId>lombok</artifactId>
  26. <optional>true</optional>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-test</artifactId>
  31. <scope>test</scope>
  32. </dependency>
  33. </dependencies>

YML

  1. server:
  2. port: 9001
  3. spring:
  4. application:
  5. name: nacos-payment-provider
  6. cloud:
  7. nacos:
  8. discovery:
  9. server-addr: localhost:8848 #配置Nacos地址
  10. management:
  11. endpoints:
  12. web:
  13. exposure:
  14. include: '*'

主启动

  1. @EnableDiscoveryClient
  2. @SpringBootApplication
  3. public class PaymentMain9001{
  4. public static void main(String[] args) {
  5. SpringApplication.run(PaymentMain9001.class, args);
  6. }
  7. }

业务类

  1. @RestController
  2. public class PaymentController{
  3. @Value("${server.port}")
  4. private String serverPort;
  5. @GetMapping(value = "/payment/nacos/{id}")
  6. public String getPayment(@PathVariable("id") Integer id){
  7. return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
  8. }
  9. }

测试

http://localhost:9001/payment/nacos/1

nacos控制台

SpringCloud-第二部分 - 图132

nacos服务注册中心+服务提供者9001都OK了

为了下一章节演示nacos的负载均衡,参照9001新建9002

新建cloudalibaba-provider-payment9002

9002其它步骤你懂的

或者取巧不想新建重复体力劳动,直接拷贝虚拟端口映射

SpringCloud-第二部分 - 图133

SpringCloud-第二部分 - 图134

SpringCloud-第二部分 - 图135

基于Nacos的服务消费者

新建Module

cloudalibaba-consumer-nacos-order83

POM

  1. <dependencies>
  2. <!--SpringCloud ailibaba nacos -->
  3. <dependency>
  4. <groupId>com.alibaba.cloud</groupId>
  5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  6. </dependency>
  7. <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  8. <dependency>
  9. <groupId>com.atguigu.springcloud</groupId>
  10. <artifactId>cloud-api-commons</artifactId>
  11. <version>${project.version}</version>
  12. </dependency>
  13. <!-- SpringBoot整合Web组件 -->
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-web</artifactId>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.boot</groupId>
  20. <artifactId>spring-boot-starter-actuator</artifactId>
  21. </dependency>
  22. <!--日常通用jar包配置-->
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-devtools</artifactId>
  26. <scope>runtime</scope>
  27. <optional>true</optional>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.projectlombok</groupId>
  31. <artifactId>lombok</artifactId>
  32. <optional>true</optional>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.springframework.boot</groupId>
  36. <artifactId>spring-boot-starter-test</artifactId>
  37. <scope>test</scope>
  38. </dependency>
  39. </dependencies>

为什么nacos支持负载均衡

SpringCloud-第二部分 - 图136

YML

  1. server:
  2. port: 83
  3. spring:
  4. application:
  5. name: nacos-order-consumer
  6. cloud:
  7. nacos:
  8. discovery:
  9. server-addr: localhost:8848
  10. #消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
  11. service-url:
  12. nacos-user-service: http://nacos-payment-provider

主启动

  1. @EnableDiscoveryClient
  2. @SpringBootApplication
  3. public class OrderNacosMain83{
  4. public static void main(String[] args){
  5. SpringApplication.run(OrderNacosMain83.class,args);
  6. }
  7. }

业务类

  1. @RestController
  2. public class PaymentController{
  3. @Value("${server.port}")
  4. private String serverPort;
  5. @GetMapping(value = "/payment/{id}")
  6. public String getPayment(@PathVariable("id") Integer id){
  7. return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
  8. }
  9. }

ApplicationContextBean

  1. @Configuration
  2. public class ApplicationContextBean{
  3. @Bean
  4. @LoadBalanced
  5. public RestTemplate getRestTemplate(){
  6. return new RestTemplate();
  7. }
  8. }

OrderNacosController

  1. @RestController
  2. public class OrderNacosController{
  3. @Resource
  4. private RestTemplate restTemplate;
  5. @Value("${service-url.nacos-user-service}")
  6. private String serverURL;
  7. @GetMapping("/consumer/payment/nacos/{id}")
  8. public String paymentInfo(@PathVariable("id") Long id){
  9. return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
  10. }
  11. }

测试

nacos控制台

SpringCloud-第二部分 - 图137

http://localhost:83/consumer/payment/nacos/13

83访问9001/9002,轮询负载OK

服务注册中心对比

各种注册中心对比

Nacos全景图所示

SpringCloud-第二部分 - 图138

Nacos和CAP

SpringCloud-第二部分 - 图139

SpringCloud-第二部分 - 图140

切换

Nacos 支持AP和CP模式的切换

  1. C是所有节点在同一时间看到的数据是一致的;而A的定义是所有的请求都会收到响应。
  2. 何时选择使用何种模式?
  3. 一般来说,
  4. 如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如 Spring cloud Dubbo 服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
  5. 如果需要在服务级别编辑或者存储配置信息,那么 CP 是必须,K8S服务和DNS服务则适用于CP模式。
  6. CP模式下则支持注册持久化实例,此时则是以 Raft 协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。
  7. curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'

Nacos作为服务配置中心演示

Nacos作为配置中心-基础配置

cloudalibaba-config-nacos-client3377

POM

  1. <dependencies>
  2. <!--nacos-config-->
  3. <dependency>
  4. <groupId>com.alibaba.cloud</groupId>
  5. <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  6. </dependency>
  7. <!--nacos-discovery-->
  8. <dependency>
  9. <groupId>com.alibaba.cloud</groupId>
  10. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  11. </dependency>
  12. <!--web + actuator-->
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-web</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-actuator</artifactId>
  20. </dependency>
  21. <!--一般基础配置-->
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-devtools</artifactId>
  25. <scope>runtime</scope>
  26. <optional>true</optional>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.projectlombok</groupId>
  30. <artifactId>lombok</artifactId>
  31. <optional>true</optional>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.springframework.boot</groupId>
  35. <artifactId>spring-boot-starter-test</artifactId>
  36. <scope>test</scope>
  37. </dependency>
  38. </dependencies>

YML

why配置两个

Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,
拉取配置之后,才能保证项目的正常启动。

springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application

YML

bootstrap

  1. # nacos配置
  2. server:
  3. port: 3377
  4. spring:
  5. application:
  6. name: nacos-config-client
  7. cloud:
  8. nacos:
  9. discovery:
  10. server-addr: localhost:8848 #Nacos服务注册中心地址
  11. config:
  12. server-addr: localhost:8848 #Nacos作为配置中心地址
  13. file-extension: yaml #指定yaml格式的配置
  14. # ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}

application

  1. spring:
  2. profiles:
  3. active: dev # 表示开发环境

主启动

  1. @EnableDiscoveryClient
  2. @SpringBootApplication
  3. public class NacosConfigClientMain3377{
  4. public static void main(String[] args) {
  5. SpringApplication.run(NacosConfigClientMain3377.class, args);
  6. }
  7. }

业务类

ConfigClientController

  1. @RestController
  2. @RefreshScope //在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能。
  3. public class ConfigClientController{
  4. @Value("${config.info}")
  5. private String configInfo;
  6. @GetMapping("/config/info")
  7. public String getConfigInfo() {
  8. return configInfo;
  9. }
  10. }

@RefreshScope

SpringCloud-第二部分 - 图141

在Nacos中添加配置信息:Nacos中的匹配规则

理论

Nacos中的dataid的组成格式及与SpringBoot配置文件中的匹配规则

官网 https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html

SpringCloud-第二部分 - 图142

最后公式:

SpringCloud-第二部分 - 图143{spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

实操

配置新增

SpringCloud-第二部分 - 图144

nacos-config-client-dev

Nacos界面配置对应

SpringCloud-第二部分 - 图145

设置DataId

公式:

SpringCloud-第二部分 - 图146{spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

prefix 默认为 spring.application.name 的值

spring.profile.active 即为当前环境对应的 profile,可以通过配置项 spring.profile.active 来配置。

file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置

小总结说明

SpringCloud-第二部分 - 图147

历史配置

Nacos会记录配置文件的历史版本默认保留30天,此外还有一键回滚功能,回滚操作将会触发配置更新

回滚

SpringCloud-第二部分 - 图148

测试

启动前需要在nacos客户端-配置管理-配置管理栏目下有对应的yaml配置文件

运行cloud-config-nacos-client3377的主启动类

调用接口查看配置信息 http://localhost:3377/config/info

自带动态刷新

修改下Nacos中的yaml配置文件,再次调用查看配置的接口,就会发现配置已经刷新

Nacos作为配置中心-分类配置

问题

多环境多项目管理

  1. 问题1
  2. 实际开发中,通常一个系统会准备
  3. dev开发环境
  4. test测试环境
  5. prod生产环境。
  6. 如何保证指定环境启动时服务能正确读取到Nacos上相应环境的配置文件呢?
  7. 问题2
  8. 一个大型分布式微服务系统会有很多微服务子项目,
  9. 每个微服务项目又都会有相应的开发环境、测试环境、预发环境、正式环境......
  10. 那怎么对这些微服务配置进行管理呢?

Nacos的图形化管理界面

配置管理

SpringCloud-第二部分 - 图149

命名空间

SpringCloud-第二部分 - 图150

Namespace+Group+Data ID三者关系?为什么这么设计?

1 是什么
类似Java里面的package名和类名
最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。
2 三者情况

SpringCloud-第二部分 - 图151

默认情况:
Namespace=public,Group=DEFAULT_GROUP, 默认Cluster是DEFAULT

Nacos默认的命名空间是public,Namespace主要用来实现隔离。
比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的Namespace之间是隔离的。

Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去

Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。
比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,
这时就可以给杭州机房的Service微服务起一个集群名称(HZ),
给广州机房的Service微服务起一个集群名称(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。

最后是Instance,就是微服务的实例。

Case三种方案加载配置

DataID方案

指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置

默认空间+默认分组+新建dev和test两个DataID

新建dev配置DataID

SpringCloud-第二部分 - 图152

新建test配置DataID

SpringCloud-第二部分 - 图153

通过spring.profile.active属性就能进行多环境下配置文件的读取

SpringCloud-第二部分 - 图154

测试

http://localhost:3377/config/info

配置是什么就加载什么 test

Group方案

通过Group实现环境区分

新建Group

SpringCloud-第二部分 - 图155

在nacos图形界面控制台上面新建配置文件DataID

SpringCloud-第二部分 - 图156

bootstrap+application

SpringCloud-第二部分 - 图157

在config下增加一条group的配置即可。可配置为DEV_GROUP或TEST_GROUP

Namespace方案

新建dev/test的Namespace

SpringCloud-第二部分 - 图158

回到服务管理-服务列表查看

SpringCloud-第二部分 - 图159

按照域名配置填写

SpringCloud-第二部分 - 图160

YML

bootstrap

```yml

nacos注册中心

server: port: 3377

spring: application: name: nacos-order cloud: nacos: discovery: server-addr: localhost:8848 #Nacos服务注册中心地址 config: server-addr: localhost:8848 #Nacos作为配置中心地址 file-extension: yaml #这里我们获取的yaml格式的配置 namespace: 5da1dccc-ee26-49e0-b8e5-7d9559b95ab0

  1. #group: DEV_GROUP
  2. group: TEST_GROUP
  1. > application
  2. > ```yml
  3. # Nacos注册配置,application.yml
  4. spring:
  5. profiles:
  6. #active: test
  7. active: dev
  8. #active: info

Nacos集群和持久化配置(重要)

官网说明

https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html

官网架构图

SpringCloud-第二部分 - 图161

上图官网翻译,真实情况

SpringCloud-第二部分 - 图162

说明

默认Nacos使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题的。
为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL的存储。

SpringCloud-第二部分 - 图163

SpringCloud-第二部分 - 图164

按照上述,我们需要mysql数据库

官网说明 https://nacos.io/zh-cn/docs/deployment.html

Nacos持久化配置解释

Nacos默认自带的是嵌入式数据库derby

https://github.com/alibaba/nacos/blob/develop/config/pom.xml

derby到mysql切换配置步骤

nacos-server-1.1.4\nacos\conf目录下找到sql脚本

nacos-mysql.sql

执行脚本

nacos-server-1.1.4\nacos\conf目录下找到application.properties

```properties spring.datasource.platform=mysql

db.num=1 db.url.0=jdbc:mysql://192.168.200.188:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true db.user=root db.password=root

  1. > 启动Nacos,可以看到是个全新的空记录界面,以前是记录进derby
  2. **LinuxNacos+MySQL生产环境配置**
  3. 预计需要,1Nginx+3nacos注册中心+1mysql
  4. Nacos下载Linux
  5. ![](https://uuie-figure-bed.oss-cn-beijing.aliyuncs.com/img/markdownPic/image-20220430140840508.png#alt=image-20220430140840508)
  6. > [https://github.com/alibaba/nacos/releases/tag/1.1.4](https://github.com/alibaba/nacos/releases/tag/1.1.4)
  7. > nacos-server-1.1.4.tar.gz
  8. > 解压后安装
  9. > ![](https://uuie-figure-bed.oss-cn-beijing.aliyuncs.com/img/markdownPic/image-20220430140922849.png#alt=image-20220430140922849)
  10. **集群配置步骤(重点)**
  11. 1Linux服务器上mysql数据库配置
  12. SQL脚本在哪里
  13. ![](https://uuie-figure-bed.oss-cn-beijing.aliyuncs.com/img/markdownPic/image-20220430141153663.png#alt=image-20220430141153663)
  14. sql语句源文件 nacos-mysql.sql
  15. 自己Linux机器上的Mysql数据库粘贴,执行后结果
  16. ![](https://uuie-figure-bed.oss-cn-beijing.aliyuncs.com/img/markdownPic/image-20220430141219450.png#alt=image-20220430141219450)
  17. 2application.properties 配置
  18. 位置
  19. ![](https://uuie-figure-bed.oss-cn-beijing.aliyuncs.com/img/markdownPic/image-20220430141246802.png#alt=image-20220430141246802)
  20. ![](https://uuie-figure-bed.oss-cn-beijing.aliyuncs.com/img/markdownPic/image-20220430141251034.png#alt=image-20220430141251034)
  21. 内容
  22. application.properties 文件打开后的最后面,配置如下内容:
  23. ```properties
  24. spring.datasource.platform=mysql
  25. db.num=1
  26. db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
  27. db.user=root
  28. db.password=123456

SpringCloud-第二部分 - 图165

3、Linux服务器上nacos的集群配置cluster.conf

梳理出3台nacos集器的不同服务端口号

复制出cluster.conf

SpringCloud-第二部分 - 图166

SpringCloud-第二部分 - 图167

SpringCloud-第二部分 - 图168

内容

SpringCloud-第二部分 - 图169

这个IP不能写127.0.0.1,必须是Linux命令hostname -i能够识别的IP

SpringCloud-第二部分 - 图170

4、编辑Nacos的启动脚本startup.sh,使它能够接受不同的启动端口

/mynacos/nacos/bin 目录下有startup.sh

在什么地方,修改什么,怎么修改

思考

  1. /mynacos/nacos/bin 目录下有startup.sh
  2. 平时单机版的启动,都是./startup.sh即可。
  3. 但是
  4. 集群启动,我们希望可以类似其它软件的shell命令,传递不同的端口号启动不同的nacos实例。
  5. 命令:./startup.sh -p 3333 表示启动端口号为3333nacos服务器实例,和上一步的cluster.conf配置的一致。

修改内容

SpringCloud-第二部分 - 图171

修改前 修改后
SpringCloud-第二部分 - 图172 SpringCloud-第二部分 - 图173
SpringCloud-第二部分 - 图174 SpringCloud-第二部分 - 图175

执行方式

SpringCloud-第二部分 - 图176

5、Nginx的配置,由它作为负载均衡器

修改nginx的配置文件

SpringCloud-第二部分 - 图177

nginx.conf

  1. upstream cluster{
  2. server 127.0.0.1:3333;
  3. server 127.0.0.1:4444;
  4. server 127.0.0.1:5555;
  5. }
  1. server {
  2. listen 1111;
  3. server_name localhost;
  4. #charset koi8-r;
  5. #access_log logs/host.access.log main;
  6. location / {
  7. #root html;
  8. #index index.html index.htm;
  9. proxy_pass http://cluster;
  10. }
  11. .......省略

SpringCloud-第二部分 - 图178

按照指定启动

SpringCloud-第二部分 - 图179

6、截止到此处,1个Nginx+3个nacos注册中心+1个mysql

测试通过nginx访问nacos

http://192.168.111.144:1111/nacos/#/login

新建一个配置测试

SpringCloud-第二部分 - 图180

linux服务器的mysql插入一条记录

SpringCloud-第二部分 - 图181

测试

微服务cloudalibaba-provider-payment9002启动注册进nacos集群

yml

  1. server:
  2. port: 9002
  3. spring:
  4. application:
  5. name: nacos-payment-provider
  6. cloud:
  7. nacos:
  8. discovery:
  9. #配置Nacos地址
  10. #server-addr: localhost:8848
  11. # 换成nginx的1111端口,做集群
  12. server-addr: 192.168.111.144:1111
  13. management:
  14. endpoints:
  15. web:
  16. exposure:
  17. include: '*'

结果

SpringCloud-第二部分 - 图182

高可用小总

SpringCloud-第二部分 - 图183

第十九章、Sentinel

概述

官网

https://github.com/alibaba/Sentinel

中文 https://github.com/alibaba/Sentinel/wiki/介绍

是什么

SpringCloud-第二部分 - 图184

SpringCloud-第二部分 - 图185

去哪下

https://github.com/alibaba/Sentinel/releases

SpringCloud-第二部分 - 图186

能干嘛

SpringCloud-第二部分 - 图187

怎么玩

https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel

服务使用中的各种问题

服务雪崩

服务降级

服务熔断

服务限流

安装Sentinel控制台

sentinel组件由2部分构成

SpringCloud-第二部分 - 图188

后台\前台8080

安装步骤

下载https://github.com/alibaba/Sentinel/releases 下载到本地sentinel-dashboard-1.7.0.jar

SpringCloud-第二部分 - 图189

运行命令

前提 java8环境OK 8080端口不能被占用

命令 java -jar sentinel-dashboard-1.7.0.jar

SpringCloud-第二部分 - 图190

访问sentinel管理界面

http://localhost:8080

SpringCloud-第二部分 - 图191

登录账号密码均为sentinel

初始化演示工程

启动Nacos8848成功 http://localhost:8848/nacos/#/login

Module

cloudalibaba-sentinel-service8401

POM

  1. <dependencies>
  2. <!--SpringCloud ailibaba nacos -->
  3. <dependency>
  4. <groupId>com.alibaba.cloud</groupId>
  5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  6. </dependency>
  7. <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
  8. <dependency>
  9. <groupId>com.alibaba.csp</groupId>
  10. <artifactId>sentinel-datasource-nacos</artifactId>
  11. </dependency>
  12. <!--SpringCloud ailibaba sentinel -->
  13. <dependency>
  14. <groupId>com.alibaba.cloud</groupId>
  15. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
  16. </dependency>
  17. <!--openfeign-->
  18. <dependency>
  19. <groupId>org.springframework.cloud</groupId>
  20. <artifactId>spring-cloud-starter-openfeign</artifactId>
  21. </dependency>
  22. <!-- SpringBoot整合Web组件+actuator -->
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-web</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-actuator</artifactId>
  30. </dependency>
  31. <!--日常通用jar包配置-->
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-devtools</artifactId>
  35. <scope>runtime</scope>
  36. <optional>true</optional>
  37. </dependency>
  38. <dependency>
  39. <groupId>cn.hutool</groupId>
  40. <artifactId>hutool-all</artifactId>
  41. <version>4.6.3</version>
  42. </dependency>
  43. <dependency>
  44. <groupId>org.projectlombok</groupId>
  45. <artifactId>lombok</artifactId>
  46. <optional>true</optional>
  47. </dependency>
  48. <dependency>
  49. <groupId>org.springframework.boot</groupId>
  50. <artifactId>spring-boot-starter-test</artifactId>
  51. <scope>test</scope>
  52. </dependency>
  53. </dependencies>

YML

  1. server:
  2. port: 8401
  3. spring:
  4. application:
  5. name: cloudalibaba-sentinel-service
  6. cloud:
  7. nacos:
  8. discovery:
  9. #Nacos服务注册中心地址
  10. server-addr: localhost:8848
  11. sentinel:
  12. transport:
  13. #配置Sentinel dashboard地址
  14. dashboard: localhost:8080
  15. #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
  16. port: 8719
  17. management:
  18. endpoints:
  19. web:
  20. exposure:
  21. include: '*'

主启动

  1. @EnableDiscoveryClient
  2. @SpringBootApplication
  3. public class MainApp8401{
  4. public static void main(String[] args) {
  5. SpringApplication.run(MainApp8401.class, args);
  6. }
  7. }

业务类FlowLimitController

  1. @RestController
  2. public class FlowLimitController{
  3. @GetMapping("/testA")
  4. public String testA(){
  5. return "------testA";
  6. }
  7. @GetMapping("/testB")
  8. public String testB(){
  9. return "------testB";
  10. }
  11. }

启动Sentinel8080 java -jar sentinel-dashboard-1.7.0.jar

启动微服务8401

启动8401微服务后查看sentienl控制台

SpringCloud-第二部分 - 图192

Sentinel采用的懒加载说明

执行一次访问即可

http://localhost:8401/testA

http://localhost:8401/testB

SpringCloud-第二部分 - 图193

结论: sentinel8080正在监控微服务8401

流控规则

基本介绍

SpringCloud-第二部分 - 图194

SpringCloud-第二部分 - 图195

流控模式

直接(默认) 直接->快速失败 ——系统默认

配置及说明

表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误

SpringCloud-第二部分 - 图196

测试

快速点击访问http://localhost:8401/testA

结果 Blocked by Sentinel (flow limiting)

思考

直接调用默认报错信息,技术方面OK,但是是否应该有我们自己的后续处理?类似有个fallback的兜底方法?

关联

配置A

设置效果
当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名

SpringCloud-第二部分 - 图197

postman模拟并发密集访问testB

SpringCloud-第二部分 - 图198

访问testB成功

SpringCloud-第二部分 - 图199

postman里新建多线程集合组

SpringCloud-第二部分 - 图200

将访问地址添加进新新线程组

SpringCloud-第二部分 - 图201

Run

大批量线程高并发访问B,导致A失效了

运行后发现testA挂了 点击访问http://localhost:8401/testA

结果 Blocked by Sentinel (flow limiting)

链路

多个请求调用了同一个微服务

流控效果

直接->快速失败(默认的流控处理) 直接失败,抛出异常_Blocked by Sentinel (flow limiting)

源码 com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

预热

说明

公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值

官网

https://github.com/alibaba/Sentinel/wiki/流量控制

SpringCloud-第二部分 - 图202

默认coldFactor为3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。

限流 冷启动

https://github.com/alibaba/Sentinel/wiki/限流—-冷启动

源码

com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

SpringCloud-第二部分 - 图203

WarmUp配置

默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。

案例,阀值为10+预热时长设置5秒。
系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10

SpringCloud-第二部分 - 图204

多次点击http://localhost:8401/testB 刚开始不行,后续慢慢OK

应用场景

如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。

排队等待

匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。

设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。

SpringCloud-第二部分 - 图205

匀速排队,阈值必须设置为QPS

官网 https://github.com/alibaba/Sentinel/wiki/流量控制

SpringCloud-第二部分 - 图206

源码

com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

测试

SpringCloud-第二部分 - 图207

降级规则

官网 https://github.com/alibaba/Sentinel/wiki/熔断降级

基本介绍

SpringCloud-第二部分 - 图208

  1. RT(平均响应时间,秒级)
  2. 平均响应时间 超出阈值 在时间窗口内通过的请求>=5,两个条件同时满足后触发降级
  3. 窗口期过后关闭断路器
  4. RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
  5. 异常比列(秒级)
  6. QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
  7. 异常数(分钟级)
  8. 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

进一步说明

  1. Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
  2. 当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

Sentinel的断路器是没有半开状态的

半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考Hystrix

复习Hystrix

SpringCloud-第二部分 - 图209

降级策略实战

RT

是什么

SpringCloud-第二部分 - 图210

SpringCloud-第二部分 - 图211

测试

代码

  1. @GetMapping("/testD")
  2. public String testD(){
  3. //暂停几秒钟线程
  4. try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
  5. log.info("testD 测试RT");
  6. return "------testD";
  7. }

配置

SpringCloud-第二部分 - 图212

jmeter压测

SpringCloud-第二部分 - 图213

结论

SpringCloud-第二部分 - 图214

SpringCloud-第二部分 - 图215

按照上述配置,永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了

后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK

异常比例

是什么

SpringCloud-第二部分 - 图216

SpringCloud-第二部分 - 图217

测试

代码

  1. @GetMapping("/testD")
  2. public String testD(){
  3. log.info("testD 测试RT");
  4. int age = 10/0;
  5. return "------testD";
  6. }

配置

SpringCloud-第二部分 - 图218

jmeter

SpringCloud-第二部分 - 图219

结论

按照上述配置,单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;

SpringCloud-第二部分 - 图220

开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。
断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了。

异常数

是什么

SpringCloud-第二部分 - 图221

时间窗口一定要大于等于60秒。

SpringCloud-第二部分 - 图222

异常数是按照分钟统计的

测试

代码

  1. @GetMapping("/testE")
  2. public String testE(){
  3. log.info("testE 测试异常比例");
  4. int age = 10/0;
  5. return "------testE 测试异常比例";
  6. }

配置

SpringCloud-第二部分 - 图223

http://localhost:8401/testE,第一次访问绝对报错,因为除数不能为零,我们看到error窗口,但是达到5次报错后,进入熔断后降级。

jmeter

SpringCloud-第二部分 - 图224

热点key限流

基本介绍

是什么

何为热点
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作

SpringCloud-第二部分 - 图225

官网 https://github.com/alibaba/Sentinel/wiki/热点参数限流

承上启下复习start @SentinelResource

  1. 兜底方法
  2. 分为系统默认和客户自定义,两种
  3. 之前的case,限流出问题后,都是用sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
  4. 我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
  5. 结论
  6. HystrixCommand @SentinelResource

代码 com.alibaba.csp.sentinel.slots.block.BlockException

  1. @GetMapping("/testHotKey")
  2. @SentinelResource(value = "testHotKey",blockHandler = "dealHandler_testHotKey")
  3. public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
  4. @RequestParam(value = "p2",required = false) String p2){
  5. return "------testHotKey";
  6. }
  7. public String dealHandler_testHotKey(String p1,String p2,BlockException exception){
  8. return "-----dealHandler_testHotKey";
  9. }

sentinel系统默认的提示:Blocked by Sentinel (flow limiting)

配置

SpringCloud-第二部分 - 图226

  1. 限流模式只支持QPS模式,固定写死了。(这才叫热点)
  2. @SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。
  3. 上面的抓图就是第一个参数有值的话,1秒的QPS1,超过就限流,限流后调用dealHandler_testHotKey支持方法。

SpringCloud-第二部分 - 图227

1

@SentinelResource(value = “testHotKey”)

异常打到了前台用户界面看到,不友好

2

@SentinelResource(value = “testHotKey”,blockHandler = “dealHandler_testHotKey”)

方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理

用了我们自己定义的

测试

error: http://localhost:8401/testHotKey?p1=abc

error: http://localhost:8401/testHotKey?p1=abc&p2=33

success: http://localhost:8401/testHotKey?p2=abc

参数例外项

上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流

特例情况

普通 超过1秒钟一个后,达到阈值1后马上被限流

我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样

特例 假如当p1的值等于5时,它的阈值可以达到200

配置

SpringCloud-第二部分 - 图228

SpringCloud-第二部分 - 图229

点击添加

测试

http://localhost:8401/testHotKey?p1=5

http://localhost:8401/testHotKey?p1=3

当p1等于5的时候,阈值变为200

当p1不等于5的时候,阈值就是平常的1

前提条件 热点参数的注意点,参数必须是基本类型或者String

@SentinelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;

RuntimeException
int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管

总结
@SentinelResource主管配置出错,运行出错该走异常走异常

系统规则

是什么

https://github.com/alibaba/Sentinel/wiki/系统自适应限流

各项配置参数说明

SpringCloud-第二部分 - 图230

配置全局QPS

@SentinelResource

按资源名称限流+后续处理

启动Nacos成功 http://localhost:8848/nacos/#/login

启动Sentinel成功 java -jar sentinel-dashboard-1.7.0.jar

Module

cloudalibaba-sentinel-service8401

POM

  1. <dependencies>
  2. <!--SpringCloud ailibaba nacos -->
  3. <dependency>
  4. <groupId>com.alibaba.cloud</groupId>
  5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  6. </dependency>
  7. <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  8. <groupId>com.atguigu.springcloud</groupId>
  9. <artifactId>cloud-api-commons</artifactId>
  10. <version>${project.version}</version>
  11. </dependency>
  12. <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
  13. <dependency>
  14. <groupId>com.alibaba.csp</groupId>
  15. <artifactId>sentinel-datasource-nacos</artifactId>
  16. </dependency>
  17. <!--SpringCloud ailibaba sentinel -->
  18. <dependency>
  19. <groupId>com.alibaba.cloud</groupId>
  20. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
  21. </dependency>
  22. <!--openfeign-->
  23. <dependency>
  24. <groupId>org.springframework.cloud</groupId>
  25. <artifactId>spring-cloud-starter-openfeign</artifactId>
  26. </dependency>
  27. <!-- SpringBoot整合Web组件+actuator -->
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-web</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-actuator</artifactId>
  35. </dependency>
  36. <!--日常通用jar包配置-->
  37. <dependency>
  38. <groupId>org.springframework.boot</groupId>
  39. <artifactId>spring-boot-devtools</artifactId>
  40. <scope>runtime</scope>
  41. <optional>true</optional>
  42. </dependency>
  43. <dependency>
  44. <groupId>cn.hutool</groupId>
  45. <artifactId>hutool-all</artifactId>
  46. <version>4.6.3</version>
  47. </dependency>
  48. <dependency>
  49. <groupId>org.projectlombok</groupId>
  50. <artifactId>lombok</artifactId>
  51. <optional>true</optional>
  52. </dependency>
  53. <dependency>
  54. <groupId>org.springframework.boot</groupId>
  55. <artifactId>spring-boot-starter-test</artifactId>
  56. <scope>test</scope>
  57. </dependency>
  58. </dependencies>

YML

  1. server:
  2. port: 8401
  3. spring:
  4. application:
  5. name: cloudalibaba-sentinel-service
  6. cloud:
  7. nacos:
  8. discovery:
  9. server-addr: localhost:8848 #Nacos服务注册中心地址
  10. sentinel:
  11. transport:
  12. dashboard: localhost:8080 #配置Sentinel dashboard地址
  13. port: 8719
  14. management:
  15. endpoints:
  16. web:
  17. exposure:
  18. include: '*'

业务类RateLimitController

  1. @RestController
  2. public class RateLimitController{
  3. @GetMapping("/byResource")
  4. @SentinelResource(value = "byResource",blockHandler = "handleException")
  5. public CommonResult byResource(){
  6. return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
  7. }
  8. public CommonResult handleException(BlockException exception){
  9. return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
  10. }
  11. }

主启动

  1. @EnableDiscoveryClient
  2. @SpringBootApplication
  3. public class MainApp8401{
  4. public static void main(String[] args) {
  5. SpringApplication.run(MainApp8401.class, args);
  6. }
  7. }

配置流控规则

配置步骤

SpringCloud-第二部分 - 图231

图形配置和代码关系

SpringCloud-第二部分 - 图232

表示1秒钟内查询次数大于1,就跑到我们自定义的处流,限流

测试

1秒钟点击1下,OK

超过上述,疯狂点击,返回了自己定义的限流处理信息,限流发生

SpringCloud-第二部分 - 图233

额外问题

此时关闭问服务8401看看

Sentinel控制台,流控规则消失了?????临时/持久?

按照Url地址限流+后续处理

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息

业务类RateLimitController

  1. @RestController
  2. public class RateLimitController{
  3. @GetMapping("/byResource")
  4. @SentinelResource(value = "byResource",blockHandler = "handleException")
  5. public CommonResult byResource(){
  6. return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
  7. }
  8. public CommonResult handleException(BlockException exception){
  9. return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
  10. }
  11. @GetMapping("/rateLimit/byUrl")
  12. @SentinelResource(value = "byUrl")
  13. public CommonResult byUrl(){
  14. return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
  15. }
  16. }

访问一次 http://localhost:8401/rateLimit/byUrl

Sentinel控制台配置

SpringCloud-第二部分 - 图234

测试 疯狂点击http://localhost:8401/rateLimit/byUrl

结果 会返回Sentinel自带的限流处理结果

SpringCloud-第二部分 - 图235

上面兜底方案面临的问题

  1. 1 系统默认的,没有体现我们自己的业务要求。
  2. 2 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
  3. 3 每个业务方法都添加一个兜底的,那代码膨胀加剧。
  4. 4 全局统一的处理方法没有体现。

客户自定义限流处理逻辑

创建CustomerBlockHandler类用于自定义限流处理逻辑

自定义限流处理类 CustomerBlockHandler

  1. public class CustomerBlockHandler{
  2. public static CommonResult handleException(BlockException exception){
  3. return new CommonResult(2020,"自定义的限流处理信息......CustomerBlockHandler");
  4. }
  5. }

SpringCloud-第二部分 - 图236

RateLimitController

  1. @RestController
  2. public class RateLimitController{
  3. @GetMapping("/byResource")
  4. @SentinelResource(value = "byResource",blockHandler = "handleException")
  5. public CommonResult byResource(){
  6. return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
  7. }
  8. public CommonResult handleException(BlockException exception){
  9. return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
  10. }
  11. @GetMapping("/rateLimit/byUrl")
  12. @SentinelResource(value = "byUrl")
  13. public CommonResult byUrl(){
  14. return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
  15. }
  16. /**
  17. * 自定义通用的限流处理逻辑,
  18. blockHandlerClass = CustomerBlockHandler.class
  19. blockHandler = handleException2
  20. 上述配置:找CustomerBlockHandler类里的handleException2方法进行兜底处理
  21. */
  22. /**
  23. * 自定义通用的限流处理逻辑
  24. */
  25. @GetMapping("/rateLimit/customerBlockHandler")
  26. @SentinelResource(value = "customerBlockHandler",
  27. blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException2")
  28. public CommonResult customerBlockHandler(){
  29. return new CommonResult(200,"按客户自定义限流处理逻辑");
  30. }
  31. }

启动微服务后先调用一次 http://localhost:8401/rateLimit/customerBlockHandler

Sentinel控制台配置

SpringCloud-第二部分 - 图237

测试后我们自定义的出来了

进一步说明

SpringCloud-第二部分 - 图238

更多注解属性说明

SpringCloud-第二部分 - 图239

所有的代码都要用try-catch-finally方式进行处理

Sentinel主要有三个核心Api

SphU定义资源

Tracer定义统计

ContextUtil定义了上下文

服务熔断功能

sentinel整合ribbon+openFeign+fallback

启动nacos和sentinel

提供者9003/9004

新建cloudalibaba-provider-payment9003/9004两个一样的做法

POM

  1. <dependencies>
  2. <!--SpringCloud ailibaba nacos -->
  3. <dependency>
  4. <groupId>com.alibaba.cloud</groupId>
  5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  6. </dependency>
  7. <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  8. <groupId>com.atguigu.springcloud</groupId>
  9. <artifactId>cloud-api-commons</artifactId>
  10. <version>${project.version}</version>
  11. </dependency>
  12. <!-- SpringBoot整合Web组件 -->
  13. <dependency>
  14. <groupId>org.springframework.boot</groupId>
  15. <artifactId>spring-boot-starter-web</artifactId>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-actuator</artifactId>
  20. </dependency>
  21. <!--日常通用jar包配置-->
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-devtools</artifactId>
  25. <scope>runtime</scope>
  26. <optional>true</optional>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.projectlombok</groupId>
  30. <artifactId>lombok</artifactId>
  31. <optional>true</optional>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.springframework.boot</groupId>
  35. <artifactId>spring-boot-starter-test</artifactId>
  36. <scope>test</scope>
  37. </dependency>
  38. </dependencies>

YML

  1. server:
  2. port: 9003
  3. spring:
  4. application:
  5. name: nacos-payment-provider
  6. cloud:
  7. nacos:
  8. discovery:
  9. server-addr: localhost:8848 #配置Nacos地址
  10. management:
  11. endpoints:
  12. web:
  13. exposure:
  14. include: '*'

记得修改不同的端口号

主启动

  1. @SpringBootApplication
  2. @EnableDiscoveryClient
  3. public class PaymentMain9003{
  4. public static void main(String[] args) {
  5. SpringApplication.run(PaymentMain9003.class, args);
  6. }
  7. }

业务类

  1. @RestController
  2. public class PaymentController{
  3. @Value("${server.port}")
  4. private String serverPort;
  5. public static HashMap<Long,Payment> hashMap = new HashMap<>();
  6. static{
  7. hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
  8. hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
  9. hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
  10. }
  11. @GetMapping(value = "/paymentSQL/{id}")
  12. public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
  13. Payment payment = hashMap.get(id);
  14. CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort: "+serverPort,payment);
  15. return result;
  16. }
  17. }

测试地址 http://localhost:9003/paymentSQL/1

消费者84

新建cloudalibaba-consumer-nacos-order84

POM

  1. <dependencies>
  2. <!--SpringCloud ailibaba nacos -->
  3. <dependency>
  4. <groupId>com.alibaba.cloud</groupId>
  5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  6. </dependency>
  7. <!--SpringCloud ailibaba sentinel -->
  8. <dependency>
  9. <groupId>com.alibaba.cloud</groupId>
  10. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
  11. </dependency>
  12. <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  13. <dependency>
  14. <groupId>com.atguigu.springcloud</groupId>
  15. <artifactId>cloud-api-commons</artifactId>
  16. <version>${project.version}</version>
  17. </dependency>
  18. <!-- SpringBoot整合Web组件 -->
  19. <dependency>
  20. <groupId>org.springframework.boot</groupId>
  21. <artifactId>spring-boot-starter-web</artifactId>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-actuator</artifactId>
  26. </dependency>
  27. <!--日常通用jar包配置-->
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-devtools</artifactId>
  31. <scope>runtime</scope>
  32. <optional>true</optional>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.projectlombok</groupId>
  36. <artifactId>lombok</artifactId>
  37. <optional>true</optional>
  38. </dependency>
  39. <dependency>
  40. <groupId>org.springframework.boot</groupId>
  41. <artifactId>spring-boot-starter-test</artifactId>
  42. <scope>test</scope>
  43. </dependency>
  44. </dependencies>

YML

  1. server:
  2. port: 84
  3. spring:
  4. application:
  5. name: nacos-order-consumer
  6. cloud:
  7. nacos:
  8. discovery:
  9. server-addr: localhost:8848
  10. sentinel:
  11. transport:
  12. #配置Sentinel dashboard地址
  13. dashboard: localhost:8080
  14. #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
  15. port: 8719
  16. #消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
  17. service-url:
  18. nacos-user-service: http://nacos-payment-provider

主启动

  1. @EnableDiscoveryClient
  2. @SpringBootApplication
  3. public class OrderNacosMain84{
  4. public static void main(String[] args) {
  5. SpringApplication.run(OrderNacosMain84.class, args);
  6. }
  7. }

业务类

ApplicationContextConfig

  1. @Configuration
  2. public class ApplicationContextConfig{
  3. @Bean
  4. @LoadBalanced
  5. public RestTemplate getRestTemplate(){
  6. return new RestTemplate();
  7. }
  8. }

CircleBreakerController

修改后请重启微服务

热部署对java代码级生效及时

对@SentinelResource注解内属性,有时效果不好

目的

fallback管运行异常

blockHandler管配置违规

测试地址 http://localhost:84/consumer/fallback/1

没有任何配置

  1. @RestController
  2. @Slf4j
  3. public class CircleBreakerController{
  4. public static final String SERVICE_URL = "http://nacos-payment-provider";
  5. @Resource
  6. private RestTemplate restTemplate;
  7. @RequestMapping("/consumer/fallback/{id}")
  8. @SentinelResource(value = "fallback")
  9. public CommonResult<Payment> fallback(@PathVariable Long id){
  10. CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
  11. if (id == 4) {
  12. throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
  13. }else if (result.getData() == null) {
  14. throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
  15. }
  16. return result;
  17. }
  18. }

给客户error页面,不友好

只配置fallback

编码

  1. @RestController
  2. @Slf4j
  3. public class CircleBreakerController{
  4. public static final String SERVICE_URL = "http://nacos-payment-provider";
  5. @Resource
  6. private RestTemplate restTemplate;
  7. @RequestMapping("/consumer/fallback/{id}")
  8. @SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback负责业务异常
  9. public CommonResult<Payment> fallback(@PathVariable Long id){
  10. CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
  11. if (id == 4) {
  12. throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
  13. }else if (result.getData() == null) {
  14. throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
  15. }
  16. return result;
  17. }
  18. public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
  19. Payment payment = new Payment(id,"null");
  20. return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
  21. }
  22. }

图说

SpringCloud-第二部分 - 图240

本例sentinel无配置

结果

SpringCloud-第二部分 - 图241

SpringCloud-第二部分 - 图242

SpringCloud-第二部分 - 图243

只配置blockHandler

  1. @RestController
  2. @Slf4j
  3. public class CircleBreakerController{
  4. public static final String SERVICE_URL = "http://nacos-payment-provider";
  5. @Resource
  6. private RestTemplate restTemplate;
  7. @RequestMapping("/consumer/fallback/{id}")
  8. @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler负责在sentinel里面配置的降级限流
  9. public CommonResult<Payment> fallback(@PathVariable Long id){
  10. CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
  11. if (id == 4) {
  12. throw new IllegalArgumentException ("非法参数异常....");
  13. }else if (result.getData() == null) {
  14. throw new NullPointerException ("NullPointerException,该ID没有对应记录");
  15. }
  16. return result;
  17. }
  18. public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
  19. Payment payment = new Payment(id,"null");
  20. return new CommonResult<>(444,"fallback,无此流水,exception "+e.getMessage(),payment);
  21. }
  22. public CommonResult blockHandler(@PathVariable Long id,BlockException blockException) {
  23. Payment payment = new Payment(id,"null");
  24. return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
  25. }
  26. }

SpringCloud-第二部分 - 图244

本例sentinel需配置

SpringCloud-第二部分 - 图245

异常超过2次后,断路器打开,断电跳闸,系统被保护

结果

SpringCloud-第二部分 - 图246

fallback和blockHandler都配置

  1. @RestController
  2. @Slf4j
  3. public class CircleBreakerController{
  4. public static final String SERVICE_URL = "http://nacos-payment-provider";
  5. @Resource
  6. private RestTemplate restTemplate;
  7. @RequestMapping("/consumer/fallback/{id}")
  8. @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler")
  9. public CommonResult<Payment> fallback(@PathVariable Long id){
  10. CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
  11. if (id == 4) {
  12. throw new IllegalArgumentException ("非法参数异常....");
  13. }else if (result.getData() == null) {
  14. throw new NullPointerException ("NullPointerException,该ID没有对应记录");
  15. }
  16. return result;
  17. }
  18. public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
  19. Payment payment = new Payment(id,"null");
  20. return new CommonResult<>(444,"fallback,无此流水,exception "+e.getMessage(),payment);
  21. }
  22. public CommonResult blockHandler(@PathVariable Long id,BlockException blockException) {
  23. Payment payment = new Payment(id,"null");
  24. return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
  25. }
  26. }

SpringCloud-第二部分 - 图247

本例sentinel需配置

SpringCloud-第二部分 - 图248

结果

SpringCloud-第二部分 - 图249

若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。

忽略属性…….

  1. @RestController
  2. @Slf4j
  3. public class CircleBreakerController{
  4. public static final String SERVICE_URL = "http://nacos-payment-provider";
  5. @Resource
  6. private RestTemplate restTemplate;
  7. @RequestMapping("/consumer/fallback/{id}")
  8. @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler",
  9. exceptionsToIgnore = {IllegalArgumentException.class})
  10. public CommonResult<Payment> fallback(@PathVariable Long id){
  11. CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
  12. if (id == 4) {
  13. throw new IllegalArgumentException ("非法参数异常....");
  14. }else if (result.getData() == null) {
  15. throw new NullPointerException ("NullPointerException,该ID没有对应记录");
  16. }
  17. return result;
  18. }
  19. public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
  20. Payment payment = new Payment(id,"null");
  21. return new CommonResult<>(444,"fallback,无此流水,exception "+e.getMessage(),payment);
  22. }
  23. public CommonResult blockHandler(@PathVariable Long id,BlockException blockException) {
  24. Payment payment = new Payment(id,"null");
  25. return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
  26. }
  27. }

SpringCloud-第二部分 - 图250

本例sentinel无配置

结果

SpringCloud-第二部分 - 图251

程序异常打到前台了,对用户不友好

Feign系列

修改84模块

84消费者调用提供者9003

Feign组件一般是消费侧

POM

  1. <dependencies>
  2. <!--SpringCloud ailibaba nacos -->
  3. <dependency>
  4. <groupId>com.alibaba.cloud</groupId>
  5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  6. </dependency>
  7. <!--SpringCloud ailibaba sentinel -->
  8. <dependency>
  9. <groupId>com.alibaba.cloud</groupId>
  10. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
  11. </dependency>
  12. <!--SpringCloud openfeign -->
  13. <dependency>
  14. <groupId>org.springframework.cloud</groupId>
  15. <artifactId>spring-cloud-starter-openfeign</artifactId>
  16. </dependency>
  17. <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  18. <dependency>
  19. <groupId>com.atguigu.springcloud</groupId>
  20. <artifactId>cloud-api-commons</artifactId>
  21. <version>${project.version}</version>
  22. </dependency>
  23. <!-- SpringBoot整合Web组件 -->
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-web</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-actuator</artifactId>
  31. </dependency>
  32. <!--日常通用jar包配置-->
  33. <dependency>
  34. <groupId>org.springframework.boot</groupId>
  35. <artifactId>spring-boot-devtools</artifactId>
  36. <scope>runtime</scope>
  37. <optional>true</optional>
  38. </dependency>
  39. <dependency>
  40. <groupId>org.projectlombok</groupId>
  41. <artifactId>lombok</artifactId>
  42. <optional>true</optional>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.springframework.boot</groupId>
  46. <artifactId>spring-boot-starter-test</artifactId>
  47. <scope>test</scope>
  48. </dependency>
  49. </dependencies>

YML

  1. server:
  2. port: 84
  3. spring:
  4. application:
  5. name: nacos-order-consumer
  6. cloud:
  7. nacos:
  8. discovery:
  9. #Nacos服务注册中心地址
  10. server-addr: localhost:8848
  11. sentinel:
  12. transport:
  13. #配置Sentinel dashboard地址
  14. dashboard: localhost:8080
  15. #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
  16. port: 8719
  17. management:
  18. endpoints:
  19. web:
  20. exposure:
  21. include: '*'
  22. # 激活Sentinel对Feign的支持
  23. feign:
  24. sentinel:
  25. enabled: true

激活Sentinel对Feign的支持

业务类

带@FeignClient注解的业务接口

  1. @FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)//调用中关闭9003服务提供者
  2. public interface PaymentService{
  3. @GetMapping(value = "/paymentSQL/{id}")
  4. public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
  5. }

fallback = PaymentFallbackService.class

  1. @Component
  2. public class PaymentFallbackService implements PaymentService{
  3. @Override
  4. public CommonResult<Payment> paymentSQL(Long id){
  5. return new CommonResult<>(444,"服务降级返回,没有该流水信息",new Payment(id, "errorSerial......"));
  6. }
  7. }

Controller

  1. @RestController
  2. @Slf4j
  3. public class CircleBreakerController{
  4. public static final String SERVICE_URL = "http://nacos-payment-provider";
  5. @Resource
  6. private RestTemplate restTemplate;
  7. @RequestMapping("/consumer/fallback/{id}")
  8. //@SentinelResource(value = "fallback") //没有配置
  9. //@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
  10. //@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
  11. @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",
  12. exceptionsToIgnore = {IllegalArgumentException.class})
  13. public CommonResult<Payment> fallback(@PathVariable Long id){
  14. CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);
  15. if (id == 4) {
  16. throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
  17. }else if (result.getData() == null) {
  18. throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
  19. }
  20. return result;
  21. }
  22. //本例是fallback
  23. public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
  24. Payment payment = new Payment(id,"null");
  25. return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
  26. }
  27. //本例是blockHandler
  28. public CommonResult blockHandler(@PathVariable Long id,BlockException blockException) {
  29. Payment payment = new Payment(id,"null");
  30. return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
  31. }
  32. //==================OpenFeign
  33. @Resource
  34. private PaymentService paymentService;
  35. @GetMapping(value = "/consumer/openfeign/{id}")
  36. public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
  37. if(id == 4){
  38. throw new RuntimeException("没有该id");
  39. }
  40. return paymentService.paymentSQL(id);
  41. }
  42. }

主启动

  1. @EnableDiscoveryClient
  2. @SpringBootApplication
  3. @EnableFeignClients
  4. public class OrderNacosMain84{
  5. public static void main(String[] args) {
  6. SpringApplication.run(OrderNacosMain84.class, args);
  7. }
  8. }

添加@EnableFeignClients启动Feign的功能

http://localhost:84/consumer/paymentSQL/1

测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级,不会被耗死

熔断框架比较

SpringCloud-第二部分 - 图252

规则持久化

是什么

一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化

怎么玩

将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效

步骤

修改cloudalibaba-sentinel-service8401

POM

  1. <!--SpringCloud ailibaba sentinel-datasource-nacos -->
  2. <dependency>
  3. <groupId>com.alibaba.csp</groupId>
  4. <artifactId>sentinel-datasource-nacos</artifactId>
  5. </dependency>

YML

  1. server:
  2. port: 8401
  3. spring:
  4. application:
  5. name: cloudalibaba-sentinel-service
  6. cloud:
  7. nacos:
  8. discovery:
  9. server-addr: localhost:8848 #Nacos服务注册中心地址
  10. sentinel:
  11. transport:
  12. dashboard: localhost:8080 #配置Sentinel dashboard地址
  13. port: 8719
  14. datasource:
  15. ds1:
  16. nacos:
  17. server-addr: localhost:8848
  18. dataId: cloudalibaba-sentinel-service
  19. groupId: DEFAULT_GROUP
  20. data-type: json
  21. rule-type: flow
  22. management:
  23. endpoints:
  24. web:
  25. exposure:
  26. include: '*'
  27. feign:
  28. sentinel:
  29. enabled: true # 激活Sentinel对Feign的支持

添加Nacos数据源配置

  1. spring:
  2. cloud:
  3. sentinel:
  4. datasource:
  5. ds1:
  6. nacos:
  7. server-addr: localhost:8848
  8. dataId: ${spring.application.name}
  9. groupId: DEFAULT_GROUP
  10. data-type: json
  11. rule-type: flow

添加Nacos业务规则配置

SpringCloud-第二部分 - 图253

内容解析

  1. [
  2. {
  3. "resource": "/rateLimit/byUrl",
  4. "limitApp": "default",
  5. "grade": 1,
  6. "count": 1,
  7. "strategy": 0,
  8. "controlBehavior": 0,
  9. "clusterMode": false
  10. }
  11. ]
  1. resource:资源名称;
  2. limitApp:来源应用;
  3. grade:阈值类型,0表示线程数,1表示QPS
  4. count:单机阈值;
  5. strategy:流控模式,0表示直接,1表示关联,2表示链路;
  6. controlBehavior:流控效果,0表示快速失败,1表示Warm Up2表示排队等待;
  7. clusterMode:是否集群。

启动8401后刷新sentinel发现业务规则有了

SpringCloud-第二部分 - 图254

快速访问测试接口 http://localhost:8401/rateLimit/byUrl

默认SpringCloud-第二部分 - 图255

停止8401再看sentinel

SpringCloud-第二部分 - 图256

重新启动8401再看sentinel

乍一看还是没有,稍等一会儿

多次调用 http://localhost:8401/rateLimit/byUrl

重新配置出现了,持久化验证通过

第二十章、Seata处理分布式事务

分布式事务问题

分布式前,单机单库没这个问题。从1:1 -> 1:N -> N:N

分布式之后

  1. 单体应用被拆分成微服务应用,原来的三个模块被拆分成三个独立的应用,分别使用三个独立的数据源,业务操作需要调用三个服务来完成。此时每个服务内部的数据一致性由本地事务来保证,但是全局的数据一致性问题没法保证。

SpringCloud-第二部分 - 图257

一次业务操作需要跨多个数据源或需要跨多个系统进行远程调用,就会产生分布式事务问题

Seata简介

是什么

Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

官网地址 http://seata.io/zh-cn/

能干嘛

一个典型的分布式事务过程

分布式事务处理过程的一ID+三组件模型

Transaction ID XID ——> 全局唯一的事务ID

3组件概念

  1. Transaction Coordinator (TC)
  2. 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚;
  3. Transaction Manager (TM)
  4. 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议;
  5. Resource Manager (RM)
  6. 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚

处理过程

  1. 1TM TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID
  2. 2XID 在微服务调用链路的上下文中传播;
  3. 3RM TC 注册分支事务,将其纳入 XID 对应全局事务的管辖;
  4. 4TM TC 发起针对 XID 的全局提交或回滚决议;
  5. 5TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。

SpringCloud-第二部分 - 图258

去哪下

发布说明: https://github.com/seata/seata/releases

怎么玩

本地@Transactional

全局@GlobalTransactional

SEATA 的分布式交易解决方案

SpringCloud-第二部分 - 图259

Seata-Server安装

1、官网地址 http://seata.io/zh-cn/

2、下载版本

https://github.com/seata/seata/releases

下载的是seata-server-0.9.0.zip

本次截止2020.2月份后续是否升级自己决定

seata-server-0.9.0.zip解压到指定目录并修改conf目录下的file.conf配置文件

先备份原始file.conf文件

3、主要修改:自定义事务组名称+事务日志存储模式为db+数据库连接信息

file.conf

service模块

  1. service {
  2. vgroup_mapping.my_test_tx_group = "fsp_tx_group"
  3. default.grouplist = "127.0.0.1:8091"
  4. enableDegrade = false
  5. disable = false
  6. max.commit.retry.timeout = "-1"
  7. max.rollback.retry.timeout = "-1"
  8. }

store模块

  1. ## transaction log store
  2. store {
  3. ## store mode: file、db
  4. mode = "db"
  5. ## file store
  6. file {
  7. dir = "sessionStore"
  8. # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
  9. max-branch-session-size = 16384
  10. # globe session size , if exceeded throws exceptions
  11. max-global-session-size = 512
  12. # file buffer size , if exceeded allocate new buffer
  13. file-write-buffer-cache-size = 16384
  14. # when recover batch read size
  15. session.reload.read_size = 100
  16. # async, sync
  17. flush-disk-mode = async
  18. }
  19. ## database store
  20. db {
  21. ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
  22. datasource = "dbcp"
  23. ## mysql/oracle/h2/oceanbase etc.
  24. db-type = "mysql"
  25. driver-class-name = "com.mysql.jdbc.Driver"
  26. url = "jdbc:mysql://127.0.0.1:3306/seata"
  27. user = "root"
  28. password = "你自己密码"
  29. min-conn = 1
  30. max-conn = 3
  31. global.table = "global_table"
  32. branch.table = "branch_table"
  33. lock-table = "lock_table"
  34. query-limit = 100
  35. }
  36. }

4、mysql5.7数据库新建库seata

5、在seata库里建表

建表db_store.sql在\seata-server-0.9.0\seata\conf目录里面 db_store.sql

SQL

  1. -- the table to store GlobalSession data
  2. drop table if exists `global_table`;
  3. create table `global_table` (
  4. `xid` varchar(128) not null,
  5. `transaction_id` bigint,
  6. `status` tinyint not null,
  7. `application_id` varchar(32),
  8. `transaction_service_group` varchar(32),
  9. `transaction_name` varchar(128),
  10. `timeout` int,
  11. `begin_time` bigint,
  12. `application_data` varchar(2000),
  13. `gmt_create` datetime,
  14. `gmt_modified` datetime,
  15. primary key (`xid`),
  16. key `idx_gmt_modified_status` (`gmt_modified`, `status`),
  17. key `idx_transaction_id` (`transaction_id`)
  18. );
  19. -- the table to store BranchSession data
  20. drop table if exists `branch_table`;
  21. create table `branch_table` (
  22. `branch_id` bigint not null,
  23. `xid` varchar(128) not null,
  24. `transaction_id` bigint ,
  25. `resource_group_id` varchar(32),
  26. `resource_id` varchar(256) ,
  27. `lock_key` varchar(128) ,
  28. `branch_type` varchar(8) ,
  29. `status` tinyint,
  30. `client_id` varchar(64),
  31. `application_data` varchar(2000),
  32. `gmt_create` datetime,
  33. `gmt_modified` datetime,
  34. primary key (`branch_id`),
  35. key `idx_xid` (`xid`)
  36. );
  37. -- the table to store lock data
  38. drop table if exists `lock_table`;
  39. create table `lock_table` (
  40. `row_key` varchar(128) not null,
  41. `xid` varchar(96),
  42. `transaction_id` long ,
  43. `branch_id` long,
  44. `resource_id` varchar(256) ,
  45. `table_name` varchar(32) ,
  46. `pk` varchar(36) ,
  47. `gmt_create` datetime ,
  48. `gmt_modified` datetime,
  49. primary key(`row_key`)
  50. );

SpringCloud-第二部分 - 图260

6、修改seata-server-0.9.0\seata\conf目录下的registry.conf配置文件

  1. registry {
  2. # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  3. type = "nacos"
  4. nacos {
  5. serverAddr = "localhost:8848"
  6. namespace = ""
  7. cluster = "default"
  8. }

目的是:指明注册中心为nacos,及修改nacos连接信息

7、先启动Nacos端口号8848

softs\nacos-server-1.1.4\nacos\bin

8、再启动seata-server

softs\seata-server-0.9.0\seata\bin seata-server.bat

订单/库存/账户业务数据库准备

以下演示都需要先启动Nacos后启动Seata,保证两个都OK,Seata没启动报错no available server to connect

分布式事务业务说明

业务说明

``` 这里我们会创建三个服务,一个订单服务,一个库存服务,一个账户服务。

当用户下单时,会在订单服务中创建一个订单,然后通过远程调用库存服务来扣减下单商品的库存, 再通过远程调用账户服务来扣减用户账户里面的余额, 最后在订单服务中修改订单状态为已完成。

该操作跨越三个数据库,有两次远程调用,很明显会有分布式事务问题。

  1. > 下订单--->扣库存--->减账户(余额)
  2. > 创建业务数据库
  3. > seata_order:存储订单的数据库;
  4. > seata_storage:存储库存的数据库;
  5. > seata_account:存储账户信息的数据库。
  6. > 建库SQL
  7. > ```sql
  8. CREATE DATABASE seata_order;
  9. CREATE DATABASE seata_storage;
  10. CREATE DATABASE seata_account;

按照上述3库分别建对应业务表

seata_order库下建t_order表

``sql CREATE TABLE t_order (idBIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,user_idBIGINT(11) DEFAULT NULL COMMENT '用户id',product_idBIGINT(11) DEFAULT NULL COMMENT '产品id',countINT(11) DEFAULT NULL COMMENT '数量',moneyDECIMAL(11,0) DEFAULT NULL COMMENT '金额',status` INT(1) DEFAULT NULL COMMENT ‘订单状态:0:创建中;1:已完结’ ) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

SELECT * FROM t_order;

  1. > seata_storage库下建t_storage
  2. > ```sql
  3. CREATE TABLE t_storage (
  4. `id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  5. `product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
  6. `total` INT(11) DEFAULT NULL COMMENT '总库存',
  7. `used` INT(11) DEFAULT NULL COMMENT '已用库存',
  8. `residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
  9. ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
  10. INSERT INTO seata_storage.t_storage(`id`, `product_id`, `total`, `used`, `residue`)
  11. VALUES ('1', '1', '100', '0', '100');
  12. SELECT * FROM t_storage;

seata_account库下建t_account 表

``sql CREATE TABLE t_account (idBIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',user_idBIGINT(11) DEFAULT NULL COMMENT '用户id',totalDECIMAL(10,0) DEFAULT NULL COMMENT '总额度',usedDECIMAL(10,0) DEFAULT NULL COMMENT '已用余额',residue` DECIMAL(10,0) DEFAULT ‘0’ COMMENT ‘剩余可用额度’ ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

INSERT INTO seata_account.t_account(id, user_id, total, used, residue) VALUES (‘1’, ‘1’, ‘1000’, ‘0’, ‘1000’);

SELECT * FROM t_account;

  1. > 按照上述3库分别建对应的回滚日志表
  2. >

订单-库存-账户3个库下都需要建各自的回滚日志表 \seata-server-0.9.0\seata\conf目录下的db_undo_log.sql

  1. > 建表SQL
  2. > ```sql
  3. -- the table to store seata xid data
  4. -- 0.7.0+ add context
  5. -- you must to init this sql for you business databese. the seata server not need it.
  6. -- 此脚本必须初始化在你当前的业务数据库中,用于AT 模式XID记录。与server端无关(注:业务数据库)
  7. -- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
  8. DROP TABLE `undo_log`;
  9. CREATE TABLE `undo_log` (
  10. `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  11. `branch_id` BIGINT(20) NOT NULL,
  12. `xid` VARCHAR(100) NOT NULL,
  13. `context` VARCHAR(128) NOT NULL,
  14. `rollback_info` LONGBLOB NOT NULL,
  15. `log_status` INT(11) NOT NULL,
  16. `log_created` DATETIME NOT NULL,
  17. `log_modified` DATETIME NOT NULL,
  18. `ext` VARCHAR(100) DEFAULT NULL,
  19. PRIMARY KEY (`id`),
  20. UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
  21. ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

最终效果

SpringCloud-第二部分 - 图261

订单/库存/账户业务微服务准备

seata-storage-service2002

POM

  1. <dependencies>
  2. <!--nacos-->
  3. <dependency>
  4. <groupId>com.alibaba.cloud</groupId>
  5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  6. </dependency>
  7. <!--seata-->
  8. <dependency>
  9. <groupId>com.alibaba.cloud</groupId>
  10. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  11. <exclusions>
  12. <exclusion>
  13. <artifactId>seata-all</artifactId>
  14. <groupId>io.seata</groupId>
  15. </exclusion>
  16. </exclusions>
  17. </dependency>
  18. <dependency>
  19. <groupId>io.seata</groupId>
  20. <artifactId>seata-all</artifactId>
  21. <version>0.9.0</version>
  22. </dependency>
  23. <!--feign-->
  24. <dependency>
  25. <groupId>org.springframework.cloud</groupId>
  26. <artifactId>spring-cloud-starter-openfeign</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-web</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-test</artifactId>
  35. <scope>test</scope>
  36. </dependency>
  37. <dependency>
  38. <groupId>org.mybatis.spring.boot</groupId>
  39. <artifactId>mybatis-spring-boot-starter</artifactId>
  40. <version>2.0.0</version>
  41. </dependency>
  42. <dependency>
  43. <groupId>mysql</groupId>
  44. <artifactId>mysql-connector-java</artifactId>
  45. <version>5.1.37</version>
  46. </dependency>
  47. <dependency>
  48. <groupId>com.alibaba</groupId>
  49. <artifactId>druid-spring-boot-starter</artifactId>
  50. <version>1.1.10</version>
  51. </dependency>
  52. <dependency>
  53. <groupId>org.projectlombok</groupId>
  54. <artifactId>lombok</artifactId>
  55. <optional>true</optional>
  56. </dependency>
  57. </dependencies>

YML

  1. server:
  2. port: 2002
  3. spring:
  4. application:
  5. name: seata-storage-service
  6. cloud:
  7. alibaba:
  8. seata:
  9. tx-service-group: fsp_tx_group
  10. nacos:
  11. discovery:
  12. server-addr: localhost:8848
  13. datasource:
  14. driver-class-name: com.mysql.jdbc.Driver
  15. url: jdbc:mysql://localhost:3306/seata_storage
  16. username: root
  17. password: 123456
  18. logging:
  19. level:
  20. io:
  21. seata: info
  22. mybatis:
  23. mapperLocations: classpath:mapper/*.xml

file.conf

  1. transport {
  2. # tcp udt unix-domain-socket
  3. type = "TCP"
  4. #NIO NATIVE
  5. server = "NIO"
  6. #enable heartbeat
  7. heartbeat = true
  8. #thread factory for netty
  9. thread-factory {
  10. boss-thread-prefix = "NettyBoss"
  11. worker-thread-prefix = "NettyServerNIOWorker"
  12. server-executor-thread-prefix = "NettyServerBizHandler"
  13. share-boss-worker = false
  14. client-selector-thread-prefix = "NettyClientSelector"
  15. client-selector-thread-size = 1
  16. client-worker-thread-prefix = "NettyClientWorkerThread"
  17. # netty boss thread size,will not be used for UDT
  18. boss-thread-size = 1
  19. #auto default pin or 8
  20. worker-thread-size = 8
  21. }
  22. shutdown {
  23. # when destroy server, wait seconds
  24. wait = 3
  25. }
  26. serialization = "seata"
  27. compressor = "none"
  28. }
  29. service {
  30. #vgroup->rgroup
  31. vgroup_mapping.fsp_tx_group = "default"
  32. #only support single node
  33. default.grouplist = "127.0.0.1:8091"
  34. #degrade current not support
  35. enableDegrade = false
  36. #disable
  37. disable = false
  38. #unit ms,s,m,h,d represents milliseconds, seconds, minutes, hours, days, default permanent
  39. max.commit.retry.timeout = "-1"
  40. max.rollback.retry.timeout = "-1"
  41. disableGlobalTransaction = false
  42. }
  43. client {
  44. async.commit.buffer.limit = 10000
  45. lock {
  46. retry.internal = 10
  47. retry.times = 30
  48. }
  49. report.retry.count = 5
  50. tm.commit.retry.count = 1
  51. tm.rollback.retry.count = 1
  52. }
  53. transaction {
  54. undo.data.validation = true
  55. undo.log.serialization = "jackson"
  56. undo.log.save.days = 7
  57. #schedule delete expired undo_log in milliseconds
  58. undo.log.delete.period = 86400000
  59. undo.log.table = "undo_log"
  60. }
  61. support {
  62. ## spring
  63. spring {
  64. # auto proxy the DataSource bean
  65. datasource.autoproxy = false
  66. }
  67. }

registry.conf

  1. registry {
  2. # file 、nacos 、eureka、redis、zk
  3. type = "nacos"
  4. nacos {
  5. serverAddr = "localhost:8848"
  6. namespace = ""
  7. cluster = "default"
  8. }
  9. eureka {
  10. serviceUrl = "http://localhost:8761/eureka"
  11. application = "default"
  12. weight = "1"
  13. }
  14. redis {
  15. serverAddr = "localhost:6381"
  16. db = "0"
  17. }
  18. zk {
  19. cluster = "default"
  20. serverAddr = "127.0.0.1:2181"
  21. session.timeout = 6000
  22. connect.timeout = 2000
  23. }
  24. file {
  25. name = "file.conf"
  26. }
  27. }
  28. config {
  29. # file、nacos 、apollo、zk
  30. type = "file"
  31. nacos {
  32. serverAddr = "localhost"
  33. namespace = ""
  34. cluster = "default"
  35. }
  36. apollo {
  37. app.id = "fescar-server"
  38. apollo.meta = "http://192.168.1.204:8801"
  39. }
  40. zk {
  41. serverAddr = "127.0.0.1:2181"
  42. session.timeout = 6000
  43. connect.timeout = 2000
  44. }
  45. file {
  46. name = "file.conf"
  47. }
  48. }

domain

CommonResult

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class CommonResult<T>{
  5. private Integer code;
  6. private String message;
  7. private T data;
  8. public CommonResult(Integer code, String message){
  9. this(code,message,null);
  10. }
  11. }

Storage

  1. @Data
  2. public class Storage {
  3. private Long id;
  4. /**
  5. * 产品id
  6. */
  7. private Long productId;
  8. /**
  9. * 总库存
  10. */
  11. private Integer total;
  12. /**
  13. * 已用库存
  14. */
  15. private Integer used;
  16. /**
  17. * 剩余库存
  18. */
  19. private Integer residue;
  20. }

Dao接口及实现

StorageDao

  1. @Mapper
  2. public interface StorageDao {
  3. /**
  4. * 扣减库存
  5. */
  6. void decrease(@Param("productId") Long productId, @Param("count") Integer count);
  7. }

resources文件夹下新建mapper文件夹后添加

StorageMapper.xml

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
  3. <mapper namespace="com.atguigu.springcloud.alibaba.dao.StorageDao">
  4. <resultMap id="BaseResultMap" type="com.atguigu.springcloud.alibaba.domain.Storage">
  5. <id column="id" property="id" jdbcType="BIGINT"/>
  6. <result column="product_id" property="productId" jdbcType="BIGINT"/>
  7. <result column="total" property="total" jdbcType="INTEGER"/>
  8. <result column="used" property="used" jdbcType="INTEGER"/>
  9. <result column="residue" property="residue" jdbcType="INTEGER"/>
  10. </resultMap>
  11. <update id="decrease">
  12. UPDATE t_storage
  13. SET used = used + #{count},
  14. residue = residue - #{count}
  15. WHERE product_id = #{productId}
  16. </update>
  17. </mapper>

Service接口及实现

StorageService

  1. public interface StorageService {
  2. /**
  3. * 扣减库存
  4. */
  5. void decrease(Long productId, Integer count);
  6. }

StorageServiceImpl

  1. @Service
  2. public class StorageServiceImpl implements StorageService {
  3. private static final Logger LOGGER = LoggerFactory.getLogger(StorageServiceImpl.class);
  4. @Resource
  5. private StorageDao storageDao;
  6. /**
  7. * 扣减库存
  8. */
  9. @Override
  10. public void decrease(Long productId, Integer count) {
  11. LOGGER.info("------->storage-service中扣减库存开始");
  12. storageDao.decrease(productId,count);
  13. LOGGER.info("------->storage-service中扣减库存结束");
  14. }
  15. }

Controller

  1. @RestController
  2. public class StorageController {
  3. @Autowired
  4. private StorageService storageService;
  5. /**
  6. * 扣减库存
  7. */
  8. @RequestMapping("/storage/decrease")
  9. public CommonResult decrease(Long productId, Integer count) {
  10. storageService.decrease(productId, count);
  11. return new CommonResult(200,"扣减库存成功!");
  12. }
  13. }

Config配置

MyBatisConfig

  1. @Configuration
  2. @MapperScan({"com.atguigu.springcloud.alibaba.dao"})
  3. public class MyBatisConfig {
  4. }

DataSourceProxyConfig

  1. @Configuration
  2. public class DataSourceProxyConfig {
  3. @Value("${mybatis.mapperLocations}")
  4. private String mapperLocations;
  5. @Bean
  6. @ConfigurationProperties(prefix = "spring.datasource")
  7. public DataSource druidDataSource(){
  8. return new DruidDataSource();
  9. }
  10. @Bean
  11. public DataSourceProxy dataSourceProxy(DataSource dataSource) {
  12. return new DataSourceProxy(dataSource);
  13. }
  14. @Bean
  15. public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
  16. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  17. sqlSessionFactoryBean.setDataSource(dataSourceProxy);
  18. sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
  19. sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
  20. return sqlSessionFactoryBean.getObject();
  21. }
  22. }

主启动

  1. @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
  2. @EnableDiscoveryClient
  3. @EnableFeignClients
  4. public class SeataStorageServiceApplication2002 {
  5. public static void main(String[] args) {
  6. SpringApplication.run(SeataStorageServiceApplication2002.class, args);
  7. }
  8. }

seata-account-service2003

  1. <dependencies>
  2. <!--nacos-->
  3. <dependency>
  4. <groupId>com.alibaba.cloud</groupId>
  5. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  6. </dependency>
  7. <!--seata-->
  8. <dependency>
  9. <groupId>com.alibaba.cloud</groupId>
  10. <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
  11. <exclusions>
  12. <exclusion>
  13. <artifactId>seata-all</artifactId>
  14. <groupId>io.seata</groupId>
  15. </exclusion>
  16. </exclusions>
  17. </dependency>
  18. <dependency>
  19. <groupId>io.seata</groupId>
  20. <artifactId>seata-all</artifactId>
  21. <version>0.9.0</version>
  22. </dependency>
  23. <!--feign-->
  24. <dependency>
  25. <groupId>org.springframework.cloud</groupId>
  26. <artifactId>spring-cloud-starter-openfeign</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-web</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-test</artifactId>
  35. <scope>test</scope>
  36. </dependency>
  37. <dependency>
  38. <groupId>org.mybatis.spring.boot</groupId>
  39. <artifactId>mybatis-spring-boot-starter</artifactId>
  40. <version>2.0.0</version>
  41. </dependency>
  42. <dependency>
  43. <groupId>mysql</groupId>
  44. <artifactId>mysql-connector-java</artifactId>
  45. <version>5.1.37</version>
  46. </dependency>
  47. <dependency>
  48. <groupId>com.alibaba</groupId>
  49. <artifactId>druid-spring-boot-starter</artifactId>
  50. <version>1.1.10</version>
  51. </dependency>
  52. <dependency>
  53. <groupId>org.projectlombok</groupId>
  54. <artifactId>lombok</artifactId>
  55. <optional>true</optional>
  56. </dependency>
  57. </dependencies>

yml

  1. server:
  2. port: 2003
  3. spring:
  4. application:
  5. name: seata-account-service
  6. cloud:
  7. alibaba:
  8. seata:
  9. tx-service-group: fsp_tx_group
  10. nacos:
  11. discovery:
  12. server-addr: localhost:8848
  13. datasource:
  14. driver-class-name: com.mysql.jdbc.Driver
  15. url: jdbc:mysql://localhost:3306/seata_account
  16. username: root
  17. password: 123456
  18. feign:
  19. hystrix:
  20. enabled: false
  21. logging:
  22. level:
  23. io:
  24. seata: info
  25. mybatis:
  26. mapperLocations: classpath:mapper/*.xml

file.conf

  1. transport {
  2. # tcp udt unix-domain-socket
  3. type = "TCP"
  4. #NIO NATIVE
  5. server = "NIO"
  6. #enable heartbeat
  7. heartbeat = true
  8. #thread factory for netty
  9. thread-factory {
  10. boss-thread-prefix = "NettyBoss"
  11. worker-thread-prefix = "NettyServerNIOWorker"
  12. server-executor-thread-prefix = "NettyServerBizHandler"
  13. share-boss-worker = false
  14. client-selector-thread-prefix = "NettyClientSelector"
  15. client-selector-thread-size = 1
  16. client-worker-thread-prefix = "NettyClientWorkerThread"
  17. # netty boss thread size,will not be used for UDT
  18. boss-thread-size = 1
  19. #auto default pin or 8
  20. worker-thread-size = 8
  21. }
  22. shutdown {
  23. # when destroy server, wait seconds
  24. wait = 3
  25. }
  26. serialization = "seata"
  27. compressor = "none"
  28. }
  29. service {
  30. vgroup_mapping.fsp_tx_group = "default" #修改自定义事务组名称
  31. default.grouplist = "127.0.0.1:8091"
  32. enableDegrade = false
  33. disable = false
  34. max.commit.retry.timeout = "-1"
  35. max.rollback.retry.timeout = "-1"
  36. disableGlobalTransaction = false
  37. }
  38. client {
  39. async.commit.buffer.limit = 10000
  40. lock {
  41. retry.internal = 10
  42. retry.times = 30
  43. }
  44. report.retry.count = 5
  45. tm.commit.retry.count = 1
  46. tm.rollback.retry.count = 1
  47. }
  48. ## transaction log store
  49. store {
  50. ## store mode: file、db
  51. mode = "db"
  52. ## file store
  53. file {
  54. dir = "sessionStore"
  55. # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
  56. max-branch-session-size = 16384
  57. # globe session size , if exceeded throws exceptions
  58. max-global-session-size = 512
  59. # file buffer size , if exceeded allocate new buffer
  60. file-write-buffer-cache-size = 16384
  61. # when recover batch read size
  62. session.reload.read_size = 100
  63. # async, sync
  64. flush-disk-mode = async
  65. }
  66. ## database store
  67. db {
  68. ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
  69. datasource = "dbcp"
  70. ## mysql/oracle/h2/oceanbase etc.
  71. db-type = "mysql"
  72. driver-class-name = "com.mysql.jdbc.Driver"
  73. url = "jdbc:mysql://127.0.0.1:3306/seata"
  74. user = "root"
  75. password = "123456"
  76. min-conn = 1
  77. max-conn = 3
  78. global.table = "global_table"
  79. branch.table = "branch_table"
  80. lock-table = "lock_table"
  81. query-limit = 100
  82. }
  83. }
  84. lock {
  85. ## the lock store mode: local、remote
  86. mode = "remote"
  87. local {
  88. ## store locks in user's database
  89. }
  90. remote {
  91. ## store locks in the seata's server
  92. }
  93. }
  94. recovery {
  95. #schedule committing retry period in milliseconds
  96. committing-retry-period = 1000
  97. #schedule asyn committing retry period in milliseconds
  98. asyn-committing-retry-period = 1000
  99. #schedule rollbacking retry period in milliseconds
  100. rollbacking-retry-period = 1000
  101. #schedule timeout retry period in milliseconds
  102. timeout-retry-period = 1000
  103. }
  104. transaction {
  105. undo.data.validation = true
  106. undo.log.serialization = "jackson"
  107. undo.log.save.days = 7
  108. #schedule delete expired undo_log in milliseconds
  109. undo.log.delete.period = 86400000
  110. undo.log.table = "undo_log"
  111. }
  112. ## metrics settings
  113. metrics {
  114. enabled = false
  115. registry-type = "compact"
  116. # multi exporters use comma divided
  117. exporter-list = "prometheus"
  118. exporter-prometheus-port = 9898
  119. }
  120. support {
  121. ## spring
  122. spring {
  123. # auto proxy the DataSource bean
  124. datasource.autoproxy = false
  125. }
  126. }

registry.conf

  1. registry {
  2. # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  3. type = "nacos"
  4. nacos {
  5. serverAddr = "localhost:8848"
  6. namespace = ""
  7. cluster = "default"
  8. }
  9. eureka {
  10. serviceUrl = "http://localhost:8761/eureka"
  11. application = "default"
  12. weight = "1"
  13. }
  14. redis {
  15. serverAddr = "localhost:6379"
  16. db = "0"
  17. }
  18. zk {
  19. cluster = "default"
  20. serverAddr = "127.0.0.1:2181"
  21. session.timeout = 6000
  22. connect.timeout = 2000
  23. }
  24. consul {
  25. cluster = "default"
  26. serverAddr = "127.0.0.1:8500"
  27. }
  28. etcd3 {
  29. cluster = "default"
  30. serverAddr = "http://localhost:2379"
  31. }
  32. sofa {
  33. serverAddr = "127.0.0.1:9603"
  34. application = "default"
  35. region = "DEFAULT_ZONE"
  36. datacenter = "DefaultDataCenter"
  37. cluster = "default"
  38. group = "SEATA_GROUP"
  39. addressWaitTime = "3000"
  40. }
  41. file {
  42. name = "file.conf"
  43. }
  44. }
  45. config {
  46. # file、nacos 、apollo、zk、consul、etcd3
  47. type = "file"
  48. nacos {
  49. serverAddr = "localhost"
  50. namespace = ""
  51. }
  52. consul {
  53. serverAddr = "127.0.0.1:8500"
  54. }
  55. apollo {
  56. app.id = "seata-server"
  57. apollo.meta = "http://192.168.1.204:8801"
  58. }
  59. zk {
  60. serverAddr = "127.0.0.1:2181"
  61. session.timeout = 6000
  62. connect.timeout = 2000
  63. }
  64. etcd3 {
  65. serverAddr = "http://localhost:2379"
  66. }
  67. file {
  68. name = "file.conf"
  69. }
  70. }

domain

CommonResult

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class CommonResult<T>{
  5. private Integer code;
  6. private String message;
  7. private T data;
  8. public CommonResult(Integer code, String message){
  9. this(code,message,null);
  10. }
  11. }

Account

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class Account {
  5. private Long id;
  6. /**
  7. * 用户id
  8. */
  9. private Long userId;
  10. /**
  11. * 总额度
  12. */
  13. private BigDecimal total;
  14. /**
  15. * 已用额度
  16. */
  17. private BigDecimal used;
  18. /**
  19. * 剩余额度
  20. */
  21. private BigDecimal residue;
  22. }

Dao接口及实现

AccountDao

  1. @Mapper
  2. public interface AccountDao {
  3. /**
  4. * 扣减账户余额
  5. */
  6. void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money);
  7. }

resources文件夹下新建mapper文件夹后添加

AccountMapper.xml

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
  3. <mapper namespace="com.atguigu.springcloud.alibaba.dao.AccountDao">
  4. <resultMap id="BaseResultMap" type="com.atguigu.springcloud.alibaba.domain.Account">
  5. <id column="id" property="id" jdbcType="BIGINT"/>
  6. <result column="user_id" property="userId" jdbcType="BIGINT"/>
  7. <result column="total" property="total" jdbcType="DECIMAL"/>
  8. <result column="used" property="used" jdbcType="DECIMAL"/>
  9. <result column="residue" property="residue" jdbcType="DECIMAL"/>
  10. </resultMap>
  11. <update id="decrease">
  12. UPDATE t_account
  13. SET
  14. residue = residue - #{money},used = used + #{money}
  15. WHERE
  16. user_id = #{userId};
  17. </update>
  18. </mapper>

Service接口及实现

AccountService

  1. public interface AccountService {
  2. /**
  3. * 扣减账户余额
  4. * @param userId 用户id
  5. * @param money 金额
  6. */
  7. void decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
  8. }

AccountServiceImpl

  1. @Service
  2. public class AccountServiceImpl implements AccountService {
  3. private static final Logger LOGGER = LoggerFactory.getLogger(AccountServiceImpl.class);
  4. @Resource
  5. AccountDao accountDao;
  6. /**
  7. * 扣减账户余额
  8. */
  9. @Override
  10. public void decrease(Long userId, BigDecimal money) {
  11. LOGGER.info("------->account-service中扣减账户余额开始");
  12. //模拟超时异常,全局事务回滚
  13. //暂停几秒钟线程
  14. //try { TimeUnit.SECONDS.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); }
  15. accountDao.decrease(userId,money);
  16. LOGGER.info("------->account-service中扣减账户余额结束");
  17. }
  18. }

Controller

  1. @RestController
  2. public class AccountController {
  3. @Resource
  4. AccountService accountService;
  5. /**
  6. * 扣减账户余额
  7. */
  8. @RequestMapping("/account/decrease")
  9. public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
  10. accountService.decrease(userId,money);
  11. return new CommonResult(200,"扣减账户余额成功!");
  12. }
  13. }

Config配置

MyBatisConfig

  1. @Configuration
  2. @MapperScan({"com.atguigu.springcloud.alibaba.dao"})
  3. public class MyBatisConfig {
  4. }

DataSourceProxyConfig

  1. @Configuration
  2. public class DataSourceProxyConfig {
  3. @Value("${mybatis.mapperLocations}")
  4. private String mapperLocations;
  5. @Bean
  6. @ConfigurationProperties(prefix = "spring.datasource")
  7. public DataSource druidDataSource(){
  8. return new DruidDataSource();
  9. }
  10. @Bean
  11. public DataSourceProxy dataSourceProxy(DataSource dataSource) {
  12. return new DataSourceProxy(dataSource);
  13. }
  14. @Bean
  15. public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
  16. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  17. sqlSessionFactoryBean.setDataSource(dataSourceProxy);
  18. sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
  19. sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());
  20. return sqlSessionFactoryBean.getObject();
  21. }
  22. }

主启动

  1. @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
  2. @EnableDiscoveryClient
  3. @EnableFeignClients
  4. public class SeataAccountMainApp2003{
  5. public static void main(String[] args){
  6. SpringApplication.run(SeataAccountMainApp2003.class, args);
  7. }
  8. }

Test

下订单->减库存->扣余额->改(订单)状态

SpringCloud-第二部分 - 图262

数据库初始情况

SELECT * FROM seata_order.t_order

SpringCloud-第二部分 - 图263

SELECT * FROM seata_storage.t_storage

SpringCloud-第二部分 - 图264

SELECT * FROM seata_account.t_account;

SpringCloud-第二部分 - 图265

正常下单

http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100

数据库情况

SpringCloud-第二部分 - 图266

SpringCloud-第二部分 - 图267

SpringCloud-第二部分 - 图268

超时异常,没加@GlobalTransactional

AccountServiceImpl添加超时

数据库情况

SpringCloud-第二部分 - 图269

SpringCloud-第二部分 - 图270

SpringCloud-第二部分 - 图271

故障情况

当库存和账户金额扣减后,订单状态并没有设置为已经完成,没有从零改为1

而且由于feign的重试机制,账户余额还有可能被多次扣减

超时异常,添加@GlobalTransactional

AccountServiceImpl添加超时

OrderServiceImpl@GlobalTransactional

  1. @GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
  2. public void create(Order order){
  3. }

下单后数据库数据并没有任何改变,记录都添加不进来

一部分补充

Seata

2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案

Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架

2020起始,参加工作后用1.0以后的版本

SpringCloud-第二部分 - 图272

再看TC/TM/RM三大组件

SpringCloud-第二部分 - 图273

分布式事务的执行流程

  1. TM 开启分布式事务(TM TC 注册全局事务记录);
  2. 按业务场景,编排数据库、服务等事务内资源(RM TC 汇报资源准备状态 );
  3. TM 结束分布式事务,事务一阶段结束(TM 通知 TC 提交/回滚分布式事务);
  4. TC 汇总事务信息,决定分布式事务是提交还是回滚;
  5. TC 通知所有 RM 提交/回滚 资源,事务二阶段结束。

AT模式如何做到对业务的无侵入

是什么

SpringCloud-第二部分 - 图274

一阶段加载

  1. 在一阶段,Seata 会拦截“业务 SQL”,
  2. 1 解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,
  3. 2 执行“业务 SQL”更新业务数据,在业务数据更新之后,
  4. 3 其保存成“after image”,最后生成行锁。
  5. 以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。

SpringCloud-第二部分 - 图275

二阶段提交

  1. 二阶段如是顺利提交的话,因为“业务 SQL”在一阶段已经提交至数据库,所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

SpringCloud-第二部分 - 图276

二阶段回滚

  1. 二阶段回滚:
  2. 二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。
  3. 回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

SpringCloud-第二部分 - 图277

debug

补充

SpringCloud-第二部分 - 图278