Недавно мы переделали наши системные потоки. Поскольку мы ответвились от Sphinx, ранний searchd имел все системные задачи, выполненные в стиле частого пробуждения. Каждый сервис работал в выделенном потоке, который просыпался каждые 50 мс и проверял, есть ли у него что-то делать или нет. Это означает, что даже бездействующий демон просыпается 200 раз в секунду «просто чтобы проверить» задачу. Наличие 6 таких задач приведет к 1200 пробуждениям в секунду, и это стало заметно, особенно для клиентов на Amazon AWS, которые учитывают использование CPU. Эти внутренние задачи были:
- обычная ротация индекса
- пинг агента
- предзагрузка индекса
- сброс атрибутов индекса
- сброс индекса RealTime
- и сброс бинарных журналов
Все они довольно редки (например, сброс RT может происходить раз в 10 часов), поэтому проверка их 200 раз в секунду — это просто трата CPU. Кроме того, все эти задачи по своей природе не являются постоянными (например, добавление текста в огромный журнал), а просто периодические действия (повторение одной и той же задачи периодически). Учитывая этот стиль действий, мы полностью переписали всю систему:
- Во-первых, мы добавили один поток, который обслуживает таймеры, где планируется каждое действие.
- Во-вторых, мы добавили пул потоков для выполнения самих действий.
- Таким образом, в конечном итоге действие сервиса (задача) планируется, и когда таймер срабатывает, оно перемещается в пул потоков, а затем выполняется.
- По завершении оно удаляется, так что периодические задачи просто перепланируют себя в конце и создают совершенно новую задачу.
При таком подходе нет множества выделенных сервисных потоков, только один таймерный поток. И он, в свою очередь, не просыпается каждые 20 мс, а имеет бинарную кучу с тайм-аутами и просыпается только в период, указанный самым ранним таймером в очереди. Пул потоков, в свою очередь, может запускать до 32 потоков параллельно, но на самом деле только один, иногда два, участвуют в работе. Каждый поток имеет предопределенный период бездействия (10 минут), после которого он просто завершает работу, так что в случае «нет задач» ничего не работает в режиме ожидания (даже таймерный поток инициализируется «ленивым» способом, т.е. запускается только тогда, когда необходимо запланировать фактическую задачу). В случае очень редких (>10 минут период) задач пул рабочих потоков также забрасывается, так что рабочие потоки создаются только тогда, когда есть что-то делать. Все сервисные потоки, таким образом, удалены и больше не нагружают CPU сотни раз в секунду.
Самое многообещающее изменение такого подхода — это новое поведение задачи «пинг». В прошлом мы собирали все хосты агентов, и каждый интервал пинга отправлял команду «пинг» к ним как одной группе. Таким образом, если один хост был медленным, вся группа тоже была медленной. Кроме того, это не имело ничего общего с фактическим состоянием хостов — например, когда вы загружаете хост запросами, не обязательно пинговать его отдельно, поскольку сами запросы дают исчерпывающую статистику о состоянии хоста. Теперь пинг конкретен: он планируется для каждого хоста отдельно и привязан к фактическому last_answer_time каждого из них. Если хост медленный — только его задача пинга будет ждать его, другие будут работать нормально в свое время. Если хост под нагрузкой — его last_answer_time будет монотонно обновляться, так что фактический пинг не произойдет, если какой-то запрос уже произошел с last_query_time и ping_interval.
Еще одной особенностью теперь является то, что задачи могут выполняться параллельно. Скажем, сброс индекса может выполняться для любого количества индексов одновременно, а не последовательно, а параллельно. На данный момент это число установлено на 2 задания, но это всего лишь вопрос настройки. Также, когда несколько подобных задач запланированы, теперь мы можем ограничить их количество. Скажем, 'malloc_trim' не имеет смысла планировать более одного раза, так что это своего рода «синглтон» — если одно запланировано, другая попытка запланировать будет отклонена.
Следующая функция, добавленная с таким управлением задачами, вытекает из того факта, что теперь все задачи планируются в одной очереди (а не в разных группах потоков), и мы точно знаем, когда они будут выполняться. Таким образом, теперь такая статистика может быть отображена, и это делается путем добавления 'debug sched', 'debug tasks' и 'debug systhreads'.
Первый показывает бинарную кучу таймеров: верхнее значение указывает на следующий тайм-аут и связанную с ним задачу; другие значения идут следом (хотя они сейчас отображаются как сырая бинарная куча; их можно отсортировать, если возникнет такая необходимость).
'debug tasks' показывает все задачи, зарегистрированные в демоне, с их статистикой и свойствами (сколько таких задач может выполняться параллельно; сколько может быть запланировано; сколько в настоящее время выполняется, сколько времени потрачено на задачу, когда она была в последний раз завершена, сколько раз она была выполнена, сколько было отклонено из-за перегрузки очереди и сколько сейчас находится в очереди).
Последний 'debug systhreads' отображает состояние рабочих потоков, такие как внутренний id, id потока, время последнего выполнения, общее время CPU, общее количество тиков и выполненных заданий, сколько времени заняло последнее задание и как долго рабочий находится в бездействии. Как уже упоминалось, бездействие в течение 10 минут приводит к остановке рабочего потока.
Обновите до Manticore 3.1.0 или более новой версии , чтобы воспользоваться этим изменением