Redis常见面试题总结(上)
Redis 基础
什么是 Redis?
Redis (REmote DIctionary Server)是一个基于 C 语言开发的开源 NoSQL 数据库(BSD 许可)。与传统数据库不同的是,Redis 的数据是保存在内存中的(内存数据库,支持持久化),因此读写速度非常快,被广泛应用于分布式缓存方向。并且,Redis 存储的是 KV 键值对数据。
为了满足不同的业务场景,Redis 内置了多种数据类型实现(比如 String、Hash、Sorted Set、Bitmap、HyperLogLog、GEO)。并且,Redis 还支持事务、持久化、Lua 脚本、发布订阅模型、多种开箱即用的集群方案(Redis Sentinel、Redis Cluster)。
Redis 没有外部依赖,Linux 和 OS X 是 Redis 开发和测试最多的两个操作系统,官方推荐生产环境使用 Linux 部署 Redis。
Redis为什么这么快?
Redis 内部做了非常多的性能优化,比较重要的有下面 3 点:
- Redis 基于内存,内存的访问速度比磁盘快很多;
- Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用(Redis 线程模式后面会详细介绍到);
- Redis 内置了多种优化过后的数据类型/结构实现,性能非常高。
- Redis 通信协议实现简单且解析高效。
除了 Redis,你还知道其他分布式缓存方案吗?
- Dragonfly:一种针对现代应用程序负荷需求而构建的内存数据库,完全兼容 Redis 和 Memcached 的 API,迁移时无需修改任何代码,号称全世界最快的内存数据库。
- KeyDB: Redis 的一个高性能分支,专注于多线程、内存效率和高吞吐量。
说一下 Redis和 Memcached 的区别和共同点
共同点:
- 都是基于内存的数据库,一般都用来当做缓存使用。
- 都有过期策略。
- 两者的性能都非常高。
区别:
- 数据类型:Redis 支持更丰富的数据类型(支持更复杂的应用场景)。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
- 数据持久化:Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memcached 把数据全部存在内存之中。也就是说,Redis 有灾难恢复机制而 Memcached 没有。
- 集群模式支持:Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 自 3.0 版本起是原生支持集群模式的。
- 线程模型:Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。 (Redis 6.0 针对网络数据的读写引入了多线程)
- 特性支持:Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且,Redis 支持更多的编程语言。
- 过期数据删除:Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。
为什么要用 Redis?
- 高性能与低延迟:
- Redis使用内存作为主存储介质,这使得读写操作非常快速,通常达到微秒级响应时间。
- 它采用了多种优化策略,如多路复用IO和基于事件驱动的异步机制,进一步提升了性能。
- 丰富的数据结构支持:
- Redis不仅支持基本的键值对存储,还提供了列表、集合、有序集合、哈希表等多种数据结构。
- 这为复杂的数据模型提供了便利,使得开发者能更灵活地处理数据。
- 持久化机制:
- 虽然Redis主要在内存中操作数据,但它提供了RDB(快照)和AOF(追加文件)两种持久化方式。
- 这确保了数据不会因为服务器故障而丢失,提高了数据的可靠性和持久性。
- 高可用性:
- 通过复制(Replication)、哨兵(Sentinel)和集群(Cluster)模式,Redis能够实现数据的高可用性和自动故障转移。
- 这增强了系统的稳定性和可靠性,即使在部分节点故障的情况下也能保证服务的连续性。
- 消息队列与发布/订阅:
- Redis可以作为简单的消息队列使用,利用发布/订阅模式进行消息的异步传递。
- 这非常适合解耦系统组件,实现不同服务之间的异步通信。
- 缓存:
- Redis是最常用的缓存解决方案之一,能够显著加速数据读取速度,减少数据库负载。
- 无论是网页缓存、会话缓存还是计算结果缓存,Redis都能提供良好支持。
- 会话管理:
- 网站或应用可以将用户会话信息存储在Redis中,以减少对后端数据库的频繁访问。
- 这提高了响应速度,并简化了会话管理。
- 实时数据分析与统计:
- 利用Redis的有序集合,可以快速进行计数、排序等操作。
- 这适用于实时排名、计数器等需求,为数据分析提供了有力支持。
- 限流与防刷:
- 利用Redis的原子操作特性,可以简单有效地实现访问频率限制。
- 这有助于防止恶意刷请求,保护系统的安全性。
- 分布式锁:
- Redis提供SETNX、MULTI/EXEC等命令,可以方便地实现分布式环境下的锁机制。
- 这解决了并发控制问题,确保了多个节点之间共享资源的一致性。
什么是 Redis Module?有什么用?
目前,被 Redis 官方推荐的 Module 有:
- RediSearch:用于实现搜索引擎的模块。
- RedisJSON:用于处理 JSON 数据的模块。
- RedisGraph:用于实现图形数据库的模块。
- RedisTimeSeries:用于处理时间序列数据的模块。
- RedisBloom:用于实现布隆过滤器的模块。
- RedisAI:用于执行深度学习/机器学习模型并管理其数据的模块。
- RedisCell:用于实现分布式限流的模块。
Redis 应用
- 分布式锁:通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。关于 Redis 实现分布式锁的详细介绍,可以看我写的这篇文章:分布式锁详解 。
- 限流:一般是通过 Redis + Lua 脚本的方式来实现限流。如果不想自己写 Lua 脚本的话,也可以直接利用 Redisson 中的
RRateLimiter
来实现分布式限流,其底层实现就是基于 Lua 代码+令牌桶算法。 - 消息队列:Redis 自带的 List 数据结构可以作为一个简单的队列使用。Redis 5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。
- 延时队列:Redisson 内置了延时队列(基于 Sorted Set 实现的)。
- 分布式 Session :利用 String 或者 Hash 数据类型保存 Session 数据,所有的服务器都可以访问。
- 复杂业务场景:通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 Bitmap 统计活跃用户、通过 Sorted Set 维护排行榜。
如何基于 Redis 实现分布式锁?
一、基于SETNX和EXPIRE命令
- SETNX命令:SETNX(Set if Not Exists)是Redis的一个原子操作,用于在key不存在时设置值。如果key已存在,则不做任何操作。这是实现分布式锁的关键命令,因为它能确保在同一时间只有一个客户端能够获得锁。
- EXPIRE命令:EXPIRE命令用于为key设置过期时间。这对于避免死锁非常重要,因为即使某个客户端崩溃,锁也会在一定时间后自动释放。
实现步骤:
- 客户端尝试使用SETNX命令设置锁key。
- 如果SETNX命令返回成功(即key被成功设置),则客户端获得锁,并使用EXPIRE命令为锁key设置过期时间。
- 如果SETNX命令返回失败(即key已存在),则客户端未获得锁,可以等待一段时间后重试。
注意事项:
- SETNX和EXPIRE命令需要原子性执行,以避免在SETNX成功但EXPIRE失败时导致的死锁问题。通常可以通过Lua脚本来保证这两个命令的原子性。
二、基于SET命令的扩展参数
Redis的SET命令支持多个扩展参数,包括NX、EX/PX等,这些参数可以方便地实现分布式锁。
- NX:表示key不存在时才能设值成功,即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁后才能获取锁。
- EX/PX:用于设置key的过期时间,EX以秒为单位,PX以毫秒为单位。
实现步骤:
- 客户端使用SET key value NX EX/PX命令尝试获取锁。
- 如果命令返回成功,则客户端获得锁。
- 如果命令返回失败,则客户端未获得锁,可以等待一段时间后重试。
三、Redisson分布式锁
Redisson是一个在Redis基础上实现的Java驻内存数据网格,它提供了高效的分布式锁机制。
实现原理:
- Redisson使用Lua脚本来保证加锁和解锁操作的原子性。
- 在加锁时,Redisson会生成一个随机值作为锁的唯一标识,并将其与锁key一起存储。
- 解锁时,Redisson会检查锁的唯一标识是否匹配,只有匹配时才会释放锁。
- Redisson还提供了看门狗机制,用于在锁持有者崩溃或失联时自动延长锁的持有时间,防止死锁。
使用步骤:
- 引入Redisson的依赖。
- 配置Redisson客户端。
- 使用Redisson提供的锁API(如RLock)进行加锁和解锁操作。
四、RedLock算法
RedLock是Redis官方推荐的一种基于多个Redis实例的分布式锁算法,旨在提供更高的安全性和容错能力。
实现原理:
- 客户端向多个Redis实例发送加锁请求。
- 如果大部分(通常是过半数)Redis实例都成功返回加锁结果,则客户端认为加锁成功。
- 解锁时,客户端需要向所有参与加锁的Redis实例发送解锁请求。
注意事项:
- RedLock算法需要多个Redis实例的支持,并且这些实例之间需要保持一定的独立性(如部署在不同的物理节点上)。
- 在使用RedLock算法时,需要注意网络分区和Redis实例故障等异常情况的处理。
Redis 可以做搜索引擎么?
- 数据量限制:Elasticsearch 可以支持 PB 级别的数据量,可以轻松扩展到多个节点,利用分片机制提高可用性和性能。RedisSearch 是基于 Redis 实现的,其能存储的数据量受限于 Redis 的内存容量,不太适合存储大规模的数据(内存昂贵,扩展能力较差)。
- 分布式能力较差:Elasticsearch 是为分布式环境设计的,可以轻松扩展到多个节点。虽然 RedisSearch 支持分布式部署,但在实际应用中可能会面临一些挑战,如数据分片、节点间通信、数据一致性等问题。
- 聚合功能较弱:Elasticsearch 提供了丰富的聚合功能,而 RediSearch 的聚合功能相对较弱,只支持简单的聚合操作。
- 生态较差:Elasticsearch 可以轻松和常见的一些系统/软件集成比如 Hadoop、Spark、Kibana,而 RedisSearch 则不具备该优势。
如何基于 Redis 实现延时任务?
Redisson 内置的延时队列具备下面这些优势:
- 减少了丢消息的可能:DelayedQueue 中的消息会被持久化,即使 Redis 宕机了,根据持久化机制,也只可能丢失一点消息,影响不大。当然了,你也可以使用扫描数据库的方法作为补偿机制。
- 消息不存在重复消费问题:每个客户端都是从同一个目标队列中获取任务的,不存在重复消费的问题。
Redis 数据类型
Redis 中比较常见的数据类型有下面这些:
- 5 种基础数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
- 3 种特殊数据类型:HyperLogLog(基数统计)、Bitmap (位图)、Geospatial (地理位置)。
String 的应用场景有哪些?
String 的常见应用场景如下:
- 常规数据(比如 Session、Token、序列化后的对象、图片的路径)的缓存;
- 计数比如用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数;
- 分布式锁(利用
SETNX key value
命令可以实现一个最简易的分布式锁); - ……