go-micro V2 从零开始(七)集成断路器Hystrix

本文相关代码:gitee

文章目录

  • 前言
  • 步骤
    • 一、hystrix-go
    • 二、go-plugins包
    • 三、引入Hystrix插件
    • 四、熔断参数调整
    • 五、自定义Hystrix Wrapper
  • 总结
  • 支持一下


前言

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

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

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

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


步骤

一、hystrix-go

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

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

二、go-plugins包

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

他是一个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的超时消息:
go-micro V2 从零开始(七)集成断路器Hystrix_第1张图片

四、熔断参数调整

插件默认调用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才返回,并且没有报超时而是返回了预设结果:
go-micro V2 从零开始(七)集成断路器Hystrix_第2张图片


总结

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

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

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


支持一下

原创不易,买杯咖啡,谢谢:p

支持一下

你可能感兴趣的:(go-micro,V2,从零开始,go,go-micro,微服务,hystrix)