单线程模型:为什么Redis单线程却这么快?

引言 Redis是单线程的,却能达到10万+QPS,这听起来很矛盾。多线程不是更快吗?为什么Redis坚持单线程设计?单线程如何实现如此高的性能? 今天我们深入Redis的单线程模型,揭秘高性能背后的设计哲学。 一、Redis真的是单线程吗? 1.1 核心工作线程确实是单线程 准确的说法: Redis的核心数据处理逻辑是单线程的(主线程处理所有客户端请求) 客户端1 \ 客户端2 → [主线程] → 串行执行命令 客户端3 / 单线程处理的内容: 接收客户端连接 读取请求命令 执行命令(操作数据结构) 返回响应 处理定时任务 1.2 但Redis不是完全单线程 Redis 4.0+引入多线程(后台线程): 版本 多线程功能 用途 Redis 4.0+ 后台异步删除线程(unlink、flushdb async) 避免删除大key阻塞 Redis 4.0+ AOF重写线程 后台重写AOF文件 Redis 6.0+ I/O多线程 多线程读取请求、发送响应(数据处理仍是单线程) 关键点: 数据操作:仍然是单线程(避免锁的开销) I/O操作:Redis 6.0+支持多线程(提高网络吞吐) 后台任务:多线程(避免阻塞主线程) 二、为什么选择单线程? 2.1 多线程的问题 问题1:锁的开销 // 多线程环境 void increment_counter() { pthread_mutex_lock(&mutex); // 加锁,耗时约25ns counter++; // 操作,耗时1ns pthread_mutex_unlock(&mutex); // 解锁,耗时25ns } // 总耗时:50ns(锁开销占98%) // 单线程环境 void increment_counter() { counter++; // 直接操作,1ns } // 无锁开销! 结论:对于内存操作(纳秒级),锁的开销反而成为瓶颈。 ...

2025-01-21 · maneng

Pipeline批量操作:性能优化的利器

引言 恭喜你!这是第一阶段的最后一篇文章。 前面我们学习了Redis的所有基础知识: ✅ 5大数据类型 ✅ 过期和淘汰 ✅ 持久化 ✅ 事务 今天我们学习一个性能优化神器:Pipeline(管道)。 一个真实的案例: 需求:批量获取10000个用户信息 方式1:循环GET,耗时10秒 方式2:MGET,耗时0.5秒 方式3:Pipeline,耗时0.3秒 性能提升:30倍! 一、RTT延迟问题 1.1 什么是RTT? RTT(Round-Trip Time):往返时间,从客户端发送请求到收到响应的时间。 客户端 Redis服务器 | | | ----发送命令----> | 1ms | | | <----返回结果---- | 1ms | | RTT = 2ms 实际测量: # 本地Redis $ redis-cli --latency min: 0.05, max: 2, avg: 0.12 (ms) # 同机房Redis RTT ≈ 0.5-1ms # 跨机房Redis RTT ≈ 5-10ms # 跨地域Redis RTT ≈ 50-100ms 1.2 RTT的影响 单条命令的性能: ...

2025-01-21 · maneng

缓存的第一性原理:为什么需要Redis?

引言 在开始学习Redis之前,我们先不谈具体的命令和用法,而是回到原点思考一个问题:为什么我们需要Redis? 这不是一个简单的问题。如果只是回答"因为它快"或"因为大家都在用",那就失去了深入理解的机会。让我们从第一性原理出发,理解缓存的本质,以及Redis在现代架构中的真正价值。 一、存储的时空矛盾 1.1 计算机存储的金字塔 计算机系统中存在一个永恒的矛盾:速度快的存储容量小且昂贵,容量大的存储速度慢且廉价。 让我们看看存储层次结构(从快到慢): CPU寄存器 ← 1ns ← 几KB ← 最快最贵 L1 Cache ← 1-2ns ← 几十KB ← L2 Cache ← 4-10ns ← 几百KB ← L3 Cache ← 20-40ns ← 几MB ← 内存(RAM) ← 100ns ← 几GB ← Redis在这里 SSD硬盘 ← 50-150μs ← 几百GB ← 机械硬盘 ← 5-10ms ← 几TB ← 网络存储 ← >10ms ← 无限大 ← 数据库在这里 关键观察: CPU寄存器访问需要 1纳秒 内存访问需要 100纳秒(慢100倍) SSD访问需要 100微秒(慢1000倍) 机械硬盘访问需要 10毫秒(慢100,000倍) 网络数据库访问需要 >10毫秒(慢100,000倍以上) 1.2 真实世界的类比 如果把CPU访问寄存器比作1秒,那么: ...

2025-01-21 · maneng

InnoDB架构综合实战:从原理到优化

InnoDB架构回顾 InnoDB架构(5层) ┌────────────────────────────────────┐ │ 1. 内存结构 │ │ ├─ Buffer Pool(最大) │ │ ├─ Change Buffer │ │ ├─ Adaptive Hash Index │ │ └─ Log Buffer │ ├────────────────────────────────────┤ │ 2. 后台线程 │ │ ├─ Master Thread │ │ ├─ IO Thread │ │ ├─ Page Cleaner Thread │ │ └─ Purge Thread │ ├────────────────────────────────────┤ │ 3. 磁盘结构 │ │ ├─ 表空间(Tablespace) │ │ ├─ redo log(持久性) │ │ ├─ undo log(原子性+MVCC) │ │ └─ binlog(复制) │ ├────────────────────────────────────┤ │ 4. 存储结构 │ │ └─ 表空间→段→区(1MB)→页(16KB)→行 │ ├────────────────────────────────────┤ │ 5. 锁机制 │ │ ├─ 表锁、行锁 │ │ ├─ Gap Lock、Next-Key Lock │ │ └─ MVCC │ └────────────────────────────────────┘ 实战案例1:Buffer Pool命中率优化 问题 生产环境查询慢,QPS从5000降到2000。 ...

2025-01-15 · maneng

查询优化器:生成最优执行计划的艺术

查询优化器概述 查询优化器(Optimizer) 的作用是为SQL生成最优执行计划。 -- 原始SQL SELECT * FROM orders o JOIN users u ON o.user_id = u.id WHERE u.name = 'Alice' AND o.status = 'PAID'; -- 优化器决策 1. 表连接顺序:先users还是orders? 2. 索引选择:使用哪个索引? 3. 连接算法:Nested Loop、Hash Join、Sort-Merge Join? 4. 条件下推:WHERE条件何时过滤? 优化器类型 1. 基于规则的优化器(RBO) Rule-Based Optimizer:根据预定义规则选择执行计划。 -- 规则示例 1. 有索引优于无索引 2. 主键索引优于二级索引 3. 小表驱动大表 -- 缺点: 不考虑数据分布,可能选择非最优计划 2. 基于成本的优化器(CBO) Cost-Based Optimizer(MySQL使用):根据成本评估选择最优计划。 -- 成本因素 1. IO成本:磁盘读取次数 2. CPU成本:记录比较次数 3. 内存成本:临时表、排序开销 -- 优点: 考虑数据分布,选择更优计划 成本评估 1. 成本模型 总成本 = IO成本 + CPU成本 IO成本 = 页数 × IO_BLOCK_READ_COST CPU成本 = 行数 × ROW_EVALUATE_COST -- 默认值(可调整) IO_BLOCK_READ_COST = 1.0 ROW_EVALUATE_COST = 0.1 2. 索引扫描成本 -- 全表扫描(Table Scan) 成本 = 表页数 × 1.0 + 表行数 × 0.1 -- 索引扫描(Index Scan) 成本 = 索引页数 × 1.0 + 回表行数 × 0.1 + 回表IO × 1.0 -- 示例 表:100万行,10000页 索引:1000页,回表10万行 全表扫描成本 = 10000 × 1.0 + 1000000 × 0.1 = 110000 索引扫描成本 = 1000 × 1.0 + 100000 × 0.1 + 100000 × 1.0 = 111000 -- 优化器选择:全表扫描(成本更低) 优化策略 1. 条件化简 -- 原始条件 WHERE id > 0 AND id < 100 AND id = 50 -- 化简后 WHERE id = 50 -- 原始条件 WHERE (status = 'PAID' OR status = 'PAID') AND user_id = 1 -- 化简后 WHERE status = 'PAID' AND user_id = 1 2. 常量折叠 -- 原始 WHERE price > 100 * 2 -- 优化后 WHERE price > 200 -- 原始 WHERE YEAR(created_at) = 2025 -- 无法优化(函数作用于列,索引失效) 3. 谓词下推(Predicate Pushdown) -- 原始SQL SELECT * FROM orders o JOIN users u ON o.user_id = u.id WHERE u.name = 'Alice'; -- 优化后(谓词下推) SELECT * FROM orders o JOIN (SELECT * FROM users WHERE name = 'Alice') u ON o.user_id = u.id; -- WHERE条件先过滤users表,减少JOIN的数据量 4. 外连接消除 -- 原始SQL SELECT * FROM orders o LEFT JOIN users u ON o.user_id = u.id WHERE u.id IS NOT NULL; -- 优化后(LEFT JOIN → INNER JOIN) SELECT * FROM orders o JOIN users u ON o.user_id = u.id; -- WHERE条件保证u.id不为NULL,可以消除LEFT JOIN 表连接顺序 1. 小表驱动大表 -- 示例 users表:100行 orders表:100万行 -- ✅ 好:users驱动orders FOR EACH user IN users (100次) FOR EACH order IN orders WHERE order.user_id = user.id END FOR -- 总循环次数:100次(users) × 平均100次(orders) = 10000次 -- ❌ 不好:orders驱动users FOR EACH order IN orders (100万次) FOR EACH user IN users WHERE user.id = order.user_id END FOR -- 总循环次数:100万次(效率低) 2. 连接算法 Nested Loop Join(嵌套循环): ...

2025-01-15 · maneng

Adaptive Hash Index:自适应哈希索引

Adaptive Hash Index概述 Adaptive Hash Index(AHI,自适应哈希索引) 是InnoDB的自动优化机制,在Buffer Pool上层构建哈希索引,加速等值查询。 查询流程对比: 传统B+树查询: SELECT * FROM users WHERE id = 100; → B+树查找(3-4次页访问) AHI查询: SELECT * FROM users WHERE id = 100; → Hash查找(1次内存访问) AHI的工作原理 1. 自动构建 -- InnoDB监控查询模式 SELECT * FROM users WHERE id = 1; -- 第1次 SELECT * FROM users WHERE id = 2; -- 第2次 SELECT * FROM users WHERE id = 3; -- 第3次 ... SELECT * FROM users WHERE id = 100; -- 第N次 -- 当检测到等值查询频繁访问同一索引前缀 -- InnoDB自动构建哈希索引 AHI[100] → Buffer Pool页地址 2. 哈希索引结构 B+树索引(磁盘) 10 / \ 5 15 / \ / \ 1 7 12 17 → 数据页 AHI(内存) Hash表: id=1 → 页地址0x1000 id=5 → 页地址0x1001 id=7 → 页地址0x1002 id=10 → 页地址0x1003 ... 3. 查询流程 -- 查询 SELECT * FROM users WHERE id = 100; -- 流程 1. 检查AHI是否有id=100的条目 2. ✅ 有:直接通过Hash查找,获取页地址(O(1)) 3. ❌ 没有:走B+树查找(O(log N)) AHI的优势 1. 加速等值查询 -- 等值查询(适合AHI) SELECT * FROM users WHERE id = 100; -- ✅ AHI加速 SELECT * FROM orders WHERE order_id = 1001; -- ✅ AHI加速 -- B+树查询:3-4次页访问(假设树高3) -- AHI查询:1次Hash查找(快3-4倍) 2. 减少CPU开销 -- B+树查找需要: 1. 二分查找(多次比较) 2. 页间跳转(多次指针访问) 3. 最终定位数据行 -- AHI查找需要: 1. Hash计算(一次) 2. 直接获取页地址(一次) AHI的限制 1. 只适用于等值查询 -- ✅ 适用 SELECT * FROM users WHERE id = 100; SELECT * FROM users WHERE id IN (1, 2, 3); -- ❌ 不适用 SELECT * FROM users WHERE id > 100; -- 范围查询 SELECT * FROM users WHERE id BETWEEN 1 AND 100; -- 范围查询 SELECT * FROM users WHERE name LIKE 'A%'; -- 模糊查询 2. 只针对热点数据 -- AHI只缓存频繁访问的索引前缀 -- 例如:id=100访问了1000次 → 自动加入AHI -- 而:id=999999只访问1次 → 不会加入AHI 3. 有维护开销 -- 更新/删除数据时,需要维护AHI UPDATE users SET name = 'Alice' WHERE id = 100; -- 流程: 1. 更新B+树索引 2. 更新Buffer Pool 3. 更新AHI(如果id=100在AHI中) AHI配置 1. 查看状态 -- 查看AHI开关 SHOW VARIABLES LIKE 'innodb_adaptive_hash_index'; -- ON(默认开启) -- 查看AHI分区数(MySQL 5.7+) SHOW VARIABLES LIKE 'innodb_adaptive_hash_index_parts'; -- 8(默认,减少锁竞争) 2. 开关配置 -- 关闭AHI(不推荐) SET GLOBAL innodb_adaptive_hash_index = OFF; -- 开启AHI(默认) SET GLOBAL innodb_adaptive_hash_index = ON; 注意:动态修改需要重建AHI,会短暂影响性能。 ...

2025-01-15 · maneng

Buffer Pool:MySQL的内存管理核心

Buffer Pool概述 Buffer Pool(缓冲池) 是InnoDB最重要的内存结构,用于缓存数据页和索引页。 ┌──────────────────────────────────────┐ │ MySQL内存结构 │ ├──────────────────────────────────────┤ │ Buffer Pool(最大,默认128MB) │ 缓存数据页 ├──────────────────────────────────────┤ │ Change Buffer │ 缓存写操作 ├──────────────────────────────────────┤ │ Adaptive Hash Index │ 自适应哈希索引 ├──────────────────────────────────────┤ │ Log Buffer │ 缓存redo log └──────────────────────────────────────┘ Buffer Pool的作用 1. 减少磁盘IO -- 无Buffer Pool(每次查询都访问磁盘) SELECT * FROM users WHERE id = 1; -- 磁盘IO:10ms -- 有Buffer Pool(缓存命中,直接读内存) SELECT * FROM users WHERE id = 1; -- 内存读取:0.01ms(快1000倍) 2. 提升并发性能 -- 多个事务并发读取同一数据页 事务A: SELECT * FROM users WHERE id = 1; -- 加载到Buffer Pool 事务B: SELECT * FROM users WHERE id = 1; -- 直接从Buffer Pool读取(无磁盘IO) 事务C: SELECT * FROM users WHERE id = 1; -- 直接从Buffer Pool读取 Buffer Pool的结构 1. 缓冲页(Buffer Page) 缓冲页是Buffer Pool的基本单位,与磁盘页一一对应。 ...

2025-01-15 · maneng

如约数科科技工作室

浙ICP备2025203501号

👀 本站总访问量 ...| 👤 访客数 ...| 📅 今日访问 ...