这是我参与「第四届青训营 」笔记创作活动的第4天
鄙人最近在参加分布式存储的项目时学习了本内容,特此记录
为什么要用到分布式锁
先从本地的锁开始吧,在 Golang 中可以对本地的某一资源进行加锁(如变量等),以保证你在使用该资源的时候不会被其他协程更改
而在分布式系统中,若各个节点要同时使用某一个公共资源(比如说交易要修改用户存款,进程修改日志文件等),很容易就会有读写冲突、写写冲突。这时就需要一种抢占资源的机制,在你使用的时候锁住资源,保证你在使用的时候其他人不会捣乱,确保并发安全
而一种简单的实现方法就是使用 Redis 搭建分布式锁
简单的原理
这东西听上去很高大上,但是其实非常简单
就是你在访问资源前,先尝试在 Redis 处做个标记
例如你欲编辑 /file/hello.txt ,就尝试将 ["/file/hello.txt"] = 1 写入 Redis
而其他人也想做标记的时候,就会发现你已经做过了,就知道你已经抢占了资源,要等你释放
项目实践
本人的项目地址:github.com/tiktok-dfs/…
首先肯定要初始化 Redis ,因为项目是本地单机测试的,所以就以单机服务为例
var RedisDB *redis.Client
// InitRedis 初始化redis,用于分布式锁
func InitRedis() {
RedisDB = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
_, err := RedisDB.Ping().Result()
Check(err)
}
然后就是加锁和解锁
加锁的时候,使用 .SetNX() 方法,意味只在键不存在时,才对键进行设置操作
最后一个参数是自动释放时间, 0 表示不会自动释放,设置释放时间可以避免因为进程挂掉无法释放的死锁问题
// Lock 分布式锁加锁,返回是否加锁成功
func Lock(key string) bool {
var result bool
for {
retryTimes := 0
retryTimes++
success, err := RedisDB.SetNX(key, "1", 0).Result()
// TODO:解决潜在的死锁问题,目前非主动不会释放
Check(err)
if success {
result = true
break
}
if retryTimes >= 1000 { // 重试一段时间后放弃尝试
log.Println("retryTimes>=1000")
break
}
time.Sleep(time.Millisecond)
}
return result
}
// Unlock 分布式锁解锁
func Unlock(key string) {
RedisDB.Del(key)
}