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的大小;獲取批量大小
(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,對於某些人來說,可能講的並不深入,相關內容請自行學習!!