Skip to content

Commit

Permalink
[docs update]完善对 bigkey 的介绍+ 修正部分笔误
Browse files Browse the repository at this point in the history
  • Loading branch information
Snailclimb committed Nov 1, 2023
1 parent 9cf03ba commit f7508fb
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 造成的阻塞问题如下:

Expand Down
50 changes: 42 additions & 8 deletions docs/database/redis/redis-questions-02.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down Expand Up @@ -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 持久化。
Expand All @@ -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 分析功能(一般都提供了)。
Expand All @@ -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)
Expand Down Expand Up @@ -753,6 +786,7 @@ Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删
- Redis Transactions : <https://redis.io/docs/manual/transactions/>
- What is Redis Pipeline:<https://buildatscale.tech/what-is-redis-pipeline/>
- 一文详解 Redis 中 BigKey、HotKey 的发现与处理:<https://mp.weixin.qq.com/s/FPYE1B839_8Yk1-YSiW-1Q>
- Redis 延迟问题全面排障指南:https://mp.weixin.qq.com/s/mIc6a9mfEGdaNDD3MmfFsg
- Bigkey 问题的解决思路与方式探索:<https://mp.weixin.qq.com/s/Sej7D9TpdAobcCmdYdMIyA>
- Redis 延迟问题全面排障指南:<https://mp.weixin.qq.com/s/mIc6a9mfEGdaNDD3MmfFsg>

<!-- @include: @article-footer.snippet.md -->
2 changes: 0 additions & 2 deletions docs/java/concurrent/java-concurrent-questions-02.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,13 @@ try {
lock.unlock();
}
```

高并发的场景下,激烈的锁竞争会造成线程阻塞,大量阻塞线程会导致系统的上下文切换,增加系统的性能开销。并且,悲观锁还可能会存在死锁问题,影响代码的正常运行。

### 什么是乐观锁?

乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 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
Expand Down

0 comments on commit f7508fb

Please sign in to comment.