理解了 RPC 是什么 之后,就来看看 Dubbo-go 的 RPC 流程。接下来,我以 dubbo-go-samples/helloworld 为例,进行分析。
下图为 Dubbo-go RPC 流程概况。从请求发起方(后文称 Consumer)分别经过 6 个模块处理,信息发送到服务端(后文称 Provider),再经过 5 个模块处理,最终调用远程方法的整个流程。
图 2 :Dubbo-go RPC 流程
从讲解 Consumer 是怎么通过 6 个模块处理后进行 RPC 开始。
如何启动 Conusmer
dubbo-go-samples/helloworld 中,Consumer 如何进行 RPC。从代码层面,可以看出 main 方法中调用 userProvider 的 GetUser 属性。但是,为什么调用一个属性可以正确进行 RPC 呢?请看一个例子,代码如下所示:
`func init() {`
`config.SetConsumerService(userProvider`
`hessian.RegisterPOJO(&pkg.User{}`
`}`
`func main() {`
`config.Load(`
`user := &pkg.User`
`// 调用本地方法,发起 Provider 的 RPC`
`err := userProvider.GetUser(context.TODO(), []interface{}{"A001"}, user)`
`}`
Proxy
发起 RPC 之后,随即执行代理层代理方法。通过属性正确发起 RPC 的关键点在于 config.Load() 。进入加载 consumer 配置的 loadConsumerConfig() ,Dubbo-go 读取 consumerConfig 中的 References 属性与 config.SetConsumerService() 信息对应,如正确找到,即可初始化引用 Provider 相关信息(如下代码中 ref.Refer 方法),如:组织需要注册的 URL、获取集群管理策略、获取代理类等。
`// 加载 Consumer 配置`
`func loadConsumerConfig() {`
`...`
`for key, ref := range consumerConfig.References {`
`...`
`ref.Refer(rpcService) <------初始化引用 Provider 相关信息`
`// 代理相关属性`
`ref.Implement(rpcService)`
`...`
`}`
代理类代理本地调用属性( userProvider.GetUser ),将调用属性的转到代理中,执行 RPC 相关方法,对用户透明的达到 RPC 效果。代理类可统一组织 RPC 需要的信息,并调用 Invoker 发起 RPC 调用。调用后,则返回响应结果。
`// 加载 Consumer 配置`
`func loadConsumerConfig() {`
`...`
`for key, ref := range consumerConfig.References {`
`...`
`ref.Refer(rpcService) <------初始化引用 Provider 相关信息`
`// 代理相关属性`
`ref.Implement(rpcService)`
`...`
`}`
代理层处理完后,就需要对 Provider 发起远程调用。发起远程调用则需要支持网络协议,接下来,进入调用层,我们再来看看 Dubbo-go 是怎么做。
Invoker
调用层,用于实现 Consumer 调用 Provider 方式的接口(如下代码所示)。目前 Dubbo-go 实现了几种业界上使用比较多的通讯协议,如:原生的 Dubbo 协议(支持 Hessian2 数据序列化)、gRPC 协议(支持 protobuffer 数据序列化)、JSONRPC 等,让用户可根据实际情况选择使用,也可以实现自定义的协议。
`// 实现 Consumer 调用 Provider 方式的接口`
`type Invoker interface {`
`common.Nod`
`// 调用远程方法并返回 result`
`// context.Context: 用于做链路跟踪或者获取调用期间的上下文`
`// Invocation:每个远程方法对应一个 Invocation`
`Invoke(context.Context, Invocation) Resul`
`}`
本例中主要涉及的是 Dubbo 协议(如下代码所示)。判断发起什么形式的请求为本方法的主要处理逻辑。Dubbo 协议支持多种请求方式:one-way、two-way、异步处理。方便地满足用户不一样场景下的需求。
`// 调用远程方法`
`func (di *DubboInvoker) Invoke(ctx context.Context, invocation protocol.Invocation) protocol.Result {`
`..`
`if async {`
`// 异步处理`
`if callBack, ok := inv.CallBack().(func(response common.CallbackResponse)); ok {`
`result.Err = di.client.AsyncRequest(&invocation, url, timeout, callBack, rest`
`} else {`
`// one-way`
`result.Err = di.client.Send(&invocation, url, timeout`
`} else {`
`..`
`// two-way`
`result.Err = di.client.Request(&invocation, url, timeout, rest`
`..`
`..`
`}`
经过调用层之后,把信息传入交换层,由其将 Consumer 提供的所有信息进行统一的整理与收集。
Exchange
进入 di.client.Request 分析如何进行 two-way 通讯。通过 Provider 相关信息实例化 Consumer 的连接池、数据序列化方法等连接信息。
-
invocation:Provider 被调用方法的相关信息,包括方法名,参数列表等。
-
url:Provider 连接相关信息,包括 IP、端口、应用名等。
-
timeout:请求超时时间,通过配置文件 references.methods.timeout 项配置。
-
result:请求结果,RPC 返回结果通过转换之后放入该对象,
整个交换层后续有更详细的章节进行分析。
`// two way 请求`
`func (client *ExchangeClient) Request(invocation *protocol.Invocation, url *common.URL, timeout time.Duration,`
`result *protocol.RPCResult) error {`
`// 根据 Provider URL 初始化连接相关信息,如:连接池、数据序列化方法。`
`if er := client.doInit(url); er != nil {`
`return er`
`}`
`...`
`// 发起远程调用`
`err := client.client.Request(request, timeout, rsp`
`...`
`}`
经过交换层整理与收集之后,数据传递到序列化层,将所有数据通过数据序列化协议进行序列化。
Codec
序列化层的数据序列化协议可由用户自行实现,但在本章中讲解的主要为 hessian2(Dubbo 与 Dubbo-go 的原生协议)。主要分为两步:
-
首先将 request 对象转换为 DubboPackage 对象;
-
转换完成后,将调用 Serializer.Marshal 将其序列化成 hessian2 协议内容,用于在 RPC 发送给服务端的信息。整个序列化层后续有更详细的章节进行分析。
`// 编码接口`
`type Codec interface {`
`// encode 请求内容`
`EncodeRequest(request *Request) (*bytes.Buffer, error`
`// encode 响应内容`
`EncodeResponse(response *Response) (*bytes.Buffer, error`
`// decode 请求/响应内容`
`Decode(data []byte) (DecodeResult, int, error)`
`}`
`// 序列化接口`
`type Serializer interface {`
`// 将 DubboPackage 对象序列化成 []byte`
`Marshal(p DubboPackage) ([]byte, error)`
`// 将 []byte 反序列化成 DubboPackage`
`Unmarshal([]byte, *DubboPackage) error`
`}`
数据经过序列化,则可以进入负责数据传输阶的传输层。
Transport
最后就是传输层,Dubbo-go 在传输层使用的是 apache/dubbo-getty 组件进行 TCP 通讯并传输数据。其中实现 Connection 接口是支持多传输协议的关键,目前已支持 UDP、TCP、WebSocket 协议。整个传输层,后续有更详细的章节进行分析。
`package getty`
`....`
`type Connection interface {`
`....`
`// 将 interface 对象通过连接发送`
`send(interface{}) (int, error)`
`....`
`}`
`...`
Consumer 至此已将数据序列化完毕,对 Provider 发起网络连接并进行数据传输。数据到达 Provider 后,Provider 会怎么样处理呢?不用急,接下来将继续讲解。
总结
相信你已经知道了调用 Consumer 的本地方法时, Dubbo-go 是怎样逐层将信息编码,并发送到 Provider。
关于它,还有很多深层的知识值得我们一一探索和学习,比如:
-
Dubbo-go 通过什么方式进行网络传输?
-
Dubbo-go 通过什么方式进行数据序列及反序列化?
这一系列问题的答案,都需要在后续文章中,慢慢寻找答案。
欢迎加入社区
关注公众号【部长技术之路】,在公众号后台回复关键字【dubbogo】加入社区。