面试题
我看你做的项目中,都用到了redis,你在最近的项目中那些场景使用了redis呢
如果回答了分布式锁,那么就会有以下这个问题
redis分布式锁,是如何实现的?
需要结合项目中的业务进行回答,通常情况下,分布式锁使用的场景:
集群情况下的定时任务、枪单、幂等性场景
案例
以下设置一个抢券场景
抢券执行流程
当线程1查询优惠券时,查到还有一张,当进行查询库存是否充足时,线程2也进行查询,此时线程2也可以查到一张优惠券,线程1和线程2同时减1,咋会出现超卖情况
如果项目是单体项目,并且只启动一台服务,加synchronized锁可以解决这个问题,如下
但是为了支持更多的并发请求,往往会把项目进行集群部署,即将代码部署到多台服务器上,如果使用集群部署的话,再使用synchronized锁就会出现问题
synchronized锁是本地的锁,属于gvm,每个服务都有各自的gvm,只能解决同一个gvm下线程的互斥,解决不了多个gvm下线程的互斥,所以在集群情况下,就不能使用本地的锁来解决了
只能使用外部的锁来解决,即分布式锁,Redis就可以作为分布式锁
一. Redis分布式锁
Redis实现分布式锁主要利用Redis的setnx命令。setnx时SET if not exists(如果不存在 ,则AET)的简写。
这里面试官有可能就问了
这里有两种方案,一种是根据业务执行时间预估,一种是给锁续期
根据业务执行时间预估是不可行的,如果出现卡顿等问题,都会导致业务执行时间变慢;
第二种给锁续期也就是redisson实现的分布式锁,以下是执行流程(问的比较多)
如果面试官问:
使用redisson实现的分布式锁是否可以重入?
同一个线程是可以重入的,不是同一个线程则不可以重入
redisson实现的分布式锁能保证主从数据一致性吗?
使用RedLock红锁保证分布式锁的主从一致性
但是红锁是由缺点的,实现复杂,性能差,运维繁琐,所以使用的很少。
总结
面试官:Redis分布式锁如何实现?
候选人:在redis中提供了一个命令setnx(SET if not exists)
由于redis是单线程的,用了命令之后,只能有一个客户端对某一个key设置值,在没有过期或者删除key的时候是其他客户端不能设置这个key的
面试官:好的,那你如何控制Redis实现分布式锁的有效时长呢?
候选人:嗯,的确,redis的setnx指令不好控制这个问题,我们当时采用的redis的一个框架redisson实现的。
在redisson中需要手动加锁,并且可以控制锁的失效时间和等待时间,当锁住的一个业务还没有执行完成的时候,在redisson中引入了一个看门狗机制,就是说每隔一段时间就检查当前业务是否还持有锁,如果持有就增加加锁的持有时间,当业务完成之后需要使用释放锁就可以了
还有一个好处就是 在高并发下,一个业务有可能会执行很快,先客户1持有锁的时候,客户2来了以后并不会马上拒绝,他会自选不断尝试获取锁,如果客户1释放之后,客户2就可以马上持有锁,性能也得到了提升。
面试官:好的,redisson实现的分布式锁是可重入的吗?
候选人:嗯,是可重入的。这样做是为了避免死锁的产生。这个重入其实在内部就是判断是否是当前线程持有的锁,如果是当前线程持有的锁就会计数,如果释放锁就会在计算上减一。在存储数据的时候采用hash结构,大key可以按照自己的业务进行定制,其中小key是当前线程的唯一标识,value是当前线程重入的次数
面试官:redisson实现的分布式锁能解决主从一致性问题吗?
候选人:这个是不能的,但是可以使用redisson提供的红锁来解决,但是这样的话,性能就太低了,如果业务中非要保证数据的强一致性,建议采用zookeeper实现的分布式锁
还有一些redis其他的面试问题,这些问题可能跟业务没啥关系,但是仍是一些高频面试问题。
二. Redis集群方案
Redis集群有哪些方案,知道吗?
在Redis中提供集群方案总共有三种
- 主从复制
- 哨兵模式
- 分片集群
在集群中其他的面试问题有以下几个
2.1 主从复制
特点:单节点Redis的并发能力是有上限的,要进一步提高redis的并发能力,就需要搭建主从集群,实现读写分离
主从数据同步原理
主从全量同步
主从增量同步
总结
2.2 哨兵模式
作用:Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。哨兵结构和作用如下
极大保障redis主从的高可用
服务状态监控
Sentinel基于心跳机制检测服务状态,每隔一秒向集群的每个实例发送ping命令:
- 主管下线:如果sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。
- 客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。
哨兵模式会出现脑裂问题
这是一个正常结合哨兵模式的主从架构
此时由于网络问题,主节点master和哨兵模式处在不同分区,此时哨兵只能检测从节点,检测不了主节点
当网络恢复后
解决方案如下:
总结
2.3 分片集群
主从和哨兵可以解决高可用,高并发读的问题。但是仍然有两个问题没有解决:
- 海量数据存储问题
- 高并发写的问题
使用分片集群可以解决上述问题,分片集群的特征:
- 集群中有多个master,每个master保存不同数据
- 每个master都可以有多个slave节点
- master之间通过ping检测彼此健康状态
- 客户端请求可以访问集群任意节点,最终都会被转发到正确节点
分片集群结构-数据读写
Redis分片集群引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模解决放置哪个槽,集群的每一个节点负责一部分hash槽。
总结
面试官:redis的分片集群有什么作用?
候选人:分片集群主要解决的是,海量数据存储的问题,集群中有多个master,每个master保存不同数据,并且还可以给每一个master设置多个slave节点,就可以继续增大集群的高并发能力。同时每个master之间通过ping检测彼此健康状态,就类似于哨兵模式了。当客户端请求可以访问集群任意节点,最终都会被转发到正确节点
面试官:Redis分片集群中数据是怎么存储和读取的?
候选人:嗯,在redis集群中是这样的
Redis集群引入了哈希槽的概念,有16384个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围,key通过CRC16校验后对16384取模来决定放置哪个槽,通过槽找到对应的节点进行存储。
取值的逻辑是一样的。
三.其他面试问题
Redis是单线程的,但是为什么还那么快?
- Redis是纯内存操作,执行速度非常快
- 采用单线程,避免不必要的上下文切换可竞争条件,多线程还要考虑线程安全问题
- 使用I/O多路复用模型,非阻塞IO
能解释一下I/O多路复用模型吗
Redis是纯内存操作,执行速度非常快,他的性能瓶颈是网络延迟而不是执行速度,I/O多路复用模型主要就是实现了搞笑的网络请求
- 用户空间和内核空间
- 常见的IO模型
- 阻塞IO(Blocking IO)
- 非阻塞IO(Nonblocking IO)
- IO多路复用(IO Multiplexing)
- Redis网络模型
3.1 用户空间和内核空间
3.2 阻塞IO(Blocking IO)
3.3 非阻塞IO(Nonblocking IO)
3.4 IO多路复用(IO Multiplexing)
IO多路复用是利用单个线程来同时监听多个Socket,并在某个Socket可读,可写时得到通知,从而避免无效的等待,充分利用CPU资源。不过监听Socket的方式,通知的方式又有多种实现,常见的有:
- select
- poll
- epoll
三者是有差异的
3.4 Redis网络模型
Redis通过IO多路复用来提高网络性能,并且支持各种不同的多路复用实现,并且将这些实现进行封装,提供了统一的高性能事件库
总结
能解释一下I/O多路复用模型吗?
面试官:Redis是单线程的,为什么还那么快?
候选人:嗯,这个有几个原因
1.完全基于内存,C语言编写
2. 采用单线程,避免不必要的上下文切换可竞争条件
3.使用多路I/O复用模型,非阻塞IO
例如:bgsave和bgrewriteaof都是在后台执行操作,不影响主线程的正常使用,不会产生阻塞。
面试官:能解释一下I/O多路复用模型?
候选人:嗯,I/O多路复用是指利用单个线程来同时监听多个Socket,并在某个Socket可读,可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的I/O多路复用都是采用的epoll模式实现,他会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。
其中Redis的网络模型就是使用I/O多路复用结合事件的处理器来应对多个Socket请求,比如,提供了连接应答处理器,命令回复处理器,命令请求处理器。
在Redis6.0之后,为了提升更好的性能,在命令回复处理器使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程