原创分享 go-kit 微服务 服务熔断 (hystrix-go 实现)

hwholiday · 2020年02月12日 · 794 次阅读

go-kit 微服务 服务熔断(hystrix-go 实现)

  • 对客户端请求 login 方法添加熔断

Hystrix

  • 在微服务架构中,每个服务都是相互关联的,比如我们下单服务和扣钱服务是分开的,现在扣钱服务出现的 bug 不能正常服务
  • Hystrix 可以让我们在在微服务架构中对服务间的调用进行控制,加入一些调用延迟或者服务降级的容错机制。

Hystrix 的设计原则

  • 对依赖服务调用时出现的调用延迟和调用失败进行控制和容错保护
  • 在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延
  • 提供 fail-fast(快速失败)和快速恢复的支持
  • 提供 fallback 优雅降级的支持
  • 支持近实时的监控、报警以及运维操作

编写 Hystrix 类

import (
    "errors"
    "github.com/afex/hystrix-go/hystrix"
    "sync"
)

var config = hystrix.CommandConfig{
    Timeout:                5000, //执行command的超时时间(毫秒)
    MaxConcurrentRequests:  8,    //command的最大并发量
    SleepWindow:            1000, //过多长时间,熔断器再次检测是否开启。单位毫秒
    ErrorPercentThreshold:  30,   //错误率 请求数量大于等于RequestVolumeThreshold并且错误率到达这个百分比后就会启动
    RequestVolumeThreshold: 5,    //请求阈值(一个统计窗口10秒内请求数量)  熔断器是否打开首先要满足这个条件;这里的设置表示至少有5个请求才进行ErrorPercentThreshold错误百分比计算
}

type runFunc func() error

type Hystrix struct {
    loadMap  *sync.Map //储存每个调用函数对应的 Hystrix
    fallback string   //降级信息
}

func NewHystrix(msg string) *Hystrix {
    return &Hystrix{
        loadMap:  new(sync.Map),
        fallback: msg,
    }
}

func (s *Hystrix) Run(name string, run runFunc) error {
    if _, ok := s.loadMap.Load(name); !ok {
        hystrix.ConfigureCommand(name, config)
        s.loadMap.Store(name, name)
    }
    //name 为执行的命令名称
    //run  我们要执行的函数
    //fallback:run运行过程中发生错误时的回调方法
    err := hystrix.Do(name, func() error {
        return run()
    }, func(err error) error {
        //fmt.Println("运行 run 方法错误", err)
        return errors.New(s.fallback)
    })
    if err != nil {
        return err
    }
    return nil
}

添加 Hystrix 到调用客户端

  • 这里为了展示 Hystrix 的状态去掉了一些日志信息
hy := utils.NewHystrix("调用错误服务降级")
cbs, _, _ := hystrix.GetCircuit("login")
for i := 0; i < 100; i++ {
    time.Sleep(time.Millisecond * 100)
    userAgent, err := client.UserAgentClient()
    if err != nil {
        t.Error(err)
        return
    }
err = hy.Run("login", func() error {
    _, err := userAgent.Login(context.Background(), &pb.Login{
        Account:  "hwholiday",
        Password: "123456",
    })
    if err != nil {
        return err
    }
        //fmt.Println(ack.Token)
        return nil
    })
fmt.Println("熔断器开启状态:", cbs.IsOpen(), "请求是否允许:", cbs.AllowRequest())
    if err != nil {
        t.Log(err)
    }
}

去掉服务端的请求限制功能

func NewEndPointServer(svc Service, limit *rate.Limiter) EndPointServer {
    var loginEndPoint endpoint.Endpoint
    {
        loginEndPoint = MakeLoginEndPoint(svc)
        //loginEndPoint = NewGolangRateAllowMiddleware(limit)(loginEndPoint)
    }
    return EndPointServer{LoginEndPoint: loginEndPoint}
}

模拟服务器错误功能

if in.Account != "hwholiday" || in.Password != "123456" {
    err = errors.New("用户信息错误")
    return
}
//模拟耗时
//rand.Seed(time.Now().UnixNano())
//sl := rand.Int31n(10-1) + 1
//time.Sleep(time.Duration(sl) * time.Millisecond * 100)
//模拟错误
if rand.Intn(10) > 3 {
    err = errors.New("服务器运行错误")
    return
}
ack = &pb.LoginAck{}

运行

  • 运行 TestNewUserAgentClient 方法 (调用 Login 接口 100 次)
熔断器开启状态: true 请求是否允许: false
熔断器开启状态: true 请求是否允许: false
s.LoginEndPoint rpc error: code = Unknown desc = 服务器运行错误
s.LoginEndPoint rpc error: code = Unknown desc = 服务器运行错误
s.LoginEndPoint rpc error: code = Unknown desc = 服务器运行错误
s.LoginEndPoint rpc error: code = Unknown desc = 服务器运行错误 (previously: rpc error: code = Unknown desc = 服务器运行错误; rpc error: code = Unknown desc = 服务器运行错误)
熔断器开启状态: true 请求是否允许: false
熔断器开启状态: true 请求是否允许: false
熔断器开启状态: true 请求是否允许: false
熔断器开启状态: true 请求是否允许: false
熔断器开启状态: true 请求是否允许: false
熔断器开启状态: true 请求是否允许: false
熔断器开启状态: true 请求是否允许: false
熔断器开启状态: true 请求是否允许: false
熔断器开启状态: true 请求是否允许: false
熔断器开启状态: true 请求是否允许: false
熔断器开启状态: false 请求是否允许: true
s.LoginEndPoint rpc error: code = Unknown desc = 服务器运行错误
熔断器开启状态: false 请求是否允许: true

结语

  • 我们可以看到 Hystrix 的状态从 打开 ->关闭 -> 打开
  • Hystrix 的用法还有很多,这里只展示简单的使用,更加高级的功能欢迎大家一起讨论
  • 欢迎添加 QQ 一起讨论

完整代码地址

联系 QQ: 3355168235

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册