引入
微服务的雪崩效应
假如我们开发了⼀套分布式应⽤系统,前端应⽤分别向A/H/I/P四个服务发起调用请求:
但随着时间推移,假如服务 I 因为优化问题,导致需要 20 秒才能返回响应,这就必然会导致20秒内该请求线程会⼀直处于阻塞状态。
但是,如果这种状况放在高并发场景下,就绝对不允许出现,假如在 20 秒内有 10 万个请求通过应用访问到后端微服务。容器会因为大量请求阻塞积压导致连接池爆满,而这种情况后果极其严重. 轻则"服务无响应",重则前端应⽤直接崩溃。以上这种因为某⼀个节点长时间处理导致应用请求积压崩溃的现象被称为微服务的"雪崩效应"。
如何有效避免雪崩效应?
刚才我们分析了雪崩现象,是因为出现瞬间⼤流量+微服务响应慢造成的.针对这两点在架构设计时要采用不同方案:
● 采用限流方式:控制请求的流入,让流量有序的进入应用,保证流量在⼀个可控的范围内;
● 采用服务降级与熔断
以下图为例,在用户支付完成后,通过消息通知服务向⽤户邮箱发送"订单已确认"的邮件, 但假设消息通知服务出现异常需要10秒钟才能完成发送请求, 这是不能接受的。为了预防雪崩,我们可以在微服务体系中增加服务降级的功能,预设2秒钟有效期,如遇延迟变大最多允许2秒,2秒内服务未处理完成则直接降级并返回响应,此时支付服务会收到"邮件发送超时"的错误信息。这也就意味着消息通知服务最多只能有两秒钟的处理时间,要么发送成功,要么超时降级。 因此阻塞时间缩短,产生雪崩的概率会大大降低。
1.Sentinel和Hystrix
1.1.限流和熔断
限流 , 限制流量,这里的流量我们可以理解成请求数量,其实就是限制服务器的请求并发数量
为什么要这么做?如果不做限流,那么在大量并发请求下我们的服务器会慢慢的变慢然后顶不住压力而挂掉(类似堵车)。并不是说并发越大越好,有的时候我们的项目规模和业务决定了我们不需要那么大的并发性,当大量的并发请求访问到服务器时我们需要把部分请求拒绝在外,这个是流量限制 —— 限流。
熔断机制是对服务调用链路的保护机制,如果链路上的某个服务不可访问,调用超时,发生异常等,服务会进行触发熔断,触发降级返回托底数据。
简单理解就是当服务器不可访问,可以返回一些预先准备好的兜底数据给用户,比如友好的提示信息,不至于直接向客户抛出异常。
1.2.Hystrix的熔断和资源隔离
Hystrix的资源隔离可以通过线程池隔离和信号量隔离两种方式来实现对流量的控制。
线程池隔离: 造成线程上下切换对资源的消耗比较大
信号量隔离: 效果不错,但是无法对慢调用进行自动降级
1.3.Sentinel介绍
Sentinel诞生于阿里巴巴,
其主要目标是流量控制和服务熔断
。
通过限制并发线程的数量来减少不稳定资源的影响,而不是使用线程池,省去了线程切换的性能开销。
当线程数累积到一定数量时,新的传入请求将被拒绝。
资源的响应时间太大时,将在指定的时间窗口中拒绝所有对该资源的访问。——熔断机制
2.Sentinel限流实战(重要)
2.1.Sentinel Server服务端
Sentinel 提供了现成的服务端供我们使用,下载地址如下:
Releases · alibaba/Sentinel (github.com)
下载之后通过命令行启动
nohup java -jar -Dserver.port=1111 sentinel-dashboard-1.8.6.jar > sentinel.log 2>&1 &
访问:http://127.0.0.1:1111
进入控制台,使用账号、密码进行登录。
账号:sentinel 密码:sentinel
注意:只有1.6.0及以上版本才有这个登录页面。默认用户名和密码都是sentinel
。对于用户登录的相关配置可以在启动命令中增加下面的参数来进行配置:
-Dsentinel.dashboard.auth.username=sentinel
: 用于指定控制台的登录用户名为 sentinel;
-Dsentinel.dashboard.auth.password=123456
: 用于指定控制台的登录密码为 123456;如果省略这两个参数,默认用户和密码均为 sentinel
-Dserver.servlet.session.timeout=7200
: 用于指定 Spring Boot 服务端 session 的过期时间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟;
-Dserver.port=1111
:配置端口
主页面效果:
2.2.Sentinel 客户端接入
1.导入依赖
加入sentinel依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.配置Sentinel
修改yml配置,添加senticel服务控制台地址
spring:
cloud:
sentinel:
transport:
dashboard: 192.168.10.100:1111
3.资源限流
Sentinel为我们提供了 @SentinelResource
注解标记需要限流的资源。
案例如下:
@Value("${temp.notify}")
private String notify;
@GetMapping("/user/{id}")
//限流降级
@SentinelResource(value="user",blockHandler="exceptionHandler")
public String getById(@PathVariable Long id){
return id + "===" + notify;
}
// 限流与阻塞处理 : 参数要和 被降级的方法参数一样
public String exceptionHandler(@PathVariable Long id, BlockException ex) {
ex.printStackTrace();
System.out.println("限流了...");
return "被限流了!!!";
}
提示:这里通过@SentinelResource
的value属性为资源取名为 “user” ,后续我们可以根据该资源名来进行限流。
同时这里通过 blockHandler
属性我配置了一个限流降级方法,即当“user”资源触发限流了会调用blockHandler
指向的降级方法返回兜底数据,不至于抛出默认的限流异常信息给客户端,需要注意的是:降级方法要和被限流的方法参数一致,然后加上 BlockException异常对象。
当然,也可以通过 blockHandlerClass 属性把降级方法写在一个专门的类中,如:
@SentinelResource(value="user",blockHandler="exceptionHandler"
,blockHandlerClass=ExceptionUtil.Class)
降级类
public final class ExceptionUtil {
public static User exceptionHandler(Long id ,lockException ex) {
//...
}
}
2.3.Sentinel设置限流策略
发送请求http://localhost:1010/user/11
然后登录Sentinel控制台,在“实时监控”列表中可以看到资源的相关监控信息的
在 “族点链路” 列表中可以看到资源的调用链 ,并且可以通过“流控”按钮设置流控规则
也可以在“流量控制”菜单中我们可以针对资源进行限流规则的设置。如下:
这里我添加了一个流控规则,资源名对应客户端 @SentinelResource(value="user"..
注解的资源,通过QPS限流(每秒请求数量),阈值是 1 ,意思是“user”这个资源每秒只能有1个请求进来,多余的请求会触发限流,返回降级数据。
2.4.限流测试
通过浏览器频发访问 “user”资源,当QPS大于1就会触发限流,效果如下:
3.Sentinel流控模式
3.1.直接
Sentinel默认的流控处理就是【直接->快速失败】,QPS达到阈值,当前资源直接失败
。在流控规则中配置如下:
3.2.关联
关联的资源达到某个阈值,限流自己,如:限流的资源是/user/delete
,关联的资源是/user/list
,当/user/list
达到阈值,限流user/delete
, 举例: 支付并发太高,可以限制下单的流量
3.3.链路
限流线路调用链路的入口,如 /user/list
资源中 调用了 /dept/list
,对/dept/list
添加限流,当/dept/list
达到阈值,其实限流的是/user/list
,因为他是访问的入口
4.Sentinel流控效果
4.1.快速失败
快速失败:(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)是默认的流控方式,当流量达到阀值直接返回异常
,QPS达到任何规则阈值后,后续请求就会立即拒绝
,并抛出FlowException 异常。简单理解:并发太高,直接请求拒绝
4.2.Warm Up预热
Warm Up预热:(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,根据codeFactor(默认3)的值,从(阀值/codeFactor)为初始阀值,经过预热时长,才到达设置的QPS的阀值,即预热/冷启动方式。简单理解:慢慢的增大处理并发的能力
提示:初始的QPS阈值为 100 / 3 =33 ,10秒后 QPS阈值达到 100.
当系统长期并发不高,流量突然增加可能会直接把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值的上限,给系统一个预热的时间,避免冷系统被压垮。
4.3.排队等待
排队等待(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER),忽然增加的请求并发量达到了限流阈值后续请求会被拒绝,有时候我们可能更希望后续的请求可以加入队列进行排队,慢慢执行,而不是直接拒绝请求,这种方式严格控制请求通过的时间间隔,也即是让请求以均匀的速度通过,对应的是漏桶算法,这种方式主要用于处理间隔性突发的流量,例如消息队列。 简单理解:突发流量处理不过来,让请求排队。
提示:QPS阈值为100,超过的请求会排队,排队超时时间为 10S
5.热点限流
还有一种特殊的动态限流规则,用于限制动态的热点资源 , 比如对同一个用户的请求频率做限定,比如对参数进行限定,比如对参数的值做限定(比如对商品ID为1的资源做限流)。
5.1.参数限流
参数限流就是 对资源的参数进行限流,我们来编写一个方法,接受两个参数:p1和p2,并设置好限流降级。
//限流降级
@SentinelResource(value="/parameterLimit",blockHandler="parameterLimitHandler")
@GetMapping(value="/parameterLimit")
public String parameterLimit(@RequestParam("p1") String p1 ,@RequestParam("p2") String p2){
return "parameterLimit方法调用成功...";
}
// 限流与阻塞处理
public String parameterLimitHandler(@RequestParam("p1") String p1 ,@RequestParam("p2") String p2,BlockException ex) {
return "限流了...";
}
配置热点规则 , 对第一个参数限流 , 当第一个参数超过了1的QPS就熔断降级。
5.2.参数值限流
对某一个参数的值满足某种条件的时候就进行限流,如下配置
意思是第一个参数的值为 haha 的时候限流阈值为10 , 超过 QPS > 10的并发就限流。
举例:应用场景比如说商品名称为“华为p40”的商品并发特别高,我们可以针对参数商品名为“华为p40”的商品进行限流。
6.系统规则
6.1.配置全局限流规则
系统规则可以看做是总的限流策略,所有的都要受到系统规则的限制。 上面的意思是最大并发只能允许 10 个线程数,并且是作用于全局的。
五.Sentinel熔断
1.概述
1.1.什么是熔断
在上一章节我们探讨了Sentinel的流控(限流)功能,Sentinel除了流控还提供了服务熔断和降级机制,服务之间的调用关系错综复杂,微服务的调用链上的某些服务资源不稳定(宕机,异常,超时)可能会导致可能请求的失败和请求的堆积,调用链产生连锁反应可能会导致整个微服务架构瘫痪
。服务熔断降级机制是保障高可用的重要措施之一。
1.2.Sentinel熔断
Sentinel的服务熔断机制会对调用链上的某个不稳定(宕机,异常,超时)的资源,做出请求限制,快速失败,避免影响到其它的服务而导致级联错误
。资源熔断后,在后续的一定时间(时间窗口)之内,对该服务的请求都自动熔断,抛出 DegradeException异常。
Sentinel拥有比Hystrix更强大和丰富的功能,能满足我们的各种应用场景,并且经历过淘宝双十一的考验,是微服务架构中熔断机制的不二选择。
2.Sentinel熔断实战
2.1.资源熔断降级
修改springcloudalibaba-user-1010工程,修改UserController ,通过@SentinelResource
注解的fallback
属性指定降级方法。
@GetMapping("/user/{id}")
//限流降级
@SentinelResource(value="user",blockHandler="exceptionHandler",fallback = "getByIdfallback")
public User getById(@PathVariable Long id){
int i = 1 / 0; //方法异常,触发熔断
return new User(id,"zs:"+id, "我是zs");
}
// 限流降级
public User exceptionHandler(@PathVariable Long id, BlockException ex) {
ex.printStackTrace();
System.out.println("限流了...");
return new User(-1L,"限流了","限流了");
}
// 熔断降级,参数和返回值与源方法一致
public User getByIdfallback(@PathVariable Long id){
System.out.println(notify);
return new User(id,"zs:"+id, "熔断托底了");
}
提示:方法中通过 int i = 1 / 0;
模拟异常,然后会熔断触发降级调用降级方法 。 通过 fallback
属性指定熔断的降级方法 ,熔断方法参数也要和被熔断方法参数一致。
注意:这里可以通过 @SentinelResource
注解的 exceptionsToTrace
属性忽略异常,即针对某个异常不熔断。
2.2.配置降级策略
在Sentinel控制台,在族点链路菜单中找到“user”资源,然后点击“降级”按钮添加降级策略,如下: 这里的降级策略为“RT”,大概意思是:如果并发数大于5 (QPS > 5) ,然后平均响应时间大于200毫秒,那么接下来的2秒钟之内对该资源的请求会被熔断降级。
2.3.测试熔断
启动springcloudalibaba-user-1010工程,访问 http://localhost:1010/user/2
,浏览器返回: 这里已经返回了托底数据,其实是因为“user”资源方法中抛出了异常,触发了熔断降级。
3.熔断策略
资源在什么情况下会触发熔断降级?调用异常,达到流控,调用超时 都会触发熔断降级,在上面的案例中我们看到资源的降级策略有 RT,异常比例,异常数三种方式,我们可以通过这三种方式来定义资源是否稳定,决定是否要进行熔断降级。
3.1.慢调用比例RT
慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
是不是很生涩难懂?我们根据下面这个配置来理解:
我们挑关键信息来理解上面那句话 这里配置的RT是200意思是对资源的多次请求平均响应时间都超过200毫秒,意思是:
1s 内持续进入 5 个请求(即 QPS >= 5),这五个请求中有20%的平均响应时间都超过了200ms,后续的10秒之内(时间窗口)请求这个方法都被熔断,触发降级。
总结一下:RT其实就是平均相应时间太长资源熔断。
3.2.异常比例
异常比例(DEGRADE_GRADE_EXCEPTION_RATIO):每秒请求量 > 5 ,当资源的每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
根据下面这个配置来理解
上面配置的意思就是“user”这个资源的异常比例超过了0.2,即10个请求有两个都异常了,资源被熔断,在接下来的10秒钟之内请求这个方法都被熔断,触发降级。
总结一下:异常比例就是按照资源的请求失败率来熔断。
3.3.异常数
异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 s的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是秒级别的,若 timeWindow 小于熔断时长,则结束熔断状态后仍可能再进入熔断状态。
根据下面这个配置来理解
这里的意思是一秒内(1000ms),超过1个异常请求,服务进行熔断。后续请求都拒绝。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效
总结一下:异常数就是按照 异常的数量 来熔断。
4.OpenFeign整合Sentinel熔断
Spring Cloud Alibaba是Spring Cloud的一个子项目,OpenFeign是Spring Cloud的客户端负载均衡器,使用Spring Cloud Alibaba依然可以很方便的集成OpenFeign,如果要使用OpenFeign作为服务客户端负载均衡,那么我们需要考虑OpenFeign开启Sentinel进行服务熔断降级。
4.1.开启Sentinel
OpenFeign与Sentinel组件集成除了引入sentinel-starter
依赖关系之外,还需要在属性文件中启用Sentinel支持:feign.sentinel.enabled=true
feign:
sentinel:
enabled: true #熔断
4.2.给Feign接口降级
这里跟Feign开启Hystrix降级一样,直接可以使用fallback属性
@FeignClient(value = "user-server",fallback = UserClientFallback.class)
public interface UserClient {
@GetMapping("/user/{id}")
User getById(@PathVariable Long id);
}
4.3.编写降级类
@Component
public class UserClientFallback implements UserClient {
@Override
public User getById(Long id) {
return new User(-1L,"无此用户","无此用户");
}
}