某协作平台如何通过引入编排级熔断器,提高开发者效率,并防止内部故障在服务之间蔓延。
SEO 摘要: 本文介绍 CI/CD 编排平台如何通过编排级熔断器降低级联故障风险,重点讨论持续集成、持续交付、异步作业队列、负载卸除、延迟执行、依赖服务健康检查、测试稳定性和开发者效率等实践,帮助团队在大规模研发场景下提升构建、测试、部署和发布流程的可靠性。

当分布式服务面临海量内部请求时,会发生什么?如何防止内部服务之间出现级联故障?当简单的横向扩展或纵向扩展都达到极限时,又该如何重新设计工作流?
2020 年,某协作平台的工程团队在日常研发工作流中遇到了多项挑战。多个内部服务承受了过高负载,导致服务之间出现连锁故障。
所谓连锁故障,是指一种正反馈循环:系统中的某一部分大规模故障,导致相邻系统开始积压请求;积压继续扩大后,又会导致另一个系统因负载过高而崩溃。
多年来,随着内部工程师数量增长,以及服务和测试复杂度持续提升,持续集成和持续交付请求量每月增长约 10%。内部工具和服务越来越难以承受这种增长。这些故障减慢了开发速度,也迫使内部工具和基础设施工程师频繁投入紧急修复。
工程团队曾通过以下方式在短期内恢复服务:
- 将企业级代码托管设备扩展到当时云平台可用的最大硬件规格,但这也限制了后续纵向扩展空间;
- 为某个服务扩展更多节点以应对新的峰值负载,却发现这反而导致基础设施中的另一个服务出现故障。
显然,这些方案只能在内部服务负载达到下一个新峰值之前暂时有效。团队需要一种新的方式来思考这个问题。
本文介绍了某协作平台的工程团队如何在内部工具中实现编排级熔断器,从而提升开发人员生产力。开发者生产力团队在内部 CI/CD 编排平台中引入了一种“摩擦机制”,用于延迟或丢弃部分请求。
对于企业研发团队来说,CI/CD 编排稳定性不仅是基础设施问题,也会影响需求交付、测试反馈、发布上线和故障复盘等完整研发链路。借助 PingCode 这类智能化研发管理工具,团队可以将目标、需求、开发、测试、发布、缺陷追踪和 Wiki 知识沉淀串联起来,并打通研发工具链,让 CI/CD 熔断机制背后的数据、流程和改进动作更容易被追踪和复盘。
CI/CD 编排平台面临的复杂性与规模挑战
让我们回到 2020 年。当时,团队面临两类相互关联的问题:规模问题和复杂性问题。
在平台早期发展阶段,工程师使用持续集成进行开发,使用持续交付完成部署和发布。内部 CI/CD 编排平台负责协调代码构建、测试、部署和发布。
随着开发人员数量增加、功能发布频率提升,CI/CD 系统承受了额外负载。功能越多,配套的自动化测试也越多。
这两个方向的增长,使负载周期性出现新的峰值,进而引发单个服务中的新故障模式,并最终导致内部服务之间发生级联故障。由于每个服务的演进速度不同,并不是所有服务都能轻松通过横向扩展或纵向扩展来应对新的峰值。
工程师们经常不得不紧急处理这些大规模内部事件,以解决级联故障。虽然这些事件没有直接影响外部客户,但它们会占用其他工作的时间,通常涉及多个团队,并持续数天。
在此期间,开发人员会遇到持续集成测试执行降级或停止、持续交付工作流可用性受限等问题。
当每日测试请求峰值超过代码托管系统的处理能力时,工程师和 CI/CD 测试、持续交付工作流会遇到代码托管错误。这会导致编排平台中用于编排测试的异步作业数量增加。
这些延迟会进一步导致编排平台和测试执行系统中的队列不断积压。工程师在测试资源有限的情况下继续开发,又会在初始作业尚未处理完成之前,向这些队列中提交更多请求。
代码版本控制系统是持续集成和开发者工具的基础。对于一些大型组织来说,代码版本控制系统的扩展问题早已存在。有些组织会构建自己的抽象层,有些则会采用其他源代码管理系统。某些大型代码库也会借助大文件存储扩展来处理大型文件。
在此期间,代码托管设备持续纵向扩展。随着开发人员数量增加,在大型代码库中处理大量代码始终是一个挑战。定制化的源代码管理系统,或者单体仓库维护机制,可以在一定程度上缓解这类问题。
内部 CI/CD 编排平台维护了一个异步作业队列,用于保存 CI/CD 编排状态。该作业队列和调度器会重试失败请求。调度器会限制并发作业数量,以降低数据库负载,并减少失败请求。
然而,当队列中作业过多,例如测试请求作业过多时,这种并发限制会给 CI/CD 用户带来延迟。用户可能会重复请求相同作业,从而引发正反馈循环,让队列进一步扩大。
在这个项目之前,内部工具工程师为了应对不断增长的工程师数量,经常增加测试执行器和测试环境数量。然而,团队最初并没有完全意识到问题的严重性:测试规模,也就是测试执行器数量,与测试环境请求量叠加在一起后,会使请求总量超过持续集成搜索集群的处理能力,进而引发错误,并进一步加剧系统负载。
以下是一些可能导致 CI 服务和工具之间故障级联的工作流示例:
- 测试请求激增,导致代码托管系统响应变慢;
- 编排平台中的异步作业队列开始积压;
- 测试执行器等待编排结果,持续重试;
- 队列进一步扩大,导致更多服务出现延迟或错误;
- 工程师重复提交测试请求,放大系统压力。
Web 应用复杂性为何会放大 CI/CD 负载
某协作平台使用集成测试和端到端测试,验证涉及多个服务的复杂工作流是否正确。平台最初只有一个主要 Web 应用服务,但如今已经由多个服务共同支撑用户体验。客户端会连接多个不同 API,以便实时呈现用户看到的内容。
团队曾经总结过 Web 应用测试面临的挑战,也曾改进开发人员工作流,以应对大量端到端测试带来的问题。对于复杂应用来说,不同租户形态、企业配置、跨组织消息等场景,都会带来不同代码路径。为了测试这些复杂路径,产品团队和测试工程师会编写表达力更强的自动化测试,而这些测试往往依赖大量动态组件。
编排级熔断器如何防止级联故障
软件熔断器借鉴了系统工程中的概念。它可以检测外部系统故障,并停止向已知故障系统继续发起调用。通常,客户端是实现熔断器的位置。
在这个场景中,CI/CD 编排层负责管理系统中的请求流。因此,团队决定在编排器的消费者侧实现熔断器客户端。多个并发作业会先调用该客户端,然后再将请求发送到下一个系统。当然,工程师和服务负责人也需要及时了解系统行为变化。
团队的假设是:熔断器可以最大限度减少级联故障,并为跨多个服务的程序化指标查询提供更高杠杆,而不必分别在每个客户端或服务中实现单独逻辑。
与单个服务中的传统熔断器不同,编排层系统中的熔断器可以调节系统之间的请求接口。
当依赖服务负载过高,或者因负载过高而出现错误时,熔断器会打开。编排平台会以程序化方式从多个依赖服务中检索健康指标。如果下游系统无法处理请求,这些请求就会被延迟或丢弃。
当依赖服务恢复正常后,熔断器关闭,被延迟的请求会再次开始执行。通过这种方式,系统可以管理那些已知会失败的请求,减少影响构建、测试、部署和发布能力的级联故障事件,也减少持续集成中的不稳定执行。
方法论:在 CI/CD 编排层实现熔断机制
团队首先使用内部服务开发语言实现了一个抽象类,以此为基础展开讨论,并构建新工作流的原型。
讨论重点放在 CI/CD 编排服务上,而不是构建客户端或测试客户端。编排平台负责协调 CI/CD 工作流,其后台作业系统是构建、测试、部署和发布的核心。
该编排平台有一个 API 端点。当代码托管系统创建新提交时,这个端点会接收 webhook。随后,编排平台会根据这次提交,将多个后台作业加入队列。这些作业会触发测试执行系统进行资产构建或测试,然后更新数据库中的测试结果。
团队选择重点关注编排平台后台作业中的熔断机制,包括延迟执行和负载卸除。虽然熔断机制也可以存在于客户端逻辑中,例如等待恢复或阻塞工作流,但编排平台的后台作业系统提供了一个独特机会:它可以通过调度器充当多个系统之间的中介。
团队使用指标查询缓存代理,通过监控查询语言,对多个指标监控集群中的依赖服务指标进行程序化查询。该服务充当多个指标集群查询的前端、代理和缓存。
由于编排平台内部后台作业会重试,并且系统使用了延迟 CI 请求,因此这里不需要半开状态。半开状态通常用于让单个客户端请求逐步通过,并指示这些客户端服务已经恢复。
但在这个编排平台中,后台作业系统本身具有重试机制,并且熔断器对指标查询设置了生存时间。一旦打开状态的熔断器恢复,编排平台就可以立即恢复工作。
下面是熔断器抽象类的简化伪代码:abstract class CircuitBreaker { getState(): CircuitBreakerState bypass(): bool { return false } allowRequest(): bool { state = getState() recordMetric(breaker_type, breaker_state) if bypass() { return true } return state == CLOSED } }
这个抽象类包含三个关键能力:
- 获取当前熔断器状态;
- 支持绕过熔断器,用于紧急场景;
- 判断当前请求是否允许继续执行,并记录相关指标。
熔断器实现方式:延迟执行与负载卸除
在第一个实施阶段,团队重点关注编排服务自身健康状况相关的熔断器:
- 当编排平台和测试执行系统队列达到一定阈值时,延迟测试作业;
- 当所有测试环境都繁忙时,推迟端到端测试任务;
- 对分支上较早提交的测试执行负载卸除;
- 对持续失败的测试套件执行测试重试负载卸除。
在后续实施阶段,团队将重点扩展到共享依赖服务的熔断器,包括:
- 边缘缓存服务:用于返回频繁获取的团队数据;
- 数据库服务:作为客户数据的权威来源,用于部署、扩展和管理大型数据库实例集群;
- 搜索服务:提供消息、文件和人员索引,并同时计算实时集合与离线集合。
以边缘缓存服务熔断器为例,简化实现包括以下逻辑:
- 使用缓存保存最近一次错误率查询结果,并设置生存时间;
- 通过指标查询系统获取依赖服务当前错误率;
- 如果错误率超过阈值,打开熔断器,并发送告警;
- 如果错误率恢复到阈值以下,关闭熔断器,并结束对应问题记录;
- 如果指标查询系统本身返回错误,则保持熔断器关闭,允许请求继续通过。
这种设计中的安全性非常重要。如果指标查询系统或指标集群返回错误,系统会保持熔断器关闭,而不是贸然阻断请求。同样,团队会在异步作业之间缓存客户端请求的响应,以保证行为一致性。
下面是依赖服务熔断器的简化伪代码:class ServiceDependencyCircuitBreaker extends CircuitBreaker { TTL = 60 seconds ERROR_RATE_THRESHOLD = 5 getState() { cached = readCache() if cached is valid { return cached.error_rate < threshold ? CLOSED : OPEN } result = queryDependencyErrorRate() if result is error { return CLOSED } writeCache(result.error_rate) if result.error_rate >= threshold { sendIssueIfStateChangedToOpen() return OPEN } endIssueIfStateChangedToClosed() return CLOSED } }
这个实现的关键点是:熔断器基于依赖服务错误率判断状态,并通过缓存减少频繁指标查询带来的额外压力。
熔断器用户反馈:让开发者理解测试延迟原因
每个熔断器都会获取数据,并在检测到问题时向相应频道发出告警。熔断器打开时,系统会展示同一问题的多个视图。典型工作流是:团队成员注意到熔断器打开,然后将详细信息上报到相应团队频道。
在自动熔断消息中,每个链接都展示同一问题的不同视图。编排平台前端也会显示类似的延迟执行消息。例如,当测试执行系统队列过高时,前端会提示:当前队列较高,队列下降后测试将继续执行。
前文提到,编排平台会查询不同服务的错误率。团队还创建了一个小型内部库,用于报告尚未关闭的熔断器。通过测量这些特定问题,而不是只关注无差别的错误峰值,团队能够更好地分析熔断器随时间变化的情况。
此外,团队还扩展了这个问题库,使其能够检测测试执行器、测试环境和测试套件中的异常情况,例如高于预期的失败率、错误率、持续时间或不稳定率。这些改进最终都有助于提升开发人员体验。
这类熔断器反馈机制也需要良好的跨团队协作配合,例如告警升级、负责人确认、处理进展同步、问题记录和复盘跟进。团队可以借助 Worktile 这类通用项目协作系统统一管理任务、项目、文档、IM 沟通、日历、甘特图、工时和审批流程,让 CI/CD 故障响应和跨团队协作更加清晰可控。
编排级熔断器对开发者效率的影响
自从引入基础设施熔断器和依赖服务熔断器以来,团队通过延迟测试减少了级联故障的表面积,并通过负载卸除平滑了测试执行吞吐量。
结果是,开发人员体验显著改善。在之后的两年里,内部工具中没有再发生系统间级联故障事件。同时,关键服务负载显著下降,CI/CD 用户体验也得到改善。
在 2020 年的两个实施阶段之前,这类事件非常常见。如今,团队经常通过在 CI 编排中以程序化方式查询依赖服务负载,来发现新的峰值负载。
在最近一次代码大文件存储相关事件中,虽然症状与过去的事件类似,但问题被限定在测试执行器上,团队能够修复并隔离故障,避免级联故障发生。
反馈循环也变得更加完善。现在,当测试因为系统恢复而被推迟时,工程师可以通过编排平台前端和内部沟通工具获取反馈。在熔断机制实施之前,由于下游系统过载,这些测试往往会变得不稳定或直接失败。
推迟测试总体上减少了不稳定情况,也减少了相关性较低的测试执行。例如,某些测试请求已经不再与最初提交它们的工程师相关,因为新的提交已经出现;另一些原本需要多次测试才能解决不稳定问题的有效测试请求,则会被推迟执行。
此外,团队还能够重新调整一部分服务器支出和资源配置。这些资源原本用于针对非最新开发分支提交运行测试。通过负载卸除、延迟作业和流水线变化,测试执行曲线相较原本预测发生了明显变化。
为了更好理解测试反馈循环,持续集成团队还统一制定了一项业务指标:测试结果响应时间。该指标衡量开发人员从 CI 中获得构建和测试结果的延迟。
一开始,团队成员曾担心,引入熔断机制来延迟或减轻负载,似乎会与快速返回结果的目标相冲突。然而,该指标并没有朝错误方向发展,也就是没有变慢,而是在过去几年中保持稳定。原因是,许多相同的测试即使被立即执行,也会失败,并向用户返回不稳定结果。
如何将 CI/CD 熔断机制应用到自己的组织
每个组织的 CI/CD 文化、工具和基础设施都不相同。因此,这个方案无法直接复制到所有组织中。不过,你可以借鉴其中的一些思路,利用编排层熔断器来分离关注点。
某协作平台当时之所以决定在 CI/CD 中使用熔断器,是因为团队重点关注 Web 应用。Web 应用是客户业务逻辑的主要承载位置,也是多数开发人员花费最多时间的地方。
测试环境非常复杂。单元测试和集成测试在不同组织中可能代表不同含义。在一些组织中,单元测试和集成测试可能使用模拟环境和模拟请求;在另一些组织中,它们可能依赖共享环境。
对某协作平台来说,许多 Web 应用测试依赖同步和异步请求,例如后台任务或 WebSocket 连接。举例来说,一条发布消息的测试可能首先访问 API 端点,然后该端点会将多个异步任务加入队列,用于向其他客户端发送通知、把消息加入搜索队列等。
如今,团队定义了一套测试分类体系,与业界常见的小型、中型、大型测试分类方式保持一致,并强调从以端到端测试和单元测试为主的“沙漏型”测试模式,转向更多中型集成测试,以验证安全性和关键业务路径。
如果你的组织希望将这种模式应用到 CI/CD 中,可以从多个 CI/CD 编排平台中选择合适方案,并参考本文方法论部分提到的设计决策。
为了选择合适的控制手段,建议使用指标、事件、日志或追踪信息,深入分析流水线中存在的问题或常见瓶颈。团队当时的做法是:在正式打开熔断器并延迟或放弃请求之前,先通过内部沟通工具告知大家熔断器已打开。这样,在编排平台中迭代部署熔断器的过程中,团队能够更好理解问题所在。
由于 Web 应用服务布局通常比较复杂,产品团队和测试团队也会遵循这种复杂布局,创建与之匹配的测试用例。
如今,工程团队正在积极构建持续部署流程。多年来,持续交付过程一直依赖一套相对较薄的测试用例。要构建自动化部署和发布测试流程,需要多方协调,因为其中涉及很多环节,例如在流程中检测到故障时请求自动回滚。
总结来说,团队正在努力实现更多中型测试和持续部署,并在流程后期引入自动回滚,从而帮助工程师提升工作效率。
结论:用编排级熔断器提升 CI/CD 稳定性
本文分享了某协作平台内部 CI/CD 编排系统中编排级熔断器的决策点和实施结果。
在项目实施之前,工程师们面临诸多挑战:内部工具请求量不断达到新峰值,系统可能出现故障,并可能引发连锁反应,波及其他系统。
在持续集成系统的系统接口上设置熔断器后,团队最大限度减少了级联故障,并为跨多个服务的程序化指标查询提供了更高效率,而不必采用基于单个客户端或单个服务的方案。
自项目完成以来,工程师们在内部工具中不再遇到系统间级联故障问题。团队还发现,编排平台的服务可用性和整体吞吐量都有所提升,并减少了因服务故障导致的不稳定测试等糟糕开发体验。
熔断机制的实施显著提高了所有工程师的生产力。
现在,许多团队正尝试利用这种程序化指标查询框架,通过自动化构建、测试、部署、发布和回滚,持续推进更成熟的持续部署流程。
文章包含AI辅助创作:CI/CD 熔断机制:如何通过编排级熔断器提升开发者效率,发布者:shang,转载请注明出处:https://worktile.com/kb/p/3978899
微信扫一扫
支付宝扫一扫