维护了rangeserver进程的全局变量.其中有几个队列需要区分:
global::maintenance_queue:MaintenanceQueue类指针,表示 rangeserver的后台维护任务队列;
global::work_queue:MetaLog::EntityTask类指针数组,表示一组针对rangeserver的metalog entity的任务。
这两个队列是有联系的,后者可以作为前者中的一个元素,即可将针对一组metalog entity的操作作为一项任务添加到前者中,例如以下代码(MaintenanceScheduler.schedule片段):
MaintenanceTaskWorkQueue *task = 0;
{
ScopedLock lock(Global::mutex);
if (!Global::work_queue.empty())
task = new MaintenanceTaskWorkQueue(3, 0, Global::work_queue);
}
if (task)
Global::maintenance_queue->add(task);
最新版本的CellStore类。
2.1. 成员函数
2.1.1. load_block_index
声明:void load_block_index();;
功能:对于一个特定的range,加载其在CellStore文件中的所有索引块。每个索引块将记录offset和key(该索引中的最大key),多个索引块形成一个数组。索引快中的key对应的实际数据将被统一保存在一个StaticBuffer中。即block_index占用的内存为数组内存+StaticBuffer内存。在rangeserver的内存统计中添加block_index的占用内存量。
2.1.2. open
声明:void open(const String &fname, const String &start_row, const String &end_row, int32_t fd, int64_t file_length, CellStoreTrailer *trailer);
功能:在文件描述符fd对应的CellStore文件中,获取start_row和end_row对应的range的索引块。在rangeserver的内存统计中添加一个CellStoreV6和CellStoreInfo对象的内存占用量。注意:该函数将置成员变量m_64bit_index为false。
该类只有一个静态函数open。该函数打开指定名称的CellStore文件,获取文件的trailer,从trailer中读取version。针对不同的版本,创建不同的CellStoreTrailer对象和CellStore对象指针。由于当前使用的version是6,所以创建CellStoreTrailerV6对象和CellStoreV6对象指针。已经打开文件的trailer将反序列化到CellStoreTrailer对象,也就是说该对象持有的就是当前文件的trailer。新建一个CellStoreV6对象,并加载block_index,然后返回其指针。
一个access group的全名为:table_name + "[" + start_row + ".." + end_row + "](" + access_group_name + ")"。
该类包含3个子类:CellStoreMaintenanceData、MaintenanceData、Hints。
CellStoreMaintenanceData表示每个CellStore文件的维护信息,并有成员变量指向该CellStore文件。
MaintenanceData表示该access group的维护信息,包含其对应的CellStore文件的维护信息,并有成员变量指向该access group。
Hints记录了该access group的名称、latest_stored_revision、disk_usage和files,见AccessGroupHintsFile类。
每个access group对象都有一些状态变量,access group对象构造后会马上需要这些变量。这些变量能够提升系统性能,但是计算代价比较昂贵,因为它需要在METADATA表中扫描父rang对应的记录,并且需要打开和加载组成该access group的所有CellStore文件的块索引。如果允许在集群启动时计算这些变量,然后将其保持在名为hints的磁盘文件中,并且在access group对象构造前读取hints文件,这将是一种比较划算的方案。
每个range将会有一个hints文件,组成该range的每个access group将在文件中对应一块内容。每个range的hints文件路径如下:
/hypertable/tables/<table-id>/default/<md5-end-row>/hints
该文件是YAML格式,包含了一个版本号、start row、end row和每个access group的概要信息,如下所示:
Version: 2
Start Row: <start-row>
End Row: <start-row>
Access Groups: {
ag_name: {
LatestStoredRevision: <revision>,
DiskUsage: $bytes,
Files: $file_list
}
...
}
每个access group的概要信息描述如下:
LatestStoredRevision:已经写入到access group的CellStore文件中的cell的最新的revision。集群重启后回放commit log时,access group能安全的删除commit log中revision小于等于该值的所有的cell。
DiskUsage:access group的CellStore文件所占据的磁盘空间,用于判断一个range是否需要分裂。
Files:access group的CellStore文件名称列表,以分号分隔。access group对象构造后,该属性暂不需要。但是如果METADATA表损坏,或者hyperspace丢失时,它能用于灾难恢复。
将一个range的所有access group的概要信息写入一个hints文件是为了提升启动性能,即在启动时,不管有多少个access group,只对一个range读取一次hints文件。
一个range的全名为: table_name + "[" + start_row + ".." + end_row + "]"。
rang对象构造时主要完成的任务:
1)计算METADATA和用户表的range分裂大小,前者如果没有配置,则默认和后者一致;
2)计算split_off为hight还是low;
3)读取hints文件;
4)创建每个access group对象,并将其记录到m_access_group_map、m_access_group_vector和m_column_family_vector中。m_access_group_map的key为access group名称,value为access group指针,m_column_family_vector的下标为cf的编号,值为access group指针。
抽象类,用于在一个range集合中删除一个range,或者改变range的startrow/endrow。
管理一个表中处于激活状态的所有range。
8.1. 成员函数
8.1.1. find_containing_range
声明:bool find_containing_range(const String &row, RangePtr &range, String &start_row, String &end_row);
功能:寻找给定的row属于的range。
参数:row给定的row。后三个都是输出参数,分别表示找到的range,以及range的start row和end row。
返回值:如果找到返回true,否则返回false。
统计应用程序的负载,包括:scan、update和sync。该类用一个时间周期初始化,即表示统计的间隔周期。大部分最新收集的统计信息存储在m_computed成员变量中,当前周期正在收集的统计信息存储在m_running成员变量中。
RangeServer统计类,用于RangeServer的监控功能。
设置range维护任务的优先级。其包含了一个嵌套类MemoryState,表示当前的内存状态。MemoryState有三个成员变量:limit表示rangeserver的内存限制;balance表示rangeserver当前使用的内存量;needed表示rangeserver当前需求的内存量。MemoryState有两个成员函数:decrement_needed表示减少仍然需要的内存量;need_more表示是否仍然需要更多的内存量。
下述几个成员函数在操作range时,都是针对非busy状态的range。
11.1. 成员函数
11.1.1. Schedule_initialization_operation
声明:void schedule_initialization_operations(std::vector<RangeData> &range_data, int32_t &priority);
功能:
修改未初始化的range的优先级。
遍历range_data。如果range未初始化,置成员变量m_uninitialized_ranges_seen为true;如果range处于busy状态,或者range的优先级不为0,则忽略本range,开始处理下一个range;如果range未初始化,则置其优先级为priority,并将priority加1.
参数:
Range_data:range集合;
Priority:初始优先级。
11.1.2. Schedule_inprogress_operation
声明:bool schedule_inprogress_operations(std::vector<RangeData> &range_data, MemoryState &memory_state, int32_t &priority, String *trace);
功能:对指定的一组range中,维护信息中:字段state为RELINQUISH_LOG_INSTALLED或者SPLIT_LOG_INSTALLED的range,释放其占用的内存,并且改变其维护信息中的维护标记和优先级。
遍历range_data,对不处于busy状态的range执行下列操作之一:
a)如果range状态(state)为RELINQUISH_LOG_INSTALLED,则在range的维护标记(maintenance_flags)上追加RELINQUISH;
b)如果range状态为SPLIT_LOG_INSTALLED或者SPLIT_SHRUNK,则在range的维护标记上追加SPLIT。
上述两个操作都会将in_progress置为true。如果in_progress为true,执行下列操作:
a)置range的优先级为priority,并将priority加1;
b)如果range状态为RELINQUISH_LOG_INSTALLED或者SPLIT_LOG_INSTALLED,从rangeserver当前需求的内存量中减去该range的内存占用量(包括每个ag的内存分配量,以及ag中每个CellStore文件的bloom_filter和block_index的内存占用量)。
参数:
Range_data:range集合;
memory_state:当前rangeserver的内存状态;
Priority:初始优先级。
返回值:true表示rangeserver当前需求的内存量没有满足,反之为false。
11.1.3. Schedule_split_ and_relinquishes
声明:bool schedule_split_and_relinquishes(std::vector<RangeData> &range_data, MemoryState &memory_state, int32_t &priority, String *trace);
功能:对指定的一组range中,维护信息中:优先级为0, relinquish或者needs_split字段为true的range,释放其占用的内存,并且改变其维护信息中的维护标记和优先级。
遍历range_data,统计不处于busy状态或优先级为0的range的磁盘和内存占用量。前者为range中所有ag的磁盘预估量(disk_estimate)总和,后者不但包含每个ag的内存分配量(mem_allocated),还包含每个ag中每个CellStore文件的bloom_filter和block_index的内存占用量。
如果range没有出现RANGESERVER_ROW_OVERFLOW类型的错误,执行下列两种操作之一:
a)如果range需要relinquish,则在range的维护标记上追加RELINQUISH;
b)如果range需要split,并且不是root表的range,则在range的维护标记上追加SPLIT。
上述两个操作都会置range的优先级为priority,并将priority加1。还会从rangeserver当前需求的内存量中减去该range的内存占用量。
参数:
Range_data:range集合;
memory_state:当前rangeserver的内存状态;
Priority:初始优先级。
返回值:true表示rangeserver当前需求的内存量没有满足,反之为false。
11.1.4. Schedule_ necessary_compactions
声明:bool schedule_necessary_compactions(std::vector<RangeData> &range_data, CommitLog *log, int64_t prune_threshold, MemoryState &memory_state, int32_t &priority, String *trace);
功能:在指定的一组range中,对满足commit log清理条件的range,以及有compaction需求的range,追加compac维护标记,并根据不同的compaction类型,为其ag追加不同的compaction维护标记,还改变其维护信息中的优先级。如果当前rangeserver有内存需求,释放其ag占用的内存。
获取commit log的frament的统计信息到comulative_size_map。
遍历range_data,对不处于busy状态的range遍历其ag集合。如果能在comulative_size_map中发现大于等于ag的earliest_cached_revision的元素,则表明commit log中有数据能compact到该ag。如果comulative_size_map中该元素(也就是revision)累积的fragment大小超过了commit log清理阈值,并且ag已使用的内存量也大于0,则在range维护标记上追加COMPACT,在ag维护标记上追加COMPACT_MINOR。如果当前rangeserver还需要更多的内存,则从rangeserver当前需求的内存量中减去已经分配给该ag的内存量,并分别在range和ag的维护标记上追加MEMORY_PURGE和MEMORY_PURGE_SHADOW_CACHE。如果range的优先级为0,则置其为priority,并将priority加1。
遍历range_data,对不处于busy状态的range遍历其ag集合。如果ag的维护标记为0,执行下列操作之一:
a)如果range需要compaction(compaction_type_needed),则在ag的维护标记上追加range的compaction类型。如果当前rangeserver还需要更多的内存,则从rangeserver当前需求的内存量中减去已经分配给该ag的内存量;
b)如果ag需要进行gc(gc_needed),则在ag的维护标记上追加COMPACT_GC;
c)如果ag不是in_memory模式,并且ag已使用的内存大于Global::access_group_max_mem(默认1G),则在ag的维护标记上追加COMPACT_MINOR。如果当前rangeserver还需要更多的内存,则从rangeserver当前需求的内存量中减去已经分配给该ag的内存量,并分别给range和ag的维护标记追加MEMORY_PURGE和MEMORY_PURGE_SHADOW_CACHE;
d)如果ag需要进行merge操作(needs_merging),则在ag的维护标记上追加COMPACT_MERGING。
上述操作执行时,都会在range的维护标记上追加COMPACT,并且如果range的优先级为0,则置其为priority,并将priority加1.
参数:
Range_data:range集合;
memory_state:当前rangeserver的内存状态;
Priority:初始优先级。
Log:commit log
返回值:true表示rangeserver当前需求的内存量没有满足,反之为false。
11.1.5. purge_cellstore_indexes
声明:bool purge_cellstore_indexes(std::vector<RangeData> &range_data, MemoryState &memory_state, int32_t &priority, String *trace);
功能:对指定的一组range中,处于非split状态,并且其bloom_filter或者block_index已经占用了内存的range,释放其bloom_filter和block_index占用的内存,并且改变其维护信息中的维护标记和优先级。
遍历range_data,判断其中的每个range是否同时满足下列条件:a)不处于busy状态或split状态;b)range包含的任一CellStore文件的bloom_filter或者block_index占用的内存量大于0。如果满足,则执行下列操作:
a)在range和ag的维护标记上追加MEMORY_PURGE,在CellStore的维护标记上追加MEMORY_PURGE_CELLSTORE;
b)从rangeserver当前需求的内存量中减去CellStore文件的bloom_filter和block_index占用的内存量。
C)如果range的优先级为0,则置其为priority,并将priority加1。
参数:
Range_data:range集合;
memory_state:当前rangeserver的内存状态;
Priority:初始优先级。
返回值:true表示rangeserver当前需求的内存量没有满足,反之为false。
11.1.6. compact_cellcaches
声明:bool compact_cellcaches(std::vector<RangeData> &range_data, MemoryState &memory_state, int32_t &priority, String *trace);
功能:指定的一组range中,非split状态的range,如果其任一ag处于非major_compaction状态,并且已经占用了内存,则释放其ag占用的内存,并且改变其维护信息中的维护标记和优先级。
遍历range_data,判断其中的每个range是否同时满足下列条件:a)不处于busy状态或split状态;b)range包含的任一ag不处于major compaction状态,并且占用的内存量大于0,还要不是in_memory状态。如果满足,则执行下列操作:
a)在range的维护标记上追加COMPACT和MEMORY_PURGE,在ag的维护标记上追加COMPACT_MINOR和MEMORY_PURGE_SHADOW_CACHE;
b)从rangeserver当前需求的内存量中减去ag占用的内存量;
c)如果range的优先级为0,则置其为priority,并将priority加1。
参数:
Range_data:range集合;
memory_state:当前rangeserver的内存状态;
Priority:初始优先级。
返回值:true表示rangeserver当前需求的内存量没有满足,反之为false。
低内存模式下,维护任务将通过此类释放内存。
该类主函数为prioritize。该函数声明为void prioritize(std::vector<RangeData> &range_data, MemoryState &memory_state, int32_t &priority, String *trace)。主体思路为:
将集合range_data分为四个子集合:range_data_root、range_data_metadata、range_data_system、range_data_user。
对range_data_root、range_data_metadata、range_data_system集合分别执行下列操作。
1)对每个处于RELINQUISH_LOG_INSTALLED或者SPLIT_LOG_INSTALLED状态的的range,释放其占用的内存(分配给每个ag的,以及每个CellStore的bloom_filter和block_index内存),并且在其维护标记中追加RELINQUISH或者SPLIT,并增加range优先级。如果rangeserver当前还需要内存,则转步骤2;
2)对每个处于relinquish或者need_split状态的的range,释放其占用的内存(分配给每个ag的,以及每个CellStore的bloom_filter和block_index内存),并且在其维护标记中追加RELINQUISH或者SPLIT,并增加range优先级。如果rangeserver当前还需要内存,则转步骤3;
3)对每个满足commit log清理条件的range,以及有compaction需求的range,追加compac维护标记,并根据不同的compaction类型,为其ag追加不同的compaction维护标记,还改变其维护信息中的优先级,并增加range优先级。如果当前rangeserver有内存需求,释放其占用的内存(分配给每个ag的内存);
4)增加未初始化的range的优先级。
对range_data_user集合执行下列操作。
1)对每个处于RELINQUISH_LOG_INSTALLED或者SPLIT_LOG_INSTALLED状态的的range,释放其占用的内存(分配给每个ag的,以及每个CellStore的bloom_filter和block_index内存),并且在其维护标记中追加RELINQUISH或者SPLIT,并增加range优先级。如果rangeserver当前还需要内存,则转步骤2,否则转步骤3;
2)对每个处于relinquish或者need_split状态的的range,释放其占用的内存(分配给每个ag的,以及每个CellStore的bloom_filter和block_index内存),并且在其维护标记中追加RELINQUISH或者SPLIT,并增加range优先级;
3)如果当前读负载高,则首先紧缩每个range的CellCache(compact_cellcache)。如果rangeserver当前还需要内存,再紧缩每个range的block_cache。如果rangeserver当前还需要内存,最后清理每个range的CellStore的索引所占的内存(purge_cellstore_index)。转步骤5;
4)如果当前写负载高,则首先紧缩每个range的block_cache。如果rangeserver当前还需要内存,再清理每个range的CellStore的索引所占的内存(purge_cellstore_index)。如果rangeserver当前还需要内存,最后紧缩每个range的CellCache(compact_cellcache);
5)对每个满足commit log清理条件的range,以及有compaction需求的range,追加compac维护标记,并根据不同的compaction类型,为其ag追加不同的compaction维护标记,还改变其维护信息中的优先级,并增加range优先级。如果当前rangeserver有内存需求,释放其占用的内存(分配给每个ag的内存);
6)修改未初始化的range的优先级。
存放周期性维护任务的一个队列,每个维护任务是类MaintenanceTask或其子类的一个对象指针。维护任务依次按其级别(level)、优先级(priority)和开始时间(start_time)进行排序,级别越高,优先级越高,开始时间越早的任务越靠前。
维护线程的数量可配置,默认在CPU核数和磁盘个数*3/2之间选择最大值。
该类有三个嵌套类:LtMaintenanceTask、MaintenanceQueueState、Worker。LtMaintenanceTask类描述MaintenanceTask在队列中的排序规则。MaintenanceQueueState类维护队列状态,其内部维护了一个任务队列,并且维护了一个range集合。Worker类是一个函数对象,表示维护任务队列的工作线程,用于从队列中调度维护任务。
该类实例化时将会创建指定数量的线程,多个线程共享一个MaintenanceQueueState对象m_state。工作线程将从m_state.queue中获取一个维护任务(MaintenanceTask)对象指针,然后调用其execute函数去执行实际的操作。
14. class MaintenanceTaskWorkQueue : public MaintenanceTask
此类本身不是一个队列,也是一项维护任务,但是由于其内部有一个需要维护的MetaLog::EntityTaskPtr集合,所以名称中就有了队列的称号。该类用于metalog的维护。
15. MaintenanceScheduler
后台维护任务调度类。该类的主函数为schedule,其主体逻辑如下:1)重新计算并获取rangeserver内存的各项度量指标;
2)删除Global::maintenance_queue中与range相关的维护任务;
3)获取rangeserver上所有处于激活状态的range和可以安全删除的transfer log,分别放入ranges和remove_ok_logs,并获取ranges中每个range最新的维护信息;
4)遍历ranges,得到的每种range中最小的revision(earlist_cached_revision),并清理各种类型的commit log;
5)遍历ranges,统计并打印rangeserver的内存统计结果和分配比例;
6)根据统计得到的内存信息,为ranges中的每个range分配维护任务标记和优先级,即设置range维护信息的maintenance_flags和priority字段;
7)遍历ranges,根据每个range的状态,创建相应的维护任务,将其加入到Global::maintenance_queue。例如:range状态为RangeState::RELINQUISH_LOG_INSTALLED时,将创建MaintenanceTaskRelinquish对象。如果是非初次执行本函数,将会首先基于优先级对ranges进行排序,然后才会进行遍历;
8)如果存在还未完成的MaintenanceTaskWorkQueue,即metalog处理任务,也将其加入到Global::maintenance_queue。
16. Class TableInfoMap
管理一个rangeserver中处于激活状态的range集合和可被安全删除的commit log集合。range集合其实是一个map,表示tableid到tableinfo的映射, tableinfo包含了一组处于激活状态的range。
维护任务调度器(maintenance scheduler)负责删除commit log,以及链接的transfer log。删除前,需要获取激活状态的range集合,计算每个range的统计信息。如果一个commit log不再包含能被任何range compact的数据,则其可以安全删除。
然而,加载range时需要两步操作:1)链接range的transfer log到commit log;2)持久化range到metalog。如果维护任务调度器恰好在这两步之间执行commit log的删除操作,则可能在range成功加载之前,误删一个新加入的transfer log。
为了避免这个问题,引入了一个新类:MetaLogEntityRemoveOkLogs。该类包含了一组虽然已经链接到commit log,但是可以被安全删除的transfer log。通过TableInfoMap::add_staged_range方法,可将该类的一个实体(entity)持久化到metalog中作为一个新的range实体。通过TableInfoMap::get_ranges方法,不但可以获取当前处于激活状态的range集合的一致性的快照,还可获取transfer log集合。
17. Class ConnectionHandler
负责分发针对rangeserver的网络通信事件,即将针对rangeserver的不同请求交由对应的ApplicationHandler子类进行处理。例如:当rangeserver接收到compact请求时,将由此类派发请求到RequestHandlerCompact类进行处理。该类持有一个rangeserver指针、一个应用程序队列(ApplicationQueue)指针。
18. class HandlerFactory : public ConnectionHandlerFactory
rangeserver的分发器构造类,即为rangeserver构造ConnectionHandler对象。
19. Class TimerHandler:public DispatchHandler
目前主要用于定时的处理维护队列调度。每次定时触发时,它会执行handle方法,其中会添加一个RequestHandlerDoMaintenance对象到rangeserver的应用程序队列。它也会执行低内存检测,如果发现低内存状态,将触发维护优先级算法去积极的释放内存。它也会暂停rangeserver的应用程序队列,以使落后的维护线程赶上。
19.1. 成员函数
19.1.1. handle
声明:void TimerHandler::handle(Hypertable::EventPtr &event);功能:
1)检测rangeserver的应用程序队列是否处于暂停状态,如果是,执行步骤2,否则执行步骤3;
2)如果满足以下3个条件之一,则重启rangeserver的应用程序队列:a、之前已经处于低内存模式,但是现在已经不处于低内存模式;b、暂停时间已经超过最大的等待时间(默认两分钟);c、m_restart_generation<=Global::maintenance_queue->generation()。转向步骤6;
3)检测当前是否处于低内存状态,如果是,执行步骤4,否则执行步骤5;
4)如果m_low_memory_mode为true,则暂停rangeserver的应用程序队列,否则置m_low_memory_mode为true。转向步骤6;
5)置m_low_memory_mode为false。检测commit log大小如果超过阈值,则暂停rangeserver的应用程序队列。
6)如果要处理的事件类型为定时器事件(TIMER),则判断是否要进行后台维护任务,如果是,则添加一个RequestHandlerDoMaintenance对象到rangeserver的应用程序队列,并置m_schedule_outstanding标记为true,否则添加一个定时器事件。
20. Class DefinitionRangeServer : public Metalog::Definition
Rangeserver的metalog类。
21. class MetaLogEntityRange : public MetaLog::Entity
rangeserver的metalog由一组entity组成。该类的每个对象记录一个range的状态。
22. class MetaLogEntityRemoveOkLogs: public MetaLog::Entity
rangeserver的metalog由一组entity组成。该类的每个对象用于追踪可被安全删除的transfer log。
23. class MetaLog::EntityTask : public MetaLog::Entity
在rangeserver的metalog entity上执行的任务。
24. Class RangeServer
Rangeserver进程的主类,将在进程的main函数中被实例化。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轮的逻辑。
24.1. 成员函数
24.1.1. Local_recover
声明:void local_recover();
功能:
1)读取rangeserver所有的metalog entity到entities集合。如果集合不为空,则执行步骤2-4,否则执行步骤5;
2)将集合中属于MetaLog::EntityTask 的entity添加到global::work_queue,并从集合中去除RangeState::PHANTOM状态的entity;
3)如果entities集合包含root表的entity,则对每个entity执行回放和加载range操作(replay_load_range函数)。创建一个CommitLogReader对象root_log_reader,其将对应root表的所有fragment。使用root_log_reader回放fragment(replay_log函数),即将fragment中的cell添加到对应的range,并获取fragment对应的transfer log。获取root表处于激活状态的所有range,判断每个range的状态,如果处于RangeState::SPLIT_LOG_INSTALLED、RangeState::SPLIT_SHRUNK、RangeState::RELINQUISH_LOG_INSTALLED状态之一,则构建相应的维护任务对象,并将其添加到维护任务数组(maintenace_tasks)中。将root表对应的TableInfoMap对象合并到m_live_map。创建root表对应的commit log对象Global::root_log,用于接收之后对于该表的写入。将数组maintenace_tasks中的维护任务添加到Global::maintenance_queue中,发出回放完成的通知。注意:这里的root_log_reader用于处理已有的root类型的commit log文件,Global::root_log将用于处理之后新的root类型的commit log文件;
4)对METADATA表、SYSTEM表和USER表执行类似于root表的操作。然后转向6;
5)新建ROOT表、METADATA表、SYSTEM表和USER表对应的commit log对象Global::root_log、Global::metadata_log、Global::system_log、Global::user_log,并且通知这四种log已经回放完成;
6)如果Global::remove_ok_logs为空,则新建它,并将第三步和第四步中产生的可以安全移除的transfer log插入其中,并持久化其到metalog中。
参数:
24.1.2. replay_load_range
声明:void replay_load_range(TableInfoMap &replay_map, MetaLogEntityRange *range_entity);
功能:回放和加载range_entity对应的range,并在replay_map中记录range_entity对应表的TableInfoMap;
参数:
24.1.3. replay_update
声明:void replay_update(TableInfoMap &replay_map, const uint8_t *data, size_t len);
功能:将data指向的cell block解析为一个个的cell,然后添加到对应表的range中。
参数:
24.1.4. replay_log
声明:void replay_update(TableInfoMap &replay_map, CommitLogReaderPtr &log_reader);
功能:将rangeserver的commit log逐块的添加到对应表的range中,每块的添加将调用replay_update函数。
参数: replay_map:需要回放的table,如果发现回放的块所属表不在其中,则本块数据不进行回放;log_reader:用来读取需要回放的commit log块。
24.1.5. do_maintenance
声明:void do_maintenance()
功能:执行rangeserver的后台维护任务。清理过期的scanner(默认过期时间30分钟),判断当前rangeserver是否处于低内存状态,然后调用m_maintenace_scheduler.schedule函数进行后台维护。该函数最后将调用m_timer_handler->maintenance_scheduled_notify设置定时器,该定时器触发时最终又会回到本函数,从而实现了反复的后台维护任务调度。