服务治理的三种服务保护方法:熔断、限流、降级。

最近在看陈皓的一些课,感触很深,这里大概记录一下心得。

一、熔断

熔断的目的

个人理解,熔断大多来源于:有限时间内过多的重试。之所以重试这么多次,可能是服务端或者请求方本身出现了问题。而引入熔断之后,可能避免过多无意义的失败请求。若服务方出现问题,请求方被熔断,从而使得请求方不会继续盲目调用。若请求方本身出现问题,也可以让请求方直接失败,避免自身无意义的等待或阻塞。

熔断器的状态

熔断器可以使用状态机来实现。

闭合(Closed)状态:关闭熔断。我们需要一个调用失败的计数器,如果调用失败,则使失败次数加 1。如果最近失败次数超过了在给定时间内允许失败的阈值,则切换到断开 (Open) 状态。此时开启了一个超时时钟,经过一定的时间之后,会切换到半断开(Half-Open)状态。过了这段时间之后,进入半断开目的是让系统有机会来修正错误,以回到正常工作的状态。在 Closed 状态下,错误计数器是基于时间的。在特定的时间间隔内会自动重置。这能够防止由于某次的偶然错误导致熔断器进入断开状态。也可以基于连续失败的次数。

打开 (Open) 状态:打开熔断。在该状态下,对应用程序的请求会立即返回错误响应,而不调用后端服务。这样也许比较粗暴,有些时候,我们可以 cache 住上次成功请求,直接返回缓存(当 然,这个缓存放在本地内存就好了),如果没有缓存再返回错误(缓存的机制最好用在全站 一样的数据,而不是用在不同的用户间不同的数据,因为后者需要缓存的数据有可能会很 多)。

半开(Half-Open)状态:允许应用程序一定数量的请求去调用服务。如果这些请求对服务的调用成功,那么可以认为之前导致调用失败的错误已经修正,此时熔断器切换到闭合状态 (并且将错误计数器重置)。 如果这一定数量的请求有调用失败的情况,则认为导致之前调用失败的问题仍然存在,熔断器切 回到断开状态,然后重置计时器来给系统一定的时间来修正错误。半断开状态能够有效防止正在恢复中的服务被突然而来的大量请求再次拖垮。

调用失败过多,触发熔断,熔断器处于打开状态。

熔断打开后,阻拦后续的用户请求,过了一定时间(会设置一个超时时间),自动切换到半打开状态。

半打开状态下,熔断器会释放一部分请求,试探系统是否恢复正常。如果这部分请求都成功了,那么熔断就会关闭,用户正常请求,不会再被阻拦。

熔断机制的设计

1.错误类型的判定。有些请求,经过重试后再触发熔断。有的请求需要直接触发熔断,比如服务端已经瘫痪。所以我们需要对报错的类型进行判定。是先重试,还是说上来应该直接熔断。

2.对服务端探活。对于远程服务瘫痪的情况下。就应该单独设置一个探活机制,而不是等待熔断器默认的超时机制(超过一定时间变成半打开状态),熔断器直接对服务端进行探活。当服务端可用时,就可以直接变为半打开状态。

3.熔断可手动重置。管理人员可以按照当前情况进行熔断手动控制。

4.并发无影响。熔断器本身不应该影响高并发状态下,各个请求的执行状态。对于请求本身应该是无侵入的。不会影响并发结果。

5.熔断控制粒度。熔断应该针对具体资源来说,比如Redis-cluster模式下,不能因为某一个节点流量过高,而限制对所有节点的访问。(虽然redis本身对请求具有很好的哈希分散,这里仅仅举个例子)

6.日志记录。熔断器本身硬具有日志监控功能,记录失败和尝试的情况。以便了解熔断器下的业务调用状态。

7.熔断的触发场景可能非常负责。有时候,我们发现大量请求方调用服务都会失败。但是我们探活服务端是存活的。这时候,可能是服务端的问题,也可能是客户端的问题。比如业务请求需要在服务端落盘,而服务端所在机器的磁盘出现问题,这是探活探不到的,导致每一次请求都会失败或者超时。再比如我们客户端请求方本身代码有问题,导致无法正常对服务端调用,这时候,反而不应该进行熔断。

二、限流

限流的目的

1.为了向用户承诺SLA的指标。我们保证我们的系统在某个速度下的响应时间以及可用性。

SLA是服务等级协议,常见的指标为:可用性、准确性、系统容量和延迟

2.可以用来阻止在多租户的情况下,某一用户把资源耗尽而让所有的用户都无法访问的问题。

3.能够顶得住突发的流量。

4.节约成本。我们目的就是在有限的资源下能够承受比较高的流量。

限流的种类

拒绝服务

一般来说,好的限流系统在受到流量暴增时,会统计当前哪个客户端来的请求最多,直接拒掉这个客户端,这种行为可以把一些不正常的或者是带 有恶意的高并发访问抵挡掉。

服务降级

降级有很多方式,一种是把一些不重要的服务给停掉,把 CPU、内存或是数据的资源让给更重要的功能;一种是不再返回全量数据,只返回部分数据。(因为全量数据需要做 SQL Join 操作,部分的数据则不需要,所以可以让 SQL 执行更快,还有最快的一种是直接返回预设的缓存,以牺牲一致性的方式来获得更大的性能吞吐)

特权请求

所谓特权请求的意思是,资源不够了,我只能把有限的资源分给重要的用户,比 如:分给权利更高的 VIP 用户。在多租户系统下,限流的时候应该保大客户的,所以大客户有特权可以优先处理。

延时处理

在这种情况下,一般会有一个队列来缓冲大量的请求,这个队列如果满了,那么 就只能拒绝用户了,如果这个队列中的任务超时了,也要返回系统繁忙的错误了。使用缓冲 队列只是为了减缓压力,一般用于应对短暂的峰刺请求。 (这也就是MQ所谓的削峰的功能)

弹性伸缩

动用自动化运维的方式对相应的服务做自动化的伸缩。这个需要一个应用性能的监控系统,能够感知到目前最繁忙的 TOP 5 的服务是哪几个。

限流的具体方式

计数器算法

采用计数器实现限流有点简单粗暴,一般我们会限制一秒钟的能够通过的请求数,比如限流qps为100,算法的实现思路就是从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了100,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。

具体的实现可以是这样的:对于每次服务调用,可以通过 AtomicLong#incrementAndGet()方法来给计数器加1并返回最新值,通过这个最新值和阈值进行比较。

这种实现方式,相信大家都知道有一个弊端:如果我在单位时间1s内的前10ms,已经通过了100个请求,那后面的990ms,只能眼巴巴的把请求拒绝,我们把这种现象称为“突刺现象”

漏桶算法

以固定速率从桶中流出水滴,以任意速率往桶中放入水滴,桶容量大小是不会发生改变的。

因为桶中的容量是固定的,如果流入水滴的速率 > 流出的水滴速率,桶中的水滴可能会溢出。那么溢出的水滴请求都是拒绝访问的,或者直接调用服务降级方法。前提是同一时刻

对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输。这时候漏桶算法可能就不合适了,令牌桶算法更为适合。

令牌桶算法

算法会以规定的速率往令牌桶中放入 token,用户请求必须获取到令牌桶中的 token才可以调用我们的服务,如果没有从令牌桶中获取到 token ,拒绝访问。 在高并发情况下,如果我们的请求过多 超出了令牌桶生成令牌的速度,这时候请求就会被驳回,提示请稍后重试! 优势:能够控制请求的速率。

并不能说明令牌桶一定比漏洞好,她们使用场景不一样。令牌桶可以用来保护自己,主要用来对调用者频率进行限流,为的是让自己不被打垮。所以如果自己本身有处理能力的时候,如果流量突发(实际消费能力强于配置的流量限制),那么实际处理速率可以超过配置的限制。而漏桶算法,是用来保护被我们调用的第三方系统,是一种自我控制。主要场景是,当调用的第三方系统本身没有保护机制,或者有流量限制的时候,我们的调用速度不能超过他的限制,由于我们不能更改第三方系统,所以只有在主调方控制。这个时候,即使流量突发,也必须舍弃。因为消费能力是第三方决定的。

令牌桶和漏桶算法的总结

如果要让自己的系统不被打垮,用令牌桶。如果怕伤害被我们调用的系统,用漏桶算法

基于响应时间的动态限流

我们记录下每次调用后端请求的响应时间,然后在一个时间区间内(比如,过去 10 秒)的请求计算一个响应时间的 P90 或 P99 值,也就是把过去 10 秒内的请求的响应时间排个序,然后看 90% 或 99% 的位置是多少,如果这个 P90 或 P99 超过我们设定的阈值, 那么我们就自动限流

限流设计的注意事项

1.设计架构时就该考虑。限流应该是在架构的早期考虑。当架构形成后,限流不是很容易加入。

2.限流模块本身应具有良好性能。限流模块必需是非常好的性能,而且对流量的变化也是非常灵敏的,否则太过迟钝的限流, 系统早因为过载而挂掉了。

3.可手动开启。限流应该有个手动的开关,这样在应急的时候,可以手动操作。

4.能主动通知。当限流发生时,应该有个监控事件通知。让我们知道有限流事件发生,这样,运维人员可以 及时跟进。而且还可以自动化触发扩容或降级,以缓解系统压力。

5.限流应返回专门的错误。当限流发生时,对于拒掉的请求,我们应该返回一个特定的限流错误码。这样,可以和其它错误区分开来。而客户端看到限流,可以调整发送速度,或是走重试机制。

6.限流应该让后端服务感知到。限流发生时,我们应该在协议头中塞进一个标识,比如 HTTP Header 中,放入一个限流的级别,告诉后端服务目前正在限流中。这样,后端服务可以根据这个标识决定是否做降级。

三、降级

服务降级的方式

降低数据和流程一致性

降低数据一致性:比如在商品界面,不展示剩余库存,只显示有或者无

降低流程一致性:使用更简化的流程

使用异步的方式来简化流程

比如双11的支付变为货到付款

停止次要功能

比如双11的时候,暂停商品评论的功能。但是一般情况下,不建议停止功能,建议可以对这些次用进行限流。

简化功能

我们开发API的时候,同样的API我们可以有两个版本,一个返回全量数据,一个返回部分数据,以便降级时使用。

降级设计要点

1.确定降级由什么条件触发。降级的条件可能是:流量过大、响应过慢、失败次数过多、服务存在部分故障。

2.设计好降级之后的服务。哪些是必须保住的功能,哪些是可以牺牲的功能。为可降级的服务做好准备,规划好降级之后服务的设计和流程。准备好应急流程。

3.使用缓存和异步。发生降级之后,我们对于读操作,进行缓存化来解决。对于写操作,我们使用异步来解决。

4.规划降级怎么开启。可以动态推送降级配置。也可以直接在为API准备好降级后的版本,由上游来调用。

5.降级大多需要和前端交流和配合。降级前后,前端收到的数据未必是一致的。这需要和前端沟通。

6.做好演练

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值