微服务如何保证不会出现连锁反应?Go 实现的断路器了解下
本文作者:杨锡坤
【微服务如何保证不会出现连锁反应?Go 实现的断路器了解下】原文链接:
本文学习参考自: Circuit Breaker pattern [1] 和 cep21/circuit [2]
业务问题场景在业务系统中 , 通常存在服务之间的相互调用 , 例如服务 A 调用服务 B , 当出现如下情形:
- 服务 A 与服务 B 之间的网络出现异常
- 服务 B 过载
- 服务 B 出现异常
断路器模式在家庭电路中有一个叫断路器的安全设备 , 当出现电路过载、短路、漏电等情况时 , 就会发生跳闸 , 防止出现安全事故 。 类比到上面描述的业务问题场景 , 我们需要在系统中实现一个类似断路器功能的组件 , 用于阻止系统 A 重复尝试很可能失败的调用 。
在断路器模式中 , 断路器组件需要监测到最近失败的调用 , 并且利用这些信息去决定新的调用是否执行 , 还是立即抛出异常 。 当断路器组件“跳闸”之后 , 还需要能探测被调用服务是否恢复正常 ,
断路器模式的代码实现 , 使用了有限状态机的思想 。 最基本的实现有三种状态:
- 关闭(Closed):调用正常执行 。 断路器组件对最近失败的调用进行计数 , 当达到阈值时 , 则断路器组件“跳闸” , 进入“打开”状态 。
- 打开(Open):调用请求会立即失败 , 断路器组件抛出异常 。
- 半打开(Half-Open):当处于“打开”状态时 , 会启动一个超时定时器 , 当超时后 , 断路器组件会进入“半打开”状态 , 此时允许执行部分调用 , 断路器会对成功执行的调用进行计数 , 达到阈值后 , 会认为被调用服务恢复正常 , 断路器状态回到“关闭”状态 , 如果有请求出现失败 , 则回到“打开”状态 。
文章插图
问题和注意事项
- 异常处理:系统需要考虑到断路器抛出的各类异常该如何处理 。 比如采取降级措施 , 把请求转发给备份服务 , 或者通知上游稍后重试 。
- 异常的类型:服务调用请求可能出现超时 , 或网络不通 , 下游服务明确返回失败的情况 , 断路器可能需要针对不同情况的错误 , 采取不同的状态切换策略 。 例如触发切换到“打开”状态的条件 , 可以是超时错误的阈值比下游服务明确返回失败的阈值更高 。
- 日志:断路器需要记录下所有失败的请求 , 方便相关人员监控定位问题 。
- 恢复:配置合适的策略 , 让断路器检测下游服务是否恢复正常 ,
- “打开”到“半打开”的状态切换:可以不使用定时器 , 而是周期性的探测下游服务是否恢复 。
- 人为干预:服务异常恢复需要的时间有长有短 , 断路器最好能提供人为控制的接口 , 方便将断路器强制切换到“打开”或“关闭”状态 。
- 并发:一个断路器可能会被很多请求并发访问 , 所以断路器工程化实现所需的时间和空间消耗需要尽量的小 。
- 资源差异:为不同的资源访问 , 单独创建相应的断路器 。
- 加速“跳闸”:当可以从下游服务获取到足够明确的异常时 , 则立即切换到“打开”状态 。
cep21/circuit 中主要的类型和接口:
circuit.Manager
// 管理多个circuits对象实例type Manager struct {// func (h *Manager) CreateCircuit(name string, configs ...Config) (*Circuit, error) 方法创建circuits对象实例时 , 使用的配置 , 会按照逆序将多个配置合并为最终的配置DefaultCircuitProperties []CommandPropertiesConstructor// 每个circuits会有一个唯一命名的标识circuitMap map[string]*Circuit// 用于circuitMap的读写锁mu sync.RWMutex}
circuit.Circuittype Circuit struct {//circuitStatsCmdMetricCollectorRunMetricsCollection// 统计调用出现的各种情况FallbackMetricCollector FallbackMetricsCollection// 统计降级调用出现的各种情况CircuitMetricsCollector MetricsCollection// 统计Circuit状态切换的情况// This is used to help run `Go` calls in the backgroundgoroutineWrapper goroutineWrapper // 用于异步调用的封装namestring// 断路器唯一命名的标识notThreadSafeConfig Config // 非线程安全的断路器配置notThreadSafeConfigMu sync.MutexthreadSafeConfigatomicCircuitConfig // 线程安全的断路器配置// Tracks if the circuit has been shut open or closedisOpen faststats.AtomicBoolean // 断路器只有“打开”和“关闭”两种状态// Tracks how many commands are currently runningconcurrentCommands faststats.AtomicInt64// 统计有多少并发调用// Tracks how many fallbacks are currently runningconcurrentFallbacks faststats.AtomicInt64// 统计有多少降级的并发调用// ClosedToOpen controls when to open a closed circuitClosedToOpen ClosedToOpen// 控制断路器由“关闭”状态切换到“打开”状态// openToClosed controls when to close an open circuitOpenToClose OpenToClosed// 控制断路器由“打开”状态切换到“关闭”状态timeNow func() time.Time// 对time.Now的封装 , 值始终为config.General.TimeKeeper.Now , 从config.TimeKeeper的解释看是为了方便测试 , 当没在测试代码里有看到使用}
- 人民币|天猫国际新增“服务大类”,知舟集团提醒入驻这些类目的要注意
- 页面|如何简单、快速制作流程图?上班族的画图技巧get
- 培育|跨境电商人才如何培育,长沙有“谱”了
- 出海|出海日报丨短视频生产服务商小影科技完成近4亿元 C 轮融资;华为成为俄罗斯在线出售智能手机的第一品牌
- 抖音小店|抖音进军电商,短视频的商业模式与变现,创业者该如何抓住机遇?
- 计费|5G是如何计费的?
- 成为佛山移动服务体验官 表白留言赢取百元话费
- 车轮旋转|牵引力控制系统是如何工作的?它有什么作用?
- 正确|新昌消防丨听说,这才是微信新表情的正确打开方式
- 视频|短视频如何在前3秒吸引用户眼球?