使用Redis的SET实现锁机制【C# &Go&Python实现】

其实网上正确地使用Redis的SETNX实现锁机制 和 高并发1-Redis分布式锁setnx,setex连用 说的都对,只是现在的redis做了很多优化比如现在的Set 指令如下

复制代码
set key value [EX seconds] [PX milliseconds] [NX|XX]
EX seconds:设置失效时长,单位秒
PX milliseconds:设置失效时长,单位毫秒
NX:key不存在时设置value,成功返回OK,失败返回(nil)
XX:key存在时设置value,成功返回OK,失败返回(nil)
 
案例:设置name=p7+,失效时长100s,不存在时设置
1.1.1.1:6379> set name gavin ex 100 nx
OK
1.1.1.1:6379> get name
"gavin"
1.1.1.1:6379> ttl name
(integer) 94
复制代码

从上面可以看出,多个命令放在同一个redis连接中并且redis是单线程的,因此上面的操作可以看成setnxexpire的结合体,是原子性的。

所以设置的时候不用lua脚本了,大致逻辑如下:

复制代码
$rs = $redis->set($key, $random, ex $time nx);
if ($rs) {
     //处理更新缓存逻辑
    // ......
    //先判断随机数,是同一个则删除锁
    if ($redis->get($key) == $random) {
        $redis->del($key);
    }
}
复制代码

解决了那些问题:

1.缓存雪崩: 例如某个查询数据库的接口因为请求量比较大所以加了缓存,并设定缓存过期后刷新。当并发量比较大并且缓存过期的瞬间,大量并发请求会直接查询数据库导致雪崩。如果使用锁机制来控制只有一个请求去更新缓存就能避免雪崩的问题。这里的参数nx 是setNX,是set if not exists 的缩写,也就是只有不存在的时候才设置, 设置成功时返回 1 , 设置失败时返回 0 。可以利用它来实现锁的效果,

2.key的过期时间,参数ex 是setex(set expire value)。 如果更新缓存的时候因为某些原因意外退出了,那么这个锁需要自动删除。【果果过期时间来完成】,由于这里是1条命令, 所以不需要用Multi/Exec 来保证原子性。 

3.如果一个请求更新缓存的时间比锁的有效期还要长,导致在缓存更新过程中锁就失效了,此时另一个请求就会获取到锁,但前一个请求在缓存更新完毕的时候,直接删除锁的话就会出现误删其它请求创建的锁的情况。所以要避免这种问题,删除key的时候判断一下value是否是当前value,是的话删除,否则不执行删除,LUA如下:

复制代码
local lockKey = KEYS[1]
local lockValue = ARGV[1]
local result_1 = redis.call('get', lockKey)
if result_1 == lockValue
then
   local result_2= redis.call('del', lockKey)
   return result_2
else
    return 0
end
复制代码

在C# 的demo, 需要安装 相应的包, 我这里用的是 StackExchange.Redis,首先封装RedisLock.cs

复制代码
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Text;
 
namespace ConsoleApp
{
    public class RedisLock
    {
        IDatabase db;
        ConnectionMultiplexer connection;
        string deleScript = $@"
                    local lockKey = KEYS[1]
                    local lockValue = ARGV[1]
                    local result_1 = redis.call('get', lockKey)
                    if result_1 == lockValue
                    then
                        local result_2= redis.call('del', lockKey)
                        return result_2
                    else
                        return 0
                    end";
        public RedisLock(string connStr, int database = 0)
        {
            connection = ConnectionMultiplexer.Connect(connStr);
            db = connection.GetDatabase(database);
        }
        //加锁
        public bool Lock(string key, string value, int timeOut)
        {
            var result = db.Execute("set", key, value, "NX", "EX", timeOut);
            if (result.ToString().Contains("OK"))
            {
                return true;
            }
            return false;
        }
        //解锁
        public bool UnLock(string key, string value) {
            var keys = new List<RedisKey> { key };
            var values = new List<RedisValue> { value };
           var result = db.ScriptEvaluate(deleScript, keys.ToArray(), values.ToArray());
          return  Convert.ToInt32(result.ToString()) == 1;
        }
        public void Close()
        {
            if (connection.IsConnected)
            {
                connection.Close(true);
            }
        }
    }
}
复制代码

使用很简单:

复制代码
 static void Main(string[] args)
        {
            string redisConn = "localhost:6379";
            string key = "Name";
            string value = "gavin";
            int timeOut = 100;
 
            RedisLock rl = new RedisLock(redisConn, 0);
            if (rl.Lock(key, value, timeOut)) {
 
                Console.WriteLine("Hello World!");
 
                if (!rl.UnLock(key, value))
                {
                    Console.WriteLine("UnLock failed");
                }
                else {
                    rl.Close();
                    Console.WriteLine("UnLock Okay");
                }
            }
            else {
                rl.Close();
            }
            Console.ReadKey();
        }
复制代码

go的demo, 这里用    "github.com/go-redis/redis"插件,封装redisLock.go

复制代码
package utils
 
import (
    "time"
 
    "github.com/go-redis/redis"
)
 
type RedisLock struct {
    rc *redis.Client
}
 
func NewRedisLock(addr, password string, db int) *RedisLock {
    rdb := redis.NewClient(&redis.Options{
        Addr:     addr,
        Password: password, // no password set
        DB:       db,       // use default DB
    })
    return &RedisLock{rc: rdb}
}
 
func (rl *RedisLock) Lock(key, value string, timeOut int) (bool, error) {
    set, err := rl.rc.SetNX(key, value, time.Duration(timeOut)*time.Second).Result()
    return set, err
}
 
func (rl *RedisLock) Unlock(key, value string) (bool, error) {
    ret, error := rl.rc.Get(key).Result()
    if error == nil {
        if value == ret {
            ressult, er := rl.rc.Del(key).Result()
            return ressult == 1, er
        } else {
            return false, error
        }
    }
    return false, error
}
 
func (rl *RedisLock) Close() error {
    return rl.rc.Close()
}
复制代码

调用如下:

复制代码
package main
 
import (
    "fmt"
    "main/utils"
)
 
func main() {
    key := "name"
    val := "gavin"
    timeOut := 100
    rl := utils.NewRedisLock("localhost:6379", "", 1)
 
    if ret, _ := rl.Lock(key, val, timeOut); ret {
        fmt.Println("Lock okay")
        ///
        if result, _ := rl.Unlock(key, val); result {
            fmt.Println("Unlock okay")
        }
 
    }
    rl.Close()
}
复制代码

 Python实现, 创建redislock.py文件: 需要安装RedisPy库

复制代码
from redis import StrictRedis
from redis.connection import ConnectionPool
 
class RedisLock(object):
 
    def __init__(self,addr):
        #addr = 'redis://:foobared@localhost:6379/0' redis://[:password]@host:port/db
        #r =redis.Redis(host="123.516.74.190",port=6379,password="6666666666")
        pool = ConnectionPool.from_url(addr)
        redis = StrictRedis(connection_pool=pool)
        self.redis=redis
    
    def Lock(self,key,value,timeOut):
       return self.redis.set(key,value,ex=timeOut,nx=True)
 
    def  UnLock(self,key,value):
        ret=False
        valByte=self.redis.get(key)
        val = bytes.decode(valByte)
        if val==value:
            self.redis.delete(key)
            ret=True
        
        return ret
复制代码

调用:

复制代码
import redisLock
 
key="name"
value="gavin"
timeOut=100
rs=redisLock.RedisLock("redis://@127.0.0.1:6379/2")
if rs.Lock(key,value,timeOut):
    print("Lock Ok")
    if rs.UnLock(key,value):
        print("Unlock Ok")
print("done")
复制代码

 

posted on   dz45693  阅读(1496)  评论(0编辑  收藏  举报

编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示