备份,十分经典又十分硬核的一章
1.why
- 跨机房备份可以提供更低的读取速度,例如东西海岸两个DC.
- 主数据库挂了,可以由备份升级成主,持续提供服务。
- 读多的情况下,备可以提供部分读请求。
2.基础
节点类型:
- leader(master, primary):读写
- followers(read replica, slave, secondary, hot standby):只读
形式:
- 一主n从: 主写,从读。
- 多主:多个主都可以写,然后相互同步
- 无主:任意一个节点都可以读写
3.一主多从
先从一主的形式来看,其他两种相对更加复杂,不仅有这种问题,还会有附加的问题。
3.1异步/同步备份
同步:主写完,每个备份都写一遍,再返回写成功。缺点明显,如果一个从库写失败,就会失败。而且延迟可能会是个问题。如果网络堵塞,网络中断,或者就是机器硬盘满了,等等问题都会导致失败或者延迟特别大。 好处也明显,任何一个从,都是和主一样的数据,所以一旦主节点挂了,任何一个从都可以升级为主,继续提供服务。
异步:主写完,就返回写成功,后面再异步的备份到从库。好处:性能好,从库的问题不会影响写操作。坏处反过来:1.读从库可能是老数据。2.主节点挂了,数据就丢了。也就是数据的duribility不能保证。
半同步:一个从使用同步备份,其他从使用异步备份,主挂了之后,同步备份升级,其中一个异步备份成为同步备份。
3.2 加新follower节点
- 主库做一个snapshot,拷贝到新节点。(其实我觉得从库也可以,待研究)
- 连上主库,备份过来snapshot之后的所有修改。这一块就有很多熟悉的概念。PostgreSQL叫log sequence number,SqlServer好像也这么叫,Mysql就是binlog coordinates。
3.3 节点outage处理
follower节点挂:
重启节点,然后从主节点拿修改的log+本地的一些log,然后处理。
主节点挂:此时要进行failover
- 首先得确认主节点是挂了。一般用心跳,可以监听30s,如果一直都没有反应,则认为主节点已经挂了。
- 选择新的主节点: 选举算法待了解,目前就知道Zookeeper的raft算法?不过不清楚具体细节。一般来说选leader需要大多数节点同意,而且这个新主的数据是最接近老主的。也就是lag比较小。
- 重新配置系统,然后都使用这个新主。这里得注意老主在回复后可能会重新加入系统,而且认为自己还是主。这种情况需要保证恢复的老主会变成follower。
failover中的问题:
- 如果采用异步的replication变成了新主,他会比老主少一部分最新数据,当老主回复加入网络后,这里其实就有冲突了。常用解决办法:丢弃老主的冲突数据,不过这个会影响数据的durability。另外如果说其他有系统在用那些被discard的data,这个是十分危险的。文中距离gayhub出过的问题:data用了自增primary key,failover的时候丢掉了一些,但是在缓存里也用了哪些primary key,通过缓存的那层并不知道后面数据的变化,所以数据就乱了。
- 脑裂问题(split brain),就是一个系统里本来只应该有一个主,但是因为failover后老主上线,认为还是主。这种两个都可以接受写请求,就可能导致冲突和data corruption。有解决方法:挂壁其中一个主,但是要小心同时关掉两个主。
- timeout的定义,长短都不合适。
所以说有的不用自动failover机制,而是手动的,运维就比较惨。
不过我理解这里主要是对DB这种stateful的service来说,如果是stateless的,完全不用担心,我们以前的多standby的stateless-service就随便failover,有个db是同步的,所以failover起来问题也不大。
3.4 replication log的几种形式
3.4.1. statement-based replication
就是直接把insert,update之类的statement传过去replication。听着简单,一堆问题。
问题:
- 如果语句中有不确定的,比如now(),rand()。
- 在db支持并发写的时候,有些语句在执行的时候可能会依赖其他的数据,比如update ... where <>,并发情况下,可能有其他语句又在修改这个数据。这样要保证所有的replication上的执行顺序都和主是一样一样的,不然一致性就破坏了。
- 用户可以跑一些trigger,stored procedures,这里就可能有不同的side effect。还是和1一样,如果用户的sp里面有随机数之类的,不可控。
Mysql 5.1之前是用这个方法。现在是如果发现有不确定性,就改成row-based replication。VoltDB用的是statement-based,但是强制要求transaction是确定的。
3.4.2 Write-ahead log WAL
前面章节说过的log structure的db,直接就记录那个log,这个就可以用来做replication。
对于b树,每次真正修改block之前,先把怎么修改的记录写下来,这样可以恢复也可以用这些log来replicate。
Postgres和orcal就是这种机制。坏处是:这种log都是有一定得格式,而且特别具体,具体到某个byte该怎么修改,在upgrade时候比较麻烦。UBER关于mysql和postgres的那个博文里就提到了postgres在升级版本的时候,蛋疼的要命。
3.4.3 logical log (row-based)
从WAL的那种和storage engine绑定的问题中解耦出来。不是事无巨细的说道每个row怎么修改,而是直接做到row粒度的修改,这些log就logical log,这个是和具体physical db里每个byte怎么改的区别。mysql的binlog就是这种模式。
因为和engine解耦了,在updated时候就不那么麻烦,甚至可以直接将log给外部系统,比如dataware house里用离线分析。
3.4.4 trigger based
这个很冷门?我没咋听过。就是利用数据库提供的trigger和stored procedures做文章,里面自己定制化,一些data replication的方法。在有些case,比如仅仅想replicate部分特殊data,可以。如果这样,我觉得把这个逻辑上升到调用端也不是不行,毕竟在应用端会比较透明,不然出个问题,debug够爽的。
3. 5 Replication中lag导致的问题
一主多从,都同步备份,性能太差,采用异步,这个lag有可能会比较大,理论上叫eventually consistence。但是在实际应用中,lag还是会导致很多实际的问题。
- reading your write
读写分离情况下,刚写的进主库,再去从库读的时候还没有更新。所以eventually consistence不够的,这里需要read-after-write consistency.
实现起来说简单简单,说复杂很复杂。
简单:对于用户可以修改的东西,全从主库读写。
复杂:如果很多都是可以修改的,那个这个主库压力就太大了。
解决1:在客户端track这个修改的上次时间戳,如果小于一定时间,就从主库读。另外可以后端限制lag大于一定时间的replication服务读操作。
问题1:
网友评论