聊聊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帧进行处理,保证服务的可用性

相关推荐
MC丶科3 小时前
【SpringBoot常见报错与解决方案】中文乱码?Spring Boot 统一解决前后端中文乱码问题(含 Postman 测试)!别再百度“加 UTF-8”了!
spring boot·后端·postman
XXOOXRT7 小时前
基于SpringBoot的加法计算器
java·spring boot·后端·html5
moxiaoran57538 小时前
Go语言的错误处理
开发语言·后端·golang
上海云盾安全满满9 小时前
高防IP线路质量重要吗
网络·网络协议·tcp/ip
hoududubaba10 小时前
ORAN共享小区的基本概念
网络·网络协议
tobias.b13 小时前
408真题解析-2009-39-网络-TCP拥塞控制
网络·网络协议·tcp/ip·计算机考研·408考研·408真题解析
数通工程师13 小时前
IPv4和IPv6 地址分配:从划分到工具全解析
网络·网络协议·tcp/ip·华为
短剑重铸之日14 小时前
《7天学会Redis》特别篇: Redis分布式锁
java·redis·分布式·后端·缓存·redission·看门狗机制
小北方城市网14 小时前
SpringBoot 全局异常处理与接口规范实战:打造健壮可维护接口
java·spring boot·redis·后端·python·spring·缓存
hanqunfeng15 小时前
(三十三)Redisson 实战
java·spring boot·后端