Redis常见面试问题
什么是Redis,具有哪些特点?
Redis是一个基于内存的数据库,读写速度非常快,通常被用作缓存、消息队列、分布式锁和键值存储数据库。它还支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等,Redis还提供了分布式特性,可以将数据分布在多个节点上,以提高可扩展性和可用性。
MySQL和Redis的区别?**
- 存储方式:redis基于键值对,支持多种数据结构;而MySQL是一种关系型数据库,使用表来组织数据。
- 持久化:redis将数据存在内存中,通过持久化机制将数据写入磁盘;MySQL通常将数据存储在磁盘上。
- 复杂查询支持:redis不使用SQL,而是使用自己的命令集,不支持复杂的查询;MySQL使用SQL语言,可以进行复杂的查询操作。
- 应用场景:redis以高性能能低延迟为目标,适用于读多写少的应用场景;MySQL适用于需要复杂查询、事务处理和大规模数据存储的应用场景。
Redis有什么优缺点?为什么用Redis查询会比较快?
优点:Redis是一个基于内存的数据库,读写速度非常快,通常被用作缓存、消息队列、分布式锁和键值存储数据库。常支持多种数据类型,如字符串、哈希表、列表、集合、有序集合等,Redis还提供了分布式特性,可以将数据分布在多个节点上,以提高可扩展性和可用性。
缺点:1.内存限制,redis将数据存储在内存中,因此它受到物理内存大小限制;2.单线程模型,redis使用单线程处理客户端请求,这限制了他在高并发场景下的吞吐量;3.redis不支持SQL数据库那样的复杂查询操作。
redis查询速度快的原因:
- 基于内存:内存的本身的读写速度很快,这是redis速度快的主要原因;
- 高效的数据结构:redis专门设计了STRING、LIST、HASH等高效数据结构,依赖各种数据结构提升了读写的效率;
- 单线程:单线程操作避免了多线程资源竞争和上下文切换带来的性能损失;
- I/O多路复用:采用I/O多路复用同时监听多个Socket,根据Socket上的事件来选择对应的事件处理器进行处理。
Redis是单线程的还是多线程的,为什么?***
Redis是单线程的,原因如下:
- CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存或者网络带宽。
- 单线程容易实现,并且单线程避免了多线程的资源竞争和上下文切换的开销。
Redis的数据类型?有哪些使用场景?
Redis常见的数据类型有:String(字符串)、Hash(哈希表)、List(列表)、Set(集合)、Zset(有序集合)
- 字符串:存储字符串数据,也可以存储整数、浮点数,是最基本的数据类型,常用于缓存对象、常规计数、分布式锁、共享Session信息等;
- 哈希表:存储字段和值的映射,常用于缓存对象、购物车等;
- 列表:存储有序的字符串元素,常用于消息队列(有两个问题:1. ⽣产者需要自行实现全局唯⼀ ID;2. 不能以消费组形式消费数据);
- 集合:存储无序不重复的字符串元素,常用于聚合运算场景(并集、交集、差集),如点赞、共同关注、抽奖活动;
- 有序集合:类似于集合,但是可以根据元素所关联的分数进行排序,常用于排序场景,如排行榜。
随着Redis版本更新,又更新了这些数据类型:
- BitMap:存储位的数据结构,可以处理一些位运算操作,比如签到、登录状态等;
- HyperLogLog:用于基数估算的数据结构,用于统计元素的唯⼀数量,如海量数据基数统计的场景;
- GEO:存储地理位置信息的数据结构;
- Stream:专门为消息队列设计的数据类型。
Redis持久化机制有哪些?
- AOF日志:每次执行一条写操作指令,就把该指令以追加的方式写入到一个文件里;
- RDB快照:将某一时刻的内存数据,以二进制的方式写入磁盘;
- 混合持久化方式:集成了前两种的优点。(AOF优点是服务器宕机时丢失数据少,但是数据恢复不够快;RDB的优点是数据恢复快,但是保存快照的频率不好把握,频率高会影响新能,频率低会丢失的数据较多。)
混合持久化如何实现的?
混合持久化工作在 AOF 日志重写过程。在 AOF 重写日志时,fork出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的AOF 文件。文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据。
总结:首先将内存的数据以RDB的方式写入到AOF文件,而后续主线程的写操作命令会被记录到重写缓冲区,重写缓冲区里的增量命令会以AOF 方式写入到 AOF 文件,最后前半部分是RDB格式的全量数据、后半部分是AOF格式的增量数据的AOF文件再持久化到磁盘上。
AOF的三种写回策略?
Always、Everysec和No,这三种策略的可靠性是从高到低,而性能是从低到高。
Always是每次写操作命令执行完后,同步将AOF日志数据写回磁盘;Always每次写操作命令执行完后,先将命令写入到AOF文件的内核缓冲区,然后每隔一秒将缓冲区的内容写回磁盘;No就是不控制写回磁盘的时机。每次写操作命令执行完后,先将写操作命令写入到AOF文件的内核缓冲区,再由操作系统决定何时将缓冲区的内容写回磁盘。
Redis集群模式有哪些/Redis有哪些部署方案?哨兵模式的功能?
- 主从复制:将一个Redis实例的数据复制到其他实例,其中一个是主节点(master),其余是从节点(slave)。主节点将写操作传播到所有从节点。
- 哨兵(Sentinel):监控Redis实例的状态,发现主节点故障时,自动进行故障转移。
- 切片集群(Cluster):将数据分布在不同的服务器上,以此来降低系统对单主节点的依赖。
Redis哨兵用于监控Redis实例的状态,发现主节点的故障并自动进行故障转移。
- 监控:监控Redis主服务器和从服务器的状态,包括连接状态、是否能够执行命令、是否有持久性问题等。
- 故障转移:当哨兵(sentinel)发现主服务器不可用时(例如宕机),它会通过一定的选举机制选择一个从服务器升级为新的主服务器。
- 通知:哨兵(sentinel)在选举完新的主服务器后,通知其他从服务器将其切换到新的主服务器。
哨兵的工作原理?
判断节点是否存活:每个哨兵定期向Redis服务器发送PING命令,以检测服务器是否处于活跃状态。若哨兵在连续一定次数未收到服务器的响应,就认为该服务器主观下线。然后哨兵就会从从节点中选择一个作为主节点。
选出新主节点:在发现主服务器下线后,哨兵们会协调选举一个新的主服务器。这个过程中,哨兵会考虑每个可用的从服务器,选择个作为新的主服务器,并将其他从服务器配置为复制新的主服务器。
具体过程:
- 选择候选从服务器:哨兵会从可用的从服务器中选择一组候选服务器,通常选择复制偏移量 (replicationoffset) 最大的从服务器。
- 计算投票:每个哨兵为每个候选从服务器投票。投票的考量因素包括从服务器的复制偏移量、连接质量、优先级等。
- 达成共识:哨兵们根据投票结果达成共识,选择一个从服务器作为新的主服务器。这通常需要获得多数哨兵的同意。
更新配置信息:一旦新的主服务器被选出,哨兵会更新 Redis 集群的配置信息,包括将新的主服务器的地址和端口通知给其他哨兵和客户端。
通知客户端:哨兵会向客户端发送通知,告知客户端新的主服务器的位置,以便客户端能够重新连接。
Redis过期删除策略有哪些?
- 定时删除:设置key的过期时间,当时间到达时,自动执行key的删除操作。
- 惰性删除:每次从数据库访问 key 时,都检测 key 是否过期,如果过期则删除该 key。
- 定期删除:每隔一段时间随机从数据库中取出一定数量的 key 进行检查,并删除其中的过期key。
Redis选择 惰性删除 + 定期删除 这两种策略配合使用。
缓存雪崩、击穿、穿透和解决办法
缓存雪崩:指的是在某个时间点,缓存中大量数据同时失效,导致请求直接访问数据库或其他后端系统,增加了系统的负载。
原因:大量数据的过期时间相近或者Redis服务器故障宕机。
解决方法:对于第一种情况可以均匀设置过期时间、加互斥锁、后台更新缓存(让缓存“永久有效”,并将更新缓存的⼯作交由后台线程定时更新)等策略;对于第二种情况可以采用服务熔断或请求限流机制,还有构建Redis缓存高可用集群等方法。
缓存击穿:指的是有大量请求查询一个缓存中不存在但数据库中存在的数据时,这些请求直接访问到数据库,增加数据库的负载。典型的场景就是一个热点数据过期被删除,但此时又有大量请求访问这个数据。
解决方法:缓存击穿是缓存雪崩的一个子集,可以采用互斥锁和后台更新缓存等策略。
缓存穿透:指的是查询一个缓存和数据库都不存在的数据,这个数据始终无法被缓存,导致每次请求都直接访问数据库,增加数据库的负载。
原因:恶意攻击、业务误操作,缓存和数据库中的数据都被删除了。
解决方法:限制非法请求、对查询的数据,在缓存中设置空值、使用布隆过滤器过滤恶意请求。
如何保证数据库和缓存的一致性?
Cache Aside
原理: 先从缓存中读取数据,如果没有就再去数据库里面读数据,然后把数据放回缓存中,如果缓存中可以找到数据就直接返回数据,更新数据的时候先把数据持久化到数据库,然后再让缓存失效。
问题: 假如有两个操作一个更新一个查询,第一个操作先更新数据库,还没来及删除数据库,查询操作可能拿到的就是旧的数据,更新操作马上让缓存失效了,所以后续的查询可以保证数据的一致性;还有的问题就是有一个是读操作没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,也会造成脏数据。
可行性: 出现上述问题的概率其实非常低,需要同时达成读缓存时缓存失效并且有并发写的操作。数据库读写要比缓存慢得多,所以读操作在写操作之前进入数据库,并目在写操作之后更新,概率比较低。
Read/Write Through
原理: Read/Write Through原理是把更新数据库(Repository) 的操作由缓存代理,应用认为后端是一个单一的存储,而存储自己维护自己的缓存。
Read Through: 就是在查询操作中更新缓存,也就是说,当缓存失效的时候,Cache Aside策略是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对调用方是透明的。
Write Through: 当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由缓存自己更新数据库 (这是一个同步操作)
Write Behind
原理: 在更新数据的时候,只更新缓存,不更新数据库,而缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作非常快,带来的问题是,数据不是强一致性的,而且可能会丢。
第二步失效问题: 这种可能性极小,缓存删除只是标记一下无效的软删除,可以看作不耗时间。如果会出问题,一般程序在写数据库那里就没有完成: 故意在写完数据库后,休眠很长时间再来删除缓存。
先更新数据库,再删除缓存
先更新数据库,再删缓存这种情况不存在并发问题么?
不是的。假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存
ok,如果发生上述情况,确实是会发生脏数据。
然而,发生这种情况的概率又有多少呢?
发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。
先更新数据库,再删缓存依然会有问题,不过,问题出现的可能性会因为上面说的原因,变得比较低!
所以,如果你想实现基础的缓存数据库双写一致的逻辑,那么在大多数情况下,在不想做过多设计,增加太大工作量的情况下,请先更新数据库,再删缓存!