前文已经分享过 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】加入社区。