Replication就是把相同的数据复制到多个机器上。
有以下几个缘由mysql
在本节咱们假设数据集足够小,每一个机器均可以存储整个数据集。git
若是你要复制的数据不随着时间变化,replication就很简单。你只需复制数据到每个节点,而后就作完了。可是现实却不是这样,当你复制数据的时候,源数据可能发生变化,replication的难点就在于此。github
接下来咱们会讨论三个流行的算法来处理处理节点间数据的变化:single-leader, multi-leader, and leaderless replication 。几乎全部分布式数据库都会用这些算法,每一个算法都有各自的优缺点。算法
每一个节点保存数据库的一个copy,这个节点叫作replica(复制品),当replica愈来愈多的时候问题就来了,怎么保证数据都会到达每一个replica上。sql
每一次写数据到数据库,其余的replicas都要写这份数据,不然你们的数据就不一致了。数据库
常见的解决方案是leader-based复制,又叫主从复制。缓存
replication是许多关系数据库的内置特性,好比:PostgreSQL (since version 9.0), MySQL, Oracle Data Guard [2], and SQL Server’s AlwaysOn Availability Groups [3].网络
非关系数据库也在用,好比:MongoDB, RethinkDB, and Espresso数据结构
leader-based replication不只仅用于数据库,一些分布式消息中间件也在用,好比:Kafka,RabbitMQ,另一些文件系统,replicated block devices好比DRBD也是相似的原理架构
复制系统中重要一点是使用同步复制仍是异步复制。在关系数数据库中是能够配置的,其余系统通常是hardcode两者之一。
想一想图5-1发生了什么,当网站的用户更新本身的照片时,客户端发送更新请求给leader节点,leader节点收到以后,数据变化转发给follows,最终leader告诉客户端更新成功。
图5-2是系统中几个组件的交流状况:客户端,leader,followers,时间从左到右,请求和响应用箭头表示。
不难看出,follow1是同步复制,他复制完后会给leader响应,leader确认follower1复制完成后再告诉客户端更新成功。follower2相反是异步的,leader不会等待follower2,图中有明显的延迟。
同步复制的优势是follower节点都会确保有最新的copy。若是leader节点挂了,follower节点还有完整的数据能够用。缺点是follower节点若是因为某些缘由(网络延迟,机器挂了等)不能响应,那么leader节点就得傻等着,写请求也不能处理。leader节点必须锁住全部写操做,直到同步复制响应或恢复正常。
因为以上缘由,让全部follower节点都同步复制是不现实的:任何一个节点中断都会致使整个系统暂停。在实战中,若是你在数据库中启动同步复制,通常是一个follower同步复制,其余followers异步复制,若是同步复制的节点很慢或不可用,那么会把另一个follower节点变成同步复制,从而这样就保证了至少两个节点拥有完整的copy:leader和同步复制的follower。这种配置被叫作semi-synchronous (半同步)
一般leader-based replication会被配置成彻底异步复制的。在这种状况中,若是leader挂了或不能回复,任何尚未来得及在followers上复制的的“数据变化”都会丢失。也便是说,写操做不保证持久化,即便已经被客户端确认。然而彻底异步复制的好处是leader能够继续进行写操做,即便全部followers节点挂了。
弱持久看起来是个很差的妥协,可是asynchronous replication却被普遍应用,尤为存在不少followers或者分布在不一样的地理位置。
有时你须要添加新的followers节点,要么增长replicas的数量,要么替换失败的节点。问题来了,你怎么确保新的follower能获得准确的leader数据的copy呢?
简单的copy是远远不够的,由于客户端在持续的向数据库写数据,数据一直在不断的变化,因此在不用的时间点复制数据会产生不一样的版本。这样的结果是没有意义的。
添加新follower节点过程大体以下:
添加新follower在不一样数据库中的实现各有不一样。一些数据库是全自动实现,一些须要手动实现。
系统中任何节点均可能挂掉,可能因为意外,也极可能因为维护重启。在重启的同时又不影响系统正常提供服务对于运维来讲是颇有利的。咱们的目标是保持整个系统运行,尽管有某些节点挂掉,同时将单点故障带来的影响降到最低。
那么咱们怎么用leader-based replication实现高可用呢?
Follower failure: Catch-up recovery (follower节点故障:catch-up恢复)
在本地磁盘上,每一个follower节点都保存一个log文件,用来记录来自leader的数据变化。若是follower节点挂了或者重启,或者leader和follower节点之间的网络中断了。follower节点能很快恢复,由于log记录了故障发生前的最后一次transaction。当follower连上leader以后,就能够请求获得因为故障而缺失的数据变化,把缺失补回来以后,就和leader一致了,而后就能够继续接受leader的数据变化流了。
Leader failure: Failover (leader节点故障:故障转移)
处理leader节点故障更棘手:一个follower节点要被晋升为leader,客户端要被从新配置,而后把写请求发给新leader。其余的followers开始接受新leader的数据变化。这个过程叫作故障转移。
故障转移能够手动操做也能够自动完成。一个自动的故障转移流程包括如下几步:
故障转移充满各类容易出错的点:
目前没有容易的方案来解决这种问题,因此不少运维团队宁愿选择手动failover,即便系统支持自动failover。
节点失败,网络不可靠,围绕数据复制一致性的权衡,持久化,可用性,延迟是分布式中的基本问题,咱们会在后面章节详细讨论。
基于leader的replication在后台是怎么运行的?在实战中有几个不一样的方法在使用,让咱们一个个看。
Statement-based replication (基于语句的复制)
最简单的案例,leader记录下每一个写请求(statement语句),把写请求语句发给followers。对于关系数据库,就是把INSERT, UPDATE, or DELETE 语句发给followers,而后follower解析执行SQL语句。
这种方法看起来不错,可是在复制过程当中可能失败:
我也能够绕过这些问题,好比在语句被记录下的时候,把不肯定的函数调用全换成肯定的返回值。这样每一个follower都会获得相同的值。然而,现实场景会有大量的边界案例,因此其余的replication方法会被预先考虑。
Mysql5.1版本以前采用的是基于语句的replication。今天仍在应用,由于他很紧凑。在有不肯定的语句中,Mysql默认转换成基于行的replication。
Write-ahead log (WAL) shipping(预写日志运送)
第三节咱们讨论了,存储引擎怎么在磁盘上存储数据。咱们发现每次写都会追加到到一个日志中:
不管哪一种状况,日志是一个只能追加的字节序列,它包括了全部的写操做。咱们可使用彻底相同的日志在另一个节点上去生成一份replica。除了把日志写到磁盘上,leader也会把经过网络把日志发到其余follower上。当follower处理日志的时候,它会生成一份和leader相同的数据结构。
这种replication方法被应用在PostgreSQL和Oracle等数据库。主要缺点是,日志描述的数据很详细,好比,一个WAL包含哪一个磁盘块的哪一个字节被改变。这就致使了replication和存储引擎牢牢耦合在一块儿。好比数据库的存储格式从一个版本换成另外一个版本,通常leader和follower不能运行不一样的版本,因此就不能顺利的升级版本。
这看起来是个小的实现细节,可是对运维有很大的影响。若是replication协议容许follower运行比leader更高的版本,那么能够先升级全部的followers,而后进行failover,把某个已升级的follower选为leader。若是replication协议不容许版本不匹配,WAL shipping一般是这样的,这种状况须要停机来升级。
Logical (row-based) log replication 基于逻辑日志
WAL shipping中replication和存储引擎使用一样的日志格式,这也是耦合的缘由。让存储引擎和replication用不一样的日志格式,就能够解耦了。这种日志叫逻辑日志(logical log),区分于存储引擎的物理数据表示。
对于关系数据库,逻辑日志是描述对数据库表写操做的序列记录,粒度是行(row)。
一个修改好几行的transaction会生成这样的日志记录,紧接着是一条记录代表transaction被提交。mysql的binlog(若是配置成row-based replication)就会用这种方法。
因为逻辑日志和存储引擎内部解耦了,因此很容易向下兼容,这样leader和follower就能够用不一样的版本,或者用不一样的存储引擎。
逻辑日志也能够很简单的被外部的应用解析。若是你想把数据库的内容发给外部的应用,这点事很是有用的。好比把数据给数据仓库作离线分析,定制索引和缓存。这种技术叫change data capture,第11节接着讨论。
Trigger-based replication 基于触发器
以上讨论的replication方法都是在数据库内部实现的,没有任何的应用层的代码。在不少状况下,你可能须要更灵活。好比,replicate数据库数据的一部分,把数据从一个数据库复制到另外一个数据库,或者你须要添加解决冲突的逻辑代码,那么你就须要把replication挪到应用层。
一些工具,好比Oracle GoldenGate,应用可使用这些工具从日志中读取数据变化。另一个方法是用关系数据库的特性:trigger和stored procedure。
trigger可让你注册本身的代码,当数据变化时,代码会运行。trigger也能够把数据变化存到另外一张表,外部应用能够读着张表来获取数据变化。Databus for Oracle和 Bucardo for Postgres就是这样的。
Trigger-based replication比其余replication方法有更大的开销。对于素菊开内置的replication方法,更容易出问题和限制。而后这种方法因为他的灵活性而很是有用。
容忍单节点错误是使用replication的一个缘由,另外还有扩展性(增长节点从而处理更多的请求),延迟(根据地理位置来放置节点)。
Leader-based replication中,写要经过leader来执行,读能够经过全部节点执行。在多读少写的场景,简单的增长follower节点就能够减小leader的负担。而后这种read-scaling架构只适用于异步replication,若是使用同步replication,一个节点挂了,就会致使整部系统不可用。节点越多,越可能出现问题。因此彻底同步的replication是不可靠的。
不幸的是,当应用从一个异步的follower节点读数据后,它可能获得过期的数据,由于那个节点可能落后于leader节点。这就致使了明显的数据不一致,由于全部的写操做并无当即反应在followers节点上。这种不一致时暂时的,等你中止写数据后,过一会,followers节点都跟上后,就和leader一致了。因此叫作最终一致。