go-zero支持三种服务注册与发现方式:
- 直连:指定目标地址endpoints
- 基于etcd
- 基于k8s:依赖k8s的集群管理机制,服务发现时直接去k8s的Endpoints里获
本篇讨论etcd的服务注册与发现方式~
1 server端的服务注册
1.1 用法
只需要在rpc的服务配置yaml文件中,定义etcd配置即可:
Rpc:
Etcd:
Hosts:
- 192.168.0.111:2379
- 192.168.0.112:2379
- 192.168.0.113:2379
Key: user.rpc
- Host: etcd集群地址
- Key: 服务标识,go-zero将以此key作为etcd注册key的前缀
使用配置启动服务,可以看到,go-zero已经帮我们把key注册到了etcd中了:
$ etcdctl --endpoints=121.0.0.1:2379 --prefix=true get user.rpc
user.rpc/7952388177561657362
localhost:9000
- key:{服务标识}/{etcd租约id}
- value:服务请求地址。
1.2 原理
go-zero在启动服务时的服务注册过程如下:
rpc服务入口初始化的调用链 zrpc.MustNewServer->zrpc.NewServer->internal.NewRpcPubServer
在internal.NewRpcPubServer中定义出了etcd服务注册的func registerEtcd, 代码:
func NewRpcPubServer(etcd discov.EtcdConf, listenOn string, middlewares ServerMiddlewaresConf, opts ...ServerOption) (Server, error) {
registerEtcd := func() error {
//...
// etcd身份认证
if etcd.HasAccount() {
pubOpts = append(pubOpts, discov.WithPubEtcdAccount(etcd.User, etcd.Pass))
}
// tls认证
if etcd.HasTLS() {
pubOpts = append(pubOpts, discov.WithPubEtcdTLS(etcd.CertFile, etcd.CertKeyFile,
etcd.CACertFile, etcd.InsecureSkipVerify))
}
// 是否指定了leaseId
if etcd.HasID() {
pubOpts = append(pubOpts, discov.WithId(etcd.ID))
}
// 将key写入etcd,并开启一个goroutine持续续租
pubClient := discov.NewPublisher(etcd.Hosts, etcd.Key, pubListenOn, pubOpts...)
return pubClient.KeepAlive()
}
server := keepAliveServer{
registerEtcd: registerEtcd,
Server: NewRpcServer(listenOn, middlewares, opts...),
}
return server, nil
}
那么在服务启动时,通过调用链:s.Start->rs.server.Start->keepAliveServer.Start->registerEtcd
就实际调用了registerEtcd()方法,完成服务的注册。
2 client端的服务发现
2.1 用法
client端在与server端建立连接时,在代码中添加etcd配置即可:
func main() {
conn := zrpc.MustNewClient(zrpc.RpcClientConf{
Etcd: discov.EtcdConf{
Hosts: []string{"127.0.0.1:2379"},
Key: "user.rpc", // 服务标识,与服务端yaml中定义的保持一致
},
})
client := greet.NewGreetClient(conn.Conn())
resp, err := client.Ping(context.Background(), &greet.Request{})
//...
}
这样客户端就可以直接通过etcd去发现目的服务了。
2.2 原理
go-zero在客户端连接时的服务发现过程如下:
初始化客户端NewClient的调用链:zrpc.MustNewClient->NewClient->internal.NewClient->cli.dial->grpc.DialContext->cc.idlenessMgr.ExitIdleMode->m.enforcer.ExitIdleMode->idler.ExitIdleMode->cc.resolverWrapper.start->ccr.cc.resolverBuilder.Build
最后调到的实际是go-zero中定义的discovBuilder.Build,它实现了grpc resolver的Build,完成实际的服务发现,即从etcd查找与监听服务注册的key的过程。
看一下discovBuilder.Build的实现:
type discovBuilder struct{}
func (b *discovBuilder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
//...
// subscriber订阅etcd事件,监听key的变化
sub, err := discov.NewSubscriber(hosts, targets.GetEndpoints(target))
if err != nil {
return nil, err
}
// 取出当前etcd中的值,也就是目标地址(多个),采用某种负载均衡策略选择一个进行连接
update := func() {
var addrs []resolver.Address
// sub.Values中保存了实时的value, 有事件变化时会更新
for _, val := range subset(sub.Values(), subsetSize) {
addrs = append(addrs, resolver.Address{
Addr: val,
})
}
// 有变更时将地址更新给grpc
if err := cc.UpdateState(resolver.State{
Addresses: addrs,
}); err != nil {
logx.Error(err)
}
}
sub.AddListener(update)
update()
return &nopResolver{cc: cc}, nil
}