Skip to content

Zeze 解决的三大痛点

这是整套文档里最重要的一篇。读完它,你会明白 Zeze 为什么被设计出来、它替你扛下了哪些最折磨人的活儿。

软件开发的本质是控制复杂度。复杂度分两种:本质复杂度(问题本身决定的,躲不掉)和附属性复杂度(我们为了实现方案而自己引入的,原则上能减则减)。Zeze 的目标很明确——帮你干掉服务端开发里三块最大的附属性复杂度,让你把精力留给真正的业务。

一个业务操作往往要修改好几份数据。如果改到一半抛了异常,已经改完的部分就处于不一致状态。看一个最直白的例子——转账:

accountA.balance -= 100 // 第 1 步:成功
accountB.balance += 100 // 第 2 步:抛异常

第 1 步扣款成功了,第 2 步入账却炸了。100 块钱凭空消失,数据不再一致。这在简单系统里还好,一旦业务流程跨多个模块互相调用,任何一步失败都会破坏数据完整性。

在没有事务的环境里,常见的应对是:先把所有条件检查一遍,最后再一起改

简单系统凑合能用。但模块一嵌套,这个办法就崩了:A 调 B、B 调 C,每个模块都得拆成「检查」和「修改」两套接口,业务逻辑被技术细节切碎,代码结构和需求描述对不上。更糟的是,检查完到真正修改之间还有时间窗口,还得祈祷这中间别出错。

也有人用「先存局部变量,失败时拷贝恢复」——这其实就是手搓的事务,只是每个项目都得重新造一遍轮子。

Zeze 直接在程序里提供完整的事务:所有通过 Zeze 定义的数据,读写都运行在事务保护下,要么全部成功提交,要么在任意失败点自动回滚到事务开始前的状态

你不需要手动拆分「检查」和「修改」两阶段,不需要给每个模块写回滚逻辑,代码完全按业务逻辑的自然顺序书写。看个加经验的例子(C# 风格,语言不重要):

void TryLevelUp(long NewExperience)
{
Role.Experience += NewExperience; // 累加新经验
while (Role.Experience >= NextLevelExp[Role.Level])
{
Role.Experience -= NextLevelExp[Role.Level];
Role.Level += 1;
if (Role.Level % 10 == 0)
{
Role.Skills.Add(LearnSkill[Level / 10]); // 这步可能抛异常
}
}
}

如果要求「一次加经验带来的所有升级」是一个不可分割的事务,而中间那行 Skills.Add 可能抛异常——没有事务时,这段代码几乎没法正确处理。有了事务,它就是这么直观。

「为程序开发直接提供事务」是 Zeze 最初的目的。让实现代码和业务描述尽可能贴近,模块划分自然,嵌套调用也自然,不管中间什么时候出错,事务保证所有修改回滚到初始状态。

高并发场景下,线程间共享数据需要加锁。但锁引入了一连串难题:竞态条件、死锁、优先级反转……即便经验丰富的工程师也难以完全避免。

死锁的本质是循环等待:线程 A 持有锁 1 等 锁 2,线程 B 持有 锁 2 等 锁 1,双方永远卡死。看个真实的死锁场景——A 请求加 B 为好友,需要同时锁住两人的好友表记录:

# 错误写法:分两次加锁,可能死锁
select * from friends where id=[aid] for update # A 线程锁住 aid
select * from friends where id=[bid] for update # 此时 B 线程正持有 bid ...

如果此刻 B 也在加 A 为好友,A 持有 aid 等 bid、B 持有 bid 等 aid,死锁就形成了。MySQL 里能靠 in (aid, bid) 一次性取出来让数据库排序来规避,但一旦数据散在不同表里,就没有通用解法了

Zeze 的答案:乐观锁,原理上无死锁

Section titled “Zeze 的答案:乐观锁,原理上无死锁”

Zeze 采用乐观锁,与传统悲观锁的思路截然相反:

对比悲观锁(如数据库行锁)Zeze 乐观锁
怎么做先加锁,阻塞等待先执行,冲突时重试
死锁风险存在,需要超时或检测不存在
性能冲突少时空耗低,冲突多时等待严重冲突少时吞吐量极高

乐观锁的流程是:

  1. 执行阶段:事务读写数据时,只记录访问的记录和时间戳,不加任何锁。写操作记成日志,不动原始数据。
  2. 提交阶段:对访问过的记录按固定全局顺序加锁,逐一比对时间戳。
  3. 无冲突:所有时间戳没变 → 应用日志、递增版本号、释放锁。
  4. 有冲突:某条记录时间戳变了 → 丢弃日志,整个事务重做

为什么不可能死锁?两条保证:

  • 执行时不持锁,多个事务能同时读写同一数据不互相阻塞;
  • 提交时按固定顺序加锁(所有事务都用同一套全局排序请求锁),不可能形成环形等待。

这是经典的「资源有序分配」策略——死锁在 Zeze 里不是「很难发生」,而是从原理上不可能发生

乐观锁的代价是冲突时要重做。但绝大多数业务场景里(游戏、社交),不同用户操作的数据交集很小,冲突概率低,乐观锁的性能优势极其显著。

程序把数据放在内存里跑得快,但内存不是持久存储。什么时候把内存数据写到数据库?

  • 写早了——每改一次就写一次库,I/O 浪费严重;
  • 写晚了——攒着不写,一旦崩溃数据就丢了;
  • 用多种数据库——MySQL 存账号、Redis 存热数据、MongoDB 存日志……同步逻辑成倍增加。

Zeze 的答案:一致性缓存 + 自动同步

Section titled “Zeze 的答案:一致性缓存 + 自动同步”

Zeze 维护一个内存中的一致性缓存:数据先在内存里完成事务操作,然后由框架的 Checkpoint 机制自动、批量地同步到后端数据库。开发者不需要写任何 SQL 或持久化逻辑。

而且它支持一长串后端:RocksDB、MySQL、PostgreSQL、MongoDB、Redis、TiKV、SqlServer、Dbh2,还能同时混用多种数据库,切换只需改配置。对 MySQL 和 PostgreSQL 还额外提供二维表映射模式。

一个绝妙的类比:Zeze 之于服务器,就像 CPU 缓存之于内存。CPU 访问内存走 cache,cache miss 时 stall 一下等内存返回;服务器访问数据库走内存 cache,cache miss 时等数据库返回。CPU 能忍受偶尔的 cache miss,服务器也能——只要命中率够高(通常 >99%)。详见 Zeze 如何工作

这三个问题不是孤立的,它们互相纠缠:为了并发安全你加了锁,锁又可能死锁;为了性能你引入了缓存,缓存又带来一致性问题;为了简单你手搓了回滚,回滚逻辑又难以维护。

Zeze 用一个统一的方案同时解决三者:内存事务(解决一致性)+ 乐观锁(解决死锁)+ 一致性缓存(解决持久化)。开发者只需定义数据、写业务逻辑,剩下的全交给框架。

痛点传统方案的附属性复杂度Zeze 的答案
数据改一半出错手写回滚 / 拆检查-修改两阶段内存事务,自动整体回滚
多线程死锁手动加锁、排序、超时检测乐观锁,原理上无死锁
数据何时落库手写 SQL、管缓存一致性一致性缓存 + Checkpoint 自动同步

Zeze 最适合具备这些特征的应用:

  • 高并发 —— 需要把负载分布到多台服务器;
  • 数据局部性强 —— 同一份数据短时间内被反复访问(如玩家登录期间的操作);
  • 对数据一致性要求严格 —— 不允许部分修改导致的数据损坏。

典型代表:在线游戏服务器、实时通信平台、物联网设备管理——长期在线、高并发的服务端系统。