• redis分布式锁


    C# Redis分布式锁的应用

    1、背景

    我们在开发很多业务场景会使用到锁,例如库存控制,抽奖等。一般我们会使用内存锁的方式来保证线性的执行。但现在大多站点都会使用分布式部署,那多台服务器间的就必须使用同一个目标来判断锁。分布式与单机情况下最大的不同在于其不是多线程而是多进程。

    2、演变

    [分布式站点使用内存锁方式如下图]
    clipboard.png
    假设有3个用户同时购买一件商品,商品库存只剩下1,如果3个用户同时购买,负载均衡把3个用户分别指向站点1、2、3,那结果将会是3个用户都购买成功。下面我们使用分布式锁解决这个问题。

    [分布式站点使用分布式锁如下图]
    clipboard.png
    单台服务器由于可以共享堆内存,因此可以简单的采取内存作为标记存储位置。而多服务器之间都不在同一台物理机上,因此需要将标记存储在一个所有进程都能看到的地方。

    3、实现

    选型

    想想我们实现分布式锁要满足哪些条件?
    1、在分布式系统环境下,一个锁在同一时间只能被一个服务器获取;(这是所有分布式锁的基础) 
    2、高性能的获取锁和释放锁;(锁用完了,要及时释放,以供别人继续使用)
    3、具备锁失效机制,防止死锁;(防止因为某些意外,锁没有得到释放,那别人将永远无法使用)
    4、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。(满足等待锁的同时,也要满足非阻塞锁特性,便于多样性的业务场景使用)

    分布式锁有很多的实现方式:数据库排他锁、Zookeeper、缓存(redis、memcached)等,本文选用Redis实现。原因如下:
    1、Redis有很高的性能;
    2、Redis命令对此支持较好,实现起来比较方便;
    Redis官网上对C#的使用推荐有ServiceStack.Redis和StackExchange.Redis
    由于StackExchange.Redis虽然有提供LockTake方法,很方便的使用锁,但是只支持.Net4.5以上。公司很多站点都是3.5和4.0的,所以选用ServiceStack.Redis,自己封装一下。

    Redis使用命令介绍

    1、SETNX
    SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
    2、expire
    expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
    3、delete
    delete key:删除key
    在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

    C#代码

    1、锁方法

    /// <summary>

    /// 分布式锁

    /// </summary>

    /// <param name="key">锁key</param>

    /// <param name="lockExpirySeconds">锁自动超时时间(秒)</param>

    /// <param name="waitLockMs">等待锁时间(秒)</param>

    /// <returns></returns>

    public static bool Lock(string key, int lockExpirySeconds = 10, double waitLockSeconds = 0)

    {

    //间隔等待50毫秒

    int waitIntervalMs = 50;



    RedisClient client = (RedisClient)RedisClientManager.GetClient(RedisProviderName.RedisCommon, 0);



    string lockKey = "LockForSetNX:" + key;



    DateTime begin = DateTime.Now;

    using (client)

    {

    while (true)

    {

    //循环获取取锁

    if (client.SetNX(lockKey, new byte[] { 1 }) == 1)

    {

    //设置锁的过期时间

    client.Expire(lockKey, lockExpirySeconds);

    return true;

    }



    //不等待锁则返回

    if (waitLockSeconds == 0) break;



    //超过等待时间,则不再等待

    if ((DateTime.Now - begin).TotalSeconds >= waitLockSeconds) break;



    Thread.Sleep(waitIntervalMs);

    }

    return false;

    }

    }
    2、释放锁


    /// <summary>

    /// 删除锁 执行完代码以后调用释放锁

    /// </summary>

    /// <param name="key"></param>

    public static void DelLock(string key)

    {

    RedisClient client = (RedisClient)RedisClientManager.GetClient(RedisProviderName.RedisCommon, 0);

    string lockKey = "LockForSetNX:" + key;

    using (client)

    {

    client.Del(lockKey);

    }

    }
    3、业务应用代码


    try

    {

    //取锁,设置key10秒后失效,最大等待锁5秒

    if (RedisHelper.Lock("LockKey", 10, 5))

    {

    //取到锁,执行具体业务

    //例如商品购买,库存-1

    }

    }

    finally

    {

    //释放锁

    DelLock("LockKey");
    }

  • 相关阅读:
    Arrays.fill方法的陷阱
    彻底弄懂最短路径问题
    《c++primer》疑惑记录
    C++ 隐含的this 指针
    c++ 内存分配
    抽象 与 封装 区别
    iconv 文件编码转换
    python中文分词工具——结巴分词
    词形变换和词干提取工具(英文)
    python 绘图工具 matplotlib 入门
  • 原文地址:https://www.cnblogs.com/wugh8726254/p/13584392.html
Copyright © 2020-2023  润新知