天天看點

Hypertable源碼解讀之RangeServer啟動過程

Main函數中首先建立一個ConnectionManage對象,用于維護與rangeserver通訊的TCP連接配接。再建立一個預設大小為50的應用程式隊列(ApplicationQueue),用于存放針對rangeserver的請求事件。然後連接配接hyperspace,建立HyperSpace::Session對象。最後用ConnectionManage對象指針、應用程式隊列指針和HyperSpace::Session對象為參數執行個體化rangeserver。

注意:main函數在建立應用程式隊列時,已經預設建立了50個線程。這些線程值守在應用程式隊列上,一旦發現隊列中有請求事件,将會馬上予以處理。

Rangeserver對象構造時,會擷取相關的配置參數,請留意以下配置項:

Hypertable.RangeServer.MaintenanceThreads:背景維護線程數量。預設在CPU核數和磁盤個數*3/2之間選擇最大值;

Maintenance.Interval:背景維護任務的排程間隔,預設30秒;

Hypertable.RangeServer.AccessGroup.CellCache.ScannerCacheSize:該值最小需要為10000;

Hypertable.RangeServer.MemoryLimit和Hypertable.RangeServer.MemoryLimit.Percentage:rangeserver程序的記憶體限制。兩個效果類似,但是第一個優先級高,即如果配置了第一個,則會忽略第二個;

Hypertable.RangeServer.QueryCache.MaxMemory:該值最大隻能為程序記憶體限制的五分之一;

DfsBroker.Timeout:rangeserver通路DfsBroker的逾時時間。如果預設,則為Hypertable.Request.Timeout(預設6分鐘);

Hypertable.RangeServer.CommitLog.DfsBroker.Host:rangeserver操作commit log時所需的DfsBroker機器。如果預設,則等同于DfsBroker.Host(預設值localhost);

Hypertable.RangeServer.CommitLog.PruneThreshold.Max/Min/Max.MemoryPercentage:commit log需要被清理的最大和最小門檻值。即commit log小于最小值時,背景維護線程将不考慮清理,但是大于最大值時,必須予以清理。Max和Max.MemoryPercentage作用類似,但是前者優先級高于後者。後者參照的基數是實體記憶體。

Rangeserver對象構造時,會建立以下重要的對象:

Global::load_statistics:LoadStatistics類執行個體,用于統計rangeserver程序的scan、update和sync資訊,預設統計間隔時間為30秒,即為背景維護任務排程的間隔時間;

m_stats:StatsRangeServer類執行個體,用于統計rangeserver程序的監控資訊;

Global::memory_tracker:MemoryTracker類執行個體,用于統計rangeserver程序的記憶體使用資訊;

Global::log_dfs和Global::dfs:DfsBroker::Client類執行個體,rangeserver通路DfsBroker的用戶端對象;

Global::maintenance_queue:MaintenanceQueue類執行個體,rangeserver的背景維護任務隊列。該對象建立後,也會建立多個線程值守在該隊列上,一旦發現隊列中有維護任務,将會馬上予以執行;

m_live_map:TableInfoMap類執行個體,用于管理該rangeserver中處于激活狀态的range集合和可被安全删除的commit log集合;

Global::master_client和m_master_client:MasterClient類執行個體,兩者是同一類對象,通路master的用戶端;

m_maintenance_scheduler:MaintenanceScheduler類執行個體,背景維護隊列排程器,負責排程rangeserver的背景維護隊列(Global::maintenance_queue);

m_timer_handler:TimerHandler類執行個體,用于定時的啟動背景維護隊列排程器。

兩個事件分發器(ConnectionHandler對象):分别用于分發來自master的事件和非master的事件。所有針對rangeserer的事件都将被分發器添加到rangeserer的應用程式隊列中。

注意,此時rangeser已經擁有了兩個隊列:一個應用程式隊列(m_app_queue),一個背景維護任務隊列(Global::maintenance_queue)。

當Global::master_client和m_master_client對象建立後,相當于rangeserver已經向master進行了報到。Master将周期性的采集rangeserver的狀态資訊用于監控顯示,預設周期30秒,通過Hypertable.Monitoring.Interval配置項設定。來自master的采集請求會由rangeserver的事件分發器加入到應用程式隊列(m_app_queue),應用程式隊列的值守線程擷取到該請求後,會調用RequestHandlerGetStatistics對象的run函數,該函數中最終會調用rangeserver的get_statistics函數,是以rangeserver日志中會周期性的看到“Entering get_statistics()”和“Exiting get_statistics()”字樣。

接下來,構造函數會調用local_recover成員函數加載所有的range,并回放(replay)commit log。然後調用m_timer_handler.start設定一個即刻觸發的定時器,觸發對象即為自身。觸發後會調用m_timer_handler.handle函數,其會在rangeserver的應用程式隊列中添加一個RequestHandlerDoMaintenance對象。應用程式隊列的值守線程擷取到該對象後,會調用該對象的run函數,函數中實際隻調用了rangeserver對象的do_maintenance函數。該函數執行完畢前會再設定一個定時器,觸發時最終又會回到本函數,進而實作了反複的背景維護任務排程。

在local_recover和do_maintenance中,都有可能建立維護任務,并添加其到背景維護任務隊列(Global::maintenance_queue)中。此時,隊列上的值守線程将會執行具體的維護任務。local_recover隻在rangeserver程序啟動時執行一次,但是do_maintenance通過定時器觸發将會周期性的執行。

do_maintenance中主要執行的是m_maintenance_scheduler的schedule函數。當第二次觸發do_maintenance時,schedule函數會為每個未初始化的range建立一個MaintenanceTaskDeferredInitialization對象,并添加對象指針到背景維護任務隊列(Global::maintenance_queue)。此時rangeserver對象應該已經構造完畢。Global::maintenance_queue上的值守線程拿到MaintenanceTaskDeferredInitialization時,會調用其execute函數,繼而轉調range的deferred_initialization函數。

range的deferred_initialization函數會調用load_cell_stores函數,是以在rangeserver日志中會看到“Loading cellstores for range_name”字樣。load_cell_stores函數從METADATA中擷取該range包含的所有ag,以及ag中的files和nextcsid。對于每個file将會打開一個CellStore對象(CellStoreFactory::open),并将其加入ag的m_stores集合中,此時在rangeserver日志中會看到“Loading  CellStore filename”字樣。當一個range的所有檔案加載完畢後,rangeserver日志中會看到“Finished Loading  cellstores  for  range_name”字樣。

每個CellStore檔案加載時,都會記錄其占用的記憶體和磁盤大小。這些值後續會追加到ag和range的維護資訊中,進而決定後續對range的維護任務。

注意:deferred_initialization函數結束前會置m_initialized為true,這會引起後續的連鎖反應。因為range維護資訊中的字段initialized源自m_initialized,是以下一輪在do_maintenance中擷取range的維護資料時,會得到為true的initialized字段。而initialized字段值會影響後續的針對range的維護任務的配置設定以及優先級的設定。

低記憶體模式處理

m_timer_handler每次處理定時器事件時,會檢查rangeserveer目前是否處于低記憶體狀态(已使用的記憶體大于記憶體限制)。

假設m_timer_handler在第M次時,首次檢測到低記憶體狀态,則置狀态變量m_low_memory_mode為true,并在應用程式隊列中添加一個RequestHandlerDoMaintenance對象。如前所述,應用程式隊列的值守線程獲最終會調用rangeserver對象的do_maintenance函數。該函數會從m_timer_handler中獲知到低記憶體狀态,并将此狀态傳遞到rangeserver的維護任務排程器對象m_maintenance_scheduler。該對象在緊跟其後的schedule函數中計算目前需要釋放的記憶體量(超限記憶體+記憶體限制*10%,10%是通過LowMemoryLimit.Percentage配置項指定的),并将其儲存到memory_state.needed中。如果此時維護任務隊列已滿,就結束本次維護任務,否則會通過m_prioritizer->prioritize調整目前range的優先級。

Prioritize函數中,會調整記憶體可以被釋放的range的維護辨別和優先級,并會從memory_state.needed中預先減去該range占用的記憶體,注意此時記憶體還沒有真正的釋放。随後,針對調整後優先級大于0的range,根據其維護辨別添加相應的維護任務到Global::maintenance_queue,該隊列的值守線程馬上啟動第M輪的維護任務。此時,do_maintenance函數已經準備結束了,目前結束前它不會忘記設定下一輪的定時器……

下一輪(M+1)的m_timer_handler定時器處理事件中,如果檢測到rangeserveer仍處于低記憶體狀态,則會暫停rangeserver的應用程式隊列,日志中會看到“Application queuePAUSED due to low memory”字樣,随後也會在應用程式隊列中添加一個RequestHandlerDoMaintenance對象。雖然應用程式隊列已經暫停,但是由于RequestHandlerDoMaintenance屬于緊急任務,是以該對象可被值守線程擷取。如果此時rangeserveer仍處于低記憶體狀态,則rangeserver的do_maintenance函數如上次處理邏輯。

再下一輪(M+2)的m_timer_handler定時器處理事件中,如果檢測到rangeserveer應用程式隊列已暫停,則會試圖重新開機它,此時日志中會看到“Restarting application queue”字樣。重新開機條件為三者之一:1)雖然之前是低記憶體模式,但是現在檢測已經不是低記憶體模式;2)暫停時間已經超過了最長等待時間(Hypertable.RangeServer.Maintenance.MaxAppQueuePause,預設兩分鐘);3)。本輪一般情況下不會再添加RequestHandlerDoMaintenance對象,而是直接設定下一輪的定時器。 

後面的定時器處理事件,将類似于M+1和M+2輪的邏輯。