缓存

1 缓存基础

1 缓存的读写模式

1.1 Cache Aside(旁路缓存)

  • 适合场景
    • 数据一致性要求高,缓存数据更新比较复杂的业务。
  • 缺点
    • 需要同时维护 缓存 和 DB 两个数据存储方,过于繁琐
  • 写操作
    • 先更新数据库,直接将key从缓存中删除,然后由数据库驱动缓存数据的更新。
      • 数据库驱动缓存的更新如
        • 使用一个 Trigger 组件,读取并解析mysql的binlog,然后进行一些业务逻辑处理,更新缓存数据
      • 可以先删除缓存,再更新数据,再队列方式公共更新缓存,去除重复。
  • 读操作
    • 先读缓存,如果缓存没有,则读数据库,同时将 数据库中读取的数据回写到缓存。

1.2 Read/Write Through(读写穿透)

  • 适合场景
    • 数据有冷热区分
  • 描述
    • 业务应用只关注一个存储服务即可,业务方的读写 cache 和 DB操作。 的操作,都由存储服务代理
  • 存储服务 写操作
    • 先查 缓存,
      • 缓存存在 先更新 缓存,再更新db
      • 缓存不存在 只更新db
  • 存储服务读
    • 先读缓存,缓存没有,读db,同时将db中数据回写到数据库。

1.3 Write Behind Caching(异步缓存写入)

  • 适合场景
    • 写频率超高,需要合并写请求的业务,一致性要求不高。
  • 缺点
    • 即数据的一致性变差,甚至在一些极端场景下可能会丢失数据,
  • 描述
    • 由数据存储服务来管理 cache 和 DB 的读写,数据更新 只更新缓存,不直接更新db,改为异步批量的方式更新db。
  • 存储服务 写操作
    • 先查 缓存,
      • 缓存存在 先更新缓存,异步批量更新db。
      • 缓存不存在 只更新db
  • 存储服务读
    • 先读缓存,缓存没有,读db,同时将db中数据回写到数据库。

1.4 好的db缓存方案

  1. 实时一致性方案

    • 采用“先写 MySQL,再删除 Redis”的策略,这种情况虽然也会存在两者不一致,但是需要满足的条件有点苛刻,所以是满足实时性条件下,能尽量满足一致性的最优解。
    • image.png
  2. 最终一致性方案

    • 采用“先写 MySQL,通过 Binlog,异步更新 Redis”,可以通过 Binlog,结合消息队列异步更新 Redis,是最终一致性的最优解。
    • 这种方案有个前提,查询的请求,不会回写 Redis。
    • image.png
  3. 先删除 Redis,再写 MySQL,再删除 Redis(负责方案不推荐) 缓存双删

    • image.png
    • “删除缓存 10”必须在“回写缓存10”后面,那如何才能保证一定是在后面呢?网上给出的第一个方案是,让请求 A 的最后一次删除,等待 500ms。完全不行。
    • image.png

3 缓存穿透,缓存击穿,缓存雪崩解决方案分析

可参考

1 缓存穿透

  • 含义: 查询一个一定不存在的数据,然后导致查数据库,导致DB挂掉,有人利用不存在的key频繁攻击我们的应用,这就是漏洞
  • 原因
    • 系统设计,更多考虑的是正常路径,对特殊访问路径考虑欠缺。
  • 解决方案:
    • 布隆过滤器,所有可能存在的key hash到一个足够大的bitmap中。不存在的key,被bitmap拦截掉。
      • 问题 如果数据量特别大,不合适
      • 解决
        • 10亿以内最佳(1.2GB),可以使用布隆过滤器
          • 10亿 用10倍大小的位图存储, 也就是 100 亿的二进制
        • 数据量过大,用布隆过滤器,缓存非法key
          • 会导致key持续高速增长
          • 要定期清零处理
    • 如果数据库查询为空(不管数据是不存在,还是系统故障),缓存都对空结果key缓存,过期时间会很短,最长不超过5分钟。
      • 问题 如果访问大量不存在key,也会占用大量存储空间
      • 解决
        • 设计的时间短一些,让它尽快过期
        • 设计一个独立的公共缓存非法key
          • 查询先查 正常缓存组件,如果没有再查非法key的缓存。
          • DB 查出来为空,就记录 非法key缓存

2 缓存雪崩

  • 问题描述 部分缓存节点不可用,导致整个缓存体系甚至服务系统不可用的情况。

  • 解决办法

    • 对业务db的访问增加读写开关
      • 当db 请求变慢,阻塞,慢请求超过阈值时,就关闭读开关。
      • 部分或所有读db的请求进行 failfast 立即返回,待db恢复后再打开读开关。
    • 对缓存增加多个副本
      • 缓存异常,或请求miss后,再读取其他缓存副本。
        • 多个副本 尽量部署在不同 机架,保证可用。
    • 对缓存体系进行实时监控
      • 当请求访问的慢速比超过阀值时,及时报警,通过机器替换、服务替换进行及时恢复;也可以通过各种自动故障转移策略,自动关闭异常接口、停止边缘服务、停止部分非核心功能措施,确保在极端场景下,核心功能的正常运行。
  • 实际方案

    • redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
    • 本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
    • redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
  • 含义: 某一时刻缓存全部失效,可能是 设置了相同的过期时间,可能redis挂掉

  • 解决方案:

    • 大多数 加锁,或者队列方式
    • 简单方案 在原有失效时间基础上增加一个随机值 比如1-5分钟随机。
    • 做好集群

3 缓存击穿

  • 含义 某一个key,在某个时间点被超高并发访问,恰好这个时间点过期了。 与雪崩区别这里 雪崩是很多key。
  • 解决方案:
    • 使用互斥锁 redis分布式锁
    • 设置永不过期
      • 如 我解析规则 一直不过期,如果修改了, 手动清除缓存。
      • 通过后台定时刷新,根据缓存失效时间节点去批量刷新缓存数据
        • 这个适合 Key 失效时间相对固定的场景。
    • hystrix 做资源隔离

4 如何保证缓存与数据库双写的一致性

    • 先读缓存
      • 缓存没有 读数据库,然后取出数据放入缓存,同时响应
  • 更新
    • 先更新数据库,然后删除缓存
      • 为什么是删除缓存,而不是更新缓存
        • 在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值
      • 问题:
        • 先更新数据库,删除缓存失败
          • 导致 数据库是新数据,缓存时旧的数据
        • 解决思路:
          • 先删除缓存,再更新数据库
            • 如果数据库修改失败,缓存时空的,数据库是旧的。
    • 最终 先删除缓存,再更新数据库
      • 问题:
        • 大并发,还没修改,但是缓存时空的,查到缓存时空的。
        • 解决思路
          • 还是要串行,考虑如何串行
            • 队列
              • 更新缓存 不管是读,还是写, 生成唯一标识,放入java队列中。
                • 队列还可以做过滤,相同的更新缓存没有意义。
                  • 如 写到缓存后,后面读 可以直接从缓存中拿。
            • 锁实现呢?

5 缓存失效

  • 因为很多key 设置了相同过期时间,导致一起失效
  • 解决
    • 过期时间=baes时间+随机时间

6 如何设计一个动态缓存热点数据的策略

  • 由于数据存储受限,系统并不是将所有数据都需要存放到缓存中的,而只是将其中一部分热点数据缓存起来
  • 策略思路
    • 判断数据最新访问时间来做排名,并过滤掉不常访问的数据,只留下经常访问的数据
      • 如商品数据
        • 先通过缓存系统做一个排序队列(如存放1000个商品),系统根据商品访问时间 正序排序
        • 同时缓存系统定期过滤掉最后 200 个商品, 再从数据中 随机读取200 个商品,加入队列。
        • 这样 请求每次到达的时候,先从队列获取商品ID,如果命中,再根据ID 从另一个缓存结构 读取商品信息读取实际的商品信息,并返回。

7 热key 问题

  • 问题

    • 热key,大流量,直接打到一个缓存机器,这个缓存机器很容易到达 CPU,网卡,带宽极限,从而导致缓存访问变慢。
  • 解决

    • 找到热key
      • 提前评估可能出现的热key
      • 突发的可以用 spark,flink 进行事实分析
      • 之前发生的,可以通过hadoop 离线计算。 找到最近历史数据中的热 key
    • 将热 key 分散,
      • 如 hotkey 被分散成 hotkey#1, hotkey#2, ... hotkeyn。 n 就是key分散的多个缓存节点。
      • 客户端访问 随机 hotkey 1-n 的后缀。
    • 也可以
      • key 名字不变,对缓存提前进行多副本,多级结合的缓存架构
      • 对缓存监控,快速扩容来减少热key的冲击
      • 业务端将热key记录在本地缓存。

8 大key 问题

  • 方案
    • 设计缓存阈值,当value 超过阈值,进行压缩
    • 大value 对于结构元素很多 如set, 可以进行序列化构建,通过restore 一次性写入。
    • 将大value拆分,,尽量减少大 key 的存在
    • 由于一些大key 一旦穿透到DB,加载耗时,可以设置过期时间长一些。

9 数据不一致

  • 问题

    • 如 更新 DB 后,写缓存失败,导致缓存的是老数据
    • 采用 rehash 自动漂移策略,多个副本数据不一致
  • 解决

    • 缓存更新失败以后,进行重试
      • 如果重试失败 ,缓存服务出了问题, 写入MQ服务, 缓存服务恢复,从MQ 删除。
    • 缓存设置较短的时间,让缓存及时过期。
    • 不采用rehash 飘逸策略,而采用缓存分层策略。

10 懒加载的缓存过期方案,性能毛刺 23讲搞定后台架构实战

  • 懒加载的缓过过期方案
    • Redis 作为主存储,MySQL 作为兜底来构建
    • 当读服务接受请求时,会先去缓存中查询数据,如果没有查询到数据,就会降级到数据库中查询,并将查询结果保存在 Redis 中,以供下一次请求进行查询。保存在 Redis 中的数据会设置一个过期时间,防止数据库的数据变更了,请求还一直读取缓存中的脏数据
  • 性能毛刺
    • 当缓存过期时,读服务的请求都会穿透到数据库中,对于穿透请求的性能和使用缓存的性能差距非常大,时常是毫秒和秒级别的差异。
  • 解决方案
    • 全量缓存(适合读类型的业务)
      • 将数据库种的所有数据都存储在缓存种,同时在缓存种不设置过期时间的一种实现方式。
      • image.png
        • 没有解决分布式事务问题,反而把问题放大了。
    • 基于 Binlog 的全量缓存的基本架构
      • binlog 的开源工具
        • Canal
        • mysql_Streamer
        • Maxwell
        • Databus
      • image.png
      • image.png
      • image.png
        • 优化
          • image.png
      • 缺点
        • 提升了系统的整体复杂度
          • 整个资源同步 的流程变长,且关注点合出错点由一个中间件变成了两个
        • 缓存的容量会成倍上升,响应的资源成本也大幅上升
          • 在一些对性能要求极致且是实时性高的场景下,只能进行取舍。
          • 优化
            • 缓存数据进行筛选,有业务含义且被查询
            • 存储在缓存种的数据可以进行压缩。
              • 数据json 序列号时候,字段上添加替代标识。 -
                image.png
              • 如果使用的redis hash 结构。hash结构 field 字段 也可以用 json 标识一样的模式
            • 使用全量缓存 承接所有请求时候,会出现无法感知缓存丢失问题。
              • image.png
              • image.png
            • image.png
      • 技巧
        • redis 还是可能丢失数据,使用 异步校准加报警及自动化补齐的方式来应对
          • 从缓存获取数据
          • 通过mq通知对比程序
          • 对比程序根据条件 查询数据库
          • 进行对比,不一致进行告警,并自动把数据刷新至缓存。

11 数据并发竞争

  • 描述
    • 在高并发访问场景,一旦缓存访问没有找到数据,大量请求就会并发查询 DB,导致 DB 压力大增的现象
    • 主要是同一个key
  • 解决
    • 1 使用全局锁
    • 2 对缓存数据保持多个备份,即便其中一个备份中的数据过期或被剔除了,还可以访问其他备份,从而减少数据并发竞争的情况

4 常见系统设计

1 秒杀系统缓存解析

  • 设计原则
    • 尽力将请求拦截在系统上游,减轻后端压力。
    • 充分利用缓存,提高系统性能和可用性。
  • 架构设计
    • 前端静态资源 cdn 前置

    • 前端请求限制

    • 负载均衡分发请求

    • web 服务预先处理

      • 权限检测
      • 服务前置检查
    • 业务请求处理

      • 所有处理交给缓存
      • 后续事务操作 通过MQ 缓存,降低 db压力。

2 海量计数缓存解析

  • chang

你可能感兴趣的:(缓存)