2.4.1流控效果-warm up
阈值一般是一个微服务能承担的最大QPS 但是一个服务刚刚启动时 一切资源尚未初始化(冷启动)
如果直接将QPS跑到最大值 可能导致服务瞬间宕机
因此出现warm up也叫预热模式 是应对服务冷启动的一种方案 请求阈值初始值是 maxThreshold / coldFactor
持续指定时长后 逐渐提高到maxThreshold值 而coldFactor的默认值是3
例如 我设置QPS的maxThreshold为10 预热时间为5秒
那么初始阈值就是10/3 也就是3 然后在5秒后逐渐增长到10
2.4.2流控效果-排队等待
当请求超过QPS阈值时 快速失败和warm up 会拒绝新的请求并抛出异常
而排队等待则是让所有请求进入一个队列中 然后按照阈值允许的时间间隔依次执行
后来的请求必须等待前面执行完成 如果请求预期的等待时间超出最大时长 则会被拒绝
工作原理:
例如:QPS = 5 意味着每200ms处理一个队列中的请求
timeout = 2000意味着预期等待时长超过2000ms的请求会被拒绝并抛出异常。
2.4.3总结
流控效果
- 快速失败:QPS超过阈值时,拒绝新的请求
- warm up: QPS超过阈值时 拒绝新的请求 QPS阈值是逐渐提升的 可以避免冷启动时高并发导致服务宕机
- 排队等待:请求会进入队列 按照阈值允许的时间间隔依次执行请求
如果请求预期等待时长大于超时时间 直接拒绝
2.5热点参数限流
限流是统计访问某个资源的所有请求 判断是否超过QPS阈值
而热点参数限流是分别统计参数值相同的请求 判断是否超过QPS阈值
2.5.1全局参数限流
例如,一个根据id查询商品的接口:
访问/goods/{id}的请求中 id参数值会有变化 热点参数限流会根据参数值分别统计QPS 统计结果:
当id=1的请求触发阈值被限流时 id值不为1的请求不受影响
配置示例:
代表的含义是:对hot这个资源的0号参数(第一个参数)做统计 每1秒相同参数值的请求数不能超过5
2.5.2热点参数限流
刚才的配置中,对查询商品这个接口的所有商品一视同仁,QPS都限定为5.
而在实际开发中 可能部分商品是热点商品 例如秒杀商品 希望这部分商品的QPS限制与其它商品不一样 高一些
那就需要配置热点参数限流的高级选项了:
结合上一个配置 这里的含义是对0号的long类型参数限流 每1秒相同参数的QPS不能超过5 有两个例外:
- 如果参数值是100,则每1秒允许的QPS为10
- 如果参数值是101,则每1秒允许的QPS为15
3.隔离和降级
限流是一种预防措施 虽然限流可以尽量避免因高并发而引起的服务故障 但服务还会因为其它原因而故障
而要将这些故障控制在一定范围 避免雪崩 就要靠线程隔离(舱壁模式)和熔断降级手段了
线程隔离之前讲到过:调用者在调用服务提供者时 给每个调用的请求分配独立线程池 出现故障时 最多消耗这个线程池内资源 避免把调用者的所有资源耗尽
熔断降级:是在调用方这边加入断路器 统计对服务提供者的调用 如果调用的失败比例过高 则熔断该业务 不允许访问该服务的提供者了
不管是线程隔离还是熔断降级 都是对客户端( 调用方)的保护
需要在调用方发起远程调用时做线程隔离、或者服务熔断
而我们的微服务远程调用都是基于Feign来完成的 因此我们需要将Feign与Sentinel整合
在Feign里面实现线程隔离和服务熔断
3.1FeignClient整合Sentinel
SpringCloud中 微服务调用都是通过Feign来实现的 因此做客户端保护必须整合Feign和Sentinel
3.1.1修改配置 开启Sentinel功能
修改OrderService的application.yml文件 开启Feign的Sentinel功能:
feign: sentinel: enabled: true # 开启feign对sentinel的支持
3.1.2编写失败降级逻辑
业务失败后 不能直接报错 而应该返回用户一个友好提示或者默认结果 这个就是失败降级逻辑
给FeignClient编写失败后的降级逻辑
- 方式一:FallbackClass 无法对远程调用的异常做处理
- 方式二:FallbackFactory 可以对远程调用的异常做处理
通过方式二进行失败降级处理:
步骤一: 在feing-api项目中定义类,实现FallbackFactory
package cn.itcast.feign.clients.fallback; import cn.itcast.feign.clients.UserClient; import cn.itcast.feign.pojo.User; import feign.hystrix.FallbackFactory; import lombok.extern.slf4j.Slf4j; /** * @author bobochang * @description * @created 2022/5/26-09:17 **/ @Slf4j public class UserClientFallBackFactory implements FallbackFactory<UserClient> { @Override public UserClient create(Throwable throwable) { return new UserClient() { @Override public User findById(Long id) { // 日志记录异常信息 log.error("查询结果异常",throwable); // 返回默认数据 空对象 return new User(); } }; } }
步骤二: 在feing-api项目中的DefaultFeignConfiguration类中将UserClientFallbackFactory注册为一个Bean
@Bean public UserClientFallbackFactory userClientFallbackFactory(){ return new UserClientFallbackFactory(); }
步骤三: :在feing-api项目中的UserClient接口中使用UserClientFallbackFactory
@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class) public interface UserClient {}
重启后,访问一次订单查询业务,然后查看sentinel控制台,可以看到新的簇点链路:
3.1.3总结
Sentinel支持的雪崩解决方案:
- 线程隔离(仓壁模式)
- 降级熔断
Feign整合Sentinel的步骤:
- 在application.yml中配置:feign.sentienl.enable=true
- 给FeignClient编写FallbackFactory并注册为Bean
- 将FallbackFactory配置到FeignClient
3.2线程隔离(舱壁模式)
3.2.1线程隔离方式
线程隔离有两种方式实现:
- 线程池隔离
- 信号量隔离(Sentinel默认采用)
线程池隔离:给每个服务调用业务分配一个线程池 利用线程池本身实现隔离效果
信号量隔离:不创建线程池 而是计数器模式 记录业务使用的线程数量 达到信号量上限时 禁止新的请求