大家好,这里是小奏 ,觉得文章不错可以关注公众号小奏技术
背景
我们先看看我们的client和server通信的这么一种场景
client和server需要保持长连接。然后无论客户端还是服务端出现异常重启。我们都希望长连接都应该具备重试保活。
HTTP2中是如何实现这种重连机制呢?
答案就是GOAWAY帧。
HTTP2 使用 GOAWAY 帧信号来控制连接关闭。当服务端需要重启的时候,就会给client发送一个 GOAWAY 帧,告诉业务方,我要重启了,你们需要重连了。
client收到GOAWAY帧后,会关闭当前连接,然后重新发起连接。
大致流程如下
大部分的网络通信封装
实际我们如果去看开源项目。很多开源项目client和server无论是私有协议还是GRPC协议对GOAWAY帧的实现都不是很好,或者没有这种处理
RocketMQ中的GOAWAY帧应用
在早期的4.0或者早一点的5.x版本都没有实现类似GOAWAY帧
我们来看看最新的RocketMQ现在是如何实现GOAWAY优雅停机的
- 首先在
ResponseCode新增了一个状态码
java
public static final int GO_AWAY = 1500;
- 如果服务端要进行停机,会给
client发送一个GOAWAY帧
java
protected AtomicBoolean isShuttingDown = new AtomicBoolean(false);
public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) {
// 省略部分代码
if (isShuttingDown.get()) {
if (cmd.getVersion() > MQVersion.Version.V5_3_1.ordinal()) {
final RemotingCommand response = RemotingCommand.createResponseCommand(ResponseCode.GO_AWAY,
"please go away");
response.setOpaque(opaque);
writeResponse(ctx.channel(), cmd, response);
log.info("proxy is shutting down, write response GO_AWAY. channel={}, requestCode={}, opaque={}", ctx.channel(), cmd.getCode(), opaque);
return;
}
}
}
- 客户端如果接收到
GOAWAY帧,会关闭当前连接,然后重新发起连接
- NettyRemotingClient
java
public CompletableFuture<ResponseFuture> invokeImpl(final Channel channel, final RemotingCommand request,
final long timeoutMillis) {
if (response.getCode() == ResponseCode.GO_AWAY) {
ChannelWrapper channelWrapper = channelWrapperTables.computeIfPresent(channel, (channel0, channelWrapper0) -> {
try {
// 重新建立连接
if (channelWrapper0.reconnect(channel0)) {
LOGGER.info("Receive go away from channelId={}, channel={}, recreate the channelId={}", channel0.id(), channel0, channelWrapper0.getChannel().id());
channelWrapperTables.put(channelWrapper0.getChannel(), channelWrapper0);
}
} catch (Throwable t) {
LOGGER.error("Channel {} reconnect error", channelWrapper0, t);
}
return channelWrapper0;
});
}
}
重新建立连接的时候也会对重新建立连接的broker进行心跳发送。
这里主要是对所有的channal进行了封装
- NettyConnectManageHandler
每次出发netty相关的比如channal CLOSE或者ACTIVE等事件
java
public enum NettyEventType {
CONNECT,
CLOSE,
IDLE,
EXCEPTION,
ACTIVE
}
就会触发对应的NettyEvent事件给客户端处理
java
protected final NettyEventExecutor nettyEventExecutor = new NettyEventExecutor();
public void putNettyEvent(final NettyEvent event) {
this.nettyEventExecutor.putNettyEvent(event);
}
public void putNettyEvent(final NettyEvent event) {
int currentSize = this.eventQueue.size();
int maxSize = 10000;
if (currentSize <= maxSize) {
this.eventQueue.add(event);
} else {
log.warn("event queue size [{}] over the limit [{}], so drop this event {}", currentSize, maxSize, event.toString());
}
}
其中client就可以监听各种Netty的事件进行对应的业务逻辑处理,比如监听ACTIVE事件重新发送心跳给broker
java
@Override
public void onChannelActive(String remoteAddr, Channel channel) {
for (Map.Entry<String, HashMap<Long, String>> addressEntry : brokerAddrTable.entrySet()) {
for (Map.Entry<Long, String> entry : addressEntry.getValue().entrySet()) {
String addr = entry.getValue();
if (addr.equals(remoteAddr)) {
long id = entry.getKey();
String brokerName = addressEntry.getKey();
if (sendHeartbeatToBroker(id, brokerName, addr)) {
rebalanceImmediately();
}
break;
}
}
}
}
总结
GOAWAY帧是HTTP2中的一个重要的帧,用来告知client服务端要重启了,client需要重新连接。以此保证服务的可用性
大多数开源项目中的私有协议或者使用GRPC通信的开源中间件都没有很好的去处理GOAWAY帧
如果想要client和server连接的可用性更高,我们可以参考RocketMQ的实现,对GOAWAY帧进行处理,保证服务的可用性