图1 TaurusDB整体架构
图2 slice功能组件
从图2可知,TaurusDB的存储层,不单单只做存储相关的工作,也需要大量的算力,比如consolidation生成特定数据页、compation回收旧版本数据、BufferPool缓存热点数据页等任务。为了能加快这些任务的高效执行,我们首先能想到的就是能够并行执行这些任务,也就是采用多线程技术处理的方式,增加处理器单元的吞吐能力,从而提高存储端的执行效率。
2.线程池化设计思想
2.1线程为什么需要池化
首先,线程是稀缺的资源,如果频繁创建和销毁线程的开销是可观的,所占用的时间可能多于实际任务的执行;且当需要执行任务时,都去创建一个对应的线程去处理,那么服务器的资源(比如地址空间和内核参数)很快就会被耗尽,导致而导致OOM问题。
其次,通过事先创建好一定数量的线程并置于公共池之中,这样当有任务需要执行时,只需从公共池取一个线程执行当前的任务即可,待任务结束后,此线程又可以执行其他任务或处于休眠状态,等待下一次被调度,达到线程资源重复使用的目的。
2.2线程池如何管理
为了能有效的管理多线程,TaurusDB存储端采用了如图3的线程池模型。
图3 线程池模型
ThreadPool: 主要负责控制线程池的大小、状态变更、线程的创建、销毁、调度策略的选取;
Scheduler:负责具体任务的接收、被调度的顺序,并触发任务的执行;
Worker:负责具体任务的执行;
Monitor:负责监控线程执行任务时是否出现异常,以及异常告警,比如线程执行一次任务长时间执行未能结束。
2.3 线程池的调度策略
当前TaurusDB存储端线程池支持三种策略:先进先出调度(FifoScheduler)、定时调度(TimeScheduler)、基于容量调度(CapacityScheduler)。
对于FifoScheduler和TimeScheduler,比较容易理解。当有任务需要执行时,只需将此任务存放在一个队列即可,有scheduler按照顺序逐一调度即可。
对于CapacityScheduler,是一种基于事先为某一类型的任务预留可执行线程的思想,其预留的线程个数由下发任务的用户指定。具体调度过程见图4。
图4 CapacityScheduler调度
比如:
初始化线程大小为10,TaskType1预留线程数4,TaskType2:预留线程数5,TaskTypeN:预留线程数4
当线程池处于如图4状态时,任务类型是1和3的尚未达到预留值,任务类型N已达到阈值。此时如果Threadpool中处于idle的线程数为1,则该线程将会被调度到任务类型为2的队列中。
2.4 任务异常监控告警
我们知道,一旦任务被调度的线程执行过程中,可能会出现异常情况,比如线程死锁,导致该任务不能按照预期推进,轻者引发系统出现CPU、IO等系统资源使用率飚高的情况,严重者会导致系统down情形。比如TaurusDB的存储端,执行log的checkpoint的线程出现长时间卡顿,会导致存储端旧的log不能正常回收,导致磁盘空间逐步膨胀,进而影响存储端其他各个模块平滑的推进。如果能够识别出处于异常状态的线程,并能够进行告警,基于事先自定义规则进行修复,将能够持续保证存储端业务的连续性。
线程池Monitor组件就是用于识别处于异常状态的线程,其基本思想就是,定期巡检线程池中的各线程处于状态,如果发现线程状态长时间未更新,则判定该线程处于异常状态,上报告警,并基于相应的处理规则处理。
3. 下一步演进方向
从2.3中可以看出,CapacityScheduler策略是基于实际在执行的线程数,作为idle线程线程被调度的依据,尚未衡量实时任务的重要程度。考虑这样一种场景,如果有两种类型的任务A、B,在某一时间,用于执行A、B任务的线程数恰好线程或差值极小,但B类型任务的优先级大于A任务,这时可能出现idle线程被调度执行A类型任务,B类型任务不能分配到充足的线程数(预留值是静态分配),用于加快推进任务的处理。
考虑的方案:
1.限制类型任务类型队列的长度,这样可以均衡各类型任务的调度,不至于某一类或几类任务数过多;
2.在系统资源有限的前提下,支持动态伸缩线程池大小的功能,这样可以在工作负载过重时,扩充线程池大小,用于调度到急需执行的任务;
3.099进一步细化CapacityScheduler策略,采用任务的重要程度和实际执行的线程数的规则,作为idle线程被调度的依据。
点击关注,第一时间了解华为云新鲜技术~