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]
}
相关推荐
温暖小土27 分钟前
在 CentOS 上安装 Redis Stack
linux·redis·centos
Mikko73 小时前
Redis 连接数满了服务端有错误信息吗?连接数不增加却命令超时?6.2 实测连接池、超时与大 key 怎么判定
java·spring boot·redis·后端
无言听风雨丶4 小时前
Redis 运维实战与排错
运维·数据库·redis
Patrick_Wilson5 小时前
读懂一个 Go JSON 处理函数:从输入数据追踪结构体、校验与错误返回
go·json·编程语言
Patrick_Wilson5 小时前
第一次读 Go 项目:从两个源码文件弄清 package、import 和 go.mod
后端·go·编程语言
Patrick_Wilson5 小时前
执行 go build . 后,Go 怎样找到代码并生成程序
后端·go·编程语言
Patrick_Wilson5 小时前
Go 变量不赋值时,为什么也有值
后端·go·编程语言
Patrick_Wilson5 小时前
读 Go 项目里的命令:run、build、test 分别做了什么
后端·go·编程语言
字节探索8 小时前
Redis 分布式锁:从 SETNX 误删到看门狗自动续期的原理与实践
redis·分布式
vipxieliang9 小时前
Go GC 性能优化实践:从 GOGC、GOMEMLIMIT 到分配热点定位
go