Flume-NG原始碼閱讀之AvroSink

玖瘋發表於2014-05-27

  org.apache.flume.sink.AvroSink是用來通過網路來傳輸資料的,可以將event傳送到RPC伺服器(比如AvroSource),使用AvroSink和AvroSource可以組成分層結構。它繼承自AbstractRpcSink  extends AbstractSink implements Configurable這跟其他的sink一樣都得extends AbstractSink implements Configurable,所以重點也在confgure、start、process、stop這四個方法,實現了initializeRpcClient(Properties props)方法。

  一、configure(Context context)方法,先獲取配置檔案中的主機hostname和埠port;設定clientProps的屬性hosts=h1,hosts.h1=hostname:port;然後將配置資訊中的所有資訊放入clientProps中;獲取cxnResetInterval表示重複建立連線的時間間隔,預設是0就是不重複建立連線。

  二、start()方法是呼叫createConnection()建立連線,如果出現異常就呼叫destroyConnection()掐斷連線,避免資源洩漏。createConnection()方法主要是初始化client = initializeRpcClient(clientProps)以及建立一個執行緒,並執行在給定延遲cxnResetInterval後執行一次銷燬連結destroyConnection(),由於預設cxnResetInterval=0,所以是不會執行這個執行緒的。這點不是很明白,為什麼要銷燬???initializeRpcClient(clientProps)方法會根據配置檔案中的資訊進行構造相應的RpcClient:首先會獲取"client.type"引數指定的型別可用的有四種(NettyAvroRpcClient(如果沒有"client.type"則使用這個作為預設Client)、FailoverRpcClient、LoadBalancingRpcClient、ThriftRpcClient),例項化之後需要對其在進行必要的配置執行client.configure(properties)進行配置:

  (1)NettyAvroRpcClient.configure(Properties properties)方法首先會獲取鎖,檢查connState連線狀態要保證是沒有配置過的;其次獲取"batch-size"設定batchSize,如果配置的小於1則使用預設值100;獲取“hosts”,如果配置了多個hosts則只使用第一個;獲取"hosts."字首,如果有多個則使用第一個,再解析出hostname和port,構建一個InetSocketAddress的物件address;獲取連線超時時間"connect-timeout",設定connectTimeout,如果配置的小於1000則使用預設值20000,單位是ms;獲取相應時間"request-timeout",設定requestTimeout,如果配置的小於1000,則使用預設值20000,單位ms;獲取壓縮型別"compression-type",如果有配置壓縮還需要獲取壓縮的等級compressionLevel;最後呼叫connect()連結RPC伺服器。

  實際的連結在connect(long timeout, TimeUnit tu)方法中,先構造一個執行緒池callTimeoutPool;然後根據是否有壓縮構造相應的工廠類CompressionChannelFactory(有壓縮配置)或者NioClientSocketChannelFactory(無壓縮配置);構造一個

NettyTransceiver(this.address,socketChannelFactory,tu.toMillis(timeout))收發器物件transceiver;根據transceiver返回一個avroClient;最後設定連結狀態為READY。

  (2)FailoverRpcClient.configure(Properties properties)方法會呼叫configureHosts(Properties properties)方法,這個方法會獲取配置檔案中的host列表hosts;獲取最大嘗試次數"max-attempts",設定maxTries,預設是hosts的大小;獲取批量大小

"batch-size",設定batchSize,如果配置的小於1則使用預設大小100;將此client置為活動的isActive=true。可以看出這個client可以使用多個host。

  (3)LoadBalancingRpcClient.configure(Properties properties)會獲取配置檔案中的host列表hosts,且不允許少於兩個,否則爆異常;獲取主機選擇器"host-selector",有兩種內建的選擇器:LoadBalancingRpcClient.RoundRobinHostSelector和LoadBalancingRpcClient.RandomOrderHostSelector,預設是ROUND_ROBIN(即RoundRobinHostSelector)輪詢的方式(也可以自定義,要實現LoadBalancingRpcClient.HostSelector介面);獲取"backoff",設定backoff(是否使用推遲演算法,就是sink.process出問題後對這個sink設定懲罰時間,在此期間不再認為其可活動)的boolean值(預設false就是不啟用);獲取最大推遲時間"maxBackoff",設定maxBackoff;然後根據選擇器是ROUND_ROBIN還是RANDOM選擇對應的類並例項化selector,最後設定主機selector.setHosts(hosts)。

  這兩個內建選擇器:RoundRobinHostSelector實際使用的是RoundRobinOrderSelector;RandomOrderHostSelector實際使用的是RandomOrderSelector,這兩個都在Flume-NG原始碼閱讀之SinkGroups和SinkRunner 這篇文章中有介紹,這裡不再說明。

  (4)ThriftRpcClient.configure(Properties properties)會獲取狀態鎖stateLock.lock();獲取配置檔案中的host列表中的第一個,只需要一個;獲取批量大小"batch-size",設定batchSize,如果配置的小於1則使用預設大小100;獲取主機名hostname和埠port;獲取響應時間requestTimeout,如果小於1000設定為預設的20000ms;獲取連線池大小"maxConnections",設定connectionPoolSize,如果大小小於1則設定為預設的值5;建立連線池管理物件connectionManager= new ConnectionPoolManager(connectionPoolSize);設定連線狀態為READY,connState = State.READY;最後狀態鎖解鎖stateLock.unlock()。

  這四個Client都是extends AbstractRpcClient implements RpcClient。

  三、process()方法,程式碼如下:

 1   public Status process() throws EventDeliveryException {
 2     Status status = Status.READY;
 3     Channel channel = getChannel();    //獲得channel
 4     Transaction transaction = channel.getTransaction();    //建立事務
 5 
 6     try {
 7       transaction.begin();    //事務開始
 8 
 9       verifyConnection();    //確儲存在連結且處於活動狀態,如果連結處於非活動狀態銷燬並重建連結
10 
11       List<Event> batch = Lists.newLinkedList();
12 
13       for (int i = 0; i < client.getBatchSize(); i++) {    //保證這批次的event數量不可能超過客戶端批量處理的最大處理數量
14         Event event = channel.take();
15 
16         if (event == null) {        //表示channel中沒有資料了
17           break;
18         }
19 
20         batch.add(event);    //加入event列表
21       }
22 
23       int size = batch.size();    //獲取這批次取得的event的數量
24       int batchSize = client.getBatchSize();        //獲取客戶端可以批量處理的大小
25 
26       if (size == 0) {
27         sinkCounter.incrementBatchEmptyCount();
28         status = Status.BACKOFF;
29       } else {
30         if (size < batchSize) {
31           sinkCounter.incrementBatchUnderflowCount();
32         } else {
33           sinkCounter.incrementBatchCompleteCount();
34         }
35         sinkCounter.addToEventDrainAttemptCount(size);
36         client.appendBatch(batch);        //批量處理event
37       }
38 
39       transaction.commit();        //事務提交
40       sinkCounter.addToEventDrainSuccessCount(size);
41 
42     } catch (Throwable t) {
43       transaction.rollback();    //事務回滾
44       if (t instanceof Error) {
45         throw (Error) t;
46       } else if (t instanceof ChannelException) {
47         logger.error("Rpc Sink " + getName() + ": Unable to get event from" +
48             " channel " + channel.getName() + ". Exception follows.", t);
49         status = Status.BACKOFF;
50       } else {
51         destroyConnection();        //銷燬連結
52         throw new EventDeliveryException("Failed to send events", t);
53       }
54     } finally {
55       transaction.close();    //事務關閉
56     }
57 
58     return status;
59   }

  即使本批次event的數量達不到client.getBatchSize()(channel中沒資料了)也會立即傳送到RPC伺服器。verifyConnection()方法是確儲存在連結且處於活動狀態,如果連結處於非活動狀態銷燬並重建連結。如果本批次沒有event,則不會想RPC傳送任何資料。client.appendBatch(batch)方法是批量傳送event。

  (1)NettyAvroRpcClient.appendBatch(batch)方法會呼叫appendBatch(events, requestTimeout, TimeUnit.MILLISECONDS)方法,該方法會首先確認連結處於READY狀態,否則報錯;然後將每個event重新封裝成AvroFlumeEvent,放入avroEvents列表中;然後構造一個CallFuture和avroEvents一同封裝成一個Callable放入執行緒池 handshake = callTimeoutPool.submit(callable)中去執行,其call方法內容是avroClient.appendBatch(avroEvents, callFuture)就是在此批量提交到RPC伺服器;然後handshake.get(connectTimeout, TimeUnit.MILLISECONDS)在規定時間等待執行的返回結果以及等待append的完成waitForStatusOK(callFuture, timeout, tu),詳細的可看這裡Flume的Avro Sink和Avro Source研究之二 : Avro Sink ,有對於這兩個future更深入的分析。一個批次傳輸的event的數量是min(batchSize,events.size())

  (2)FailoverRpcClient.appendBatch(batch)方法會做最多maxTries次嘗試直到獲取到可以正確傳送events的Client,通過localClient=getClient()--》getNextClient()來獲取client,這個方法每次會獲取hosts中的下一個HostInfo,並使用NettyAvroRpcClient來作為RPC Client,這就又回到了(1)中,這個方法還有一個要注意的就是會先從當前的lastCheckedhost+1位置向後找可以使用的Client,如果不行會再從開始到到lastCheckedhost再找,再找不到就報錯。使用localClient.appendBatch(events)來處理events,可參考(1)。

  (3)LoadBalancingRpcClient.appendBatch(batch)方法,首先會獲取可以傳送到的RPC伺服器的迭代器Iterator<HostInfo> it = selector.createHostIterator();然後取一個HostInfo,RpcClient client = getClient(host)這個Client和(2)一樣都是NettyAvroRpcClient,但是getClient方法會設定一個儲存名字和client對映的clientMap;client.appendBatch(events)執行之後就會跳出迴圈,下一次appendBatch會選擇下一個client執行。

  (4)ThriftRpcClient.appendBatch(batch)方法,從connectionManager.checkout()獲取一個client,ConnectionPoolManager類主要維護倆物件availableClients用來存放可用的client(是一個ClientWrapper,維護一個ThriftSourceProtocol.Client client 是用來批量處理event的)、checkedOutClients用來儲存從availableClients中拿出的Client表示正在使用的Client;ConnectionPoolManager.checkout()用於從availableClients中remove出client並放入checkedOutClients中,返回這個client;ConnectionPoolManager.checkIn(ClientWrapper client)方法用於將指定的Client從checkedOutClient中remove出並放入availableClients中;ConnectionPoolManager.destroy(ClientWrapper client)用於將checkedOutClients中的指定Client   remove並close。appendBatch方法中獲得client後,會每次封裝min(batchSize,events.size())個event,把他們封裝成ThriftFlumeEvent加入thriftFlumeEvents列表,然後如果thriftFlumeEvents>0則執行doAppendBatch(client, thriftFlumeEvents).get(requestTimeout,TimeUnit.MILLISECONDS)阻塞等待傳輸完畢。doAppendBatch方法會構建一個Callable其call方法執行client.client.appendBatch(e),將這個Callable放入執行緒池callTimeoutPool中執行並返回執行結果Future。

  以上四種RpcClient的append(Event event)方法也比較容易理解,不再講述。

  四、stop()方法主要是銷燬連結,關閉cxnResetExecutor。

  

  其實flume支援avro和thrift兩種(目前)傳輸,上面的(2)和(3)只不過是對(1)的上層業務做了一次封裝而已,本質上還是一樣的都是avro(基於netty)。同時記住avrosink是支援壓縮的。

  在此,由於博主對avro、netty、thrift並未深入研究過,所以只能從flume層面講解avrosink,對於某些人來說,可能講的並不深入,相關內容請自行學習!!

相關文章