English | 中文
客户端通过桩代码发起 RPC 请求,请求会经过框架的 client 模块,进行服务发现,执行拦截器,序列化,压缩等,最后通过 transport 模块发送到网络中;而接收到网络响应后,会执行解压缩,反序列化,执行拦截器,最终返回响应给用户。client 模块中的每个步骤都是可以自定义的,用户可以自定义服务发现方式,自定义拦截器,自定义序列化和压缩等。
用户可以在发起 RPC 请求的时候传入不同的客户端选项,包括请求的目的地址,网络类型等;也可以通过选项指定各个步骤的具体实现,包括服务发现方式,序列化方式等。
proxy := pb.NewGreeterClientProxy()
rsp, err := proxy.Hello(
context.Background(),
&pb.HelloRequest{Msg: "world"},
client.WithTarget("ip://127.0.0.1:9000"), // 指定 client 选项
)
常用的选项如下:
-
WithTarget(target string)
设置服务端地址 -
WithNetwork(network string)
设置网络类型 -
WithNamespace(ns string)
设置服务端命名空间(Production/Development) -
WithServiceName(name string)
设置服务端服务名,服务名会直接在服务发现用于寻址 -
WithTimeout(timeout time.Duration)
设置请求的超时时间 -
WithNamedFilter(name string, f filter.ClientFilter)
设置拦截器 -
WithSerializationType(t int)
设置序列化类型,内置的序列化方式有 protobuf,json,flatbuffer 等,自定义序列化类型需要先使用codec.RegisterSerializer
注册序列化类型 -
WithCompressType(t int)
设置压缩类型,内置的序列化方式有 gzip,snappy,zlib 等,自定义压缩类型需要先使用codec.RegisterCompressor
注册压缩类型 -
WithProtocol(s string)
设置自定义协议类型(默认是 trpc),需要先使用codec.Register
注册协议类型
用户不仅可以在发起 RPC 请求时传入不同的选项,还可以在配置文件中添加客户端的配置。客户端选项和客户端配置的功能是部分重合的,客户端选项的优先级高于客户端配置,如果同时添加了配置和选项的话,选项中的内容会覆盖配置。使用客户端配置的优点是方便修改配置内容,不需要频繁变更代码。
client: # 客户端配置
timeout: 1000 # 所有请求最长处理时间(ms)
namespace: Development # 所有请求服务端的环境
filter: # 所有请求的拦截器
- debuglog # 使用 debuglog 打印具体请求和响应数据
service: # 请求特定服务端的配置
- callee: trpc.test.helloworld.Greeter # 请求服务端协议文件的 service name, 如果 callee 和下面的 name 一样,那只需要配置其中之一即可
name: trpc.test.helloworld.Greeter1 # 请求服务名字路由的 service name
target: ip://127.0.0.1:8000 # 服务端地址,如果 name 可以直接用作服务发现,则可以不用配置,例如 ip://ip:port, polaris://servicename
network: tcp # 请求的网络类型 tcp udp
protocol: trpc # 应用层协议 trpc http
timeout: 800 # 请求超时时间(ms)
serialization: 0 # 序列化方式 0-pb 2-json 3-flatbuffer,默认不用配置
compression: 1 # 压缩方式 1-gzip 2-snappy 3-zlib,默认不用配置
配置中的 callee
和 name
的区别:
callee
是指被调方的 pb 协议文件的 service name,格式是 pbpackage.service
。
如 pb 为:
package trpc.a.b;
service Greeter {
rpc SayHello(request) returns reply
}
那么 callee
即为 trpc.a.b.Greeter
name
是指被调方注册在名字服务上面的服务名,也就是被调服务配置文件里面的 server.service.name
的字段值。
一般情况下,callee
和 name
是相同的,只需配置其中任何一个即可,但是有些场景下,如存储服务,同一份 pb 会部署多个实例,这个时候的名字服务的 service name 和 pb service name 就不一样了,此时配置文件就必须同时配置 callee
和 name
client:
service:
- callee: pbpackage.service # 必须同时配置 callee 和 name,callee 是 pb 的 service name,用于匹配 client proxy 和配置
name: polaris-service-name # 名字服务的 service name,用于寻址
protocol: trpc
通过 pb 生成的 client 桩代码,默认会把 pb servicename 填入到 client 中,所以 client 寻找配置时只会 以 callee 为 key
(也就是 pb 的 service name)来匹配
而通过类似 redis.NewClientProxy("trpc.a.b.c")
等(包括 database 下面所有插件以及 http)生成的 client,默认 service name 就是用户自己输入的字符串,所以 client 寻找配置时以 NewClientProxy 的输入参数为 key(即以上的 trpc.a.b.c
)来匹配
同时,框架还支持了同时以 callee
及 name
为 key 来寻找配置,比如以下两个客户端配置共享了相同的 callee
, 但是 name
不同:
client:
service:
- callee: pbpackage.service # callee 是 pb 的 service name
name: polaris-service-name1 # 名字服务的 service name,用于寻址
network: tcp # 使用 TCP
- callee: pbpackage.service # callee 是 pb 的 service name
name: polaris-service-name2 # 另一个名字服务的 service name,用于寻址
network: udp # 使用 UDP
用户在代码中可以使用 client.WithServiceName
来同时用 called
以及 name
作为 key 进行配置的寻找:
// proxy1 使用第一项配置,使用 TCP
proxy1 := pb.NewClientProxy(client.WithServiceName("polaris-service-name1"))
// proxy2 使用第二项配置,使用 UDP
proxy2 := pb.NewClientProxy(client.WithServiceName("polaris-service-name2"))
- 用户传入请求,在使用桩代码发起 RPC 调用
- 进入 client 模块
- 根据配置的 option 和配置文件信息完成客户端配置
- 进行服务发现,根据服务名获取服务真实地址
- 调用拦截器,执行拦截器前置阶段
- 序列化请求体,得到二进制数据
- 压缩请求体
- 打包完整请请求,添加协议头
- transport 模块发起网络请求
- transport 模块接收网络响应
- 解包响应,得到协议头和响应体
- 解压响应体
- 反序列化响应,得到响应结构
- 调用拦截器,执行拦截器后置阶段
- 将响应返回给用户