1. 项目概述与核心场景
先聊一个现象:在很多技术分享里,动不动就看到“单机百万 TPS”“千万级并发”之类的数字。这些数字有多少能落到生产环境,其实要打个问号。尤其在压测场景里,TPS 虚高是个绕不开的话题——被压测框架优化掉的开销、没被消费的队列、预热不足的 JIT、GC 参数恰好命中的巧合,都会把数字抬得很漂亮。但这并不意味着高性能编程这条路线本身有问题。真正的问题是:我们是否理解了这些高性能组件背后到底做了什么,是否知道数字是怎么测出来的,以及它能不能在你的真实业务场景里复现。
Disruptor 就是这样一个经常和“吓人数字”绑在一起的组件。它是英国外汇交易公司 LMAX 开源的高性能无锁队列框架,论文里公开的测试数据是单机每秒处理 600 万订单,国内技术社区流传较广的说法是“单机支撑 200 万 TPS”。它解决的问题非常朴素:在 Java 这种有 GC、有锁、有内存屏障的平台上,如何把跨线程数据传递的延迟压到极低,把吞吐量推到接近硬件上限。200 万 TPS 这个数字并不过分,但它成立的前提是:单生产者或少数生产者、单消费者或少数消费者、合理配置等待策略、避免伪共享、充分预热 JIT,并且消费逻辑本身足够轻量。
这篇文章我想从原理讲到实战,把环形队列(RingBuffer)的核心设计拆开,给出可直接参考的代码写法,最后再认真聊一聊压测方法论——也就是怎么让“200 万 TPS”这个数字经得起复现,为什么很多人的压测结果虚高,以及怎样才能测出接近真实业务的表现。适合的人群是:被高并发队列性能困扰的后端开发者、准备做中间件或网关类系统的工程师,以及所有想真正理解无锁编程的人。即使你第一次听说 Disruptor,我也会从最基础的概念讲起。
2. Disruptor 的核心设计思路
2.1 为什么选择环形队列:预分配与零 GC 的博弈
普通的有界队列(比如 ArrayBlockingQueue)在高并发下会遇到两个问题:锁竞争和对象分配。锁竞争很好理解:多个生产者入队、多个消费者出队,必然争抢同一把锁。对象分配则容易被忽略——在 Java 里往队列里塞对象,通常意味着不断 new 新对象,而高吞吐场景下对象的创建和回收会带来巨大的 GC 压力。
Disruptor 的思路是反过来的。它把存储结构设计成环形数组,数组在启动时就一次性创建好,后续复用的是固定数量的对象槽位。生产者写入时并不是创建新对象,而是从队尾找到下一个可用槽位,把数据填充进去;消费者读取时从队首取出槽位里的数据。整个过程没有对象的创建和销毁,GC 压力被压到最低。环形数组的另一个好处是:数组本身在内存中是连续的一段空间,CPU 缓存行命中率远高于链表结构。链表节点分散在堆的不同位置,访问时频繁触发缓存未命中,而数组可以做到顺序预读。
这里有一个很容易忽略的细节:环形队列的“满”和“空”是怎么判断的。Disruptor 采用序号(Sequence)机制,每个生产者和消费者都持有自己的序号。生产者写入前检查目标槽位是否已经被消费者消费,消费者读取前检查目标槽位是否已经写入。因为序号是单调递增的,所以判空判满只需要做两次简单的数值比较,不需要加锁。
2.2 无锁并发:Sequence 与内存屏障的配合
无锁并发最核心的问题是“如何安全地发布数据”。在 Disruptor 里,这个任务交给了 Sequence 类。它本质上是 AtomicLong 的增强版,内部使用 Unsafe 提供的 CAS 操作来保证序号更新的原子性。但这还不够——CAS 只能保证“写入不冲突”,不能保证“其他线程看到最新值”。
这里需要引入内存屏障的概念。简单理解:CPU 为了优化指令执行顺序,可能会对读操作和写操作进行重排;多核 CPU 下的各个核心又有各自独立的缓存,一个核心修改了数据,另一个核心未必立刻可见。JMM(Java 内存模型)要求:volatile 变量的写操作立刻刷新到主内存,读操作从主内存读取最新值。Sequence 内部把 value 声明为 volatile,并用lazySet来减少不必要的内存屏障开销。lazySet不允许写操作重排到其他写操作之后,但不强制立即刷新到主内存,这在“只有一个线程写、多个线程读”的场景下性能极佳。
Disruptor 的另一个关键点是“生产者—消费者之间的依赖关系”。比如 A 消费者处理完数据后,B 消费者才能处理同一条数据,这本质上是定义了一个消费依赖图。消费者会持有“依赖的 Sequence”,在读取新数据之前先确认依赖方已经消费到了哪个位置。这个过程不需要锁,因为读的都是 volatile 序号,写序号用的是 CAS。这也是 Disruptor 能把延迟打到微秒级的最重要原因之一。
2.3 伪共享(False Sharing)与缓存行填充
这是一个性能杀手,却经常被人忽略。CPU 的 L1/L2 缓存以缓存行(通常是 64 字节)为单位加载数据。如果两个不同的数据恰好落在同一个缓存行里,即使它们之间没有逻辑关系,一个核心修改了其中一个数据,另一个核心里的整行缓存都会失效,被迫从主内存重新加载。这叫做伪共享。
Disruptor 在设计上也做了防范。最典型的就是:在多生产者场景下,多个生产者线程各自持有独立的序号,如果这些序号恰好分布在同一个缓存行中,就会产生严重的伪共享竞争。Disruptor 的解决方法是在序号对象内部填充protected long p1, p2, p3...这类无业务意义的字段,把缓存行占满。经典写法是:
class Sequence { private volatile long value; // 缓存行填充 protected long p1, p2, p3, p4, p5, p6, p7; private static final Unsafe UNSAFE = Unsafe.getUnsafe(); }这样设计之后,每个线程需要高频读写的序号,在内存中与相邻数据隔离,不会被意外共享同一缓存行。你在使用 Disruptor 时不太需要自己去写这些填充代码,但理解这一点,对后面做性能调优很有帮助——比如当你自定义的消费者 Handler 里写了某些会被多个线程共享的字段时,可能就需要考虑缓存行的影响。
3. 核心组件与运作机制详细解析
3.1 从 3.x 到 4.x:版本选择与 API 变化
在开始写代码之前,先明确一件容易踩坑的事情:Disruptor 的 API 在 4.x 版本做了大幅重构。如果你在网上搜到大量基于 3.x 的教程,比如RingBuffer上直接调getCursor()、用EventHandler来写消费者,那这些代码在 4.x 里很可能编不过。当前还在广泛使用的 3.4.x 版本依然稳定可靠,大量开源项目(包括一些中间件)仍在用它。4.x 的主要变化是把“生产者”和“消费者”的职责进一步拆成了 ProducerGate 和 ConsumerGate,并引入了 SeqNo、Cursor、Boundary 等新抽象,同时支持多 RingBuffer 的有序连接。
对于初学者我建议用 3.4.x 上手,因为资料多、社区讨论充分,踩坑时容易找到答案。当你理解了序号、栅栏、依赖这些概念之后,再看 4.x 的 Gate 抽象,会顺畅很多。本文的实战部分以 3.4.x 为例,最后我会单独用一节来梳理 4.x 的关键区别。
3.2 核心概念逐个拆解
RingBuffer:存储事件数据的环形数组核心结构。初始化时需要事件工厂(用于预创建对象)、队列容量(必须是 2 的幂次方)和生产者类型(单生产者还是多生产者)。Sequence:单调递增的序号。生产者和消费者都持有自己的序号,用来表示当前处理到的位置。Sequencer:序号分配器。它是生产端的核心,负责协调多个生产者安全地申请槽位。分为单生产者序号分配器SingleProducerSequencer和多生产者序号分配器MultiProducerSequencer,两种实现锁策略不同,性能也不同。WaitStrategy:消费者等待策略。当消费者发现队列里没有新数据时,怎么等待?有BlockingWaitStrategy(锁 + Condition)、BusySpinWaitStrategy(死循环旋转)、SleepingWaitStrategy(自旋 + 睡眠)等多种选择。EventHandler:事件处理器接口,消费者收到数据后回调,业务逻辑写在这里面。WorkHandler:工作处理器接口。用于把消息分发给多个竞争消费者,同一条消息只会被其中一个消费者处理。ExceptionHandler:异常处理器。消费者抛异常时的兜底逻辑。
3.3 生产者、消费者与依赖关系的运作流程
一个典型的 Disruptor 处理流程是这样的:生产者调用RingBuffer.next()申请下一个可用槽位,获得序号后写入事件数据,再调用publish(sequence)发布。消费者线程在后台监听,拿到可读区间的序号后,把批量数据交给 EventHandler 处理。如果你配置了多个消费者,并且它们之间有前后依赖关系,比如“先解密再写库”,就需要手动声明依赖序列,让后面的消费者等待前面的消费者处理完同一条数据后再开始。
我举个实际场景:一个网关系统,收到请求后要先做鉴权、再转发到后台服务、最后记录请求日志。这三个环节有先后关系。用 Disruptor 实现时,可以创建三个 EventHandler,分别设置依赖:鉴权消费者处理完的序号,转发消费者才能开始;转发消费者处理完的序号,日志消费者才能开始。这样一个流水线就搭出来了,整条链路的吞吐取决于最慢的环节。
3.4 等待策略的取舍:从忙等到阻塞的谱系
等待策略的选择直接决定 CPU 占用和延迟的平衡,这里展开说说。
BlockingWaitStrategy是默认策略,内部用了锁和 Condition,最保守、延迟最高,但在 CPU 资源紧张时不会空转。BusySpinWaitStrategy是性能最高的策略,消费者线程用循环不断检查序号是否可用,不释放 CPU,在单核或多核机器上(以及消费者个数不超过 CPU 核数时)能压出最低延迟,但会让 CPU 占满。SleepingWaitStrategy介于两者之间:一开始自旋,如果自旋一段时间还没有新数据,就让出 CPU 并 sleep 一个短时间,对 CPU 友好,适合延迟要求不那么极端但吞吐要求高的场景。YieldingWaitStrategy在自旋的同时调用Thread.yield(),也是折中方案。
我的经验是:如果你的系统是中间件,接受一定 CPU 开销来换最低延迟,选BusySpinWaitStrategy并确保消费者的线程绑定到独立 CPU 核心;如果跑在共享的云服务器上,选SleepingWaitStrategy或YieldingWaitStrategy更稳妥。
4. 实战:从单生产者到多消费者依赖链
4.1 环境准备与依赖引入
创建一个普通的 Maven 工程,引入 Disruptor 依赖:
<dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.4.4</version> </dependency>JDK 版本建议 8 以上。乌班图或 CentOS 上的 CPU 核数越多越好,因为后面的压测需要足够多的核心来跑消费者线程和生产者线程,避免它们互相争抢。
4.2 定义事件对象与事件工厂
假设我们要处理一个简单的订单事件,包含订单号和金额:
public class OrderEvent { private String orderId; private double amount; public String getOrderId() { return orderId; } public void setOrderId(String orderId) { this.orderId = orderId; } public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } }事件工厂的作用是:在 RingBuffer 初始化时一次性把 N 个OrderEvent对象创建好,后续只是复用它们:
public class OrderEventFactory implements EventFactory<OrderEvent> { @Override public OrderEvent newInstance() { return new OrderEvent(); } }为什么要工厂?因为 Disruptor 的核心“零 GC”策略,就是靠预分配对象槽位实现的。如果谁用了默认构造器在发布时才创建事件对象,那 GC 压力立刻回来,性能会大打折扣。这一点几乎是我见过最多的错误用法。
4.3 单生产者-单消费者最小案例
先写一个最简版本,让你立刻能跑起来并理解全流程:
public class DisruptorDemo { public static void main(String[] args) throws InterruptedException { int bufferSize = 1024 * 8; Disruptor<OrderEvent> disruptor = new Disruptor<>( new OrderEventFactory(), bufferSize, Executors.defaultThreadFactory(), ProducerType.SINGLE, new BusySpinWaitStrategy() ); disruptor.handleEventsWith(new OrderEventHandler()); disruptor.start(); RingBuffer<OrderEvent> ringBuffer = disruptor.getRingBuffer(); for (long i = 1; i <= 1000; i++) { long sequence = ringBuffer.next(); try { OrderEvent event = ringBuffer.get(sequence); event.setOrderId("ORDER_" + i); event.setAmount(100.0 + i); } finally { ringBuffer.publish(sequence); } } Thread.sleep(2000); disruptor.shutdown(); } }事件的处理器:
public class OrderEventHandler implements EventHandler<OrderEvent> { @Override public void onEvent(OrderEvent event, long sequence, boolean endOfBatch) throws Exception { System.out.println("消费订单: " + event.getOrderId() + ", 金额: " + event.getAmount()); } }注意finally里publish这一步非常关键。如果业务写入抛了异常,序号也不会被卡住,不会导致整个 RingBuffer 永久无法推进。这个习惯必须养好。
4.4 多生产者-多消费者且带依赖链的完整写法
下面这个例子更贴近真实业务:订单消息需要进行“校验 → 持久化 → 通知”三个阶段的依次处理。这里用多个 Handler 并通过then串联依赖:
public class PipelineDemo { public static void main(String[] args) throws InterruptedException { int bufferSize = 1024 * 16; Disruptor<OrderEvent> disruptor = new Disruptor<>( new OrderEventFactory(), bufferSize, Executors.defaultThreadFactory(), ProducerType.MULTI, new YieldingWaitStrategy() ); // 校验、持久化、通知三个处理器按顺序执行 disruptor.handleEventsWith(new ValidateHandler()) .then(new PersistHandler()) .then(new NotifyHandler()); disruptor.start(); // 模拟 4 个生产者线程并发发布订单 RingBuffer<OrderEvent> ringBuffer = disruptor.getRingBuffer(); ExecutorService producers = Executors.newFixedThreadPool(4); for (int t = 0; t < 4; t++) { final int threadId = t; producers.submit(() -> { for (long i = 0; i < 500000; i++) { long sequence = ringBuffer.next(); try { OrderEvent event = ringBuffer.get(sequence); event.setOrderId("T" + threadId + "_" + i); event.setAmount(Math.random() * 1000); } finally { ringBuffer.publish(sequence); } } }); } Thread.sleep(10000); disruptor.shutdown(); producers.shutdown(); } }三个 Handler 的写法类似,以校验为例:
public class ValidateHandler implements EventHandler<OrderEvent> { @Override public void onEvent(OrderEvent event, long sequence, boolean endOfBatch) throws Exception { if (event.getAmount() < 0) { throw new IllegalArgumentException("订单金额非法: " + event.getAmount()); } // 模拟校验耗时 Thread.sleep(1); } }在带依赖链的场景里,你要注意一个关键点:后一个 Handler 不会等前一个 Handler 完全处理完才启动。Disruptor 的依赖机制保证了:如果持久化处理器正在处理第 10 条数据,通知处理器最多只能处理到第 10 条之前的某一条,具体推进到哪里取决于依赖方序号的最小值。换句话说,它不会跳过未处理的数据,但会在依赖边界上等待。这种方式比“全量同步”高效得多,整个流水线的吞吐由最慢的 Handler 决定。
4.5 多消费者竞争模式:WorkHandler
如果你需要“多个消费者共同分担消息”,比如订单消息有 2 个消费者线程同时在消费,同一条消息只给其中一个,那就不能用 EventHandler + then 了,要用 WorkHandler:
public class OrderWorkHandler implements WorkHandler<OrderEvent> { @Override public void onEvent(OrderEvent event) throws Exception { System.out.println(Thread.currentThread().getName() + " 处理: " + event.getOrderId()); } } disruptor.handleEventsWithWorkerPool(new OrderWorkHandler(), new OrderWorkHandler());WorkPool 的语义是“广播任务分片”,适合并行消费场景。它和 EventHandler 的区别在于:EventHandler 模式下每个消费者都会收到同一条事件;WorkHandler 模式下同一条事件只会被一个消费者收到。这个区别非常重要,很多人在引入 Disruptor 时把这两者搞混,导致数据重复处理。
5. 压测方法论与 TPS 虚高的真相
5.1 拆解 TPS 虚高的常见来源
Disruptor 的性能数字那么高,为什么你自己怎么压都压不出来?先盘一下“虚高”有哪些来源。
第一,系统预热不足。JIT 编译需要时间,如果刚启动就直接压测,客户端测到的 TPS 从一开始就偏低,但服务端日志统计里可能只记录了后半段的峰值,导致“报告上的 TPS”远高于整个压测周期的实际表现。反过来,另一个极端是:很多人不会正确克隆对象、不会做足够的预热,导致报告数字忽高忽低。
第二,批量消费被统计成单条处理。Disruptor 的 EventHandler 里有endOfBatch参数,它标识当前这批次是否是批量中的最后一条。如果消费者每次从队列一次拉出多条数据,并且只统计一个“处理完成”信号,那么报告里的 TPS 可能是实际业务处理量的几倍。这不是 Disruptor 的问题,而是统计逻辑的问题。
第三,业务处理被移出压测链路。常见的做法是:只测队列的读写,不测业务逻辑;或者消费者只消费并丢弃数据,不执行实际逻辑。这样的压测结果只能证明“队列本身”快,无法证明“你的系统”快。网上很多 200 万 TPS 的测试,很多是基于空操作消费者——消息发布后立刻丢弃,不做任何运算、不写日志、不加持久化。这种数字看看就好。
第四,GC 偶然性。如果压测期间恰好没有发生 GC(比如内存足够大、对象没有频繁创建),那么这个阶段测出的数字就会很高。但生产环境不可能长期不 GC。要评估真实性能,应该完整记录压测期间的 GC 日志,看整个压测周期内的吞吐曲线是否平稳。
5.2 走高可信度 TPS 的压测方法
想测出一个能经得起质疑的 TPS,我建议按这个方法来。
- 预热:先跑 30 秒以上的预热流量,让 JIT 充分编译热点代码。
- 验证消费逻辑:消费者 Handler 里必须包含至少一次内存写或最终态更新,不能只是空函数。
- 统计周期完整:从压测启动到结束完整统计,不要截取最高峰片段。
- 打印 GC 日志:
-Xlog:gc:/tmp/gc.log(JDK 9+),压测结束后查看是否有大量 GC,如果频发的 GC 中断,说明类似业务高峰下可能还会更差。 - 控制并发变量:生产者线程数、消费者线程数、RingBuffer 容量、等待策略每次只改一个变量,不要同时调多个。
我实测过的参考数据:在 8 核心 16G 机器上,用单生产者多消费者、忙轮询策略、消费者做简单的内存累加,大致能得到 300 多万条/秒的下发吞吐。但一旦消费者里加了网络 RPC 或磁盘 IO,这个数字会骤降到 10 万以内。这说明,在真实业务里,瓶颈永远是业务逻辑本身,而不是队列。
5.3 一个压测 Demo:附带消费者统计
为了避免“消费者丢弃消息”的争议,这里给一个统计型的消费者 Demo,压测时可以作为参考模板。它能统计每秒完成消费的事件数量,并可以定期打印,这样报告的 TPS 有据可查、可复现。
public class StatsEventHandler implements EventHandler<OrderEvent> { private long count = 0; private long lastPrintTime = System.currentTimeMillis(); private long lastCount = 0; @Override public void onEvent(OrderEvent event, long sequence, boolean endOfBatch) { count++; // 模拟实际业务开销,time 单位为纳秒 long start = System.nanoTime(); long total = 0; for (int i = 0; i < 50; i++) { total += (i * event.getAmount()); } event.setAmount(event.getAmount() + total % 1000); long cost = System.nanoTime() - start; // 这里将耗时累加到某个变量,避免 JIT 优化掉整个计算 if (System.currentTimeMillis() - lastPrintTime >= 1000) { long currentCount = count; long currentTime = System.currentTimeMillis(); long tps = (currentCount - lastCount) * 1000 / (currentTime - lastPrintTime); System.out.println("TPS: " + tps); lastCount = currentCount; lastPrintTime = currentTime; } } }这段代码里我特意做了一次“无意义的纯循环+”运算,目的是模拟业务开销,防止 JIT 将整个过程优化成空操作。这个细节很多压测报告不提,但它导致了数字的巨大差异——有空操作和无空操作的吞吐可能相差 10 倍以上。
6. 常见问题与排查技巧实录
从 3.x 迁移到 4.x、从阻塞队列换到 Disruptor、到真实业务上线,这几个阶段每个我都踩过一些坑,整理如下。
6.1 RingBuffer 容量必须为 2 的幂次方吗
是的,源码强制要求。原因是:Disruptor 使用二进制位运算index = sequence & (bufferSize - 1)来计算数组下标。只有容量是 2 的幂次方,bufferSize - 1的二进制低位才是全 1,位运算才能等价于取模。如果你传了非 2 的幂次方,启动时会直接抛出异常。
提示:如果业务中需要很大的容量,比如 32K 或 64K,因为每个槽位都需要预创建对象,容量越大初始内存占用越高。你的事件对象如果很重(包含大数组),建议估算一下总内存再定容量。
6.2 消费者处理速度慢导致生产者阻塞变严重
生产者在next()申请序号时,如果队列满了,会顺着 Sequencer 的等待策略自旋等待。如果消费者处理慢,生产者自旋时间长,CPU 占用飙升。这不是死锁,是背压(backpressure)机制在起作用。解决思路是:优化消费者逻辑、增加消费者线程数(WorkHandler 模式)、或者扩大 RingBuffer 容量。但要注意,扩大容量并不能提高吞吐,只是让生产者等待更少、消费者可以缓冲更多数据而已。
6.3 消息丢失风险其实很低,但有一个前提
Disruptor 宕机或进程崩溃时,如果事件还没被消费者落库,那这些数据确实是会丢的。它本质上是一个内存队列,没有持久化机制。要保证不丢消息,需要消费者在收到数据后尽快写入持久化存储,并采用 ACK 机制。这也是为什么在金融、交易这种强一致场景,Disruptor 通常只作为内存加速层,后端还要挂消息中间件或数据库来做持久化。
6.4 多生产者并发发布如何保证顺序
如果业务要求严格的全局顺序,比如数据库 binlog 的有序回放,就不能用多生产者,必须用单生产者模式。多生产者并发发布时,序号分配是原子的,但是不同生产者进入的顺序和发布的顺序并不完全一致——线程 A 先申请了序号 1,可能因为被线程调度暂停,线程 B 随后申请了序号 2 并先发布了。如果全局顺序是硬要求,请使用ProducerType.SINGLE,并且业务上自己保证在同一生产线程内顺序提交。
6.5 ErrorHandler 没配置导致消费者静默失败
EventHandler 里的异常如果不做处理,会传到 Disruptor 设的异常处理器。如果没有配置,异常可能会被吞掉,你只能从日志里发现消费者线程停止了。建议至少要配置一个ExceptionHandler并做日志记录:
disruptor.handleExceptionsFor(new ExceptionHandler<OrderEvent>() { public void handleEventException(Throwable ex, long sequence, OrderEvent event) { log.error("消费异常,sequence={}", sequence, ex); } public void handleOnShutdownException(Throwable ex) {} public void handleOnStartException(Throwable ex) {} });6.6 4.x 版本迁移的几个关键差异
如果你真的要上 4.x,需要接受这些变化。4.x 中RingBuffer被设计成更薄的数据结构类,Sequence拆分为Cursor(消费位置游标)和ProducerGate(生产者准入控制),ConsumerGate取代了消费者依赖序列。原来的 Eh 接口变为Consumer抽象,你不再直接用start(),而是通过ProducerGate.tryPublish()发布数据。4.x 的价值在于让“多 RingBuffer 串联”场景更自然,但它对新手的学习曲线也更陡。当前开源社区的迁移热度还不算高,团队里如果没人熟悉 4.x,我建议 3.4.x 再用一段时间完全不丢人。
7. 写在最后的一些经验
说了这么多技术细节,最后回归到一个更本质的问题:你的业务真的需要 Disruptor 吗?
我在实际项目中做过一次改造,把一个核心链路上的 ArrayBlockingQueue 替换成 Disruptor。改造前系统的吞吐瓶颈确实在队列锁竞争上,替换后吞吐提升了约 40%。但同一时期另一个项目,瓶颈在数据库写入,替换 Disruptor 后几乎没有任何提升。判断是否值得引入 Disruptor,先看你的链路里是否存在“高并发跨线程传递数据”这个明确瓶颈。如果你的业务单机每秒也就几万个请求,普通的有界队列配合合理的并发策略完全够用,没必要引入无锁编程的复杂度。
还有一点容易被忽视:Disruptor 的生产者、消费者都在同一 JVM 内,它解决的是线程间通信问题,不是跨进程、跨网络通信问题。如果你的场景是微服务之间调接口,请考虑消息队列或 RPC 框架,Disruptor 不适用。
如果你决定用,我的建议是:把 RingBuffer 容量定在默认值 1024 的 8 倍(8192)附近开始压测,消费端尽量做到快速释放事件资源,等待策略先用 Yielding 跑到热点 CPU 资源吃紧时再调优。希望这篇从原理到实战再到压测方法论的文章,能帮你在面对“200 万 TPS”这样的数字时,多一分冷静,也多一分让数字落地的能力。