键值设计
key 结构设计
Redis 中对 key 并未规定格式,可以任意设置。key 结构设计应当具有可读性强、避免冲突的特点,最佳实践约定如下设计规范:
- 遵循基本格式
<用途>:<业务名称>:<数据名>:<id>:例如biz:login:user:123,此处biz表示业务数据,还有lock用于分布式锁、cache用于分布式缓存等,login为业务名称,user为数据名,123为数据id。 - 不使用特殊字符
- 长度不超过44字节:key 为 string 类型,不超过44字节会使用
embstr编码字符串
value 类型选用
例1:存储一个User对象。
方案一:整个对象转 JSON 字符串,统一存储
一个值为 string 类型的键值对
user:1 => {"name": "Jack", "age": 21}
优点:实现简单粗暴
缺点:数据耦合,不够灵活
方案二:字段打散,分别存储
两个值为 string 类型的键值对
user:1:name => Jack
user:1:age => 21
优点:可以灵活访问对象任意字段
缺点:占用空间大、没办法做统一控制
方案三:hash
一个值为 hash 类型的键值对
user:1 => name: jack, age: 21
优点:底层使用ziplist,空间占用小,可以灵活访问对象的任意字段
缺点:代码相对复杂
上述三个方案,站在 Redis 应用角度上分析,hash 是最合适的
例2:百万键值对常驻内存
方案一:一个 value 为 hash 类型的键值对
优点:结构简单,可直接访问相关字段
缺点:单个 value 内存占用大,包含元素过多
方案二:拆分为百万 value 为 string 类型的键值对
优点:简短粗暴,可访问任意字段
缺点:string结构底层没有太多内存优化,内存占用较多,且批量获取数据比较麻烦
方案三:分组拆分,拆分为小的 hash,将 id / 100 作为 key,将 id % 100 作为 field,这样每组100个元素构成一个hash,hash 元素在500个以内使用 ZipList ,内存占用相对较小
优点:内存占用小
缺点:代码逻辑复杂
批处理
此处批处理用以节约网络传输耗时。一次命令的响应时间 = 1次往返的网络传输耗时 + 1次Redis执行命令耗时,则 N次命令的响应时间 = N次往返的网络传输耗时 + N次Redis执行命令耗时,若将N次命令打包,则只需一次网络传输,此时N次命令的响应时间 = 1次往返的网络传输耗时 + N次Redis执行命令耗时。
批处理命令
Redis提供了很多Mxxx这样的命令,可以实现批量插入数据,例如:
mset
hmset
利用mset批量插入10万条数据:
@Test
void testMxx() {
String[] arr = new String[2000];
int j;
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
j = (i % 1000) << 1;
arr[j] = "test:key_" + i;
arr[j + 1] = "value_" + i;
if (j == 0) {
jedis.mset(arr);
}
}
long e = System.currentTimeMillis();
System.out.println("time: " + (e - b));
}
pipeline
MSET虽然可以批处理,但是却只能操作部分数据类型,因此如果有对复杂数据类型的批处理需要,建议使用Pipeline功能:
@Test
void testPipeline() {
// 创建管道
Pipeline pipeline = jedis.pipelined();
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
// 放入命令到管道
pipeline.set("test:key_" + i, "value_" + i);
if (i % 1000 == 0) {
// 每放入1000条命令,批量执行
pipeline.sync();
}
}
long e = System.currentTimeMillis();
System.out.println("time: " + (e - b));
}
注意事项
- 批处理时不建议一次携带太多命令,否则单次命令占用带宽过多,会导致网络阻塞
- Pipeline的多个命令之间不具备原子性
- 集群批处理
如MSET或Pipeline这样的批处理需要在一次请求中携带多条命令,而此时如果Redis是一个集群,那批处理命令的多个key必须落在一个插槽中,否则就会导致执行失败。
| 串行命令 | 串行slot | 并行slot | hash_tag | |
|---|---|---|---|---|
| 实现思路 | for循环遍历,依次执行每个命令 | 在客户端计算每个key的slot,将slot一致分为一组,每组都利用Pipeline批处理。串行执行各组命令 | 在客户端计算每个key的slot,将slot一致分为一组,每组都利用Pipeline批处理。并行执行各组命令 | 将所有key设置相同的hash_tag,则所有key的slot一定相同 |
| 耗时 | N次网络耗时 + N次命令耗时 | m次网络耗时 + N次命令耗时m = key的slot个数 | 1次网络耗时 + N次命令耗时 | 1次网络耗时 + N次命令耗时 |
| 优点 | 实现简单 | 耗时较短 | 耗时非常短 | 耗时非常短、实现简单 |
| 缺点 | 耗时非常久 | 实现稍复杂slot越多,耗时越久 | 实现复杂 | 容易出现数据倾斜 |
命令执行原子性
命令执行原子性首选Lua脚本
服务端优化
持久化配置
Redis的持久化虽然可以保证数据安全,但也会带来很多额外的开销,因此持久化请遵循下列建议:
用来做缓存的Redis实例尽量不要开启持久化功能
建议关闭RDB持久化功能,使用AOF持久化
利用脚本定期在slave节点做RDB,实现数据备份
设置合理的rewrite阈值,避免频繁的bgrewrite
配置no-appendfsync-on-rewrite = yes,禁止在rewrite期间做aof,避免因AOF引起的阻塞
部署有关建议:
Redis实例的物理机要预留足够内存,应对fork和rewrite
单个Redis实例内存上限不要太大,例如4G或8G。可以加快fork的速度、减少主从同步、数据迁移压力
不要与CPU密集型应用部署在一起
不要与高硬盘负载应用一起部署。例如:数据库、消息队列
redis缓存业务,单独redis实例,建议不配置持久化,要求安全性不高
redis分布式锁,单独redis实例,建议配置持久化。要求安全性比价高
rdb与aof,建议使用aof作为持久化措施。但是由于aof体积比较大,耗费大,需要设定合适的rewrite机制
rdb进行手动措施作为数据备份
no-appendfsync-on-rewrite=yes,
redis慢查询
应用部署,CPU密集、IO密集分离
慢查询
慢查询的阈值可以通过配置指定:
slowlog-log-slower-than:慢查询阈值,单位是微秒。默认是10000,建议1000
慢查询会被放入慢查询日志中,日志的长度有上限,可以通过配置指定:
slowlog-max-len:慢查询日志(本质是一个队列)的长度。默认是128,建议1000
修改这两个配置可以使用:config set命令:
查看慢查询日志列表:
slowlog len:查询慢查询日志长度
slowlog get [n]:读取n条慢查询日志
slowlog reset:清空慢查询列表
安全
Redis会绑定在0.0.0.0:6379,这样将会将Redis服务暴露到公网上,而Redis如果没有做身份认证,会出现严重的安全漏洞.
漏洞重现方式:https://cloud.tencent.com/developer/article/1039000
漏洞出现的核心的原因有以下几点:
Redis未设置密码
利用了Redis的config set命令动态修改Redis配置
使用了Root账号权限启动Redis
为了避免这样的漏洞,这里给出一些建议:
- Redis一定要设置密码
- 禁止线上使用下面命令:keys、flushall、flushdb、config set等命令。可以利用rename-command禁用。命令隐匿,uuid形式使用命令或者禁用命令,keys、flushall、flushdb、config set等命令,rename-command
- bind:限制网卡,禁止外网网卡访问
- 开启防火墙
- 不要使用Root账户启动Redis
- 尽量不是有默认的端口
内存配置
当Redis内存不足时,可能导致Key频繁被删除、响应时间变长、QPS不稳定等问题。当内存使用率达到90%以上时就需要我们警惕,并快速定位到内存占用的原因。
| 内存占用 | 说明 |
|---|---|
| 数据内存 | 是Redis最主要的部分,存储Redis的键值信息。主要问题是BigKey问题、内存碎片问题 |
| 进程内存 | Redis主进程本身运⾏肯定需要占⽤内存,如代码、常量池等等;这部分内存⼤约⼏兆,在⼤多数⽣产环境中与Redis数据占⽤的内存相⽐可以忽略。 |
| 缓冲区内存 | 一般包括客户端缓冲区、AOF缓冲区、复制缓冲区等。客户端缓冲区又包括输入缓冲区和输出缓冲区两种。这部分内存占用波动较大,不当使用BigKey,可能导致内存溢出。 |
数据内存
Redis提供了一些命令,可以查看到Redis目前的内存分配状态:
info memory
memory xxx
内存缓冲区
内存缓冲区常见的有三种:
复制缓冲区:主从复制的repl_backlog_buf,如果太小可能导致频繁的全量复制,影响性能。通过repl-backlog-size来设置,默认1mb
AOF缓冲区:AOF刷盘之前的缓存区域,AOF执行rewrite的缓冲区。无法设置容量上限
客户端缓冲区:分为输入缓冲区和输出缓冲区,输入缓冲区最大1G且不能设置。输出缓冲区可以设置
client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>
<class>
客户端类型
normal:普通客户端
replica:主从复制客户端
pubsub:PubSub客户端
<hard limit>
缓冲区上限在超过limit后断开客户端
<soft limit> <soft seconds>
缓冲区上限,在超过soft limit 并且持续了 soft seconds秒后断开客户端
默认配置
集群还是主从
集群虽然具备高可用特性,能实现自动故障恢复,但是如果使用不当,也会存在一些问题:
- 集群完整性问题
- 集群带宽问题
- 数据倾斜问题
- 客户端性能问题
- 命令的集群兼容性问题
- lua和事务问题
单体Redis(主从Redis)已经能达到万级别的QPS,并且也具备很强的高可用特性。如果主从能满足业务需求的情况下,尽量不搭建Redis集群。
集群带宽
集群节点之间会不断的互相Ping来确定集群中其它节点的状态。每次Ping携带的信息至少包括:
插槽信息
集群状态信息
集群中节点越多,集群状态信息数据量也越大,10个节点的相关信息可能达到1kb,此时每次集群互通需要的带宽会非常高。
解决途径:
避免大集群,集群节点数不要太多,最好少于1000,如果业务庞大,则建立多个集群。
避免在单个物理机中运行太多Redis实例
配置合适的cluster-node-timeout值
集群插槽
在Redis的默认配置中,如果发现任意一个插槽不可用,则整个集群都会停止对外服务
为了保证高可用特性,这里建议将 cluster-require-full-coverage配置为false
注意,jedis没有解决集群下slot分组问题,spring提供的lettuce中解决了这个问题(异步slot)
Redis常见性能问题和解决方案?
- Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特
别是不要启用内存快照做持久化。 - 如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一
次。 - 为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局
域网内。 - 尽量避免在压力较大的主库上增加从库
- Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大
量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。 - 为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳
定,即主从关系为:Master<–Slave1<–Slave2<–Slave3…,这样的结构
也方便解决单点故障问题,实现Slave对Master的替换,也即,如果
Master挂了,可以立马启用Slave1做Master,其他不变。
假如Redis里面有1亿个key,其中有10w个key是以某个
固定的已知的前缀开头的,如果将它们全部找出来?
使用keys指令可以扫出指定模式的key列表。
对方接着追问:如果这个redis正在给线上的业务提供服务,那使用keys指令会
有什么问题?
这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线
程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时
候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是
会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会
比直接用keys指令长。
阿里广告平台,海量数据里查询某一固定前缀的key?
答:使用keys pattern来查询,但是面度大数量的数据,不推荐。
使用scan cursor [Match pattern] [Count count] 来限制匹配模式,和数量,是更好地选择。
小红书,你如何生产上限制keys */flushdb/flushall等危险命令以防止误删误用?
答:可以在配置文件加上 rename-command keys "" 来加以限制。
美团,MEMORY USAGE 命令你用过吗?
答:用过,这是用来查询特定key占用了多大内存的命令
memory usage key [Samples count] 对于嵌套式数据还可以加上一个count来表示抽样查询该key内的元素的个数,设为0表示查询所有。
BigKey问题,多大算big? 你如何发现? 如何删除? 如何处理?
答:String类型认为大于10k就算bigkey。其余类型的元素个数超过5000就视为bigkey。
使用 redis-cli -a 111111 --bigkeys 和 memory usage来发现他们
使用del 或 unlink来删除string类型的key
其余类型要配合scan来渐进式删除
BigKey你做过调优吗? 惰性释放lazyfree了解过吗?
答:可以在配置文件上设置懒惰模式,延后删除操作的释放内存的时间,优化数据同步策略,添加异步删除的配置等等。
Morekey问题,生产上redis数据库有1000W记录,你如何遍历? key *可以吗?
答:不能用keys * 会阻塞主线程非常长的时间,很危险。
要用scan cursor pattern count命令来少量多次的遍历
BigKey
BigKey 是指键值对中 value 占用内存多或包含数据数量多的 key。
危害
在Redis中,每个key都有一个对应的value,如果某个key的value过大,就会导致Redis的性能下降或者崩溃。
因为Redis需要将大key全部加载到内存中,这会占用大量的内存空间,会降低Redis的响应速度,这个问题被称为Big Key问题。
不要小看这个问题,它可是能让你的Redis瞬间变成“乌龟”,由于Redis单线程的特性,操作Big Key的通常比较耗时,也就意味着Big Key阻塞Redis的可能性很大,这样会造成客户端阻塞或者引起故障切换,有可能导致“慢查询”或其他连锁反应。
网络阻塞
对BigKey执行读请求时,少量的QPS就可能导致带宽使用率被占满,导致Redis实例,乃至所在物理机变慢
数据倾斜
BigKey所在的Redis实例内存使用率远超其他实例,无法使数据分片的内存资源达到均衡
Redis阻塞
对元素较多的hash、list、zset等做运算会耗时较旧,使主线程被阻塞
CPU压力
对BigKey的数据序列化和反序列化会导致CPU的使用率飙升,影响Redis实例和本机其它应用
判定
一般而言,下面这两种情况可以被称为Big Key:
String 类型的 key 对应的value超过 10 MB。
list、set、hash、zset等集合类型,集合元素个数超过 5000个。
以上对Big Key的判断标准并不唯一,只是一个大体的标准。在实际业务开发中,对Big Key的判断是需要根据具体的使用场景做不同的判断。比如操作某个 key 导致请求响应时间变慢,那么这个 key 就可以判定成 Big Key。
BigKey 的判定需要从 value 占用内存大小和 value 包含元素数量综合分析,也就是从 value 的数据类型这一数据结构的空间复杂度和时间复杂度两个维度分析,最佳实践约定如下判定标准:
正例:
- 单个 key 的 value 内存占用小于等于 10 kb
- 对于容器类型的 key,元素数量小于等于 1000
反例:
- string 类型 value 超过 5M
- ZSET 类型 value 包含元素超过 10000
- hash 类型 value 包含元素 300 但内存占用 超过 100 M
发现
redis-cli --bigkeys -c -a 123456 -p 8001 -i 0.1
redis-cli --bigkeys仅扫描命令中指定的Redis节点,要扫描全分片集群就需要在每个分片分别进行扫描redis-cli --bigkeys会扫描节点所有key,节点key较多时,即使使用-i参数也会对该Redis性能有一定影响,建议在分片的从节点执行redis-cli --bigkeys命令仅会返回统计情况和每种类型最大的key,top-k的key无法获取redis-cli --bigkeys命令容器类型仅返回元素数量,内存占用并不会统计,需要memory usage key进行抽样统计
info keyspace
[root@yc-cn-centos-abc ~]# redis-cli --bigkeys -c -a 123456 -p 8001 -i 0.1
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).
[00.00%] Biggest list found so far '"bk:list:key_162"' with 400 items
[00.00%] Biggest set found so far '"bk:set:key_39"' with 1173 members
[00.00%] Biggest string found so far '"bk:str:key_32"' with 2263 bytes
[00.00%] Biggest string found so far '"bk:str:key_144"' with 32627 bytes
[00.00%] Biggest zset found so far '"bk:zset:key_113"' with 187 members
[00.00%] Biggest zset found so far '"bk:zset:key_54"' with 4745 members
[00.00%] Biggest list found so far '"bk:list:key_200"' with 1856 items
[00.00%] Biggest list found so far '"bk:list:key_13"' with 8127 items
[06.12%] Biggest set found so far '"bk:set:key_177"' with 5030 members
[06.12%] Biggest hash found so far '"bk:hash:key_192"' with 1572 fields
[09.17%] Biggest hash found so far '"bk:hash:key_123"' with 4904 fields
[09.17%] Biggest hash found so far '"bk:hash:key_83"' with 5050 fields
[15.29%] Biggest zset found so far '"bk:zset:key_51"' with 4948 members
[18.35%] Biggest hash found so far '"bk:hash:key_11"' with 5215 fields
[24.77%] Biggest hash found so far '"bk:hash:key_2"' with 5389 fields
[27.83%] Biggest set found so far '"bk:set:key_49"' with 5302 members
[31.19%] Biggest zset found so far '"bk:zset:key_43"' with 5053 members
[47.09%] Biggest set found so far '"bk:set:key_2"' with 5389 members
[47.09%] Biggest list found so far '"bk:list:key_1"' with 9079 items
[47.09%] Biggest zset found so far '"bk:zset:key_58"' with 5176 members
[56.27%] Biggest zset found so far '"bk:zset:key_11"' with 5215 members
[75.23%] Biggest list found so far '"bk:list:key_40"' with 9842 items
[84.40%] Biggest zset found so far '"bk:zset:key_73"' with 5322 members
[90.52%] Biggest list found so far '"bk:list:key_16"' with 11010 items
-------- summary -------
Sampled 327 keys in the keyspace!
Total key length in bytes is 4597 (avg len 14.06)
Biggest list found '"bk:list:key_16"' has 11010 items
Biggest hash found '"bk:hash:key_2"' has 5389 fields
Biggest string found '"bk:str:key_144"' has 32627 bytes
Biggest set found '"bk:set:key_2"' has 5389 members
Biggest zset found '"bk:zset:key_73"' has 5322 members
67 lists with 188982 items (20.49% of keys, avg size 2820.63)
67 hashs with 142398 fields (20.49% of keys, avg size 2125.34)
66 strings with 1117562 bytes (20.18% of keys, avg size 16932.76)
0 streams with 0 entries (00.00% of keys, avg size 0.00)
62 sets with 162046 members (18.96% of keys, avg size 2613.65)
65 zsets with 133174 members (19.88% of keys, avg size 2048.83)
解读:
redis-cli --bitkeys -i 0.1命令扫描整个keyspace,找到最大的key以及每种key类型的平均大小。可以使用-i 0.1每100个SCAN命令休眠0.1秒(通常不需要)。keyspace中共抽取327个key,总key长度为4597字节,平均每个key长度14.06字节,注意这里是指key-value中的key长度。
列出抽取数据中五种类型的value最大的key-value
抽取数据中最大的list为
bk:list:key_16,包含11010个元素抽取数据中最大的hash为
bk:hash:key_2,包含5389个字段抽取数据中最大的string为
bk:str:key_144,大小为32627字节抽取数据中最大的set为
bk:set:key_2,包含为8389个成员抽取数据中最大的zset为
bk:zset:key_73,包含5322个成员
列出抽取数据中六种类型的总大小,占比和平均大小
- 抽取数据包含67个list,共计188982个元素(约占key总数量的20.49%,平均每个list包含2820.63个元素)
- 抽取数据包含67个hash,共计142398个字段(约占key总数量的20.49%,平均每个hash包含2125.34个字段)
- 抽取数据包含66个string,共计1117562字节,(约占key总数量的20.18%,平均每个string大小为16932.76字节)
- 抽取数据包含0个stream,共计0个实体(约占key总数量的00.00%,平均每个stream包含0.00个实体)
- 抽取数据包含62个set,共计162046个成员(约占key总数量的18.96%,平均每个set包含2613.65个成员)
- 抽取数据包含65个zset,共计133174个成员(约占key总数量的19.88%,平均每个zset包含2048.83个成员)
debug object key
debug object bk:list:key_16
- DEBUG OBJECT命令是存在误差
127.0.0.1:8001> debug object bk:list:key_16
Value at:0x18a1f80 refcount:1 encoding:quicklist serializedlength:54860 lru:3185183 lru_seconds_idle:2616 ql_nodes:18 ql_avg_node:611.67 ql_ziplist_max:-2 ql_compressed:0 ql_uncompressed_size:140007
value at:内存地址,0x18a1f80refcount:引用计数,1encoding:编码方式,quicklistserializedlength:对象使用RDB序列化后的长度,单位为字节,54860lru:最近最少使用,机制??,3185183lru_seconds_idle:上次访问后至今的时间,单位为秒,2616ql_nodesql_avg_nodeql_ziplist_maxql_compressedql_uncompressed_size
memory usage key
专用于分析key内存占用,string类型直接获取内存占用,list、hash、set、zset、stream类型采用抽样方式进行估计,抽样数量越接近总数估计值越准确,不指定抽样数量则默认为5个
# bk:list:key_16包含11010个元素
memory usage bk:list:key_16 # 121575
memory usage bk:list:key_16 samples 1000 # 140687
scan扫描
scan + memory usage
自己编程,利用scan扫描Redis中的所有key,利用strlen、hlen等命令判断key的长度(此处不建议使用MEMORY USAGE)
第三方工具
利用第三方工具,如 redis-rdb-tools 分析RDB快照文件,全面分析内存使用情况
redis-rdb-tools 是一个 python 的解析 rdb 文件的工具,在分析内存的时候,我们主要用它生成内存快照。可以把 rdb 快照文件生成 CSV 或 JSON 文件,也可以导入到 MySQL 生成报表来分析。
pip install rdbtools
rdb -c memory --bytes 10240 dump.rdb > memory.csv
在生成的 CSV 文件中主要有以下几列:
databasekey在Redis的dbtypekey类型keykey值size_in_byteskey的内存大小encodingvalue的存储编码形式num_elementskey中的value的个数len_largest_elementkey中的value的长度
可以在MySQL中新建表然后导入进行分析,然后可以直接通过SQL语句进行查询分析。
代码语言:javascript
复制
CREATE TABLE `memory` (
`database` int(128) DEFAULT NULL,
`type` varchar(128) DEFAULT NULL,
`KEY` varchar(128),
`size_in_bytes` bigint(20) DEFAULT NULL,
`encoding` varchar(128) DEFAULT NULL,
`num_elements` bigint(20) DEFAULT NULL,
`len_largest_element` varchar(128) DEFAULT NULL,
PRIMARY KEY (`KEY`)
);
解决
压缩
如果大key的产生原因主要是由于对象序列化后的体积过大,我们可以考虑使用压缩算法来减小对象的大小。需要在客户端使用一些压缩算法对数据进行压缩和解压缩操作,例如LZF、Snappy等。
分割
将Big Key拆分成多个小key。这个方法比较简单,但是需要修改应用程序的代码。就像是把一个大蛋糕切成小蛋糕一样,有点费力,但是可以解决问题。
或者尝试将Big Key转换成Redis的其他数据结构。例如,将Big Key转换成Hash,List或者Set等数据结构。
删除
BigKey内存占用较多,即便时删除这样的key也需要耗费很长时间,导致Redis主线程阻塞,引发一系列问题。
redis 3.0 及以下版本,分批部分删除
如果是集合类型,则遍历BigKey的元素,先逐个删除子元素,最后删除BigKey
# list
def del_large_list():
r = redis.StrictRedis(host='redis-host1', port=6379)
large_list_key = 'xxx' #要删除的大list的键名
while r.llen(large_list_key)>0:
#每次只删除最右100个元素
r.ltrim(large_list_key, 0, -101)
# hash
def del_large_hash():
r = redis.StrictRedis(host='redis-host1', port=6379)
large_hash_key ="xxx" #要删除的大hash键名
cursor = '0'
while cursor != 0:
# 使用 hscan 命令,每次获取 100 个字段
cursor, data = r.hscan(large_hash_key, cursor=cursor, count=100)
for item in data.items():
# 再用 hdel 命令,每次删除1个字段
r.hdel(large_hash_key, item[0])
# set
def del_large_set():
r = redis.StrictRedis(host='redis-host1', port=6379)
large_set_key = 'xxx' # 要删除的大set的键名
cursor = '0'
while cursor != 0:
# 使用 sscan 命令,每次扫描集合中 100 个元素
cursor, data = r.sscan(large_set_key, cursor=cursor, count=100)
for item in data:
# 再用 srem 命令每次删除一个键
r.srem(large_size_key, item)
# zset
def del_large_sortedset():
r = redis.StrictRedis(host='large_sortedset_key', port=6379)
large_sortedset_key='xxx'
while r.zcard(large_sortedset_key)>0:
# 使用 zremrangebyrank 命令,每次删除 top 100个元素
r.zremrangebyrank(large_sortedset_key,0,99)
Redis 4.0以后
Redis在4.0后提供了异步删除的命令:unlink
数据倾斜
redis集群出现倾斜的影响
倾斜问题对于redis这类纯内存和单线程服务影响较大,存在以下痛点:
- qps集中到少数redis节点,引起少数节点过载,会拖垮整个服务,同时集群处理qps能力不具备可扩展性;
- 数据容量倾斜,导致少数节点内存爆增,出现OOM Killer和集群存储容量不具备可扩展性;
- 运维管理变复杂,类似监控告警内存使用量、QPS、连接数、redis cpu busy等值不便统一;
- 因集群内其他节点资源不能被充分利用,导致redis服务器/容器资源利率低;
- 增大自动化配置管理难度;单集群节点尽量统一参数配置;
分析完影响,那我们再看生产环境中,导致Redis集群严重“倾斜”的常见原因。
导致Redis集群倾斜的常见原因
一般是系统设计时,键空间(keyspace)设计不合理:
- 系统设计时,redis键空间(keyspace)设计不合理,出现”热点key”,导致这类key所在节点qps过载,集群出现qps倾斜;
- 系统存在大的集合key(hash,set,list等),导致大key所在节点的容量和QPS过载,集群出现qps和容量倾斜;
- DBA在规划集群或扩容不当,导致数据槽(slot)数分配不均匀,导致容量和请求qps倾斜;
- 系统大量使用Keys hash tags, 可能导致某些数据槽位的key数量多,集群集群出现qps和容量倾斜;
- 工程师执行monitor这类命令,导致当前节点client输出缓冲区增大;used_memory_rss被撑大;导致节点内存容量增大,出现容量倾斜;
接下来,当集群出现内存容量、键数量或QPS请求量严重倾斜时,我们应该排查定位问题呢?
访问倾斜
redis-faina
使用monitor命令在紧急情况时找出热Key
Redis的monitor命令能够忠实的打印Redis中的所有请求,包括时间信息、Client信息、命令以及Key信息。在发生紧急情况时,我们可以通过短暂执行monitor命令并将输出重定向至文件,在关闭monitor命令后通过对文件中请求进行归类分析即可找出这段时间中的热Key。
由于monitor命令对Redis的CPU、内存、网络资源均有一定的占用。因此,对于一个已处于高压状态的Redis,monitor可能会起到雪上加霜的作用。同时,这种异步收集并分析的方案的时效性较差,并且由于分析的精确度取决于monitor的执行时间,因此在多数无法长时间执行该命令的线上场景中本方案的精确度也不够好。
三大Key与热Key带来的问题
在Redis的使用中,大Key及热Key会给Redis带来各种各样的问题,而最常见的问题为性能下降、访问超时、数据不均衡等。
1 大Key带来的常见问题
- Client发现Redis变慢;
- Redis内存不断变大引发OOM,或达到maxmemory设置值引发写阻塞或重要Key被逐出;
- Redis Cluster中的某个node内存远超其余node,但因Redis Cluster的数据迁移最小粒度为Key而无法将node上的内存均衡化;
- 大Key上的读请求使Redis占用服务器全部带宽,自身变慢的同时影响到该服务器上的其它服务;
- 删除一个大Key造成主库较长时间的阻塞并引发同步中断或主从切换;
2 热Key带来的常见问题
- 热Key占用大量的Redis CPU时间使其性能变差并影响其它请求;
- Redis Cluster中各node流量不均衡造成Redis Cluster的分布式优势无法被Client利用,一个分片负载很高而其它分片十分空闲从而产生读/写热点问题;
- 在抢购、秒杀活动中,由于商品对应库存Key的请求量过大超出Redis处理能力造成超卖;
- 热Key的请求压力数量超出Redis的承受能力造成缓存击穿,此时大量强求将直接指向后端存储将其打挂并影响到其它业务;
四 大Key与热Key的常见产生原因
业务规划不足、Redis不正确的使用、无效数据的堆积、访问突增等都会产生大Key与热Key,如:
- 将Redis用在并不适合其能力的场景,造成Key的value过大,如使用String类型的Key存放大体积二进制文件型数据(大Key);
- 业务上线前规划设计考虑不足没有对Key中的成员进行合理的拆分,造成个别Key中的成员数量过多(大Key);
- 没有对无效数据进行定期清理,造成如HASH类型Key中的成员持续不断的增加(大Key);
- 预期外的访问量陡增,如突然出现的爆款商品、访问量暴涨的热点新闻、直播间某大主播搞活动带来的大量刷屏点赞、游戏中某区域发生多个工会间的战斗涉及大量玩家等(热Key);
- 使用LIST类型Key的业务消费侧代码故障,造成对应Key的成员只增不减(大Key);
监控
- 数据倾斜,bigkey
- 访问倾斜,hotkey
慢查询
# 超过5毫秒为慢命令
config set slowlog-log-slower-than 5000
bigkey
redis-cli --bigkeys
redis-cli --hotkeys
key数量、内存占用、客户端连接数、连接被拒绝数、请求数
redis-cli --stat -c -a 123456 -p 8001
内存碎片率
redis-cli -h {ip} -p {port} info | grep mem_fragmentation_ratio