手搓 RPC 框架 -- #5 调用语义
RPC 调用语义
- 异步调用:是指用户发起调用之后,就可以去做别的事情,在一段时间之后再处理结果。
- 单向调用(one-way):是指用户发起调用之后,不需要结果。
- 回调:用户在发起调用的时候注册一个回调,当结果返回来之后,再执行回调。
Go 里面的框架一般不会提供异步调用,因为用户可以自己开协程处理。
类似的,回调在 Go 里面也不需要支持,因为用户同样可以自己开协程处理。
在 Java 之类的语言里面,异步调用是通过 Future 来达成的。即最开始调用的时候,用户只能拿到一个 Future 对象,等用户调用 Future 对象上的 Get 方法时就会被阻塞,直到拿到响应。
RPC 单向调用
单向调用分成真假两种:
- 虚假的单向调用:是指用户的请求发过去之后,服务端会把响应发回来,但是客户端收到响应之后会直接丢弃。
- 真实的单向调用:是指用户的请求发过去之后,服务端发现这是一个单向调用,直接就不会发回响应,读完请求之后,连接等资源就被释放了。
核心在于:单向调用一定是为了尽快释放两端资源。因此虚假的单向调用,毫无意义。
在 Go 里面,虚假的单向调用,用户完全可以自己开一个 goroutine,只不过在 goroutine 里面用户不会处理响应而已。
单向调用支持
为了支持真实的单向调用,我们需要明确告知服务端该请求是单向的。根据协议设计,可以通过以下方式传递这一信息:
- 元数据标记:在元数据中添加一个标记位,明确表示这是单向调用。
- 头部字段:在请求头部设计一个额外字段,用于指示是否为单向调用。
接下来,用户如何告知 RPC 客户端这是单向调用请求呢?
- 第一种方法:在初始化客户端时,指定这是一个单向调用的服务。
- 第二种方法:在
context.Context
中携带一个标记位。
客户端
通过 meta 传递是否是单向调用
var meta map[string]string
if isOneway(ctx) {
meta = map[string]string{
"one-way": "true",
}
}
req := &message.Request{
ServiceName: service.Name(),
MethodName: fieldTyp.Name,
Data: reqArg,
Serializer: s.Code(),
Meta: meta,
}
发送完请求后直接返回
_, err = conn.Write(data)
if err != nil {
return nil, err
}
if isOneway(ctx) {
// oneway 调用直接返回
return nil, errors.New("micro: oneway")
}
服务端
if isOneway(ctx) {
go func() {
_, _ = stub.invoke(ctx, req)
}()
return nil, nil
}
总结
- 什么是异步调用?Go 里面怎么实现?核心还是那句话,在有 goroutine 的情况下,框架设计者没有必要支持。
- 什么是回调?Go 里面怎么实现?同上
- 什么是 one-way(单向)调用?要注意详细解释真伪两种 one-way 的形态,最关键的地方在于,服务端究竟会不会回写响应,而后进一步讨论两种形态对性能的影响。
- one-way 用在什么场景?一般就是用在你不需要返回值的地方,即便了失败了也无所谓的地方。
- 使用 one-way 有什么优点?性能,能尽早释放资源。
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。