正文
-
排序好的数据会打包成一个个 block,成为数据分发和获取的基石。这样每个客户端同步数据就变得简单:我现在块高在 9527,你是 9627,所以我需要 9528 - 9627 之间的所有数据。在此期间,新收到的数据我暂时存着,直到我的状态达到最新,再处理。
我们再看 Git:
-
客户端拥有全部状态。你可以不联网,依旧可以访问到所有本地的数据并该干嘛干嘛,联网后可以
手动
获取最新的 commits,本地一个 commit 一个 commit 去更新工作目录(working directory),把本地工作目录更新到最新。
-
客户端发送 commit 以及这个 commit 里更改过的 blob,「服务端」不自动进行数据的排序,完全依赖于客户端提交的 commit 基于最新的 commit。如果两个 commit 有冲突,客户端需要
手动
解决(区块链通过预置的 consensus 算法解决)。
这样提交到服务端的 commits 自然是排好序的。
-
对于 Git 来说,commit 以及和 commit 有关的 blobs 是数据分发和获取的基石。客户端同步数据也很简单:我的 head 是 0b39,你的 head 是 a758,我们中间有 100 个 commits,于是我需要所有这些 commits 以及它们包含的 blobs。如果这个过程中或者过程前本地产生了新的 commit,那需要经历一次 merge。
我们从两者的共通点看看可以学到什么有用的东西:
-
客户端产生事件(event),事件经过排序(无论手工还是自动)后被打包储存成方便读取的形式(区块链一般使用 linklist,而 Git 使用 DAG),供其他人访问。
-
事件是原子的,不可修改的。客户端按顺序拿到所有有关的事件后,就可以构建一致的状态了。
-
客户端告诉服务器自己获取到的最新的事件是什么,就可以很容易从服务器拉取从这点之后的所有事件了。除了第一次拉取数据外,以后的更新,都是增量更新。
-
如果客户端的状态被破坏了,只需要重新 replay 一遍所有的事件,就能复原整个状态。
-
客户端如果卸载了,全部本地数据丢失,也可以重装后全量 clone 一下服务端的数据,就能恢复整个状态。
循着这个思路,我们可以构造出客户端和瘦服务器间的所有交互:
在这个基础上,还可以加上 Subscribe/Unsubscribe 来监听某些感兴趣的服务端得到的新的事件。
这和我们熟悉的 Git 操作非常类似。
当然,这些操作披着 Git 的外衣,但处理方式更贴近区块链。因为我们希望由服务端的共识算法来决定如何排序,而不是靠客户端之间手工同步,这种方式,对用户很不友好。
但是,我们又很不喜欢区块链低效的,全局排序的处理方式(全部数据全局共识)。事件之间是有内在联系的,不相干的事件放在一起排序是没有意义的(这就是我认为 BTC 的全网共识的思路是 ok 的,而 ETH 是别扭的,不 ok 的,每个 contract 的 tx 应该只跟这个 contract 相关的 tx 进行排序做共识)。所以,从 Git 那里,我们可以偷师 repo 的概念:事件的排序发生在 repo 内部;不同 repo 间的数据,可以不需要排序。
所以,我们用 repo 作为事件的防火墙。一个 repo 类似一条区块链。
兵棋推演:瘦服务端的实现
好,我们有了基本的架构思路后,接下来就是怎么实现它。
一般到这个时候,就是数据库的选型了。
等等。
为什么我们需要数据库?
呃。。呃。。因为,好像,我们现在做软件设计不都是从 database schema 开始么?
似乎也对。那么稳妥起见,我们找个 RDBMS,比如 Postgres 开始吧。
我们要存储的主要对象是事件。按照我们刚才的描述,一个事件大概长这个样子:
repo_id:事件属于哪个 repo(这个 repo_id,类似于 partition key)