写点什么

MySQL MGR + 只读节点高可用

作者:lixiaofeng
  • 2021 年 12 月 13 日
  • 本文字数:2673 字

    阅读完需:约 9 分钟

MySQL MGR + 只读节点高可用

 MySQL 作为使用范围最广泛的数据库之一,官网一直没有出故障转移和高可用。无论是 MHA , 半同步,还是增强半同步,Xenon 等, 都是围绕 binlog 这个逻辑复制展开的, 要么是官网对逻辑复制的增强,要么是第三方做的故障转移和高可用。终于在 MySQL5.7 官网出了 MGR 故障转移方案, 但是要注意,这里只有故障转移,并没有高可用。MGR 的原理图如下:


 当事务得到组内多个节点认证时,主节点就可以提交事务。当主节点宕机时,集群内部会选出一个新的主节点, 实现了故障转移。MGR 集群保证了数据库的数据安全和数据的一致性, 对于应用来说,应用并不知道 primary 节点已经转移了,于是应用的连接会大量的报错。而现在的 MySQL 驱动也没有想 MongoDB 那样,允许连接多个集群内节点的 IP,驱动自己判断并连接到 primary 上。对于使用 MGR 的同学来说, 怎么让应用能适中保持主节点的连接 ,就是我们要解决的问题了。

上篇文章实现了 MGR+自研脚本的高可用。详细参见:

https://xie.infoq.cn/article/d5082aa1c4d111bf9902ac0de


本篇分享只读节点的高可用。对写节点的高可用,我么主要检测主节点的变化,当主节点变化时,我们重新绑定 vip(或者 dns)即可。对于只读节点的高可用,情况会复杂一些。可以分成两种情况。我们举例来说。

假如有一个三节点的 MGR 集群 A,B,C;

其中 A 是主节点绑定 vip(10.10.10.10 或者域名);

C 是绑定只读的 vip(10.10.10.11 或者域名);

第一种情况,C 节点挂了, 此时我们需要把 只读的 vip(10.10.10.11 或者域名), 重新绑定的 B 上;

第二种情况,C 正常, 检测的时候,发现 C 已经成为了 primary (这里我们不用关心 C 是怎么成为主节点的), 此时我们需要把(10.10.10.11 或者域名)重新绑定的一台 Secondary 节点上。

咱们一个一个来。

第一种情况,C 节点挂了, 此时我们需要把 只读的 vip(10.10.10.11 或者域名), 重新绑定的 B 上;

核心代码如下:

首先我们只查询只读的从节点,z.GetRow(SQLText) 方法是循环查询三个节点(防止某个节点宕机),直到正常返回(没有查询到数据的返回也是正常返回)。

若是正常返回,且没有查询到数据,说明该节点已经因故障,退出了集群,我们就需要 vip(或者 dns)的切换了。


SQLText = "select MEMBER_HOST,MEMBER_STATE,MEMBER_ROLE from performance_schema.replication_group_members where MEMBER_HOST='" + z.Vip.ReadHost + "';" rows = z.GetRow(SQLText) ... ...   // irows ==0 说明,没有查询到数据,也就是说负责只读的mysqld 已经挂了    // 将只读vip 绑定到另外一个从节点上    if irows == 0 {        SQLText = "select MEMBER_HOST,MEMBER_STATE,MEMBER_ROLE from performance_schema.replication_group_members where MEMBER_ROLE='SECONDARY';"        rows = z.GetRow(SQLText)        for rows.Next() {            err := rows.Scan(&member.MEMBER_HOST, &member.MEMBER_STATE, &member.MEMBER_ROLE)            if err != nil {                z.Log.Error(" rows scan error : [%v]", err.Error())            }            newReadIP := mysqlnode[member.MEMBER_HOST]            z.Log.Warning("read only mysqld[%v]  stop. prepare bind vip[%v] to secondary [%v]", z.CurrentReadIP, z.Vip.ReadVip, newReadIP)            z.unbind(z.CurrentReadIP, z.Vip.ReadVip)            z.bind(newReadIP, z.Vip.ReadVip)            z.CurrentReadIP = newReadIP            z.Vip.ReadHost = member.MEMBER_HOST        }    }
复制代码


第二种情况,C 正常, 检测的时候,发现 C 已经成为了 primary (这里我们不用关心 C 是怎么成为主节点的), 此时我们需要把(10.10.10.11 或者域名)重新绑定的一台 Secondary 节点上。

这种情况下,查询一定正常返回了 C 节点的数据。

我们进一步判断,MEMBER_ROLE 的值,若是 SECONDARY, 则是正常状态,写下日志返回。

若 MEMBER_ROLE 的是 PRIMARY, 则说明我们的只读 vip 指定的节点已经成为主节点, 就需要把只读的 vip 重新一个 SECONDAR 节点上。

SQLText = "select MEMBER_HOST,MEMBER_STATE,MEMBER_ROLE from performance_schema.replication_group_members where MEMBER_HOST='" + z.Vip.ReadHost + "';"rows = z.GetRow(SQLText)......    for rows.Next() {        irows = 1        err := rows.Scan(&member.MEMBER_HOST, &member.MEMBER_STATE, &member.MEMBER_ROLE)        if err != nil {            z.Log.Error(" rows scan error : [%v]", err.Error())        }        // 若只读节点还活着,,则判断它的role是不是primary;        if member.MEMBER_ROLE == "SECONDARY" {            z.Log.Info("secondary node [%v] is ok ", z.CurrentReadIP)            rows.Close()            return        }        if member.MEMBER_ROLE == "PRIMARY" {            // 若我们的只读的节点已经变成primary,,则需要把只读vip 绑定到一个从节点上            z.Log.Warning("secondary node [%v] is  switch to primary", z.CurrentReadIP)            z.Log.Warning("prepare unbind [%v] from [%v]", z.Vip.ReadVip, z.CurrentReadIP)            SQLText = "select MEMBER_HOST,MEMBER_STATE,MEMBER_ROLE from performance_schema.replication_group_members where MEMBER_ROLE='SECONDARY';"            rows = z.GetRow(SQLText)            for rows.Next() {                err := rows.Scan(&member.MEMBER_HOST, &member.MEMBER_STATE, &member.MEMBER_ROLE)                if err != nil {                    z.Log.Error(" rows scan error : [%v]", err.Error())                }                newReadIP := mysqlnode[member.MEMBER_HOST]                z.Log.Warning("read only mysqld[%v]  switch to primary. prepare bind vip[%v] to secondary [%v]", z.CurrentReadIP, z.Vip.ReadVip, newReadIP)                z.unbind(z.CurrentReadIP, z.Vip.ReadVip)                z.bind(newReadIP, z.Vip.ReadVip)                z.CurrentReadIP = newReadIP                z.Vip.ReadHost = member.MEMBER_HOST            }        }    }
复制代码


到现在,我们已经使用脚本检测 MGR 状态的变化, 分别绑定读写 vip(或者 dns)和只读 vip(或者 dns), 实现了基于 MGR 的高可用和读写分离。 这里其实可以优化一下, 启动两个 go routinue, 一个负责写 vip 的高可用,一个负责只读 vip 的高可用。

发布于: 3 小时前阅读数: 6
用户头像

lixiaofeng

关注

还未添加个人签名 2018.04.25 加入

还未添加个人简介

评论

发布
暂无评论
MySQL MGR + 只读节点高可用