文档章节

微服务的断路器实现图解Golang通用版

8小时
 8小时
发布于 05/15 09:17
字数 1813
阅读 165
收藏 5

断路器背景

微服务连锁故障场景

在分布式环境中,各个微服务相互调用,当某些情况下,比如后端中间件服务故障、第三方服务中断导致某个服务无限期不可用,短时间无法恢复,则可能会导致连锁故障,最终影响压垮整个业务集群

断路器与重试

断路器模式不同于重试模式,重试模式是使应用程序可以重试操作以期望它会成功,而断路器模式是防止应用程序执行一个可能失败的操作,减少执行可能失败操作的CPU、内存、线程等资源的浪费,从而保证服务的整体可用

断路器设计解析

基于代理模式的断路器

断路器相当于一个请求操作执行的代理,托管请求操作的执行

实现原理流程:

  1. 拦截服务执行的请求,通过当前状态决定是否直接返回,如果否则执行后续操作
  2. 尝试执行操作,并获取返回结果
  3. 根据返回结果和当前统计信息,决定当前断路器的状态,修改状态
  4. 返回执行结果

断路器状态机

断路器状态机实现上有三种状态:Closed(断路器关闭)、Open(开放)、HalfOpen(半开放)

状态说明备注
Closed关闭断路器关闭正常执行操作
Open打开断路器开放,所有请求直接返回错误,不执行任何请求
HalfOpen半开放允许有限数量的请求通过,如果执行成功,恢复到关闭状态,如果仍然失败,则恢复到开放,然后重新启动超时定时器

#断路器实现

实现原理图解

断路器实现实现主要分为三部分:状态统计、状态转移、请求执行

状态统计:统计已经执行的请求的成功失败的数量,以确定是否需要进行状态转移 状态转移:根据当前统计信息和当前状态来进行目标状态的确定及转移操作 请求执行:代理前端任务的执行,如果当前状态不需要进行尝试执行,就直接返回错误,避免资源浪费

Golang里面已经有开源的实现,https://github.com/sony/gobreaker/blob/, 接下来救市剖析它的实现

状态统计-计数器Counts

Counts就是一个计数器,记录当前请求成功和失败的数量

type Counts struct {
	Requests             uint32    // 请求数
	TotalSuccesses       uint32    // 成功
	TotalFailures        uint32    // 失败
	ConsecutiveSuccesses uint32    // 连续成功
	ConsecutiveFailures  uint32 // 连续失败
}

计数器完成对应请求状态的次数,为后续状态转移提供数据, Counts提供了onRequest、onSuccess、onFailure、clear几个辅助接口用于实现对应请求状态的操作,感兴趣可以看下

状态机- CircuitBreaker

type CircuitBreaker struct {
	name          string
        // maxRequests限制half-open状态下最大的请求数,避免海量请求将在恢复过程中的服务再次失败
	maxRequests   uint32
     // interval用于在closed状态下,断路器多久清除一次Counts信息,如果设置为0则在closed状态下不会清除Counts
	interval      time.Duration
        // timeout进入open状态下,多长时间切换到half-open状态,默认60s
	timeout       time.Duration
        // readyToTrip熔断条件,当执行失败后,会根据readyToTrip决定是否进入Open状态
	readyToTrip   func(counts Counts) bool
        // onStateChange断路器状态变更回调函数
	onStateChange func(name string, from State, to State)

	mutex      sync.Mutex
        //. state 断路器状态
	state      State
        // generation 是一个递增值,相当于当前断路器状态切换的次数, 为了避免状态切换后,未完成请求对新状态的统计的影响,如果发现一个请求的generation同当前的generation不同,则不会进行统计计数
	generation uint64
        //  Counts 统计
	counts     Counts
        // expiry 超时过期用于open状态到half-open状态的切换,当超时后,会从open状态切换到half-open状态
	expiry     time.Time
}

核心流程

CircuitBreaker.Execute

请求执行,对外开放的请求执行接口

func (cb *CircuitBreaker) Execute(req func() (interface{}, error)) (interface{}, error) {
        // 执行请求钩子,会根据当前状态,来返回当前的generation和err(如果位于open和half-open则不为nil), 通过err来进行判断是否直接返回
	generation, err := cb.beforeRequest()
	if err != nil {
		return nil, err
	}

        // 捕获panic,避免应用函数错误造成断路器panic
	defer func() {
		e := recover()
		if e != nil {
			cb.afterRequest(generation, false)
			panic(e)
		}
	}()

    // 执行请求
	result, err := req()
        // 根据结果来进行对应状态的统计, 同时传递generation
	cb.afterRequest(generation, err == nil)
	return result, err
}

CircuitBreaker.beforeRequest

func (cb *CircuitBreaker) beforeRequest() (uint64, error) {
	cb.mutex.Lock()
	defer cb.mutex.Unlock()

        // 获取当前的状态
	now := time.Now()
	state, generation := cb.currentState(now)

    // open和half-open状态则直接返回
	if state == StateOpen {
		return generation, ErrOpenState
	} else if state == StateHalfOpen && cb.counts.Requests >= cb.maxRequests {
            // 避免海量请求对处于恢复服务的影响,这里有一个限流的操作,避免请求数超过最大请求数
		return generation, ErrTooManyRequests
	}
        // 统计状态
	cb.counts.onRequest()
	return generation, nil
}

CircuitBreaker.afterRequest

func (cb *CircuitBreaker) afterRequest(before uint64, success bool) {
	cb.mutex.Lock()
	defer cb.mutex.Unlock()

    // 重新获取状态
	now := time.Now()
	state, generation := cb.currentState(now)
        // 如果前后状态不一致,则不计数
	if generation != before {
		return
	}

        // 根据状态计数
	if success {
		cb.onSuccess(state, now)
	} else {
		cb.onFailure(state, now)
	}
}

CircuitBreaker.currentState

func (cb *CircuitBreaker) currentState(now time.Time) (State, uint64) {
	switch cb.state {
	case StateClosed:
                // 如果当前当前是closed状态,并且有设置expiry,则递增Generation到新一轮统计计数
		if !cb.expiry.IsZero() && cb.expiry.Before(now) {
			cb.toNewGeneration(now)
		}
	case StateOpen:
                // 如果是Open状态,并且超时,则尝试到半打开状态
		if cb.expiry.Before(now) {
			cb.setState(StateHalfOpen, now)
		}
	}
	return cb.state, cb.generation
}

CircuitBreaker.toNewgeneration


func (cb *CircuitBreaker) toNewGeneration(now time.Time) {
        // 递增generation, 清除状态
	cb.generation++
	cb.counts.clear()

        // 设置超时时间
	var zero time.Time
	switch cb.state {
	case StateClosed:
		if cb.interval == 0 {
			cb.expiry = zero
		} else {
			cb.expiry = now.Add(cb.interval)
		}
	case StateOpen:
		cb.expiry = now.Add(cb.timeout)
	default: // StateHalfOpen
		cb.expiry = zero
	}
}

总结

断路器黄金链路

  • beforeRequest :完成当前请求是否可以执行请求,状态超时切换,同时返回当前的genenration
  • req: 执行请求
  • afterRequest: 完成请求状态统计,决定状态切换

断路器的优缺点

断路器比较适合针对远程服务或者第三方服务的调用,如果该操作极有可能会失败,则断路器可以尽可能的减小失败对应用的影响,避免资源浪费

但缺点也显而易见,断路器本身相当于一层代理,在应用程序执行进行统计和控制,本身就有一定的资源消耗,同时内部基于synx.Mutex锁来实现,高并发下肯定会有锁争用问题,可能需要根据业务来使用多个断路器,来分散这种锁争用,同时应该避免在断路器req函数内,去执行重试和过长时间的超时等待,因为断路器核心是快速失败

更多文章可以访问http://www.sreguide.com/

© 著作权归作者所有

8小时
粉丝 2
博文 5
码字总数 9401
作品 0
私信 提问
跟我学Spring Cloud(Finchley版)-12-微服务容错三板斧

至此,我们已实现服务发现、负载均衡,同时,使用Feign也实现了良好的远程调用——我们的代码是可读、可维护的。理论上,我们现在已经能构建一个不错的分布式应用了,但微服务之间是通过网络...

周立_ITMuch
01/17
0
0
spring-cloud-starter-hystrix(断路器)服务不通或者调用失败后的错误处理和回调

雪崩效应 在微服务架构中通常会有多个服务层调用,大量的微服务通过网络进行通信,从而支撑起整个系统。各个微服务之间也难免存在大量的依赖关系。然而任何服务都不是100%可用的,网络往往也...

easonjim
2017/09/30
0
0
程序猿DD/SpringCloud-Learning

Spring Cloud教程 本项目内容为Spring Cloud教程的程序样例。如您觉得该项目对您有用,欢迎点击右上方的Star按钮,给予支持!! 我的博客:http://blog.didispace.com 我的小密圈(深度交流与...

程序猿DD
2016/11/03
0
0
跟我学Spring Cloud(Finchley版)-13-通用方式使用Hystrix

本节详细讲解使用Hystrix的通用方式。 简介 Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要...

周立_ITMuch
01/19
0
0
spring cloud - 概述

什么是微服务? 微服务没有一个标准统一的概念,个人理解为:微服务是一种可以让软件职责单一、松耦合、自包含、可以独立运行和部署的架构思想。 关键思想就是:拆分、单一、独立、组件化。把...

明理萝
2018/09/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

运维规范

命名规范 发布流程 监控告警 故障定位 状态 日志 监控

以谁为师
26分钟前
1
0
约瑟夫环(报数游戏)java实现

开端 公司组织考试,一拿到考题,就是算法里说的约瑟夫环,仔细想想 以前老师将的都忘了,还是自己琢磨把~ package basic.gzy;import java.util.Iterator;import java.util.LinkedList;...

无极之岚
43分钟前
2
0
Kernel字符设备驱动框架

Linux设备分为三大类:字符设备,块设备和网络设备,这三种设备基于不同的设备框架。相较于块设备和网络设备,字符设备在kernel中是最简单的,也是唯一没有基于设备基础框架(device结构)的...

yepanl
今天
3
0
Jenkins 中文本地化的重大进展

本文首发于:Jenkins 中文社区 我从2017年开始,参与 Jenkins 社区贡献。作为一名新成员,翻译可能是帮助社区项目最简单的方法。 本地化的优化通常是较小的改动,你无需了解项目完整的上下文...

Jenkins中文社区
昨天
4
0
Spring中如何使用设计模式

关于设计模式,如果使用得当,将会使我们的代码更加简洁,并且更具扩展性。本文主要讲解Spring中如何使用策略模式,工厂方法模式以及Builder模式。 1. 策略模式 关于策略模式的使用方式,在S...

爱宝贝丶
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部