distributed system, replication, partition, transaction,

分布式系统的常见数据场景:复制(Replication)、分区(Partitioning)和事务(Transactions) --《设计数据密集型应用》读书笔记

波斯王子 波斯王子 Follow Jun 08, 2020 · 1 min read
分布式系统的常见数据场景:复制(Replication)、分区(Partitioning)和事务(Transactions)    --《设计数据密集型应用》读书笔记
Share this

复制和分区是把数据分布在多台机器节点上的两种通用方式。其中,复制是把相同的数据复制一份放到多个不同的节点上(常常是物理上的不同地点),以数据冗余的方式提供系统的高可用(HA)和高性能;分区是把一个大的数据库拆分成一个个小的子集(叫partition),把它们分布到多个节点上。两者常常伴随一起使用。

复制(Replication)

复制最常见的目的在于:降低数据延迟、高可用和提高读的吞吐量。 最流行的三种复制算法是:

  • 单主复制(single-leader)
  • 多主复制(multi-leader)
  • 无主复制(leaderless)

当我们考虑使用复制的时候,其实有很多考量点需要我们去权衡,例如:准备使用同步复制还是异步复制、如何处理有故障的副本等等。下面我们来详细地一探究竟。

单主复制(master-slave主从复制)

单主复制,或主从复制中,只有一个副本是leader,其余都是follower,写只可以写到leader副本上,当leader把数据写进本地存储空间时,它也会把数据以复制日志(replication log)的形式发送给它所有的follower副本,同时读的时候既可以从leader副本读,也可以从follower副本读。 这种复制模式是当前很多关系型数据库的内置特性,比如:PostgresSQL, MySQL, Oracle Data Guard和SQL Server的AlwaysOn Availability Groups。它也被应用于像MongoDB, RethinkDB和Espresso这些非关系型数据库中。而且这种模式不仅限于数据库中的使用,分布式消息队列像Kafka和RabbitMQ高可用队列也使用到了它。

同步复制(Synchronous Repl.) VS 异步复制(Asynchronous Repl.)

这两个也很容易理解,客户端在写的时候,leader副本需要等待确认follower副本也写成功了,才返回客户端写成功了,这种follower副本进行的就是同步复制,否则,leader副本不需要等待的那些follower副本进行的就是异步复制。 同步复制的好处当然是每个副本上都是最新的一致数据,但是显然这样写的速度会更慢,甚至当某些需要同步的follower副本不响应时整个写都会失败。所以实际使用情况是,你打开了一个数据库的同步复制功能,也只有一个follower是同步的,其余follower都是异步的。当同步的副本变慢或者不可用时,我们就会把另一台异步的副本变为同步的。这种方法能保证你始终有一份最新的备份数据,这种配置方法有时也叫半同步复制(semi-synchronous)。 很多情况下主从复制也会被配置为完全的异步复制,这种情况下如果主副本挂了的话可能会导致数据丢失,听起来很糟糕,但也确实有其优势:即使所有的从副本都延迟了的话,主副本还是可以正常处理写的请求。这种弱持久性看起来不那么好,但是异步复制仍旧广泛被应用,特别是当你拥有很多个从副本或者它们是在地理上分布得很开的时候。

如何做到不停机添加新的从副本?
  • 取数据库主副本在某个时间点上的稳定快照;
  • 把这个快照拷贝到一台从节点上;
  • 从节点连接到主节点,并请求从此快照生成之后主节点发生的所有数据变化,这关联到主节点的复制日志中的一个准确位置,比如MySQL叫做binlog坐标(binlog coordinates);
  • 当从节点处理了从快照生成之后的所有积压(backlog),我们就说它已经追上来了,下面就可以开始正常的复制流程。
节点故障了,怎么做到高可用?

从节点故障:Catch-up恢复。很简单,故障恢复后可以自己根据log差异把数据追上来。

主节点故障Failover(故障转移)。一个从节点需要被提升为主节点,客户端需要重新配置为把写的请求都发送给这台新的主节点,而其余所有从节点也要开始从这台新主节点获取数据变化,这就叫故障转移(failover)。故障转移既可以人工操作,也可以自动操作。一个自动故障转移的流程常常包括一下三步:

  1. 确定主节点发生了故障;
  2. 选择一台新的主节点(可以通过选举流程,涉及到共识问题后面文章另说,或由提前定好的控制节点来指定);
  3. 重新配置系统来使用这台新的主节点(系统必须保证故障恢复后的旧主节点变为从节点,并且知道新的主节点)。

故障转移充斥着可能会出错的地方:

  • 如果使用了异步复制,新的主节点可能就不会收到旧的主节点发生故障前所有的写数据,当旧的主节点再次加入集群时,新的主节点或许这期间已经收到了新的彼此冲突的写数据,那么旧的那些写数据该怎么办?最通用的做法就是直接丢弃,但这就违背了客户端对数据持久性的期望;
  • 在有数据库之外的其它的存储系统需要和数据库的内容做配合的时候,直接丢弃写数据有时会尤其危险。这一点Github之前就发生过,由于一台过期的MYSQL从节点被直接提升为主节点后,MySQL和Redis就自增主键不一致,导致一些私人数据被展示给了错误的用户的情形;
  • 脑裂(split brain)问题,是指同时有两个节点都认为它们是主节点的故障情形,这种情况特别危险,因为如果有两个节点都可以收到写数据的话,如果没有专门的进程来处理冲突,数据就极有可能丢失或被破坏。因此为了安全起见,一些系统有关掉一个主节点的机制,来应对这种情形;
  • 判断主节点是挂掉了的合理的超时时间问题,既不宜太长也不能太短;
复制日志(Replication Logs)的实现

主从复制的底层原理基本都是基于复制日志的实现,主要有如下四种:

  • 基于执行语句的复制(Statement-based replication), MySQL 5.1版本之前使用这种复制方式,不过5.1版本之后就切换到默认基于行的复制,VoltDB也是使用的这种复制方式。
  • Write-ahead log(WAL)日志传送, PostgreSQL和Oracle使用这种复制方法,它最主要的缺点是日志是从很底层的角度去描述数据,这就使得复制是和存储引擎耦合得很紧,如果数据库在新版本中更改了存储格式,这种方式就不能实现不停机升级数据库了。
  • 逻辑日志复制(Logical log replication)-基于行模式, 用逻辑日志的方式来和存储引擎解耦,MySQL的binlog就是采用的这种方式。
  • 基于触发器的复制(Trigger-based replication)

复制延迟的问题(Replication Lag)

复制延迟的3种问题案例及其解决办法:

读你自己的写(Reading Your Own Writes)

写之后读的一致性(read-after-write consistency):可以根据具体数据内容选择性地读主节点,或者读用户最后写时间的范围内未过期的从节点,以此来保证读到你自己的写数据。

单调读(Monotonic Reads)
一致前缀读(Consistent Prefix Reads)

多主复制(Multi-Leader Replication)

无主复制(Leaderless Replication)

分区(Partitioning)

当数据集非常大,或者查询吞吐量非常高的时候,光有复制是不够的,我们还需要把数据集拆分成多个分区(partitions),或者叫分片(sharding)。 这部分我们主要考虑3个议题:

  • 不同的分区方法,以及看看数据的索引是如何和分区交互的
  • 重平衡(Rebalancing)
  • 数据库如何把请求路由到正确的分区然后执行查询的

K-V数据的分区

分区和二级索引(Partition and Secondary Indexes)

重平衡分区(Rebalancing Partitions)

请求路由(Request Routing)

参考链接:

Join Newsletter
Get the latest news right in your inbox. We never spam!
波斯王子
Written by 波斯王子 Follow
去读书吧,发现不一样的世界!😉