基于对象大小的队列(Size-based BlockingQueue)
- ✅ 控制内存缓存(Map、List、Queue)的字节大小;
- ❌ 不再基于“个数”控制;
- ✅ 防止 OOM;
- ✅ 支持生产者-消费者模型;
- ✅ 可扩展支持 Map 缓存结构;
- ✅ 提供完整的概要设计 + 详细设计 + 示例代码 + 流程图。
🧱 一、需求背景
数据库迁移场景描述:
- 源端:MySQL / Oracle / PostgreSQL
- 目标端:MySQL / Hive / Kafka / ES 等
- 迁移方式:全量 + 增量
- 中间缓存结构:
BlockingQueue<Row>
存放待处理数据;
Map<String, Object>
存放缓存元数据或进度信息;
- 问题:使用传统基于“个数”的队列无法有效控制内存占用,容易造成 OOM。
📦 二、整体架构设计
+---------------------+
| Producer |
| (源端读取模块) |
+----------+----------+
|
v
+---------------------+
| SizeBasedBlockingQueue |
| - 基于字节大小控制 |
| - 支持 put() / take()|
+----------+----------+
|
v
+---------------------+
| Consumer |
| (目标端写入模块) |
+---------------------+
✅ 功能目标
功能 |
描述 |
✅ 基于字节大小控制 |
控制队列中对象的总字节数不超过设定值 |
✅ 支持阻塞操作 |
生产者满时阻塞,消费者空时等待 |
✅ 对业务透明 |
接口兼容 BlockingQueue ,无缝替换 |
✅ 支持异步报警 |
当接近阈值时可触发预警 |
✅ 支持自定义序列化 |
可配置对象大小估算方式 |
📈 三、概要设计方案
1️⃣ 核心思路
- 使用装饰器模式封装
BlockingQueue
;
- 维护一个
AtomicLong currentSize
表示当前队列中的总字节数;
- 在每次
put()
和 take()
时更新字节数;
- 当超过
maxByteSize
时阻塞生产者线程;
2️⃣ 技术选型建议
技术点 |
实现方式 |
字节估算 |
使用 jol-core 或 Instrumentation.getObjectSize() |
序列化 |
可插拔接口,如 Object -> byte[] |
队列实现 |
基于 ArrayBlockingQueue 或 LinkedBlockingQueue 封装 |
异常处理 |
超时机制、中断机制兼容原生 API |
可视化监控 |
可集成 Prometheus Exporter |
🖼️ 四、流程图
+---------------------+
| 用户调用 put(object) |
+----------+----------+
|
v
+-------+--------+
| 计算 object 字节大小 |
+------------------+
|
v
+---------------------+
| 是否 currentSize + size > maxByteSize? |
| 是 → awaitNotFull() 阻塞 |
+---------------------+
|
v
+---------------------+
| 加锁(ReentrantLock) |
+---------------------+
|
v
+---------------------+
| 入队 + 更新 currentSize |
+---------------------+
|
v
+---------------------+
| 唤醒 notEmpty 条件 |
+---------------------+
|
v
+---------------------+
| 用户调用 take() |
| 获取对象并减去字节数 |
+---------------------+
🧪 五、核心类设计与代码实现
✅ 1. SizeBasedBlockingQueue.java(主控类)
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class SizeBasedBlockingQueue<T> {
private final BlockingQueue<T> delegate;
private final long maxByteSize;
private final ObjectSizer<T> sizer;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private volatile long currentByteSize = 0;
public SizeBasedBlockingQueue(long maxByteSize, int capacity, ObjectSizer<T> sizer) {
this.delegate = new LinkedBlockingQueue<>(capacity);
this.maxByteSize = maxByteSize;