前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Redis创建分布式锁

使用Redis创建分布式锁

作者头像
leon公众号精选
发布2022-04-27 14:50:07
4520
发布2022-04-27 14:50:07
举报
文章被收录于专栏:架构师高级俱乐部

在本文中,我们将讨论如何在.NET Core中使用Redis创建分布式锁。

当我们构建分布式系统时,我们将面临多个进程一起处理共享资源,由于其中只有一个可以一次使用共享资源,因此会导致一些意外问题!

我们可以使用分布式锁来解决这个问题。

为什么分布式锁?

首先在非集群单体应用下,我们使用锁来处理这个问题。

以下显示了一些演示锁的使用的示例代码。

代码语言:javascript
复制
public void SomeMethod()
{  
//do something...  
    lock(obj)  
    {  
//do ....  
    }  
//do something...  
}  

但是,这种类型的锁不能帮助我们很好地解决问题!这是一个进程内锁,只能用共享资源解决一个进程。

这也是我们需要分布式锁的主要原因!

我将使用Redis在这里创建一个简单的分布式锁。

为什么我使用Redis来完成这项工作?由于Redis的单线程特性及其执行原子操作的能力。

如何创建一个锁?

我将创建一个.NET Core Console应用程序来向您展示大概流程。

在下一步之前,我们应该运行Redis服务器!

StackExchange.Redis是.NET中最受欢迎的Reids客户端,我们将使用它来完成以下工作。

首先与Redis建立联系。

代码语言:javascript
复制
/// <summary>  
/// The lazy connection.  
/// </summary>  
private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>  
{  
    ConfigurationOptions configuration = new ConfigurationOptions  
    {  
        AbortOnConnectFail = false,  
        ConnectTimeout = 5000,  
    };  
 
    configuration.EndPoints.Add("localhost", 6379);  
 
    return ConnectionMultiplexer.Connect(configuration.ToString());  
});  
 
/// <summary>  
/// Gets the connection.  
/// </summary>  
/// <value>The connection.</value>  
public static ConnectionMultiplexer Connection => lazyConnection.Value;

为了请求锁定共享资源,我们执行以下操作:

代码语言:javascript
复制
SET resource_name unique_value NX PX duration 

resource_name是应用程序的所有实例将共享的值。

unique_value必须对应用程序的每个实例都是唯一的。而他的主要目的是取消锁定(解锁)。

最后,我们还提供一个持续时间(以毫秒为单位),之后Redis将自动删除锁定。

这是C#代码中的实现。

代码语言:javascript
复制
/// <summary>  
/// Acquires the lock.  
/// </summary>  
/// <returns><c>true</c>, if lock was acquired, <c>false</c> otherwise.</returns>  
/// <param name="key">Key.</param>  
/// <param name="value">Value.</param>  
/// <param name="expiration">Expiration.</param>  
static bool AcquireLock(string key, string value, TimeSpan expiration)
{  
    bool flag = false;  
 
try
    {  
        flag = Connection.GetDatabase().StringSet(key, value, expiration, When.NotExists);  
    }  
    catch (Exception ex)  
    {  
        Console.WriteLine($"Acquire lock fail...{ex.Message}");  
        flag = true;  
    }  
 
    return flag;  
}

这是测试获取锁定的代码。

代码语言:javascript
复制
static void Main(string[] args)
{  
    string lockKey = "lock:eat";  
    TimeSpan expiration = TimeSpan.FromSeconds(5);  
//5 person eat something...  
    Parallel.For(0, 5, x =>  
    {  
        string person = $"person:{x}";  
        bool isLocked = AcquireLock(lockKey, person, expiration);  
 
        if (isLocked)  
        {  
            Console.WriteLine($"{person} begin eat food(with lock) at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}.");  
        }  
else
        {  
            Console.WriteLine($"{person} can not eat food due to don't get the lock.");  
        }  
    });  
 
    Console.WriteLine("end");  
    Console.Read();  
}  

运行代码后,我们可能会得到以下结果。

只有一个人可以获得锁定!其他人等待。

虽然Redis会自动删除锁,但它也没有很好地利用共享资源!

因为当一个进程完成它的工作时,应该让其他人使用该资源,而不是无休止地等待!

所以我们也需要释放锁。

如何释放锁定?

要释放锁,我们只需删除Redis中对应的key/value!

正如我们在创建锁中所做的那样,我们需要匹配资源的唯一值,这样可以更安全地释放正确的锁。

匹配时,我们将删除锁定,这意味着解锁成功。否则,解锁不成功。

我们需要一次执行getdel命令,因此我们将使用lua脚本来执行此操作!

代码语言:javascript
复制
/// <summary>  
/// Releases the lock.  
/// </summary>  
/// <returns><c>true</c>, if lock was released, <c>false</c> otherwise.</returns>  
/// <param name="key">Key.</param>  
/// <param name="value">Value.</param>  
static bool ReleaseLock(string key, string value)
{  
    string lua_script = @"  
    if (redis.call('GET', KEYS[1]) == ARGV[1]) then  
        redis.call('DEL', KEYS[1])  
        return true  
    else  
        return false  
    end  
    ";  
 
try
    {  
        var res = Connection.GetDatabase().ScriptEvaluate(lua_script,  
                                                   new RedisKey[] { key },  
                                                   new RedisValue[] { value });  
        return (bool)res;  
    }  
    catch (Exception ex)  
    {  
        Console.WriteLine($"ReleaseLock lock fail...{ex.Message}");  
        return false;  
    }  
}  

我们应该在进程完成后调用此方法。

当进程获得锁定并且由于某些原因而未释放锁定时,其他进程不能等到它被释放。此时,其他流程应该继续进行。

这是一个处理这个场景的示例。

代码语言:javascript
复制
Parallel.For(0, 5, x =>  
{  
    string person = $"person:{x}";  
    var val = 0;  
    bool isLocked = AcquireLock(lockKey, person, expiration);  
    while (!isLocked && val <= 5000)  
    {  
        val += 250;  
        System.Threading.Thread.Sleep(250);  
        isLocked = AcquireLock(lockKey, person, expiration);  
    }  
 
    if (isLocked)  
    {  
        Console.WriteLine($"{person} begin eat food(with lock) at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}.");  
        if (new Random().NextDouble() < 0.6)  
        {  
            Console.WriteLine($"{person} release lock {ReleaseLock(lockKey, person)}  {DateTimeOffset.Now.ToUnixTimeMilliseconds()}");  
        }  
else
        {  
            Console.WriteLine($"{person} do not release lock ....");  
        }  
    }  
else
    {  
        Console.WriteLine($"{person} begin eat food(without lock) at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}.");  
    }  
});  

运行该示例后,您将会得到以下结果。

如图所示,第3和第4在无锁情况下运行。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构师高级俱乐部 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档