Dialplan или AGI в Asterisk: что выбрать
АйТи Фреш
Серверы и инфраструктура

Как разделить маршрутизацию и логику звонка в Asterisk

Автор: , директор ООО «АйТи-Фреш» · · ~11 мин чтения
Запутанные телефонные провода разделены на два аккуратных пучка — метафора разделения диалплана и AGI в Asterisk
Маршрутизация звонков и бизнес-логика должны жить в разных местах.

Маршрутизацию, меню и очереди я оставляю в диалплане. Короткий HTTP-запрос можно сделать через CURL с настроенным таймаутом; разбор данных из CRM и несколько внешних шагов удобнее вынести в AGI или FastAGI. ARI нужен, когда отдельное приложение должно управлять каналами и мостами. Ниже — критерии выбора и безопасный порядок изменений.

Почему extensions.conf превращается в лабиринт

Типовая история: телефонию поднял подрядчик пять лет назад, потом каждый новый запрос директора дописывали прямо в extensions.conf. Сначала меню, потом переадресация на мобильные, потом «если звонит постоянный клиент — сразу на менеджера», потом проверка задолженности. Через пару лет это файл на несколько тысяч строк, где GotoIf прыгает в контекст, который включён через include в другой контекст. Когда мы берём такую систему на обслуживание сервера телефонии и ИТ-аутсорсинг, первым делом слышим: «только ничего не сломайте, там всё работает». Работает — пока никто не дышит рядом.

Проблема не в том, что диалплан плохой. Это хороший язык для того, для чего он создан: принять канал, решить, куда его отправить, проиграть файл, поставить в очередь, записать разговор. Проблема начинается, когда в него пытаются запихнуть бизнес-логику: циклы по спискам, разбор ответа от CRM строковыми функциями, хранение состояния в переменных канала и в astdb одновременно. У диалплана есть свои средства просмотра и отладки, но сложную бизнес-логику в нём труднее отдельно тестировать и менять. Если каждый новый сценарий добавляет переходы и исключения, разбор реального пути звонка превращается в ручную работу.

Второй источник хаоса — обратный перекос. Кто-то начитался про AGI и сделал так, что на каждый входящий звонок стартует PHP-скрипт, который сам решает всё: меню, очередь, запись. Диалплан там из трёх строк, а вся логика — в скрипте, который пишет один человек и который не обновлялся с тех пор, как этот человек уволился. Лабиринт никуда не делся — он просто переехал из конфигурации в код. Если внешний скрипт не обрабатывает ошибку или зависает на запросе к CRM, абонент может ждать вместо перехода к запасному маршруту. Это не повод обвинять AGI: это повод описать отказ и проверить сценарий без CRM.

Если в вашем extensions.conf есть контекст с названием вроде test2_new_final — у вас не диалплан, у вас археологический слой.

Что Asterisk умеет без внешних скриптов

Прежде чем выносить логику наружу, стоит честно посмотреть, что диалплан делает сам. Ветвления — GotoIf и ExecIf. Подпрограммы — Gosub с аргументами и Return, это нормальная замена копипасте одинаковых блоков по десятку внутренних номеров. Время работы — приложение GotoIfTime. Хранение простых флагов вроде «ночной режим включён» — функция DB() во встроенной базе astdb. Запросы во внешние HTTP-сервисы — функция CURL(), к SQL-базе — func_odbc. Одного процента для всех офисов не существует: объём зависит от сценариев и установленных модулей. Моя граница практическая: пока условие понятно в диалплане и не требует сложной обработки данных, оставляю его там.

Главное преимущество диалплана — он исполняется внутри процесса Asterisk. Для обычного диалплана не запускается отдельный AGI-скрипт; сетевой вызов появится только если его задаёт сам сценарий. Перечитать диалплан можно командой dialplan reload: документация Asterisk указывает, что это загружает новую конфигурацию без нарушения работы PBX. Перед изменением всё равно проверьте синтаксис и сделайте тестовый звонок. Посмотреть, что реально загружено, — dialplan show имя_контекста. Это дешёвый, быстрый и предсказуемый слой, и я хочу, чтобы маршрутизация звонков жила именно там. Всё, что лежит в диалплане, видно в одном месте, проверяется в консоли и не зависит от того, жив ли соседний сервер с CRM или упал после ночного обновления.

Читаемость диалплана — вопрос дисциплины, а не языка. Мы раскладываем конфиг на файлы через #include: отдельно входящие по транкам, отдельно внутренние, отдельно подпрограммы. Каждая подпрограмма — один смысл: sub-record для записи, sub-worktime для проверки графика, sub-crm-lookup для обращения к CRM. Метки вместо номеров приоритетов, комментарий над каждым контекстом. Такую раскладку проще искать и сравнивать в системе контроля версий. Отдельный плюс: такой конфиг удобно сравнивать между версиями, и можно проверить, в каком изменении появился новый маршрут.

Дерево выбора диалплана, CURL, AGI и ARI для логики Asterisk
Выбирайте слой по сложности задачи и точкам интеграции.

Когда без AGI действительно не обойтись

AGI нужен там, где начинается настоящая программа: несколько запросов к разным системам, разбор JSON, обработка ошибок, повторные попытки, логирование в понятном виде. Пример: по номеру звонящего найти контрагента в CRM, проверить, закреплён ли за ним менеджер, свободен ли менеджер, и если нет — выбрать дежурного из того же отдела. На диалплане это реально написать, но сопровождать — нет. В отдельном скрипте проще разделить шаги, разобрать структурированный ответ, записать диагностические данные и проверить ветви ошибки. Логируйте идентификатор вызова и итог решения, а персональные данные и секреты не записывайте без необходимости. В диалплане ту же диагностику придётся собирать из вывода консоли по кусочкам.

Механика простая: приложение AGI() в диалплане передаёт управление внешней программе, та общается с Asterisk через stdin и stdout командами AGI; Asterisk передаёт канал и окружение скрипту, а скрипт отправляет команды протокола. Например, GET VARIABLE читает переменную канала, SET VARIABLE задаёт её. Обычный AGI запускает новый процесс на каждый вызов, и при скрипте на интерпретируемом языке это ощутимые накладные расходы: старт интерпретатора, загрузка библиотек, подключение к базе. Время запуска и нагрузку надо измерять на своей системе: язык, библиотеки и работа самого скрипта меняют результат. Если один процесс при каждом вызове загружает большой справочник или долго ждёт базу, ищите узкое место там, а не в названии интерфейса.

FastAGI использует тот же протокол через TCP-соединение с внешним сервисом; URI имеет вид agi://узел/скрипт, порт задаётся в URI или настройке сервиса. Это позволяет держать процесс и соединения к CRM вне Asterisk, но добавляет сетевую зависимость. Если же логика должна полностью управлять звонком — мосты, переводы, сложные сценарии с событиями, — смотрите в сторону ARI: там звонок передаётся приложению Stasis, а дальше вы управляете каналами через REST и WebSocket. ARI выбирают, когда приложению нужны события и управление каналами, а не просто один ответ CRM. Для простой маршрутизации это лишний уровень ответственности.

AGI-скрипт без таймаута на запрос к CRM — это способ сделать так, чтобы при падении CRM у вас заодно упала и телефония.

Производительность и отказоустойчивость: честное сравнение

Скорость здесь нельзя честно свести к универсальной таблице миллисекунд: результат зависит от кода, сети, модулей и нагрузки. Разница в устройстве заметнее: обычный AGI запускает локальную программу, FastAGI подключается к внешнему TCP-сервису, а ARI требует приложения, которое работает с событиями и управляет каналами через API. Для этих вариантов важны разные точки отказа.

Внешняя система может отвечать медленно или быть недоступной. Поэтому после AGI() проверяйте AGISTATUS: официальная документация перечисляет SUCCESS, FAILURE, NOTFOUND и HANGUP. Сам SUCCESS говорит о результате запуска AGI, но не подтверждает, что бизнес-ответ CRM корректен. Скрипт должен отдельно вернуть понятный результат, а диалплан — проверить его и отправить вызов в согласованную очередь или на другой резервный маршрут при ошибке. Не оставляйте абонента ждать неизвестно чего.

Для CURL() в диалплане задавайте лимиты ожидания через CURLOPT(): для HTTP-действия в документации есть httptimeout, для установления соединения — conntimeout, также можно ограничить DNS-ожидание через dnstimeout. Это именно настройки libcurl в поддерживаемой сборке Asterisk; проверьте наличие функции и поведение на своей версии. Пустой ответ — не единственный признак сбоя: проверяйте и формат, и код ответа, а на ошибке выбирайте запасной маршрут. Для диагностики AGI используйте CLI-команду agi set debug on временно и выключайте подробный вывод после разбора.

Нужна помощь с границей между диалпланом и CRM? Мы разберём сценарии, опишем запасной маршрут и подготовим перенос интеграции без резкого переключения. Обсудить задачу
Сравнение диалплана, AGI, FastAGI и ARI по исполнению и зависимости
У каждого слоя свой механизм и отдельная точка отказа.

Практический разбор: CRM отдельно, маршрут — в диалплане

Покажу типовую схему без привязки к реальному заказчику и без выдуманной статистики. В старой конфигурации встречается такой узел: диалплан вызывает CRM, разбирает ответ строковыми функциями и по результату выбирает внутренний номер. Пока CRM отдаёт данные в ожидаемом формате, сценарий выглядит рабочим. После изменения ответа проверка может тихо выбрать неверную ветку. Здесь проблема не в количестве строк, а в том, что формат интеграции и правила маршрутизации смешаны.

Я разделяю ответственность. Диалплан получает результат вроде «назначенный сотрудник» или «не найден», проверяет его и решает, куда передать вызов. Внешняя программа обращается к CRM, проверяет ответ, журналирует исход запроса и возвращает короткий статус. Если CRM недоступна, сценарий не должен выдавать техническую ошибку абоненту: он отправляет звонок в заранее выбранную общую очередь. Сам таймаут выбирают по SLA и допустимому ожиданию, а не копируют магическое число из чужого примера.

Перед переключением я сохраняю исходную конфигурацию, записываю контракт между диалпланом и скриптом и проверяю четыре исхода: найден сотрудник, запись не найдена, CRM вернула некорректный ответ, CRM не отвечает. Затем проверяю сам маршрут тестовым звонком и смотрю журнал. Так видно, что именно произошло, и возврат к прежнему вызову AGI остаётся простым. Ни один из этих шагов не требует заявлять, что мы сократили чужой конфиг втрое или ускорили работу на выдуманный процент.

Как провести границу у себя: пошаговый план

Начните с инвентаризации. Выгрузите dialplan show в файл, составьте список контекстов и найдите переходы, которые на них ссылаются. Отдельно проверьте входы от транков и каналов: отсутствие очевидного Goto ещё не доказывает, что контекст мёртв. Затем разметьте каждый оставшийся кусок: это маршрутизация звонка или это бизнес-правило. Маршрутизация остаётся, бизнес-правила с внешними данными и сложными условиями едут в FastAGI. Не пытайтесь переписать всё за один раз: выносите по одной функции, проверяйте, потом следующую. До проверки держите прежний сценарий доступным для быстрого возврата; длительность отката зависит от того, как устроен конфиг и релизный процесс.

Дальше — контракт между слоями. Скрипт получает ограниченный набор входных переменных и выставляет ограниченный набор выходных, например CRM_MANAGER и CRM_STATUS. Диалплан ничего не знает о том, как устроена CRM, а скрипт ничего не знает о том, как устроены очереди. Такой контракт пишется на половину страницы, и именно он спасает при смене подрядчика или CRM. Новый программист читает контракт и сразу понимает, что от него ждут, а не реконструирует логику по чужому коду. Описание лежит рядом с конфигом в том же репозитории, а не в почте уволившегося админа.

И последнее: конфиги — в git. Это не прихоть разработчиков, а способ ответить на вопрос «что поменяли вчера, что всё сломалось». Перед каждым dialplan reload — коммит, после — проверка тестовым звонком по чек-листу: входящий в рабочее время, в нерабочее, на закреплённого менеджера, при выключенной интеграции. Короткая проверка маршрутов дешевле, чем разбирать пропущенные звонки после выкладки. И держите копию рабочего конфига, к которой можно вернуться одной командой, — страховкой пользуются редко, но всегда в самый неудобный момент.

Схема обработки ответа CRM и перехода на запасной маршрут в Asterisk
Проверка ошибки должна завершаться маршрутом, а не тишиной.

Частые вопросы

Что быстрее — диалплан или AGI?

Диалплан не создаёт отдельный AGI-процесс для выполнения своей логики. Обычный AGI запускает локальную программу, FastAGI подключается к внешнему сервису; фактическую задержку нужно измерять на своей конфигурации.

Чем FastAGI отличается от AGI?

FastAGI использует AGI-протокол поверх TCP: Asterisk подключается к постоянно работающему сервису по URI agi://. Это снимает запуск локального скрипта при каждом вызове, но добавляет зависимость от сервиса и сети.

Нужен ли ARI небольшому офису?

Чаще нет. ARI нужен, когда приложение должно полностью управлять звонками и мостами. Для интеграции с CRM и умной маршрутизации обычно хватает диалплана и FastAGI.

Можно ли перечитать диалплан без обрыва звонков?

Да. Команда `dialplan reload` загружает диалплан без нарушения работы PBX. После изменения всё равно проверьте вывод `dialplan show` и выполните тестовый вызов.

Что делать, если AGI-скрипт завис?

Ограничьте ожидание ответа CRM внутри скрипта и задайте запасной маршрут. После `AGI()` проверяйте `AGISTATUS` и отдельный статус бизнес-ответа: `SUCCESS` не гарантирует корректность данных CRM.

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи