Прошло много времени с моего последнего поста о Channels, и вместе с этим много чего случилось — API разработано и стабилизировано, добавился функционал вроде "контроля за переполнением" (backpressure), ситуация с бекендами выглядит гораздо лучше, особенно после того, как слой взаимодействия локальных и удаленных машин стал немного взрослее.
С другой стороны, однако, появилось недопонимание и озабоченность относительно направления, в котором развивается Channels; направления, которое этот проект задает для Django и Python. При разработке Channels пришлось касаться и даже бороться с моими собственными переживаниями об это направлении, выбирать правильный набор компромиссов — иногда даже из двух одинаково правильных вариантов.
Я не стал публично обсуждать мои обоснования и видение развития для Channels настолько, насколько я бы мог; я надеюсь, этот пост немного прояснит мою точку зрения. Позвольте обозначить определенное множество проблем, которые я пытаюсь решить, объяснить, почему я выбрал тот дизайн архитектуры, который я выбрал, и рассказать о дальнейших шагах развития.
Это всё не только о WebSocket-ах
У многих людей реакция на Channels двойная: во-первых, почему-то все считают Channels только способом получить поддержку WebSocket в Django (это штука, которая случайно получилась в ходе разработки, а не единственная причина создания проекта — позже объясню подробнее), и во-вторых, потом все говорят, что реализация WebSocket-ов через распределенную систему обработки сообщений это стрельба из пушки по воробьям.
Тут они правы. Асинхронные возможности Python становятся всё лучше, и можно достаточно просто запилить WebSocket-сервер за несколько часов с помощью существующих библиотек (например, Autobahn). Возможно Вам придется стандартизировать интерфейс, чтобы Вы могли общаться между этим сервером и остальным вашим проектом, но это не сложно.
Это был первый путь, по которому я пошел, и именно так работали ранние версии Channels (тогда они еще назывались django-onair). Однако, по мере разработки и обдумывания, как запускать всё в серьезных масштабах, стала ясной реальная проблема.
Понимаете, обработка WebSocket-протокола ИМХО не проблема; проблема — использование этих сокетов в большем проекте. Большинство сценариев использования сокетов событийно-ориентированные: вы посылаете данные в сокет, когда что-то происходит "снаружи", например, сохраняется модель, меняется внешняя система, или получено другое сообщение в другом WebSocket-е.
Все эти разные источники событий живут в разных местах развернутого проекта. Если Вы пойдете по протоптанной тропинке запуска кучи серверов, каждый с вебсервером и Python-кодом, Вы быстро осознаете, что необходим способ взаимодействия между серверами. Поддержка WebSocket-ов это одна из задач, а вот отправка сообщения группам сокетам, когда что-то происходит — вот действительно то, ЧТО Вы пишите, когда разрабатываете приложения.
Представьте себе чат-сервер большого масштаба, где разные люди залогинены на разных физических машинах. Как Вы будете отправлять входящие сообщения чата остальным собеседникам того же чата, на всех остальных серверах? Где Вы будете отслеживать, кто находится в чате? Что будете делать с протухшими сессиями?
А как насчет системы, где вы отправляете уведомления пользователям, когда кто-то просматривает их профиль? Эти просмотры наверняка будут происходить на разных серверах, так как вы получите это событие через сервер, на котором WebSocket пользователя только что отвалился?
Это очень сложная проблема, для решения которой и предназначен Channels; это не просто обработка протокола WebSocket, а задача построения сложных приложений вокруг WebSocket-ов. Обработка сообщений в распределенных системах это действительно трудно, и я верю, что это одна из тех задач, которые гораздо больше выигрывают от нескольких общих полированных решений, чем от от тупой инструкции, как заставить работать вместе кучу асинхронного кода.
Обнадеживающая асинхронность
Одна из вещей, которую делает Channels — это запуск Django в синхронной манере, что позволяет Вам писать все обработчики сообщений таким же образом. Channels просто выполняет этот код в быстром цикле, отбивая Вам охоту делать в этом цикле блокирующие операции.
Проблема в том, что все кажется считают, что это единственный способ, которым предполагается писать код для Channels. Это не так: имеется в виду, что Channels делает коммуникацию между синхронными и асинхронными программами проще в целом, предлагая Вам выбор лучшего инструмента для задачи (и я бы поспорил, что для огромного числа простых бизнес-сценариев Вы возможно захотите синхронный код, так как его гораздо проще писать и поддерживать).
На самом деле, Channels делают написание полностью асинхронных приложений, общающихся с остальным Python-проектом проще чем когда либо. В конце концов, это всё сервер с WebSocket-интерфейсов Daphne. Хотите асинхронные запросы по URL? Коммуникации с Интернетом Вещей? Исходящие сокеты? Вы можете писать асинхронный код как обычно, а потом, благодаря Channels, оставить остальной код в знакомом синхронном фреймворке и общаться в обе стороны с вашим специализированным кодом.
Когда у Вас есть проект, запускающийся поверх Channels, становится проще чем когда либо добавлять больше асинхронного кода в виде отдельного процесса для выполнения какой-нибудь новой задачи, и иметь понятное рабочее решение для взаимодействия с другими асинхронными и синхронными процессами. Большую пользу приносят опыт коммьюнити и документация, статьи об использовании и разобранные примеры других кто уже все это проходил — в конце концов, всё это сделано на единой общей платформе в едином стиле.
Больше протоколов
Конечно, всё это приводит нас обратно к идее о том, что Channels не только про WebSocket-ы. Это униварсальная кросс-процессная система событий для Django (и, надеюсь, вообще для Python). WebSocket-ы это один из протоколов, поддержка которого указана в Channels, однако уже идет работа над интерфейсными серверами Slack (позволяющими Вам добавить интеграцию чата в серверный кластер), и почтой (что позволит писать обработчиков для входящих сообщений рядом с Вашим HTTP и WebSocket-кодом).
Спецификации формата сообщений также позволяют делать альтернативные реализации; так же как существует много WSGI серверов, форматы сообщений позволяют существовать большому числу ASGI-совместимых HTTP и WebSocket-серверов, даже работающих вместе на одной системе.
Некоторые протоколы не требуют функционала широковещательных сообщений, как WebSocket-ы, особенно, если в них нет stateful-коннектов, однако хорошая архитектура слоя каналов позволяет маршрутизировать их на тот же сервер. Хотя слой каналов проектировался как кросс-процессный и не зависящий от сети, это не означает, что каждое сообщение должно маршрутизироваться через центральную точку. Структура Channels была разработана так, чтобы позволить отличать сообщения, которые могут быть обработаны локально, от тех, что обязательно должны быть отправлены куда-либо еще.
На самом деле, с недавним добавлением RedisLocalChannelLayer в пакет asgi_redis, Вы можете запускать сервера в стандартном режиме мастер-воркер (в оригинале socket-terminator and worker pair), и уровень каналов будет стараться оставлять как можно больше сообщений на одной машине, передавая данные по сети только тогда, когда ему необходимо найти определенный сокет, чтобы отправить что-нибудь другому пользователю, ну или отправить групповое сообщение.
Распределенные системы это сложно
В своем ядре Channels решает проблему коммуникации и широковещательных сообщений в распределенных системах. Распределенные системы — это область где нет идеальных решений; Вам всегда придется идти на компромиссы. Один из вариантов — логика At-least-once и at-most-once (гарантированная доставка и гарантированное отсутствие дублей). CAP-теорема про распределенные базы данных — это побочный эффект других компромиссов.
Channels идет на определенный набор компромиссов, с целью быть лучшим решением для сценариев и протоколов, которые широко используются в Web, особенно для WebSocket-ов. К примеру, потери пакетов и закрытие сокета предпочтительнее дублирующимся пакетам; клиент может переподключиться, а вот запилить распределенную систему дедупликации для действий — это дико сложно, если только Вы не сделаете абсолютно все действия идемпотентными. Я надеюсь написать отдельный пост о том, какие конкретно компромиссы я выбрал и какие были альтернативы; поверьте, каждый из них был выбран по конкретной причине.
Channels никогда не будет подходить для любого случая, это недостижимая цель. Вместо этого, предполагается решение примерно для 90% случаев — что-то, что не всегда идеально, но в целом делает то, что Вы хотите; решение, где компромиссы перекрываются огромными преимуществами единой общей платформы и коммьюнити, которое образовывается вокруг проекта. Во многом это как Django, который не может быть идеальным web-фреймворком; просто невозможно решить каждую проблему каждого разработчика, но мы можем решить 90% остальных проблем, которые возникают у разработчиков постоянно, имея при этом стандарты и архитектуру, следствием которых является переиспользование кода и легкость в освоении.
ASGI API, на основе которого написан Channels, умышленно сделано очень гибким. Оно специфицирует необходимый для работы минимум, так что Вы можете получить полноценно работающую среду для разных бекендов, оставляя многое на усмотрение конкретному бэкенду, и за счет этого получая большую гибкость в механизме передачи сообщений. По мере роста, Ваши потребности будут меняться и уточняться; в этом поможет абстракция слоя каналов, позволяя расти не выходя за пределы абстракции, будучи гибкой и одновременно сохраняя то же самое базовое API, с которым Вы работали, когда начинали; каналы, группы, отправка и получение.
Я не ожидаю появления сайтов из Top-100, которые бы работали с немодифицированным слоем каналов ASGI, аналогично тому, как они бы не работали с дефолтной поставкой Django. По мере роста и уточнения требований, Вам понадобится решение, которое оставляет возможность неспешно и аккуратно заменить его, и моей целью при разработке ASGI было то, что даже когда Вы уберете весь код Channels, Вы останетесь с абстракцией и архитектурой, которая работает с намного более специализированными распределенными системами и событиями. Так же как и ядро Django, ASGI позволяет глубоко модицифировать и заменять части кода по мере роста, и уберется с Вашей дороги, когда больше не будет Вам нужно.
В конце концов, это и есть философия Channels — решение, которое предлагается не как панацея, а как общая база для упрощения разработки приложений, которые используют несколько серверов и работают со statful-протоколами вроде WebSockets. Небольшие команды, web-студии, и сайты средних размеров могут использовать его без изменений; большие проекты скорее всего потребуют допиливания бекенда слоя каналов, и, может быть, часть базовых обработчиков, но все еще могут выиграть от знакомства разработчиков с абстракцией и паттернами Channels.
Забегая вперед
С учетом всего сказанного, какой путь предстоит Channels и ASGI? WebSocket-проекты сами по себе пока еще находятся во младенческом возрасте: очень немногие развернуты в достаточном масштабе; не говоря уж об использовании Channels — поэтому мы должны пройти период взросления не смотря ни на что. Сайты, которые уже используют Channels в продакшне, и отзывы, которые я получаю о нем, были в основном позитивными, так что тут мы на правильном пути ко взрослой жизни.
Daphne сама по себе во многом основана на коде Twisted для обработки HTTP, и на Autobahn для WebSocket-ов — две библиотеки с длинной историей стабильности — в то время как ASGI основан на нашем опыте и исследованиях масштабируемых событийных систем в Eventbrite, моих предыдущих экспериментах с распределенной передачей сообщений, исследованиях и примерах из IT, и обсуждениях похожих проблем с коллегами. ASGI настолько же прочный, насколько возможно в ситуации, когда нет успешного open-source решения для примера.
Отзывы, которые я получил в процессе обсуждения включения Channels в Django-1.10 были очень ценными (включить в релиз до дедлайна не удалось, но разработка пакета продолжится как внешнее приложение для Django-1.10). Некоторые последние изменения и разработки, такие как "backpressure" и "local-and-remote Redis backend", основаны как раз на отзывах к предложению, и я предполагаю, что добавится еще больше улучшений, по мере того как всё больше проектов на Channels выйдут в продакшн.
Вместе с тем, я считаю, что фундаментальная абстракция архитектуры, "распределенная передача сообещний", является достаночно прочным и грамотным API для построения Django-приложений будущего, с потребностями и сложностью Web-приложений, гораздо большими, чем простая обработка запросов-ответов. Это место где Django и Python имеют возможность показать пример в архитектуре и эксплуатации такого типа приложений.
Я также заинтересован в получении статуса PEP для форматов сообщений и интерфейса ASGI стандарта, но до этого я собираюсь попробовать другие web-фреймворки, чтобы убедиться, что этот стандарт действительно работает независимо от фреймворка, как всегда и предполагалось. Так же необходимо испытать и исправить потенциальные проблемы в реальных сценариях использования.
Я не уверен, какое будущее уготовано Channels — в идеальном мире этот проект позволил бы Django и Python стать решением для гораздо большего класса проблем, чем те, для которых они используются сейчас; превнося лучшие качества языка, фреймворка и коммьюнити растущей аудитории разработчиков, сталкивающихся с написанием больших систем со stateful-протоколами. Также возможно, что проект останется слоем управления WebSocket-ами для Django, но даже для этой цели важно, чтобы его архитектура стала идеальной.
Я надеюсь, что этот пост осветил некоторый контекст и планы на Channels; отзывы сообщества очень важны для развития проекта, и если Channels Вам помог, или у Вас есть еще вопросы, оставайтесь на связи и дайте мне знать. Важно, чтобы каждый понимал и реализацию, и контекст решаемой проблемы — одно без другого ничего не значит. И я надеюсь, что в перспективе мы будем иметь ясное представление, какое значение они имеют вместе.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (4)
maxel_e
21.06.2016 09:05Считаю, что такой путь для Django именно то, что необходимо. Реалтайм значительно расширит сферу применения. Наблюдаю за проектом давно и жду включения в Django. Удачи!
amureki
27.06.2016 14:23Попробовал ради интереса написать простенький сайт-голосовалку в реальном времени с применением channels:
https://github.com/amureki/lunch-with-channels
Забавная штука, ждем внедрения в основную ветку Django.
Amareis
Просто интересно, мне одному "Вы" с большой буквы глаза режет?
А по теме статьи — интересная штука, раньше не слышал. Надо бы обязательно потыкать палочкой, выглядит многообещающе.