事件总线核心是稳:生产者零阻塞、消费者不丢事件、上下游解耦、流量可缓冲;关键在选对队列(如linkedblockingqueue限容、synchronousqueue直传)、轻量事件模型、固定线程池消费者、offer快速失败。

用Java阻塞队列构建事件总线,核心不是“堆性能”,而是让生产者不等、消费者不丢、上下游解耦、流量可缓冲。关键在选对队列类型、控制好线程模型、避免锁竞争和内存溢出。
选对阻塞队列:吞吐量与语义必须匹配
不同阻塞队列行为差异极大,不能只看“线程安全”就随便用:
-
高吞吐首选
LinkedBlockingQueue(无界)或SynchronousQueue(纯传递):前者靠链表+双锁分离入队/出队,适合突发流量缓冲;后者零存储、直接移交,适合低延迟、强响应场景(如RPC回调分发),但要求消费者始终就绪,否则生产者会阻塞。 - 慎用
ArrayBlockingQueue:数组结构缓存局部性好,但单锁串行入队/出队,高并发下易成瓶颈;仅当容量严格受限且吞吐适中时考虑。 - 绝对避免无界
LinkedBlockingQueue配合无限生产——内存耗尽只是时间问题。务必设合理容量(如new LinkedBlockingQueue(1024)),并配合拒绝策略。
事件模型要轻量:别把总线变成对象工厂
事件对象本身应是不可变的POJO或record,禁止在事件里携带业务上下文(如Spring Bean、数据库连接、HTTP请求体):
- 只保留必要字段:事件类型(枚举)、唯一ID、时间戳、核心载荷(如订单ID、用户ID、状态码)。
- 大对象(如原始图片、JSON长文本)不塞进事件,改存外部存储URL或ID,由消费者按需加载。
- 用
record Event(String type, long id, Map<string object> payload)</string>替代臃肿的继承体系,减少GC压力和序列化开销。
消费者线程池:固定数 + 拒绝即丢,不重试不堆积
消费者不是越多越好,线程切换和上下文切换成本会吃掉吞吐优势:
- 用
Executors.newFixedThreadPool(n),n 通常设为 CPU 核心数 × 1.5~2(I/O密集型可略高),避免创建过多空闲线程。 - 每个消费者循环调用
queue.take(),处理完再取下一个——不要一次拉多个再for循环,防止单个慢处理拖垮整条流水线。 - 遇到异常事件(如反序列化失败),记录日志后 直接丢弃,不重试、不入死信队列——总线定位是“尽力投递”,可靠性由上层业务保障(如DB落库+定时校验)。
生产者零阻塞:异步写 + 快速失败
业务代码调用 eventBus.publish(event) 必须毫秒级返回:
- 内部调用
queue.offer(event)而非put();若队列满,立即返回false或抛自定义EventRejectedException。 - 拒绝后不重试,也不降级到日志——业务方应自行判断是否需要补偿(如写DB+异步重发),总线不越权。
- 可加一层简单缓冲(如环形数组+CAS),在offer失败前做最后一次尝试,但缓冲区大小必须极小(≤16),避免掩盖背压问题。
这套设计不追求理论极限QPS,而是让系统在流量毛刺、个别消费者卡顿、偶发OOM时仍保持主干可用。事件总线的价值在于稳,不在快。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











