【云原生进阶之PaaS中间件】第一章Redis-2.4缓存更新机制

这篇具有很好参考价值的文章主要介绍了【云原生进阶之PaaS中间件】第一章Redis-2.4缓存更新机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【云原生进阶之PaaS中间件】第一章Redis-2.4缓存更新机制,云原生进阶-PaaS专栏,云原生,缓存,paas,redis,架构

1 缓存和数据库的数据一致性分析

1.1 Redis 中如何保证缓存和数据库双写时的数据一致性?

        无论先操作db还是cache,都会有各自的问题,根本原因是cache和db的更新不是一个原子操作,因此总会有不一致的问题。想要彻底解决这种问题必须将cache和db的更新操作归在一个事务之下(例如使用一些分布式事务,或者强一致性的分布式协议)。或者采用串行化,可以保证强一致性。

【云原生进阶之PaaS中间件】第一章Redis-2.4缓存更新机制,云原生进阶-PaaS专栏,云原生,缓存,paas,redis,架构

1.1.1 写请求为什么更新数据库后是删除缓存而不是更新缓存?

【云原生进阶之PaaS中间件】第一章Redis-2.4缓存更新机制,云原生进阶-PaaS专栏,云原生,缓存,paas,redis,架构

        注意看上面的图片,当有两个写请求的线程,线程一比线程二先执行,反而是线程二先执行完。这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据不一致了。

1.1.2 写请求时,为什么更新数据库,然后再删除缓存?

【云原生进阶之PaaS中间件】第一章Redis-2.4缓存更新机制,云原生进阶-PaaS专栏,云原生,缓存,paas,redis,架构

        如果采用写请求,先删除缓存,再更新数据库就会出现如上图的情况,线程B读到的是老的数据,并且缓存中也保存的是老的数据。

1.1.3 写请求时,先更新数据,后删除缓存一定没有问题吗?

【云原生进阶之PaaS中间件】第一章Redis-2.4缓存更新机制,云原生进阶-PaaS专栏,云原生,缓存,paas,redis,架构

        可以看到一个读请求和一个写请求,读请求可能会读取到旧的数据,或者当写请求删除缓存失败,读请求会一直读取的是旧的缓存数据。只不过是这种情况,相对于其他的实现方式概率要低很多。

1.2 三种方案保证数据库与缓存的一致性

1.2.1 缓存延时双删

        第二次删除缓存一般会采用延时的操作,主要是用来删除读请求产生的缓存数据

1.2.2 删除缓存重试机制

        延时双删和普通写操作的删除操作都有可能会操作失败,导致数据不一致,删除重试机制就是为了保证删除可靠性。(删除失败的key放到消息队列中)这种机制会造成大量的业务代码入侵。

1.2.3 读取biglog异步删除缓存

        通过binlog日志,将要删除的key发送到消息队列中。

1.3 如何使用 Redis 做异步队列和延时队列?

1.3.1 延时队列

        将需要延时执行的任务放到 Redis 中的 Zset 类型中,Zset会根据 score 自动进行数据排序(score使用时间戳),定义一个延时任务检测器,检测器使用 zrangebysocre 命令查询 Redis 中符合执行条件的任务执行.

1.3.2 异步队列

        Redis的队列list是有序的且可以重复的,作为消息队列使用时可使用rpush/lpush操作入队,使用lpop/rpop操作出队。当发布消息是执行lpush命令,将消息从列表左侧加入队列。消息接收方执行rpop命令从列表右侧弹出消息。

        如果队列空了,消费者会陷入pop死循环,即使没有数据也不会停止。空轮询不但消耗消费者的CPU资源还会影响Redis的性能。并且需要不停的调用rpop查看列表中是否有待处理的消息。每调用一次都会发起一次连接,势必造成不必要的资源浪费。

        入队的速度大于出队的速度,消息队列长度会一直增大,时间长了会占用大量的空间。

        针对上面的 rpop 命令会一直阻塞队列,Redis提供了一种更优的 brpop命令,brpop可以设置一个超时时间,

1.4 Redis 中的过期策略

Redis 中的过期策略共有三种:

  1. 定时删除
  2. 定期删除
  3. 惰性删除

        Redis 采用的过期策略是 定期+惰性 删除。

1.4.1 定时删除

        在设置key的过期时间的同时为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除

优点: 保证内存被尽快释放

缺点: 过期的key太多,删除这些key会占用很多的CPU时间。设置了过多的定时器,会对redis 的性能造成影响。

1.4.2 定期删除

        默认一段时间就去随机部分扫描redis中的设置了过期时间的key,检查是否过期,过期的话就移除key。

1.4.2.1 为什么定期删除只扫描部分设置了过期时间的key

        因为扫描全部的key会非常多,很影响性能。

1.4.3 惰性删除

        惰性删除就是等到有查询key的请求过来的时候,我看看这个key有没有过期,过期的话就删除这个key。

缺点: 可能会造成内存泄漏

1.5 Redis 中的内存淘汰机制

        设置方式: config set maxmemory-policy volatile-lru

  • no-eviction: 禁止驱逐数据(当内存达到限制时,就报错)
  • allkeys-lru: 从redis 中回收最近使用最少的键
  • volatile-lru: 从设置了过期时间的键中,回收最近使用最少的键
  • allkeys-random:随机回收redis中的键
  • volitile-random:从设置了过期时间的键中,随机回收
  • volitile-ttl:从设置了过期时间的键中,回收存活时间较少的键

        关于volatile-lru:LRU 算法实现:1.通过双向链表来实现,新数据插入到链表头部;2.每当缓存命中(即缓 存数据被访问),则将数据移到链表头部;3.当链表满的时候,将链表尾部的数据丢弃。

        指定redis 的淘汰策略

# maxmemory-policy noeviction

2 缓存更新机制

        当执行写操作后,需要保证从缓存读取到的数据与数据库中的数据是一致的,因此需要对缓存进行更新。因为涉及到数据库和缓存两步操作,难以保证更新的原子性。在设计更新策略时,我们需要考虑多个方面的问题,对系统吞吐量的影响、并发安全性、更新失败的影响。

        更新缓存有两种方式:

  1. 删除失效缓存: 读取时会因为未命中缓存而从数据库中读取新的数据并更新到缓存中
  2. 更新缓存: 直接将新的数据写入缓存覆盖过期数据

        更新缓存和更新数据库有两种顺序:

  1. 先数据库后缓存
  2. 先缓存后数据库

        两两组合共有四种更新策略,现在我们逐一进行分析。

2.1 更新策略分析

        先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。

2.1.1 先更新数据库,再删除缓存(推荐)

        若数据库更新成功,删除缓存操作失败,则此后读到的都是缓存中过期的数据,造成不一致问题。

        假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生:

(1)缓存刚好失效;

(2)请求A查询数据库,得一个旧值;

(3)请求B将新值写入数据库;

(4)请求B删除缓存;

(5)请求A将查到的旧值写入缓存;

        假设,有人非要抬杠,有强迫症,一定要解决怎么办?

        如何解决上述并发问题?首先,给缓存设置有效时间是一种方案。其次,采用异步延时删除策略,redis自己起一个线程,异步删除保证读请求完成以后,再进行删除操作。

2.1.2 先更新数据库,再更新缓存(反对)

        同删除缓存策略一样,若数据库更新成功缓存更新失败则会造成数据不一致问题。反对此方案

原因一(线程安全角度)

        同时有请求A和请求B进行更新操作,那么会出现:

(1)线程A更新了数据库

(2)线程B更新了数据库

(3)线程B更新了缓存

(4)线程A更新了缓存

        ​这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。

原因二(业务场景角度)

有如下两点:

(1)如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。

(2)如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。

        接下来讨论的就是争议最大的,先删缓存,再更新数据库。还是先更新数据库,再删缓存的问题。

2.1.3 先删除缓存,再更新数据库

        该方案会导致不一致的原因是。同时有一个请求A进行操作,另一个请求B进行查询操作。那么会出现如下情形:

(1)请求A进行写操作,删除缓存;

(2)请求B查询发现缓存不存在;

(3)请求B去数据库查询得到旧值;

(4)请求B将旧值写入缓存;

(5)请求A将新值写入数据库;

【云原生进阶之PaaS中间件】第一章Redis-2.4缓存更新机制,云原生进阶-PaaS专栏,云原生,缓存,paas,redis,架构

        上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。

        那么,如何解决呢?

        采用延时双删策略:

(1)先淘汰删除缓存;

(2)再写数据库(这两步和原来一样);

(3)休眠1秒,再次淘汰缓存;

        这么做,可以将1秒内所造成的缓存脏数据,再次删除。那么,这个1秒怎么确定的,具体该休眠多久呢?这确实需要根据实际情况而定:

        如果你用了MySQL的读写分离架构怎么办?还是使用延时双删策略。

        采用这种同步淘汰策略,吞吐量降低怎么办?ok,那就将第二次删除作为异步的。自己起一个线程,异步删除。

        第二次删除,如果删除失败怎么办?这是个非常好的问题,因为第二次删除失败,就会出现如下情形。还是有两个请求,一个请求A进行更新操作,另一个请求B进行查询操作,为了方便,假设是单库:

(1)请求A进行写操作,删除缓存;

(2)请求B查询发现缓存不存在;

(3)请求B去数据库查询得到旧值;

(4)请求B将旧值写入缓存;

(5)请求A将新值写入数据库;

(6)请求A试图去删除请求B写入对缓存值,结果失败了。

        ok,这也就是说。如果第二次删除缓存失败,会再次出现缓存和数据库不一致的问题。

如何解决呢?

2.1.4 先更新缓存,再更新数据库(反对)

        若缓存更新成功数据库更新失败, 则此后读到的都是未持久化的数据。因为缓存中的数据是易失的,这种状态非常危险。

2.2 缓存雪崩/击穿/穿透

        正常情况下的流程是这样的,先查缓存,缓存无就查数据库。

【云原生进阶之PaaS中间件】第一章Redis-2.4缓存更新机制,云原生进阶-PaaS专栏,云原生,缓存,paas,redis,架构

2.2.1 缓存雪崩

        缓存雪崩是指缓存中的数据大批量的过期 ,而查询量巨大,造成数据库压力过大而崩溃。

解决方法:

  1. 缓存的过期时间随机设置,防止大量数据同时过期。
  2. 尽量保证redis集群的高可用性,当发现机器坠机时尽快补上。
  3. 选择合适的缓存淘汰策略。

2.2.2 缓存击穿

        缓存击穿是指缓存中没有数据,而数据库中有数据,一般是缓存中的数据过期了,然后很多用户并发查询该数据,同时在缓存中读取该数据没读取到,就同时去数据库中查,造成数据库压力过大。缓存击穿强调的是一个数据过期,同时并发地去数据库访问该数据;而缓存雪崩是强调大量的数据过期。

解决方法:

  1. 设置热点数据永不过期。
  2. 加互斥锁。逻辑如下:从缓存中获取当前数据,如果缓存中没有,则尝试去获取锁,如果获取成功则查询数据库,然后写进缓存,然后释放锁。

2.2.3 缓存穿透

        缓存穿透是指缓存中没有该数据,数据库中也没有该数据。而用户不断地发请求,比如不断发出一些id=-1或者是根本就很不合理的数据来发生请求。这种一般是别人想攻击你。攻击会导致数据库压力过大。

​         对于这种情况很好解决,我们可以在redis缓存一个空字符串或者特殊字符串,比如&&,下次我们去redis中查询的时候,当取到的值是空或者&&,我们就知道这个值在数据库中是没有的,就不会再去数据库中查询,ps:这里缓存不存在key的时候一定要设置过期时间,不然当数据库已经新增了这一条记录的时候,这样会导致缓存和数据库不一致的情况。

        上面这个只是重复查询同一个不存在的值的情况,如果每次查询的不存在的值是不一样的呢?那怎么办,难道自己手动缓存许多特殊字符串吗?别人想攻击你,即使你每次缓存很多特殊字符串也没用,太有概率性了,这时候数据库的压力是相当大,怎么办呢,布隆过滤器就登场了。

        布隆过滤器使用场景:

①、原本有10亿个数,现在又来了10万个数,要快速准确判断这10万个数是否在10亿个数库中?

  • 办法一:将10亿个数存入数据库,再数据库查询,查出值为null,代表不存在,准确性有了,但是速度会比较慢。
  • 办法二:将10亿数放入内存中,比如Redis中,这里我们算一下占用内存大小:10亿*8字节=8GB,通过内存查询,准确性和速度都有了,但是大约8GB的内存空间,挺浪费内存空间的。

        那么对于类似这种,大数据量集合,如何准确快速的判断某个数据是否在大数据量集合中,并且不占用内存,布隆过滤器应运而生了。

布隆过滤器:使用位图实现,是由一串很长的二进制向量组成,数组中只存在0.1

        当要向布隆过滤器中添加一个元素key时,我们通过多个hash函数,算出一个值,然后将这个值所在的方格置为1。 如下图:

【云原生进阶之PaaS中间件】第一章Redis-2.4缓存更新机制,云原生进阶-PaaS专栏,云原生,缓存,paas,redis,架构

如何查询是否存在呢?

        我们只需要将这个新的数据通过上面自定义的几个哈希函数,分别算出各个值,然后看其对应的地方是否都是1,如果存在一个不是1的情况,那么我们可以说,该新数据一定不存在于这个布隆过滤器中。

        反过来说,如果通过哈希函数算出来的值,对应的地方都是1,那么我们能够肯定的得出:这个数据一定存在于这个布隆过滤器中吗?

        答案是否定的,因为多个不同的数据通过hash函数算出来的结果是会有重复的,所以会存在某个位置是别的数据通过hash函数置为的1。比如这个d,通过三次计算发现得到的结果也都是1,那么我们能说d在布隆过滤器中是存在的吗,显然是不行的,我们仔细看d得到的三个1其实是f1(a),f1(b),f2©存进去的,并不是d自己存进去的,这个还是哈希碰撞导致的。

        结论:布隆过滤器可以判断某个数据一定不存在,但是无法判断一定存在。

参考链接

Redis的缓存更新策略和缓存问题_redis更新缓存数据_〖雪月清〗的博客-CSDN博客

Redis-基本概念_redis定义_SeaDhdhdhdhdh的博客-CSDN博客

一文搞懂 Redis 架构演化之路 

Redis设计与实现

redis架构_剑八-的博客-CSDN博客

Redis高可用方案—主从(masterslave)架构

Redis高可用架构—哨兵(sentinel)机制详细介绍

Redis高可用架构—Redis集群(Redis Cluster)详细介绍

Redis基本概念知识_redis 基本概念_Gatsby_codeLife的博客-CSDN博客

03 Redis 网络IO模型简介_redis的io模型_天秤座的架构师的博客-CSDN博客

Redis 详解_王叮咚的博客-CSDN博客文章来源地址https://www.toymoban.com/news/detail-687012.html

到了这里,关于【云原生进阶之PaaS中间件】第一章Redis-2.4缓存更新机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【云原生进阶之PaaS中间件】第一章Redis-2.3.3集群模式

            Redis集群是一个提供在多个Redis节点之间共享数据的程序集。它并不像Redis主从复制模式那样只提供一个master节点提供写服务,而是会提供多个master节点提供写服务,每个master节点中存储的数据都不一样,这些数据通过数据分片的方式被自动分割到不同的master节点上

    2024年02月10日
    浏览(60)
  • 【云原生进阶之PaaS中间件】第一章Redis-1.6.1Java项目使用Redis

            redis的java客户端很多,官方推荐的有三种: Jedis Lettuce Redisson Spring 对Redis 客户端进行了整合,提供了Spring Date Redis ,在Spring Boot项目中还提供了对应的Starter,即spring-boot-starter-data-redis。         使用Jedis操作Redis的步骤: 1.获取链接; 2.执行操作; 3.关闭连接

    2024年02月09日
    浏览(54)
  • 【云原生进阶之PaaS中间件】第二章Zookeeper-1-综述

            ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,它包含一个简单的原语集,分布式应用程序可以基于它实现同步服务,配置维护和命名服务等。 Zookeeper是hadoop的一个子项目,其发展历程无需赘述。在分布式应用中,由于工程师不能很好地使用锁机

    2024年02月09日
    浏览(54)
  • 【云原生进阶之PaaS中间件】第二章Zookeeper-3.2架构详解

    » 领导者(leader),负责进行投票的发起和决议,更新系统状态 » 学习者(learner),包括跟随者(follower)和观察者(observer),follower用于接受客户端请求并想客户端返回结果,在选主过程中参与投票 » Observer可以接受客户端连接,将写请求转发给leader,但observer不参加投票

    2024年02月08日
    浏览(56)
  • 【云原生进阶之PaaS中间件】第四章RabbitMQ-3-RabbitMQ安装

    1.1.1 环境准备         要在Linux环境下安装RabbitMQ,首先我们要有一个Linux环境,此处我们使用CentOS7虚拟机进行演示。如果本地还没有装过虚拟机,可以参考我之前的文章搭建虚拟机环境:VMware Workstation 14安装教程、虚拟机环境搭建(VMware Workstation14 + centos7)、VMware+CentO

    2024年02月20日
    浏览(58)
  • 【云原生进阶之PaaS中间件】第四章RabbitMQ-4.1-原理机制与进阶特性

    1.客户端连接到消息队列服务器,打开一个Channel。 2.客户端声明一个Exchange,并设置相关属性。 3.客户端声明一个Queue,并设置相关属性。 4.客户端使用Routing key,在Exchange和Queue之间建立好绑定关系。 5.客户端投递消息到Exchange。 6.Exchange接收到消息后,就根据消息的key和已经

    2024年02月21日
    浏览(51)
  • 【云原生进阶之PaaS中间件】第四章RabbitMQ-1-简介及工作模式

            RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。AMQP(Advanced Message Queue:高级消息队列协议)它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。RabbitMQ 最初起源于

    2024年02月21日
    浏览(85)
  • 【云原生进阶之PaaS中间件】第三章Kafka-4.4-消费者工作流程

    1.1.1 消费者群组         Kafka 里消费者从属于消费者群组,一个群组里的消费者订阅的都是同一个主题,每个消费者接收主题一部分分区的消息。         如上图,主题 T 有 4 个分区,群组中只有一个消费者,则该消费者将收到主题 T1 全部 4 个分区的消息。      

    2024年02月22日
    浏览(50)
  • 【云原生进阶之PaaS中间件】第四章RabbitMQ-4.3-如何保证消息的可靠性投递与消费

            根据RabbitMQ的工作模式,一条消息从生产者发出,到消费者消费,需要经历以下4个步骤: 生产者将消息发送给RabbitMQ的Exchange交换机; Exchange交换机根据Routing key将消息路由到指定的Queue队列; 消息在Queue中暂存,等待消费者消费消息; 消费者从Queue中取出消息消费

    2024年03月11日
    浏览(66)
  • 云原生中间件开源现状分析与华为中间件案例解读

    开源中间件在企业分布式架构搭建和服务治理中扮演着重要的角色,尤其是在解决我国网络高并发和业务复杂性问题方面。然而,尽管中间件市场由商业闭源厂商主导,提供了一系列基础中间件和数据类中间件以支持稳定的应用程序运行环境,开源中间件生态却相对分散和薄

    2024年02月02日
    浏览(79)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包