В сентябре на 2gis.ru появилась новая фича — b2c-мессенджер для общения с организациями. Чат очень удобен при поиске товара или услуги: можно написать сразу в несколько компаний, не нужно слушать голоса роботов-автоответчиков или ожидать на линии, пока оператор уточнит цену или остаток нужного товара. Выберите компанию, нажмите на иконку сообщения на карточке компании, и откроется чат.

Чтобы сделать мессенджер, нам пришлось немного поразбираться с тем, как вообще работают чаты и что под капотом у «больших братьев» типа WhatsApp или Telegram. Оказалось, всё не так страшно.

Зачем 2ГИС мессенджер


Начать, пожалуй, стоит не с мессенджера, а с того, как мы пришли к этой идее. Раньше мы показывали в справочнике email компании. Затем добавили форму связи с компанией прямо из справочника. Пишут меньше, чем звонят, но длина цепочки писем — в среднем от 2 до 10 сообщений. То есть мессенджер — логичное развитие уже существующей возможности отправлять письма в компании.

Как это работает сейчас




А ещё лучше — просто попробуйте задать нам вопрос. Мы в Новосибирске, и когда в Москве 18:00, у нас уже 22:00.

Беглый обзор: выбираем велосипед


Сперва мы посмотрели на BaaS (Backend as a Service), типа quickblox.com, backendless.com, sendbird.com и т.д. Все они предоставляют инструменты для добавления стандартной чатовой функциональности в мобильные или веб-приложения.

Реализация основного (отправка email-уведомлений в компании) и более продвинутых сценариев на основе существующего BaaS-решения потребует от нас как разработки собственных бекендов для интеграции через вебхуки, так и сверхусилий по интеграции с API вендора. Поэтому BaaS нам не подошёл. Кроме того, мы хотели иметь полный контроль над фичей, то есть не зависеть от сторонних решений. В конце концов, мы ведь инженеры и решились на эксперимент, чтобы получить новый для нас опыт.

А как сообщения-то доставлять?


Чтобы сделать свой бэкенд для мессенджера, сначала надо разобраться с кучей вопросов: как вообще передавать сообщения с клиента на сервер и обратно, где их хранить, как справиться с кучей одновременных соединений, как масштабировать сервис. Подумали, погуглили, собрали способы доставки сообщений в табличку для сравнения.

Таблица
Простите, но мы не смогли оформить таблицы в Хабраредакторе красиво.
Способ Описание Транспорт Протокол Поддержка в браузерах
WebSockets Клиент открывает постоянное соединение с сервером, используя API WebSockets. API Websockets TCP (необходим HTTPhandshake для открытия соединения) caniuse.com/#feat=websockets
Streaming Клиент открывает постоянное соединение с сервером. XMLHttpRequest (multipart onload), XMLHttpRequest (onprogress), Iframe tag HTTP caniuse.com/#feat=streaming
Server Sent Events Клиент открывает постоянное соединение с сервером, используя API Server Sent Events. API Server Sent Events HTTP Нет поддержки в IE, caniuse.com/#feat=eventsource
Polling Клиент периодически опрашивает сервер на предмет наличия новых сообщений. Любой доступный HTTP
Long Polling Клиент открывает долгоживущее соединение с сервером, которое не закрывается до появления нового сообщения или истечения таймаута. Сразу же после закрытия соединения клиент открывает новое. XMLHttpRequest
Script tag
HTTP
Browser Plugins (Java / Flash) Клиент открывает постоянное соединение с сервером, используя API браузерного плагина (Java или Flash). API плагина TCP/UDP


Большинство современных чат-приложений используют один из двух способов: long polling или websockets. Websockets обладает рядом преимуществ перед long polling (полнодуплексное соединение, отсутствие лишнего трафика при переконнектах), широко поддержан в браузерах и opensource-библиотеках, поэтому представляется оптимальным выбором для нашей задачи. Есть и недостатки: отличия имплементации в разных браузерах, более сложная логика на сервере.

Наиболее распространённый открытый general-purpose протокол для передачи данных — XMPP.
Положительные стороны Отрицательные стороны
Открытый стандарт IETF Избыточность передаваемой информации
Большое количество opensource-реализаций XML
Широкие возможности для кастомизации Потребует доработки под специфику нашей задачи
Обзор больших мессенджеров
Мессенджер Способ доставки Протокол Сервер Система хранения
WhatsApp Websockets Начинали с XMPP, но потом перешли на in-house протокол Вся серверная инфраструктура построена на Erlang + FreeBS, это позволяет держать до 2kk TCP-соединений на одном сервере. Начинали на ejabberd, но очень сильно его дорабатывали под себя впоследствии. Yaws, lighttpd Не хранят историю сообщений на сервере, сообщения удаляются с сервера после получения клиентом, mnesia
Line Нет веб-версии, только Chrome app Thrift для мобильных клиентов Java, С++, Nginx Начали с кластера из трёх инстансов Redis, после взрывного роста пользовательской базы смигрировали в Hbase «холодные» данные: историю сообщений и историю изменений юзеров / групп / контактов, MySQL для бэкапов и аналитики
Viber Нет веб-версии In-house С++, хостятся в AWS Начали с самописного in-memory хранилища написанного на C++, потом перешили на Mongo и Redis в качестве кеша. Mongo не устроила по производительности, в итоге мигрировали на Couchbase
Facebook Messenger Long polling In-house json-based для веба, Thrift для мобильных приложений Erlang — очереди сообщений. C++ — сервис информации о присутствии, хранилище истории сообщений. PHP — фронтенд, обрабатывает все пользовательские запросы, кроме long polling. Сервисы между собой общаются через Thrift
Очередь на MySQL, HBase
Slack Websockets In-house json-based Java messaging server, LAMP for core app/APIs, хостятся в AWS Redis, MySql, Apache Solr

Однако, изучив решения различных «больших» мессенджеров, мы не обнаружили ни одной значимой истории успеха использования XMPP без его последующей доработки и кастомизации. Поразмыслив, решили разработать простой json-based протокол, изначально учитывающий специфику нашей задачи.

Технологический стек


Так исторически сложилось, что наш основной бэкенд-сервис — АПИ 2ГИС — был написан на PHP5. Два года назад мы приняли решение уйти от PHP, мигрировать на Scala и Go. Go позволяет очень легко строить довольно сложные concurrent-программы. Это стало решающим фактором при выборе его как основной технологии реализации мессенджера. Да и кое-какой опыт разработки на Go у нас уже был.

Итак, бэкенд пишем на Go, а фронтенд — на React + Redux. В качестве системы обмена сообщениями мы выбрали RabbitMQ; для хранения данных используем Redis и PostgeSQL. Приложение пакуем в Docker-контейнер и деплоим через Gitlab-CI в платформу Deis.

Как я сказал выше, мы хотели использовать вебсокеты, но когда дело дошло до реализации, чуть переосмыслили решение. Дело в том, что мы хотели выпустить фичу как можно быстрее, чтобы проверить гипотезу (пресловутый MVP). Чтобы ускориться, решили разделить логику на использование вебсокетов для отправки данных с клиента на сервер и простейшего REST API в другую сторону.

Что с уведомлениями?


Внезапно самым сложным в реализации оказались нотификации. Казалось бы, всё просто: отправляй пуши в телефон, письма в почту, отображай нотификашки в браузере. Но нашей задачей было сделать так, чтобы все уведомления прочитывались максимально скоро и при этом не превращались в поток спама.

Есть решение, которое называют каскадной системой нотификаций. Например, у нас есть три основных канала: уведомления на 2gis.ru, письма и пуши на телефон.

В этом случае система нотификаций принимает вид:

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

На самом деле, пуш-нотификации из этого сценария прямо сейчас мы не отправляем, но очень скоро научимся это делать.

Планы


В ближайших планах — добавить функционал мессенджера в мобильные приложения 2ГИС, добить основной функционал (возможность приложить аттач к сообщению, браузерные пуши), реализовать продвинутые сценарии общения (например, запрос в несколько компаний сразу).

Пока компании не привыкли к мессенджеру, и не все быстро отвечают на сообщения. Но мы верим, что у взаимодействия людей и компаний через текст большое будущее.
Поделиться с друзьями
-->

Комментарии (18)


  1. datacompboy
    18.10.2016 11:57
    +7

    Господи, ну КДПВ вызывает желание взвопить «вы вообще для кого это делали»…
    2гис, пишем с карты ГОРОДА. Ну зачем, ЗАЧЕМ уточнять город?
    Потому что как обычно, саппорт общий (или вообще сгружен в «индию»).
    Ну почему нельзя для саппорта показывать ОТКУДА запрос пришел, с какого города?

    Потому что каждый раз, когда я вижу у саппорта «КЫСА ТЫ С КАКОВА ГОРАДА», я закрываю этот саппорт нафиг.

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


    1. suslayer
      18.10.2016 12:02
      -2

      Как увидел сей пост зашел оставить ровно такой же комментарий, присоединяюсь к каждому слову. Первый старт приложения он запрашивает/определяет город, но это слишком просто…


    1. mrmixsun
      18.10.2016 12:02

      Потому что как обычно, саппорт общий (или вообще сгружен в «индию»).

      В общем, вот и ответ на ваш вопрос. Мы, конечно, показываем в личном кабинете, откуда запрос, только отвечает-то на него сотрудник магазина, который может это упустить, потому что он один, например, разбирает запросы из 19 филиалов по разным городам.

      Что на КДПВ это не учли — тут да, посыпаем голову пеплом, можно было и получше пример привести.


      1. datacompboy
        18.10.2016 12:25

        Это означает, что вы не учли в запросе. Выделяйте это в пришедшем запросе.
        Вы же знаете кейсы! Точное определение в какой филиал писали — это ваша фишка!


        1. svboobnov
          18.10.2016 12:35

          По моему опыту, чтобы саппорт (часто перегруженный) понял, что от него требуют, приходится устраивать слайд-шоу:
          СервисДеск: — К вам запрос от Ивана из отдела маркетнига
          Пользователь: — тык мышкой
          СервисДеск: Иван находится в регионе Санкт-Петербург
          Пользователь: тык мышкой (ну, или Enter, если нашёл такую кнопку на клавиатуре)
          СервисДеск: Ценники не соответствуют данным в базе
          Пользователь: тык мышкой


      1. datacompboy
        18.10.2016 12:35
        -1

        Кстати, есть и менеджерское решение — каждому клиенту присылать «доведите до сведения вашего саппорта, что ГОРОД МОЖНО ПОСМОРЕТЬ В ОКНЕ ЧАТА».
        И тупо не подписывать пока не пришлют скриншот где это — то есть, не подтвердят что видели.

        Но это уже, конечно, перебор.


        1. svboobnov
          18.10.2016 12:39
          +1

          Не только инструкцию выдать, но и со стороны чата надо выделить город/филиал жирным и цветом (зелёным, к примеру).


      1. staticlab
        19.10.2016 11:07

        Да напишите прямо в окне чата: Клиент (Москва, 12:36). Зачем отправлять сотрудника куда-то ещё, если вы делаете специализированный чат поддержки? Это же основной рабочий инструмент. Не смотрите на универсальные вотсапы-телеграмы.


    1. svboobnov
      18.10.2016 12:29

      Люди не любят читать, им читать очень тяжело. Нас (тех, кто взял в руки книги), и развивался сам — меньшинство.
      И более того: когда я работал программистом/консультантом 1С, мне часто звонили пользователи и спрашивали: «А почему так не работает?», а когда я к ним приходил, отбирал мышку (чтоб не нажимали «ОК» на рефлексах), и заставлял прочитать объяснение от системы, мне заявляли: «Ну я же не программист, чтобы эти штуки читать!!».
      И да, порой приходилось читать с экрана.


      1. datacompboy
        18.10.2016 12:33

        В таких случаях в окне кнопочка «ОК» убирается в другую сторону, например.
        То есть вот задача отключить рефлексы и заставить прочитать.
        Выделяется жирным самое важное, например.


  1. svboobnov
    18.10.2016 12:19
    +2

    Хорошая идея. Но люди (пользователи со стороны компаний)…
    Вот искал я работу, на нескольких работных сайтах отправлял отклики, и эти отклики висели в статусе «не прочитано» по 5..8 дней. Т.е., компания заплатила за сервис, как-то обучила кадровика, но кадровик не пользуется сервисом.
    Боюсь, если вы не найдёте именно «ваших» клиентов (компании ориентированные на быстрое общение с клиентами), то дело не пойдёт.


  1. Loki3000
    18.10.2016 15:56
    +1

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


  1. dmitry_dvm
    18.10.2016 17:00

    Как я пользовался 2гисом:
    Живу в историческом центре, интернет только от одного прова и только адсл.
    Зашел на 2гис, искал что-то рядом с домом. Вдруг увидел, что по информации 2гиса мой дом обслуживают аж 7 провайдеров. Обзвонил их всех. Обломался 7 раз подряд. Больше 2 гис не открывал и не открою.


    1. EndUser
      22.10.2016 18:42

      Там есть форма — данные не верны. Нет?


  1. ittakir
    18.10.2016 20:03

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


    1. svboobnov
      19.10.2016 19:04

      Ну, почему же? Я вот пользовался как-то сайтом, на котором можно запросить примерную стоимость ремонта, и из 8 опрошенных сервисов ответили 6. Скорее всего, у слесаря есть жена / наёмный секретарь, который(ая) мониторит всякие сайты и озвучивает запросы мастеру.


  1. Shkinev
    18.10.2016 21:05

    Пара вопросов:
    1. А если компания пока не хочет получать сообщения? Почему нет кнопки отключить? Знаю что можно удалить почту, но она то нужна.
    2. Филиальная компания, в каждом городе свой менеджер. Сейчас все вопросы падают в общий личный кабинет компании, пускать всех менеджеров в личный кабинет не хочется. Как в этом случае отвечать на вопросы?
    3. Почему время в чате Новосибирское, а не мое локальное?


  1. edikl
    19.10.2016 07:23

    Здравствуйте. А в приложении для Android скоро эта функция появится?