Wiki 深度科普:为什么应用软件难以实现“双活(Active-Active)”?底层机制剖析
要理解为什么双活如此困难,我们需要从单机文件 I/O 的“欺骗性”说起,进而推演到多机并发时的灾难。
1. 机制一:操作系统的“页缓存(Page Cache)”与丢失更新(Lost Update)
在单机时代,操作系统(OS)为了追求极致的性能,设计了一套“欺骗”应用程序的机制:
[ 应用程序 ] --(写入数据 "A")--> [ OS 内存页缓存 (Page Cache) ] --(立即返回 "成功")
|
+---(异步后台刷盘)---> [ 物理磁盘 ]
当程序调用 write() 写入文件时,操作系统并没有立即把数据写到磁盘上,而是写到了内存的 页缓存(Page Cache) 中,然后就对程序宣告“写入成功”。随后,操作系统在后台异步将缓存刷新到磁盘。
当这个机制放到“双活 + 共享文件系统(如 NFS)”中,灾难就发生了:
假设有两台双活的主机(Host A 和 Host B)挂载了同一个 NFS 共享存储,同时修改同一个配置文件 config.xml:
[ NFS 共享存储 (config.xml) ]
| |
+-----------------------+ +-----------------------+
| (读取旧版本) | (读取旧版本)
v v
[ Host A 内存 ] [ Host B 正常 ]
修改内容为 "A" 修改内容为 "B"
| |
v (写入 Page Cache) v (写入 Page Cache)
[ Host A 刷盘 ] [ Host B 刷盘 ]
| |
+---------------------> [ 相互覆盖 ] <---------------+
- Host A 读取了
config.xml(内容为0)。 - Host B 也读取了
config.xml(内容为0)。 - Host A 将内存中的内容修改为
A,并写入本地 Page Cache,随后刷盘写入 NFS。此时 NFS 上文件内容变为A。 - Host B 此时完全不知道 Host A 做了修改。它将自己内存中的内容修改为
B,同样写入本地 Page Cache,随后刷盘写入 NFS。 - 结果:Host B 的写入完全覆盖了 Host A 的写入。Host A 的修改凭空消失了,这在数据库领域被称为“丢失更新”(Lost Update)或“脏写”(Dirty Write)。
2. 机制二:内存状态与磁盘状态的“脱节(Asynchrony)”
一个应用软件在运行时的“状态(State)”,是由【磁盘上的持久化文件】和【JVM/内存中的活跃线程与数据结构】共同构成的。
像 Jenkins、Jira、GitLab 这样的应用,其底层设计是:内存是磁盘的绝对权威。
- 当用户在 Jenkins 页面上创建一个新任务时,Jenkins 会在内存中生成这个任务对象,然后异步将其序列化为 XML 文件保存到磁盘。
- 双活状态下的致命问题:
如果 Host A 修改了磁盘上的 XML,Host B 的 JVM 内存是完全感知不到的。Host B 内存中保存的依然是旧的配置。
除非 Host B 每次操作前都去重新扫描一次磁盘(这会带来极其恐怖的磁盘 I/O 性能损耗),否则 Host B 就会用其内存中的旧数据去覆盖磁盘,导致数据回滚和逻辑错乱。
3. 机制三:分布式系统中的“共识协议(Consensus Protocol)”缺失
那么,为什么像 MySQL、Oracle、Elasticsearch、ZooKeeper 这样的软件就可以做多节点高可用,甚至部分实现多写呢?
因为它们底层拥有极其复杂的共识算法与分布式锁管理器(DLM),而普通应用软件没有。
| 比较维度 | 普通应用软件(如 Jenkins) | 分布式数据库 / 共识引擎(如 ZooKeeper, MySQL) |
|---|---|---|
| 写入介质 | 直接通过 OS 提供的 File I/O API(如 FileWriter)直接写文件。 |
通过特定的协议(如 Raft, Paxos, 二阶段提交 2PC)进行多节点写协商。 |
| 并发控制 | 无。完全依赖操作系统和文件系统的锁,对应用层不透明。 | 强控制。拥有多版本并发控制(MVCC)、分布式意向锁、行级锁等。 |
| 数据同步 | 依赖外部共享存储的单方面写入,节点间不进行内存同步。 | 节点间通过 Binlog、Raft log 等在内存中实时广播和同步状态。 |
如果要让 Jenkins 实现真正的双活,其研发团队必须重构整套底层代码,引入像 Raft 这样的分布式共识算法,或者将所有的文件读写改成分布式数据库(如 Spanner)调用。这相当于重新写一个软件,开发成本和性能代价极大。
4. 机制四:CAP 定理的物理极限约束
分布式领域著名的 CAP 定理 证明:在一个分布式系统中,一致性(Consistency)、可用性(Availability) 和 分区容错性(Partition tolerance) 这三者不可兼得,最多只能同时满足两个。
Consistency (强一致性)
/\
/ \
/ \
/ * \ <-- 开源 Jenkins 难以跨越的物理极限
/________\
Availability Partition tolerance
(高可用性) (分区容错性)
当网络发生抖动或分裂时(出现了 Partition):
- 如果双活的两台机器都允许写入(保证了 Availability),那么它们在无法通信的情况下,数据必然会往不同的方向分叉,最终导致数据不一致(丧失了 Consistency)。
- 如果要保证数据绝对一致(Consistency),在网络出现问题时,系统就必须拒绝其中一台主机的写入(丧失了 Availability),这就退化成了主备模式。
5. 总结:存储介质与并发控制矩阵
为了方便您在 Wiki 中进行对比,可以使用以下矩阵:
| 存储方案 | 允许多机并发写入? | 底层后果 / 解决机制 |
|---|---|---|
| 本地文件系统 (Ext4/XFS) | 绝对禁止(无法多机挂载) | 物理隔离。 |
| 共享文件系统 (NFS/NAS) | 允许挂载,但禁止应用级并发写 | 缺乏应用层分布式锁,并发写直接导致文件内容覆盖和损坏。 |
| 分布式块存储 (SAN/GFS) | 允许(需要配合 GFS2 等集群文件系统) | 需要极其复杂的分布式锁管理器(DLM)协调块设备写入。 |
| 分布式共识数据库 (Raft/Paxos) | 允许 | 通过共识算法在软件协议层解决冲突,确保强一致性(代价是写入延迟增加)。 |
因此,对于绝大多数非数据库类的企业级应用软件,“主备热备(Active-Passive)”是目前成本最低、技术最成熟、数据最安全的唯一高可用落地方案。
浙公网安备 33010602011771号