Chatto事件溯源架构揭秘为什么用NATS JetStream替代关系型数据库【免费下载链接】chattoA fully-featured team and group chat application that you can easily selfhost.项目地址: https://gitcode.com/gh_mirrors/chatt/chattoChatto 是一款功能完整的团队与群组聊天应用主打免费、轻松自托管。它最反直觉的设计是整套系统不使用任何关系型数据库而是把开源消息系统NATS JetStream当作唯一的数据存储并用**事件溯源Event Sourcing**来管理全部业务状态。这篇文章面向新手用尽量少的代码讲清楚这个架构的来龙去脉和真实取舍。一眼看懂一个没有数据库的聊天应用Chatto 支持房间、线程、提及、表情回应、文件上传、语音通话、机器人、管理员面板等完整功能。下面就是它的聊天界面通常这类应用需要一整套基础设施Web 服务器关系型数据库PostgreSQL / MySQL消息队列做实时推送对象存储 / 缓存每一样都意味着部署、备份、连接池、版本兼容问题。而 Chatto 的做法是一个可执行文件一个端口一个数据目录全部搞定。传统做法的痛点数据库 消息队列二件套传统聊天应用里数据流是这样的写入 → 存进关系型数据库一条INSERT语句推送 → 再发一条消息到 Pub/Sub 系统前端通过 WebSocket 收到备份 → 数据库、消息队列、缓存各备各的这里有个隐藏成本存储层和实时推送层是两套系统服务同一份数据。你需要保证两者最终一致需要 CDC、轮询或同步层还要为每套系统分别做运维和备份。而聊天领域的本质恰好是事件驱动的消息是事件、在线状态是事件、正在输入是事件。存储和推送处理的本来就是同一份数据——那为什么要拆成两个系统核心决策NATS JetStream 作为唯一数据底座这个决策记录在 docs/adr/ADR-001-nats-jetstream-as-primary-data-store.md 中关键结论是使用 NATS JetStream 作为唯一持久化存储。没有关系型数据库、没有 ORM、没有 SQL 迁移。具体来说JetStream 提供了几种容器各司其职类型名字存什么新手理解Stream事件流EVT所有领域事实用户、房间、消息、成员关系一本永不擦除的流水账KV 桶RUNTIME_STATE最新值状态凭据、在线设置、通知边界一张只保留最新值的便签纸KV 桶MEMORY_CACHE易失的租约、计数器、健康心跳内存里的临时草稿对象存储SERVER_ASSETS上传的图片、文件等二进制仓库货架NATS Corelive.sync.不持久化的实时信号打字、在线状态广播喇叭完整清单见 docs/architecture/nats-resources.md。事件溯源是什么用餐厅账本打比方 事件溯源的核心思想一句话不记录现在是什么样只记录发生了什么。就像餐厅不记录当前库存 50 个鸡蛋而是记录购入 100 个用了 30 个退回 20 个。任何时候想知道库存把账本从头重放一遍就能算出来。Chatto 的EVT流就是这样一本账本用户加入房间 → 追加一条user_joined事件发送消息 → 追加一条message_posted事件编辑消息 →不修改原事件再追加一条message_edited事件所谓当前状态比如某个房间现在有哪些成员由**投影Projection**从事件流实时推导出来——大多数投影只是进程内存里的数据结构启动时重放事件流即可重建。这一演进过程记录在 docs/adr/ADR-033-event-sourced-state-with-projections.md 中更早的KV 存现状 流存历史模式见已被取代的 ADR-006。这套设计带来四个实打实的好处审计日志是天然的审计追踪不是附加的旁路数据本身就是日志改数据结构 重建投影加字段、改推导逻辑只要丢掉投影、重放事件流不需要写数据迁移脚本写入原语统一所有写操作都走带乐观并发控制OCC追加事件这一条路代码里只有一种变更模式内存占用可控主题subject数量只随聚合体房间、用户增长而不随消息数量无界增长单流设计为什么所有事件只写进EVT一个自然的疑问消息、用户、房间……难道不该分多条流吗docs/adr/ADR-034-single-event-stream.md 选择了单条主事件流因为备份时只有一个目标、一个位置要跟踪运维工具面对的资源更少顺序保证不依赖每聚合一条流JetStream 对每个主题内部都有独立的单调序列号房间 X 的事件天然线性有序事件流的地址格式为evt.{聚合类型}.{聚合ID}.{事件类型}例如evt.room.{房间ID}.message_posted。投影只需订阅自己关心的主题前缀服务端就会过滤——关心谁加入了房间的投影根本不会收到海量消息事件。并发安全乐观并发控制OCC多人同时操作同一个房间怎么办Chatto 的答案是强制 OCC每次追加事件都携带Nats-Expected-Last-Subject-Sequence头声明我以为该聚合的最新序号是 N如果实际序号已变说明有人抢先写入框架整体重跑决策基于新状态重新判断框架不提供不带 OCC 的写入原语——想跳过并发检查都做不到代价是热点聚合上可能出现重试循环但换来的是整个代码库再无我是不是和别人撞车了这类问题。自托管者的福音备份与恢复只需一条命令事件溯源让备份变得异常简单——整个数据边界就是 NATS 里的几类资源chatto backup把EVT、RUNTIME_STATE、通知历史、NATS 资产、投影快照打包成一个压缩归档可选 age 口令加密chatto restore在停服的部署上恢复该归档恢复后从EVT重放即可重建全部投影细节见 docs/fdr/FDR-040-backup-and-restore.md命令实现在 cli/cmd/backup.go。对比传统方案数据库 dump 对象存储同步 队列状态协调的多步骤流程这是显著简化。部署形态嵌入式 NATS一条命令启动docs/adr/ADR-002-single-binary-with-embedded-nats.md 决定了NATS 服务器以库的形式内嵌在 Chatto 的 Go 二进制里。chatto run一条命令启动 NATS、HTTP API/实时服务器和内嵌的前端单节点模式下应用和数据存储之间零网络调用没有连接池、重连、网络分区问题升级是原子的换一个二进制、重启一次进程高级场景仍可外接 NATS 集群实现水平扩展——嵌入式只是起点不是上限坦诚的代价这个架构放弃了什么没有免费的午餐官方 ADR 也明确列出了代价没有任意 SQL 查询JetStream 不是查询引擎没有 JOIN。相关数据靠投影和 API 组装层拼装全文搜索要另做需要专门的投影或可插拔搜索系统见 ADR-055EVT本身不支持LIKE %xxx%重启成本随流长度增长没有可用快照时重启要花时间重放事件流可选的加密快照只是加速器正确性基线仍是完整重放运维知识转移运维者要懂 NATS 流、消费者、KV 语义而不是 SQL 和数据库调优跨进程一致性是最终一致两个进程对同一份状态的读取可能有亚毫秒级的瞬间不一致——对聊天场景可接受延伸阅读架构是怎么长出来的 Chatto 的文档体系非常值得学习——每个关键决策都有 ADR架构决策记录每个功能行为都有 FDR功能决策记录想深入了解去哪里看为什么选 NATS JetStreamdocs/adr/ADR-001-nats-jetstream-as-primary-data-store.md单二进制 嵌入式 NATSdocs/adr/ADR-002-single-binary-with-embedded-nats.md事件溯源 投影模式docs/adr/ADR-033-event-sourced-state-with-projections.md单事件流EVT设计docs/adr/ADR-034-single-event-stream.md运行时状态与事件流的边界docs/adr/ADR-036-runtime-state-kv-boundary.mdNATS 资源完整清单docs/architecture/nats-resources.md事件溯源框架独立模块pkg/events/README.md架构总览索引docs/ARCHITECTURE.md总结Chatto 用一个大胆但自洽的架构回答了自托管聊天应用的复杂度问题存储与推送合一——JetStream 既是数据库也是消息总线消灭了同步层事件溯源——EVT流是唯一事实来源状态是派生物审计、迁移、恢复全部简化单二进制部署——嵌入式 NATS 让一条命令跑起来成为现实备份即打包——一个归档搞定全部持久化数据对普通用户而言你只需要chatto run一个命令对进阶部署者外接 NATS 集群就是水平扩展路径。这就是事件溯源 NATS JetStream 组合的完整故事用领域模型的自然形态事件来设计存储而不是把领域硬塞进一张张关系表。【免费下载链接】chattoA fully-featured team and group chat application that you can easily selfhost.项目地址: https://gitcode.com/gh_mirrors/chatt/chatto创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考