【Redis核心知识 四】Redis分布式锁实战(下)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 【Redis核心知识 四】Redis分布式锁实战(下)

技术特性

在以上的实现逻辑中,用到了Redis的几个经典的特性:队列锁、分组锁、指定元素出队

队列锁

队列锁即对队列的控制权,我们可以设定一个约定好的常量值即可,使用Redis的特性SetNx操作,天然的原子操作:只在键 key 不存在的情况下, 将键 key 的值设置为 value 。若键 key 已经存在, 则 SETNX 命令不做任何动作,需要注意的是队列锁需要设置过期时间,如果线程意外关闭没有来得及释放队列锁,会导致死锁。

/// <summary>
        /// 获取缓存队列的队列锁
        /// </summary>
        /// <returns></returns>
        public static bool GetQueueLock()
        {
            //申请成功标志
            const string queueLockkey = "QueueLock";
            const int tenantId = TenantIdInRedis;
            try
            {
                using (var redis = new RedisNativeProviderV2(KeySpaceInRedis, tenantId))
                {
                    //redis中,如果返回true设置成功代表队列锁空闲,如果返回false设置失败表明队列锁正在被持有
                    if (RedisSetNx(queueLockkey))  //如果为true,设置队列锁并设置该锁的过期时间
                    {
                        RedisExpire(queueLockkey, 600);//设置过期时间为10分钟
                        return true;
                    }
                }
            }
            catch (Exception ex)
            {
                //进行查询异常操作
                Loggging.Error($"在redis 设置队列锁[{queueLockkey}]异常", ex);
                //抛出异常
            }
            return false;
        }
        /// <summary>
        /// 给缓存队列的队列解锁
        /// </summary>
        /// <returns></returns>
        public static bool QueueUnLock()
        {
            //申请成功标志
            const string queueLockkey = "QueueLock";
            const int tenantId = TenantIdInRedis;
            try
            {
                using (var redis = new RedisNativeProviderV2(KeySpaceInRedis, tenantId))
                {
                    return RedisDeleteKey(queueLockkey);
                    //redis中,如果返回true设置成功代表原来不存在这样的分组,如果返回false设置失败表明原来存在这样的分组
                }
            }
            catch (Exception ex)
            {
                //进行查询异常操作
                Loggging.Error($"在redis 解除队列锁[{queueLockkey}]异常", ex);
                //抛出异常
            }
            return false;
        }

SetNx的执代码如下:

/// <summary>
        /// 如果返回true设置成功代表原来不存在这样的锁,如果返回false设置失败表明原来存在这样的锁
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public static bool RedisSetNx(string key)
        {
            const int tenantId = TenantIdInRedis;
            try
            {
                using (var redis = new RedisNativeProviderV2(KeySpaceInRedis, tenantId))
                {
                    return redis.SetNx(key, StringToBytes(key));
                }
            }
            catch (Exception ex)
            {
                //进行锁创建异常的操作
                Loggging.Error($"在redis setNx[{key}]:[{key}]异常", ex);
            }
            finally
            {
                //进行锁创建成功或失败的操作
                Loggging.Info($"在redis setNx[{key}]:[{key}]");
            }
            return false;
        }

分组锁

同理,分组锁也是通过类似形式实现的,需要注意的是分组锁同样需要设置过期时间,如果线程意外关闭没有来得及释放分组锁,会导致死锁。

/// <summary>
        ///给分组加分组锁
        /// </summary>
        /// <returns></returns>
        public static bool GetGroupLock(string groupKey)
        {
            const int tenantId = TenantIdInRedis;
            try
            {
                using (var redis = new RedisNativeProviderV2(KeySpaceInRedis, tenantId))
                {
                    //redis中,如果返回true设置成功代表分组锁空闲,如果返回false设置失败表明队分组锁正在被持有
                    if (RedisSetNx(groupKey))  //如果为true,设置分组锁并设置该锁的过期时间
                    {
                        RedisExpire(groupKey, 300);//设置过期时间为5分钟
                        return true;
                    }
                }
            }
            catch (Exception ex)
            {
                //进行查询异常操作
                Loggging.Error($"在redis 设置分组锁[{groupKey}]异常", ex);
                //抛出异常
            }
            return false;
        }
        /// <summary>
        /// 给分组锁解锁
        /// </summary>
        /// <returns></returns>
        public static bool GroupUnLock(ImportRequestDataModel model)
        {
            //申请成功标志
            const int tenantId = TenantIdInRedis;
            var groupKey = ImportParallelismHelper.GetMessageGroupId(model.MetaObjName, model.TenantId);
            try
            {
                using (var redis = new RedisNativeProviderV2(KeySpaceInRedis, tenantId))
                {
                    return RedisDeleteKey(groupKey);
                    //redis中,如果返回true设置成功代表原来不存在这样的分组,如果返回false设置失败表明原来存在这样的分组
                }
            }
            catch (Exception ex)
            {
                //进行查询异常操作
                Loggging.Error($"在redis 解除分组锁[{groupKey}]异常", ex);
                //抛出异常
            }
            return false;
        }

相同元素出队

这也算队列的一个难题,因为队列没有从中间出队的,搜索后才发现,其实可以通过相同元素出队的方式处理:

/// <summary>
        /// 从队尾开始删除第一个与model相同的元素
        /// </summary>
        /// <param name="value"></param>
        private static void RedisLRem(string value)
        {
            //申请成功标志
            const int tenantId = TenantIdInRedis;
            try
            {
                using (var redis = new RedisNativeProviderV2(KeySpaceInRedis, tenantId))
                {
                    //redis中,如果返回true设置成功代表原来不存在这样的分组,如果返回false设置失败表明原来存在这样的分组
                    redis.LRem(ImportQueueRedisKey, -1, StringToBytes(value));
                }
            }
            catch (Exception ex)
            {
                //进行锁创建异常的操作
                Loggging.Error($"在redis RedisLRem[{ImportQueueRedisKey}]异常", ex);
            }
            finally
            {
                //进行锁创建成功或失败的操作
                Loggging.Info($"在redis RedisLRem[{ImportQueueRedisKey}]异常");
            }
        }

感觉还是在实战中能更加深刻的理解理论,当然过程还是很痛苦的,核对方案,考虑各种失败的可能性,以及对Redis的理解,尤其困难的是分布式的代码很难调试,只能通过日志来模拟场景。总之算是一个较大的提升吧,在实践中训练之后要再从理论中获取更多知识更好的服务于实践吧!可以了解了解SetNX加过期时间的整体原子操作。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
20小时前
|
NoSQL 测试技术 Go
【Golang】国密SM2公钥私钥序列化到redis中并加密解密实战_sm2反编(1)
【Golang】国密SM2公钥私钥序列化到redis中并加密解密实战_sm2反编(1)
|
1天前
|
存储 缓存 NoSQL
实战:第十一篇:StringRedisTemplate获取redis信息,面试官突击一问
实战:第十一篇:StringRedisTemplate获取redis信息,面试官突击一问
|
2天前
|
存储 监控 NoSQL
【Redis】分布式锁及其他常见问题
【Redis】分布式锁及其他常见问题
16 0
|
2天前
|
NoSQL Java Redis
【Redis】Redis实现分布式锁
【Redis】Redis实现分布式锁
7 0
|
3天前
|
存储 NoSQL Redis
Redis数据结构精讲:选择与应用实战指南
Redis数据结构精讲:选择与应用实战指南
14 0
|
3天前
|
监控 NoSQL 算法
探秘Redis分布式锁:实战与注意事项
本文介绍了Redis分区容错中的分布式锁概念,包括利用Watch实现乐观锁和使用setnx防止库存超卖。乐观锁通过Watch命令监控键值变化,在事务中执行修改,若键值被改变则事务失败。Java代码示例展示了具体实现。setnx命令用于库存操作,确保无超卖,通过设置锁并检查库存来更新。文章还讨论了分布式锁存在的问题,如客户端阻塞、时钟漂移和单点故障,并提出了RedLock算法来提高可靠性。Redisson作为生产环境的分布式锁实现,提供了可重入锁、读写锁等高级功能。最后,文章对比了Redis、Zookeeper和etcd的分布式锁特性。
134 16
探秘Redis分布式锁:实战与注意事项
|
3天前
|
NoSQL Java 大数据
介绍redis分布式锁
分布式锁是解决多进程在分布式环境中争夺资源的问题,与本地锁相似但适用于不同进程。以Redis为例,通过`setIfAbsent`实现占锁,加锁同时设置过期时间避免死锁。然而,获取锁与设置过期时间非原子性可能导致并发问题,解决方案是使用`setIfAbsent`的超时参数。此外,释放锁前需验证归属,防止误删他人锁,可借助Lua脚本确保原子性。实际应用中还有锁续期、重试机制等复杂问题,现成解决方案如RedisLockRegistry和Redisson。
|
NoSQL Java 关系型数据库
浅谈Redis实现分布式锁
浅谈Redis实现分布式锁
|
存储 canal 缓存
|
NoSQL PHP Redis
redis实现分布式锁
redis实现分布式锁
144 0
redis实现分布式锁

热门文章

最新文章