本篇总结于 Go + Redis 实现分布式锁

鄙人最近在参加分布式存储的项目时学习了本内容,特此记录


为什么要用到分布式锁

先从本地的锁开始吧,在 Golang 中可以对本地的某一资源进行加锁(如变量等),以保证你在使用该资源的时候不会被其他协程更改

而在分布式系统中,若各个节点要同时使用某一个公共资源(比如说交易要修改用户存款,进程修改日志文件等),很容易就会有读写冲突、写写冲突。这时就需要一种抢占资源的机制,在你使用的时候锁住资源,保证你在使用的时候其他人不会捣乱,确保并发安全

而一种简单的实现方法就是使用 Redis 搭建分布式锁

简单的原理

这东西听上去很高大上,但是其实非常简单

就是你在访问资源前,先尝试在 Redis 处做个标记

例如你欲编辑 /file/hello.txt ,就尝试将 ["/file/hello.txt"] = 1 写入 Redis

而其他人也想做标记的时候,就会发现你已经做过了,就知道你已经抢占了资源,要等你释放

项目实践

本人的项目地址:https://github.com/tiktok-dfs/dfs

首先肯定要初始化 Redis ,因为项目是本地单机测试的,所以就以单机服务为例

1
2
3
4
5
6
7
8
9
10
11
var RedisDB *redis.Client

// InitRedis 初始化redis,用于分布式锁
func InitRedis() {
RedisDB = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
_, err := RedisDB.Ping().Result()

Check(err)
}

然后就是加锁和解锁

加锁的时候,使用 .SetNX() 方法,意味只在键不存在时,才对键进行设置操作

最后一个参数是自动释放时间, 0 表示不会自动释放,设置释放时间可以避免因为进程挂掉无法释放的死锁问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 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)
}

image-20220831121740214