序言
此文章待重构排版。
优雅的key结构
Redis 的 Key 虽然可以自定义,但最好遵循下面的几个最佳实践约定:
- 遵循基本格式:
[业务名称]:[数据名]:[id] - 长度不超过44字节
- 不包含特殊字符
优点:
- ① 可读性强
- ② 避免key冲突
- ③ 方便管理
- 更节省内存:虽然 key 是 string 类型, 但其底层编码包含 int、embt 和 raw 三种。embst 在小于 44 字节使用,采用连续内存空间,内存占用更小
BigKey
在使用 Redis 时,常常会听到 BigKey 这个名词,那么,什么是 BigKey?为什么尽量需要避免使用 BigKey 呢?又怎么去发现 BigKey 呢?
简介
简单来说,BigKey 通常以 Key 的大小和 Key 中成员的数量来综合判定,例如:
- Key 本身的数据量过大: 一个 String 类型的 Key,它的值为 5 MB
- Key 中的成员数过多: 一个 ZSET 类型的 Key, 它的成员数量为 10000 个
- Key 中成员的数据量过大: 一个 Hash 类型的 Key, 它的成员数量虽然只有 1000 个,但这些成员的Value(值)总大小为 100 MB
推荐值:
- 单个 key 的 value 小于 10KB
- 对于集合类型的 key,建议元素数量小于 1000
危害
- 网络阻塞:对 BigKey 执行读请求时,少量的 QPS 就可能导致带宽使用率被占满,导致Redis实例,乃至所在物理机变慢
- 数据倾斜:若 BigKey 所在的 Redis 节点内存使用率远超其他实例,无法使数据分片的内存资源达到均衡
- 线程阻塞:对元素较多的 hash、list、zset 类型等做运算会耗时较久,使主线程被阻塞
- CPU 压力:对 BigKey 的数据序列化和反序列化会导致 CPU 的使用率飙升,影响 Redis 节点和本机其它应用
如何发现 BigKey?
redis-cli –bigkeys
利用redis-cli提供的--bigkeys参数, 可以遍历分析所有key,并返回 Key 的整体统计信息与每个数据的 Top1 的 bigkey。
缺点是信息不够完整。
scan 扫描
自己编程, 利用 scan 扫描 Redis 中的所有 key, 利用 strlen、hlen 等命令判断key的长度 (此处不建议使用 MEMORY USAGE)
第三方工具
利用第三方工具,如 Redis-Rdb-Tools、rdb_bigkeys 分析 RDB 快照文件, 全面分析内存使用情况。
网络监控
自定义工具, 监控进出Redis的网络数据, 超出预警值时主动告警。
如何删除 BigKey
BigKey 内存占用较多,即便时删除这样的 key 也需要耗费很长时间,导致 Redis 主线程阻塞,引发一系列问题。
另外,需要事先评估删除是否对生产业务存在比较大的影响,否则不建议操作。
Redis 3.0 及以下版本
如果是集合类型,则遍历 BigKey 的元素,先逐个删除子元素,最后删除 BigKey
Redis 4.0 以后
Redis 在 4.0 后提供了异步删除的命令:unlink
如何选择恰当的数据类型?
例1:比如存储一个 User 对象,我们有三种存储方式:
- ① 直接存储 JSON 字符串
- 优点:实现简单粗暴
- 缺点:数据耦合,不够灵活
- ② 将字段打散再存储
- 优点:可以灵活访问对象任意字段
- 缺点:占用空间大、没办法做统一控制
- ③ 使用 hash 结构存储
- 优点:底层使用zipist,空间占用小,可以灵活访问对象的任意字段
- 缺点:代码相对复杂
例2:假如有 hash 类型的 key,其中有 100 万对 field 和 value, field 是自增 id,这个 key 存在什么问题?如何优化?
慢查询
慢查询:在 Redis 执行时耗时超过某个阈值的命令,称为慢查询。
查看
查看慢查询日志相关命令:
slowlog len: 查询慢查询日志长度slowlog get [n]:读取 n 条慢查询日志slowlog reset: 清空慢查询列表
优化
慢查询的阈值可以通过配置指定:
slowlog-log-slower-than: 慢查询阈值,单位是微秒。默认是 10000,建议 1000,通过config set slowlog-log-slower-than 1000设置
慢查询会被放入慢查询日志中, 日志的长度有上限,可以通过配置指定:
slowlog-max-len: 慢查询日志 (本质是一个队列)的长度。默认是128, 建议1000,tong gconfig set slowlog-max-len 1000设置
安全配置
Redis 会绑定在0.0.0.0:6379, 这样将会将 Redis 服务暴露到公网上, 而 Redis 如果没有做身份认证, 会出现严重的安全漏洞.
漏洞出现的核心的原因有以下几点:
- Redis 未设置密码
- Linux 使用了 root 账号权限启动 Redis
- 利用了 Redis 的
config set命令动态修改 Redis 配置
为了避免这样的漏洞,这里给出一些建议:
- ① Redis一定定要设置密码
- ② 禁止线上使用下面命令: keys、flushall、 flushdb、 config set等命令。可以利用
rename-command禁用 - ③ bind:限制网卡,禁止外网网卡访问
- ④ 开启防火墙
- ⑤ 不要使用 root 账户启动 Redis
- ⑥ 尽量不是有默认的端口
内存配置
当 Redis 内存不足时,可能导致 Key 频繁被删除、响应时间变长、QPS 不稳定等问题。当内存使用率达到 90% 以上时就需要我们警惕,并快速定位到内存占用的原因。
| 内存类型 | 说明 |
|---|---|
| 数据内存 | Redis 最主要的部分,存储键值信息。主要问题是 BigKey 问题、内存碎片问题 |
| 进程内存 | Redis 主进程本身运行肯定需要占用内存,如代码、常量池等等;这部分内存大约几兆,在大多数生产环境中与 Redis 数据占用的内存相比可以忽略。 |
| 缓冲区内存 | 一般包括客户端缓冲区、AOF 缓冲区、复制缓冲区等。客户端缓冲区又包括输入缓冲区和输出缓冲区两种。这部分内存占用波动较大,不当使用 BigKey,可能导致内存溢出 |
Redis提供了一些命令,可以查看到Redis目前的内存分配状态:
info memorymemory <key_name>
内存缓冲区配置
内存缓冲区常见的有三种:
- 复制缓冲区:主从复制的
repl_backlog_buf,如果太小可能导致频繁的全量复制, 影响性能。通过repl-backlog-size来设置,默认1mb - AOF 缓冲区: AOF 刷盘之前的缓存区域, AOF 执行 rewrite 的缓冲区。 无法设置容量上限
- 客户端缓冲区:分为输入缓冲区和输出缓冲区, 输入缓冲区最大 1G 且不能设置。 输出缓冲区可以设置
集群差异与选择
对于 Redis 集群的三种不同的架构而言,差异如下图:
| 架构 | 解决什么问题 | 数据是否分片 | 自动故障转移 | 主要特点 |
|---|---|---|---|---|
| 主从复制 Replication | 数据备份、读写分离 | ❌ | ❌ | 最基础 |
| 哨兵 Sentinel | 主节点故障自动切换 | ❌ | ✅ | 主从 + 自动故障转移 |
| Redis Cluster | 数据分片 + 高可用 | ✅ | ✅ | Redis 官方分布式集群方案 |
可以把它们理解成一个逐步演进的过程:
1 | 单机 Redis |
主从复制 Replication
这是最基础的一种模式,架构大概是:
1 | ┌─────────────┐ |
当 Master 执行命令后会通过复制机制把数据同步给 Slave。:
1 | SET user:1 xxx |
Slave 默认主要承担:
1 | GET user:1 |
解决的问题
主要两个:
- 数据备份:Master 挂了,还有 Slave。
- 读写分离:降低 Master 的读压力。写
SET -> Master,读GET -> Slave,例如:1
2
3
4
5
6Redis Master
/ | \
/ | \
Slave1 Slave2 Slave3
↑ ↑ ↑
读请求 读请求 读请求
主从复制的问题
最大的问题是:Master 挂了以后,谁来当新的 Master?
例如Master挂了之后,Redis 本身不会自动完成Slave1 → Master:
换而言之,主从复制本身,有复制,没有自动故障转移。
这时候就出现了 Sentinel 模式。
Redis Sentinel 哨兵
Sentinel 可以理解为:给 Redis 主从架构增加了一套“监控 + 自动故障转移”机制。
架构:
1 | Sentinel |
实际生产环境一般至少 3 个 Sentinel
例如:
1 | Sentinel 1 |
Sentinel 怎么实现高可用?
假设:
1 | Master |
Sentinel 会发现 Master 不可用,然后多个 Sentinel 进行判断,最终决定:
1 | Master |
然后:
1 | Sentinel |
同时 Sentinel 会告诉客户端:原来的 Master 已经挂了,现在新的 Master 是 Slave1。
Sentinel 解决了什么?
所以可以简单记:
1 | 主从复制: |
Sentinel:
1 | Sentinel |
所以:
Sentinel = 主从复制 + 自动故障转移。
Sentinel 问题
注意一个非常重要的问题:Sentinel 并没有解决数据容量问题。
假设:
1 | Master |
Slave:
1 | Slave1 100GB |
你增加 Slave:
1 | Master |
Redis 整体的数据容量仍然是:
1 | 100GB |
因为每一个节点都是完整数据。
你不能因为增加了 10 个 Slave:
1 | 10 × 100GB = 1TB |
就认为 Redis 可以存 1TB。
实际上仍然是:
1 | 100GB |
Slave 主要解决的是:高可用 + 读扩展,而不是:数据容量水平扩展。
Redis Cluster
这时候就需要 Redis Cluster,Redis Cluster 的核心思想是:把整个 Redis 数据拆分到多个节点上。
数据分片
例如有 3 个 Master,Master1、Master2、Master3,Redis 存放的数据,并不是全部放在 Master1,而是尽量均衡:
1 | Redis Cluster |
这就是: 数据分片。
Redis Cluster 怎么分片?
Redis Cluster 使用: 16384 个 Hash Slot(哈希槽)
可以理解成0 ~ 16383一共:16384 个槽,例如:
- Master1 负责:0 ~ 5460
- Master2 负责:5461 ~ 10922
- Master3 负责:10923 ~ 16383
当执行:
1 | SET user:100 xxx |
Redis 会计算:
1 | key |
所以:
1 | user:100 |
而:
1 | product:100 |
为什么 Cluster 能水平扩展?
这是 Cluster 最核心的价值,假设Master1、Master2、Master3负责 16384 slots
现在数据越来越多,可以增加Master4,然后重新分配 Slot:
- Master1 → 一部分 Slot
- Master2 → 一部分 Slot
- Master3 → 一部分 Slot
- Master4 → 一部分 Slot
所以 Redis Cluster 才是真正意义上的:Redis 数据水平扩展。
Redis Cluster 同样支持高可用
Cluster 不仅仅是分片。
它同样可以给 Master 配 Slave。
例如:
1 | Redis Cluster |
如果Master1挂掉了,可以:
1 | Slave1 |
所以 Redis Cluster 同时解决:
1 | 数据分片 |
三种架构对比
| 对比项 | 主从复制 | Sentinel | Redis Cluster |
|---|---|---|---|
| 数据复制 | ✅ | ✅ | ✅ |
| 读写分离 | ✅ | ✅ | ✅ |
| 自动故障转移 | ❌ | ✅ | ✅ |
| 数据分片 | ❌ | ❌ | ✅ |
| 水平扩容 | ❌ | ❌ | ✅ |
| 单节点容量限制 | 有 | 有 | 可通过分片扩展 |
| 架构复杂度 | 低 | 中 | 高 |
| 适合场景 | 简单高可用/读扩展 | 中小规模高可用 | 大规模 Redis |
| Redis 官方分布式集群 | ❌ | ❌ | ✅ |
生产环境怎么选?
可以简单这么判断。
场景一:数据量不大,只想做简单高可用
例如:
1 | Redis Master |
如果对自动切换要求不高:主从复制即可。
场景二:数据量不大,但要求高可用
例如:
1 | Master |
选择 Sentinel 比较典型。
场景三:数据量很大
例如 Redis 数据几十 GB,甚至几百 GB、TB 级
单个 Master 扛不住,则可以使用 Redis Cluster
例如:
1 | Redis Cluster |
数据:
1 | M1 → 1/3 |
同时具备高可用。