1. 前言
最近在做数据平台 整个接入系统的重构,重点看了flume的高可高机制,在看memoryChannel源码的时候发现几个有意思的实现,刚好最近得空,写出来,刚好也练练自己的文笔,哈~
2. 需要知道的组件背景
flume核心组件agent,三部分组成,SOURCE,CHANNEL,SINK。还有俩个核心部分CHANNEL_PROCESSOR,SINK_PROCESSOR。保证整个流程的高可靠,核心就在从source抽取数据到channel,从channel抽取到sink,当sink被消费后channel数据删除的这三个环节。而这些环节在flume中被统一的用事物管理起来。可以说,这是flume高可靠的关键一点。理解这一点,我们在看源码的时候就聚焦于此。于是有趣的事情来了在动态扩容上采用锁,在容量控制上上使用信号量,前者是一个比较常见东西,后面如果常规的实现应该也是使用锁。在采用信号量这种实现方式,在channel空间还是富余的情况,也能够比较保证较高的性能。虽然上面的构思不是最优,但是看到这个构思,也是颇有意思。(01世界,只有没有想到,没有做不到)
3. 源码赏析
下面是扩容的代码,在这里面可以看到严谨的代码逻辑,看了这些代码让人不禁有比较欲,如果我去实现,需要多久!我能比他写出更漂亮的代码吗,哈哈~
private void resizeQueue(int capacity) throws InterruptedException {
int oldCapacity;
synchronized (queueLock) {
//老的容量=队列现有余额+在事务被处理了但是是未被提交的容量
oldCapacity = queue.size() + queue.remainingCapacity();
}
if (oldCapacity == capacity) {
return;
} else if (oldCapacity > capacity) {
//缩容
//尝试占用即将缩减的空间,以防被他人占用
if (!queueRemaining.tryAcquire(oldCapacity - capacity, keepAlive, TimeUnit.SECONDS)) {
LOGGER.warn("Couldn't acquire permits to downsize the queue, resizing has been aborted");
} else {
//直接缩容量
synchronized (queueLock) {
LinkedBlockingDeque<Event> newQueue = new LinkedBlockingDeque<Event>(capacity);
newQueue.addAll(queue);
queue = newQueue;
}
}
} else {
//扩容
synchronized (queueLock) {
LinkedBlockingDeque<Event> newQueue = new LinkedBlockingDeque<Event>(capacity);
newQueue.addAll(queue);
queue = newQueue;
}
queueRemaining.release(capacity - oldCapacity);
}
}
上面我们看到锁,配合信号实现动态增减容量。下面在来看更多信号量在容量控制的更多使用。下面这段代码是发送在commit阶段 commit阶段主要做的事情,将从source拿数据LinkedBlockingDeque putlist,和从channel拿数据的LinkedBlockingDeque takelist,进行“持久化”
@Override
protected void doCommit() throws InterruptedException {
int remainingChange = takeList.size() - putList.size();
if (remainingChange < 0) {
//putByteCounter是需要推到channel中的数据大小,bytesRemainingchannel容量剩余
if (!bytesRemaining.tryAcquire(putByteCounter, keepAlive, TimeUnit.SECONDS)) {
//channel 数据大小容量不足,事物不能提交
throw new ChannelException("Cannot commit transaction. Byte capacity " +
"allocated to store event body " + byteCapacity * byteCapacitySlotSize +
"reached. Please increase heap space/byte capacity allocated to " +
"the channel as the sinks may not be keeping up with the sources");
}
if (!queueRemaining.tryAcquire(-remainingChange, keepAlive, TimeUnit.SECONDS)) {
//及时释放!!!这个如果是第一次写这类的代码,很容易漏掉这类的代码
//remainingChange如果是负数的话,说明source的生产速度,大于sink的消费速度,且这个速度大于channel所能承载的值
bytesRemaining.release(putByteCounter);
throw new ChannelFullException("Space for commit to queue couldn't be acquired." +
" Sinks are likely not keeping up with sources, or the buffer size is too tight");
}
}
int puts = putList.size();
int takes = takeList.size();
//锁住队列开始,进行数据的流转
synchronized (queueLock) {
if (puts > 0) {
while (!putList.isEmpty()) {
if (!queue.offer(putList.removeFirst())) {
throw new RuntimeException("Queue add failed, this shouldn't be able to happen");
}
}
}
putList.clear();
takeList.clear();
}
bytesRemaining.release(takeByteCounter);
takeByteCounter = 0;
putByteCounter = 0;
queueStored.release(puts);
if (remainingChange > 0) {
queueRemaining.release(remainingChange);
}
if (puts > 0) {
channelCounter.addToEventPutSuccessCount(puts);
}
if (takes > 0) {
channelCounter.addToEventTakeSuccessCount(takes);
}
channelCounter.setChannelSize(queue.size());
}
4. 看这篇收货了啥
信号量,队列的使用(add,put,offer,take,poll,remove)差别,严谨的使用队列逻辑(多读,多看,到实际生产中,他会成为你自然而然的思考)