go-zero学习笔记(四)
在api中访问rpc服务
-
必备条件
api端(客户端)有rpc端的pb文件,以及客户端文件(非必要),且已知rpc端的服务注册方式(etcd、k8s)及地址,端口等
-
api端配置文件编写
# 新增如下配置: # 如果rpc端的服务注册为ETCD DemoRpcConf: Etcd: Hosts: - 0.0.0.0:2379 # etcd的地址及端口 key: demo.rpc #rpc注册时的key # 如果rpc端的服务注册为K8S DemoRpcConf: Target: kubernetes:///<service-name>.<namespace>.svc.cluster.local:port
当然还有其他方式,但主要以这两种为主
-
api端internal/config/config.go编写
// 在config结构体中新增如下代码: DemoRpcConf zrpc.RpcClientConf
-
api端internal/svc/servicecontext.go编写
// 在ServiceContext结构体中新增如下代码: DemoRpc demo.DemoClient
// 在NewServiceContext方法的返回值中新增如下代码: DemoRpc: democlient.NewDemo(zrpc.MustNewClient(c.DemoRpcConf)),//如果你有democlient包 DemoRpc: demo.NewDemoClient(zrpc.MustNewClient(c.DemoRpcConf).Conn()), // 只有pb文件
这里,如果你的rpc服务做了多节点部署,zrpc会自动实现基于p2c的负载均衡算法,使客户端到服务端的访问变为负载均衡模式。
-
rpc方法调用
经过上述步骤,已经实现rpc客户端的初始化,并将其保存在svc中,那么在logic中,你就可以直接调用具体方法了。
本地测试rpc服务
-
前提
如果你在写完rpc的logic层以后,想做一个简单的测试,且你电脑上也没有安装ETCD、k8s等服务注册、服务发现组件,那么此时你可以使用直连的方式。 -
在rpc的配置文件中etc/demo.yaml删除关于etcd的配置
Name: demo.rpc ListenOn: 0.0.0.0:8080 Etcd: Hosts: - 127.0.0.1:2379 Key: demo.rpc
-
直连代码
package main import ( "context" "demo/rpc/demo" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" ) func main() { addr := ":8080" // rpc端口 conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { // 错误处理 fmt.Println(err.Error()) } defer conn.Close() client := demo.NewDemoClient(conn) ctx := metadata.AppendToOutgoingContext(context.Background(), "key", "value") resp, err := client.Search(ctx, nil) // 具体方法 if err != nil { fmt.Println(err) return } fmt.Printf("==============%#v==============", resp.Projects) }
api请求rpc时使用ctx传值
-
需求:
在上述代码中,api调用rpc时需要传入上下文ctx,有时候,我们需要将一些固定的值由ctx带入,而不是在请求参数中带过去,比如api版本,请求唯一ID等。 -
问题:
如果你使用的是:ctx := context.WithValue(r.Context(), "key", "value")
你会发现,在rpc端是无法读取到对应的数据的,也就是说这种方式是无法将值存储在ctx中传递给rpc服务端的。
-
解决方案:使用metadata包实现
// 客户端(api端)实现如下方法设置ctx: ctx := metadata.AppendToOutgoingContext(context.Background(), "key", "value") // 也可以使用,但是NewOutgoingContext会将之前的数据覆盖,因此推荐第一种 md := metadata.New(map[string]string{"key": "value", "key1": "value1"}) ctx := metadata.NewOutgoingContext(context.Background(), md)
// 服务端(rpc端)读取ctx md, ok := metadata.FromIncomingContext(l.ctx) if ok { for k, v := range md { fmt.Printf("key:%s value:%s\n", k, v) } }
更多metadata包使用详情可参考 写给go开发者的gRPC教程-metadata