当前位置: 首页 > news >正文

Redis篇(十):分布式锁、缓存一致性与延迟队列

一、Redis 应用全景

Redis 不仅是缓存中间件,更是分布式系统中不可或缺的组件。本文聚焦三大核心应用场景:分布式锁、缓存一致性、延迟队列。


二、分布式锁:从 SETNX 到 Redisson

2.1 为什么需要分布式锁?

在分布式系统中,多个服务实例可能同时操作共享资源(如库存、订单、配置),需要一种跨进程的互斥机制来保证数据一致性。

典型应用场景

  • 库存扣减(防止超卖)
  • 分布式任务调度(避免重复执行)
  • 配置中心原子更新
  • 分布式会话管理

2.2 分布式锁的演进

V1.0:SETNX + EXPIRE(存在死锁风险)

SETNX lock:order:10011# 加锁EXPIRE lock:order:100110# 设置过期# 问题:非原子操作,如果 SETNX 后崩溃,锁永远无法释放

V2.0:SET … NX PX(原子加锁 + 过期)

SET lock:order:1001 request_id NX PX10000# 问题:业务执行时间超过锁过期时间,导致锁提前释放

V3.0:Redisson 看门狗(原子加锁 + 自动续期)

RLocklock=redisson.getLock("order:1001");try{lock.lock();// 执行业务逻辑}finally{lock.unlock();}

2.3 保证加锁和解锁的原子性

加锁原子性SET key value NX PX ttl是单条命令,Redis 单线程执行天然原子。

解锁原子性:使用 Lua 脚本保证判断 + 删除的原子性。

// Lua 脚本释放锁StringunlockScript="if redis.call('get', KEYS[1]) == ARGV[1] then "+"return redis.call('del', KEYS[1]) "+"else return 0 end";redisTemplate.execute(newDefaultRedisScript<>(unlockScript,Long.class),Collections.singletonList("lock:order:1001"),requestId);

2.4 Redisson 看门狗机制

业务线程获取锁 ↓ 看门狗线程启动(delay = lockWatchdogTimeout / 3,默认 10s/3 ≈ 3.3s) ↓ 每 3.3s 检查锁是否仍被持有 ↓ 若是 → 续期至 30s ↓ 业务完成 → unlock() → 看门狗停止 ↓ 异常崩溃 → 锁自动过期释放(避免死锁)

2.5 分布式锁的优缺点

优点缺点
性能高效超时时间不好设置
实现方便主从复制异步导致锁不可靠
避免单点故障(RedLock)需要额外组件(Redisson)

2.6 合理的超时时间设置

// Redisson 自动处理:默认 30s 过期,看门狗每 10s 续期Configconfig=newConfig();config.useSingleServer().setAddress("redis://127.0.0.1:6379");RedissonClientredisson=Redisson.create(config);RLocklock=redisson.getLock("myLock");lock.lock();// 看门狗自动续期// 业务逻辑lock.unlock();

三、缓存一致性策略

3.1 五种策略对比

策略读流程写流程一致性适用场景
Cache-Aside先读缓存,未命中读 DB先更新 DB,再删缓存最终一致读多写少
延迟双删同 Cache-Aside先删缓存→更新 DB→延迟再删最终一致高并发写
Read/Write-Through缓存代理查询缓存代理更新,同步写 DB强一致金融交易
Write-Behind只读缓存只更新缓存,异步批量写 DB最终一致秒杀库存
Binlog 同步先读缓存更新 DB,Canal 监听异步删缓存最终一致多级缓存

3.2 Cache-Aside 旁路缓存(最常用)

publicStringread(Stringkey){Stringdata=redisTemplate.opsForValue().get(key);if(data==null){data=db.query(key);redisTemplate.opsForValue().set(key,data,Duration.ofHours(1));}returndata;}publicvoidwrite(Stringkey,Stringdata){db.update(key,data);redisTemplate.delete(key);// 删除缓存,非更新}

为什么删除缓存而不是更新缓存?

  • 更新缓存需要计算新值,可能涉及复杂逻辑
  • 删除缓存更简单,下次读取时自动回填最新值
  • 避免并发更新导致缓存数据不一致

3.3 延迟双删策略(高并发优化)

publicvoidwriteWithDoubleDelete(Stringkey,Stringdata){redisTemplate.delete(key);// 第一次删除db.update(key,data);// 更新数据库// 延迟第二次删除(通过消息队列或延迟队列)delayedQueue.add(()->redisTemplate.delete(key),500);// 延迟 500ms}

原理:第一次删除后,在数据库更新完成前,可能有读请求将旧数据回填到缓存。延迟第二次删除确保这些旧数据被清除。

3.4 如何保证删除缓存操作一定能成功?

方案一:消息队列重试

publicvoiddeleteCacheWithRetry(Stringkey){try{redisTemplate.delete(key);}catch(Exceptione){mqProducer.send(newCacheDeleteMessage(key));}}

方案二:订阅 Binlog 补偿

@CanalListener(destination="mydb")publicvoidonBinlog(CanalEntry.Entryentry){if(entry.getHeader().getEventType()==EventType.UPDATE){Stringkey=buildCacheKey(entry);redisTemplate.delete(key);}}

3.5 生产推荐组合

Cache-Aside + 延迟双删 + Binlog 补偿

写操作:先删缓存 → 更新 DB → 延迟双删 ↓ Canal 监听 binlog → 异步补偿删除缓存 ↓ 最终一致性保障

四、缓存三大问题:雪崩、击穿、穿透

4.1 缓存雪崩

问题:大量 key 在同一时间过期或 Redis 故障,所有请求同时打到数据库。

解决方案

// 1. 随机过期时间intbaseTtl=3600;intrandomOffset=ThreadLocalRandom.current().nextInt(0,600);redisTemplate.opsForValue().set(key,data,Duration.ofSeconds(baseTtl+randomOffset));// 2. 多级缓存@Cacheable(value="local",cacheManager="caffeineCacheManager")@Cacheable(value="redis",cacheManager="redisCacheManager")// 3. 熔断降级@SentinelResource(value="getData",fallback="getDataFallback")

4.2 缓存击穿

问题:热点 key 恰好过期,高并发请求瞬间穿透到数据库。

解决方案

// 互斥锁publicStringgetHotData(Stringkey){Stringdata=redisTemplate.opsForValue().get(key);if(data!=null)returndata;StringlockKey="lock:"+key;booleanlocked=redisTemplate.opsForValue().setIfAbsent(lockKey,"1",Duration.ofSeconds(10));if(locked){try{data=redisTemplate.opsForValue().get(key);if(data==null){data=db.query(key);redisTemplate.opsForValue().set(key,data,Duration.ofHours(1));}}finally{redisTemplate.delete(lockKey);}}else{Thread.sleep(100);returngetHotData(key);}returndata;}

4.3 缓存穿透

问题:查询不存在的数据,请求直接打到数据库。

解决方案

// 布隆过滤器BF.ADDusers user:1001BF.EXISTSusers user:1003// 返回 0 → 一定不存在

五、Redis 延迟队列实现

延迟队列是一种特殊的消息队列,消息在发送后不会立即被消费,而是延迟指定时间后才能被处理。

5.1 基于 ZSet 的实现

@ServicepublicclassRedisDelayQueue{@AutowiredprivateStringRedisTemplateredisTemplate;// 添加延迟任务publicvoidaddTask(StringtaskId,longdelaySeconds){longexecuteTime=System.currentTimeMillis()+delaySeconds*1000;redisTemplate.opsForZSet().add("delayed_queue",taskId,executeTime);}// 轮询消费到期任务@Scheduled(fixedRate=1000)publicvoidconsume(){longnow=System.currentTimeMillis();Set<String>tasks=redisTemplate.opsForZSet().rangeByScore("delayed_queue",0,now,0,1);for(StringtaskId:tasks){Longremoved=redisTemplate.opsForZSet().remove("delayed_queue",taskId);if(removed!=null&&removed>0){executeTask(taskId);}}}}

5.2 适用场景

场景说明
订单超时关闭订单创建后 30 分钟未支付自动关闭
定时提醒预约成功后 1 小时发送提醒
任务重试失败任务延迟 5 分钟后重试
优惠券过期优惠券到期前 1 天发送提醒

六、Pipeline 与事务的区别

6.1 Pipeline 的核心特点

  • 客户端功能:批量打包命令,减少网络 RTT
  • 非原子性:命令可被其他客户端插入
  • 单条失败继续:某条命令失败不影响后续执行
  • 适用场景:批量数据操作、高吞吐场景
Pipelinepipeline=jedis.pipelined();for(inti=0;i<10000;i++){pipeline.set("key:"+i,"value"+i);}List<Object>results=pipeline.syncAndReturnAll();

6.2 事务的核心特点

  • 服务端功能:MULTI/EXEC 保证命令原子执行
  • 原子性:EXEC 后所有命令按顺序执行,不可插入
  • 语法错误全失败:入队时发现语法错误,EXEC 全部放弃
  • 运行时错误不回滚:执行时某条失败,后续继续执行
  • 适用场景:原子性操作、乐观锁并发控制
MULTI SET balance:10011000DECRBY balance:1001100EXEC

6.3 核心差异对比

维度Pipeline事务
原子性✗ 非原子✓ 原子执行
隔离性✗ 可被插入✓ 天然隔离
错误处理单条失败继续语法错误全失败
网络开销批量减少 RTT正常 RTT
性能更高较低

6.4 为什么优先使用 Lua 脚本?

-- Lua 脚本:原子性 + 灵活性 + 高性能localstock=redis.call('GET',KEYS[1])iftonumber(stock)>=tonumber(ARGV[1])thenredis.call('DECRBY',KEYS[1],ARGV[1])return1elsereturn0end

Lua 脚本的优势

  • 原子性:整个脚本作为一个命令执行,不会被其他命令插入
  • 灵活性:支持复杂逻辑判断和计算
  • 高性能:一次网络往返,减少 RTT

七、Redis 事务 vs MySQL 事务

7.1 核心差异

维度Redis 事务MySQL 事务
原子性部分原子:语法错误全失败,运行时错误不回滚完全原子:全部成功或全部回滚
隔离性天然串行化(单线程)多级别隔离(RU/RC/RR/S)
持久性依赖 RDB/AOF 配置Redo Log 保证提交不丢失
回滚机制无回滚支持 Rollback
锁机制乐观锁(WATCH)悲观锁(表锁/行锁)
复杂度简单,命令批量执行复杂,支持嵌套/保存点
适用场景高并发简单操作强一致业务场景

7.2 为什么 Redis 事务不支持回滚?

  1. 错误类型:Redis 事务错误通常是编程错误(命令拼写、参数类型),开发环境可发现
  2. 设计哲学:追求简单和快速,回滚增加复杂度
  3. 替代方案:可用 DISCARD 主动放弃,或用 Lua 脚本实现复杂逻辑

八、综合对比表

机制核心问题最佳方案关键细节
分布式锁跨节点互斥访问Redisson 看门狗原子加锁 + 自动续期
缓存一致DB 与缓存不一致Cache-Aside + 延迟双删先更新 DB 再删缓存
缓存雪崩大量 key 同时失效随机 TTL + 多级缓存分散过期时间
缓存击穿热点 key 过期互斥锁 + 逻辑不过期控制并发重建
缓存穿透查询不存在数据布隆过滤器 + 空值缓存拦截非法请求
延迟队列定时触发任务ZSet + 轮询消费score 存储执行时间
Pipeline批量命令优化客户端打包减少网络 RTT
事务原子性操作MULTI/EXEC/WATCH单线程天然隔离

如果本文对你有帮助,欢迎点赞 👍 + 收藏 ⭐ + 关注 🔖,你的支持是我持续创作的动力!

http://www.rkmt.cn/news/1543323.html

相关文章:

  • ZigBee Green Power技术解析:实现物联网设备零功耗通信的工程实践
  • 国内主流隔膜泵厂家实测排行 聚焦耐腐性与适配性 - 奔跑123
  • 2026氮气分析仪/氮气品质检测仪/高纯氮检测仪源头生产厂家优选:整机质检严格运行故障率更低 - 品牌推荐大师
  • 终极Windows 11界面修复指南:三步恢复经典开始菜单磁贴
  • 2026年10款论文AI智能降重工具实测:从90%降至10%的靠谱之选 - 降AI小能手
  • ZigBee 3.0网络开发实战:从协议栈初始化到节点通信全解析
  • # 小程序 form 表单完整讲解
  • 闲置包包放一年贬值一半?2026郑州出手黄金时间段别错过 - 奢侈品回收评测
  • JoyBuilder首批接入!智谱GLM-5.2正式上线京东云
  • 广东女子职业技术学院周边正规驾校排行实测 - 奔跑123
  • 招投标必读:一体化预制泵站、一体化污水提升泵站、一体式泵站核心参数与选型指南 - 泵站19832680777
  • Python 数据容器详解,list、tuple、str、set、dict 到底怎么选
  • 承德工伤维权索赔太难怎么办?2026年这5位专业律师推荐 - 本地品牌推荐
  • 2026年口碑好的 权威推荐 国内宋式美学家具品牌、北美黑胡桃木家具源头厂家排行:5家原创品牌深度盘点 - 奔跑123
  • 2026副主任医师考前一个月,内科学高频易错题精讲课TOP对比盘点! - 医考机构品牌测评专家
  • 从选样本到模型训练的完整指南
  • 基因笑传之测测 Bovine
  • 2027主管护师考试哪个机构押题准?实测盘点! - 医考机构品牌测评专家
  • 2026年6月 最新推荐 茶叶品牌加盟总部、茶叶加盟哪家好?行业标杆名录一览 - 奔跑123
  • 2026年天津武清工程机械租赁推荐:5家配套齐全的服务商 - 本地品牌推荐
  • AI时代的到来,外贸网站优化该怎么办?
  • 湖南马上学教育怎么样 值不值得推荐 零基础择校权威参考指南 - 讲清楚了
  • 2026年工业辊道窑选型必读:从科研实验到规模量产,适配厂家一键查询 - 品牌推荐大师1
  • 2026太仓全域空调维修实测推荐榜|本地人实测避雷,空调维保首选 - 星际AI
  • 医考顺利上岸,过来人分析各家医考机构真实通过率! - 医考机构品牌测评专家
  • 卫生副高职称考试前,全科医学押题冲刺课哪家口碑好?过来人实测 - 医考机构品牌测评专家
  • 2026专属测评,苦恼怎么把论文ai率从80%降到个位数?5款主流降ai率工具实测来了! - 殷念写论文
  • 2026年 抽屉盒机/抽屉盒成型机 源头厂家推荐:高效精准成型与耐用核心优势深度解析 - 品牌发掘
  • 选阔叶黄檀定制,90%人都找错工厂 - 新闻快传
  • 药事管理与法条太多记不住?盘点主任药师冲刺好用的快速记忆课! - 医考机构品牌测评专家