在微服务架构中,服务熔断和降级是保障系统高可用的重要手段。当某个服务出现故障或响应过慢时,如果没有有效的容错机制,故障可能会蔓延到整个系统,导致雪崩效应。熔断降级就是防止这种情况发生的关键技术。

我们团队最近完成了一次从旧单体系统到微服务架构的迁移,整个过程持续了三个多月。其中,熔断降级机制的搭建和调优是最关键的环节之一,也踩了不少坑。今天就来分享这次迁移的实战经验,希望能给正在做类似工作的团队一些参考。

一、为什么需要熔断降级

在讲迁移过程之前,先说说为什么熔断降级这么重要。

我们的旧系统是一个典型的单体应用,所有功能都在一个项目里,部署在几台服务器上。这种架构在业务初期没问题,开发部署都很简单。但是随着业务增长,系统越来越庞大,代码量超过了五十万行,团队人数也从最初的几个人增加到了二十多人。问题开始逐渐显现。

首先是耦合严重。所有模块共享同一个数据库,模块之间通过直接调用方法来通信,改一个地方可能影响到其他模块。有一次我们修改了用户模块的一个查询逻辑,结果导致订单模块的下单功能出了问题,排查了半天才找到原因。这种隐性的依赖让开发变得小心翼翼,每次发版都提心吊胆。

其次是扩展困难。单体应用只能整体扩展,即使只是某个模块压力大,也需要把整个应用多部署几个实例。我们的系统中,商品查询的访问量最大,但是为了支撑商品查询的流量,不得不把整个应用扩容,其他模块的资源就浪费了。而且单体应用启动慢,扩容一次需要十几分钟,应对突发流量的时候很被动。

最严重的问题是故障蔓延。单体应用中,任何一个模块出问题都可能影响整个系统。有一次,我们的推荐服务调用了一个第三方接口,那个第三方接口出了问题,响应特别慢。结果推荐服务的线程都被阻塞了,慢慢耗尽了应用的线程池,导致整个系统都无法响应。那次故障持续了将近一个小时,影响了所有业务,损失不小。

那次故障之后,我们意识到必须改变架构。经过讨论,决定向微服务架构迁移,把系统拆分成多个独立的服务,每个服务独立部署、独立扩展。同时,必须建立完善的服务容错机制,防止一个服务的故障蔓延到整个系统。熔断降级就是这个容错机制的核心。

二、熔断降级的基本概念

在深入实战之前,先简单回顾一下熔断降级的基本概念,方便后面的讨论。

熔断(Circuit Breaking)的概念来自于电路中的保险丝。当电路中的电流过大时,保险丝会自动断开,保护电路不被烧毁。在软件系统中,熔断的原理类似。当某个服务的错误率或响应时间超过阈值时,熔断器会自动"断开",后续的请求不再调用这个服务,而是直接返回失败或降级结果。这样可以防止故障服务的资源被耗尽,也避免了调用方因为等待响应而被阻塞。

熔断器一般有三种状态:关闭(Closed)、打开(Open)、半开(Half-Open)。正常情况下熔断器是关闭的,请求正常通过。当错误率达到阈值时,熔断器打开,请求直接被拒绝。打开一段时间后,熔断器进入半开状态,允许少量请求通过,探测服务是否恢复。如果这些请求成功,熔断器关闭,恢复正常;如果失败,继续保持打开状态。

降级(Degradation)是指当系统压力过大或者某个服务不可用时,有策略地放弃一些非核心功能,保证核心功能的可用性。比如电商系统在大促期间,可以降级商品推荐、用户评价这些非核心功能,把资源留给下单、支付这些核心功能。降级可以是自动的,也可以是手动的。

熔断和降级经常一起使用。熔断是一种自动的保护机制,当服务故障时自动触发;降级是一种更通用的容错策略,可以在各种场景下使用。熔断打开后,通常会执行降级逻辑,返回一个兜底结果,而不是直接报错。

三、旧系统的问题和迁移目标

我们的旧系统在服务容错方面几乎是空白。服务之间的调用都是直接的HTTP调用,没有超时控制,没有重试机制,更没有熔断降级。一个服务出问题,调用方就会一直等待,直到连接超时。而默认的连接超时时间很长,有三十秒,这就导致故障很容易蔓延。

具体来说,旧系统存在以下几个问题:

  1. 没有超时控制。服务调用的超时时间设置得很长,甚至有些地方没有设置超时,依赖底层的TCP超时。一个慢服务就能把调用方的线程池耗尽。
  2. 没有熔断机制。某个服务持续失败时,调用方还是会不断地发起请求,每次都等待超时,浪费资源。
  3. 没有降级方案。系统压力大的时候,所有功能都在跑,核心功能和非核心功能争抢资源,导致核心功能也受影响。
  4. 故障排查困难。服务调用链不透明,出了问题不知道是哪个服务引起的,排查故障需要翻日志、查代码,效率很低。

针对这些问题,我们制定了迁移的目标:

  1. 建立完善的服务熔断机制,防止故障蔓延。
  2. 实现服务降级能力,在必要时牺牲非核心功能保障核心功能。
  3. 统一服务调用的超时和重试策略,避免长时间阻塞。
  4. 建立服务监控和调用链追踪,快速定位问题。
  5. 保证迁移过程的平滑,不影响线上业务。

四、新系统的熔断降级方案设计

技术选型方面,我们选择了Hystrix作为熔断降级的核心框架。Hystrix是Netflix开源的容错库,功能完善,社区活跃,和Spring Cloud集成得很好。我们的微服务框架用的就是Spring Cloud,所以选Hystrix是顺理成章的事情。

整体方案分为以下几个层次:

1. 服务网关层

服务网关是所有请求的入口,我们在网关层做了第一层熔断降级。网关使用的是Spring Cloud Zuul,集成了Hystrix和Ribbon。对于每个后端服务,都配置了独立的Hystrix命令,设置了合理的超时时间和线程池大小。

当某个后端服务不可用时,网关层的熔断器会打开,请求直接返回降级结果。我们为不同类型的服务设计了不同的降级策略。对于查询类服务,降级时返回缓存的数据或者默认值;对于操作类服务,降级时返回友好的错误提示,引导用户稍后重试。

网关层还做了限流。我们使用了自研的限流组件,基于令牌桶算法实现,对每个接口的QPS进行限制。当请求量超过阈值时,直接拒绝多余的请求,防止后端服务被打垮。限流的阈值可以动态调整,大促期间可以临时调高。

2. 服务间调用层

服务之间的调用通过Feign客户端进行,Feign默认集成了Ribbon和Hystrix。我们为每个Feign客户端都启用了Hystrix支持,配置了统一的超时和熔断参数。

超时策略方面,我们根据不同服务的特点设置了不同的超时时间。核心服务比如用户服务、订单服务,超时时间设置得短一些,一般是两秒,因为这些服务响应应该很快,超时说明有问题。非核心服务比如推荐服务、搜索服务,超时时间可以长一些,五秒左右。所有服务调用都有超时,没有无限等待的情况。

重试策略方面,我们只对GET请求进行重试,POST请求不重试,避免重复提交。重试次数一般是一次,重试间隔用指数退避。重试只在连接异常和读超时时触发,业务异常不重试。

熔断参数方面,我们使用了Hystrix的默认参数作为基础,然后根据每个服务的实际情况进行调优。核心参数包括:

  • 滚动窗口时间:默认10秒
  • 最小请求数:默认20个,也就是10秒内至少20个请求才会触发熔断判断
  • 错误率阈值:默认50%,错误率超过50%就打开熔断器
  • 熔断打开时间:默认5秒,5秒后进入半开状态
  • 半开状态允许的请求数:默认5个,这5个请求都成功才关闭熔断器

线程池隔离方面,Hystrix默认使用线程池隔离,每个依赖服务用独立的线程池。这样即使某个依赖服务慢,也只会占用自己的线程池,不会影响其他服务的调用。我们根据每个服务的QPS和响应时间,计算了合理的线程池大小,一般是峰值QPS乘以平均响应时间再乘以1.5的系数。

3. 业务降级层

除了自动的熔断机制,我们还设计了业务层面的降级方案。每个服务都梳理了核心功能和非核心功能,制定了降级预案。

比如商品详情页,核心功能是商品基本信息展示和购买按钮,非核心功能是推荐商品、用户评价、浏览记录。当系统压力大的时候,可以降级非核心功能,只展示商品基本信息。降级可以通过配置中心动态开关,不需要发版。

我们还设计了多级降级策略。一级降级是关闭一些实时计算,改用缓存数据;二级降级是关闭非核心功能模块;三级降级是只保留最核心的读写功能。根据系统负载情况,逐级降级,保证核心功能的可用性。

降级开关通过配置中心管理,支持全局开关和服务级开关。运维人员可以在监控面板上看到系统的负载情况,需要降级的时候一键开启。同时也设置了自动降级,当系统的CPU使用率、内存使用率、接口响应时间等指标超过阈值时,自动触发降级。

4. 监控和告警层

熔断降级离不开完善的监控。我们搭建了一套完整的监控体系,包括服务监控、Hystrix监控、调用链追踪。

服务监控方面,使用Prometheus + Grafana,采集每个服务的QPS、响应时间、错误率、CPU、内存等指标,在Grafana面板上展示。设置了告警规则,当指标异常时通过短信和邮件通知。

Hystrix监控方面,使用Hystrix Dashboard + Turbine。Hystrix Dashboard可以实时展示每个熔断器的状态、请求量、错误率、线程池使用情况等。Turbine可以聚合多个服务的Hystrix数据,在一个面板上查看所有服务的熔断状态。当熔断器打开时,面板上会有明显的提示,运维人员可以第一时间发现。

调用链追踪方面,使用Zipkin。每个请求在网关层生成一个traceId,在服务调用过程中传递,记录每个服务节点的调用时间和结果。出了问题的时候,通过traceId可以快速定位到是哪个服务出了问题,耗时在哪里。调用链数据也存储在Elasticsearch中,支持按各种维度查询。

五、迁移过程

方案设计好了,接下来就是迁移实施。我们采用的是渐进式迁移策略,不是一次性把旧系统换掉,而是逐步把功能从旧系统迁移到新系统。

第一阶段:基础设施搭建

首先搭建微服务的基础设施,包括服务注册中心(Eureka)、配置中心(Spring Cloud Config)、服务网关(Zuul)、监控系统(Prometheus + Grafana + Zipkin)。这些基础设施是微服务运行的基础,必须先搭好。

这个阶段还包括制定开发规范,比如服务拆分原则、接口设计规范、异常处理规范、日志规范等。规范先行,避免后面各个服务各自为政,增加整合的难度。

第二阶段:边缘服务迁移

先迁移一些边缘的、非核心的服务,比如内容管理、消息通知、数据统计等。这些服务影响面小,迁移风险低,可以用来验证技术方案和流程。

迁移的时候,先在新系统中开发对应的服务,然后通过网关把相关请求路由到新服务。旧系统中对应的功能保留一段时间,作为备份。验证新服务稳定运行一周后,再把旧系统中的代码下线。

这个阶段我们踩了第一个坑:超时时间设置不合理。刚开始我们把所有服务的超时时间都设成了五秒,结果有一个服务依赖了一个慢查询接口,平均响应时间就有四秒,五秒的超时经常触发,导致频繁熔断。后来我们根据每个服务的实际响应时间调整了超时设置,问题才解决。这个教训告诉我们,超时时间不能一刀切,要根据服务的实际情况来定。

第三阶段:核心服务迁移

边缘服务验证没问题之后,开始迁移核心服务,包括用户服务、商品服务、订单服务、支付服务。这些服务影响面大,迁移风险高,我们采取了更谨慎的策略。

每个核心服务的迁移都分几步走:

  1. 新系统开发服务,做好单元测试和集成测试。
  2. 在测试环境和旧系统做对比测试,验证数据一致性。
  3. 线上灰度,先让小流量走新服务,监控各项指标。
  4. 逐步扩大流量比例,直到100%。
  5. 稳定运行一周后,下线旧系统中的对应代码。

这个阶段踩的坑比较多。

第一个坑是数据库双写一致性问题。迁移过程中,新旧系统都在运行,有些数据需要双写。我们一开始用的是同步双写,新系统写成功后再写旧系统。结果有一次旧系统写失败了,但是新系统已经写成功了,导致两边数据不一致。后来我们改成了异步双写,通过消息队列保证最终一致性,同时增加了数据对账任务,每天核对两边的数据,发现不一致及时修复。

第二个坑是熔断参数过于敏感。订单服务迁移之后,我们发现熔断器经常在高峰期打开,影响了下单功能。排查后发现,是因为最小请求数设得太低了(默认20个),高峰期某个瞬间有几个请求超时,错误率就超过了50%,触发了熔断。后来我们把核心服务的最小请求数调到了50个,错误率阈值调到了60%,熔断打开时间缩短到3秒,就很少出现误熔断了。核心服务的熔断参数需要更保守一些,避免影响核心业务。

第三个坑是降级预案不完善。有一次推荐服务出了问题,熔断器打开了,但是我们没有准备好降级逻辑,结果商品详情页的推荐区域直接报错,显示了一片空白,用户体验很差。后来我们紧急加了降级逻辑,推荐区域显示热门商品。这件事之后,我们要求每个服务在上线前必须准备好降级方案,没有降级方案的服务不允许上线。

第四阶段:旧系统下线

所有服务都迁移到新系统之后,旧系统还运行了一段时间,作为最后的备份。期间我们持续监控新系统的稳定性,同时把旧系统的流量逐步降到零。确认新系统稳定运行了一个月,没有出现重大问题之后,才正式下线旧系统。

下线旧系统的那天,团队所有人都在,心里既紧张又兴奋。当最后一台旧服务器关机的时候,大家都松了一口气,三个多月的辛苦终于有了结果。

六、迁移效果

迁移完成后,系统的稳定性和性能都有了明显提升。

稳定性方面,迁移前平均每个月有两三次故障,每次故障影响范围都比较大。迁移后,虽然也有个别服务出问题,但是因为有熔断降级机制,故障都被限制在单个服务内,没有蔓延到整个系统。用户几乎感知不到服务故障,核心功能的可用性达到了99.9%以上。

性能方面,微服务架构支持独立扩展,我们对访问量大的服务进行了单独扩容,资源利用率提高了不少。系统的整体响应时间也有下降,平均响应时间从原来的八百毫秒降到了五百毫秒左右。服务启动速度从原来的两三分钟降到了十几秒,扩容和发布都更快了。

开发效率方面,服务拆分之后,各个团队可以独立开发、独立部署,不再互相等待。代码仓库也拆分了,每个服务的代码量不大,理解和维护都更容易了。新人入职后,只需要了解自己负责的服务,不需要理解整个系统,上手速度快了很多。

当然,微服务架构也带来了一些新的挑战。服务多了,运维复杂度增加了,对运维团队的要求更高了。分布式事务、数据一致性、服务间通信等问题也需要花精力处理。但是总体来说,收益大于代价,这次迁移是值得的。

七、经验总结

回顾整个迁移过程,有几点经验值得总结。

第一,熔断降级要尽早做。不要等出了故障才想起来做容错,应该在系统设计阶段就把熔断降级考虑进去。我们就是因为旧系统没有容错机制,吃了大亏,才下决心迁移。新系统从一开始就把熔断降级作为基础能力来建设,后面就省心很多。

第二,参数调优要基于实际数据。Hystrix的参数很多,超时时间、线程池大小、熔断阈值这些,不能凭感觉设置,也不能直接用默认值。应该根据服务的实际QPS、响应时间、错误率等数据来计算,然后在运行中持续观察和调整。我们的做法是先用保守的参数上线,运行一周后根据监控数据来调优,效果比较好。

第三,降级方案要提前准备。每个服务都应该有对应的降级方案,而且要在上线前就准备好并测试通过。不要等熔断器打开了才发现没有降级逻辑,那样用户体验会很差。降级方案要考虑各种异常场景,包括服务不可用、响应慢、数据异常等,每个场景都要有对应的处理策略。

第四,监控是熔断降级的眼睛。没有完善的监控,熔断降级就是瞎子。你不知道熔断器有没有打开,不知道为什么打开,也不知道降级有没有生效。一定要把监控做好,Hystrix Dashboard、调用链追踪、业务监控都要有,这样出了问题才能快速发现和处理。

第五,迁移要渐进式,不要一步到位。从旧系统迁移到新系统,风险很高,一定要循序渐进。先迁移非核心服务,验证方案和流程,再迁移核心服务。核心服务的迁移要灰度,从小流量开始,逐步扩大。整个过程中要有回滚方案,出了问题能快速回退。我们这次迁移能比较顺利,和渐进式的策略有很大关系。

结语

服务熔断降级是微服务架构中不可或缺的一环,它能有效防止故障蔓延,保障系统的高可用性。从旧系统迁移到新系统的过程虽然辛苦,但是收获也很大。我们不仅得到了一个更稳定、更高效的系统,也积累了宝贵的微服务实践经验。

如果你也在考虑做微服务改造,或者正在为系统的稳定性问题头疼,希望这篇文章能给你一些启发。熔断降级不是什么高深的技术,但是要做好需要细心和耐心。从现在开始,给你的系统加上熔断降级这道保险吧。