2 代码实现
2.1 总体流程
服务提供者:
(1)监听网络
(2)创建gRPC服务端,并将具体的服务进行注册
(3)利用服务地址、服务名等注册etcd服务配置
(4)gRPC监听服务
服务消费者:
(1)注册etcd解析器
(2)连接etcd服务
(3)获取gRPC客户端
(4)调用gRPC服务
2.2 代码
2.2.1 服务提供方
var (
cli *clientv3.Client
Schema = "ns"
Host = "127.0.0.1"
Port = 3000 //端口
ServiceName = "api_log_service" //服务名称
EtcdAddr = "127.0.0.1:2379" //etcd地址
)
type ApiLogServer struct{}
func (api *ApiLogServer) GetApiLogByUid(ctx context.Context, req *proto.ApiLogRequest) (*proto.ApiLogResponse, error) {
resp := &proto.ApiLogResponse{
Msg: "ok",
Data: "Hello",
}
return resp, nil
}
//将服务地址注册到etcd中
func register(etcdAddr, serviceName, serverAddr string, ttl int64) error {
var err error
if cli == nil {
cli, err = clientv3.New(clientv3.Config{
Endpoints: strings.Split(etcdAddr, ";"),
DialTimeout: 50 * time.Second,
})
if err != nil {
fmt.Printf("connection server err : %s\n", err)
return err
}
}
//与etcd建立长连接,并保证连接不断(心跳检测)
ticker := time.NewTicker(time.Second * time.Duration(ttl))
go func() {
key := "/" + Schema + "/" + serviceName + "/" + serverAddr
for {
resp, err := cli.Get(context.Background(), key)
if err != nil {
fmt.Printf("get server address err : %s", err)
} else if resp.Count == 0 { //尚未注册
err = keepAlive(serviceName, serverAddr, ttl)
if err != nil {
fmt.Printf("keepAlive err : %s", err)
}
}
<-ticker.C
}
}()
return nil
}
//保持服务器与etcd的长连接
func keepAlive(serviceName, serverAddr string, ttl int64) error {
//创建租约
leaseResp, err := cli.Grant(context.Background(), ttl)
if err != nil {
fmt.Printf("create grant err : %s\n", err)
return err
}
//将服务地址注册到etcd中
key := "/" + Schema + "/" + serviceName + "/" + serverAddr
_, err = cli.Put(context.Background(), key, serverAddr, clientv3.WithLease(leaseResp.ID))
if err != nil {
fmt.Printf("register service err : %s", err)
return err
}
//建立长连接
ch, err := cli.KeepAlive(context.Background(), leaseResp.ID)
if err != nil {
fmt.Printf("KeepAlive err : %s\n", err)
return err
}
//清空keepAlive返回的channel
go func() {
for {
<-ch
}
}()
return nil
}
//取消注册
func unRegister(serviceName, serverAddr string) {
if cli != nil {
key := "/" + Schema + "/" + serviceName + "/" + serverAddr
cli.Delete(context.Background(), key)
}
}
func RunApiLog() {
//监听网络
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", Port))
if err != nil {
fmt.Println("Listen network err :", err)
return
}
defer listener.Close()
//创建grpc
srv := grpc.NewServer()
defer srv.GracefulStop()
//注册到grpc服务中
proto.RegisterApiLogServiceServer(srv, &ApiLogServer{})
//将服务地址注册到etcd中
serverAddr := fmt.Sprintf("%s:%d", Host, Port)
fmt.Printf("rpc server address: %s\n", serverAddr)
register(EtcdAddr, ServiceName, serverAddr, 10)
//关闭信号处理
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGQUIT)
go func() {
s := <-ch
unRegister(ServiceName, serverAddr)
if i, ok := s.(syscall.Signal); ok {
os.Exit(int(i))
} else {
os.Exit(0)
}
}()
//监听服务
err = srv.Serve(listener)
if err != nil {
fmt.Println("rpc server err : ", err)
return
}
}
2.2.2 服务消费方
var (
cli *clientv3.Client
Schema = "ns"
ServiceName = "api_log_service" //服务名称
EtcdAddr = "127.0.0.1:2379" //etcd地址
)
type EtcdResolver struct {
etcdAddr string
clientConn resolver.ClientConn
}
func NewEtcdResolver(etcdAddr string) resolver.Builder {
return &EtcdResolver{etcdAddr: etcdAddr}
}
func (r *EtcdResolver) Scheme() string {
return Schema
}
//ResolveNow watch有变化调用
func (r *EtcdResolver) ResolveNow(rn resolver.ResolveNowOptions) {
fmt.Println(rn)
}
//Close 解析器关闭时调用
func (r *EtcdResolver) Close() {
fmt.Println("Close")
}
//Build 构建解析器 grpc.Dial()时调用
func (r *EtcdResolver) Build(target resolver.Target, clientConn resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
var err error
//构建etcd client
if cli == nil {
cli, err = clientv3.New(clientv3.Config{
Endpoints: strings.Split(r.etcdAddr, ";"),
DialTimeout: 15 * time.Second,
})
if err != nil {
fmt.Printf("connect etcd err : %s\n", err)
return nil, err
}
}
r.clientConn = clientConn
go r.watch("/" + target.Scheme + "/" + target.Endpoint + "/")
return r, nil
}
//watch机制:监听etcd中某个key前缀的服务地址列表的变化
func (r *EtcdResolver) watch(keyPrefix string) {
//初始化服务地址列表
var addrList []resolver.Address
resp, err := cli.Get(context.Background(), keyPrefix, clientv3.WithPrefix())
if err != nil {
fmt.Println("get service list err : ", err)
} else {
for i := range resp.Kvs {
addrList = append(addrList, resolver.Address{Addr: strings.TrimPrefix(string(resp.Kvs[i].Key), keyPrefix)})
}
}
r.clientConn.NewAddress(addrList)
//监听服务地址列表的变化
rch := cli.Watch(context.Background(), keyPrefix, clientv3.WithPrefix())
for n := range rch {
for _, ev := range n.Events {
addr := strings.TrimPrefix(string(ev.Kv.Key), keyPrefix)
switch ev.Type {
case mvccpb.PUT:
if !exists(addrList, addr) {
addrList = append(addrList, resolver.Address{Addr: addr})
r.clientConn.NewAddress(addrList)
}
case mvccpb.DELETE:
if s, ok := remove(addrList, addr); ok {
addrList = s
r.clientConn.NewAddress(addrList)
}
}
}
}
}
func exists(l []resolver.Address, addr string) bool {
for i := range l {
if l[i].Addr == addr {
return true
}
}
return false
}
func remove(s []resolver.Address, addr string) ([]resolver.Address, bool) {
for i := range s {
if s[i].Addr == addr {
s[i] = s[len(s)-1]
return s[:len(s)-1], true
}
}
return nil, false
}
func RunClient() {
//注册etcd解析器
r := NewEtcdResolver(EtcdAddr)
resolver.Register(r)
//连接服务器,同步调用r.Build()
conn, err := grpc.Dial(r.Scheme()+"://author/"+ServiceName, grpc.WithBalancerName("round_robin"), grpc.WithInsecure())
if err != nil {
fmt.Printf("connect err : %s", err)
}
defer conn.Close()
//获得gRPC客户端
c := proto.NewApiLogServiceClient(conn)
//调用服务
resp, err := c.GetApiLogByUid(
context.Background(),
&proto.ApiLogRequest{UId: 0},
)
if err != nil {
fmt.Printf("call service err : %s", err)
return
}
fmt.Printf("resp : %s , data : %s", resp.Msg, resp.Data)
}
2.2.3 公共组件
syntax = "proto3";
package proto;
option go_package = "../api_log";
service ApiLogService {
rpc GetApiLogByUid(ApiLogRequest) returns (ApiLogResponse){}
}
message ApiLogRequest{
int32 u_id = 1;
}
message ApiLogResponse{
int64 code = 1;
string msg = 2;
int64 count = 3;
string data = 4;
}
注意要在编译后进行使用哈
2.3 注意事项
在我编写代码进行实现的过程中遇到过种种问题,但是最让人记忆深刻的就是etcd与gRPC版本不兼容的问题,用了很长时间才搞定,在这里记录下吧:
原因是etcd3.x版本不支持grpc1.27版本以上,但是grpc1.27以下编译成的中间代码又不支持新版本的proto buffer,这就陷入了一个两难的处境,最后通过Stack Overflow才查到:
https://stackoverflow.com/questions/64815927/undefined-grpc-clientconninterface-when-compiling-grpc
解决,在go.mod中加入这几行代码:
replace (
github.com/coreos/etcd => github.com/ozonru/etcd v3.3.20-grpc1.27-origmodule+incompatible
google.golang.org/grpc => google.golang.org/grpc v1.27.0
)