From c59f7652271c2c209865e89b41ee18df1ba24db6 Mon Sep 17 00:00:00 2001 From: Guide Date: Sun, 17 Aug 2025 16:34:26 +0800 Subject: [PATCH] =?UTF-8?q?update:=20Spring=20=E9=83=A8=E5=88=86=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/database/redis/redis-persistence.md | 8 +-- docs/database/sql/sql-questions-05.md | 53 ++++++++++++++----- .../spring-knowledge-and-questions-summary.md | 41 +++++++++----- 3 files changed, 72 insertions(+), 30 deletions(-) diff --git a/docs/database/redis/redis-persistence.md b/docs/database/redis/redis-persistence.md index c17fe7db..2b61a125 100644 --- a/docs/database/redis/redis-persistence.md +++ b/docs/database/redis/redis-persistence.md @@ -71,7 +71,7 @@ AOF 持久化功能的实现可以简单分为 5 步: 1. **命令追加(append)**:所有的写命令会追加到 AOF 缓冲区中。 2. **文件写入(write)**:将 AOF 缓冲区的数据写入到 AOF 文件中。这一步需要调用`write`函数(系统调用),`write`将数据写入到了系统内核缓冲区之后直接返回了(延迟写)。注意!!!此时并没有同步到磁盘。 -3. **文件同步(fsync)**:AOF 缓冲区根据对应的持久化方式( `fsync` 策略)向硬盘做同步操作。这一步需要调用 `fsync` 函数(系统调用), `fsync` 针对单个文件操作,对其进行强制硬盘同步,`fsync` 将阻塞直到写入磁盘完成后返回,保证了数据持久化。 +3. **文件同步(fsync)**:这一步才是持久化的核心!根据你在 `redis.conf` 文件里 `appendfsync` 配置的策略,Redis 会在不同的时机,调用 `fsync` 函数(系统调用)。`fsync` 针对单个文件操作,对其进行强制硬盘同步(文件在内核缓冲区里的数据写到硬盘),`fsync` 将阻塞直到写入磁盘完成后返回,保证了数据持久化。 4. **文件重写(rewrite)**:随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。 5. **重启加载(load)**:当 Redis 重启时,可以加载 AOF 文件进行数据恢复。 @@ -90,9 +90,9 @@ AOF 工作流程图如下: 在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是: -1. `appendfsync always`:主线程调用 `write` 执行写操作后,后台线程( `aof_fsync` 线程)立即会调用 `fsync` 函数同步 AOF 文件(刷盘),`fsync` 完成后线程返回,这样会严重降低 Redis 的性能(`write` + `fsync`)。 -2. `appendfsync everysec`:主线程调用 `write` 执行写操作后立即返回,由后台线程( `aof_fsync` 线程)每秒钟调用 `fsync` 函数(系统调用)同步一次 AOF 文件(`write`+`fsync`,`fsync`间隔为 1 秒) -3. `appendfsync no`:主线程调用 `write` 执行写操作后立即返回,让操作系统决定何时进行同步,Linux 下一般为 30 秒一次(`write`但不`fsync`,`fsync` 的时机由操作系统决定)。 +1. `appendfsync always`:主线程调用 `write` 执行写操作后,会立刻调用 `fsync` 函数同步 AOF 文件(刷盘)。主线程会阻塞,直到 `fsync` 将数据完全刷到磁盘后才会返回。这种方式数据最安全,理论上不会有任何数据丢失。但因为每个写操作都会同步阻塞主线程,所以性能极差。 +2. `appendfsync everysec`:主线程调用 `write` 执行写操作后立即返回,由后台线程( `aof_fsync` 线程)每秒钟调用 `fsync` 函数(系统调用)同步一次 AOF 文件(`write`+`fsync`,`fsync`间隔为 1 秒)。这种方式主线程的性能基本不受影响。在性能和数据安全之间做出了绝佳的平衡。不过,在 Redis 异常宕机时,最多可能丢失最近 1 秒内的数据。 +3. `appendfsync no`:主线程调用 `write` 执行写操作后立即返回,让操作系统决定何时进行同步,Linux 下一般为 30 秒一次(`write`但不`fsync`,`fsync` 的时机由操作系统决定)。 这种方式性能最好,因为避免了 `fsync` 的阻塞。但数据安全性最差,宕机时丢失的数据量不可控,取决于操作系统上一次同步的时间点。 可以看出:**这 3 种持久化方式的主要区别在于 `fsync` 同步 AOF 文件的时机(刷盘)**。 diff --git a/docs/database/sql/sql-questions-05.md b/docs/database/sql/sql-questions-05.md index c20af2ca..88084ddd 100644 --- a/docs/database/sql/sql-questions-05.md +++ b/docs/database/sql/sql-questions-05.md @@ -41,26 +41,53 @@ tag: 写法 1: ```sql -SELECT exam_id, - count(submit_time IS NULL OR NULL) incomplete_cnt, - ROUND(count(submit_time IS NULL OR NULL) / count(*), 3) complete_rate -FROM exam_record -GROUP BY exam_id -HAVING incomplete_cnt <> 0 +SELECT + exam_id, + (COUNT(*) - COUNT(submit_time)) AS incomplete_cnt, + ROUND((COUNT(*) - COUNT(submit_time)) / COUNT(*), 3) AS incomplete_rate +FROM + exam_record +GROUP BY + exam_id +HAVING + (COUNT(*) - COUNT(submit_time)) > 0; ``` +利用 `COUNT(*) `统计分组内的总记录数,`COUNT(submit_time)` 只统计 `submit_time` 字段不为 NULL 的记录数(即已完成数)。两者相减,就是未完成数。 + 写法 2: ```sql -SELECT exam_id, - count(submit_time IS NULL OR NULL) incomplete_cnt, - ROUND(count(submit_time IS NULL OR NULL) / count(*), 3) complete_rate -FROM exam_record -GROUP BY exam_id -HAVING incomplete_cnt <> 0 +SELECT + exam_id, + COUNT(CASE WHEN submit_time IS NULL THEN 1 END) AS incomplete_cnt, + ROUND(COUNT(CASE WHEN submit_time IS NULL THEN 1 END) / COUNT(*), 3) AS incomplete_rate +FROM + exam_record +GROUP BY + exam_id +HAVING + COUNT(CASE WHEN submit_time IS NULL THEN 1 END) > 0; ``` -两种写法都可以,只有中间的写法不一样,一个是对符合条件的才`COUNT`,一个是直接上`IF`,后者更为直观,最后这个`having`解释一下, 无论是 `complete_rate` 还是 `incomplete_cnt`,只要不为 0 即可,不为 0 就意味着有未完成的。 +使用 `CASE` 表达式,当条件满足时返回一个非 `NULL` 值(例如 1),否则返回 `NULL`。然后用 `COUNT` 函数来统计非 `NULL` 值的数量。 + +写法 3: + +```sql +SELECT + exam_id, + SUM(submit_time IS NULL) AS incomplete_cnt, + ROUND(SUM(submit_time IS NULL) / COUNT(*), 3) AS incomplete_rate +FROM + exam_record +GROUP BY + exam_id +HAVING + incomplete_cnt > 0; +``` + +利用 `SUM` 函数对一个表达式求和。当 `submit_time` 为 `NULL` 时,表达式 `(submit_time IS NULL)` 的值为 1 (TRUE),否则为 0 (FALSE)。将这些 1 和 0 加起来,就得到了未完成的数量。 ### 0 级用户高难度试卷的平均用时和平均得分 diff --git a/docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md b/docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md index eab9117a..c61ad792 100644 --- a/docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md +++ b/docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md @@ -110,29 +110,44 @@ Spring Boot 只是简化了配置,如果你需要构建 MVC 架构的 Web 程 ## Spring IoC -### 谈谈自己对于 Spring IoC 的了解 +### 什么是 IoC? -**IoC(Inversion of Control:控制反转)** 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。 +IoC (Inversion of Control )即控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java 开发领域对象的创建以及管理的问题。 -**为什么叫控制反转?** +例如:现有类 A 依赖于类 B -- **控制**:指的是对象创建(实例化、管理)的权力 -- **反转**:控制权交给外部环境(Spring 框架、IoC 容器) +- **传统的开发方式** :往往是在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来 +- **使用 IoC 思想的开发方式** :不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面去取即可。 -![IoC 图解](https://oss.javaguide.cn/java-guide-blog/frc-365faceb5697f04f31399937c059c162.png) +从以上两种开发方式的对比来看:我们 “丧失了一个权力” (创建、管理对象的权力),从而也得到了一个好处(不用再考虑对象的创建、管理等一系列的事情) -将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 +**为什么叫控制反转?** -在实际项目中一个 Service 类可能依赖了很多其他的类,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。 +- **控制** :指的是对象创建(实例化、管理)的权力 +- **反转** :控制权交给外部环境(IoC 容器) -在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。 +![IoC 图解](https://oss.javaguide.cn/github/javaguide/system-design/framework/spring/IoC&Aop-ioc-illustration.png) -Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。 +### IoC 解决了什么问题? -相关阅读: +IoC 的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢? -- [IoC 源码阅读](https://javadoop.com/post/spring-ioc) -- [IoC & AOP 详解(快速搞懂)](./ioc-and-aop.md) +1. 对象之间的耦合度或者说依赖程度降低; +2. 资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。 + +例如:现有一个针对 User 的操作,利用 Service 和 Dao 两层结构进行开发 + +在没有使用 IoC 思想的情况下,Service 层想要使用 Dao 层的具体实现的话,需要通过 new 关键字在`UserServiceImpl` 中手动 new 出 `IUserDao` 的具体实现类 `UserDaoImpl`(不能直接 new 接口类)。 + +很完美,这种方式也是可以实现的,但是我们想象一下如下场景: + +开发过程中突然接到一个新的需求,针对`IUserDao` 接口开发出另一个具体实现类。因为 Server 层依赖了`IUserDao`的具体实现,所以我们需要修改`UserServiceImpl`中 new 的对象。如果只有一个类引用了`IUserDao`的具体实现,可能觉得还好,修改起来也不是很费力气,但是如果有许许多多的地方都引用了`IUserDao`的具体实现的话,一旦需要更换`IUserDao` 的实现方式,那修改起来将会非常的头疼。 + +![IoC&Aop-ioc-illustration-dao-service](https://oss.javaguide.cn/github/javaguide/system-design/framework/spring/IoC&Aop-ioc-illustration-dao-service.png) + +使用 IoC 的思想,我们将对象的控制权(创建、管理)交由 IoC 容器去管理,我们在使用的时候直接向 IoC 容器 “要” 就可以了 + +![](https://oss.javaguide.cn/github/javaguide/system-design/framework/spring/IoC&Aop-ioc-illustration-dao.png) ### 什么是 Spring Bean?