恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

防止缓存击穿的几种方案

  • 首页
  • 资讯中心
  • /
  • 防止缓存击穿的几种方案

相关资讯

换台不卡顿:这款Android原生电视直播软件,让老机顶盒也能流畅看直播 2026/8/17 21:02:33
5分钟上手DeepL翻译插件:用开源Chrome划词翻译扩展告别来回切换 2026/8/17 20:57:32
TranslucentTB 快速上手:5 分钟搞定 Windows 任务栏透明化 2026/8/17 20:57:32

最新资讯

字符串哈希核心原理与实战:从Rabin-Karp算法到工程优化
Dell Precision 7820工作站安装Windows 7全攻略:驱动注入与UEFI配置详解
纯Python实现条形码生成:Code 128与EAN-13编码原理与工程实践
UUIDv7 终于标准化了!Go 开发者为什么要关心这个“带时间戳的 UUID“
网盘直链下载助手使用指南:八大网盘直链获取,从此告别龟速下载
macOS预览应用:被低估的瑞士军刀,图片与PDF高效处理全攻略

今日推荐

数据缺失处理:从MCAR、MAR到MNAR的机制解析与多重插补实践
MAGS-SLAM:多智能体协同3D高斯泼溅SLAM系统解析
LLM智能体记忆管理:基于关键词门控的混合激活机制CAMeR详解

本周热门

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码
【双层规划,节点出清价,绿证交易,CVaR方法】两级电力市场环境下计及风险的省间交易商最优购电模型附Matlab代码
隐式mpc+自适应mpc+时变mpc,线性时变模型预测控制附Simulink仿真

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

防止缓存击穿的几种方案

发布时间:2026/8/17 21:02:33
防止缓存击穿的几种方案 防止缓存击穿的几种方案每个 key 分别加分布式锁逻辑过期异步更新缓存永不过期监听数据库 binlog 更新缓存使用本地锁而不是分布式锁缓存击穿指的是热点 key 过期导致大量请求都直接访问数据库数据库的压力剧增甚至直接导致数据库宕机。每个 key 分别加分布式锁先查缓存如果缓存存在则直接返回不存在则加分布式锁保证一个 key 只有一个线程处理加锁后再次检查缓存是否存在存在则直接返回不存在则从数据库查询数据然后填充缓存并返回最后解锁。加锁后再次检查缓存是否存在是为了防止其他等待线程拿到锁后重复查询数据库导致数据库的压力剧增。适用于对数据一致性要求较高的场景。packageexample;importlombok.RequiredArgsConstructor;importorg.redisson.api.RBucket;importorg.redisson.api.RLock;importorg.redisson.api.RedissonClient;importorg.springframework.stereotype.Service;importjava.util.concurrent.TimeUnit;RequiredArgsConstructorServicepublicclassDistributedLockCacheService{privatefinalRedissonClientredissonClient;publicStringget(Stringkey){// get cacheRBucketStringcacheredissonClient.getBucket(key);Stringdatacache.get();if(data!null){returndata;}RLocklockredissonClient.getLock(lock:key);booleanlocked;try{// 加分布式锁lockedlock.tryLock(3L,TimeUnit.SECONDS);}catch(InterruptedExceptione){Thread.currentThread().interrupt();thrownewRuntimeException(获取锁被中断,e);}if(!locked){thrownewRuntimeException(获取锁超时);}try{// double checkdatacache.get();if(data!null){returndata;}datagetFromDatabase(key);// set cache、expirecache.set(data,30,TimeUnit.MINUTES);returndata;}finally{lock.unlock();}}privateStringgetFromDatabase(Stringkey){returnxx;}}逻辑过期异步更新缓存缓存不设置过期时间而是在数据中添加一个逻辑过期字段。查询缓存如果缓存为空必须使用分布式锁方案填充缓存不能使用异步更新缓存方案因为可能多个线程都发现缓存为空会顺序多次重复更新缓存。另一个原因是异步更新缓存方案只能返回旧数据这里只能返回null用户体验不好。如果缓存不为空则查看缓存的逻辑过期时间是否已过期如果已过期则需要提交异步任务更新缓存。最后返回旧数据。适用于对数据一致性要求不高的场景。packageexample;importlombok.RequiredArgsConstructor;importorg.redisson.api.RBucket;importorg.redisson.api.RLock;importorg.redisson.api.RedissonClient;importorg.springframework.stereotype.Service;importjava.util.concurrent.ThreadPoolExecutor;RequiredArgsConstructorServicepublicclassLogicalExpireCacheService{privatefinalRedissonClientredissonClient;privatefinalDistributedLockCacheServicedistributedLockCacheService;privatefinalThreadPoolExecutorthreadPoolExecutor;publicStringget(Stringkey){// get cacheRBucketStringcacheredissonClient.getBucket(key);StringcacheDatacache.get();if(cacheDatanull){// 缓存为空使用分布式锁方案填充缓存returndistributedLockCacheService.get(key);}else{// 0: value, 1 expireAtString[]parsedCacheDataparseCacheData(cacheData);// 缓存已过期if(isExpired(parsedCacheData[1])){// 异步更新缓存updateCacheAsync(cache,key);}// 返回旧数据returnparsedCacheData[0];}}privatevoidupdateCacheAsync(RBucketStringcache,Stringkey){// 提交异步任务更新缓存threadPoolExecutor.execute(()-{RLocklockredissonClient.getLock(lock:key);// 加分布式锁注意这里加锁不需要 timeout 阻塞等待加锁失败说明其他线程正在更新缓存if(lock.tryLock()){try{StringcacheDatacache.get();String[]parsedCacheDataparseCacheData(cacheData);// 加锁后再次检查是否过期防止重复更新if(isExpired(parsedCacheData[1])){StringdatagetFromDatabase(key);// set cachecache.set(buildCacheData(data,System.currentTimeMillis()));}}finally{// 解锁lock.unlock();}}});}privateString[]parseCacheData(StringcacheData){intindexcacheData.lastIndexOf(:);returnnewString[]{cacheData.substring(0,index),cacheData.substring(index1)};}privatebooleanisExpired(StringexpireAt){// 缓存已过期returnLong.parseLong(expireAt)System.currentTimeMillis();}privateStringbuildCacheData(Stringdata,LongexpireAt){returndata:expireAt;}privateStringgetFromDatabase(Stringkey){returnxx;}}以下是我之前的错误实现加锁和解锁不在同一个线程导致永远无法解锁千万不要这样写privatevoidupdateCacheAsync(RBucketStringcache,Stringkey){RLocklockredissonClient.getLock(lock:key);// 加分布式锁注意这里加锁不需要 timeout 阻塞等待加锁失败说明其他线程正在更新缓存if(lock.tryLock()){// 提交异步任务更新缓存threadPoolExecutor.execute(()-{try{StringdatagetFromDatabase(key);// set cachecache.set(buildCacheData(data,System.currentTimeMillis()));}finally{// 解锁lock.unlock();}});}}永不过期监听数据库 binlog 更新缓存缓存不设置过期时间如果缓存为空使用分布式锁方案兜底监听数据库 binlog 实时更新缓存。数据一致性很高。使用本地锁而不是分布式锁对于机器数较少、并发不高的场景我个人认为可以使用本地锁代替分布式锁能够减少分布式锁的性能损耗代价是 key 不在缓存中时重复填充缓存的次数最多等于机器数。packageexample;importlombok.RequiredArgsConstructor;importorg.redisson.api.RBucket;importorg.redisson.api.RedissonClient;importorg.springframework.stereotype.Service;importjava.util.concurrent.ConcurrentHashMap;importjava.util.concurrent.TimeUnit;RequiredArgsConstructorServicepublicclassLocalLockCacheService{privatefinalRedissonClientredissonClient;privatefinalConcurrentHashMapString,ObjectlocksnewConcurrentHashMap();publicStringget(Stringkey){// get cacheRBucketStringcacheredissonClient.getBucket(key);Stringdatacache.get();if(data!null){returndata;}// 类似 redis 的 set nxObjectlocklocks.computeIfAbsent(key,k-newObject());synchronized(lock){// double checkdatacache.get();if(data!null){returndata;}datagetFromDatabase(key);// set cache、expirecache.set(data,30,TimeUnit.MINUTES);// 删除锁对象防止 locks 导致 OOMlocks.remove(key,lock);}returndata;}privateStringgetFromDatabase(Stringkey){returnxx;}}

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号