Pull to refresh

Asterisk в примерах: балансировка каналов

Reading time 5 min
Views 50K
Всё больше и больше организаций выбирают для телефонии не астрономически дорогие, жутко запутанные и ограниченные по функционалу готовые ATC, а современный, расширяемый и абсолютно бесплатный софт, который можно установить на любой дистрибутив Linux. Самым известным и широко распространённым решением для телефонии на базе Linux является, безусловно, Asterisk.

К сожалению для системных администраторов, Asterisk недалеко ушёл от корпоративных АТС в плане простоты настройки. Безусловно, Asterisk может, пожалуй, всё, что только возможно вообразить, но ценой этому является далеко не тривиальная настройка.

У меня за время работы с Asterisk накопилось множество различных примеров конфигурации. Полностью цифровые факсы с возможностью отправки из любого приложения в один клик, интеллектуальная запись звонков, всякие штуки с IVR и т.д. и т.п. Будет заинтересованность — со временем выложу.

В этом же посте хотелось бы поделиться системой простой балансировки исходящих соединений исходя из «веса» канала. Простейший пример, для чего это может понадобиться — звонки через обычные SIM нескольких операторов с безлимитными тарифами. У всех операторов есть некое максимальное значение минут, которые можно бесплатно проговорить в рамках тарифа в месяц. Поэтому хотелось бы распределить исходящие звонки по симкам в некой пропорции.

Предполагается, что вы умеете базово настраивать Asterisk, знаете, как пользоваться диалпланом и т.д. Писать очередную статью для новичков, а-ля как поставить Asterisk, было бы глупо.

С другой стороны, полноценное решение проблемы балансировки исходя из количества минут предполагает настройку БД с ведением статистики по длительности разговоров, AGI скрипты и прочие достаточно сложные и тяжёлые навароты. Я же опишу более простое, хоть и не до конца всеобъемлющее решение, которое, тем не менее, крайне хорошо себя зарекомендовало.

Итак: есть две SIP линии, каждая ведёт на свой GSM канал. Операторы Tele2 и Мегафон. На Tele2 у нас 300 минут, на мегафоне — всего 150. Соответственно нужно, чтобы на Tele2 поступало в два раза больше звонков. Кроме этого исходящий вызов должен идти через незанятую симку, а если оба канала заняты — то вызывающему абоненту об этом должна сообщать добрая тётенька, которая к тому же должна предложить подождать освобождения.

Итак, в sip.conf имеет примерно такое описание линий:

; ################################
; GSM каналы
; ################################
[gsm-lines](!)
deny=0.0.0.0/0
permit=10.42.42.42/32
type=friend
secret=*******
qualify=yes
host=dynamic
callcounter=yes         ; активируем возможность использовать DEVICE_STATE
call-limit=1            ; максимум 1 соединение на линию
group = 1
context = from-gsm      ; куда поступают входящие звонки
insecure=invite
canreinvite=no
nat=no

; МегаФон
[gsmline1](gsm-lines)

; Tele2
[gsmline2](gsm-lines)

Важными тут являются параметры callcounter, call-limit и context. Думаю, с ними всё понятно.

Собственно, в extensions.conf указанный контекст описан так:

; Звонки с GSM линий
[from-gsm]
; МегаФон (первая линия)
exten => +79310000000,1,Set(GROUP(gsm)=public)         ; Устанавливаем группу, дабы считать занятые каналы
exten => +79310000000,n,Goto(to-internal,queue,1)
; Tele2
exten => +79520000000,1,Set(GROUP(gsm)=public)         ; Устанавливаем группу, дабы считать занятые каналы
exten => +79520000000,n,Goto(to-internal,queue,1)

Безусловно, имена екстеншенов у вас будут другие (я использовал номера соответствующих телефонов, чтобы проще было). Они настраиваются на GSM шлюзе в настройках SIP подключения для каждого канала.

Тут нам важно, что для каждого входящего звонка устанавливается группа public в категории gsm. Это нужно для подсчёта текущего количества занятых каналов.

Теперь самое интересное — исходящий контекст:

; Звонки на сотовые
[to-gsm]
; Проверяем, есть ли свободные линии
exten => _89XX.,1,GotoIf($["${GROUP_COUNT(public@gsm)}" >= "2"]?noline)
; Вроде как есть свободные...
exten => _89XX.,n,Set(GROUP(gsm)=public)                       ; Устанавливаем группу, дабы считать занятые каналы
; Ok, проверяем - не мегафоновский ли номер вызываем?
exten => _89XX.,n,Set(PR=${EXTEN:1:3})
exten => _89XX.,n,GotoIf($[$["${PR}"="921"] | $["${PR}"="931"] | $["${PR}"="929"]]?prefer-megafon)
; Распределяем нагрузку по симкам: 
; На Tele2 у нас 300 бесплатных минут, на МегаФоне - 150.
; Берём рандом от суммы, и если он меньше 300 - значит отдаём предпочтение tele2, инчаче - мегафону
exten => _89XX.,n,Set(BALANCE=${RAND(0,450)})
exten => _89XX.,n,GotoIf($[${BALANCE}<=300]?prefer-tele2:prefer-megafon)
; Предпочитаем Tele2 и проверяем, не занята ли его линия
exten => _89XX.,n(prefer-tele2),GotoIf($["${DEVICE_STATE(SIP/gsmline2)}" = "NOT_INUSE"]?tele2:megafon)
; Или всё же мегафон...
exten => _89XX.,n(prefer-megafon),GotoIf($["${DEVICE_STATE(SIP/gsmline1)}" = "NOT_INUSE"]?megafon:tele2)
; Соединяемся с возможностью перевода и продолжением выполнения диалплана.
exten => _89XX.,n(tele2),Dial(SIP/gsmline2/${EXTEN},120,Tg)
exten => _89XX.,n,Goto(after-dial,${EXTEN},1)                   ; Переходим к пост-обработке звонка
exten => _89XX.,n(megafon),Dial(SIP/gsmline1/${EXTEN},120,Tg)
exten => _89XX.,n,Goto(after-dial,${EXTEN},1)                   ; Переходим к пост-обработке звонка
; Если все каналы заняты
exten => _89XX.,n(noline),Set(__CALLED_GSM_NUM=${EXTEN})        ; Запоминаем вызываемый номер
exten => _89XX.,n,Goto(ivr-gsm,no-line,1)
; Если повесил трубку вызывающий абонент нам надо сделать постобработку
exten => h,1,Goto(after-dial,h,1)

В целом, для всех операций присутствуют комментарии. За само распределение отвечает хитрый трюк с рандомом. Сразу скажу — на практике такое, весьма приближённое, решение для балансировки показало результаты не хуже, чем полноценная БД с подсчётом продолжительности каждого звонка. Вы ведь покупаете бесплатные минуты с запасом, я надеюсь? Т.е. вам всего лишь нужно примерно соблюдать баланс, что с успехом и делает предложенное решение.

Контекст after-dial нужен для пост-обработки звонка. Там могут быть всякие действия с записью, тем же подсчётом длительности и т.д. Для данной статьи это всё неактуально.

А вот ivr-gsm представляет некий интерес:

; IVR для GSM линий
[ivr-gsm]
; Проигрываем предупреждение об отсутсвии свободных линий и ждём
exten => no-line,1,Background(no-gsm-line,,custom)
exten => no-line,n,Wait(10)
; Если есть запомненный номер - снова пытаемся его вызвать
exten => no-line,n,GotoIf($["${CALLED_GSM_NUM}" != ""]?to-gsm,${CALLED_GSM_NUM},1)
; Нету - просто тихо выходим
exten => no-line,n,Hangup()

В файлике no-gsm-line девушка должна говорить приятным голосом примерно следующее: Все исходящие линии заняты. Можете подождать или попробовать перезвонить позже.

В принципе, предложенный выше диалплан достаточно просто расширяется на большее количество линий. Просто после проверки DEVICE_STATE в случае занятости желаемой линии надо отправлять на некий экстеншн, который просто будет искать первую незанятую.

Если статья будет востребована — постараюсь продолжить серию и выложить, например, конфигурацию для цифрового факса.
Tags:
Hubs:
+31
Comments 15
Comments Comments 15

Articles