【JAVA企业级开发】全网最详细的一篇微服务雪崩效应治理组件Sentinel,Hystrix,Resilience4j各自应用过程和功能特点介绍以及微服务治理技术选型的更替,整合。

一级目录

二级目录

三级目录

一微服务雪崩效应

1服务雪崩效应情景概括

分布式系统中经常会出现某个基础服务不可用造成整个系统不可用的情况

2服务雪崩效应的定义

服务雪崩效应是一种因 服务提供者 的不可用导致 服务调用者 的不可用,并将不可用 逐渐放大 的过程

3 服务雪崩效应形成的原因

我把服务雪崩的参与者简化为 服务提供者 和 服务调用者, 并将服务雪崩产生的过程分为以下三个阶段来分析形成的原因:

①服务提供者不可用

服务不可用 的原因有:

硬件故障

程序Bug

缓存击穿

用户大量请求

硬件故障可能为硬件损坏造成的服务器主机宕机, 网络硬件故障造成的服务提供者的不可访问.
缓存击穿一般发生在缓存应用重启, 所有缓存被清空时,以及短时间内大量缓存失效时. 大量的缓存不命中, 使请求直击后端,造成服务提供者超负荷运行,引起服务不可用.
在秒杀和大促开始前,如果准备不充分,用户发起大量请求也会造成服务提供者的不可用.

②重试加大流量

重试加大流量 的原因有:

用户重试

代码逻辑重试

在服务提供者不可用后, 用户由于忍受不了界面上长时间的等待,而不断刷新页面甚至提交表单.
服务调用端的会存在大量服务异常后的重试逻辑.
这些重试都会进一步加大请求流量.

③服务调用者不可用

服务调用者不可用 产生的主要原因是:

同步等待造成的资源耗尽

当服务调用者使用 同步调用 时, 会产生大量的等待线程占用系统资源. 一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了.

4服务雪崩的应对策略

针对造成服务雪崩的不同原因, 可以使用不同的应对策略:

①流量控制

流量控制 的具体措施包括:

网关限流

用户交互限流

关闭重试

因为Nginx的高性能, 目前一线互联网公司大量采用Nginx+Lua的网关进行流量控制, 由此而来的OpenResty也越来越热门.

用户交互限流的具体措施有:

  1. 采用加载动画,提高用户的忍耐等待时间.
  2. 提交按钮添加强制等待时间机制.

②改进缓存模式

改进缓存模式 的措施包括:

缓存预加载

同步改为异步刷新

③服务自动扩容

服务自动扩容 的措施主要有:

AWS的auto scaling

④服务调用者降级服务

服务调用者降级服务 的措施包括:

资源隔离:

资源隔离主要是对调用服务的线程池进行隔离.

对依赖服务进行分类:

我们根据具体业务,将依赖服务分为: 强依赖和弱依赖. 强依赖服务不可用会导致当前业务中止,而弱依赖服务的不可用不会导致当前业务的中止.

不可用服务的调用快速失败:

不可用服务的调用快速失败一般通过 超时机制, 熔断器熔断后的 降级方法 来实现.

二Hystrix

1 2018年Hystrix停更消息

在这里插入图片描述

如果还没有用过Hystrix的同学,大可不必学习Hystrix,请直接看下两个替代组件。

这里我也不过多介绍,只介绍一下概念

2使用Hystrix预防服务雪崩

Hystrix [hɪst’rɪks]的中文含义是豪猪, 因其背上长满了刺,而拥有自我保护能力. Netflix的 Hystrix 是一个帮助解决分布式系统交互时超时处理和容错的类库, 它同样拥有保护系统的能力

Hystrix的设计原则包括:

①资源隔离

货船为了进行防止漏水和火灾的扩散,会将货仓分隔为多个,

种资源隔离减少风险的方式被称为:Bulkheads(舱壁隔离模式).
Hystrix将同样的模式运用到了服务调用者上.

在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务,比如:
商品详情展示服务会依赖商品服务, 价格服务, 商品评论服务

调用三个依赖服务会共享商品详情服务的线程池. 如果其中的商品评论服务不可用, 就会出现线程池里所有线程都因等待响应而被阻塞, 从而造成服务雪崩

Hystrix通过将每个依赖服务分配独立的线程池进行资源隔离, 从而避免服务雪崩.
当商品评论服务不可用时, 即使商品服务独立分配的20个线程全部处于同步等待状态,也不会影响其他依赖服务的调用

②熔断器

服务的健康状况 = 请求失败数 / 请求总数.
熔断器开关由关闭到打开的状态转换是通过当前服务健康状况和设定阈值比较决定的.

当熔断器开关关闭时, 请求被允许通过熔断器. 如果当前健康状况高于设定阈值, 开关继续保持关闭. 如果当前健康状况低于设定阈值, 开关则切换为打开状态.

当熔断器开关打开时, 请求被禁止通过.

当熔断器开关处于打开状态, 经过一段时间后, 熔断器会自动进入半开状态, 这时熔断器只允许一个请求通过. 当该请求调用成功时, 熔断器恢复到关闭状态. 若该请求失败, 熔断器继续保持打开状态, 接下来的请求被禁止通过.

熔断器的开关能保证服务调用者在调用异常服务时, 快速返回结果, 避免大量的同步等待. 并且熔断器能在一段时间后继续侦测请求执行结果, 提供恢复服务调用的可能.

③命令模式

Hystrix使用命令模式(继承HystrixCommand类)来包裹具体的服务调用逻辑(run方法), 并在命令模式中添加了服务调用失败后的降级逻辑(getFallback).

同时我们在Command的构造方法中可以定义当前服务线程池和熔断器的相关参数. 如下代码所示:

public class Service1HystrixCommand extends HystrixCommand<Response> {
  private Service1 service;
  private Request request;

  public Service1HystrixCommand(Service1 service, Request request){
    supper(
      Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ServiceGroup"))
          .andCommandKey(HystrixCommandKey.Factory.asKey("servcie1query"))
          .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("service1ThreadPool"))
          .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
            .withCoreSize(20))//服务线程池数量
          .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
            .withCircuitBreakerErrorThresholdPercentage(60)//熔断器关闭到打开阈值
            .withCircuitBreakerSleepWindowInMilliseconds(3000)//熔断器打开到关闭的时间窗长度
      ))
      this.service = service;
      this.request = request;
    );
  }

  @Override
  protected Response run(){
    return service1.call(request);
  }

  @Override
  protected Response getFallback(){
    return Response.dummy();
  }
}

在使用了Command模式构建了服务对象之后, 服务便拥有了熔断器和线程池的功能.

Hystrix的内部处理逻辑

构建Hystrix的Command对象, 调用执行方法.

Hystrix检查当前服务的熔断器开关是否开启, 若开启, 则执行降级服务getFallback方法.

若熔断器开关关闭, 则Hystrix检查当前服务的线程池是否能接收新的请求, 若超过线程池已满, 则执行降级服务getFallback方法.

若线程池接受请求, 则Hystrix开始执行服务调用具体逻辑run方法.

若服务执行失败, 则执行降级服务getFallback方法, 并将执行结果上报Metrics更新服务健康状况.

若服务执行超时, 则执行降级服务getFallback方法, 并将执行结果上报Metrics更新服务健康状况.

若服务执行成功, 返回正常结果.

若服务降级方法getFallback执行成功, 则返回降级结果.

若服务降级方法getFallback执行失败, 则抛出异常.
Hystrix Metrics的实现
Hystrix的Metrics中保存了当前服务的健康状况, 包括服务调用总次数和服务调用失败次数等. 根据Metrics的计数, 熔断器从而能计算出当前服务的调用失败率, 用来和设定的阈值比较从而决定熔断器的状态切换逻辑. 因此Metrics的实现非常重要.

Hystrix1.4之前的滑动窗口实现
Hystrix在这些版本中的使用自己定义的滑动窗口数据结构来记录当前时间窗的各种事件(成功,失败,超时,线程池拒绝等)的计数.
事件产生时, 数据结构根据当前时间确定使用旧桶还是创建新桶来计数, 并在桶中对计数器经行修改. 
这些修改是多线程并发执行的, 代码中有不少加锁操作,逻辑较为复杂.

Hystrix1.5之后的滑动窗口实现
Hystrix在这些版本中开始使用RxJava的Observable.window()实现滑动窗口.
RxJava的window使用后台线程创建新桶, 避免了并发创建桶的问题.
同时RxJava的单线程无锁特性也保证了计数变更时的线程安全. 从而使代码更加简洁. 
以下为我使用RxJava的window方法实现的一个简易滑动窗口Metrics, 短短几行代码便能完成统计功能,足以证明RxJava的强大:
@Test
public void timeWindowTest() throws Exception{
  Observable<Integer> source = Observable.interval(50, TimeUnit.MILLISECONDS).map(i -> RandomUtils.nextInt(2));
  source.window(1, TimeUnit.SECONDS).subscribe(window -> {
    int[] metrics = new int[2];
    window.subscribe(i -> metrics[i]++,
      InternalObservableUtils.ERROR_NOT_IMPLEMENTED,
      () -> System.out.println("窗口Metrics:" + JSON.toJSONString(metrics)));
  });
  TimeUnit.SECONDS.sleep(3);
}

简单测试
引入jar包:

 

   <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-hystrix</artifactId>
        <version>1.4.3.RELEASE</version>
    </dependency>

导入配置:

server:
  port: 11111
  
#default可替换
hystrix:
  command:
    default:
      execution:
        isolation:
          #线程池隔离还是信号量隔离 默认是THREAD 信号量是SEMAPHORE
          strategy: THREAD
          semaphore:
            #使用信号量隔离时,支持的最大并发数 默认10
            maxConcurrentRequests: 10
          thread:
            #command的执行的超时时间 默认是1000
            timeoutInMilliseconds: 1000
            #HystrixCommand.run()执行超时时是否被打断 默认true
            interruptOnTimeout: true
            #HystrixCommand.run()被取消时是否被打断 默认false
            interruptOnCancel: false
        timeout:
          #command执行时间超时是否抛异常 默认是true
          enabled: true
        fallback:
          #当执行失败或者请求被拒绝,是否会尝试调用hystrixCommand.getFallback()
          enabled: true
          isolation:
            semaphore:
              #如果并发数达到该设置值,请求会被拒绝和抛出异常并且fallback不会被调用 默认10
              maxConcurrentRequests: 10
      circuitBreaker:
        #用来跟踪熔断器的健康性,如果未达标则让request短路 默认true
        enabled: true
        #一个rolling window内最小的请求数。如果设为20,那么当一个rolling window的时间内
        #(比如说1个rolling window是10秒)收到19个请求,即使19个请求都失败,也不会触发circuit break。默认20
        requestVolumeThreshold: 5
        # 触发短路的时间值,当该值设为5000时,则当触发circuit break后的5000毫秒内
        #都会拒绝request,也就是5000毫秒后才会关闭circuit,放部分请求过去。默认5000
        sleepWindowInMilliseconds: 5000
        #错误比率阀值,如果错误率>=该值,circuit会被打开,并短路所有请求触发fallback。默认50
        errorThresholdPercentage: 50
        #强制打开熔断器,如果打开这个开关,那么拒绝所有request,默认false
        forceOpen: false
        #强制关闭熔断器 如果这个开关打开,circuit将一直关闭且忽略
        forceClosed: false
      metrics:
        rollingStats:
          #设置统计的时间窗口值的,毫秒值,circuit break 的打开会根据1个rolling window的统计来计算。若rolling window被设为10000毫秒,
          #则rolling window会被分成n个buckets,每个bucket包含success,failure,timeout,rejection的次数的统计信息。默认10000
          timeInMilliseconds: 10000
          #设置一个rolling window被划分的数量,若numBuckets=10,rolling window=10000,
          #那么一个bucket的时间即1秒。必须符合rolling window % numberBuckets == 0。默认10
          numBuckets: 10
        rollingPercentile:
          #执行时是否enable指标的计算和跟踪,默认true
          enabled: true
          #设置rolling percentile window的时间,默认60000
          timeInMilliseconds: 60000
          #设置rolling percentile window的numberBuckets。逻辑同上。默认6
          numBuckets: 6
          #如果bucket size=100,window=10s,若这10s里有500次执行,
          #只有最后100次执行会被统计到bucket里去。增加该值会增加内存开销以及排序的开销。默认100
          bucketSize: 100
        healthSnapshot:
          #记录health 快照(用来统计成功和错误绿)的间隔,默认500ms
          intervalInMilliseconds: 500
      requestCache:
        #默认true,需要重载getCacheKey(),返回null时不缓存
        enabled: true
      requestLog:
        #记录日志到HystrixRequestLog,默认true
        enabled: true
  collapser:
    default:
      #单次批处理的最大请求数,达到该数量触发批处理,默认Integer.MAX_VALUE
      maxRequestsInBatch: 2147483647
      #触发批处理的延迟,也可以为创建批处理的时间+该值,默认10
      timerDelayInMilliseconds: 10
      requestCache:
        #是否对HystrixCollapser.execute() and HystrixCollapser.queue()的cache,默认true
        enabled: true
  threadpool:
    default:
      #并发执行的最大线程数,默认10
      coreSize: 10
      #Since 1.5.9 能正常运行command的最大支付并发数
      maximumSize: 10
      #BlockingQueue的最大队列数,当设为-1,会使用SynchronousQueue,值为正时使用LinkedBlcokingQueue。
      #该设置只会在初始化时有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。
      #默认-1。
      maxQueueSize: -1
      #即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝。
      #因为maxQueueSize不能被动态修改,这个参数将允许我们动态设置该值。if maxQueueSize == -1,该字段将不起作用
      queueSizeRejectionThreshold: 5
      #Since 1.5.9 该属性使maximumSize生效,值须大于等于coreSize,当设置coreSize小于maximumSize
      allowMaximumSizeToDivergeFromCoreSize: false
      #如果corePoolSize和maxPoolSize设成一样(默认实现)该设置无效。
      #如果通过plugin(https://github.com/Netflix/Hystrix/wiki/Plugins)使用自定义实现,该设置才有用,默认1.
      keepAliveTimeMinutes: 1
      metrics:
        rollingStats:
          #线程池统计指标的时间,默认10000
          timeInMilliseconds: 10000
          #将rolling window划分为n个buckets,默认10
          numBuckets: 10

上面是默认的配置,我们可以对自己的配置进行分组:

针对不同的组在配置文件里面加上不同的配置就好了,在@MyCommand注解里面指定group为abc就行;其他的配置也是这个规则,还有默认的配置是default;这样可以把一个组的配置独立出来,便于配置,而且开发者也会方便很多,代码简洁;

下面是代码:

package cn.chinotan.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: test
 * @description: hystrix控制器
 * @author: xingcheng
 * @create: 2018-11-03 19:27
 **/
@RestController
@RequestMapping("/hystrix")
public class HystrixController {

    @HystrixCommand(fallbackMethod = "helloFallback")
    @RequestMapping("/sayHello")
    public String sayHello(String name, Integer time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello, " + name;
    }

    @HystrixCommand(fallbackMethod = "hiFallback")
    @RequestMapping("/sayHi")
    public String sayHi(String name) {
        if (StringUtils.isBlank(name)) {
            throw new RuntimeException("name不能为空");
        }
        return "Good morning, " + name;
    }

    /**
     * fallback
     */
    public String helloFallback(String name, Integer time) {
        System.out.println("helloFallback: " + name);
        return "helloFallback" + name;
    }

    /**
     * fallback
     */
    public String hiFallback(String name) {
        System.out.println("hiFallback: " + name);
        return "hiFallback" + name;
    }
}

package cn.chinotan.config;

import com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @program: test
 * @description: HystrixConfig
 * @author: xingcheng
 **/
@Configuration
public class HystrixConfig {

    /**
     * 用来拦截处理HystrixCommand注解
     * @return
     */
    @Bean
    public HystrixCommandAspect hystrixAspect() {
        return new HystrixCommandAspect();
    }

    /**
     * 用来像监控中心Dashboard发送stream信息
     * @return
     */
    @Bean
    public ServletRegistrationBean hystrixMetricsStreamServlet() {
        ServletRegistrationBean registration = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
        registration.addUrlMappings("/hystrix.stream");
        return registration;
    }
    
}

3配置监控后台Hystrix-Dashboard

1.github上下载源码https://github.com/kennedyoliveira/standalone-hystrix-dashboard

2.参考其wiki文档,部署成功后,默认端口是7979;

3.点击 http://127.0.0.1:7979/hystrix-dashboard 打开页面

二Resilience4j

Hystrix 的关注点在于以隔离和熔断为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 Fallback 机制。

Hystrix 是元老级别的存在,但是在 2018 年 11 月 Netflix 官方宣布停止更新(就是这么不靠谱,说跳票就跳票)。虽然停止更新,但是社区又推出了新的替代工具:Resilience4j。

Resilience4j 的模块化做的比较好,将每个功能点(如熔断、限速器、自动重试)都拆成了单独的模块。

这样整体结构很清晰,用户也只需要引入相应功能的依赖即可;另外 Resilience4j 是针对 Java 8 和函数式编程设计的,API 比较简洁优雅。

同时与 Hystrix 相比,Resilience4j 增加了简单的限速器和自动重试特性,使用场景更加丰富。

相比 Hystrix , Resilience4j 的优势在于:

针对 Java 8 和函数式编程设计,提供函数式和响应式风格的 API。增加了 rate limiting 和 automatic retrying 两个模块。其中 rate limiting 引入了简单的速率控制实现,补充了流量控制这一块的功能。而 automatic retrying 则是封装了自动重试的逻辑,简化了异常恢复的流程。

Resilience4j 属于一个新兴项目,社区也在蓬勃发展。总的来说,Resilience4j 是比较轻量的库,在较小较新的项目中使用还是比较方便的。

但是 Resilience4j 只包含限流降级的基本场景,对于非常复杂的企业级服务架构可能无法很好地 cover 住。

同时 Resilience4j 缺乏生产级别的配套设施(如提供规则管理和实时监控能力的控制台)。

三 Sentinel(重点)

Sentinel的官方文档https://github.com/alibaba/Sentinel/wiki

1、与传统微服务治理框架Hystrix共同特性和区别

①、资源模型和执行模型上的对比

Hystrix 的资源模型设计上采用了命令模式,将对外部资源的调用和 fallback 逻辑封装成一个命令对象(HystrixCommand/ HystrixObservableCommand),其底层的执行是基于 RxJava 实现的。每个 Command 创建时都要指定 commandKey 和 groupKey(用于区分资源)以及对应的隔离策略(线程池隔离 or 信号量隔离)。线程池隔离模式下需要配置线程池对应的参数(线程池名称、容量、排队超时等),然后 Command 就会在指定的线程池按照指定的容错策略执行;信号量隔离模式下需要配置最大并发数,执行 Command 时 Hystrix 就会限制其并发调用。

Sentinel 的设计则更为简单。相比 Hystrix Command 强依赖隔离规则,Sentinel 的资源定义与规则配置的耦合度更低。Hystrix 的 Command 强依赖于隔离规则配置的原因是隔离规则会直接影响 Command 的执行。在执行的时候 Hystrix 会解析 Command 的隔离规则来创建 RxJava Scheduler 并在其上调度执行,若是线程池模式则 Scheduler 底层的线程池为配置的线程池,若是信号量模式则简单包装成当前线程执行的 Scheduler。

而Sentinel则不一样,开发的时候只需要考虑这个方法/代码是否需要保护,置于用什么来保护,可以任何时候动态实时的区修改。

从 0.1.1 版本开始,Sentinel 还支持基于注解的资源定义方式,可以通过注解参数指定异常处理函数和 fallback 函数。Sentinel 提供多样化的规则配置方式。除了直接通过 loadRules API 将规则注册到内存态之外,用户还可以注册各种外部数据源来提供动态的规则。用户可以根据系统当前的实时情况去动态地变更规则配置,数据源会将变更推送至 Sentinel 并即时生效。

②、隔离设计上的对比

隔离是 Hystrix 的核心功能之一。Hystrix 提供两种隔离策略:线程池隔离(Bulkhead Pattern)和信号量隔离,其中最推荐也是最常用的是线程池隔离。Hystrix 的线程池隔离针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败,并可以提供 fallback 机制。线程池隔离的好处是隔离度比较高,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。

但是,实际情况下,线程池隔离并没有带来非常多的好处。最直接的影响,就是会让机器资源碎片化。考虑这样一个常见的场景,在 Tomcat 之类的 Servlet 容器使用 Hystrix,本身 Tomcat 自身的线程数目就非常多了(可能到几十或一百多),如果加上 Hystrix 为各个资源创建的线程池,总共线程数目会非常多(几百个线程),这样上下文切换会有非常大的损耗。另外,线程池模式比较彻底的隔离性使得 Hystrix 可以针对不同资源线程池的排队、超时情况分别进行处理,但这其实是超时熔断和流量控制要解决的问题,如果组件具备了超时熔断和流量控制的能力,线程池隔离就显得没有那么必要了。

Hystrix 的信号量隔离限制对某个资源调用的并发数。这样的隔离非常轻量级,仅限制对某个资源调用的并发数,而不是显式地去创建线程池,所以 overhead 比较小,但是效果不错。但缺点是无法对慢调用自动进行降级,只能等待客户端自己超时,因此仍然可能会出现级联阻塞的情况。

Sentinel 可以通过并发线程数模式的流量控制来提供信号量隔离的功能。并且结合基于响应时间的熔断降级模式,可以在不稳定资源的平均响应时间比较高的时候自动降级,防止过多的慢调用占满并发数,影响整个系统。

③、熔断降级的对比

Sentinel 和 Hystrix 的熔断降级功能本质上都是基于熔断器模式(Circuit Breaker Pattern)。Sentinel 与 Hystrix 都支持基于失败比率(异常比率)的熔断降级,在调用达到一定量级并且失败比率达到设定的阈值时自动进行熔断,此时所有对该资源的调用都会被 block,直到过了指定的时间窗口后才启发性地恢复。上面提到过,Sentinel 还支持基于平均响应时间的熔断降级,可以在服务响应时间持续飙高的时候自动熔断,拒绝掉更多的请求,直到一段时间后才恢复。这样可以防止调用非常慢造成级联阻塞的情况。

④、实时指标统计实现的对比

Hystrix 和 Sentinel 的实时指标数据统计实现都是基于滑动窗口的。Hystrix 1.5 之前的版本是通过环形数组实现的滑动窗口,通过锁配合 CAS 的操作对每个桶的统计信息进行更新。Hystrix 1.5 开始对实时指标统计的实现进行了重构,将指标统计数据结构抽象成了响应式流(reactive stream)的形式,方便消费者去利用指标信息。同时底层改造成了基于 RxJava 的事件驱动模式,在服务调用成功/失败/超时的时候发布相应的事件,通过一系列的变换和聚合最终得到实时的指标统计数据流,可以被熔断器或 Dashboard 消费。

Sentinel 目前抽象出了 Metric 指标统计接口,底层可以有不同的实现,目前默认的实现是基于 LeapArray 的滑动窗口,后续根据需要可能会引入 reactive stream 等实现。

2、Sentinel 特性

除了之前提到的两者的共同特性之外,Sentinel 还提供以下的特色功能:

①轻量级、高性能

Sentinel 作为一个功能完备的高可用流量管控组件,其核心 sentinel-core 没有任何多余依赖,打包后只有不到 200 KB,非常轻量级。开发者可以放心地引入 sentinel-core 而不需担心依赖问题。同时,Sentinel 提供了多种扩展点,用户可以很方便地根据需求去进行扩展,并且无缝地切合到 Sentinel 中。

引入 Sentinel 带来的性能损耗非常小。只有在业务单机量级超过 25W QPS 的时候才会有一些显著的影响(5% - 10% 左右),单机 QPS 不太大的时候损耗几乎可以忽略不计。

②流量控制

Sentinel 可以针对不同的调用关系,以不同的运行指标(如 QPS、并发调用数、系统负载等)为基准,对资源调用进行流量控制,将随机的请求调整成合适的形状。

Sentinel 支持多样化的流量整形策略,在 QPS 过高的时候可以自动将流量调整成合适的形状。常用的有:

直接拒绝模式:即超出的请求直接拒绝。

慢启动预热模式:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

匀速器模式:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。

③系统负载保护

Sentinel 对系统的维度提供保护,负载保护算法借鉴了 TCP BBR 的思想。当系统负载较高的时候,如果仍持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

④实时监控和控制面板

Sentinel 提供 HTTP API 用于获取实时的监控信息,如调用链路统计信息、簇点信息、规则信息等。如果用户正在使用 Spring Boot/Spring Cloud 并使用了Sentinel Spring Cloud Starter,还可以方便地通过其暴露的 Actuator Endpoint 来获取运行时的一些信息,如动态规则等。未来 Sentinel 还会支持标准化的指标监控 API,可以方便地整合各种监控系统和可视化系统,如 Prometheus、Grafana 等。

Sentinel控制台(Dashboard)提供了机器发现、配置规则、查看实时监控、查看调用链路信息等功能,使得用户可以非常方便地去查看监控和进行配置。

⑤生态

Sentinel 目前已经针对 Servlet、Dubbo、Spring Boot/Spring Cloud、gRPC 等进行了适配,用户只需引入相应依赖并进行简单配置即可非常方便地享受 Sentinel 的高可用流量防护能力。未来     Sentinel 还会对更多常用框架进行适配,并且会为 Service Mesh 提供集群流量防护的能力。

在这里插入图片描述

3 Sentinel与 SpringCloud,Ribbon,OpenFeign整合

Sentinel由于对代码的侵入性底的特性,整合的时候只和微服务消费者端整合,完全不侵入微服务提供者。

①依赖

父项目依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>FengboSoft</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>eureka-server-1</module>
        <module>eureka-server-2</module>
        <module>eureka-server-3</module>
        <module>eureka-client-account</module>
        <module>eureka-common-entity</module>
        <module>eureka-system-account</module>
        <module>eureka-client-finance</module>
        <module>eureka-client-meter</module>
    </modules>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.0.6.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>0.2.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

服务消费者端依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>FengboSoft</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>eureka-system-account</artifactId>

    <dependencies>
        <!--公共实体类API-->
        <dependency>
            <artifactId>eureka-common-entity</artifactId>
            <groupId>org.example</groupId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--Web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--eureka启动配置依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
<!--        &lt;!&ndash;微服务负载均衡调用&ndash;&gt;
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>-->
        <!--微服务组件-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--服务调用openFeign,OpenFeign依赖中包含了负载均衡组件ribbon-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
    </dependencies>

</project>

②controller

package fengbo.controller;

import fengbo.service.FeignOpenServiceFace;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;


/**
 * Created by @author LiuChunhang on 2020/7/21.
 */
@Controller
public class AccountController {
 /*   @Autowired
    public RestTemplate restTemplate;

    public RestTemplate getRestTemplate() {
        return restTemplate;
    }

    public void setRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }*/
    @Autowired
    public FeignOpenServiceFace feignOpenServiceFace;

    public FeignOpenServiceFace getFeignOpenServiceFace() {
        return feignOpenServiceFace;
    }

    public void setFeignOpenServiceFace(FeignOpenServiceFace feignOpenServiceFace) {
        this.feignOpenServiceFace = feignOpenServiceFace;
    }


    @ResponseBody
    @RequestMapping(value = "account/login")
    public Object index() {
        //return restTemplate.getForObject("http://accountprovider/login", Object.class);
        return feignOpenServiceFace.fengbo();
    }
}

③ 微服务代理接口

package fengbo.service;

import fengbo.entity.Account;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;

/*
*
 * Created by @author LiuChunhang on 2020/7/27.

*/


/**使用openfeign,定制化服务*/

@FeignClient(value = "accountprovider",fallback = SentinelEcho.class)
public interface FeignOpenServiceFace {
    @ResponseBody
    @RequestMapping(value = "login")
    public List<Account> fengbo();
}

④负载均衡规则配置

package ribbonbalance;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created by @author LiuChunhang on 2020/7/27.
 */
@Configuration
public class IndividualBalance {
    @Bean
    public IRule getRule(){
        //指定负载均衡算法
        return  new RoundRobinRule();
    }

}

⑤启动类

package fengbo;

import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import ribbonbalance.IndividualBalance;

/**
 * Created by @author LiuChunhang on 2020/7/21.
 */
@SpringBootApplication
@EnableEurekaClient
/**此注解的属性用于服务定制化负载均衡,且负载均衡配置类不能在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是说我们达不到特殊化定制的目的了。*/
@RibbonClient(name = "accountprovider",configuration = IndividualBalance.class)
@EnableFeignClients(basePackages = "fengbo.service")
public class AccountSystem {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(AccountSystem.class);
        application.setBannerMode(Banner.Mode.OFF);
        application.run(args);
    }
}

⑥配置文件

无 feign.enable.sentinel=true的配置项,过去与feign整合需要该配置,现在不需要该配置,直接引入依赖即可监控所有http服务。

#该服务端口号
server.port=8081
spring.application.name=sentinelconsumer
#禁止自己当做服务注册,微服务消费者只消费服务,不像注册中心注册
eureka.client.register-with-eureka=false
#指定向那些eureka注册
eureka.client.service-url.defaultZone=http://eurekaserver3:9003/eureka/,http://eurekaserver2:9002/eureka/,http://eurekaserver1:9001/eureka/
#微服务治理控制台url
#spring.cloud.sentinel.transport.dashboard=49.235.16.100:8888,xxxxx控制台不能部署到集群之外的机器上,否则心跳包会延时,尽量和消费者集成在一块。
spring.cloud.sentinel.transport.dashboard=localhost:8080
#与应用交互的httpserver端口号
spring.cloud.sentinel.transport.port=8719
#取消控制台懒加载
spring.cloud.sentinel.eager=true

⑦ 其他模块

在这里插入图片描述

见我前面的文章
【JAVA企业级开发】SpringCloud微服务调用组件Feign,OpenFeign,Ribbon相互之间的区别,以及这三个微服务调用组件单独使用其中一种和两种相互组合一起使用的效果和特点
【JAVA企业级开发】详解分布式系统中负责服务调用的Ribbon组件与Eureka组件的集成应用,通过配置类修改调用服务的负载均衡算法和自定义负载均衡规则的过程
【JAVA企业级开发】详解在本机环境下构建高可用分布式Eureka server集群的配置过程,应用过程和测试监控面板变化过程
【JAVA企业级开发】通过一个基于REST的服务发现框架Euraka的开发,生产和运维案例,带你掌握SpringCloud微服务架构的微服务基础件Euraka的系统服务注册消费原理和应用过程

4 sentinel-dashboard

链接:https://pan.baidu.com/s/1hos9Wnq0TnAZImrMTgHRCQ 
提取码:m5qb

window后台启动Jar包(需在同一集群中):

 start  javaw  sentinel-dashboard-1.7.0.jar

Linux后台启动(需在同一集群中)
监控自己:

nohup java  -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.0.jar $

不监控自己

nohup java  -Dserver.port=8080  -jar sentinel-dashboard-1.7.0.jar $

5控制大屏(默认端口8080,可以在启动时修改)

在这里插入图片描述

四 总结

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牵牛刘先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值