首页 缓存常见问题与解决方案
文章
取消

缓存常见问题与解决方案

一. 缓存穿透

查询一个数据,缓存里面没有,数据库里面也没有, 造成严重的性能问题, 这个就叫做缓存穿透

解决方案:

这里讨论的解决方案都是为了保护后方的数据库

  1. 解决那些永远不可能有值的查询:做参数校验, 避免产生一些永远不可能值的查询
  2. 过了参数校验,但是依然是两边都没有值的情况, 这种可以将key对应的值设置为空,但是过期时间要设置短一点,避免后面新插入的没及时更新到缓存
  3. 过了参数校验,但是依然两边都没有值的情况,但是进一步地不想再多一次redis的网络开销影响性能, 可以使用布隆过滤器,表示可能存在或者一定不存在这个key在缓存和数据库中。

布隆过滤器优化:

布隆过滤器主要的缺点是误判,比如在缓存和数据库中已经删除了, 但是布隆过滤器依然可能会放行,这里不能做到绝对的屏蔽所有不存在的key流量,但是可以尽可能地减少这部分的流量。

  1. 为布隆过滤器的每个bit添加计数器, 删除一个key的时候计数器减1, 增加映射的时候加1, 减到0的时候就可以把这个bit置为0;
  2. 为布隆过滤器添加白名单或黑名单,当布隆过滤器放行了一个key,但是实际是不存在的时候添加黑名单。

二. 缓存击穿

某个key的访问量很大,比如秒杀活动, 这个key在某一个时刻过期, 然后所有的请求一瞬间都到数据库了

解决方案:

  1. 热点数据永不过期
  2. 查询缓存不加锁, 业务查询数据库加锁,在发现缓存失效后, 第一个查询数据库的线程进行加互斥锁查询,等其返回了以后再释放锁。

三. 缓存雪崩

某个时间多个key失效或者缓存服务器宕机,这样大量的请求查询不到缓存,流量都打到了数据库里面

解决方案:

  1. 设置随机的缓存过期时间
  2. 搭建缓存的高可用, 例如redis-cluster

四. 双写不一致

  1. 查询数据流程,先查缓存,没有再查数据库,然后再插入缓存中。
  2. 更新数据流程,先删缓存,再更新数据库。

更新缓存的方式:

  1. 先更新数据库,再更新缓存,可能出现的问题,数据库更新成功,缓存更新失败,看价格是一个价格,下单又是另外一个价格,更新操作敏感,不好进行重试。
  2. 先删除缓存,再更新数据库,可能出现的问题,更新的线程更新完了,但是另一个线程再查询的时候发现数据被删了,然后查数据库更新,这个时候更新线程还没更新完, 于是缓存的数据依然是旧的
  3. 先更新数据库,在删除缓存,这样能保证在更新完数据库后, 所有的线程的动作都是一样的,而且即便是删除失败,也可以放心地做重试操作。

五. 热点key问题

当一个key处于被访问得过于频繁的时候, 频繁到超过单server的物理网卡极限,达到类似于发动机过热无法工作的现象。可能造成的后果:

  1. 网卡满载, 请求过多,缓存分片服务被打垮
  2. 缓存满载,DB被击穿

解决方案:

  1. 本地做一级缓存,实现一个LRUCache,或者使用memcache或者ehcache

    本地缓存需要解决的问题:

    1. 本地缓存的脏读问题,如何感知同步更新数据,不一致时间误差多少
    2. 由于本地缓存的容量小,所以只能缓存相对应的热点数据, 那么这个热点数据如何发现,有什么策略
  2. 如何发现热点key

    1. 凭借业务经验进行预估,缺点:有一些热点数据是无法预估的, 例如用户喜好的推荐
    2. 客户端进行统计收集,简单快速。缺点:缺点,有代码入侵
    3. 在proxy层进行统计处理,优点:无入侵,缺点:开发难度大
    4. 使用redis自带命令进行实时统计,例如monitor命令可以

缓存上能做的事情:

  1. sdk方式本地集成方案一级本地, 二级redis,三级db,提供统一封装接口,本地通过LRU计算缓存热点key数据。由解决方案统一
  2. 单独proxy,实现redis协议,在proxy中实现sdk中的功能,这样子的优势是读客户端无感,可以像操作原生redis一样操作数据。

cachesdk:

热点数据的发现:

  1. 应用通过接口存取数据的时候, 注册一条信息给sdk, 这个注册动作是异步的,不影响应用的执行速度

  2. Sdk提供回调接口注册,当sdk发现热点数据的时候,进行回调通知应用热点数据,或者直接在自己内部集成热点数据

参考:

高并发百万级热点key处理方案

本文由作者按照 CC BY 4.0 进行授权

Antlr入门

ruby安装