在AbstractConfigurationProvider類中loadSinks方法會呼叫loadSinkGroups方法將所有的sink和sinkgroup放到了Map<String, SinkRunner> sinkRunnerMap之中。
SinkRunner可能對應一個sink也可能對應一個sinkgroup。因為如果配置檔案中有sinkgroup則這個sinkgroup對應的sink會組成一個group然後封裝為一個sinkRunner,然後不在sinkgroup中的sink會自己成為一個sinkRunner。每個SinkRunner的構造方法的引數是一個SinkProcessor是用來處理多個sink的。
一、如果一個SinkRunner對應一個sink。SinkProcessor pr = new DefaultSinkProcessor()是預設的SinkProcessor。loadSinkGroups方法中的相關程式碼如下:
1 SinkProcessor pr = new DefaultSinkProcessor(); 2 List<Sink> sinkMap = new ArrayList<Sink>(); 3 sinkMap.add(entry.getValue()); 4 pr.setSinks(sinkMap); 5 Configurables.configure(pr, new Context()); 6 sinkRunnerMap.put(entry.getKey(),new SinkRunner(pr));
DefaultSinkProcessor.configure(Context context)是空方法,所有這句Configurables.configure(pr, new Context())沒啥作用,重要的是DefaultSinkProcessor.process()方法,就一句程式碼就是return sink.process()直接呼叫sink的process。setSinks方法只會設定一個sink。DefaultSinkProcessor的start()方法也會直接呼叫sink.start()來啟動sink。
二、如果一個SinkRunner對應多個sink。則會構造一個SinkGroup group = new SinkGroup(groupSinks)然後獲取SinkProcessor:group.getProcessor()。loadSinkGroups方法中的相關程式碼如下:
1 SinkGroup group = new SinkGroup(groupSinks); 2 Configurables.configure(group, groupConf); 3 sinkRunnerMap.put(comp.getComponentName(), new SinkRunner(group.getProcessor()));
Configurables.configure(group, groupConf)會呼叫SinkGroupConfiguration.configure(Context context),該方法會獲取配置檔案中關於"processor."的屬性通過getKnownSinkProcessor方法獲取SinkProcessorType(是FailoverSinkProcessor或者是LoadBalancingSinkProcessor),並執行該SinkProcessor.configure(processorContext)進行例項化和配置。
1、如果SinkProcessor是LoadBalancingSinkProcessor,這是負載均衡的processor,會將channel中的傳送到指定的所有sink。通過配置選擇器selector來選擇何種方式的負載均衡,1.3有兩種:ROUND_ROBIN,輪詢,就是輪流向channel傳送資料;RANDOM,隨機選擇channel傳送資料。只有這個SinkProcessor有選擇器。
(1)configure(Context context)方法。先獲取選擇器selector的型別,預設是ROUND_ROBIN,輪詢;獲取backoff(是否使用推遲演算法,就是sink.process出問題後對這個sink設定懲罰時間,在此期間不再認為其可活動)的boolean值(預設false就是不啟用);根據型別構造相應的選擇器物件RoundRobinSinkSelector(實際上會構造一個RoundRobinOrderSelector)或者RandomOrderSinkSelector(實際上會構造一個RandomOrderSelector);然後例項化並設定sinks;最後對selector執行其configure(context)方法進行初始化。
A、RoundRobinSinkSelector的實際操作者是RoundRobinOrderSelector extends OrderSelector,它實現了createIterator()方法,該方法用來選出所有的sink及其可活動sinkl的索引封裝成一個SpecificOrderIterator<T>(indexOrder, getObjects())並返回,可以通過SpecificOrderIterator.hasNext()方法判斷是否還有sink,用next()方法獲取下一個sink。這樣可以按照索引遞增的順序依次獲取sink進行操作。SpecificOrderIterator主要是將兩個:一個是索引陣列,一個是sink列表。createIterator()方法程式碼如下:
1 @Override 2 public Iterator<T> createIterator() { 3 List<Integer> activeIndices = getIndexList(); //會獲取最新的活動的sink的索引列表 4 int size = activeIndices.size(); 5 // possible that the size has shrunk so gotta adjust nextHead for that 6 if (nextHead >= size) { //可能會出現sink的總數調整,所以總得getIndexList()並調整nextHead 7 nextHead = 0; 8 } 9 int begin = nextHead++; //注意++在後面說明是先賦值,在自加 10 if (nextHead == activeIndices.size()) { 11 nextHead = 0; 12 } 13 14 int[] indexOrder = new int[size]; 15 16 for (int i = 0; i < size; i++) { 17 indexOrder[i] = activeIndices.get((begin + i) % size); 18 } 19 20 return new SpecificOrderIterator<T>(indexOrder, getObjects()); //組成兩個陣列,大小都一樣 21 }
createIterator()方法中總會呼叫getIndexList()方法,因為可能有sink中斷,或者sinkgroup再調整等情況,使得sinkgroup中實際活動的sink數產生變化。nextHead始終指向下一個可活動的sink索引,這樣就可以保證輪詢。indexOrder是新的活動sink的索引陣列;getObjects()則返回所有sink的List,通過索引即可即可獲取此List中對應的sink。
B、RandomOrderSinkSelector的實際操作者是RandomOrderSelector extends OrderSelector,它實現了createIterator()方法:
1 public synchronized Iterator<T> createIterator() { 2 List<Integer> indexList = getIndexList(); 3 4 int size = indexList.size(); 5 int[] indexOrder = new int[size]; 6 //indexList由於remove操作會動態變化,所以一直使用indexList.size()會獲得實際大小 7 while (indexList.size() != 1) { 8 int pick = random.nextInt(indexList.size()); 9 indexOrder[indexList.size() - 1] = indexList.remove(pick); //取出pick位置的索引,這句總是使得indexOrder從後向前插入資料 10 } 11 12 indexOrder[0] = indexList.get(0); //將最後一個索引放入indexOrder 13 14 return new SpecificOrderIterator<T>(indexOrder, getObjects()); 15 }
這個方法最終只是將"可活動"的sink的順序按隨機的方式打亂了而已。注意的一個是總是呼叫indexList.size()動態獲取最新的大小;一個是indexOrder[indexList.size() - 1]始終是從後向前插入資料;一個是indexOrder[0] = indexList.get(0)將最後一個插入保證完整性。
A和B都是OrderSelector抽象類的子類,都只實現了createIterator()方法,對於getIndexList()和sink.process()方法出現錯誤的時的selector.informSinkFailed(sink)都是一樣的這兩個方法決定了出現問題的sink的推遲時間,如果要修改推遲時間可以重寫這兩個方法。當sink.process執行出問題時informSinkFailed會更新對應sink的FailureState(就三個數,sequentialFails記錄出錯次數、restoreTime記錄出錯後懲罰恢復時間(在此期間不再認為是可活動的sink,通過getIndexList()來過濾)、lastFail記錄上一次出錯時間,三個初始化都是0),maxTimeout預設是3000。看informFailure程式碼:
public void informFailure(T failedObject) { if (!shouldBackOff) { //不允許推遲 return; } FailureState state = stateMap.get(failedObject); long now = System.currentTimeMillis(); //獲取現在系統時間 long delta = now - state.lastFail; //獲取和上次失敗時間之間的時間間隔 long lastBackoffLength = Math.min(maxTimeout, 1000 * (1 << state.sequentialFails)); //獲取上一次要推遲的時間增量 long allowableDiff = lastBackoffLength + CONSIDER_SEQUENTIAL_RANGE; //CONSIDER_SEQUENTIAL_RANGE=2000 if (allowableDiff > delta) { if (state.sequentialFails < EXP_BACKOFF_COUNTER_LIMIT) { //說明是連續失敗 state.sequentialFails++; } } else { //說明期間曾重新正確process,重新計數 state.sequentialFails = 1; } state.lastFail = now; state.restoreTime = now + Math.min(maxTimeout, 1000 * (1 << state.sequentialFails)); }
這個informFailure方法有需要說明的地方:如何判斷是連續失敗?關鍵在於CONSIDER_SEQUENTIAL_RANGE這個變數是寬限期,等於2000,因為首先在懲罰時間內是“不被認可”的,是不被認為是可活動的,所以超過懲罰時間後自然會被重新認為是活動的,如果在“懲罰期+寬限期”=allowableDiff,(allowableDiff > delta), 內再次失敗說明是連續失敗,所以在失敗次數不超過EXP_BACKOFF_COUNTER_LIMIT(等於16)時就增加state.sequentialFails,一旦超過16就不再增加就是16;當allowableDiff <=delta成立時認為process至少一次成功但這次失敗,需要重置state.sequentialFails為1。
getIndexList()方法用來過濾“不被認可”的sink的索引。程式碼如下:
1 protected List<Integer> getIndexList() { 2 long now = System.currentTimeMillis(); 3 4 List<Integer> indexList = new ArrayList<Integer>(); 5 6 int i = 0; 7 for (T obj : stateMap.keySet()) { //是sink的集合 8 if (!isShouldBackOff() || stateMap.get(obj).restoreTime < now) { // 9 indexList.add(i); //將索引儲存 10 } 11 i++; 12 } 13 return indexList; 14 }
如果shouldBackOff=true則會返回的列表將是所有的sink的索引。stateMap.get(obj).restoreTime < now這句會過濾掉當前還處在懲罰時間內不被認可的sink的索引。
(2)start()方法會先啟動AbstractSinkProcessor.start()方法將所有的sink啟動(start()),然後啟動選擇器 selector.start()。
(3)process()方法遍歷sinkIterator一次獲取可活動的sink,執行sink.process()方法,如果有異常就跳出迴圈並執行失敗處理informSinkFailed,如果如果正常執行完畢就退出迴圈,迴圈就是找到第一個可以正常處理完畢的sink後退出。
2、如果SinkProcessor是FailoverSinkProcessor,這是容錯的processor,一旦有一個sink中斷可以使用其他的代替。
(1)setSinks(List<Sink> sinks)方法會將sinks列表中的所有sink,先呼叫父類的setSinks方法為的是可以執行父類的start和stop方法(子類中沒有實現這倆方法),然後放入Map<String, Sink> sinks中。
(2)configure(Context context)方法,會先獲取中斷時間的上限maxPenalty,然後將所有的sink及其對應的優先順序放入liveSinks(這是一個TreeMap,預設根據鍵值的自然順序排序儲存),最後activeSink = liveSinks.get(liveSinks.lastKey())獲取優先順序最高的sink作為活動sink。failedSinks = new PriorityQueue<FailedSink>()是一個儲存中斷sink的一個優先順序佇列。
(3)process()方法。迴圈執行如果failedSinks不為空並且記錄懲罰時間小於當前系統時間,則取出failedSinks的head然後嘗試執行getSink().process()如果能獲取到Rady狀態說明這個節點又重新建立了連結,則將其加入liveSinks,並重新獲取優先順序最高的sink作為activeSink,如果獲取的是backOff狀態則重新將其加入failedSinks,返回狀態,如果出現異常則cur.incFails()重新記錄懲罰時間並加入failedSinks,懲罰時間會動態變化,會根據失敗的次數增加(會和設定的比較取較大者)。如果failedSinks為空或者當前系統小於懲罰時間則使用當前活動的sink:activeSink.process()。
注:懲罰時間是動態變化的,會隨著連結失敗的次數而變化,失敗次數越多到下次使用它的間隔越長。
返回頂端sinkRunnerMap會在Application.startAllComponents方法中呼叫,放放到LifecycleSupervisor.supervise方法中去執行,最終會執行SinkRunner.start()方法來啟動元件。
1 public void start() { 2 SinkProcessor policy = getPolicy(); 3 4 policy.start(); 5 6 runner = new PollingRunner(); 7 8 runner.policy = policy; 9 runner.counterGroup = counterGroup; 10 runner.shouldStop = new AtomicBoolean(); 11 12 runnerThread = new Thread(runner); 13 runnerThread.setName("SinkRunner-PollingRunner-" + 14 policy.getClass().getSimpleName()); 15 runnerThread.start(); 16 17 lifecycleState = LifecycleState.START; 18 }
上述程式碼中的policy其實就是SinkProcessor,可能是LoadBalancingSinkProcessor、FailoverSinkProcessor、DefaultSinkProcessor三者中其中之一。policy.start()會啟動SinkProcessor。然後會啟動一個執行緒PollingRunner,該執行緒會始終執行policy.process()方法根據返回的狀態做一些統計。這就是我們自定義也要實現process方法的所在,及其需要返回Status的原因。
至此SinkGroup的介紹完結。