diff --git a/docs/.vuepress/sidebar.ts b/docs/.vuepress/sidebar.ts index c0b07fb9..61530d36 100644 --- a/docs/.vuepress/sidebar.ts +++ b/docs/.vuepress/sidebar.ts @@ -290,6 +290,7 @@ export const sidebarConfig = sidebar({ "redis-data-structures-01", "redis-data-structures-02", "redis-memory-fragmentation", + "redis-common-blocking-problems-summary", "redis-cluster", ], }, diff --git a/docs/database/redis/images/redis-aof-write-log-disc.png b/docs/database/redis/images/redis-aof-write-log-disc.png deleted file mode 100644 index c0dc2029..00000000 Binary files a/docs/database/redis/images/redis-aof-write-log-disc.png and /dev/null differ diff --git a/docs/database/redis/redis-common-blocking-problems-summary.md b/docs/database/redis/redis-common-blocking-problems-summary.md new file mode 100644 index 00000000..b8974393 --- /dev/null +++ b/docs/database/redis/redis-common-blocking-problems-summary.md @@ -0,0 +1,107 @@ +--- +title: Redis 常见阻塞原因总结 +category: 数据库 +tag: + - Redis +--- + +> 本文整理完善自:https://mp.weixin.qq.com/s/0Nqfq_eQrUb12QH6eBbHXA ,作者:阿Q说代码 + +## O(n) 命令阻塞 + +使用` O(n)` 命令可能会导致阻塞,例如`keys *` 、`hgetall`、`lrange`、`smembers`、`zrange`、`sinter` 、`sunion` 命令。这些命令时间复杂度是 O(n),有时候也会全表扫描,随着 n 的增大耗时也会越大从而导致客户端阻塞。 + +不过, 这些命令并不是一定不能使用,但是需要明确 N 的值。有遍历的需求可以使用 `hscan`、`sscan`、`zscan` 代替。 + +## SAVE 创建 RDB 快照阻塞 + +Redis 提供了两个命令来生成 RDB 快照文件: + +- `save` : 同步保存操作,会阻塞 Redis 主线程; +- `bgsave` : fork 出一个子进程,子进程执行,不会阻塞 Redis 主线程,默认选项。 + +默认情况下,Redis 默认配置会使用 `bgsave` 命令。如果手动使用 `save` 命令生成 RDB 快照文件的话,就会阻塞主线程。 + +## AOF 日志记录阻塞 + +Redis AOF 持久化机制是在执行完命令之后再记录日志,这和关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复)不同。 + +![AOF 记录日志过程](https://oss.javaguide.cn/github/javaguide/database/redis/redis-aof-write-log-disc.png) + +**为什么是在执行完命令之后记录日志呢?** + +- 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查; +- 在命令执行完之后再记录,不会阻塞当前的命令执行。 + +这样也带来了风险(我在前面介绍 AOF 持久化的时候也提到过): + +- 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失; +- **可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)**。 + +## AOF 刷盘阻塞 + +开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。 + +在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是: + +```bash +appendfsync always #每次有数据修改发生时都会调用fsync函数同步AOF文件,fsync完成后线程返回,这样会严重降低Redis的速度 +appendfsync everysec #每秒钟调用fsync函数同步一次AOF文件 +appendfsync no #让操作系统决定何时进行同步,一般为30秒一次 +``` + +当调用 fsync 函数同步 AOF 文件时,需要等待,直到写入完成。当磁盘压力太大的时候,主线程可能会被阻塞。 + +## AOF 重写阻塞 + +1. fork 出一条子线程来将文件重写,在执行 `BGREWRITEAOF` 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子线程创建新 AOF 文件期间,记录服务器执行的所有写命令。 +2. 当子线程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。 +3. 最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。 + +阻塞就是出现在第 2 步的过程中,将缓冲区中新数据写到新文件的过程中会产生**阻塞**。 + +相关阅读:[Redis AOF重写阻塞问题分析](https://cloud.tencent.com/developer/article/1633077)。 + +## 大 Key 问题 + +如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准:string 类型的 value 超过 10 kb,复合类型的 value 包含的元素超过 5000 个(对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。 + +大 key 造成的阻塞问题如下: + +- 客户端超时阻塞:由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。 +- 引发网络阻塞:每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。 +- 阻塞工作线程:如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。 + +### 查找大 key + +当我们在使用 Redis 自带的 `--bigkeys` 参数查找大 key 时,最好选择在从节点上执行该命令,因为主节点上执行时,会**阻塞**主节点。 + +- 我们还可以使用 SCAN 命令来查找大 key; + +- 通过分析 RDB 文件来找出 big key,这种方案的前提是 Redis 采用的是 RDB 持久化。网上有现成的工具: + +- - redis-rdb-tools:Python 语言写的用来分析 Redis 的 RDB 快照文件用的工具 + - rdb_bigkeys:Go 语言写的用来分析 Redis 的 RDB 快照文件用的工具,性能更好。 + +### 删除大 key + +删除操作的本质是要释放键值对占用的内存空间。 + +释放内存只是第一步,为了更加高效地管理内存空间,在应用程序释放内存时,**操作系统需要把释放掉的内存块插入一个空闲内存块的链表**,以便后续进行管理和再分配。这个过程本身需要一定时间,而且会**阻塞**当前释放内存的应用程序。 + +所以,如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞,如果主线程发生了阻塞,其他所有请求可能都会超时,超时越来越多,会造成 Redis 连接耗尽,产生各种异常。 + +删除大 key 时建议采用分批次删除和异步删除的方式进行。 + +## 清空数据库 + +清空数据库和上面 bigkey 删除也是同样道理,`flushdb`、`flushall` 也涉及到删除和释放所有的键值对,也是 Redis 的阻塞点。 + +## 集群扩容 + +Redis 集群可以进行节点的动态扩容缩容,这一过程目前还处于半自动状态,需要人工介入。 + +在扩缩容的时候,需要进行数据迁移。而 Redis 为了保证迁移的一致性,迁移所有操作都是同步操作。 + +执行迁移时,两端的 Redis 均会进入时长不等的阻塞状态,对于小 Key,该时间可以忽略不计,但如果一旦 Key 的内存使用过大,严重的时候会触发集群内的故障转移,造成不必要的切换。 + diff --git a/docs/database/redis/redis-questions-01.md b/docs/database/redis/redis-questions-01.md index 7625d4fd..0c157cf8 100644 --- a/docs/database/redis/redis-questions-01.md +++ b/docs/database/redis/redis-questions-01.md @@ -524,9 +524,13 @@ Redis 提供 6 种数据淘汰策略: ### 怎么保证 Redis 挂掉之后再重启数据可以进行恢复? -很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。 +使用缓存的时候,我们经常需要对内容中的数据进行持久化也就是将内存中的数据写入到硬盘里面。大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。 -Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持两种不同的持久化操作。**Redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)**。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。 +Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持 3 种持久化方式: + +- 快照(snapshotting,RDB) +- 只追加文件(append-only file, AOF) +- RDB 和 AOF 的混合持久化(Redis 4.0 新增) ### 什么是 RDB 持久化? @@ -553,36 +557,52 @@ Redis 提供了两个命令来生成 RDB 快照文件: ### 什么是 AOF 持久化? -与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启: +与快照持久化相比,AOF 持久化的实时性更好。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化(Redis 6.0 之后已经默认是开启了),可以通过 appendonly 参数开启: ```bash appendonly yes ``` -开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。 +开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到AOF 缓冲区 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。 AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 `appendonly.aof`。 -在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是: +在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是: ```bash -appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 -appendfsync everysec #每秒钟同步一次,显式地将多个写命令同步到硬盘 -appendfsync no #让操作系统决定何时进行同步 +appendfsync always #每次有数据修改发生时都会调用fsync函数同步AOF文件,fsync完成后线程返回,这样会严重降低Redis的速度 +appendfsync everysec #每秒钟调用fsync函数同步一次AOF文件 +appendfsync no #让操作系统决定何时进行同步,一般为30秒一次 ``` -为了兼顾数据和写入性能,用户可以考虑 `appendfsync everysec` 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 +为了兼顾数据和写入性能,可以考虑 `appendfsync everysec` 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 -**相关 issue** : +从 Redis 7.0.0 开始,Redis 使用了 **Multi Part AOF** 机制。顾名思义,Multi Part AOF 就是将原来的单个 AOF 文件拆分成多个 AOF 文件。在 Multi Part AOF 中,AOF 文件被分为三种类型,分别为: -- [Redis 的 AOF 方式 #783](https://github.com/Snailclimb/JavaGuide/issues/783) -- [Redis AOF 重写描述不准确 #1439](https://github.com/Snailclimb/JavaGuide/issues/1439) +- BASE:表示基础 AOF 文件,它一般由子进程通过重写产生,该文件最多只有一个。 +- INCR:表示增量 AOF 文件,它一般会在 AOFRW 开始执行时被创建,该文件可能存在多个。 +- HISTORY:表示历史 AOF 文件,它由 BASE 和 INCR AOF 变化而来,每次 AOFRW 成功完成时,本次 AOFRW 之前对应的 BASE 和 INCR AOF 都将变为 HISTORY,HISTORY 类型的 AOF 会被 Redis 自动删除。 -### AOF 日志是如何实现的? +Multi Part AOF 不是重点,了解即可,详细介绍可以看看阿里开发者的[Redis 7.0 Multi Part AOF 的设计和实现](https://zhuanlan.zhihu.com/p/467217082) 这篇文章。 + +**相关 issue** :[Redis 的 AOF 方式 #783](https://github.com/Snailclimb/JavaGuide/issues/783)。 + +### AOF工作基本流程是怎样的? + +AOF 持久化功能的实现可以简单分为 4 步: + +1. 命令写入(append):所有的写命令会追加到 AOF 缓冲区中。 +2. 文件同步(sync):AOF缓冲区根据对应的 fsync 策略向硬盘做同步操作。 +3. 文件重写(rewrite):随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。 +4. 重启加载(load):当 Redis 重启时,可以加载 AOF 文件进行数据恢复。 + +![](https://oss.javaguide.cn/github/javaguide/database/redis/aof-work-process.png) + +### AOF 为什么是在执行完命令之后记录日志? 关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。 -![AOF 记录日志过程](./images/redis-aof-write-log-disc.png) +![AOF 记录日志过程](https://oss.javaguide.cn/github/javaguide/database/redis/redis-aof-write-log-disc.png) **为什么是在执行完命令之后记录日志呢?** @@ -604,6 +624,18 @@ AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的 Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。 +**相关 issue** :[Redis AOF 重写描述不准确 #1439](https://github.com/Snailclimb/JavaGuide/issues/1439) + +### Redis 4.0 对于持久化机制做了什么优化? + +由于 RDB 和 AOF 各有优势,于是,Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。 + +如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。 + +官方文档地址:https://redis.io/topics/persistence + +![](https://oss.javaguide.cn/github/javaguide/database/redis/redis4.0-persitence.png) + ### 如何选择 RDB 和 AOF? 关于 RDB 和 AOF 的优缺点,官网上面也给了比较详细的说明[Redis persistence](https://redis.io/docs/manual/persistence/),这里结合自己的理解简单总结一下。 @@ -619,15 +651,11 @@ Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使 - RDB 文件是以特定的二进制格式保存的,并且在 Redis 版本演进中有多个版本的 RDB,所以存在老版本的 Redis 服务不兼容新版本的 RDB 格式的问题。 - AOF 以一种易于理解和解析的格式包含所有操作的日志。你可以轻松地导出 AOF 文件进行分析,你也可以直接操作 AOF 文件来解决一些问题。比如,如果执行`FLUSHALL`命令意外地刷新了所有内容后,只要 AOF 文件没有被重写,删除最新命令并重启即可恢复之前的状态。 -### Redis 4.0 对于持久化机制做了什么优化? +综上: -由于 RDB 和 AOF 各有优势,于是,Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。 - -如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。 - -官方文档地址:https://redis.io/topics/persistence - -![](https://oss.javaguide.cn/github/javaguide/database/redis/redis4.0-persitence.png) +- Redis 保存的数据丢失一些也没什么影响的话,可以选择使用 RDB。 +- 不建议单独使用 AOF,因为时不时地创建一个 RDB 快照可以进行数据库备份、更快的重启以及解决 AOF 引擎错误。 +- 如果保存的数据要求安全性比较高的话,建议同时开启 RDB 和 AOF 持久化或者开启 RDB 和 AOF 混合持久化。 ## 参考 @@ -636,3 +664,4 @@ Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使 - Redis 命令手册:https://www.redis.com.cn/commands.html - WHY Redis choose single thread (vs multi threads): [https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153](https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153) - The difference between AOF and RDB persistence:https://www.sobyte.net/post/2022-04/redis-rdb-and-aof/ +- Redis AOF 持久化详解 - 程序员历小冰:http://remcarpediem.net/article/376c55d8/ diff --git a/docs/database/redis/redis-questions-02.md b/docs/database/redis/redis-questions-02.md index 45be64ab..9651d36a 100644 --- a/docs/database/redis/redis-questions-02.md +++ b/docs/database/redis/redis-questions-02.md @@ -432,7 +432,11 @@ Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删 1. **缓存失效时间变短(不推荐,治标不治本)** :我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。 2. **增加 cache 更新重试机制(常用)**: 如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的 key 删除即可。 -相关文章推荐:[缓存和数据库一致性问题,看这篇就够了 - 水滴与银弹](https://mp.weixin.qq.com/s?__biz=MzIyOTYxNDI5OA==&mid=2247487312&idx=1&sn=fa19566f5729d6598155b5c676eee62d&chksm=e8beb8e5dfc931f3e35655da9da0b61c79f2843101c130cf38996446975014f958a6481aacf1&scene=178&cur_album_id=1699766580538032128#rd) +相关文章推荐:[缓存和数据库一致性问题,看这篇就够了 - 水滴与银弹](https://mp.weixin.qq.com/s?__biz=MzIyOTYxNDI5OA==&mid=2247487312&idx=1&sn=fa19566f5729d6598155b5c676eee62d&chksm=e8beb8e5dfc931f3e35655da9da0b61c79f2843101c130cf38996446975014f958a6481aacf1&scene=178&cur_album_id=1699766580538032128#rd)。 + +### 哪些情况可能会导致 Redis 阻塞? + +单独抽了一篇文章来总结可能会导致 Redis 阻塞的情况:[Redis 常见阻塞原因总结](./redis-memory-fragmentation.md)。 ## Redis 集群 diff --git a/docs/distributed-system/images/distributed-lock/distributed-lock-redisson-renew-expiration.drawio b/docs/distributed-system/images/distributed-lock/distributed-lock-redisson-renew-expiration.drawio index 43a096b3..8493d73f 100644 --- a/docs/distributed-system/images/distributed-lock/distributed-lock-redisson-renew-expiration.drawio +++ b/docs/distributed-system/images/distributed-lock/distributed-lock-redisson-renew-expiration.drawio @@ -1 +1 @@ -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 \ No newline at end of file +7VxZd6LKFv41rHXOQ7IKKBAeGTSxj5DJdNq8ISKCAx7FAX793VUMyqCxO5qk+57OWh1qoIY9f7uKMLw23d4srPnICAbOhOHQYMvwOsNxLItE+EVqoqSmIclJhbvwBmmnXcWTFztpJUprV97AWRY6hkEwCb15sdIOZjPHDgt11mIRbIrdhsGkOOvccp1KxZNtTaq1L94gHGX7EuVdw63juaNsalHASUvfssfuIljN0gkZjm+JrVZLSpqnVjZYutPlyBoEm70qvsnw2iIIwuRputWcCSFuRrfkvdaB1nzhC2cWnvJCxM6d5+Wt1n/d3Pz7Q9Xix/DqKhtmGUYZRZwBECgtBotwFLjBzJo0d7WqvVqsHTIqC4Vdl04QzNNK3wnDKGW2tQoDqBqF00naugwXwTgnNw81w2AWpt1ZAcrVvWXrDFYL2zmyIXiddiS72HszJcmNE0ydcBFBh4UzsUJvXRQDK5UmN++Xv3ofeLAWDqWSz7PidTpVKvkSQsVBQmvhOmH63o4x8LC3kF0VZddPsC7b6dqarJxMBEu8pOKZs2oz8kLnaW5RAm5An99kizeZaMEkWNDR+KFkO7ad98xaZsHMOcaztbMIne1RZqStua6kJBVwIyPyZk85ZT6pG+3rJYcOs7BA+p+m84eTeTjkPpTMnIw+m8hchcjfvhtQwTQlRm0xsso0G4wiMZJaIT5sOqyjcIlu+0ROq6yJ586gaAMpHahXCQk98A1K2jD1BgNq8epYumM6Klkw6UxsEhsFNvGoyiZZqHLpYkzi/zBf8WGuoshHQfhYR4ErujUJ7PE1+e+vv2tZ2rH6EOYV+HC6riycpRdbfToe0Yw52RfdqaAygl7DwQmZTs3DqTcN3jHhrKhXHjqmSypEX3Vqd4WuITot+vbUPL1TEDBbChkyF54NEQyHS+ciMlAXK4iTMGVGQQLEf1dB1nC1pGxSoAMrzLe7Rnhyye9v1toipllViJk+y5B7lj4ZDjacjJi0swfqL+yVJce2HPGSXhnjknjwuMYvY1y1+GWDcr7Yp2o7KmQGXDMnj96UQq3cMFAjch8svdALiIHoB2EYTA+rexL5wL8a4xISb6Fay3kCAYfelvBVpVMqWS3KauB5YIUWCFlS5FrLtctw6ha4zWn3tyb3Gqm4/7Jd2THyrNtHZOvBusMP+EEk8EYkrO2pvTZ8ZWNocjyY2l77dhT2b4T4bjZaWi/C4v7pWzC4fdzcedIa3uI7MzvuTOXoNZK2d92x0OGTfm1PRfdP7a3p90h9fKePt6bei6He79+0Yjr/i7nue213MJ1MBujb2tGRZ2jKpq03N4b/4BpdJTI10v49srnJuu9D+xPewhibzstkDONHRuwiKPPWyyOy4H1Td732zWhivQyCQVq+97eb3o/HoH3zILfHaGt2jZXpKcj0xysjbkYD/4G7e8KxGb8GZtzkOv7oBdYqdHxja4zdpP9sbppkTTCfpbuCOetxpq62Tf/xhdQ5nhqauhJ3/Db0acO746gXocj0MG9226HRfViZcRv6PKzuNIQMD8eG/pCVgfZYMDxl0+k+8B2/uTE1BRka3hq+G97pz3SdbZi34/f2yg9QfuDJe2kbWd8K+Iez8q6vAm3NlenD/rpN3PEVdKcpLOx7a8S9dB1Kso5Zb2t0n1d33R7udR9Qx3fdtH1Dx3lCGN7j2rpKxtqaT1gw/ZHY7Rox7AvmNrB548Zm112ZrV5kdl99Q0OCqeGI0OBOV2CNz+RdHvbI3eku0MeG9RI62RtYNwaaCHcajKsDzTXgWQy88hQe6ImMbi/r7+b9b2HNdA3PW0KPXtyE/WGUrLnNwTOsC9aqYRZ4H5m+S/ZD1sYZ3TZ+0tsI9h5999usOTWAD00EsoaADzAurBfWTdacjdXpQr9ZALL2Ou/fbOS2Z057vssaugI6o4TWC8ia3wa5f/Y6MZY6POhejIm2n8dmysXQSuSqFpOvCZF54VIGs1E1mE2BkRRGASwjMzJmJJZpiowkMYC6kia59RuAGvFcoEYuBsPSJ4Ma7oQ0ijMbKCTXSMg6sZZLzz4bSHkTfOzRJXPzda7/3aEpX1Qk1CgOkYCpCkapDCTIbwx0YbDDybWI5suC1IQcx3DNqYLyTgmQEfolxlUGaki/Jkpng7vyQayznFuzWmCyy+Vf2Yk5Jfhk4fb/4rBE0lAcrAVxZOjsWUB/12EXsOwyozRI3kphGYl07aysNJMlYUZWSB9ZYxSN4iaVUfS0Rm7tIZtkqQeQzQdj9HrTf1T9zgLDEeaK7p19n627PMzmatz/W7JXBsViPSgWiKyoYpYQbbFoyTRbjKqRUAIiC2iVJSp2TUbBWZOcTd9fHJHRqiCSB56RoAYzqk4qTxTN3ywVLsilE4cGe82deOTQ4K4vFUbyNTHIF/BiQOdF9IPYjOu82EtNCC3o20IpSksHGfVmipY/2U3KH+QmpWKOpiF/bDKXz/Kc+wBDJKqvVFHEJ+RyT/QTmYCfxU+wXIEl2WHS13UTwmeo968q73GzcBQWnk+9hbNHwe+zzzWHwrkWgqsGo6CI1HdKJDVOPK5GgX+eAchS5gePNRejYNpfLd92omUNLHrgikN1xIs6VFku4XuRqzvCb9RA2bIlPR+3xM9VNyyyBYW7lkEIjisdLd07Cw8oQEz0CZpIlXrrhT+yIeC5t1N3KO3mIoUzOGf5RO3NwuJLO2dJksqXcsqZvguDT/4w+Dz5VOwAAKiL0UtZxDTo107DHENr6k2iZFJosqZzOh/PY2pF+gGIdLk6x79La7a8WoKADg+AFcRIYsHeAfIF/EvgC6xboNtoUECTP8j0AacgRm3SPevkObekHOIRBdMNAmBk7mcgUAW6fMgxYn3etuRBS1baGQzFoXwmgyxx142SUmT6uGeROZTb6X2bLF7KJuOvd5nkfaEM/qA8XfXioVxm0qXvk1z+Rtybhx00lOk3EH+mU4nSjTgJcdWYpS4HcLGTd8zVEHnfqh9O5lBbSfKMybkTe2rq5s89c8Il7rJV+8cKNdy92KETPuFexW9+dze3Sp+nQjUnexAlSA2iFiTqwDTGyEKU/1t1kBs1AO1j1UE8T9RMg7qaYBTCTSUxjBoJMUkNxIaNNEKUFXKL7YZ8iLKfLX/XnDRwlWncq0BUKudT+TAV/ebl2p6dGoF+DUk8fkuzEtFCec/8qPSHjhla6YUwCmnHTmiPskL2hcxRWPoTyX2hJOg1F6hZqSYTwV5K0IW6vFEV2AkEvqgsBUuYUYSP8g1lJl7KV7A5hXOP/NnOQqjJq7+FoktHxcLudFjcPTakv8kzhc7nhd37g+4ZJenQ+aECoWKSpmwSQase8SWWksgdJlgbDBcRQIHCdpHUyGgPyFcymOnJpJQdcCt1walAwLmk7SUv9jIDCryu51mMF4uYBg7pgUshf2K+lTTAlRKFEanmCGRCFe2dkoJXF+jqajMQLRIFFA47SxmILJGy+/wEZkRZeJ0sk663kNTNF5YvVSDvKmgvFVzNf+Qjw4AcpXo1SOFpH+7XQ/lzeQL2pzwBd4r5OWe2A+Pi5RMs1H1Bll+sPnf6+WH1z7j5TxD0Dd0dPD6N7IZo/okfkPFF8133ZdLlPiCrJXIVLv/3ARnXKH1AVuNlL3XXspZJh33sf1+hfJWvUITKVyhCjXKf6SsUKO4+gE/yk7s/M8A3/wc= \ No newline at end of file diff --git a/docs/java/concurrent/java-concurrent-questions-02.md b/docs/java/concurrent/java-concurrent-questions-02.md index 4ffad45e..b39a17fa 100644 --- a/docs/java/concurrent/java-concurrent-questions-02.md +++ b/docs/java/concurrent/java-concurrent-questions-02.md @@ -204,6 +204,8 @@ try { 在 Java 中`java.util.concurrent.atomic`包下面的原子变量类(比如`AtomicInteger`、`LongAdder`)就是使用了乐观锁的一种实现方式 **CAS** 实现的。 +![JUC原子类概览](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JUC原子类概览.png) + ```java // LongAdder 在高并发场景下会比 AtomicInteger 和 AtomicLong 的性能更好 // 代价就是会消耗更多的内存空间(空间换时间) @@ -211,10 +213,15 @@ LongAdder sum = new LongAdder(); sum.increment(); ``` -高并发的场景下,乐观锁相比悲观锁来说,不存在锁竞争造成线程阻塞,也不会有死锁的问题,在性能上往往会更胜一筹。不过,如果冲突频繁发生(写占比非常多的情况),会频繁失败和重试,这样同样会非常影响性能,导致 CPU 飙升。 +高并发的场景下,乐观锁相比悲观锁来说,不存在锁竞争造成线程阻塞,也不会有死锁的问题,在性能上往往会更胜一筹。但是,如果冲突频繁发生(写占比非常多的情况),会频繁失败和重试,这样同样会非常影响性能,导致 CPU 飙升。 不过,大量失败重试的问题也是可以解决的,像我们前面提到的 `LongAdder`以空间换时间的方式就解决了这个问题。 +理论上来说: + +- 悲观锁通常多用于写比较多的情况下(多写场景,竞争激烈),这样可以避免频繁失败和重试影响性能,悲观锁的开销是固定的。不过,如果乐观锁解决了频繁失败和重试这个问题的话(比如`LongAdder`),也是可以考虑使用乐观锁的,要视实际情况而定。 +- 乐观锁通常多于写比较少的情况下(多读场景,竞争较少),这样可以避免频繁加锁影响性能。不过,乐观锁主要针对的对象是单个共享变量(参考`java.util.concurrent.atomic`包下面的原子变量类)。 + ### 如何实现乐观锁? 乐观锁一般会使用版本号机制或 CAS 算法实现,CAS 算法相对来说更多一些,这里需要格外注意。 diff --git a/docs/java/concurrent/optimistic-lock-and-pessimistic-lock.md b/docs/java/concurrent/optimistic-lock-and-pessimistic-lock.md index 85c58147..930dfac1 100644 --- a/docs/java/concurrent/optimistic-lock-and-pessimistic-lock.md +++ b/docs/java/concurrent/optimistic-lock-and-pessimistic-lock.md @@ -37,19 +37,29 @@ try { 乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。 -在 Java 中`java.util.concurrent.atomic`包下面的原子变量类(比如`AtomicInteger`、`LongAdder`)就是使用了乐观锁的一种实现方式 **CAS** 实现的。 +像 Java 中`java.util.concurrent.atomic`包下面的原子变量类(比如`AtomicInteger`、`LongAdder`)就是使用了乐观锁的一种实现方式 **CAS** 实现的。 + +![JUC原子类概览](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JUC原子类概览.png) ```java // LongAdder 在高并发场景下会比 AtomicInteger 和 AtomicLong 的性能更好 // 代价就是会消耗更多的内存空间(空间换时间) -LongAdder sum = new LongAdder(); -sum.increment(); +LongAdder longAdder = new LongAdder(); +// 自增 +longAdder.increment(); +// 获取结果 +longAdder.sum(); ``` -高并发的场景下,乐观锁相比悲观锁来说,不存在锁竞争造成线程阻塞,也不会有死锁的问题,在性能上往往会更胜一筹。不过,如果冲突频繁发生(写占比非常多的情况),会频繁失败和重试,这样同样会非常影响性能,导致 CPU 飙升。 +高并发的场景下,乐观锁相比悲观锁来说,不存在锁竞争造成线程阻塞,也不会有死锁的问题,在性能上往往会更胜一筹。但是,如果冲突频繁发生(写占比非常多的情况),会频繁失败和重试(悲观锁的开销是固定的),这样同样会非常影响性能,导致 CPU 飙升。 不过,大量失败重试的问题也是可以解决的,像我们前面提到的 `LongAdder`以空间换时间的方式就解决了这个问题。 +理论上来说: + +- 悲观锁通常多用于写比较多的情况下(多写场景,竞争激烈),这样可以避免频繁失败和重试影响性能,悲观锁的开销是固定的。不过,如果乐观锁解决了频繁失败和重试这个问题的话(比如`LongAdder`),也是可以考虑使用乐观锁的,要视实际情况而定。 +- 乐观锁通常多于写比较少的情况下(多读场景,竞争较少),这样可以避免频繁加锁影响性能。不过,乐观锁主要针对的对象是单个共享变量(参考`java.util.concurrent.atomic`包下面的原子变量类)。 + ## 如何实现乐观锁? 乐观锁一般会使用版本号机制或 CAS 算法实现,CAS 算法相对来说更多一些,这里需要格外注意。 @@ -150,7 +160,15 @@ CAS 经常会用到自旋操作来进行重试,也就是不成功就一直循 CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5 开始,提供了`AtomicReference`类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用`AtomicReference`类把多个共享变量合并成一个共享变量来操作。 +## 总结 + +- 高并发的场景下,激烈的锁竞争会造成线程阻塞,大量阻塞线程会导致系统的上下文切换,增加系统的性能开销。并且,悲观锁还可能会存在死锁问题,影响代码的正常运行。乐观锁相比悲观锁来说,不存在锁竞争造成线程阻塞,也不会有死锁的问题,在性能上往往会更胜一筹。不过,如果冲突频繁发生(写占比非常多的情况),会频繁失败和重试,这样同样会非常影响性能,导致 CPU 飙升。 +- 乐观锁一般会使用版本号机制或 CAS 算法实现,CAS 算法相对来说更多一些,这里需要格外注意。 +- CAS 的全称是 **Compare And Swap(比较与交换)** ,用于实现乐观锁,被广泛应用于各大框架中。CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。 +- 乐观锁的问题:ABA 问题、循环时间长开销大、只能保证一个共享变量的原子操作。 + ## 参考 +- 《Java 并发编程核心 78 讲》 - 通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其 Java 实现!:https://zhuanlan.zhihu.com/p/71156910 - 一文彻底搞懂CAS实现原理 & 深入到CPU指令:https://zhuanlan.zhihu.com/p/94976168