# 我们如何在Manticore Search中重新设计系统线程

最近我们重新设计了系统线程。由于我们从Sphinx分叉出来，早期的searchd所有系统任务都采用经常唤醒的风格。每个服务都在专用线程中运行，每50毫秒唤醒一次并检查是否有任务需要执行。这意味着即使空闲的守护进程也会每秒唤醒200次“只是为了检查”任务。拥有6个此类任务会使每秒唤醒1200次，这在亚马逊AWS上的客户特别明显，因为AWS会计算CPU使用率。这些内部任务包括：

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

所有这些任务都相当罕见（例如刷新实时索引可能每10小时发生一次），因此每秒检查200次只是浪费CPU资源。此外，所有这些任务本质上都不是连续的（例如向大日志追加文本），而是周期性操作（定期重复相同任务）。考虑到这种操作方式，我们完全重写了整个系统：

1. 首先，我们添加了一个线程来处理定时器，每个操作都会被计划。
2. 其次，我们添加了线程池来执行操作本身。
3. 因此，最终一个服务操作（任务）会被计划，当定时器触发时，它会被移动到线程池中，然后执行。
4. 完成后会被删除，因此周期性任务只需在结束时重新计划自己并生成全新的任务。

在这种方法下，没有一堆专用服务线程，只有一个定时器线程。而该线程不会每20毫秒唤醒一次，而是拥有一个二叉堆来管理超时，并仅在队列中最早定时器指定的时间段唤醒。线程池可以并行运行最多32个线程，但实际上通常只有1个或2个在运行。每个线程都有预定义的空闲时间（10分钟），在此之后线程会终止，因此在“没有任务”的情况下完全不会运行任何空闲线程（甚至定时器线程也是以“懒惰”方式初始化，即仅在实际需要计划任务时才启动）。在任务非常罕见（超过10分钟周期）的情况下，工作线程池也会被丢弃，因此只有在有任务需要执行时才会创建工作线程。因此，所有服务线程都被移除，不会再每秒数百次地唤醒CPU。

这种新方法最显著的变化是“ping”任务的新行为。过去，我们收集所有代理主机，并在每次ping间隔时一次性向它们发送“ping”命令。因此，如果某台主机响应缓慢，整个批次都会变慢。此外，这与实际主机状态无关——例如，当通过查询加载主机时，无需单独ping它，因为查询本身已经提供了关于主机状态的全面统计信息。现在，ping是具体的：每个主机单独计划，并且与每个主机的实际last_answer_time绑定。如果某台主机响应缓慢，只有它的ping任务会等待，其他主机的ping任务会在其时间正常执行。如果某台主机负载较高，其last_answer_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或更新版本](https://manticoresearch.com/downloads/)以享受此更改
