Redis 面试题

忘忧 2022年11月08日 81次浏览

1.1. 谈下你对 Redis 的了解?

Redis(全称:Remote Dictionary Server 远程字典服务)是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API

1.2. Redis 与其他 key – value 缓存产品有以下特点

  1. Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  2. Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  3. Redis支持数据的备份,即master-slave模式的数据备份。
  4. 使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求
  5. value 值大小不同:Redis 最大可以达到 512M;Memcache 只有 1mb

1.3. Redis 一般都有哪些使用场景?

  1. 缓存:减轻 MySQL 的查询压力,提升系统性能
  2. 排行榜:利用 Redis 的 SortSet(有序集合)实现
  3. 计数器/限速器:利用 Redis 中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等。这类操作如果用 MySQL,频繁的读写会带来相当大的压力;限速器比较典型的使用场景是限制某个用户访问某个 API 的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力
  4. 好友关系:利用集合的一些命令,比如求交集、并集、差集等。可以方便解决一些共同好友、共同爱好之类的功能
  5. 消息队列:除了 Redis 自身的发布/订阅模式,我们也可以利用 List 来实现一个队列机制,比如:到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的 DB 压力,完全可以用 List 来完成异步解耦

1.4. Redis 有哪些常见的功能?

  1. 数据缓存功能

  2. 分布式锁的功能

  3. 支持数据持久化

  4. 支持事务

  5. 支持消息队列

1.5. Redis为什么这么快

  1. 基于内存
  2. 数据结构简单,数据操作简单
  3. 采用单线程
  4. 使用多路I/O复用模型,非堵塞IO

1.6. Redis 数据类型

基础

  • string
  • hash
  • list
  • set
  • zset 即 sorted set

高级

  • HyperLogLog
  • Geo
  • Pub/Sub
  • Redis Module
  • BloomFilter
  • RedisSearch
  • Redis-ML

1.7. Redis持久化

RDB
RDB( Redis Database ),按照一定的时间周期策略把Redis内存中的数据保存以快照的形式保存到硬盘的二进制文件
。生成dump.rdb 文件

AOF
AOF(Append-only file),将收到的Redis命令以日志的形式追加到文件,类似于MySQL的binlog。
生成.aof 文件

常用持久化模式

RDB+AOF
RDB做镜像全量持久化,AOF做增量持久化

1.8. RDB和AOF的区别

  1. AOF 文件比 RDB 更新频率高,优先使用 AOF 还原数据

  2. AOF比 RDB 更安全也更大

  3. RDB 性能比 AOF 好

  4. 如果两个都配了优先加载 AOF

1.9. Redis常见问题

1.9.1. 缓存雪崩

解释

大量的key过期时间设置过于集中,到过期时间节点,缓存大面积过期,Redis处理短暂卡顿现象,导致大量的查询请求打到数据库,数据库可能宕机

如何处理

  1. 热点数据永不过期
  2. 随机设置缓存过期时间

1.9.2. 缓存穿透

解释

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,我们数据库的 id 都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库

如何处理

  1. 查询参数校验
  2. 查询数据为null 设置缓存 过期时间较短
  3. 布隆过滤器(Bloom Filter)
  4. Nginx网关限制单IP请求数

1.9.3. 缓存击穿

解释

热点key大并发期间突然失效,导致大量请求请求数据库

如何处理

  1. 热点数据永不过期
  2. 互斥锁

1.10. Redis 内存淘汰策略有哪些?

no-enviction 默认Redis淘汰策略

  • volatile-lru:从已设置过期时间的数据集(server. db[i]. expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server. db[i]. expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server. db[i]. expires)中任意选择数据淘汰
  • allkeys-lru:从数据集(server. db[i]. dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server. db[i]. dict)中任意选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据
  • noeviction:达到内存限制时不保存新值。当数据库使用复制时,这适用于主数据库
  • allkeys-lru:保留最近使用的密钥;删除最近最少使用 (LRU) 键
  • allkeys-lfu : 保存常用键;删除最不常用 (LFU) 键
  • volatile-lfuexpire :删除字段设置为的最不常用键true。

1.11. Redis 常见性能问题和解决方案?

  1. Master不做RDB和AOF备份,由Slave开启AOF备份
  2. 主从服务器在同一局域网内
  3. 主从复制不采用树结构,使用单项链表如:Master <- Slave1 <- Slave2 <- Slave3…
  4. 尽量避免在压力很大的主库上增加从库

1.12. Redis如何做内存优化?

  1. 控制RedisKey数量
  2. key值名称长度越短越好
  3. value值内容越短越好

1.13. 缓存预热,缓存降级,缓存更新

缓存预热

缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。

如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。

缓存预热解决方案:

数据量不大的时候,工程启动的时候进行加载缓存动作;
数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;
数据量太大的时候,优先保证热点数据进行提前加载到缓存。

缓存降级

缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。

缓存降级解决方案:
在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;

错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;

严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

缓存刷新

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰

缓存刷新解决方案:

  • 定时去清理过期的缓存;
  • 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

1.14. 为什么Redis的操作是原子性的,怎么保证原子性的?

对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。

Redis的操作之所以是原子性的,是因为Redis是单线程的。(Redis新版本已经引入多线程,这里基于旧版本的Redis)

Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。

多个命令在并发中也是原子性的吗?

不一定, 将get和set改成单命令操作,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现.

1.15. Redis实现分布式锁

Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。

将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作
image

解锁:使用 del key 命令就能释放锁

del lock-key

解决死锁:

  • 通过Redis中expire()给锁设定最大持有时间,如果超过,则Redis来帮我们释放锁。
  • 使用 setnx key “当前系统时间+锁持有的时间”和getset key “当前系统时间+锁持有的时间”组合的命令就可以实现。

1.16. Redis高可用

高可用是通过设计,减少系统不能提供服务的时间,是分布式系统的基础也是保障系统可靠性的重要手段

  • 数据持久化(RDB+AOF)
  • 主从数据同步(主从复制)
  • Redis 哨兵模式(Sentinel)
  • Redis 集群(Cluster)
    其中数据持久化保证了系统在发生宕机或者重启之后数据不会丢失,增加了系统的可靠性和减少了系统不可用的时间(省去了手动恢复数据的过程);而主从数据同步可以将数据存储至多台服务器,这样当遇到一台服务器宕机之后,可以很快地切换至另一台服务器以继续提供服务;哨兵模式用于发生故障之后自动切换服务器;而 Redis 集群提供了多主多从的 Redis 分布式集群环境,用于提供性能更好的 Redis 服务,并且它自身拥有故障自动切换的能力

1.16.1. 数据持久化

RDB+AOF 混合配置

127.0.0.1:6379> config get aof-use-rdb-preamble
1) "aof-use-rdb-preamble"
2) "yes"

1.16.2. Redis 主从同步

主从同步是 Redis 多机运行中最基础的功能,它是把多个 Redis 节点组成一个 Redis 集群,在这个集群当中有一个主节点用来进行数据的操作,其他从节点用于同步主节点的内容,并且提供给客户端进行数据查询。
Redis 主从同步分为:主从模式和从从模式。
主从模式就是一个主节点和多个一级从节点,而从从模式是指一级从节点下面还可以拥有更多的从节点。
主从模式可以提高 Redis 的整体运行速度,因为使用主从模式就可以实现数据的读写分离,把写操作的请求分发到主节点上,把其他的读操作请求分发到从节点上,这样就减轻了 Redis 主节点的运行压力,并且提高了 Redis 的整体运行速度。
不但如此使用主从模式还实现了 Redis 的高可用,当主服务器宕机之后,可以很迅速的把从节点提升为主节点,为 Redis 服务器的宕机恢复节省了宝贵的时间。
并且主从复制还降低了数据丢失的风险,因为数据是完整拷贝在多台服务器上的,当一个服务器磁盘坏掉之后,可以从其他服务器拿到完整的备份数据。

1.16.3. Redis 哨兵模式

Redis 主从复制模式有那么多的优点,但是有一个致命的缺点,就是当 Redis 的主节点宕机之后,必须人工介入手动恢复,那么到特殊时间段,比如公司组织全体团建或者半夜突然发生主节点宕机的问题,此时如果等待人工去处理就会很慢,这个时间是我们不允许的,并且我们还需要招聘专职的人来负责数据恢复的事,同时招聘的人还需要懂得相关的技术才能胜任这份工作。既然如此的麻烦,那有没有简单一点的解决方案,这个时候我们就需要用到 Redis 的哨兵模式了。
Redis 哨兵模式就是用来监视 Redis 主从服务器的,当 Redis 的主从服务器发生故障之后,Redis 哨兵提供了自动容灾修复的功能.Redis 哨兵模块存储在 Redis 的 src/redis-sentinel 目录.
我们可以使用命令./src/redis-sentinel sentinel.conf来启动哨兵功能。
有了哨兵功能之后,就再也不怕 Redis 主从服务器宕机了。哨兵的工作原理是每个哨兵会以每秒钟 1 次的频率,向已知的主服务器和从服务器,发送一个 PING 命令。如果最后一次有效回复 PING 命令的时间,超过了配置的最大下线时间(Down-After-Milliseconds)时,默认是 30s,那么这个实例会被哨兵标记为主观下线。
如果一个主服务器被标记为主观下线,那么正在监视这个主服务器的所有哨兵节点,要以每秒 1 次的频率确认主服务器是否进入了主观下线的状态。如果有足够数量(quorum 配置值)的哨兵证实该主服务器为主观下线,那么这个主服务器被标记为客观下线。此时所有的哨兵会按照规则(协商)自动选出新的主节点服务器,并自动完成主服务器的自动切换功能,而整个过程都是无须人工干预的。

1.16.4. Redis 集群

Redis 集群也就是 Redis Cluster,它是 Redis 3.0 版本推出的 Redis 集群方案,将数据分布在不同的主服务器上,以此来降低系统对单主节点的依赖,并且可以大大提高 Redis 服务的读写性能。Redis 集群除了拥有主从模式 + 哨兵模式的所有功能之外,还提供了多个主从节点的集群功能,实现了真正意义上的分布式集群服务.
Redis 集群可以实现数据分片服务,也就是说在 Redis 集群中有 16384 个槽位用来存储所有的数据,当我们有 N 个主节点时,可以把 16384 个槽位平均分配到 N 台主服务器上。当有键值存储时,Redis 会使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位,再把此键值存储在对应的服务器上,读取操作也是同样的道理,这样我们就实现了数据分片的功能。

未完待续ing…