Flume-NG原始碼閱讀之SinkGroups和SinkRunner

玖瘋發表於2014-05-25

  在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的介紹完結。

相關文章