Redis
大约 20 分钟
阅读提示
Redis 高频问题集中在:数据结构、持久化、一致性、缓存异常。
建议每题都补一句“线上防护动作”(如限流、降级、双删、布隆过滤器)。
Redis
1、Redis支持哪些数据类型及应用场景?
- String:字符串、整数、浮点型,可用于计数器、分布式锁
- Hash:包含键值对的无序散列表,可用于存储用户信息对象(User对象)
- List:列表,可用于存储集合,消息队列
- Set:无序集合不可重复,可用于好友关系(Set交并差集)
- Zset:有序集合不可重复,可用于排行榜(zset的score)
2、缓存和数据库的一致性
- 如果业务量不大,并发不高的情况,可以选择先更新数据库,后删除缓存的方式
- 业务量比较大,并发度很高的话,那么建议选择先删除缓存,先删除缓存有两种方案:
- 延时双删:先删缓存再更新数据库,再延时删除缓存(比如休眠500ms再删),然后再写入缓存
- 分布式锁:删除缓存,对key加锁,更新数据库再写缓存,再释放锁
3、Redis的持久化机制
- 持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。RDB(默认)和AOF两种。
- rdb是在一定的间隔时间中(几分钟,几小时。几天),检测key的变化情况,然后生成快照持久化数据到磁盘中;
- AOF是日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,通过一个后台线程执行一次fsync操作,持久化数据。
- 如何选择:
- RDB 二进制存储节省空间,只有一个dump.rdb文件。灾难恢复较快。性能最大化(主进程处理命令的效率,而不是持久化的效率),采取的是fork()+copyonwrite技术。 但是安全性相对较低,因为rdb是每隔一段时间进行持久化,数据丢失率较高。持久化速度相对aof较低,因为aof直接append追加,rdb是全量。
- AOF数据安全,可以配置always,也就是每进行一次命令操作就记录到aof文件中一次。持久化速度较快,每次都只是追加一个语句到文件,带rewrite机制。AOF 文件比 RDB 文件大,所以灾难性恢复速度慢。会对主进程对外提供请求的效率造成影响,接收请求、处理请求、写aof文件这三步是串行原子执行的。而非异步多线程执行的。Redis单线程!
- Redis4.0后支持混合持久化,也就是rdb+aof可以同时开启,然后使用AOF来重建数据,因为AOF中的数据更加完整,让rdb作为冷备份。
4、Redis中key的过期策略
- 定期删除:指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。假设redis里放了10万个key,都设置了过期时间,你每隔几百毫秒,就检查10万个key,那redis基本上就死了,cpu负载会很高的,消耗在你的检查过期key上了。注意,这里可不是每隔100ms就遍历所有的设置过期时间的key,那样就是一场性能上的灾难。实际上redis是每隔100ms随机抽取一些key来检查和删除的。
- 惰性删除:定期删除可能会导致很多过期key到了时间并没有被删除掉,那么惰性删除就是在你获取某个key的时候,redis会检查一下 ,这个key如果过期了就删除,不会给你返回任何东西。
5、Redis的淘汰策略(了解)
- 如果redis的内存占用过多的时候,此时会进行内存淘汰策略。
- volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰;
- volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰;
- volatile-random:从已设置过期时间的数据集中任意选择数据淘汰;
- allkeys-lru:从数据集中挑选最近最少使用的数据淘汰;
- allkeys-random:从数据集中任意选择数据淘汰;
- no-enviction:不淘汰任何键值对,直接返回错误信息;(默认)
6、缓存雪崩、缓存穿透、缓存击穿、缓存预热、缓存降级
- 缓存雪崩:缓存大面积同时间段失效 ,请求都去查询数据库了,对数据库CPU和内存造成巨大压力。
- 解决办法:
- 将缓存失效时间分散开;
- 考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。
- 缓存穿透:缓存穿透指的是当查询一个数据库中不存在的数据时,由于缓存层没有存储这个数据的记录(即缓存未命中),因此每次查询都会穿透到数据库层,导致数据库承受不必要的压力。
- 解决办法:
- 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
- 也可以将查询返回的数据为空的这个结果进行缓存,过期时间设置短一点短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。
- 缓存击穿:某个 key 非常热点,访问非常的频繁,高并发访问的情况下,当这个 key在失效的瞬间,大量的请求进来,这时候就击穿了缓存,直接请求到了数据库。
- 解决办法:
- 热点数据永不过期。
- 分布式锁,来个请求先锁住,然后去db查,查到后再将数据set到redis里。只有当redis里getKey没拿到数据需要请求db的时候才加锁。
- 缓存预热:就是系统启动的时候就查询数据库将相关的数据直接缓存到Redis中。这样可以避免用户查询的时候先判断是否有缓存,没的话在查db进行缓存等一系列操作了。
- 实现方案
- 如果数据量不大的话,可以在项目启动的时候进行缓存。
- 如果数据量很大的话可以写个简易的页面,上线时手工点一下进行缓存。
- 定时任务定时刷新缓存。
- 缓存降级:就是当缓存出故障的时候,别影响主业务系统,让他可以继续对外提供服务
- 实现方案
- 很简单,在catch里去查db,而不是抛出异常。
- 利用熔断降级组件返回错误码。
7、Redis是单线程的吗
- Redis是单线程的,Redis用一个线程来处理所有的请求和操作。
- Redis 之所以选择单线程的设计,因为它是一个基于内存的存储系统,而且它的瓶颈通常在于 CPU 的速度而不是多核的并发。通过使用单线程,Redis 简化了并发控制和数据结构的实现,避免了多线程的复杂性和线程安全的问题,通过多路复用 I/O模型能够在单线程中处理大量的并发连接。
- Redis 也提供了一些多线程的功能,比如AOF、RDB 持久化以及从节点复制可以在其他线程中执行。
8、Redis的setnx命令原理(重要)
- SETNX即Set if Not eXists,setnx是redis的一个命令,它会检查指定的键是否已经存在于数据库中,如果键不存在,则将该键设置指定的值,并返回1,表示操作成功。如果键已经存在,SETNX命令将不执行任何操作,返回0,表示操作失败。
- 这个原理保证了在多个客户端同时尝试设置同一个键时,只有一个客户端能够成功执行设置操作,其他客户端将得到失败的返回值。这使得setnx 可以用于实现分布式锁。
9、列举几个Redis应用场景?
- 计数器
- 数据缓存
- 页面缓存
- 消息队列(blpop)
- 分布式锁
- 好友关系(set交并差集)
- 排行榜(zset的score)
10、Redis线程模型知道吗?(了解)
- redis 内部使用文件事件处理器 file event handler,它是单线程的,所以redis才叫做单线程模型。它采用IO多路复用机制同时监听多个 socket。多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。
11、RDB持久化的原理是怎样的?(了解)
- RDB(Redis DataBase)持久化的原理主要是通过在指定的时间间隔内,将内存中的数据集快照(Snapshot)写入到磁盘中,以保存为rdb文件的形式。这个过程是通过fork一个子进程来完成的,子进程会共享父进程的内存数据,然后遍历整个内存数据,将数据写入到一个临时文件中。当持久化过程结束后,会用这个临时文件替换上一次的持久化文件(rdb文件)。
- 在RDB持久化的过程中,主进程(父进程)仍然可以继续处理客户端的请求,而子进程则负责完成持久化的工作。由于子进程是父进程的一个副本,因此它包含了父进程在fork时刻的内存数据状态。所以,即使主进程在持久化过程中继续处理请求并修改了数据,这些修改也不会影响子进程进行持久化的数据状态。
- RDB持久化的优点在于它生成的文件是一个紧凑的二进制文件,可以很方便地进行备份和传输。而且,由于它是基于快照的持久化方式,因此可以很好地支持数据的恢复。但是,RDB持久化也有一些缺点。首先,由于它是在某个时间点上对整个内存数据进行快照,因此可能会丢失在这个时间点之后的数据修改。其次,在进行RDB持久化时,会消耗一定的系统资源(如CPU和内存),可能会影响Redis的性能。
12、为什么要用copyonwrite?(了解)
- 假设是全量复制,那么内存空间直接减半,浪费资源不说,数据量10g,全量复制这10g的时间也够长的。这谁顶得住?
- 如果不全量复制,会是怎样?相当于我一边复制,你一边写数据,看着貌似问题不大,其实不然。比如现在Redis里有k1的值是1,k2的值是2,比如bgsave了,这时候rdb写入了k1的值,在写k2的值之前时,有个客户端请求
- 暂时无法在飞书文档外展示此内容
- 那么持久化进去的是k2 22,但是k1的值还是1,而不是最新的11,所以会造成数据问题,所以采取了copyonwrite技术来保证触发bgsave请求的时候无论你怎么更改,都对我rdb文件的数据持久化不会造成任何影响。
13、AOF持久化的原理是怎样的?(了解)
- 就是每次都在aof文件后面追加命令。他与主进程收到请求、处理请求是串行化的,而非异步并行的。图示如下:
- 所以aof的频率高的话绝逼会对Redis带来性能影响,因为每次都是刷盘操作。跟mysql一样了。Redis每次都是先将命令放到缓冲区,然后根据具体策略(每秒/每条指令/缓冲区满)进行刷盘操作。如果配置的always,那么就是典型阻塞,如果是sec,每秒的话,那么会开一个同步线程去每秒进行刷盘操作,对主线程影响稍小。
14、Redis的事务概念和原理
- Redis的事务并不像Mysql那么灵活,有隔离级别,出问题后还能回滚数据等高级操作。Redis毕竟是非关系型数据库,他目前事务回滚机制是不执行命令,也就是可以采取watch命令模拟乐观锁,进行监听数据,发现数据不是事务开始时候的样子了,那么我这个事务里的命令就不会得到执行。
- 原理:
- watch
- 每个数据库都维护一个字典watched_keys,key为监听的key,value即为监听的客户端对象。 所有客户端的修改都会去检查这个字典,如果key一致则会去打开客户端对象的 REDIS_DIRTY_CAS.
- multi 和 exec
- multi命令将客户端标记为“事物状态”,这个客户端后续的普通操作指令都会放入事务队列(客户端会保存一个事务队列),直到执行multi、exec、watch(?)、discard。
- exec时会服务端去查当前客户端的REDIS_DIRTY_CAS,如果被打开了,则拒绝客户端提交的事务。没打开则遍历客户端事务队列,挨条执行。
15、Redis的哨兵机制
- 哨兵的核心功能:在主从复制的基础上,哨兵引入了主节点的自动故障转移。
- Sentinel 定期向 Redis 主从节点发送 ping 命令,检查节点的健康状况。通过监测节点的响应时间和是否能够正常处理命令,Sentinel 可以判断节点的状态,通过多个哨兵之间的协商来达成一致的判断。当一个哨兵认为某个节点可能故障时,它会将该节点标记为主观下线,当多个哨兵都认为某个节点都下线时,该节点才被判断为客观下线。
- 当节点被判断为客观下线时,Sentinel 会发送通知,通知其他 Sentinel 和客户端。Sentinel 在发现主节点故障时,会从当前的从节点中投票选举一个新的主节点,再更新配置。这确保了整个 Redis 集群在主节点故障后的自动恢复。
16、Redis主从复制(了解)
- 主从复制是指将一台 Redis 服务器的数据复制到其他的 Redis 服务器。前者称为主节点(Master),后者称为从节点(Slave);数据的复制是单向的,只能由主节点到从节点。 默认情况下,每台 Redis 服务器都是主节点,且一个主节点可以有多个从节点 (或没有从节点),但一个从节点只能有一个主节点。
- 主从复制实现了数据的热备份;当主节点出现问题时,可以由从节点提供服务,实现故障的快速恢复;在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务 (即写 Redis 数据时应用连接主节点,读 Redis 数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。主从复制是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
- 主从复制的原理:若启动一个Slave机器进程,则它会向Master机器发送一个“sync command" 命令,请求同步连接。无论是第一次连接还是重新连接,Master机器都会启动一个后台进程,将数据快照保存到数据文件中(执行rdb操作) ,同时 Master 还会记录修改数据的所有命令并缓存在数据文件中。后台进程完成缓存操作之后,Master 机器就会向 Slave 机器发送数据文件,Slave 端机器将数据文件保存到硬盘上,然后将其加载到内存中,接着 Master 机器就会将修改数据的所有操作并发送给 Slave 端机器。若 Slave 出现故障导致宕机,则恢复正常后会自动重新连接。Master机器收到 Slave 端机器的连接后,将其完整的数据文件发送给 Slave 端机器,如果 Mater 同时收到多个 Slave 发来的同步请求,则 Master 会在后台启动一个进程以保存数据文件,然后将其发送给所有的Slave端机器,确保所有的 Slave 端机器都正常。
17、Redis的集群(了解)
什么是 Redis 集群
简单理解,Redis 集群是由多个 Redis 实例协同工作,以完成相同的任务或在高并发情况下分担负载。它不仅能够代替主节点(Master)成为新的主节点以继续任务,还能保持数据的完整性和一致性。
Redis 的部署模式
Redis 提供了以下三种主要的部署模式,以满足不同的高可用性和扩展性需求:
主从复制模式(Master-Slave Replication)
哨兵模式(Sentinel Mode)
集群模式(Cluster Mode)
主从复制模式(Master-Slave Replication)
描述:
- 主节点(Master):负责处理所有的写操作和部分读操作。
- 从节点(Slave):复制主节点的数据和状态信息,仅用于数据备份和分担读请求。
特点:
- 数据冗余:通过多个从节点备份主节点的数据,提高数据的可靠性。
- 读写分离:将读请求分发到从节点,提升读性能。
- 手动故障切换:当主节点故障时,需要手动将某个从节点提升为新的主节点。
适用场景:
- 数据备份和恢复。
- 读操作较多的应用,通过从节点分担读压力。
- 哨兵模式(Sentinel Mode)
描述:
- 基于主从复制模式,增加了 哨兵(Sentinel) 节点来监控 Redis 集群的状态。
- 哨兵节点负责自动检测主节点的故障,并在故障发生时自动进行主从切换。
特点:
- 高可用性:自动故障检测与切换,减少人工干预。
- 监控与通知:实时监控 Redis 节点的状态,并在必要时发送通知。
- 不支持数据分片:仍然受限于单机的存储容量。
适用场景:
- 需要高可用性的应用,但数据量在单机可承受范围内。
- 希望简化运维,减少手动故障处理的场景。
- 集群模式(Cluster Mode)
描述:
- Redis Cluster 通过 数据分片(Sharding) 将数据分布到多个主节点,每个主节点可以有一个或多个从节点。
- 集群中的每个主节点负责部分键空间,实现水平扩展。
特点:
- 数据分片:打破单机内存限制,将数据分布到多个节点,支持更大规模的数据存储。
- 高性能:多个主节点并行处理请求,提高整体吞吐量。
- 高可用性:支持主节点的自动故障转移,每个主节点的从节点可以接管其任务。
- 负载均衡:读写请求可以分布到不同的主节点,提升整体性能。
- 复杂性增加:需要更多的配置和管理,节点之间需要协调一致。
适用场景:
- 大规模数据存储和高并发访问需求。
- 需要分布式存储和高可用性的应用。
- 需要扩展 Redis 集群以应对增长的数据量和流量。
三种模式的比较
- 主从复制模式(Master-Slave Replication)
- 优势:数据备份、读写分离、配置简单。
- 劣势:主节点故障时需要手动切换,写操作无法负载均衡,存储能力受限于单机。
- 哨兵模式(Sentinel Mode)
- 优势:基于主从复制,自动故障恢复,提高高可用性,减少人工干预。
- 劣势:写操作无法负载均衡,存储能力受限于单机,不支持数据分片。
- 集群模式(Cluster Mode)
- 优势:实现数据分片和负载均衡,突破单机存储限制,支持高并发和高性能,具备较完善的高可用方案。
- 劣势:配置和管理复杂,节点之间需要良好的协调机制。
18、Redis的脑裂
- 某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着,此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master,这个时候,集群里就会有两个master也就是所谓的脑裂。
- 解决方案:
- min-replicas-to-write 3 //与主节点通信的从节点数量必须大于等于该值主节点,否则主节点拒绝写入
- min-replicas-max-lag 10 //主节点与从节点通信的ACK消息延迟必须小于该值,否则主节点拒绝写入
- 按照上面的配置,要求至少3个slave节点,且数据复制和同步的延迟不能超过10秒,否则的话master就会拒绝写请求,配置了这两个参数之后,如果发生集群脑裂,故障期间满足min-slaves-to-write和min-slaves-max-ag的要求,那么主节点就会被禁止写入,脑裂造成的数据丢失情况自然也就解决了。
19、哨兵之间是怎么发现彼此的 ?(了解)
- 是通过PSUBSCRIBE这个Redis内置的发布订阅命令来实现的,他们共同监听 sentinel:hello 这个channel,每隔两秒钟,每个哨兵都会往自己监控的某个master+slaves对应的__sentinel__:hello channel 里发送一个消息,内容是自己的host、ip和runid还有对这个master的监控配置,每个哨兵也会去监听自己监控的每个master+slaves对应的 sentinel:hello channel ,然后去感知到同样在监听这个master+slaves的其他哨兵的存在,每个哨兵还会跟其他哨兵交换对master的监控配置,互相进行监控配置的同步。
20、如何优化Redis内存?
- 选择合适的数据类型
- 大value选择gzip压缩
- 避免使用大key
- 批处理采取Pipeline(小心配置单次pipeline命令总大小,不要撑爆socket缓冲区)
21、Redis为什么这么快?(了解)
- 纯内存操作,且时间复杂度大多都是O(1),也有特例,比如zset底层跳表是log(n),keys....等!
- 数据结构底层存储的设计比较节省空间
- 采取单线程避免了不必要的上下文切换。
- 采取I/O多路复用,epoll模型,非阻塞IO。
22、五种数据类型的底层存储原理是什么?(了解,跳表需要重点看下)
- string:int、raw、embstr
- list:ziplist、linkedlist
- hash:ziplist、hashtable
- set:intset、hashtable
- sorted set:ziplist、skiplist+dict
