系统架构与设计(4)- 如何设计高并发系统的数据库架构 (三)
在部署 MySQL 数据库集群之前,我们先来搞清楚几个重要的概念和功能。
1. MySQL 主从复制
MySQL 主从复制是指数据可以从一个 MySQL 数据库服务器(或数据库实例)主节点复制到一个或多个从节点。MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。
1) 主要用途
(1) 读写分离:在开发工作中,有时候会遇见某个 SQL 语句需要锁表,导致暂时不能使用读的服务,这样就会影响现有业务,使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了锁表的情景,通过读从库也可以保证业务的正常运作;
(2) 数据实时备份:当系统中某个节点出现故障的时候,方便切换;
(3) 高可用 (HA, High Availability);
(4) 架构扩展:随着系统中业务访问量的增大,如果是单机部署数据库,就会导致 I/O 访问频率过高。有了主从复制,增加多个数据存储节点,将负载分布在多个从节点上,降低单机磁盘 I/O 访问的频率,提高单个机器的 I/O 性能;
2) 主从形式
(1) 一主N从:一主一从和一主多从是最常见的主从架构,实施起来简单并且有效,不仅可以实现 HA,而且还能读写分离,进而提升集群的并发能力;
(2) 多主一从:从 MySQL 5.7 开始支持,多主一从可以将多个 MySQL 数据库备份到一台存储性能比较好的服务器上;
(3) 双主复制:也就是互做主从复制,每个 master 既是 master,又是另外一台服务器的 slave。这样任何一方所做的变更,都会通过复制应用到另外一方的数据库中。
(4) 级联复制:级联复制模式下,部分 slave 的数据同步不连接主节点,而是连接从节点。因为如果主节点有太多的从节点,就会损耗一部分性能用于 replication,那么我们可以让 3 ~ 5 个从节点连接主节点,其它从节点作为二级或者三级与从节点连接,这样不仅可以缓解主节点的压力,并且对数据一致性没有负面影响。
3) 主从复制模式
(1) 异步模式(MySQL Async-mode)
默认是异步的模式。MySQL 增删改操作会全部记录在 binary log 中,当 slave 节点连接master 时,会主动从 master 处获取最新的 bin log 文件。并把 bin log 中的sql relay。
这种模式下,主节点不会主动 push bin log 到从节点,这样有可能导致failover的情况下,也许从节点没有即时地将最新的 bin log 同步到本地。
(2) 半同步模式 (MySQL Semi-sync)
这种模式下主节点只需要接收到其中一台从节点的返回信息,就会 commit;否则需要等待直到超时时间然后切换成异步模式再提交;这样做的目的可以使主从数据库的数据延迟缩小,可以提高数据安全性,确保了事务提交后,binlog 至少传输到了一个从节点上,不能保证从节点将此事务更新到db 中。性能上会有一定的降低,响应时间会变长。
(3) 全同步模式
全同步模式是指主节点和从节点全部执行了commit 并确认才会向客户端返回成功。
4) 基于 binlog 记录复制
基于 binglog 记录复制有三种复制方式:基于SQL语句的复制(statement-based replication,SBR),基于行的复制(row-based replication,RBR),混合模式复制(mixed-based replication,MBR)。对应的 binlog 文件的格式也有三种:STATEMENT, ROW, MIXED。
(1) 基于 SQL 语句的复制(statement-based replication,SBR)
这种方式就是记录 SQL 语句在 bin log 中,Mysql 5.1.4 及之前的版本都是使用的这种复制格式。
优点是只需要记录会修改数据的 SQL 语句到 binlog 中,减少了 binlog 日志量,节约 I/O,提高性能。
缺点是在某些情况下,会导致主从节点中数据不一致(比如 sleep(), now() 等)。
(2) 基于行的复制(row-based replication,RBR)
这种方式是 MySQL master 将 SQL 语句分解为基于 Row 更改的语句并记录在 bin log 中,也就是只记录哪条数据被修改了,修改成什么样。
优点是不会出现某些特定情况下的存储过程、或者函数、或者 trigger 的调用或者触发无法被正确复制的问题。
缺点是会产生大量的日志,尤其是修改 table 的时候会让日志暴增,同时增加 bin log 同步时间。也不能通过 bin log 解析获取执行过的 SQL 语句,只能看到发生的 data 变更。
(3) 混合模式复制(mixed-based replication,MBR)
MySQL NDB cluster 7.3 和 7.4 使用的 MBR 是以上两种模式的混合,对于一般的复制使用 STATEMENT 模式保存到 binlog,对于 STATEMENT 模式无法复制的操作则使用 ROW 模式来保存,MySQL 会根据执行的 SQL 语句选择日志保存方式。
5) 基于 GTID 复制
在 MySQL 5.6 里面,不用再找 binlog 和 pos 点,我们只需要知道主节点的 IP,端口,以及账号密码就行,因为复制是自动的,MySQL 会通过内部机制 GTID 自动找点同步。
基于 GTID 复制实现的工作原理:
(1) 主节点更新数据时,会在事务前产生 GTID,一起记录到 binlog 日志中;
(2) 从节点的 I/O 线程将变更的 bin log,写入到本地的 relay log 中;
(3) SQL 线程从 relay log 中获取 GTID,然后对比本地 binlog 是否有记录(所以 MySQL 从节点必须要开启 binary log);
(4) 如果有记录,说明该 GTID 的事务已经执行,从节点会忽略;
(5) 如果没有记录,从节点就会从 relay log 中执行该 GTID 的事务,并记录到 bin log;
(6) 在解析过程中会判断是否有主键,如果没有就用二级索引,如果有就用全部扫描;
2. MySQL 读写分离
读写分离适用的场景:读操作的频率远高于写操作的频率,写操作的耗时长于读操作的耗时,即长耗时低频率的写操作降低了整个数据库的高并发读写能力,同时读操作对数据实时性要求不高,允许一定时间的延时。
MySQL 读写分离基本原理是让 master 数据库处理写操作,slave 数据库处理读操作,master 将写操作的变更同步到各个 slave 节点。
MySQL 读写分离能提高系统性能的原因:
(1) 物理服务器增加,机器处理能力提升,拿硬件换性能;
(2) 主从只负责各自的读和写,极大程度缓解 X 锁和 S 锁争用;
(3) slave 可以配置 MyIASM 引擎,提升查询性能以及节约系统开销;
(4) master 直接写是并发的,slave 通过主库发送来的 binlog 恢复数据是异步;
(5) slave 可以单独设置一些参数来提升其读的性能;
(6) 增加冗余,提高可用性;
实现读写分离的方案:
1) 基于 MySQL Proxy 代理的方式
MySQL 的代理最常见的是 mysql-proxy、cobar、mycat、Atlas 等。这种方式对于应用来说,MySQL Proxy 是完全透明的,应用则只需要连接到 MySQL Proxy 的监听端口即可。
当然,这样 Proxy 机器可能会单点失效,但可以使用多个 Proxy 机器做为冗余,在应用服务器的连接池配置中配置到多个 Proxy 的连接参数即可。
(1) mysql-proxy 是一个轻量的中间代理,是官方提供的 MySQL 中间件产品可以实现负载平衡,读写分离,failover 等,依靠内部一个 lua 脚本实现读写语句的判断。项目地址:https://github.com/mysql/mysql-proxy ,该项目已经六七年没有维护了,官方也不建议应用于生成环境。
(2) cobar 是阿里提供的一个中间件,已经停止更新。项目地址:https://github.com/alibaba/cobar。
(3) mycat 的前身就是 cobar,活跃度比较高,完全使用 java 语言开发。 项目地址:https://github.com/MyCATApache/Mycat-Server。
(4) moeba(变形虫)是阿里工程师陈思儒基于 java 开发的一款数据库读写分离的项目(读写分离只是它的一个小功能),与 MySQL 官方的 MySQL Proxy 相比,作者强调的是 amoeba 配置的方便(基于XML的配置文件,用 SQLJEP 语法书写规则,比基于 lua 脚本的MySQL Proxy 简单)。更多详细介绍请参考:https://www.biaodianfu.com/amoeba.html , 下载地址:https://sourceforge.net/projects/amoeba/ 。
(5) Atlas 是奇虎 360 的一个开源中间代理,是在 MySQL 官方 mysql-proxy 0.8.2 的基础上进行了优化,增加一些新的功能特性。 项目地址: https://github.com/Qihoo360/Atlas 。
2) 基于应用内路由的方式
基于 Spring 的 AOP 实现: 用AOP 来拦截 Spring 项目的 dao 层方法,根据方法名称就可以判断要执行的 SQL 类型(即是 read 还是 write 类型),进而动态切换主从数据源。
参考项目:https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter
3) 基于 mysql-connector-java 的 jdbc 驱动方式
使用 MySQL 驱动 Connector/J 的可以实现读写分离。即在 jdbc 的 url 中配置为如下的形示:
jdbc:mysql:replication://master,slave1,slave2,slave3/test
Java 程序通过在连接 MySQL 的 jdbc 中配置主库与从库等地址,jdbc 会自动将读请求发送给从库,将写请求发送给主库,此外,mysql 的 jdbc 驱动还能够实现多个从库的负载均衡。
关于 jdbc 文档地址:https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-jdbc-url-format.html
关于读写分离文档地址:https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-source-replica-replication-connection.html
4) 基于 sharding-jdbc 的方式
sharding-sphere 是强大的读写分离、分表分库中间件,sharding-jdbc 是 sharding-sphere 的核心模块,sharding-jdbc 可以与 Springboot 集成。官方网址:https://shardingsphere.apache.org/。
以上四种方案各有优缺点,基于 MySQL Proxy 代理的方式对于应用来说相对简单,但是在项目稳定性、事务支持性等方面还存在问题;而基于应用内路由的方式固然灵活度比较高,但是也增加了应用逻辑的复杂度;基于 mysql-connector-java 的 jdbc 驱动和 sharding-jdbc 的方式在使用上相对简单,但限制了需要使用 java 开发。
3. 故障转移 (Failover)
故障转移 (Failover),就是当活动的服务或应用意外终止时,快速启用冗余或备用的服务器、系统、硬件或者网络接替它们工作。简单来说就是当系统某块服务不可用了,系统其它服务模块能够自动的继续提供服务。
对于要求高可用和高稳定性的服务器、系统或者网络,系统设计者通常会设计故障转移功能。比如,作为负载均衡时的 nginx、haproxy 可以支持后端检测和 backup,分布式的数据密集型应用也会包含 Failover。 系统支持 Failover,那必须要保证能有 backup 或者 replics 来作为新 "master" 继续提供服务。
MySQL 自身没有实现 Failover,所以当 master 异常的时候,需要使用中间件来实现 Failover 并处理数据库切换。下面是几个常用的包含 Failover 功能的中间件:
1) mysqlfailover
mysqlfailover 是 mysql utilities 工具包中包含的一个重要的高可用命令,用于对主从复制架构进行健康检测以及实现故障自动转移。它会定期按指定的时间间隔探测各节点的健康状态,一旦在捕获到主节点不可用时,将触发故障转移相关动作,自动执行故障切换到当前最佳的从服务器上。同时整个主从架构内的其他从节点将指向新的主节点,自动完成主从拓扑结构更新。
mysqlfailover 特点:
(1) 持续监控主从主从拓扑结构健康状况,当主节点不可用时,触发自动故障转移;
(2) 支持 GTID 全局事务标识符,传统主从模式不支持;
(3) 支持设置故障转移首选及备选节点,支持投票选举方式选择新的主节点以及仅监测模式(不切换主从);
(4) 支持自定义时间监测间隔;
(5) 支持交互模式以及守护进程的模式开启 mysqlfailover;
(6) 支持在切换前或切换后执行指定的脚本;
(7) 支持操作记录到日志不同的粒度以及日志老化;
mysqlfailover 限制条件:
(1) 主从需要开启 GTID 模式 ( MySQL 使用 5.6.5 以上版本);
(2) 所有的 slave 端需要配置以下参数,建议主库也添加 (切换后主从模式变化):
report-host
report-port
master-info-repository=TABLE
relay-log-info-repository=TABLE
(3) 权限 (mysqlfailover 工具检测及切换期间需要,主从都需要):
SHOW SLAVE STATUS
SHOW MASTER STATUS
STOP SLAVE, START SLAVE, WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS, CHANGE MASTER TO
REPLICATE SLAVE
SUPER, GRANT OPTION, RELOAD, DROP, CREATE, INSERT, SELECT 权限
2) MMM(Master Replication Manager for MySQL)
MMM(Master Replication Manager for MySQL)是在 MySQL Replication 的基础上,对其进行优化。
官网:https://mysql-mmm.org/
MMM 是双主多从结构,它是 Google 的开源项目,使用 Perl 语言来对 MySQL Replication 做扩展,提供一套支持双主故障切换和双主日常管理的脚本程序,主要用来监控 MySQL 主主复制并做失败转移。
注:这里的双主节点,虽然叫做双主复制,但是业务上同一时刻只允许对一个主进行写入,另一台备选主上提供部分读服务,以加速在主主切换时刻备选主的预热。
优点:
(1) 自动的主主 Failover 切换,一般 3s 以内切换备机。
(2) 多个从节点读的负载均衡。
3) MHA(Master High Availability)
MHA(Master High Availability)也是在 MySQL Replication 的基础上,对其进行优化。
MHA 是多主多从结构,它是日本 DeNA 公司的 youshimaton 开发,主要提供更多的主节点,但是缺少VIP(虚拟IP),需要配合 keepalived 等一起使用。
要搭建 MHA,要求一个复制集群中必须最少有三台数据库服务器,一主二从,即一台充当 master,一台充当备用 master,另外一台充当从库。
优点:
(1) 可以进行故障的自动检测和故障转移(Failover);
(2) 具备自动数据补偿能力,在主库异常崩溃时能够最大程度的保证数据的一致性。
4. 负载均衡
负载均衡的基本思路很简单:在一个服务器集群中尽可能地的平均负载量。基于这个思路,我们通常的做法是在服务器前端设置一个负载均衡器。负载均衡器的作用是将请求的连接路由到最空闲的可用服务器上。
在一主多从的 MySQL 集群中:一主 (Master) 没有备份的 Master,所以无法做到 Failover,即无法实现高可用。多从,使用 haproxy 实现负载均衡。
在双主多从的 MySQL 集群中:双主使用 Keepalived 做高可用(当然也可以加上 haproxy 做负载均衡),多从使用 haproxy 做负载均衡。
5. 数据库拆分
当我们使用读写分离、缓存后,数据库的压力还是很大的时候,这就需要使用到数据库拆分了。
数据库拆分简单来说,就是指通过某种特定的条件,按照某个维度,将我们存放在同一个数据库中的数据分散存放到多个数据库(主机)上面以达到分散单库(主机)负载的效果。
切分模式: 垂直(纵向)拆分、水平拆分。
1) 垂直拆分 (专库专用)
一个数据库由很多表的构成,每个表对应着不同的业务,垂直切分是指按照业务将表进行分类,分布到不同的数据库上面,这样也就将数据或者说压力分担到不同的库上面。
优点:
(1) 拆分后业务清晰,拆分规则明确;
(2) 系统之间整合或扩展容易;
(3) 数据维护简单;
缺点:
(1) 部分业务表无法 join,只能通过接口方式解决,提高了系统复杂度;
(2) 受每种业务不同的限制存在单库性能瓶颈,不易数据扩展跟性能提高;
(3) 事务处理复杂;
2) 水平拆分
垂直拆分后遇到单机瓶颈,可以使用水平拆分。相对于垂直拆分的区别是:垂直拆分是把不同的表拆到不同的数据库中,而水平拆分是把同一个表拆到不同的数据库中。
相对于垂直拆分,水平拆分不是将表的数据做分类,而是按照某个字段的某种规则来分散到多个库之中,每个表中包含一部分数据。简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中 的某些行切分到一个数据库,而另外的某些行又切分到其他的数据库中,主要有分表,分库两种模式。
优点:
(1) 不存在单库大数据,高并发的性能瓶颈;
(2) 对应用透明,应用端改造较少;
(3) 按照合理拆分规则拆分,join 操作基本避免跨库;
(4) 提高了系统的稳定性跟负载能力;
缺点:
(1) 拆分规则难以抽象;
(2) 分片事务一致性难以解决;
(3) 数据多次扩展难度跟维护量极大;
(4) 跨库 join 性能较差;
3) 中间件
sharding-jdbc 是轻量级的 java 框架,增强版的 JDBC 驱动。它不是做分库分表,是操作多个库多个表。sharding-jdbc 的数据分片和读写分离功能,可以简化对分库分表之后的数据相关操作。
Mycat 实现水平拆分,对前端应用是完全透明的,不用调整前台逻辑;Mycat 不是配置以后,就能完全解决分表分库和读写分离问题。Mycat 支持两个表联表的查询,多于两个表的查询不支持。