Gun.js原理
一、文字版架构图
┌─────────────────────────────────────────────────────────────┐
│ Gun.js 去中心化架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 用户 A (浏览器/Node) 用户 B (浏览器/Node) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 应用层 │ │ 应用层 │ │
│ │ gun.get('key') │ │ gun.get('key') │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ ┌────────▼────────┐ ┌────────▼────────┐ │
│ │ Gun.js 内核 │◄────────►│ Gun.js 内核 │ │
│ │ - CRDT 冲突 │ P2P直连 │ - CRDT 冲突 │ │
│ │ - HAM 图 │ │ - HAM 图 │ │
│ │ - 数据图结构 │ │ - 数据图结构 │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ ┌────────▼────────┐ ┌────────▼────────┐ │
│ │ 本地存储 │ │ 本地存储 │ │
│ │ IndexedDB / │ │ IndexedDB / │ │
│ │ localStorage │ │ localStorage │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ 中继节点 │ (可选,非必须) │
│ │ (Relay Peer) │ │
│ │ 仅帮忙转发 │ │
│ │ 不存储数据 │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
二、核心原理分层说明
| 层级 | 作用 | 关键点 |
|---|---|---|
| 应用层 | 你用 gun.get() / gun.put() 读写数据 |
和普通 API 一样简单 |
| Gun.js 内核 | 把所有数据组织成一个巨大的图 (Graph),用 HAM 算法 做 CRDT 冲突解决 | 任何人离线改了数据,重新连接后自动合并,不会丢 |
| 传输层 | 默认走 WebSocket 连中继节点;也可切换 WebRTC 做真 P2P | 连接建立后,数据直接端到端,不经过服务器 |
| 本地持久化 | Radisk 引擎自动把数据存进 IndexedDB | 刷新页面、断网,数据都在,重连后自动同步 |
三、Mermaid 流程图(可渲染为图片)
你可以把下面这段代码复制到 Mermaid Live Editor 里,立刻生成一张架构流程图:
一句话总结 Gun.js 的原理:
每个客户端都是一个完整节点,数据以图结构存本地,通过 CRDT 算法实现无冲突多主写入,网络恢复后自动同步最新状态,中继节点只转发不存储,真正去掉中心数据库。
CRDT 全称是 Conflict-free Replicated Data Type(无冲突复制数据类型)。
这是一种数据结构设计的“指导思想”,专门用来解决一个核心问题:当多个人同时离线编辑一份数据,而且没有中央服务器来当裁判时,大家重新联网后怎么做到自动合并、不打架、最终所有人手里的数据都能一模一样。
你可以把它想象成一块“魔法白板”:
- 小明在北京往上画了一个红圈,小红在洛杉矶往上画了一个蓝方块。
- 两人此时网络断开,看不到对方干了什么。
- 重新联网后,白板自动同时呈现出红圈和蓝方块,既不会只保留红圈,也不会因为冲突而碎掉。
为什么 Gun.js 需要 CRDT?
在你之前设想的场景里(北京和洛杉矶两人实时看到彼此状态,没有后端数据库),最大的挑战就是:
- 两人可能同时改同一个状态(比如都在“备注”栏里写字)
- 网络随时可能断开
- 没有中央服务器来决定“以谁为准”
传统的做法需要一把“锁”或一个中心服务器来排队处理请求,而 CRDT 允许无锁、无中心的自由修改,只要数据结构本身设计得足够“聪明”,无论并发操作以什么顺序到达,最终结果都能自动收敛成一致。
CRDT 怎么做到“不冲突”?
关键在于它要求所有的修改操作必须满足三个数学性质:
- 结合律:先收到 A 再收到 B,和先收到 B 再收到 A,最终结果一样。
- 交换律:合并操作不依赖顺序。
- 幂等律:同一个操作被重复传输多次,效果只保留一次,不会叠加。
满足这三条,即便消息在不同人之间乱序到达,最终数据也能自动收敛到同一个结果。
最常见的两种实现思路
-
基于状态的
- 每隔一段时间,把整个数据结构的完整状态发送给对方。
- 对方收到后,通过一个 merge(合并) 函数把自己的状态和对方的状态合并起来。
- 哪怕中间少收了几次状态,只要最后一次完整合并,结果就不会错。
-
基于操作的
- 只把修改动作本身(比如“在坐标3的位置插入字母A”)广播出去。
- 要求消息系统本身保证不丢消息、不乱序。
- 传输内容很小,但对网络可靠性要求高。
Gun.js 内部用的是 HAM(Hypothetical Amnesia Machine,假设性遗忘机器),可以理解为 CRDT 的一种具体实现算法,基于状态合并,专门针对“图数据”做冲突解决。
用集合来举个最简单的例子
假设我们两人维护一个只有添加、没有删除的集合:
- 我离线时往集合里加了
{A} - 你离线时往集合里加了
{B} - 联网后我们互相把集合发给对方
- 合并操作就是取并集
{A} ∪ {B} = {A, B}
显然:
- 先合并 A 后合并 B,还是反过来,结果都是
{A, B}(交换律) - 重复收到
{A}做多次并集依然是{A, B}(幂等性)
这个“只增不删的集”就是一种最经典的 CRDT,叫 G-Counter 或 Grow-Only Set。
当然,现实里更复杂——要支持删除、数值加减、文本编辑,所以会有 PN-Counter、RGA、WOOT 等高级算法,但核心精神都一样:用数学性质保证最终一致,不用锁和中心协调器。
本文来自博客园,作者:AceFenix,转载请注明原文链接:https://www.cnblogs.com/ukzq/p/20096434

浙公网安备 33010602011771号