Redis 最佳实践

序言

  此文章待重构排版。

优雅的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 memory
  • memory <key_name>

内存缓冲区配置

  内存缓冲区常见的有三种:

  • 复制缓冲区:主从复制的repl_backlog_buf,如果太小可能导致频繁的全量复制, 影响性能。通过repl-backlog-size来设置,默认1mb
  • AOF 缓冲区: AOF 刷盘之前的缓存区域, AOF 执行 rewrite 的缓冲区。 无法设置容量上限
  • 客户端缓冲区:分为输入缓冲区和输出缓冲区, 输入缓冲区最大 1G 且不能设置。 输出缓冲区可以设置

集群差异与选择

对于 Redis 集群的三种不同的架构而言,差异如下图:

架构 解决什么问题 数据是否分片 自动故障转移 主要特点
主从复制 Replication 数据备份、读写分离 最基础
哨兵 Sentinel 主节点故障自动切换 主从 + 自动故障转移
Redis Cluster 数据分片 + 高可用 Redis 官方分布式集群方案

可以把它们理解成一个逐步演进的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
单机 Redis


主从复制
解决:数据备份、读写分离


Sentinel 哨兵
解决:主节点挂了怎么办?


Redis Cluster
解决:数据太多怎么办?
+ 节点挂了怎么办?
+ 如何水平扩展?

主从复制 Replication

这是最基础的一种模式,架构大概是:

1
2
3
4
5
6
7
8
9
10
11
12
       ┌─────────────┐
│ Master │
│ 主节点 │
└──────┬──────┘

数据复制 │
┌───────┴───────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ Slave 1 │ │ Slave 2 │
│ 从节点 │ │ 从节点 │
└──────────┘ └──────────┘

当 Master 执行命令后会通过复制机制把数据同步给 Slave。:

1
SET user:1 xxx

Slave 默认主要承担:

1
2
GET user:1
GET product:100

解决的问题

主要两个:

  • 数据备份:Master 挂了,还有 Slave。
  • 读写分离:降低 Master 的读压力。写SET -> Master,读GET -> Slave,例如:
    1
    2
    3
    4
    5
    6
          Redis Master
    / | \
    / | \
    Slave1 Slave2 Slave3
    ↑ ↑ ↑
    读请求 读请求 读请求

主从复制的问题

最大的问题是:Master 挂了以后,谁来当新的 Master?

例如Master挂了之后,Redis 本身不会自动完成Slave1 → Master

换而言之,主从复制本身,有复制,没有自动故障转移。

这时候就出现了 Sentinel 模式。

Redis Sentinel 哨兵

Sentinel 可以理解为:给 Redis 主从架构增加了一套“监控 + 自动故障转移”机制。

架构:

1
2
3
4
5
6
7
8
9
10
11
12
            Sentinel
/ | \
/ | \
▼ ▼ ▼
Sentinel Sentinel Sentinel

│ 监控

Master
/ \
▼ ▼
Slave1 Slave2

实际生产环境一般至少 3 个 Sentinel

例如:

1
2
3
4
5
6
7
8
9
Sentinel 1
Sentinel 2
Sentinel 3


Redis Master
/ \
↓ ↓
Slave1 Slave2

Sentinel 怎么实现高可用?

假设:

1
2
3
Master
X
挂掉

Sentinel 会发现 Master 不可用,然后多个 Sentinel 进行判断,最终决定:

1
2
3
4
5
6
Master
X

Slave1 ← 提升为 Master

Slave2 ← 继续作为 Slave

然后:

1
2
3
4
5
6
7
8
9
10
     Sentinel

│ 故障转移

Slave1

新 Master
/ \
/ \
Slave2 其他节点

同时 Sentinel 会告诉客户端:原来的 Master 已经挂了,现在新的 Master 是 Slave1。

Sentinel 解决了什么?

所以可以简单记:

1
2
3
4
5
6
7
8
9
10
11
12
13
主从复制:

数据复制

Master
/ \
Slave Slave

但是:

Master 挂了

没人自动接管

Sentinel:

1
2
3
4
5
6
7
    Sentinel
/ | \
/ | \
↓ ↓ ↓
监控 选举 故障转移

Slave → Master

所以:

Sentinel = 主从复制 + 自动故障转移。

Sentinel 问题

注意一个非常重要的问题:Sentinel 并没有解决数据容量问题。

假设:

1
2
Master
100GB

Slave:

1
2
Slave1 100GB
Slave2 100GB

你增加 Slave:

1
2
3
4
Master
100GB
/ \
100GB 100GB

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
2
3
4
5
6
         Redis Cluster

┌──────────┼──────────┐
▼ ▼ ▼
Master1 Master2 Master3
1/3 1/3 1/3

这就是: 数据分片。

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
2
3
4
5
6
7
8
9
key

CRC16

16384

得到 Slot

找到对应 Master

所以:

1
2
3
4
5
user:100

Slot 5000

Master1

而:

1
2
3
4
5
product:100

Slot 8000

Master2

为什么 Cluster 能水平扩展?

这是 Cluster 最核心的价值,假设Master1Master2Master3负责 16384 slots

现在数据越来越多,可以增加Master4,然后重新分配 Slot:

  • Master1 → 一部分 Slot
  • Master2 → 一部分 Slot
  • Master3 → 一部分 Slot
  • Master4 → 一部分 Slot

所以 Redis Cluster 才是真正意义上的:Redis 数据水平扩展。

Redis Cluster 同样支持高可用

Cluster 不仅仅是分片。

它同样可以给 Master 配 Slave。

例如:

1
2
3
4
5
6
7
8
            Redis Cluster

┌─────────────┼─────────────┐
▼ ▼ ▼
Master1 Master2 Master3
│ │ │
▼ ▼ ▼
Slave1 Slave2 Slave3

如果Master1挂掉了,可以:

1
2
3
4
5
Slave1

升级

Master1'

所以 Redis Cluster 同时解决:

1
2
3
4
5
6
7
数据分片
+
高可用
+
故障转移
+
水平扩展

三种架构对比

对比项 主从复制 Sentinel Redis Cluster
数据复制
读写分离
自动故障转移
数据分片
水平扩容
单节点容量限制 可通过分片扩展
架构复杂度
适合场景 简单高可用/读扩展 中小规模高可用 大规模 Redis
Redis 官方分布式集群

生产环境怎么选?

可以简单这么判断。

场景一:数据量不大,只想做简单高可用

例如:

1
2
3
4
5
Redis Master

┌───┴───┐
↓ ↓
Slave Slave

如果对自动切换要求不高:主从复制即可。

场景二:数据量不大,但要求高可用

例如:

1
2
3
4
5
Master
/ \
S1 S2

+ Sentinel

选择 Sentinel 比较典型。

场景三:数据量很大

例如 Redis 数据几十 GB,甚至几百 GB、TB 级

单个 Master 扛不住,则可以使用 Redis Cluster

例如:

1
2
3
4
5
    Redis Cluster

M1 M2 M3
│ │ │
S1 S2 S3

数据:

1
2
3
M1 → 1/3
M2 → 1/3
M3 → 1/3

同时具备高可用。

0%