Redis 面试题

redis

什么是redis

全称remote dictionary server, 是一个基于内存的高性能key-value数据库

有什么优点

  • 1.速度快:数据存在内存中
  • 2.支持丰富数据类型:支持 String ,List,Set,Sorted Set,Hash 五种基础的数据结构,单个 Value 的最大限制是 1GB,还提供 Bitmap,HyperLogLog,GEO等高级的数据结构.
  • 3.丰富的特性:订阅发布 Pub / Sub 功能、Key 过期策略、事务、支持多个 DB、计数
  • 4.持久化存储:提供RDB和AOF两种数据的持久化存储方案
  • 5.高可用:内置 Redis Sentinel ,提供高可用方案,实现主从故障自动转移。内置 Redis Cluster ,提供集群方案,实现基于槽的分片方案,从而支持更大的 Redis 规模。

Redis 有什么缺点

  • 1、由于 Redis 是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然 Redis 本身有 Key 过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。
  • 2、如果进行完整重同步,由于需要生成 RDB 文件,并进行传输,会占用主机的 CPU ,并会消耗现网的带宽。
  • 3、修改配置文件,进行重启,将硬盘中的数据加载进内存,时间比较久。在这个过程中,Redis 不能提供服务。

Redis 和 Memcached 的区别有哪些?

1. Redis 支持复杂的数据结构

  • Memcached 仅提供简单的字符串。
  • Redis 提供复杂的数据结构,丰富的数据操作。

2. Redis 原生支持集群模式

3. 性能对比

  • Redis 只使用单核,而 Memcached 可以使用多核,所以平均每一个核上 Redis在存储小数据时比 Memcached 性能更高。
  • 在 100k 以上的数据中,Memcached 性能要高于 Redis 。虽然 Redis 最近也在存储大数据的性能上进行优化,但是比起 Memcached,还是稍有逊色。

4. 内存管理机制不同

相比来说,Redis 的内存管理机制,会更加简单。

  • Redis 采用的是包装的 malloc/free ,使用时现场申请的方式。
  • Memcached 采用的是 Slab Allocation 机制管理内存,预分配的内存池的方式。

如果对比两者的内存使用效率:

  • 简单的 Key-Value 存储的话,Memcached 的内存利用率更高,可以使用类似内存池。
  • 如果 Redis 采用 hash 结构来做 key-value 存储,由于其组合式的压缩, 其内存利用率会高于 Memcached 。

5. 网络 IO 模型

  • Memcached 是多线程,非阻塞 IO 复用的网络模型,原型上接近 Nignx 。
  • Redis 使用单线程的 IO 复用模型,自己封装了一个简单的 AeEvent 事件处理框架,主要实现了 epoll , kqueue 和 select ,更接近 Apache 早期的模式。

6. 持久化存储

  • Memcached 不支持持久化存储,重启时,数据被清空。
  • Redis 支持持久化存储,重启时,可以恢复已持久化的数据。

请说说 Redis 的线程模型?

非阻塞 IO ,多路复用

Redis 内部使用文件事件处理器 file event handler,这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 Socket,根据 Socket 上的事件来选择对应的事件处理器进行处理。

文件事件处理器的结构包含 4 个部分:

  • 多个 Socket 。
  • IO 多路复用程序。
  • 文件事件分派器。
  • 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)。

多个 Socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。

来看客户端与 redis 的一次通信过程:

redis-single-thread-model

  • 客户端 Socket01 向 Redis 的 Server Socket 请求建立连接,此时 Server Socket 会产生一个 AE_READABLE 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给连接应答处理器。连接应答处理器会创建一个能与客户端通信的 Socket01,并将该 Socket01 的 AE_READABLE 事件与命令请求处理器关联。

  • 假设此时客户端发送了一个 set key value 请求,此时 Redis 中的 Socket01 会产生 AE_READABLE 事件,IO 多路复用程序将事件压入队列,此时事件分派器从队列中获取到该事件,由于前面 Socket01 的 AE_READABLE 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 Scket01 的 set key value 并在自己内存中完成 set key value 的设置。操作完成后,它会将 Scket01 的 AE_WRITABLE 事件与令回复处理器关联。

  • 如果此时客户端准备好接收返回结果了,那么 Redis 中的 Socket01 会产生一个 AE_WRITABLE 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 ok,之后解除 Socket01 的 AE_WRITABLE 事件与命令回复处理器的关联。

为什么 Redis 单线程模型也能效率这么高?

  • 1、C 语言实现。

  • 2、纯内存操作。

  • 3、基于非阻塞的 IO 多路复用机制。

  • 4、单线程,避免了多线程的频繁上下文切换问题。

  • 5、丰富的数据结构。

    Redis 全程使用 hash 结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化。例如,压缩表,对短数据进行压缩存储;再再如,跳表,使用有序的数据结构加快读取的速度。

    也因为 Redis 是单线程的,所以可以实现丰富的数据结构,无需考虑并发的问题。

Redis 是单线程的,如何提高多核 CPU 的利用率?

可以在同一个服务器部署多个 Redis 的实例,并把他们当作不同的服务器来使用,在某些时候,无论如何一个服务器是不够的, 所以,如果你想使用多个 CPU ,你可以考虑一下分区。

Redis 有几种持久化方式?

Redis 提供了两种方式,实现数据的持久化到硬盘。
1、【全量】RDB 持久化,在指定的时间间隔内将内存中的数据集快照写入磁盘。实际操作过程是,fork 一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
2、【增量】AOF持久化,以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

RDB持久化优缺点

优点

  • 灵活设置备份频率和周期。
  • 非常适合冷备份,对于灾难恢复而言,RDB 是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。对于 Redis 的服务进程而言,在开始持久化时,它唯一需要做的只是 fork 出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行 IO 操作了。也就是说,RDB 对 Redis 对外提供的读写服务,影响非常小,可以让 Redis 保持高性能。
  • 恢复更快。相比于 AOF 机制,RDB 的恢复速度更更快,更适合恢复数据,特别是在数据集非常大的情况。

缺点

如果想保证数据的高可用性,即最大限度的避免数据丢失,那么 RDB 将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。所以,RDB 实际场景下,需要和 AOF 一起使用。

由于 RDB 是通过 fork 子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是 1 秒钟。所以,RDB 建议在业务低估,例如在半夜执行。

AOF持久化优缺点

优点

该机制可以带来更高的数据安全性,即数据持久性。Redis 中提供了 3 种同步策略:即每秒同步、每修改(执行一个命令)同步和不同步。

  • 1.每秒同步:异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。
  • 2.每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。
  • 3.不同步,无需多言。

由于该机制对日志文件的写入操作采用的是 append 模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。因为以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高。

另外,如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在 Redis 下一次启动之前,我们可以通过 redis-check-aof 工具来帮助我们解决数据一致性的问题。

如果 AOF 日志过大,Redis 可以自动启用 rewrite 机制。即使出现后台重写操作,也不会影响客户端的读写。因为在 rewrite log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。

注意,AOF rewrite 机制,和 RDB 一样,也需要 fork 出一次子进程,如果 Redis 内存比较大,可能会因为 fork 阻塞下主进程。AOF 包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

缺点

对于相同数量的数据集而言,AOF 文件通常要大于 RDB 文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
根据同步策略的不同,AOF 在运行效率上往往会慢于 RDB 。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和 RDB 一样高效。
以前 AOF 发生过 bug ,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志/merge/回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug 。不过 AOF 就是为了避免 rewrite 过程导致的 bug ,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。

如何选择持久化

不要仅仅使用 RDB,因为那样会导致你丢失很多数据。也不要仅仅使用 AOF,因为那样有两个问题,第一,你通过 AOF 做冷备,没有 RDB 做冷备,来的恢复速度更快; 第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug

Redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。

如果同时使用 RDB 和 AOF 两种持久化机制,那么在 Redis 重启的时候,会使用 AOF 来重新构建数据,因为 AOF 中的数据更加完整。

Redis 有几种数据“过期”策略?

Redis 的过期策略:当 Redis 中缓存的 key 过期了,Redis 如何处理。Redis 提供了 3 种数据过期策略:

  • 被动删除:当读/写一个已经过期的 key 时,会触发惰性删除策略,直接删除掉这个过期 key 。
  • 主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以 Redis 会定期主动淘汰一批已过期的 key 。
  • 主动删除:当前已用内存超过 maxmemory 限定时,触发主动清理策略,

在 Redis 中,同时使用了上述 3 种策略,即它们非互斥的。想要进一步了解,可以看看 《关于 Redis 数据过期策略》 文章。

Redis 有哪几种数据“淘汰”策略?

Redis 内存数据集大小上升到一定大小的时候,就会进行数据淘汰策略。Redis 提供了 6 种数据淘汰策略:

  • 1.volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。redis并不是保证取得所有数据集中最近最少使用的键值对,而只是随机挑选的几个键值对中的, 当内存达到限制的时候无法写入非过期时间的数据集。
  • 2.volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰。redis 并不是保证取得所有数据集中最近将要过期的键值对,而只是随机挑选的几个键值对中的, 当内存达到限制的时候无法写入非过期时间的数据集。
  • 3.volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。当内存达到限制的时候无法写入非过期时间的数据集。
  • 4.allkeys-lru:从数据集中挑选最近最少使用的数据淘汰。当内存达到限制的时候,对所有数据集挑选最近最少使用的数据淘汰,可写入新的数据集。
  • 5.allkeys-random:从数据集中任意选择数据淘汰,当内存达到限制的时候,对所有数据集挑选随机淘汰,可写入新的数据集。
  • 6.【默认策略】no-enviction:当内存达到限制的时候,不淘汰任何数据,不可写入任何数据集,所有引起申请内存的命令会报错。

如何选择淘汰策略

  • allkeys-lru:如果我们的应用对缓存的访问符合幂律分布,也就是存在相对热点数据,或者我们不太清楚我们应用的缓存访问分布状况,我们可以选择allkeys-lru策略。

  • allkeys-random:如果我们的应用对于缓存key的访问概率相等,则可以使用这个策略。

  • volatile-ttl:这种策略使得我们可以向Redis提示哪些key更适合被收回。

另外,volatile-lru策略和volatile-random策略适合我们将一个Redis实例既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个Redis实例来达到相同的效果,值得一提的是将key设置过期时间实际上会消耗更多的内存,因此我们建议使用allkeys-lru策略从而更有效率的使用内存。

Redis 回收进程如何工作的?

一个客户端运行了新的写命令,添加了新的数据。Redis 检查内存Redis 执行新命令。

如果有大量的 key 需要设置同一时间过期,一般需要注意什么?

如果大量的 key 过期时间设置的过于集中,到过期的那个时间点,Redis可能会出现短暂的卡顿现象。一般需要在时间上加一个随机值,使得过期时间分散一些。

Redis 有哪些数据结构?

如果你是 Redis 普通玩家,可能你的回答是如下五种数据结构:

  • 字符串 String
  • 字典Hash
  • 列表List
  • 集合Set
  • 有序集合 SortedSet

如果你是 Redis 中级玩家,还需要加上下面几种数据结构:

  • HyperLogLog
  • Geo
  • Bitmap

如果你是 Redis 高端玩家,你可能玩过 Redis Module ,可以再加上下面几种数据结构:

  • BloomFilter
  • RedisSearch
  • Redis-ML
  • JSON

聊聊 Redis 使用场景

Redis 可用的场景非常之多:

  • 数据缓存
  • 分布式锁
  • 会话缓存:如将 web session 存放在 Redis 中。
  • 时效性数据:如验证码只有60秒有效期,超过时间无法使用
  • 访问频率:出于减轻服务器的压力控制访问频率,如限制 IP 在一段时间的访问量
  • 计数器:如点赞数、收藏数、分享数等
  • 社交列表:社交属性相关的列表信息,如用户点赞列表、用户分享列表
  • 记录用户判定信息:如用户是否点赞、用户是否收藏、用户是否分享
  • 交集、并集和差集:如实现共同好友,共同关注,共同偏好等社交关系
  • 热门列表与排行榜:按照得分进行排序,如展示最热、点击率最高、活跃度最高
  • 最新动态:按照时间顺序排列的最新动态,使用 Sorted Set 类型的分数权重存储 Unix 时间戳进行排序
  • 消息队列:Redis 能作为一个很好的消息队列来使用,依赖 List 类型利用 LPUSH 命令将数据添加到链表头部,通过 BRPOP 命令将元素从链表尾部取出。同时,市面上成熟的消息队列产品有很多,例如 RabbitMQ。因此,更加建议使用 RabbitMQ 作为消息中间件。

Redis 支持的 Java 客户端都有哪些?

使用比较广泛的有三个 Java 客户端:

  • Redisson:是一个高级的分布式协调 Redis 客服端,能帮助用户在分布式环境中轻松实现一些 Java 的对象。
  • Jedis: Redis 的 Java 实现的客户端,其 API 提供了比较全面的 Redis 命令的支持。
  • Lettuce:一个可伸缩线程安全的 Redis 客户端。多个线程可以共享同一个 RedisConnection 。它利用优秀 Netty NIO 框架来高效地管理多个连接。

Redis 官方推荐使用 Redisson 或 Jedis 。

如何使用 Redis 实现分布式锁?

方案一:set 指令

先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放。这种方式会有问题,如果在 setnx 之后执行 expire 之前进程意外 crash 或者要重启维护了,那会怎么样?这个锁就永远得不到释放了。

set 指令有非常复杂的参数,可以同时把 setnx 和 expire 合成一条指令来用的!

public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}

可以看到,我们加锁就一行代码:jedis.set(String key, String value, String nxxx, String expx, int time),这个set()方法一共有五个形参:

第一个为key,我们使用key来当锁,因为key是唯一的。
第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。
第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
第五个为time,与第四个参数相呼应,代表key的过期时间。

具体的实现,可以参考如下文章:* 《Redis 分布式锁的正确实现方式(Java 版)》

方案二:Redlock
set 指令的方案,适合用于在单机 Redis 节点的场景下,在多 Redis 节点的场景下,会存在分布式锁丢失的问题。所以,Redis 作者 Antirez 基于分布式环境下提出了一种更高级的分布式锁的实现方式:Redlock 。

具体的源码解析,可以看看 《精尽 Redisson 源码分析 —— 可靠分布式锁 RedLock》 文章。

具体的方案,胖友可以看看老友飞哥的两篇博客:

  • 《Redlock:Redis分布式锁最牛逼的实现》
  • 《Redisson 实现 Redis 分布式锁的 N 种姿势》

最近画了一个 Redisson 实现分布式锁的流程图,胖友可以点击传送门阅读。

对比 Zookeeper 分布式锁

  • 从可靠性上来说,Zookeeper 分布式锁好于 Redis 分布式锁。
  • 从性能上来说,Redis 分布式锁好于 Zookeeper 分布式锁。

如何使用 Redis 实现消息队列?

一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有消息的时候,要适当 sleep 一会再重试。

  • 如果对方追问可不可以不用 sleep 呢?list 还有个指令叫 blpop ,在没有消息的时候,它会阻塞住直到消息到来。

  • 如果对方追问能不能生产一次消费多次呢?使用 pub / sub 主题订阅者模式,可以实现 1:N 的消息队列。

  • 如果对方追问 pub / sub 有什么缺点?在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 rabbitmq 等。

  • 如果对方追问 redis 如何实现延时队列?使用 sortedset ,拿时间戳作为 score ,消息内容作为 key 调用 zadd 来生产消息,消费者用 zrangebyscore 指令获取 N 秒之前的数据轮询进行处理。

    可以看看 《Redis 学习笔记之延时队列》 。面试中,能回答到 Redis zset 实现延迟队列,还是蛮加分的。

实际上 Redis 真的真的真的不推荐作为消息队列使用,它最多只是消息队列的存储层,上层的逻辑,还需要做大量的封装和支持。

什么是 Redis Pipelining ?

一次请求/响应服务器能实现处理新的请求即使旧的请求还未被响应。这样就可以将多个命令发送到服务器,而不用等待回复,最后在一个步骤中读取该答复。Redis Pipelining 是 Redis Client 实现的功能,而不是 Redis Server 提供的特性。假设我们有 3 个请求进行下举例子。

  • 未使用 Pipeline 时,那么整个执行的顺序是,req1->req2->req3 。
  • 在使用 Pipeline 时,那么整个执行的顺序是,[req1,req2,req3] 一起发给 Redis Server ,而 Redis Server 收到请求后,一个一个请求进行执行,然后响应,不会进行什么特殊处理。而 Client 在收到 resp1,resp2,resp3 后,进行响应给业务上层。

所以,Pipeline 的作用,是避免每发一个请求,就阻塞等待这个请求的结果。

这就是管道(pipelining),是一种几十年来广泛使用的技术。例如许多 POP3 协议已经实现支持这个功能,大大加快了从服务器下载新邮件的过程。

Redis 很早就支持管道(pipelining)技术,因此无论你运行的是什么版本,你都可以使用管道(pipelining)操作 Redis。

Redis 如何做大量数据插入?

Redis 2.6 开始,Redis-cli 支持一种新的被称之为 pipe mode 的新模式用于执行大量数据插入工作。

什么是 Redis 事务?

可以一次性执行多条命令,本质上是一组命令的集合。一个事务中的所有命令都会序列化,然后按顺序地串行化执行,而不会被插入其他命令

事务相关的命令

  • 1)DISCARD:取消事务,放弃执行事务块中的所有命令
  • 2)EXEC:执行事务块中的命令
  • 3)MULTI:标记一个事务的开始
  • 4)UNWATCH:取消WATCH命令对所有 key 的监视
  • 5)WATCH key [key...]:监视一个(或多个)key,如果在事务之前执行这个(或者这些)key被其他命令所改动,那么事务将会被打断。乐观锁

事务报错问题

  • 1)语句错误:会直接在添加队列的时候报错,如果出现这个错误,则整个事务都会回滚
  • 2)逻辑错误:例如给一个字符串 + 1,在执行的时候才会报错。这种错误则不会影响事务中的其他操作,只有本条会报错

事务特性

1、单独的隔离操作:事务中的所有命令都会序列化、按顺序执行。事务执行过程中,不会被其他客户端发送来的命令请求打断。

2、没有隔离级别的概念:队列中的命令没有提交之前不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在“事务内的查询要看到是物理的更新,在事务外查询不能看到这个问题了”

3、不保证原子性:redis 同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。除非加入队列的时候就出错,即类似 java 的编译时异常和执行时一异常,编译时会导致回滚,执行时异常不回滚)

Redis哨兵(Sentinel)模式

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

image

这里的哨兵有两个作用

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。

  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。

如果使用 Redis Cluster 实现高可用?

详细,可以看看如下:https://www.cnblogs.com/williamjie/p/11132211.html

说说 Redis 哈希槽的概念?

Redis Cluster 没有使用一致性 hash ,而是引入了哈希槽的概念。Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。

因为最大是 16384 个哈希槽,所以考虑 Redis 集群中的每个节点都能分配到一个哈希槽,所以最多支持 16384 个 Redis 节点。

为什么是 16384 呢?主要考虑集群内的网络带宽,而 16384 刚好是 2K 字节大小。

Redis Cluster 的主从复制模型是怎样的?

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有 N-1 个复制节点。所以,Redis Cluster 可以说是 Redis Sentinel 带分片的加强版。也可以说:

  • Redis Sentinel 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master ,继续提供服务。
  • Redis Cluster 着眼于扩展性,在单个 Redis 内存不足时,使用 Cluster 进行分片存储。

Redis Cluster 方案什么情况下会导致整个集群不可用?

有 A,B,C 三个节点的集群,在没有复制模型的情况下,如果节点 B 宕机了,那么整个集群就会以为缺少 5501-11000 这个范围的槽而不可用。当然,这种情况也可以配置 cluster-require-full-coverage=no ,整个集群无需所有槽位覆盖。

Redis Cluster 会有写操作丢失吗?为什么?

Redis 并不能保证数据的强一致性,而是【异步复制】,这意味这在实际中集群在特定的条件下可能会丢失写操作。

注意,无论对于 Redis Sentinel 还是 Redis Cluster 方案,都是通过主从复制,所以在数据的复制方面,都存在相同的情况。

Redis 集群如何选择数据库?

Redis 集群目前无法做数据库选择,默认在 0 数据库。

请说说生产环境中的 Redis 是怎么部署的?

  • Redis Cluster ,10 台机器,5 台机器部署了 Redis 主实例,另外 5 台机器部署了 Redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰 qps 可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求每秒。

  • 机器是什么配置?32G 内存 + 8 核 CPU + 1T 磁盘,但是分配给 Redis 进程的是 10G 内存,一般线上生产环境,Redis 的内存尽量不要超过 10G,超过 10G 可能会有问题。那么,5 台机器对外提供读写,一共有 50G 内存。

  • 因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,Redis 从实例会自动变成主实例继续提供读写服务。

  • 你往内存里写的是什么数据?每条数据的大小是多少?商品数据,每条数据是 10kb 。100 条数据是 1mb ,10 万条数据是 1G 。常驻内存的是 200 万条商品数据,占用内存是 20G ,仅仅不到总内存的 50% 。目前高峰期每秒就是 3500 左右的请求量。

一般来说,当公司体量大了之后,建议是一个业务线独占一个或多个 Redis Cluster 集群,实现好业务线与业务线之间的隔离。

什么是 Redis 分区?

Redis 分区是一种模式,将数据分区到不同的 Redis 节点上,而 Redis 集群的 Redis Cluster、Twemproxy、Codis、客户端分片( 不包括 Redis Sentinel ) 这四种方案,是 Redis 分区的具体实现。

注意,Redis Sentinel 实现的是 Redis 的高可用

  • Redis 每个分区,如果想要实现高可用,需要使用到 Redis 主从复制。

你知道有哪些 Redis 分区实现方案

Redis 分区方案,主要分成两种类型:

  • 客户端分区,就是在客户端就已经决定数据会被存储到哪个 Redis 节点或者从哪个 Redis 节点读取。大多数客户端已经实现了客户端分区。案例:Redis Cluster 和客户端分区。
  • 代理分区,意味着客户端将请求发送给代理,然后代理决定去哪个节点写数据或者读数据。代理根据分区规则决定请求哪些 Redis 实例,然后根据 Redis 的响应结果返回给客户端。案例:Twemproxy 和 Codis 。

Redis Cluster 实现了一种混合形式的查询路由,但并不是直接将请求从一个Redis 节点转发到另一个 Redis 节点,而是在客户端的帮助下直接 Redirect 到正确的 Redis 节点。

Redis 有哪些重要的健康指标?

推荐阅读 《Redis 几个重要的健康指标》

  • 存活情况
  • 连接数
  • 阻塞客户端数量
  • 使用内存峰值
  • 内存碎片率
  • 缓存命中率
  • OPS
  • 持久化
  • 失效KEY
  • 慢日志

如何提高 Redis 命中率?
聚焦在高频访问且时效性要求不高的热点业务上(如字典数据、session、token),通过缓存预加载(预热)、增加存储容量、调整缓存粒度、更新缓存等手段来提高命中率。
推荐阅读 《如何提高缓存命中率(Redis)》 。

怎么优化 Redis 的内存占用?

推荐阅读 《Redis 的内存优化》

  • redisObject 对象
  • 缩减键值对象
  • 共享对象池
  • 字符串优化
  • 编码优化
  • 控制 key 的数量

一个 Redis 实例最多能存放多少的 keys?List、Set、Sorted Set 他们最多能存放多少元素?

理论上,Redis 可以处理多达 2^32 的 keys ,并且在实际中进行了测试,每个实例至少存放了 2 亿 5 千万的 keys。

任何 list、set、和 sorted set 都可以放 2^32 个元素。

假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?

使用 keys 指令可以扫出指定模式的 key 列表。

  • 对方接着追问:如果这个 Redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?
  • 这个时候你要回答 Redis 关键的一个特性:Redis 的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。

Redis 常见的性能问题都有哪些?如何解决?

1、Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件。

一般是主节点开启 AOF ,从节点开启 AOF + RDB 。

  • Master 写内存快照,save 命令调度 rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以 Master 最好不要写内存快照。
  • Master AOF 持久化,如果不重写 AOF 文件,这个持久化方式对性能的影响是最小的,但是 AOF 文件会不断增大,AOF 文件过大会影响 Master 重启的恢复速度。
  • 所以,Master 最好不要做任何持久化工作,包括内存快照和 AOF 日志文件,特别是不要启用内存快照做持久化。如果数据比较关键,某个 Slave 开启AOF备份数据,策略为每秒同步一次。

2、Master 调用 BGREWRITEAOF 重写 AOF 文件,AOF 在重写的时候会占大量的 CPU 和内存资源,导致服务 load 过高,出现短暂服务暂停现象。

  • 一般来说,出现这个问题,很多时候是因为 Master 的内存过大,一次 AOF 重写需要占用的 CPU 和内存的资源较多,此时可以考虑 Redis Cluster 方案。

3、尽量避免在压力很大的主库上增加过多的从库。

  • 可以考虑在从上挂载其它的从。

4、主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3... 。

  • 这样的结构,也方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master挂了,可以立刻启用 Slave1 做 Master ,其他不变。

  • 从节点在切换主节点作为复制源的时候,会重新发起全量复制。所以此处通过 Slave1 挂在 Slave 下,可以规避这个问题。同时,也减少了 Master 的复制压力。当然,坏处就是 Slave1 的延迟可能会高一些些,所以还是需要取舍。

5、Redis 主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave 和 Master 最好在同一个局域网内。

修改配置不重启 Redis 会实时生效吗?

针对运行实例,有许多配置选项可以通过 CONFIG SET 命令进行修改,而无需执行任何形式的重启。

从 Redis 2.2 开始,可以从 AOF 切换到 RDB 的快照持久性或其他方式而不需要重启 Redis。检索 CONFIG GET * 命令获取更多信息。

但偶尔重新启动是必须的,如为升级 Redis 程序到新的版本,或者当你需要修改某些目前 CONFIG 命令还不支持的配置参数的时候。

你可能感兴趣的:(Redis 面试题)