redis精选面试题
文章目录
Redis的单线程指什么
Redis 单线程主要指的是网络IO和键值对读写是由一个线程来完成的,Redis在处理客户端的请求的时候包括获取(Socket),解析,执行,内容返回(Socket写)等由一个顺序串行的主线程处理,这即为单线程
但Redis的其它功能,比如持久化,异步删除,集群数据同步等等,都是由额外的线程执行的,是多线程
即Redis工作线程是单线程的,但是,整个Redis来说是多线程的
Redis单线程为什么很快
1、基于内存操作:Redis的所有数据都存在内存中,因此所有的运算都是内存级别
2、数据结构简单:Redis的数据结构简单,查找和操作时间大部分时间复杂度是O(1)
3、多路复用和非阻塞:Redis使用多路复用功能来监听多个Socket链接客服端,这样可以一个线程处理多个请求,减少线程切换带来的开销,同时也避免了IO阻塞操作
4、避免上下文切换:因为是单线程模型,因此避免了多线程切换和多线程竞争,减少时间和性能的消耗
Redis系列第一讲:Redis是单线程吗 - 掘金 (juejin.cn)
谈谈redis的对象机制(redisObject)
Redis的5种基础数据类型,在底层是采用对象机制实现的。
Redis的每种对象其实都由对象结构(redisObject) 与 对应编码的数据结构组合而成,而每种对象类型对应若干编码方式,不同的编码方式所对应的底层数据结构是不同的。
redisObject 是 Redis 类型系统的核心, 数据库中的每个键、值, 以及 Redis 本身处理的参数, 都表示为这种数据类型。
Redis相关知识----对象机制_小舟~的博客-CSDN博客
redis 优化措施
-
避免大key的存在
-
设置合理的淘汰策略
内存尽可能大; 淘汰策略改为随机淘汰,随机淘汰比 LRU 要快很多(视业务情况调整)
- 谨慎fork操作带来的阻塞
控制 Redis 实例的内存:尽量在 10G 以下,执行 fork 的耗时与实例大小有关,实例越大,耗时越久
- 关闭操作系统的内存大页
应用在申请内存时,linux会给一个完整的内存页大小, 申请的多了, 分配内存也就大了 , 耗时也就长了
对于 Redis 这种对性能和延迟极其敏感的数据库来说,我们希望 Redis 在每次申请内存时,耗时尽量短
- 开启AOF后设置 重写时不刷盘
# AOF rewrite 期间,AOF 后台子线程不进行刷盘操作 # 相当于在这期间,临时把 appendfsync 设置为了 none no-appendfsync-on-rewrite yes
建议使用 固态硬盘, 加快数据落磁盘
- 关闭swap 内存
操作系统为了缓解内存不足对应用程序的影响,允许把一部分内存中的数据换到磁盘上,以达到应用程序对内存使用的缓冲,这些内存数据被换到磁盘上的区域,就是 Swap。
Redis进阶 - 性能调优:Redis性能调优详解 | Java 全栈知识体系 (pdai.tech)
热key的处理
- 利用二级缓存
将热key加载到jvm中, 利用本地缓存抗住
- 备份热key
将热key多备份几份,用后缀区分, 这样请求就会分发到不同的key上了
检测热key
- 经验预估, 比如秒杀key
- 客户端收集并统计
- redis 自带的命令 ,
monitor
和hotkeys
但都有性能问题
【原创】谈谈redis的热key问题如何解决 - 孤独烟 - 博客园 (cnblogs.com)
缓存穿透
缓存穿透,是指频繁查询一个数据库一定不存在的数据,这种redis一般不会保存,这样redis被穿过了,直接去访问了数据库,这种穿透多了,数据库就压力大。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象
缓存被穿透了,因为总是查询一些在redis中查不到的值
解决方案:
- 使用锁,将这次请求锁住,等数据库返回后才释放(分布式环境用分布式锁,单机用普通锁(synchronized等))
- 接口限流与熔断、降级
- 布隆过滤器(java的guava包中默认误判率3%(最合适)) (将全部"可能"数据存到布隆过滤器中,当redis中找不到时,先经过布隆,如果过滤器说不存在,就不用访问数据库了,直接返回)
- 将这种key存到redis,设置较短的过期时间,比如1分钟/5分钟
缓存雪崩
缓存雪崩,是指在某一个时间段,缓存集中过期失效,大批量key失效,命中redis的概率就小了,只能去查数据库了.
其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,那么那个时候数据库能顶住压力,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。
解决方案:
-
也是像解决缓存穿透一样加锁排队
-
建立备份缓存, 采用哨兵或者集群 ,多起几个节点
-
给同时添加key时设置不同过期时间
-
事前:确保Redis本身的高可用性,数据恢复备份、主从架构+哨兵,Redis cluster,一旦主节点挂了,从节点跟上
-
事中:当redis不可用时,少量请求可以走缓存生产服务的本地缓存ehcache获取数据,基于hystrix对商品服务和redis操作做限流保护 配置降级、超时、熔断策略;从而保证发生缓存雪崩时缓存生产服务不会被拖死
-
事后:基于redis的数据备份,快速将redis重新跑起来对外提供服务
缓存击穿
缓存击穿,热点key在请求高峰期失效了,瞬间大量请求落到数据库,就像在一个屏障上凿开了一个洞。
解决方案:
- 本地缓存 + redis缓存的多级缓存
- 也是像解决缓存穿透一样加锁排队
- 设置永不过期或者定时更新过期时间
链接:https://baijiahao.baidu.com/s?id=1619572269435584821&wfr=spider&for=pc
https://blog.csdn.net/fanrenxiang/article/details/80542580
[如何解决redis缓存击穿问题_Sebastian Xia的博客-CSDN博客_redis缓存击穿怎么解决
三种部署模式
特别是集群模式和哨兵模式他们的区别, 要深入理解这两种模式
1. 主从复制
复制过程:
- 从服务器连接主服务器,发送SYNC(同步)命令;
- 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;
- 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
- 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
- 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
- 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;(从服务器初始化完成)
- 主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令(从服务器初始化完成后的操作)
优点:
- 为了分载Master的读操作压力,Slave服务器可以为客户端提供只读操作的服务,写服务仍然必须由Master来完成
- Slave同样可以接受其它Slaves的连接和同步请求,这样可以有效的分载Master的同步压力。
- Master Server是以非阻塞的方式为Slaves提供服务。所以在Master-Slave同步期间,客户端仍然可以提交查询或修改请求。
- Slave Server同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据
缺点
- Redis不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
- 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
- Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
链接:https://www.jianshu.com/p/7967f95655b2
主从复制是基础,后面的哨兵和Cluster都是基于这种方式,但是这种方式太垃圾了,master死了就不能写操作了,所以一般会用后面两种
使用主从复制很简单,在配置文件中指定master/slave,并设置密码(必须使用)
fork耗时严重问题
我们可能会开启后台定时 RDB 和 AOF rewrite 功能。但如果你发现,操作 Redis 延迟变大,都发生在 Redis 后台 RDB 和 AOF rewrite 期间,在这期间有可能导致变慢的情况。
当 Redis 开启了后台 RDB 和 AOF rewrite 后,在执行时,它们都需要主进程创建出一个子进程进行数据的持久化。
主进程创建子进程,会调用操作系统提供的 fork 函数。
而 fork 在执行过程中,主进程需要拷贝自己的内存页表给子进程,如果这个实例很大,那么这个拷贝的过程也会比较耗时。
而且这个 fork 过程会消耗大量的 CPU 资源,在完成 fork 之前,整个 Redis 实例会被阻塞住,无法处理任何客户端请求。
如果此时你的 CPU 资源本来就很紧张,那么 fork 的耗时会更长,甚至达到秒级,这会严重影响 Redis 的性能。
也就是说 主从同步和AOF重写操作 时都是用fork函数来同步的
要想避免这种情况,你可以采取以下方案进行优化:
- 控制 Redis 实例的内存:尽量在 10G 以下,执行 fork 的耗时与实例大小有关,实例越大,耗时越久
- 合理配置数据持久化策略:在 slave 节点执行 RDB 备份,推荐在低峰期执行,而对于丢失数据不敏感的业务(例如把 Redis 当做纯缓存使用),可以关闭 AOF 和 AOF rewrite
- Redis 实例不要部署在虚拟机上:fork 的耗时也与系统也有关,虚拟机比物理机耗时更久
- 降低主从库全量同步的概率:适当调大 repl-backlog-size 参数,避免主从全量同步
Redis进阶 - 性能调优:Redis性能调优详解 | Java 全栈知识体系 (pdai.tech)
2.哨兵模式
哨兵的作用就是监控Redis系统的运行状况。它的功能包括以下两个。
-
监控主服务器和从服务器是否正常运行。
-
主服务器出现故障时自动将从服务器转换为主服务器。
弥补了主从模式下的master死亡的情况
哨兵是集群,有主节点和从节点,也会产生选举(基于Raft算法(但不一样),和kafka一样),会选举哨兵主节点,redis的master,( 每个redis的bin下都有一个哨兵启动文件),在sentinel模式下,客户端就不用直接连接Redis,而是连接sentinel的ip和port
哨兵是一个独立的进程,作为进程,它会独立运行
每个哨兵通过主节点去发现其他从节点和其他哨兵的
工作方式
- 每个sentinel以每秒钟一次的频率向它所知的master,slave以及其他sentinel实例发送一个 PING 命令
- 如果一个实例距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被sentinel标记为主观下线。
- 如果一个master被标记为主观下线,则正在监视这个master的所有sentinel要以每秒一次的频率(发送info命令)确认master的确进入了主观下线状态
- 当有足够数量(
quorum
)的sentinel在30s(down-after-milliseconds
)内确认master的确进入了主观下线状态, 则master会被标记为客观下线 - 在一般情况下, 每个sentinel会以每 10 秒一次的频率向它已知的所有master,slave发送 INFO 命令
- 当master被sentinel标记为客观下线时,sentinel向下线的master的所有slave发送 INFO 命令的频率会从 10 秒一次改为 1 秒一次
- 若没有足够数量的sentinel同意master已经下线,master的客观下线状态就会被移除; 若master重新向sentinel的 PING 命令返回有效回复,master的主观下线状态就会被移除
redis自带哨兵,不需要借助第三方,只需要简单配置即可 ,生产环境建议让redis Sentinel部署到不同的物理机上。
原文链接:https://blog.csdn.net/miss1181248983/article/details/90056960
哨兵可以解决大量读的问题,如果有大量写,哨兵也扛不住,这需要Twemproxy,做数据的分片处理,再配合 Lvs 和 Keepalived 解决Twemproxy的单点故障
https://www.jianshu.com/p/84dbb25cc8dc
3. Cluster模式
redis的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台redis服务器都存储相同的数据,很浪费内存,所以在redis3.0上加入了cluster模式,实现的redis的分布式存储,也就是说每台redis节点上存储不同的内容。(取数据的时候直接找到对应地址去取)
默认情况下,redis集群的读和写都是到master上去执行的,不支持slave节点读和写,跟Redis主从复制下读写分离不一样,因为redis集群的核心的理念,主要是使用slave做数据的热备,以及master故障时的主备切换,实现高可用的。
所以集群模式相比哨兵模式, 为了扩展写能力和存储能力
Redis-Cluster采用无中心结构,它的特点如下
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
- 节点的fail是通过集群中超过半数的节点检测失效时才生效。
- 客户端与redis节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
工作方式
在redis的每一个节点上,都有这么两个东西,一个是插槽(slot),它的的取值范围是:0-16383。还有一个就是cluster,可以理解为是一个集群管理的插件。当我们的存取的key到达的时候,redis会根据crc16的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。只有master节点会被分配槽位,slave节点不会分配槽位。
当Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息,并将其缓存在客户端本地。这样当客户端要查找某个 key 时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
客户端会存一份槽位的分布信息 , 如果槽位信息不对(例如槽位发生了迁移),客户端会更新为最新的槽位信息.
也就意味着, 客户端一般能直接命中key,不会发生二次访问
这个客户端是 smart客户端,就是指客户端本地维护一份hashslot => node的映射表缓存,大部分情况下,直接走本地缓存就可以找到hashslot => node,不需要通过节点进行moved重定向,(jedisCluster就集成了它)
Redis集群所有节点之间的通信机制
介绍集群模式时, 可以附带提一下通信机制 (如果面试官深问但你不懂, 可能会减分, 所以慎重)
在Redis集群中,不同的节点之间采用gossip协议进行通信,节点之间通讯的目的是为了维护节点之间的元数据信息。这些元数据就是每个节点包含哪些数据,是否出现故障,通过gossip协议,达到最终数据的一致性。
gossip协议,是基于流行病传播方式的节点或者进程之间信息交换的协议。原理就是在不同的节点间不断地通信交换信息,一段时间后,所有的节点就都有了整个集群的完整信息,并且所有节点的状态都会达成一致。每个节点会携带集群节点总数的1/10(至少3个)的信息,但只要这些节点可以通过网络连通,最终他们的状态就会是一致的。Gossip协议最大的好处在于,即使集群节点的数量增加,每个节点的负载也不会增加很多,几乎是恒定的。
也意味着元数据的更新有延时,可能导致集群中的一些操作会有一些滞后, 同时结点数太多,意味着达到最终一致性的时间也相对变长,因此官方推荐最大节点数为1000左右。
redis cluster架构下的每个redis都要开放两个端口号,比如一个是6379,另一个就是加10000的端口号16379。
- 6379端口号就是redis服务器入口。
- 16379端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用的是一种叫gossip 协议的二进制协议
维护集群的元数据有集中式和 gossip两种方式, 像zookeeper就是集中式
由于Redis集群的去中心化以及gossip通信机制,Redis集群中的节点只能保证最终一致性。例如当加入新节点时(meet),只有邀请节点和被邀请节点知道这件事,其余节点要等待 ping 消息一层一层扩散。除了 Fail 是立即全网通知的,其他诸如新节点、节点重上线、从节点选举成为主节点、槽变化等,都需要等待被通知到,也就是Gossip协议是最终一致性的协议。
mget性能问题
现象: 使用mget时性能出现明显下降
原因: 要查的key分布在多个节点上,所以需要给多个节点发送请求且为串行, 我们使用了一个mget,实际上访问了很多次
浅谈Redis集群下mget的性能问题 - SegmentFault 思否
redisson分布式锁
Redisson是一个基于java编程框架netty进行扩展了的redis。 Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid),加锁和解锁都是通过lua脚本来执行
一把分布式锁在redis中是怎么存储的?
具体值: 这里数据类型是Hash类型,Hash类型相当于我们java的 <keyName,<field,value>> 类型,
这里keyName是指 ‘redisson’,
field值它的组成是:uuid + 当前线程的ID,
value是重入次数
uuid是客户端实例化时就创建好了,它是客户端的标识
这里还有个知识点:
- redisson怎么实现重入的?
答: 通过控制value来控制重入次数, 通过判断key来判断是否抢锁还是重入锁
重入过程:
在持有锁的时间内,业务没有执行完,怎么办?
redisson有一个看门狗机制, 通过一个守护线程去判断当前业务是否执行完成, 若没有完成则会给锁自动续过期时间, 当获取锁时不设置锁时间, 看门狗才会生效, 例如
redissonClient.getLock(""lockName"").lock(); // 看门狗生效, 默认锁定30s, 续锁也是30s
redissonClient.getLock(""lockName"").lock(10, TimeUnit.SECONDS); // 看门狗失效, 锁10秒后释放
集群模式下, 锁只在一个节点上, 若此时挂了, 可能导致锁会被重复拥有, 应该怎么办?
由于节点之间是采用异步通信的方式。如果刚刚在 Master 节点上加了锁,但是数据还没被同步到 Salve。这时 Master 节点挂了,它上面的锁就没了,等新的 Master 出来后(主从模式的手动切换或者哨兵模式的一次 failover 的过程),就可以再次获取同样的锁,出现一把锁被拿到了两次的场景。
redisson有redLock方案,
红锁是多把锁合成一把锁,获得锁时会向全部节点发送lua脚本申请锁,只有获得(n/2+1)个节点的锁时才真正获得锁,(获得锁需要时间,而锁也有过期时间,所以最终时间是过期时间减去获得锁花费的时间)
原文地址:https://blog.csdn.net/Q176782/article/details/144001183
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!