redis时钟漂移造成的 key值失效

问题现象

问题:最近客户新切了生产环境,结果有些接口不能用了,一直报“xxx失效,校验失败”,排查了一下,代码也没改动,其他环境同样也是好的。

原因

苦思不得其解,问了大佬,才知道,可能是集群内服务器时间不一样,造成redis时钟漂移,导致key值校验时已经过期了。一排查,果真如此。

那么这个redis时钟漂移到底是什么呢?为什么集群内服务器时间不一样,会导致一个设定好过期的key值提前失效呢?

详解redis时钟漂移

关键点:resis设置的过期时间,是当前服务器时间加上设定的expires时间。
场景:服务器A 在2023/6/28 00:00:00设置了 30分钟过期的key1,key1在00:30:00 失效过期,假如服务器B比服务器A快1一个小时,那么B将一直拿不到A设定的key1

解决方案

无非是让集群时间保持一致,以下将围绕这个点进行讨论

方式一:与互联网时间同步
阿里云时钟同步服务器
ntpdate ntp4.aliyun.com
创建 crontab 任务调度
crontab -e
*/1 * * * * /usr/sbin/ntpdate ntp4.aliyun.com;
输入date,测试

式二:和内网某台机器同步
参考这里

<think>好的,我现在需要向用户介绍Redis自制的分布式锁。首先,我得回忆一下分布式锁的基本概念和Redis在这方面的应用。分布式锁主要用于在分布式系统中协调多个进程或服务对共享资源的访问,确保同一时间只有一个客户端能够执行特定操作。 用户提到的是“自制”分布式锁,可能是指不依赖现成库,自己实现。那么,我需要分步骤解释如何用Redis实现这样的锁。首先,应该提到最基础的实现方式,比如使用SET命令加锁,设置过期时间,以及使用Lua脚本保证原子性操作。 接下来,可能会想到使用SET命令的NX和PX选项,NX表示只有当键不存在时才设置,PX设置过期时间。这样可以避免死锁,因为即使客户端崩溃,锁也会自动释放。这里需要确保命令的原子性,所以必须用SET命令而非分开设置NX和过期时间。 然后是释放锁的部分。释放锁时,需要验证当前客户端是否持有锁,防止误删其他客户端的锁。这通常通过生成唯一的(比如UUID)作为锁的,并在删除时检查该是否匹配。使用Lua脚本来保证原子性的检查和删除操作是关键,因为EVAL命令能确保脚本执行期间不会被其他命令打断。 但是,用户可能还会关心这种实现方式的潜在问题。比如,锁的过期时间设置是否合理,如果业务逻辑执行时间超过过期时间,会导致锁提前释放,进而引发问题。这时候可能需要引入自动续期的机制,也就是“看门狗”机制,定期延长锁的持有时间。 另外,还需要考虑Redis的高可用性。单点Redis实例存在故障风险,所以可能需要使用Redis集群或者RedLock算法来增强可靠性。不过RedLock也有其争议,需要权衡利弊。 还有,处理网络延迟和时钟漂移的问题。比如,客户端A获取锁后发生长时间GC停顿,导致锁过期,客户端B获取锁,之后A恢复并释放了B的锁。因此,唯一标识符和原子释放非常重要。 可能的步骤总结: 1. 使用SET NX PX命令获取锁,设置唯一。 2. 执行操作。 3. 使用Lua脚本检查并删除键来释放锁。 4. 处理锁续期和超时问题。 5. 考虑集群环境下的可靠性。 需要确保每个步骤的细节准确,比如Lua脚本的正确写法,如何生成唯一,以及如何处理异常情况。同时,指出自制锁的局限性,比如不如官方实现的Redisson完善,但在简单场景下可用。 可能还需要比较其他实现方式,比如基于Zookeeper的分布式锁,但用户问的是Redis,所以重点在Redis方案。 最后,要结构清晰地分点说明,确保用户能一步步理解实现过程、关键点和注意事项,同时提醒潜在问题和改进方向。</think>以下是对Redis自制分布式锁的逐步介绍: --- ### 一、分布式锁的核心需求 在分布式系统中,多个服务实例需协调对共享资源的访问。分布式锁需满足: 1. **互斥性**:同一时刻仅一个客户端持有锁 2. **防死锁**:持有锁的客户端崩溃后,锁能自动释放 3. **安全性**:只能由锁的持有者释放锁 4. **容错性**:Redis节点故障时仍能正常运作(需集群方案) --- ### 二、基础实现方案 #### 步骤1:获取锁 使用Redis的`SET`命令实现原子性加锁: ```lua SET lock_key $unique_value NX PX 30000 ``` - **NX**:仅当`lock_key`不存在时设置 - **PX 30000**:设置30秒过期时间 - `$unique_value`:唯一标识(如UUID),用于安全释放 #### 步骤2:释放锁 通过Lua脚本保证原子性检查与删除: ```lua if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end ``` - 用`EVAL`执行脚本,确保操作原子性 - 验证`unique_value`匹配后才删除锁 --- ### 三、关键问题与优化 #### 1. 锁续期(Watchdog机制) - **问题**:业务操作超时导致锁提前过期 - **方案**:后台线程定期续期(如每10秒延长过期时间) #### 2. 集群环境下的可靠性 - **单点故障风险**:单Redis节点宕机会导致锁失效 - **RedLock算法**(争议方案): - 向多个独立节点申请锁 - 半数以上节点获取成功才算加锁成功 - 需权衡性能与复杂性 #### 3. 网络延迟与时钟问题 - 客户端因GC停顿导致锁过期后误删其他锁 - **防御措施**:严格使用唯一标识符验证锁归属 --- ### 四、代码示例(Python伪代码) ```python import redis import uuid client = redis.Redis() lock_key = "resource_lock" unique_id = str(uuid.uuid4()) # 尝试加锁 success = client.set(lock_key, unique_id, nx=True, px=30000) if not success: raise Exception("获取锁失败") try: # 执行业务逻辑 do_something() finally: # 释放锁 script = """ if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end """ client.eval(script, 1, lock_key, unique_id) ``` --- ### 五、局限性 1. **锁续期实现复杂**:需自行管理后台线程 2. **时钟同步问题**:多节点环境下时钟漂移可能影响RedLock 3. **性能损耗**:相比原生支持的分布式锁库(如Redisson)效率较低 --- ### 六、适用场景 - 对一致性要求**不是极端严格**的场景 - 短期任务且能预估最大执行时间 - 无现成分布式锁中间件可用时的临时方案 --- ### 七、推荐改进方向 1. 使用**Redisson**等成熟库(支持自动续期、多种锁类型) 2. 结合ZooKeeper实现更严格的锁(但性能较低) 3. 若业务允许,考虑采用无锁设计(如CAS操作)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值