SpringCloud( H版 & alibaba )框架开发教程(中级)

中级

编码五部曲:

  • 建module
  • 改pom
  • 写yml
  • 主启动
  • 业务类

所有源码地址:https://gitee.com/xyy-kk_admin/spring-cloud

入门篇链接:https://blog.csdn.net/qiwunongqingyin/article/details/117927188
初级篇链接:https://blog.csdn.net/qiwunongqingyin/article/details/118028552
中级篇链接:https://blog.csdn.net/qiwunongqingyin/article/details/118151377
高级篇链接:https://blog.csdn.net/qiwunongqingyin/article/details/118411828


  1. Hystrix 断路器
  2. zuul 路由网关
  3. Gateway 新一代网关
  4. SpringCloud Config 分布式配置中心
  5. SpringCloud Bus 消息总线
  6. SpringCloud Stream 消息驱动
  7. SpringCloud Sleuth 分布式请求链路跟踪

10. Hystrix 断路器(服务降级)

已停更,很牛逼…以后其他的框架可以抄作业

10.1 概述

  1. 分布式系统面临的问题

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

  2. 服务雪崩

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

    对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

    所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

  3. 是什么?

    Hystrix是一个用于处理分布式系统的延迟容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

    "断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

  4. 能干嘛?

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

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

  6. Hystrix 官宣,停更进维,推荐使用resilience4j(国外用的多,国内用的极少,可以去用 高级篇的第20章 SpringCloud Alibaba Sentinel 现实熔断与限流)

10.2 重要概念

  1. 服务降级 -> fallback

    服务器忙,请稍后再试,提示:fallback

    降级条件:
    -> 运行异常
    -> 超时
    -> 熔断触发降级
    -> 线程池,信号量打满也会导致降级

  2. 服务熔断 -> break

    保险丝直接断掉,拉闸,然后调用降级返回友好提示

  3. 服务限流 -> flowlimit

    秒杀或高并发等操作,严禁一窝蜂拥挤,排队,每秒钟n个

10.3 编写基础内容 8001

本章从,正确,错误,熔断,恢复慢慢来

  1. 创建子项目cloud-provider-hystrix-payment8001

  2. pom

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloudartifactId>
            <groupId>com.xyygroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>cloud-provider-hystrix-payment8001artifactId>
    
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            
            <dependency>
                <groupId>com.xyygroupId>
                <artifactId>cloud-api-commonsartifactId>
                <version>${project.version}version>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-actuatorartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    project>
    
  3. yml

    server:
      port: 8001
    
    eureka:
      client:
        #表示是否将自己注册进Eurekaserver默认为true。
        register-with-eureka: true
        #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
        fetchRegistry: true
        service-url:
          #defaultZone: http://localhost:7001/eureka
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
      instance:
        instance-id: payment8001
        prefer-ip-address: true
    
    spring:
      application:
        name: cloud-provider-hystrix-payment
    
  4. 启动类:PaymentHystrixMain8001.java
    @SpringBootApplication@EnableEurekaClient注解

  5. service类: 我这里没写接口,懒得写…

    @Service
    public class PaymentServiceImpl{
    
        public String paymentInfo_OK(Integer id) {
            return "线程池:   "+Thread.currentThread().getName()+"   paymentInfo_OK,id:   "+id;
        }
    
        public String paymentInfo_timeout(Integer id) {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "线程池:   "+Thread.currentThread().getName()+"   paymentInfo_timeout,耗时3秒,id:   "+id;
        }
    }
    
  6. controller类编写:

    @RestController
    @Slf4j
    @RequestMapping("/payment/hystrix")
    public class PaymentController {
    
        @Resource
        private IPaymentService iPaymentService;
    
        @Value("${server.port}")
        private String serverPort;
    
        @GetMapping("ok/{id}")
        public String paymentInfo_OK(@PathVariable("id")Integer id){
            String result = iPaymentService.paymentInfo_OK(id);
            log.info(result);
            return result;
        }
    
        @GetMapping("timeout/{id}")
        public String paymentInfo_timeout(@PathVariable("id")Integer id){
            String result = iPaymentService.paymentInfo_timeout(id);
            log.info(result);
            return result;
        }
    }
    
  7. 测试:
    http://localhost:8001/payment/hystrix/ok/1
    http://localhost:8001/payment/hystrix/timeout/1

    会发现两个都成功了,只不过第二个由于我们写了延迟…响应时间会长一些

10.4 高并发测试 jmeeter

  1. 测试准备
    本次使用jmeeter测试工具

    下载地址: https://jmeter.apache.org/download_jmeter.cgi

    安装方法:

    1. 解压,
    2. 配置系统环境变量 K:JMETER_HOME,V:F:\省略\apache-jmeter-5.4.1
    3. 配置path路径:分别为:
      %JMETER_HOME%\lib\ext\ApacheJMeter_core.jar
      %JMETER_HOME%\lib\jorphan.jar
      %JMETER_HOME%\lib\logkit-2.0.jar

    使用方法:
    bin目录下有一个名为:jmeter.bat的文件,右击,发送快捷方式到桌面,会打开两个窗口,一个是命令窗口,一个是图形化窗口,命令窗口不要关毕

    汉化方式:https://jingyan.baidu.com/article/b0b63dbf25733b4a4830708f.html

  2. 开始测试:
    右击 Test Plan -> 添加 -> 线程(用户) -> 线程组

    线程数200,次数100,总量200*100=2W,ctrl+s选择路径保存
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第1张图片
    右击项目 -> 添加 -> 取样器 -> HTTP请求
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第2张图片
    !!!----------------------启动----------------------------!!!
    本来,ok方法是毫秒级响应,现在再刷新,会有一些延迟
    而timeout也不会报错,响应时间会拉长,线程池会加大

    大部分资源都在timeout中,ok被拖累

    而此时80来访问时间会超长,用户不满意,8001被拖死

10.5 消费者 80 加入

  1. 创建子项目cloud-consumer-feign-hystrix-order80

  2. pom

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloudartifactId>
            <groupId>com.xyygroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>cloud-consumer-feign-hystrix-order80artifactId>
    
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-openfeignartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            
            <dependency>
                <groupId>com.xyygroupId>
                <artifactId>cloud-api-commonsartifactId>
                <version>${project.version}version>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-actuatorartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    project>
    
  3. yml

    server:
      port: 80
    
    eureka:
      client:
        register-with-eureka: true
        service-url:
          #defaultZone: http://localhost:7001/eureka
          defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
    spring:
      application:
        name: cloud-feign-hystrix-order
    
    #设置feign客户端超时时间(OpenFeign默认支持ribbon)(单位:毫秒)
    ribbon:
      #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
      ReadTimeout: 5000
      #指的是建立连接后从服务器读取到可用资源所用的时间
      ConnectTimeout: 5000
    
  4. 创建启动类OrderHystrixMain80.java
    @SpringBootApplication@EnableFeignClients注解

  5. 创建service

    @Component
    //8001 项目名spring.application.name
    @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
    public interface PaymentHystrixService {
        final String path="/payment/hystrix/";
    
        @GetMapping(path+"ok/{id}")
        public String paymentInfo_OK(@PathVariable("id")Integer id);
    
        @GetMapping(path+"timeout/{id}")
        public String paymentInfo_timeout(@PathVariable("id")Integer id);
    }
    
  6. 创建controller

    @RestController
    @RequestMapping("consumer/hystrix")
    public class OrderHystrixController {
        @Resource
        private PaymentHystrixService paymentHystrixService;
        
        @GetMapping("ok/{id}")
        public String paymentInfo_OK(@PathVariable("id")Integer id){
            return paymentHystrixService.paymentInfo_OK(id);
        }
    
        @GetMapping("timeout/{id}")
        public String paymentInfo_timeout(@PathVariable("id")Integer id){
            return paymentHystrixService.paymentInfo_timeout(id);
        }
    }
    
  7. 启动测试: 启动jmeeter(或者你想的话,在加一个80的HTTP请求一起访问)
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第3张图片

    访问网页:http://localhost/consumer/hystrix/ok/2
    会发现ok好慢好慢

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

10.6 如何解决?要求?

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

  2. 出错(宕机或运行出错) -> 出错有兜底

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

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

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

10.7 服务降级(方法降级Fallback…特事特办)

可以放在8001服务端,也可以放在80消费端,一般会放在消费端

8001端增加服务降级

  1. 降级配置 @HystrixCommand

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

  3. 修改8001的service: 当timeout处理时间超过3秒将会跳转到timeoutHandler

    @Service
    public class PaymentServiceImpl implements IPaymentService {
    
        @Override
        public String paymentInfo_OK(Integer id) {
            return "线程池:   "+Thread.currentThread().getName()+"   paymentInfo_OK,id:   "+id;
        }
        //commandProperties,执行时间设置
        @HystrixCommand(fallbackMethod = "paymentInfo_timeoutHandler",commandProperties = {
                @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
        })
        @Override
        public String paymentInfo_timeout(Integer id) {
        	// 一个超时,一个被除数0报错,看一下是否都会跳转到降级服务中心
            try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
    		//int age = 10/0;
            return "线程池:   "+Thread.currentThread().getName()+"   paymentInfo_timeout,耗时3秒,id:   "+id;
        }
        
        public String paymentInfo_timeoutHandler(Integer id) {
            return "线程池:   "+Thread.currentThread().getName()+"  系统繁忙请稍后再试,id:   "+id+"o(╥﹏╥)o";
        }
    }
    
  4. 8001启动类增加注解@EnableCircuitBreaker //服务熔断的,后面会讲到

  5. 测试:http://localhost/consumer/hystrix/timeout/2
    结果:

    在超时中,会爆线程休眠被打断的错误,
    在被除数为0的情况下,会直接跳转到降级服务timeoutHandler中,控制台不会报错

80端增加服务降级

  1. yml

    feign:
      hystrix:
        enabled: true
    
  2. 启动类增加注解@EnableHystrix -> 跟8001的注解不一样

  3. 修改controller:

        @HystrixCommand(fallbackMethod = "paymentInfo_timeout2",commandProperties = {
                @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
        })
        @GetMapping("timeout/{id}")
        public String paymentInfo_timeout(@PathVariable("id")Integer id){
            return paymentHystrixService.paymentInfo_timeout(id);
        }
    
    	//这里会把参数也给带过来,不需要写@PathVariable的
        public String paymentInfo_timeout2(Integer id){
            return "80端,支付繁忙,请稍后,请自检o(╥﹏╥)o"+id;
        }
    
  4. 测试: http://localhost/consumer/hystrix/timeout/2

    注意:要把8001 service 层的线程休眠打开

10.8 服务降级(全局降级DefaultProperties 通杀)

@DefaultParoperties(defaultFallback="")

  1. 1每个方法配置一个服务降级方法,技术上可以,实际上傻X
  2. N除了个别重要核心业务有专属,其它普通的可以通过@DefaultProperties(defaultFallack = "")统一跳转到统—处理结果页面

通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量,(〃‘▽’〃)

直接修改80端的c层,分三步

  1. 添加当前类的全局降级方法

        //全局fallback方法
        public String payment_Global_FalllbackMethod(){
            return "payment_Global_FalllbackMethod    ";
        }
    
  2. 添加类注解@DefaultProperties(defaultFallback = "降级的方法名")
    示例:

    @DefaultProperties(defaultFallback = "payment_Global_FalllbackMethod")
    
  3. 添加方法注解:@HystrixCommand 跟指定降级一个注解,只不过不需要写参数

        @HystrixCommand
        @GetMapping("timeout3/{id}")
        public String paymentInfo_timeout3(@PathVariable("id")Integer id){
            return paymentHystrixService.paymentInfo_timeout(id);
        }
    

整个OrderHystrixController.java 类的结构是这样的:

@RestController
@Slf4j
@RequestMapping("consumer/hystrix")
//全局通用降级配置指定
@DefaultProperties(defaultFallback = "payment_Global_FalllbackMethod")
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("ok/{id}")
    public String paymentInfo_OK(@PathVariable("id")Integer id){
        return paymentHystrixService.paymentInfo_OK(id);
    }

	//指定降级方法
    @HystrixCommand(fallbackMethod = "paymentInfo_timeout2",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })
    @GetMapping("timeout/{id}")
    public String paymentInfo_timeout(@PathVariable("id")Integer id){
        return paymentHystrixService.paymentInfo_timeout(id);
    }
    
	//指定降级目标方法
    public String paymentInfo_timeout2(Integer id){
        return "80端,支付繁忙,请稍后,请自检o(╥﹏╥)o"+id;
    }
    
	//全局通用降级注解
    @HystrixCommand
    @GetMapping("timeout3/{id}")
    public String paymentInfo_timeout3(@PathVariable("id")Integer id){
        log.info("======================");
        try {
        	//当此处报错时,将会跳转到降级方法payment_Global_FalllbackMethod中
            int age = 10 / 0;
        }catch (Exception e){
            log.info("+++++++++++++++++++++++++++++++");
        }
        log.info("----------------------");
        return paymentHystrixService.paymentInfo_timeout(id);
    }
    
    //全局通用降级目标方法
    public String payment_Global_FalllbackMethod(){
    	log.info("@@@@@@@@@@@@@@@@@@@@@@@@@@@");
        return "payment_Global_FalllbackMethod";
    }
}

注:

  1. timeout3的被除数报错时,(= + -)三个符号,控制台都打印出来,并且会跳转到降级方法中
  2. 如果80的controller层调用service层, service层是java8的default方法的话,会先执行其中的方法体,但却并不会显示返回的内容,而是直接降级到备用方法中
  3. 执行循序为 -> 80原服务(模拟出错) -> 8001原服务(模拟出错) -> 8001降级服务 -> 80降级服务

8001 timeout服务错误,并不会直接跳出,而是会继续执行timeout 并且执行timeout的降级服务,与此同时,80的降级服务会直接执行,他们俩等于并行,而不是直接结束…

时间片:

	//80 执行:
	2021-06-24 12:18:58.157  INFO 12560 --- [rixController-1] c.x.s.controller.OrderHystrixController  : ======================
	2021-06-24 12:18:58.157  INFO 12560 --- [rixController-1] c.x.s.controller.OrderHystrixController  : +++++++++++++++++++++++++++++++
	2021-06-24 12:18:58.157  INFO 12560 --- [rixController-1] c.x.s.controller.OrderHystrixController  : ----------------------
	//服务跳转8001
	2021-06-24 12:18:58.401  INFO 12560 --- [STRIX-PAYMENT-1] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client CLOUD-PROVIDER-HYSTRIX-PAYMENT initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=CLOUD-PROVIDER-HYSTRIX-PAYMENT,current list of Servers=[192.168.0.180:8001],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;	Instance count:1;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
	},Server stats: [[Server:192.168.0.180:8001;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 08:00:00 CST 1970;	First connection made: Thu Jan 01 08:00:00 CST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
	]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@34074542
	//80降级服务
	2021-06-24 12:18:59.158  INFO 12560 --- [ HystrixTimer-1] c.x.s.controller.OrderHystrixController  : @@@@@@@@@@@@@@@@@@@@@@@@@@@
	
	//8001service执行任务:
	2021-06-24 12:18:58.763  INFO 16128 --- [ntServiceImpl-1] c.x.s.service.Impl.PaymentServiceImpl    : #######################################################
	java.lang.InterruptedException: sleep interrupted	//此时线程休眠被打断
	2021-06-24 12:19:01.768  INFO 16128 --- [ntServiceImpl-1] c.x.s.service.Impl.PaymentServiceImpl    : *********************************************************
	//8001 降级服务
	2021-06-24 12:19:01.774  INFO 16128 --- [ HystrixTimer-1] c.x.s.service.Impl.PaymentServiceImpl    : $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

10.9 服务降级(通配降级FeignFallback 宕机后备)

所有代码都在一个类里面 —> 耦合度高,
每个方法配置一个 —> 代码膨胀
和业务逻辑混合在一起 —> 混乱

由于controller要调用service的@FeignClient(value = '8001服务端项目(集群)名'),我们为何不在service通配一下?

我们通过 @FeignClient(value = "8001服务名",fallback=实现类.class) 通配提取出来

本次是在客户端80实现,与服务端8001没有任何关系,(实际上加在那个随意,根据需求修改)只需要为Feign客户端定义的接口类添加一个降级处理的实现类即可完成解耦

  1. yml:这个应该已经加过了,提一下,别忘了

    feign:
      hystrix:
        enabled: true
    
  2. 编写80端的service的实现类

    @Component
    public class PaymentHystrixServiceImpl implements IPaymentHystrixService {
    
        @Override
        public String paymentInfo_OK(Integer id) {
            return "---------PaymentHystrixServiceImpl paymentInfo_OK ,(*^▽^*))";
        }
    
        @Override
        public String paymentInfo_timeout(Integer id) {
            return "---------PaymentHystrixServiceImpl paymentInfo_timeout ,o(╥﹏╥)o";
        }
    }
    
  3. service 接口类上修改一下注解

    @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentHystrixServiceImplImpl.class)
    
  4. 测试: http://localhost/consumer/hystrix/ok/1
    结果为8001实现类的的结果…没有问题

    然后关闭8001服务再次测试,会发现直接调用了80service实现类中的实现方法

10.10 服务熔断

执行循序…->服务降级->进而熔断->恢复

Martin Fowler(马丁福勒)的笔记: https://martinfowler.com/bliki/CircuitBreaker.html

  1. 理论

    拉闸断电,容错恢复

     这个简单的断路器避免在电路打开时进行受保护的呼叫,但当一切恢复正常时需要外部干预来重置它。对于建筑物中的电路断路器,这是一种合理的方法,但对于软件断路器,我们可以让断路器本身检测底层调用是否再次工作。我们可以通过在合适的时间间隔后再次尝试受保护的调用来实现这种自重置行为,并在成功时重置断路器。
    

    熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。

    当检测到该节点微服务调用响应正常后,恢复调用链路…

    在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况
    当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand(在上面降级中用过)。

10.10.1 实操

国人开发的工具包(hutool 糊涂): hutool.cn

以下看不懂的鼠标右击->翻译成中文!!!

The precise way that the circuit opening and closing occurs is as follows:

  1. Assuming the volume across a circuit meets a certain threshold : HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()
  2. And assuming that the error percentage, as defined above exceeds the error percentage defined in : HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()
  3. Then the circuit-breaker transitions from CLOSED to OPEN.
  4. While it is open, it short-circuits all requests made against that circuit-breaker.
  5. After some amount of time (HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()), the next request is let through. If it fails, the command stays OPEN for the sleep window. If it succeeds, it transitions to CLOSED and the logic in 1) takes over again.

修改cloud-provider-hystrix-payment8001

  1. 8001 的 service层 添加方法
    这里看得到有很多字符串的注解…都是在package com.netflix.hystrix.HystrixCommandProperties.java类中的东西

    idea中双击shift搜索进入 HystrixCommandProperties可以看到N多个变量,下载源可以看到每个变量的解释,(ctrl+左击)变量可以移动到下方的构造方法中去…每个都有对应的字符串,这就是@HystrixProperty(name='',value='')中name的值,value请看,每个值对应的解释设置
    在这里插入图片描述

    @Service
    @Slf4j
    public class PaymentServiceImpl implements IPaymentService {
    
    	//服务降级的那些东西这里就不写了
    	//服务降级的那些东西这里就不写了
    	//服务降级的那些东西这里就不写了
        
        //=====服务熔断
        @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
                @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否开启断路器
                @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 请求次数
                @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口期
                @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),// 失败率达到多少后跳闸
        })
        @Override
        // 开启断路器,在10秒内(时间窗口期),请求10次,失败率达到60%,跳闸
        public String paymentCircuitBreaker(Integer id) {
        	id为负数手动抛出异常
            if(id < 0) {throw new RuntimeException("******id 不能负数"); }
            String serialNumber = IdUtil.simpleUUID();
            return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
        }
        
        public String paymentCircuitBreaker_fallback(Integer id) {
            return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~   id: " +id;
        }
    
  2. 在8001的controller添加方法

        @GetMapping("circuit/{id}")
        public  String paymentCircuitBreaker(@PathVariable("id") Integer id){
            String result = iPaymentService.paymentCircuitBreaker(id);
            log.info("**********result = "+result);
            return result;
        }
    
  3. 测试…因为我们设置了 在10秒内(时间窗口期),请求10次,失败率达到60%,跳闸,所以
    先访问一次,正确的方式:http://localhost:8001/payment/hystrix/circuit/4确保成功…
    再数次访问错误的方式http://localhost:8001/payment/hystrix/circuit/-4
    在次访问正确的方式后,会发现系统直接调用了降级服务,过一会儿之后才会恢复访问

    id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: 4
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第4张图片

10.10.2 总结 (所有配置解释)

  1. 在什么情况下开启?
    10.10.1 实操, (8001 的 service层 添加方法) 讲清楚了已经

  2. 开启或关闭条件

    • 当满足一定的阀值的时候(默认10秒内超过20个请求次数)
    • 当失败率达到一定的时候(默认10秒内超过50%的请求失败)
    • 到达以上阀值,断路器将会开启
    • 当开启的时候,所有请求都不会进行转发
    • 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5
  3. 断路器打开之后干了什么?

    再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback、通过断路器. 实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。

  4. 原来的主逻辑要如何恢复呢?
    对于这一问题,hystrix也为我们实现了自动恢复功能。

    当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。

  5. 所有配置解释,自行搭配使用

    @HystrixCommand(fallbackMethod = "fallbackMethod", 
        groupKey = "strGroupCommand", 
        commandKey = "strCommand", 
        threadPoolKey = "strThreadPool",
        
        commandProperties = {
            // 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
            @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
            // 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
            @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
            // 配置命令执行的超时时间
            @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
            // 是否启用超时时间
            @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
            // 执行超时的时候是否中断
            @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
            
            // 执行被取消的时候是否中断
            @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
            // 允许回调方法执行的最大并发数
            @HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
            // 服务降级是否启用,是否执行回调函数
            @HystrixProperty(name = "fallback.enabled", value = "true"),
            // 是否启用断路器
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
            // 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
            
            // 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过 circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50, 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
            // 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,如果成功就设置为 "关闭" 状态。
            @HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
            // 断路器强制打开
            @HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
            // 断路器强制关闭
            @HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
            // 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
            @HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
            
            // 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
            // 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
            @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
            // 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
            @HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
            // 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
            @HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
            // 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
            @HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
            // 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
            // 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
            // 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
            @HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
            
            // 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
            @HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
            // 是否开启请求缓存
            @HystrixProperty(name = "requestCache.enabled", value = "true"),
            // HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
            @HystrixProperty(name = "requestLog.enabled", value = "true"),
    
        },
        threadPoolProperties = {
            // 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
            @HystrixProperty(name = "coreSize", value = "10"),
            // 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,否则将使用 LinkedBlockingQueue 实现的队列。
            @HystrixProperty(name = "maxQueueSize", value = "-1"),
            // 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
            // 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
            @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
        }
       )
    //实际方法,上面都是注解
    public String doSomething() {
    	...
    }
    

10.11 服务限流

呃呃呃.这玩意儿直接去看alibabasentinel吧,hystrix毕竟已经停更了,ali的更好用,更强大,没必要搞这个了

hystrix学习思想,实际使用sentinel…呸,渣男~~~

10.12 Hystrix工作流程

看不懂翻译成中文: https://github.com/Netflix/Hystrix/wiki/How-it-Works

流程图:
SpringCloud( H版 & alibaba )框架开发教程(中级)_第5张图片

10.13 Dashboard 图形化

  1. 概述

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

  2. 创建子项目cloud-consumer-hystrix-dashboard9001

  3. pom
    所有子项目如果想在页面中展示,web和actuator是必不可少的

            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-actuatorartifactId>
            dependency>
    

    9001完整pom

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloudartifactId>
            <groupId>com.xyygroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>cloud-consumer-hystrix-dashboard9001artifactId>
    
        <dependencies>
        
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-actuatorartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    project>
    
  4. yml

    spring:
    	port: 9001
    
  5. 启动类HystrixDashboardMain9001.java
    注解@EnableHystrixDashboard

  6. 浏览器输入 地址: http://localhost:9001/hystrix查看是否启动成功
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第6张图片

  7. 子项目cloud-provider-hystrix-payment8001的启动类中加入:
    注:新版本Hystrix需要在主启动类PaymentHystrixMain8001中指定监控路径

        /**
         *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
         *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
         *只要在自己的项目里配置上下面的servlet就可以了
         *否则,Unable to connect to Command Metric Stream 404
         */
        @Bean
        public ServletRegistrationBean getServlet() {
            HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
            ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
            registrationBean.setLoadOnStartup(1);
            registrationBean.addUrlMappings("/hystrix.stream");
            registrationBean.setName("HystrixMetricsStreamServlet");
            return registrationBean;
        }
    
  8. 重启 Hystrix 8001测试:在http://localhost:9001/hystrix的地址栏中输入8001的测试地址http://localhost:8001/hystrix.stream不需要输入准确的方法地址,只需要输入hystrix的地址即可,会自动适配

    凡是有@HystrixCommand的方法都会出现在Hystrix Monitor中,(如果注解配置不全的话,可能会爆Bad chunk header错误,不是啥大毛病)

    现在Hystrix Monitor中应该有了信息:
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第7张图片

10.13.1 图形化界面解释

  1. 实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。

    该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例。

  2. 曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第8张图片

完整图例:
SpringCloud( H版 & alibaba )框架开发教程(中级)_第9张图片

11. zuul 路由网关

停止更新了…暂时不讲了,精力放在Gateway
zull github地址: http://github.com/Netflix/zuul/wiki

12. Gateway 新一代网关

Gateway官网:https://spring.io/projects/spring-cloud-gateway

12.1 概述简介

gateway是原zuul1.x版的替代

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

Cloud全家桶中有个很重要的组件就是网关I在1.x版本中都是采用的Zuul网关;

但在2.x版本中,zuul的升级—直跳票,SpringCloud最后自己研发了一个网关替代Zuul,那就是SpringCloud Gateway

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

SpringCloud( H版 & alibaba )框架开发教程(中级)_第10张图片

  1. 能干嘛?
    所有微服务的入口
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第11张图片

  2. 为什么选择Gateway?

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

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

       基于 Spring Framework 5、Project Reactor 和 Spring Boot 2.0
       能够匹配任何请求属性的路由。
       谓词和过滤器特定于路由。
       断路器集成。
       Spring Cloud DiscoveryClient 集成
       易于编写谓词和过滤器
       请求速率限制
       路径重写
      
    3. SpringCloud Gateway 与Zuul的区别

      zuul | 阻塞性框架
      Gateway基于WebFlux+Netty | 非阻塞+函数式

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

    Route(路由)

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

    Predicate(断言)

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

    Flter(过滤)

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

    总结: 路由转发+执行过滤链
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第12张图片

12.2 搭建 Gateway

  1. 新建子项目cloud-gateway-gateway9527

  2. pom

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloudartifactId>
            <groupId>com.xyygroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>cloud-gateway-gateway9527artifactId>
    
        
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-gatewayartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    project>
    
  3. yml: 这样配置越来越庞大,繁重,可以看12.3编码注入RouteLocator,不过代码也多

    server:
      port: 9527
    
    spring:
      application:
        name: cloud-gateway
      #############################新增网关配置###########################
      cloud:
        gateway:
          routes:
            - id: payment8001_get #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              uri: http://localhost:8001          #匹配后提供服务的路由地址
              predicates:
                - Path=/payment/get/**         # 断言,路径相匹配的进行路由
    
            - id: payment8001_port #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              uri: http://localhost:8001          #匹配后提供服务的路由地址
              predicates:
                - Path=/payment/port       # 断言,路径相匹配的进行路由
    ####################################################################
    
    eureka:
      instance:
        hostname: cloud-gateway-service
      client: #服务提供者provider注册进eureka服务列表内
        service-url:
          register-with-eureka: true
          fetch-registry: true
          defaultZone: http://eureka7001.com:7001/eureka
          #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
    
  4. 创建启动类GatewayMain9527,注解:@EnableEurekaClient@SpringBootApplication

  5. 测试:
    配置前:
    http://localhost:8001/payment/get/123
    http://localhost:8001/payment/port
    配置后:
    http://localhost:9527/payment/get/123
    http://localhost:9527/payment/port

12.3 编码注入 RouteLocator

百度新闻国内地址: https://news.baidu.com/guonei

将这个国内地址用代码形式注入:(这里使用了java8新特性lamda表达式)

创建编写GatewayConfig.java类: 后启动测试:

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder){
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        RouteLocator baidu_guonei_uri = routes.route("baidu_guonei",
                r -> r.path("/guonei")
                        .uri("http://news.baidu.com/guonei")).build();
        return baidu_guonei_uri;
    }
    @Bean
    public RouteLocator customRouteLocator2(RouteLocatorBuilder routeLocatorBuilder){
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        RouteLocator baidu_guonei_uri= routes.route("baidu_guoji",r -> r.path("/guoji").uri("http://news.baidu.com/guoji")).build();
        return baidu_guonei_uri;
    }
}

这里:routes.route(id,Function)写不写id都可以,不写会生成一个 str(UUID),建议写上

12.4 通过微服务名动态路由

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

以下方式二选一:

  1. 修改9527的yml
    server:
      port: 9527
    
    spring:
      application:
        name: cloud-gateway
      #############################新增网关配置###########################
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true #开启从注册中心动态创建路由功能,对服务名路由
          routes:
            - id: payment8001_get #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              #uri: http://localhost:8001          #匹配后提供服务的路由地址
              uri: lb://cloud-payment-service #匹配后提供服务的路由地址
              predicates:
                - Path=/payment/get/**         # 断言,路径相匹配的进行路由
    
            - id: payment8001_port #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
              #uri: http://localhost:8001          #匹配后提供服务的路由地址
              uri: lb://CLOUD-PAYMENT-SERVICE	 #匹配后提供服务的路由地址
              predicates:
                - Path=/payment/port       # 断言,路径相匹配的进行路由
    ####################################################################
    
    eureka:
      instance:
        hostname: cloud-gateway-service
      client: #服务提供者provider注册进eureka服务列表内
        service-url:
          register-with-eureka: true
          fetch-registry: true
          defaultZone: http://eureka7001.com:7001/eureka
          #defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
    
  2. 编写配置
        @Bean
        public RouteLocator customRouteLocator3(RouteLocatorBuilder routeLocatorBuilder){
            RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
            RouteLocator baidu = routes.route("port",r -> r.path("/payment/port").uri("lb://cloud-payment-service")).build();
            return baidu;
        }
    

测试: http://localhost:9527/payment/port

12.5 常用的 Predicate

注:官网的文档中有配置工厂的解释和示例: 看不懂的右键翻译成中文
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

在启动9527时,控制台会打印一部分东西,这部分东西就是所有可配置项:并且可以混合一起用:
SpringCloud( H版 & alibaba )框架开发教程(中级)_第13张图片

  1. After:
    此谓词匹配在指定日期时间之后发生的请求。datetime

    spring:
      cloud:
        gateway:
          routes:
          	- id:  ...
          	  url: ...
          	  predicates:
          	  	- Path=/payment/port       
    			- After=2017-01-20T17:42:47.789-07:00[America/Denver]
    

    这个时间可用:java8:java.time.ZonedDateTime.now()获得当前时间后手动修改

  2. Before:
    此谓词匹配发生在指定 之前的请求. datetime

    spring:
      cloud:
        gateway:
          routes:
          	- id:  ...
          	  url: ...
          	  predicates:
    			- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
    
  3. Between:
    Between路线谓词工厂有两个参数,datetime1并且datetime2 这是JavaZonedDateTime对象。此谓词匹配发生在 之后datetime1和之前的请求datetime2。该datetime2参数必须是后datetime1

    spring:
      cloud:
        gateway:
          routes:
          - id: between_route
            uri: https://example.org
            predicates:
            - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
    
  4. Cookie:
    所述Cookie路线谓词工厂采用两个参数,该cookienameregexp(其是Java正则表达式)。此谓词匹配具有给定名称且其值与正则表达式匹配的 cookie。

    spring:
      cloud:
        gateway:
          routes:
          - id: cookie_route
            uri: https://example.org
            predicates:
            - Cookie=key , value||正则
    
  5. Header:
    所述Header路线谓词工厂采用两个参数,报头name和一个regexp(其是Java正则表达式)。此谓词与具有给定名称的标头匹配,其值与正则表达式匹配

    spring:
      cloud:
        gateway:
          routes:
          - id: header_route
            uri: https://example.org
            predicates:
            - Header=X-Request-Id, \d+  #请求头要有X-Request-Id属性,并且值要匹配 \d+(正则) 
    

这里就讲这几个,其他的去官网自己看吧,地址在本节最上面有
或者如果用代码的方式可以这样写:

    @Bean
    public RouteLocator customRouteLocator2(RouteLocatorBuilder routeLocatorBuilder){
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        RouteLocator baidu = routes.route("baidu_guoji",
                                r -> r.path("/guoji")
                        .and().before(ZonedDateTime.parse("2021-06-28T15:13:42.198+08:00[Asia/Shanghai]"))
                        .and().method(HttpMethod.GET)
                        //在uri之前可以一直.下去
                        .uri("http://news.baidu.com/guoji")).build();
        return baidu;
    }

12.6 过滤 Filter

web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。predicate就是我们的匹配条件;

而flter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了

  1. 生命周期
    pre(在业务逻辑之前),
    post(在业务逻辑之后)

  2. 种类
    看不懂右击翻译成中文:

    GatewayFilter(单一): 有31个…多的自己看吧
    https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

    GlobalFilter(全局):有9个
    https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters

  3. 示例1:

    GatewayFilter之 AddRequestHeader:

    该AddRequestHeader GatewayFilter工厂需要keyvalue参数。

    	spring:
    	  cloud:
    	    gateway:
    	      routes:
    	        - id: payment8001_get #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
    	          #uri: http://localhost:8001          #匹配后提供服务的路由地址
    	          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
    	          predicates:
    	            - After=2017-01-20T17:42:47.789-07:00[America/Denver]
    	            - Path=/payment/get/{segment}         # 断言,路径相匹配的进行路由
    	          filters:
    	          	#过滤的工厂会在匹配请求头上加上一对请求头,key为:X-Request-Red,值为:Blue-{segment}
    	            - AddRequestHeader=X-Request-Red, Blue-{segment} 
    	```
    8001修改代码:
    ```java
        @ResponseBody
        @GetMapping("get/{id}")
        public Output getPaymentById(@PathVariable("id") Long id, HttpServletRequest request) {
               String value = request.getHeader("x-request-red");
               System.out.println(value);
    
            Payment paymentById = iPaymentService.getPaymentById(id);
            log.info("****查询id:" + paymentById + "=============");
            if (paymentById != null) {
                return Output.success("查询成功+serverProt=" + serverPort, paymentById);
            }
            return Output.failure(444, "查询失败" + id);
        }
    

    访问路径:http://localhost:9527/payment/get/1433223
    这段代码结果为:

    Blue-1433223

    其中Blue-9527 yml文件中写死的,1433223为访问路径中的最后一位,既是9527 yml文件中的{segment}零时变量,又是8001中的{id}

    其他的去官网慢慢看吧!!!

  4. 示例2: 全局过滤GlobalFilter

    主要实现两个接口: implements GlobalFilter,Ordered

    作用: 全局日志记录,统一网关

    9527创建MyLogGatewayFilter,并且实现GlobalFilter, Ordered两个接口

    @Component
    @Slf4j
    public class MyLogGatewayFilter implements GlobalFilter, Ordered {
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
           log.info("******come in MyLogGatewayFilter"+new Date());
            String uname = exchange.getRequest().getQueryParams().getFirst("uname");
            if(uname==null){
                log.warn("非法用户");
                exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
                return exchange.getResponse().setComplete();
            }
            return chain.filter(exchange);
        }
    
        @Override
        public int getOrder() {
            return 0;
        }
    }
    

    启动测试:
    之前访问方式: http://localhost:9527/payment/get/1 会406,返回无法解析的数据
    现在访问方式: http://localhost:9527/payment/get/1?uname=23

13. SpringCloud Config 分布式配置中心

官网: https://spring.io/projects/spring-cloud-config

13.1 概述

  1. 面临的问题?

    微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以—套集中式的、动态的配置管理设施是必不可少的。

    SpringCloud提供了ConfigServer来解决这个问题,我们每一个微服务自己带着一个application.yml,上百个配置文件的管理…(ToT)/~~

  2. 是什么?

    SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第14张图片

  3. 能干嘛?

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

    SpringCloud Config分为服务端和客户端两部分。

    服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口
    客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,

    并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过git客户端工具来方便的管理和访问配置内容

13.2 Config 配置 server 端

嘤嘤嘤,这破玩意儿干了我一整天…

需要掌握git的使用(不会的话,在这儿无脑跟也一样),gitee或github的基础使用!

git下载地址: https://git-scm.com/downloads

  1. 在你的本地文件夹(最好是之前cloud项目的同上级目录)中打开 git 的git bash here,输入以下命令:

    git全局设置:(新建)

    git config --global user.name “你的用户名”"
    git config --global user.email “你的邮箱”

  2. 登陆gitee或者github创建新仓库springcloud-config,并且公有(不懂请看备注)

  3. 创建 git 仓库:
    git窗口命令

    mkdir springcloud
    cd springcloud
    git init
    touch README.md
    git add README.md
    git commit -m “首次提交备注”
    git remote add origin [gitee上的springcloud-config的ssh地址]
    git push -u origin master

  4. 在当前目录创建三个文件,分别为:config-dev.yml,config-prod.yml,config-test.yml,并且分开写入以下内容
    (名字格式不懂请看备注)

    #config-dev.yml中写入
    config:
      info: "master branch,springcloud-config/config-dev.yml version=1"
      
    #config-prod.yml中写入
    config:
     info: "master branch,springcloud-config/config-prod.yml version=1"
     
    #config-test.yml中写入
    config:
     info: "master branch,springcloud-config/config-test.yml version=1"
    
  5. 创建子项目cloud-config-center3344

  6. pom

    
    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://maven.apache.org/POM/4.0.0"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloudartifactId>
            <groupId>com.xyygroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>cloud-config-center3344artifactId>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-config-serverartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-actuatorartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    project>
    
  7. yml

    server:
      port: 3344
    
    spring:
      application:
        name: cloud-config-center #注册进Eureka服务器的微服务名
      cloud:
        config:
          server:
            git:
              uri: https://gitee.com/xyy-kk_admin/springcloud-config.git  #Gitee上面的git仓库名字,建议用https请求
              ####搜索目录
              search-paths:
                - springcloud-config
              #username: yourName   # ssh请求需要,私有√,公有×
              #password: youPassword    #ssh请求需要,私有√,公有×
          #服务注册到eureka地址
          label: master
    
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    
  8. 创建启动类ConfigCenterMain3344,额外注解@EnableConfigServer,其他不变

  9. 测试,先启动7001,在启动3344;
    访问: http://localhost:3344/master/config-dev.yml
    显示结果:

    config:
      info: master branch,springcloud-config/config-dev.yml version=7
    

    可以看一下为什么让写成指定格式的名字:访问:
    http://localhost:3344/master/config_dev.yml 和 http://localhost:3344/master/config.dev.yml值为json串而不是文件内容

13.3 Config 配置 client 端

  1. 创建子项目cloud-config-client3355

  2. pom

    
    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://maven.apache.org/POM/4.0.0"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloudartifactId>
            <groupId>com.xyygroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>cloud-config-client3355artifactId>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-configartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-actuatorartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    project>
    
  3. bootstrap.yml

    applicaiton.yml是用户级的资源配置项

    bootstrap.yml是系统级的,优先级更加高

    Spring Cloud会创建一个Bootstrap Context,作为Spring应用的Application Context的父上下文。他们的图标就有不同在这里插入图片描述

    初始化的时候,BootstrapContext负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment。

    Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。 Bootstrap context和Application Context有着不同的约定,所以新增了一个 bootstrap.yml文件,保证Bootstrap Context和Application Context配置的分离。

    要将Client模块下的application.yml文件改为bootstrap.yml,这是很关键的,因为bootstrap.yml是比application.yml先加载的。bootstrap.yml优先级高于application.yml。

    #bootstrap.yml
    server:
      port: 3355
    
    spring:
      application:
        name: cloud-config-client
      cloud:
        #Config客户端配置
        config:
          label: master #分支名称
          name: config #配置文件名称
          profile: dev #读取后缀名称
          #           (label)  (name)- (profile).yml
          #上述3个综合:master分支上 config - dev.yml 的配置文件被读取(http://localhost:3344/master/config-dev.yml)
          uri: http://localhost:3344 #配置中心地址
    
    
    #服务注册到eureka地址
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:7001/eureka
    
  4. 启动类ConfigClientMain3355,额外注解:@EnableEurekaClient

  5. 业务类ConfigClientController

    @RestController
    @RefreshScope
    @RequestMapping("configclient")
    public class ConfigClientController {
        //@Value("${xxx}")获取的是配置文件中的属性值,
        //因为没有配置config.info属性,而配置了云配置中心,所以访问的将是3344(13.2Config 配置 server 端)所写的config-dev.yml文件,此文件中有config.info属性
        //为什么访问config-dev.yml?   因为bootstrap.yml配置类config的label,name,profile三个属性,
        @Value("${config.info}")
        private String configInfo;
    
        @Value("${server.port}")
        private String port;
    
        @GetMapping("/configInfo")
        public String getConfigInfo() {
            return "port:  " + port + "  configInfo:  " + configInfo;
        }
    }
    
  6. 启动测试:先访问3344自测成功:http://localhost:3344/master/config-dev.yml(显示config.info:xxx)
    在访问3355的configinfo方法:http://localhost:3355/configclient/configInfo将会直接显示config.info的值

13.4 client端之手动动态刷新

现在直接修改github或者gitee中的config-dev.yml文件,将版本号从1改为2
刷新3344会看到版本号已经被修改,再刷新3355发现3355的版本号并没有改变,除非重启或重新加载

运维工程师改一次就要重新启动一次?每次重启时间?

  1. pom:
    pom文件中必须有actuator依赖

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-actuatorartifactId>
        dependency>
    
  2. bootstrap.yml

    management:
      endpoints:
        web:
          exposure:
          	#暴露什么端口?
            include: "*"
            #不暴露什么端口
            #exclude: "*"
            #这里底下配了一张图,包含所有参数
    

    SpringCloud( H版 & alibaba )框架开发教程(中级)_第15张图片

  3. controller增加以下注解:

    import org.springframework.cloud.context.config.annotation.RefreshScope;
    @RefreshScope  //刷新的注解标签
    
  4. 测试:重启3355后,修改gitee中config-dev的版本号,访问3355是否修改???没有!!!没有修改成功!!!why???

    这是因为现在不是自动的,而是要手动的告诉3355刷新了:
    打开cmd命令窗口输入以下内容:必须用post!!!

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

    在次访问3355(不要重启3355),此时,修改成功

注: gitee修改之后因为3344是直连gitee,所以会直接刷新,而3355需要由修改者额外通知
curl -X POST "http://ip:port/actuator/refresh"

由于微服务可能过多,每个都要 执行一次,或者写个脚本批量刷新,但是太麻烦了,所以请看下一章(14 springcloud Bus 消息总线)

13.5 备注

  1. gitee和github 选择?

    gitee 国内快 github国外慢

    私有访问需要配置ssh和用户名密码,公有不需要,

    然后在github在2020.12.15日更新后,master分支更名为main分支,并且 github 给我发了一封邮件,说的是↓↓↓看下面: 然后我自己进不去…)

    使用密码进行Git的基本身份验证已经被弃用了,很快就不能使用了。访问https://github.blog/2020-12-15-token-authentication-requirements-for-git-operations/以获得更多关于建议的变通方法和移除日期的信息。

  2. ssh秘(公)钥生成:
    打开 git bash:

    $ ssh-keygen -t rsa -C "你的邮箱"
    
    

    密钥类型可以用 -t 选项指定。如果没有指定则默认生成用于SSH-2的RSA密钥。这里使用的是rsa。
    同时在密钥中有一个注释字段,用-C来指定所指定的注释,可以方便用户标识这个密钥,指出密钥的用途或其他有用的信息。所以在这里输入自己的邮箱或者其他都行,当然,如果不想要这些可以直接输入:

    $ ssh-keygen
    

    输入完毕后按回车,程序会要求输入一个密码,输入完密码后按回车会要求再确认一次密码,如果不想要密码可以在要求输入密码的时候按两次回车,表示密码为空,并且确认密码为空,此时[c盘>用户>自己的用户名>.ssh]目录下已经生成好了。分别为:id_rsa(私钥),id_rsa.pub(公钥),known_hosts(访问成功过得其他主机),

    将ssh公钥在github或gitee中设置,添加ssh即可

  3. 配置文件名规则?
    config-test.yml ? (测试环境配置文件)

    官网:https://docs.spring.io/spring-cloud-config/docs/current/reference/html/

    不写label,默认master,建议用第三种

    1. /{application}/{profile}[/{label}]
    2. /{application}-{profile}.yml
    3. /{label}/{application}-{profile}.yml
    4. /{application}-{profile}.properties
    5. /{label}/{application}-{profile}.properties

    释义:

    / { 应用名 } / { 环境名 } [ / { 分支名 } ]
    / { 应用名 } - { 环境名 }.yml
    / { 应用名 } - { 环境名 }.properties
    / { 分支名 } / { 应用名 } - { 环境名 }.yml
    / { 分支名 } / { 应用名 } - { 环境名 }.properties

14. SpringCloud Bus 消息总线

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

14.1 概述

  1. 问题?

    假如有多个微服务客户端3355/3366/3377。。 。。 。 。
    每个微服务都要执行一次post请求,手动刷新?
    可否广播,一次通知,处处生效?
    我们想大范围的自动刷新,求方法

  2. 是什么?

    Spring Cloud Bus是用来将分布式系统的节点与轻量级消息系统链接起来的框架,

    它整合了Java的事件处理机制和消息中间件的功能。

    Bus支持两种消息代理:KafkaRabbitMQ(此次使用)

    bilibili 周洋老师的消息中间件ActiveMQ点我SpringCloud( H版 & alibaba )框架开发教程(中级)_第16张图片

  3. 能干嘛?
    Spring Cloud Bus能管理和传播分布式系统间的消息,就像一个分布式执行器,可用于广播状态更改、事件推送等,也可以当作微服务间的通信通道
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第17张图片

  4. 什么是总线?

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

  5. 基本原理?

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

14.2 RabblitMQ环境配置

安装rabbitMQ前必须安装好Erlang,否则无法安装

  1. 下载Erlang(win-64): https://www.erlang.org/downloads
  2. 下载RabbitMQ: https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.18/rabbitmq-server-3.8.18.exe
  3. 进入RabbitMQ安装目录的sbin目录下,下载可视化插件,打开cmd执行:

    rabbitmq-plugins enable rabbitmq_management
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第18张图片

  4. 此时菜单栏就有了RabbitMQ,的启动关闭等工具:
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第19张图片
  5. start启动,stop关闭,remove删除,install安装
    双击start启动,即可在网页中输入http://localhost:15672进入RabbitMQ界面:
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第20张图片
  6. 输入用户名:guest 密码:guest即可登录:
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第21张图片

14.3 SpringCloud Bus 动态刷新全局广播

  1. 必须具备量好的RabbitMQ环境

  2. 以3355为模板制作3366

  3. 设计思想:
    3.1 利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端配置
    3.2 利用消息总线触发一个服务端ConfigServer的/bus/refresh,而刷新所有客户端配置

    触发3344总配置感染3355,3366,而不是,让3355,3366互相传染!!!

  4. 3344 配置中心服务端添加消息总线支持:

    1. pom 添加
          
          <dependency>
              <groupId>org.springframework.cloudgroupId>
              <artifactId>spring-cloud-starter-bus-amqpartifactId>
          dependency>
      
    2. yml 添加
      spring:
        rabbitmq:
      	host: localhost
      	#注意,这里是5672(mq访问端口),不是15672(web访问端口),
      	port: 5672
      	username: guest
      	password: guest
      
      # 暴露bus刷新配置的端点
      management:
        endpoints:
          web:
            exposure:
              include: 'bus-refresh'
      
  5. 3355,3366,跟3344配置相同

  6. 启动7001,查看Eureka注册信息
    在这里插入图片描述

    1. 启动3344,3355,3366三个服务,访问3344自测通过
    2. 访问3355,3366自测通过
    3. 修改gitee的config-dev.yml文件的版本号version
    4. 刷新3344,刷新3355,3366(3355,3366,并没有修改)
    5. 使用post请求http://localhost:3344/actuator/bus-refresh路径,通知3344修改了配置中心,让3344配置中心去通知client和server端,如cmd窗口输入以下内容:
      curl -X POST "http://localhost:3344/actuator/bus-refresh"
      
    6. 刷新3355,3366,发现version已经被修改,测试通过

注意:在发送post请求时,可以再RabbitMQ的web端的Exchanges(交流) -> springCloudBus(春云巴士) 的窗口中看到通知波动:
SpringCloud( H版 & alibaba )框架开发教程(中级)_第22张图片

14.4 SpringCloud Bus 动态刷新定点通知

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

  1. 概述

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

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

  2. 只通知,3355,不通知3366

    1. 修改gitee版本号
    2. 发送post请求curl -X POST "http://localhost:3344/actuator/bus-refresh/cloud-config-client:3355"
  3. 总结:
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第23张图片

15. SpringCloud Stream 消息驱动

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

官网: https://spring.io/projects/spring-cloud-stream

15.1 概述

  1. 什么是springcloud Stream?

    官方定义Spring Cloud Stream是一个构建消息驱动微服务的框架。

    应用程序通过inputs或者 outputs来与Spring Cloud Stream中binder对象交互。
    通过我们配置来binding(绑定),而Spring Cloud Stream的 binder对象负责与消息中间件交互。所以,我们只需要搞清楚如何与Spring Cloud Stream交互就可以方便使用消息驱动的方式。

    通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。
    Spring Cloud Stream为一些供应商的消息中间件产品提供了个性化的自动化配置实现

    引用了发布-订阅、消费组、分区的三个核心概念。

    目前只支持:RabbitMQ,kafka

  2. 为什么引入cloud Stream

    比方说我们用到了RabbitMQ和Kafka,由于这两个消息中间件的架构上的不同,像RabbitMQ有exchange,kafka有Topic和Partitions分区。

    在企业中,使用了过多消息中间件:ActiveMQ,RabbitMQ,RocketMQ,Kafka. 每个平台使用的MQ都不相同,将会碰到切换,维护,开发等不同的难题,有没有一种新的技术?让我们不再关注具体MQ的细节,我们只需要用一种适配绑定的方式,自动的给我们在名种MQ内切换??? Cloud Stream!!!

  3. 设计思想:

    1. 标准MQ:

      谁订阅给谁发
      SpringCloud( H版 & alibaba )框架开发教程(中级)_第24张图片
      生产者/消费者之间靠消息媒介传递信息内容
      消息必须走特定的通道 - 消息通道 Message Channel
      消息通道里的消息如何被消费呢,谁负责收发处理 - 消息通道MessageChannel的子接口SubscribableChannel,由MessageHandler消息处理器所订阅。

    2. steam 凭什么可以统一底层差异?

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

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

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

      SpringCloud( H版 & alibaba )框架开发教程(中级)_第25张图片

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

      Topic主题进行广播

      在RabbitMQ就是Exchange
      在Kakfa中就是Topic

15.2 SpringCloud Stream 标准流程

SpringCloud( H版 & alibaba )框架开发教程(中级)_第26张图片

  • Binder - 很方便的连接中间件,屏蔽差异。
  • Channel - 通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过Channel对队列进行配置。
  • Source和Sink - 简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入。

15.3 主要注解

组成 说明
Middleware 中间件,目前只支持RabbitMQ和Kafka
Binder Binder是应用与消息中间件之间的封装,目前实行了Kafka和RabbitMQ的Binder,
@Input 注解标识输入通道,通过该输乎通道接收到的消息进入应用程序
@Output 注解标识输出通道,发布的消息将通过该通道离开应用程序
@StreamListener 监听队列,用于消费者的队列的消息接收
@EnableBinding 指信道channel和exchange绑定在一起

15.4 案例说明

  1. RabbitMQ环境安装(未安装请查看14.2)
  2. 新建三个子工程:
    cloud-stream-rabbitmq-provider8801 生产者发消息模块
    cloud-stream-rabbitmq-consumer8802 消息接受模块
    cloud-stream-rabbitmq-consumer8803 消息接受模块

15.5 消息驱动之生产者

  1. 创建 cloud-stream-rabbitmq-provider8801 生产者发消息模块

  2. pom :
    rabbit 和 kafka 任选其一

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloudartifactId>
            <groupId>com.xyygroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>cloud-stream-rabbitmq-provider8801artifactId>
    
        <dependencies>
            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-stream-rabbitartifactId>
            dependency>
            
            
            
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-actuatorartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    project>
    
  3. application.yml

    server:
      port: 8801
    
    spring:
      application:
        name: cloud-stream-provider
      cloud:
        stream:
          binders: # 在此处配置要绑定的rabbitmq的服务信息;
            defaultRabbit: # 表示定义的名称,用于于binding整合
              type: rabbit # 消息组件类型
              environment: # 设置rabbitmq的相关的环境配置
                spring:
                  rabbitmq:
                    host: localhost
                    port: 5672
                    username: guest
                    password: guest
          bindings: # 服务的整合处理
            output: # 这个名字是一个通道的名称
              destination: studyExchange # rabbit的Exchanges中的注册名,什么都行
              content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
              #binder此处爆红千万别动,动了无法启动
              binder: defaultRabbit # 设置要绑定的消息服务的具体设置
    
    eureka:
      client: # 客户端进行Eureka注册的配置
        service-url:
          defaultZone: http://localhost:7001/eureka
      instance:
        lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
        lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
        instance-id: stream-8801  # 在信息列表时显示主机名称
        prefer-ip-address: true     # 访问的路径变为IP地址
    
  4. 启动类:StreamRabbitMQMain8801注解@EnableEurekaClient

  5. service:
    创建接口IMessageProvider :

    public interface IMessageProvider {
        String sendInfo();
    }
    

    创建实现类MessageProviderImpl :

    这里为什么是@EnableBinding(Source.class)MessageChannel,请点击我,看官网示例 并且查看15.2 SpringCloud Stream 标准流程

    @EnableBinding(Source.class)    //定义消息推送管道
    @Slf4j
    public class MessageProviderImpl implements IMessageProvider {
    
        //不需要dao,操作数据库,需要的是:RabbitMQ消息中间件
        @Resource
        private MessageChannel output;     //消息发送管道
    
        @Override
        public String sendInfo() {
            String serial = UUID.randomUUID().toString();
            output.send(MessageBuilder.withPayload(serial).build());
            log.info("*********serial: "+serial);
            return serial;
        }
    }
    
  6. controller

    @RestController
    @RequestMapping("stream")
    public class SendMessageController {
    
        @Resource
        private IMessageProvider iMessageProvider;
    
        @RequestMapping("sendMessage")
        public String sendMessage(){
            return iMessageProvider.sendInfo();
        }
    }
    
  7. 测试:
    启动 7001,rabbitMQ,8801(yml的binder: defaultRabbit 缩进会报错)

    登陆rabbitMQ -> Exchanges -> studyExchange (下图最下面那个就是8801中yml定义的destination)
    在这里插入图片描述

    访问8801的sendMessage()路径:http://localhost:8801/stream/sendMessage

    可以看到rabbitMQ页面中会有波动(右上角默认5秒一刷新,请稍等)

    这里可以看到右侧只有Pubish(In),注意,这是因为只有输出,还没有配置输入Publish(Out),这里的输出是8801(in)
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第27张图片

15.6 消息驱动之消费者

  1. 创建子项目: 消息接受者cloud-stream-rabbitmq-consumer8802

  2. pom

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>cloudartifactId>
            <groupId>com.xyygroupId>
            <version>1.0-SNAPSHOTversion>
        parent>
        <modelVersion>4.0.0modelVersion>
    
        <artifactId>cloud-stream-rabbitmq-consumer8802artifactId>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-stream-rabbitartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-actuatorartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    project>
    
  3. application.yml
    除了端口和项目名以外,其他都没有修改(destination的值一定要和8801一样)

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

  5. controllerReceiveMessageListenerController:
    不懂得 请点击我,查看官网示例 并且查看15.2 SpringCloud Stream 标准流程

    @RestController
    //标记接收数据类,
    @EnableBinding(Sink.class)
    public class ReceiveMessageListenerController {
        @Value("${server.port}")
        private String serverPort;
    	
    	//标记接收数据的方法(Message和8801的传输值相同)
        @StreamListener(Sink.INPUT)
        public void input(Message<String> message) {
            System.out.println("消费者8802号,----->接受到的消息: "+message.getPayload()+"\t  port: "+serverPort);
        }
    }
    
  6. 启动8802测试:
    疯狂访问8801的路径:http://localhost:8801/stream/sendMessage
    可以看到8802的控制台打出的内容跟8801发送的内容是一样的,也可以看到rabbitMQ的页面:

    SpringCloud( H版 & alibaba )框架开发教程(中级)_第28张图片
    可以看到输入输出都是相同的速率(之前是没有out蓝色的),并且下面有一个匿名绑定(8802)

15.7 分组消费与持久化 *

SpringCloud( H版 & alibaba )框架开发教程(中级)_第29张图片

  1. 出现的问题?

    创建cloud-stream-rabbitmq-consumer8803按照8802的模板

    访问8801 : http://localhost:8801/stream/sendMessage

    8001发送的消息,8002,8003同时收到了,而8002和8003是集群,产生了重复消费现象!!!这是因为rabbitmq默认每一个服务都是一个组,组流水号不同:
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第30张图片

  2. 解决重复消费

    自定义配置分组
    自定义配置分为同一个组

     微服务应用放置于同一个group中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。
    
  3. 修改8802,8803的yml,同时添加以下内容(也就只有一个group,分成同组)

    spring:
      cloud:
        stream:
          bindings: # 服务的整合处理
            input: # 这个名字是一个通道的名称
              group: stream-consumer
    
  4. 测试:
    访问8801: http://localhost:8801/stream/sendMessage
    查看8002,8003的控制台: 是轮巡打印
    查看rabbit:

    会发现绑定的服务只有一个,并且名字是yml里的内容
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第31张图片
    点击studyExchange.stream-consumer组进去查看,拥有两个不同成员
    在这里插入图片描述

--------------------------------------------------------重要--------------------------------------------------------

注: 就算8801发送前or发送中,8802,8803宕机了,重新启动也是可以自动接收的,rabbit会推送过来

---------------------------------------------------------!!!-----------------------------------------------------------

16. SpringCloud Sleuth 分布式请求链路跟踪

官网: https://spring.io/projects/spring-cloud-sleuth

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

16.1 概述

  1. 为什么要 Sleuth 分布式请求链路跟踪?

    (走过了多少微服务,走过了多少节点?)

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

  2. 是什么?

    将跟踪和跨度 ID 添加到 Slf4J MDC,因此您可以从日志聚合器中的给定跟踪或跨度中提取所有日志。

    检测 Spring 应用程序的常见入口和出口点(servlet 过滤器、rest 模板、计划操作、消息通道、伪装客户端)。

    如果spring-cloud-sleuth-zipkin可用,则应用程序将通过 HTTP生成并报告与Zipkin兼容的跟踪。默认情况下,它将它们发送到本地主机(端口 9411)上的 Zipkin 收集器服务。使用 配置服务的位置spring.zipkin.baseUrl。

  3. zipkin下载安装
    可以直接使用pom文件管理

     
     <dependency>
         <groupId>org.springframework.cloudgroupId>
         <artifactId>spring-cloud-starter-zipkinartifactId>
     dependency>
    

    或者下载运行jar
    下载地址, 我用的是2.12.9-exec.jar:
    运行jar -> cmd -> java -jar zipkin-server-2.12.9-exec.jar
    浏览器访问: http://localhost:9411
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第33张图片

16.2 搭建链路监控步骤

完整的调用链路

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

SpringCloud( H版 & alibaba )框架开发教程(中级)_第34张图片
—条链路通过Trace ld唯一标识,Span标识发起的请求信息,各span通过parent id关联起来。
SpringCloud( H版 & alibaba )框架开发教程(中级)_第35张图片
整个链路的依赖关系如下:
SpringCloud( H版 & alibaba )框架开发教程(中级)_第36张图片

名词解释

  • Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识
  • span:表示调用链路来源,通俗的理解span就是一次请求信息

16.3 搭建分布式请求监控

  1. pom, 以下三个微服务全部添加
    cloud-consumer-order80
    cloud-provider-payment8001
    cloud-provider-payment8002
    
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-zipkinartifactId>
    dependency>
    
  2. application.yml 以下三个微服务全部添加
    cloud-consumer-order80
    cloud-provider-payment8001
    cloud-provider-payment8002
    spring:
      zipkin: #注册目标地址
        base-url: http://localhost:9411
        sleuth: #<-------------------------------------关键
          sampler:
          #采样率值介于 0 到 1 之间,1 则表示全部采集
          probability: 1
    
  3. 测试访问:启动7001(7002),8001,8001,80
    访问地址: http://localhost/order/consumer/get/1
    打开zipkin的页面: http://localhost:9411/
    这里可以看到所有访问过的地址
    SpringCloud( H版 & alibaba )框架开发教程(中级)_第37张图片
    包括每个服务的执行时间
    在这里插入图片描述

中级到此结束,接下来是springcloud alibaba 高级篇

你可能感兴趣的:(java,cloud,java,cloud,微服务)