资讯动态

Zulip 服务器与 Web 应用架构全景解析:Django 主服务、Tornado 实时推送与多组件协作设计

发布时间:2026/9/12 23:25:17 来源:尧图企业网站定制
Zulip 服务器与 Web 应用架构全景解析Django 主服务、Tornado 实时推送与多组件协作设计【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulip本指南以 Zulip 官方架构总览为骨架系统拆解这套开源团队聊天服务器Python 3.x Django与 Web 应用JavaScript/TypeScript的整体设计从 Django 主应用服务器与 Tornado 实时推送系统的职责划分到 nginx 路由、Supervisor 进程编排、memcached 缓存、Redis 限流、RabbitMQ 队列与 PostgreSQL 持久化的协作关系。读完本文你将掌握 Zulip 的一次请求从 nginx 入口到各组件落库/推送的完整链路并能在源码与配置层面定位每个组件对应的实现文件为二次开发、性能排查与生产部署打下基础。关键代码库与周边生态Zulip 的主代码库是一个单体仓库同时包含三大部分后端使用 Python 3.x 与 Django 框架实现Web 应用使用 JavaScript 与 TypeScript 实现入站 Webhook 集成库面向其他服务与应用的大量集成即zerver/webhooks/目录下数百个第三方集成。除主仓库外Zulip 官方还维护多个独立仓库基于 Flutter 构建、同时支持 iOS 与 Android 的官方移动端 Zulip Flutter覆盖 macOS、Linux、Windows 的官方桌面端 Zulip Desktop以及官方终端客户端 Zulip Terminal。此外还有一批胶水代码仓库Python API 绑定库、JavaScript API 绑定库、Hubot 适配器、Jenkins 插件、Redmine 插件、Trello 迁移工具等。翻译工作则通过 Weblate 平台协作完成。本文聚焦于核心的 Zulip 服务器与 Web 应用本身即当前仓库 zerver/、web/、zproject/ 等目录所承载的内容。仓库的整体目录组织可参考 目录结构指南其核心脉络为zproject/urls.pyDjango 路由入口定义 URL 与视图函数的映射zerver/models/Django 模型定义数据库表结构zerver/lib/大部分库代码zerver/actions/写入用户可见数据表的代码集中于此且所有调用send_event_on_commit触发向客户端推送的代码都必须位于此目录zerver/views/大多数 Django 视图zerver/tornado/views.pyTornado 视图zerver/worker/队列消费者进程zerver/lib/markdown/后端 Markdown 处理器。核心使用模型Realm 多组织架构Zulip 定位为实时团队聊天应用面向从公司、志愿者项目到朋友小群体等各类组织规模从数人的小团队到数万用户不等。它支持 iOS、Android、Linux、Windows、macOS 专用客户端、所有现代浏览器、若干跨协议聊天客户端以及大量基于 Zulip API 的专用客户端如机器人。在架构层面最值得先理解的概念是Realm组织一台服务器可以托管多个 ZulipRealm组织每个 Realm 拥有独立的子域名大多数安装只托管一个组织但有些部署托管数千个组织如 zulip.com每个组织是一个独立隔间拥有自己的用户、频道、定制配置等因此一个人可能同时是多个 Realm 的用户组织管理员对谁能注册账号、新用户拥有什么权限等有很强的控制权。关于安全配置选项可进一步参考 保护 Zulip 服务器指南。多租户multi-tenant这一设计直接影响了后续的组件分工例如事件系统需要按 realm 进行分片sharding这也是 Tornado 支持多端口实例部署的原因之一见下文 Supervisor 一节中的tornado_ports配置。总体架构一个组件一张图Zulip 的整体架构可用下图概括nginx 作为唯一的对外入口将不同路径的请求分流到 DjangouWSGI或 TornadoDjango 负责处理业务请求与写数据库Tornado 负责维持海量长连接并推送事件memcached、Redis、RabbitMQ、PostgreSQL 各司其职Supervisor 统一管理所有进程。Django 与 Tornado主应用服务器与实时推送系统的分工Zulip 主体基于 Django Python Web 框架实现同时使用 Tornado 作为实时推送系统。Django 是主 Web 应用服务器处理相对低频的交互例如用户输入内容、点击按钮触发的请求承担绝大多数业务逻辑、数据库读写与页面渲染Tornado 运行服务器到客户端的实时推送系统作为异步服务器它专门用于同时维持数万条长连接long-polling负责事件消息投递除此之外几乎不做别的事。两者的进程启动方式由 Supervisor 配置决定见下文Supervisor而哪些 HTTP 请求该送到哪个应用服务器由 nginx 配置决定。Tornado 的代码路径中刻意避免任何阻塞调用——因为不希望投递延迟影响成千上万条其他连接否则 Zulip 就不再实时了。例如Tornado 代码路径中不做缓存或数据库查询因为这类阻塞请求对单线程异步服务器而言性能代价极高。原则上可以做非阻塞请求但代码库大部分使用的 Django 数据库库并不支持而且 Zulip 的架构也不需要 Tornado 去做这件事。实时推送与事件队列系统有专门文档 实时推送与事件系统 详述大部分实现代码位于zerver/tornado/包括event_queue.py、handlers.py、sharding.py、views.py等。HTML 模板、JavaScript 与前端资源Zulip 的 HTML 主要使用两种模板后端模板基于 Jinja2 模板引擎用于登录页portico页面以及 Web 应用的基础内容存放于templates/zerver/zerver 应用的登录内容在templates/zerver/app前端模板基于 Handlebars用于从 JavaScript 实时渲染 HTML例如主消息流存放于web/templates/。前端相关的更多细节可参考 翻译指南、HTML/CSS 子系统文档含模板与静态资源管线 与 目录结构指南。前端自有 JavaScript/TypeScript 源码位于web/src/自有 CSS 位于web/styles/第三方 vendored 代码位于web/third/。nginx统一流量入口与路由规则nginx 是所有 Zulip 流量的前端 Web 服务器它直接提供静态资源并将动态请求代理给 Django 与 Tornado。其路由规则分散在puppet/zulip/files/nginx/与puppet/zulip/templates/nginx/下的众多配置文件中其中最核心的是puppet/zulip/files/nginx/zulip-include-frontend/app它定义了外部请求进来后发生的一切。结合当前仓库该文件的实际内容请求分流规则如下静态资源/static/生产环境中所有以/static/开头的 URL 直接从/home/zulip/prod-static/对应文件提供生产构建流程tools/build-release-tarball会把静态资源编译、压缩并安装到prod-static/树中并开启gzip_static。对含哈希文件名Django 12 位十六进制、Webpack 20 位十六进制的不可变资源使用immutable_headers激进缓存。开发环境中则直接由 Git 仓库中的/static/提供实时推送/json/events与/api/v1/events这两个长轮询端点被发送到 Tornado 服务器proxy_pass $tornado_server并附带proxy_longpolling配置。配置中还对请求方法做了约束——仅允许OPTIONS、GET、DELETE其余返回 405Tornado 内部转发~ ^/internal/tornado/(\d)(/.*)$处理 Tornado 之间的X-Accel-Redirect内部跳转internal指令仅内部可访问用于 Tornado 实例间的转发其他所有路径通过uWSGIUnix socketunix:/home/zulip/deployments/uwsgi-socket发送给 Django 应用。其中/api/路径附带api_headers/thumbnail、/avatar、/user_uploads等移动端与 Web 共享的路由同样附加 API 头/api/internal/仅允许本机127.0.0.1、::1访问用户上传内容默认情况下设置了LOCAL_UPLOADS_DIRnginx 直接提供头像、自定义 emoji、上传文件等用户上传内容也可配置改用 Amazon S3 等云存储服务。需要特别说明开发环境不使用 nginx而是改用基于 Tornado 的简单代理见zproject/dev_urls.py与 dev 相关配置以简化本地开发。Supervisor进程编排与后台队列消费者Zulip 使用 supervisord 来启动服务器进程、在崩溃时自动重启并统一管理日志。核心配置文件是puppet/zulip/templates/supervisor/zulip.conf.template.erb从当前仓库内容看它编排了以下关键进程[program:zulip-django]nice -n5 uv run --no-sync uwsgi --ini /etc/zulip/uwsgi.ini以zulip用户运行 Django 应用日志写入/var/log/zulip/django.log[program:zulip-tornado]manage.py runtornado。当配置了多个tornado_ports时通过numprocs% tornado_ports.length %与process_namezulip-tornado-port-98%(process_num)02d启动多实例如127.0.0.1:9800起单端口时运行在127.0.0.1:9800。多端口实例与 Realm 分片sharding配合可横向扩展实时推送能力[program:zulip_events] / zulip_events_ ]队列消费者进程。queues_multiprocess为真时为每个队列生成独立进程manage.py process_queue --queue_namequeue其中missedmessage_mobile_notifications与user_activity队列可按分片数mobile_notification_shards、user_activity_shards横向扩展否则单进程以--multi_threaded模式消费全部队列。这些队列用于在后台处理性能昂贵且不必同步完成的任务例如发送邮件、更新分析数据等。事件队列本身有专门文档 队列处理指南Zulip 用 RabbitMQ 管理一套内部队列用于异步执行发送邮件这类可能以秒计的操作、异步更新非时间敏感的统计表如UserActivityInternal、把事件从 Django 主进程传送到 Tornado 事件系统、处理移动推送通知与邮件镜像消息、批量化处理错误与慢查询等。新增队列处理器时在zerver/worker/中用assign_queue装饰器定义处理器并把队列加入puppet/zulip/manifests/app_frontend_base.pp中的base_queues变量本地可用./manage.py process_queue --queueuser_activity单队列手动运行测试或排障时可用./manage.py purge_queue queue_name清空队列。memcached数据库模型对象缓存memcached 用于缓存数据库模型对象。zerver/lib/cache.py与zerver/lib/cache_helpers.py负责把对象写入 memcached并在值变化时失效缓存。memcached 配置位于puppet/zulip/templates/memcached.conf.template.erb从当前仓库内容看其关键配置包括-m % memcached_memory %内存上限默认 64MB由 Puppet 参数注入-p 11211默认监听端口 11211-l 127.0.0.1仅监听本机回环地址这是 memcached 最重要的安全措施之一-S启用 SASL 认证-I % memcached_max_item_size %可配置更大 max-item-size默认1m-o track_sizes按需启用stats sizes统计。缓存的失效与一致性机制详见 缓存指南。Redis短生命周期数据与限流Redis 被用于少量极短生命周期的数据存储最主要的是限流rate-limiting系统。Redis 配置位于puppet/zulip/templates/zulip-redis.template.erb核心内容如下即关闭持久化以优化性能# Disable saving to disk to optimize performance save 此外若在zulip-secrets.conf中配置了 Redis 密码模板会生成requirepass % redis_password %启用密码认证。很多人会问能否用 Redis 替换 memcached或替换 RabbitMQ尽管会损失部分功能Zulip 官方的回答是大概率可以但不会让 Zulip 变得更好操作上现有组合在开发和运维层面比纯 Redis 方案更简单用 Redis 的常见动机是少跑几个服务以降低内存但这种收益不会兑现缓存本身占用可观内存且换成 Redis 内存占用基本相同这些服务的最低内存要求都很低实际上 Zulip 对 Redis 和 RabbitMQ 的应用即使在大规模下也不占用显著内存很可能需要运行多个不同配置的 Redis 实例才能保证纯 LRU 用例memcached 的角色不会把需要保留到过期Redis 限流或被消费RabbitMQ 延迟任务队列的数据挤出。RabbitMQ可靠异步队列RabbitMQ 是 Zulip 的队列系统。其配置文件位于puppet/zulip/files/rabbitmq初始配置在scripts/setup/configure-rabbitmq中完成。RabbitMQ 承担两类核心职责排队执行昂贵的工作如消息触发的邮件发送、推送通知、部分分析任务等这些任务需要可靠投递但不想占用主线程应用服务器与 Tornado 推送系统之间的通信Django 进程产生的实时事件通过 RabbitMQ 传递给 Tornado。代码层面zerver/lib/queue.py提供了两个围绕pikaPython RabbitMQ 客户端的轻量封装一个异步客户端供 Tornado 使用一个通用客户端供其他场景使用。QueueClient抽象基类定义了_connect、_reconnect、_get_parameters等接口异步版本需要启用 RabbitMQ heartbeatTornadoConnection阻塞版本则显式禁用 heartbeat 并依赖 TCP keepalive且会调整 TCP_KEEPIDLE 以避免容器网络Kubernetes/Docker Swarm杀掉空闲连接。Supervisor 启动的大多数进程都是队列消费者持续从 RabbitMQ 队列取出任务并处理它们定义在zerver/worker/。队列细节同样见 队列处理指南。PostgreSQL全部持久化数据PostgreSQL 存储所有持久化数据即预期超过当前会话存活期的数据。从 Zulip 3.0 开始新安装会安装现代 PostgreSQL 发行版而不是使用操作系统自带的旧版本。生产环境PostgreSQL 以默认配置安装。puppet/zulip/files/postgresql目录下只有工具脚本和一个供 PostgreSQL 扩展使用的自定义停用词表stopwords文件开发环境该 PostgreSQL 扩展的配置由tools/postgresql-init-dev-db处理由tools/provision调用该脚本还负责创建开发用 PostgreSQL 用户tools/provision还会调用tools/rebuild-dev-database创建带 schema 的实际数据库。Zulip 的数据库表结构定义在zerver/models/zerver/actions/中集中了写用户可见数据表的业务代码。Nagios可选监控组件Nagios 是可选组件用于在宕机等情况下向系统管理员发送通知。puppet/zulip/manifests/nagios_plugins.pp从puppet/zulip/files/nagios_plugins/安装 Nagios 插件该组件主要用于安装在 Nagios 服务器上运行的插件而大部分 Zulip Nagios 插件设计为在 Zulip 服务器本身上运行随服务器相应组件一同安装——例如puppet/zulip/manifests/app_frontend_base.pp会在/usr/lib/nagios/plugins/zulip_app_frontend下安装若干插件。队列文档中提到新队列处理器会自动被纳入scripts/nagios/check-rabbitmq-consumers的监控确保队列消费者进程在运行。一次请求的完整旅程组件如何协同将上述组件串联起来一次典型交互的路径如下用户浏览器发起请求nginx接收并根据 URL 分流静态资源直接返回/json/events、/api/v1/events长轮询请求转发给Tornado其余业务请求经 uWSGI 转发给DjangoDjango处理业务逻辑读写PostgreSQL持久化数据通过memcached缓存模型对象通过Redis检查限流若操作需要通知其他客户端如新消息、设置变更Django 在事务提交后把事件放入RabbitMQ的notify_tornado队列详见 实时推送与事件系统send_event_on_commit(realm, event, users)在数据库事务内被调用确保仅在事务成功提交后才发送事件事件以 JSON 形式投入队列Tornado从 RabbitMQ 拉取事件投递给对应客户端的事件队列若该客户端正以长轮询等待GET /json/events则立即返回 HTTP 响应打破长轮询实现准实时投递需要后台完成的昂贵任务邮件、分析统计等由Supervisor托管的队列消费者进程从 RabbitMQ 消费执行。其中实时推送部分的关键设计值得强调见 实时推送与事件系统事件系统基于长轮询客户端循环调用GET /json/events服务器在无事件时挂起请求、有事件时立即返回事件 IDlast_event_id用于在网络故障下实现近似精确一次的投递每个客户端注册时由 Django 创建事件队列POST /json/register启动时先拉取初始状态再通过先建队列、取状态、回补队列中事件、apply_events应用的非原子算法实现原子性无活动队列约 10 分钟默认后会被垃圾回收客户端收到 queue not found 后重新加载即可自愈。相关实现位于zerver/views/events_register.py与zerver/lib/events.py。小结Zulip 的架构可以概括为一句话Django 负责想Tornado 负责说。Django 处理全部业务逻辑与数据变更通过 RabbitMQ 把事件可靠地传递给 TornadoTornado 以长轮询方式维持海量客户端连接并即时投递事件nginx 在前端完成静态资源服务与流量分流memcached、Redis、PostgreSQL 分别承担缓存、限流与持久化Supervisor 统一编排 Django、Tornado 与各队列消费者进程保证崩溃自愈Nagios 则在需要时提供监控告警。理解这组组件的边界与协作方式是深入 Zulip 源码zerver/、zproject/、web/和进行生产部署运维的基础。若想继续深入推荐按顺序阅读 实时推送与事件系统、队列处理指南、缓存指南 与 目录结构指南。【免费下载链接】zulipZulip server and web application. Open-source team chat that helps teams stay productive and focused.项目地址: https://gitcode.com/GitHub_Trending/zu/zulip创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价