sofa-bolt
sofa-bolt copied to clipboard
增加 Goaway 指令以支持服务端优雅退出
问题描述: 我们在跨 VPC 的通信场景中使用了 BOLT 协议,由于不同 VPC 的网络是隔离的,我们使用了 VIP 来做网络打通 这个时候当 VIP 后面的 Real Server 在进行版本升级或者其他运维操作的时候,请求总是会出现抖动 因为我们虽然可以修改 VIP 上 Real Server 的权重或者状态,但是只能控制不在向 Real Server 建新的链接,Real Server 上的已有的长链接本身还是存在,这个时候如果客户端还继续向这个链接发送请求,在 Real Server 进程退出的瞬间就会出现部分请求没有被处理完成的情况。因此客户端需要感知到哪些链接背后的 Server 状态发生变化,链接需要关闭,停止向该链接继续发送新的请求。新的请求使用新的链接进行处理
解决方案: 目前 HTTP2 上已经存在 Goaway 帧可以解决这个问题,客户端在收到 Goaway 帧后就会停止想这个链接发送请求。因此 BOLT 协议可以参考 HTTP2 协议,增加一个新的 Goaway Command,客户端在收到这个 Command 之后,会停止向该链接发送新请求,后续请求使用新的链接处理
具体 Goaway Command 的实现和 Heartbeat 类似,对应的 Command Code 为 0x3
public class GoawayCommand extends RequestCommand {
public HeartbeatCommand() {
super(CommonCommandCode.GOAWAY); // 0x3
this.setId(IDGenerator.nextId());
}
}
需要注意的问题: 目前 Command 不支持新增,遇到不认识的 Command 是直接抛出异常断链的,需要考虑兼容性问题。后续可以考虑增加版本协商的功能,以防止出现不识别的 Command 导致断链的场景。协商机制可以考虑参考 HTTP2 建链后交换 Window Size 的操作。
未来可能增加更多控制指令,控制指令的编码序列,可以考虑从100开始
HTTP2 的 Goaway 桢的定义是 0x7,在不冲突的情况下我们是否可以和 http2 标准保持一致?https://datatracker.ietf.org/doc/html/rfc7540#section-6.8
HTTP2 的 Goaway 桢的定义是 0x7,在不冲突的情况下我们是否可以和 http2 标准保持一致?https://datatracker.ietf.org/doc/html/rfc7540#section-6.8
目前已经定义的 Command Code
Heartbeat 0x0 Request 0x1 Response 0x2
顺延的话就是 0x3 ,目前和 0x7 确实是不冲突的,不过感觉上有点奇怪,因为 0x1 和 0x2 的值应该是没有考虑 HTTP2 的
未来可能增加更多控制指令,控制指令的编码序列,可以考虑从100开始
Command Code 是个 short 类型的变量,100 感觉有点大了
@hui-cha h2这儿的实现并不是很完美,nextId()会导致还在请求过程中的请求失败,可以做一些变通,或者看是否需要强校验这个nextId()
http2 的 goaway 协议还是比较复杂的,我们总结了三种优雅断连的协议模式,不知道 bolt 会采用哪种模式呢? 我们建议用 1 或者 2 就足够了
Server 通知 client 主动断连
第 1 种模式,最简单,Server 给 Client 发一个 GoAway 帧,Client 收到之后:
- 新的请求,通过新建连接来发送
- 当前老的连接上,不再发送新的请求
- 并且,之前发送出去的请求,全部处理完毕(收到相应/超时),关闭老的连接
Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接
Server 通知 client 不再发送请求,client 再 ack 通知 Server 断连
第 2 中模式,比模式 1 多了一步
Server 给 Client 发一个 GoAway 帧,Client 收到之后:
- 新的请求,通过新建连接来发送
- 当前老的连接上,不再发送新的请求
- 并且,反馈一个 GoAway 的 ack 帧
- 在所有请求都结束后,client 可以不关闭连接,也可以关闭
Server 收到 client 发的 GoAway ack 帧之后:
- 继续处理已经接受到请求(为了简化逻辑,即使 ack 之后的新请求也处理,不做判断,但是 server 不保证 ack 之后的新请求,会被处理)
- 所有请求处理完之后,主动关闭连接
Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接,即使没有收到 GoAway ack
Server 发送带 laststream id 的 GoAway 帧
也就是 http2 的协议:
server 可以向 client 发送 goaway,带上当前收到的 max stream ID,表示超过这个 ID 的请求会被忽略(这些请求 client 可以安全重试) 此时 client 收到 goaway 之后:
- 不用回 goaway
- 当前老的连接,不再发新请求
- 新的请求,通过新建连接来发送
- 已经发送出去的请求,如果请求 ID 大于 max stream ID,则直接在新连接上重试
- 老连接上的请求结束之后,也不用主动断连
另外,client 也可以向 server 发起 goaway server 收到 client goaway 之后,还是会向 client 发送 goaway,走上面一样的流程
server 处理完请求之后,会主动关闭连接(通常会优雅等一等),client 则不需要主动关闭连接
同时,http2 是全双工的,存在 server push 所以 client 也可以主动发 goaway,server 收到 goaway 之后,也会再给 client 发 goaway,走上面一样的流程。
http2 的 goaway 协议还是比较复杂的,我们总结了三种优雅断连的协议模式,不知道 bolt 会采用哪种模式呢? 我们建议用 1 或者 2 就足够了
Server 通知 client 主动断连
第 1 种模式,最简单,Server 给 Client 发一个 GoAway 帧,Client 收到之后:
- 新的请求,通过新建连接来发送
- 当前老的连接上,不再发送新的请求
- 并且,之前发送出去的请求,全部处理完毕(收到相应/超时),关闭老的连接
Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接
Server 通知 client 不再发送请求,client 再 ack 通知 Server 断连
第 2 中模式,比模式 1 多了一步
Server 给 Client 发一个 GoAway 帧,Client 收到之后:
- 新的请求,通过新建连接来发送
- 当前老的连接上,不再发送新的请求
- 并且,反馈一个 GoAway 的 ack 帧
- 在所有请求都结束后,client 可以不关闭连接,也可以关闭
Server 收到 client 发的 GoAway ack 帧之后:
- 继续处理已经接受到请求(为了简化逻辑,即使 ack 之后的新请求也处理,不做判断,但是 server 不保证 ack 之后的新请求,会被处理)
- 所有请求处理完之后,主动关闭连接
Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接,即使没有收到 GoAway ack
Server 发送带 laststream id 的 GoAway 帧
也就是 http2 的协议:
server 可以向 client 发送 goaway,带上当前收到的 max stream ID,表示超过这个 ID 的请求会被忽略(这些请求 client 可以安全重试) 此时 client 收到 goaway 之后:
- 不用回 goaway
- 当前老的连接,不再发新请求
- 新的请求,通过新建连接来发送
- 已经发送出去的请求,如果请求 ID 大于 max stream ID,则直接在新连接上重试
- 老连接上的请求结束之后,也不用主动断连
另外,client 也可以向 server 发起 goaway server 收到 client goaway 之后,还是会向 client 发送 goaway,走上面一样的流程
server 处理完请求之后,会主动关闭连接(通常会优雅等一等),client 则不需要主动关闭连接
同时,http2 是全双工的,存在 server push 所以 client 也可以主动发 goaway,server 收到 goaway 之后,也会再给 client 发 goaway,走上面一样的流程。
这个问题今天也有聊,我觉得使用 1 就可以了
http2 的 goaway 协议还是比较复杂的,感觉没有必要完全按照其实现。 从问题本身出发,主要是服务器用于连接管理(主动优雅关闭连接)能力,可以按照上面第一种方式实现即可满足要求。 Server 给 Client 发一个 GoAway 帧,Client 收到之后: 1)当前老的连接上,不再发送新的请求 2)并且,之前发送出去的请求,全部处理完毕(收到相应/超时),关闭老的连接
Command Code 建议从100开始,代表控制指令
http2 的 goaway 协议还是比较复杂的,我们总结了三种优雅断连的协议模式,不知道 bolt 会采用哪种模式呢? 我们建议用 1 或者 2 就足够了
Server 通知 client 主动断连
第 1 种模式,最简单,Server 给 Client 发一个 GoAway 帧,Client 收到之后:
- 新的请求,通过新建连接来发送
- 当前老的连接上,不再发送新的请求
- 并且,之前发送出去的请求,全部处理完毕(收到相应/超时),关闭老的连接
Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接
Server 通知 client 不再发送请求,client 再 ack 通知 Server 断连
第 2 中模式,比模式 1 多了一步 Server 给 Client 发一个 GoAway 帧,Client 收到之后:
- 新的请求,通过新建连接来发送
- 当前老的连接上,不再发送新的请求
- 并且,反馈一个 GoAway 的 ack 帧
- 在所有请求都结束后,client 可以不关闭连接,也可以关闭
Server 收到 client 发的 GoAway ack 帧之后:
- 继续处理已经接受到请求(为了简化逻辑,即使 ack 之后的新请求也处理,不做判断,但是 server 不保证 ack 之后的新请求,会被处理)
- 所有请求处理完之后,主动关闭连接
Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接,即使没有收到 GoAway ack
Server 发送带 laststream id 的 GoAway 帧
也就是 http2 的协议: server 可以向 client 发送 goaway,带上当前收到的 max stream ID,表示超过这个 ID 的请求会被忽略(这些请求 client 可以安全重试) 此时 client 收到 goaway 之后:
- 不用回 goaway
- 当前老的连接,不再发新请求
- 新的请求,通过新建连接来发送
- 已经发送出去的请求,如果请求 ID 大于 max stream ID,则直接在新连接上重试
- 老连接上的请求结束之后,也不用主动断连
另外,client 也可以向 server 发起 goaway server 收到 client goaway 之后,还是会向 client 发送 goaway,走上面一样的流程 server 处理完请求之后,会主动关闭连接(通常会优雅等一等),client 则不需要主动关闭连接 同时,http2 是全双工的,存在 server push 所以 client 也可以主动发 goaway,server 收到 goaway 之后,也会再给 client 发 goaway,走上面一样的流程。
这个问题今天也有聊,我觉得使用 1 就可以了
我也建议使用方案1比较好。和我已经写过的一个总结是差不多的:http://note.alphababy.icu/#/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/rpc%E4%BC%98%E9%9B%85%E5%90%AF%E5%81%9C
不过我有一点建议: 在 server push 这种场景下,server 需要等到:向 client 发送的所有请求都处理完成后再向 client 发送 goaway
http2 的 goaway 协议还是比较复杂的,我们总结了三种优雅断连的协议模式,不知道 bolt 会采用哪种模式呢? 我们建议用 1 或者 2 就足够了
Server 通知 client 主动断连
第 1 种模式,最简单,Server 给 Client 发一个 GoAway 帧,Client 收到之后:
- 新的请求,通过新建连接来发送
- 当前老的连接上,不再发送新的请求
- 并且,之前发送出去的请求,全部处理完毕(收到相应/超时),关闭老的连接
Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接
Server 通知 client 不再发送请求,client 再 ack 通知 Server 断连
第 2 中模式,比模式 1 多了一步 Server 给 Client 发一个 GoAway 帧,Client 收到之后:
- 新的请求,通过新建连接来发送
- 当前老的连接上,不再发送新的请求
- 并且,反馈一个 GoAway 的 ack 帧
- 在所有请求都结束后,client 可以不关闭连接,也可以关闭
Server 收到 client 发的 GoAway ack 帧之后:
- 继续处理已经接受到请求(为了简化逻辑,即使 ack 之后的新请求也处理,不做判断,但是 server 不保证 ack 之后的新请求,会被处理)
- 所有请求处理完之后,主动关闭连接
Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接,即使没有收到 GoAway ack
Server 发送带 laststream id 的 GoAway 帧
也就是 http2 的协议: server 可以向 client 发送 goaway,带上当前收到的 max stream ID,表示超过这个 ID 的请求会被忽略(这些请求 client 可以安全重试) 此时 client 收到 goaway 之后:
- 不用回 goaway
- 当前老的连接,不再发新请求
- 新的请求,通过新建连接来发送
- 已经发送出去的请求,如果请求 ID 大于 max stream ID,则直接在新连接上重试
- 老连接上的请求结束之后,也不用主动断连
另外,client 也可以向 server 发起 goaway server 收到 client goaway 之后,还是会向 client 发送 goaway,走上面一样的流程 server 处理完请求之后,会主动关闭连接(通常会优雅等一等),client 则不需要主动关闭连接 同时,http2 是全双工的,存在 server push 所以 client 也可以主动发 goaway,server 收到 goaway 之后,也会再给 client 发 goaway,走上面一样的流程。
这个问题今天也有聊,我觉得使用 1 就可以了
我也建议使用方案1比较好。和我已经写过的一个总结是差不多的:http://note.alphababy.icu/#/%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1/rpc%E4%BC%98%E9%9B%85%E5%90%AF%E5%81%9C
不过我有一点建议: 在 server push 这种场景下,server 需要等到:向 client 发送的所有请求都处理完成后再向 client 发送 goaway
如果server处理完再回复goaway,会不会存在一种可能就是client不知道这个情况,一直发req,导致这个失效了,感觉还是应该主动通知出去。
在 server push 这种场景下,server 需要等到:向 client 发送的所有请求都处理完成后再向 client 发送 goaway
如果server处理完再回复goaway,会不会存在一种可能就是client不知道这个情况,一直发req,导致这个失效了,感觉还是应该主动通知出去。
我上面提到的等 server 处理完毕,指的并不是:server 一直等,等到 server 处理完 client 主动发送的请求。 而是指:server 主动发送给 client 的请求,server 都收到了响应或者 server 收到超时。
server 在发送 goaway 之前是需要释放一些资源的,例如:结束给 client 推送数据;向注册中心发起反注册等等