缓存

作者: 格林哈 | 来源:发表于2022-09-14 10:15 被阅读0次

    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

    相关文章

      网友评论

        本文标题:缓存

        本文链接:https://www.haomeiwen.com/subject/xhuavrtx.html