Merge pull request #1463 from CyC2018/add-index-log-redis-topics

补充索引失效、MySQL 日志、Redis 编码与集群等内容
This commit is contained in:
CyC2018
2026-10-26 03:49:53 +08:00
committed by GitHub
7 changed files with 300 additions and 1 deletions
+105 -1
View File
@@ -5,6 +5,7 @@
* [B+ Tree 原理](#b-tree-原理)
* [MySQL 索引](#mysql-索引)
* [索引优化](#索引优化)
* [索引失效](#索引失效)
* [索引的优点](#索引的优点)
* [索引的使用条件](#索引的使用条件)
* [二、查询性能优化](#二查询性能优化)
@@ -29,6 +30,10 @@
* [六、复制](#六复制)
* [主从复制](#主从复制)
* [读写分离](#读写分离)
* [七、日志](#七日志)
* [redo log](#redo-log)
* [binlog](#binlog)
* [两阶段提交](#两阶段提交)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
@@ -75,6 +80,8 @@ B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具
索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。
下面按数据结构介绍几种索引。按用途还可以分为主键索引、唯一索引、普通索引、联合索引和前缀索引;按叶子节点中存放的是整行还是主键,分为主索引(聚簇索引)和辅助索引(二级索引),见下文的 B+Tree 索引。
#### 1. B+Tree 索引
是大多数 MySQL 存储引擎的默认索引类型。
@@ -178,6 +185,37 @@ customer_id_selectivity: 0.0373
- 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。
- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。
### 索引失效
索引失效指的是查询条件中有索引列,执行计划却没有使用这个索引,或者只用到了联合索引的一部分。下面在一张 200 万行的订单表 orders 上用 Explain 验证,表上有联合索引 idx_user_status_time(user_id, status, create_time)、单列索引 idx_create_time(create_time) 和 idx_order_no(order_no),其中 order_no 是 VARCHAR 类型:
| 查询条件 | type | 原因 |
| --- | --- | --- |
| `status = 1 AND create_time > '2025-01-20'` | ALL | 没有从联合索引最左边的 user_id 开始 |
| `DATE(create_time) = '2025-01-12'` | ALL | 对索引列使用了函数 |
| `order_no = 7007` | ALL | 字符串列与数字比较,发生了隐式类型转换 |
| `order_no LIKE '%7007'` | ALL | 以 % 开头,无法利用索引的顺序 |
| `user_id = 5 OR amount = 10.99` | ALL | OR 的一侧没有索引,仍然要扫描全表 |
| `user_id != 5` | ALL | 几乎匹配所有行 |
对应的写法:
- 把函数改写为范围:`create_time >= '2025-01-12' AND create_time < '2025-01-13'`,type 变为 range;
- 字符串列用字符串比较:`order_no = '7007'`,type 变为 ref。这是因为 '7007'、' 7007'、'7007a' 转换成数字后都等于 7007,没法按索引的顺序查找;反过来,整数列与字符串比较(`id = '1001'`)仍然走主键,因为被转换的是常量而不是列;
- 只在右边模糊匹配:`LIKE '7007%'`,type 为 range;
- OR 两侧的列都有索引时,可以分别扫描两个索引再合并结果,type 为 index_merge。
联合索引中出现范围条件时,范围条件之后的列不能再用来缩小扫描的范围。Explain 的 key_len 表示用到了索引的前几列,user_id 为 INT,占 4 字节,status 为 TINYINT,占 1 字节,create_time 为 DATETIME,占 5 字节:
```sql
WHERE user_id = 5 AND status = 1 AND create_time > '2025-01-05' -- key_len = 10,三列都用上了
WHERE user_id = 5 AND status > 0 AND create_time > '2025-01-05' -- key_len = 5,只用到前两列
```
不过第二条语句的 Extra 为 Using index condition,也就是索引下推:create_time 虽然不能缩小扫描范围,但仍然在索引上过滤,不满足条件的行不需要回表,所以范围条件之后的列并不是完全没有作用。
需要注意的是,是否使用索引是优化器按成本估算决定的。走二级索引的每一行都要回表,匹配的行越多回表越多,超过一定比例后全表扫描反而更快:`create_time > '2025-01-23'` 匹配 9.9 万行,走 idx_create_time;`create_time > '2025-01-02'` 匹配 191 万行,就选择了全表扫描。IN 也一样,`user_id IN` 5 个值时走索引,3 万多个值时全表扫描。因此同一种写法有时走索引有时不走,可能是因为匹配的数据量不同。
### 索引的优点
- 大大减少了服务器需要扫描的数据行数。
@@ -192,7 +230,11 @@ customer_id_selectivity: 0.0373
- 对于中到大型的表,索引就非常有效;
- 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。
- 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术;
- 索引并不是越多越好:插入、删除数据和修改索引列时,要同时维护每个相关的索引,索引也要占用存储空间。在上面的 orders 表上插入 50 万行,没有二级索引时耗时 2.0 s,有 4 个二级索引时耗时 5.5 s,这 4 个索引占用 73 MB,而数据本身是 89 MB;
- 适合建索引的是经常出现在 WHERE、JOIN、ORDER BY、GROUP BY 中,并且区分度高的列,区分度很低的列(例如只有几种取值的状态)单独建索引的作用不大。
## 二、查询性能优化
@@ -467,6 +509,65 @@ TIMESTAMP 比 DATETIME 少占用 1 字节,但是只能表示到 2038 年,需
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/master-slave-proxy.png" width=""> </div><br>
## 七、日志
InnoDB 执行一次修改时,会写下面三种日志:
| 日志 | 由谁写 | 记录的内容 | 作用 |
| --- | --- | --- | --- |
| undo log | InnoDB | 修改前的旧版本 | 回滚事务,MVCC 读取旧版本 |
| redo log | InnoDB | 对数据页的修改 | 崩溃恢复 |
| binlog | Server 层 | 修改前后的整行(ROW 格式) | 主从复制,恢复到指定时间点 |
undo log 的作用见 [多版本并发控制](数据库系统原理.md#五多版本并发控制)。
### redo log
InnoDB 以 16 KB 的页为单位存储数据,修改数据时先修改内存中缓存的页。如果每次提交都把修改过的页写回磁盘,修改几行数据就要随机写好几个 16 KB 的页。redo log 的做法是:提交时只把对数据页的修改顺序追加到 redo log 并刷盘,数据页之后再由后台线程批量写回。崩溃重启时重放 redo log 中还没有写回数据页的修改,已提交的事务就不会丢失。这种先写日志、后写数据的方式称为预写日志(Write-Ahead Logging, WAL)。
innodb_flush_log_at_trx_commit 控制提交时 redo log 的刷盘方式:
- 1(默认):每次提交都写入并刷盘,已提交的事务不会丢失;
- 2:每次提交都写入操作系统的缓存,每秒刷盘一次,MySQL 进程崩溃不会丢失数据,操作系统崩溃或者断电可能丢失最近 1 秒的事务;
- 0:每秒写入并刷盘一次,MySQL 进程崩溃也可能丢失最近 1 秒的事务。
redo log 的空间是固定的,循环写入:数据页写回磁盘之后,对应的 redo log 就可以被覆盖。MySQL 8.0.30 起由 innodb_redo_log_capacity 控制大小,默认为 100 MB。
### binlog
binlog 由 Server 层记录,与存储引擎无关,用于 [主从复制](#主从复制) 和把数据恢复到指定的时间点。MySQL 8.0 默认使用 ROW 格式,记录每一行修改前后的值。例如执行 UPDATE orders SET status = 3 WHERE id = 1024 后,用 mysqlbinlog 解析 binlog(省略了其它列):
```text
### UPDATE `demo`.`orders`
### WHERE
### @1=1024
### @4=0
### SET
### @1=1024
### @4=3
... Xid = 341
COMMIT
```
redo log 与 binlog 的区别:
- redo log 是 InnoDB 的日志,记录对数据页的修改,空间固定、循环写,用于崩溃恢复;
- binlog 是 Server 层的日志,记录行的修改或者 SQL 语句,追加写、不会覆盖,可以发送给从库,也可以用来把数据恢复到任意时间点。
### 两阶段提交
一个事务既要写 redo log 又要写 binlog,如果只写成功了一个就崩溃,主库和从库的数据就会不一致。例如 redo log 写了而 binlog 没写,主库重启后保留了这次修改,从库却收不到。因此 MySQL 把提交分为两个阶段:
<div align="center"> <img src="pics/mysql-2pc.png" width="720px"/> </div><br>
1. prepare:写入 redo log 并刷盘,事务处于 prepare 状态;
2. 写入 binlog 并刷盘,binlog 中记录了事务的 ID(Xid);
3. commit:在 redo log 中把事务标记为提交。
崩溃重启时,对于处于 prepare 状态的事务,到 binlog 中查找它的 Xid:找到了说明 binlog 已经写完,就提交这个事务;找不到就回滚。这样主库的数据与 binlog 总是一致的,从库按照 binlog 复制,也就与主库一致。
innodb_flush_log_at_trx_commit 和 sync_binlog 的默认值都是 1,也就是每次提交时两种日志都会刷盘,常被称为“双 1”配置。
## 参考资料
- [MySQL 8.0: InnoDB Restrictions and Limitations](https://dev.mysql.com/doc/refman/8.0/en/innodb-restrictions-limitations.html)
@@ -484,3 +585,6 @@ TIMESTAMP 比 DATETIME 少占用 1 字节,但是只能表示到 2038 年,需
- [How Sharding Works](https://medium.com/@jeeyoungk/how-sharding-works-b4dec46b3f6)
- [大众点评订单系统分库分表实践](https://tech.meituan.com/dianping_order_db_sharding.html)
- [B + 树](https://zh.wikipedia.org/wiki/B%2B%E6%A0%91)
- [MySQL 8.0: Type Conversion in Expression Evaluation](https://dev.mysql.com/doc/refman/8.0/en/type-conversion.html)
- [MySQL 8.0: Redo Log](https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html)
- [MySQL 8.0: The Binary Log](https://dev.mysql.com/doc/refman/8.0/en/binary-log.html)
+35
View File
@@ -9,6 +9,7 @@
* [HASH](#hash)
* [ZSET](#zset)
* [三、数据结构](#三数据结构)
* [对象编码](#对象编码)
* [字典](#字典)
* [跳跃表](#跳跃表)
* [四、使用场景](#四使用场景)
@@ -39,6 +40,7 @@
* [主从链](#主从链)
* [十二、Sentinel](#十二sentinel)
* [十三、分片](#十三分片)
* [Redis Cluster](#redis-cluster)
* [十四、一个简单的论坛系统分析](#十四一个简单的论坛系统分析)
* [文章信息](#文章信息)
* [点赞功能](#点赞功能)
@@ -209,6 +211,20 @@ OK
## 三、数据结构
### 对象编码
同一种数据类型会根据元素的数量和大小选择不同的底层结构,称为编码,可以用 OBJECT ENCODING 查看。元素少时使用 listpack,它把所有元素连续地存放在一块内存中,节省内存,查找时虽然要逐个比较,但元素很少时并不慢;元素增多后转换为查找更快的结构。以下是在 Redis 7.4 上用默认配置验证的结果:
| 类型 | 元素少时 | 超过阈值后 | 阈值 |
| --- | --- | --- | --- |
| STRING | int(整数)、embstr | raw | 44 字节 |
| LIST | listpack | quicklist(多个 listpack 组成的链表) | 单个 listpack 超过 8 KB |
| HASH | listpack | hashtable | 512 个字段,或者有值超过 64 字节 |
| SET | intset(全是整数)、listpack | hashtable | 整数 512 个,其它 128 个 |
| ZSET | listpack | skiplist(跳跃表 + 字典) | 128 个成员,或者有成员超过 64 字节 |
例如一个 HASH 有 512 个字段时编码为 listpack,再写入一个字段就变为 hashtable。ZSET 转换后同时使用跳跃表和字典:字典按成员查分数,跳跃表按分数排序和范围查询。
### 字典
dictht 是一个散列表结构,使用拉链法解决哈希冲突。
@@ -570,6 +586,24 @@ Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入
- 代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上。
- 服务器分片:Redis Cluster,使用 CRC16(key) % 16384 计算键所属的哈希槽,再把 16384 个哈希槽分配给各个节点。
### Redis Cluster
Redis 有三种常见的部署方式,解决的问题不同:
- 主从复制:从服务器复制主服务器的数据,分担读请求,主服务器宕机后需要手动切换;
- Sentinel:在主从复制的基础上自动监控和故障转移,但仍然只有一个主服务器负责写入,数据量受单机内存限制;
- Redis Cluster:把数据分散到多个主节点上,每个主节点有自己的从节点,同时解决容量和高可用的问题。
Redis Cluster 把键空间分为 16384 个哈希槽,每个主节点负责一部分槽。下面是在本机创建的 3 主 3 从的集群:
<div align="center"> <img src="pics/redis-cluster.png" width="720px"/> </div><br>
- 客户端向不负责这个槽的节点发送命令时,会收到 MOVED 重定向。例如 user:1001 所在的槽 5712 属于 7002,发给 7001 会收到 MOVED 5712 127.0.0.1:7002。客户端会缓存槽与节点的对应关系,之后直接访问正确的节点;
- 一条命令涉及多个键时,这些键必须在同一个槽中,否则报错 CROSSSLOT,Lua 脚本也是如此。可以用哈希标签(hash tag)让相关的键落到同一个槽:只有 {} 中的部分参与计算,{user:1001}:cart 和 {user:1001}:orders 都在槽 5712;
- 主节点宕机后,它的一个从节点会被选为新的主节点。在本机把 cluster-node-timeout 设为 5 秒,停掉一个主节点后,这部分槽约 9 秒无法写入。
需要注意的是,主从之间是异步复制,主节点宕机前刚写入、还没有复制到从节点的数据会丢失,所以 Redis Cluster 不保证强一致性。
## 十四、一个简单的论坛系统分析
该论坛系统功能如下:
@@ -610,3 +644,4 @@ Redis 没有关系型数据库中的表这一概念来将同种类型的数据
- [Redis 3.0 中文版- 分片](http://wiki.jikexueyuan.com/project/redis-guide)
- [Redis 应用场景](http://www.scienjus.com/redis-use-case/)
- [Using Redis as an LRU cache](https://redis.io/topics/lru-cache)
- [Redis: Scale with Redis Cluster](https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/)
Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

+26
View File
@@ -154,6 +154,30 @@ CDN 主要有以下优点:
- 减少网络通信次数;
- 降低接入成本,使用长连接 / 连接池,NIO 等。
### 大 Key 与热 Key
大 Key 指的是值很大的键,例如几 MB 的字符串,或者包含几十万个元素的集合;热 Key 指的是访问量远高于其它键的键,例如秒杀商品的库存。
大 Key 的问题:
- 阻塞:Redis 用一个线程执行命令,对大 Key 执行 HGETALL、DEL 这类 O(N) 的命令时,其它命令都要等待。在本机删除一个有 100 万个字段、占用 64 MB 内存的 HASH,DEL 耗时 341 ms,而 UNLINK 只需 35 μs,它在后台线程中释放内存;
- 网络:一次读写的数据量大,占用带宽;
- 数据倾斜:在集群中,大 Key 所在节点的内存和流量都会高于其它节点。
大 Key 的发现与处理:
- 用 redis-cli --bigkeys 或者 --memkeys 扫描,它们使用 SCAN 命令遍历,不会阻塞服务器;用 MEMORY USAGE 查看单个键占用的内存;
- 拆分:例如把一个大的 HASH 按字段的哈希值拆成 16 个小 HASH,user:1001:fav:0 到 user:1001:fav:15;
- 删除用 UNLINK;读取集合时用 HSCAN、SSCAN 分批读取,不要一次读出所有元素。
热 Key 的问题是请求集中到同一个节点,集群中其它节点再多也无法分担。处理方法:
- 发现:redis-cli --hotkeys,它要求淘汰策略为 LFU;也可以在客户端或者代理中统计访问频率;
- 本地缓存:在应用中缓存热 Key 的值,大部分请求不再访问 Redis,代价是数据会有短暂的不一致,见上文的 [本地缓存](#本地缓存);
- 复制多份:把同一个值写到 stock#0 到 stock#9 这 10 个键中,读取时随机选一个。这些键分布在不同的槽上,请求就分散到了多个节点,代价是写入时要更新所有副本。
热 Key 过期时大量请求同时打到数据库的问题,见上文的缓存击穿。
## 五、数据分布
### 哈希分布
@@ -342,3 +366,5 @@ public class LRU<K, V> implements Iterable<K> {
- [一致性哈希算法](https://my.oschina.net/jayhu/blog/732849)
- [内容分发网络](https://zh.wikipedia.org/wiki/%E5%85%A7%E5%AE%B9%E5%82%B3%E9%81%9E%E7%B6%B2%E8%B7%AF)
- [How Aspiration CDN helps to improve your website loading speed?](https://www.aspirationhosting.com/aspiration-cdn/)
- [Redis: UNLINK](https://redis.io/docs/latest/commands/unlink/)
- [Redis CLI](https://redis.io/docs/latest/develop/tools/cli/)
+65
View File
@@ -0,0 +1,65 @@
<svg xmlns="http://www.w3.org/2000/svg" width="720" height="300" viewBox="0 0 720 300">
<style>
text { font-family: "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; fill: #666; font-size: 12px; }
.title { font-weight: bold; font-size: 15px; fill: #777; text-anchor: middle; }
.b { font-weight: bold; }
.c { text-anchor: middle; }
.e { text-anchor: end; }
.note { fill: #C0605A; }
.ok { fill: #6A9F68; }
.sub { font-size: 11px; fill: #888; }
.mono { font-family: Menlo, Consolas, monospace; }
.arrow { stroke: #666; stroke-width: 1.3; fill: none; marker-end: url(#a); }
.red { stroke: #C0605A; marker-end: url(#r); }
.green { stroke: #6A9F68; marker-end: url(#g); }
.dash { stroke-dasharray: 5 4; }
.line { stroke: #666; stroke-width: 1.3; fill: none; }
.frame { fill: none; stroke: #999; stroke-width: 1; stroke-dasharray: 4 3; }
.shadow { fill: #DADADA; }
</style>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1 L9,5 L1,9" fill="none" stroke="#666" stroke-width="1.6"/>
</marker>
<marker id="r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1 L9,5 L1,9" fill="none" stroke="#C0605A" stroke-width="1.6"/>
</marker>
<marker id="g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1 L9,5 L1,9" fill="none" stroke="#6A9F68" stroke-width="1.6"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#FFFFFF"/>
<text x="360" y="28" class="title">两阶段提交与崩溃恢复</text>
<rect x="10" y="64" width="124" height="60" rx="6" class="shadow"/>
<rect x="8" y="62" width="124" height="60" rx="6" fill="#E6E6E6"/>
<text x="70" y="90" class="b c">开始事务</text>
<text x="70" y="105" class="sub c">修改内存中的页</text>
<rect x="160" y="64" width="124" height="60" rx="6" class="shadow"/>
<rect x="158" y="62" width="124" height="60" rx="6" fill="#B1DDF0"/>
<text x="220" y="90" class="b c">① prepare</text>
<text x="220" y="105" class="sub c">写 redo log 并刷盘</text>
<rect x="340" y="64" width="124" height="60" rx="6" class="shadow"/>
<rect x="338" y="62" width="124" height="60" rx="6" fill="#B0E3E6"/>
<text x="400" y="90" class="b c">② 写 binlog</text>
<text x="400" y="105" class="sub c">含 Xid,刷盘</text>
<rect x="520" y="64" width="124" height="60" rx="6" class="shadow"/>
<rect x="518" y="62" width="124" height="60" rx="6" fill="#DDD7F6"/>
<text x="580" y="90" class="b c">③ commit</text>
<text x="580" y="105" class="sub c">redo log 标记提交</text>
<line x1="134" y1="92" x2="154" y2="92" class="arrow "/>
<line x1="284" y1="92" x2="334" y2="92" class="arrow "/>
<line x1="464" y1="92" x2="514" y2="92" class="arrow "/>
<line x1="310" y1="132" x2="310" y2="172" stroke="#C0605A" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="310" y="190" class="c note">崩溃 A</text>
<line x1="490" y1="132" x2="490" y2="172" stroke="#C0605A" stroke-width="1.5" stroke-dasharray="4 3"/>
<text x="490" y="190" class="c note">崩溃 B</text>
<rect x="200" y="210" width="220" height="54" rx="4" fill="#FFFFFF" stroke="#C8C8C8"/>
<text x="310" y="232" class="c sub">redo log 处于 prepare,binlog 中没有 Xid</text>
<text x="310" y="252" class="c ok">重启后回滚,主从都没有这次修改</text>
<rect x="430" y="210" width="270" height="54" rx="4" fill="#FFFFFF" stroke="#C8C8C8"/>
<text x="565" y="232" class="c sub">redo log 处于 prepare,binlog 中有 Xid</text>
<text x="565" y="252" class="c ok">重启后提交,从库也会通过 binlog 收到修改</text>
<line x1="310" y1="196" x2="310" y2="206" class="arrow "/>
<line x1="490" y1="196" x2="520" y2="206" class="arrow "/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

+69
View File
@@ -0,0 +1,69 @@
<svg xmlns="http://www.w3.org/2000/svg" width="720" height="340" viewBox="0 0 720 340">
<style>
text { font-family: "PingFang SC", "Microsoft YaHei", Helvetica, Arial, sans-serif; fill: #666; font-size: 12px; }
.title { font-weight: bold; font-size: 15px; fill: #777; text-anchor: middle; }
.b { font-weight: bold; }
.c { text-anchor: middle; }
.e { text-anchor: end; }
.note { fill: #C0605A; }
.ok { fill: #6A9F68; }
.sub { font-size: 11px; fill: #888; }
.mono { font-family: Menlo, Consolas, monospace; }
.arrow { stroke: #666; stroke-width: 1.3; fill: none; marker-end: url(#a); }
.red { stroke: #C0605A; marker-end: url(#r); }
.green { stroke: #6A9F68; marker-end: url(#g); }
.dash { stroke-dasharray: 5 4; }
.line { stroke: #666; stroke-width: 1.3; fill: none; }
.frame { fill: none; stroke: #999; stroke-width: 1; stroke-dasharray: 4 3; }
.shadow { fill: #DADADA; }
</style>
<defs>
<marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1 L9,5 L1,9" fill="none" stroke="#666" stroke-width="1.6"/>
</marker>
<marker id="r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1 L9,5 L1,9" fill="none" stroke="#C0605A" stroke-width="1.6"/>
</marker>
<marker id="g" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M1,1 L9,5 L1,9" fill="none" stroke="#6A9F68" stroke-width="1.6"/>
</marker>
</defs>
<rect width="100%" height="100%" fill="#FFFFFF"/>
<text x="360" y="28" class="title">Redis Cluster:3 主 3 从</text>
<rect x="22" y="54" width="210" height="54" rx="6" class="shadow"/>
<rect x="20" y="52" width="210" height="54" rx="6" fill="#DDD7F6"/>
<text x="125" y="74" class="c b mono">user:1001</text>
<text x="125" y="94" class="c sub mono">CRC16(key) % 16384 = 5712</text>
<rect x="252" y="152" width="140" height="52" rx="6" class="shadow"/>
<rect x="250" y="150" width="140" height="52" rx="6" fill="#B1DDF0"/>
<text x="320" y="174" class="b c">主节点 7001</text>
<text x="320" y="189" class="sub c">槽 0~5460</text>
<rect x="262" y="252" width="120" height="36" rx="6" class="shadow"/>
<rect x="260" y="250" width="120" height="36" rx="6" fill="#E6E6E6"/>
<text x="320" y="272.5" class="b c">从节点</text>
<line x1="320" y1="205" x2="320" y2="246" class="arrow dash"/>
<rect x="402" y="152" width="140" height="52" rx="6" class="shadow"/>
<rect x="400" y="150" width="140" height="52" rx="6" fill="#B1DDF0"/>
<text x="470" y="174" class="b c">主节点 7002</text>
<text x="470" y="189" class="sub c">槽 5461~10922</text>
<rect x="412" y="252" width="120" height="36" rx="6" class="shadow"/>
<rect x="410" y="250" width="120" height="36" rx="6" fill="#E6E6E6"/>
<text x="470" y="272.5" class="b c">从节点</text>
<line x1="470" y1="205" x2="470" y2="246" class="arrow dash"/>
<rect x="552" y="152" width="140" height="52" rx="6" class="shadow"/>
<rect x="550" y="150" width="140" height="52" rx="6" fill="#B1DDF0"/>
<text x="620" y="174" class="b c">主节点 7003</text>
<text x="620" y="189" class="sub c">槽 10923~16383</text>
<rect x="562" y="252" width="120" height="36" rx="6" class="shadow"/>
<rect x="560" y="250" width="120" height="36" rx="6" fill="#E6E6E6"/>
<text x="620" y="272.5" class="b c">从节点</text>
<line x1="620" y1="205" x2="620" y2="246" class="arrow dash"/>
<text x="325" y="236" class="sub">异步复制</text>
<path d="M232,92 C330,96 440,110 470,146" class="arrow"/>
<text x="380" y="104" class="c sub">槽 5712 在 7002</text>
<text x="20" y="128" class="sub note">如果发给 7001:MOVED 5712 127.0.0.1:7002</text>
<path d="M100,136 C100,166 200,176 246,176" class="arrow red dash"/>
<text x="20" y="312" class="sub">{user:1001}:cart 和 {user:1001}:orders 只按 {} 中的部分计算,都在槽 5712,可以在同一条命令或 Lua 脚本中使用</text>
<text x="20" y="330" class="sub">主节点宕机后,从节点被选为新的主节点;本机 cluster-node-timeout 为 5 秒时,这部分槽约 9 秒无法写入</text>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB