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]
}
相关推荐
{⌐■_■}20 分钟前
【Kafka】登录日志处理的三次阶梯式优化实践:从同步写入到Kafka多分区批处理
数据库·分布式·mysql·kafka·go
高hongyuan2 小时前
Go语言教程-开发工具-Visual Studio
go·visual studio
考虑考虑5 小时前
go中的切片
后端·go
weixin_438335406 小时前
Redis:分组与设备在 Redis 中缓存存储设计
redis·缓存·bootstrap
JAVA坚守者7 小时前
Redis Desktop Manager 使用前的准备工作
redis·centos·网络配置·redis manager·安全设置
秋也凉11 小时前
redis的命令集合
数据库·redis·缓存
都叫我大帅哥12 小时前
Redis内存淘汰策略:从OOM崩溃到丝滑运行的终极指南
java·redis
R-sz13 小时前
java内存缓存实现 与 redis缓存实现 (ConcurrentHashMap 应用)
java·redis·缓存
程序员爱钓鱼17 小时前
限流、控并发、减GC!一文搞懂Go项目资源优化的正确姿势
后端·google·go
岁岁岁平安21 小时前
Redis基础学习(五大值数据类型的常用操作命令)
数据库·redis·学习·redis list·redis hash·redis set·redis string