断路器与Ruby在2025年:不要毁掉你的发布
凌晨4点,你的团队来电……应用宕机了。是时候醒来,喝杯咖啡,开始调查。你发现了一系列故障:集成的支付处理器响应变慢→你的结账请求开始超时→用户疯狂刷新→现在整个Rails应用无响应。这就是级联故障,比想象中更常见。
什么是级联故障?
在软件开发中,级联故障(也称为多米诺骨牌效应)是一个连锁反应,其中一个组件的故障导致其他组件崩溃。考虑以下系统:
- 单体应用,即我们的主应用程序
- 从单体中提取的支付服务
- 处理实际支付的外部支付处理器
想象支付处理器变得不可用。典型的用户场景可能这样展开:
- 用户尝试完成购买,单体将请求转发给支付服务
- 支付服务尝试联系支付处理器,但请求超时
- 支付服务向单体返回408(请求超时)
断路器救援!
断路器模式是防止级联故障的经典解决方案。与物理断路器类似,当错误过多时,断路器"跳闸",暂时阻止对受保护服务的进一步调用。
断路器状态
断路器有三种可能状态:
- 🟢 闭合(Closed)- 一切正常工作,请求正常流动
- 🔴 打开(Opened)- 进一步调用被阻止,断路器已跳闸
- 🟡 半开(Half-Opened)- 恢复状态,允许有限流量探测外部服务
简化断路器实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
# 简化的断路器实现,仅包含"打开"和"闭合"状态
require 'net/http'
class CircuitBreaker
class CircuitBreakerOpened < StandardError; end
def initialize(error_threshold:, cool_off_time:)
@error_threshold = error_threshold
@cool_off_time = cool_off_time
@errors_count = 0
@state = :closed
end
def run(&block)
if (@state == :opened) && (Time.now > @transition_time + @cool_off_time)
@errors_count = 0
@state = :closed
end
raise CircuitBreakerOpened if @state == :opened
block.call
rescue StandardError => error
raise if error.is_a? CircuitBreakerOpened
@errors_count += 1
if @errors_count >= @error_threshold
@transition_time = Time.now
@state = :opened
end
raise
end
end
|
Stoplight:简单易用的断路器gem
Stoplight是为性能和简洁性构建的解决方案,让工程师花更少时间配置断路器,更多时间构建业务逻辑。
安装和配置
首先,将Stoplight添加到Gemfile:
然后配置Stoplight与Redis:
1
2
3
4
5
6
7
|
# config/initializers/stoplight.rb
require 'redis'
Stoplight.configure do |config|
config.data_store = Stoplight::DataStore::Redis.new(Redis.new)
end
|
使用Stoplight发送网络请求
1
2
3
4
5
6
7
|
require 'net/http'
require 'stoplight'
light = Stoplight('Highway to Mars')
light.run do
Net::HTTP.get(URI('https://evilmartians-mars-base.com/'))
end
|
Stoplight的交通灯抽象
Stoplight使用直观的交通灯颜色:
- 🟢 绿色 - 闭合:一切正常运作
- 🔴 红色 - 打开:目标当前失败,阻止所有出站请求
- 🟡 黄色 - 半开:谨慎重新开放路由进行测试
命名和分组最佳实践
按服务而不是HTTP方法或功能对灯进行分组:
1
2
3
4
|
light = Stoplight('Earth to Mars Mission corridor')
light.run do
Net::HTTP.get(URI('https://evilmartians-mars-base.com/'))
end
|
自定义配置
全局配置:
1
2
3
4
5
6
7
|
Stoplight.configure do |config|
config.data_store = Stoplight::DataStore::Redis.new(Redis.new)
config.skipped_errors = [ActiveRecord::RecordNotFound]
config.cool_off_time = 30
config.threshold = 10
config.window_size = 60
end
|
单个灯配置:
1
2
3
4
5
6
|
light = Stoplight(
"Mars freight lane",
skipped_errors: [ActiveRecord::RecordNotFound],
threshold: 10,
window_size: 60
)
|
Stoplight管理面板
将管理面板安装到Rails应用中:
1
2
3
|
Rails.application.routes.draw do
mount Stoplight::Admin => '/stoplights'
end
|
管理面板功能包括:
- 实时灯状态:监控所有灯的状态
- 手动覆盖:将任何灯锁定在特定状态
- 事件响应:实时事件管理和系统控制
结论
Stoplight将"全员出动"的危机时刻转变为非事件:几次重试失败,断路器跳闸,系统的其余部分保持健康,而你则可以修复问题。即使是初创公司也可以从早期阶段轻松实施。
故障是不可避免的,但停机时间不一定是。点亮天空,自信发布!