我们如何在Manticore Search中重构系统线程

最近我们重构了系统线程。自从我们从Sphinx分叉以来,早期的searchd以一种经常唤醒的方式执行所有系统任务。每个服务在一个专用线程中工作,每50毫秒唤醒一次,检查是否有事情要做。这意味着即使是空闲的守护进程每秒唤醒200次“只是为了检查”一个任务。拥有6个这样的任务将导致每秒1200次唤醒,尤其在使用Amazon AWS的客户中,这种情况变得很明显,因为它计算CPU使用情况。这些内部任务包括:

  • 普通索引轮换
  • 发送心跳给代理
  • 索引预加载
  • 刷新索引属性
  • 刷新实时索引
  • 刷新二进制日志

它们的发生非常少(比如刷新实时索引可能每10小时才发生一次),因此每秒检查200次只是浪费CPU。此外,所有这些任务本质上并不固定(例如将文本附加到一个大型日志中),而只是周期性操作(定期重复同一任务)。考虑到这种操作方式,我们彻底重写了整个内容:

  1. 首先,我们添加了一个线程来服务定时器,每个操作都被调度在其中。
  2. 其次,我们添加了线程池来执行这些操作。
  3. 所以最终一个服务操作(任务)被调度,当定时器到期时,它被移动到线程池中,然后执行。
  4. 完成后该任务被删除,因此周期性任务在结束时只是重新调度自己,产生一个全新的任务。

采用这种方法,没有一堆专用的服务线程,只有一个定时器线程。并且,它本身不会每20毫秒唤醒,而是使用一个带有超时的二进制堆,只在队列中最早的定时器指定的时间唤醒。线程池最多可以同时运行32个线程,但实际上只有一个,有时候是两个在线上。每个线程都有预定义的空闲期(10分钟),在该期间后它会自动结束,因此在“没有任务”的情况下,什么都不会空闲运行(即使是定时器线程也是一种“懒惰”的方式初始化,即仅在实际需要调度任务时才启动)。在很少发生的任务( >10分钟的周期)中,工作线程池也会被放弃,以便仅在有事情要做时创建工作线程。因此,所有服务线程都被移除,不再每秒激活CPU数百次。

这种方法最令人期待的变化是“ping”任务的新行为。在过去,我们收集了所有代理主机,在每个ping间隔内向它们发出“ping”命令,作为一组发送。因此,如果一个主机速度慢,整个组的速度也会慢。此外,这与实际主机状态没有关系 - 比如当你通过查询加载一个主机时,不必单独ping它,因为查询本身提供有关主机状态的全面统计信息。现在ping是具体的:它为每个主机单独计划,并与每个主机的last_answer_time紧密相连。如果主机速度慢 - 只有它的ping任务会等待,其他任务会正常进行。如果主机在负载下 - 它的last_answer_time会单调更新,因此如果自last_query_time和ping_interval以来已经有某个查询发生,则实际的ping将不会发生。

现在的另一个特点是任务可以并行工作。假设,索引刷新可以同时对任意数量的索引进行,而不是串行,而是并行。目前,这个数量被设定为2个作业,但这只是调整的问题。当几个相似的任务被调度时,现在我们可以限制它们的数量。比如说,“malloc_trim”没有必要被调度超过一次,因此它是一种“单例” - 如果一个被调度,另一个尝试调度将被丢弃。

通过这种任务管理增加的下一个特性是现在所有任务都在一个队列中调度/计划(而不是不同的一组线程),我们准确知道它将何时运行。因此,现在可以显示这种统计信息,通过添加“debug sched”,“debug tasks”和“debug systhreads”。

第一个显示了定时器的二进制堆:顶部值指示下一个超时和与其相关的任务;其他值依次显示(不过它们现在以原始二进制堆的形式显示;如果需要,可以进行排序)。

“debug tasks”显示了守护进程中注册的所有任务,带有它们的统计信息和属性(可以并行运行多少个这样的任务;可以调度多少个;当前执行多少个,花费多少时间在某个任务上,最后一次完成的时间,执行了多少次,由于队列过载而丢弃了多少次,以及现在排队的数量)。

最后,“debug systhreads”显示工作线程的状态,如内部ID、线程ID、上次运行时间、总CPU时间、完成的总tick和作业数量、最后作业耗时,以及工作线程空闲多久。如前所述,空闲10分钟将导致工作线程停止。

升级到Manticore 3.1.0或更新版本 以受益于这一变化

安装Manticore Search

安装Manticore Search