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]
}
相关推荐
qq_2975746737 分钟前
RuoYi框架二次开发系列(五)性能优化、Redis缓存实战、分布式部署、接口限流与安全加固生产方案
redis·缓存·性能优化
番茄炒鸡蛋加糖1 小时前
Redis--Lua 脚本原子性与滑动窗口限流
数据库·redis·lua
顺风尿一寸3 小时前
Redis List 阻塞队列底层探秘:LPUSH 如何唤醒沉睡的 BRPOP
redis
雨的旋律20994 小时前
redis
java·数据库·redis
江湖十年4 小时前
Go 语言中 YAML to JSON 踩坑笔记
后端·go·json
向日的葵0065 小时前
Redis会话机制vsJWT机制深度解析
数据库·redis·python·缓存·系统架构·jwt
小龙报6 小时前
【优选算法】1. 水果成蓝 2.找到字符串中所有字母的异位词
java·c语言·数据结构·数据库·c++·redis·算法
三言老师8 小时前
CentOS7.9:Redis 数据持久化结构化实战教程
数据库·redis·缓存
亿牛云爬虫专家8 小时前
如何设计一套高可用的爬虫任务队列,保证断点续爬与故障转移?
redis·爬虫·故障转移·任务队列·代理ip·requests·隧道代理
程序员爱钓鱼9 小时前
第一个 Go 程序:Hello World
前端·后端·go