Go-zero使用(二) | 青训营

429 阅读5分钟

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-goprotoc-gen-go-grpc 插件来配合生成 Go 语言的 gRPC 代码。

我们可以使用之前下载的goctl进行一键安装protocprotoc-gen-goprotoc-gen-go-grpc 相关组件:

goctl env check --install --verbose --force

其中:

  • verbose表示是否输出执行日志
  • force 表示默认安装

然后,我们通过输入如下命令可以验证是否安装成功。:

goctl env check --verbose

image.png

(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服务差不多,如图:

image.png

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来作为注册中心,实现服务的注册与发现。

实现一个简单的RPC框架 - 掘金 (juejin.cn)

image.png 我们需要从github.com/etcd-io/etc…下载相应的压缩包,解压到对应目录后在目录下启动etcd.exe即启动etcd服务:

image-20230825150328163

image.png 如果想在任意目录下使用Etcd命令,需要将下载路径加入到环境变量中。

写操作:

etcdctl put good9 "Hello world"
OK

读操作:

etcdctl get good9
good9
Hello world
完成一个简单的rpc服务

如图所示,我们在goods.go文件的main中 可以看到我们在zrpc中注册了goods服务:

image-20230825155115216

image.png 然后我们对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服务注册的情况:

image.png --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: 8888GoodsRpc:       // 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接口:

image.png

其中,输出日志的参数解释:

  • "@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}。