concurrenthashmap 是存储百万级连接状态的轻量高效方案,纯内存操作、o(1)读写、无锁读、cas写,仅存long型id与纳秒时间戳,批量扫描替代单连接定时器,避免gc风暴。

用 ConcurrentHashMap 存储百万级活跃连接的状态,是轻量、高效且生产验证过的主流做法。它不依赖外部存储,规避了 Redis 网络开销和序列化成本,特别适合单机高吞吐场景下的实时心跳判断与快速上下线处理。
为什么选 ConcurrentHashMap 而不是 Redis 或数据库
Redis 虽然快,但每次心跳更新都要走一次网络请求(哪怕本地部署也有毫秒级延迟),在百万连接每 5–30 秒上报一次心跳的场景下,QPS 可达数万,容易成为瓶颈;数据库更不适合——写放大严重、事务开销大、连接池易打满。而 ConcurrentHashMap 是纯内存操作,put / get 平均时间复杂度 O(1),无锁写入(CAS + 分段扩容),读操作完全无锁,天然适配“高频写+低延迟读”的状态管理需求。
关键设计:只存必要字段,用 long 替代对象
避免为每个连接创建完整对象(如 ConnectionWrapper),直接用连接 ID(long 类型)作为 key,value 存一个精确到纳秒的时间戳:
- key:客户端唯一标识(如 device_id 或 session_id 的 long 哈希值,或直接使用 long 类型 ID)
- value:System.nanoTime() 记录的最后心跳时间(非 System.currentTimeMillis(),防系统时钟跳变)
- 不存 channel、user info 等扩展字段——这些应由其他模块(如 session manager 或 user cache)负责,保持状态 map 职责单一
批量扫描替代逐个定时器:降低 GC 与调度压力
绝不为每个连接 new 一个 HashedWheelTimer Timeout —— 百万级 timeout 对象会迅速引发 GC 风暴和内存溢出。正确做法是:
- 启动一个固定周期的调度器(如 ScheduledExecutorService,间隔 300–500ms)
- 每次执行时调用 map.keySet().toArray() 获取当前 key 快照(避免遍历时修改异常)
- 遍历快照,用 System.nanoTime() - lastNanoTime > timeoutNanos 判断是否超时
- 对超时项统一执行下线逻辑(关闭 Netty Channel、发离线通知、清理关联资源),再 remove(key)
稳定性增强:配合弱引用或软引用缓存通道引用(可选)
若需在状态检查后快速获取对应 Channel(比如要主动踢人),可在 ConcurrentHashMap 中 value 不仅存时间戳,还可封装为轻量结构体:
- 例如:
static final class ActiveEntry { final long lastNano; final WeakReference<channel> channelRef; }</channel> - WeakReference 避免强引用阻碍 Channel GC,同时保证多数情况下能安全取到有效 channel
- 注意:channel 关闭后需主动清理 entry,或依赖扫描时判空后 remove
不复杂但容易忽略。核心就三点:只存最小必要数据、用纳秒时间戳、靠批量扫描代替单连接定时器。










