Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

增加 Goaway 指令以支持服务端优雅退出 #278

Open
hui-cha opened this issue Feb 25, 2022 · 11 comments
Open

增加 Goaway 指令以支持服务端优雅退出 #278

hui-cha opened this issue Feb 25, 2022 · 11 comments

Comments

@hui-cha
Copy link

hui-cha commented Feb 25, 2022

问题描述:
我们在跨 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 的操作。

@loull521
Copy link

未来可能增加更多控制指令,控制指令的编码序列,可以考虑从100开始

@JervyShi
Copy link
Member

HTTP2 的 Goaway 桢的定义是 0x7,在不冲突的情况下我们是否可以和 http2 标准保持一致?https://datatracker.ietf.org/doc/html/rfc7540#section-6.8

@hui-cha
Copy link
Author

hui-cha commented Feb 25, 2022

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 的

@hui-cha
Copy link
Author

hui-cha commented Feb 25, 2022

未来可能增加更多控制指令,控制指令的编码序列,可以考虑从100开始

Command Code 是个 short 类型的变量,100 感觉有点大了

@taoyuanyuan
Copy link

@hui-cha h2这儿的实现并不是很完美,nextId()会导致还在请求过程中的请求失败,可以做一些变通,或者看是否需要强校验这个nextId()

@doujiang24
Copy link

http2 的 goaway 协议还是比较复杂的,我们总结了三种优雅断连的协议模式,不知道 bolt 会采用哪种模式呢?
我们建议用 1 或者 2 就足够了

Server 通知 client 主动断连

第 1 种模式,最简单,Server 给 Client 发一个 GoAway 帧,Client 收到之后:

  1. 新的请求,通过新建连接来发送
  2. 当前老的连接上,不再发送新的请求
  3. 并且,之前发送出去的请求,全部处理完毕(收到相应/超时),关闭老的连接

Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接

Server 通知 client 不再发送请求,client 再 ack 通知 Server 断连

第 2 中模式,比模式 1 多了一步

Server 给 Client 发一个 GoAway 帧,Client 收到之后:

  1. 新的请求,通过新建连接来发送
  2. 当前老的连接上,不再发送新的请求
  3. 并且,反馈一个 GoAway 的 ack 帧
  4. 在所有请求都结束后,client 可以不关闭连接,也可以关闭

Server 收到 client 发的 GoAway ack 帧之后:

  1. 继续处理已经接受到请求(为了简化逻辑,即使 ack 之后的新请求也处理,不做判断,但是 server 不保证 ack 之后的新请求,会被处理)
  2. 所有请求处理完之后,主动关闭连接

Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接,即使没有收到 GoAway ack

Server 发送带 laststream id 的 GoAway 帧

也就是 http2 的协议:

server 可以向 client 发送 goaway,带上当前收到的 max stream ID,表示超过这个 ID 的请求会被忽略(这些请求 client 可以安全重试)
此时 client 收到 goaway 之后:

  1. 不用回 goaway
  2. 当前老的连接,不再发新请求
  3. 新的请求,通过新建连接来发送
  4. 已经发送出去的请求,如果请求 ID 大于 max stream ID,则直接在新连接上重试
  5. 老连接上的请求结束之后,也不用主动断连

另外,client 也可以向 server 发起 goaway
server 收到 client goaway 之后,还是会向 client 发送 goaway,走上面一样的流程

server 处理完请求之后,会主动关闭连接(通常会优雅等一等),client 则不需要主动关闭连接

同时,http2 是全双工的,存在 server push
所以 client 也可以主动发 goaway,server 收到 goaway 之后,也会再给 client 发 goaway,走上面一样的流程。

@hui-cha
Copy link
Author

hui-cha commented Feb 25, 2022

http2 的 goaway 协议还是比较复杂的,我们总结了三种优雅断连的协议模式,不知道 bolt 会采用哪种模式呢? 我们建议用 1 或者 2 就足够了

Server 通知 client 主动断连

第 1 种模式,最简单,Server 给 Client 发一个 GoAway 帧,Client 收到之后:

  1. 新的请求,通过新建连接来发送
  2. 当前老的连接上,不再发送新的请求
  3. 并且,之前发送出去的请求,全部处理完毕(收到相应/超时),关闭老的连接

Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接

Server 通知 client 不再发送请求,client 再 ack 通知 Server 断连

第 2 中模式,比模式 1 多了一步

Server 给 Client 发一个 GoAway 帧,Client 收到之后:

  1. 新的请求,通过新建连接来发送
  2. 当前老的连接上,不再发送新的请求
  3. 并且,反馈一个 GoAway 的 ack 帧
  4. 在所有请求都结束后,client 可以不关闭连接,也可以关闭

Server 收到 client 发的 GoAway ack 帧之后:

  1. 继续处理已经接受到请求(为了简化逻辑,即使 ack 之后的新请求也处理,不做判断,但是 server 不保证 ack 之后的新请求,会被处理)
  2. 所有请求处理完之后,主动关闭连接

Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接,即使没有收到 GoAway ack

Server 发送带 laststream id 的 GoAway 帧

也就是 http2 的协议:

server 可以向 client 发送 goaway,带上当前收到的 max stream ID,表示超过这个 ID 的请求会被忽略(这些请求 client 可以安全重试) 此时 client 收到 goaway 之后:

  1. 不用回 goaway
  2. 当前老的连接,不再发新请求
  3. 新的请求,通过新建连接来发送
  4. 已经发送出去的请求,如果请求 ID 大于 max stream ID,则直接在新连接上重试
  5. 老连接上的请求结束之后,也不用主动断连

另外,client 也可以向 server 发起 goaway server 收到 client goaway 之后,还是会向 client 发送 goaway,走上面一样的流程

server 处理完请求之后,会主动关闭连接(通常会优雅等一等),client 则不需要主动关闭连接

同时,http2 是全双工的,存在 server push 所以 client 也可以主动发 goaway,server 收到 goaway 之后,也会再给 client 发 goaway,走上面一样的流程。

这个问题今天也有聊,我觉得使用 1 就可以了

@chuailiwu
Copy link
Collaborator

http2 的 goaway 协议还是比较复杂的,感觉没有必要完全按照其实现。
从问题本身出发,主要是服务器用于连接管理(主动优雅关闭连接)能力,可以按照上面第一种方式实现即可满足要求。
Server 给 Client 发一个 GoAway 帧,Client 收到之后:
1)当前老的连接上,不再发送新的请求
2)并且,之前发送出去的请求,全部处理完毕(收到相应/超时),关闭老的连接

Command Code 建议从100开始,代表控制指令

@alpha-baby
Copy link
Contributor

http2 的 goaway 协议还是比较复杂的,我们总结了三种优雅断连的协议模式,不知道 bolt 会采用哪种模式呢? 我们建议用 1 或者 2 就足够了

Server 通知 client 主动断连

第 1 种模式,最简单,Server 给 Client 发一个 GoAway 帧,Client 收到之后:

  1. 新的请求,通过新建连接来发送
  2. 当前老的连接上,不再发送新的请求
  3. 并且,之前发送出去的请求,全部处理完毕(收到相应/超时),关闭老的连接

Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接

Server 通知 client 不再发送请求,client 再 ack 通知 Server 断连

第 2 中模式,比模式 1 多了一步
Server 给 Client 发一个 GoAway 帧,Client 收到之后:

  1. 新的请求,通过新建连接来发送
  2. 当前老的连接上,不再发送新的请求
  3. 并且,反馈一个 GoAway 的 ack 帧
  4. 在所有请求都结束后,client 可以不关闭连接,也可以关闭

Server 收到 client 发的 GoAway ack 帧之后:

  1. 继续处理已经接受到请求(为了简化逻辑,即使 ack 之后的新请求也处理,不做判断,但是 server 不保证 ack 之后的新请求,会被处理)
  2. 所有请求处理完之后,主动关闭连接

Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接,即使没有收到 GoAway ack

Server 发送带 laststream id 的 GoAway 帧

也就是 http2 的协议:
server 可以向 client 发送 goaway,带上当前收到的 max stream ID,表示超过这个 ID 的请求会被忽略(这些请求 client 可以安全重试) 此时 client 收到 goaway 之后:

  1. 不用回 goaway
  2. 当前老的连接,不再发新请求
  3. 新的请求,通过新建连接来发送
  4. 已经发送出去的请求,如果请求 ID 大于 max stream ID,则直接在新连接上重试
  5. 老连接上的请求结束之后,也不用主动断连

另外,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

@nejisama
Copy link

http2 的 goaway 协议还是比较复杂的,我们总结了三种优雅断连的协议模式,不知道 bolt 会采用哪种模式呢? 我们建议用 1 或者 2 就足够了

Server 通知 client 主动断连

第 1 种模式,最简单,Server 给 Client 发一个 GoAway 帧,Client 收到之后:

  1. 新的请求,通过新建连接来发送
  2. 当前老的连接上,不再发送新的请求
  3. 并且,之前发送出去的请求,全部处理完毕(收到相应/超时),关闭老的连接

Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接

Server 通知 client 不再发送请求,client 再 ack 通知 Server 断连

第 2 中模式,比模式 1 多了一步
Server 给 Client 发一个 GoAway 帧,Client 收到之后:

  1. 新的请求,通过新建连接来发送
  2. 当前老的连接上,不再发送新的请求
  3. 并且,反馈一个 GoAway 的 ack 帧
  4. 在所有请求都结束后,client 可以不关闭连接,也可以关闭

Server 收到 client 发的 GoAway ack 帧之后:

  1. 继续处理已经接受到请求(为了简化逻辑,即使 ack 之后的新请求也处理,不做判断,但是 server 不保证 ack 之后的新请求,会被处理)
  2. 所有请求处理完之后,主动关闭连接

Server 发完 GoAway 之后,有一个最大等待时间,比如 60s 之后关闭连接,即使没有收到 GoAway ack

Server 发送带 laststream id 的 GoAway 帧

也就是 http2 的协议:
server 可以向 client 发送 goaway,带上当前收到的 max stream ID,表示超过这个 ID 的请求会被忽略(这些请求 client 可以安全重试) 此时 client 收到 goaway 之后:

  1. 不用回 goaway
  2. 当前老的连接,不再发新请求
  3. 新的请求,通过新建连接来发送
  4. 已经发送出去的请求,如果请求 ID 大于 max stream ID,则直接在新连接上重试
  5. 老连接上的请求结束之后,也不用主动断连

另外,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,导致这个失效了,感觉还是应该主动通知出去。

@alpha-baby
Copy link
Contributor

在 server push 这种场景下,server 需要等到:向 client 发送的所有请求都处理完成后再向 client 发送 goaway

如果server处理完再回复goaway,会不会存在一种可能就是client不知道这个情况,一直发req,导致这个失效了,感觉还是应该主动通知出去。

我上面提到的等 server 处理完毕,指的并不是:server 一直等,等到 server 处理完 client 主动发送的请求。
而是指:server 主动发送给 client 的请求,server 都收到了响应或者 server 收到超时。

server 在发送 goaway 之前是需要释放一些资源的,例如:结束给 client 推送数据;向注册中心发起反注册等等

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants