Как распределить входящие звонки в Asterisk
Чтобы распределять звонки в Asterisk, подберите стратегию под работу отдела, задайте время дозвона и общий лимит ожидания, а каждому выходу из очереди назначьте понятный маршрут. Покажу, где проверить конфигурацию и как считать пропущенные, не выдавая примерные цифры за результат.
Почему все звонки достаются одному оператору
Сценарий узнаваемый. В отделе пять человек, а звонки берёт один, самый быстрый на руку, или первый в списке. Остальные сидят спокойно, а вечером этот один уходит с ощущением, что работал за всех. Руководитель смотрит на отчёт и не понимает, почему при пяти операторах клиенты ждут. Когда нас зовут на ИТ-аутсорсинг для офиса с такой телефонией, первым делом открываем queues.conf и почти всегда находим там стратегию по умолчанию или linear, выбранную «чтобы был порядок».
У ringall (это стратегия по умолчанию в Asterisk) логика простая: звонят все доступные участники сразу, отвечает тот, кто первым снял трубку. В маленькой команде это превращается в соревнование реакции, и выигрывает всегда один и тот же человек. У linear другая беда: звонок всегда начинается с первого участника в списке, и пока он свободен, до остальных очередь доходит редко. Обе стратегии рабочие, но выбор без понимания графика и нагрузки быстро превращает очередь в лотерею.
Вторая половина жалобы — абоненты долго ждут без выхода. Очередь без общего таймаута и без альтернативного маршрута держит человека под музыку сколько угодно. Ирония в том, что такая очередь в отчётах выглядит прекрасно: пропущенных нет, все «дождались». Просто часть из них положила трубку на пятой минуте, и это записано как ABANDON, который никто не смотрит. Если звонок приходит через IVR, логика меню должна вести в очередь только в рабочее время; подробнее о маршрутизации рассказываем в материале про схему IVR в Asterisk.
Стратегии очереди в queues.conf: какую выбрать
Стратегию задаёт параметр strategy в секции очереди файла queues.conf. Диалплан отправляет вызов в очередь приложением Queue(). Я начинаю выбор не с названия красивого алгоритма, а с правил отдела: должны ли сотрудники получать сопоставимое число звонков, важна ли скорость ответа или нужен основной оператор с резервом. Алгоритм распределяет вызовы, но сам по себе не учитывает сложность разговора, длительность оформления и качество ответа. | Стратегия | Как работает | Когда подходит | |---|---|---| | ringall | звонят все свободные сразу | 2–3 человека, важна скорость ответа | | rrmemory | по кругу, помнит, на ком остановились | равная нагрузка в отделе | | leastrecent | тому, кто дольше всех без звонка | длинные разговоры, нужен отдых | | fewestcalls | тому, у кого меньше принятых | выравнивание по количеству | | linear | всегда по порядку списка | есть старший и резерв | | random / wrandom | случайный выбор; wrandom учитывает penalty участника | только если есть причина |
Для небольшой команды с одинаковыми обязанностями я обычно начинаю с rrmemory: очередь продолжает распределение с участника, на котором остановилась. leastrecent выбирает того, кто дольше не получал вызов. Ни один вариант не делит нагрузку идеально: fewestcalls сравнивает число принятых звонков, а не минуты разговора и не сложность заявки. Три длинных разговора могут вымотать сильнее десяти коротких, и конфигурация этого не угадает.
linear уместна, если порядок участников намеренный: сначала дежурный, затем резерв. Она опирается на порядок, заданный конфигурацией, поэтому перестановка строк может изменить маршрутизацию. После правки проверьте актуальную для вашей сборки процедуру перезагрузки. В типовой консоли Asterisk конфигурацию очередей перечитывают командой module reload app_queue.so, затем проверяют queue show; отдельная команда queue reload в статье была бы неточной. Перед изменением стратегии я сохраняю старые значения и проверяю поведение на тестовом вызове.
Таймауты очереди: timeout, retry, wrapuptime и общее ожидание
Таймаутов в очереди несколько, и их постоянно путают. timeout в queues.conf — сколько секунд звонит телефон одного оператора, прежде чем очередь решит, что он не ответил. retry — пауза перед следующей попыткой обзвона. wrapuptime — время после завершения разговора, когда оператору не дают новый звонок, чтобы он успел записать заявку. А общий таймаут ожидания абонента в очереди задаётся отдельно, аргументом приложения Queue() в диалплане.
[support]
strategy = rrmemory
timeout = 15
retry = 3
wrapuptime = 20
ringinuse = no
maxlen = 8
joinfull = no
announce-frequency = 60
announce-holdtime = no
servicelevel = 30
joinempty = paused,unavailable,invalid
leavewhenempty = paused,unavailable,invalid
context = support-exit
member => PJSIP/301
member => PJSIP/302timeout=15 означает время попытки дозвона участнику, а не общий срок ожидания абонента. Пятнадцать секунд — только отправная точка: длительность гудков зависит от телефонии и сценария, поэтому я проверяю её реальным звонком. retry задаёт паузу перед следующей попыткой, а wrapuptime — минимальный интервал после разговора перед новым вызовом этому участнику. Для заполнения карточки обращения может хватить 20–30 секунд, но это надо замерить на рабочем процессе, а не объявлять универсальной нормой.
ringinuse=no запрещает предлагать новый вызов участнику, который уже занят. Но результат зависит от того, видит ли Asterisk корректное состояние канала. Я проверяю участника командой queue show и тестовым звонком: название PJSIP само по себе не гарантирует правильный device state. При канале-обёртке или нестандартной маршрутизации может понадобиться отдельно настроить state_interface.
Выходы из очереди: таймаут, клавиша, пустая очередь
Очередь должна иметь понятный маршрут на случай ожидания, пустой группы и выхода по клавише. Общий таймаут задаётся аргументом приложения Queue() в диалплане; в примере ниже пятый аргумент — 180 секунд. Параметр context в queues.conf задаёт контекст, куда Asterisk передаёт нажатия во время ожидания. Если доступных участников нет, joinempty и leavewhenempty определяют, можно ли войти в очередь и что делать с уже ожидающим звонком.
[q-support]
exten => s,1,Answer()
same => n,Queue(support,,,,180)
same => n,GotoIf($["${QUEUESTATUS}" = "TIMEOUT"]?vm,1)
same => n,GotoIf($["${QUEUESTATUS}" = "JOINEMPTY"]?vm,1)
same => n,GotoIf($["${QUEUESTATUS}" = "LEAVEEMPTY"]?vm,1)
same => n,Hangup()
exten => vm,1,VoiceMail(500@support)
[support-exit]
exten => 1,1,VoiceMail(500@support)После выхода из Queue() проверьте ${QUEUESTATUS}. Документированные состояния включают TIMEOUT, FULL, JOINEMPTY, LEAVEEMPTY, JOINUNAVAIL и LEAVEUNAVAIL. Для каждого состояния задайте маршрут или понятное завершение; при неразобранном результате диалплан может просто перейти к следующему приоритету и закончить вызов. Обработчик TIMEOUT — не замена настройкам пустой очереди: это разные причины выхода и разные сценарии.
maxlen ограничивает число ожидающих; при joinfull=no новый вызов не присоединяется к заполненной очереди, а Queue() возвращает статус FULL. Значение восемь — пример для настройки, не рекомендация для любого отдела. Рассчитайте лимит по числу доступных операторов, средней длительности обслуживания и готовности перезванивать. Голосовая почта полезна лишь тогда, когда назначен ответственный и задан срок обработки.
Расписание, паузы и динамические участники очереди
Отдел живёт не по конфигу. Кто-то на обеде, кто-то на выезде, кто-то ушёл на совещание и забыл про телефон. Если участники очереди прописаны статически, очередь будет звонить на пустой стол каждый круг, тратя timeout на каждого. Поэтому я делаю операторов динамическими или, как минимум, даю им паузу. Приложения AddQueueMember, RemoveQueueMember и PauseQueueMember вешаются на служебные коды: оператор набирает код и выходит на обед или возвращается.
Для короткого перерыва пауза обычно удобнее удаления: участник остаётся в списке, а его состояние видно в queue show. Когда joinempty и leavewhenempty учитывают paused, можно не принимать новые звонки и вывести уже ожидающие, если все операторы на паузе. Это поведение нужно проверить на своей версии и с фактическим состоянием каналов. Я также задаю причину паузы, если используемая конфигурация её поддерживает: потом проще отличить обед от технической недоступности.
Расписание самой очереди проверяется до вызова Queue(), так же как в меню, через GotoIfTime. Вне рабочего времени звонок не должен попадать в очередь вовсе. Отдельный нюанс — обеденный перерыв: если весь отдел уходит обедать одновременно, на это время звонки лучше направить дежурному или в почту, а не держать людей в очереди с музыкой. Ни один клиент ещё не сказал спасибо за мелодию, прослушанную до конца.
Контроль пропущенных: queue_log и уровень обслуживания
Asterisk по умолчанию пишет журнал очереди в /var/log/asterisk/queue_log, если логирование не перенастроено. Строки разделены символом |: сначала идут UNIX-время, идентификатор канала, очередь, участник, тип события и его параметры. ENTERQUEUE отмечает вход, CONNECT — соединение с оператором, ABANDON — уход абонента до ответа, EXITWITHTIMEOUT — истечение таймаута, а COMPLETEAGENT и COMPLETECALLER — завершение разговора оператором или абонентом. В отличие от CDR, queue_log показывает этапы ожидания и маршрутизации.
# суммарно в текущем файле: брошенные и вышедшие по таймауту
grep -E 'ABANDON|EXITWITHTIMEOUT' /var/log/asterisk/queue_log | wc -l
# кто чаще не берёт трубку
grep RINGNOANSWER /var/log/asterisk/queue_log | awk -F'|' '{print $4}' | sort | uniq -cПервая команда считает записи во всём текущем файле, а не «за сегодня»: queue_log не содержит дату в читаемом виде, первое поле — UNIX-время. Для дневной статистики отберите интервал по этому полю или используйте систему отчётности, учитывающую ротацию файлов. Вторая команда выводит участника из четвёртого поля строк RINGNOANSWER. Параметр servicelevel задаёт порог ожидания для показателя SL; queue show показывает долю вызовов, обработанных в пределах этого порога. Сам порог выбирайте по обещанию клиенту и фактической нагрузке, а не по чужому шаблону.
Журнал ENTERQUEUE обычно содержит Caller ID Number, если идентификатор доступен и его запись не ограничена настройками приватности. Сверьте его с CDR по идентификатору вызова, назначьте ответственного за обратный звонок и фиксируйте результат. Не обещаю, что перезвон «в течение часа» вернёт большую часть клиентов: это зависит от отрасли и скорости реакции. Для контроля можно связать очередь с метриками качества звонков Asterisk и смотреть динамику по своим данным.
Пример политики для небольшого отдела
Возьмём условную небольшую компанию с шестью сотрудниками на входящей линии. Это учебный пример конфигурации, а не история конкретного клиента или замер результата. В исходном сценарии стоит ringall, все участники статические, общего лимита ожидания и маршрута по QUEUESTATUS нет. В обед телефоны остаются доступными для очереди, хотя ответить некому. Такая схема проверяется не по впечатлению руководителя, а по queue_log и тестовым вызовам.
В учебном варианте я бы проверил rrmemory как начальный выбор, выставил короткий timeout дозвона и измерил фактическое время заполнения карточки, прежде чем назначать wrapuptime. На обед оператор ставит паузу, joinempty и leavewhenempty выводят вызов на резервный маршрут, а общий таймаут в Queue() ведёт к голосовой почте с назначенным ответственным. maxlen и интервалы здесь подбираются после оценки потока. Срок внедрения зависит от текущего диалплана и телефонии, поэтому обещать фиксированные три дня без обследования было бы нечестно.
После запуска я сравниваю показатели за сопоставимые рабочие дни: сколько вызовов вошло, сколько обработано, сколько оставлено и сколько покинуто до ответа. Если доля ABANDON не меняется, значит, одной сменой стратегии проблему не решили — надо смотреть расписание, число доступных операторов и длительность обслуживания. Важно не подменять результат красивой цифрой: цель — понятный маршрут для звонящего и своевременный обратный звонок. Для записи разговоров отдельно проверяют права и срок хранения.
Частые вопросы
Какую стратегию очереди Asterisk выбрать для небольшого отдела?
При одинаковых задачах начните с rrmemory и сравните распределение по queue_log. ringall звонит всем доступным участникам сразу, linear сохраняет порядок списка; ни одна стратегия не учитывает сложность разговора.
Чем timeout очереди отличается от таймаута Queue()?
timeout в queues.conf ограничивает попытку дозвона участнику. Аргумент timeout приложения Queue() ограничивает общее ожидание вызывающего в очереди.
Как включить выход из очереди по клавише?
Укажите параметр context в секции очереди и задайте в этом контексте маршрут для нажатия. Проверьте на тестовом вызове, что DTMF доходит и звонок действительно выходит.
Что означает QUEUESTATUS?
Это переменная с причиной выхода из Queue(), например TIMEOUT, FULL, JOINEMPTY, LEAVEEMPTY, JOINUNAVAIL или LEAVEUNAVAIL. Для нужных состояний настройте маршруты в диалплане.
Где смотреть пропущенные звонки очереди?
В queue_log: ABANDON отмечает уход абонента до ответа, EXITWITHTIMEOUT — выход по таймауту. Caller ID может быть в записи ENTERQUEUE; учитывайте доступность и ограничения идентификатора.
Источники
- Asterisk Documentation: Queue — Синтаксис Queue(), аргумент таймаута и возвращаемые статусы: https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/Queue/
- Asterisk Documentation: Queue Logs — Путь журнала по умолчанию, формат строк и значения ABANDON, ENTERQUEUE, EXITWITHTIMEOUT, RINGNOANSWER: https://docs.asterisk.org/Operation/Logging/Queue-Logs/
- Asterisk Documentation: Building Queues — Примеры queues.conf, стратегии, состояние участников, queue show и перезагрузка app_queue: https://docs.asterisk.org/Configuration/Applications/Asterisk-Queues/Building-Queues/
- Asterisk API: PauseQueueMember — Синтаксис постановки участника очереди на паузу: https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/PauseQueueMember/
- Asterisk API: AddQueueMember — Синтаксис динамического добавления участника очереди: https://docs.asterisk.org/Latest_API/API_Documentation/Dialplan_Applications/AddQueueMember/
- Asterisk source: queues.conf.sample — Параметры стратегии, таймаутов, состояния занятости, лимита, joinempty и leavewhenempty: https://github.com/asterisk/asterisk/blob/master/configs/samples/queues.conf.sample
