【dubbo-go 源码解析】Dubbo-go 服务端如何处理 RPC 请求?|Go主题月

1,414 阅读4分钟

前文已经分享过 Dubbo-go RPC 请求如何到达服务端,接下来,讲解 Provider 怎样处理接收到的数据。在讲解之前,首先需要了解 Provider 怎样接收数据。众所周知,接收网络请求需要启动一个 Web server 并监听一个网络端口。所以,本文从如何启动 Provider 的 Web Server 开始讲解,在进行深入分析。

启动 Provider

启动 Dubbo-go 之前,都需要将提供服务的结构体(本例中的 UserProvider)与参数为非基础类型的结构体(本例中的 User)手工注册到 Dubbo-go 中。

`func init() {`
 `// 注册 Provider`
 `config.SetProviderService(new(UserProvider))`
 `// 注册 hessian 2 结构体,非基础数据类型都需要注册`
 `hessian.RegisterPOJO(&User{})`
`}`
`func main() {`
 `// 通过配置文件启动`
 `config.Load()`
 `initSignal()`
`}`

调用 Provider 的启动方法(config.Load)后,Dubbo-go 就会通过读取用户编写的配置,按照其配置启动 Web Server 。

启动 Web Server

前文已经提到 Web Server 是接收网络数据的重要步骤,所以,分析 Provider 接收数据的 RPC 过程之前,先了解 Web Server 是如何启动。(具体过程,后续文章中会详细讲解)

`// 启动 dubbo 服务`
`func (s *Server) Start() {`
 `...`
 `// 开启端口监听并进行信息接收`
 `tcpServer.RunEventLoop(s.newSession)`
 `logger.Debugf("s bind addr{%s} ok!", s.addr)`
 `s.tcpServer = tcpServer`
`}` 

知道了 Web Server 怎么启动之后,Web server 会作为 Provider 监听端口、提供接口服务,并向注册中心注册 Provider 信息(包括:应用及服务元信息),让 Consumer 将其拉取到本地,准备随时发起 RPC 。

Transport

到这里,可能会有另外的疑问:Provider 怎么知道应该执行什么方法?怎么找到对应的方法?

其实在传输层里,会先接收 Consumer 发过来的信息,根据数据包带有的信息找到对应的方法。代码如下所示:

`func (s *session) handlePackage() {`
 `...`
 `// 获取连接并判断是否 TCP`
 `if _, ok := s.Connection.(*gettyTCPConn); ok {`
 `if s.reader == nil {`
 `errStr := fmt.Sprintf("session{name:%s, conn:%#v, reader:%#v}", s.name, s.Connection, s.reader)`
 `log.Error(errStr)`
 `panic(errStr)`
 
 `// 解析 tcp 包`
 `err = s.handleTCPPackage()`
 `}`
 `...`
`}` 

获取到数据包,就需要将其转换成 Provider 提供的接口能执行的结构体,这就是下一步序列化层需要执行的反序列化操作。

Codec

序列化层 s.Reader 使用 RpcServerPackageHandler 解析 Consumer 发送过来的流信息,转换成 remoting.DecodeResult ,包含对应需要反射的方法、与请求参数等各项信息。

`// 通过 TCP 流生成对应的 dubbo package 结构体`
`func (s *session) handleTCPPackage() error {`
 `...`
 `for {`
 `// 读取网络包并解析成对应的结构体`
 `pkg, pkgLen, err = s.reader.Read(s, pktBuf.Bytes()`
 `...`
 
 `// 将解析好的包,放到业务执行队列中执行。`
 `s.addTask(pkg)`
 
 `...`
`}`

读取网络包并解释成对应的结构体。

`func (p *RpcServerPackageHandler) Read(ss getty.Session, data []byte) (interface{}, int, error) {`
 `// 转换成 remoting.DecodeResult ,包含对应需要反射的方法、与请求参数等各项信息`
 `req, length, err := (p.server.codec).Decode(data`
 `...`
`}`

转换成 remoting.DecodeResult ,再判断是否需要放到队列中等待操作具体 Provider 的实现的接口逻辑。

`func (s *session) addTask(pkg interface{}) {`
 `// 生成对应业务执行回调方法`
 `f := func() {`
 `s.listener.OnMessage(s, pkg`
 `s.incReadPkgNum(`
 
 `// 增加到队列中或者直接执行`
 `if s.tPool != nil {`
 `s.tPool.AddTask(f)  <<<<< 增加到队列`
 `return`
 
 `f()  <<<<<< 直接执行`
`}`

 

最后则到了 Provider 最后一个阶段,通过监听器执行实际 Provider 提供的接口处理逻辑。

Listener

监听器使用序列化层解析出来的结果调用执行 Invocation 的代理方法,通过 Consumer 提供的 service key 找到对应的方法,然后执行对应的业务方法。最后,将执行结果一层一层往上返回,并返回给 Consumer 调用方。

`// 从getty 获取请求,更新会话reqNum并向客户机回复响应`
`func (h *RpcServerHandler) OnMessage(session getty.Session, pkg interface{}) {`
 `...`
 `invoc, ok := req.Data.(*invocation.RPCInvocation)`
 `...`
 `// 执行对应被调用的方法,那 requestHandler 是哪里来的呢?`
 `result := h.server.requestHandler(invoc)`
 `...`
`}`

在 Providr 启动时,通过建立代理 handler 。将获取到网络请求反序列化之后,通过 Consumer 提供的 service key 找到对应的方法,然后执行对应的业务方法。

`func (dp *DubboProtocol) openServer(url *common.URL) {`
 `...`
 `// 新建代理 RPC Invocation 的方法`
 `handler := func(invocation *invocation.RPCInvocation) protocol.RPCResult {`
 `// 通过 Consumer 提供的 service key 找到对应的方法,然后执行对应的业务方法`
 `return doHandleRequest(invocation)`
 
 `// 设置进 Web server 实例`
 `srv := remoting.NewExchangeServer(url, getty.NewServer(url, handler)`
 `dp.serverMap[url.Location] = sr`
 `srv.Start()`
 `...`
`}` 

以上就是 Dubbo-go 的 Provider 处理 RPC 请求的整个过程。

总结

相信你已经知道了Provider 如何获取到信息,并将信息逐层解析并调用对应方法的整个过程。而关于它,还有很多深层的知识值得我们一一探索和学习,比如:Dubbo-go 在整个 RPC 过程中,如何暴露服务?关于该问题答案,需要在后续的文章中,继续寻找答案。

欢迎加入社区

关注公众号【部长技术之路】,在公众号后台回复关键字【dubbogo】加入社区。