许春植(Luocs)
(阿里巴巴高级数据库管理员,7年以上数据库运维管理经验,擅长MySQL、Oracle及MongoDB数据库,目前主要研究并建设MongoDB一套完整的运维体系)
编辑手记:感谢许春植授权独家转载其精华文章,这是系列文章之一,与大家分享其个人学习与经验总结,编辑时略有修订与节略。也欢迎读者朋友向我们投稿。
首先我们看一下数据库以及常看到的 HA 以及分布式架构方案:
数据库类型 |
架构方案 |
架构类型 |
MySQL |
Keepalived+MySQL Replication |
HA |
MHA+MySQL Replication |
HA |
|
Febric |
HA/Sharding |
|
Other |
HA/Sharding |
|
Oracle |
MAA / Sharding (12.2) |
HA / Sharding |
MongoDB |
Replica Set |
HA |
Sharding |
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 的方案,我们逐个讨论。
1Keepalived+MySQL Replication
简单画出来如下图所示,我们通过开源 HA 软件 Keepalived 来实现高可用,DB 的话可以选择 MySQL MM 或者 MS 架构,个人更建议使用 MM架构,因为 MS 架构在一次切换之后需要重做同步,而 MM 则大部分情况下不用重做,除非出现数据不一致现象。这种架构读写压力都在 VIP 所在的一端,当然我们完全灵活地将部分读压力放到另一端,比如手动查询或者可用性不太敏感的读程序,大家要考虑清楚备机是没有高可用的保护的。
一般在如下情况下将会触发 Keepalived 进行一次 HA 切换:
① 当前主服务器宕机;
② 当前主服务器 Keepalived 本身出现故障;
③ 当前主库出现故障;
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 的心跳中断,这时候会是怎样的情况? 该问题留给大家自己思考。
2MHA+MySQL Replication
MHA 有个监控管理节点,该节点可管理多套 MySQL 集群,如果 Master 遇到故障,MHA 就触发一次 Failover,候选的主节点会提升为主库,其他 slave 节点重新 Change master 到新主库,其中通过在配置文件里设置优先级来确定候选主节点。
MHA 进行 Failover 过程:
① 检测到 Master 异常,进行一系列判断,最后确定 Master 宕掉;
② 检查配置信息,罗列出当前架构中各节点的状态;
③ 根据定义的脚本处理故障的 Master,VIP漂移或者关掉mysqld服务;
④ 所有 Slave 比较位点,选出位点最新的 Slave,再与 Master 比较并获得 binlog 的差异,copy 到管理节点;
⑤ 从候选节点中选择新的 Master,新的 Master 会和位点最新的 Slave 进行比较并获得 relaylog 的差异;
⑥ 管理节点把 binlog 的差异 copy 到新 Master,新 Master 应用 binlog 差异和 relaylog 差异,最后获得位点信息,并接受写请求(read_only=0);
⑦ 其他 Slave 与位点最新的 Slave 进行比较,并获得 relaylog 的差异,copy 到对应的 Slave;
⑧ 管理节点把 binlog 的差异 copy 到每个 Slave,比较 Exec_Master_Log_Pos 和 Read_Master_Log_Pos,获得差异日志;
⑨ 每个Slave应用所有差异日志,然后 reset slave 并重新指向新 Master;
⑩ 新 Master reset slave 来清除 Slave 信息。
MHA 还支持在线切换,过程简化如下:
① 核实复制配置并识别出当前的 Master;
② 识别出新的 Master;
③ 拒绝当前主写入;
④ 等待所有的 SLAVE 追上数据;
⑤ 新的 Master 开启写入;
⑥ 其他 Slave 全部指向新的 Master。
MHA 能够保证主库出现异常的时候可以正常切换,但它却不保证 Slave 的问题,如果你的应用直连 Slave 进行只读,当 Slave 出现故障的时候业务会受到影响。
我们可以在 Slave 节点之上加一层 SLB 层,也就是做一下负载均衡,如下:
MySQL 复制选择异步还是半同步,这个问题在上面已经讨论过,如果想不丢失数据,就选择半同步复制。
另外,我们思考一个问题,管理节点故障会产生什么影响?如何保护管理节点?其宕机不可恢复的情况下如何处理?
3Fabric
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。
分片的大致工作流我们了解到了,我们还需关心其稳定性以及性能,因为目前还没在生产环境中真正使用过,这个问题先挂在这里。
4Other
除了上面介绍的方案之外,还有非常多的高可用解决方案,比如 MMM、Galera、DRBD+Pacemaker+Corosync、Heartbeat+DRBD 等等,而分库分表的话可以使用淘宝非常知名的 TDDL。
当然,如果条件允许也完全可以自己开发出一套强大的HA软件和中间件,或者对上述开源软件进行二次开发,只不过我们需要在开发之初就将规模化的成分加入进去,要知道我们开发出来的产品不应该仅限于某几个场合或者某几种条件之下,而应充分体现大众化,就像是可插拔的 API,适应大多数场景,在规模化运维环境之下发挥良好作用。
本文出自数据和云公众号,原文链接