基础
说说什么是 Redis?
Redis 是一种基于键值对的 NoSQL 数据库。
它主要的特点是把数据放在内存当中,相比直接访问磁盘的关系型数据库,读写速度会快很多,基本上能达到微秒级的响应。
Redis 可以用来干什么?
Redis 可以用来做缓存,比如说把高频访问的文章详情、商品信息、用户信息放入 Redis 当中,并通过设置过期时间来保证数据一致性,这样就可以减轻数据库的访问压力。
Redis 的 Zset 还可以用来实现积分榜、热搜榜,通过 score 字段进行排序,然后取前 N 个元素,就能实现 TOPN 的榜单功能。
利用 Redis 的 SETNX 命令或者 Redisson 还可以实现分布式锁,确保同一时间只有一个节点可以持有锁;为了防止出现死锁,可以给锁设置一个超时时间,到期后自动释放;并且最好开启一个监听线程,当任务尚未完成时给锁自动续期。
如果是秒杀接口,还可以使用 Lua 脚本来实现令牌桶算法,限制每秒只能处理 N 个请求。
redis做缓存要考虑哪些问题,在业务方面呢
一类是经典的缓存系统设计问题(穿透、击穿、雪崩),另一类是与业务逻辑紧密相关的业务缓存问题(数据一致性、缓存粒度等)。
当修改了数据库的数据后,如何保证缓存里的数据也同步更新?如果处理不好,用户就会看到“脏数据”。
另外就是我们应该缓存一个完整的、包含各种关联信息的复杂对象,还是只缓存那些最常用的基础字段?
Redis有哪些数据类型?

还有三种扩展数据类型,分别是用于位级操作的 Bitmap、用于基数估算的 HyperLogLog、支持存储和查询地理坐标的 GEO。
详细介绍下字符串?
字符串是最基本的数据类型,可以存储文本、数字或者二进制数据,最大容量是 512 MB。
适合缓存单个对象,比如验证码、token、计数器等。
详细介绍下列表?
列表是一个有序的元素集合,支持从头部或尾部插入/删除元素,常用于消息队列或任务列表。
详细介绍下哈希?
哈希是一个键值对集合,适合存储对象,如商品信息、用户信息等。比如说 value = {name: '沉默王二', age: 18}。
详细介绍下集合?
集合是无序且不重复的,支持交集、并集操作,查询效率能达到 O(1) 级别,主要用于去重、标签、共同好友等场景。
详细介绍下有序集合?
有序集合的元素按分数进行排序,支持范围查询,适用于排行榜或优先级队列。
详细介绍下Bitmap?
Bitmap 可以把一组二进制位紧凑地存储在一块连续内存中,每一位代表一个对象的状态,比如是否签到、是否活跃等。
比如用户 0 的已签到 1、用户 1 未签到 0、用户 2 已签到,Redis 就会把这些状态放进一个连续的二进制串 101,1 亿用户签到仅需 100,000,000 / 8 / 1024 ≈ 12MB 的空间,真的省到离谱。
详细介绍下HyperLogLog?
HyperLogLog 是一种用于基数统计的概率性数据结构,可以在仅有 12KB 的内存空间下,统计海量数据集中不重复元素的个数,误差率仅 0.81%。
底层基于 LogLog 算法改进,先把每个元素哈希成一个二进制串,然后取前 14 位进行分组,放到 16384 个桶中,记录每组最大的前导零数量,最后用一个近似公式推算出总体的基数。
详细介绍下GEO?
GEO 用于存储和查询地理位置信息,可以用来计算两点之间的距离,查找某位置半径内的其他元素。
常见的应用场景包括:附近的人或者商家、计算外卖员和商家的距离、判断用户是否进入某个区域等。
底层基于 ZSet 实现,通过 Geohash 算法把经纬度编码成 score。
比如说查询附近的商家时,Redis 会根据中心点经纬度反推可能的 Geohash 范围, 在 ZSet 上做范围查询,拿到候选点后,用 Haversine 公式精确计算球面距离,筛选出最终符合要求的位置。
为什么使用 hash 类型而不使用 string 类型序列化存储?
Hash 可以只读取或者修改某一个字段,而 String 需要一次性把整个对象取出来。
Redis 为什么快呢?
第一,Redis 的所有数据都放在内存中,而内存的读写速度本身就比磁盘快几个数量级。
第二,Redis 采用了基于 IO 多路复用技术的事件驱动模型来处理客户端请求和执行 Redis 命令。
其中的 IO 多路复用技术可以在只有一个线程的情况下,同时监听成千上万个客户端连接,解决传统 IO 模型中每个连接都需要一个独立线程带来的性能开销。
IO 多路复用会持续监听请求,然后把准备好的请求压入到一个队列当中,并将其有序地传递给文件事件分派器,最后由事件处理器来执行对应的 accept、read 和 write 请求。
Redis 会根据操作系统选择最优的 IO 多路复用技术,比如 Linux 下使用 epoll,macOS 下使用 kqueue 等。
为了进一步解决网络 IO 的性能瓶颈,Redis 6.0 引入了多线程机制,把网络 IO 和命令执行分开,网络 IO 交给线程池来处理,而命令执行仍然在主线程中进行,这样就可以充分利用多核 CPU 的性能。
主线程专注于命令执行,网络IO 由其他线程分担,在多核 CPU 环境下,Redis 的性能可以得到显著提升
第三,Redis 对底层数据结构做了极致的优化,比如说 String 的底层数据结构动态字符串支持动态扩容、预分配冗余空间,能够减少内存碎片和内存分配的开销。
能详细说一下IO多路复用吗?
IO 多路复用是一种允许单个进程同时监视多个文件描述符的技术,使得程序能够高效处理多个并发连接而无需创建大量线程。
IO 多路复用的核心思想是:让单个线程可以等待多个文件描述符就绪,然后对就绪的描述符进行操作。这样可以在不使用多线程或多进程的情况下处理并发连接。
主要的实现机制包括 select、poll、epoll、kqueue 和 IOCP 等。
请说说 select、poll、epoll、kqueue 和 IOCP 的区别?
select 的缺点是单个进程能监视的文件描述符数量有限,一般为 1024 个,且每次调用都需要将文件描述符集合从用户态复制到内核态,然后遍历找出就绪的描述符,性能较差。
poll 的优点是没有最大文件描述符数量的限制,但是每次调用仍然需要将文件描述符集合从用户态复制到内核态,依然需要遍历,性能仍然较差。
epoll 是 Linux 特有的 IO 多路复用机制,支持大规模并发连接,使用事件驱动模型,性能更高。其工作原理是将文件描述符注册到内核中,然后通过事件通知机制来处理就绪的文件描述符,不需要轮询,也不需要数据拷贝,更没有数量限制,所以性能非常高。
kqueue 是 BSD/macOS 系统下的 IO 多路复用机制,类似于 epoll,支持大规模并发连接,使用事件驱动模型。
IOCP 是 Windows 系统下的 IO 多路复用机制,使用使用完成端口模型而非事件通知。
IO多路复用
比如说我是一名数学老师,上课时提出了一个问题:“今天谁来证明一下勾股定律?”
同学小王举手,我就让小王回答;小李举手,我就让小李回答;小张举手,我就让小张回答。
这种模式就是 IO 多路复用,我只需要在讲台上等,谁举手谁回答,不需要一个一个去问。
举例子说一下阻塞 IO和 IO 多路复用的差别?
假设我是一名老师,让学生解答一道题目。
我的第一种选择:按顺序逐个检查,先检查 A同学,然后是 B,之后是 C、D。。。这中间如果有一个学生卡住,全班都会被耽误。
这种就是阻塞 IO,不具有并发能力。
我的第二种选择,我站在讲台上等,谁举手我去检查谁。C、D 举手,我去检查 C、D 的答案,然后继续回到讲台上等。此时 E、A 又举手,然后去处理 E 和 A。
select、poll 和 epoll 的实现原理?
select 和 poll 都是通过把所有文件描述符传递给内核,由内核遍历判断哪些就绪。
select 将文件描述符 FD 通过 BitsMap 传入内核,轮询所有的 FD,通过调用 file->poll 函数查询是否有对应事件,没有就将 task 加入 FD 对应 file 的待唤醒队列,等待事件来临被唤醒。
poll 改进了连接数上限问题,不再用 BitsMap 来传入 FD,取而代之的是动态数组 pollfd,但本质上仍是线性遍历,性能没有提升太多
select和poll的模式都是,一次将参数拷贝到内核空间,等有结果了再一次拷贝出去。
epoll 将监听的 FD 注册进内核的红黑树,由内核在事件触发时将就绪的 FD 放入 ready list。应用程序通过 epoll_wait 获取就绪的 FD,从而避免遍历所有连接的开销。
epoll 最大的优点是:支持事件驱动 + 边缘触发,ADD 时拷贝一次,epoll_wait 时利用 MMAP 和用户共享空间,直接拷贝数据到用户空间,因此在高并发场景下性能远高于 select 和 poll。
Redis为什么早期选择单线程?
第一,单线程模型不需要考虑复杂的锁机制,不存在多线程环境下的死锁、竞态条件等问题,开发起来更快,也更容易维护。
第二,Redis 是IO 密集型而非 CPU 密集型,主要受内存和网络 IO 限制,而非 CPU 的计算能力,单线程可以避免线程上下文切换的开销。
哪怕我们在一个普通的 Linux 服务器上启动 Redis 服务,它也能在 1s 内处理 1000000 个用户请求。
第三,单线程可以保证命令执行的原子性,无需额外的同步机制。
Redis 6.0 使用多线程是怎么回事?
Redis 6.0 的多线程仅用于处理网络 IO,包括网络数据的读取、写入,以及请求解析。而命令的执行依然是单线程,这种设计被称为“IO 线程化”,能够在高负载的情况下,最大限度地提升 Redis 的响应速度。
说说 Redis 的常用命令
Redis 支持多种数据结构,常用的命令也比较多,比如说操作字符串可以用 SET/GET/INCR,操作哈希可以用 HSET/HGET/HGETALL,操作列表可以用 LPUSH/LPOP/LRANGE,操作集合可以用 SADD/SISMEMBER,操作有序集合可以用 ZADD/ZRANGE/ZINCRBY等,通用命令有 EXPIRE/DEL/KEYS 等。
详细说说 set 命令?
SET 命令用于设置字符串的 key,支持过期时间和条件写入,常用于设置缓存、实现分布式锁、延长 Session 等场景。
默认情况下,SET 会覆盖键已有的值。
支持多种设置过期时间的方式,比如说 EX 设置秒级过期时间,PX 设置毫秒过期时间。
支持条件写入,使其可以实现原子性操作,比如说 NX 仅在键不存在时设置值,XX 仅在键存在时设置值。
sadd 命令的时间复杂度是多少?
SADD 支持一次添加多个元素,返回值为实际添加成功的元素数量,时间复杂度为 O(N)。
incr命令了解吗?
INCR 是一个原子命令,可以将指定键的值加 1,如果 key 不存在,会先将其设置为 0,再执行加 1 操作。
常用于网站访问量、文章点赞数等计数器的实现;结合过期时间实现限流器;生成分布式唯一 ID;库存扣减等。
单线程的Redis QPS 能到多少
根据官方的基准测试,一个普通服务器的 Redis 实例通常可以达到每秒十万左右的 QPS。
