背景
集群中如果需要主备,可以基于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 {}}
郭少