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

4 阅读5分钟

很多工程师第一次接触 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 正是因为单线程,才获得了极高性能。

消除了并发问题

在多线程系统中,几乎所有共享数据结构都需要:

  • mutex
  • rwlock
  • CAS
  • 原子变量

而 Redis 完全不需要。

例如:

// 两个客户端同时执行
INCR counter

执行顺序:

Thread model:
Client A -> INCR counter -> result=1
Client B -> INCR counter -> result=2

Redis 天然保证:

  • 原子性
  • 无竞争
  • 无锁

避免上下文切换

多线程系统会产生:

  • 线程调度
  • CPU context switch
  • lock contention

这些都会产生性能损耗。

Redis 的执行流程:

loop {
    read request
    execute command
    write response
}

完全没有线程调度成本。

性能更加稳定

多线程系统常见问题:

  • Lock contention
  • CPU cache invalidation
  • Priority inversion

Redis 的性能更加 ​**可预测(Predictable latency)**​。

单线程为什么还能处理大量连接

如果只有一个线程,Redis 怎么能处理:

  • 几万连接
  • 每秒十万请求

答案是:

I/O 多路复用

I/O 多路复用模型

Redis 使用操作系统提供的机制:

OS机制
Linuxepoll
MacOS / BSDkqueue
旧系统select

工作方式:

┌───────────────┐
Client1  ──────►│               │
Client2  ──────►│   epoll/kqueue │
Client3  ──────►│               │
Client4  ──────►│               │
                └───────┬───────┘
                        │
                Redis Event Loop
                        │
            sequential command execution

流程:

  1. Redis 注册所有 socket
  2. OS 监控 socket 事件
  3. 数据到达时通知 Redis
  4. Redis 读取并执行命令

关键点:

Redis 不是为每个连接创建线程

而是:

一个线程处理所有连接

实际执行过程

例如:

客户端发送请求:

Client A: SET user:1 "John"
Client B: GET user:1
Client C: INCR counter

Redis 执行顺序:

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 counter

Redis 会直接进行 ​整数运算​。而不是字符串解析。

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 Set:Redis 的王牌

Sorted Set 是 Redis 最强大的结构之一。

典型场景:

  • 排行榜
  • 实时排名
  • 延迟任务
  • 优先级队列

示例:

ZADD leaderboard 9500 player1
ZADD leaderboard 8700 player2

查询 Top10:

ZREVRANGE 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 内存

Hash:Redis 的内存优化利器

假设存储用户信息:

方案 1:多个 Key

SET user:1:name John
SET user:1:age 30
SET user:1:email a@b.com

Key 数量:

3 keys

方案 2:Hash

HSET user:1 name John
HSET user:1 age 30
HSET user:1 email a@b.com

Key 数量:

1 key

内部优化:ziplist

当满足条件:

字段 < 512
value < 64 byte

Redis 使用 ​紧凑编码​:

ziplist / listpack

内存节省可达:

60%+

在大规模用户数据系统中非常关键。

总结

Redis 高性能来自于几项核心设计:

设计作用
单线程执行消除锁竞争
I/O 多路复用支持大量连接
高效数据结构降低复杂度
紧凑内存编码节省内存

但这些设计也带来新的问题:

  • 慢命令会阻塞整个实例
  • 持久化可能造成延迟
  • 内存碎片可能膨胀
  • 多命令操作可能产生竞态

因此,在下一篇中我们将继续深入:

  • Redis 持久化机制(RDB / AOF)
  • Lua 脚本为什么存在
  • Redis 内存管理
  • Pipeline 与事务
  • Redis Cluster 架构