Go-zero使用(二) | 青训营
gRPC服务:
介绍
首先,RPC,全称Remote Procedure Call,中文译为远程过程调用。通俗地讲,使用RPC进行通信,调用远程函数就像调用本地函数一样,RPC底层会做好数据的序列化与传输,从而能使我们更轻松地创建分布式应用和服务。
而gRPC,则是RPC的一种,它是免费且开源的,由谷歌出品。使用gRPC,我们只需要定义好每个API的Request和Response,剩下的gRPC这个框架会帮我们自动搞定。
另外,gRPC的典型特征就是使用protobuf(全称protocol buffers)作为其接口定义语言(Interface Definition Language,缩写IDL),同时底层的消息交换格式也是使用protobuf。
安装protoc
首先,我们需要先下载protoc工具,它可以自动用于生成代码的工具,可以根据 proto 文件生成C++、Java、Python、Go、PHP 等多重语言的代码,而 gRPC 的代码生成还依赖 protoc-gen-go,protoc-gen-go-grpc 插件来配合生成 Go 语言的 gRPC 代码。
我们可以使用之前下载的goctl进行一键安装protoc,protoc-gen-go,protoc-gen-go-grpc 相关组件:
goctl env check --install --verbose --force
其中:
- verbose表示是否输出执行日志
- force 表示默认安装
然后,我们通过输入如下命令可以验证是否安装成功。:
goctl env check --verbose
(protoc-gen-go组件一直显示找不到,但是可以正常运行。。。。)
创建一个简单的rpc服务
首先,我们创建一个goods.proto文件,定义自己微服务:
syntax = "proto3";
package goods;
// protoc-gen-go 版本大于1.4.0, proto文件需要加上go_package,否则无法生成
option go_package = "./goods";
//定义请求体
message GoodsRequest {
int64 goods_id = 1;
}
//定义响应体
message GoodsResponse {
// 商品id
int64 goods_id = 1;
// 商品名称
string name = 2;
}
service Goods {
//rpc方法
rpc getGoods(GoodsRequest) returns(GoodsResponse);
//可以继续定义多个方法
}
然后,我们在命令行中通过goctl自动生成go代码的rpc项目:
goctl rpc protoc goods.proto --go_out=./types --go-grpc_out=./types --zrpc_out=.
目录结构和api服务差不多,如图:
Etcd
我们打开/etc/goods.yaml文件可以看到
Name: goods.rpc
ListenOn: 0.0.0.0:8080
Etcd:
Hosts:
- 127.0.0.1:2379
Key: goods.rpc
Etcd是一个分布式、高可用的键值存储系统,它被设计为可靠的、安全的、快速的,并具有简单的API。它是用 Go 语言开发,基于 Raft算法实现了分布式一致性。可用于存储集群中的关键配置信息、服务发现、锁等。
它的数据模型类似于一个简单的文件系统,支持 PUT、GET、DELETE 等操作,每个节点的数据会自动同步到其他节点上,因此可以实现高可用、自动故障转移等功能。etcd 还支持 Watch 机制,可以监控数据变化并触发相应的操作。
这里使用Etcd来作为注册中心,实现服务的注册与发现。
我们需要从github.com/etcd-io/etc…下载相应的压缩包,解压到对应目录后在目录下启动etcd.exe即启动etcd服务:
如果想在任意目录下使用Etcd命令,需要将下载路径加入到环境变量中。
写操作:
etcdctl put good9 "Hello world"
OK
读操作:
etcdctl get good9
good9
Hello world
完成一个简单的rpc服务
如图所示,我们在goods.go文件的main中 可以看到我们在zrpc中注册了goods服务:
然后我们对
GetGoods()进行修改,使其根据id返回不同的货物信息:
func (l *GetGoodsLogic) GetGoods(in *goods.GoodsRequest) (res *goods.GoodsResponse, err error) {
//根据订单id获取商品信息
goodsId := in.GoodsId
res = new(goods.GoodsResponse)
if goodsId == 1 {
res.Name = "手机"
} else if goodsId == 2 {
res.Name = "电脑"
} else {
res.Name = "未知"
}
res.GoodsId = goodsId
return
}
最后,启动rpc服务前需要打开Etcd服务,然后就可以运行goods.go文件启动rpc服务。
我们可以在Etcdctl中查看rpc服务注册的情况:
--prefix 指的是以前缀查询,
etcdctl get --prefix ""代表查询所有的key。
上图中第一行为key,第二行为value,表示rpc服务注册到了Etcd中,它的地址和端口为169.254.67.152:8080。
api调用rpc服务
我们使用上一篇中的api订单服务调用这个rpc服务,首先需要在zero_test_order/etc/order-api.yaml中添加要调用的rpc服务的Etcd配置,和grpc_test/etc/goods.yaml下的内容对应:
Name: order-api
Host: 0.0.0.0
Port: 8888
GoodsRpc: // rpc服务名称
Etcd:
Hosts:
- 127.0.0.1:2379 //Etcd的ip和端口
Key: goods.rpc //rpc服务注册的key
然后,我们要在zero_test_order/internal/config/config.go文件中定义rpc服务:
type Config struct {
rest.RestConf
//定义rpc服务
GoodsRpc zrpc.RpcClientConf
}
在zero_test_order/internal/svc/serviceContext.go中定义rpc类型和在api服务中引入rpc服务:
type ServiceContext struct {
Config config.Config
//定义rpc类型
Goods goodsclient.Goods
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
//引入prc服务
Goods: goodsclient.NewGoods(zrpc.MustNewClient(c.GoodsRpc)),
}
}
最后,我们在业务逻辑代码zero_test_order/internal/logic/orderInfoLogic.go中对调用rpc服务返回的数据进行业务处理:
func (l *OrderInfoLogic) OrderInfo(req *types.Request) (resp *types.Response, err error) {
orderId := req.OrderId
goodRequest := new(goods.GoodsRequest)
goodRequest.GoodsId = orderId
goodsInfo, err := l.svcCtx.Goods.GetGoods(l.ctx, goodRequest)
if err != nil {
return nil, err
}
resp = new(types.Response)
resp.GoodsName = goodsInfo.Name
resp.OrderId = goodsInfo.GoodsId
return
}
依次启动Etcd、rpc、api后,通过curl请求api接口:
其中,输出日志的参数解释:
- "@timestamp": 日志记录的时间戳,格式为ISO 8601。
- "caller": 日志记录的来源位置,指示在代码的哪个位置生成了该日志。
- "content": 日志记录的内容,描述了请求的细节信息。
- "duration": 请求处理的持续时间,以毫秒为单位。
- "level": 日志的级别,这里是"info",表示信息级别的日志。
- "span": 请求的跟踪标识,用于在分布式系统中追踪请求的路径。
- "trace": 请求的追踪标识,用于在分布式系统中追踪请求的路径。
API服务输出的日志记录了一个来自IP地址为127.0.0.1、端口为56689的curl请求( curl/8.0.1表示版本),在0.5毫秒内成功处理了一个名为"/order/info"的POST请求。
RPC服务输出的日志记录了一个请求来源于IP地址为169.254.67.152,端口号为56447的客户端,该客户端调用了"/goods.Goods/getGoods"的方法,并传递了参数{"goods_id":4}。