Redis
需要注意的是,在所有 master-slave 系统中,可以是 CAP 中 CP 系统, 即 C(strong consistency)可以被保证。
CP 系统 - Strong Consistency
Solution 1
即 Master Slave 也可以是强一致性的。
比如:当我们写 Master 的时候,Master 负责先写自己,等成功后,再写 Slave,两者都写成功后,才返回写操作成功。
整个过程是同步的,如果写 Slave 失败了,那么两种方法:
- retry
- 或者 rollback master 的写入(如果 rollback 失败,则一直 retry)
你可以看到,如果 Master-Slave 需要做成强一致性有多复杂。
Solution 2
开启 Redis 的 AOF appendfsync always,这意味着每一个写操作在写入 memory 后,都会同步的写入 disk 中(不仅仅写入 kernal buffer,因为如果写入 kernal buffer,当突然断电时,强一致性仍然无法被保证)。
而且还要考虑长时间写入 AOF 的 Log Rewriting 问题和 Data Loading 问题,即
- Log Rewriting 仍然需要耗费大量的 CPU 的
- Data Loading 问题会导致当 Redis Node 重启后,需要先 Loading Data,这时候 Redis 是无法对外进行工作的。
- 因而如果 Data 非常大,down time 时间也无法接受
Analysis
显然,这两种 solution 的性能都很差,因为都是 sync write。
而且,个人理解,从 Redis 的设计理念来说,其本身就不是为了保证的一致性的(甚至最终一致性都很难保证,除非牺牲大量的性能),其本身的设计始终优先最求性能和可用性,而数据的持久性的一致性相对比较次要。
No Consistency
通常的 master-slave architecture 就无法保证 Consistency,因为如果当一部分数据被写入 master 后,这一部分数据会被 async 地同步到 slave 中,而当这个同步还没有完成时,master node 挂了,这时候一个 slave node 被 promote,以代替这个挂了的 master node,并作为新的 master node。这时候因为丢到的数据不会在新的 master node 中(而且之后也不可能在),因而 eventual consistency 都没有被保证。
在无法保证 Consistency 的情况下,我们仍然可以做一些 trade-off,即性能与不一致的程度。
单机持久化
Redis 提供两种持久化方式分别是:RDB 和 AOF。需要说明的一点是 * 写入文件并不代表持久化成功,还需要将文件同步到磁盘。*
比如
- 开启 RDB,以定时打 snapshot
- AOF 的
appendfsync everysec
可以保证最多 1s 的数据被丢失
master-slave
这个是非常常用的 architecture。
MySQL
Master-slave MySQL
对于这种架构,Slave 一般是 Master 的备份。在这样的系统中,一般是如下设计的:
- 读写请求都由 Master 负责
- 写请求写到 Master 上后,由 Master 同步(sync)或者异步(async)地同步到 Slave 上。
从 Master 同步到 Slave 上,你可以使用异步,也可以使用同步;可以使用 Master 来 push,也可以使用 Slave 来 pull。 通常来说是 Slave 来周期性的 pull。
如果 Slave pull,如果 Master 被下次 pull 之后 down 了,那么这个时间片内的数据直接会丢失,且永远
Master-master MySQL
Master-Master,又叫 Multi-master,是指一个系统存在两个或多个 Master,每个 Master 都提供 read-write 服务。这个模型是 Master-Slave 的加强版,数据间同步一般是通过 Master 间的异步完成,所以是最终一致性。 Master-Master 的好处是,一台 Master 挂了,别的 Master 可以正常做读写服务,他和 Master-Slave 一样,当数据没有被复制到别的 Master 上时,数据会丢失。很多数据库都支持 Master-Master 的 Replication 的机制。
另外,如果多个 Master 对同一个数据进行修改的时候,这个模型的恶梦就出现了 —— 对数据间的冲突合并,这并不是一件容易的事情。看看 Dynamo 的 Vector Clock 的设计(记录数据的版本号和修改者)就知道这个事并不那么简单,而且 Dynamo 对数据冲突这个事是交给用户自己搞的。就像我们的 SVN 源码冲突一样,对于同一行代码的冲突,只能交给开发者自己来处理。
MySQL + Redis
数据更新时 - 异步更新缓存
- single source of truth 为 DB
缓存与数据库双存储双写
数据更新时 - 缓存延时双删
即发生数据更新时,
-
先写数据库
-
删除缓存
-
休眠 1 秒,再次删除缓存
-
这一步可以这样实现:
- Solution 1:在第一次删除缓存后,开启一个线程,并让这个线程在 1s 后,执行再次删除
- Solution 2:通过读取 DB 的 binlog 和一个消息队列来实现再次删除
-
Analysis
- single source of truth 为 DB
- 这里具体休眠多久要结合业务情况考虑。
- 如果考虑到删除可能失败,再增加删除失败时的重试机制。
Reference
- https://xiaomi-info.github.io/2020/01/02/distributed-transaction/
- https://www.infoq.cn/article/hh4iouiijhwb4x46vxeo
- https://www.jdon.com/51363