# 9.5 链复制（Chain Replication）

这一部分，我们来讨论另一个论文CRAQ（Chain Replication with Apportioned Queries）。我们选择CRAQ论文有两个原因：第一个是它通过复制实现了容错；第二是它通过以链复制API请求这种有趣的方式，提供了与Raft相比不一样的属性。

CRAQ是对于一个叫链式复制（Chain Replication）的旧方案的改进。Chain Replication实际上用的还挺多的，有许多现实世界的系统使用了它，CRAQ是对它的改进。CRAQ采用的方式与Zookeeper非常相似，它通过将读请求分发到任意副本去执行，来提升读请求的吞吐量，所以副本的数量与读请求性能成正比。CRAQ有意思的地方在于，它在任意副本上执行读请求的前提下，还可以保证线性一致性（Linearizability）。这与Zookeeper不太一样，Zookeeper为了能够从任意副本执行读请求，不得不牺牲数据的实时性，因此也就不是线性一致的。CRAQ却可以从任意副本执行读请求，同时也保留线性一致性，这一点非常有趣。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MF0k3-I6-lUQ0BjQYFI%2F-MF37Q0z5t5TlnVPuP4W%2Fimage.png?alt=media\&token=ce7a1e8a-ce66-489a-b2bc-bf9139765913)

首先，我想讨论旧的Chain Replication系统。Chain Replication是这样一种方案，你有多个副本，你想确保它们都看到相同顺序的写请求（这样副本的状态才能保持一致），这与Raft的思想是一致的，但是它却采用了与Raft不同的拓扑结构。

首先，在Chain Replication中，有一些服务器按照链排列。第一个服务器称为HEAD，最后一个被称为TAIL。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MF0k3-I6-lUQ0BjQYFI%2F-MF38mLZ4Ubtws2a8Of5%2Fimage.png?alt=media\&token=a8f3b291-631c-496d-9145-67a0315f5ef0)

当客户端想要发送一个写请求，写请求总是发送给HEAD。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MF0k3-I6-lUQ0BjQYFI%2F-MF392OCLpTN1Cmu8cbw%2Fimage.png?alt=media\&token=e6b04f83-4a29-4409-a831-56db2ba8773f)

HEAD根据写请求更新本地数据，我们假设现在是一个支持PUT/GET的key-value数据库。所有的服务器本地数据都从A开始。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MF0k3-I6-lUQ0BjQYFI%2F-MF39XOcVZdYZ8zzxGQJ%2Fimage.png?alt=media\&token=71100897-1f1b-45b9-a38b-bd722e523ab7)

当HEAD收到了写请求，将本地数据更新成了B，之后会再将写请求通过链向下一个服务器传递。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MF0k3-I6-lUQ0BjQYFI%2F-MF3BaI-krSRybj_UPyq%2Fimage.png?alt=media\&token=54b6d1d1-bbfc-4112-9ad6-e801685d0a00)

下一个服务器执行完写请求之后，再将写请求向下一个服务器传递，以此类推，所有的服务器都可以看到写请求。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MF0k3-I6-lUQ0BjQYFI%2F-MF3Br0c_CV1OFSSknzf%2Fimage.png?alt=media\&token=b510bb1e-a74a-43cd-9246-c7509b97f251)

当写请求到达TAIL时，TAIL将回复发送给客户端，表明写请求已经完成了。这是处理写请求的过程。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MF0k3-I6-lUQ0BjQYFI%2F-MF3C1shzLnUgx-xESti%2Fimage.png?alt=media\&token=e787f439-d218-4910-8264-aa2232d9cdd9)

对于读请求，如果一个客户端想要读数据，它将读请求发往TAIL，

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MF0k3-I6-lUQ0BjQYFI%2F-MF3CPtWZ-uEy7hFIR11%2Fimage.png?alt=media\&token=b5dcfa02-5b6f-427d-88ca-af80a000330a)

TAIL直接根据自己的当前状态来回复读请求。所以，如果当前状态是B，那么TAIL直接返回B。读请求处理的非常的简单。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MF0k3-I6-lUQ0BjQYFI%2F-MF3D71HWTIHVlhFrS8q%2Fimage.png?alt=media\&token=fc0aaa62-ce13-4fea-9c7d-cc495e3a480d)

这里只是Chain Replication，并不是CRAQ。Chain Replication本身是线性一致的，在没有故障时，从一致性的角度来说，整个系统就像只有TAIL一台服务器一样，TAIL可以看到所有的写请求，也可以看到所有的读请求，它一次只处理一个请求，读请求可以看到最新写入的数据。如果没有出现故障的话，一致性是这么得到保证的，非常的简单。

从一个全局角度来看，除非写请求到达了TAIL，否则一个写请求是不会commit，也不会向客户端回复确认，也不能将数据通过读请求暴露出来。而为了让写请求到达TAIL，它需要经过并被链上的每一个服务器处理。所以我们知道，一旦我们commit一个写请求，一旦向客户端回复确认，一旦将写请求的数据通过读请求暴露出来，那意味着链上的每一个服务器都知道了这个写请求。
