背景 集群中如果需要主備,可以基於Redis、zk的分散式鎖等實現,本文將介紹如何利用Mysql分散式鎖進行實現。 原理 資料庫中包含數據欄位(此處為Master的主機名)、版本號和上一次更新時間。 Master不斷上傳自己的心跳,即刷新資料庫中的"更新時間"。 上一次更新時間超過了一定時間,則認為 ...
背景
集群中如果需要主備,可以基於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[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 {}
}
郭少