Bootstrap

面试--redis基础

1、理解

redis是一个基于kv结构存储的nosql数据库,基于redis实现分布式缓存,从而提高数据的检索效率,有几个特点:基于内存存储,进行数据IO时能够达到10wQPS,提供了很多的数据结构String、set、list、hash等,底层是基于单线程实现数据的IO,避免了并发下的线程安全问题。支持持久化,避免服务器故障导致数据丢失的问题。提供了分布式锁等成熟的方法。

2、为什么这么快
  • 网络:采用多路复用的设计,提升了并发处理的连接数,服务端的所有IO操作都是由一个主线程处理的,redis6以后增加了多线程来优化服务端的IO,但具体的数据还是主线程处理的
  • cpu:采用单线程就可以。如果用多线程,那么为了保证线程安全需要同步锁,会影响到性能。linux上redis可以处理100wQPS,而计算复杂度主要是ON,不会消耗太多cpu
  • 内存:本身就是一个内存数据库,内存的IO速度很快,所以只受制于内存大小

3、redis和mysql如何保证数据一致性

redis一般用来实现应用和数据库之间读操作的缓存层,主要目的是减少数据库IO,提升IO性能,当去读取某个数据时,先从redis加载如果命中就返回,没雨命中就从数据库查询,查询到再把数据缓存到redis,就会出现一个问题,当数据发生变化,需要同时变化redis和mysql,更新会有先后顺序,出现数据一致性问题。可以:

先更新数据库,再更新缓存:如果缓存更新失败,导致数据不一致

先删除缓存,再更新数据库:不是原子的

可以基于rocketMq的可靠性消息通信,实现最终一致性,失败的请求写入MQ事务消息,异步重试

如果业务场景不能接受数据的短期不一致性,那么可以加入读写锁保证强一致性,影响性能

4、Redis存在线程安全问题吗?

redis本身是一个kv数据库,是单线程的,不会存在线程安全问题。redis6加入了多线程模型,但是对于指令的执行过程,仍然是主线程处理,所以不会存在多个线程通知执行操作指令的情况。但是如果有多个redis客户端同时执行多个指令,就无法保证原子性。可以对客户端的资源访问加锁,或者通过lua脚本实现多个指令操作

5、持久化RDB和AOF实现原理和优缺点

RDB和AOF都是持久化机制,RDB是通过快照方式实现持久化,AOF是通过命令追加的方式实现持久化。

  • RDB指定时间间隔内将内存中的数据集快照写入磁盘,恢复时将快照文件直接读到内存里。

    redis会单独创建(一个fork)一个子进程来进行持久化,先将数据写入到一个临时文件,待持久化过程结束,再用这个临时文件替换上次持久化好的文件。整个过程,主进程是不进行任何IO操作的,确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式比AOF方式更加高效。RDB的缺点是最后一次持久化的数据可能丢失。

  • AOF是近乎实时完成持久化的,客户端执行一个数据变更的操作,服务端就会把这个命令追加AOF缓冲区末尾,再把缓冲区写入到磁盘的AOF文件,避免AOF文件过大,提供了AOF重写机制,对文件里相同的指令进行压缩。

  • RDB一段时间触发持久化,数据安全性低,AOF可以做到实时持久化,数据安全性高

    RDB采用压缩的方式持久化,AOF存储的是指令,所以RDB在数据恢复的时候比AOF好

6、AOF重写过程

是redis里面的一种数据持久化方式,采用了指令追加的方式,近乎实时的视线数据指令的持久化,AOF把每个数据更改的操作指令,追加存储到AOF文件,可能会造成AOF文件过大,设计了重写机制,把AOF文件里相同的指令进行压缩,只保留最新的数据指令。

如果AOF文件里存储了某个k的多次变更记录,但实际上,最终在做数据恢复的时候,只需要执行最新的指令操作就可以了,历史数据没必要再这个文件里占空间。

根据当前redis内存里的数据,重新构建一个新的AOF文件,读取当前redis里的数据,写入到新的AOF文件,重写完成后,AOF文件覆盖。

整个过程是比较耗时的,所以redis把重写过程放在一个后台子进程里完成,主进程依旧可以处理客户端请求,为了避免子进程在重写过程中,主进程的数据发生变化造成的数据不一致性问题,主进程的数据变更会追加到AOF重写缓冲区里,等到AOF重写完成后,再把重写缓冲区的内容加到新的AOF文件中。

7、分布式锁的理解

分布式系统中对锁的使用,对共享资源的互斥性。要考虑到可重入性、锁失效机制等。

redis中提供了setnx实现锁的排他性,当k不存在返回1,存在返0,还可以用expire设置锁的失效时间,可能存在锁过期了但是业务逻辑没有执行完,redission中内置了一个看门狗机制对k做续期,折中能够解决大多数问题,如果在redis搭建了高可用集群的情况下出现出从切换k失效,可能造成多个线程抢占同一个锁的情况,redis提供了redlock解决。

8、缓存雪崩和缓存穿透

缓存雪崩:存储在缓存里面的大量数据在同一时刻全部过期,大流量直接请求数据库,数据库崩溃。

主要有两个原因:缓存中间件宕机,可以使用高可用集群避免;大部分的k设置相同过期时间,可以在失效时间上增加一个1-5分钟的随机值;数据预热,先把可能的数据预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问之前手动出发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间尽可能均匀

缓存穿透:短时间内有大量的不存在的k请求,在缓存中找不到,访问数据库造成压力。

主要有两个方法:把无效的key保存到redis,设置一个特殊的值null;使用布隆过滤器,系统启动的时候把目标数据全部缓存到布隆过滤器,当攻击者用不存在k请求,先到过滤器查询,如果不存在意味着k在数据库也不存在

9、缓存击穿

指一个key非常热点,大并发集中对这一个key进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库。这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,导致数据库瞬间压力过大。

解决方案:

① 设置热点数据永不过期,或者进行续期

② 分布式锁:保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获取分布式锁的权限,只需要等待即可。

10、哨兵机制和集群有什么区别
  • 哨兵集群是基于主从复制来实现的,实现读写分离,分担redis读操作的压力

redis cluster集群的从节点只是实现冷备机制,只有在主节点宕机之后才会工作

  • 哨兵集群无法在线扩容,所以并发压力受限于单个服务器的资源配置

redis cluster提供了基于slot槽的数据分片机制,实现在线扩容机制提升写数据的性能

  • 哨兵是一主多从,cluster是多主多从

11、哨兵选举算法

主节点出现故障,哨兵节点检测到以后,会从redis集群中其他从节点选举一个作为新的主节点

首先会筛选,过滤下线或者没有回复哨兵心跳响应的从节点,评估网络连接情况,如果主从经常断链并且超出了一定的阈值,也不会考虑

之后评估,根据redis.conf的配置从节点优先级,选择数据偏移量差距最小的,也就是主从复制进度差距最小的避免丢失过多数据,最后选用runid最小的说明创建时间越早。

选举Leader过程受到Paxos算法和Raft算法等分布式一致性算法的影响。

12、主从复制的原理

redis主从复制包括全量复制和增量复制。

全量复制是发生在初始化阶段,从节点主动向主节点发起一个同步请求,主节点收到请求后会生成一份当前数据的快照发送给从节点,从节点收到数据后进行加载,完成全量复制。

增量复制是发生在每次主节点数据发生变化,会把变化的数据同步给所有的从节点,通过维护offset复制偏移量实现的。

13、Redis的内存淘汰算法和原理是什么

内存达到上限时的一种内存释放

  • Random,随机移除

  • TTL,设置了过期时间的k,把更早过期时间的k移除

  • LRU,移除最近很少使用,厂家,维护一个大小为16的候选池,根据时间排序,每次随机取出5个放入候选池,满了以后,访问的时间间隔最大的key就会从候选池取出来淘汰,但是会存在如果一个k的访问率很低,只是最近一次偶尔访问到,就会认为是一个热点k,不会被淘汰

  • LFU,移除最近很少使用,增加了访问频率维度统计数据的热点情况,当添加元素的时候,访问次数默认1,找到相同访问频次的节点,添加到相同频率节点对应的双向链表头部,当访问元素的时候,增加对应k的访问频次,把当前访问的节点移动到下一个频次节点,LFU通过使用频率和上次访问时间来标记数据的热度,如果一段时间没有读写,就减少访问频率。

14、redis过期策略
  • 定时过期:每个设置过期时间的k都需要创建一个定时器,到时间立即清除

    对内存友好,但是会占用大量的cpu处理过期数据,影响缓存的响应时间和吞吐量

  • 惰性过期:被动访问某个k的时候,才会判断k是否过期,过期则清除

    最大化的节省cpu,对内存不友好,极端可能出现大量过期k没有再次被访问就不会被清除

  • 定期过期:周期性轮询redis的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频率,通过调整定时扫描的时间间隔和每次扫描耗时,达到平衡效果

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

如果你需要优化定期删除策略的效果,可以考虑以下几点:

调整 hz 参数的值:默认情况下,Redis 每秒钟执行 10 次定期删除操作。如果系统负载较低,可以适当降低该值,减少定期删除操作对系统性能的影响;系统负载较高,可以适当提高该值,加快过期键的删除速度。

调整 Redis 的最大内存限制:如果 Redis 的内存使用率达到了最大内存限制,那么定期删除策略会变得非常重要,因为它可以自动删除过期键,释放内存空间。

避免批量删除操作:定期删除策略的效率取决于过期键的数量。如果你在 Redis 中进行了大量的删除操作,就会导致定期删除操作的效率降低。尽量避免批量删除操作,避免对定期删除策略的影响。

合理设置过期时间:如果你的系统中有大量的键需要设置过期时间,那么你应该尽量合理地设置过期时间。如果过期时间设置得过短,就会导致定期删除策略频繁执行,影响系统性能;如果过期时间设置得过长,就会导致过期键长时间占用内存,影响系统的稳定性。

15、redis遇到哈希冲突问题,怎么解决的

redis底层是使用了一张全局hash表来保存所有键值对,这张哈希表有多个哈希桶组成,哈希桶中的entry元素保存k和v指针。哈希冲突是指不同的k,计算落到同一个hash桶,redis采用了链式寻址法。为了保持解决链表过长的情况,redis对哈希表做rehash操作,增加哈希桶来减少冲突,为了rehash更高效,默认使用了两个全局哈希表,一个用于当前,一个用于扩容。

16、热key,如何解决

访问频率很高的k,访问量较大,热点k有可能会导致服务器资源不足出现宕机。

可以redis检测工具去比较哪些k是热点,还有有一个JD轻量级框架etcd

数据分片:热点数据分散存储在多个节点上,避免单个节点负载过高,cluster模式下自动按照槽位分布

读写分离:降低单个节点的负载

缓存预热:系统启动时,主动将热点数据加载到缓存,可以通过定时任务或应用启动加载

限流:空值请求的速率

17、为什么redis的最大槽数是16384
  • 对于网络通信开销的平衡:Redis 集群中每个节点会发送心跳消息,心跳包中会携带节点的完整配置,以幂等的方式来更新配置。如果采用 16384 个插槽,而每个插槽信息占用的位数为 1,因此每个节点需要维护的配置信息占用空间大小就是 16384/节点数/8KB,假设是 3个节点的集群,则每个节点需要维护的配置信息占用空间大小为 2KB。其次,CRC16 算法产生的 hash 值有 16 位,如果按照 2^16 次方计算得到 65536 个槽,那就会导致每个节点维护的配置信息占 8kb。8kb 数量的心跳数据看起来不大,但是这个心跳包每秒都需要把当前节点的信息同步给集群中的其他节点,相比于 16384 个 hash slot,整体增加了 4 倍,这样会对网络带块带来极大的浪费。并且这个浪费并没有带来更好的效果。

  • 集群规模的限制:Redis Cluster 不太可能扩展到超过 1000 个主节点,太多可能会导致网络拥堵等问题

  • 16384 个插槽可以确保每个主节点都有足够的插槽,同时也可以保证插槽数目不会过多或过少,从而保证了 Redis Cluster 的稳定性和高性能。

【拓展】

对热点key的理解:https://blog.csdn.net/weixin_51146329/article/details/132079434

对哨兵机制的详解:https://zhuanlan.zhihu.com/p/651457150

;