前言
最近有个朋友参加小米二面,被问到一个很经典的问题:
他第一反应是:
因为 Redis 是基于内存的,内存读写速度快。
这个回答不能说错,但明显不够。
面试官接着追问:
Memcached 也是内存数据库,为什么 Redis 支持的数据结构更丰富,性能还这么高?
这一下,他就有点接不住了。
其实这类问题,面试官真正想考察的并不是"你知不知道 Redis 快",而是想看你能不能从存储模型、数据结构、网络模型、线程模型、工程优化这几个角度,把 Redis 高性能的原因讲清楚。
今天这篇文章就围绕这个问题展开:
Redis 为什么能支撑 10 万+ QPS?
一、10 万+ QPS 到底是什么水平?
先简单理解一下 QPS。
QPS,全称是 Queries Per Second,也就是每秒请求数。
如果 Redis 单机能够支撑 10 万 QPS,意味着它每秒可以处理大约 10 万次请求。
在一些常见压测场景中,Redis 的表现大致如下:
GET请求:可以达到 10 万级 QPS;SET请求:也可以达到 10 万级 QPS;INCR这类简单原子操作:同样可以达到非常高的吞吐;- 开启 Pipeline 后,某些简单命令甚至可以冲到几十万、上百万 QPS。
当然,具体性能和机器配置、网络环境、数据大小、命令类型、持久化配置、客户端使用方式都有关系。
但是有一点基本可以确定:
Redis 的高性能,并不是单纯因为它"用内存"。
内存只是基础条件。
真正让 Redis 快起来的,是下面几类能力共同作用的结果:
- 内存存储,避开磁盘随机 IO;
- 高效的数据结构设计;
- 单线程命令执行,减少锁竞争;
- IO 多路复用,高效处理大量连接;
- Pipeline、批量操作、合理编码等工程优化;
- Redis 6.0 之后引入多线程 IO,进一步提升网络读写效率。
下面我们逐个来看。
二、第一点:数据主要在内存中,天然具备高访问速度
Redis 最直观的优势,就是数据主要存放在内存里。
和磁盘相比,内存访问速度快了好几个数量级。
大致可以这样理解:
如果一个请求需要频繁访问磁盘,即使有索引,也可能存在页读取、磁盘寻址、缓冲池命中率等问题。
而 Redis 大部分情况下直接在内存中完成数据读取和修改,所以访问路径非常短。
举个简单例子:
如果要根据用户 ID 查询用户信息。
在 MySQL 中,哪怕走索引,也可能涉及:
- B+ 树索引查找;
- 回表;
- 缓冲池命中或磁盘读取;
- SQL 解析和执行计划;
- 结果集返回。
而 Redis 中,如果用 String 或 Hash 存储用户信息,大致就是:
- 根据 key 计算 hash;
- 在内存哈希表中定位;
- 返回 value。
路径更短,访问更直接。
所以,Redis 快的第一个基础原因是:
它把核心数据放在内存中处理,减少了磁盘 IO 带来的巨大延迟。
但是,只说这一点还不够。
因为如果数据结构设计得不好,即使在内存里,也可能很慢。
三、第二点:Redis 的数据结构不是简单封装,而是深度优化
Redis 之所以好用,是因为它提供了丰富的数据结构。
常见的有:
- String;
- Hash;
- List;
- Set;
- ZSet;
- Bitmap;
- HyperLogLog;
- Stream;
- Geo。
但 Redis 的优秀之处,不只是"支持这些结构",而是它在底层针对不同场景做了很多优化。
同样一个 Hash,在数据量小的时候和数据量大的时候,底层编码可能是不一样的。
同样一个 List,在不同版本中,也会结合压缩结构和链表结构来平衡空间与性能。
这就是 Redis 能兼顾功能丰富和高性能的重要原因。
四、SDS:Redis 为什么不用 C 原生字符串?
Redis 是用 C 语言写的。
但 Redis 并没有直接使用 C 语言原生字符串,而是自己实现了一套字符串结构,叫 SDS。
SDS 全称是 Simple Dynamic String,简单动态字符串。
它大致包含几个核心信息:
ini
structsdshdr{
intlen; // 当前字符串已使用长度
intfree; // 剩余可用空间
charbuf[]; // 真正存储字符串内容的数组
};