Skip to content

如何保证查询节目时避免缓存穿透?

建议小伙伴先要理解缓存穿透的概念,以及在用户服务中的注册用户业务时是怎样防止缓存穿透的,可跳转到下面的章节来学习

业务讲解-用户注册-如何巧妙应对缓存穿透

阅读过上面章节的小伙伴应该知道了 缓存穿透是指查询的数据在缓存和数据库中都不存在导致每次查询这条数据都会穿透过缓存,直接去查询数据库,相当于没有缓存一样。

而这种问题在查询节目详情时同样会存在,比如说某个黑客调用节目详情接口时,就会传入一个不存在的节目id,先查一遍缓存,缓存不存在则再去查询数据库,结果数据库也不存在,当并发高时,就会对数据库造成很大的压力

在注册用户功能中,我们结合了验证码和布隆过滤器的方案来解决缓存穿透,那么在查询节目详情功能中,应该用什么方案呢?我们从业务特点来入手:

正常用户查询节目详情时,都是事先查询了节目主页或者节目列表后,再来查询节目详情的,也就是说节目id时肯定存在缓存或者数据库中的,如果是缓存和数据库都不存在的节目id说明此id就是不正常的,服务收到了此id就应该直接拒绝,不再执行后续的流程

注意这里的关键点,如果判断节目id不存在,则说明此节目id肯定是不正常的,这是不是和布隆过滤器的判断特点非常的贴合!**布隆过滤器的特点就是如果判断某个元素不存在,那么说明此元素一定就不存在!**关于布隆过滤器的详细讲解,小伙伴可跳转下面章节进行学习

技术精华-完全解读布隆过滤器

所以这里使用布隆过滤器来判断是再适合不过了!这里我们直接使用封装好的布隆过滤器组件来实现功能。

还有个问题?此逻辑属于是在查询节目详情之前的验证逻辑,而且项目中的查询逻辑有v1和v2版本,对于将具体查询逻辑分离以及进行验证复用,所以这里使用了验证功能组件来实现此验证功能

此验证组件是使用了组合模式和树形结构来将业务的验证逻辑进行复用并且串联起来按照树形结构执行

关于验证组件的详细介绍,小伙伴可跳转到相应章节进行学习

组件讲解-利用组合模式打造强大验证功能,轻松应对复杂验证需求

配置

yaml
bloom-filter:
  name: program-detail-bloom-filter
  expectedInsertions: 1000
  falseProbability: 0.01

控制层

java
@ApiOperation(value = "查询详情(根据id)")
@PostMapping(value = "/detail")
public ApiResponse<ProgramVo> getDetail(@Valid @RequestBody ProgramGetDto programGetDto) {
    return ApiResponse.ok(programService.detail(programGetDto));
}
java
public ProgramVo detail(ProgramGetDto programGetDto) {
    //查询节目id是否存在布隆过滤器中
    compositeContainer.execute(CompositeCheckType.PROGRAM_DETAIL_CHECK.getValue(),programGetDto);
    //验证通过执行节目查询
    return getDetail(programGetDto);
}

验证的具体逻辑

java
@Component
public class ProgramBloomFilterCheckHandler extends AbstractComposite<ProgramGetDto> {
    
    @Autowired
    private BloomFilterHandler bloomFilterHandler;
    
    @Override
    protected void execute(final ProgramGetDto param) {
        boolean contains = bloomFilterHandler.contains(String.valueOf(param.getId()));
        if (!contains) {
            throw new DaMaiFrameException(BaseCode.PROGRAM_NOT_EXIST);
        }
    }
    
    @Override
    public String type() {
        return CompositeCheckType.PROGRAM_DETAIL_CHECK.getValue();
    }
    
    @Override
    public Integer executeParentOrder() {
        return 0;
    }
    
    @Override
    public Integer executeTier() {
        return 1;
    }
    
    @Override
    public Integer executeOrder() {
        return 1;
    }
}

可以看到这里是直接通过布隆过滤器来判断节目id是否存在的,但什么时候放进去的呢?

其实这里是在节目启动初始化的时候放入的,从数据库中查询出所有节目id然后放入布隆过滤器中,这么做也是为了方便小伙伴学习,节目启动后可以直接的来学习了。

而实际的生产环境肯定不会这么做,节目的数据上千万上亿,直接全部查询不现实,肯定是通过运营人员,添加好节目后,再添加到布隆过滤器中的,而这里的初始化流程同样值得小伙伴好好的学习,这里针对Spring的初始化不同策略进行封装成了组件,使用了抽象思想和设计模式,关于节目数据的初始化详细介绍和初始化组件的详细设计,小伙伴可跳转到相应章节

业务讲解-节目服务的数据初始化统一管理

更新: 2025-10-13 11:58:32
原文: https://www.yuque.com/u22210564/ykdrdh/lrh49w2qipnz0l72

Java 后端面试知识库