深入理解Sentinel: 02 为什么需要服务降级以及常见的几种降级方式
为什么需要服务降级常见的降级方式有哪些上一篇跟大家分享了一个真实的服务雪崩的故事也分析了造成服务雪崩的真正原因那么如何才能避免服务雪崩的出现呢我知道你会说增加硬件没有什么是钱解决不了的。注意是避免在有限的硬件条件下避免流量突增导致服务雪崩。最好方案应该就是服务降级处理不过来就不处理了呗。当服务 B 业务线程池全部用满的状态时应该拒绝服务 A 的请求这一点 Dubbo 就做得很好了保护自己不被服务 A 拖垮服务 A 在服务 B 多次拒绝请求的情况下不应该再向服务 B 发送请求懂得体量它人不连累它人服务 A 在请求大量堆积的情况下也应该拒绝客户端的请求而不是继续堆积请求请求处理不过来堆积再多也没有任何意义。这些便是我们要讨论的服务降级。服务降级是服务自我保护的一种方式或者保护下游服务的一种方式用于确保服务不会受请求突增影响变得不可用至少确保服务不会奔溃。常见的服务降级实现方式有开关降级、限流降级、熔断降级。限流降级假设服务 A 需要依赖服务 B 完成客户端的一次请求那么服务 B 可以通过压测方式预测单节点所能处理的最大并发请求数只要最大并发数不超过自己的极限服务就能稳定运行。限制服务 B 处理最大并发请求就是限流例如限制每秒只处理 200 个请求超出的请求采取流量控制策略处理或直接拒绝或排队等待这便是限流降级。限流不仅可以限制单个节点的流量还可限制发往一个服务集群的流量即对一个服务集群限流。不过集群限流方式实现上需要合计单位时间内该集群的流量。流量控制除了直接拒绝外还可以采取一些策略尽可能处理更多的请求例如均速排队这种方式主要用于处理间隔性突发的流量例如在某一秒有大量的请求到来而接下来的几秒都处于空闲状态我们希望系统能够在接下来的空闲期间逐渐处理这些请求而不是在第一秒直接拒绝多余的请求。如果限流用在电商的下单场景会有什么影响一但限流就会有很多的用户下单失败这意味着收益的流失老板宁愿多花点钱搞多几台服务器也不愿看到用户想买东西都买不了的情况。所以限流降级不是解决这种问题的有效手段这种情况还是需要通过集群自动伸缩去解决。那限流降级适合哪些场景我认为秒杀场景最合适不过抢到商品的都是有效流量抢不到商品的都是无效流量对于无效流量我们可以采用直接拒绝或者匀速排队的流量控制策略。算法深度对比算法 实现复杂度 内存占用 是否支持突发 适用场景令牌桶 中 低几个long变量 是 允许流量突刺的通用场景漏桶 中 低 否 强制平滑流量的场景如数据库连接池保护滑动窗口 高 高需维护数组 可控 精确QPS控制防刷场景重点Sentinel的滑动窗口实现Sentinel并没有采用简单的令牌桶而是使用了滑动窗口LeapArray因为它能更精确地控制“每秒请求数”这种离散指标。它的核心代码逻辑简化版// 每个窗口存储一个 MetricBucket包含 passQps, blockQps, rt 等 public class WindowWrapT{private long windowStart;// 窗口开始时间 private T value;// 指标数据}// 滑动窗口数组通过时间戳定位到具体的窗口 public class LeapArrayT{private int windowLengthInMs;// 窗口长度如 1s private int sampleCount;// 窗口数如2-每个窗口 500ms private AtomicReferenceArrayWindowWrapTarray;}Sentinel默认统计1秒内的数据但通过将1秒拆分为2个500ms的窗口进行滑动实现了毫秒级的精准流控避免了令牌桶在窗口边界可能出现的“双倍请求”问题。限流的粒度与隔离在Java微服务中限流不能只做全局QPS更要做多维度限流。SentinelResource(valuegetOrder, blockHandlerhandleBlock, fallbackhandleFallback)public Order getOrder(Long orderId){// 业务逻辑}// 限流触发后的降级方法 public Order handleBlock(Long orderId, BlockException ex){//1. 记录限流日志异步打印避免影响主流程 //2. 返回友好提示returnOrder.createDegradeOrder(系统繁忙请稍后重试);}重点热点参数限流Sentinel支持针对特定的参数值如userId进行限流。原理是维护一个LRU结构的ParameterMetric对高频参数进行精细化控制。这是防止“恶意刷单”或“热点账户攻击”的关键。网关层限流 vs 服务层限流网关Spring Cloud Gateway适合做IP、设备指纹等粗粒度限流利用Redis Lua脚本做分布式限流。服务Spring Boot适合做业务维度如SKU、用户等级的限流通常使用Sentinel这种侵入式组件结合线程池隔离ThreadPoolExecutor来保护业务线程不被慢请求耗尽。熔断降级假设服务 A 需要依赖服务 B 完成客户端的一次请求服务 A 如果能够感知到服务 B 的状态在服务 B“不行”的时候不再去请求服务 B就能确保服务 A 自身不会受服务 B 的影响。那么如何知道服务 B 到底行不行呢假设一秒内向服务 b 发送 230 个请求结果有 30 个请求或超时异常或响应异常根据这个数字就可以预测后续请求服务 B 大概率也会响应异常。服务 B 已经处理不过来了那么后续的请求就没有必要再发送了反正发出去也是异常不如直接放弃。当服务 A 的下游服务 B 突然变得不可用或者不稳定时服务 A 可以自动切断与服务 B 的交互从而保证自己可用就像保险丝一样当电流异常升高到一定高度的时候保险丝切断电流这就是熔断降级。但是服务 B 不会一直不行当服务 B 恢复之后服务 A 也应该能感知到才行所以熔断需要以一个时长为周期比如 1 秒这个周期也称为时间窗口每个时间窗口都重新计算请求总数、异常总数这些指标数据这样就能实现自动恢复。熔断降级不是只能由“别人”来实现自己也可以实现。别人发现你的缺点可能会疏远你对你印象不好实际上我们自己也能够发现自己的缺点当自己发现自己缺点时可以及时弥补这一缺点避免给别人不好印象。熔断降级也是如此服务提供者也能自己统计接口的处理情况当发现请求处理不过来时触发熔断拒绝上游的请求如果可以自己自动伸缩就更好了。所以熔断降级可以在消费端实现也可以在提供端实现。如果对接的是第三方的接口那么就只能是在消费端实现。Sentinel 支持的系统负载保护也算是一种熔断降级方式。熔断降级的常见降级策略在每秒请求异常数超过多少时触发熔断降级在每秒请求异常错误率超过多少时触发熔断降级在每秒请求平均耗时超过多少时触发熔断降级响应异常数越多或者异常比率越大、平均耗时越高都说明服务的处理能力在下降。开关降级开关降级也是服务降级的一种实现方式。开关降级用于在有限的硬件条件下提升系统核心功能的并发处理能力以最少的硬件成本应对流量高峰。做电商项目的朋友可能接触最多的就是开关降级一般我们在搞大促之前都会通过开关方式将一些无关紧要的业务接口变成“不可用”。例如通过配置中心或者通过 Redis 控制服务降级开关当开关打开时需要降级的接口直接响应一个表示当前服务降级的状态码给调用者。控制服务降级开关的方式可以是人工也可以是定时任务在某个时段开启、某个时段关闭。定时任务控制开关方式适合固定时间段请求突增的场景例如点外卖的高峰期在中午那么就可以在 11 点左右打开开关在 13 点半之后关闭开关。实现方式不仅仅是布尔值在Java中开关降级的核心是配置的动态刷新。低级做法使用volatile变量 定时任务轮询数据库。高级做法利用配置中心如Nacos、Apollo的长连接监听机制。ComponentpublicclassDowngradeSwitch{// 使用 AtomicBoolean 保证内存可见性且 CAS 操作轻量privateAtomicBooleanrecommendSwitchnewAtomicBoolean(true);PostConstructpublicvoidinit(){// 监听 Nacos 配置变化ConfigService.addListener(downgrade.properties,(config)-{Stringvalueconfig.getProperty(recommend.downgrade,false);booleannewStatusBoolean.parseBoolean(value);recommendSwitch.set(newStatus);// 高级点记录配置变更日志发送到监控中心Metrics.recordConfigChange(recommendSwitch,newStatus);});}publicbooleanisRecommendOpen(){returnrecommendSwitch.get();}}重点为什么用AtomicBoolean而不是synchronized因为降级开关的判断频率极高每次请求AtomicBoolean基于CAS在低竞争下性能远优于加锁。避免配置中心故障的影响监听器启动时必须加载本地缓存配置防止配置中心宕机导致开关状态丢失Apollo有ConfigService.getAppConfig()的本地缓存机制。降级后的行为优雅的“空对象模式”开关降级后不是简单地return null而是返回一个有意义的空对象。publicclassRecommendService{publicListProductgetRecommendList(LonguserId){if(downgradeSwitch.isRecommendOpen()){// 降级返回静态的默认推荐列表来自本地内存或CDNreturngetDefaultRecommendList();}// 正常调用RPC或数据库returnrpcCall(userId);}privateListProductgetDefaultRecommendList(){// 使用 Guava LoadingCache 定时刷新本地默认数据returnLocalCache.get(default_recommend);}}重点降级时尽量避免查询任何外部依赖数据库、Redis、RPC否则可能引发级联降级。最佳实践是将兜底数据放在本地内存缓存Caffeine/Guava Cache中甚至直接使用final static常量。总结服务降级只是为了保障服务能够稳定运行应对流量突增用降级牺牲一些流量换取系统的稳定。限流降级与熔断降级都可以实现在消费端限流或者服务端限流限流可以采取流量控制策略处理超过阈值的流量。限流降级即便没有达到系统的瓶颈只要流量达到设定的阈值超出部分就会触发限流降级而熔断降级可以实现尽最大的可能去完成所有的请求容忍一些失败熔断降级也能自动恢复。开关降级适用于促销活动这种可以明确预估到并发会突增的场景。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2447324.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!