Disruptor高性能队列 YYDS

一 背景

我们项目中有大量写库操作,当服务出现高并发时会导致数据库IO压力变大。

为了应对这种情况,降低数据库压力,我们需要基于Disruptor设计一套通用的异步批处理流水线。

二 技术选型

其实选择 Disruptor高性能队列之前,本来是想使用 Java内置队列的。

但是都被我pass掉了。原因如下:

concurrentLinkedQueue 虽然是 无锁代表着吞吐量高,但是它内存是不可控的会有OOM风险,以及它的数据结构是链表,频繁的创建和销毁导致GC压力大。

LinkedBlokingQueue 有锁,可选有界 但是数据结构是 链表会有GC压力大。

ArrayBlockingQueue 虽然是 有界队列,内存可控,数据结构为数组对GC友好,但是它存在性能瓶颈原因是因为存在锁竞争,锁竞争就会导致 cpu 工作效率低下,因为时间都花在 锁、挂起、唤醒上。还有一点就是她存在 伪共享,导致缓存命中率下降。

意识到这些问题,决定使用disruptor 高性能队列框架。

原因是因为 disruptor是环形数组天然有界,而且内存是预分配的可控,不会导致OOM,而且采用了CAS来解决锁竞争,使用了缓存行填充解决了 伪共享的问题。

基于 Disruptor 的 无锁设计 和 内存预分配,使得发布事件非常快。Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,每秒可支撑600万订单。

三 实践

核心思想是将高频的单条写入请求,在内存中聚合成批次,最后进行批量写入,极大地减少了数据库的IO次数。

3.1 核心组件

3.1.1 BatchDataRing(RingBuffer 管理器)

负责初始化和管理 Disruptor 框架,提供事件发布接口和注册 ==> 门面模式

主要配置参数
  • ring-size: RingBuffer 大小,默认 4,194,304(1024 * 128 * 32)
  • db-batch-size: 数据库批量处理大小,默认 640
  • db-batch-chunk-size: 数据块大小,默认 40
  • ring-batch-size: RingBuffer 批量处理大小,默认 1024
核心机制
  • 单消费者模式:通过 LimitedThreadFactory 保证只有一个线程处理事件
3.1.2 BatchDataHandler(消费者)

实现 EventHandler 接口,负责处理 RingBuffer 中的事件。

设计亮点是 支持多种数据类型,内部为每种类型维护以下数据结构

数据结构:

private Map<Class,Function<List, Boolean>> funcs = new ConcurrentHashMap<>(); // 类型处理函数映射

private Map<Class,List> caches = new ConcurrentHashMap<>(); // 类型数据缓存映射

3.1.3 数据单元 dataEvent

就是 ringBuffer 流动的盒子

3.1.4 具体的业务适配器

3.2 工作流程

3.2.1 事件发布流程

通过 BatchDataRing.publish() 方法发布事件到 RingBuffer ,事件按 sequence 顺序存储在 RingBuffer 中。

3.2.2 事件处理流程
  • 单线程按 sequence 顺序处理事件
  • 将事件数据按类型分类缓存到对应列表中
  • 达到批量处理条件时触发批量保存
3.2.3 批量处理触发条件
  • 每处理 dbBatchSize(640) 个事件
  • RingBuffer 结束且未达到 ringBatchSize(1024) 的倍数
3.2.4 批量保存机制
  • 遍历所有类型的数据缓存
  • 每种类型数据独立进行批量处理
  • 将每种类型的数据按 dbBatchChunkSize(40) 分割成多个 chunk
  • 使用线程池并发处理所有 chunk
  • 等待所有 chunk 处理完成,记录处理时间

3.3. 并发控制策略

3.3.1 外部串行,内部并发
  • 外部串行:事件按 sequence 顺序由单线程处理,保证处理顺序性
  • 内部并发:批量数据通过线程池并发处理,提升处理性能
3.3.2 线程控制

通过 CAS 操作确保只创建一个处理线程,保证事件处理的顺序性。

java 复制代码
public class LimitedThreadFactory implements ThreadFactory {
    private final AtomicInteger count = new AtomicInteger(0);

    public Thread newThread(Runnable r) {
        if (count.compareAndSet(0, 2)) {
            return new Thread(r);
        } else {
            throw new IllegalStateException("Created more that one thread");
        }
    }
}

3.4. 性能优化设计

3.4.1 分层批量处理
scss 复制代码
RingBuffer (4,194,304) 
    ↓
RingBuffer 批量 (1024)
    ↓
数据库批量 (640)
    ↓
并发处理单元 (40)
3.4.2 多类型数据处理
  • 不同类型数据独立缓存和处理
  • 每种类型数据达到批量条件时独立处理
  • 避免不同类型数据之间的相互阻塞
3.4.3 并发处理优化
  • 使用 Executors.newCachedThreadPool() 线程池
  • 数据分块并发处理,提升 I/O 利用率
相关推荐
hui函数21 分钟前
Flask电影投票系统全解析
后端·python·flask
小厂永远得不到的男人2 小时前
基于 Spring Validation 实现全局参数校验异常处理
java·后端·架构
毅航6 小时前
从原理到实践,讲透 MyBatis 内部池化思想的核心逻辑
后端·面试·mybatis
展信佳_daydayup6 小时前
02 基础篇-OpenHarmony 的编译工具
后端·面试·编译器
Always_Passion6 小时前
二、开发一个简单的MCP Server
后端
用户721522078776 小时前
基于LD_PRELOAD的命令行参数安全混淆技术
后端
笃行3507 小时前
开源大模型实战:GPT-OSS本地部署与全面测评
后端
知其然亦知其所以然7 小时前
SpringAI:Mistral AI 聊天?一文带你跑通!
后端·spring·openai
庚云7 小时前
🔒 前后端 AES 加密解密实战(Vue3 + Node.js)
前端·后端
超级小忍7 小时前
使用 GraalVM Native Image 将 Spring Boot 应用编译为跨平台原生镜像:完整指南
java·spring boot·后端