Flume-NG(1.5版本)中SpillableMemoryChannel源码级分析
SpillableMemoryChannel是1.5版本新增的一个channel。这个channel优先将evnet放在内存中,一旦内存达到设定的容量就使用file channel写入磁盘。然后读的时候会按照顺序读取:会通过一个DrainOrderQueue来保证不管是内存中的还是溢出(本文的“溢出”指的是内存channel已满,需要使用file channel存储数据)文件中的顺序。这个Channel是memory channel和file channel的一个折中,虽然在内存中的数据仍然可能因为进程的突然中断而丢失,但是相对于memory channel而言一旦sink处理速度跟不上不至于丢失数据(后者一旦满了爆发异常会丢失后续的数据),提高了数据的可靠性;相对于file channel而言自然是大大提高了速度,但是可靠性较file channel有所降低。
我们来看一下SpillableMemoryChannel的继承结构:SpillableMemoryChannel extends FileChannel,原来SpillableMemoryChannel是file的子类,天热具有file channel的特性。但是它的BasicTransactionSemantics是自己实现的。接下来我们来分析分析这个channel,这个channel可以看成是两个channel。相关内容传送门:Flume-NG源码阅读之FileChannel 和 flume-ng源码阅读memory-channel(原创) 。
Stack,在用作队列时快于 LinkedList,但是不是线程安全的不支持多线程并发操作;put操作总是对queue中的最后(尾)一个元素操作,take操作总是对queue中第一个(头)操作;put时,如果是内存channel,在queue增加的就是正数,如果是溢出操作增加的就是负数,内存和溢出分别对应queue中不同的元素(可以分类去读);take时,如果从内存中取数据,就会使得queue第一个元素的值不断缩小(正数)至0,然后删除这个元素,如果是从溢出文件中取数据则会使得queue中第一个元素不断增大(负数)至0,然后删除这个元素;这样就会形成流,使得put不断追加数据到流中,take不断从流中取数据,这个流就是有序的,且流中元素其实就是内存中的evnet个数和溢出文件中event的个数。
public static class DrainOrderQueue {
public ArrayDeque<MutableInteger> queue = new ArrayDeque<MutableInteger>(1000);
public int totalPuts = 0; // for debugging only
private long overflowCounter = 0; // # of items in overflow channel
public String dump() {
StringBuilder sb = new StringBuilder();
sb.append(" [ ");
for (MutableInteger i : queue) {
sb.append(i.intValue());
sb.append(" ");
}
sb.append("]");
return sb.toString();
}
public void putPrimary(Integer eventCount) {
totalPuts += eventCount;
if ( (queue.peekLast() == null) || queue.getLast().intValue() < 0) { //获取,但不移除此双端队列的最后一个元素;如果此双端队列为空,则返回 null
queue.addLast(new MutableInteger(eventCount));
} else {
queue.getLast().add(eventCount);//获取,但不移除此双端队列的第一个元素。
}
}
public void putFirstPrimary(Integer eventCount) {
if ( (queue.peekFirst() == null) || queue.getFirst().intValue() < 0) { //获取,但不移除此双端队列的第一个元素;如果此双端队列为空,则返回 null。
queue.addFirst(new MutableInteger(eventCount));
} else {
queue.getFirst().add(eventCount);//获取,但不移除此双端队列的第一个元素。
}
}
public void putOverflow(Integer eventCount) {
totalPuts += eventCount;
if ( (queue.peekLast() == null) || queue.getLast().intValue() > 0) {
queue.addLast(new MutableInteger(-eventCount));
} else {
queue.getLast().add(-eventCount);
}
overflowCounter += eventCount;
}
public void putFirstOverflow(Integer eventCount) {
if ( (queue.peekFirst() == null) || queue.getFirst().intValue() > 0) {
queue.addFirst(new MutableInteger(-eventCount));
} else {
queue.getFirst().add(-eventCount);
}
overflowCounter += eventCount;
}
public int front() {
return queue.getFirst().intValue();
}
public boolean isEmpty() {
return queue.isEmpty();
}
public void takePrimary(int takeCount) {
MutableInteger headValue = queue.getFirst();
// this condition is optimization to avoid redundant conversions of
// int -> Integer -> string in hot path
if (headValue.intValue() < takeCount) {
throw new IllegalStateException("Cannot take " + takeCount +
" from " + headValue.intValue() + " in DrainOrder Queue");
}
headValue.add(-takeCount);
if (headValue.intValue() == 0) {
queue.removeFirst();
}
}
public void takeOverflow(int takeCount) {
MutableInteger headValue = queue.getFirst();
if(headValue.intValue() > -takeCount) {
throw new IllegalStateException("Cannot take " + takeCount + " from "
+ headValue.intValue() + " in DrainOrder Queue head " );
}
headValue.add(takeCount);
if (headValue.intValue() == 0) {
queue.removeFirst(); //获取并移除此双端队列第一个元素。
}
overflowCounter -= takeCount;
}
}
我们一个方法一个方法的来剖析这个类:
(1)dump(),这个方法比较简单就是获得queue中所有元素的数据量;
(2)putPrimary(Integer eventCount),这个方法用在put操作的commit时,在commitPutsToPrimary()方法中被调用,表示向内存提交数据。这个方法会尝试获取queue中最后一个元素,如果为空(说明没数据)或者元素数值小于0(说明这个元素是面向溢出文件的),就新建一个元素赋值这个事务的event数量加入queue;否则表示当前是的元素表征的是内存中的event数量,直接累加即可。
(3)putFirstPrimary(Integer eventCount),在doRollback()回滚的时候被调用,表示将takeList中的数据放回内存memQueue的头。这个方法会尝试获取queue中第一个元素,如果为空(说明没数据)或者元素数值小于0(说明这个元素是面向溢出文件的),就新建一个元素赋值takeList的event数量加入queue;否则表示当前是的元素表征的是内存中的event数量,直接累加即可。
(4)putOverflow(Integer eventCount),这个方法发生在put操作的commit时,在commitPutsToOverflow_core方法和start()方法中,后者是设置初始量,前者表示内存channel已满要溢出到file channel。这个方法会尝试获取queue中最后一个元素,如果为空(说明没数据)或者元素数值大于0(表示这个元素是面向内存的),就新建一个元素赋值这个事务的event数量加入queue,这里赋值为负数;否则表示当前是的元素表征的是溢出文件中的event数量,直接累加负数即可。
(5)putFirstOverflow(Integer eventCount),在doRollback()回滚的时候被调用,表示将takeList中event的数量放回溢出文件。这个方法会尝试获取queue中第一个元素,如果为空(说明没数据)或者元素数值大于0(表示这个元素是面向内存的),就新建一个元素赋值这个事务的 event数量加入queue,这里赋值为负数;否则表示当前是的元素表征的是溢出到文件中的event数量,直接累加负数即可。
(6)front(),返回queue中第一个元素的值
(7)takePrimary(int takeCount),这个方法在doTake()中被调用,表示take发生之后,要将内存中的event数量减takeCount(这个值一般都是1,即每次取一个)。这个方法会获取第一个元素的值(表示内存channel中有多少event),如果这个值比takeCount小,说明内存中没有足够的数量,这种情况不应该发生,报错;否则将这个元素的值减去takeCount,表示已取出takeCount个。最后如果这个元素的值为0,则从queue中删除这个元素。注意这里虽然是可以取takeCount个,但是源码调用这个参数都是一次取1个而已。
(8)takeOverflow(int takeCount),这个方法在doTake()中被调用,表示take发生之后,要将溢出文件中的event数量加上takeCount(这个值一般都是1,即每次取一个)。这个 方法会获取第一个元素的值(表示溢出文件中有多少event),如果这个值比takeCount的负值大,说明文件中没有足够的数量,这种情况不应该发生,报错;否则将这个元素的值加上takeCount,表示已取出takeCount个。最后如果这个元素的值为0,则从queue中删除这个元素。注意这里虽然是可以取 takeCount个,但是源码调用这个参数都是一次取1个而已。
protected void doPut(Event event) throws InterruptedException {
channelCounter.incrementEventPutAttemptCount();
putCalled = true; //说明是在put操作
int eventByteSize = (int)Math.ceil(estimateEventSize(event)/ avgEventSize);//获取这个event可以占用几个slot
if (!putList.offer(event)) { //加入putList
throw new ChannelFullException("Put queue in " + getName() +
" channel's Transaction having capacity " + putList.size() +
" full, consider reducing batch size of sources");
}
putListByteCount += eventByteSize;
}
这个方法比较简单,就是put开始;设置putCalled为true表示put操作;计算占用slot个数;将event放入putList等待commit操作;putListByteCount加上这个evnet占用的slot数。
protected Event doTake() throws InterruptedException {
channelCounter.incrementEventTakeAttemptCount();
if (!totalStored.tryAcquire(overflowTimeout, TimeUnit.SECONDS)) {
LOGGER.debug("Take is backing off as channel is empty.");
return null;
}
boolean takeSuceeded = false;
try {
Event event;
synchronized(queueLock) {
int drainOrderTop = drainOrder.front();
if (!takeCalled) {
takeCalled = true;
if (drainOrderTop < 0) {
useOverflow = true;
overflowTakeTx = getOverflowTx(); //获取file channle的事务
overflowTakeTx.begin();
}
}
if (useOverflow) {
if (drainOrderTop > 0) {
LOGGER.debug("Take is switching to primary");
return null; // takes should now occur from primary channel
}
event = overflowTakeTx.take();
++takeCount;
drainOrder.takeOverflow(1);
} else {
if (drainOrderTop < 0) {
LOGGER.debug("Take is switching to overflow");
return null; // takes should now occur from overflow channel
}
event = memQueue.poll(); //获取并移除此双端队列所表示的队列的头(换句话说,此双端队列的第一个元素);如果此双端队列为空,则返回 null。
++takeCount;
drainOrder.takePrimary(1);
Preconditions.checkNotNull(event, "Queue.poll returned NULL despite"
+ " semaphore signalling existence of entry");
}
}
int eventByteSize = (int)Math.ceil(estimateEventSize(event)/ avgEventSize);
if (!useOverflow) {
// takeList is thd pvt, so no need to do this in synchronized block
takeList.offer(event);
}
takeListByteCount += eventByteSize;
takeSuceeded = true;
return event;
} finally {
if(!takeSuceeded) {
totalStored.release();
}
}
}
由于ArrayDeque是非线程安全的(memQueue就是ArrayDeque),所以take操作从memQueue获取数据时,要独占memQueue。任何对memQueue都要进行同步,这里是同步queueLock。
doTake方法会先检查totalStored中有无许可,即channel中有无数据;然后同步;再获取drainOrder的头元素,如果takeCalled为false(初始为false),则设置其为true,再判断获取到的drainOrder头元素的值是否为负数,负数说明数据在溢出文件中,设置useOverflow为true表示要从溢出文件中读取数据并且获取file channel的FileBackedTransaction赋值给overflowTakeTx,begin()可以获取数据。如果useOverflow为true则转到调用overflowTakeTx.take获取event,然后takeCount自增1,调用drainOrder.takeOverflow(1)修改队列中溢出event数量的值。如果useOverflow为false说明数据在内存中,直接调用memQueue.poll()获得event,然后takeCount自增1,调用drainOrder.takePrimary(1)修改队列中内存中evnet数量的值。然后计算这个event占用的slot数。如果是从内存channel中读取的event则将其放入takeList中;takeListByteCount加上这个evnet占用的slot数。最后返回event。
protected void doRollback() {
LOGGER.debug("Rollback() of " +
(takeCalled ? " Take Tx" : (putCalled ? " Put Tx" : "Empty Tx")));
if (putCalled) {
if (overflowPutTx!=null) {
overflowPutTx.rollback();
}
if (!useOverflow) {
bytesRemaining.release(putListByteCount);
putList.clear();
}
putListByteCount = 0;
} else if (takeCalled) {
synchronized(queueLock) {
if (overflowTakeTx!=null) {
overflowTakeTx.rollback();
}
if (useOverflow) {
drainOrder.putFirstOverflow(takeCount);
} else {
int remainingCapacity = memoryCapacity - memQueue.size();
Preconditions.checkState(remainingCapacity >= takeCount,
"Not enough space in memory queue to rollback takes. This" +
" should never happen, please report");
while (!takeList.isEmpty()) {
memQueue.addFirst(takeList.removeLast());
}
drainOrder.putFirstPrimary(takeCount);
}
}
totalStored.release(takeCount);
} else {
overflowTakeTx.rollback();
}
channelCounter.setChannelSize(memQueue.size() + drainOrder.overflowCounter);
}
如果putCalled为true,则表明正在进行的是put操作。如果overflowPutTx不为null,说明是在溢出,执行overflowPutTx的roolback方法进行回滚。如果没有溢出,则bytesRemaining释放putListByteCount许可,表示腾出putListByteCount个slot;清空putList;最后将putListByteCount置为0。如果takeCalled为true,说明正在进行的操作是take,如果overflowTakeTx不为null,说明是在溢出,执行overflowTakeTx的roolback方法进行回滚;如果在溢出,则调用drainOrder.putFirstOverflow(takeCount)修改queue中溢出文件中的event的数量;如果在使用内存channel,则计算出内存channel中还可以最多存储event的数量,如果这个数量小于takeCount,则报错,否则将takeList中的所有event加入memQueue的头部,执行drainOrder.putFirstPrimary(takeCount)来修改queue中内存channel存放的event的数量;然后totalStored释放takeCount个许可,表示内存channel中增加了takeCount个event。
五、stop方法,会调用父类file channel中的stop方法。
六、createTransaction()方法,直接返回一个SpillableMemoryTransaction对象。这说明take和put可以并发执行,但是当涉及到memQueue时,还是需要同步。
至此,这个新的channel介绍完了。总体来说SpillableMemoryChannel是精心设计的一个channel,兼顾Flume内置的file channel和memory channel的优点,又增加了一个选择,大伙可根据需要选择合适的channel。
Flume-NG(1.5版本)中SpillableMemoryChannel源码级分析的更多相关文章
- MapReduce的ReduceTask任务的运行源码级分析
MapReduce的MapTask任务的运行源码级分析 这篇文章好不容易恢复了...谢天谢地...这篇文章讲了MapTask的执行流程.咱们这一节讲解ReduceTask的执行流程.ReduceTas ...
- MapReduce的MapTask任务的运行源码级分析
TaskTracker任务初始化及启动task源码级分析 这篇文章中分析了任务的启动,每个task都会使用一个进程占用一个JVM来执行,org.apache.hadoop.mapred.Child方法 ...
- TaskTracker任务初始化及启动task源码级分析
在监听器初始化Job.JobTracker相应TaskTracker心跳.调度器分配task源码级分析中我们分析的Tasktracker发送心跳的机制,这一节我们分析TaskTracker接受JobT ...
- 监听器初始化Job、JobTracker相应TaskTracker心跳、调度器分配task源码级分析
JobTracker和TaskTracker分别启动之后(JobTracker启动流程源码级分析,TaskTracker启动过程源码级分析),taskTracker会通过心跳与JobTracker通信 ...
- TableInputFormat分片及分片数据读取源码级分析
我们在MapReduce中TextInputFormat分片和读取分片数据源码级分析 这篇中以TextInputFormat为例讲解了InputFormat的分片过程以及RecordReader读取分 ...
- MapReduce job在JobTracker初始化源码级分析
mapreduce job提交流程源码级分析(三)中已经说明用户最终调用JobTracker.submitJob方法来向JobTracker提交作业.而这个方法的核心提交方法是JobTracker.a ...
- Flume-NG内置计数器(监控)源码级分析
Flume的内置监控怎么整?这个问题有很多人问.目前了解到的信息是可以使用Cloudera Manager.Ganglia有图形的监控工具,以及从浏览器获取json串,或者自定义向其他监控系统汇报信息 ...
- Shell主要逻辑源码级分析 (2)——SHELL作业控制
版权声明:本文由李航原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/110 来源:腾云阁 https://www.qclou ...
- Shell主要逻辑源码级分析(1)——SHELL运行流程
版权声明:本文由李航原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/109 来源:腾云阁 https://www.qclou ...
随机推荐
- IOS开发之—— 各种加密的使用(MD5,base64,DES,AES)
基本的单向加密算法: BASE64 严格地说,属于编码格式,而非加密算法 MD5(Message Digest algorithm 5,信息摘要算法)SHA(Secure Hash Algorithm ...
- Jsp内置对象及EL表达式的使用
一.JSP的内置对象(9个JSP内置对象) JSP的内置对象引用名称 对应的类型 request HttpServletRequest response HttpServletResponse ses ...
- onload是代码在也买你的追加元素的完成,而不是http请求的完成
- 【BZOJ1006】【HNOI2008】神奇的国度(弦图染色)
1006: [HNOI2008]神奇的国度 Time Limit: 20 Sec Memory Limit: 162 MBSubmit: 1467 Solved: 603[Submit][Stat ...
- Callable、Future和FutureTask
创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口.这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果. 如果需要获取执行结果,就必须通过共享变量或者使用线 ...
- java.lang.NoClassDefFoundError: org/objectweb/asm/Type
Instantiation of bean failed; nested exception is java.lang.NoClassDefFoundError: org/objectweb/asm/ ...
- 小菜鸟学 Spring-Dependency injection(二)
注入方式一:set注入 <bean id="exampleBean" class="examples.ExampleBean"> <!-- s ...
- Java-异常Throwable,Exception,Error
异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等. 异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程. Java通过API中Throwable类的众多子类描述各种不同的 ...
- 【HDU 4925】BUPT 2015 newbie practice #2 div2-C-HDU 4925 Apple Tree
http://acm.hust.edu.cn/vjudge/contest/view.action?cid=102419#problem/C Description I’ve bought an or ...
- 让Jayrock插上翅膀(加入输入输出参数注释,测试页面有注释,下拉框可以搜索)
继上一篇文章介绍了Jayrock组件开发接口的具体步骤和优缺点之后,今天给大家带来的就是,如何修复这些缺点. 首先来回顾一下修复的缺点有哪些: 1.每个接口的只能写大概的注释,不能分开来写,如接口的主 ...