导读:本文介绍了一套基于MySQL Router、Cluster和MySQLShell的InnoDB Cluster高可用方案,以同时实现跨机房的高可用、自动Failover、高一致性、读写分离、读库高可用、读请求负载均衡。 |
全文约4500字,可参阅下面的大纲阅读。
1. 背景和目标 2. 方案
3. 压测结果 4. 总结
1.背景和目标
数据库的技术水平总是与业务发展紧密相连。
3年来,洋码头的业务增长了上百倍,数据库也从数台物理机扩展到了上百台,从原来单一的SQLServer发展到目前的Mysql为主,外加SQL Server、Mongo、Redis、InfluxDB的多元化的技术路线。每年黑五、双十一等重大活动节点,数据库总是会迎来前所未有的流量高峰,2017年的黑五更是比同期增长了7倍之多,新一轮的改造势在必行。
洋码头早期的核心业务数据库使用的是基于Alwayson的mssql的高可用架构,可以实现单机房高可用、自动Failover、高一致性、读写分离、读请求负载均衡,但在重大运营活动的峰值压力下,单机性能已经达到了极限,为了应对更高的流量,分库分表是常规的快速解决方案之一,但分库分表会导致数据库实例的大量增加,考虑到众所周知的成本问题以及将来的扩展性,我们决心把核心的订单、支付业务也迁移至MYSQL。我们的目标是:同时实现双机房高可用、自动Failover、强一致性、读写分离、读库高可用、读请求负载均衡。
2.技术方案
通过严谨的技术测试和分析研究,我们认为传统的基于半同步+MHA的主从模式仍然存在数据丢失的风险,网络波动可能导致脑裂,无法实现跨机房高可用、读库高可用以及读请求负载均衡。在多方对比后,我们选择了2016年底推出的MYSQL InnoDB Cluster作为高可用、高扩展性的分布式架构,配合LVS,能够完美的实现以上所有的需求。
介绍具体的方案之前,我们先介绍一下MySQL Innodb Cluster高可用架构的基础:MySQLGroup Replication。
2.1 基于MySQL Group Replication的同步方案
MySQL Group Replication,简称组**,是MySQLInnodb Cluster高可用架构的基础,是官方2016年底发布的高可用、高扩展性的集群服务。它基于原生GTID**和Paxos协议[1],自动化的一致性保障,日志顺序分发、多数投票机制确保了主从数据的强一致性(之前的半同步**极端情况是会出现不一致的情况),同时动态成员关系管理、错误节点监测确保了组内节点对外的可用性,各节点间在组信息上能够实时保持一致。采用插件的方式安装,对原有的数据库表基本没有影响(表必须具有主键),只是需要把MySQL升级到5.7.17以上,下面的介绍完全基于5.7的组**,8.0的刚刚推出,还有待观察。
2.1.1 组**的同步
MySQL传统的同步**一直被诟病的是其糟糕的一致性控制和从库可写导致的冲突问题,由于从库可读写,同步时并不强制binlog的连续性,同步异常问题时有发生,组**很好的解决了这个问题。
MySQL Group Replication Protocol
组**的同步仍然是基于原生的**,不同的是组**的每个RW事务在commit时都需要组内超过半数的成员确认该事务的全局顺序,即共同确认一个全局的顺序ID,顺序ID中包含了修改行的唯一标识,最终所有的成员都能够通过相同的顺序执行所有的RW事务,从而保证了数据的强一致性[2]。
因为这个特性,组**也能够支持多主模式,并有一套完善的冲突解决方案。当不同节点同时对同一条记录发起修改事务时,因为transaction_write_set有行的唯一标识,基于Paxos的冲突检测机制会提交先获得多数选票的事务,而另一个申请中的、顺序ID小于已提交事务的事务会因此而无法获得多数选票从而最终回滚,从而达到最终一致。
那么多主模式是否提升整体写负载呢?我们使用sysbench做了压力测试,3个写节点,5000000条记录随机更新,40个并发,大约8000TPS,就会产生大量的冲突,而单节点时可以达到11000TPS,多节点写TPS降低了近30%,平均响应时间也基本翻番。这是因为热点数据冲突的缘故,所以多节点同时写同一个库的设置是不**的。但如果多主的模式是应用在写请求按库隔离分布在各个节点的模式下,那么由于日志同步的开销要少于原本的事务执行,此时是可以提升整体写负载能力的。
在单主模式下的组**,除了主节点以外的成员,会自动开启read_only和super_read_only,以确保同一时刻只有一个节点能够产生事务日志,进一步增强一致性,降低了脑裂的可能。
目前一个组**集群最多支持9个节点。
2.1.2 组**的failover
组**成员之间会通过一个特定的端口相互进行心跳检测,这个端口在配置文件中配置,该端口不同于对外提供数据库连接的端口。
Failover在不同的场景下会有不同的响应方式,当主节点关闭组**,或者关闭实例时,组**会认为该节点是正常退出**集群,此时退出行为会被广播到所有的剩余成员(errorlog 会有相应的view change 记录),并且剩余成员会发起选举,根据权重参数选举出新的主节点,如果权重参数相同,则选择UUID最小的实例为主节点。如果主节点是异常连接中断,比如网络断开,服务器断电等,其他成员因为没有收到主节点的退出消息,这时会检查剩余节点是否大于半数,如果大于半数,则认为主节点异常,会从集群组中踢出主节点,并发起选举,选出新的主节点,如果剩余节点小于或等于半数,即只剩最后一个节点,为了避免脑裂,那么该从节点状态不变,没有新的主节点产生,唯一的从节点依然处于只读状态,直到我们手动指定新的主节点或者原主节点恢复连接。根据Paxos协议,任何节点成为主节点前都必须获取所有事务日志,并且relay完成,该机制保证了failover过程中事务不会丢失。整个failover过程是完全自动的。
2.1.3 组**的配置与启用
下列是与组**相关的配置项,基本都是必须配置的项目。
创建同步账号
初始化集群 对于第一个加入集群的实例,需要开启该选项(group_replication_bootstrap_group)才能启动组**,但如果组内已经有Primary,再加入开启了该选项的新成员,会导致脑裂和同步异常。当整个集群所有实例同时关闭再启动时,需要手动指定一个新的Primary,也需要开启该参数。
集群成员之间需要能够相互ping通hostname,可以通过DNS或者hosts文件实现。
组**提供了一套完善的容错功能,保障了数据的强一致性,自动failover功能则提供了数据库层面的高可用和快速的恢复服务能力,并且具有横向弹性扩展的特性。但是组**并不能实现读请求的负载均衡、读写分离以及对应用层的高可用,要实现这些就需要Cluster、MySQLRouter、MySQLShell。
2.2 MySQL Router、Cluster和MySQL Shell构成的Mysql InnoDB Cluster高可用方案
InnoDB cluster overview
2.2.1 MySQL Cluster简介
Cluster是这个高可用方案中的一个虚拟节点,它会在组**的所有成员上创建一个名为MySQL_innodb_cluster_metadata的数据库,存储集群的元数据信息,包括集群信息、集群成员、组**信息、连接的MySQL Router等信息,以提供MySQL Router查询。它相当于对组**上的成员做了一层逻辑上的封装,以一个集群的模式展现出来,各节点的状态与对应实例在组**中成员的状态实时同步,但是集群的节点与组**的成员只在创建集群时同步,后期组**的成员变更并不自动同步到集群中,可以在集群中做手动的节点增减,这样使得面向应用端的具体实例实现了更可控更灵活的高可用。
2.2.2 MySQL Router 简介 MySQL Router可以说是MySQL Proxy的升级产品,是介于Client与MySQL实例之间的**程序。MySQL Router会周期性的访问Cluster创建的MySQL_innodb_cluster_metadata库中的元数据获取集群成员信息,再通过performance_schema的系统表获取可连接实例及其状态,启动后会产生2个端口,分别对应集群的读写节点和只读节点,它使应用能够透明的连接InnodbCluster下的数据库,即使集群发生failover或者增减成员也不用修改应用配置。这里我们实现了读写分离和读请求的负载均衡。
注意,Router需要能够ping通集群成员的hostname,可以通过DNS或者hosts文件实现。在2.1.3及之前的版本,单个MySQL Router实例只支持上限500个连接数,需要根据实际连接数情况,部署足够数量的Router节点。
2.2.3 使用MySQL Shell配置MySQL Cluster
MySQL Shell是新的MySQL客户端工具,支持JavaScript、Python和MySQL脚本,用作搭建InnodbCluster。
MySQL Shell搭建Cluster的一些常用命令
2.2.4 初始化MySQL Router
初始化Router时如果不用默认端口可以指定写端口,读端口会自动设置为写端口+1,Router的name会记录在MySQL_innodb_cluster_metadata中,每台Router必须使用不同的name。初始化完成后在指定目录下产生一个配置文件,其中包含了初始化时关联集群的成员信息,这些信息只在启动Router后做连接用,启动期间集群成员的变化会实时被Router接收,但是并不会固化到配置文件中,如果需要修改配置文件可以重新初始化或者手动更新。
到这里,MySQLRouter、Cluster、MySQLGroup replication都配置完成,应用通过连接Router的读写端口能够透明连接数据库主从节点,单实例异常或failover的影响都可以控制在十多秒的窗口内。但此时Router本身仍是一个单点,官方文档中**将MySQLRouter安装在应用端来解决其单点问题,优点是减少网络传输带来的延迟、可使用Socket连接、容易扩展,与应用一对一绑定。但实际情况下需要考虑应用部署、发布、健康监测以及集群节点变更后更新Router配置文件等问题,这些问题会大大增加运维的复杂度,需要对原有的应用和发布系统做升级改造,成本较高。因此最终我们选择了集中部署加上LVS的模式,既方便维护,又具有灵活的扩展性。
2.3 通过LVS解决router的单点问题
基于MySQLInnodb Cluster和LVS的三机房高可用网络拓扑图
上图是基于三机房的完整性方案,数据库一主多从,每个机房各至少一个数据库实例,两组Router群集,一组映射出与数据库端口相同的读写端口,一组映射出与数据库端口相同的只读端口,每组Router集群的成员数量可动态扩展,每个机房配置1套LVS,每套LVS映射2个本机房网段的VIP,这样读写端口与只读端口都与数据库端口一致,并通过2个VIP分别绑定2个域名,这样就有只读和读写两个域名绑定相同的端口向应用分别提供服务。这样做的目的是,一旦中间层LVS、Router、Cluster出现异常或者有大规模调整需要重建中间层,因为应用配置的读写、只读端口都与数据库实例端口相同,可以先把2个域名直接指向数据库实例IP,在中间层调整完成后再指回LVS。同时,每个机房使用独立的DNS服务,确保本机房的域名指向本机机房的VIP,这样的架构中,当一整个机房断电或者单条跨机房光纤发生故障,即使是主机房故障,整个系统也会自动完成failover,快速恢复数据库服务,同时因为应用服务器访问的都是本机房的中间层,因此中间层和应用配置也无需变动,能够真正实现跨机房的高可用。
因为考虑机房容灾,所以这个架构的中间层冗余较多(其实所有数据库实例可以共用一套中间层,所以也并不算多),如果对机房容灾的要求较低,可以只在一个机房部署LVS和Router,而GroupReplication的从库可缩减为最小配置两台,当主机房异常时,备用机房的数据库从库手动设置为单点可用,读写域名和只读域名都手动指向该数据库实例IP即可。
基于MySQLInnodbCluster和LVS的双机房高可用网络拓扑图
3. 压测结果
使用Router+LVS不会影响数据库服务器本身性能但会影响整体响应时间,下面的压测会比较各种场景下各节点对响应时间的影响。
无跨机房网络结构
最差情况的2次跨机房网路结构
压测使用的是sysbench,对原生的测试脚本做了一些调整,耗时指单个语句的平均耗时。40个并发、读写4:1、每个查询平均100逻辑读。压测期间系统CPU峰值:数据库CPU:55% Router CPU:4% LVS CPU:0% net send:50MB/s。
根据压测数据,中间节点(Router、LVS)和跨机房连接均会延长响应时间,导致不同程度的TPS下降,单个query的平均响应时间受影响程度很小,如下:
由于响应时间增加极少,而TPS在高并发下最终会达到数据库的上限,因此这些影响在生产环境完全可以接受。
4. 总结
这套架构全面实现了双机房高可用、自动Failover、强一致性、读写分离、读库高可用、读请求负载均衡,且任何单点异常都可以在3-15秒内完全自动恢复,即使主机房整体异常也只需手动指定Mysql主库即可恢复读写,整体架构可以高效可靠的保障超大并发下的业务稳定,目前此架构已经在洋码头的数据库体系中完美运作。
[1]http://mysqlhighavailability.com/the-king-is-dead-long-live-the-king-our-homegrown-paxos-based-consensus/ [2]http://mysqlhighavailability.com/mysql-group-replication-transaction-life-cycle-explained/
|