hystrix 全局熔断_go-micro V2 从零开始(七)集成断路器Hystrix

hystrix 全局熔断_go-micro V2 从零开始(七)集成断路器Hystrix_第1张图片

本文相关代码:gitee


前言

断路器是微服务体系中的保险丝,避免小的故障通过连锁反应造成整个系统瘫痪甚至损坏,这一章我们演示如何在项目中集成知名断路器Hystrix

为了实现集成,本章会涉及到go-micro wrapper的概念。

简单说,wrapper是一个函数装饰器,类似java springMVC中拦截器,或者gin 中间件的概念,可以无侵入的设定函数执行前后的行为,我们借此可以做很多工作,例如本章的熔断,还有后面即将介绍的鉴权、日志等。

更多关于wrapper的知识请自行搜索学习。


步骤

一、hystrix-go

Netflix/Hystrix是一个由Netflix开源的容错库,旨在隔离指向远程系统,服务和第三方库的请求,杜绝级联故障。

Netflix/Hystrix​github.com
hystrix 全局熔断_go-micro V2 从零开始(七)集成断路器Hystrix_第2张图片

而afex/hystrix-go项目脱胎于Hystrix,是他的go语言实现 。

afex/hystrix-go​github.com
hystrix 全局熔断_go-micro V2 从零开始(七)集成断路器Hystrix_第3张图片

二、go-plugins包

在正式开始编码前,我们还需要介绍一个micro拓展项目go-plugins

micro/go-plugins​github.com

他是一个go-micro的功能增强包,其中包含了很多独立的插件程序,以插件的形式为我们提供即插即用的服务功能扩展,你可以直接下载他们中的某一款,也可以参考这些实现自己编写自己的插件。

go-plugins为我们分别实现了hystrixgobreaker两款断路器的插件化,他们的调用方法完全一致。

三、引入Hystrix插件

task-api服务为例,改写之前的task-api/main.go 注意:

  1. 以下不是完整代码,路由实现部分略去:gitee
  2. go-plugins包是独立于go-micro之外的,写完上述代码你还需要go mod tidy下载相关依赖,不要漏掉路径最后的v2,否则会默认引入v1版本 。
package main

import (
    "github.com/gin-gonic/gin"
    "github.com/micro/go-micro/v2"
    "github.com/micro/go-micro/v2/registry"
    "github.com/micro/go-micro/v2/registry/etcd"
    "github.com/micro/go-micro/v2/web"
    "github.com/micro/go-plugins/wrapper/breaker/hystrix/v2"
    pb "go-todolist/task-srv/proto/task"
    "log"
)

// task-srv服务的restful api映射
func main() {
      
    etcdRegister := etcd.NewRegistry(
        registry.Addrs("172.18.0.58:2379"),
    )
    // 之前我们使用client.DefaultClient注入到pb.NewTaskService中
    // 现在改为标准的服务创建方式创建服务对象
    // 但这个服务并不真的运行(我们并不调用他的Init()和Run()方法)
    // 如果是task-srv这类本来就是用micro.NewService服务创建的服务
    // 则直接增加包装器,不需要再额外新增服务
    app := micro.NewService(
        micro.Name("go.micro.client.task"),
        micro.Registry(etcdRegister),
        micro.WrapClient(
            // 引入hystrix包装器
            hystrix.NewClientWrapper(),
        ),
    )
    taskService := pb.NewTaskService("go.micro.service.task", app.Client())

    webHandler := gin.Default()
    // 这个服务才是真正运行的服务
    service := web.NewService(
        web.Name("go.micro.api.task"),
        web.Address(":8888"),
        web.Handler(webHandler),
        web.Registry(etcdRegister),
    )

    // 配置web路由
    router(webHandler, taskService)

    service.Init()
    if err := service.Run(); err != nil {
      
        log.Fatal(err)
    }
}

// web路由实现部分略
// 详情见https://gitee.com/xieyu1989/go-micro-study-notes/tree/master/go-todolist4
func router(g *gin.Engine, taskService pb.TaskService){}

接下来验证一下成果。hystrix的默认请求超时阈值为1秒,修改task-srv/handler/task.goSearch函数,在第一行增加3秒延时:

func (t *TaskHandler) Search(ctx context.Context, req *pb.SearchRequest, resp *pb.SearchResponse) error {
      

    // 增加3秒延时
    time.Sleep(3 * time.Second)

    ...
}

然后用postman中调用localhost:8888/task/search,就会收到hystrix的超时消息:

hystrix 全局熔断_go-micro V2 从零开始(七)集成断路器Hystrix_第4张图片

四、熔断参数调整

插件默认调用1秒熔断,最大并发数是10,我们可以进一步调整,注意这里需要引入github.com/afex/hystrix-go/hystrix包,起别名hystrixGo避免与hystrix插件包名冲突:

import  hystrixGo "github.com/afex/hystrix-go/hystrix"
...
    app := micro.NewService(
        micro.Name("go.micro.client.task"),
        micro.Registry(etcdRegister),
        micro.WrapClient(
            // 引入hystrix包装器
            hystrix.NewClientWrapper(),
        ),
    )
    // 自定义全局默认超时时间和最大并发数
    hystrixGo.DefaultSleepWindow = 200
    hystrixGo.DefaultMaxConcurrent = 3

    // 针对指定服务接口使用不同熔断配置
    // 第一个参数name=服务名.接口.方法名,这并不是固定写法,而是因为官方plugin默认用这种方式拼接命令name
    // 之后我们自定义wrapper也同样使用了这种格式
    // 如果你采用了不同的name定义方式则以你的自定义格式为准
    hystrixGo.ConfigureCommand("go.micro.service.task.TaskService.Search",
        hystrixGo.CommandConfig{
      
            MaxConcurrentRequests: 50,
            Timeout:               2000,
        })
...

五、自定义Hystrix Wrapper

下面我们参考官方插件编写自己的Hystrix Wrapper,相对于官方插件,我们自定义调用失败时的降级操作。

创建并编辑task-api/wrapper/breaker/hystrix/hystrix.go(这个路径也参考了官方)设置超时时间为2秒:

package hystrix

import (
    "github.com/afex/hystrix-go/hystrix"
    "github.com/micro/go-micro/v2/client"
    pb "go-todolist/task-srv/proto/task"
    "log"

    "context"
)

type clientWrapper struct {
      
    client.Client
}

// 这是我们真正需要实现的方法
func (c *clientWrapper) Call(ctx context.Context, req client.Request, rsp interface{}, opts ...client.CallOption) error {
      
    // 命令名的写法参考官方插件,服务名和方法名拼接
    name := req.Service() + "." + req.Endpoint()
    // 自定义当前命令的熔断配置,除了超时时间还有很多其他配置请自行研究
    // 这些配置在wrapper调用时才执行,因此具有最高的优先级
    // ---如果打算使用全局参数配置,请注释掉下面几行---
    config := hystrix.CommandConfig{
      
	Timeout: 2000,
    }
    hystrix.ConfigureCommand(name, config)
    // ---如果打算使用全局参数配置,请注释掉上面几行---

    return hystrix.Do(name,
        func() error {
      
            // 这里调用了真正的服务
            return c.Client.Call(ctx, req, rsp, opts...)
        },
        // 这个叫做降级函数,用来自定义调用失败后的处理
        // 一般我们可以选择返回特定错误信息,或者返回预设默认值
        // 这个方法尽量简单,尽量不要加入额外的服务调用和IO操作,避免降级函数自身异常
        func(err error) error {
      
            // 因为是示例程序,只处理请求超时这一种错误的降级,其他错误仍抛给上级调用函数
            if err != hystrix.ErrTimeout {
      
                return err
            }
            // 直接返回默认的查询结果并记录日志
            switch r:=rsp.(type) {
      
            // 这个服务我只实现了search一个接口的调用*pb.SearchResponse
            case *pb.SearchResponse:
                log.Print("search task fail: ", err)
                *r = pb.SearchResponse{
      
                    PageSize: 20,
                    PageCode: 1,
                    SortBy:   "createTime",
                    Order:    -1,
                    Rows: []*pb.Task{
      
                        {
      Body: "示例任务"},
                    },
                }
            default:
                log.Print("unknown err: ", err)
            }
            return nil
        })
}

// NewClientWrapper returns a hystrix client Wrapper.
func NewClientWrapper() client.Wrapper {
      
    return func(c client.Client) client.Client {
      
        return &clientWrapper{
      c}
    }
}

然后修改main.go的hystix包路径:

// 官方插件
    // import "github.com/micro/go-plugins/wrapper/breaker/hystrix/v2"
    // 自定义插件
    import "go-todolist/task-api/wrapper/breaker/hystrix"

重启task-api,然后用postman中调用localhost:8888/task/search,会发现界面卡了2S才返回,并且没有报超时而是返回了预设结果:

hystrix 全局熔断_go-micro V2 从零开始(七)集成断路器Hystrix_第5张图片

总结

本章我们以wrapper的方式为服务集成了断路器hystrix,实现了服务调用的熔断。

建议读者更多的学习hystrix其他功能,实现更加完善的熔断控制。

同时go-plugins包中还有大量插件,分门别类等待咱们的使用和学习,仔细阅读文档,可以在项目中少造很多轮子:

micro/go-plugins​github.com
hystrix 全局熔断_go-micro V2 从零开始(七)集成断路器Hystrix_第6张图片

你可能感兴趣的:(hystrix,全局熔断)