艾体宝干货|【Redis实用技巧#15】架构解析(上):为什么单线程的 Redis 依然可以跑到 10 万 QPS

张开发
2026/5/22 2:59:13 15 分钟阅读
艾体宝干货|【Redis实用技巧#15】架构解析(上):为什么单线程的 Redis 依然可以跑到 10 万 QPS
很多工程师第一次接触 Redis 时都会产生一个误解Redis 内存 Key-Value 缓存但当系统规模逐渐扩大之后你会发现 Redis 的行为远远不止“缓存”这么简单。例如排行榜系统突然变慢分布式锁偶尔出现双持锁限流器偶尔放过超额请求Redis 实例周期性出现延迟尖刺这些问题的根源几乎都与Redis 架构设计有关。Redis 并不是简单地因为“在内存里”而快而是依赖一套非常克制且精妙的架构设计单线程执行模型I/O 多路复用高效的数据结构实现特殊的持久化机制Lua 原子执行理解这些设计之后你会发现Redis 本质是一个 ​**高性能数据结构服务器Data Structure Server**​。本文将从架构层面拆解 Redis 为什么可以如此高性能。Redis 架构总览Redis 的核心执行模型非常简单Client Connections │ │ I/O Multiplexing (epoll / kqueue / select) │ │ Command Processing (Single Thread) │ ┌──────────────┼───────────────┐ │ │ │ Data Structures Persistence Replication (Memory Store) (RDB / AOF) (Replica)关键特点模块职责I/O 多路复用同时处理成千上万连接单线程执行顺序执行命令数据结构层高效内存存储持久化RDB / AOF集群横向扩展Redis 的性能优势来自 ​架构整体设计而不是某一个点优化​。Redis 为什么选择单线程Redis 最令人困惑的一点Redis 的命令执行是单线程在多核 CPU 普及的今天这看起来像是反模式。但实际上Redis 正是因为单线程才获得了极高性能。消除了并发问题在多线程系统中几乎所有共享数据结构都需要mutexrwlockCAS原子变量而 Redis 完全不需要。例如// 两个客户端同时执行 INCR counter执行顺序Thread model: Client A - INCR counter - result1 Client B - INCR counter - result2Redis 天然保证原子性无竞争无锁避免上下文切换多线程系统会产生线程调度CPU context switchlock contention这些都会产生性能损耗。Redis 的执行流程loop { read request execute command write response }完全没有线程调度成本。性能更加稳定多线程系统常见问题Lock contentionCPU cache invalidationPriority inversionRedis 的性能更加 ​**可预测Predictable latency**​。单线程为什么还能处理大量连接如果只有一个线程Redis 怎么能处理几万连接每秒十万请求答案是I/O 多路复用I/O 多路复用模型Redis 使用操作系统提供的机制OS机制LinuxepollMacOS / BSDkqueue旧系统select工作方式┌───────────────┐ Client1 ──────►│ │ Client2 ──────►│ epoll/kqueue │ Client3 ──────►│ │ Client4 ──────►│ │ └───────┬───────┘ │ Redis Event Loop │ sequential command execution流程Redis 注册所有 socketOS 监控 socket 事件数据到达时通知 RedisRedis 读取并执行命令关键点Redis不是为每个连接创建线程而是一个线程处理所有连接实际执行过程例如客户端发送请求Client A: SET user:1 John Client B: GET user:1 Client C: INCR counterRedis 执行顺序1 SET user:1 John 2 GET user:1 3 INCR counter假设每个操作耗时SET ≈ 500ns GET ≈ 300ns INCR ≈ 400ns总耗时1.2 microseconds因此 ​客户端几乎同时收到响应​。Redis 的真正核心数据结构设计Redis 的名字其实就说明了一切REmote DIctionary Server它本质是一个 ​数据结构服务器​。String并不只是字符串Redis String二进制安全最大 512MB但内部实现有多种编码类型编码整数int短字符串embstr长字符串raw示例SET counter 42 INCR counterRedis 会直接进行 ​整数运算​。而不是字符串解析。List双端队列Redis List 支持队列栈消息队列示例队列LPUSH queue task1 LPUSH queue task2 RPOP queue执行顺序task1 task2栈LPUSH stack a LPUSH stack b LPOP stack结果b复杂度操作复杂度LPUSHO(1)RPOPO(1)LINDEXO(N)陷阱访问中间元素LINDEX list 50000时间复杂度O(N)在大列表中会明显变慢。Sorted SetRedis 的王牌Sorted Set 是 Redis 最强大的结构之一。典型场景排行榜实时排名延迟任务优先级队列示例ZADD leaderboard 9500 player1 ZADD leaderboard 8700 player2查询 Top10ZREVRANGE leaderboard 0 9查询排名ZREVRANK leaderboard player1内部结构Sorted Set 使用hash table skip list Skip List (按 score 排序) │ │ Hash Table (member - score)优势操作复杂度插入O(logN)删除O(logN)排名O(logN)查分数O(1)因此非常适合百万级排行榜系统代价是什么Sorted Set ​内存占用较高​。因为每个成员需要存两份skip list node hash entry例如1000 万排行榜 ≈ 500MB 内存HashRedis 的内存优化利器假设存储用户信息方案 1多个 KeySET user:1:name John SET user:1:age 30 SET user:1:email ab.comKey 数量3 keys方案 2HashHSET user:1 name John HSET user:1 age 30 HSET user:1 email ab.comKey 数量1 key内部优化ziplist当满足条件字段 512 value 64 byteRedis 使用 ​紧凑编码​ziplist / listpack内存节省可达60%在大规模用户数据系统中非常关键。总结Redis 高性能来自于几项核心设计设计作用单线程执行消除锁竞争I/O 多路复用支持大量连接高效数据结构降低复杂度紧凑内存编码节省内存但这些设计也带来新的问题慢命令会阻塞整个实例持久化可能造成延迟内存碎片可能膨胀多命令操作可能产生竞态因此在下一篇中我们将继续深入Redis 持久化机制RDB / AOFLua 脚本为什么存在Redis 内存管理Pipeline 与事务Redis Cluster 架构

更多文章