Redis 很屌,不懂使用规范就糟蹋了
昨天我被公司 Leader 批评了。
我在单身红娘婚恋类型互联网公司工作,在双十一推出下单就送女朋友的活动。
谁曾想,凌晨 12 点之后,用户量暴增,出现了一个技术故障,用户无法下单,当时老大火冒三丈!
经过查找发现 Redis 报 Could not get a resource from the pool
。
获取不到连接资源,并且集群中的单台 Redis 连接量很高。
于是各种更改最大连接数、连接等待数,虽然报错信息频率有所缓解,但还是持续报错。
后来经过线下测试,发现存放 Redis 中的字符数据很大,平均 1s 返回数据。
❝可以分享下使用 Redis 的规范么?我想做一个唯快不破的真男人!
通过 Redis 为什么这么快?这篇文章我们知道 Redis 为了高性能和节省内存费劲心思。
所以,只有规范的使用 Redis,才能实现高性能和节省内存,否则再屌的 Redis 也禁不起我们瞎折腾。
Redis 使用规范围绕如下几个纬度展开:
键值对使用规范;
命令使用规范;
数据保存规范;
运维规范。
键值对使用规范
好的 key
命名,才能提供可读性强、可维护性高的 key,便于定位问题和寻找数据。value
要避免出现bigkey
、选择高效的序列化和压缩、使用对象共享池、选择高效恰当的数据类型(可参考《Redis 实战篇:巧用数据类型实现亿级数据统计》)。
key 命名规范
key
命名,在遇到问题的时候能够方便定位。Redis 属于 没有 Scheme
的 NoSQL
数据库。Scheme
语意,就好比根据不同的场景我们建立不同的数据库。Scheme
),通过「冒号」分隔,再加上「具体业务名」。key
前缀来区分不同的业务数据,清晰明了。set 公众号:技术类:AAA 100000
SDS
,SDS 结构中会包含字符串长度、分配空间大小等元数据信息。不要使用 bigkey
bigkey
的读写操作就会阻塞线程,降低 Redis 的处理效率。bigkey
包含两种情况:键值对的 value
很大,比如value
保存了2MB
的String
数据;键值对的 value
是集合类型,元素很多,比如保存了 5 万个元素的List
集合。
key
和string
类型 value
限制均为512MB
。string
类型控制在10KB
以内,hash、list、set、zset
元素个数不要超过 5000。gzip
数据压缩来减小数据大小:/**
* 使用gzip压缩字符串
*/
public static String compress(String str) {
if (str == null || str.length() == 0) {
return str;
}
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(out)) {
gzip.write(str.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
return new sun.misc.BASE64Encoder().encode(out.toByteArray());
}
/**
* 使用gzip解压缩
*/
public static String uncompress(String compressedStr) {
if (compressedStr == null || compressedStr.length() == 0) {
return compressedStr;
}
byte[] compressed = new sun.misc.BASE64Decoder().decodeBuffer(compressedStr);;
String decompressed = null;
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayInputStream in = new ByteArrayInputStream(compressed);
GZIPInputStream ginzip = new GZIPInputStream(in);) {
byte[] buffer = new byte[1024];
int offset = -1;
while ((offset = ginzip.read(buffer)) != -1) {
out.write(buffer, 0, offset);
}
decompressed = out.toString();
} catch (IOException e) {
e.printStackTrace();
}
return decompressed;
}
使用高效序列化和压缩方法
value
的大小。protostuff
和 kryo
这两种序列化方法,就要比 Java
内置的序列化方法效率更高。JSON
或者 XML
,为了避免数据占用空间大,我们可以使用压缩工具(snappy、 gzip)将数据压缩再存到 Redis 中。使用整数对象共享池
Redis 中设置了 maxmemory
,而且启用了LRU
策略(allkeys-lru 或 volatile-lru 策略
),那么,整数对象共享池就无法使用了。❝这是因为 LRU 需要统计每个键值对的使用时间,如果不同的键值对都复用一个整数对象就无法统计了。 如果集合类型数据采用 ziplist 编码,而集合元素是整数,这个时候,也不能使用共享池。 ❝因为 ziplist 使用了紧凑型内存结构,判断整数对象的共享情况效率低。
命令使用规范
生产禁用的指令
KEYS:该命令需要对 Redis 的全局哈希表进行全表扫描,严重阻塞 Redis 主线程; ❝应该使用 SCAN 来代替,分批返回符合条件的键值对,避免主线程阻塞。 FLUSHALL:删除 Redis 实例上的所有数据,如果数据量很大,会严重阻塞 Redis 主线程; FLUSHDB,删除当前数据库中的数据,如果数据量很大,同样会阻塞 Redis 主线程。 ❝加上 ASYNC 选项,让 FLUSHALL,FLUSHDB 异步执行。
rename-command
命令在配置文件中对这些命令进行重命名,让客户端无法使用这些命令。慎用 MONITOR 命令
慎用全量操作命令
使用 SSCAN、HSCAN
等命令分批返回集合数据;把大集合拆成小集合,比如按照时间、区域等划分。
数据保存规范
冷热数据分离
业务数据隔离
设置过期时间
控制单实例的内存容量
防止缓存雪崩
运维规范
使用 Cluster 集群或者哨兵集群,做到高可用; 实例设置最大连接数,防止过多客户端连接导致实例负载过高,影响性能。 不开启 AOF 或开启 AOF 配置为每秒刷盘,避免磁盘 IO 拖慢 Redis 性能。 设置合理的 repl-backlog,降低主从全量同步的概率 设置合理的 slave client-output-buffer-limit,避免主从复制中断情况发生。 根据实际场景设置合适的内存淘汰策略。 使用连接池操作 Redis。
有道无术,术可成;有术无道,止于术
欢迎大家关注Java之道公众号
好文章,我在看❤️