Redis最佳实践

Redis最佳实践

1.Redis键值设计

1.1优雅的Key结构

Redis的Key虽然可以自定义,但是最好遵循下面的几个最佳实践约定:

  • 遵循基本格式:[业务名称]:[数据名称]:[id]

image-20240323111251493

  • 长度不超过44字节(String的embstr编码,申请内存时只需要调用一次内存分配函数,效率更高,因为Redis中内存是统一jemalloc控制分配的,通常是是2、4、8、16、32、64等,64-16-3-1=44,其中16代表redisObject的长度,3表示sdshdr8的头部长度,1表示字符数组中末尾的\0标识符)

image-20240224173959297

image-20240224103827942

  • 不包含特殊字符(防止出现意外的Bug)

这么做Key的可读性强,方便管理,也能有效避免Key冲突问题。

1.2拒绝BigKey

什么是BigKey

BigKey通常以Key的大小和Key中成员的数量来总和判定:

  • Key本身的数据量过大:一个String类型的Key,它的值为5MB
  • Key中的成员数过多:一个ZSet类型的Key,它的成员数量为10000个
  • Key中成员的数据量过大:一个Hash类型的Key,它的成员数量虽然只有1000个,但是这些成员的Value总大小为100MB

推荐值:

  • 单个Key的Value大小小于10KB
  • 对于集合类型的Key,建议元素数量小于1000

BigKey的危害

  • 网络阻塞

对于BigKey执行读请求时,少量的QPS就可能导致带宽使用率被占满,导致Redis实例乃至所在物理机变慢

  • 数据倾斜

BigKey所在的Redis实例内存使用率远高于其他实例,无法使数据分片的内存资源达到均衡

  • Redis阻塞

对于元素过多的Hash、List、ZSet等做运算耗时较久,使得处理命令的主线程阻塞

  • CPU压力

对BigKey的数据序列化与反序列化会导致CPU的使用率飙高,影响Redis实例和本机其他应用

如何发现BigKey

  • redis-cli –bigkeys

利用redis-cli提供的--bigkeys参数,可以遍历分析所有Key,并返回Key的整体统计信息与每个数据的Top1的bigkey

image-20240323115330016

  • scan扫描

自己编程,利用scan扫描Redis中的所有Key(千万不能使用keys *命令),利用strlen、hlen等命令判断key的长度(此处不建议使用MEMORY USAGE)

image-20240323120114972

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
final static int STR_MAX_LEN = 10 * 1024;
final static int HASH_MAX_LEN = 512;
final static int LIST_MAX_LEN = 1000;
final static int SET_MAX_LEN = 512;
final static int ZSET_MAX_LEN = 128;
@Test
void testScan() {
int maxLen = 0;
long len = 0;
String cursor = "0";
do {
// 扫描部分Key
ScanResult<String> result = jedis.scan(cursor);
// 更新游标
cursor = result.getCursor();
// 遍历Key
List<String> list = result.getResult();
if (list == null || list.isEmpty()) {
break;
}
for (String key : list) {
// 判断Key的类型
String type = jedis.type(key);
switch (type) {
case "string":
len = jedis.strlen(key);
maxLen = STR_MAX_LEN;
break;
case "hash":
len = jedis.hlen(key);
maxLen = HASH_MAX_LEN;
break;
case "list":
len = jedis.llen(key);
maxLen = LIST_MAX_LEN;
break;
case "set":
len = jedis.scard(key);
maxLen = SET_MAX_LEN;
break;
case "zset":
len = jedis.zcard(key);
maxLen = ZSET_MAX_LEN;
break;
}
if (len >= maxLen) {
System.out.printf("Found big key : %s, type : %s, length or size : %d \n", key, type, len);
}
}
} while (!cursor.equals("0"));
}
  • 第三方工具

利用第三方工具,如Redis-Rdb-Tools工具分析RDB快照文件,全面分析内存使用情况

  • 网络监控

自定义工具,监控进出Redis的网络数据,超出预警值时主动告警

如何删除BigKey

BigKey内存占用较多,删除这样的Key需要耗费很长时间,导致Redis主线程阻塞,引发一系列问题。

  • Redis3.0及以下版本

如果是集合类型,则遍历BigKey的元素,先逐个删除子元素,最后删除BigKey

image-20240323125445970

  • Redis4.0及以后版本

Redis在4.0以后提供了异步删除的命令:unlink

image-20240323125503727

1.3恰当的数据类型

image-20240323125711160

image-20240323125932508

image-20240323130115992

image-20240323130159107

Key的最佳实践:

  • 固定格式:[业务名称]:[数据名称]:[id]

  • 足够简短:不超过44字节

  • 不包含特殊字符

Value的最佳实践:

  • 合理的拆分数据,拒绝BigKey
  • 选择合适的数据结构
  • Hash结构的entry数量不要超过512
  • 设置合理的超时时间

2.批处理优化

2.1Pipeline

image-20240323132456167

image-20240323132525065

image-20240323132547986

2.2集群下的批处理

image-20240323134054456

3.服务端优化

3.1持久化配置

Redis的持久化虽然可以保证数据的安全性,但也会带来很多额外的开销,因此持久化请遵循下列建议:

  • 用来作为缓存的Redis实例尽量不要开启持久化功能(安全性较高的业务如分布式锁、库存、流水等放在独立Redis实例中并开启持久化)
  • 建议关闭RDB持久化功能,使用AOF持久化,利用脚本定期在Slave节点做RDB,实现数据备份
  • 设置合理的rewrite阈值即auto-aof-rewrite-min-size,避免频繁的bgrewrite
  • 配置no-appendfsync-on-rewrite=yes,禁止在rewrite期间做AOF,避免因AOF引起的阻塞,但可能存在数据丢失的风险

部署有关建议:

  • Redis实例的物理机需要预留足够内存,应对fork和rewrite
  • 单个Redis实例内存上限不要太大,例如4G或8G,可以加快fork的速度、减少主从同步、数据迁移的压力
  • 不要和高硬盘负载应用一起部署,如数据库、消息队列

3.2慢查询

慢查询的阈值可以通过配置指定:

  • slowlog-log-slower-than:慢查询阈值,单位是微妙,默认是10000
  • slowlog-max-len:慢查询日志的长度,默认是128,建议增大为1000

image-20240323184126506

查看慢查询日志列表:

  • slowlog len:查询慢查询日志长度
  • slowlog get[n]:读取n条慢查询日志
  • slowlog reset:清空慢查询列表

image-20240323165059646

3.3命令及安全配置

Redis会绑定在0.0.0.0:6379,这样将会使得Redis服务暴露到公网上,而Redis如果没有做身份认证,则会出现严重的安全漏洞(SSH免密登录漏洞)。

为了避免这样的漏洞,这里给出一些建议:

  • Redis一定要设置密码

image-20240323172156353

  • 禁止线上使用下面命令:keysflushallflushdbconfig set等命令,可以利用rename-command禁用

image-20240323172316145

  • bind限制网卡,禁止外网网卡访问

image-20240323172015210

  • 开启防火墙,不要使用root账户启动Redis
  • 尽量不要使用默认的6379端口号

image-20240323172412420

3.4内存配置

image-20240323182601207

image-20240323193006107

4.集群最佳实践

4.1集群完整性问题

image-20240323193324340

4.2集群带宽问题

image-20240323194344937

4.3集群还是主从

image-20240323194726369