Java NIO实时流量统计需在SocketChannel读写路径埋点,为每个Channel绑定AtomicLong计数器作为SelectionKey附件,读写回调中累加字节数,通过CopyOnWriteArrayList维护活跃连接统计器并线程安全聚合查询,严格同步连接生命周期避免泄漏。

Java 用 NIO 统计所有活动连接的实时流量数据,核心在于在 SocketChannel 的读写路径中埋点计数,同时避免锁竞争和精度丢失。关键不是“监听端口”,而是“拦截每个字节进出”。
在 Channel 注册时绑定流量计数器
每个 SocketChannel 对应一个连接,需为其分配独立的原子计数器(如 AtomicLong),并作为附件(attachment)绑定到对应的 SelectionKey 上:
- 调用
channel.register(selector, ops, attachment)时,把封装了inBytes和outBytes的对象传入 - 推荐用轻量级内部类(如
ConnStats),避免每次 new 对象,可配合对象池复用 - 不要用
ConcurrentHashMap按 channel 做全局映射——高并发下查表开销大,且易引发 GC 压力
在 read/write 回调中累加字节数
在 Selector 循环中处理就绪事件时,从 key.attachment() 取出计数器并更新:
-
读操作:调用
channel.read(buffer)后,若返回值n > 0,则stats.inBytes.addAndGet(n) -
写操作:调用
channel.write(buffer)后,若返回值n > 0,则stats.outBytes.addAndGet(n) - 注意:
write()可能只写出部分数据(buffer 未清空),需循环调用直到 buffer 为空或返回 0;每次成功写出都应计入 - 不建议在
OP_READ或OP_WRITE之外统计——NIO 是事件驱动,只有真正触发 I/O 才算真实流量
提供线程安全的实时查询接口
外部系统(如监控 HTTP 接口、定时聚合任务)需要低开销读取当前汇总数据:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
- 维护一个
CopyOnWriteArrayList<ConnStats>存储所有活跃连接的统计器引用(在 channel 注册时 add,关闭时 remove) - 查询总流入/流出:遍历列表,对每个
stats.inBytes.get()和stats.outBytes.get()累加,无需加锁 - 如需每秒速率,可记录上一次查询时间戳与累计值,用差值除以时间间隔(注意避免负值,因
AtomicLong可能被重置或溢出) - 避免高频全量扫描——可按需采样(如每 5 秒聚合一次)或用 RingBuffer 缓存最近 N 秒的增量
注意连接生命周期与资源清理
流量统计必须与连接状态严格同步,否则会出现内存泄漏或统计漂移:
- 在
key.cancel()和channel.close()前,将该连接的ConnStats从列表中移除,并可选地归档其最终值 - 使用
try-with-resources或SelectionKey的attach(null)清理附件,防止 attachment 引用导致 GC 失败 - 警惕半关闭连接(
shutdownInput/Output):仍可能有单向流量,需继续统计对应方向,直到 channel 真正关闭
不复杂但容易忽略。重点是让统计逻辑嵌入在真正的 I/O 路径里,而不是靠轮询或代理包装。只要每个读写调用后立刻记一笔,再配合适当的聚合方式,就能拿到准确、低延迟的实时流量数据。

















