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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
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
相关文章
|
1月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
7天前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
40 16
|
21天前
|
NoSQL 关系型数据库 MySQL
MySQL与Redis协同作战:优化百万数据查询的实战经验
【10月更文挑战第13天】 在处理大规模数据集时,传统的关系型数据库如MySQL可能会遇到性能瓶颈。为了提升数据处理的效率,我们可以结合使用MySQL和Redis,利用两者的优势来优化数据查询。本文将分享一次实战经验,探讨如何通过MySQL与Redis的协同工作来优化百万级数据统计。
48 5
|
27天前
|
缓存 NoSQL Java
Spring Boot与Redis:整合与实战
【10月更文挑战第15天】本文介绍了如何在Spring Boot项目中整合Redis,通过一个电商商品推荐系统的案例,详细展示了从添加依赖、配置连接信息到创建配置类的具体步骤。实战部分演示了如何利用Redis缓存提高系统响应速度,减少数据库访问压力,从而提升用户体验。
69 2
|
1月前
|
NoSQL Java Redis
开发实战:使用Redisson实现分布式延时消息,订单30分钟关闭的另外一种实现!
本文详细介绍了 Redisson 延迟队列(DelayedQueue)的实现原理,包括基本使用、内部数据结构、基本流程、发送和获取延时消息以及初始化延时队列等内容。文章通过代码示例和流程图,逐步解析了延迟消息的发送、接收及处理机制,帮助读者深入了解 Redisson 延迟队列的工作原理。
|
1月前
|
NoSQL Redis 数据库
计数器 分布式锁 redis实现
【10月更文挑战第5天】
47 1
|
1月前
|
NoSQL 算法 关系型数据库
Redis分布式锁
【10月更文挑战第1天】分布式锁用于在多进程环境中保护共享资源,防止并发冲突。通常借助外部系统如Redis或Zookeeper实现。通过`SETNX`命令加锁,并设置过期时间防止死锁。为避免误删他人锁,加锁时附带唯一标识,解锁前验证。面对锁提前过期的问题,可使用守护线程自动续期。在Redis集群中,需考虑主从同步延迟导致的锁丢失问题,Redlock算法可提高锁的可靠性。
73 4
|
1月前
|
缓存 NoSQL 算法
面试题:Redis如何实现分布式锁!
面试题:Redis如何实现分布式锁!
|
机器学习/深度学习 缓存 NoSQL
|
缓存 NoSQL Java
为什么分布式一定要有redis?
1、为什么使用redis 分析:博主觉得在项目中使用redis,主要是从两个角度去考虑:性能和并发。当然,redis还具备可以做分布式锁等其他功能,但是如果只是为了分布式锁这些其他功能,完全还有其他中间件(如zookpeer等)代替,并不是非要使用redis。
1364 0