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