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

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

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的结合体,是原子性的。git

因此设置的时候不用lua脚本了,大体逻辑以下:github

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

解决了那些问题:redis

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.csdom

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()
}
相关文章
相关标签/搜索