• 为什么需要缓存?


    原因:

    用缓存,主要有两个用途:高性能、高并发

    高性能

    非实时变化的数据-查询mysql耗时需要300ms,存到缓存redis,每次查询仅仅1ms,性能瞬间提升百倍。

    高并发

    mysql 单机支撑到2K QPS就容易报警了,如果系统中高峰时期1s请求1万,仅单机mysql是支撑不了的,但是使用缓存的话,单机支撑的并发量轻松1s几万~十几万。

    原因是缓存位于内存,内存对高并发的良好支持。

    常见的缓存问题:

    1、缓存与数据库双写不一致
    2、缓存雪崩、缓存穿透
    3、缓存并发竞争

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

    最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。

    • 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
    • 更新的时候,先更新数据库,然后再删除缓存。

    为什么是删除缓存,而不是更新缓存?-> 用到缓存才去算缓存-lazy加载思想。

    非高并发场景数据不一致问题:

    先修改数据库,再删除缓存。如果缓存删除失败,导致缓存中是旧数据。

    解决方法:

    先删除缓存,再修改数据库。如果缓存删除失败,则整个操作失败,如果修改数据库失败,缓存已为空,则请求数据时,会重新加载数据库的数据,
    虽然都是旧数据,但保持了数据一致性。

    高并发场景数据不一致问题:

    先删除了缓存,然后要去修改数据库,此时还没修改。(定义为步骤A

    一个请求过来,去读缓存,发现缓存空了,去查询数据库(定义为步骤B1)。查到了修改前的旧数据,放到了缓存中。(定义为步骤B2

    随后数据变更的程序完成了数据库的修改。此时数据库和缓存数据不一致了。

    解决方法:

    定义一个FIFO的阻塞队列,例如:LinkedBlockingQueue,将步骤A和步骤B放入同一个队列中。步骤A必然在步骤B的前面。

    当场景发生了上述步骤B1时,只有2个情况:缓存已删除,数据库已修改或者数据库还未修改。不考虑已修改的正常情况,则步骤A必然已发生。
    则可以在,步骤A步骤B1发生时均按照数据唯一标识(ID)同一个队列步骤A先于步骤B1入队,按照FIFO的方式,步骤A会先完成,则问题
    理论上得以解决。

    注意事项:

    • 高并发场景下肯定会有同一个数据多个步骤B的出现,可以过滤去重。即:队列中已存在则不用再入队了。
    • 由于步骤B已变成异步读请求,基于我们的高并发场景,需要考虑读超时的问题。如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。
    • 如果大量的数据更新频繁,导致队列中堆积大量的更新操作,然后大量的读请求超时,最后导致大量的请求直接走数据库。则需要根据具体业务模拟测试峰值,部署多个应用分摊更新操作。

    2、缓存雪崩、缓存穿透-> 请移步这篇文章 缓存雪崩、缓存穿透

    3、Redis的并发竞争问题

    场景:

    • redis的并发竞争问题,主要是发生在并发写竞争
    • redis本身是单线程,不存在并发问题,但我们在使用过程中会存在并发问题:更新操作分成了3步骤,读取数据,数据操作,设新值回去。

    例如:redis有一个key=“product_num”,value=10, 此时有2个客户端同时对这个key做加1操作,预期结果是value=12。
    但有这样的情况:第一个客户端还未设新值回去的时候第2个客户端获取到值,为10,则2个客户端最终操作结果value=11,与预期不符!

    解决方案:

    • 利用redis自带的 incr 命令。
    • CAS乐观锁

    某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。

    zookeeper-distributed-lock

    :写mysql数据库时保存一个时间戳(或者version),从 mysql 查询的时候,时间戳也带出来。

    每次要写DB前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。

  • 相关阅读:
    '?'变化左右括号,使得字符串括号匹配
    二叉树中最大值节电和最小值节点之间的路径
    python 处理传输层的报文 TCP/UDP
    hook笔记分享
    scrapy-splash学习
    pycharm解决关闭flask后依旧可以访问服务
    python一些常用代码块
    阿里系纯滑块验证码破解思路
    python代理池搭建
    关于scrapy的验证码处理
  • 原文地址:https://www.cnblogs.com/charm-j/p/10375413.html
Copyright © 2020-2023  润新知