火影 发表于 2023-2-26 00:47:54

Golang基于Mysql分布式锁实现集群主备

背景

集群中如果需要主备,可以基于Redis、zk的分布式锁等实现,本文将介绍如何利用Mysql分布式锁进行实现。
原理


[*]数据库中包含数据字段(此处为Master的主机名)、版本号和上一次更新时间。
[*]Master不断上传自己的心跳,即刷新数据库中的"更新时间"。
[*]上一次更新时间超过了一定时间,则认为Master已Down,则可以抢Master。
[*]抢Master和更新心跳时,版本号+1,要判断版本号是否与上一次读取的数据相同。如果相同,则修改成功。如果不相同,则说明Master已经被其他主机抢走。
数据库建表


[*]master存放主机名
CREATE TABLE `host_master` (
`id` int NOT NULL AUTO_INCREMENT,
`master` varchar(64) NOT NULL COMMENT '主机名',
`version` int COMMENT '版本号',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
PRIMARY KEY (`id`)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into host_master(master,version) value('',0);    //插入一条空数据Golang实现集群主备

package main

import (
    "errors"
    "fmt"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "os"
    "time"
)
var (
    DB *gorm.DB
    curHost = "2"
    healthTime float64    = 10    //上传心跳的周期
    healthTimeout float64 = 30    //健康检查过期时间
)
type HostMaster struct {
    ID         int64   `gorm:"column:id"`
    Master   string    `gorm:"column:master"`      //主机名
    Version    int64   `gorm:"column:version"`   //版本号
    UpdateTime *time.Time `gorm:"column:update_time"` //保存数据时间,自动生成
}
//初始化数据库
func InitDB()error{
    var err error
    DB, err = gorm.Open("mysql", "root:123456@(192.168.191.128:3306)/test?charset=utf8&parseTime=True&loc=Local")
    if err != nil {
      return err
    }
    DB.SingularTable(true)
    return nil
}
//获取Master的信息
func GetMasterInfo()(HostMaster,error){
    var hostMasters []HostMaster
    ret := DB.Find(&hostMasters)
    if ret.Error!=nil{
      return HostMaster{},ret.Error
    }
    if ret.RowsAffected==0 || ret.RowsAffected>1{
      return HostMaster{},errors.New(fmt.Sprintf("HostMaster表中的条目为%d",ret.RowsAffected))
    }
    return hostMasters,nil
}
//抢Master与更新心跳
func GrabMaster()error{
    //获取Master的信息
    hostMaster,err := GetMasterInfo()
    if err!=nil{
      return err
    }
    //当前主机为Master则更新心跳.或Master已down则抢Master
    if hostMaster.Master==curHost || time.Now().Sub(*hostMaster.UpdateTime).Seconds()>healthTimeout{
      ret := DB.Model(&HostMaster{}).Where("version = ?",hostMaster.Version).Updates(mapinterface{}{"master":curHost,"version":hostMaster.Version+1})
      if ret.Error!=nil{
            return errors.New("修改失败: "+ret.Error.Error())
      }
      if ret.RowsAffected==0{
            return nil
      }else{
            if hostMaster.Master==curHost{
                fmt.Println(curHost+"更新了心跳")
            }else{
                fmt.Println(curHost+"抢Master成功")
            }
      }
    }
    return nil
}
func main() {
    //初始化数据库
    err := InitDB()
    if err!=nil{
      fmt.Println(err)
      os.Exit(1)
    }
    //周期性更新心跳和抢Master
    go func(){
      for{
            err := GrabMaster()
            if err!=nil{
                fmt.Println(err)
            }
            time.Sleep(10*time.Second)
      }
    }()
    select {}
}



郭少

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Golang基于Mysql分布式锁实现集群主备