[2020] Redis 最新面试题
Redis 的数据类型(数据结构)
- string (二进制安全,可以存储任意类型的数据)
- list(链表)
- 字典(就是hashmap)
- set(不重复无序的hashmap)
- zset(按照给定的 score 排序的 set)
- HyperLogLog(来做基数统计的算法,简介)
- Geo(支持地理位置的操作,使用简介)
- Pub/Sub
- BloomFilter
- RedisSearch
- Redis-ML
缓存雪崩(缓存击穿)
他们出现的原理都是访问缓存的时候,key 刚好失效,导致直接访问 DB,压垮后台。
解决办法就是
- 让 key 的过期时间分散开,不要集中失效
- 使用锁或者消息队列的方式
分布式锁
使用 setnx 命令后为了防止死锁,需要对 key 施加 expire 命令,防止死锁,但是存在执行 expire 命令前宕机,造成死锁的发生。
解决办法就是使用复杂的 setnx 命令,他可以把 setnx 和 expire 一起原子执行
如何寻找有固定前缀的 key
使用 KEYS pattern 命令,如:KEYS alib*
但是因为 Redis 是单线程的,执行该命令后会导致 Redis 阻塞住。
解决办法就是使用 scan 命令,scan 命令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys指令长
如何用 Redis 做异步队列
使用 list 数据结构,在一边加入,另一边取出,若取出来的是 null,则消费线程应该 sleep,或者消费线程不使用 lpop 或 rpop 命令,改为 blpop 或者 brpop 命令,若没有元素可取,它会阻塞列表直到等待超时或发现可弹出元素为止。
如何生产一次,消费多次
使用发布订阅模式
但是在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 rabbitmq
Redis 如何实现延时队列
使用 zset ,用时间戳作为 score,消息会按照时间顺序排序
然后使用 zrangebyscore key min max [WITHSCORES] [LIMIT offset count] 来取出比当前时间小的 key 的 value
持久化
Redis 4.0 时代以 RDB 为主,AOF 只记录上一次 RDB 到现在的更改记录
开启混合持久化:aof-use-rdb-preamble yes
工作原理:其实还是一种 AOF 机制,但是新增了 RDB 的特性,先看此模式下的 AOF 的数据结构图
- 看图就知道混合模式指的就是重写 AOF 的时候,将此刻内存里面的数据做成 RDB,在此过程中增量的数据写入到缓冲区,最终形成新的 aof 文件。接着删除旧的 AOF。
- 重启恢复时先恢复 RDB,再重放新增的 AOF 指令
- AOF:记录每一次的写操作到日志上,重启时重放日志以重建数据
- 每隔一段时间调用系统的 fsync 函数强制将 os cache 里面的数据刷新到磁盘上
- RDB:每隔一段时间保存一次当前时间点上的数据快照
- 快照就是一次又一次地从头开始创造一切,全量的
持久化如何工作的
关键词:写时复制和 fork 子进程
- 每当 Redis 需要转储数据集到磁盘时,会发生:
- Redis 调用 fork()。于是我们有了父子两个进程。
- 子进程开始将数据集写入一个临时 RDB / AOF 文件。
- 当子进程完成了新 RDB 文件,替换掉旧文件。
- AOF 的 fork(),与 RDB 不同的是父进程会在一个内存缓冲区中积累新的变更,同时将新的变更写入新的 AOF 文件,所以即使重写失败我们也安全。当子进程完成重写文件,父进程收到一个信号,追加内存缓冲区到子进程创建的文件末尾,接着自动重命名文件为新的,然后开始追加新数据到新文件
- 这个方法可以让 Redis 获益于写时复制(copy-on-write)机制。
AOF 为什么要重写
AOF 记录的是 Redis 的每一次变更,这个变更包含了大量的冗余操作,导致 AOF 体积变大,恢复缓慢。
通过重写这个体积大的 AOF 文件,可以实现新的 AOF 文件不会包含任何浪费空间的冗余命令,通常体积会较旧 AOF 文件小很多。
Pipeline
将多个指令一起发送,减少 IO,提高吞吐量
Redis 的同步机制
Redis 可以使用主从同步,从从同步。
第一次同步时,主节点做一次 bgsave,并同时将后续修改操作记录到内存 buffer,待完成后将 RDB 文件全量同步到复制节点,复制节点接受完成后将 RDB 镜像加载到内存。
加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过 AOF 日志同步即可,有点类似数据库的 binlog
Redis 集群
Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务。
Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行分片存储。
Redis 的乐观锁 Watch 是怎么实现的
Watch 会在事务开始之前盯住 1 个或多个关键变量,如下图:
当事务执行时,也就是服务器收到了 exec 指令要顺序执行缓存的事务队列时, Redis 会检查关键变量自 Watch 之后,是否被修改了。
上图显示,watch abc 之后执行事务之前,执行了一次 incr 操作,所以在 exec 的时候失败,watch 的实现原理不是 CAS 中的 Cmpxchg 指令,而是借助 Redis 的单线程执行机制,采用了 watched_keys 的数据结构和串行流程实现了乐观锁,具体解释就是:
每一个被 watch 的 key 都会被构造成一个 watched_keys 数据类型,多个被 watch 的 key 构造成链表存储着
假设客户端 A 和 B 都 watch abc
但是并发时 Redis Server 中只会有一个线程在执行,
当 A 修改了 watch 命令监视的 key 后,会改变 abc 的 watched_keys 的状态为 dirty,
客户端 B 会检查这个被 watch 的 abc,发现他的状态是 dirty 的时候就会终止事务
Redis 如何节省内存
关键词:ziplist、quicklist、对象共享
Ziplist 是一个紧凑的数据结构,每一个元素之间都是连续的内存,如果在 Redis 中,Redis 启用的数据结构数据量很小时,Redis 就会切换到使用紧凑存储的形式来进行压缩存储。
Linkedlist 是 ziplist 的双向链表版本,可以在两端执行 push 和 pop 操作
对象共享:指的就是多个key的value是一样的话,就把多个key指向同一个value即可,如下图:
A和B都指向值100,则A、B 共用同一个100对象
Redis 的过期策略
- 定时删除:创建一个定时器,让定时器在键过期时来执行删除
- 对CPU不友好
- 影响性能
- 定期删除:每隔一段时间,程序都要对数据库进行一次检查,删除里面的过期键,至于要删除多少过期键,由算法而定。
- 要么对 CPU 不友好
- 要么对内存不友好
- 惰性删除:get Key 的时候才检查是否过期,过期了就删除返回 null
- 对内存不友好
- 可能导致内存溢出
Redis 同步策略
最简单的架构模式就是:一台 master 和多个 slave
仅 master 开启持久化策略,负责写入操作,slave 只负责读取操作
同步的目的就是为了【读写分离】和【容灾备份】
同步的过程:
- slave 发送 SYNC 给 master
- master 接收到命令后一边缓存继续写入的命令,一边 fork 子进程生成 RDB 文件
- 子进程写完 RDB 之后,父进程把 RDB 发送给 slave,slave 接收 RDB 并重现数据
- 父进程增量地将缓存的写命令发送给 slave
Redis 为什么是单进程单线程的
注意:这里的单线程指的是处理 I/O 事件是单线程的,并发的请求进入 Redis 后会排队,只有上一个处理完了,才会继续处理下一个。
注意:Redis 可不完全是单进程的,开启持久化的时候,会 fork 子进程完成 RDB/AOF 的创建