- GreatSQL社区原创内容未经受权不得随便应用,转载请分割小编并注明起源。
原创:万里数据库,花家舍
导读
在多写(多节点写入)数据库(例如MySQL MGR的multi-primary mode)与利用之间,往往会加一层代理组件,通过算法调节不同节点负载,散发高并发读写申请。
要求代理工具须要具备申请转发、负载平衡、故障转移的性能。
在后端节点故障产生或者连贯因为客户端异样、网络问题断开时,须要及时将故障节点及时踢出负载平衡队列或者敞开异样连贯,做到故障转移。
这就是接下来介绍的次要内容,应用golang简略编写一个这样的工具,来深刻学习一下负载平衡代理的实现。
1、性能一览
负载平衡
将利用端的连贯申请(负载)依照既定的平衡算法转发到不同的后端节点,服务程序建设利用(客户端)与数据库节点之间的通信并放弃至客户端断开连接。
故障转移
在后端节点呈现故障时,能及时的检测到故障,并将故障节点踢出负载平衡队列,不再将利用申请路由到故障节点,做到利用无感知。在故障复原后,可能检测到节点状态复原,将其再次退出到负载平衡队列。
2、实现细节
外围性能
申请转发
代理须要做到将申请散发到不同的后端节点下来,并放弃利用与对应节点的通信,直至其中一端退出(故障或者被动)。
负载平衡
对利用的负载,平衡的散发的不同的节点,须要对应的算法反对。目前通用的负载平衡算法有随机、轮询、加权轮询,代码实现了这三种算法。
此外还有动静判断后端节点负载状况,依据负载状况动静调整负载散发,这须要额定的负载监控工作,这里没有实现。
故障检测
负载平衡代理须要防止向生效的节点散发申请。故障类型无疑是很多的,如果八面玲珑的对每个故障类型都关照到,无疑减少了实现难度。
例如在分布式中,不牢靠的网络减少了检测故障难度,对于数据库实例,在分布式中很难判断节点到底是crash了还是网络中断导致的。
并且节点因为负载较高无奈及时响应申请,这时也是很难判断节点状态,此时进行重试可能会加剧节点的负载。
在这里并不是要含糊这种判断,而是理论状况切实是太简单了,我并不是相干领域专家,所以在实现故障检测时,只思考了几种确定性较高或者容易判断的状况。
过程实现
其中, 转发 实现过程是在接管到申请后,定义一个后端节点的地址,并建设一个和这个地址的连贯。
在开启两个协程,一个负责将利用(客户端)发送的数据包传递给后端的连贯,另一个是将后端的返回的数据传递给利用,这样就在利用与后端节点之间搭建起了通信,使之像间接通信一样替换数据,外围的步骤能够参考上面代码的实现。
sConn, err := l.Accept()dTcpAddr, _ := net.ResolveTCPAddr("tcp4", addr)dConn, err := net.DialTCP("tcp", nil, dTcpAddr)go io.Copy(sConn, dConn)go io.Copy(dConn, sConn)
在呈现连贯实现既定通信后断开连接,或者连贯因为故障退出,须要代理将客户端的申请连贯与转发向后端的连贯一起敞开。
这里应用的形式是获取连贯传输数据时的状态来判断,即io.Copy(sConn, dConn)在呈现谬误时,连贯就能够敞开了。这里借助channel的阻塞个性来向主线程告诉退出。所以对上述的。
go io.Copy(sConn, dConn)go io.Copy(dConn, sConn)
代码进行批改后如下:
// channel长度为1,任意时刻只写入一个bool值,在其中的值未被读取之前,处于阻塞状态 exitCH := make(chan bool, 1) // 把客户端的的申请转发给后端 go func(s net.Conn, d *net.TCPConn, ex chan bool) { _, err := io.Copy(sConn, dConn) if err != nil { Error.Println("Send data failure: ", err) } exitCH <- true }(sConn, dConn, exitCH) // 把响应的数据返回给客户端 go func(s net.Conn, d *net.TCPConn, ex chan bool) { _, err := io.Copy(dConn, sConn) if err != nil { Error.Println("Receive data failure: ", err) } exitCH <- true }(sConn, dConn, exitCH) // channel阻塞,读取连贯敞开状态 <-exitCH // channel收到信息(连贯终止)后,敞开连贯 _ = dConn.Close()
负载平衡 算法的实现则是在每次向后端建设连贯的时候,这个后端地址是依据算法的不同,返回一个负载平衡算法举荐的后端节点的地址,而后应用这个地址建设一个连贯,并与利用搭建起通信(正如上一步骤介绍的那样)。
其中随机算法较为简单,外围是随机数的获取,应用这个随机数作为下标在负载平衡队列中拿到具体的节点:
type Random struct { CurIndex int Nodes []*node.Node}func (r *Random) Next() *node.Node { if len(r.Nodes) == 0 { return nil } r.CurIndex = rand.Intn(len(r.Nodes)) return r.Nodes[r.CurIndex]}
轮询算法则是每次获取后端节点信息是采取的一一查问的形式获取须要散发申请的节点:
type RoundRobin struct { CurIndex int Nodes []*node.Node}func (r *RoundRobin) Next() *node.Node { if len(r.Nodes) == 0 { return nil } l := len(r.Nodes) if r.CurIndex >= l { r.CurIndex = 0 } currAddr := r.Nodes[r.CurIndex] r.CurIndex = (r.CurIndex + 1) % l return currAddr}
加权轮询算法实现上绝对简单一些,为每个后端节点减少权重属性,蕴含三个权重属性:权重(Weight)、长期权重(CurWeight)、无效权重(EffectWeight)。
其中CurWeight、EffectWeight初始值为0,Weight值则读取配置文件设定来初始化。CurWeight每轮都会变动,EffectWeight默认与Weight雷同。
实现逻辑
1、currentWeight = currentWeight + effecitveWeight
2、选中最大的 currentWeight 节点为选中节点
3、currentWeight = currentWeight - totalWeight
type WeightRoundRobin struct { Nodes []*node.Node}func (r *WeightRoundRobin) Next() *node.Node { var n *node.Node total := 0 for i := 0; i < len(r.Nodes); i++ { w := r.Nodes[i] total += w.EffectWeight w.CurWeight += w.EffectWeight if w.EffectWeight < w.Weight { w.EffectWeight++ } if n == nil || w.CurWeight > n.CurWeight { n = w } } if n == nil { return nil } n.CurWeight -= total return n}
故障检测 是保障负载平衡队列中的节点是能够失常拜访并且提供牢靠服务的前提,在检测到后端节点存在故障后,须要及时的从队列中剔除,并敞开与之对应的连贯。
检测在理论实现上应用了两种根本办法。一种是根本的连通性检测,一种是利用MGR或者GreatDB提供的外部视图来判断节点是否可写。
这种在MGR中从以后节点查问本节点状态可能并不精确例如:产生网络分区,从以后节点查看状态为ONLINE,但从其余节点查看,则以后可能为ERROR状态,代码并未思考这个状况。
后续可减少对一个节点可写状态判断须要与其余节点的状态查问综合思考。
连通性检测:
_, err := net.DialTimeout("tcp", addr, time.Duration(dialtimeout)*time.Millisecond)
这里是借助命令行工具实现可写检测,没有应用开源的连贯驱动,次要是思考代码的简洁。
可写检测:
var CMD = "mysql"func State(detectSql string, user string, pass string, port string, host string, cluster string) (bool, error) { ok, _ := CommandOk(CMD) if ok { sqlComLine := CMD + " -u" + user + " -p" + pass + " -h" + host + " -P" + port + " -NBe '" if cluster == "greatdb" { sqlComLine += detectSql + " WHERE HOST=" + "\"" + host + "\"" + "'" } else if cluster == "mgr" { sqlComLine += detectSql + " WHERE MEMBER_HOST=" + "\"" + host + "\"" + "'" } cmd := exec.Command("bash", "-c", sqlComLine) out, err := cmd.CombinedOutput() rest := strings.Replace(string(out), "\n", "", -1) if err == nil { if rest == "ONLINE" { return true, nil } else { return false, errors.New("instance is exists but cannot write") } } return false, err } else { return false, errors.New("cannot detect instance state") }}func CommandOk(c string) (bool, error) { command := "which " + c cmd := exec.Command("bash", "-c", command) out, err := cmd.CombinedOutput() if err == nil { context := strings.Fields(strings.Replace(string(out), "\n", "", -1)) if len(context) > 2 { if context[1] == "no" { return false, nil } } return true, nil } return false, err}
在检测到后端节点连通性有问题或者节点状态为不可写,须要将节点踢出负载平衡队列,这里通过加锁来避免并发操作队列引入新的代码谬误。
而后通过channel告诉主线程负载平衡队列产生了变动,须要更新。其次是告诉主线程须要将各个协程在解决的与故障节点无关的连贯,须要敞开。
func DelNode(n *node.Node) { for i := 0; i < len(nodeList); i++ { if nodeList[i].Ip == n.Ip && nodeList[i].Port == n.Port { mu.Lock() nodeList = append(nodeList[:i], nodeList[i+1:]...) listChange <- 1 connClose <- n.Ip mu.Unlock() Error.Println("The destination address is removed from the load balance list :", net.JoinHostPort(n.Ip, strconv.Itoa(n.Port))) } }}
在心跳检测到后端节点可写状态复原,则须要将其再次退出到负载平衡队列,新的连贯会依据负载平衡算法的均衡,路由到复原的节点上,也就是会再次散发申请到失常节点。
// 在队列中不存在,则增加 if exists == false { mu.Lock() defer mu.Unlock() nodeList = append(nodeList, n) ch <- 1 Info.Println("The destination address is added to the load balance list :", addr) }
3、应用问题
程序启动
目前只在CentOS 7.6上进行了简略测试,测试了后端节点被kill、机器reboot、连贯异样断开等故障状况
cd easy-proxy<br>go build main/easyproxy<br>
批改配置,减少后端节点、端口、权重等
如果须要疾速故障转移,能够配置ticktime和dialtimeout参数,单位是毫秒。
nohup ./easyproxy --cnf=conf/easy.conf &
可能问题
在应用过程可能会遇到
accept tcp [::]:3310: accept4: too many open files
或者
dial tcp 127.0.0.0:3310 socket: too many files
这是系统文件描述符的数量不够用了,解决办法是能够减少文件描述符的数量
ulimit -n 1024000
批改文件描述符后,重新启动过程,查看过程最大关上文件数:Max open files
cat /proc/18659/limits
......Max open files 1024000 1024000 files ......
一点想法
后续可思考对程序减少守护过程,保障程序肯定水平的可用性,代理工具无状态,也能够进行扩大来实现HA。
这里只是简略的实现了一下申请代理和负载平衡,通过编码加深对负载平衡的了解不失为一个无效办法,测试并不充沛。
代码约600行左右,没有通过DB Driver连贯数据库,而是借助命令行来操作,后续会持续欠缺。心愿能带来一些对负载平衡的思考。
源码地址:https://gitee.com/huajiashe_byte/easy-proxy
Enjoy GreatSQL :)
文章举荐:
面向金融级利用的GreatSQL正式开源
https://mp.weixin.qq.com/s/cI...
Changes in GreatSQL 8.0.25 (2021-8-18)
https://mp.weixin.qq.com/s/qc...
MGR及GreatSQL资源汇总
https://mp.weixin.qq.com/s/qX...
GreatSQL MGR FAQ
https://mp.weixin.qq.com/s/J6...
在Linux下源码编译装置GreatSQL/MySQL
https://mp.weixin.qq.com/s/WZ...
# 对于 GreatSQL
GreatSQL是由万里数据库保护的MySQL分支,专一于晋升MGR可靠性及性能,反对InnoDB并行查问个性,是实用于金融级利用的MySQL分支版本。
Gitee:
https://gitee.com/GreatSQL/Gr...
GitHub:
https://github.com/GreatSQL/G...
Bilibili:
https://space.bilibili.com/13...
微信&QQ群:
可搜寻增加GreatSQL社区助手微信好友,发送验证信息“加群”退出GreatSQL/MGR交换微信群
QQ群:533341697
微信小助手:wanlidbc
本文由博客一文多发平台 OpenWrite 公布!