diff --git a/docs/database/redis/redis-common-blocking-problems-summary.md b/docs/database/redis/redis-common-blocking-problems-summary.md index 8b129e0e8bb..2b5d84a32aa 100644 --- a/docs/database/redis/redis-common-blocking-problems-summary.md +++ b/docs/database/redis/redis-common-blocking-problems-summary.md @@ -81,7 +81,10 @@ Redis AOF 持久化机制是在执行完命令之后再记录日志,这和关 ## 大 Key -如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准:string 类型的 value 超过 10 kb,复合类型的 value 包含的元素超过 5000 个(对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。 +如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准: + +- string 类型的 value 超过 1MB +- 复合类型(列表、哈希、集合、有序集合等)的 value 包含的元素超过 5000 个(对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。 大 key 造成的阻塞问题如下: diff --git a/docs/database/redis/redis-questions-02.md b/docs/database/redis/redis-questions-02.md index 7b1efc34fd5..3634359afc1 100644 --- a/docs/database/redis/redis-questions-02.md +++ b/docs/database/redis/redis-questions-02.md @@ -282,11 +282,30 @@ Lua 脚本同样支持批量操作多条命令。一段 Lua 脚本可以视作 #### 什么是 bigkey? -简单来说,如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准:string 类型的 value 超过 10 kb,复合类型的 value 包含的元素超过 5000 个(对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。 +简单来说,如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准: -#### bigkey 有什么危害? +- string 类型的 value 超过 1MB +- 复合类型(List、Hash、Set、Sorted Set 等)的 value 包含的元素超过 5000 个(对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。 -bigkey 除了会消耗更多的内存空间和带宽,还会对性能造成比较大的影响。因此,我们应该尽量避免 Redis 中存在 bigkey。 +#### bigkey 是怎么产生的?有什么危害? + +bigkey 通常是由于下面这些原因产生的: + +- 程序设计不当,比如直接使用 string 类型存储较大的文件对应的二进制数据。 +- 对于业务的数据规模考虑不周到,比如使用集合类型的时候没有考虑到数据量的快速增长。 +- 未及时清理垃圾数据,比如哈希中冗余了大量的无用键值对。 + +bigkey 除了会消耗更多的内存空间和带宽,还会对性能造成比较大的影响。 + +在 [Redis 常见阻塞原因总结](./redis-common-blocking-problems-summary.md)这篇文章中我们提到:大 key 还会造成阻塞问题。具体来说,主要体现在下面三个方面: + +1. 客户端超时阻塞:由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。 +2. 网络阻塞:每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。 +3. 工作线程阻塞:如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。 + +大 key 造成的阻塞问题还会进一步影响到主从同步和集群扩容。 + +综上,大 key 带来的潜在问题是非常多的,我们应该尽量避免 Redis 中存在 bigkey。 #### 如何发现 bigkey? @@ -322,7 +341,21 @@ Biggest string found '"ballcat:oauth:refresh_auth:f6cdb384-9a9d-4f2f-af01-dc3f28 在线上执行该命令时,为了降低对 Redis 的影响,需要指定 `-i` 参数控制扫描的频率。`redis-cli -p 6379 --bigkeys -i 3` 表示扫描过程中每次扫描后休息的时间间隔为 3 秒。 -**2、借助开源工具分析 RDB 文件。** +**2、使用 Redis 自带的 SCAN 命令** + +`SCAN` 命令可以按照一定的模式和数量返回匹配的 key。获取了 key 之后,可以利用 `STRLEN`、`HLEN`、`LLEN`等命令返回其长度或成员数量。 + +| 数据结构 | 命令 | 复杂度 | 结果(对应 key) | +| ---------- | ------ | ------ | ------------------ | +| String | STRLEN | O(1) | 字符串值的长度 | +| Hash | HLEN | O(1) | 哈希表中字段的数量 | +| List | LLEN | O(1) | 列表元素数量 | +| Set | SCARD | O(1) | 集合元素数量 | +| Sorted Set | ZCARD | O(1) | 有序集合的元素数量 | + +对于集合类型还可以使用 `MEMORY USAGE` 命令(Redis 4.0+),这个命令会返回键值对占用的内存空间。 + +**3、借助开源工具分析 RDB 文件。** 通过分析 RDB 文件来找出 big key。这种方案的前提是你的 Redis 采用的是 RDB 持久化。 @@ -331,7 +364,7 @@ Biggest string found '"ballcat:oauth:refresh_auth:f6cdb384-9a9d-4f2f-af01-dc3f28 - [redis-rdb-tools](https://github.com/sripathikrishnan/redis-rdb-tools):Python 语言写的用来分析 Redis 的 RDB 快照文件用的工具 - [rdb_bigkeys](https://github.com/weiyanwei412/rdb_bigkeys) : Go 语言写的用来分析 Redis 的 RDB 快照文件用的工具,性能更好。 -**3、借助公有云的 Redis 分析服务。** +**4、借助公有云的 Redis 分析服务。** 如果你用的是公有云的 Redis 服务的话,可以看看其是否提供了 key 分析功能(一般都提供了)。 @@ -343,9 +376,9 @@ Biggest string found '"ballcat:oauth:refresh_auth:f6cdb384-9a9d-4f2f-af01-dc3f28 bigkey 的常见处理以及优化办法如下(这些方法可以配合起来使用): -- **分割 bigkey**:将一个 bigkey 分割为多个小 key。这种方式需要修改业务层的代码,一般不推荐这样做。 +- **分割 bigkey**:将一个 bigkey 分割为多个小 key。例如,将一个含有上万字段数量的 Hash 按照一定策略(比如二次哈希)拆分为多个 Hash。 - **手动清理**:Redis 4.0+ 可以使用 `UNLINK` 命令来异步删除一个或多个指定的 key。Redis 4.0 以下可以考虑使用 `SCAN` 命令结合 `DEL` 命令来分批次删除。 -- **采用合适的数据结构**:比如使用 HyperLogLog 统计页面 UV。 +- **采用合适的数据结构**:例如,文件二进制数据不使用 String 保存、使用 HyperLogLog 统计页面 UV、Bitmap 保存状态信息(0/1)。 - **开启 lazy-free(惰性删除/延迟释放)** :lazy-free 特性是 Redis 4.0 开始引入的,指的是让 Redis 采用异步方式延迟释放 key 使用的内存,将该操作交给单独的子线程处理,避免阻塞主线程。 ### Redis hotkey(热 Key) @@ -753,6 +786,7 @@ Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删 - Redis Transactions : - What is Redis Pipeline: - 一文详解 Redis 中 BigKey、HotKey 的发现与处理: -- Redis 延迟问题全面排障指南:https://mp.weixin.qq.com/s/mIc6a9mfEGdaNDD3MmfFsg +- Bigkey 问题的解决思路与方式探索: +- Redis 延迟问题全面排障指南: diff --git a/docs/java/concurrent/java-concurrent-questions-02.md b/docs/java/concurrent/java-concurrent-questions-02.md index d6892d5dee5..d42a9e2910f 100644 --- a/docs/java/concurrent/java-concurrent-questions-02.md +++ b/docs/java/concurrent/java-concurrent-questions-02.md @@ -197,7 +197,6 @@ try { lock.unlock(); } ``` - 高并发的场景下,激烈的锁竞争会造成线程阻塞,大量阻塞线程会导致系统的上下文切换,增加系统的性能开销。并且,悲观锁还可能会存在死锁问题,影响代码的正常运行。 ### 什么是乐观锁? @@ -205,7 +204,6 @@ try { 乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。 在 Java 中`java.util.concurrent.atomic`包下面的原子变量类(比如`AtomicInteger`、`LongAdder`)就是使用了乐观锁的一种实现方式 **CAS** 实现的。 - ![JUC原子类概览](https://oss.javaguide.cn/github/javaguide/java/JUC%E5%8E%9F%E5%AD%90%E7%B1%BB%E6%A6%82%E8%A7%88-20230814005211968.png) ```java