共计 2040 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点:传统轮询方案的性能瓶颈
在高并发场景下,传统的 HTTP 轮询方案暴露出了明显的性能缺陷。当系统面临万人并发时,这些问题尤为突出:

- 连接数爆炸 :每个客户端每隔几秒就发起一次 HTTP 请求,导致服务器需要维持大量 TCP 连接,消耗大量内存和 CPU 资源
- 消息延迟高 :轮询间隔导致消息无法实时推送,用户体验差
- 带宽浪费 :大量空轮询请求占用网络带宽
- 服务端压力大 :每次轮询都需要完整的 HTTP 请求 / 响应流程,增加了服务器处理负担
架构设计:技术选型与分层架构
WebSocket vs SSE vs 长轮询
经过对比分析,我们选择了 WebSocket 作为主要的通信协议:
- WebSocket:全双工通信,连接建立后可以持续通信,适合实时性要求高的场景
- SSE:服务器单向推送,适合服务器向客户端推送数据的场景
- 长轮询 :兼容性好但实时性较差
分层架构设计
采用三层架构设计,各层职责明确:
- 接入层 :负责维护 WebSocket 连接,处理原始消息的接收和发送
- 逻辑层 :处理业务逻辑,消息路由,会话管理等
- 持久层 :负责数据存储和缓存
这种分层设计带来了以下优势:
- 各层可以独立扩展
- 职责分离,代码更易维护
- 可以针对每层特点进行针对性优化
核心实现
基于 Spring Boot 的 WebSocket 端点实现
@Controller
public class AgentWebSocketEndpoint {
@Autowired
private SimpMessagingTemplate messagingTemplate;
/**
* 处理客户端发送的消息
*/
@MessageMapping("/agent/chat")
@SendToUser("/queue/reply") // 发送给指定用户
public ChatMessage handleMessage(@Payload ChatMessage message,
Principal principal) {
// 业务处理逻辑
return processMessage(message, principal.getName());
}
// 其他方法...
}
Redis 分布式会话存储的 Lua 脚本
-- 原子化更新会话状态
local key = KEYS[1]
local timestamp = ARGV[1]
local status = ARGV[2]
local current = redis.call('HGET', key, 'status')
if current and current ~= 'active' then
return 0
end
redis.call('HSET', key, 'status', status)
redis.call('HSET', key, 'last_update', timestamp)
return 1
Kafka 消息分区策略
public class AgentIdPartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes,
Object value, byte[] valueBytes, Cluster cluster) {List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
int numPartitions = partitions.size();
// 根据 agentId 哈希值分配分区
String agentId = ((AgentMessage)key).getAgentId();
return Math.abs(agentId.hashCode()) % numPartitions;
}
// 其他方法...
}
性能优化
Linux 内核参数调优
# 优化 TCP 连接回收
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_max_tw_buckets = 200000
# 增加文件描述符限制
fs.file-max = 1000000
JVM GC 策略选择
经过测试对比 G1 和 ZGC 两种垃圾回收器:
- G1:延迟相对较高 (50-200ms),但内存占用小
- ZGC:延迟极低 (<10ms),但需要更多内存
根据我们的需求,最终选择了 ZGC,因为实时性是我们的首要考虑因素。
避坑指南
1. 心跳包丢失导致的假死连接
解决方案:
- 客户端每 30 秒发送一次心跳包
- 服务端检测到 60 秒无活动后主动断开连接
- 使用 Netty 的 IdleStateHandler 实现
2. 消息幂等性保障
采用雪花算法生成唯一消息 ID:
public class SnowflakeIdGenerator {// 实现略}
3. 灰度发布时的协议版本兼容
- 在握手阶段协商协议版本
- 旧版本客户端使用兼容模式
- 新功能逐步放量
总结与展望
通过这套架构,我们成功将系统吞吐量提升了 300%,同时保证了消息的实时性。未来我们将继续优化系统性能,并探索跨机房会话同步方案。
开放性问题 :如何设计跨机房会话同步方案?需要考虑哪些因素?
正文完
发表至: 技术架构
近一天内
