Не откладывайте в почтовый ящик: b2c-мессенджер 2ГИС



    В сентябре на 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ГИС, добить основной функционал (возможность приложить аттач к сообщению, браузерные пуши), реализовать продвинутые сценарии общения (например, запрос в несколько компаний сразу).

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

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

      То же самое относится к саппортам через соцсети, когда в профиле есть вся необходимая информация для решения проблемы — они всё равно задают десятки наводящих вопросов.
      «Давайте я вам почитаю вслух с экрана» блин.
      • –2
        Как увидел сей пост зашел оставить ровно такой же комментарий, присоединяюсь к каждому слову. Первый старт приложения он запрашивает/определяет город, но это слишком просто…
        • 0
          Потому что как обычно, саппорт общий (или вообще сгружен в «индию»).

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

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

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

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

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

              • 0
                Люди не любят читать, им читать очень тяжело. Нас (тех, кто взял в руки книги), и развивался сам — меньшинство.
                И более того: когда я работал программистом/консультантом 1С, мне часто звонили пользователи и спрашивали: «А почему так не работает?», а когда я к ним приходил, отбирал мышку (чтоб не нажимали «ОК» на рефлексах), и заставлял прочитать объяснение от системы, мне заявляли: «Ну я же не программист, чтобы эти штуки читать!!».
                И да, порой приходилось читать с экрана.
                • 0
                  В таких случаях в окне кнопочка «ОК» убирается в другую сторону, например.
                  То есть вот задача отключить рефлексы и заставить прочитать.
                  Выделяется жирным самое важное, например.
              • +2
                Хорошая идея. Но люди (пользователи со стороны компаний)…
                Вот искал я работу, на нескольких работных сайтах отправлял отклики, и эти отклики висели в статусе «не прочитано» по 5..8 дней. Т.е., компания заплатила за сервис, как-то обучила кадровика, но кадровик не пользуется сервисом.
                Боюсь, если вы не найдёте именно «ваших» клиентов (компании ориентированные на быстрое общение с клиентами), то дело не пойдёт.
                • +1
                  По моему опыту, процентов 80 почтовых запросов различные организации остаются без ответа. Если вы не придумали механизм мотивировать организации отвечать клиентам, то не взлетит. Причем, отвечать должны не просто большинство организаций, а почти все. В противном случае сами пользователи перестанут этим пользоваться — какой смысл писать в пустоту?
                  • 0
                    Как я пользовался 2гисом:
                    Живу в историческом центре, интернет только от одного прова и только адсл.
                    Зашел на 2гис, искал что-то рядом с домом. Вдруг увидел, что по информации 2гиса мой дом обслуживают аж 7 провайдеров. Обзвонил их всех. Обломался 7 раз подряд. Больше 2 гис не открывал и не открою.
                    • 0
                      Там есть форма — данные не верны. Нет?
                    • 0
                      Проверил, большая часть СТО в гаражных кооперативах имеют эту опцию в интерфейсе. Но что-то я сомневаюсь, что кто-то там будет сидеть по уши в мазуте и читать эти чаты. Также и с остальными организациями.
                      • 0
                        Ну, почему же? Я вот пользовался как-то сайтом, на котором можно запросить примерную стоимость ремонта, и из 8 опрошенных сервисов ответили 6. Скорее всего, у слесаря есть жена / наёмный секретарь, который(ая) мониторит всякие сайты и озвучивает запросы мастеру.
                      • 0
                        Пара вопросов:
                        1. А если компания пока не хочет получать сообщения? Почему нет кнопки отключить? Знаю что можно удалить почту, но она то нужна.
                        2. Филиальная компания, в каждом городе свой менеджер. Сейчас все вопросы падают в общий личный кабинет компании, пускать всех менеджеров в личный кабинет не хочется. Как в этом случае отвечать на вопросы?
                        3. Почему время в чате Новосибирское, а не мое локальное?
                        • 0
                          Здравствуйте. А в приложении для Android скоро эта функция появится?
                          • 0
                            Если Вы используете Scala в разработке, то чем не устроило решение на Akka HTTP? Веб-сокеты очень удачно ложатся на Akka Stream, а в Akka HTTP даже готовый route есть- handleWebSocketMessages, в который просто надо передать наш Flow[Message, Message]. В Actor Platform например так (там вообще один Flow используют и для TCP/IP, и для WebSockets — по мне очень удачное решение).

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое