Разработка системы синхронизации в реальном времени с использованием SockJS, Django, Tornado и ZeroMQ

от автора

Не так давно, разрабатывая очередной программный продукт, наша команда разработчиков столкнулись с задачей реализации полноценной системы синхронизации пользовательских данных в реальном времени, путем рассылки (PUSH метод) изменений сервером. В самом приложении объем данных был не велик, но они могли просматриваться несколькими пользователями одновременно. Поэтому нам был необходим легковесный и достаточно производительный подход к синхронизации данных в рамках Веб-приложения. После того как были рассмотрены различные пути к решению этой задачи, мы остановили свой выбор на достаточно популярном эмуляторе WebSocket’ов – SockJS, который использует различные алгоритмы обмена данными между клиентом и сервером, в зависимости от браузера, которым пользуется клиент. В рамках данной статьи я не буду заострять внимание на том, почему был сделан именно такой выбор (по этому поводу написано немало статей, в том числе и на хабрахабре), а просто скажу, что мы ещё ни разу об этом не пожалели.

Изначально при изучении стандартных подходов к реализации подобного рода задач мы столкнулись с одной проблемой. Эта проблема заключалась в том, что взаимодействие с нашей системой производилось не только посредством веб интерфейса, но также посредством использования API сторонними продуктами, которые мы не могли контролировать. И конечный пользователь нашего продукта, безусловно, ожидает увидеть всю информацию об изменениях в данных, которые его касаются. Стандартный подход использования sockjs сервера подразумевает, что уведомления об изменении каких-либо данных в системе будут посылаться с использованием того же самого JS клиента, который используется для получения информации об этих изменениях. Именно поэтому в нашем случае такой подход был бы неприменим.

В этой статье я хотел бы рассказать о том, как мы решили эту задачу.

Итак, исходные данные:

  • портал (написан на Django) с API
  • веб клиент (с sockjs-client библиотекой для организации синхронизации)
  • SockJS сервер (в виде tornado-sockjs, что являлось одной из причин, по которой мы сделали свой выбор в сторону этого продукта)

Общую задачу можно разделить на две подзадачи:

  • Организация получения уведомлений конечным пользователем об изменениях в системе (тривиальная задача)
  • Создание сообщений/уведомлений о наличии изменений в системе используя средства Django (напомним, что мы не можем использовать стандартный подход и создавать такие сообщения, используя стандартный JS клиент, т.к. в таком случае нам не удастся обработать изменения, сделанные при помощи набора APIs).

Изучив доступную информацию, стало ясно, что без “посредника” эту задачу решить нельзя. В качестве посредника была выбрана система обмена сообщениями, а именно ZeroMQ. Среди основных достоинств именно этой системы стоит отметить её простоту, легкость, а также поддержку языка Python (что для нас является весьма важным условием).

В целом полученное решение можно изобразить в виде схемы, представленной ниже.

Изменения данных в системе, сделанные конечным пользователем с использованием веб интерфейса или APIs, в конечном счете попадают на наш Django сервер, где через ZeroMQ передаются в SockJS сервер, который, в свою очередь, уведомляет пользователей о факте изменения данных отображаемого объекта.

Давайте перейдем к коду

Веб клиент

Для получения уведомлений нам необходимо подключить библиотеку sockjs-client, устанавливать соединение с сервером и подписаться на нужные события. Сделать это можно примерно следующим образом:

<script src="/js/sockjs-0.3.4.min.js"></script> <script> var SyncServer = new SockJS("http://example.com/echo"); SyncServer.onmessage = function(e) {     // ваш обработчик сообщений }; </script> 

ZeroMQ сервер

Для реализации этой части нам понадобится сам ZeroMQ и библиотека Python для работы с ним — pyzmq. После этого необходимо настроить и запустить сервер в режиме прокси (данный режим называется FORWARDER). Сделать это можно буквально несколькими строками кода:

import zmq  def main():     try:         context = zmq.Context(1)         # Socket facing clients         frontend = context.socket(zmq.SUB)         frontend.bind("tcp://127.0.0.1:XXXX")          frontend.setsockopt(zmq.SUBSCRIBE, "")          # Socket facing services         backend = context.socket(zmq.PUB)         backend.bind("tcp://127.0.0.1:YYYY")          zmq.device(zmq.FORWARDER, frontend, backend)     except Exception, e:         # Handle exception         pass     finally:         frontend.close()         backend.close()         context.term()  if __name__ == '__main__':     main()  

Запустив подобный скрипт, вы получите proxy сервер, ожидающий сообщения на порту XXXX и отправляющих их на порт YYYY.

SockJS сервер

В качестве SockJS сервера был выбран стандартный SockJS-tornado с некоторыми небольшими изменениями, позволяющими ему принимать сообщения от ZeroMQ сервера (эти изменения вы можете найти в __init__ методе класса SockJSMyRouter)

from sockjs.tornado import SockJSConnection, SockJSRouter from tornado import ioloop as tornado_ioloop, web import zmq from zmq.eventloop import ioloop from zmq.eventloop.zmqstream import ZMQStream  ioloop.install() io_loop = tornado_ioloop.IOLoop.instance()  class SyncConnection(SockJSConnection):     _connected = set()     stream = None      def __init__(self, session):         super(SyncConnection, self).__init__(session)         self.stream.on_recv(self.on_server_message)      def on_open(self, request):         self._connected.add(self)      def on_message(self, data):         pass      def on_server_message(self, data):         message = "your message"         self.broadcast(self._connected, {'message': message})      def on_close(self):         self._connected.remove(self)   class SockJSMyRouter(SockJSRouter):      def __init__(self, *args, **kw):         super(SockJSMyRouter, self).__init__(*args, **kw)         socket = context.socket(zmq.SUB)         socket.setsockopt(zmq.SUBSCRIBE, "")         socket.connect("tcp://127.0.0.1:YYYY")         self._connection.stream = ZMQStream(socket, self.io_loop)  if __name__ == '__main__':     context = zmq.Context()      EchoRouter = SockJSMyRouter(SyncConnection, '/echo')      app = web.Application(EchoRouter.urls)     app.listen("ZZZZ")     io_loop.start() 

Django сервер

В рамках Django сервера нам необходимо добавить лишь несколько строк, ответственных за создание уведомлений и отправку их на наш ZeroMQ сервер. Сделать это можно следующим образом:

import zmq  context = zmq.Context() socket = context.socket(zmq.PUB) socket.connect("XXX") socket.send("your message") socket.close() 

Вот похоже и всё! Дописав методы, отвечающие за создание и обработку сообщений в Django, Sockjs сервере и клиенте, вы получите полноценную работающую систему синхронизации в реальном времени для вашего приложения.

ссылка на оригинал статьи http://habrahabr.ru/post/181037/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *