Skip to content

Latest commit

 

History

History
146 lines (96 loc) · 7.18 KB

分布式-断路器模式.md

File metadata and controls

146 lines (96 loc) · 7.18 KB

分布式-断路器模式

本文会介绍断路器模式,以及断路器模式在 Golang 的实现框架:gobreaker

断路器模式

分布式环境存在的问题

在分布式环境中,对远程资源和服务的调用可能会由于临时性故障(如网络连接缓慢、超时、资源过载或资源暂时不可用)而失败。 这些故障通常会在短时间内自行更正,而且,应该会准备一个可靠的云应用程序,通过重试模式这样的策略来处理它们。

但是,也可能遇到由于意外事件而导致的故障,且需要更长的时间来进行修复。 这些故障轻则导致部分连接中断,重则导致服务完全瘫痪。 这类情况下,让应用程序持续重试不可能成功的操作是毫无意义的。相反,应用程序应该快速认识到操作已失败,并相应地处理此故障。

在这些情况下,更有益的做法便是让操作立即失败并只在服务可能成功时才尝试调用服务。

解决方案

断路器模式的目的与重试模式不同。 重试模式在预期操作将成功的情况下让应用程序重试操作。 断路器模式则防止应用程序执行很可能失败的操作。 应用程序可以使用重试模式通过断路器调用操作,来组合这两种模式。 但重试逻辑应该对断路器返回的任何异常保持敏感,并且在断路器指示故障为非临时性的情况下放弃重试尝试。

针对可能失败的操作,断路器充当其代理。 代理应监视最近发生的失败次数,并使用此信息来决定是允许操作继续进行,还是立即返回异常:

  • 关闭:将来自应用程序的请求路由到操作。 代理维护最近失败次数的计数,如果对操作的调用不成功,代理将递增此计数。 如果在给定时间段内最近失败次数超过指定的阈值,则代理将置于 打开 状态。 此时,代理会启动超时计时器,并且当此计时器过期时,代理将置于 半开 状态。

超时计时器的目的是给系统一段时间来解决导致失败的问题,并允许应用程序再次尝试执行操作。

  • 打开:来自应用程序的请求立即失败,并向应用程序返回异常。

  • 半开:允许数量有限的来自应用程序的请求通过并调用操作。 如果这些请求成功,则假定先前导致失败的问题已被修复,并且断路器将切换到 关闭 状态(失败计数器重置)。 如果有任何请求失败,则断路器将假定故障仍然存在,因此它会恢复到 打开 状态,并重新启动超时计时器,再给系统一段时间来从故障中恢复。

半开 状态对于防止恢复服务突然被大量请求淹没很有用。 在服务恢复的同时,它或许能够支持数量有限的请求,直至恢复完成;但当恢复正在进行时,大量的工作可能导致服务超时或再次失败。

分布式-断路器-断路器原理图

在图中,关闭 状态所使用的失败计数器是基于时间的。 它会定期自动重置。 这有助于防止断路器在遇到偶然失败时进入 打开 状态。 仅当在指定间隔期间内发生指定数量的失败时,才会达到将断路器跳闸到 打开 状态的故障阈值。 半开 状态使用的计数器记录成功调用操作的次数。 在指定数量的连续操作调用成功后,断路器将恢复到 关闭 状态。 如果任何调用失败,断路器会立即进入 打开 状态,成功计数器会在下次进入 半开 状态时重置。

系统恢复是从外部进行的,可能的方法是通过还原或重新启动失败的组件,或修复网络连接。

断路器模式在系统从故障中恢复时提供稳定性,并将对性能的影响降至最低。 它可以通过快速拒绝很可能失败的操作的请求(而非等待操作超时或永不返回)来帮助维持系统的响应时间。 如果断路器在每次改变状态时引发事件,则该信息可以用于监视由断路器保护的系统部分的运行状况,或者当断路器跳闸到 打开 状态时,对管理员发出警报。

gobreaker

gobreaker是索尼公司开源的组件,使用Golang语言开发,用于实现断路器模式。

安装

go get github.com/sony/gobreaker

使用方法

CircuitBreaker是一个状态机,用于防止发送可能失败的请求。函数NewCircuitBreaker用于创建一个新的CircuitBreaker

状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型。

func NewCircuitBreaker(st Settings) *CircuitBreaker

你可以通过Settings来配置CircuitBreaker

type Settings struct {
	Name          string
	MaxRequests   uint32
	Interval      time.Duration
	Timeout       time.Duration
	ReadyToTrip   func(counts Counts) bool
	OnStateChange func(name string, from State, to State)
}
  • Name:CircuitBreaker的名字。

  • MaxRequests:CircuitBreaker半开时允许通过的最大请求数,如果MaxRequests为0,CircuitBreaker只允许 1 个请求。

  • Interval:CircuitBreaker清除内部Counts的闭合状态的循环周期,如果间隔为0,CircuitBreaker在闭合状态下不清除内部Counts

  • Timeout:CircuitBreaker处于半开状态后的断开状态时间,如果Timeout为 0,则CircuitBreaker的超时值设置为 60 秒。

  • ReadyToTrip:每当请求在关闭状态下失败时,都会使用Counts的副本调用ReadyToTrip。如果ReadyToTrip返回 true,CircuitBreaker 将设置为断开状态,如果ReadyToTrip为 nil,则使用默认的ReadyToTrip。当连续故障数超过5时,默认的ReadyToTrip返回 true。

  • OnStateChange:只要CircuitBreaker的状态发生变化,就会调用OnStateChange

参数Counts保存请求数量以及成功,失败的数量:

type Counts struct {
	Requests             uint32
	TotalSuccesses       uint32
	TotalFailures        uint32
	ConsecutiveSuccesses uint32
	ConsecutiveFailures  uint32
}

CircuitBreaker清除状态变化或闭合状态间隔的内部CountsCounts忽略清除前发送请求的结果。

CircuitBreaker可以包装任何函数用来发送请求:

func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error)

如果CircuitBreaker接受给定的请求,则Execute方法将运行该请求。

如果CircuitBreaker拒绝请求,Execute方法立即返回错误。否则,Execute返回请求的结果。

如果请求中发生panicCircuitBreaker将其作为错误处理,并向外抛出相同的panic

例子

var cb *breaker.CircuitBreaker

func Get(url string) ([]byte, error) {
	body, err := cb.Execute(func() (interface{}, error) {
		resp, err := http.Get(url)
		if err != nil {
			return nil, err
		}

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}

		return body, nil
	})
	if err != nil {
		return nil, err
	}

	return body.([]byte), nil
}

参考资料