Golang 使用 Redis 搭建简易分布式锁 | 青训营笔记

145 阅读2分钟

这是我参与「第四届青训营 」笔记创作活动的第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)
}

image-20220831121740214