22-07-22 西安 SpringCloud(02) Hystrix断路器、jmeter并发测试、Gateway网关、zipkin+sleuth 分布式链路请求跟踪

发布于:2023-02-17 ⋅ 阅读:(1153) ⋅ 点赞:(0)

你可能非常勤奋,但是在趋势面前,一个人的努力相对来说会显得很渺小。一片树叶从树上落下,你说是因为风的追求,还是因为树的不挽留?其实都不是,造成这个结果最重要的原因,叫作“季节”。         


Hystrix开源库

1、雪崩效应和Hystrix

如果扇出的链路上某个微服务的调用响应的时间过长或者不可用,对微服A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的"雪崩效应"。

扇出

多个微服务之间调用的时候,假如微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的"扇出"。

服务的调用大量的积压,产生大量的调用等待和重试调用,慢慢会耗尽服务资源比如内存或CPU,然后也down掉。如果不考虑服务的熔断和限流,就是雪崩的源头

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

=========

Hystrix

Hystrix是一个用于处理分布式系统的延迟容错的开源库

在分布式(不同服务器部署不同业务的微服务)系统里,许多依赖不可避免的会调用失败,比如超时异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

解决思路:

向调用方返回一个符合预期的、可处理的备选响应(Fallback),而不是长时间的等待或者抛出调用方无法处理的异常。

遗憾的是,Hystrix官宣,停更进维


2、服务降级 Fallback

服务降级就是不让客户端等待并返回一个友好提示,比如:服务器忙,请稍候再试

触发降级最常见的情况

  1. 程序运行异常
  2. 超时自动降级
  3. 服务熔断触发服务降级
  4. 线程池/信号量打满也会导致服务降级
  5. 人工降级

搭建好我们的工程,cloud-provider-hystrix-payment8001

引入依赖:当然还有很多别的启动器与依赖。。有点多就不写了

        <!--新增hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

编写配置文件

server:
  port: 8001

spring:
  application:
    name: cloud-hystrix-payment-service  #服务名

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka/

主启动类:

@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }
}

再写一堆乱七八糟的service层和controller层后,

再创建工程 cloud-consumer-feign-hystrix-order80

又是索然无味的那三步骤,导入依赖,编写配置,创建主启动类(真给我写烦了)

引入依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

编写配置文件

server:
  port: 80

spring:
  application:
    name: cloud-consumer-hystrix-order-service

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka/

ribbon:
  ReadTimeout:  6000 #读取
  ConnectTimeout: 6000  #连接

主启动类,使用openFeign远程调用

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class,args);
    }
}

因为我们在服务端的方法做了手脚

    public String payment_Timeout(Integer id){
        int timeNumber = 5;
        try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_TimeOut,id:  "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;
    }

所以消费者访问的时候是要等5秒的,又因为在消费者配置文件里配置了feign超时时间是6s,所以此时浏览器是能在5秒后看到页面效果的。

那么当并发量高的时候,还有这样的效果吗?


3、Jmeter压测测试

模拟真正的大量并发请求

下载Jmeter:Apache JMeter - Download Apache JMeter

当然,我是有老师直接给的工具的,双击那个jar包或者 在小黑窗里 java -jar jar包路径运行。(swing做界面)

1.解压apache-jmeter-5.4.zip文件至目录下(不要有空格和中文)

 2.配置环境变量

我的电脑----》属性----》高级----》环境变量----》在系统变量中----》点击新建JMETER_HOME,

   变量名输入:JMETER_HOME 

   变量输入:E:\server\jmeter\apache-jmeter-5.4.1\apache-jmeter-5.4.1

3、编辑CLASSPATH变量

加上下面这行,然后确定

%JMETER_HOME%\lib\ext\ApacheJMeter_core.jar;%JMETER_HOME%\lib\jorphan.jar;%JMETER_HOME%\lib\logkit-2.0.jar;

4、点击Jmeter中bin目录下面的jmeter.bat文件即可打开Jmeter了。(Linux运行Jmeter.sh)

 接着会打开俩个窗口:Jmeter的命令窗口和Jmeter的图形操作界面,不可以关闭命令窗口

5.换一下语言 


开启Jmeter

如下是后续补充:

线程组右键,查看结果树

 效果变成了

结果树中大概就长这样

1.添加线程组

2.来20000个并发压死8001

3. 添加取样器  http请求,20000个请求都去访问paymentInfo_TimeOut服务

先在浏览器测试 这是可以访问成功的 http://localhost:8001/payment/hystrix/timeout/1

4、保存测试计划,下次启动的时候也能用

现在我们在保证访问http://localhost/consumer/payment/hystrix/timeout/2

 可以在5秒后成功访问到

接着开启并发压力测试,

再在浏览器访问相同的http://localhost/consumer/payment/hystrix/timeout/2 

结果就访问不了了,这就是高并发威力

为什么会打圈呢,最终导致消费端超时报错?

Tomcat是一个Web应用服务器,同时也是一个Servlet/JSP容器。Tomcat作为Servlet容器,负责处理客户端请求,把请求传送给Servlet,并将Servlet的响应返回给客户端。

tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。

server:
  tomcat:
    max-connections: 10000 #最大连接数,默认为10000
    accept-count: 100 # 最大连接等待数,默认100 
    max-threads: 200  #最大工作线程数,默认200
    min-spare-threads: 10 #最小工作线程数,默认10
每一次HTTP请求到达Web服务器,Web服务器都会创建一个线程来处理该请求.最大工作线程数:(4核8g内存,线程数800,一般是 核数*200 )

4、服务降级处理

解决思路:

超时不再等待,宕机或程序运行出错要有兜底

什么时候降级

  1.   对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
  2.   对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级
  3.   对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

3.1  8001提供端的服务降级

@HystrixCommand

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

我们让线程睡眠15秒,这样就达到了降级的要求,超时和算数异常,都会走兜底方法payment_TimeoutHandler——服务降级

    //fallbackMethod 降级方法的名字
    //timeoutInMilliseconds 当前方法最大的等待时间(超过5秒立刻降级)
    @HystrixCommand(fallbackMethod = "payment_TimeoutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000") //5秒钟以内就是正常的业务逻辑
    })
    public String payment_Timeout(Integer id){
        //int timeNumber = 5;
        int timeNumber = 15; //模拟非正常情况
        //int i = 1/0 ; //模拟非正常情况
        try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_TimeOut,id:  "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;
    }



    //兜底方法,上面方法出问题,我来处理,返回一个出错信息
    //注意1、返回值类型与目标方法一致
    //2.让方法参数与目标方法一致(我是上面方法的备胎)
    public String payment_TimeoutHandler(Integer id) {
        return "线程池:"+Thread.currentThread().getName()+" payment_TimeoutHandler,系统繁忙,请稍后再试\t o(╥﹏╥)o ";
    }

测试:http://localhost/consumer/payment/hystrix/timeout/2

此时浏览器页面返回的就是我们自定义的错误页面了,越来越有那个味道了,嗯~~~


3.2  80消费端降级处理

服务降级可以在服务提供者侧,也可以在服务消费者侧。更多是在服务消费者侧

下面配置是默认开启的,所以不用配置,知道有就好了

feign:
  hystrix:
    enabled: true #如果处理自身的容错就开启(使用自己的降级处理,默认开启)。开启方式与生产端不一样。

再在消费端的主启动类加注解@EnableHystrix,消费端的Controller中加入降级处理如下:

    @HystrixCommand(fallbackMethod = "payment_TimeoutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="2000")//超过2秒就降级自己
    })
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.payment_Timeout(id);
        log.info("*******result:"+result);
        return result;
    }

    //兜底方法,上面方法出问题,我来处理,返回一个出错信息
    public String payment_TimeoutHandler(Integer id) {
        return "我是消费者80,对方支付系统繁忙请10秒后再试。或自己运行出错,请检查自己。";
    }

再次测试

默认是1秒后,返回我消费端自己的降级处理

存在问题,我们改一下,消费端的降级处理中的等待时间为3秒 

再次测试,发现是1秒就走了消费端的兜底方法?为什么呢

因为咱没配置开启,所以即使表面上看的是3000,实际上用的是默认的1秒。


5、hystrix超时配置

9000就是消费端等待提供端的时间。

在配置文件里开启了hystrix的超时时间为9000.

以下纯属我自己的憨憨想法。。

消费端80 降级处理器等3秒,服务端8001 降级处理器等5秒

1、线程在服务端睡了13秒,那么此时走的降级处理器是消费端自己的

2、那要是线程睡了4秒,还是选择消费端的自己的降级处理器

那为啥还要在服务端配置降级处理器,都用不到它的兜底方法?

#hystrix的超时时间
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 9000

再去访问

http://localhost/consumer/payment/hystrix/timeout/2

那就好使了


6、全局降级处理器(优化)

通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量

1、每个方法配置一个服务降级方法,技术上可以,但实际上傻X

2、除了个别重要核心业务有专属,其它普通的可以通过统一跳转到统一处理结果页面

@DefaultProperties(defaultFallback = "全局降级处理方法") 写在类上

@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")  //全局的
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

    //超时降级演示
    @HystrixCommand(commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")//超过1.5秒就降级自己
    })
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.payment_Timeout(id);
        log.info("*******result:"+result);
        return result;
    }

    //下面是全局默认fallback方法
    public String payment_Global_FallbackMethod(){
        return "Global异常处理信息,请稍后再试,(┬_┬)";
    }
}

测试


7、服务端宕机或关闭的降级处理

当然你也不写,那就是混在一块了。超时、报错、服务器宕机都用同一套降级处理方法。为了追求完美,单独处理服务器宕机的情况

@Component
public class  PaymentFallbackService implements PaymentHystrixService{
    @Override
    public String paymentInfo_OK(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_OK , (┬_┬)";
    }

    @Override
    public String payment_Timeout(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_TimeOut , (┬_┬)";
    }
}

顺便告诉他,服务器宕机的话要走得降级处理器在哪


服务熔断与服务监控

1、熔断机制

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

熔断机制的注解还是@HystrixCommand

Hystrix会监控微服务间调用的状态,当失败的调用到一定阈值,缺省是10秒内20次调用并有50%的失败情况,就会启动熔断机制

熔断类型:

  1. closed 是通的
  2. open 不通
  3. half open (reset timeout 倒计时),会尝试

熔断半开:断路器保持在开路状态一段时间后(默认5秒),自动切换到半开路状态(HALF-OPEN),此时会判断下一次请求的返回情况,如果请求成功,断路器切回闭路状态(CLOSED),否则重新切换到开路状态(OPEN).

断路器会直接切断请求链,避免发送大量无效请求影响系统吞吐量,并且断路器有自我检测并恢复的能力,


2、断路器参数配置

断路器的三个重要参数:快照时间窗、请求总数阈值、错误百分比阈值。

1、快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。

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

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

circuitBreaker.enabled,value = "true"   是否开启断路器
circuitBreaker.requestVolumeThreshold,value = "10" 当快照时间窗(默认10秒)内达到此数量才有资格打开断路,默认20个
circuitBreaker.sleepWindowInMilliseconds,value = "10000" 休眠时间窗,即断路多久以后切换到半开路状态,开始尝试恢复,默认5s
circuitBreaker.errorThresholdPercentage,value = "60" 出错百分比阈值,当达到此阈值后,开始短路。默认50%
    //服务熔断
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),  //是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),   //当在配置时间窗口内达到此数量,达到错误百分比阈值,默认20个
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),  //断路多久以后开始尝试是否恢复,默认5s
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),//出错百分比阈值,当达到此阈值后,开始短路。默认50%
    })
    public String paymentCircuitBreaker(Integer id){
        if (id < 0){
            throw new RuntimeException("*****id 不能负数");
        }
        String serialNumber = IdUtil.simpleUUID();//hutool.cn工具包

        return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
    }

    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
        return "id 不能负数,请稍候再试,(┬_┬)/~~     id: " +id;
    }

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


3、hystrix自动恢复功能

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

熔断测试:

controller加一个方法

    //===服务熔断
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
        String result = paymentService.paymentCircuitBreaker(id);
        log.info("*******result:"+result);
        return result;
    }

1、浏览器输入正确的id

2、浏览器输入错误的id(会服务降级处理FallBack)

3、触发熔断,10s内起码错误的请求占比60%以上,

4、 再回去输入正确的id,发现也不好使了(已经熔断了)

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


4、服务监控hystrixDashboard

创建新的工程  cloud-consumer-hystrix-dashboard9001

1、引入依赖

!--新增hystrix dashboard-->
dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
/dependency>
dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-actuator</artifactId>
/dependency>

2、配置文件

server:
  port: 9001
hystrix:
  dashboard:
    proxy-stream-allow-list: "localhost"

3、主启动

4.被监控服务要引入的依赖

actuator 直译为“执行器”

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

5.在被监控服务主启动类指定监控路径

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }

    /**
     *此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
     *ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
     *只要在自己的项目里配置上下面的servlet就可以了
     */
    @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;
    }
}

浏览器输入网址测试: http://localhost:9001/hystrix

 点击Monitor Stream后,跳转到如下界面 

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

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

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


Gateway网关

1、网关的位置与作用

官网:Spring Cloud Gateway

GetewayZuul的替代

  • Zuul:路由和过滤
  • Zuul最终还是会注册到Eureka

Zuul网关采用同步阻塞模式不符合要求。
Spring Cloud Gateway基于Webflux,比较完美地支持异步非阻塞编程,很多功能实现起来比较方便。

一、相同点:

1、底层都是servlet

2、两者均是web网关,处理的是http请求

二、不同点:

1、内部实现:

  gateway对比zuul多依赖了spring-webflux,在spring的支持下,功能更强大,内部实现了限流、负载均衡等,扩展性也更强,但同时也限制了仅适合于Spring Cloud套件
  zuul则可以扩展至其他微服务框架中,其内部没有实现限流、负载均衡等。
2、是否支持异步
  zuul仅支持同步
  gateway支持异步。理论上gateway则更适合于提高系统吞吐量(但不一定能有更好的性能),最终性能还需要通过严密的压测来决定
3、框架设计的角度
  gateway具有更好的扩展性,并且其已经发布了2.0.0的RELESE版本,稳定性也是非常好的
4、性能
  WebFlux 模块的名称是 spring-webflux,名称中的 Flux 来源于 Reactor 中的类 Flux。Spring webflux 有一个全新的非堵塞的函数式 Reactive Web 框架,可以用来构建异步的、非堵塞的、事件驱动的服务,在伸缩性方面表现非常好。使用非阻塞API。 Websockets得到支持,并且由于它与Spring紧密集成,所以将会是一个更好的 开发 体验。
  Zuul 1.x,是一个基于阻塞io的API Gateway。Zuul已经发布了Zuul 2.x,基于Netty,也是非阻塞的,支持长连接,但Spring Cloud暂时还没有整合计划。

三、总结
  总的来说,在微服务架构,如果使用了Spring Cloud生态的基础组件,则Spring Cloud Gateway相比而言更加具备优势,单从流式编程+支持异步上就足以让开发者选择它了。
  对于小型微服务架构或是复杂架构(不仅包括微服务应用还有其他非Spring Cloud服务节点),zuul也是一个不错的选择。

Gateway是一个web项目,本质也是微服务(可当做过滤器使用),也是微服务的入口。

Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断限流重试

使用要求
基于Spring5,SpringBoot2

SpringCloud Gatway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通讯框架Netty

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

统一的路由方式且基于Filter链

网关的位置
nginx做负载均衡。(nginx没法写业务)

nginx去找网关。

网关的作用负载均衡、过滤请求、验证令牌(统一鉴权)、 全局熔断


2、GateWay工作原理

网关原理:经过各个过滤器的过滤之后,将满足指定断言规则的请求路由到指定位置 


 

1.客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求匹配的路由,
将其发送到Gateway Web Handler.

2.Handler再通过指定的过滤器链来将请求发送给我们实际的服务执行业务逻辑,然后返回。

上图中过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前("pre")或之后("post")执行业务逻辑。

Filter在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,

在"post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量控制等有着非常重要的作用

3、动态路由(通过服务名实现)

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

创建新的工程:cloud-gateway-gateway9527,又是那无聊至极的3步

1、引入依赖

        <!--新增gateway,不需要引入web和actuator模块-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

2、配置文件

server:
  port: 9527
spring:
  application:
    name: cloud-gateway

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
      register-with-eureka: true
      fetch-registry: true
      service-url:
          defaultZone: http://localhost:7001/eureka

3、主启动

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
    public static void main(String[] args) {
            SpringApplication.run( GateWayMain9527.class,args);
        }
}

网关做路由映射,就在配置文件中

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
       - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
         uri: http://localhost:8001   #匹配后提供服务的路由地址
         predicates:
           - Path=/payment/get/**   #断言,路径相匹配的进行路由
 
       - id: payment_routh2
         uri: http://localhost:8001
         predicates:
           - Path=/payment/lb/**   #断言,路径相匹配的进行路由

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka

按道理说,网关后面应该跟的是消费端,现在咱不那么麻烦,直接让网关路由到提供端8001。

启动测试:

当然,你要是直接访问8001也是好使的,老师所说的隐藏我也不觉得安全啊!,照样能访问

动态路由

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

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

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true  #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://CLOUD-PAYMENT-SERVICE
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由

使用网关访问,

第一次

第二次 


4、断言(Predicate)的使用

如果请求与断言规则相匹配则进行路由

Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合

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

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true  #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://CLOUD-PAYMENT-SERVICE
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由
            #- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]  没到时间进行测试会报错
            #- Cookie=username,xiaoyumao #并且Cookie是username=xiaoyumao才能访问
            #- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
            #- Host=**.atguigu.com
            #- Method=GET
            #- Query=username, \d+ #要有参数名称并且是正整数才能路由

5、Filter的使用

断言predicates):满足指定的要求就开始工作;

路由Route):满足断言的条件就路由到这个地方;

过滤器(Filter):到达指定地方前后由所有过滤器一起工作;

使用过滤器,可以在请求被路由前或者之后对请求进行修改

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

内置过滤器都是动态为请求添加一些行为

在路由配置中加入过滤器,这个演示的路由过滤器会给请求加一个请求头

当然是可以同时存在的,断言是判断请求对不对,过滤器是动态添加请求头

我们在8001服务的controller中获取并打印一下

String header = request.getHeader("X-Request-red");
System.out.println("header = "+header);

自定义全局过滤器   implements 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("username");
        if(StringUtils.isEmpty(uname)){
            log.info("*****用户名为Null 非法用户,(┬_┬)");
            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/35

 

当你加一个请求参数username,访问的结果就又不一样了

此时:不管是自定义的过滤器还是内置的过滤器,都是会走的,只是在俩个不同的控制台,一个在9527,一个在8001。。。我当时还纠结了好久怎么只有其中一个过滤器生效呢,服了


6、gateway如何实现限流?

1. Spring Cloud Gateway使用令牌桶算法实现限流。

2. Spring Cloud Gateway默认使用redis的Ratelimter限流算法来实现。所以我们要使用首先需要引入redis的依赖。

3.使用过程中,我们主要配置    令牌桶填充的速率,令牌桶的容量,指定限流的Key。

4.限流的Key,可以根据用户来限流  、IP限流、接口限流等等。

============

令牌桶算法(Token Bucket)

随着时间流逝系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token;如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务

"令牌桶算法”在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在"令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,所以它适合于具有突发特性的流量。

以redis限流IP为例

1,引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

2,IP限流的keyResolver

@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono. just(exchange. getRequest(). getRemoteAddress(). getHostName());
}

3,配置文件

filters :
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@ipKeyResolver}"

flter名称必须是RequestRateLimiter

redis-rate-limiter.replenishRafe :允许用户每秒处理多少个请求

redis-rate-limiter.burstCapacity :令牌桶的容量,允许在-秒钟内完成的最大请求数

key-resolver :使用SpEL按名称引用bean

=============================

漏桶(Leaky Bucket)算法 

水(请求)先进入到漏桶内,桶以一定的速度出水(接口的响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法强行限制数据的传输速率


Sleuth+Zipkin 分布式链路请求跟踪

1、分布式系统调用跟踪

在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生最后的请求结果

每一个前端请求都会形成一个复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。

Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案,在分布式系统中提供追踪解决方案并且兼容支持了zipkin(负责展现) ;

分布式链路追踪:

sleuth:采集当前项目被访问时的数据

zipkin:sleuth 将数据提交给zipkin,zipkin 提供了图形界面 可以查询sleuth采集的数据


2、安装启动Zipkin

直接启动这个jar包:zipkin-server-2.23.4.jar

 浏览器访问:http://your_host:9411/zipkin/


3、完整的调用链路测试

一条链路通过Trace Id唯一标识,Span标识发起的请求信息,各span通过parent id 关联起来,通俗的理解span就是一次请求信息

(1)Trace它是由一组有相同Trace ID的Span串联形成一个树状结构。为了实现请求跟踪,当请求请求到分布式系统的入口端点时,只需要服务跟踪框架为该请求创建一个唯一的跟踪标识(即前文提到的Trace ID),同时在分布式系统内部流转的时候,框架始终保持传递该唯一标识,直到返回请求为止,我们通过它将所有请求过程中的日志关联起来;


(2)Span它代表了一个基础的工作单元,例如服务调用。为了统计各处理单元的时间延迟,当前请求到达各个服务组件时,也通过一个唯一标识(即前文提到的Span ID)来标记它的开始、具体过程以及结束。通过span的开始和结束的时间戳,就能统计该span的时间延迟,除此之外,我们还可以获取如事件名称、请求信息等元数据。

修改服务提供者cloud-provider-payment8001

1.加入核心依赖

sleuth对数据采集,汇报给zipkin

<!--包含了sleuth+zipkin-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

2.修改application.yaml配置文件

spring:
  application:
    name: cloud-payment-service
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      #采样率值介于0~1之间,1表示全部采样
      probability: 1

如果服务的流量很大,全部采集对传输、存储压力比较大。这个时候可以设置采样率,sleuth可以通过配置 spring.sleuth.sampler.probability=X.Y(如配置为1.0,则采样率为100%,采集服务的全部追踪数据),若不配置默认采样率是0.1(即10%)

修改服务消费者 cloud-consumer-order80

1、引入依赖

<!--包含了sleuth+zipkin-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

2、修改yml配置文件

spring:
  application:
    name: cloud-consumer-order80
  zipkin:
    base-url: http://localhost:9411
  sleuth:
    sampler:
      #采样率值介于0~1之间,1表示全部采样
      probability: 1

测试浏览器输入: http://localhost/consumer/payment/get/31


4、sleuth和zipkin作用及总结

1.提供链路追踪。通过sleuth可以很清楚的看出一个请求都经过了哪些服务;可以很方便的理清服务间的调用关系。

2.可视化错误。对于程序未捕捉的异常,可以结合zipkin分析。

3.分析耗时。通过sleuth可以很方便的看出每个采样请求的耗时,分析出哪些服务调用比较耗时。当服务调用的耗时随着请求量的增大而增大时,也可以对服务的扩容提供一定的提醒作用。

4.优化链路。对于调用频繁的服务,可以并行调用或针对业务做一些优化措施等。


网站公告

今日签到

点亮在社区的每一天
去签到