@@ -210,17 +210,17 @@ Redis 从 2.6 版本开始支持执行 Lua 脚本,它的功能和事务非常
210
210
211
211
Redis 中有一些原生支持批量操作的命令,比如:
212
212
213
- - ` mget ` (获取一个或多个指定 key 的值)、` mset ` (设置一个或多个指定 key 的值)、
214
- - ` hmget ` (获取指定哈希表中一个或者多个指定字段的值)、` hmset ` (同时将一个或多个 field-value 对设置到指定哈希表中)、
215
- - ` sadd ` (向指定集合添加一个或多个元素)
213
+ - ` MGET ` (获取一个或多个指定 key 的值)、` MSET ` (设置一个或多个指定 key 的值)、
214
+ - ` HMGET ` (获取指定哈希表中一个或者多个指定字段的值)、` HMSET ` (同时将一个或多个 field-value 对设置到指定哈希表中)、
215
+ - ` SADD ` (向指定集合添加一个或多个元素)
216
216
- ......
217
217
218
- 不过,在 Redis 官方提供的分片集群解决方案 Redis Cluster 下,使用这些原生批量操作命令可能会存在一些小问题需要解决。就比如说 ` mget ` 无法保证所有的 key 都在同一个 ** hash slot** (哈希槽)上,` mget ` 可能还是需要多次网络传输,原子操作也无法保证了。不过,相较于非批量操作,还是可以节省不少网络传输次数。
218
+ 不过,在 Redis 官方提供的分片集群解决方案 Redis Cluster 下,使用这些原生批量操作命令可能会存在一些小问题需要解决。就比如说 ` MGET ` 无法保证所有的 key 都在同一个 ** hash slot** (哈希槽)上,` MGET ` 可能还是需要多次网络传输,原子操作也无法保证了。不过,相较于非批量操作,还是可以节省不少网络传输次数。
219
219
220
220
整个步骤的简化版如下(通常由 Redis 客户端实现,无需我们自己再手动实现):
221
221
222
222
1 . 找到 key 对应的所有 hash slot;
223
- 2 . 分别向对应的 Redis 节点发起 ` mget ` 请求获取数据;
223
+ 2 . 分别向对应的 Redis 节点发起 ` MGET ` 请求获取数据;
224
224
3 . 等待所有请求执行结束,重新组装结果数据,保持跟入参 key 的顺序一致,然后返回结果。
225
225
226
226
如果想要解决这个多次网络传输的问题,比较常用的办法是自己维护 key 与 slot 的关系。不过这样不太灵活,虽然带来了性能提升,但同样让系统复杂性提升。
@@ -233,7 +233,7 @@ Redis 中有一些原生支持批量操作的命令,比如:
233
233
234
234
对于不支持批量操作的命令,我们可以利用 ** pipeline(流水线)** 将一批 Redis 命令封装成一组,这些 Redis 命令会被一次性提交到 Redis 服务器,只需要一次网络传输。不过,需要注意控制一次批量操作的 ** 元素个数** (例如 500 以内,实际也和元素字节数有关),避免网络传输的数据量过大。
235
235
236
- 与` mget ` 、` mset ` 等原生批量操作命令一样,pipeline 同样在 Redis Cluster 上使用会存在一些小问题。原因类似,无法保证所有的 key 都在同一个 ** hash slot** (哈希槽)上。如果想要使用的话,客户端需要自己维护 key 与 slot 的关系。
236
+ 与` MGET ` 、` MSET ` 等原生批量操作命令一样,pipeline 同样在 Redis Cluster 上使用会存在一些小问题。原因类似,无法保证所有的 key 都在同一个 ** hash slot** (哈希槽)上。如果想要使用的话,客户端需要自己维护 key 与 slot 的关系。
237
237
238
238
原生批量操作命令和 pipeline 的是有区别的,使用的时候需要注意:
239
239
@@ -269,7 +269,7 @@ Lua 脚本同样支持批量操作多条命令。一段 Lua 脚本可以视作
269
269
270
270
定期删除执行过程中,如果突然遇到大量过期 key 的话,客户端请求必须等待定期清理过期 key 任务线程执行完成,因为这个这个定期任务线程是在 Redis 主线程中执行的。这就导致客户端请求没办法被及时处理,响应速度会比较慢。
271
271
272
- 如何解决呢?下面是两种常见的方法:
272
+ ** 如何解决呢?** 下面是两种常见的方法:
273
273
274
274
1 . 给 key 设置随机过期时间。
275
275
2 . 开启 lazy-free(惰性删除/延迟释放) 。lazy-free 特性是 Redis 4.0 开始引入的,指的是让 Redis 采用异步方式延迟释放 key 使用的内存,将该操作交给单独的子线程处理,避免阻塞主线程。
@@ -390,17 +390,17 @@ maxmemory-policy volatile-lfu
390
390
maxmemory-policy allkeys-lfu
391
391
` ` `
392
392
393
- 需要注意的是,` hotkeys` 命令也会增加 Redis 实例的 CPU 和内存消耗(全局扫描),因此需要谨慎使用。
393
+ 需要注意的是,` hotkeys` 参数命令也会增加 Redis 实例的 CPU 和内存消耗(全局扫描),因此需要谨慎使用。
394
394
395
- ** 2、使用` monitor ` 命令。**
395
+ ** 2、使用` MONITOR ` 命令。**
396
396
397
- ` monitor ` 命令是 Redis 提供的一种实时查看 Redis 的所有操作的方式,可以用于临时监控 Redis 实例的操作情况,包括读写、删除等操作。
397
+ ` MONITOR ` 命令是 Redis 提供的一种实时查看 Redis 的所有操作的方式,可以用于临时监控 Redis 实例的操作情况,包括读写、删除等操作。
398
398
399
- 由于该命令对 Redis 性能的影响比较大,因此禁止长时间开启 ` monitor ` (生产环境中建议谨慎使用该命令)。
399
+ 由于该命令对 Redis 性能的影响比较大,因此禁止长时间开启 ` MONITOR ` (生产环境中建议谨慎使用该命令)。
400
400
401
401
` ` ` java
402
402
# redis-cli
403
- 127.0.0.1:6379> monitor
403
+ 127.0.0.1:6379> MONITOR
404
404
OK
405
405
1683638260.637378 [0 172.17.0.1:61516] " ping"
406
406
1683638267.144236 [0 172.17.0.1:61518] " smembers" " mySet"
413
413
1683638276.327234 [0 172.17.0.1:61518] " smembers" " mySet"
414
414
` ` `
415
415
416
- 在发生紧急情况时,我们可以选择在合适的时机短暂执行 monitor 命令并将输出重定向至文件,在关闭 monitor 命令后通过对文件中请求进行归类分析即可找出这段时间中的 hotkey。
416
+ 在发生紧急情况时,我们可以选择在合适的时机短暂执行 ` MONITOR ` 命令并将输出重定向至文件,在关闭 ` MONITOR ` 命令后通过对文件中请求进行归类分析即可找出这段时间中的 hotkey。
417
417
418
418
** 3、借助开源项目。**
419
419
@@ -451,6 +451,104 @@ hotkey 的常见处理以及优化办法如下(这些方法可以配合起来
451
451
452
452
! [通过阿里云的Proxy Query Cache优化热点Key问题](https://oss.javaguide.cn/github/javaguide/database/redis/aliyun-hotkey-proxy-query-cache.png)
453
453
454
+ # ## 慢查询命令
455
+
456
+ # ### 为什么会有慢查询命令?
457
+
458
+ 我们知道一个 Redis 命令的执行可以简化为以下 4 步:
459
+
460
+ 1. 发送命令
461
+ 2. 命令排队
462
+ 3. 命令执行
463
+ 4. 返回结果
464
+
465
+ Redis 慢查询统计的是命令执行这一步骤的耗时,慢查询命令也就是那些命令执行时间较长的命令。
466
+
467
+ Redis 为什么会有慢查询命令呢?
468
+
469
+ Redis 中的大部分命令都是 O(1)时间复杂度,但也有少部分 O(n) 时间复杂度的命令,例如:
470
+
471
+ - ` KEYS * ` :会返回所有符合规则的 key。
472
+ - ` HGETALL` :会返回一个 Hash 中所有的键值对。
473
+ - ` LRANGE` :会返回 List 中指定范围内的元素。
474
+ - ` SMEMBERS` :返回 Set 中的所有元素。
475
+ - ` SINTER` /` SUNION` /` SDIFF` :计算多个 Set 的交集/并集/差集。
476
+ - ......
477
+
478
+ 由于这些命令时间复杂度是 O(n),有时候也会全表扫描,随着 n 的增大,执行耗时也会越长。不过, 这些命令并不是一定不能使用,但是需要明确 N 的值。另外,有遍历的需求可以使用 ` HSCAN` 、` SSCAN` 、` ZSCAN` 代替。
479
+
480
+ 除了这些 O(n)时间复杂度的命令可能会导致慢查询之外, 还有一些时间复杂度可能在 O(N) 以上的命令,例如:
481
+
482
+ - ` ZRANGE` /` ZREVRANGE` :返回指定 Sorted Set 中指定排名范围内的所有元素。时间复杂度为 O(log(n)+m),n 为所有元素的数量, m 为返回的元素数量,当 m 和 n 相当大时,O(n) 的时间复杂度更小。
483
+ - ` ZREMRANGEBYRANK` /` ZREMRANGEBYSCORE` :移除 Sorted Set 中指定排名范围/指定 score 范围内的所有元素。时间复杂度为 O(log(n)+m),n 为所有元素的数量, m 被删除元素的数量,当 m 和 n 相当大时,O(n) 的时间复杂度更小。
484
+ - ......
485
+
486
+ # ### 如何找到慢查询命令?
487
+
488
+ 在 ` redis.conf` 文件中,我们可以使用 ` slowlog-log-slower-than` 参数设置耗时命令的阈值,并使用 ` slowlog-max-len` 参数设置耗时命令的最大记录条数。
489
+
490
+ 当 Redis 服务器检测到执行时间超过 ` slowlog-log-slower-than` 阈值的命令时,就会将该命令记录在慢查询日志(slow log) 中,这点和 MySQL 记录慢查询语句类似。当慢查询日志超过设定的最大记录条数之后,Redis 会把最早的执行命令依次舍弃。
491
+
492
+ ⚠️注意:由于慢查询日志会占用一定内存空间,如果设置最大记录条数过大,可能会导致内存占用过高的问题。
493
+
494
+ ` slowlog-log-slower-than` 和` slowlog-max-len` 的默认配置如下(可以自行修改):
495
+
496
+ ` ` ` nginx
497
+ # The following time is expressed in microseconds, so 1000000 is equivalent
498
+ # to one second. Note that a negative number disables the slow log, while
499
+ # a value of zero forces the logging of every command.
500
+ slowlog-log-slower-than 10000
501
+
502
+ # There is no limit to this length. Just be aware that it will consume memory.
503
+ # You can reclaim memory used by the slow log with SLOWLOG RESET.
504
+ slowlog-max-len 128
505
+ ` ` `
506
+
507
+ 除了修改配置文件之外,你也可以直接通过 ` CONFIG` 命令直接设置:
508
+
509
+ ` ` ` bash
510
+ # 命令执行耗时超过 10000 微妙(即10毫秒)就会被记录
511
+ CONFIG SET slowlog-log-slower-than 10000
512
+ # 只保留最近 128 条耗时命令
513
+ CONFIG SET slowlog-max-len 128
514
+ ` ` `
515
+
516
+ 获取慢查询日志的内容很简单,直接使用` SLOWLOG GET` 命令即可。
517
+
518
+ ` ` ` java
519
+ 127.0.0.1:6379> SLOWLOG GET # 慢日志查询
520
+ 1) 1) (integer) 5
521
+ 2) (integer) 1684326682
522
+ 3) (integer) 12000
523
+ 4) 1) " KEYS"
524
+ 2) " *"
525
+ 5) " 172.17.0.1:61152"
526
+ 6) " "
527
+ // ...
528
+ ```
529
+
530
+ 慢查询日志中的每个条目都由以下六个值组成:
531
+
532
+ 1 . 唯一渐进的日志标识符。
533
+ 2 . 处理记录命令的 Unix 时间戳。
534
+ 3 . 执行所需的时间量,以微秒为单位。
535
+ 4 . 组成命令参数的数组。
536
+ 5 . 客户端 IP 地址和端口。
537
+ 6 . 客户端名称。
538
+
539
+ ` SLOWLOG GET ` 命令默认返回最近 10 条的的慢查询命令,你也自己可以指定返回的慢查询命令的数量 ` SLOWLOG GET N ` 。
540
+
541
+ 下面是其他比较常用的慢查询相关的命令:
542
+
543
+ ``` bash
544
+ # 返回慢查询命令的数量
545
+ 127.0.0.1:6379> SLOWLOG LEN
546
+ (integer) 128
547
+ # 清空慢查询命令
548
+ 127.0.0.1:6379> SLOWLOG RESET
549
+ OK
550
+ ```
551
+
454
552
### Redis 内存碎片
455
553
456
554
** 相关问题** :
@@ -634,8 +732,8 @@ Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删
634
732
实际使用 Redis 的过程中,我们尽量要准守一些常见的规范,比如:
635
733
636
734
1 . 使用连接池:避免频繁创建关闭客户端连接。
637
- 2. 尽量不使用 O(n)指令,使用 O(N) 命令时要关注 N 的数量:例如 ` hgetall ` 、` lrange ` 、` smembers ` 、` zrange ` 、` sinter ` 、 ` sunion ` 命令并非不能使用,但是需要明确 N 的值。有遍历的需求可以使用 ` hscan ` 、` sscan ` 、` zscan ` 代替。
638
- 3. 使用批量操作减少网络传输:原生批量操作命令(比如 ` mget ` 、` mset ` 等等)、pipeline、Lua 脚本。
735
+ 2 . 尽量不使用 O(n)指令,使用 O(n) 命令时要关注 n 的数量:像 ` KEYS * ` 、` HGETALL ` 、` LRANGE ` 、` SMEMBERS ` 、` SINTER ` / ` SUNION ` / ` SDIFF ` 等 O(n) 命令并非不能使用,但是需要明确 n 的值。另外, 有遍历的需求可以使用 ` HSCAN ` 、` SSCAN ` 、` ZSCAN ` 代替。
736
+ 3 . 使用批量操作减少网络传输:原生批量操作命令(比如 ` MGET ` 、` MSET ` 等等)、pipeline、Lua 脚本。
639
737
4 . 尽量不适用 Redis 事务:Redis 事务实现的功能比较鸡肋,可以使用 Lua 脚本代替。
640
738
5 . 禁止长时间开启 monitor:对性能影响比较大。
641
739
6 . 控制 key 的生命周期:避免 Redis 中存放了太多不经常被访问的数据。
0 commit comments