京东京麦Netty长连接网关架构
京东京麦从 HTTP 网关逐步升级为 TCP 长连接网关,最终基于 Netty 4.x + Protobuf 3.x 构建高可用、高性能、高稳定的接入层。相比 HTTP 网关,TCP 网关性能提升 10 倍以上,稳定性也显著增强。
一、TCP 网关的网络结构
客户端通过域名 + 端口访问 TCP 网关,不同域名对应不同运营商的 VIP。VIP 发布在 LVS 上,LVS 把请求转发给 HAProxy,再由 HAProxy 转发到 Netty 的 IP + Port。
这是典型的高并发接入层标准架构:LVS 做四层转发,HAProxy 做七层转发,Netty 做业务接入。
二、长连接容器架构
Netty 的 Reactor 模型中,每个连接对应一个 Channel,一个 Channel 对应唯一 ChannelPipeline,多个 Handler 串行加入 Pipeline。
TCP 属于传输层,为了给应用层提供稳定服务,京麦在 Netty 之上建立 Session 管理机制:
每次 Session 请求通过 Proxy 机制调用 Service 层,数据返回后写回 Channel。下行推送时,通过 Session Manager 找到 Active Session,再写入对应 ChannelHandlerContext,实现点对点或广播推送。
三、线程模型
TCP 网关使用三组线程池:
| 线程池 | 职责 |
|---|---|
| BossGroup | 接收客户端 TCP 连接 |
| WorkerGroup | 处理 I/O、系统 Task、定时任务 |
| ExecutorGroup | 处理加解密、限流、路由、转发业务 |
Boss Group 作为 Acceptor 接收连接并转发给 Worker Group;Worker Group 负责 Socket 读写;Task Queue 和 Delay Task Queue 负责链路空闲检测、心跳等定时任务。
四、Session 管理源码
Session 创建时会保存 SessionId、创建时间、上次访问时间、Connection 和 SessionListener。
@OverridepublicsynchronizedSessioncreateSession(StringsessionId,ChannelHandlerContextctx){Sessionsession=sessions.get(sessionId);if(session!=null){session.close();}session=newExchangeSession();session.setSessionId(sessionId);session.setValid(true);session.setMaxInactiveInterval(this.getMaxInactiveInterval());session.setCreationTime(System.currentTimeMillis());session.setLastAccessedTime(System.currentTimeMillis());session.setSessionManager(this);session.setConnection(createTcpConnection(session,ctx));for(SessionListenerlistener:essionListeners){session.addSessionListener(listener);}returnsession;}这里要特别注意断线重连 Channel 的处理。如果 Session 已存在就关闭,可能会误关闭同一个 Channel 上的旧连接。解决方式是在 Channel 中存入 SessionId,每次请求判断 Channel 是否已有 SessionId,从而识别是否为断线重连 Channel。
privateStringgetChannelSessionHook(ChannelHandlerContextctx){returnctx.channel().attr(Constants.SERVER_SESSION_HOOK).get();}privatevoidsetChannelSessionHook(ChannelHandlerContextctx,StringsessionId){ctx.channel().attr(Constants.SERVER_SESSION_HOOK).set(sessionId);}五、心跳检测
客户端定期发送心跳,服务端更新 Session 最后访问时间。Session 创建后会添加TcpHeartbeatListener,由守护线程轮询 Session 集合,清理过期连接。
Sessionsession=tcpSessionManager.createSession(wrapper.getSessionId(),ctx);session.addSessionListener(tcpHeartbeatListener);session.connect();tcpSessionManager.addSession(session);publicvoidcheckHeartBeat(){Session[]sessions=tcpSessionManager.getSessions();for(Sessionsession:sessions){if(session.expire()){session.close();logger.info("heart is expire, clear sessionId:"+session.getSessionId());}}}session.connect()会触发所有 Listener 的sessionCreated事件,心跳监听也是在这一过程中被启动。
六、数据上行与下行
数据上行在channelRead中处理。京麦通过消息格式区分是请求还是应答:
publicvoidchannelRead(ChannelHandlerContextctx,Objecto)throwsException{try{if(oinstanceofMessageBuf.JMTransfer){SystemMessagesMsg=generateSystemMessage(ctx);MessageBuf.JMTransfermessage=(MessageBuf.JMTransfer)o;if(message.getFormat()==SEND){MessageWrapperwrapper=proxy.invoke(sMsg,message);if(wrapper!=null){this.receive(ctx,wrapper);}}if(message.getFormat()==REPLY){notify.reply(message);}}else{logger.warn("TcpServerHandler channelRead message is not proto.");}}catch(Exceptione){logger.error("TcpServerHandler handler error.",e);throwe;}}数据下行通过 MQ 广播到所有服务器,每台服务器判断消息目标是否在本地 Session 中。点对点推送也先广播,再由目标服务器判断并下发。
下行通知如果异步方法需要获取返回值,需要把异步转同步。NotifyFuture实现java.util.concurrent.Future,通过超时和 seq 关联响应。
publicvoidreply(MessageBuf.JMTransfermessage)throwsException{try{longseq=message.getSeq();finalNotifyFuturefuture=this.futureMap.get(seq);if(future!=null){future.setSuccess(true);futureMap.remove(seq);}}catch(Exceptione){throwe;}}写数据时不要用cf.await阻塞等待写入结果。它偶发出现BlockingOperationException,原因是 Netty 会检查当前线程是否就是该 Channel 的 EventLoop,如果是则可能判定死锁。正确做法是使用writeAndFlush加ChannelFutureListener。
ChannelFuturecf=cxt.writeAndFlush(message);cf.addListener(newChannelFutureListener(){@OverridepublicvoidoperationComplete(ChannelFuturefuture)throwsPushException{if(future.isSuccess()){logger.debug("send success.");}else{thrownewPushException("Failed to send message.");}Throwablecause=future.cause();if(cause!=null){thrownewPushException(cause);}}});七、总结
京东京麦 TCP 网关的核心不是 Netty API 的简单使用,而是把连接、会话、心跳、上下行通知、异步回调和线程模型组合成一套可运维的长连接系统。Session 管理解决应用层状态,心跳解决连接有效性,Channel 属性解决断线重连,异步 Future 和 EventLoop 约束解决线程安全与死锁。