在微服务架构中,单体服务被拆分为若干微服务,一个服务一般须要调用(网络方式)多个服务才能完成预期功能,服务的稳定性受其余服务总体稳定性的制约。若一个服务出现故障,将会影响服务消费方没法正常工做,并将影响逐步放大,甚至致使整个服务集群崩溃,也就是服务雪崩效应。html
为防止服务雪崩,研发人员采用了流量控制、改进缓存、服务自动扩容、服务降级与熔断等方式。本文将介绍服务熔断,并使用go-kit+Hystrix实现微服务的熔断方案。git
服务熔断是指调用方发现服务提供方响应缓慢或者不可用时,调用方为了自保直接失败,再也不调用目标服务。考虑到服务提供方可能会恢复,在一段时间后会进行尝试访问。本质上这是一个“断路器模式”的应用,Martin Fowler有专门的文章对该模式进行讲解。经过下面的断路器开关状态图进行说明:github
针对服务熔断,业内使用的最多的当属Netflix退出的Hystrix,它为Spring Cloud构建的微服务提供了便利。如下引自官方对Hystrix的介绍:docker
Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.bootstrap
Hystrix是一个延迟和容错库,旨在隔离对远程系统、服务和第三方库的访问点,中止级联故障,并在故障不可避免的复杂分布式系统中实现恢复能力。segmentfault
本示例将使用Hystrix的go语言版本afex/hystrix-go
实现服务熔断治理。api
本示例基于arithmetic_trace_demo更改,在gateway
中增长服务熔断治理策略,register
中除了临时增长一些故障模拟代码不作其余改动。浏览器
复制arithmetic_trace_demo目录,重命名为arithmetic_circuitbreaker_demo。缓存
下载该示例所需的go依赖:bash
go get github.com/afex/hystrix-go
复制代码
修改docker/docker-compose.yml,增长hystrix-dashboard实例,内容以下:
version: '2'
consul:
image: progrium/consul:latest
ports:
- 8400:8400
- 8500:8500
- 8600:53/udp
hostname: consulserver
command: -server -bootstrap -ui-dir /ui
zipkin:
image: openzipkin/zipkin
ports:
- 9411:9411
hystrix:
image: mlabouardy/hystrix-dashboard:latest
ports:
- 8181:9002
复制代码
开始以前先简单说下hystrix-go的命令模式,它提供了Do
方法经过异步模式执行用户的业务逻辑,在执行成功或发生错误返回以前将被阻塞,定义以下:
func Do(name string, run runFunc, fallback fallbackFunc) error
复制代码
为了完成hystrix-go的调用,我把原来反向代理的逻辑封装到Do方法中,并经过HystrixRouter类型实现了ServeHTTP,在其中封装了链路追踪和服务发现逻辑(这么作仅仅为了演示)。
HystrixRouter
定义和新建方法以下所示,主要对链路追踪、服务发现进行封装。
// HystrixRouter hystrix路由
type HystrixRouter struct {
svcMap *sync.Map //服务实例,存储已经经过hystrix监控服务列表
logger log.Logger //日志工具
fallbackMsg string //回调消息
consulClient *api.Client //consul客户端对象
tracer *zipkin.Tracer //服务追踪对象
}
func Routes(client *api.Client, zikkinTracer *zipkin.Tracer, fbMsg string, logger log.Logger) http.Handler {
return HystrixRouter{
svcMap: &sync.Map{},
logger: logger,
fallbackMsg: fbMsg,
consulClient: client,
tracer: zikkinTracer,
}
}
复制代码
接下来的主要逻辑将在ServeHTTP中实现,主要思路为:
详细代码以下所示,可经过注释进行理解:
func (router HystrixRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//查询原始请求路径,如:/arithmetic/calculate/10/5
reqPath := r.URL.Path
if reqPath == "" {
return
}
//按照分隔符'/'对路径进行分解,获取服务名称serviceName
pathArray := strings.Split(reqPath, "/")
serviceName := pathArray[1]
//检查是否已经加入监控
if _, ok := router.svcMap.Load(serviceName); !ok {
//把serviceName做为命令对象,设置参数
hystrix.ConfigureCommand(serviceName, hystrix.CommandConfig{Timeout: 1000})
router.svcMap.Store(serviceName, serviceName)
}
//执行命令
err := hystrix.Do(serviceName, func() (err error) {
//调用consul api查询serviceNam
result, _, err := router.consulClient.Catalog().Service(serviceName, "", nil)
if err != nil {
router.logger.Log("ReverseProxy failed", "query service instace error", err.Error())
return
}
if len(result) == 0 {
router.logger.Log("ReverseProxy failed", "no such service instance", serviceName)
return errors.New("no such service instance")
}
director := func(req *http.Request) {
//从新组织请求路径,去掉服务名称部分
destPath := strings.Join(pathArray[2:], "/")
//随机选择一个服务实例
tgt := result[rand.Int()%len(result)]
router.logger.Log("service id", tgt.ServiceID)
//设置代理服务地址信息
req.URL.Scheme = "http"
req.URL.Host = fmt.Sprintf("%s:%d", tgt.ServiceAddress, tgt.ServicePort)
req.URL.Path = "/" + destPath
}
var proxyError error = nil
// 为反向代理增长追踪逻辑,使用以下RoundTrip代替默认Transport
roundTrip, _ := zipkinhttpsvr.NewTransport(router.tracer, zipkinhttpsvr.TransportTrace(true))
//反向代理失败时错误处理
errorHandler := func(ew http.ResponseWriter, er *http.Request, err error) {
proxyError = err
}
proxy := &httputil.ReverseProxy{
Director: director,
Transport: roundTrip,
ErrorHandler: errorHandler,
}
proxy.ServeHTTP(w, r)
return proxyError
}, func(err error) error {
//run执行失败,返回fallback信息
router.logger.Log("fallback error description", err.Error())
return errors.New(router.fallbackMsg)
})
// Do方法执行失败,响应错误信息
if err != nil {
w.WriteHeader(500)
w.Write([]byte(err.Error()))
}
}
复制代码
首先建立hystrixRouter对象,按照方法参数列表传递参数;而后将返回值使用zipkin服务中间件进行包装。
hystrixRouter := Routes(consulClient, zipkinTracer, "Circuit Breaker:Service unavailable", logger)
handler := zipkinhttpsvr.NewServerMiddleware(
zipkinTracer,
zipkinhttpsvr.SpanName("gateway"),
zipkinhttpsvr.TagResponseSize(true),
zipkinhttpsvr.ServerTags(tags),
)(hystrixRouter)
复制代码
为了经过hystrix-dashboard
对服务进行监控,须要启用hystrix的实时监控服务,代码以下:
//启用hystrix实时监控,监听端口为9010
hystrixStreamHandler := hystrix.NewStreamHandler()
hystrixStreamHandler.Start()
go func() {
errc <- http.ListenAndServe(net.JoinHostPort("", "9010"), hystrixStreamHandler)
}()
复制代码
好了,gateway的代码就修改完成了。
依次启动docker、register、gateway,而后使用postman的Runner工具进行测试。
#启动consul、zipkin、hystrix-dashboard
sudo docker-compose -f docker/docker-compose.yml up
./register/register -consul.host localhost -consul.port 8500 -service.host 192.168.192.146 -service.port 9000
./gateway/gateway -consul.host localhost -consul.port 8500
复制代码
Postman中新增一个collection,命名为circuitbreaker,新建post请求。打开Runner(左上角),选择新建的集合,设置时间间隔为100毫秒,迭代次数为1000(暂时不运行)。以下图:
打开浏览器,输入http://localhost:8181/hystrix
,在输入框输入hystrix的监控地址http://192.168.192.146:9010
(这里须要配置你的主机地址),而后启动监控,以下图:
准备工做完成了,下面开始测试。
在Postman的Runner界面点击“Run circuitbreak”按钮,而后查看Hysytrix监控面板,会看到以下界面,断路器的状态为Closed
:
而后,关闭register服务(直接在终端中止,方便下面快速启动)。会发现断路器状态很快变为Open
:
再开启register服务,断路器状态恢复为Closed
:
Hystrix默认设置的参数为:
初始时register服务正常,全部请求顺利执行,因此为Closed状态;当关闭register(模拟故障)后,请求失败次数到达设定阈值时,切换为Open状态;服务恢复后,待Hystrix到达重试时机,服务恢复。
本文在go-kit中使用hystrix-go为网关服务gateway
增长了服务熔断治理方案,经过模拟register服务从“正常-故障-恢复”,在hystrix-dashboard
中观察到断路器状态的变化。
在实际开发过程当中,因为服务之间的依赖关系复杂,很是有必要为咱们的服务增长服务熔断治理措施,确保及时止损,防止因单个依赖服务的故障影响全部业务线。本文仅做为服务熔断的入门,下来还须要深刻研究Hystrix的熔断与降级机制。
本文首发于本人微信公众号【兮一昂吧】,欢迎扫码关注!