手写批量缓存队列
场景
我希望从MySQL同步数据到另一个大数据数据库中,如Hive,Doris。
我希望在这个同步的过程中做一定的数据处理,只保留可以用来分析的数据。
我对这个数据的实时性要求不高,最终一致即可。
这个大数据数据库暴露了HTTP接口来新增数据。
那么对于这么一个场景,我们该如何处理是好?
问题分析
首先,这个问题很明显是需要用队列解决。
如果是一个单纯的队列的话,MySQL每增加一个数据,那么我们就需要把MySQL的磁盘中的数据放入内存中进行逻辑处理,再发一次HTTP请求,去同步,这样做无疑效率是很低下的。
因为每一条数据都会对应一次从磁盘到内存的再到建立HTTP连接,结束HTTP连接的过程。
问题解决
那么参考MySQL对每条查询语句都会把它放入buffer pool的思路,我们可以想到,我们可以让这个队列具备等待的能力,只有这个队列的数量满足一定额度,我们才会处理。这样就可以做到让多条数据共用一次HTTP请求,减少与Doris连接的消耗。
那么如果此时队列已经满最大额度了,就让这个线程休眠等待即可。
代码实战
csharp
public class BulkCacheQueue<T> {
private ConcurrentLinkedQueue<T> cacheQueue;
private int capacity;
private long timeout;
private AtomicInteger counter;
private final ReentrantLock lock;
private long startTime;
private AtomicBoolean reset = new AtomicBoolean(true);
private final Condition notFull;
/**
* Constructor
*
* @param capacity
* @param timeout
*/
public BulkCacheQueue(int capacity, long timeout) {
this.capacity = capacity;
this.timeout = timeout;
this.cacheQueue = new ConcurrentLinkedQueue<>();
this.counter = new AtomicInteger(capacity);
this.lock = new ReentrantLock();
this.notFull = lock.newCondition();
}
/**
*
* 此方法会一直等待直到push成功
*
* @param object
*/
public void push(T object) {
checkNotNull(object);
final ReentrantLock currentLock = this.lock;
currentLock.lock();
try {
while (counter.get() == 0) {
notFull.await();
}
cacheQueue.offer(object);
counter.decrementAndGet();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
currentLock.unlock();
}
}
/**
* 如果队列已满,立刻刷新
* 如果超过时间也立刻刷新
*
* @return
*/
public List<T> aPull() {
final ReentrantLock currentLock = this.lock;
currentLock.lock();
if (reset.get()) {
startTime = currentTimeMillis();
reset.set(false);
}
try {
if (counter.get() == 0 || currentTimeMillis() - startTime >= MILLISECONDS.toMillis(timeout)) {
return flushToList();
}
} finally {
currentLock.unlock();
}
return new ArrayList<>();
}
private List<T> flushToList() {
final List<T> list = new ArrayList<>();
while (!cacheQueue.isEmpty()) {
list.add(cacheQueue.poll());
}
counter.set(capacity);
reset.set(true);
notFull.signal();
return list;
}
/**
* 立刻刷新队列
*/
public List<T> flush(boolean force) {
if (force) {
counter.set(0);
}
return aPull();
}
}