MIT 6.824 - Lab 1 (4): Go RPC
Lab 1
虽然是个单机器多线程、多进程的程序,但主节点和工作节点的交互依然通过 RPC
实现,Go
本身也提供了开箱即用的 RPC
功能,下面将通过一个简单的求和服务来了解在 Go
中如何实现一个 RPC
服务。
定义请求体和响应体
请求体和响应体都非常简单,SumRequest
中包含要求和的两个数字,SumReply
中存放求和的结果:
1 | package pb |
服务端
首先定义服务类 SumService
和提供的方法:
1 | type SumService struct { |
SumService
只有一个 Sum
方法,接收 SumRequest
和 SumReply
两个参数,求和后将结果放回到 SumReply
中(Sum
的方法签名必须是这样的形式,即两个入参和一个 error
类型的出参,具体规则见下文描述)。
然后进行服务注册:
1 | sumService := &SumService{} |
通过 rpc.Register
这个方法可以知道,一个服务类及其提供的方法必须满足以下条件才能注册成功:
- 服务类必须是公共的
- 服务类提供的方法必须是公共的
- 服务类提供的方法入参必须是两个,一个表示请求,一个表示响应(从编码的角度来说方法入参是两个,但是实际代码是判断是否等于3个,因为在这种场景下定义的方法的第一个入参类似于
Java
中的this
) - 服务类提供的方法的第一个参数类型必须是公共的或者是
Go
内置的数据类型 - 服务类提供的方法的第二个参数类型也必须是公共的或者是
Go
内置的数据类型,且必须是指针类型 - 服务类提供的方法的出参个数只能是1个
- 服务类提供的方法的出参类型必须是
error
而 rpc.HandleHTTP()
表示通过 HTTP
作为客户端和服务端间的通信协议,当客户端发起一个 RPC
调用时,本质上是将要调用的方法和参数包装成一个 HTTP
请求,服务端收到 HTTP
请求后,解码出要调用的本地方法名称和入参,然后调用本地方法,在本地方法调用完成后再将结果写入到 HTTP
响应中,客户端收到响应后,再解析出远程调用的结果。
rpc.HandleHTTP()
本质上是个 HTTP
路由注册,实际上是调用 Handle(pattern string, handler Handler)
方法,当请求路由匹配 pattern
时,会调用对应的 handler
执行,对于 Go RPC
来说,固定路由路径是 /_goRPC_
。
所以,在完成 HTTP
路由注册后,还需要配合开启一个 HTTP
服务,这样才能接受远程服务调用:
1 | listener, err := net.Listen("tcp", ":1234") |
http.Serve
方法中对于每一个客户端的连接,最终会分配一个 goroutine
来调用 Handler
的 ServeHTTP(ResponseWriter, *Request)
方法来处理,对于 Go
的 RPC
包来说,则可以实现该方法来处理 RPC
请求:
1 | // ServeHTTP implements an http.Handler that answers RPC requests. |
客户端
对于客户端来说,发起远程方法调用前需要先和服务端建立连接:
1 | client, err := rpc.DialHTTP("tcp", ":1234") |
该方法同时返回了一个 RPC
客户端类,内部同时负责对 RPC
请求的编码和解码。
然后通过 client.Call
来发起远程调用:
1 | sumRequest := &pb.SumRequest{ |
这里的调用一共有三个参数,第一个是被调用的远程方法名,需要是 类名.方法名
的形式,后两个则是远程方法的约定入参。
完整的代码可参考 go-rpc-demo。