一般在微服架构中,有一个组件角色叫熔断器。顾名思义,熔断器起的作用就是在特定的场景下关掉当前的通路,从而起到保护整个系统的效果。
在微服务架构中,一般我们的独立服务是比较多的,每个独立服务之间划分责任边界,并通过约定协议接口来进行通信。当我们的调用链路复杂依赖多时,很可能会发生雪崩效应。
假设有这么一个场景,有A, B, C, D四个独立服务,A会依赖B,C,D;当D发生负载过高或网络异常等导致响应过慢或超时时,很可能A会因此堆积过多的等待链接,从而导致A的状态也转为异常,后面依赖到A的其他服务跟着发生链式反应,这将会导致大面积的服务不可用,即使本来是一些没有依赖到B,C,D的服务。如下图所示:
这不是我们希望看到的结果,所以这个时候熔断器可以派上用场。最简单的做法,我们为每个依赖服务配置一个熔断器开关,正常情况下是关闭的,也就是可以正常发起请求;当请求失败(超时或者其他异常)次数超过预设值时,熔断器自动打开,这时所有经过这个熔断器的请求都会直接返回失败,并没有真正到达所依赖的服务上。这时服务A本身仍然是能正常服务的。
那么熔断器具体又是怎么工作的呢?来看下,一个拥有基本功能的熔断器的状态机大体是这样子的:
当熔断器处于关闭状态时,请求是可以被放行的;
当熔断器统计的失败次数触发开关时,转为打开状态。
当熔断器处于打开状态时,所有请求都是不被放行的,直接返回失败;
只有在经过一个设定的时间窗口周期后,熔断器才会转换到半开状态
当熔断器处于半开状态时,当前只能有一个请求被放行;
这个被放行的请求获得远端服务的响应后,假如是成功的,熔断器转换为关闭状态,否则转换到打开状态。
本质:隔离远程服务请求,防止级联故障
获取
go get github.com/afex/hystrix-go
//熔断器配置
config := hystrix.CommandConfig{
Timeout: 1000*5,
}
//根据配置设置熔断器,并起个名字
hystrix.ConfigureCommand("testhystrix",config)
其中熔断器配置共有5个参数
//Do方法为同步执行,
//参数一:指定熔断器
//参数二:处理任务方法
//参数三:当处理方法失败后需要执行的方法,一般为降级服务
err := hystrix.Do("testhystrix",func()error{
//执行任务
//TODO
fmt.Println(getName())
return nil
},func(e error)error{
//执行降级服务
fmt.Println(defaultName())
return nil
})
if err!=nil{
fmt.Println(err)
}
//Go为异步方法,数据和错误传输借助于管道
err := hystrix.Go("testhystrix",func()error{
testchan<-getName()
return nil
},func(e error)error{
testchan<-defaultName()
return nil
})
select {
case res := <- testchan:
fmt.Println(res)
case r := <-err:
fmt.Println(r.Error())
}
这里搭配gin框架使用,至于gin和go-kit如何搭配,之后会出文章详解
首先创建配置熔断器
//初始化的时候执行
func InitHystrix(){
//熔断器配置,根据实际来配置,每个子服务搭配一个熔断器,
//我只有一个子服务,声明了一个全局熔断器
config := hystrix.CommandConfig{
Timeout: 1000*10,
MaxConcurrentRequests: 100,
RequestVolumeThreshold: 100,
ErrorPercentThreshold: 20,
}
//根据配置设置熔断器,并起个名字
hystrix.ConfigureCommand("globalhystrix",config)
}
//将熔断器封装一下
func DecorationGlobal(f func()error,e func(error)error)error{
return hystrix.Do("globalhystrix",f,e)
}
使用熔断器
//首先声明一个创建一个endpoint
userserver := http.NewServer(epoint.GetUserEnpoint(new(service.UserService)),transport.DecodeUserRequest,transport.EncodeUserResponse)
//将endpoint添加到gin路由上
g.GET("/user",func(c * gin.Context){
//第一个函数是正常执行的方法
//第二个函数是错误处理方法
err := middle.DecorationGlobal(func()error{
userserver.ServeHTTP(c.Writer,c.Request)
return nil
}, func(err error) error {
c.Status(500)
return nil
})
if err!=nil{
fmt.Println(err)
}
})