使用etcd做服务注册与发现时,etcd已经集成了grpc客户端的服务发现 功能,自己做服务注册功能。
服务注册:
package balancer
import (
"context"
clientv3 "go.etcd.io/etcd/client/v3"
"strconv"
"time"
)
var cli *clientv3.Client
var interval = 5
//注册服务
//etcdAddrs:etcd地址
//serviceName:注册到etcd的服务名称
//serviceAddr:自己grpc业务监听的地址端口
func Register(etcdAddrs []string, serviceName string, serviceAddr string) error {
//获取链接
var err error
if cli == nil {
cli, err = clientv3.New(clientv3.Config{
Endpoints: etcdAddrs,
DialTimeout: 5 * time.Second,
})
if err != nil {
return err
}
}
//注册续租
register(serviceName, serviceAddr)
return nil
}
//etcd服务发现时,底层解析的是一个json串,且包含Addr字段
func getValue(addr string) string {
return "{\"Addr\":\"" + addr + "\"}"
}
func register(serviceName, serviceAddr string) error {
//注册服务
leaseResp, err := cli.Grant(context.Background(), int64(interval + 1))
if err != nil {
return err
}
fullKey := serviceName + "/" + strconv.Itoa(int(leaseResp.ID))
_, err = cli.Put(context.Background(), fullKey, getValue(serviceAddr), clientv3.WithLease(leaseResp.ID))
if err != nil {
return err
}
keepAlive(serviceName, serviceAddr, leaseResp)
return nil
}
//异步续约
func keepAlive(name string, addr string, leaseResp *clientv3.LeaseGrantResponse) {
//永久续约,续约成功后,etcd客户端和服务器会保持通讯,通讯成功会写数据到返回的通道中
//停止进程后,服务器链接不上客户端,相应key租约到期会被服务器自动删除
c, err := cli.KeepAlive(cli.Ctx(), leaseResp.ID)
go func() {
if err == nil {
for {
select {
case _, ok := <-c:
if !ok {//续约失败
cli.Revoke(cli.Ctx(), leaseResp.ID)
register(name, addr)
return
}
}
}
defer cli.Revoke(cli.Ctx(), leaseResp.ID)
}
}()
}
由以下proto生成grpc代码:
syntax = "proto3";
option go_package = "./;distest";
message ReqUser {
string Account = 1;
}
message ReplyUser {
string Account = 1;
string Name = 2;
int32 Age = 3;
}
service UserService {
rpc GetUserService(ReqUser) returns (ReplyUser);
}
服务器注册服务:
//实现服务功能
type UserService struct {
}
func (s *UserService) GetUserService(c context.Context,r *distest.ReqUser) (*distest.ReplyUser, error) {
log.Println("server收到消息", *r)
return &distest.ReplyUser{
Account: "989876",
Name: "lx01",
Age: 30,
}, nil
}
func main() {
port := ""
flag.StringVar(&port, "p", "10010", "端口")
flag.Parse()
addr := "127.0.0.1:" + port
lis, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("failed to listen: %s", err)
}
defer lis.Close()
s := grpc.NewServer()
defer s.GracefulStop()
//grpc注册服务
distest.RegisterUserServiceServer(s, &UserService{})
//向etcd注册grpc服务,服务名称为project_test。前端做服务发现时,要和此服务名相同,最上面自己写的注册方式
balancer.Register([]string{"127.0.0.1:2379"}, "project_test/", addr)
log.Println("启动Grpc服务器:", addr)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %s", err)
}
}
以上代码不懂的,自行补充grpc和etcd基础知识。
下面是客户端通过etcd发现grpc服务,并调用服务:
func TestEtctDisClient(t *testing.T) {
//初始化etcd客户端
var addr = "127.0.0.1:2379"
cli, _ := clientv3.New(clientv3.Config{
Endpoints: []string{addr},
DialTimeout: 5 * time.Second,
})
//新建builder,etcd官方实现的Builder对象
r, _ := resolver.NewBuilder(cli)
//向grpc注册builder,这样Dial时,就可以按照Scheme查找到此Builder,
grpcResolver.Register(r)
//注意:project_test为服务名称,要和服务器注册服务名称相匹配。round_robin表示以轮询方式访问grpc服务
conn, err := grpc.Dial(r.Scheme() + "://"+addr + "/project_test", grpc.WithInsecure(), grpc.WithBalancerName("round_robin"))
if err != nil {
panic(err)
}
//下面每隔1s对服务器调用一次
client := distest.NewUserServiceClient(conn)
for {
resp, err := client.GetUserService(context.Background(), &distest.ReqUser{Account: "xxxx"})
if err != nil {
log.Println(err)
} else {
log.Println(resp)
}
<-time.After(time.Second)
}
}
运行:
启动三个服务器端:
go run main.go -p 9001
go run main.go -p 9002
go run main.go -p 9003
然后再golang中启动TestEtctDisClient测试,结果如下图:
服务器均收到客户发的数据并返回。此时,任意打开、或者关闭服务器,均不影响客户端正常执行。
核心问题,当我们注册多个服务时,前端做服务发现时,为什么要服务名一直?
注册的服务,在etcd中像下面这样:
在etcd中,key为服务名//租约ID拼合而成。
客服端做服务发现时,核心时etcd的watch,底层watch的是以project_test开头的key值,所以能监听到project_testxxxxxxx为key的value变化。
etcd底层监听源码:
func (m *endpointManager) watch(ctx context.Context, rev int64, upch chan []*Update) {
defer close(upch)
lg := m.client.GetLogger()
opts := []clientv3.OpOption{clientv3.WithRev(rev), clientv3.WithPrefix()}
wch := m.client.Watch(ctx, m.target, opts...)
重点在clientv3.WithPrefix()上。
我们在使用etcd命令行工具时,也可以监听前缀:
.\etcdctl.exe watch lx --prefix
以上监听以lx开头的key值变化。
grpc服务发现原理,百度很多。