面试题答案
一键面试保证缓存与数据库一致性
- 读写策略
- 读操作:先从缓存读取数据,若缓存命中则直接返回;若缓存未命中,从数据库读取数据,然后将数据写入缓存,并返回给客户端。这样可以尽量减少对数据库的直接读取压力。
- 写操作:
- 先更新数据库,再删除缓存:这是较为常用的方法。先确保数据库中的数据是最新的,然后删除缓存,下次读取时就会从数据库重新加载最新数据到缓存。但存在并发问题,比如在删除缓存前,另一个线程读取了旧的缓存数据,不过这种情况概率相对较小。
- 先删除缓存,再更新数据库:可能会出现数据库更新失败,但缓存已删除,导致数据不一致问题,所以需要结合重试机制或事务来确保数据库更新成功。
- 缓存版本控制:给缓存数据设置版本号,每次数据库更新时,同时更新版本号。读取缓存时,不仅读取数据,还读取版本号,若发现版本号与预期不符,则重新从数据库加载数据并更新缓存和版本号。
减少缓存穿透问题
- 布隆过滤器:在查询数据前,先通过布隆过滤器判断数据是否存在。布隆过滤器可以快速判断一个元素是否在集合中,几乎不会漏判,但可能会误判。如果布隆过滤器判断数据不存在,就直接返回,不再查询数据库,从而避免大量无效查询穿透到数据库。
- 缓存空值:当查询数据库发现数据不存在时,也将空值缓存起来,并设置一个较短的过期时间,这样后续相同的查询就可以直接从缓存获取空值,而不会穿透到数据库。
减少缓存雪崩问题
- 设置不同过期时间:避免大量缓存同时过期。给缓存设置随机的过期时间,在一个合理的时间区间内波动,这样可以分散缓存过期的时间点,降低同一时间大量缓存失效的风险。
- 热点数据不过期:对于一些频繁访问的热点数据,不设置过期时间,或者通过后台线程定时更新缓存,保证数据的实时性。
- 构建多级缓存:比如采用本地缓存(如 Ehcache)和分布式缓存(如 Redis)相结合的方式。当分布式缓存出现雪崩时,本地缓存可以继续提供服务,减轻数据库压力,给系统恢复争取时间。
减少缓存击穿问题
- 互斥锁:在缓存失效的瞬间,只允许一个线程去查询数据库并更新缓存,其他线程等待。获取锁的线程查询数据库后更新缓存,然后释放锁,其他线程再从缓存获取数据。可以使用 Redis 的 SETNX 命令实现简单的互斥锁。
- 热点数据不过期:同缓存雪崩中热点数据不过期的方法,对于可能导致缓存击穿的热点数据,不设置过期时间,通过其他机制保证数据的实时性。