照搬Spark Streaming + Flume Integration Guide的例子搭建了Pull-based Approach using a Custom Sink的流式计算例子。
结果spark端输出的全是"Received 0 flume events"(其实也不全是0,只是Spark一秒输出一次收到的flume events数量,大部分都是0,所以没注意到不为零的记录)
当时很纠结,只能一行一行源码的看。现在总算对这个Pull-based Approach using a Custom Sink的流程有了一个大概的了解
在Spark这一侧:
1. Spark端代码在初始化的时候创建FlumePollingInputDStream类的对象stream
2. stream对象在onStart方法中创建了一个avro rpc的client和FlumeBatchFetcher对象并将其放入线程调度器receiverExecutor
3. FlumeBatchFetcher对象在run方法中调用stream.client的getEventBatch方法批量获取SparkSinkEvent
在Flume这一侧:
1. flume agent上的spark sink处理rpc调用:SparkAvroCallbackHandler对象的getEventBatch方法每次被调用就创建一个TransactionProcessor对象放入线程调度器
2. TransactionProcessor对象在call方法中调用populateEvents()从flume channel获取event写入自己的eventBatch属性中
3. SparkAvroCallbackHandler对象通过TransactionProcessor对象的getEventBatch获得eventBatch
最后,FlumeBatchFetcher对象就获得eventBath啦。(至于eventBatch怎么到FlumePollingInputDStream对象stream里去的暂时就不管啦~)
源码里有几个参数值得注意一下:
1. TransactionProcessor的populateEvents()里用到了变量maxBatchSize控制一次获取的event数量。
这个变量来自哪里呢?
源头是FlumeUtils.createStream()。这个方法重载了好多次,根方法是
createPollingStream(
ssc: StreamingContext,
addresses: Seq[InetSocketAddress],
storageLevel: StorageLevel,
maxBatchSize: Int,
parallelism: Int
)
而maxBatchSize有个默认值DEFAULT_POLLING_BATCH_SIZE = 1000
这解释了我遇到的一个问题。
我在flume agent里使用了memory channel。当transactionCapacity设置的值小于1000时,flume agent经常抛出channel capacity full的异常。
原因就是spark sink每次从channel获取的event数量就是根据maxBatchSize来的。而且每次获取event的操作就是一个transaction。
transaction已经满了,而spark sink还要往这个transaction里塞event,异常就发生啦
另外,TransactionProcessor的populateEvents()里读取event的是一个breakable的循环。
当channel里的event空了的时候,程序会跳出循环,提前结束这个transaction。
所以,有时候spark端的程序获得的event数量会小于1000