Redis

Redis面试题

Redis相比memcached有哪些优势?

这是一道非常常见的面试题,也是大家在工作中很容易忽略掉的点,大部分场景下redis确实更适合用于我们项目,但是我们可能答不上来它们都作为键值对数据库其中的区别是什么。

从数据结构侧来说,memcached仅支持value为string类型,而我们redis支持的类型是相当丰富的,有string、hash、list、set、sort set等等,所以在功能上redis是比我们memcached支持的更好的。还有就是memcached的单value值容量只有1M,而我们的redis则最大支持至512M。

从数据持久化来说,memcached只做缓存,没有可靠性的需求,所以是不支持的,只要断电或者服务关闭之后那么就会丢失内存中的数据,而redis更倾向于内存数据库,如果我们有持久化需求的话可以优先考虑redis。

同时我们的redis还支持lua脚本,脚本提交是原子执行的,我们在面对复杂业务场景中,需要保证按照我们所需的顺序一步步执行就可以通过我们的lua脚本来解决。

Redis真的是单线程吗?

所谓的redis单线程其实指的是在网络IO和键值对读写时是通过一个线程完成的。而其他的一些模块比如说持久化存储、集群支撑模块这些都是多线程的。

那为什么网络操作模块和数据存储模块不用多线程呢?

其实非常简单,首先网络IO模块的性能瓶颈就不在CPU上,而是要提升我们的IO利用率,虽然使用多线程能带来一些提升,但是多线程也是存在一定的弊端的,首先是多线程模型下的共享资源和并发控制非常复杂,线程的上线文切换也会带来一定的性能损耗,所以Redis在这块采用的是IO多路复用。

另一方面,Redis的绝大部分操作都是在内存中完成的,内存操作本来就比硬盘读写快了百倍以上,并且在数据结构上也进行了大量的优化,比如hash表和跳表。而使用单线程还能避免多线程下的锁竞争,省去了线程的时间和性能开销也不会存在锁竞争的问题。

Redis6为何引入多线程?

redis6中引入的多线程是正对于网络IO模块进行了多线程改造,因为多路复用的IO模型本质上来说还是同步阻塞型IO模型,在调用epoll的过程是阻塞的,并发量极高的场景就成为了性能瓶颈,那么在碰到这类问题上,就可以通过多线程来解决。它通过多线程解决了网络IO等待造成的影响,还可以充分利用CPU的多核优势。对于我们读写模块依旧还是采用的单线程模型,避免了多线程环境下并发访问带来的很多问题。在简单的get/set命令性能上多线程IO模型提升了有接近一倍。

Redis是如何解决Hash冲突的?

redis是通过我们的链式hash来解决我们的hash冲突问题,哈希算法产生的哈希值的长度是固定并且是有限的,比如说我们通过MD5算法生成32位的散列值,那么它能生成出来的长度则是有限的,我们的数据如果大于32位是不是就可能存在不同数据生成同一个散列值,那么redis通过链式hash,以不扩容的前提下把有相同值的数据链接起来,但是如果链表变得很长就会导致性能下降,那么redis就采用了rehash的机制来解决,类似于hashmap里面的扩容机制,但是redis中的rehash并不是一次把hash表中的数据映射到另外一张表,而是通过了一种渐进式的方式来处理,将rehash分散到多次请求过程中,避免阻塞耗时。

Redis是如何处理过期的数据的?

我们可以通过expire来设置redis的过期时间

那么通常情况下我们有三种方式来进行处理过期数据:

        定时删除,每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

        惰性删除,惰性删除其实就是当访问数据时判断,如果数据已经过期的话直接删除并返回null回去,它可以只占用很少的一部分系统资源来处理但是这种策略的也有一定的缺陷在面对冷数据会占用空间删除不及时,造成空间浪费,只能靠redis数据淘汰策略来进行处理。

        另一种是随机删除,定期删除是每隔一段时间随机取出设置了过期时间的一定数量的key进行检查,然后删除其中过期的key。如果超过了百分之25%的数据过期了,就会再次执行。它通过限制删除操作的时长和频率来减少对CPU和内存空间占用的影响,但是它的删除效果并没有定时删除的效果好,同时也没有比惰性删除所占用的系统资源低。

而我们的redis则采用的是惰性删除与随机删除

MySQL里有2000w数据Redis中只存20w的数据,如何保证 redis 中的数据都是热点数据?

首先我们可以看到Redis的空间时间上比我们MySQL少的多,那么Redis如何能够筛选出热点数据,这道题主要考察的是Redis的数据淘汰策略(这里有个误区,很多人容易混淆把数据淘汰策略当做数据过期策略),在Redis 4.0之后是为我们提供了8种淘汰策略,4.0之前则是提供的6种,主要是新增了LFU算法。其实说说是有8种,但是真正意义上是5种,针对random、lru、lfu是提供了两种不同数据范围的策略,一种是针对设置了过期时间的,一种是没有设置过期时间的。具体的五种策略分别为:

  •         noeviction 选择这种策略则代表不进行数据淘汰,同时它也是redis中默认的淘汰策略,当缓存写满时redis就不再提供写服务了,写请求则直接返回失败。
  •         random 随机策略这块则是分为两种,一种是volatile,这种是设置了过期时间得数据集,而另外一种是allkeys,这种是包含了所有的数据,当我们缓存满了的时候,选用这种策略就会在我们的数据集中进行随机删除。
  •         volatile-ttl 这种策略是针对设置了过期时间的数据,并且按照过期时间的先后顺序进行删除,越早过期的越先被删除
  •         lru 这里的lru策略和我们上面random策略一样也是提供了两种数据集进行处理,LRU算法全程为(最近最少使用)简单一句话来概括就是“如果数据最近被访问过,那么将来被访问的几率也就越高”。这种算法其实已经比较符合我们的实际业务需求了,但是还是存在一些缺陷。
  •         lfu 最后一种策略就是我们的LFU算法,它是在我么LRU算法基础上增加了请求数统计,这样能够更加精准的代表我们的热点数据。

我们再回看我们的这个问题,我们能很清楚的知道,我们需要的策略是LFU算法。选择volatile还是allkeys就要根据具体的业务需求了。

高并发场景下我们如何保证幂等性?

首先普及下幂等的概念

        “在计算机中编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。”

那么在我们的实际业务场景中幂等是一个非常高频的场景,比如:

  •         电商场景中用户因网络问题多次点击导致重复下单问题
  •         MQ消息队列的重复消费
  •         RPC中的超时重试机制等等

那么我们有那些方案可以解决我们的幂等性问题呢?

  •         数据库唯一主键实现幂等性
    • 其实现方式是使用分布式ID充当主键,不使用MySQL中的自增主键
  •         乐观锁实现幂等性
    • 在表中增加版本号标识,只有版本号标识一直才更新成功
  •         分布式锁
    • 简单来说就是分布式的排他锁,但是我们可以控制锁的粒度以提高程序的执行性能
  •         获取token

                服务端提供获取 Token 的接口,请求前客户端调用接口获取 Token

                然后将该串存入 Redis 数据库中,以该 Token 作为 Redis 的键(注意设置过期时间)。        

                将 Token 返回到客户端,在执行业务请求带上该 Token

                服务端接收到请求后根据 Token 到 Redis 中查找该 key 是否存在(注意原子性),

                如果存在就将该 key 删除,然后正常执行业务逻辑。如果不存在就抛异常,返回重复提交的错误信息。

Redis如何保证与数据库的双写一致性 (北冥)

我们来分析一下这道面试题,这道题主要是偏实际应用的

缓存可以提升性能,减轻数据库压力,在获取这部分好处的同时,它却带来了一些新的问题,缓存和数据库之间的数据一致性问题。

想必大家在工作中只要用了咱们缓存势必就会遇到过此类问题,那这道题该如何回答呢?

首先我们来看看一致性:

  •         强一致性
  •         弱一致性

解决双写一致性方案:

  •         延迟双删
    • 延迟双删策略是分布式系统中数据库存储和缓存数据保持一致性的常用策略,但它不是强一致。
    • 实现思路:也是非常简单的,先删除缓存然后更新DB在最后延迟 N 秒去再去执行一次缓存删除
    • 弊端:小概率会出现不一致情况、耦合程度高
  •         通过MQ进行重试删除
    • 更新完DB之后进行删除,如果删除失败则向MQ发送一条消息,然后消费者不断进行删除尝试。
  •         binlog异步删除
    • 实现思路:低耦合的解决方案是使用canal。canal伪装成mysql的从机,监听主机mysql的二进制文件,当数据发生变化时发送给MQ。最终消费进行删除

谈谈缓存穿透、击穿、雪崩的区别,又如何去解决?(北冥)

面试题分析

这道题主要考察的是求职者是否具有高并发思维,它也是在面试中一道高频的考点

缓存穿透

缓存穿透代表的意思是在我们的缓存中没有找到缓存信息,那么我们在高并发场景下就会面临所有的请求都会直接打到DB,缓存则失去了它原本的意义,并且极有可能导致数据库压力过大而造成服务不可用。

  •         缓存空结果信息
  •         布隆过滤器(不存在的一定不存在,存在的可能不存在,通过bitmap实现,想深入布隆过滤器可以专门去看看这部分专题内容)
  •         过滤常见非法参数,拦截大部分无效请求
缓存击穿

缓存击穿代表的意思是我们数据库中存在数据,但是缓存中不存在数据.这种场景一般是在缓存失效时发生的. 在高并发的场景下极有可能瞬间打垮数据库.

  •         我们可以考虑面对请求量大的热点接口直接将缓存设置永不过期.
  •         当然我们也可能碰到一些特殊场景不能设置永久缓存,那么我们可以在db为空时设置互斥锁,当查询完db更新至缓存时再释放锁
缓存雪崩

缓存雪崩代表是意思是我们在某一个时间段,碰到大量热点缓存数据过期导致大量请求直接打垮数据库

  •         我们可以考虑面对请求量大的热点接口直接将缓存设置永不过期.
  •         缓存过期时间可以设置一个随机的波动值,防止大量数据在同一时间过期

10分钟带你了解灰度发布

灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。

全量发布
  •         回滚周期长
  •         BUG导致服务集群雪崩
  •         服务可用性差,影响用户体验
灰度发布
  •         降低发布影响面,提升用户体验
  •         可以做到不停机迁移
  •         回滚速度快
发布方案对比图

策略

零停机

生产流量测试

针对特定用户

机器资源成本

回滚时长

负面影响

实现复杂度

全量发布

×

×

×

蓝绿发布

×

×

高(双倍)

金丝雀发布

中(按需)

全链路灰度

中(按需)

金丝雀发布

据说以前有个典故,矿工开矿前,会先放一只金丝雀下去,看金丝雀是否能活下来,用来探测是否有毒气,金丝雀发布也是由此得名。

灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度,而我们平常所说的金丝雀部署也就是灰度发布的一种方式。

全链路灰度发布

全链路灰度治理策略主要专注于整个调用链,它不关心链路上经过具体哪些微服务,流量控制视角从服务转移至请求链路上,仅需要少量的治理规则即可构建出从网关到整个后端服务的多个流量隔离环境,有效保证了多个亲密关系的服务顺利安全发布以及服务多版本并行开发,进一步促进业务的快速发展。

雪花ID算法的前世今生

snowflake是Twitter开源的分布式ID生成算法,结果是64bit的Long类型的ID,有着全局唯一和有序递增的特点。

        最高位是符号位,因为生成的 ID 总是正数,始终为0,不可用。

        41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。

        10位的机器标识,10位的长度最多支持部署1024个节点。

        12位的计数序列号,序列号即一系列的自增ID,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。缺点也是有的,就是强依赖机器时钟,如果机器上时钟回拨,有可能会导致主键重复的问题。

Redis 事务支持 ACID 么?

原子性(Atomicity):一个事务的多个操作必须完成,或者都不完成。

一致性(Consistency):事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后顺序都是合法数据状态。

隔离性(Isolation):事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(Durability):事务一旦提交,所有的修改将永久的保存到数据库中,即使系统崩溃重启后数据也不会丢失。

redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的

Redis会将一个事务中的所有命令序列化,然后按顺序执行。

单独的隔离操作

  •         事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

没有隔离级别的概念

  •         队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行

不保证原子性

  •         事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

注:redis的discard只是结束本次事务,正确命令造成的影响仍然存在.

        1)MULTI命令用于开启一个事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。

        2)EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil 。

        3)通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。

        4)WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。

说说你对缓存双写不一致的理解

缓存双写不一致是指在使用缓存的架构中,当数据更新时,由于缓存和数据库的写操作没有同步进行,导致数据在缓存和数据库之间出现不一致的情况。

以下是对缓存双写不一致的一般理解:

  1.         更新顺序问题:当应用程序更新了数据库中的数据,但在更新缓存之前发生了错误或异常,导致缓存中的数据仍然是旧值。这种情况下,数据库中的数据已经被修改,但缓存中的数据仍然是旧的,导致缓存和数据库之间存在不一致。
  2.         缓存失效问题:当数据库中的数据发生变化,并成功更新后,缓存中的数据却没有及时更新或失效了。这可能是由于缓存的过期策略、缓存维护、网络延迟等原因导致的。此时,从缓存读取的数据将是旧值,与数据库中的新值不一致。
  3.         并发更新问题:当多个应用程序同时更新相同的数据时,缓存和数据库的更新操作可能不是原子性的。如果两个更新操作同时进行,可能会导致缓存和数据库在更新时发生冲突,导致不一致的结果。

为了解决缓存双写不一致的问题,可以考虑以下方法:

  1.         缓存更新策略:在更新数据库的同时,立即更新缓存,确保缓存中的数据与数据库中的数据保持一致。可以使用同步或异步方式进行。数据库与缓存的事务性操作:通过数据库事务和缓存的原子性操作来保证更新的一致性。
  2.         更新通知机制:通过发布-订阅(Pub/Sub)模式,或使用消息队列等机制来通知缓存节点更新数据,确保缓存的实时性。
  3.         使用强一致性缓存:如 Redis 的事务和 pipeline 特性,可以确保对缓存的多个操作按顺序执行,减少不一致的概率。
  4.         定期刷新缓存:通过定期刷新缓存,保证缓存中的数据不会过期太久,降低不一致性发生的概率。

综上所述,缓存双写不一致是在使用缓存时常见的问题,通过合理的缓存更新策略、事务性操作、更新通知机制等措施,可以有效地减少不一致的发生。

什么是Redis哨兵机制

Redis 哨兵是一种用于高可用性的解决方案,它可以监控 Redis 主从复制模式下的主节点和从节点,发现节点故障,并自动进行故障转移,保证 Redis 系统的稳定性和可靠性。

Redis 哨兵机制由多个相互独立的进程组成,这些进程使用 TCP/IP 协议相互通信,实现 Redis 节点的监控和故障转移。哨兵机制的关键进程包括:

  1.         sentinel:主进程,用于监控 Redis 节点的状态,并执行故障转移操作。
  2.         monitor:哨兵进程,用于监控 Redis 的主节点和从节点是否正常工作,并在需要时通知其他哨兵进程和客户端。
  3.         judge:哨兵进程,用于对节点的健康状况进行评估,并根据预定义的阈值决定是否要将一个不健康的节点标记为“主观下线”。
  4.         failover:哨兵进程,负责执行故障转移操作,将主节点故障时选举出来的从节点晋升为新的主节点,并通知其他 Redis 节点更新配置信息。

通过上述哨兵进程的协同工作,Redis 哨兵机制可以实现自动化的故障转移,使得 Redis 的高可用性得到有效保障。在实际应用中,可以通过配置多个哨兵进程,使其互相监控和备份,从而进一步提高 Redis 系统的可靠性和稳定性。

说说你对PIPELINE的理解

PIPELINE 是 Redis 提供的优化命令执行的机制,通过减少网络往返次数和批量处理命令来提高性能。它将多个命令打包发送给服务器,减少了网络延迟,提升了吞吐量。

使用 PIPELINE 还可以降低服务器资源消耗,提高整体效率。适用于需要连续执行多个命令或批量操作的场景,特别适合延迟敏感或大量请求的应用程序。

总之,PIPELINE 是一种高效的方式来优化 Redis 命令执行,提升性能和效果。

REDIS集群会有写操作丢失吗?为什么

在Redis集群中,由于采用了主从复制模型的异步复制机制,写操作有一定的丢失风险。

当客户端向主节点发送写操作时,主节点会立即返回成功响应,而不等待所有从节点执行复制。如果主节点在执行完写操作后出现故障或网络问题,导致从节点无法及时接收到复制操作,那么这些未复制的写操作将会丢失。

为了减少写操作丢失的可能性,可以采取以下措施

  1.         定期监测集群状态,确保主从节点之间的复制正常进行;
  2.         设置合理的持久化策略,将数据写入磁盘或使用AOF模式以便数据恢复;
  3.         在应用程序层实施数据确认机制,检查写操作是否成功。

需要注意的是,Redis集群的主从复制模型无法完全消除写操作丢失的风险,但通过配置和监控的合理手段,可以最大限度地降低写操作丢失的可能性,保障数据的安全性和可靠性。

REDIS集群方案什么情况下会导致整个集群不可用

Redis 集群在以下情况下可能导致整个集群不可用:

  1.         多个主节点同时故障:如果多个主节点同时发生故障,而且它们的从节点无法正常升级为新的主节点,那么整个集群将无法提供读写服务。
  2.         集群管理节点故障:集群管理节点负责监控集群状态和协调故障转移操作。如果集群管理节点发生故障,并且无法及时恢复或替换,那么集群将失去管理和协调能力,可能导致集群不可用。
  3.         网络分区:如果集群中的节点之间发生网络分区,即无法互相通信,那么可能会引起脑裂(split-brain)问题。在这种情况下,每个分区内的节点可能会认为自己是合法的 Redis 集群,导致数据冲突和不一致性,最终导致整个集群无法正常工作。
  4.         配置错误:如果 Redis 集群的配置出现错误或者某些节点的配置不一致,可能导致集群无法正常运行。
  5.         内存不足:如果集群中的某个节点的内存不足以容纳当前处理的数据量,可能会导致该节点性能下降甚至崩溃,从而影响整个集群的可用性。

为避免整个集群不可用,建议采取以下措施:

  1.         配置正确的主从复制和故障转移机制,确保每个主节点都有足够的从节点,并定期进行故障转移测试。
  2.         部署多个独立的集群管理节点,以确保高可用性和决策一致性。
  3.         定期检查和监控集群配置,确保各个节点之间的配置一致性。
  4.         实施网络分区容忍策略,例如使用网络拓扑结构和分布式一致性协议,以减少脑裂问题的发生。
  5.         监控集群节点的内存使用情况,及时扩容或优化内存管理,避免内存不足问题。

综上所述,要确保 Redis 集群的高可用性和稳定性,需要合理设计和配置集群架构,并采取适当的监控和容错措施来应对潜在的故障情况。

REDIS 集群的原理是什么

Redis 集群通过数据分片和主从复制实现了横向扩展和高可用性。它将数据划分为 16384 个哈希槽,并将这些槽均匀地分配到多个节点上。每个节点负责处理一部分槽的数据,实现了数据的分散存储和负载均衡。

在集群中,每个哈希槽有一个主节点和多个从节点。主节点负责处理读写请求,而从节点则通过主从复制机制复制主节点的数据,提供数据的冗余备份和故障恢复功能

当主节点发生故障时,集群会自动进行故障转移。它会选举一个从节点升级为新的主节点,保证服务的持续可用性。同时,集群管理节点负责监控节点的状态,并协调故障转移过程。

客户端在与 Redis 集群交互时,根据键的哈希值将请求发送到相应的节点。客户端还可以通过集群管理节点获取整个集群的拓扑信息,了解哪些键存储在哪个节点上。

通过数据分片和主从复制,Redis 集群实现了数据水平切分、负载均衡和高可用性。它允许数据规模和吞吐量的线性扩展,并能自动处理节点故障。集群管理节点协调集群状态,客户端通过哈希槽映射与集群交互,从而实现了一个稳定和可靠的 Redis 分布式系统。

什么是缓存击穿、缓存穿透、缓存雪崩

缓存击穿、缓存穿透和缓存雪崩是与缓存相关的一些常见问题,具体定义如下:

  1.         缓存击穿:指当一个缓存键(key)对应的数据在缓存中不存在,同时又有大量并发请求访问该缓存键时,这些请求会直接绕过缓存,查询数据库或其他存储系统,导致数据库压力增大。缓存击穿通常在缓存过期后发生。
  2.         缓存穿透:指当一个查询请求访问一个不存在于缓存中且也不存在于数据库中的数据时,这个请求会无效地继续访问数据库,而不会被缓存。如果黑客故意发送大量非法请求,则缓存层无法起到过滤作用,可能导致数据库负载过大。
  3.         缓存雪崩:指当缓存集中在某个时间点失效或由于某个原因发生故障,导致大量的请求直接打到后端数据库,造成数据库瞬时压力过大,甚至引起数据库崩溃。在缓存雪崩期间,系统性能急剧下降,无法正常提供服务。

为了应对以上问题,可以采取以下措施:

  1.         对热点数据采用永不过期策略,避免缓存击穿。
  2.         在缓存层进行空值缓存,即将查询结果为空的数据也缓存一段时间,避免缓存穿透。
  3.         设置合理的缓存过期时间,并使用分布式缓存的多节点部署,避免缓存雪崩。
  4.         引入限流、熔断等机制,控制并发访问量,保护后端系统。
  5.         对重要数据做冷备份,确保即使缓存失效或故障,仍能从其他系统中恢复数据。

深入讲解

1.Redis缓存击穿、缓存雪崩、缓存穿透

缓存击穿、缓存雪崩和缓存穿透是我们在日常开发手撕面试官过程中必须battle的常见问题,下面我会解释它们的含义与解决方案。

1.缓存击穿(Cache Miss)

        什么是缓存击穿?

缓存击穿是指在高并发访问下,一个热点数据失效时,大量请求会直接绕过缓存,直接查询数据库,导致数据库压力剧增。通常情况下,缓存是为了减轻数据库的负载,提高读取性能而设置的。当某个特定的缓存键(key)失效后,在下一次请求该缓存时,由于缓存中没有对应的数据,因此会去数据库中查询,这就是缓存击穿。

解决方案:

  •         合理的过期时间:设置热点数据永不过期,或者设置较长的过期时间,以免频繁失效。
  •         使用互斥锁:保证同一时间只有一个线程来查询数据库,其他线程等待查询结果。
缓存雪崩(Cache Avalanche)

什么是缓存雪崩?

        缓存雪崩是指在大规模缓存失效或者缓存宕机的情况下,大量请求同时涌入数据库,导致数据库负载过大甚至崩溃的情况。

        正常情况下,缓存中的数据会根据过期时间进行更新,当大量数据同时失效时,下一次请求就会直接访问数据库,给数据库带来巨大压力。

解决方案:

  •         合理的过期时间:为缓存的过期时间引入随机值,分散缓存过期时间,避免大规模同时失效。或者是粗暴的设置热点数据永不过期
  •         多级缓存:使用多级缓存架构,如本地缓存 + 分布式缓存,提高系统的容错能力。
  •         使用互斥锁:保证同一时间只有一个线程来查询数据库,其他线程等待查询结果。
  •         高可用架构:使用Redis主从复制或者集群来增加缓存的可用性,避免单点故障导致整个系统无法使用。
缓存穿透(Cache Penetration)

什么是缓存穿透?

        缓存穿透是指恶意请求查询一个不存在于缓存和数据库中的数据,导致每次请求都直接访问数据库,从而增加数据库的负载。攻击者可以通过故意构造不存在的 Key 来进行缓存穿透攻击。

解决方案:

缓存空对象:对于查询结果为空的情况,也将其缓存起来,但使用较短的过期时间,防止攻击者利用同样的 key 进行频繁攻击。

参数校验:在接收到请求之前进行参数校验,判断请求参数是否合法。

布隆过滤器:判断请求的参数是否存在于缓存或数据库中。

Redis有哪些常用应用场景

Redis的常用应用场景主要包括:

  1.         缓存:作为高性能缓存层,提供快速数据访问。
  2.         分布式会话管理:实现跨服务器的会话共享。
  3.         消息队列:用作中间件实现异步通信和任务队列。
  4.         实时排行榜/计数器:用有序集合实现实时排名和计数功能。
  5.         地理位置信息存储与查询:支持存储地理位置信息并进行位置查询。
  6.         实时数据分析:存储实时生成的数据,进行快速统计和分析。

需要注意根据具体场景合理使用,充分考虑内存容量和数据持久化等因素。同时,Redis也可与其他存储系统结合使用构建复杂应用架构。

说说Redis的内存淘汰策略

Redis中的内存淘汰策略用于在内存不足时选择要淘汰的键,以释放内存空间。以下是几种常见的内存淘汰策略:

  1.         LRU(最近最少使用): LRU是Redis默认的内存淘汰策略。根据最近使用的时间戳来判断键的热度,将最久未被使用的键淘汰出去。这种策略保留了最近较常访问的键,适合于热点数据的场景。
  2.         LFU(最不经常使用): LFU根据键被访问的频率来判断热度,淘汰访问频率最低的键。这种策略适用于访问模式稳定但不同键的访问频率差异明显的场景。
  3.         Random(随机淘汰): 随机淘汰策略是一种基于概率的淘汰方法,随机选择一个键进行淘汰。这种策略简单高效,但可能导致较高的缓存命中率下降。
  4.         TTL(生存时间): TTL策略基于键的过期时间,淘汰剩余生存时间最短的键。适用于关注数据实效性的场景。
  5.         Maxmemory Policy(最大内存策略): Redis提供了几种最大内存策略,包括noeviction(禁止淘汰)、allkeys-lru、allkeys-random等。这些策略是在达到设定的最大内存限制后,对写操作返回错误,避免继续写入导致系统崩溃。

在Redis中,可以通过配置文件或动态命令来设置内存淘汰策略。根据具体的业务需求和数据访问模式,选择合适的内存淘汰策略以提高缓存的效率和性能。

深入精讲

Redis的内存淘汰策略

内存淘汰策略允许Redis在内存资源紧张时,根据一定的策略主动删除一些键值对,以释放内存空间并保持系统的稳定性。

  1.         noeviction(不淘汰策略)当内存不足以容纳新写入数据时,Redis 将新写入的命令返回错误。这个策略确保数据的完整性,但会导致写入操作失败。
  2.         volatile-lru(最近最少使用)从设置了过期时间的键中选择最少使用的键进行删除。该策略优先删除最久未被访问的键,保留最常用的键。
  3.         volatile-ttl(根据过期时间优先)从设置了过期时间的键中选择剩余时间最短的键进行删除。该策略优先删除剩余时间较短的键,以尽量保留剩余时间更长的键。
  4.         volatile-random(随机删除)从设置了过期时间的键中随机选择一个键进行删除。
  5.         allkeys-lru(全局最近最少使用)从所有键中选择最少使用的键进行删除。无论键是否设置了过期时间,都将参与淘汰。
  6.         allkeys-random(全局随机删除)从所有键中随机选择一个键进行删除。

说说Redis的过期策略

Redis的过期策略主要有三种:惰性删除、定期删除和定期淘汰

  1.         惰性删除: 惰性删除是Redis默认的过期键删除策略。当客户端尝试访问一个已过期的键时,Redis会立即将该键删除,并返回空值。这种策略的优点是删除操作是在需要时进行,减少了不必要的删除开销。但是,如果大量过期键在一次性被访问之前没有被访问过,这些键会一直占据内存空间。
  2.         定期删除:Redis会每隔一段时间执行一次检查,删除那些已过期的键。默认情况下,Redis每秒执行10次检查。定期删除通过释放过期键所占据的内存空间,使得内存能够及时被回收。但这种方式可能会导致内存占用较高,因为Redis并不保证在每次定期删除操作中都会删除足够数量的过期键。
  3.         定期淘汰: 定期淘汰是Redis 4.0版本引入的一种新的过期策略。与定期删除不同的是,定期淘汰不仅删除已过期的键,而且会主动查找并淘汰一些尚未过期但是由于内存不足而需要释放的键。通过定期淘汰,Redis可以更主动地管理内存,避免因为内存持续增长而导致系统性能下降。

总的来说,我们需要根据实际需求和业务场景选择最适合的过期策略,以平衡内存使用和系统性能。

深入精讲

Redis的过期策略

        1.惰性删除(Lazy expiration)

  •                 当客户端尝试访问某个键时,Redis会先检查该键是否设置了过期时间,并判断是否过期。
  •                 如果键已过期,则Redis会立即将其删除。这就是惰性删除策略。

该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

        2.定期删除(Active expiration)

  •                 Redis会每隔一段时间(默认100毫秒)随机检查一部分设置了过期时间的键。
  •                 定期过期策略通过使用循环遍历方式,逐个检查键是否过期,并删除已过期的键值对。

通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果

Redis中同时使用了惰性过期和定期过期两种过期策略。

  •         假设Redis当前存放20万个key,并且都设置了过期时间,如果你每隔100ms就去检查这全部的key,CPU负载会特别高,最后可能会挂掉。
  •         因此redis采取的是定期过期,每隔100ms就随机抽取一定数量的key来检查和删除的。
  •         但是呢,最后可能会有很多已经过期的key没被删除。这时候,redis采用惰性删除。在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且已经过期了,此时就会删除。

需要注意如果定期删除漏掉了很多过期的key,然后也没走惰性删除。就会有很多过期key积在内存中,可能会导致内存溢出,或者是业务量太大,内存不够用然后溢出了,为了应对这个问题,Redis引入了内存淘汰策略进行优化。

说说Redis的持久化机制

Redis有两种持久化机制:RDB和AOF。

  •         RDB是一种快照持久化的方式,它会将Redis在某个时间点的数据状态以二进制的方式保存到硬盘上的一个文件中。RDB持久化可以通过配置定时或手动触发,也可以设置自动触发的条件。RDB的优点是生成的文件比AOF文件更小,恢复速度也更快,适合用于备份和灾难恢复。
  •         AOF是一种追加日志持久化方式,它会将Redis执行的写命令追加到一个文件的末尾。当Redis重启时,它会重新执行这些写命令来恢复数据状态。AOF提供了更可靠的持久化方式,因为它可以保证每个写操作都被记录下来,并且不会发生数据丢失的情况。AOF文件可以根据配置进行同步写入硬盘的频率,包括每秒同步、每写入命令同步和禁用同步三种模式。

在使用持久化机制时,可以选择同时使用RDB和AOF,也可以只使用其中一种。同时使用两种方式时,Redis在重启时会先加载AOF文件来恢复数据,如果AOF文件不存在或损坏,则会尝试加载RDB文件。因此,AOF具有更高的优先级。

深入精讲

1.全面解析Redis-RDB与AOF持久化机制

Redis之所以能够提供高速读写操作是因为数据存储在内存中,但这也带来了一个风险,即在服务器宕机或断电的情况下,内存中的数据会丢失。为了解决这个问题,Redis提供了持久化机制来确保数据的持久性和可靠性。

Redis持久化机制:

  1.         RDB(Redis Data Base) :
  2.         内存快照AOF(Append Only File): 增量日志
  3.         混合持久化:RDB + AOF

RDB持久化

在指定的时间间隔内将内存中的数据集快照写入磁盘,RDB是内存快照(内存数据的二进制序列化形式)的方式持久化,每次都是从Redis中生成一个快照进行数据的全量备份。

RDB持久化流程:

image.png

RDB持久化方案进行备份时,Redis会单独fork一个子进程来进行持久化,会将数据写入一个临时文件中,持久化完成后替换旧的RDB文件。在整个持久化过程中,主进程(为客户端提供服务的进程)不参与IO操作,这样能确保Redis服务的高性能,RDB持久化机制适合对数据完整性要求不高但追求高效恢复的使用场景。

RDB触发规则

        手动触发

                save:阻塞当前 Redis进程,直到RDB持久化过程完成,如果内存实例比较大会造成长时间阻塞,尽量不要使用这方式

                bgsave:Redis主进程fork创建子进程,由子进程完成持久化,阻塞时间很短(微秒级)

        自动触发

                配置触发:在Redis安装目录下的redis.conf配置文件中搜索 /snapshot即可快速定位,配置文件默认注释了下面三行数据,通过配置规则来触发RDB的持久化,需要开启或者根据自己的需求按照规则来配置。

image.png

                save 3600 1 -- 3600 秒内有1个key被修改,触发RDB

                save 300 100 -- 300 秒内有100个key被修改,触发RDB

                save 60 10000 -- 60 秒内有10000个key被修改,触发RDB

        shutdown触发:

                shutdown触发Redis的RDB持久化机制非常简单,我们在客户端执行shutdown即可。

image.png

        flushall触发:

                flushall清空Redis所有数据库的数据(16个库数据都会被删除)(等同于删库跑路)

image.png

优点:

        性能高:RDB持久化是通过生成一个快照文件来保存数据,因此在恢复数据时速度非常快。        

        文件紧凑:RDB文件是二进制格式的数据库文件,相对于AOF文件来说,文件体积较小。

缺点:

        可能丢失数据:由于RDB是定期生成的快照文件,如果Redis意外宕机,最近一次的修改可能会丢失。

TIPS

        Redis持久化默认开启为RDB持久化

AOF持久化

        AOF持久化需要手动修改conf配置开启。

AOF持久化流程:

image.png

AOF持久化方案进行备份时,客户端所有请求的写命令都会被追加到AOF缓冲区中,缓冲区中的数据会根据Redis配置文件中配置的同步策略来同步到磁盘上的AOF文件中,同时当AOF的文件达到重写策略配置的阈值时,Redis会对AOF日志文件进行重写,给AOF日志文件瘦身。Redis服务重启的时候,通过加载AOF日志文件来恢复数据。

AOF配置:

AOF默认不开启,默认为appendonly no,开启则需要修改为appendonly yes

image.png

关闭AOF+RDB混合模式,设为no:

image.png

AOF同步策略:

image.png

        appendfsync always:

                每次Redis写操作,都写入AOF日志,非常耗性能的。

        appendfsync everysec

                每秒刷新一次缓冲区中的数据到AOF文件,这个Redis配置文件中默认的策略,兼容了性能和数据完整性的折中方案,这种配置,理论上丢失的数据在一秒钟左右

        appendfsync no

                Redis进程不会主动的去刷新缓冲区中的数据到AOF文件中,而是直接交给操作系统去判断,这种操作也是不推荐的,丢失数据的可能性非常大。

AOF修复功能:

redis 7版本,AOF文件存储在appendonlydir文件下,base是基准文件,incr是追加数据。

image.png

先存入三条数据,然后破坏incr结尾的文件内容,末尾加上baili

image.png

image.png

重新启动报错:

image.png

使用redis-check-aof --fix appendonlydir/appendonly.aof.1.incr.aof 对AOF日志文件进行修复

image.png

观察数据可以知道,丢失了cc-key值。这种丢失是被允许的。

AOF重写

重写其实是针对AOF存储的重复性冗余指令进行整理,比如有些key反复修改,又或者key反复修改后最终被删除,这些过程中的指令都是冗余且不需要存储的。

        自动重写:

        当AOF日志文件达到阈值时会触发自动重写。

        重写阈值配置:

image.png

                auto-aof-rewrite-percentage 100:当AOF文件体积达到上次重写之后的体积的100%时,会触发AOF重写。

                auto-aof-rewrite-min-size 64mb:当AOF文件体积超过这个阈值时,会触发AOF重写。当AOF文件的体积达到或超过上次重写之后的比例,并且超过了最小体积阈值时,Redis会自动触发AOF重写操作,生成一个新的AOF文件。

                手动重写:bgrewriteaof

                正常启动后存在三个文件:

image.png

通过set命令存储三条数据,最后在修改aa数据,然后手动重写:

image.png

观察结果可以得知key值aa历史轨迹已经被删除

image.png

优点:

        数据更加可靠:AOF持久化记录了每个写命令的操作,因此在出现故障时,可以通过重新执行AOF文件来保证数据的完整性。

        可以保留写命令历史:AOF文件是一个追加日志文件,可以用于回放过去的写操作。

缺点:

        文件较大:由于记录了每个写命令,AOF文件体积通常比RDB文件要大。

        恢复速度较慢:当AOF文件较大时,Redis重启时需要重新执行整个AOF文件,恢复速度相对较慢。

混合持久化

        Redis4.0版本开始支持混合持久化,因为RDB虽然加载快但是存在数据丢失,AOF数据安全但是加载缓慢。混合持久化通过aof-use-rdb-preamble yes开启,Redis 4.0以上版本默认开启

image.png

开启混合持久化之后:appendonlydir文件下存在一个rdb文件与一个aof文件

image.png

存入数据,然后执行bgrewriteaof重写文件。

image.png

image.png

总结

  •         推荐两者均开启
  •         如果对数据不敏感,可以选单独用RDB
  •         不建议单独用AOF,因为可能会出现Bug
  •         如果只是做纯内存缓存,可以都不用

说说你对Redis操作原子性的理解

Redis 的操作是原子性的,这是因为 Redis 的每个命令都是以单线程的方式执行的,整个命令的执行过程是不可中断的,要么全部执行成功,要么全部执行失败。

在 Redis 中,每个命令都会被转换成一个或多个底层操作,这些操作会基于数据结构的特定实现来执行。比如,对于字符串类型,获取一个键值对、设置一个键值对等操作都是原子性的。在执行这些底层操作时,Redis 会使用一些技术来保证原子性,主要包括以下两点:

  1.         Redis 使用单线程模型,避免了多线程之间的竞争条件和锁开销,从而保证了操作的原子性。
  2.         Redis 在执行一些复杂的操作时,比如事务、Lua 脚本等,会将多个底层操作打包成一个原子性操作,这些底层操作要么全部执行成功,要么全部执行失败。在事务和 Lua 脚本中,Redis 同时支持回滚操作,即当一些命令执行成功,后面的命令出错时,Redis 可以自动撤销已经执行的命令。

因此,Redis 的操作是原子性的,这得益于 Redis 单线程模型和底层操作的实现方式。这种原子性操作保证了 Redis 能够提供高效和可靠的服务。

使用 Redis 有哪些好处

使用Redis的好处包括:

  1.         缓存功能:作为高性能缓存系统,Redis能够将热门数据存储在内存中,提升数据访问速度和减轻数据库负载。
  2.         高性能:Redis采用内存存储和高效数据结构,具备快速读写速度,适用于处理大量请求和实时数据需求。
  3. 多样化数据类型:Redis支持多种数据类型,如字符串、哈希、列表、集合和有序集合,满足各种数据存储和操作需求。
  4.         支持持久化:Redis支持数据持久化,可将数据写入磁盘,确保数据的可靠性和持久存储。
  5.         支持分布式:Redis提供集群和分片机制,实现数据分布和水平扩展,提供高可用性和可扩展性。
  6.         简单易用:Redis具有简洁的命令接口,易于使用和管理,同时提供丰富的内置命令和客户端库。

总之,Redis以其高性能、多样化数据类型、持久化支持、缓存功能、分布式支持和简单易用等特点,成为广泛应用于各种场景的数据存储解决方案。

说说 Redis 的数据类型

Redis支持五种主要的数据类型:

  1.         String:String是最常用的数据类型,在Redis中以二进制安全的方式存储字符串值。它可以包含任何类型的数据,比如文本、整数或二进制数据。
  2.         Hash:Hash是一个键值对的集合,其中每个键都与一个值相关联。在Redis中,Hash可以用于存储和操作对象,每个键值对相当于对象的字段和值。
  3.         List:List是一个按照插入顺序排序的字符串元素集合。集合中的元素可以重复,可以从列表的两端进行插入和删除操作,可用于实现队列、栈等数据结构。
  4.         Set:Set是一个无序、唯一的字符串集合,不允许重复的成员。可以对集合执行添加、删除和判断成员是否存在等操作,也支持集合间的交集、并集和差集运算。
  5.         Sorted Set:Sorted Set是一个有序的字符串集合,每个成员都关联着一个分数。集合中的成员根据分数的大小进行排序,可以进行范围查询和按分数排名操作。

除了这些主要的数据类型,Redis还提供了其他一些特殊的数据结构和功能,如HyperLogLog用于基数统计、Geo用于地理位置信息存储、Pub/Sub用于发布与订阅等。通过这些不同的数据类型,Redis可以灵活地存储和操作各种类型的数据,满足不同应用场景下的需求。

为什么Redis 单线程模型效率也能那么高

尽管Redis采用了单线程模型,但其效率仍然非常高。以下是一些原因:

  1.         非阻塞IO:Redis使用了事件驱动的非阻塞IO机制。它通过事件循环处理来自客户端的请求,在等待数据IO时并不会阻塞主线程,而是继续处理其他请求。这种机制允许Redis以高效地方式处理大量的并发连接。
  2.         内存操作:Redis主要将数据存储在内存中,并且由于单线程模型的存在,在内存操作的情况下,Redis可以通过简单的指针操作来实现快速读写,而不需要考虑复杂的数据同步和竞争条件。
  3.         单线程避免的开销:与多线程模型相比,单线程模型避免了线程间的上下文切换、锁竞争和资源管理开销。这使得Redis可以更高效地使用CPU资源,并减少了大量与线程相关的开销。

需要注意的是,Redis单线程模型适合于处理大量的短期操作和快速响应的场景,但在处理长时间运行的计算密集型任务时可能会有性能上的不足。为了提高处理能力和并发性,可以使用Redis的集群模式、多实例部署或将计算密集型任务委托给其他更适合的工具或语言来处理。

Redis是单线程还是多线程

Redis 采用的是单线程模型。通常说得单线程,主要指的是 Redis 对外提供的键值存储服务的主要流程是单线程的,即网络 I/O 和数据读写是由单个线程来完成的。这样设计可以避免多线程之间的竞争条件和锁开销,提高了访问共享数据的效率。

然而,除了对外提供的键值存储服务,Redis 在某些功能上会使用额外的线程来执行,比如持久化、异步删除和集群数据同步等。这些功能需要在后台执行,不参与主要的网络 I/O 和数据处理。因此,严格来说,Redis 并不是完全单线程。

怎么实现Redis的高可用?

要实现Redis的高可用性,可以采取以下几个关键步骤和措施:

  1.         主从复制:通过设置主从复制,将主节点的数据同步到多个从节点上。主节点负责处理写操作,并将写操作的日志复制给从节点,从节点则负责处理读请求。如果主节点发生故障,可以将一个从节点升级为新的主节点,从而实现故障转移和高可用。
  2.         哨兵机制:使用Redis 哨兵来监控主节点和从节点的状态。哨兵是一组独立运行的进程,它会监控Redis实例的健康状态,并在主节点出现故障时自动进行故障转移。它还能够监控从节点,并在需要时将其提升为主节点。
  3.         集群模式:Redis 集群是一种分布式方案,可以将多个Redis节点组成一个逻辑集群,提供数据分片和自动故障恢复。每个节点负责存储和处理部分数据,通过节点间的数据分片和分布式算法保证数据的可用性和负载均衡。当集群中的某个节点出现故障时,集群会自动进行故障转移和恢复。

Redis为什么把所有数据都放内存

Redis将所有数据放到内存中的主要原因是为了提供高性能的读写操作。

以下是几个主要的原因:

  1.         高速读写:内存访问速度快,相比于磁盘和数据库,内存操作速度更快,能够更迅速地响应读写请求。将数据存储在内存中可以大大缩短读写的延迟,提高系统的响应速度和吞吐量。
  2.         简单数据结构:Redis使用简单的数据结构来存储数据,如字符串、列表、哈希、集合和有序集合等。这些数据结构直接映射到内存,不需要进行复杂的数据转换和序列化操作,提高了读写效率。
  3.         数据持久化:尽管Redis将数据存储在内存中,但它也支持数据的持久化。通过使用RDB快照和AOF日志两种方式,Redis可以将内存中的数据定期或实时写入磁盘,以保证数据的持久性和安全性。

需要注意的是,由于内存容量有限,Redis的内存管理也是需要考虑的。通过设置合适的数据过期策略、内存淘汰策略和最大内存限制等措施,可以在保证高性能的同时,有效地管理内存使用。同时,Redis也可以通过集群和分片等方式来扩展内存容量和提高系统的可用性和性能。

Redis为什么这么快

Redis之所以快速的原因主要包括以下几点:

  1.         内存存储:Redis将数据存储在内存中,实现了快速的读写操作。
  2.         单线程模型:Redis采用单线程处理请求,避免了多线程的竞争和上下文切换开销。
  3. 高效的数据结构:Redis内部使用了高效的数据结构,如哈希表、跳跃表等,提供了快速的数据访问和操作。
  4.         异步IO:Redis利用异步IO来处理网络请求,能够同时处理多个请求,提高并发性能。
  5.         事件驱动架构:Redis基于事件驱动的模型,通过事件循环机制处理请求和操作,提高系统的效率。
  6.         优化的操作:Redis对常用操作进行了优化,如批量操作和管道技术,减少了网络通信开销。

综上所述,Redis之所以快速在于内存存储、单线程模型、高效的数据结构、异步IO、事件驱动架构和优化的操作等因素的综合作用。这使得Redis能够以高性能和高响应速度处理各类数据操作请求。

一个REDIS实例最多能存放多少KEYS

Redis 的每个实例最多可以存放约 2^32 - 1 个keys,即大约 42 亿个keys。这是由 Redis 内部使用的哈希表实现决定的,它使用 32 位有符号整数作为索引。Redis 使用的哈希函数和负载因子等因素也会影响实际可存放键的数量。

需要注意的是,尽管 Redis 允许存储数量庞大的键,但在实践中,存储过多的键可能会导致性能下降和内存消耗增加。因此,在设计应用程序时,需要根据实际需求和硬件资源来合理规划键的数量,避免过度使用 Redis 实例造成负担。如果需要存储更多的键值对,可以考虑使用 Redis 集群或分片技术,以扩展整体存储容量。

  • 24
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值