redis 分布式锁

完整代码传送门

go 复制代码
package redis_distributed_lock

import (
	"context"
	"fmt"
	"redis-distributed-lock/redis_client"
	"runtime"
	"strings"
	"time"

	"github.com/redis/go-redis/v9"
)

type RedisDistributedLock struct {
	client     *redis.Client
	lockKey    string
	lockValue  string
	expireTime int
}

const redisLockKey = "redisLock"
const redisLockExpireTime = 30

func NewRedisDistributedLock(client *redis.Client) *RedisDistributedLock {
	return &RedisDistributedLock{
		client:     client,
		lockKey:    redisLockKey,
		lockValue:  GoID(),
		expireTime: redisLockExpireTime,
	}
}

func (r *RedisDistributedLock) Lock() {
	script := `
if redis.call("exists", KEYS[1]) == 0 or redis.call("hexists", KEYS[1], ARGV[1]) == 1 then
	redis.call("hincrby", KEYS[1], ARGV[1], 1)
	redis.call("expire", KEYS[1], ARGV[2])
	return 1
else
	return 0
end`
	client := redis_client.NewClient()
	for {
		val, err := client.Eval(context.Background(), script, []string{r.lockKey}, r.lockValue, r.expireTime).Result()
		if err != nil {
			fmt.Printf("lock failed, err: %v\n", err)
			return
		}
		if val != int64(1) {
			time.Sleep(30 * time.Millisecond)
		} else {
			fmt.Printf("lock successfully, lockName: %v, lockValue: %v\n", r.lockKey, r.lockValue)
			go r.renewExpire()
			break
		}
	}
}

func (r *RedisDistributedLock) renewExpire() {
	script := `
if redis.call("hexists", KEYS[1], ARGV[1]) == 1 then
	return redis.call("expire", KEYS[1], ARGV[2])
else
	return 0
end`
	for {
		time.Sleep(time.Duration(r.expireTime/3) * time.Second)
		val, err := r.client.Eval(context.Background(), script, []string{r.lockKey}, r.lockValue, r.expireTime).Result()
		if err != nil {
			fmt.Printf("renewExpire failed, err: %v\n", err)
			return
		}
		if val == int64(0) {
			return
		}
	}
}

func (r *RedisDistributedLock) Unlock() {
	script := `
if redis.call("hexists", KEYS[1], ARGV[1]) == 0 then
	return nil
elseif redis.call("hincrby", KEYS[1], ARGV[1], -1) == 0 then
	return redis.call("del", KEYS[1])
else
	return 0
end`
	client := redis_client.NewClient()
	val, err := client.Eval(context.Background(), script, []string{r.lockKey}, r.lockValue).Result()
	if err != nil {
		fmt.Printf("unlock failed, err: %v\n", err)
		return
	}
	if val == redis.Nil {
		fmt.Printf("lock does not exist\n")
		return
	}
	fmt.Printf("unlock successfully, lockName: %v, lockValue: %v\n", r.lockKey, r.lockValue)
}

func GoID() string {
	var buf [64]byte
	n := runtime.Stack(buf[:], false)
	return strings.Fields(strings.TrimPrefix(string(buf[:n]), "goroutine "))[0]
}
相关推荐
莫得感情 o5 小时前
Redis 05 · 持久化:RDB 与 AOF 怎么保证数据不丢
redis·缓存
curd_boy6 小时前
【Redis】Redis从缓存到AI向量平台
人工智能·redis·缓存
莫得感情 o7 小时前
Redis 02 · 数据结构:String 到 Stream 怎么选
redis
devpotato8 小时前
缓存与数据库更新顺序不一致问题
java·数据库·redis
我的div丢了肿么办9 小时前
go语言中的map,map定义不同的数据类型,循环map,map的无序性
后端·go
NeverFear79213 小时前
【黑马点评】短信登录
java·redis·tomcat·intellij-idea
攻城有术14 小时前
专项攻克——重写 Redis 依赖包方法的 6 种实现方式
java·数据库·redis·bootstrap
名字还没想好☜14 小时前
Go 1.21 context.WithoutCancel 实战:父 context 取消了,收尾任务还要继续跑
开发语言·后端·golang·go
spencer_tseng14 小时前
kylin redis-6.2.14.tar.gz
linux·redis·kylin·edis-6.2.14
xbgRS14 小时前
Redis基本概念及应用
redis·缓存