MySQL5_1高可用集群
主从同步
MySQL的主从同步是基于bin log实现的。
bin log 同步流程
备库 B 和主库 A 之间维持了一个长连接,主库 A 内部有一个线程专门服务于与 B 的 bin log 同步,一个事务日志同步的过程如下:
- 在备库 B 上通过 change master 命令,设置主库 A 的 IP、端口、用户名、密码,以及要从哪个位置开始请求 binlog,这个位置包含文件名和日志偏移量;
- 在备库 B 上执行 start slave 命令,这时候备库会启动两个线程,就是图中的
io_thread
和sql_thread
。其中 io_thread 负责与主库建立连接。 - 主库 A 校验完用户名、密码后,开始按照备库 B 传过来的位置,从本地读取 bin log,发给 B。
- 备库 B 拿到 bin log 后,写到本地文件,称为中转日志(relay log)。
- sql_thread 读取中转日志,解析出日志里的命令,并执行。
binlog格式
MySQL的bin log主要支持三种格式,分别是statement、row以及mixed。MySQL是在5.1.5版本开始支持row的、在5.1.8版本中开始支持mixed。
- statement:binlog中记录的是SQL语句的原文
容易引起主从不一致,因为binlog是按事务提交顺序记录的,但是两个并发执行的事务中update语句可能是乱序的。 - row:记录原数据
主备延迟
产生主备延迟的可能情况:
- 备库所在的机器性能较主库差;
- 备库的压力较大,比如因为备库不跑业务,所以很多人会随意执行一些特别耗时的操作,这些查询耗费大量的 CPU 资源,影响了同步速度,造成主备延迟。
- 出现了大事务,比如,一次性用 delete 语句删除大量数据,或者大表的 DDL。
- 一个服务器开放N个链接给客户端来连接,但是Slave里读取binlog的线程只有一个,当某个SQL在Slave上执行的时间稍长或由于某个SQL要进行锁表就会导致Master的SQL大量积压,未被同步到Slave,这就导致了主从不一致,也就是主从延迟。
并行复制策略
为了避免备库追不上主库的情况,MySQL 利用并行复制策略提高复制的效率,从上面的主备同步流程图可知,并行化可以加到客户端连接和写入数据两个过程中。
- 按表分发策略
如果两个事务更新不同的表,它们就可以并行。因为数据是存储在表里的,所以按表分发,可以保证两个 worker 不会更新同一行。
当然,如果有跨表的事务,还是要把两张表放在一起考虑的。 - 按行分发策略
按表复制存在热点表的并行复制问题,即热点表会被分配给一个 worker 执行复制,这样就会退化成单线程复制。
按行复制的核心思路是:如果两个事务没有更新相同的行,则它们在备库上可以并行执行,为了知道具体修改了哪些行,这种模式需要设置 binlog 的格式为 row(因为 statement 格式直接记录更新语句,row 记录的是受影响的具体数据的 ID)。
半同步
在 MySQL 5.5 版本之前一直采用的是上述的异步复制方案,主库的事务执行不会管备库的同步进度,如果备库落后,主库不幸 crash,那么就会导致数据丢失。
于是在 MySQL 在 5.5 中就顺其自然地引入了半同步复制,主库在应答客户端提交的事务前需要保证至少一个从库接收并写到 relay log 中。
异步 & 半同步 & 全同步
- 对于异步复制,主库将事务 Binlog 事件写入到 bin log 文件中,此时主库只会通知一下 Dump 线程发送这些新的 Binlog,然后主库就会继续处理提交操作,而此时不会保证这些 Binlog 传到任何一个从库节点上。
- 对于全同步复制,当主库提交事务之后,所有的从库节点必须收到,APPLY 并且提交这些事务,然后主库线程才能继续做后续操作。这里面有一个很明显的缺点就是,主库完成一个事务的时间被拉长,性能降低。
- 对于半同步复制,是介于全同步复制和异步复制之间的一种,主库只需要等待至少一个从库节点收到并且 Flush Binlog 到 Relay Log 文件即可,主库不需要等待所有从库给主库反馈。同时,这里只是一个收到的反馈,而不是已经完全执行并且提交的反馈,这样就节省了很多时间。
主备切换
主备功能主要是通过 bin log 实现的。
主备切换流程
如图示,客户端的读写都是直接访问的节点 A,而节点 B 是 A 的备库,通常是只读的,只是将 A 的更新同步过来到本地执行,节点 A 的 update 同步到节点 B 的流程图如下所示:
- 主库接收到客户端的更新请求后,执行内部事务的更新逻辑,同时写 binlog;
- 备库 B 跟主库 A 之间维持了一个长连接,专门用于服务备库 B 的事务日志同步;
当需要切换时,切换到状态 2,这时候客户端读写访问的都是节点 B,而节点 A 是 B 的备库;
双 M 架构
实际生产中更多采用的是双 M 架构:
与原先的方案相比,只是节点 A 和 B 之间多了一条线,这样,节点 A 和 B 之间总是互为主备关系,在切换的时候就不用再修改主备关系。
可靠性优先策略
双 M 结构的可靠性优先主备切换流程如下:
- 判断备库 B 现在的
seconds_behind_master
,如果小于某个值(比如 5 秒)继续下一步,否则持续重试这一步; - 把主库 A 改成只读状态,即把 readonly 设置为 true;
- 判断备库 B 的 seconds_behind_master 的值,直到这个值变成 0 为止;
- 把备库 B 改成可读写状态,也就是把 readonly 设置为 false;
- 把业务请求切到备库 B。
这个切换流程一般由专门的 HA 系统来完成,称为可靠性优先流程。
注意:
- 这个过程中,比较耗时的是第 3 步,可能会耗费好几秒的时间,因此一般会先在第 1 步中做判断,确保 seconds_behind_master 足够小后才执行。
可用性优先策略
可靠性优先策略中,同步流程中存在一段系统不可用的时间,如果强行把步骤 4、5 调整到最开始执行,也就是说不等主备数据同步,直接把连接切到备库 B,并且让备库 B 可以读写,那么系统几乎就没有不可用时间了,这个流程称为可用性优先策略。
一主多从
平时使用数据库一般都是读多写少,在发展过程中很可能会先遇到读性能问题,为了解决读性能问题,在架构上的解决方式是一主多从。
其中:
- A 和 A’互为主备;
- 从库 B、C、D 指向主库 A,主库负责所有写入和一部分读,其他的读请求由从库分担。
- 主备切换后,A’将成为新的主库;
- 从库 B、C、D 改成连接到 A’。
读写分离
上述的主从结构其实形成了一种读写分离的架构,连接信息一般保存到客户端,由客户端执行负载均衡。
另一种读写分离架构在客户端和服务器之间架设了一个代理层 proxy,客户端全部连接到这个 proxy,由 proxy 根据请求类型和上下文执行请求的路由分发。
- 直连的架构,少了一层 proxy,因此性能稍微更好一点,排查问题也更方便,但是主备切换库迁移时客户端会感知到,所以客户端需要一个后端管理组件,比如 Zookeeper。
- 带 proxy 架构,对客户端友好,但是同时 proxy 架构也更加复杂。
“过期读”问题
当客户端先写入再读取时可能会读到修改前的值,因为写入是对主库写入,读取是对从库读,而主从同步存在延迟,刚写入主库的数据可能还没有同步到所有的从库。
解决过期读问题的方案:
- 强制走主库方案;
一些必须拿到最新结果的请求,可以强制将其发到主库上,比如用户支付后需要马上看到商品是否已经购买成功,这个请求需要马上拿到最新的结果,因此最好走主库;
一些请求没有必要立刻拿到最新的结果,比如商户发布商品后,用户即使没有马上看到商品也是可以的,因此用户读取商品列表的请求完全可以路由到从库上去。 - sleep 方案;
不大靠谱,但是一定程度上还是可以解决问题的。 - 判断主备无延迟方案;
判断show slave status
结果里的seconds_behind_master
参数的值,等于 0 才执行查询请求,这个参数可以表明从库是否已经完全同步。 - 配合 semi-sync 方案;
- 等主库位点方案;
- 等 GTID 方案。
探活
在一主一备的双 M 架构里,主备切换只需要把客户端流量切到备库;而在一主多从架构里,主备切换除了要把客户端流量切到备库外,还需要把从库接到新主库上。
主备切换有两种场景:主动切换和被动切换,其中被动切换往往是因为主库出问题而由 HA 系统发起的。
select 1
select 1
只能用于判断该数据库进程仍能执行,但是不能说明主库没有问题,比如,数据库线程池(由参数 innodb_thread_concurrency 控制)被打满的情况下,虽然select 1
能执行,但是线程池还是会被堵住。
innodb_thread_concurrency 控制的是并发查询,而不是并发连接,因为并发连接多只是多占用一些内存空间,并不会占用 CPU 资源。
查表判断
为了知道线程池是否被打满,我们可以创建一张health_check
表,里面只放一条数据,然后定时执行:
1 | select * from mysql.health_check; |
这种方法的缺点是,不能用于判断磁盘空间是否满了,因为如果磁盘空间满了,所有的更新语句和事务提交语句都会被堵塞,但是查询语句仍能执行。
更新判断
更新一行数据,一般会放一个 timestamp 字段,用来表示最后一次执行检测的时间。
但是要注意如果主备都要检测,就不能只有一行数据了,因为会产生行冲突,导致主备同步的停止。一般会采用数据库实例的 server_id 作为主键,因为 MySQL 规定了主备服务器的 server_id 必须不同,这样就能保证主备的检测命令不会冲突了。
这种方式仍然存在一种问题:这种更新语句占用的 IO 资源很少,即使当时 IO 已经 100%,检测语句仍可以获得 IO 资源来执行,但系统可能已经出问题了,也就是说,这种检测存在随机性。
内部统计
前面几种方法都是通过外部调用来发现问题的,更好的方式是利用 MySQL 本身的统计数据:performance_schema
库的file_summary_by_event_name
表。
QA
主从同步的流程
主备服务器之间维持了一个长连接,备库上回启动两个线程,一个 io_thread 负责与主库建立连接并读取 bin log,另一个 sql_thread 负责解析命令并执行。
MySQL 是怎么保证数据不丢失的
MySQL 是怎么保证高可用的
MySQL如何优化千万级的大表?
- 优化SQL和索引;
- 加缓存,比如Memcached或Redis;
- 主从复制或主主复制,实现读写分离
可以在应用层做,效率高
也可以用三方工具,如360的atlas - 使用MySQL自带的分区表
优点是对应用透明,但是SQL需要针对分区表做一些优化,sql条件中要带上分区条件的列,从而使查询定位到少量的分区上,否则就会扫描全部分区。 - 垂直拆分,根据模块耦合情况将一个大系统分为多个小系统
- 水平切分,选择合适的sharding key将大表数据拆分到多个小表上