• 使用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")
  • 相关阅读:
    激活win10系统的方法(亲测)
    java使用jxl,自动导出数据excle,quartz自动发送邮件
    java对接网银支付案例
    将数字转换成Excel表头格式的字母序号
    mysql date_add日期函数的使用
    itext A4纸张横向创建PDF
    Spring 定时器 定时访问数据库并发送邮件
    mvc路由报错
    eval()和$.parseJSON()注意事项
    跨域学习笔记
  • 原文地址:https://www.cnblogs.com/majiang/p/14189990.html
Copyright © 2020-2023  润新知