聊聊HTTP2中的GOAWAY帧以及RocketMQ对GOAWAY的实现

大家好,这里是小奏 ,觉得文章不错可以关注公众号小奏技术

背景

我们先看看我们的clientserver通信的这么一种场景

clientserver需要保持长连接。然后无论客户端还是服务端出现异常重启。我们都希望长连接都应该具备重试保活

HTTP2中是如何实现这种重连机制呢?

答案就是GOAWAY帧。

HTTP2 使用 GOAWAY 帧信号来控制连接关闭。当服务端需要重启的时候,就会给client发送一个 GOAWAY 帧,告诉业务方,我要重启了,你们需要重连了。

client收到GOAWAY帧后,会关闭当前连接,然后重新发起连接。

大致流程如下

大部分的网络通信封装

实际我们如果去看开源项目。很多开源项目clientserver无论是私有协议还是GRPC协议对GOAWAY帧的实现都不是很好,或者没有这种处理

RocketMQ中的GOAWAY帧应用

在早期的4.0或者早一点的5.x版本都没有实现类似GOAWAY

我们来看看最新的RocketMQ现在是如何实现GOAWAY优雅停机的

  1. 首先在ResponseCode新增了一个状态码
java 复制代码
public static final int GO_AWAY = 1500;
  1. 如果服务端要进行停机,会给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;
        }
    }
    
}
  1. 客户端如果接收到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

如果想要clientserver连接的可用性更高,我们可以参考RocketMQ的实现,对GOAWAY帧进行处理,保证服务的可用性

相关推荐
Danileaf_Guo5 分钟前
常用VPN性能对比测试(IPsec、L2TP VPN、SSL VPN、L2TP over IPsec等)
网络·网络协议·ssl
奉常席梦雨6 分钟前
聊天室项目之http知识
网络·网络协议·http
登登登__8 分钟前
Dubbo、HTTP、RMI之间的区别
网络协议·http·dubbo
扫地僧0099 分钟前
【中大厂面试题】腾讯 后端 校招 最新面试题
java·数据结构·后端·算法·面试·排序算法
东方醴歌2 小时前
VMware安装飞牛私有云fnOS并挂载小雅Alist实现异地远程访问
开发语言·后端·golang
她说彩礼65万2 小时前
Asp.NET Core WebApi 创建带鉴权机制的Api
后端·asp.net
bobz9652 小时前
qemu ovs tap down
后端
游戏开发爱好者82 小时前
使用克魔助手查看iOS 应用程序使用历史记录和耗能历史记录
websocket·网络协议·tcp/ip·http·网络安全·https·udp
uhakadotcom2 小时前
简单理解 x402 支付协议
后端·架构·github
shepherd1262 小时前
从零搭建高可用Kafka集群与EFAK监控平台:全流程实战总结
分布式·后端·kafka