基于 MySQL 分布式锁,防止多副本应用初始化数据重复


本文摘自网络,作者,侵删。

现在有一个需求,应用启动时需要初始化一些数据,为了保证高可用,会启动多副本(replicas >= 3),如何保证数据不会重复?

方案一:数据带上主键

最简单的方法,初始化数据都带上主键,这样主键冲突就会报错。但是这么做我们需要对冲突的错误进行额外处理,因为插入我们一般会复用已写好的 DAO 层代码。

另外,初始化数据的主键可能是动态生成的,并不想把主键写死。所以下面来介绍此次的主角:基于 MySQL 的分布式锁的解决方案。

方案二:基于 MySQL 的分布式锁

多副本分布式应用,在这种 n 选 1 竞争某个资源或执行权的场景,一般都会用到分布式锁。分布式有很多种实现方式,如基于 redis,etcd,zookeeper,file 等系统。本质上,就是找个多个节点都认可的地方保存数据,通过数据竞态来实现锁,当然这个依赖最好是高可用,否则会引发单点故障。

多个副本都使用同一个 MySQL,所以我们可以很方便的基于 MySQL 实现一个分布式锁。原理很简单,利用唯一索引保证只有一个副本能插入某条数据,插入成功则表示取锁成功,执行完毕则删除该条数据释放锁。

建一个表用来存放锁数据,将 Action 设为唯一索引,表示对某个动作加锁,如:init 初始化,cronjob 定时任务等不同动作之间加锁互不影响。

type lock struct {
    Id        string `gorm:"primary_key"`
    CreatedAt time.Time
    UpdatedAt time.Time
    ExpiredAt time.Time // 锁过期时间
    Action    string `gorm:"unique;not null"`
    Holder    string // 持锁人信息,可以使用 hostname
}

既然有过期时间,那么持锁时间设为多长合适呢?设置太短可能逻辑还没执行完锁就过期了;设置太长如果程序中途挂了没有释放锁,那么这段时间所有节点都拿不到锁。

要解决这个问题我们可以使用租约机制(lease),设置较短的持锁时间,然后在持锁周期内,不断延长持锁时间,直到主动释放。这样即使程序崩溃没有 UnLock,锁也会因为没有刷新租约很快过期,不影响其他节点获取锁。

Lock 时启动一个 goroutine 刷新租约,Unlock 时通过 stopCh 将其停止。

另外,MySQL 中并没有线程去处理过期的记录,所以我们在调用 Lock 时先尝试将过期记录删掉。

阅读剩余部分

相关阅读 >>

Go strings

Go - 使用 defer 函数 要注意的几个点

Golang中的闭包

07 Golang值类型——数组

Golang怎么判断是否为ip

Go-锁机制

Golint的简易使用方法

Golang如何写http请求

Go的垃圾回收机制(gc)

Go语言缓存穿透解题思路(singleflight)

更多相关阅读请进入《Go》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...