Если попробовать загуглить sms шлюзы с открытым исходным кодом, то скорее всего вы найдёте Jasmin Gateway и Kannel. Про Kannel легко найти хорошие статьи в рускоязычном сегменте интернета, а вот Jasmin таким вниманием обделен, поэтому я решил сегодня рассказать немного про его устройство.

Если сделать короткую выдержку

  1. Написан на языке Python
  2. Реализует SMPP 3.4
  3. Доступен как пакет начиная с ubuntu 15.10
  4. Для своей работы использует rabbitMQ и redis
  5. Существенно однопоточен(есть отдельные части, которые теоретически могут работать в несколько потоков, но на практике это не случается)
  6. Изменение настроек обычно идёт с даунтаймом
  7. Реальная производительность 100 сообщений в секунду (ограничение на вход, исходящие может отправлять со скоростью в 1000 сообщений) (версия 0.9b)
  8. Умеет принимать на вход смс с http протокола
  9. Умеет работать и как SMPP клиент, и как SMPP сервер

Что Jasmin умеет:

  1. Принимать смс отправленные через smpp или http
  2. Пересылать полученные смс на внешний smpp сервис (например на компанию поствщика мобильных услуг)
  3. Получать и передавать дальше статистику по смс
  4. Поддерживает различные кодировки смс
  5. Поддерживает длинные смс(состоящие из нескольких сообщений)
  6. Возможность гибкой настройки переключения каналов отправки смс в зависимости от отправителя\содержания\времени
  7. Учёт финансов и объема смс трафика

Как устроен Jasmin

Сами разработчики описывают архитектуру Jasmin так:

Общая архитектура Jasmin

Но я бы предложил посмотреть на архитектуру с точки зрения того, что происходит с сообщением, когда оно попадает на Jasmin сервер. Я рассмотрю путь для smpp сообщения, для http путь будет такой же:

  1. Слушая порт Jasmin получает сообщение. Он проверяет его на валидность исходя из контекста и, если оно не валидно, то отбрасывает его (тут кстати заключается одно из самых слабых мест Jasmin: почти все компании с которыми мне довелось работать не используют протокол Smpp как таковой, а используют какие-то свои решения, которые в отдельных моментах отличаются от протокола - такие места приходилось допиливать руками по месту).
  2. Распарсив сообщение Jasmin кладет его в очередь в rabbitMQ, для дальнейшей обработки и начинает дальше слушать порт.
  3. В другом слушателе(но в рамках того же потока), Jasmin получает сообщение из очереди и отдает его на intercept, если он настроен(intercept - это механизм в рамках которого вашему заранее заданному скрипту отдается сообщение, и скрипт может решить что с сообщением делать дальше: например можно отклонить, изменить получателя, или перенаправить в зависимоти от содержания сообщения. Самым большим минусом работы с intercept в Jasmin является то, что для изменения скрипта нужно сбрасывать перехватчик, что при большой нагрузке означает потерю части сообщений).
  4. После этого на основе получаенных команд от intercept и таблица оутинга jasmin вычисляет, куда нужно отправить сообщение, и кладёт его в отдельную очередь для каждого поставщика услуг по отправке смс
  5. В другом слушателе(но в рамках того же потока), Jasmin получает сообщение из очереди поставщика услуг и проверяет нужно ли по сообщению отдавать статистику, если да, то асинхронно записывает сообщение в redis, и отправляет сообщение

Получив же данные по статистике сообщения Jasmin делает следующее:

  1. Слушая порт Jasmin получает сообщение. Он проверяет его на валидность исходя из контекста и, если оно не валидно, то отбрасывает его.
  2. Распарсив сообщение Jasmin ищет в redis исходное сообщение, чтобы понять кому нужно отправить ответ. Сразу же после этого сообщение из redis удаляется. Если исходного сообщения нет, то ответ отбрасывается, иначе обратное сообщение кладется в очередь rabbitMQ.
  3. В другом слушателе(но в рамках того же потока), Jasmin получает сообщение из очереди и отправляет сообщение.

Оценка архитектуры

Все основное взаимодействие в Jasmin строится через rabbitMQ. С учётом возможностей асинхронной работы с ним, это позволяет достаточно сильно повысить производительность по сравнению с линейным однопоточным приложением. Так как Jasmin хранит в памяти только временную информацию (от отправки смс, до получения его статуса), то он весьма нетребователен к памяти.

С другой стороны, такая система передачи внутренних команд и сообщений может привести к достаточно сложно повторимым багам: мы получаем ответ на сообщение, до того как сообщение будет записано в redis(такой баг очень любопытно наблюдать и отлаживать). Похожие причины не дают разработчикам переписать код, на многопоточный вариант, без существенных доработок и усложнения архитектуры.

Отдельным плюсов для расширения Jasmin, является использование rabbitMQ и redis. Это позволяет подключать внешние модули и хранилища без необходимости каких-либо манипуляций с кодом Jasmin.

Выводы

В отличие от Kannel, Jasmin скорее всего не подходит для серьезной промышленной работы из-за своей низкой производительности. Однако Jasmin представляет собой отличную программу для экспериментов и создании своих программ на основе кода Jasmin.