在当前的互联网技术栈中,即时通讯(IM)系统已经成为社交、电商、在线教育、企业协同等核心业务的底层基础设施。然而,自建一套高并发、低延迟、不丢消息的IM系统并非易事。传统的HTTP轮询或短连接方案在实时性和服务器资源消耗上存在天然瓶颈。
源码及演示:im.jstxym.top
基于Java生态与Netty网络框架构建高性能即时通讯系统,已经成为行业主流选择。Netty凭借其异步非阻塞、基于事件驱动、零拷贝等特性,能够轻松支撑十万级长连接。
本文将深入剖析一套开源即时通讯源码的核心架构设计,重点讲解如何利用Java/Netty实现高性能的长连接网关,并深度解析单聊与群聊的业务落地逻辑。

一套企业级的IM系统,绝不能仅仅是一个Netty服务端。为了保证高可用、可扩展以及业务解耦,通常采用微服务架构设计。
网关是IM系统的心脏。Netty的Reactor主从多线程模型是其高性能的核心。

在源码中,ImServerBootstrap类负责初始化Netty。关键代码如下:
public class ImServerBootstrap {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ImChannelInitializer());
ChannelFuture future = bootstrap.bind(8888).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
}
}
}在ImChannelInitializer中,我们装配编解码器、心跳检测器和业务处理器:
public class ImChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
// 空闲状态检测(读/写/读写空闲)
pipeline.addLast(new IdleStateHandler(60, 30, 0, TimeUnit.SECONDS));
// 自定义协议编解码
pipeline.addLast(new ImMessageDecoder());
pipeline.addLast(new ImMessageEncoder());
// 心跳处理器
pipeline.addLast(new HeartbeatHandler());
// 鉴权处理器
pipeline.addLast(new AuthHandler());
// 核心业务处理器
pipeline.addLast(new MessageHandler());
}
}为了兼顾性能和扩展性,源码通常采用二进制私有协议,并配合Protobuf进行序列化。
协议头设计如下:
+--------+--------+--------+--------+--------+--------+
| 魔数(4) | 版本(1) | 指令(1) | 序列化(1)| 长度(4) | 数据体(N) |
使用Protobuf定义单聊和群聊的消息体:
syntax = "proto3";
package com.im.protocol;
message ImMessage {
int64 msgId = 1;
int32 cmd = 2;
int64 senderId = 3;
int64 receiverId = 4;
int64 groupId = 5;
string content = 6;
int64 timestamp = 7;
int32 msgType = 8;
}
Netty提供了IdleStateHandler。如果60秒内未读到数据,触发读空闲:
public class HeartbeatHandler extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.READER_IDLE) {
System.out.println("连接读空闲,关闭通道: " + ctx.channel().id());
ctx.close();
}
}
}
}用户鉴权成功后,将UserId与Channel绑定,并将路由信息写入Redis:
public class ChannelManager {
private static final ConcurrentHashMap<Long, Channel> USER_CHANNELS = new ConcurrentHashMap<>();
public static void bind(long userId, Channel channel) {
USER_CHANNELS.put(userId, channel);
// 同步写入Redis供集群路由查询
RedisClient.set("route:" + userId, getServerNodeId());
}
public static Channel getChannel(long userId) {
return USER_CHANNELS.get(userId);
}
}单聊的核心是精准投递与消息可靠性保证。
客户端发送消息后,服务端需返回ACK确认收到:
public class MessageHandler extends SimpleChannelInboundHandler<ImMessage> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, ImMessage msg) {
if (msg.getCmd() == 1) { // 单聊消息
long receiverId = msg.getReceiverId();
Channel receiverChannel = ChannelManager.getChannel(receiverId);
if (receiverChannel != null && receiverChannel.isActive()) {
receiverChannel.writeAndFlush(msg);
} else {
// 用户离线,存入离线消息队列
OfflineMessageStore.save(msg);
}
// 构建并返回ACK
ImMessage ack = ImMessage.newBuilder()
.setMsgId(msg.getMsgId())
.setCmd(2) // ACK指令
.build();
ctx.writeAndFlush(ack);
// 异步落库
MessageService.asyncSave(msg);
}
}
}群聊面临的核心挑战是“消息扇出”(一台服务器向成百上千个成员发送同一消息)。

为了避免群聊消息阻塞Netty IO线程,源码中通常引入消息队列进行异步扇出:
public void handleGroupMessage(ImMessage msg) {
long groupId = msg.getGroupId();
// 1. 获取群成员列表
List<Long> members = GroupService.getMembers(groupId);
// 2. 将消息推入MQ,由多个消费者并行处理扇出
for (Long memberId : members) {
MqProducer.send("group_msg_topic", msg.toByteArray(), memberId.toString());
}
}public class GroupMessageConsumer {
public void consume(byte[] body) {
ImMessage msg = ImMessage.parseFrom(body);
Channel channel = ChannelManager.getChannel(msg.getReceiverId());
if (channel != null) {
channel.writeAndFlush(msg);
} else {
OfflineMessageStore.save(msg);
}
}
}用户掉线期间,消息不能丢失。
热数据放Redis List(按会话维度):
offline:msg:{userId}:{conversationId}
超阈值后异步落MySQL/MongoDB;保留时间可按业务配置(7天/30天/永久漫游)。
客户端上线:
这样做的好处是不会一次性灌几万条历史把客户端卡死;断连窗口补消息靠“拉”而不是纯“推”,更加稳定。

一套清晰的开源IM源码,应该是多模块Maven工程:
im-system/
├── im-gateway/ # Netty长连接网关
│ ├── AuthHandler # JWT/Token鉴权
│ ├── HeartbeatHandler # IdleState心跳
│ ├── Codec # 私有协议编解码
│ └── ChannelManager # 用户-连接映射
├── im-message-service/ # 消息业务
│ ├── MessageConsumer # MQ消费
│ ├── AckManager # 确认与重推
│ └── OfflineStore # 离线消息
├── im-user-service/ # 用户/好友/群组
├── im-common/ # DTO、枚举、工具、协议包
├── im-admin/ # 管理后台(可选)
└── docs/ # 部署文档、SQL
这种拆法的好处是Netty网关可以独立扩容;业务逻辑挂了不影响连接;消息、用户、群组各自迭代。
PooledByteBufAllocatorSO_REUSEADDR、TCP_NODELAYEpoll传输lastReadMsgId,不逐条ACK拿到这套源码后,常见扩展方向包括:
read_status,客户端上报水位只要协议头留了cmd和version,业务扩展基本不用动传输层。
一套“开源即时通讯源码基于Java/Netty高性能架构”真正值钱的地方,不是界面像不像微信,而是底层这几件事做得扎不扎实:
技术选型可以变(WebSocket或TCP、RabbitMQ或RocketMQ、MySQL或MongoDB),但“实时、不丢、不重、有序”这四条底线不能变。
如果你正在找可二次开发的IM底座,优先看三件事:协议是否清晰、Gateway/Logic是否解耦、ACK与离线拉取是否闭环。这三件事做对了,源码才真的能跑生产,而不只是跑demo。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。