Go猜想录
大道至简,悟者天成
手搓 RPC 框架 -- #5 调用语义

RPC 调用语义

  • 异步调用:是指用户发起调用之后,就可以去做别的事情,在一段时间之后再处理结果。
  • 单向调用(one-way):是指用户发起调用之后,不需要结果。
  • 回调:用户在发起调用的时候注册一个回调,当结果返回来之后,再执行回调。

Go 里面的框架一般不会提供异步调用,因为用户可以自己开协程处理。

类似的,回调在 Go 里面也不需要支持,因为用户同样可以自己开协程处理。

rpc-16.png

在 Java 之类的语言里面,异步调用是通过 Future 来达成的。即最开始调用的时候,用户只能拿到一个 Future 对象,等用户调用 Future 对象上的 Get 方法时就会被阻塞,直到拿到响应。

RPC 单向调用

单向调用分成真假两种:

  • 虚假的单向调用:是指用户的请求发过去之后,服务端会把响应发回来,但是客户端收到响应之后会直接丢弃。
  • 真实的单向调用:是指用户的请求发过去之后,服务端发现这是一个单向调用,直接就不会发回响应,读完请求之后,连接等资源就被释放了。

核心在于:单向调用一定是为了尽快释放两端资源。因此虚假的单向调用,毫无意义。

在 Go 里面,虚假的单向调用,用户完全可以自己开一个 goroutine,只不过在 goroutine 里面用户不会处理响应而已。

rpc-17.png

单向调用支持

为了支持真实的单向调用,我们需要明确告知服务端该请求是单向的。根据协议设计,可以通过以下方式传递这一信息:

  1. 元数据标记:在元数据中添加一个标记位,明确表示这是单向调用。
  2. 头部字段:在请求头部设计一个额外字段,用于指示是否为单向调用。

rpc-18.png

接下来,用户如何告知 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 国际许可协议进行许可。