背景

集群中如果需要主备,可以基于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 mainimport (    "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[0],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(map[string]interface{}{"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 {}}

郭少