天天看点

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轮的逻辑。