项目经验1:缓存穿透

线上系统报警,数据库QPS高,排查发现接口未走缓存,原因是请求数据在数据库不存在,缓存空结果无意义。提出对数据库查询结果为空也应缓存空对象的方法,还提及处理可能返回null方法的几种方式,如返回空集合、用注释提醒等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

昨天线上系统报警,发现数据库QPS很高。经过排查后,发现是有一个接口没有走缓存,每次请求都透到了数据库。于是,找到接口代码看了一下。大致如下:(只是个描述逻辑的代码)

CustomerObject obj = redisService.get("myKey");
if (obj == null) {
    obj = CustomerObjMapper.findBy(id);
    redisService.set(obj);
}

return obj;

乍一看代码没发现啥问题,也用了缓存。那为什么线上的请求都没没走缓存呢?抓取了线上的请求,测了一把。原来这个请求,数据库没有该数据,所以方法CustomerObjMapper.findBy直接返回了null。然后他其实就是对这个null做了缓存,那就等于没有做缓存了。当下次请求进来,redisService.get("myKey")肯定就是null。于是请求又到了数据库,最终导致数据库QPS上升。

其实,对于数据库没有数据的情况,我们也要把这个空结果缓存下来。毕竟数据库就是没有数据,你频繁查询数据库也是没有意义的。那对于这种数据库查询结果为空,怎么去做缓存呢?模板代码来了:

CustomerObject obj = redisService.get("myKey");
if (obj == null) {
    obj = CustomerObjMapper.findBy(id);
    // 创建一个空对象并缓存。
    if (obj == null) {
        obj = new CustomerObject();
    }
    redisService.set(obj);
}
// 通过Id区分对象是真正的数据对象,还是一个空对象。
if (obj.getId() == null) {
    retun null;
}

return obj;

如果dao返回null给我们,我们就创建一个空对象缓存下来,下次请求该方法时,就能从缓存中拿到这个空对象。然后判断一下Id是否有值。没有就说明这是一个空对象,我们可以直接reutrn null。如果不为空,说明拿到的对象就是真正的数据对像。

其实我想了想,这也是null一直被诟病的地方吧。null既可以表示对象没有赋值,也可以表示没有数据的情况。在平时写代码中,如何更好地去分开使用null的这2种情况,是需要注意的。一般来说,对于可能返回null的方法,我们平时有一下几种处理方法:

  1. 如果该方法返回的是集合类型,那么我们就返回一个空集合就好了。
  2. 如果想用null来表达没有数据的语义,那么可以直接返回null。但必须在方法的注释中提醒调用者,该方法会返回null,代表没有查询到相关数据。
  3. 定义一个特殊的空对象。该对象继承了返回的目标类。
  4. 使用Java8的Optional类。

大家可以阅读java匠人手法-优雅的处理空值,写得很详细。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值