В сентябре на 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 |
Широкие возможности для кастомизации | Потребует доработки под специфику нашей задачи |
Мессенджер | Способ доставки | Протокол | Сервер | Система хранения | |
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)
svboobnov
18.10.2016 12:19+2Хорошая идея. Но люди (пользователи со стороны компаний)…
Вот искал я работу, на нескольких работных сайтах отправлял отклики, и эти отклики висели в статусе «не прочитано» по 5..8 дней. Т.е., компания заплатила за сервис, как-то обучила кадровика, но кадровик не пользуется сервисом.
Боюсь, если вы не найдёте именно «ваших» клиентов (компании ориентированные на быстрое общение с клиентами), то дело не пойдёт.
Loki3000
18.10.2016 15:56+1По моему опыту, процентов 80 почтовых запросов различные организации остаются без ответа. Если вы не придумали механизм мотивировать организации отвечать клиентам, то не взлетит. Причем, отвечать должны не просто большинство организаций, а почти все. В противном случае сами пользователи перестанут этим пользоваться — какой смысл писать в пустоту?
dmitry_dvm
18.10.2016 17:00Как я пользовался 2гисом:
Живу в историческом центре, интернет только от одного прова и только адсл.
Зашел на 2гис, искал что-то рядом с домом. Вдруг увидел, что по информации 2гиса мой дом обслуживают аж 7 провайдеров. Обзвонил их всех. Обломался 7 раз подряд. Больше 2 гис не открывал и не открою.
ittakir
18.10.2016 20:03Проверил, большая часть СТО в гаражных кооперативах имеют эту опцию в интерфейсе. Но что-то я сомневаюсь, что кто-то там будет сидеть по уши в мазуте и читать эти чаты. Также и с остальными организациями.
svboobnov
19.10.2016 19:04Ну, почему же? Я вот пользовался как-то сайтом, на котором можно запросить примерную стоимость ремонта, и из 8 опрошенных сервисов ответили 6. Скорее всего, у слесаря есть жена / наёмный секретарь, который(ая) мониторит всякие сайты и озвучивает запросы мастеру.
Shkinev
18.10.2016 21:05Пара вопросов:
1. А если компания пока не хочет получать сообщения? Почему нет кнопки отключить? Знаю что можно удалить почту, но она то нужна.
2. Филиальная компания, в каждом городе свой менеджер. Сейчас все вопросы падают в общий личный кабинет компании, пускать всех менеджеров в личный кабинет не хочется. Как в этом случае отвечать на вопросы?
3. Почему время в чате Новосибирское, а не мое локальное?
datacompboy
Господи, ну КДПВ вызывает желание взвопить «вы вообще для кого это делали»…
2гис, пишем с карты ГОРОДА. Ну зачем, ЗАЧЕМ уточнять город?
Потому что как обычно, саппорт общий (или вообще сгружен в «индию»).
Ну почему нельзя для саппорта показывать ОТКУДА запрос пришел, с какого города?
Потому что каждый раз, когда я вижу у саппорта «КЫСА ТЫ С КАКОВА ГОРАДА», я закрываю этот саппорт нафиг.
То же самое относится к саппортам через соцсети, когда в профиле есть вся необходимая информация для решения проблемы — они всё равно задают десятки наводящих вопросов.
«Давайте я вам почитаю вслух с экрана» блин.
suslayer
Как увидел сей пост зашел оставить ровно такой же комментарий, присоединяюсь к каждому слову. Первый старт приложения он запрашивает/определяет город, но это слишком просто…
mrmixsun
В общем, вот и ответ на ваш вопрос. Мы, конечно, показываем в личном кабинете, откуда запрос, только отвечает-то на него сотрудник магазина, который может это упустить, потому что он один, например, разбирает запросы из 19 филиалов по разным городам.
Что на КДПВ это не учли — тут да, посыпаем голову пеплом, можно было и получше пример привести.
datacompboy
Это означает, что вы не учли в запросе. Выделяйте это в пришедшем запросе.
Вы же знаете кейсы! Точное определение в какой филиал писали — это ваша фишка!
svboobnov
По моему опыту, чтобы саппорт (часто перегруженный) понял, что от него требуют, приходится устраивать слайд-шоу:
СервисДеск: — К вам запрос от Ивана из отдела маркетнига
Пользователь: — тык мышкой
СервисДеск: Иван находится в регионе Санкт-Петербург
Пользователь: тык мышкой (ну, или Enter, если нашёл такую кнопку на клавиатуре)
СервисДеск: Ценники не соответствуют данным в базе
Пользователь: тык мышкой
…
datacompboy
Кстати, есть и менеджерское решение — каждому клиенту присылать «доведите до сведения вашего саппорта, что ГОРОД МОЖНО ПОСМОРЕТЬ В ОКНЕ ЧАТА».
И тупо не подписывать пока не пришлют скриншот где это — то есть, не подтвердят что видели.
Но это уже, конечно, перебор.
svboobnov
Не только инструкцию выдать, но и со стороны чата надо выделить город/филиал жирным и цветом (зелёным, к примеру).
staticlab
Да напишите прямо в окне чата: Клиент (Москва, 12:36). Зачем отправлять сотрудника куда-то ещё, если вы делаете специализированный чат поддержки? Это же основной рабочий инструмент. Не смотрите на универсальные вотсапы-телеграмы.
svboobnov
Люди не любят читать, им читать очень тяжело. Нас (тех, кто взял в руки книги), и развивался сам — меньшинство.
И более того: когда я работал программистом/консультантом 1С, мне часто звонили пользователи и спрашивали: «А почему так не работает?», а когда я к ним приходил, отбирал мышку (чтоб не нажимали «ОК» на рефлексах), и заставлял прочитать объяснение от системы, мне заявляли: «Ну я же не программист, чтобы эти штуки читать!!».
И да, порой приходилось читать с экрана.
datacompboy
В таких случаях в окне кнопочка «ОК» убирается в другую сторону, например.
То есть вот задача отключить рефлексы и заставить прочитать.
Выделяется жирным самое важное, например.