在本文中我们将会讨论MySQL、Oracle、MongoDB、Redis以及Oceanbase数据库,大家可能会奇怪为什么看不到有名关系型数据库MSSQL、DB2或者有名NoSQL数据库Hbase、LevelDB等,最主要原因是我对这些数据库熟悉层度不够。但相信这些数据库都会有相应的解决方案。
首先我们看一下数据库以及常看到的HA以及分布式架构方案
数据库类型 | 架构方案 | 架构类型 |
MySQL | Keepalived+MySQL Replication | HA |
| MHA+MySQL Replication | HA |
| Febric | HA/SHARDING |
| Other | HA/SHARDING |
Oracle | MAA | HA |
MongoDB | Replica Set | HA |
| Shareding | SHARDING |
Redis | keepalivied + MS | HA |
| Sentinel + MS | HA |
| Twemproxy | SHARDING |
# 本次主要讨论这些架构的实现原理,可以挖掘这些架构不足的地方在哪里,如何去改善等等。
首先,我们很有必要先阅读邓博(何登成)的《数据一致性-分区可用性-性能——多副本强同步数据库系统实现之我见》
文章前面就有我们所关心的四个问题:
问题一:数据一致性。在不使用共享存储的情况下,传统RDBMS(例如:Oracle/MySQL/PostgreSQL等),能否做到在主库出问题时的数据零丢失。
问题二:分区可用性。有多个副本的数据库,怎么在出现各种问题时保证系统的持续可用?
问题三:性能。不使用共享存储的RDBMS,为了保证多个副本间的数据一致性,是否会损失性能?如何将性能的损失降到最低?
问题四:一个极端场景的分析。
在这里,我们基本结合着第一和第二个问题来讨论本次的话题,数据库的高可用和分区解决方案。
数据一致性分为强一致性和弱一致性,其中弱一致性里包含我们在NoSQL中常听到的最终一致性。选择强一致性或者弱一致性,很大程度上取决于业务类型和数据库类型,比如:阿里淘系电商大量使用MySQL数据库保证数据强一致,比如阿里蚂蚁系金融通过Oceanbase数据库保证数据强一致,而像新浪微博则选用Redis数据库无需保证数据强一致。
从上面数据库中关系型数据库MySQL和Oracle都是基于ACID的,并且采用WAL(Write-Ahead-Logging)技术,保证事物日志先刷磁盘。MySQL有binlog日志,Oracle则有Read Log,MongoDB虽是NoSQL,但比较特殊,其同步有核心依赖Oplog。在主备复制关系中,MySQL有半同步复制,Oracle则拥有最大保护模式的DataGuard都能保证数据的强一致,MongoDB可以通过getLastError命令来保证写入的安全,但其毕竟不是事务操作,无法做到数据的强一致。
下面来看看上面列出的架构,首先看MySQL的方案,我们逐个讨论。
1、Keepalived+MySQL Replication
简单画出来如下图所示,我们通过开源HA软件Keepalived来实现高可用,DB的话可以选择MySQL MM或者MS架构,个人更建议使用MM架构,因为MS架构在一次切换之后需要重做同步,而MM则大部分情况下不用重做,除非出现数据不一致现象。这种架构读写压力都在VIP所在的一端,当然我们完全灵活地将部分读压力放到另一端,比如手动查询或者可用性不太敏感的读程序,大家要考虑清楚备机是没有高可用的保护的。
一般在如下情况下将会触发Keepalived进行一次HA切换:
1)当前主服务器宕机;
2)当前主服务器Keepalived本身出现故障;
3)当前主库出现故障;
Keepalived进行HA切换,VIP将会漂移到备机上,应用连接都是通过VIP接口来进行,所以可以说对业务是透明的操作。
但这里还是存在一些我们需要考虑的问题,比如发生第二种情况,当前主服务器上Keepalived本身出现故障导致Keepalived进行HA切换,这时候DB是正常的,如果有长任务挂在那里是有问题的,正常我们应该是kill掉这些Thread,应用配合重新在新库上执行一遍。
另外,如果MySQL采用异步同步,那还需要考虑意外宕机时造成数据丢失的问题,通常是后续需要进一步处理,可以手动也可以自动化掉。
我们在看看使用中可能会遇到的场景,业务在这环境上正常运行一段时间,在某一时刻备机上的Keepalived本身出现故障而进程退出,但因欠缺监控导致没人知晓,过一段时间主机也出现问题触发HA切换,但这时候已无心跳关系,VIP无法进行漂移,直接影响业务。这种问题在监控不到位或者监控疏漏的情况之下经常发生,后果也比较严重,所以称职的DBA务必做好监控,而且保持对告警的敏感度。
还有一种场景是采用MySQL MS架构时,业务正常运行一段时间之后进行了一次HA切换,VIP漂移到备机上,原MS同步关系遭到破坏,DBA在未知情况之下把原主库的Keepalived进程恢复,业务再运行一段时间之后再做了一次HA切换,VIP漂移到最原始的主库上,这就活脱脱产生了数据丢失,如果该问题发现很晚,那DBA就遭罪了,不光影响业务,修补数据都使得DBA疯掉!
最后我们抛出一个问题,因网络问题导致HA的心跳中断,这时候会是怎样的情况? 该问题留给大家自己思考。
2、MHA+MySQL Replication
MHA有个监控管理节点,该节点可管理多套MySQL集群,如果Master遇到故障,MHA就触发一次Failover,候选的主节点会提升为主库,其他slave节点重新Change master到新主库,其中通过在配置文件里设置优先级来确定候选主节点。
MHA进行Failover过程:
1)检测到Master异常,进行一系列判断,最后确定Master宕掉;
2)检查配置信息,罗列出当前架构中各节点的状态;
3)根据定义的脚本处理故障的Master,VIP漂移或者关掉mysqld服务;
4)所有Slave比较位点,选出位点最新的Slave,再与Master比较并获得binlog的差异,copy到管理节点;
5)从候选节点中选择新的Master,新的Master会和位点最新的Slave进行比较并获得relaylog的差异;
6)管理节点把binlog的差异copy到新Master,新Master应用binlog差异和relaylog差异,最后获得位点信息,并接受写请求(read_only=0);
7)其他Slave与位点最新的Slave进行比较,并获得relaylog的差异,copy到对应的Slave;
8)管理节点把binlog的差异copy到每个Slave,比较Exec_Master_Log_Pos和Read_Master_Log_Pos,获得差异日志;
9)每个Slave应用所有差异日志,然后reset slave并重新指向新Master;
10)新Master reset slave来清除Slave信息。
MHA还支持在线切换,过程简化如下;
1)核实复制配置并识别出当前的Master;
2)识别出新的Master;
3)拒绝当前主写入;
4)等待所有的SLAVE追上数据;
5)新的Master开启写入;
6)其他Slave全部指向新的Master。
MHA能够保证主库出现异常的时候可以正常切换,但它却不保证Slave的问题,如果你的应用直连Slave进行只读,当Slave出现故障的时候业务会受到影响。
我们可以在Slave节点之上加一层SLB层,也就是做一下负载均衡,如下
MySQL复制选择异步还是半同步,这个问题在上面已经讨论过,如果想不丢失数据,就选择半同步复制。
另外,我们思考一个问题,管理节点故障会产生什么影响?如何保护管理节点?其宕机不可恢复的情况下如何处理?
3、Fabric
Fabric是Oracle自己推出的一款产品,可以实现HA和Sharding解决方案。Fabric的功能还是蛮吸引人的,它的自动Failover,读写分离以及自动分片等这些特性在数据库架构中最为关注的特性。但毕竟是一个新兴产品,投入生产使用经验很少,暴漏出的问题也不多,所以在核心业务上使用Fabric还是有一定的风险。
Fabirc架构里有几个组件,
Fabric-aware Connectors
● Python, PHP, and Java(Connector/Python、Connector/PHP、Connector/J)
● Enhanced Connector API
MySQL Fabric Node
● Manage information about farm
● Provide status information
● Execute procedures
MySQL Servers
● Organized in High-Availability Groups
● Handling application data
应用都会请求Fabric连接器,然后通过使用XML-RPC协议访问Fabric节点,Fabric节点依赖于备用存储(backing store),其实就是MySQL实例,存储整个HA集群的元数据信息。连接器读取backing store的信息,然后将元数据缓存到cache,这样做的好处就是减少每次建立连接时与管理节点交互所带来的开销。
Fabric节点可管理多个HA Group,每个HA Group里有一个Primary和多个Secondary(slave),当Primary异常的时候会从Secondary中选出最合适的节点提升为新Primary,其余Secondary都将重新指向新Primary。这些都是自动操作,对业务是无感知的,HA切换之后还需要通知连接器更新的元数据信息。Fabirc的读写分离是怎么做到的?其实还是借助连接器,根据应用的请求类别选择发送给Primary还是Secondary,如果是写操作,连接器就路由到Primary,而如果是读操作,会以负载均衡方式发送给活跃的Secondary。
在这里我们想一个问题,如果Fabric节点出现故障会是怎样的情况? 其实很简单,如果HA Group没有因故障而产生任何变化,进而元数据信息不变,那么连接器依然会正确的路由请求,因为连接器已缓存过元数据信息。但一旦HA Group里出现故障,比如Primary或者Secondary失败,前者会导致自动failover不会产生,进而影响数据库的写入,后者则部分读请求将会失败。所以监控并管理好Fabric节点是非常重要的。
Fabric另一个主要特性是分片,Fabric支持自动分片,目前Fabric支持两种类型的分片 — HASH和RANGE,其中RANGE方法要求拆分字段属性为数字型。
应用访问数据库还是依赖连接器,并且必须指定片键。在分片的场景中,连接器会起路由分发的作用。
为保安全,强烈建议生产环境中每个分片都采用HA Group。
真实的环境中,并非所有的表都需要拆分,因此Fabric还会创建一个全局组(Global Group),里面存放所有全局表(Global Table),而每个分片都将会存放全局表的副本,这样做的好处就是方便了拆分表和非拆分表的JOIN操作。如果应用对全局表进行更新,连接器将会把请求发到全局组,全局组又将自己的变化同步到各个HA Group。
分片的大致工作流我们了解到了,我们还需关心其稳定性以及性能,因为目前还没在生产环境中真正使用过,这个问题先挂在这里。
4、Other
除了上面介绍的方案之外,还有非常多的高可用解决方案,比如MMM、Galera、DRBD+Pacemaker+Corosync、Heartbeat+DRBD等等,而分库分表的话可以使用淘宝非常知名的TDDL。
当然,如果条件允许也完全可以自己开发出一套强大的HA软件和中间件,或者对上述开源软件进行二次开发,只不过我们需要在开发之初就将规模化的成分加入进去,要知道我们开发出来的产品不应该仅限于某几个场合或者某几种条件之下,而应充分体现大众化,就像是可插拔的API,适应大多数场景,在规模化运维环境之下发挥良好作用。