微服务-服务保护

==微服务拆分容易出现的问题==

1.雪崩问题

1.1 产生背景

在微服务远程调用的过程中,还存在几个问题需要解决。

业务健壮性问题:

例如在之前的查询购物车列表业务中,购物车服务需要查询最新的商品信息,与购物车数据做对比,提醒用户。大家设想一下,如果商品服务查询时发生故障,查询购物车列表在调用商品服务时,是不是也会异常?从而导致购物车查询失败。

但从业务角度来说,为了提升用户体验,即便是商品查询失败,购物车列表也应该正确展示出来,哪怕是不包含最新的商品信息。

级联失败问题:

还是查询购物车的业务,假如商品服务业务并发较高,占用过多Tomcat连接。可能会导致商品服务的所有接口响应时间增加,延迟变高,甚至是长时间阻塞直至查询失败。

此时查询购物车业务需要查询并等待商品查询结果,从而导致查询购物车列表业务的响应时间也变长,甚至也阻塞直至无法访问。而此时如果查询购物车的请求较多,可能导致购物车服务的Tomcat连接占用较多,所有接口的响应时间都会增加,整个服务性能很差, 甚至不可用。

image-20240620110735548

依次类推,整个微服务群中与购物车服务、商品服务等有调用关系的服务可能都会出现问题,最终导致整个集群不可用。

image-20240620110826809

==雪崩【级联失败】==:微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用。

1.2 产生原因

  • 微服务相互调用,服务提供者出现故障或阻塞。

  • 服务调用者没有做好异常处理,导致自身故障。

  • 调用链中的所有服务级联失败,导致整个集群故障

1.3 解决方案

  • 尽量避免服务出现故障或阻塞。–请求限流和线程隔离

    • 保证代码的健壮性;

    • 保证网络畅通;

    • 能应对较高的并发请求;

  • 服务调用者做好远程调用异常的后备方案,避免故障扩散 –服务熔断

这些方案或多或少都会导致服务的体验上略有下降,比如请求限流,降低了并发上限;线程隔离,降低了可用资源数量;服务熔断,降低了服务的完整度,部分服务变的不可用或弱可用。因此这些方案都属于服务降级的方案。但通过这些方案,服务的健壮性得到了提升。

1.请求限流(降低访问流量)

==限制访问微服务的请求并发量,避免服务因流量激增出现故障==

服务故障最重要原因,就是并发太高!解决了这个问题,就能避免大部分故障。当然,接口的并发不是一直很高,而是突发的。因此请求限流,就是限制或控制接口访问的并发流量,避免服务因流量激增而出现故障。

请求限流往往会有一个限流器,数量高低起伏的并发请求曲线,经过限流器就变的非常平稳。这就像是水电站的大坝,起到蓄水的作用,可以通过开关控制水流出的大小,让下游水流始终维持在一个平稳的量。

image-20240620111528990

2.线程隔离(降低独占资源数量)

==限制分给其他服务的线程数,保证不会因为一个服务挂了导致其他服务消耗完自己资源也挂了==

为了避免某个接口故障或压力过大导致整个服务不可用,我们可以限定每个接口可以使用的资源范围,也就是将其“隔离”起来。

image-20240620112057241

举例子说明:

image-20240620112134291

如图所示,我们给查询购物车业务限定可用线程数量上限为20,这样即便查询购物车的请求因为查询商品服务而出现故障,也不会导致服务器的线程资源被耗尽,不会影响到其它接口。

3.快速失败(fallback后备方案)

快速失败:给业务编写一个调用失败时的处理的逻辑,称为fallback。当调用出现故障(比如无线程可用)时,按照失败处理逻辑执行业务并返回,而不是直接抛出异常。

image-20240620112448590

4.服务熔断(提前预测,不对劲就fallback)

==【相当于一个提前预判】设定一个断路器(开关),统计请求的异常比例和慢调用比例,超过阈值我就拒绝不让你用。熔断了去走服务的后备fallback逻辑(备份方案)==

image-20240620133335898

思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务,即拦截访问该服务的一切请求。而当设定熔断时间结束后它会尝试放行一次请求测试,如果成功就是服务恢复时,断路器会放行访问该服务的请求; 如果放行不通过继续走熔断状态,所有请求走fallback快速失败。

image-20240620144802785

2. 常见服务保护技术

目前我们常见的就是以下两种,我们都推荐使用Sentinel:【实习公司用Hystrix】

image-20240619172500972

==Sentinel==

==服务保护方案–Sentinel基础使用==

1.介绍

Sentinel是阿里巴巴开源的一款服务保护框架,目前已经加入SpringCloudAlibaba中。官方网站:

https://security.feishu.cn/link/safety?target=https%3A%2F%2Fsentinelguard.io%2Fzh-cn%2F&scene=ccm&logParams=%7B%22location%22%3A%22ccm_docs%22%7D&lang=zh-CN

Sentinel 的使用可以分为两个部分:

  • 核心库(Jar包):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。在项目中引入依赖即可实现服务限流、隔离、熔断等功能。
  • 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。

2.安装

为了方便监控微服务,我们先把Sentinel的控制台搭建出来

2.1 下载jar包

下载地址:Releases · alibaba/Sentinel · GitHub

2.2 启动测试

  • 1.存放jar:将jar包放在任意非中文、不包含特殊字符的目录下,重命名为sentinel-dashboard.jar

image-20240620100439761

  • 2.启动:在当前目录下cmd打开命令行输入指令启动:
1
2
3
4
5
输入以下命令:
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar

#其余配置项可以参考官方文档:
https://github.com/alibaba/Sentinel/wiki/%E5%90%AF%E5%8A%A8%E9%85%8D%E7%BD%AE%E9%A1%B9

运行结果:

image-20240620100605675

image-20240620100945847

需要输入账号和密码,默认都是:sentinel

登录后,即可看到控制台,默认会监控sentinel-dashboard服务本身

image-20240620101024738

3.微服务整合

我们以微服务-黑马商城中的cart-service购物车模块为例:

3.1 引入依赖

我们在cart-service服务pom.xml文件引入依赖

1
2
3
4
5
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

因为整合到springalibaba,所以依赖名也是spring-cloud-starter前缀

image-20240620101220519

3.2 yml配置控制台

我们在cart-service服务yml文件引入依赖

1
2
3
4
5
6
7
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090 # sentinel控制台地址
# 因为restful风格,如果不设置的话同一个controller下的接口都是一个资源
http-method-specify: true #是否设置请求方式作为资源名称

image-20240620101330326

3.3 测试

重启cart-service,然后访问查询购物车接口,sentinel的客户端就会将服务访问的信息提交到sentinel-dashboard控制台。并展示出统计信息:

image-20240620101510077

点击cart-service的簇点链路菜单,会看到下面的页面:

image-20240620101637577

所谓簇点链路,就是单机调用链路,是一次请求进入服务后经过的每一个被Sentinel监控的资源。【默认情况下,Sentinel会监控SpringMVC的每一个Endpoint(接口)】

因此,我们看到/carts这个接口路径就是其中一个簇点,我们可以对其进行限流、熔断、隔离等保护措施。

3.4 簇点链路

【默认情况下】Sentinel会把路径作为簇点资源的名称,无法区分路径相同但请求方式不同的接口,查询、删除、修改等都被识别为一个簇点资源(我们的SpringMVC接口是按照Restful风格设计,因此购物车的查询、删除、修改等接口全部都是/carts路径)

image-20240620101946062

解决方案:我们可以选择打开Sentinel的请求方式前缀,把请求方式 + 请求路径作为簇点资源名

image-20240620102041605

然后,重启服务,通过页面访问购物车的相关接口,可以看到sentinel控制台的簇点链路发生了变化:

image-20240620102128531

==服务保护方案–Sentinel四大解决方案==

我们以微服务-黑马商城中的cart-service购物车模块为例:

1.请求限流

前提:我们已经将cart-service购物车模块和sentinel建立连接,我们就可以通过控制台进行操作

1.1 控制台设置限流QPS

把查询购物车列表这个簇点资源的流量限制在了每秒6个,也就是最大QPS为6.

image-20240620102556908

1.2 Jmeter测试

【可参考笔记-jmeter快速入门】

我们利用Jemeter做限流测试,我们每秒发出10个请求:

image-20240620102852440

最终在sentinel监控结果如下:

image-20240620103024899

可以看出GET:/carts这个接口的通过QPS稳定在6附近,而拒绝的QPS在4附近,符合我们的预

2.线程隔离

2.1 控制台设置并发线程数

image-20240620104405158

2.2 Jmeter测试

【可参考笔记-jmeter快速入门】

我们利用Jemeter测试,每秒发送100个请求:

image-20240620104448371

最终在sentinel监控结果如下:

进入查询购物车的请求每秒大概在100,而在查询商品时却只剩下每秒10左右,符合我们的预期。

image-20240620104557342

此时如果我们通过页面访问购物车的其它接口,例如添加购物车、修改购物车商品数量,发现不受影响:

image-20240620104632824

利用线程隔离对查询购物车业务进行隔离,保护了购物车服务的其它接口。由于查询商品的功能耗时较高(我们模拟了500毫秒延时),再加上线程隔离限定了线程数为5,导致接口吞吐能力有限,最终QPS只有10左右。这就导致了几个问题:

第一,超出的QPS上限的请求就只能抛出异常,从而导致购物车的查询失败。但从业务角度来说,即便没有查询到最新的商品信息,购物车也应该展示给用户,用户体验更好。也就是给查询失败设置一个降级处理逻辑【fallback快速失败】。

第二,由于查询商品的延迟较高(模拟的500ms),从而导致查询购物车的响应时间也变的很长。这样不仅拖慢了购物车服务,消耗了购物车服务的更多资源,而且用户体验也很差。对于商品服务这种不太健康的接口,我们应该直接停止调用,直接走降级逻辑,避免影响到当前服务。也就是将商品查询接口熔断

3.快速失败-Fallback(后备方案)

触发限流或熔断后的请求不一定要直接报错,也可以返回一些默认数据或者友好提示,用户体验会更好。

3.1 两种配置方式

image-20240620134907425

3.2 举例-以方式二为例:

3.2.1 yml导入依赖

修改cart-service模块的application.yml文件,开启Feign的sentinel功能:

1
2
3
feign:
sentinel:
enabled: true # 开启feign对sentinel的支持
image-20240620104846227

3.2.2 编写降级处理类

在hm-api模块中给ItemClient定义降级处理类,实现FallbackFactory

image-20240620144115712

3.2.3 注入bean

hm-api模块中的com.hmall.api.config.DefaultFeignConfig类中将ItemClientFallback注册为一个Bean

image-20240620144206407

3.2.4 给对应Openfeign添加属性

hm-api模块中的ItemClient接口中使用ItemClientFallbackFactory

image-20240620144229206

3.2.5 Jmeter测试

image-20240620144351522

但是未被限流的请求延时依然很高:

image-20240620144417302

导致最终的平均响应时间较长。

4.服务熔断

查询商品的RT较高(模拟的500ms),从而导致查询购物车的RT也变的很长。这样不仅拖慢了购物车服务,消耗了购物车服务的更多资源,而且用户体验也很差。

对于商品服务这种不太健康的接口,我们应该停止调用,直接走降级逻辑,避免影响到当前服务。也就是将商品查询接口熔断。当商品服务接口恢复正常后,再允许调用。这其实就是断路器的工作模式了。

Sentinel中的断路器不仅可以统计某个接口的慢请求比例,还可以统计异常请求比例。当这些比例超出阈值时,就会熔断该接口,即拦截访问该接口的一切请求,降级处理;当该接口恢复正常时,再放行对于该接口的请求。

断路器的工作状态切换有一个状态机来控制:

image-20240620144802785

状态机包括三个状态:

  • closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
  • open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态持续一段时间后会进入half-open状态
  • half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。
    • 请求成功:则切换到closed状态【正常状态】
    • 请求失败:则切换到open状态【熔断状态】

4.1 控制台设置熔断规则

image-20240620142613725

这种是按照慢调用比例来做熔断,上述配置的含义是:

  • RT超过200毫秒的请求调用就是慢调用
  • 统计最近1000ms内的最少5次请求,如果慢调用比例不低于0.5,则触发熔断
  • 熔断持续时长20s

4.2 Jmeter测试

image-20240620145038189

此时整个购物车查询服务的平均RT影响不大:

image-20240620145054930

==Sentinel服务保护总结==

1.具体使用操作

  • 1.启动sentinel控制台(jar包)

到这已经可以打开sentinel控制台

  • 2.具体使用的微服务pom.xml文件导入sentinel依赖
  • 3.具体使用的微服务yml文件配置sentinel控制台信息

到这已经可以在sentinel控制台查看

具体的四大解决方案【1请求限流,2线程隔离,4服务熔断在平台设置就行】

  • 1和2.控制台通过具体微服务-簇点链路的流控按钮设置请求限流(设置QPS)和线程隔离(设置并发线程数)

  • 3.fallback快速失败需要编写代码(Openfeign所在微服务进行编写)

  • ①在Openfeign所在微服务编写降级处理类实现FallbackFactory重写create方法里面将ItemClient被其他微服务调用的方法都重写处理(后备方案,可以是信息提示等)

  • ②将降级处理类加入bean

  • ③在微服务ItemClient的FeignClient注解添加属性fallbackFactory=①降级处理类

  • 4.控制台通过具体微服务-簇点链路的熔断按钮设置熔断策略

2.四大解决方案总结

1.请求限流(限制访问量)和线程隔离(限制线程数,不让此服务占用所有资源)相当于对单个微服务调用做保护

2.快速失败(如果请求限流/线程隔离资源没了,那就可以走这个fallback后备方案,不至于直接抛出异常)和服务熔断(提前对这个服务判断如果经常出异常,那就熔断)是在微服务调用其他微服务情况时候做保护

如果比喻成买火车票乘车:

1.请求限流就是限制购买人数

2.线程隔离就是车次分到A候车厅(六个通道)和B候车厅(四个通道),如果B满了或者速度太慢可以去A候车厅的通道,不至于直接瘫痪。

3.服务熔断就是我提前预测哪里经常会出问题,我可以一段时间内不让大家去那候车可以去备用候车厅;如果一段时间后,我可以测试走一个人如果通那就打开去候车,如果不行那就继续用备用候车厅。

4.快速失败就是如果这个通道走不了,那你不能让乘客不上车啊,所有可以大喇叭提示等待或者提供备用候车厅上车

==底层原理==

无论是①Hystix还是②Sentinel都支持线程隔离,实现方式不同。

①Hystix:线程池隔离[默认],信号量隔离

②Sentinel:信号量隔离

1.线程隔离

线程隔离有两种方式实现:

  • 线程池隔离:给每个微服务调用业务分配一个线程池【利用线程池本身实现隔离效果】
  • 信号量隔离:给每个业务设定线程数量,达到信号量上限时就禁止新的请求【不创建线程池,而是使用计数器模式】
image-20241009100448925

两者的优缺点:

信号量隔离 线程池隔离
优点 轻量级,无额外开销 支持主动超时,支持异步调用
缺点 不支持主动超时,不支持异步调用 线程的额外开销比较大
场景 高频调用,高扇出 低扇出

2.四种算法

在熔断功能中,需要统计异常请求或慢请求比例,也就是计数。在限流的时候,要统计每秒钟的QPS,同样是计数。可见计数算法在熔断限流中的应用非常多。sentinel中采用的计数器算法就是滑动窗口计数算法。

2.1 固定窗口计数

image-20241009102754788

  • 每个窗口维护1个计数器,每有1次请求就将计数器+1。限流就是设置计数器阈值,本例为3,图中红线标记
  • 如果计数器超过了限流阈值,则超出阈值的请求都被丢弃。

image-20241009102955454

说明:

  • 第1、2秒,请求数量都小于3,没问题
  • 第3秒,请求数量为5,超过阈值,超出的请求被拒绝

特殊情况:【无法结合前后的时间窗口的数据做综合统计】—只能统计当前某1个时间窗的请求数量是否到达阈值

image-20241009190539989

说明:

  • 假如在第5、6秒,请求数量都为3,没有超过阈值,全部放行
  • 但是,如果第5秒的三次请求都是在4.5-5s之间进来;第6秒的请求是在5-5.5s之间,那么4.5-5s之间就有6次请求!也就是说每秒的QPS达到了6,远超阈值。

2.2 滑动窗口计数

固定时间窗口算法中窗口有很多,其跨度和位置是与时间区间绑定,因此是很多固定不动的窗口。而滑动时间窗口算法中只包含1个固定跨度的窗口,但窗口是可移动动的,与时间区间无关。

具体规则如下:

  • 窗口时间跨度Interval大小固定,例如1秒
  • 时间区间跨度为Interval / n ,例如n=2,则时间区间跨度为500ms
  • 窗口会随着当前请求所在时间currentTime移动,窗口范围从currentTime-Interval时刻之后的第一个时区开始,到currentTime所在时区结束。

image-20241009194901251

限流阈值依然为3,绿色小块就是请求,上面的数字是其currentTime值。

  • 在第1300ms时接收到一个请求,其所在时区就是1000~1500
  • 按照规则,currentTime-Interval值为300ms,300ms之后的第一个时区是5001000,因此窗口范围包含两个时区:5001000、1000~1500,也就是粉红色方框部分
  • 统计窗口内的请求总数,发现是3,未达到上限。

若第1400ms又来一个请求,会落在1000~1500时区,虽然该时区请求总数是3,但滑动窗口内总数已经达到4,因此该请求会被拒绝:

image-20241009194936726

假如第1600ms又来的一个请求,处于15002000时区,根据算法,滑动窗口位置应该是10001500和1500~2000这两个时区,也就是向后移动:

image-20241009194947384

这就是滑动窗口计数的原理,解决了我们之前所说的问题。而且滑动窗口内划分的时区越多,这种统计就越准确。

2.3 令牌桶算法(Sentinel的热点参数)

其基本思路如图:【Sentinel中的热点参数(一段时间内频繁访问的用户id)限流

image-20241009201027208

说明:

  • 生成令牌:以固定的速率生成令牌,存入令牌桶【令牌桶满了以后,多余令牌丢弃】
  • 进入请求:必须先尝试从桶中获取令牌,①获取到令牌后才可以被处理②令牌桶中没有令牌,则请求等待或丢弃

基于令牌桶算法,每秒产生的令牌数量==QPS上限

当然也有例外情况,例如:

  • 某一秒令牌桶中产生了很多令牌,达到令牌桶上限N,缓存在令牌桶中,但是这一秒没有请求进入。
  • 下一秒的前半秒涌入了超过2N个请求,之前缓存的令牌桶的令牌耗尽,同时这一秒又生成了N个令牌,于是总共放行了2N个请求。超出了我们设定的QPS阈值。

因此,在使用令牌桶算法时,尽量不要将令牌上限设定到服务能承受的QPS上限。而是预留一定的波动空间,这样我们才能应对突发流量。

2.4 漏桶算法(Sentinel的排队等待)

漏桶算法与令牌桶相似,但在设计上更适合应对并发波动较大的场景,解决令牌桶中的问题。

简单来说就是请求到达后不是直接处理,①放入一个队列。②固定的速率从队列中取出并处理请求。[叫漏桶算法,就是把请求看做水,队列看做是一个漏了的桶]

image-20241009203016735

漏桶的优势就是流量整型,桶就像是一个大坝,请求就是水。并发量不断波动,就如图水流时大时小,但都会被大坝拦住。而后大坝按照固定的速度放水,避免下游被洪水淹没。

因此,不管并发量如何波动,经过漏桶处理后的请求一定是相对平滑的曲线:

image-20241009203304507

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. ==微服务拆分容易出现的问题==
  2. 2. 1.雪崩问题
    1. 2.1. 1.1 产生背景
    2. 2.2. 1.2 产生原因
    3. 2.3. 1.3 解决方案
      1. 2.3.1. 1.请求限流(降低访问流量)
      2. 2.3.2. 2.线程隔离(降低独占资源数量)
      3. 2.3.3. 3.快速失败(fallback后备方案)
      4. 2.3.4. 4.服务熔断(提前预测,不对劲就fallback)
  3. 3. 2. 常见服务保护技术
  4. 4. ==Sentinel==
  5. 5. ==服务保护方案–Sentinel基础使用==
  6. 6. 1.介绍
  7. 7. 2.安装
    1. 7.1. 2.1 下载jar包
    2. 7.2. 2.2 启动测试
  8. 8. 3.微服务整合
    1. 8.1. 3.1 引入依赖
    2. 8.2. 3.2 yml配置控制台
    3. 8.3. 3.3 测试
    4. 8.4. 3.4 簇点链路
  9. 9. ==服务保护方案–Sentinel四大解决方案==
  10. 10. 1.请求限流
    1. 10.1. 1.1 控制台设置限流QPS
    2. 10.2. 1.2 Jmeter测试
  11. 11. 2.线程隔离
    1. 11.1. 2.1 控制台设置并发线程数
    2. 11.2. 2.2 Jmeter测试
  12. 12. 3.快速失败-Fallback(后备方案)
    1. 12.1. 3.1 两种配置方式
    2. 12.2. 3.2 举例-以方式二为例:
      1. 12.2.1. 3.2.1 yml导入依赖
      2. 12.2.2. 3.2.2 编写降级处理类
      3. 12.2.3. 3.2.3 注入bean
      4. 12.2.4. 3.2.4 给对应Openfeign添加属性
      5. 12.2.5. 3.2.5 Jmeter测试
  13. 13. 4.服务熔断
    1. 13.1. 4.1 控制台设置熔断规则
    2. 13.2. 4.2 Jmeter测试
  14. 14. ==Sentinel服务保护总结==
  15. 15. 1.具体使用操作
  16. 16. 2.四大解决方案总结
  17. 17. ==底层原理==
  18. 18. 1.线程隔离
  19. 19. 2.四种算法
    1. 19.1. 2.1 固定窗口计数
    2. 19.2. 2.2 滑动窗口计数
    3. 19.3. 2.3 令牌桶算法(Sentinel的热点参数)
    4. 19.4. 2.4 漏桶算法(Sentinel的排队等待)
,