几年前一个朋友聊起他们产品时提到一个两难选择:客户端实时协作功能用 Firebase 做,开发确实是快,监听数据、增量同步、离线队列都内置在 SDK 里,团队甚至不需要维护后端。但业务做到一定规模后,数据合规、账单成本、定制化需求接踵而来,他们才发现 Firebase 这条路很难往回退。换数据库不是换存储,而是要把所有查询、监听、离线逻辑重写一遍。
所以当我在 Hacker News 上看到 "Show HN: Lark, OSS realtime database, drop-in compatible with Firebase SDKs" 这个项目时,第一反应是:这正是 Firebase 开发者一直缺的那条退路。用熟悉的 Firebase API,把后端换成开源自托管。听起来很理想,但这类“兼容层”项目,真正的风险从来不在能不能跑通,而在边界到底在哪里。
1. 先搞清楚 Lark 这个定位到底解决了什么问题
1.1 用 Firebase 最爽的地方,正是最容易被锁定的地方
Firebase Realtime Database 和 Firestore 吸引人的地方,不只是“数据库在云端”这么简单。它最核心的能力是实时监听:客户端可以订阅某个路径,数据一变,推送就到达。这个能力在后端需要维护 WebSocket 连接、心跳、断线重连和增量同步逻辑,自己实现不是不行,而是工作量极大。
代价是,你的数据模型、监听方式、离线同步策略,都和 Firebase 的服务端深度绑定。Firebase 提供的是“托管服务 + 客户端 SDK”这一整套东西,你很难把客户端 SDK 单独抽出来,指向自己的服务器。就算你导出了所有数据,应用程序的读写逻辑也已经长成了 Firebase 的形状。
Lark 想改变的点不是“数据库性能更强”,而是“API 不换,后端换成开源的”。开发者不需要丢掉 Firebase SDK 的用法,只要把原来指向 Firebase 服务的连接配置,替换成指向自托管服务的地址。这就是标题里 “drop-in compatible” 的含义:尽可能减少对现有代码的改动。
注意,这里的 Lark 和字节跳动的飞书海外版(同样叫 Lark)不是同一个东西。搜索资料时很容易混入无关内容,这是一个很容易踩的认知干扰点。
1.2 “SDK 兼容”和“协议兼容”是两条不同的路
要理解这类项目,得先分清两个概念。
- SDK 兼容:提供一套和 Firebase SDK 相同或相似的客户端接口,开发者的调用方式不变,但底层可能是另一个协议的封装。
- 协议兼容:在客户端 SDK 原样不动的前提下,服务端实现了 Firebase 的 wire protocol,原版 SDK 可以直接通过改配置连上。
从 Lark 的标题看,它写的是 “compatible with Firebase SDKs”,更像前者。这意味着项目可能在客户端 SDK 上做适配,也可能重新实现了服务端协议。两种路线的维护成本、兼容程度、升级节奏都不一样。
如果只是 API 名字一样,但底层协议对不上,那么一些隐藏行为会被扰动:重连时的数据恢复、离线缓存的合并策略、安全规则的执行方式。这些很难通过几个简单 demo 暴露出来。
所以在评估 Lark 时,不要只看“代码能不能编译”。要看它是怎么实现兼容的,以及这种兼容到了哪一层。
1.3 这类项目真正的价值是降低迁移成本,不是取代 Firebase
Lark 不会让 Firebase 变差,也不会让自托管突然变成所有团队的标配。它真正提供的,是“迁移成本”上的一个选项。
Firebase 对很多团队来说依然是效率极高的方案,尤其是中小型项目、内部工具和快速原型。它的短板是数据主权:数据存储在 Google 的托管设施上,合规要求、网络条件、账单策略都可能成为变量。
Lark 这类兼容层的意义在于:你不再只能在“开发效率”和“数据自主权”之间二选一。你可以继续用熟悉的 API 开发,再把后端放到自己的基础设施上。它比单纯说“替代 Firebase”更准确,也更容易落地。
这个角度的好处是,你不需要成为自托管激进派,也能理解它的存在价值:多一条退路,选型时多一个自由度。
2. 当它说“兼容 Firebase SDKs”时,你要验证的是哪些层
2.1 第一层:客户端 API 的命名和签名
最直观的验证是客户端代码能不能少改动地跑起来。比如 Firebase JS SDK 里有这样的写法:
import { initializeApp } from 'firebase/app'; import { getDatabase, ref, onValue, set } from 'firebase/database'; const app = initializeApp({ apiKey: '...', databaseURL: 'https://your-project-default-rtdb.firebaseio.com' }); const db = getDatabase(app); const userRef = ref(db, 'users/123'); onValue(userRef, (snapshot) => { console.log(snapshot.val()); }); await set(userRef, { name: 'Alice' });如果 Lark 是 API 层兼容,这段代码可能只需要把databaseURL换成自托管地址,或者换成 Lark 提供的初始化配置。但如果它连返回类型都变了,后续的.val()、snapshot.exists()、错误对象结构就可能全部走样。
从工程经验看,API 兼容最容易忽略的是返回类型。名字一样,返回的是 Promise、自定义对象还是原始引用,直接影响await、.then和错误捕获逻辑。建议把项目中高频使用的 API 写成一个脚本,逐个验证返回值和异常行为。
2.2 第二层:实时同步模型
实时数据库的核心是“数据变了,客户端马上收到通知”。要验证的不只是“能不能收到”,还有“收到的东西对不对”。
具体来说,重点看这几点:
- 数据变更后,监听回调的触发是否及时;
- 监听顺序是否稳定;
- 断网重连后,能不能恢复断线期间错过的增量;
- 多个客户端同时写入时,事件顺序是否一致;
- 删除、更新、嵌套路径变化时,返回的数据结构是否符合预期。
这些行为在单元测试里很难覆盖,通常要模拟弱网、断线、多端并发才有体感。如果只跑一次 happy path,你只会看到“数据同步了”,看不到断线重连后的乱序或丢失问题。
如果你做的是聊天、协同编辑、实时状态同步这类强实时应用,这一层必须重点压测。
2.3 第三层:认证与安全规则
Firebase 的一大特色是安全规则。它允许你在服务端声明式地定义“谁能读、谁能写、什么条件下允许”,而不是把权限判断全堆在客户端代码里。
例如,Firestore 规则大致长这样:
match /users/{userId} { allow read, write: if request.auth.uid == userId; }Lark 这类项目是否支持近似规则,决定了你的权限模型能不能直接迁移。
这里要确认三件事:
- 它是否支持 Firebase Authentication 的 ID token 校验;
- token 是直接用 Firebase 的,还是 Lark 自己发;
- 如果客户端已经接了 Google 登录、匿名登录、自定义 token,后端能不能识别并校验。
如果项目是纯内部工具、无多租户,认证可以先简化。但如果面向公众用户,安全规则就不是“上线前再补”的问题,而是开始设计数据模型时就要同步考虑的东西。
2.4 第四层:离线缓存与冲突处理
Firebase 客户端 SDK 的另一个招牌能力是离线缓存。网络断开时,写入会进入本地队列,恢复后自动同步;读取过的数据会留在本地,离线时也能读到缓存版本。
自托管兼容层能不能实现同样的离线行为,决定了弱网环境下的可用性。
很多“兼容”项目在线场景表现不错,一旦断开网络,行为就完全不同。常见问题包括:
- 离线写入没有被排队;
- 恢复后队列没有正确上传;
- 离线读取直接报错;
- 多个客户端离线修改同一字段,冲突处理策略不一致。
如果产品有大量移动端用户,这一层必须专门测。只做 Web 端、网络稳定的内部工具,可以相对放宽。
3. 从“能连上”到“能上线”,自托管数据库的踩坑路径
3.1 环境准备和最小跑通
在 Lark 官方文档不明确的情况下,我的建议是先看它是否提供 Docker 镜像或编译好的服务端。常见的自托管路径是:
# 先拉取镜像,再设置存储目录和端口 docker pull your-registry/lark-server:latest docker run -d -p 8080:8080 \ -v ./data:/data \ your-registry/lark-server注意:上面的镜像名和参数只是通用示例。Lark 当前如果还没有官方镜像,就要先按仓库 README 编译服务端。别在没确认依赖版本前,照着网上的参数直接套。
为什么坚持先跑最小 Demo?因为实时数据库的问题通常不在“能不能启动”,而在“数据能不能流动”。先确认服务端能启动、能连上、能写入并读回一条完整数据,再往真实业务走。
3.2 不要把本地跑通当作生产可用
本地跑通只能说明代码路径没有断。从自托管到生产,还差三件事:
- 持久化存储:容器重启后数据不能丢。Docker 里要挂载 volume,不能把数据放在容器可写层。
- 备份和恢复:数据目录、数据库快照、恢复演练,都要有明确的流程。
- 监控和告警:连接数、存储增长、慢查询、错误日志,至少要有一个面板或日志采集。
如果只是做小项目或内部工具,这三件事可以简化,只要保证数据有一个备份、进程挂掉能被重新拉起就行。但如果你想把它当作团队基础设施,就得按生产标准来。
3.3 先验证数据模型和查询模式
Firebase 的文档型数据库有个特点:业务模型通常避免复杂 JOIN,而是用嵌套结构或反规范化来组织数据。迁移到自托管兼容层后,查询能力是否一致非常关键。
把现有应用里最常用的数据库操作列成清单,逐项验证:
- 基础写入、更新、删除;
- 按字段过滤;
- 排序和分页;
- 集合级和文档级监听;
- 批量写入或事务;
- 嵌套路径的读写;
- 大数据量下的读取延迟。
不要只在管理后台里手动写数据,要真的从客户端请求里模拟。真实应用的压力来自查询模式,而不是数据库里存了多少行。
3.4 安全、备份、监控是绕不开的三件事
自托管意味着你要自己承担安全责任。至少要做到:
- 关闭服务的默认公开访问;
- 设置鉴权 token、密钥或网络策略;
- 限制 CORS 来源;
- 定期检查依赖更新和已知漏洞。
另外,如果你所在的公司有开源合规流程,把 Lark 拉进依赖扫描列表是很有必要的。类似 Black Duck 这类工具,扫描后会自动提示许可证和已知漏洞风险。代码能跑起来是一回事,合规能不能通过是另一回事。
4. 它适合什么场景,又不适合什么场景
4.1 适合先试水的场景
下面几类情况,把 Lark 列入备选是合理的:
- 已经有 Firebase 代码,但想降低对单一托管服务的依赖;
- 项目处于原型阶段,想快速用熟悉的 API 搭建实时功能;
- 对数据主权、合规、隐私要求高,必须把数据放在自己服务器上;
- 团队有后端运维能力,愿意为数据自主权花一些成本;
- 想深入学习实时数据库和同步机制的人。
在这些场景里,“低迁移成本”是很实在的优势。你不需要先学一套新数据库概念,再设计一套新数据模型,而是可以沿用已有的 Firebase 使用经验。
4.2 不建议直接用的场景
下面几类情况建议谨慎:
- 项目规模已经很大,依赖了很多 Firebase 高级特性,比如云函数、扩展、App Check、ML Kit。这些和数据库内核无关,兼容层通常不会覆盖。
- 需要极高的性能和 SLA,而团队没有自托管运维经验。开源自托管意味着你自己负责升级、监控、故障恢复。
- 对数据一致性要求极其严格,需要强事务、复杂回滚、SQL 级聚合查询。文档型实时数据库的核心设计就不侧重这个。
- 团队规模很小,只想“一个服务省事”,那 Firebase 本身依然更合适。
兼容层项目通常解决“能不能用”,而不是“和 Firebase 完全一样”。把 drop-in 理解成“零成本迁移”,会有风险。
4.3 用一张验证清单代替拍脑袋
具体可以用下面这张表来验证兼容情况:
| 验证项 | 方法 | 通过标准 |
|---|---|---|
| API 兼容 | 把常用 API 写成一个脚本逐项调用 | 无编译错误,返回值和类型正常 |
| 实时推送 | 两个客户端同时监听,一端写入一端观察 | 无需刷新也能收到更新 |
| 离线重连 | 断开网络 10 秒再恢复 | 客户端能恢复并收到重连期间的变化 |
| 认证 | 用现有登录流程获取 token 后读写 | 规则按预期放行或拦截 |
| 安全规则 | 用未授权 token 访问受保护路径 | 请求被拒绝 |
| 持久化 | 重启服务端容器 | 数据不丢失 |
| 备份恢复 | 手动导出数据再恢复 | 数据完整,关联关系正常 |
这七项跑下来,基本能判断一个自托管实时数据库适不适合你的项目。
4.4 许可证和开源合规也值得提前看一眼
开源实时数据库的许可证,决定了你能不能商用、要不要开源自己的修改、能不能嵌入到闭源产品里。不同项目常用 MIT、Apache 2.0、AGPL、BSL 等许可证。AGPL 对互操作性和网络服务有特殊要求;如果做闭源 SaaS,就要特别留意。
同样重要的是项目活跃度。一个只有几颗星、几个月不更新的项目,功能再多也不敢放到生产。看 issues 的响应速度、commit 频率、维护者是否还在处理 PR,比看 README 里的功能列表更实在。开源合规扫描和社区健康度检查,应该成为选型的前置步骤。
5. 如果我想试用 Lark,该怎么开始
5.1 先跑最小 Demo,而不是直接重构
不要一开始就把整个 Firebase 项目切过去。更稳妥的顺序是:
- 单独建一个测试项目,用 Lark 跑一个“频道 + 消息”的实时聊天或通知 Demo;
- 用 Firebase SDK 初始化并写入几条数据;
- 开两个客户端,验证实时更新;
- 模拟断网和重连;
- 关闭服务端再启动,看数据是否还在。
这一步的目的不是证明产品功能,而是帮你理解它的架构。只有自己跑一遍,你才知道启动方式、依赖、配置项有哪些。
比如用 Node.js 写一个最小验证脚本,思路是:
// 这是验证思路,不是特定于 Lark 的完整代码 // 重点是看 SDK 的初始化、监听、写入是否能在自托管地址上工作 import { initializeApp } from 'firebase/app'; import { getDatabase, ref, set, onValue } from 'firebase/database'; const app = initializeApp({ apiKey: 'test-key', databaseURL: 'http://localhost:8080' // 自托管服务地址 }); const db = getDatabase(app); const testRef = ref(db, 'demo/message'); await set(testRef, 'hello from self-hosted'); onValue(testRef, (snapshot) => { console.log('received:', snapshot.val()); });这个脚本只验证一条链路:初始化、写入、监听。如果这一步都卡住,后面的业务迁移就先别开始。
5.2 用一张“兼容性核对表”做验收
真实迁移前,把现有 Firebase API 调用全部列出来,按“高频依赖”和“低频使用”排序,然后逐个测试:
- 初始化方式和连接参数是否有变化;
- 数据写入、读取、删除的行为是否一致;
- 监听事件的触发时机和数据内容是否一致;
- 离线队列是否生效;
- 错误码和异常结构是否相同;
- 认证 token 的校验和权限判断是否等价;
- 复杂查询、排序、分页是否行为一致。
如果高频 API 都通过,迁移风险就低。如果高频 API 里有几个行为不一致,就要评估改造成本,而不是硬切。
提醒:不要一上来就在生产环境直接切换。先做一段时间的并行验证,把真实流量复制到自托管服务上,观察稳定性和数据一致性,再决定是否切换。
5.3 最终看三个指标:迁移成本、运行稳定性、维护可承受度
判断一个兼容层方案值不值得长期用,最终看三个指标:
- 迁移成本:现有代码要改多少,学习新配置的成本多高。如果改造成本接近重写,那兼容层的意义就消失了。
- 运行稳定性:上线后连续运行是否稳定,重连、并发、数据一致性是否可靠。这个只有长时间压测和试运行才能验证。
- 维护可承受度:Lark 自身升级、依赖更新、故障排查,你是否有能力和精力承接。开源软件的维护成本,往往在半年后才会显现。
这三个指标组合起来,才能看出一个方案是不是真的在“开发效率”和“数据自主权”之间取得平衡。如果迁移成本低但稳定性不行,长期看还是要返工;如果功能很强但运维太重,小团队可能被拖垮。
像 Lark 这样的开源实时数据库,短期内不会让 Firebase 过时,也不会让所有团队都转向自托管。它更重要的意义,是提供了一个“还可以这么做”的选项:用 Firebase 的 API,保留开源的数据层。这个选项让技术选型不再是非黑即白的二选一,而是变成了按场景、按合规、按团队能力来权衡的动态选择。
如果你正在被 Firebase 的锁定问题困扰,与其急着重构,不如先花一个下午把这类兼容层项目跑通,再严格验证一遍实时性、认证、缓存和持久化。结果无非两种:要么发现它还不成熟,那你会更清楚 Firebase 在哪些地方不可替代;要么发现它确实够用,那你手里的自由度就多了一块。
这两种结果,对你都是收益。