Redis知识点

这篇具有很好参考价值的文章主要介绍了Redis知识点。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

Redis的5种常见数据结构以及使用场景

Redis的特性

C++中的Map也是一种缓存型数据结构,为什么不用Map,而选择Redis做缓存?

单线程的Redis为什么这么快

Redis的线程模型

Redis如何处理大量的请求

Redis设置过期时间的两种方案

假如MySQL有1000万数据,采用Redis作为中间缓存,取其中的10万,如何保证Redis中的数据都是热点数据。

Redis集群?集群的原理是什么?

缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存击穿、缓存降级

缓存降级

Redis持久化机制

两种持久化方式:快照(snapshotting)持久化(RDB持久化)

两种持久化方式:AOF(append-only file)持久化

解决Redis的并发竞争Key问题

保证缓存与数据库双写时的数据一致性

第1种方案:先写缓存,再写数据库。

第2种方案:先写数据库,再写缓存

第3种方案:先删缓存,再写数据库。

(1)高并发下的问题

(2) 缓存二删

第4种方案: 先写数据库,再删缓存

删缓存失败怎么办?

(1)、定时任务

(2)、mq(消息队列)

 Redis 的高并发和高可用是如何保证的

Redis的主从架构模式

Redis replication(复制)的核心机制

Redis 主从复制的核心原理

主从复制的断点续传

复制的完整流程


        Redis是一个数据库,不过与传统数据库不同的是Redis的数据库是存在内存中,所以读写速度非常快,因此 Redis被广泛应用于缓存方向。

        除此之外,Redis也经常用来做分布式锁,Redis提供了多种数据类型来支持不同的业务场景。除此之外,Redis 支持事务持久化、LUA脚本、LRU驱动事件、多种集群方案。

Redis的5种常见数据结构以及使用场景

(1)、String

        String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用; 常规计数:微博数,粉丝数等。

(2)、Hash

        Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅 仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来存储用户信息,商品信息等。

(3)、List

        list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表, 消息列表等功能都可以用Redis的 list 结构来实现。

        Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。

        另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功 能,基于 Redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。

(4)、Set

        set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动去重的。

        当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在 一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。

        比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常 方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程,具体命令如下:sinterstore key1 key2 key3将交集存在key1内。

(5)、Sorted Set

        和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。

        举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维 度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。

Redis的特性

 Redis 具备高性能和高并发两种特性。

  • 高性能:假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
  • 高并发:直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。

C++中的Map也是一种缓存型数据结构,为什么不用Map,而选择Redis做缓存?

严格意义上来说缓存分为本地缓存分布式缓存

        那以 C++ 语言为例,我们可以使用 STL 下自带的容器 map 来实现缓存,但只能实现本地缓存,它最主要的特点是轻量以及快速,但是其生命周期随着程序的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。

        使用 Redis 或 Memcached 之类的称为分布式缓存,在多实例的情况下,各实例共享一份缓存数据,缓存具有一致性。这是Redis或者Memcached的优点所在,但它也有缺点,那就是需要保持 Redis 或 Memcached服务的高可用,整个程序架构上较为复杂。

单线程的Redis为什么这么快

主要是有三个原因:

(1)、Redis的全部操作都是纯内存的操作;

(2)、Redis采用单线程,有效避免了频繁的上下文切换;

(3),采用了非阻塞I/O多路复用机制。

非阻塞I/O多路复用机制

它允许一个线程同时监控多个I/O通道的状态,并在有数据可读或可写时进行相应的操作,而无需阻塞等待。

基本原理如下:

  1. 非阻塞I/O(Non-blocking I/O:通过设置文件描述符为非阻塞模式,使得在进行I/O操作时不会阻塞线程的执行。当进行读取或写入操作时,如果没有数据可读或无法立即写入,系统会立即返回,而不是等待操作完成。
  2. 多路复用(Multiplexing:操作系统提供的多路复用机制允许一个线程同时监控多个I/O通道的状态。常用的多路复用函数有select、poll和epoll等。通过将多个文件描述符注册到多路复用机制中,线程可以等待任一文件描述符就绪,并进行相应的操作。

Redis的线程模型

        Redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。

Redis知识点

文件事件处理器的结构包含 4 个部分:

  • 多个 socket
  • IO多路复用程序
  • 文件事件分派器
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

        使用 I/O 多路复用程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

        多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。

        一句话总结就是:“I/O 多路复用程序负责监听多个套接字, 并向文件事件分派器传送那些产生了事件的套接字。”

Redis如何处理大量的请求

(1)、Redis是一个单线程程序,也就说同一时刻它只能处理一个客户端请求;

(2)、(select,epoll,kqueue,依据不同的平台,采取不同的实现)来处理多个客户端请求。

select是一个传统的IO多路复用系统调用,它在早期被广泛使用。它可以同时监视多个文件描述符(套接字)的状态,并在其中至少一个文件描述符就绪时返回。但是,在大规模连接的情况下性能较低,因为它使用了线性扫描的方式。

         Redis在Linux系统上使用epoll系统调用作为IO多路复用机制来实现高性能的事件驱动模型

   epoll通过使用事件通知的方式,避免了线性扫描,可以有效地处理大量的并发连接。

        在Redis中,当服务器启动时,会创建一个epoll实例,并将监听套接字(socket)添加到epoll实例中进行监听。当有新的连接到来时,Redis将新的套接字也添加到epoll实例中。然后,Redis会进入事件循环(event loop)中,不断地调用epoll_wait系统调用等待事件的发生。

  epoll_wait会阻塞,直到有事件发生或超时。当有事件发生时,epoll_wait会返回已就绪的文件描述符(套接字),然后Redis会根据就绪的套接字调用相应的回调函数来处理事件。这可以包括读取数据、写入数据或关闭连接等操作。

        使用作为IO多路复用机制,Redis能够高效地处理大规模的并发连接和高吞吐量,提供优异的性能。

        epoll是Linux特有的系统调用,因此在其他操作系统上,如BSD和macOS,Redis会使用不同的IO多路复用机制,如kqueue。这是因为不同操作系统提供了不同的底层机制来支持高性能的事件驱动模型。

Redis设置过期时间的两种方案

Redis中有个设置时间过期的功能,即对存储在 Redis 数据库中的值可以设置一个过期时间。

        作为一个缓存数据库, 这是非常实用的,比如一些 token 或者登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。

我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间,主要可采用定期删除和惰性删除两种方案。

  • 定期删除:Redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删 除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 Redis 存了几十万个 key ,每隔100ms就遍历所 有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载!
  • 惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。它是指某个键值过期后,此键值不会马上被删除,而是等到下次被使用的时候,才会被检查到过期,此时才能得到删除,惰性删除的缺点很明显是浪费内存。 除非你的系统去查一下那个 key,才会被Redis给删除掉。这就是所谓的惰性删除!

假如MySQL有1000万数据,采用Redis作为中间缓存,取其中的10万,如何保证Redis中的数据都是热点数据。

可以使用Redis的数据淘汰策略,Redis 内存数据集大小上升到一定大小的时候,就会施行这种策略。具体说来,主要有 6种内存淘汰策略:

  • voltile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

  • no-enviction(驱逐):禁止驱逐数据

Redis集群?集群的原理是什么?

        Redis Sentinel(哨兵)着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。

        Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入下线状态时,自动从服务器中选举出新的主服务器。

        Redis Cluster(集群)着眼于扩展性,在单个Redis内存不足时,使用Cluster进行分片存储。

缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存击穿、缓存降级

  • 缓存雪崩

        缓存雪崩指的是缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。

        我们可以简单的理解为:由于原有缓存失效,新缓存未到期间(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃。

解决办法

  • 事前:尽量保证整个 Redis 集群的高可用性,发现机器宕机尽快补上,选择合适的内存淘汰策略。
  • 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉, 通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  • 事后:利用 Redis 持久化机制保存的数据尽快恢复缓存

缓存穿透

        一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量 请求而崩掉。

        缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,接着查询数据库也无法查询出结果,因此也不会写入到缓存中,这将会导致每个查询都会去请求数据库,造成缓存穿透。

解决办法

1、布隆过滤器

这是最常见的一种解决方法了,它是将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压 力。

对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力;

2、缓存空对象

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;如果一个查询返回的数据为空(不管是数据不存 在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

但是这种方法会存在两个问题:

1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;

2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

我们可以从适用场景和维护成本两方面对这两汇总方法进行一个简单比较

适用场景:缓存空对象适用于1、数据命中不高 2、数据频繁变化且实时性较高 ;而布隆过滤器适用1、数据命中不高 2、数据相对固定即实时性较低

维护成本:缓存空对象的方法适合1、代码维护简单 2、需要较多的缓存空间 3、数据会出现不一致的现象;布隆过滤器适合 1、代码维护较复杂 2、缓存空间要少一些

缓存预热

缓存预热是指系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。用户会直接查询事先被预热的缓存数据!

解决思路 1、直接写个缓存刷新页面,上线时手工操作下; 2、数据量不大,可以在项目启动的时候自动进行加载; 3、定时刷新缓存;

缓存更新

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:定时删除和惰性删除,其中: (1)定时删除:定时去清理过期的缓存; (2)惰性删除:当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。 两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。

缓存击穿

缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

比如常见的电商项目中,某些货物成为“爆款”了,可以对一些主打商品的缓存直接设置为永不过期。即便某些商品自己发酵成了爆款,也是直接设为永不过期就好了。mutex key互斥锁基本上是用不上的,有个词叫做大道至简。

缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。 降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。 以参考日志级别设置预案: (1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级; (2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警; (3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级; (4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户。

Redis持久化机制

        Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。

        很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机 器、机器故障之后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置。

        实现:单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。

两种持久化方式:快照(snapshotting)持久化(RDB持久化)

Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行 备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性 能),还可以将快照留在原地以便重启服务器的时候使用。

快照持久化是Redis默认采用的持久化方式,在Redis.conf配置文件中默认有此下配置:

save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令
创建快照。

save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。

save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
两种持久化方式:AOF(append-only file)持久化

与快照持久化相比,AOF持久化的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启 AOF(append only file)方式的持久化,可以通过appendonly参数开启:appendonly yes

开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的 保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。

在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:

appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec  #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no  #让操作系统决定何时进行同步

     为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能 几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。

解决Redis的并发竞争Key问题

备注:参考(ZK(ZooKeeper)分布式锁实现-zookeeper实现分布式锁)基于ZooKeeper实现分布式锁。

所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同!

示例场景 1

例如有多个请求一起去对某个商品减库存,通常操作流程是:

  • 取出当前库存值
  • 计算新库存值
  • 写入新库存值

        基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的 与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有 序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁 无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。

保证缓存与数据库双写时的数据一致性

(参考:如何保证数据库和缓存双写一致性?-缓存数据库双写一致性)

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

更新的时候,先更新数据库,然后再删除缓存

使用缓存的目的:提高查询的性能

么,我们该如何更新缓存呢?

目前有以下4种方案:

  1. 先写缓存,再写数据库。
  2. 先写数据库,再写缓存。
  3. 先删缓存,再写数据库。
  4. 先写数据库,再删缓存。
第1种方案:先写缓存,再写数据库。

假如:某一个用户的每一次写操作,如果刚写完缓存,突然网络出现了异常,导致写数据库失败了。

我们都知道,缓存的主要目的是把数据库的数据临时保存在内存,便于后续的查询,提升查询速度。

第2种方案:先写数据库,再写缓存

(1)写缓存失败问题

如果把写数据库和写缓存操作,放在同一个事务当中,当写缓存失败了,我们可以把写入数据库的数据进行回滚。

但如果在高并发的业务场景中,写数据库和写缓存,都属于远程操作。为了防止出现大事务,造成的死锁问题,通常建议写数据库和写缓存不要放在同一个事务中。

也就是说在该方案中,如果写数据库成功了,但写缓存失败了,数据库中已写入的数据不会回滚。

这就会出现:数据库是新数据,而缓存是旧数据,两边数据不一致的情况。

(2)高并发问题

在高并发场景中,如果多个线程同时执行先写数据库,再写缓存的操作,可能会出现数据库是新值,而缓存中是旧值,两边数据不一致的情况。

(3) 浪费系统资源

该方案还有一个比较大的问题就是:每个写操作,写完数据库,会马上写缓存,比较浪费系统资源。

第3种方案:先删缓存,再写数据库。

同样会遇到高并发下的问题

(1)高并发下的问题

假设在高并发的场景中,同一个用户的同一条数据,有一个读数据请求c,还有另一个写数据请求d(一个更新操作),同时请求到业务系统。

  1. 请求d先过来,把缓存删除了。但由于网络原因,卡顿了一下,还没来得及写数据库。
  2. 这时请求c过来了,先查缓存发现没数据,再查数据库,有数据,但是旧值。
  3. 请求c将数据库中的旧值,更新到缓存中。
  4. 此时,请求d卡顿结束,把新值写入数据库。

在这个过程当中,请求d的新值并没有被请求c写入缓存,同样会导致缓存和数据库的数据不一致的情况。

(2) 缓存二删

即在写数据库之前删除一次,写完数据库后,再删除一次。

该方案有个非常关键的地方是:第二次删除缓存,并非立马就删,而是要在一定的时间间隔之后。

高并发下一个读数据请求,一个写数据请求导致数据不一致的产生过程:

  1. 请求d先过来,把缓存删除了。但由于网络原因,卡顿了一下,还没来得及写数据库。
  2. 这时请求c过来了,先查缓存发现没数据,再查数据库,有数据,但是旧值。
  3. 请求c将数据库中的旧值,更新到缓存中。
  4. 此时,请求d卡顿结束,把新值写入数据库。
  5. 一段时间之后,比如:500ms,请求d将缓存删除。

这样来看确实可以解决缓存不一致问题。

那么,为什么一定要间隔一段时间之后,才能删除缓存呢?

请求d卡顿结束,把新值写入数据库后,请求c将数据库中的旧值,更新到缓存中。

此时,如果请求d删除太快,在请求c将数据库中的旧值更新到缓存之前,就已经把缓存删除了,这次删除就没任何意义。必须要在请求c更新缓存之后,再删除缓存,才能把旧值及时删除了。

所以需要在请求d中加一个时间间隔,确保请求c,或者类似于请求c的其他请求,如果在缓存中设置了旧值,最终都能够被请求d删除掉。

接下来,还有一个问题:如果第二次删除缓存时,删除失败了该怎么办?

第4种方案: 先写数据库,再删缓存

在高并发的场景中,有一个读数据请求,有一个写数据请求,更新过程如下:

  1. 请求e先写数据库,由于网络原因卡顿了一下,没有来得及删除缓存。
  2. 请求f查询缓存,发现缓存中有数据,直接返回该数据。
  3. 请求e删除缓存。

在这个过程中,只有请求f读了一次旧数据,后来旧数据被请求e及时删除了,看起来问题不大。

但如果是读数据请求先过来呢?

  1. 请求f查询缓存,发现缓存中有数据,直接返回该数据。
  2. 请求e先写数据库。
  3. 请求e删除缓存。

删缓存失败怎么办?

(1)、定时任务

使用定时任务重试的具体方案如下:

当用户操作写完数据库,但删除缓存失败了,需要将用户数据写入重试表中。

在高并发场景中,定时任务推荐使用elastic-job。相对于xxl-job等定时任务,它可以分片处理,提升处理速度。同时每片的间隔可以设置成:1,2,3,5,7秒等。

(2)、mq(消息队列)

在高并发的业务场景中,mq(消息队列)是必不可少的技术之一。

Redis知识点

  1. 当用户操作写完数据库,但删除缓存失败了,产生一条mq消息,发送给mq服务器。
  2. mq消费者读取mq消息,重试5次删除缓存。如果其中有任意一次成功了,则返回成功。如果重试了5次,还是失败,则写入死信队列中。
  3. 推荐mq使用rocketmq,重试机制和死信队列默认是支持的。使用起来非常方便,而且还支持顺序消息,延迟消息和事务消息等多种业务场景。

当然在该方案中,删除缓存可以完全走异步。即用户的写操作,在写完数据库之后,不用立刻删除一次缓存。而直接发送mq消息,到mq服务器,然后有mq消费者全权负责删除缓存的任务。

因为mq的实时性还是比较高的,因此改良后的方案也是一种不错的选择。

 Redis 的高并发和高可用是如何保证的

Redis的主从架构模式

架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的读请求全部走从节点。这样也可以很轻松实现水平扩容,支撑读高并发

Redis replication(复制)的核心机制
  • Redis 采用异步方式复制数据到 slave 节点,不过 Redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量;
  • 一个 master node 是可以配置多个 slave node 的;
  • slave node 也可以连接其他的 slave node;
  • slave node 做复制的时候,不会 block master node 的正常工作;
  • slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
  • slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。
Redis 主从复制的核心原理

如果这是 slave node 初次连接到 master node,那么会触发一次 full resynchronization 全量复制。此时 master 会启动一个后台线程,开始生成一份 RDB 快照文件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。 RDB 文件生成完毕后, master 会将这个 RDB 发送给 slave,slave 会先写入本地磁盘,然后再从本地磁盘加载到内存中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。

Redis知识点

主从复制的断点续传

从 Redis2.8 开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。

master node 会在内存中维护一个 backlog,master 和 slave 都会保存一个 replica offset 还有一个 master run id,offset 就是保存在 backlog 中的。如果 master 和 slave 网络连接断掉了,slave 会让 master 从上次 replica offset 开始继续复制,如果没有找到对应的 offset,那么就会执行一次 resynchronization 。

复制的完整流程

slave node 启动时,会在自己本地保存 master node 的信息,包括 master node 的 host 和 ip ,但是复制流程没开始。

slave node 内部有个定时任务,每秒检查是否有新的 master node 要连接和复制,如果发现,就跟 master node 建立 socket 网络连接。然后 slave node 发送 ping 命令给 master node。如果 master 设置了 requirepass,那么 slave node 必须发送 masterauth 的口令过去进行认证。master node 第一次执行全量复制,将所有数据发给 slave node。而在后续,master node 持续将写命令,异步复制给 slave node。

Redis知识点文章来源地址https://www.toymoban.com/news/detail-494270.html

到了这里,关于Redis知识点的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 数据结构选择题练习知识点整理【3】

    n 个点连通且无环的简单无向图为连通图,连通则至少有n-1条边,无环则只有n-1条边。n个点连通且无环的简单无向图有n-1条边,非零个数为2(n-1),零元素个数为n^2-2(n-1)。得出零元素个数为n²-2n+2。 算术表达式 中缀、前缀、后缀的互相转换 中-前 从右到左 数字入栈,碰见运算

    2024年02月06日
    浏览(51)
  • 王道考研数据结构第五章知识点

    5.1.1 树的定义和基本术语   祖先节点:(对于你来说),父亲和爷爷都是祖先节点 子孙节点:对于父亲来说,父亲下面所有的节点都叫子孙节点 双亲节点(父节点):一个节点的直接前驱就是它的父节点  兄弟节点:例如二叔,三叔都是父亲的兄弟节点 堂兄弟节点:对于你来说,

    2024年02月15日
    浏览(50)
  • 数据结构之单链表的相关知识点及应用

     找往期文章包括但不限于本期文章中不懂的知识点: 个人主页 :我要学编程(ಥ_ಥ)-CSDN博客 所属专栏 :数据结构 目录 链表的概念及结构 链表与顺序表的区别与优劣势 链表的分类 单链表的实现 单链表中增加节点  单链表中尾插数据  打印单链表中节点的数据  单链表中

    2024年04月15日
    浏览(38)
  • 数据结构之双链表的相关知识点及应用

     找往期文章包括但不限于本期文章中不懂的知识点: 个人主页 :我要学编程(ಥ_ಥ)-CSDN博客 所属专栏 :数据结构 目录 双链表的实现  初始化双链表  在双链表中尾插数据  在双链表中尾删数据 在双链表中头插数据  在双链表中头删数据  在双链表中的指定位置之后插入

    2024年04月26日
    浏览(50)
  • 数据结构中一些零碎且易忘的知识点

    第一章 绪论 数据结构包含三个方面的内容: 数据的逻辑结构:描述数据之间逻辑关系的、与数据的存储无关的数学模型。相同的逻辑结构可使用不同的存储结构存储,如线性表既可顺序存储,也可链式存储 线性结构:一个线性表是n个具有相同特性的数据元素的有限序列 一

    2024年02月14日
    浏览(39)
  • C#学习笔记--数据结构、泛型、委托事件等进阶知识点

    ArrayList 元素类型以Object类型存储,支持增删查改的数组容器。 因而存在装箱拆箱操作,谨慎使用。 ArrayList和数组区别? ArrayList使用不用说明固定长度,数组则需要 数组存储的是指定类型的,ArrayList是Object ArrayList存在装箱拆箱,数组不存在 ArrayList数组长度用Count获取 而数组

    2024年02月08日
    浏览(47)
  • 【数据结构】考研真题攻克与重点知识点剖析 - 第 6 篇:图

    本文基础知识部分来自于b站:分享笔记的好人儿的思维导图与王道考研课程,感谢大佬的开源精神,习题来自老师划的重点以及考研真题。 此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析,本人技术有限,最终数据清洗结果不够理想,

    2024年04月15日
    浏览(51)
  • 软考知识点——数据结构:大顶堆与小顶堆、哈夫曼树

    目录 一、大顶堆与小顶堆 1.大顶堆与小顶堆的概念 2.大顶堆的构建 二、哈夫曼树 1.哈夫曼树的定义 2.基本概念 3.构造哈夫曼树 4.哈夫曼编码 大顶堆:每个结点的值都大于或等于其左右孩子结点的值。 小顶堆:每个结点的值都小于或等于其左右孩子结点的值。 以数组A=(2,

    2024年02月06日
    浏览(38)
  • 【数据结构入门精讲 | 第十六篇】并查集知识点及考研408、企业面试练习

    上一篇中我们进行了散列表的相关练习,在这一篇中我们要学习的是并查集。 在许多实际应用场景中,我们需要对元素进行分组,并且在这些分组中进行查询和修改操作。比如,在图论中,我们需要将节点按照连通性进行分组,以便进行最小生成树、最短路径等算法;在计算

    2024年02月03日
    浏览(48)
  • 【算法与数据结构】二叉树的三种遍历代码实现(上)—— 用递归序知识点讲解

      本篇博客 (上篇) 先带大家学习 递归方式 进行三种遍历, 而在后续的 (下篇) 中将为大家详细讲解非递归的三种遍历方式。 目录 1、二叉树 2、二叉树的递归遍历 2.1、先序遍历 2.2、中序遍历 2.3、后序遍历  二叉树(Binary tree)是树形结构的一个重要类型。许多实际问

    2024年02月08日
    浏览(41)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包