---
title: 'Replication中常见的数据一致性的问题'
pubDate: 2018-05-05
tags:
  - 'distributed'
description: 'Replication中常见的数据一致性的问题'
---

## 背景

简而言之，Replication是我们在部署分布式或集群中的一种模式，Replication可以了解为复制或冗余。

使用Replication的原因也很明显：

1. 可以在一定程度上，降低不同地区用户访问的延迟
2. 提供部分宕机情况下的可用服务
3. 提高服务的最高吞吐量

众所周知，Replication分有三种，Single-Leader、Multi-Leader以及Leader-Less。其中对于我来说，最常见的则是Single-Leader，即主从模式。

Single-Leader和Multi-Leader都是属于Leader-based，这类型的规则也很简单：

1. 用户的写入请求必须通过Leader，Leader接收到自身写入后再同步给Followers
2. 用户的读取请求则可以从所有节点中读取，不过一般只从Follower读。

Leader-based Replication分同步和异步，甚至还有位于中间的半同步。

同步意味着用户请求了写入Leader后，必须要等到Leader的数据同步到全部Follower后才将此次写入请求结果返回给用户。也就是说，同步需要等很长时间。

而异步则是用户请求了Leader后，Leader马上就返回了一个“假结果”，然后Leader再自己偷偷地将数据同步给各个Follower。这样的处理很快，但是在用户得到结果后却不能保证服务后续操作的可靠性。

当然还有中间的半同步模式，则是Leader接收到用户的请求后也先将数据同步给各个Follower，若是其中有那么一个Follower完成了这个同步操作，Leader就立刻返回结果给用户了。这种处理方法比同步快，比异步靠谱。

同步异步就到此吧。

为了享受Replication的优点，我们也必须要忍受它各种各样的缺点，如新增Follower的方法、部分节点宕机恢复后的数据恢复、脑裂情况下的处理方式、超时时间的调优。

幸运的是，问题先来，解决方法总会是陆续而来。当然以上的问题都不是本笔记的重点，本笔记的重点是谈谈数据的一致性。

## 问题

### 读取自己的写入数据（Reading your own writes）

某场景下，或者说某种接口中，用户提交了新增的数据或者更新了数据后，需要预览到自己刚才提交的数据。

如果在同步模式下，那么这个场景根本没有这个问题。然而这种做法简直是在牺牲用户体验来做较为完美的一致性，我不太认同这种trade-off。

我们还是谈谈异步模式下这个场景的问题。异步模式下，由于写入数据只到达了Leader，而Follower可能还没有同步。而如果这个时候用户读取了没有同步到的Follower，就会有一种疑惑：我刚才是否执行失败了。

而这里要做的保证就是，起码用户读取自己刚刚写入的数据是没有问题的。

这里需要引入写后读一致性（read-after-write consistency），是一种较弱的一致性。要实现这种一致性一般需要：

1. 当用户读取某一样可以会被修改的数据时，请从Leader读
2. 若大多数数据都可能会被修改，还遵循着1则会无限方法Leader的负担，这种情况下，我们可以先检测需要读取的数据是否最近被修改过（比如说一分钟内），再判断从哪里读
3. 当然客户端也可以自个保留这一个逻辑时间戳，在请求的时候就带上这个时间戳，好让服务端评断是否最近执行过更新操作

如此看来还是比较简单的。

如果场景迁移到多个Leader的多个数据仓库下的话，可能会复杂一点。再有就是同一个用户账号，在电脑端上更新了，然后短时间内在移动设备上读取，如此的跨设备写后读一致性也是需要考虑在内的。

### 单调读（Monotonic Reads）

如果一个用户每次读取的Replica节点不一样，就有可能会产生一种现象，倒退（回退）。

比如说，一个叫煎鱼的用户读取了2号节点，看到了2018-05-06 00:05:19的新闻，然后下次刷新读取了3好节点，由于3号节点具有较大的网络延迟，新闻只同步到2018-05-06 00:05:00，就会给用户一个假象，05:00到05:19的新闻怎么不见了。

当然要保证单读也很简单，就是同一个用户只读取同一个机器节点。

常见的现实方式就是负载均衡，一般以地区划分用户的访问节点，或者使用hash。

### 一致前缀读/一致顺序读（Consistent Prefix Reads）

存在一种情况，一个叫煎鱼的用户在不同的节点中读取了多条数据，由于时间延迟不一样，2号机器比3号机器慢，因此2号00:05:19的新闻显示在3号机器的00:05:40的新闻的后面，导致新闻的时间很不连贯。

当然，如果新闻的时间不连贯是没什么问题的，最严重的此类问题是发生在聊天场景中。如果聊天中各个节点的消息没有按照时间排序，就会给用户造成极大的消极影响。

因此，引入一致前缀读（个人觉得按照意思翻译成一致顺序读好了解点），意思就是在写入的时候是什么样的一个顺序，在读取的时候也要按照这个样的顺序来读取。

如，既然按照时间顺序来写入，就要按照时间顺序来解读。如果按照别的序号顺序写入，就要按照那个序号顺序来读取。

关键点就在于读取的客户端需要处理一下。

## 问题是否必要

以上数据一致性问题，都是基于异步模式下的假设。这些一致性的保证并不是说非做不可，但是不做则将会对用户体验造成重大影响。

如果不关心用户体验的话，那么也没有必要采取异步模式了是吧。

_先这样吧_

若有错误之处请指出，更多地关注[煎鱼](http://www.jianyujianyu.com)。
