5. 同城双活
单机房遇到机房断电, 网络故障便停止服务, 同城机房可弥补这个弊端.
由于同城机房物理上距离足够近, 可以搭建专线连接, 服务间做跨机房调用近似同机房. 在部署上和单机房相比不做特殊要求.
接口层的 DNS, 四层设备, 反向代理, 网关 / web 层可以随机路由.
应用层由于是无状态的, 用服务化框架连通, 服务治理的范围就在这部分, 内部可做横向扩展, 具备 scale out 能力, 接口层到应用层, 或者应用层内部服务路由策略可以自由选择, 例如随机, 轮询, 一致性哈希, sticky session, 同机房优先, 自定义路由规则等.
存储层先拿数据库做例子, 一般互联网公司使用 MySQL, 主从复制 (异步或者半同步), 主故障时候可以手工(switch over) 或者自动切换 (failover) 从为主, 解决可用性的同时对于 RPO(Recovery Point Objective)和一致性都或多或少有损, 另外这种架构, 对于存储是伪双活的, 因为同一时间只有一个 Master. 另外一种方式是使用分布式数据库, 利用 Paxos,Raft 分布式一致性协议保证强一致. 存储这放到后面的章节介绍.
6. 异地灾备(跨地域故障切换)
同区域 (同城) 的容灾, 遇到地震和洪涝等自然灾害时, 服务的持续性就会中断. 跨地域部署最大的挑战是延迟, 一些网络延迟的数据如下:
北京 - 广州 2000 公里 + 50ms
北京 - 杭州 1000 公里 + 30ms
上海 - 杭州 100 + 公里 7ms
北京多个机房间 3ms
同机房 300us
如果服务调用关系复杂, 多次的 round trip, 加上网络的抖动, 跨地域部署往往是不能接受的. 异地备份可以解决遇到区域挂掉的时候, 做冷备, 热备切换, 保证服务持续性.
接口层, 服务层, 存储层均独立部署一套. 存储层靠活着的 (alive) 区域进行异步复制.
这种同城双活 + 异地灾备的架构, 可称之为 "两地三中心" 架构, 即两个区域, 三个 IDC(可用区).
7. 异地多活
同城双活 + 异地灾备对于中小型应用往往足够了, 但是对于大型公司而言, 一是数据量极大, 二是对于可用性要求极高(5 个 9 的 SLA 水平全年停服不能超过 5min), 异地灾备架构的弊端在于:
1)跨地域机房不跑流量, 对于数据一致性很高的场景, 出事了不敢切. 恢复时间(RTO,Recovery Time Objective), 取决于方案本身或者预案在当下执行的质量.
2)备份全站, 资源利用率低, 成本高.
3)伸缩性不好, 扩容困难, 不具有扩展性, 受限于单地域的容量, 包括计算, 存储, 网络资源.
所谓异地多活, 就是说在多个不同物理地域之间同时提供业务服务.
各个地域之间不是主备关系, 所以不涉及到故障切换 failover 的工作,《High-Availability at Massive Scale: Building Google's Data Infrastructure for Ads》里也提及了 failover 的灾备策略往往不是自动化的, 而且一般比较复杂的, 需要测试演练的, 所以维护的成本, 切换的风险也很高.
异地多活下, 各个地域独立隔离, 业务流量可以不均等的分配到各个地域和可用区里面. 与异地冷备相比,
1)具备更快的恢复能力. 流量动态分配, 一个区域挂掉, 流量可以自由的在地域间调度, 切换. 在某些场景甚至还可以支持就近原则, 用户访问更快. 预期和非预期的机房故障都可以支持. 预期的可以做容灾演练, 机房维护, 这些都对业务都是透明的.
2)不用备份全站, 成本低.
3)扩展性好, 数据应用可以 shard 在各个区域, 这种伸缩性是必备的.
这里需要强调下并不是所有业务都需要做异地多活, 主链路功能, 对可用性有极致要求, 规模大的才优先做, 否则方案也会比较复杂.
8. 单元化
异地多活的弊端显而易见就在跨地域延迟上, 解决的办法可以 "单元化"(unit), 这个概念是阿里提出来的, 也就说让请求收敛到同一区域内完成, 单元高内聚, 不做跨区域访问, 即 "单元封闭". 然而不是所有服务都是单元化的, 一些长尾和小流量应用可以只做 "中心化" 架构, 仍然靠冷热备做容灾.
单元封闭的策略可以按照某个业务维度拆分用户, 例如阿里买家是按照 userId 取模划分单元, 饿了么按照 user 所处的地域划分.
如下图所示按用户分流两个地域, 单元化的架构如下. 这样的架构里面存储也是单元化的, 只承载本单元的请求, 和路由的策略保持一致.
9. 存储层的高可用与数据一致性
本文不谈高端存储, 只谈基于 commodity hardware 之上的存储, 例如互联网公司常用的 MySQL 或者分布式数据库.
MySQL 在本区域或者跨区域的高可用可通过主从复制 (replication)+ 多副本(replica) 实现, 一个实例做 standby, 主故障即可切换, 其他从可以做本区域或者跨区域的复制, 仅做只读, 扩展读能力.
然而 MySQL 可以做到 RPO=0 吗? 数据强一致吗?
如果采用异步复制, 必然有损, 切换过程中主库未来得及同步过去的数据会丢失, RPO>0, 如果从提升为主继续服务自然就不一致了, 此时还有 "脑裂" 这种双主可能性的存在, 如果停止服务, 则可用性又受到影响, 一般服务也不会牺牲可用性.
如果采用半同步复制 semi-sync, 即主库在返回客户端成功之前, 需要保证从库 ACK 接受到 binlog, 对于是否影响幻读又可以设置 AFTER_SYNC 还是 AFTER_COMMIT 模式, 注意开启 semi-sync 会影响写性能.
那么开启 semi-sync, 就能保证 RPO=0 吗? 也不一定, 一般数据库采用 InnoDB 引擎, MySQL 在 Server 层面采用 2PC 进行提交, 在事务的 Prepare 阶段 Redo log 写完日志, 事务不是 commit 状态, 需要走到 Server 层面, Binlog 写成功, 才进行解锁, 清理 Undo log 的 commit 工作, 再返回客户端.
写 Redo log 和 Binlog 都有相应的参数控制.
innodb_flush_log_at_trx_commit 控制 InnoDB Redo log 的刷盘的频率控制.
图片来源 点此 http://adminlinux.blogspot.com/2015/07/
innodb_flush_method 控制 InnoDB 相关文件的刷盘走什么模式, 经过 Page Cache 还是 Direct IO.
图片来源 点此 https://kylinyu.win/rdbms_mysql_tuning/
当 MySQL 进程 crash 或者操作系统挂掉, binlog 是否传输到从库, 相应的文件有没有落盘, 都会造成客户端, 主库, 从库的数据不一致.
举个例子, 假设在 Prepared 阶段, MySQL 或者操作系统 crash, 如果 Redo log 或者 Binlog 任意一方丢了, 那么客户端的确会感知失败了, 主库再次恢复会 rollback, 如果 binlog 同步到了从库就生效了, 从而出现主从不一致.
数据一致性的范畴应该限定于数据库实例, 不参考客户端的态度. MySQL 的主从, 再加上刷盘, 是不能保证数据强一致的. 但是从另外一个层面理解, 如果采用 semi-sync + 强制刷盘的策略, 可以做到不丢数据, 但是实例之间的数据一致性是无法保证的.
但是为了满足可用性要求, 减少数据不一致, 有一些策略可以优化, 例如,
1)采用保护模式, 如果主库挂掉, 在恢复后避免直接写, 可以尽可能的把 diff apply 到从库上, 如果没有冲突, 则安全; 如果有冲突采用业务对账手段订正数据.
2)对于切换期间正在写入的用户, 可以做一些降级, 开启禁写, 避免其再写新主库, 导致数据不一致, 可用性达标不需要满足 100% 的用户, 真发生灾难了, 原则上满足绝大部分用户才是关键.
10. 跨区域存储复制
一般互联网公司都看重可用性, 面对跨地域的高延迟和网络抖动, binlog 同步不是个好办法, 那么存储层面的复制可抽象为利用通用的 DRC(Data Replication Center)中间件解决, 阿里云的解决方案即是 DTS(Data Transmission Service). 这类中间件可以提供稳定的秒级复制, 保证事务一致性的实时 DB 同步, 其原理一般是伪装为数据库的 binlog, 异步的将数据进行复制远程传输, 中间件本身可以做持久化, 便于实现一次订阅, 多点分发的特性. 这种方式注重 AP, 牺牲了异地的一致性.
在 "单元化" 异地多活的架构里, DTS 可以承担单元和中心间的数据复制功能, 中心数据广播到各个单元, 单元之间也做数据同步, 例如饿了么的商家数据广播到各个单元, 这样请求可以做 "单元封闭"; 单元间的同步做双向复制, 又支持了区域故障时候的机房切换.
11. 分布式数据库
现在越来越多的数据库采用分布式数据库系统, 例如 Google 的 Spanner, 阿里巴巴的 PolarDB,Oceanbase, 开源的 Tidb 和 CockroachDB 等, 他们可实现存储层的数据一致性, 其依赖 Paxos 或者 Raft 协议. 把存储近似看做一个状态机, 对数据每次修改都可以看做一次增量日志, 事务的实现, 可以在存储上套一层 Paxos 或者 Raft 协议. 例如, 三个节点的集群, Raft 的 Leader 负责写, Leader 的事务日志同步到 follower, 超过多数派 (majority quorum) 落盘后, 再 commit. 这种架构对于写入的延迟明显高了, 但是这种小幅的牺牲却可以保证数据强一致. 而吞吐可以靠拆分解决, 可以存在很多的 Raft Group, 数据按照 Range 进行分区, 还可以做二级分区, 这种的 sharding 效果等同于分库分表, 而扩展性, 灵活性和容灾能力更好. 只要有半数以上的节点正常就能保证分布式数据库正常工作, 当 Leader 节点故障时发起 Leader election 自行恢复, 无需人工 switch over 或者 failover.
在阿里云上的 "金融级"RDS, 其原理也是在 MySQL 内核上套用一个 Raft 状态机控制事务, 保证了数据的一致性, 由于分布式数据库的依赖 Paxos 或者 Raft, 所以延迟更是问题, 一般部署在同地域, 例如 "金融级"RDS 要求部署在同一个区域, 依赖 DTS 做异地复制.
分布式数据库单 Group 至少 3 个节点, 更加可靠的方案可以采用 5 个节点, 3 地 3 机房 5 副本, 参考 蚂蚁金服 Oceanbase 的 "五地三中心" 架构 https://oceanbase.alipay.com/solution/HA ,Zone1 和 Zone2 同城双机房, Zone3 和 Zone4 同城双机房, 一个距离稍远的可以设计为仅做日志副本, 节省空间, 同时减少延迟带来的吞吐下降, 可做到 RPO=0,RTO 几十秒, 强一致, 可抵御个别硬件故障, 机房级灾难和区域级灾难.
12. 总结
本文先介绍了应用系统的构成和部署方式, 然后从演进的角度分析单机房, 同城多活, 异地灾备, 异地多活, 单元化的架构, 最后针对存储的多活, 特别是数据库, 从微观角度分析了存储层的高可用与数据一致性, 对于异地的复制采用类 DRC 的中间件, 最后介绍了强一致和高可用的分布式数据库. 其中各个点都是浅尝辄止, 旨在让读者有一个宏观上的概念和对构建异地多活的应用系统有个体系化的认识, 一些细节可以按图索骥的深入展开, 这也是作者在不断学习努力的方向, 愿共勉.
转载时请注明转自 neoremind.com.
来源: http://www.tuicool.com/articles/636F3ur