上一节整理了Sentinel的限流,限流可以降低微服务的负载,避免因为高并发而故障,进而传递给其他相关服务而引发服务雪崩。以上仅为避免服务故障,而当某个服务真正故障时,如何处理才能防止服务雪崩? ⇒ Sentinel支持隔离和降级两种方案
文章目录
- 1、线程隔离和熔断降级
- 2、Feign整合Sentinel
- 3、FallbackFactory代码逻辑
- 4、线程隔离
- 5、Sentinel熔断的实现原理
- 6、熔断策略--慢调用
- 7、熔断策略--异常比例、异常数
1、线程隔离和熔断降级
采用线程隔离,即舱壁模式:
- 业务1:服务A到服务B,限制10线程数
- 业务2:服务A到服务C,限制10线程数
此时,服务C故障,最大损失10个线程,不会长期占用其他线程,如此,服务A到服务B仍可正常访问,阻止了故障的传递。
熔断降级,即当请求的失败比例超过阈值时,熔断器阻断服务A到服务D的请求,以后服务A到服务D的请求过来就会直接失败,它没压根机会去访问,也就不会导致资源耗尽,也就不会发生故障传递。
可以看到,不管是线程隔离还是熔断降级,都是对服务调用方的保护,别因为别的服务故障而拖垮自己。
2、Feign整合Sentinel
SpringCloud中,微服务调用都是通过Feign来实现的,因此做客户端保护必须整合Feign和Sentinel
- 修改服务调用方的application.yaml文件,开启Feign的Sentinel功能
feign:
sentinel:
enabled: true # 开启Feign的Sentinel功能
- 给FeignClient编写失败后的降级逻辑(被阻断了,走另一条路,比如返回msg调用xxService失败)
方式一:FallbackClass,不能对远程调用的异常做处理
方式二:FallbackFactory,可以对远程调用的异常做处理,常选这种
3、FallbackFactory代码逻辑
这是未处理前,feign处理的接口:
@FeignClient(value = "userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
- 在feing-api项目中定义类,实现FallbackFactory接口,且FallbackFactory的泛型中即为关联接口的类型
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> { //泛型中指定关联的接口的类型
@Override
public UserClient create(Throwable throwable) {
// 创建UserClient接口实现类(匿名内部类),实现其中的方法,编写失败降级的处理逻辑
return new UserClient() {
@Override
public User findById(Long id) {
// 记录异常信息
log.error("查询用户异常", throwable);
// 根据业务需求返回默认的数据,这里是空用户
return new User();
}
};
}
}
- 在模块的配置类DefaultFeignConfiguration中将UserClientFallbackFactory注册为一个Bean:
public class DefaultFeignConfiguration {
//new个对象返回
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
}
- 在feing-api项目中的UserClient接口中用fallbackFactory属性,使用UserClientFallbackFactory
@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
- 重启服务,在Sentinel簇点链路中可以看到被远程调用的服务
到此,Feign整合Sentinel成功。也可再看下降级处理的效果(停掉被调用服务):
4、线程隔离
实现线程隔离有两种方式:
- 线程池隔离
- 信号量隔离(Sentinel默认采用)
基于信号量即这个资源请求能用的线程就10个,有个计数器,用一个少一个,请求完后还回去,用完后面的请求就没得用了,以达到舱壁隔离的效果。
两种方式的优缺点:
- 基于计数器模式,简单,开销小
- 基于线程池模式,有额外开销,但隔离控制更强
Sentinel中的配置示例如下:
- QPS:就是每秒的请求数
- 线程数:是该资源能使用用的tomcat线程数的最大值。也就是通过限制线程数量,实现舱壁模式
需求:给 UserClient的查询用户接口设置流控规则,线程数不能超过 2
- Sentinel中添加流控规则,阈值类型选择线程数
- 启动Jmeter,对/order/101发起测试
- 查看结果数,10个请求,8个返回结果user为null且控制台输出报错日志(请求不会失败,因为上面已经做了降级的逻辑处理)
5、Sentinel熔断的实现原理
由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器才会放行访问该服务的请求。实现思路如下:
- 断路器处于closed时,一切请求都不被拦截,且会被统计异常和慢的比例
- 当比例达到失败阈值,断路器open,所有请求一过来就快速失败
- 当熔断时间结束,切换好半开放状态,即Half-Open
- Half-Open状态会尝试放行一些请求,如果依旧失败,则回到Open状态继续拦截,如果成功,则回到Closed状态
6、熔断策略–慢调用
达到某个阈值时,触发熔断,这个就是断路器的熔断策略,分为这三种:慢调用、异常比例、异常数
慢调用:业务的响应时长(RT)大于指定时长的请求认定为慢调用请求。在指定时间内,如果请求数量超过设定的最小数量,慢调用比例大于设定的阈值,则触发熔断。
关于慢调用,即正常蹲坑6分钟,你蹲一小时,半天不释放资源,自然会影响到其他请求。
以上这个Sentinel配置,即:
- RT超过500ms的调用是慢调用
- 统计最近10000ms内的请求
- 如果请求量超过10次,并且慢调用比例不低于0.5,则触发熔断
- 熔断时长为5秒,然后进入half-open状态,放行一次请求做测试
接下来完成一个实际例子:
需求:
给 UserClient的查询用户接口设置降级规则,慢调用的RT阈值为50ms,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5
为了触发慢调用规则,这里修改UserService中的业务,使用休眠来模拟慢调用:
开始演示:
- 配置Sentinel降级规则
- 狂刷/order/101接口(或者Ctrl+R),/order/101调用的user id就是1,即可触发熔断
- 此时访问其他用户,也返回空,因为阻断器open,直接挡回去了
- 估摸着5s过去了,阻断器进入到了Half-Open,再重新请求,可以看到正常返回了,当然此时熔断器变为Closed状态
7、熔断策略–异常比例、异常数
一定时间内的请求,异常请求所占的比例达到设定的比例阈值,或者异常请求的数量达到了设定的数量阈值,即熔断。
此配置即统计最近1000ms内的请求,如果请求量超过10次,并且异常比例不低于0.4,则触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。
此配置即统计最近1000ms内的请求,如果请求量超过10次,并且异常数不低于2个,就触发熔断,熔断时长为5秒。然后进入half-open状态,放行一次请求做测试。
需求:
给 UserClient的查询用户接口设置降级规则,统计时间为1秒,最小请求数量为5,失败阈值比例为0.4,熔断时长为5s
这里手动抛异常模拟异常的发生:
- 配置降级规则
- 刷/order/102,查uid为2的用户,达到异常比例来触发熔断
- 然后再访问/order/103,此时103立即失败(被阻断了,所以耗时极小,这里只有7ms)
- 等过了阻断器open的时间,再访问就正常了
熔断策略小结: