分布式降级,限流,熔断

雪崩场景:
对于一个复杂的分布式服务来说,单个微服务处理自己的业务逻辑,这样做的好处是能够解决服务之间的依赖,降低耦合。同样也能够进行水平扩容,提高吞吐量。
但是在实际的过程中,我们往往一个服务会调用多个服务(中间以rpc作为通讯),如果其中一个服务提供者(provider)由于某些原因导致不可用,那么其他服务也会阻塞,最终导致整个系统面临崩溃的危险,而这及时雪崩场景
 
如下图所示 AB服务调用C,C服务调用D,这也是所谓的"扇出",此时由于某些原因,在扇出的链路上D服务响应过慢或者不可用,这个时候C服务的请求越来越多 而得不到处理,进而导致AB服务都出现问题
分布式降级,限流,熔断
分布式降级,限流,熔断
分布式降级,限流,熔断
雪崩是系统中的蝴蝶效应导致其发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。从源头上我们无法完全杜绝雪崩源头的发生,但是雪崩的根本原因来源于服务之间的强依赖,所以我们可以提前评估,做好相关措施。
 
预防机制
在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。
缓存
目的是提升系统访问速度和增大系统能处理的容量,在实际的开发过程中,针对于一些基础档案类数据或者配置参数类数据,我们一般用缓存读取,原因是这些数据的变化性不大,这一部分我们可以减少和数据库的IO交互
 
缓存失效分为几种场景:1.缓存服务挂了 2.高分期缓存失效 3.热点缓存失效
解决方案:注意这里的校验是两次,这里参照单列模式的DCL双重校验锁机制。(嗯....讲道理这里要整一个volatile 内存屏障 )
分布式降级,限流,熔断
 
降级
降级是指在某些高并发场景下,把某些非核心的业务统统往下调。诸如双11交易时,查看蚂蚁深林,蚂蚁庄园之类的服务,仅仅显示一百条数据。
当然降级的颗粒度 ,可以自由调配,根据实际业务和当前的服务器并发请求。比如说,你也可以限制数据库的跟新与插入,但是允许查询操作。
从RPC的角度来说,我访问的是本地服务的伪装者,而不是应用服务本身。
 
 
限流:
限流指的是降低一定时间内的并发访问量  一般两种做法 一种是拉长时间,一种是降低访问QPS()
一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。
 
 
限流算法:
限流算法一般分为以下几种:滑动窗口,漏桶,令牌桶.
 
滑动窗口:
    发送和接受方都会维护个数据帧的序列,这个序列被称作窗口。发送方的窗口大小由接受方确定,目的在于控制发送速度,以免接受方的缓存不够大,而导致溢出,同时控制流量也可以避免网络拥塞。下面图中的4,5,6号数据帧已经被发送出去,但是未收到关联的ACK7,8,9帧则是等待发送。可以看出发送端的窗口大小为6,这是由接受端告知的。此时如果发送端收到4ACK,则窗口的左边缘向右收缩,窗口的右边缘则向右扩展,此时窗口就向前滑动了,即数据帧10也可以被发送。
分布式降级,限流,熔断
这个是滑动窗口的演示地址,很形象
分布式降级,限流,熔断
 
 
漏桶(控制传输速率 leaky bucket):
漏桶算法思路是,不断的往桶里面注水,无论注水的速度是大还是小,水都是按固定的速率往外漏水;如果桶满了,水会溢出;
桶本身具有个恒定的速率往下漏水,而上方时快时慢的会有水进入桶内。当桶还未满时,上方的水可以加入。旦水满,上方的水就无法加入。
桶满正是算法中的个关键的触发条件(即流量异常判断成立的条件)。而此条件下如何处理上方流下来的水,有两种方式
在桶满水之后,常见的两种处理方式为:
1)暂时拦截住上方水的向下流动,等待桶中的部分水漏走后,再放行上方水。
2)溢出的上方水直接抛弃。
特点
1. 漏水的速率是固定的
2. 即使存在突然注水量变大的情况,漏水的速率也是固定的
 
分布式降级,限流,熔断
令牌桶(解决突发流量)
 
令牌桶算法是网络流量整形(Traffiffiffic Shaping)和速率限制(Rate Limiting)中最常使用的种算法。典型情况下,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。
令牌桶是个存放固定容量令牌(token)的桶,按照固定速率往桶里添加令牌; 令牌桶算法实际上由三部分组成:两个流和个桶,分别是令牌流、数据流和令牌桶
 
令牌流与令牌桶
系统会以定的速度生成令牌,并将其放置到令牌桶中,可以将令牌桶想象成个缓冲区(可以用队列这种数据结构来实现),当缓冲区填满的时候,新生成的令牌会被扔掉。
这里有两个变量很重要:个是生成令牌的速度,般称为 rate 。比如,我们设定 rate=2 ,即每秒钟生成 2 个令牌,也就是每 1/2 秒生成个令牌;
第二个是令牌桶的大小,般称为 burst 。比如,我们设定 burst=10 ,即令牌桶最大只能容纳 10 个令牌。
分布式降级,限流,熔断
有以下三种情形可能发生:
数据流的速率 等于 令牌流的速率。这种情况下,每个到来的数据包或者请求都能对应个令牌,后无延迟地通过队列;
数据流的速率 小于 令牌流的速率。通过队列的数据包或者请求只消耗了部分令牌,剩下的令牌会在令牌桶里积累下来,直到桶被装满。剩下的令牌可以在突发请求的时候消耗掉。
数据流的速率 大于 令牌流的速率。这意味着桶里的令牌很快就会被耗尽。导致服务中断段时间,如果数据包或者请求持续到来,将发生丢包或者拒绝响应。
 
 
处理机制
 
熔断:
熔断机制是应对雪崩效应的一种微服务链路保护机制。在微服务中,扇出的微服务不可用或者相应时间过长的话会对服务降级,进而熔断该服务节点,快速返回错误信息,释放资源。
而当检测到微服务响应正常后,则恢复调用。
 
隔离
这种模式就像对系统请求按类型划分成一个个小岛的一样,当某个小岛被火烧光了,不会影响到其他的小岛。
例如可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。
这种模式使用场景非常多,例如将一个服务拆开,对于重要的服务使用单独服务器来部署,再或者公司最近推广的多中心。
 
熔断设计
在熔断的设计主要参考了hystrix的做法。其中最重要的是三个模块:熔断请求判断算法、熔断恢复机制、熔断报警
(1)熔断请求判断机制算法:使用无锁循环队列计数,每个熔断器默认维护10个bucket,每1秒一个bucket,每个blucket记录请求的成功、失败、超时、拒绝的状态,默认错误超过50%且10秒内超过20个请求进行中断拦截。
(2)熔断恢复:对于被熔断的请求,每隔5s允许部分请求通过,若请求都是健康的(RT<250ms)则对请求健康恢复。
(3)熔断报警:对于熔断的请求打日志,异常请求超过某些设定则报警。
 
隔离设计
隔离的方式一般使用两种
(1)线程池隔离模式:使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
(2)信号量隔离模式:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)
 
服务降级与熔断的相关异同点:
相同点:
目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;
最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用;
粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改);
自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段;
 
区别:
触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
实现方式不太一样;服务降级具有代码侵入性(由控制器完成/或自动降级),熔断一般称为自我熔断。
 
 
 
实际项目(springboot+dubbo+sentinel)
 
1.整体dubbo服务搭建
    分布式降级,限流,熔断
父子工程,整体由api服务暴露者和provider服务处理者
api有且仅有一个对外的接口暴露
分布式降级,限流,熔断
看一下 provider的pom.xml
分布式降级,限流,熔断
服务端一个具体实现类,注意是用dubbo的service
分布式降级,限流,熔断
application.properties 配置服务的相关信息 以properties启动dubbo服务
分布式降级,限流,熔断
 
看一下web服务(也就是我们常常说的controller)
分布式降级,限流,熔断
分布式降级,限流,熔断
 
效果图:
分布式降级,限流,熔断
如果以上配置完成的话,代表至少这个dubbo的项目是能跑了,但是现在限流,并没有直接关系
 
2.引入sentinel.jar进行管理
先看一下provider的pom.xml 这里引入了一个核心jar,如果要看你控制台的话,还需要引入 sentinel-transport-simple-http
分布式降级,限流,熔断
引入完监控之就是这样了
分布式降级,限流,熔断
代码中需要配置限流规则
分布式降级,限流,熔断
 
这里需要解释一下,各个参数的意义(详情可以参考官方文档http://dubbo.apache.org/zh-cn/blog/sentinel-introduction-for-dubbo.html
 
  • resource:资源名,即限流规则的作用对象
  • count: 限流阈值
  • grade: 限流阈值类型(QPS 或并发线程数)
  • limitApp: 流控针对的调用来源,若为 default 则不区分调用来源
  • strategy: 调用关系限流策略
  • controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队)
 
其中的resource 指的是类名全路径 加上方法签名以及参数(参数是 java.lang.String的形式的) 
count+grade 指的是限制你每秒多少个并发线程数还是QPS
limitApp指的是 我对哪些来源进行限制
controlBehavior指的是控制行为  分为以下三种:
 
直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
 
warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考 流量控制 - Warm Up 文档,具体的例子可以参见 WarmUpFlowDemo。
 
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:
分布式降级,限流,熔断
 
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考 流量控制 - 匀速器模式,具体的例子可以参见 PaceFlowDemo。
该方式的作用如下图所示:
分布式降级,限流,熔断
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
 
 
 
 
到这一步 单体服务的限流本就可用了,我们调用一下Jmeter来看一下请求的效果
 
这边请求是瞬时50个请求
分布式降级,限流,熔断
然后成功了10个,因为我QPS设置了10个
 
分布式降级,限流,熔断
 
同样的,我们可以对来源进行区分,只限制A请求,而不限制B请求
具体如下操作:
1.限流规则中配置LimitApp
分布式降级,限流,熔断
 
2.web请求中controller声明来源
分布式降级,限流,熔断
此刻将请求切换为noSayHello
分布式降级,限流,熔断
效果如下
分布式降级,限流,熔断
 
而当超过限制的QPS范围之后,后台的报错如下。当然了这里后续可用加入一大堆业务逻辑处理
分布式降级,限流,熔断
 
3.关于搭建引起的一系列的坑以及注意点
 
1.provider要用dubbo的形式启动服务,如果没有任何封装的话,不要忘记在Application上加上线程阻塞,避免主线程关闭。如果是用第三方起来的,则不用加上线程阻塞
分布式降级,限流,熔断
2.zk注册服务注册不了,后来发现dubbo的依赖没有引完,需要两个 分别是dubbo-spring-boot-starter 以及 dubbo
分布式降级,限流,熔断
3.注意zookeeper与curator的版本问题  
分布式降级,限流,熔断
 
4.注意sentinel与dubbo的版本兼容性问题
分布式降级,限流,熔断
分布式降级,限流,熔断
就是这个,我sentinel用1.6.3 之前dubbo用2.7.0就报错 关键是每次都是报限制规则中resource的资源找不到,我一直以为是路径问题。
 
分布式实现限流规则
刚刚其实演示的只是单机情况下,限流的规则以及应用。但是存在这样的业务场景,我们如果对接的一个第三方系统的话,他要求限制入库的qps,那么我们需要对整体的接入的qps做限制,这里看一下架构图
分布式降级,限流,熔断
抽象出tokenServer(貌似没有一个官方的成熟的,要自己手写) 用于管理限流规则,Nacos用于动态的配置规则,同时单个应用服务,也注册到nacos中
 

这块分布式的代码,包括sentinel的熔断与Hystrix的熔断,我决定下次再写。这块会去参考sentinel的官方文档