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

西乡城建局网站seo公司费用

西乡城建局网站,seo公司费用,光谷做网站,沧州做网站哪家好引言 作为Java开发中常用的ORM框架,MyBatis的缓存机制(一级缓存、二级缓存)能显著提升查询效率,但实际使用中,“缓存穿透”问题却像个隐藏的“坑”——明明查的是数据库里没有的数据,请求却像潮水一样反复冲…

引言

作为Java开发中常用的ORM框架,MyBatis的缓存机制(一级缓存、二级缓存)能显著提升查询效率,但实际使用中,“缓存穿透”问题却像个隐藏的“坑”——明明查的是数据库里没有的数据,请求却像潮水一样反复冲向数据库,轻则增加DB压力,重则导致DB崩溃。

今天这篇文章,笔者将从原理到解决方案,带大家彻底搞懂MyBatis缓存穿透!

一、什么是缓存穿透?MyBatis场景下的典型表现

1. 缓存穿透的定义

缓存穿透是指:客户端查询一个数据库中不存在的数据,由于缓存中也无该数据记录,请求绕过缓存直接访问数据库。如果这类无效请求高频发生(比如恶意攻击或参数错误),数据库可能被压垮。

2. MyBatis中的特殊场景

MyBatis的一级缓存(SqlSession级别)和二级缓存(Mapper级别)对“空值”的处理差异,是导致穿透的关键:

  • 一级缓存:默认会缓存null结果(同一SqlSession中重复查询会直接命中缓存)。但如果SqlSession关闭(比如HTTP请求结束),缓存失效,下次请求仍会穿透。
  • 二级缓存:默认不缓存null结果(比如PerpetualCache实现)。即使第一次查询返回null,缓存中也不会存储该键,后续相同查询依然会穿透到数据库。

举个真实例子
之前做用户系统时,前端传了一个id=-1的查询请求(业务中id是自增正整数)。第一次查询时,MyBatis二级缓存没命中,查数据库返回null,但缓存不存null;第二次同样传id=-1,又穿透到数据库……由于前端埋点错误,这个无效请求被高频触发,DB瞬间压力飙升!

二、MyBatis缓存穿透的根因分析

1. 二级缓存不缓存null值(核心原因)

MyBatis二级缓存的默认实现(如PerpetualCache)设计逻辑是“只缓存有效数据”,数据库查不到的结果不会存入缓存。这意味着,同一个无效id的多次查询,每次都会绕过缓存直接打DB

2. 数据动态变化导致缓存失效

即使缓存了有效数据,若数据被删除(如用户注销),缓存会被清除。此时查询已删除的id(数据库无记录),又会触发穿透。

3. 恶意攻击或参数错误

  • 恶意用户故意传入不存在的id(如id=0、超大数值)。
  • 前端/客户端生成参数时逻辑错误(如循环递增id,超出数据库最大值)。

三、实战解决方案:从简单到进阶

针对MyBatis缓存穿透,需要“多层防御”——从缓存策略、前置校验到流量控制,逐层拦截无效请求。以下是我在实际项目中验证过的有效方案:

方案1:缓存空值(Null Caching)—— 最直接的拦截

核心思路:将数据库查询结果为null的键存入缓存(标记为“不存在”),后续相同请求直接从缓存获取,避免穿透。

实现步骤(以Redis为二级缓存为例)
  1. 配置MyBatis使用Redis缓存
    引入mybatis-redis依赖,在Mapper接口上添加@CacheNamespace注解,指定自定义的Redis缓存实现。

    @CacheNamespace(implementation = CustomRedisCache.class)
    public interface UserMapper {User selectById(Long id); // 查询方法
    }
    
  2. 自定义Redis缓存类,处理空值
    重写putObjectgetObject方法,将null结果序列化为特定标识(如"NULL")存入Redis,并设置短过期时间(防止脏数据)。

    public class CustomRedisCache implements Cache {private final RedisTemplate<String, Object> redisTemplate;private static final long NULL_TTL = 300; // 空值缓存5分钟@Overridepublic void putObject(Object key, Object value) {String cacheKey = "mybatis:cache:" + key.toString(); // 自定义缓存键格式if (value == null) {// 存入"NULL"标识,替代nullredisTemplate.opsForValue().set(cacheKey, "NULL", NULL_TTL, TimeUnit.SECONDS);} else {redisTemplate.opsForValue().set(cacheKey, value);}}@Overridepublic Object getObject(Object key) {String cacheKey = "mybatis:cache:" + key.toString();Object value = redisTemplate.opsForValue().get(cacheKey);// 若缓存值为"NULL",返回null;否则返回实际值return "NULL".equals(value) ? null : value;}// 其他方法(如removeObject、getSize等)按需实现...
    }
    
  3. 验证效果
    第一次查询id=-1时,数据库返回null,缓存存入"NULL";后续相同查询直接从缓存获取null,不再穿透DB。

方案2:布隆过滤器(Bloom Filter)—— 海量数据的前置拦截

核心思路:在查询数据库或缓存前,先用布隆过滤器判断id是否存在。若不存在,直接返回,避免访问缓存和DB。

实现步骤(基于Guava BloomFilter)
  1. 初始化布隆过滤器
    启动时加载所有有效id到布隆过滤器(适用于数据量稳定场景,如用户表、商品表)。

    @PostConstruct // Spring启动后初始化
    public void initBloomFilter() {// 预期插入100万条数据,误判率1%BloomFilter<Long> bloomFilter = BloomFilter.create(Funnels.longFunnel(), 1_000_000, 0.01);// 从数据库加载所有有效id(如用户表的id)List<Long> allUserIds = userMapper.selectAllIds();allUserIds.forEach(bloomFilter::put);// 将布隆过滤器注入到Service中this.bloomFilter = bloomFilter;
    }
    
  2. 在Service层拦截无效请求
    查询前通过布隆过滤器校验id是否存在,不存在则直接返回null(或抛异常)。

    public User getUserById(Long id) {// 布隆过滤器前置校验if (!bloomFilter.mightContain(id)) {log.warn("拦截无效id:{}", id);return null;}// 命中缓存或数据库return userMapper.selectById(id);
    }
    

注意:布隆过滤器存在误判率(可能将不存在的id判定为存在),因此即使校验通过,仍需查询缓存/DB二次验证;若数据动态变化(如新增id),需定期更新布隆过滤器(可通过定时任务重新加载全量数据)。

方案3:热点参数校验+限流—— 拦截恶意请求

核心思路:针对已知非法参数(如负数、超长id)或高频无效参数,在网关或Service层添加校验,直接拦截。

实现示例
public User getUserById(Long id) {// 校验1:id必须为正数(业务逻辑约束)if (id == null || id <= 0) {log.warn("非法id请求:{}", id);return null;}// 校验2:id不能超过数据库最大可能值(如MySQL的BIGINT最大值)if (id > Long.MAX_VALUE - 1000) { log.warn("id超出合理范围:{}", id);return null;}// 正常查询逻辑...
}

扩展:结合Sentinel等限流工具,对特定无效id(如id=0)的请求限流,防止恶意攻击。

方案4:分布式锁—— 防缓存击穿(穿透的极端场景)

核心思路:当缓存未命中时,仅允许一个线程查询数据库,其他线程等待结果,避免大量线程同时穿透到DB(适用于高并发热点id场景)。

实现步骤(基于Redis分布式锁)
public User getUserById(Long id) {String cacheKey = "user:cache:" + id;// 1. 先查缓存User user = redisTemplate.opsForValue().get(cacheKey);if (user != null) {return user;}// 2. 缓存未命中,尝试加锁(防止大量线程同时查DB)String lockKey = "user:lock:" + id;boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS); // 锁10秒if (!locked) {// 加锁失败,等待100ms后重试(避免无限重试)try {Thread.sleep(100);return getUserById(id); // 递归重试(实际项目中建议限制重试次数)} catch (InterruptedException e) {Thread.currentThread().interrupt();return null;}}try {// 3. 加锁成功,查数据库user = userMapper.selectById(id);// 4. 回写缓存(缓存空值或有效数据,设置合理TTL)redisTemplate.opsForValue().set(cacheKey, user != null ? user : "NULL", user != null ? 3600 : 300, // 有效数据缓存1小时,空值缓存5分钟TimeUnit.SECONDS);return user;} finally {// 5. 释放锁(Lua脚本保证原子性)redisTemplate.execute(new DefaultRedisScript<>("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", Long.class), Collections.singletonList(lockKey), "1");}
}

四、方案对比与选择建议

方案优点缺点适用场景
缓存空值实现简单,快速拦截重复无效请求需合理设置TTL,可能存储短时间脏数据无效参数固定、数据量小
布隆过滤器内存占用低,适合海量数据存在误判,需二次校验数据量大、动态变化
热点参数校验逻辑简单,拦截明显非法请求无法处理合法但数据库无记录的id已知非法参数范围
分布式锁彻底避免缓存击穿(穿透的极端情况)实现复杂,可能影响性能高并发、热点id场景

总结:多层防御,让缓存穿透无处可逃

MyBatis缓存穿透的本质是“无效请求绕过缓存直连DB”,解决思路是拦截无效请求+减少无效查询。实际项目中,建议:

  1. 优先用缓存空值拦截重复请求(简单高效);
  2. 海量数据场景补充布隆过滤器前置校验;
  3. 恶意请求用参数校验+限流精准打击;
  4. 高并发热点场景用分布式锁防击穿。

记住:没有完美的方案,只有最适合业务的组合!

如果你在实际项目中遇到过更复杂的缓存穿透问题,欢迎在评论区留言讨论~ 😊

http://www.dtcms.com/wzjs/109421.html

相关文章:

  • ui培训机构哪家好seo技术培训教程视频
  • 哪一个网站有做实验的过程如何百度收录自己的网站
  • 西楚房产网宿迁房产网搜索引擎优化的主要工作有
  • 汽车门户网站有哪些游戏推广在哪里接活
  • dw如何做网站后台谷歌搜索优化
  • 山东泰安最新疫情seo云优化
  • 百度站长提交网站地图怎么在百度上发布自己的信息
  • 货到付款网站建设阿里巴巴seo排名优化
  • 中铁建工集团有限公司官网网站seo关键词排名优化
  • 石家庄建设公司网站在线crm网站
  • 江苏省建设工程施工安全网站免费推广网
  • 网站seo方案设计seo网站关键词优化哪家好
  • 做cpa色诱网站用什么域名空间建站舆情优化公司
  • 专业网站建抖音seo什么意思
  • 中医网站源码爱站网关键词长尾挖掘
  • 如何做网站安全扫描seo站长论坛
  • 一个虚拟主机空间里放多个独立网站的方法官网关键词优化价格
  • 新昌县住房和城乡建设局网站百度热门排行榜
  • 网站开发和java哪个工资高免费google账号注册入口
  • 免费商标设计软件石家庄seo网络推广
  • e特快做单子的网站sem是什么品牌
  • 怎么做网站图片seo如何制作小程序
  • 网站建设技术规范及要求苏州百度推广分公司电话
  • 安徽省住房和城乡建设厅网站域名谷歌官方app下载
  • 保安网站建设百度seo引流怎么做
  • 大型网站建设平台百度广告投放平台叫什么
  • 汉口做网站jw100百度导航2023年最新版
  • 2013网站建设方案优化网站搜索排名
  • 查钓鱼网站google seo怎么优化
  • 专业网站建设怎么样搜索引擎优化策略