go-zero是一个集成了各种工程实践的包含了Web和RPC协议的功能完善的微服务框架,那么他的RPC功能是如何实现的呢?他自己封装了一个叫zRPC的底层模块,这个模块依赖gRPC,同时内置了服务注册、负载均衡、拦截器等功能,其中还包括自适应降载,自适应熔断,限流等微服务治理方案,是一个简单易用的可直接用于生产的企业级RPC框架。

zRPC支持直连和基于etcd服务发现两种方式,下面我们看看一个基于单机版etcd的例子:

zRPC示例

前提条件,需要先安装好etcd服务,可以参考:/2021/01/26123213-go-etcd.html

接下来创建项目文件夹hello,然后在下面创建client、model、server三个子文件夹。

第一:进入hello目录,控制台执行:

# 先生成go.mod文件
go mod init hello

# 加入下面的依赖包
require (
	github.com/golang/protobuf v1.4.2
	github.com/tal-tech/go-zero v1.1.4
	google.golang.org/grpc v1.29.1
	google.golang.org/protobuf v1.25.0
)

# 下载依赖包
go mod tidy

第二:在model文件夹下加入hello.proto

syntax = "proto3";

package model;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

执行下面命令,自动生成hello.pb.go

protoc --go_out=plugins=grpc:. hello.proto

第三:进入server目录,加入hello.yaml和server.go两个文件:

Name: hello.rpc
ListenOn: 127.0.0.1:9090
Etcd:
  Hosts:
    - xx.xx.xx.xx:2379
  Key: hello.rpc

server端的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
	"context"
	"flag"
	"github.com/tal-tech/go-zero/core/conf"
	"github.com/tal-tech/go-zero/zrpc"
	"google.golang.org/grpc"
	"hello/model"
	"log"
)

type Config struct {
	zrpc.RpcServerConf
}

var cfgFile = flag.String("f", "./hello.yaml", "cfg file")

func main() {
	flag.Parse()

	var cfg Config
	conf.MustLoad(*cfgFile, &cfg)

	srv, err := zrpc.NewServer(cfg.RpcServerConf, func(s *grpc.Server) {
		model.RegisterGreeterServer(s, &Hello{})
	})
	if err != nil {
		log.Fatal(err)
	}
	srv.Start()
}

type Hello struct{}

func (h *Hello) SayHello(ctx context.Context, in *model.HelloRequest) 
	(*model.HelloReply, error) {
	return &model.HelloReply{Message: "hello " + in.Name}, nil
}

第四:进入client目录,加入client.go两个文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
	"context"
	"github.com/tal-tech/go-zero/core/discov"
	"github.com/tal-tech/go-zero/zrpc"
	"hello/model"
	"log"
)

func main() {
	client := zrpc.MustNewClient(zrpc.RpcClientConf{
		Etcd: discov.EtcdConf{
			Hosts: []string{"xx.xx.xx.xx:2379"},
			Key:   "hello.rpc",
		},
	})

	conn := client.Conn()
	hello := model.NewGreeterClient(conn)
	reply, err := hello.SayHello(context.Background(), &model.HelloRequest{Name: "go-zero"})
	if err != nil {
		log.Fatal(err)
	}
	log.Println(reply.Message)
}

最后的目录结构如下:

hello
│  go.mod
│  go.sum
├─client
│      client.go
├─model
│      hello.pb.go
│      hello.proto
└─server
        hello.yaml
        server.go

第五:运行。

先启动服务端:go run server.go

在etcd数据库中应该可以看到rpc service已经注册到了数据库中:

# 运行
etcdctl get hello.rpc --prefix

# 结果如下
hello.rpc/7587852094525776155
127.0.0.1:9090

然后启动客户端:go run client.go,会看到调用rpc服务成功,并返回下面的结果:

2021/01/28 12:53:58 {"@timestamp":"2021-01-28T12:53:58.865+08",......}
2021/01/28 12:53:58 hello go-zero

总结

借助go-zero框架自带的zrpc模块,我们利用etcd分布式键值数据库作为注册中心,成功的注册了rpc服务,之后网关客户端成功的从注册中心发现了rpc服务,并选取调用,得到了预期的结果。一套简单的微服务rpc示例就完成了。

zrpc原理

zRPC主要有以下几个模块组成:

  • discov: 服务发现模块,基于etcd实现服务发现功能
  • resolver: 服务注册模块,实现了gRPC的resolver.Builder接口并注册到gRPC
  • interceptor: 拦截器,对请求和响应进行拦截处理
  • balancer: 负载均衡模块,实现了p2c负载均衡算法,并注册到gRPC
  • client: zRPC客户端,负责发起请求
  • server: zRPC服务端,负责处理请求

更多内容介绍,请参考:https://github.com/tal-tech/zero-doc/blob/main/doc/zrpc.md

(完)