在青训营的课程中,介绍了三件套:Hertz、Kitex、Gorm。本篇将要使用前两者,外加一个负载均衡中心——consul,来实现负载均衡。 使用Hertz和Kitex的负载均衡时,遇到不少概念。好不容易弄清楚了,当然得记录下来了。
创建一个项目
由于官方文档讲述的已经很全面,建议查看cloudwego的内容。此处不赘述,毕竟重头戏在后面
一些坑的地方
- 不要使用protobuf接口描述文件自动生成代码,会让你无法编译。估计这是一个bug。原因是编译后,将一个int64赋值给一个指针。比如
type Feed struct {
// 这里是一个指针
StatusCode *int64;
}
func (x *BaseResp) fastReadField1(buf []byte, _type int8) (offset int, err error) {
// fastpb.ReadInt64(buf, _type)返回的是int64,触发不兼容报错
x.StatusCode, offset, err = fastpb.ReadInt64(buf, _type)
return offset, err
所以建议使用thrift。一开始我使用的是protobuf,原因是别人给的就是protobuf。
- 最新版的golang不能够很好地支持hertz-client。原因是它使用的库不支持最新版的go。不去降级的原因是,时间成本。我的电脑是Arch,所以总是保持最新版本。
服务注册与发现
使用服务注册与发现,可以进行一定的故障处理。比如实现一个服务的机器有很多个,但是,其中有一个宕机了。使用该服务,可以排查宕机的机器。如果宕机的机器自动恢复了,它也可以查出来。具体原理是发包检测(大概吧)。
不论是Hertz还是Kitex,都可以使用该服务,它只是告诉程序有哪些东西可以用罢了
在这里使用的是Consul。Consul是一个独立运行的可执行程序,提供服务发现和配置管理功能,其他程序可以通过Consul的API与其交互。所以需要安装它才能够使用服务注册与发现服务。
Consul有服务端配置,还有客户端配置。当然,你在别人的开源的代码里,可能发现一个程序,它是服务端,也是客户端,不论是服务端还是客户端,都使用"NewClient"(看起来就是创建一个客户端)。这令我有些迷糊。
下面是我的一点解释,希望有点用吧
初始化过程
在集群中的每台机器上运行Consul客户端(NewClient),告诉Consul自己有哪些服务。
调用服务过程
当需要使用一个服务时,从Consul服务器端获取该服务在集群中的信息(IP、端口等)。 用这些信息,实现负载均衡(也就是从能够实现某个功能的服务器中选一个用)和故障转移(某机器宕机了,就选另一台能用的)。当服务状态变化时,Consul客户端会更新服务信息到服务器端。
负载均衡
负载均衡是一种比较直观的想法。如果能提供相同服务的机器有很多台,我们可以把请求均摊给这些机器。大概是要实现下面的效果,大概发送了二十几条请求,它们被分配到不同的程序上了:
但是其实这里是kitex的负载均衡结果。
综合实践
服务端需要实现对应的服务。
- 使用kitex的代码自动生成工具生成代码
kitex -service 服务名 -idl idl文件
- 编写handler.go。生成后,可以找到一个handler.go的文件,打开并简单编辑
func (s *FeedServiceImpl) Feed(ctx context.Context, request *feed.DouyinFeedRequest) (resp *feed.DouyinFeedResponse, err error) {
// TODO: Your code here...
fmt.Println(os.Args[1], "has been called!")
return
}
- 修改main.go,注册服务,告诉consul当前程序有哪些服务。这个服务端必须有多个,所以初始化服务的代码是这样的:
r, err := consul.NewConsulRegister("127.0.0.1:8500")
if err != nil {
log.Fatal(err)
}
svr := feed.NewServer(new(FeedServiceImpl),
server.WithServiceAddr(utils.NewNetAddr("tcp", "127.0.0.1:" + os.Args[1])),
server.WithRegistry(r),
server.WithServerBasicInfo(&rpcinfo.EndpointBasicInfo{
ServiceName: "feed.srv",
}))
err = svr.Run()
os.Args[1]的含义是从终端传入参数。这里和IP拼接为一个IP+端口的形式。使用8880、8881、8882、8883端口执行后,可以在consul的客户端里看到服务已经跑起来了。
接下来在API网关编写代码
- 使用代码生成工具生成代码
hz new
hz update -idl idl文件
- 编写handler。handler在
biz/handler/里找,这里是以feed举例。config.GlobalFeedClient是程序初始化创建的kitex客户端。
func Feed(ctx context.Context, c *app.RequestContext) {
var err error
var req hfeed.DouyinFeedRequest
err = c.BindAndValidate(&req)
if err != nil {
c.String(consts.StatusBadRequest, err.Error())
return
}
res, err := config.GlobalFeedClient.Feed(ctx, &kfeed.DouyinFeedRequest{})
if err != nil {
hlog.Error("rpc car service err", err)
}
c.JSON(consts.StatusOK, res)
}
- 发起请求。这样就可以看到负载均衡的结果了。