diff --git a/README.md b/README.md index 6ba56ffbbe3..7cf58b258f0 100644 --- a/README.md +++ b/README.md @@ -137,18 +137,19 @@ JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8 ](https://docs.oracle **总结:** -1. **[MySQL知识点总结](docs/database/MySQL.md)** (必看 :+1:) -2. [阿里巴巴开发手册数据库部分的一些最佳实践](docs/database/阿里巴巴开发手册数据库部分的一些最佳实践.md) -3. [一千行 MySQL 学习笔记](docs/database/一千行MySQL命令.md) -4. [MySQL 高性能优化规范建议](docs/database/MySQL高性能优化规范建议.md) +1. [数据库基础知识总结](docs/database/数据库基础知识.md) +2. **[MySQL知识点总结](docs/database/mysql/MySQL总结.md)** (必看 :+1:) +3. [阿里巴巴开发手册数据库部分的一些最佳实践](docs/database/mysql/阿里巴巴开发手册数据库部分的一些最佳实践.md) +4. [一千行 MySQL 学习笔记](docs/database/mysql/一千行MySQL学习笔记.md) +5. [MySQL 高性能优化规范建议](docs/database/mysql/MySQL高性能优化规范建议.md) **重要知识点:** -1. [MySQL数据库索引总结](docs/database/MySQL数据库索引.md) -2. [事务隔离级别(图文详解)]() -3. [一条 SQL 语句在 MySQL 中如何执行的](docs/database/一条sql语句在mysql中如何执行的.md) -4. [关于数据库中如何存储时间的一点思考](docs/database/关于数据库存储时间的一点思考.md) -5. [InnoDB存储引擎对MVCC的实现](docs/database/InnoDB对MVCC的实现.md) +1. [MySQL数据库索引总结](docs/database/mysql/MySQL数据库索引.md) +2. [事务隔离级别(图文详解)](docs/database/mysql/事务隔离级别(图文详解).md) +3. [InnoDB存储引擎对MVCC的实现](docs/database/mysql/InnoDB对MVCC的实现.md) +4. [一条 SQL 语句在 MySQL 中如何执行的](docs/database/mysql/一条sql语句在mysql中如何执行的.md) +5. [关于数据库中如何存储时间的一点思考](docs/database/mysql/关于数据库存储时间的一点思考.md) ### Redis diff --git a/docs/database/Redis/redis-all.md b/docs/database/Redis/redis-all.md index 3cd8ff627dc..64ea49d563a 100644 --- a/docs/database/Redis/redis-all.md +++ b/docs/database/Redis/redis-all.md @@ -595,7 +595,9 @@ save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生 appendonly yes ``` -开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入硬盘中的 AOF 文件。AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。 +开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。 + +AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 `appendonly.aof`。 在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是: @@ -605,7 +607,7 @@ appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步 appendfsync no #让操作系统决定何时进行同步 ``` -为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 +为了兼顾数据和写入性能,用户可以考虑 `appendfsync everysec` 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 **相关 issue** :[783:Redis 的 AOF 方式](https://github.com/Snailclimb/JavaGuide/issues/783) @@ -615,6 +617,10 @@ Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通 如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。 +官方文档地址:https://redis.io/topics/persistence + +![](https://cdn.jsdelivr.net/gh/javaguide-tech/image-host-github-stars-01@main/webfunny_monitor/image-20210807145107290.png) + **补充内容:AOF 重写** AOF 重写可以产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。 diff --git "a/docs/database/InnoDB\345\257\271MVCC\347\232\204\345\256\236\347\216\260.md" "b/docs/database/mysql/InnoDB\345\257\271MVCC\347\232\204\345\256\236\347\216\260.md" similarity index 52% rename from "docs/database/InnoDB\345\257\271MVCC\347\232\204\345\256\236\347\216\260.md" rename to "docs/database/mysql/InnoDB\345\257\271MVCC\347\232\204\345\256\236\347\216\260.md" index bb2a70b0cdb..e846088dbc3 100644 --- "a/docs/database/InnoDB\345\257\271MVCC\347\232\204\345\256\236\347\216\260.md" +++ "b/docs/database/mysql/InnoDB\345\257\271MVCC\347\232\204\345\256\236\347\216\260.md" @@ -1,130 +1,100 @@ - [一致性非锁定读和锁定读](#一致性非锁定读和锁定读) - - [一致性非锁定读](#一致性非锁定读) - - [锁定读](#锁定读) -- [InnoDB对MVCC的实现](#InnoDB对MVCC的实现) - - [隐藏字段](#隐藏字段]) - - [ReadView](#ReadView) - - [undo-log](#undo-log) - - [数据可见性算法](#数据可见性算法) -- [RC、RR隔离级别下MVCC的差异](#RC、RR隔离级别下MVCC的差异) -- [MVCC解决不可重复读问题](#MVCC解决不可重复读问题) - - [在RC下ReadView生成情况](#在RC下ReadView生成情况) - - [在RR下ReadView生成情况](#在RR下ReadView生成情况) -- [MVCC+Next-key-Lock防止幻读](#MVCC➕Next-key-Lock防止幻读) + - [一致性非锁定读](#一致性非锁定读) + - [锁定读](#锁定读) +- [InnoDB 对 MVCC 的实现](#InnoDB对MVCC的实现) + - [隐藏字段](#隐藏字段]) + - [ReadView](#ReadView) + - [undo-log](#undo-log) + - [数据可见性算法](#数据可见性算法) +- [RC、RR 隔离级别下 MVCC 的差异](#RC、RR隔离级别下MVCC的差异) +- [MVCC 解决不可重复读问题](#MVCC解决不可重复读问题) + - [在 RC 下 ReadView 生成情况](#在RC下ReadView生成情况) + - [在 RR 下 ReadView 生成情况](#在RR下ReadView生成情况) +- [MVCC+Next-key-Lock 防止幻读](#MVCC➕Next-key-Lock防止幻读) - - ## 一致性非锁定读和锁定读 -#### 一致性非锁定读 +### 一致性非锁定读 -对于 [**一致性非锁定读(Consistent Nonlocking Reads)** ](https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html)的实现,通常做法是加一个版本号或者时间戳字段,在更新数据的同时版本号 + 1或者更新时间戳。查询时,将当前可见的版本号与对应记录的版本号进行比对,如果记录的版本小于可见版本,则表示该记录可见 +对于 [**一致性非锁定读(Consistent Nonlocking Reads)** ](https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html)的实现,通常做法是加一个版本号或者时间戳字段,在更新数据的同时版本号 + 1 或者更新时间戳。查询时,将当前可见的版本号与对应记录的版本号进行比对,如果记录的版本小于可见版本,则表示该记录可见 在 `InnoDB` 存储引擎中,[多版本控制 (multi versioning)](https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html) 就是对非锁定读的实现。如果读取的行正在执行 `DELETE` 或 `UPDATE` 操作,这时读取操作不会去等待行上锁的释放。相反地,`InnoDB` 存储引擎会去读取行的一个快照数据,对于这种读取历史数据的方式,我们叫它快照读 (snapshot read) -在 `Repeatable Read` 和 `Read Committed` 两个隔离级别下,如果是执行普通的 `select` 语句(不包括 `select ... lock in share mode` ,` select ... for update`)则会使用 `一致性非锁定读(MVCC)`。并且在 `Repeatable Read` 下 `MVCC` 实现了可重复读和防止部分幻读 - - +在 `Repeatable Read` 和 `Read Committed` 两个隔离级别下,如果是执行普通的 `select` 语句(不包括 `select ... lock in share mode` ,`select ... for update`)则会使用 `一致性非锁定读(MVCC)`。并且在 `Repeatable Read` 下 `MVCC` 实现了可重复读和防止部分幻读 -#### 锁定读 +### 锁定读 如果执行的是下列语句,就是 [**锁定读(Locking Reads)**](https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html) -- select ... lock in share mode -- select ... for update -- insert、update、delete 操作 - - +- `select ... lock in share mode` +- `select ... for update` +- `insert`、`update`、`delete` 操作 在锁定读下,读取的是数据的最新版本,这种读也被称为 `当前读(current read)`。锁定读会对读取到的记录加锁: - `select ... lock in share mode`:对记录加 `S` 锁,其它事务也可以加`S`锁,如果加 `x` 锁则会被阻塞 -- `select ... for update`、`insert`、`update`、`delete `:对记录加 `X` 锁,且其它事务不能加任何锁 - +- `select ... for update`、`insert`、`update`、`delete`:对记录加 `X` 锁,且其它事务不能加任何锁 +在一致性非锁定读下,即使读取的记录已被其它事务加上 `X` 锁,这时记录也是可以被读取的,即读取的快照数据。上面说了,在 `Repeatable Read` 下 `MVCC` 防止了部分幻读,这边的 “部分” 是指在 `一致性非锁定读` 情况下,只能读取到第一次查询之前所插入的数据(根据 Read View 判断数据可见性,Read View 在第一次查询时生成)。但是!如果是 `当前读` ,每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。所以, **`InnoDB` 在实现`Repeatable Read` 时,如果执行的是当前读,则会对读取的记录使用 `Next-key Lock` ,来防止其它事务在间隙间插入数据** -在一致性非锁定读下,即使读取的记录已被其它事务加上 `X` 锁,这时记录也是可以被读取的,即读取的快照数据。上面说了在 `Repeatable Read` 下 `MVCC` 防止了部分幻读,这边的 “部分” 是指在 `一致性非锁定读` 情况下,只能读取到第一次查询之前所插入的数据(根据Read View判断数据可见性,Read View在第一次查询时生成),但如果是`当前读` ,每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。**所以 `InnoDB` 在实现`Repeatable Read` 时,如果执行的是当前读,则会对读取的记录使用 `Next-key Lock` ,来防止其它事务在间隙间插入数据** - - - -## InnoDB对MVCC的实现 +## InnoDB 对 MVCC 的实现 `MVCC` 的实现依赖于:**隐藏字段、Read View、undo log**。在内部实现中,`InnoDB` 通过数据行的 `DB_TRX_ID` 和 `Read View` 来判断数据的可见性,如不可见,则通过数据行的 `DB_ROLL_PTR` 找到 `undo log` 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 `Read View` 之前已经提交的修改和该事务本身做的修改 - - -#### 隐藏字段 +### 隐藏字段 在内部,`InnoDB` 存储引擎为每行数据添加了三个 [隐藏字段](https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html): -- `DB_TRX_ID(6字节)`:表示最后一次插入或更新该行的事务id。此外,`delete` 操作在内部被视为更新,只不过会在记录头 `Record header` 中的 `deleted_flag` 字段将其标记为已删除 +- `DB_TRX_ID(6字节)`:表示最后一次插入或更新该行的事务 id。此外,`delete` 操作在内部被视为更新,只不过会在记录头 `Record header` 中的 `deleted_flag` 字段将其标记为已删除 - `DB_ROLL_PTR(7字节)` 回滚指针,指向该行的 `undo log` 。如果该行未被更新,则为空 -- `DB_ROW_ID(6字节)`:如果没有设置主键且该表没有唯一非空索引时,`InnoDB` 会使用该id来生成聚簇索引 - - +- `DB_ROW_ID(6字节)`:如果没有设置主键且该表没有唯一非空索引时,`InnoDB` 会使用该 id 来生成聚簇索引 -#### ReadView +### ReadView [`Read View`](https://github.com/facebook/mysql-8.0/blob/8.0/storage/innobase/include/read0types.h#L298) 主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务” 主要有以下字段: -- `m_low_limit_id`:目前出现过的最大的事务ID+1,即下一个将被分配的事务ID。大于这个ID的数据版本均不可见 -- `m_up_limit_id`:活跃事务列表 `m_ids` 中最小的事务ID,如果 `m_ids` 为空,则 `m_up_limit_id` 为 `m_low_limit_id`。小于这个ID的数据版本均可见 -- `m_ids`:`Read View` 创建时其他未提交的活跃事务ID列表。创建 `Read View `时,将当前未提交事务ID记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。`m_ids` 不包括当前事务自己和已提交的事务(正在内存中) -- `m_creator_trx_id`:创建该 `Read View` 的事务ID +- `m_low_limit_id`:目前出现过的最大的事务 ID+1,即下一个将被分配的事务 ID。大于这个 ID 的数据版本均不可见 +- `m_up_limit_id`:活跃事务列表 `m_ids` 中最小的事务 ID,如果 `m_ids` 为空,则 `m_up_limit_id` 为 `m_low_limit_id`。小于这个 ID 的数据版本均可见 +- `m_ids`:`Read View` 创建时其他未提交的活跃事务 ID 列表。创建 `Read View`时,将当前未提交事务 ID 记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。`m_ids` 不包括当前事务自己和已提交的事务(正在内存中) +- `m_creator_trx_id`:创建该 `Read View` 的事务 ID - - -#### undo-log +### undo-log `undo log` 主要有两个作用: - 当事务回滚时用于将数据恢复到修改前的样子 - 另一个作用是 `MVCC` ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 `undo log` 读取之前的版本数据,以此实现非锁定读 - - **在 `InnoDB` 存储引擎中 `undo log` 分为两种: `insert undo log` 和 `update undo log`:** - - 1. **`insert undo log`** :指在 `insert` 操作中产生的 `undo log`。因为 `insert` 操作的记录只对事务本身可见,对其他事务不可见,故该 `undo log` 可以在事务提交后直接删除。不需要进行 `purge` 操作 - - **`insert` 时的数据初始状态:** -![markdown](https://ddmcc-1255635056.file.myqcloud.com/317e91e1-1ee1-42ad-9412-9098d5c6a9ad.png) +![](https://ddmcc-1255635056.file.myqcloud.com/317e91e1-1ee1-42ad-9412-9098d5c6a9ad.png) 2. **`update undo log`** :`update` 或 `delete` 操作中产生的 `undo log`。该 `undo log`可能需要提供 `MVCC` 机制,因此不能在事务提交时就进行删除。提交时放入 `undo log` 链表,等待 `purge线程` 进行最后的删除 - - **数据第一次被修改时:** - - -![markdown](https://ddmcc-1255635056.file.myqcloud.com/c52ff79f-10e6-46cb-b5d4-3c9cbcc1934a.png) +![](https://ddmcc-1255635056.file.myqcloud.com/c52ff79f-10e6-46cb-b5d4-3c9cbcc1934a.png) **数据第二次被修改时:** - - -![markdown](https://ddmcc-1255635056.file.myqcloud.com/6a276e7a-b0da-4c7b-bdf7-c0c7b7b3b31c.png) +![](https://ddmcc-1255635056.file.myqcloud.com/6a276e7a-b0da-4c7b-bdf7-c0c7b7b3b31c.png) 不同事务或者相同事务的对同一记录行的修改,会使该记录行的 `undo log` 成为一条链表,链首就是最新的记录,链尾就是最早的旧记录 +### 数据可见性算法 - -#### 数据可见性算法 - -在 `InnoDB` 存储引擎中,创建一个新事务后,执行每个 `select` 语句前,都会创建一个快照(Read View),**快照中保存了当前数据库系统中正处于活跃(没有commit)的事务的ID号**。其实简单的说保存的是系统中当前不应该被本事务看到的其他事务ID列表(即m_ids)。当用户在这个事务中要读取某个记录行的时候,`InnoDB` 会将该记录行的 `DB_TRX_ID` 与 `Read View` 中的一些变量及当前事务ID进行比较,判断是否满足可见性条件 +在 `InnoDB` 存储引擎中,创建一个新事务后,执行每个 `select` 语句前,都会创建一个快照(Read View),**快照中保存了当前数据库系统中正处于活跃(没有 commit)的事务的 ID 号**。其实简单的说保存的是系统中当前不应该被本事务看到的其他事务 ID 列表(即 m_ids)。当用户在这个事务中要读取某个记录行的时候,`InnoDB` 会将该记录行的 `DB_TRX_ID` 与 `Read View` 中的一些变量及当前事务 ID 进行比较,判断是否满足可见性条件 [具体的比较算法](https://github.com/facebook/mysql-8.0/blob/8.0/storage/innobase/include/read0types.h#L161)如下:[图源](https://leviathan.vip/2019/03/20/InnoDB%E7%9A%84%E4%BA%8B%E5%8A%A1%E5%88%86%E6%9E%90-MVCC/#MVCC-1) @@ -132,94 +102,78 @@ 1. 如果记录 DB_TRX_ID < m_up_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之前就提交了,所以该记录行的值对当前事务是可见的 -2. 如果 DB_TRX_ID >= m_low_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤5 +2. 如果 DB_TRX_ID >= m_low_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤 5 3. m_ids 为空,则表明在当前事务创建快照之前,修改该行的事务就已经提交了,所以该记录行的值对当前事务是可见的 4. 如果 m_up_limit_id <= DB_TRX_ID < m_up_limit_id,表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表 m_ids 进行查找(源码中是用的二分查找,因为是有序的) - - 如果在活跃事务列表 m_ids 中能找到 DB_TRX_ID,表明:①在当前事务创建快照前,该记录行的值被事务ID为 DB_TRX_ID 的事务修改了,但没有提交;或者 ②在当前事务创建快照后,该记录行的值被事务ID为 DB_TRX_ID 的事务修改了。这些情况下,这个记录行的值对当前事务都是不可见的。跳到步骤5 - - - 在活跃事务列表中找不到,则表明“id为trx_id的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见 - -5. 在该记录行的 DB_ROLL_PTR 指针所指向的 `undo log` 取出快照记录,用快照记录的 DB_TRX_ID 跳到步骤1重新开始判断,直到找到满足的快照版本或返回空 + - 如果在活跃事务列表 m_ids 中能找到 DB_TRX_ID,表明:① 在当前事务创建快照前,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了,但没有提交;或者 ② 在当前事务创建快照后,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了。这些情况下,这个记录行的值对当前事务都是不可见的。跳到步骤 5 + - 在活跃事务列表中找不到,则表明“id 为 trx_id 的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见 +5. 在该记录行的 DB_ROLL_PTR 指针所指向的 `undo log` 取出快照记录,用快照记录的 DB_TRX_ID 跳到步骤 1 重新开始判断,直到找到满足的快照版本或返回空 -## RC和RR隔离级别下MVCC的差异 +## RC 和 RR 隔离级别下 MVCC 的差异 -在事务隔离级别 `RC` 和 `RR` (InnoDB存储引擎的默认事务隔离级别)下,` InnoDB` 存储引擎使用 `MVCC`(非锁定一致性读),但它们生成 `Read View` 的时机却不同 +在事务隔离级别 `RC` 和 `RR` (InnoDB 存储引擎的默认事务隔离级别)下,`InnoDB` 存储引擎使用 `MVCC`(非锁定一致性读),但它们生成 `Read View` 的时机却不同 -- 在 RC 隔离级别下的 **`每次select`** 查询前都生成一个`Read View` (m_ids列表) -- 在 RR 隔离级别下只在事务开始后 **`第一次select`** 数据前生成一个`Read View`(m_ids列表) +- 在 RC 隔离级别下的 **`每次select`** 查询前都生成一个`Read View` (m_ids 列表) +- 在 RR 隔离级别下只在事务开始后 **`第一次select`** 数据前生成一个`Read View`(m_ids 列表) - - -## MVCC解决不可重复读问题 +## MVCC 解决不可重复读问题 虽然 RC 和 RR 都通过 `MVCC` 来读取快照数据,但由于 **生成 Read View 时机不同**,从而在 RR 级别下实现可重复读 举个例子: -![markdown](https://ddmcc-1255635056.file.myqcloud.com/6fb2b9a1-5f14-4dec-a797-e4cf388ed413.png) - +![](https://ddmcc-1255635056.file.myqcloud.com/6fb2b9a1-5f14-4dec-a797-e4cf388ed413.png) -#### **在RC下ReadView生成情况** +### 在 RC 下 ReadView 生成情况 1. **`假设时间线来到 T4 ,那么此时数据行 id = 1 的版本链为`:** - ![markdown](https://ddmcc-1255635056.file.myqcloud.com/a3fd1ec6-8f37-42fa-b090-7446d488fd04.png) - -由于 RC 级别下每次查询都会生成` Read View` ,并且事务101、102 并未提交,此时 `103` 事务生成的 `Read View` 中活跃的事务 **`m_ids` 为:[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103 - -- 此时最新记录的 `DB_TRX_ID` 为101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见 -- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是101,不可见 -- 继续找上一条 `DB_TRX_ID`为1,满足 1 < m_up_limit_id,可见,所以事务103查询到数据为 `name = 菜花` + ![](https://ddmcc-1255635056.file.myqcloud.com/a3fd1ec6-8f37-42fa-b090-7446d488fd04.png) +由于 RC 级别下每次查询都会生成`Read View` ,并且事务 101、102 并未提交,此时 `103` 事务生成的 `Read View` 中活跃的事务 **`m_ids` 为:[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103 +- 此时最新记录的 `DB_TRX_ID` 为 101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见 +- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见 +- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花` 2. **`时间线来到 T6 ,数据的版本链为`:** ![markdown](https://ddmcc-1255635056.file.myqcloud.com/528559e9-dae8-4d14-b78d-a5b657c88391.png) -因为在 RC 级别下,重新生成 `Read View`,这时事务101已经提交,102并未提交,所以此时 `Read View` 中活跃的事务 **`m_ids`:[102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:102,`m_creator_trx_id`为:103 - -- 此时最新记录的 `DB_TRX_ID` 为102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见 - -- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为101,满足 102 < m_up_limit_id,记录可见,所以在 `T6` 时间点查询到数据为 `name = 李四`,与时间 T4 查询到的结果不一致,不可重复读! +因为在 RC 级别下,重新生成 `Read View`,这时事务 101 已经提交,102 并未提交,所以此时 `Read View` 中活跃的事务 **`m_ids`:[102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:102,`m_creator_trx_id`为:103 +- 此时最新记录的 `DB_TRX_ID` 为 102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见 +- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为 101,满足 102 < m_up_limit_id,记录可见,所以在 `T6` 时间点查询到数据为 `name = 李四`,与时间 T4 查询到的结果不一致,不可重复读! 3. **`时间线来到 T9 ,数据的版本链为`:** - - ![markdown](https://ddmcc-1255635056.file.myqcloud.com/6f82703c-36a1-4458-90fe-d7f4edbac71a.png) -重新生成 `Read View`, 这时事务 101 和 102 都已经提交,所以 **m_ids** 为空,则 m_up_limit_id = m_low_limit_id = 104,最新版本事务ID为102,满足 102 < m_low_limit_id,可见,查询结果为 `name = 赵六` - +重新生成 `Read View`, 这时事务 101 和 102 都已经提交,所以 **m_ids** 为空,则 m_up_limit_id = m_low_limit_id = 104,最新版本事务 ID 为 102,满足 102 < m_low_limit_id,可见,查询结果为 `name = 赵六` +> **总结:** **在 RC 隔离级别下,事务在每次查询开始时都会生成并设置新的 Read View,所以导致不可重复读** -> **总结:** **在RC隔离级别下,事务在每次查询开始时都会生成并设置新的 Read View,所以导致不可重复读** +### 在 RR 下 ReadView 生成情况 - -#### **在RR下ReadView生成情况** - -**在可重复读级别下,只会在事务开始后第一次读取数据时生成一个Read View(m_ids列表)** +**在可重复读级别下,只会在事务开始后第一次读取数据时生成一个 Read View(m_ids 列表)** 1. **`在 T4 情况下的版本链为`:** - ![markdown](https://ddmcc-1255635056.file.myqcloud.com/0e906b95-c916-4f30-beda-9cb3e49746bf.png) 在当前执行 `select` 语句时生成一个 `Read View`,此时 **`m_ids`:[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103 此时和 RC 级别下一样: -- 最新记录的 `DB_TRX_ID` 为101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见 -- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是101,不可见 -- 继续找上一条 `DB_TRX_ID`为1,满足 1 < m_up_limit_id,可见,所以事务103查询到数据为 `name = 菜花` - +- 最新记录的 `DB_TRX_ID` 为 101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见 +- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见 +- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花` 2. **`时间点 T6 情况下`:** @@ -227,44 +181,35 @@ 在 RR 级别下只会生成一次`Read View`,所以此时依然沿用 **`m_ids` :[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103 +- 最新记录的 `DB_TRX_ID` 为 102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见 - - 最新记录的 `DB_TRX_ID` 为102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见 - - - 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为101,不可见 - - - 继续根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是101,不可见 - - - 继续找上一条 `DB_TRX_ID`为1,满足 1 < m_up_limit_id,可见,所以事务103查询到数据为 `name = 菜花` +- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为 101,不可见 +- 继续根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见 +- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花` 3. **时间点 T9 情况下:** ![markdown](https://ddmcc-1255635056.file.myqcloud.com/cbbedbc5-0e3c-4711-aafd-7f3d68a4ed4e.png) - - 此时情况跟 T6 完全一样,由于已经生成了 `Read View`,此时依然沿用 **`m_ids` :[101,102]** ,所以查询结果依然是 `name = 菜花` - - -## MVCC➕Next-key-Lock防止幻读 +## MVCC➕Next-key-Lock 防止幻读 `InnoDB`存储引擎在 RR 级别下通过 `MVCC`和 `Next-key Lock` 来解决幻读问题: -1. **执行普通 `select`,此时会以 `MVCC` 快照读的方式读取数据** +**1、执行普通 `select`,此时会以 `MVCC` 快照读的方式读取数据** 在快照读的情况下,RR 隔离级别只会在事务开启后的第一次查询生成 `Read View` ,并使用至事务提交。所以在生成 `Read View` 之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 “幻读” -2. **执行select...for update/lock in share mode、insert、update、delete等当前读** +**2、执行 select...for update/lock in share mode、insert、update、delete 等当前读** 在当前读下,读取的都是最新的数据,如果其它事务有插入新的记录,并且刚好在当前事务查询范围内,就会产生幻读!`InnoDB` 使用 [Next-key Lock](https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-next-key-locks) 来防止这种情况。当执行当前读时,会锁定读取到的记录的同时,锁定它们的间隙,防止其它事务在查询范围内插入数据。只要我不让你插入,就不会发生幻读 - - ## 参考 -- **《MySQL技术内幕InnoDB存储引擎第2版》** +- **《MySQL 技术内幕 InnoDB 存储引擎第 2 版》** -- [Innodb中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html) -- [MySQL事务与MVCC如何实现的隔离级别](https://blog.csdn.net/qq_35190492/article/details/109044141) \ No newline at end of file +- [Innodb 中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html) +- [MySQL 事务与 MVCC 如何实现的隔离级别](https://blog.csdn.net/qq_35190492/article/details/109044141) \ No newline at end of file diff --git a/docs/database/MySQL.md "b/docs/database/mysql/MySQL\346\200\273\347\273\223.md" similarity index 96% rename from docs/database/MySQL.md rename to "docs/database/mysql/MySQL\346\200\273\347\273\223.md" index 58da9e6ba3f..377935254d0 100644 --- a/docs/database/MySQL.md +++ "b/docs/database/mysql/MySQL\346\200\273\347\273\223.md" @@ -152,9 +152,9 @@ set global query_cache_type=1; set global query_cache_size=600000; ``` -如上,**开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外,如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果也不会被缓存。 +如上,**开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。(**查询缓存不命中的情况:(1)**)因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外,(**查询缓存不命中的情况:(2)**)如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果也不会被缓存。 -缓存建立之后,MySQL 的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。 +(**查询缓存不命中的情况:(3)**)**缓存建立之后**,MySQL 的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。 **缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。** 因此,开启查询缓存要谨慎,尤其对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小,一般来说其大小设置为几十 MB 比较合适。此外,**还可以通过 sql_cache 和 sql_no_cache 来控制某个查询语句是否需要缓存:** diff --git "a/docs/database/MySQL\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" "b/docs/database/mysql/MySQL\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" similarity index 100% rename from "docs/database/MySQL\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" rename to "docs/database/mysql/MySQL\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" diff --git "a/docs/database/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md" "b/docs/database/mysql/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md" similarity index 100% rename from "docs/database/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md" rename to "docs/database/mysql/MySQL\351\253\230\346\200\247\350\203\275\344\274\230\345\214\226\350\247\204\350\214\203\345\273\272\350\256\256.md" diff --git "a/docs/database/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md" "b/docs/database/mysql/\344\270\200\345\215\203\350\241\214MySQL\345\255\246\344\271\240\347\254\224\350\256\260.md" similarity index 100% rename from "docs/database/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md" rename to "docs/database/mysql/\344\270\200\345\215\203\350\241\214MySQL\345\255\246\344\271\240\347\254\224\350\256\260.md" diff --git "a/docs/database/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" "b/docs/database/mysql/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" similarity index 100% rename from "docs/database/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" rename to "docs/database/mysql/\344\270\200\346\235\241sql\350\257\255\345\217\245\345\234\250mysql\344\270\255\345\246\202\344\275\225\346\211\247\350\241\214\347\232\204.md" diff --git "a/docs/database/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" "b/docs/database/mysql/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" similarity index 83% rename from "docs/database/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" rename to "docs/database/mysql/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" index 95da8be960d..19cbd71e2e6 100644 --- "a/docs/database/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" +++ "b/docs/database/mysql/\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253(\345\233\276\346\226\207\350\257\246\350\247\243).md" @@ -69,7 +69,7 @@ | REPEATABLE-READ | × | × | √ | | SERIALIZABLE | × | × | × | -MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;` +MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;` ```sql mysql> SELECT @@tx_isolation; @@ -80,11 +80,17 @@ mysql> SELECT @@tx_isolation; +-----------------+ ``` -这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 **REPEATABLE-READ(可重读)** 事务隔离级别下,允许应用使用 Next-Key Lock 锁算法来避免幻读的产生。这与其他数据库系统(如 SQL Server)是不同的。所以说虽然 InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)** ,但是可以通过应用加锁读(例如 `select * from table for update` 语句)来保证不会产生幻读,而这个加锁度使用到的机制就是 Next-Key Lock 锁算法。从而达到了 SQL 标准的 **SERIALIZABLE(可串行化)** 隔离级别。 +~~这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 **REPEATABLE-READ(可重读)** 事务隔离级别下使用的是 Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说 InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)** 已经可以完全保证事务的隔离性要求,即达到了 SQL 标准的 **SERIALIZABLE(可串行化)** 隔离级别。~~ -因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是**READ-COMMITTED(读取提交内容):**,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)** 并不会有任何性能损失。 +🐛 问题更正:**MySQL InnoDB 的 REPEATABLE-READ(可重读)并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是 Next-Key Locks。** -InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到**SERIALIZABLE(可串行化)** 隔离级别。 +因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)** 并不会有任何性能损失。 + +InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALIZABLE(可串行化)** 隔离级别。 + +🌈 拓展一下(以下内容摘自《MySQL 技术内幕:InnoDB 存储引擎(第 2 版)》7.7 章): + +> InnoDB 存储引擎提供了对 XA 事务的支持,并通过 XA 事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源(transactional resources)参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的 ACID 要求又有了提高。另外,在使用分布式事务时,InnoDB 存储引擎的事务隔离级别必须设置为 SERIALIZABLE。 ### 实际情况演示 diff --git "a/docs/database/\345\205\263\344\272\216\346\225\260\346\215\256\345\272\223\345\255\230\345\202\250\346\227\266\351\227\264\347\232\204\344\270\200\347\202\271\346\200\235\350\200\203.md" "b/docs/database/mysql/\345\205\263\344\272\216\346\225\260\346\215\256\345\272\223\345\255\230\345\202\250\346\227\266\351\227\264\347\232\204\344\270\200\347\202\271\346\200\235\350\200\203.md" similarity index 100% rename from "docs/database/\345\205\263\344\272\216\346\225\260\346\215\256\345\272\223\345\255\230\345\202\250\346\227\266\351\227\264\347\232\204\344\270\200\347\202\271\346\200\235\350\200\203.md" rename to "docs/database/mysql/\345\205\263\344\272\216\346\225\260\346\215\256\345\272\223\345\255\230\345\202\250\346\227\266\351\227\264\347\232\204\344\270\200\347\202\271\346\200\235\350\200\203.md" diff --git "a/docs/database/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214\346\225\260\346\215\256\345\272\223\351\203\250\345\210\206\347\232\204\344\270\200\344\272\233\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/docs/database/mysql/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214\346\225\260\346\215\256\345\272\223\351\203\250\345\210\206\347\232\204\344\270\200\344\272\233\346\234\200\344\275\263\345\256\236\350\267\265.md" similarity index 100% rename from "docs/database/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214\346\225\260\346\215\256\345\272\223\351\203\250\345\210\206\347\232\204\344\270\200\344\272\233\346\234\200\344\275\263\345\256\236\350\267\265.md" rename to "docs/database/mysql/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214\346\225\260\346\215\256\345\272\223\351\203\250\345\210\206\347\232\204\344\270\200\344\272\233\346\234\200\344\275\263\345\256\236\350\267\265.md" diff --git "a/docs/database/\346\225\260\346\215\256\345\272\223\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/database/\346\225\260\346\215\256\345\272\223\345\237\272\347\241\200\347\237\245\350\257\206.md" new file mode 100644 index 00000000000..71af7f01132 --- /dev/null +++ "b/docs/database/\346\225\260\346\215\256\345\272\223\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -0,0 +1,118 @@ +数据库知识基础,这部分内容一定要理解记忆。虽然这部分内容只是理论知识,但是非常重要,这是后面学习 MySQL 数据库的基础。PS:这部分内容由于涉及太多概念性内容,所以参考了维基百科和百度百科相应的介绍。 + +## 什么是数据库,数据库管理系统,数据库系统,数据库管理员? + +- **数据库** :数据库(DataBase 简称 DB)就是信息的集合或者说数据库是由数据库管理系统管理的数据的集合。 +- **数据库管理系统** : 数据库管理系统(Database Management System 简称 DBMS)是一种操纵和管理数据库的大型软件,通常用语用于建立、使用和维护数据库。 +- **数据库系统** : 数据库系统(Data Base System,简称 DBS)通常由软件、数据库和数据管理员(DBA)组成。 +- **数据库管理员** : 数据库管理员(Database Administrator,简称 DBA)负责全面管理和控制数据库系统。 + +数据库系统基本构成如下图所示: + +![数据库系统基本构成](https://img-blog.csdnimg.cn/img_convert/e21120184e63406526a4e873cacd23f2.png) + +## 什么是元组,码,候选码,主码,外码,主属性,非主属性? + +- **元组** : 元组(tuple)是关系数据库中的基本概念,关系是一张表,表中的每行(即数据库中的每条记录)就是一个元组,每列就是一个属性。 在二维表里,元组也称为行。 +- **码** :码就是能唯一标识实体的属性,对应表中的列。 +- **候选码** : 若关系中的某一属性或属性组的值能唯一的标识一个元组,而其任何、子集都不能再标识,则称该属性组为候选码。例如:在学生实体中,“学号”是能唯一的区分学生实体的,同时又假设“姓名”、“班级”的属性组合足以区分学生实体,那么{学号}和{姓名,班级}都是候选码。 +- **主码** : 主码也叫主键。主码是从候选码中选出来的。 一个实体集中只能有一个主码,但可以有多个候选码。 +- **外码** : 外码也叫外键。如果一个关系中的一个属性是另外一个关系中的主码则这个属性为外码。 +- **主属性** : 候选码中出现过的属性称为主属性。比如关系 工人(工号,身份证号,姓名,性别,部门).显然工号和身份证号都能够唯一标示这个关系,所以都是候选码。工号、身份证号这两个属性就是主属性。如果主码是一个属性组,那么属性组中的属性都是主属性。 +- **非主属性:** 不包含在任何一个候选码中的属性称为非主属性。比如在关系——学生(学号,姓名,年龄,性别,班级)中,主码是“学号”,那么其他的“姓名”、“年龄”、“性别”、“班级”就都可以称为非主属性。 + +## 主键和外键有什么区别? + +- **主键(主码)** :主键用于唯一标识一个元组,不能有重复,不允许为空。一个表只能有一个主键。 +- **外键(外码)** :外键用来和其他表建立联系用,外键是另一表的主键,外键是可以有重复的,可以是空值。一个表可以有多个外键。 + +## 什么是 ER 图? + +> 我们做一个项目的时候一定要试着花 ER 图来捋清数据库设计,这个也是面试官问你项目的时候经常会被问道的。 + +**E-R 图**也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。 它是描述现实世界关系概念模型的有效方法。 是表示概念关系模型的一种方式。 + +下图是一个学生选课的 ER 图,每个学生可以选若干门课程,同一门课程也可以被若干人选择,所以它们之间的关系是多对多(M:N)。另外,还有其他两种关系是:1 对 1(1:1)、1 对多(1:N)。 + +![ER图示例](https://img-blog.csdnimg.cn/img_convert/4717673e36966e0e4b33fccfd753f6ea.png) + +我们试着将上面的 ER 图转换成数据库实际的关系模型(实际设计中,我们通常会将任课教师也作为一个实体来处理): + +![关系模型](https://img-blog.csdnimg.cn/img_convert/5897753dfb301dfa3a814ab06e718a5e.png) + +## 数据库范式了解吗? + +**1NF(第一范式)** + +属性(对应于表中的字段)不能再被分割,也就是这个字段只能是一个值,不能再分为多个其他的字段了。**1NF 是所有关系型数据库的最基本要求** ,也就是说关系型数据库中创建的表一定满足第一范式。 + +**2NF(第二范式)** + +2NF 在 1NF 的基础之上,消除了非主属性对于码的部分函数依赖。如下图所示,展示了第一范式到第二范式的过渡。第二范式在第一范式的基础上增加了一个列,这个列称为主键,非主属性都依赖于主键。 + +![第二范式](https://img-blog.csdnimg.cn/img_convert/bd1d31be3779342427fc9e462bf7f05c.png) + +一些重要的概念: + +- **函数依赖(functional dependency)** :若在一张表中,在属性(或属性组)X 的值确定的情况下,必定能确定属性 Y 的值,那么就可以说 Y 函数依赖于 X,写作 X → Y。 +- **部分函数依赖(partial functional dependency)** :如果 X→Y,并且存在 X 的一个真子集 X0,使得 X0→Y,则称 Y 对 X 部分函数依赖。比如学生基本信息表 R 中(学号,身份证号,姓名)当然学号属性取值是唯一的,在 R 关系中,(学号,身份证号)->(姓名),(学号)->(姓名),(身份证号)->(姓名);所以姓名部分函数依赖与(学号,身份证号); +- **完全函数依赖(Full functional dependency)** :在一个关系中,若某个非主属性数据项依赖于全部关键字称之为完全函数依赖。比如学生基本信息表 R(学号,班级,姓名)假设不同的班级学号有相同的,班级内学号不能相同,在 R 关系中,(学号,班级)->(姓名),但是(学号)->(姓名)不成立,(班级)->(姓名)不成立,所以姓名完全函数依赖与(学号,班级); +- **传递函数依赖** : 在关系模式 R(U)中,设 X,Y,Z 是 U 的不同的属性子集,如果 X 确定 Y、Y 确定 Z,且有 X 不包含 Y,Y 不确定 X,(X∪Y)∩Z=空集合,则称 Z 传递函数依赖(transitive functional dependency) 于 X。传递函数依赖会导致数据冗余和异常。传递函数依赖的 Y 和 Z 子集往往同属于某一个事物,因此可将其合并放到一个表中。比如在关系 R(学号 ,姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖。。 + +**3NF(第三范式)** + +3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。符合 3NF 要求的数据库设计,**基本**上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。比如在关系 R(学号 ,姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖,所以该表的设计,不符合 3NF 的要求。 + +**总结** + +- 1NF:属性不可再分。 +- 2NF:1NF 的基础之上,消除了非主属性对于码的部分函数依赖。 +- 3NF:3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。 + +## 什么是存储过程? + +我们可以把存储过程看成是一些 SQL 语句的集合,中间加了点逻辑控制语句。存储过程在业务比较复杂的时候是非常实用的,比如很多时候我们完成一个操作可能需要写一大串 SQL 语句,这时候我们就可以写有一个存储过程,这样也方便了我们下一次的调用。存储过程一旦调试完成通过后就能稳定运行,另外,使用存储过程比单纯 SQL 语句执行要快,因为存储过程是预编译过的。 + +存储过程在互联网公司应用不多,因为存储过程难以调试和扩展,而且没有移植性,还会消耗数据库资源。 + +阿里巴巴 Java 开发手册里要求禁止使用存储过程。 + +![阿里巴巴Java开发手册:禁止存储过程](https://img-blog.csdnimg.cn/img_convert/0fa082bc4d4f919065767476a41b2156.png) + +## drop、delete 与 truncate 区别? + +### 用法不同 + +- drop(丢弃数据): `drop table 表名` ,直接将表都删除掉,在删除表的时候使用。 +- truncate (清空数据) : `truncate table 表名` ,只删除表中的数据,再插入数据的时候自增长 id 又从 1 开始,在清空表中数据的时候使用。 +- delete(删除数据) : `delete from 表名 where 列名=值`,删除某一列的数据,如果不加 where 子句和`truncate table 表名`作用类似。 + +truncate 和不带 where 子句的 delete、以及 drop 都会删除表内的数据,但是 **truncate 和 delete 只删除数据不删除表的结构(定义),执行 drop 语句,此表的结构也会删除,也就是执行 drop 之后对应的表不复存在。** + +### 属于不同的数据库语言 + +truncate 和 drop 属于 DDL(数据定义语言)语句,操作立即生效,原数据不放到 rollback segment 中,不能回滚,操作不触发 trigger。而 delete 语句是 DML (数据库操作语言)语句,这个操作会放到 rollback segement 中,事务提交之后才生效。 + +**DML 语句和 DDL 语句区别:** + +- DML 是数据库操作语言(Data Manipulation Language)的缩写,是指对数据库中表记录的操作,主要包括表记录的插入(insert)、更新(update)、删除(delete)和查询(select),是开发人员日常使用最频繁的操作。 +- DDL (Data Definition Language)是数据定义语言的缩写,简单来说,就是对数据库内部的对象进行创建、删除、修改的操作语言。它和 DML 语言的最大区别是 DML 只是对表内部数据的操作,而不涉及到表的定义、结构的修改,更不会涉及到其他对象。DDL 语句更多的被数据库管理员(DBA)所使用,一般的开发人员很少使用。 + +### 执行速度不同 + +一般来说:drop>truncate>delete(这个我没有设计测试过)。 + +## 数据库设计通常分为哪几步? + +1. **需求分析** : 分析用户的需求,包括数据、功能和性能需求。 +2. **概念结构设计** : 主要采用 E-R 模型进行设计,包括画 E-R 图。 +3. **逻辑结构设计** : 通过将 E-R 图转换成表,实现从 E-R 模型到关系模型的转换。 +4. **物理结构设计** : 主要是为所设计的数据库选择合适的存储结构和存取路径。 +5. **数据库实施** : 包括编程、测试和试运行 +6. **数据库的运行和维护** : 系统的运行与数据库的日常维护。 + +## 参考 + +- +- +- \ No newline at end of file diff --git "a/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index 6fbc5397cbd..7c03a11179d 100644 --- "a/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -741,9 +741,37 @@ public void f5(int a) { ### 静态方法和实例方法有何不同? -1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,**调用静态方法可以无需创建对象。** +**1、调用方式** -2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。 +在外部调用静态方法时,可以使用 `类名.方法名` 的方式,也可以使用 `对象.方法名` 的方式,而实例方法只有后面这种方式。也就是说,**调用静态方法可以无需创建对象** 。 + +不过,需要注意的是一般不建议使用 `对象.方法名` 的方式来调用静态方法。这种方式非常容易造成混淆,静态方法不属于类的某个对象而是属于这个类。 + +因此,一般建议使用 `类名.方法名` 的方式来调用静态方法。 + +```java + +public class Person { + public void method() { + //...... + } + + public static void staicMethod(){ + //...... + } + public static void main(String[] args) { + Person person = new Person(); + // 调用实例方法 + person.method(); + // 调用静态方法 + Person.staicMethod() + } +} +``` + +**2、访问类成员是否存在限制** + +静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制。 ### 为什么 Java 中只有值传递? diff --git "a/docs/java/jvm/Java\345\206\205\345\255\230\345\214\272\345\237\237.md" "b/docs/java/jvm/Java\345\206\205\345\255\230\345\214\272\345\237\237.md" index 00020544446..11210af18d8 100644 --- "a/docs/java/jvm/Java\345\206\205\345\255\230\345\214\272\345\237\237.md" +++ "b/docs/java/jvm/Java\345\206\205\345\255\230\345\214\272\345\237\237.md" @@ -174,8 +174,8 @@ JDK 8 版本之后方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 堆这里最容易出现的就是 OutOfMemoryError 错误,并且出现这种错误之后的表现形式还会有几种,比如: -1. **`OutOfMemoryError: GC Overhead Limit Exceeded`** : 当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。 -2. **`java.lang.OutOfMemoryError: Java heap space`** :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发`java.lang.OutOfMemoryError: Java heap space` 错误。(和本机物理内存无关,和你配置的内存大小有关!) +1. **`java.lang.OutOfMemoryError: GC Overhead Limit Exceeded`** : 当 JVM 花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。 +2. **`java.lang.OutOfMemoryError: Java heap space`** :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误。(和配置的最大堆内存有关,且受制于物理内存大小。最大堆内存可通过`-Xmx`参数配置,若没有特别配置,将会使用默认值,详见:[Default Java 8 max heap size](https://stackoverflow.com/questions/28272923/default-xmxsize-in-java-8-max-heap-size)) 3. ...... ### 2.5 方法区 diff --git "a/docs/java/jvm/\347\261\273\345\212\240\350\275\275\345\231\250.md" "b/docs/java/jvm/\347\261\273\345\212\240\350\275\275\345\231\250.md" index 3cd31cdcebe..013397017ee 100644 --- "a/docs/java/jvm/\347\261\273\345\212\240\350\275\275\345\231\250.md" +++ "b/docs/java/jvm/\347\261\273\345\212\240\350\275\275\345\231\250.md" @@ -1,42 +1,26 @@ - - -- [回顾一下类加载过程](#回顾一下类加载过程) -- [类加载器总结](#类加载器总结) -- [双亲委派模型](#双亲委派模型) - - [双亲委派模型介绍](#双亲委派模型介绍) - - [双亲委派模型实现源码分析](#双亲委派模型实现源码分析) - - [双亲委派模型的好处](#双亲委派模型的好处) - - [如果我们不想要双亲委派模型怎么办?](#如果我们不想要双亲委派模型怎么办) -- [自定义类加载器](#自定义类加载器) -- [推荐](#推荐) - - - -> 公众号JavaGuide 后台回复关键字“1”,免费获取JavaGuide配套的Java工程师必备学习资源(文末有公众号二维码)。 - ## 回顾一下类加载过程 类加载过程:**加载->连接->初始化**。连接过程又可分为三步:**验证->准备->解析**。 ![类加载过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/类加载过程.png) -一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去完成还可以自定义类加载器去控制字节流的获取方式(重写一个类加载器的 `loadClass()` 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。 +一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以去自定义类加载器去控制字节流的获取方式(重写一个类加载器的 `loadClass()` 方法)。数组类型不通过类加载器创建,它由 Java 虚拟机直接创建。 -所有的类都由类加载器加载,加载的作用就是将 .class文件加载到内存。 +所有的类都由类加载器加载,加载的作用就是将 `.class`文件加载到内存。 ## 类加载器总结 JVM 中内置了三个重要的 ClassLoader,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自`java.lang.ClassLoader`: -1. **BootstrapClassLoader(启动类加载器)** :最顶层的加载类,由C++实现,负责加载 `%JAVA_HOME%/lib`目录下的jar包和类或者或被 `-Xbootclasspath`参数指定的路径中的所有类。 -2. **ExtensionClassLoader(扩展类加载器)** :主要负责加载目录 `%JRE_HOME%/lib/ext` 目录下的jar包和类,或被 `java.ext.dirs` 系统变量所指定的路径下的jar包。 -3. **AppClassLoader(应用程序类加载器)** :面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。 +1. **BootstrapClassLoader(启动类加载器)** :最顶层的加载类,由 C++实现,负责加载 `%JAVA_HOME%/lib`目录下的 jar 包和类或者被 `-Xbootclasspath`参数指定的路径中的所有类。 +2. **ExtensionClassLoader(扩展类加载器)** :主要负责加载 `%JRE_HOME%/lib/ext` 目录下的 jar 包和类,或被 `java.ext.dirs` 系统变量所指定的路径下的 jar 包。 +3. **AppClassLoader(应用程序类加载器)** :面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。 ## 双亲委派模型 ### 双亲委派模型介绍 -每一个类都有一个对应它的类加载器。系统中的 ClassLoder 在协同工作的时候会默认使用 **双亲委派模型** 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 `loadClass()` 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 `BootstrapClassLoader` 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 `BootstrapClassLoader` 作为父类加载器。 +每一个类都有一个对应它的类加载器。系统中的 ClassLoader 在协同工作的时候会默认使用 **双亲委派模型** 。即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派给父类加载器的 `loadClass()` 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 `BootstrapClassLoader` 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为 null 时,会使用启动类加载器 `BootstrapClassLoader` 作为父类加载器。 ![ClassLoader](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/classloader_WPS图片.png) @@ -61,18 +45,18 @@ The GrandParent of ClassLodarDemo's ClassLoader is null ``` `AppClassLoader`的父类加载器为`ExtClassLoader`, -`ExtClassLoader`的父类加载器为null,**null并不代表`ExtClassLoader`没有父类加载器,而是 `BootstrapClassLoader`** 。 +`ExtClassLoader`的父类加载器为 null,**null 并不代表`ExtClassLoader`没有父类加载器,而是 `BootstrapClassLoader`** 。 -其实这个双亲翻译的容易让别人误解,我们一般理解的双亲都是父母,这里的双亲更多地表达的是“父母这一辈”的人而已,并不是说真的有一个 Mother ClassLoader 和一个 Father ClassLoader 。另外,类加载器之间的“父子”关系也不是通过继承来体现的,是由“优先级”来决定。官方API文档对这部分的描述如下: +其实这个双亲翻译的容易让别人误解,我们一般理解的双亲都是父母,这里的双亲更多地表达的是“父母这一辈”的人而已,并不是说真的有一个 Mother ClassLoader 和一个 Father ClassLoader 。另外,类加载器之间的“父子”关系也不是通过继承来体现的,是由“优先级”来决定。官方 API 文档对这部分的描述如下: ->The Java platform uses a delegation model for loading classes. **The basic idea is that every class loader has a "parent" class loader.** When loading a class, a class loader first "delegates" the search for the class to its parent class loader before attempting to find the class itself. +> The Java platform uses a delegation model for loading classes. **The basic idea is that every class loader has a "parent" class loader.** When loading a class, a class loader first "delegates" the search for the class to its parent class loader before attempting to find the class itself. ### 双亲委派模型实现源码分析 双亲委派模型的实现代码非常简单,逻辑非常清晰,都集中在 `java.lang.ClassLoader` 的 `loadClass()` 中,相关代码如下所示。 ```java -private final ClassLoader parent; +private final ClassLoader parent; protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { @@ -90,7 +74,7 @@ protected Class loadClass(String name, boolean resolve) } catch (ClassNotFoundException e) { //抛出异常说明父类加载器无法完成加载请求 } - + if (c == null) { long t1 = System.nanoTime(); //自己尝试加载 @@ -112,7 +96,7 @@ protected Class loadClass(String name, boolean resolve) ### 双亲委派模型的好处 -双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 `java.lang.Object` 类的话,那么程序运行的时候,系统就会出现多个不同的 `Object` 类。 +双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 `java.lang.Object` 类的话,那么程序运行的时候,系统就会出现多个不同的 `Object` 类。 ### 如果我们不想用双亲委派模型怎么办? @@ -129,6 +113,3 @@ protected Class loadClass(String name, boolean resolve) - - - - - - diff --git "a/docs/java/multi-thread/2020\346\234\200\346\226\260Java\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/docs/java/multi-thread/2020\346\234\200\346\226\260Java\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" index a817d041397..8b92632dc57 100644 --- "a/docs/java/multi-thread/2020\346\234\200\346\226\260Java\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" +++ "b/docs/java/multi-thread/2020\346\234\200\346\226\260Java\345\271\266\345\217\221\350\277\233\351\230\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" @@ -479,7 +479,7 @@ static class Entry extends WeakReference> { `Runnable`自 Java 1.0 以来一直存在,但`Callable`仅在 Java 1.5 中引入,目的就是为了来处理`Runnable`不支持的用例。**`Runnable` 接口** 不会返回结果或抛出检查异常,但是 **`Callable` 接口** 可以。所以,如果任务不需要返回结果或抛出异常推荐使用 **`Runnable` 接口** ,这样代码看起来会更加简洁。 -工具类 `Executors` 可以实现 `Runnable` 对象和 `Callable` 对象之间的相互转换。(`Executors.callable(Runnable task`)或 `Executors.callable(Runnable task,Object resule)`)。 +工具类 `Executors` 可以实现将 `Runnable` 对象转换成 `Callable` 对象。(`Executors.callable(Runnable task)` 或 `Executors.callable(Runnable task, Object result)`)。 `Runnable.java` @@ -510,9 +510,9 @@ public interface Callable { ### 4.3. 执行 execute()方法和 submit()方法的区别是什么呢? 1. **`execute()`方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;** -2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 +2. **`submit()`方法用于提交需要返回值的任务。线程池会返回一个 `Future` 类型的对象,通过这个 `Future` 对象可以判断任务是否执行成功**,并且可以通过 `Future` 的 `get()`方法来获取返回值,`get()`方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 -我们以** `AbstractExecutorService` **接口中的一个 `submit` 方法为例子来看看源代码: +我们以 **`AbstractExecutorService` 接口** 中的一个 `submit` 方法为例子来看看源代码: ```java public Future submit(Runnable task) { @@ -726,36 +726,36 @@ public class ThreadPoolExecutorDemo { **Output:** ``` -pool-1-thread-2 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-5 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-4 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-1 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-3 Start. Time = Tue Nov 12 20:59:44 CST 2019 -pool-1-thread-5 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-3 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-2 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-4 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-1 End. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-2 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-1 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-4 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-3 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-5 Start. Time = Tue Nov 12 20:59:49 CST 2019 -pool-1-thread-2 End. Time = Tue Nov 12 20:59:54 CST 2019 -pool-1-thread-3 End. Time = Tue Nov 12 20:59:54 CST 2019 -pool-1-thread-4 End. Time = Tue Nov 12 20:59:54 CST 2019 -pool-1-thread-5 End. Time = Tue Nov 12 20:59:54 CST 2019 -pool-1-thread-1 End. Time = Tue Nov 12 20:59:54 CST 2019 +pool-1-thread-3 Start. Time = Sun Apr 12 11:14:37 CST 2020 +pool-1-thread-5 Start. Time = Sun Apr 12 11:14:37 CST 2020 +pool-1-thread-2 Start. Time = Sun Apr 12 11:14:37 CST 2020 +pool-1-thread-1 Start. Time = Sun Apr 12 11:14:37 CST 2020 +pool-1-thread-4 Start. Time = Sun Apr 12 11:14:37 CST 2020 +pool-1-thread-3 End. Time = Sun Apr 12 11:14:42 CST 2020 +pool-1-thread-4 End. Time = Sun Apr 12 11:14:42 CST 2020 +pool-1-thread-1 End. Time = Sun Apr 12 11:14:42 CST 2020 +pool-1-thread-5 End. Time = Sun Apr 12 11:14:42 CST 2020 +pool-1-thread-1 Start. Time = Sun Apr 12 11:14:42 CST 2020 +pool-1-thread-2 End. Time = Sun Apr 12 11:14:42 CST 2020 +pool-1-thread-5 Start. Time = Sun Apr 12 11:14:42 CST 2020 +pool-1-thread-4 Start. Time = Sun Apr 12 11:14:42 CST 2020 +pool-1-thread-3 Start. Time = Sun Apr 12 11:14:42 CST 2020 +pool-1-thread-2 Start. Time = Sun Apr 12 11:14:42 CST 2020 +pool-1-thread-1 End. Time = Sun Apr 12 11:14:47 CST 2020 +pool-1-thread-4 End. Time = Sun Apr 12 11:14:47 CST 2020 +pool-1-thread-5 End. Time = Sun Apr 12 11:14:47 CST 2020 +pool-1-thread-3 End. Time = Sun Apr 12 11:14:47 CST 2020 +pool-1-thread-2 End. Time = Sun Apr 12 11:14:47 CST 2020 ``` ### 4.7 线程池原理分析 -承接 4.6 节,我们通过代码输出结果可以看出:**线程池每次会同时执行 5 个任务,这 5 个任务执行完之后,剩余的 5 个任务才会被执行。** 大家可以先通过上面讲解的内容,分析一下到底是咋回事?(自己独立思考一会) +承接 4.6 节,我们通过代码输出结果可以看出:**线程池首先会先执行 5 个任务,然后这些任务有任务被执行完的话,就会去拿新的任务执行。** 大家可以先通过上面讲解的内容,分析一下到底是咋回事?(自己独立思考一会) 现在,我们就分析上面的输出内容来简单分析一下线程池原理。 - **为了搞懂线程池的原理,我们需要首先分析一下 `execute`方法。** 在 4.6 节中的 Demo 中我们使用 `executor.execute(worker)`来提交一个任务到线程池中去,这个方法非常重要,下面我们来看看它的源码: +**为了搞懂线程池的原理,我们需要首先分析一下 `execute`方法。** 在 4.6 节中的 Demo 中我们使用 `executor.execute(worker)`来提交一个任务到线程池中去,这个方法非常重要,下面我们来看看它的源码: ```java // 存放线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount) @@ -802,13 +802,13 @@ public void execute(Runnable command) { 通过下图可以更好的对上面这 3 步做一个展示,下图是我为了省事直接从网上找到,原地址不明。 -![图解线程池实现原理](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/图解线程池实现原理.png) +![图解线程池实现原理](images/java线程池学习总结/图解线程池实现原理.png) 现在,让我们在回到 4.6 节我们写的 Demo, 现在应该是不是很容易就可以搞懂它的原理了呢? 没搞懂的话,也没关系,可以看看我的分析: -> 我们在代码中模拟了 10 个任务,我们配置的核心线程数为 5 、等待队列容量为 100 ,所以每次只可能存在 5 个任务同时执行,剩下的 5 个任务会被放到等待队列中去。当前的 5 个任务执行完成后,才会执行剩下的 5 个任务。 +> 我们在代码中模拟了 10 个任务,我们配置的核心线程数为 5 、等待队列容量为 100 ,所以每次只可能存在 5 个任务同时执行,剩下的 5 个任务会被放到等待队列中去。当前的5个任务中如果有任务被执行完了,线程池就会去拿新的任务执行。 ## 5. Atomic 原子类 diff --git "a/docs/java/multi-thread/java\347\272\277\347\250\213\346\261\240\345\255\246\344\271\240\346\200\273\347\273\223.md" "b/docs/java/multi-thread/java\347\272\277\347\250\213\346\261\240\345\255\246\344\271\240\346\200\273\347\273\223.md" index 66d835fd73a..eb266f3aac2 100644 --- "a/docs/java/multi-thread/java\347\272\277\347\250\213\346\261\240\345\255\246\344\271\240\346\200\273\347\273\223.md" +++ "b/docs/java/multi-thread/java\347\272\277\347\250\213\346\261\240\345\255\246\344\271\240\346\200\273\347\273\223.md" @@ -337,7 +337,7 @@ pool-1-thread-2 End. Time = Sun Apr 12 11:14:47 CST 2020 ### 4.2 线程池原理分析 -承接 4.1 节,我们通过代码输出结果可以看出:**线程首先会先执行 5 个任务,然后这些任务有任务被执行完的话,就会去拿新的任务执行。** 大家可以先通过上面讲解的内容,分析一下到底是咋回事?(自己独立思考一会) +承接 4.1 节,我们通过代码输出结果可以看出:**线程池首先会先执行 5 个任务,然后这些任务有任务被执行完的话,就会去拿新的任务执行。** 大家可以先通过上面讲解的内容,分析一下到底是咋回事?(自己独立思考一会) 现在,我们就分析上面的输出内容来简单分析一下线程池原理。 @@ -509,9 +509,9 @@ pool-1-thread-2 End. Time = Sun Apr 12 11:14:47 CST 2020 #### 4.3.1 `Runnable` vs `Callable` -`Runnable`自 Java 1.0 以来一直存在,但`Callable`仅在 Java 1.5 中引入,目的就是为了来处理`Runnable`不支持的用例。**`Runnable` 接口**不会返回结果或抛出检查异常,但是**`Callable` 接口**可以。所以,如果任务不需要返回结果或抛出异常推荐使用 **`Runnable` 接口**,这样代码看起来会更加简洁。 +`Runnable`自 Java 1.0 以来一直存在,但`Callable`仅在 Java 1.5 中引入,目的就是为了来处理`Runnable`不支持的用例。**`Runnable` 接口**不会返回结果或抛出检查异常,但是 **`Callable` 接口**可以。所以,如果任务不需要返回结果或抛出异常推荐使用 **`Runnable` 接口**,这样代码看起来会更加简洁。 -工具类 `Executors` 可以实现 `Runnable` 对象和 `Callable` 对象之间的相互转换。(`Executors.callable(Runnable task`)或 `Executors.callable(Runnable task,Object resule)`)。 +工具类 `Executors` 可以实现将 `Runnable` 对象转换成 `Callable` 对象。(`Executors.callable(Runnable task)` 或 `Executors.callable(Runnable task, Object result)`)。 `Runnable.java` diff --git "a/docs/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/docs/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" index 5b593a72cfc..69a02be43da 100644 --- "a/docs/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" +++ "b/docs/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" @@ -1,4 +1,4 @@ -## 一 OSI与TCP/IP各层的结构与功能,都有哪些协议? +## 一 OSI 与 TCP/IP 各层的结构与功能,都有哪些协议? 学习计算机网络时我们一般采用折中的办法,也就是中和 OSI 和 TCP/IP 的优点,采用一种只有五层协议的体系结构,这样既简洁又能将概念阐述清楚。 @@ -8,13 +8,13 @@ ### 1.1 应用层 -**应用层(application-layer)的任务是通过应用进程间的交互来完成特定网络应用。**应用层协议定义的是应用进程(进程:主机中正在运行的程序)间的通信和交互的规则。对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多,如**域名系统DNS**,支持万维网应用的 **HTTP协议**,支持电子邮件的 **SMTP协议**等等。我们把应用层交互的数据单元称为报文。 +**应用层(application-layer)的任务是通过应用进程间的交互来完成特定网络应用。**应用层协议定义的是应用进程(进程:主机中正在运行的程序)间的通信和交互的规则。对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多,如**域名系统 DNS**,支持万维网应用的 **HTTP 协议**,支持电子邮件的 **SMTP 协议**等等。我们把应用层交互的数据单元称为报文。 **域名系统** -> 域名系统(Domain Name System缩写 DNS,Domain Name被译为域名)是因特网的一项核心服务,它作为可以将域名和IP地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。(百度百科)例如:一个公司的 Web 网站可看作是它在网上的门户,而域名就相当于其门牌地址,通常域名都使用该公司的名称或简称。例如上面提到的微软公司的域名,类似的还有:IBM 公司的域名是 www.ibm.com、Oracle 公司的域名是 www.oracle.com、Cisco公司的域名是 www.cisco.com 等。 +> 域名系统(Domain Name System 缩写 DNS,Domain Name 被译为域名)是因特网的一项核心服务,它作为可以将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的 IP 数串。(百度百科)例如:一个公司的 Web 网站可看作是它在网上的门户,而域名就相当于其门牌地址,通常域名都使用该公司的名称或简称。例如上面提到的微软公司的域名,类似的还有:IBM 公司的域名是 www.ibm.com、Oracle 公司的域名是 www.oracle.com、Cisco 公司的域名是 www.cisco.com 等。 -**HTTP协议** +**HTTP 协议** > 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW(万维网) 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。(百度百科) @@ -29,7 +29,6 @@ **TCP 与 UDP 的对比见问题三。** - ### 1.3 网络层 **在计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。** 在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 **IP 协议**,因此分组也叫 **IP 数据报** ,简称 **数据报**。 @@ -38,20 +37,22 @@ 这里强调指出,网络层中的“网络”二字已经不是我们通常谈到的具体网络,而是指计算机网络体系结构模型中第三层的名称. -互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Internet Protocol)和许多路由选择协议,因此互联网的网络层也叫做**网际层**或**IP层**。 +互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Internet Protocol)和许多路由选择协议,因此互联网的网络层也叫做**网际层**或**IP 层**。 ### 1.4 数据链路层 + **数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。** 在两个相邻节点之间传送数据时,**数据链路层将网络层交下来的 IP 数据报组装成帧**,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。 在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。这样,数据链路层在收到一个帧后,就可从中提出数据部分,上交给网络层。 控制信息还使接收端能够检测到所收到的帧中有无差错。如果发现差错,数据链路层就简单地丢弃这个出了差错的帧,以避免继续在网络中传送下去白白浪费网络资源。如果需要改正数据在链路层传输时出现差错(这就是说,数据链路层不仅要检错,而且还要纠错),那么就要采用可靠性传输协议来纠正出现的差错。这种方法会使链路层的协议复杂些。 ### 1.5 物理层 + 在物理层上所传送的数据单位是比特。 - **物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异,** 使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。 +**物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异,** 使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。 -在互联网使用的各种协议中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的TCP/IP并不一定单指TCP和IP这两个具体的协议,而往往表示互联网所使用的整个TCP/IP协议族。 +在互联网使用的各种协议中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的 TCP/IP 并不一定单指 TCP 和 IP 这两个具体的协议,而往往表示互联网所使用的整个 TCP/IP 协议族。 ### 1.6 总结一下 @@ -61,11 +62,11 @@ ## 二 TCP 三次握手和四次挥手(面试常客) -为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。 +为了准确无误地把数据送达目标处,TCP 协议采用了三次握手策略。 ### 2.1 TCP 三次握手漫画图解 -如下图所示,下面的两个机器人通过3次握手确定了对方能正确接收和发送消息(图片来源:《图解HTTP》)。 +如下图所示,下面的两个机器人通过 3 次握手确定了对方能正确接收和发送消息(图片来源:《图解 HTTP》)。 ![TCP三次握手](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/三次握手.png) **简单示意图:** @@ -75,6 +76,10 @@ - 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端 - 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端 +**详细示意图(图片来源不详)** + +![](https://img-blog.csdnimg.cn/img_convert/0c9f470819684156cfdc27c682db4def.png) + ### 2.2 为什么要三次握手 **三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。** @@ -87,53 +92,55 @@ 所以三次握手就能确认双发收发功能都正常,缺一不可。 -### 2.3 第2次握手传回了ACK,为什么还要传回SYN? +### 2.3 第 2 次握手传回了 ACK,为什么还要传回 SYN? -接收端传回发送端所发送的ACK是为了告诉客户端,我接收到的信息确实就是你所发送的信号了,这表明从客户端到服务端的通信是正常的。而回传SYN则是为了建立并确认从服务端到客户端的通信。” +接收端传回发送端所发送的 ACK 是为了告诉客户端,我接收到的信息确实就是你所发送的信号了,这表明从客户端到服务端的通信是正常的。而回传 SYN 则是为了建立并确认从服务端到客户端的通信。” -> SYN 同步序列编号(Synchronize Sequence Numbers) 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。 +> SYN 同步序列编号(Synchronize Sequence Numbers) 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。 -### 2.5 为什么要四次挥手 +### 2.5 为什么要四次挥手 ![TCP四次挥手](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/TCP四次挥手.png) 断开一个 TCP 连接则需要“四次挥手”: - 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送 -- 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号 -- 服务器-关闭与客户端的连接,发送一个FIN给客户端 -- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1 +- 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加 1 。和 SYN 一样,一个 FIN 将占用一个序号 +- 服务器-关闭与客户端的连接,发送一个 FIN 给客户端 +- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加 1 -任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。 +任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。 -举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。 +举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。 上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891) ## 三 TCP,UDP 协议的区别 + ![TCP、UDP协议的区别](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/tcp-vs-udp.jpg) UDP 在传送数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 却是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等 -TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的传输服务(TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。 +TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的传输服务(TCP 的可靠体现在 TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。 ## 四 TCP 协议如何保证可靠传输 -1. 应用数据被分割成 TCP 认为最适合发送的数据块。 -2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。 -3. **校验和:** TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。 -4. TCP 的接收端会丢弃重复的数据。 -5. **流量控制:** TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制) +1. 应用数据被分割成 TCP 认为最适合发送的数据块。 +2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。 +3. **校验和:** TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。 +4. TCP 的接收端会丢弃重复的数据。 +5. **流量控制:** TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制) 6. **拥塞控制:** 当网络拥塞时,减少数据的发送。 -7. **ARQ协议:** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。 -8. **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。 +7. **ARQ 协议:** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。 +8. **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。 + +### 4.1 ARQ 协议 -### 4.1 ARQ协议 +**自动重传请求**(Automatic Repeat-reQuest,ARQ)是 OSI 模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ 包括停止等待 ARQ 协议和连续 ARQ 协议。 -**自动重传请求**(Automatic Repeat-reQuest,ARQ)是OSI模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ包括停止等待ARQ协议和连续ARQ协议。 +#### 停止等待 ARQ 协议 -#### 停止等待ARQ协议 -停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组。 +停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复 ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组。 在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认。 @@ -152,17 +159,17 @@ TCP 提供面向连接的服务。在传送数据之前必须先建立连接, **3) 确认丢失和确认迟到** -- **确认丢失** :确认消息在传输过程丢失。当A发送M1消息,B收到后,B向A发送了一个M1确认消息,但却在传输过程中丢失。而A并不知道,在超时计时过后,A重传M1消息,B再次收到该消息后采取以下两点措施:1. 丢弃这个重复的M1消息,不向上层交付。 2. 向A发送确认消息。(不会认为已经发送过了,就不再发送。A能重传,就证明B的确认消息丢失)。 -- **确认迟到** :确认消息在传输过程中迟到。A发送M1消息,B收到并发送确认。在超时时间内没有收到确认消息,A重传M1消息,B仍然收到并继续发送确认消息(B收到了2份M1)。此时A收到了B第二次发送的确认消息。接着发送其他数据。过了一会,A收到了B第一次发送的对M1的确认消息(A也收到了2份确认消息)。处理如下:1. A收到重复的确认后,直接丢弃。2. B收到重复的M1后,也直接丢弃重复的M1。 +- **确认丢失** :确认消息在传输过程丢失。当 A 发送 M1 消息,B 收到后,B 向 A 发送了一个 M1 确认消息,但却在传输过程中丢失。而 A 并不知道,在超时计时过后,A 重传 M1 消息,B 再次收到该消息后采取以下两点措施:1. 丢弃这个重复的 M1 消息,不向上层交付。 2. 向 A 发送确认消息。(不会认为已经发送过了,就不再发送。A 能重传,就证明 B 的确认消息丢失)。 +- **确认迟到** :确认消息在传输过程中迟到。A 发送 M1 消息,B 收到并发送确认。在超时时间内没有收到确认消息,A 重传 M1 消息,B 仍然收到并继续发送确认消息(B 收到了 2 份 M1)。此时 A 收到了 B 第二次发送的确认消息。接着发送其他数据。过了一会,A 收到了 B 第一次发送的对 M1 的确认消息(A 也收到了 2 份确认消息)。处理如下:1. A 收到重复的确认后,直接丢弃。2. B 收到重复的 M1 后,也直接丢弃重复的 M1。 -#### 连续ARQ协议 +#### 连续 ARQ 协议 连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。 **优缺点:** - **优点:** 信道利用率高,容易实现,即使确认丢失,也不必重传。 -- **缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5条 消息,中间第三条丢失(3号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。 +- **缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5 条 消息,中间第三条丢失(3 号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。 ### 4.2 滑动窗口和流量控制 @@ -174,32 +181,31 @@ TCP 提供面向连接的服务。在传送数据之前必须先建立连接, 为了进行拥塞控制,TCP 发送方要维持一个 **拥塞窗口(cwnd)** 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。 -TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。 +TCP 的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。 -- **慢开始:** 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1,每经过一个传播轮次,cwnd加倍。 -- **拥塞避免:** 拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大,即每经过一个往返时间RTT就把发送放的cwnd加1. -- **快重传与快恢复:** - 在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。  当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。 +- **慢开始:** 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd 初始值为 1,每经过一个传播轮次,cwnd 加倍。 +- **拥塞避免:** 拥塞避免算法的思路是让拥塞窗口 cwnd 缓慢增大,即每经过一个往返时间 RTT 就把发送放的 cwnd 加 1. +- **快重传与快恢复:** + 在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。  当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。 - -## 五 在浏览器中输入url地址 ->> 显示主页的过程(面试常客) +## 五 在浏览器中输入 url 地址 ->> 显示主页的过程(面试常客) 百度好像最喜欢问这个问题。 > 打开一个网页,整个过程会使用哪些协议? -图解(图片来源:《图解HTTP》): +图解(图片来源:《图解 HTTP》): -> 上图有一个错误,请注意,是OSPF不是OPSF。 OSPF(Open Shortest Path First,ospf)开放最短路径优先协议,是由Internet工程任务组开发的路由选择协议 +> 上图有一个错误,请注意,是 OSPF 不是 OPSF。 OSPF(Open Shortest Path First,ospf)开放最短路径优先协议,是由 Internet 工程任务组开发的路由选择协议 总体来说分为以下几个过程: -1. DNS解析 -2. TCP连接 -3. 发送HTTP请求 -4. 服务器处理请求并返回HTTP报文 +1. DNS 解析 +2. TCP 连接 +3. 发送 HTTP 请求 +4. 服务器处理请求并返回 HTTP 报文 5. 浏览器解析渲染页面 6. 连接结束 @@ -211,85 +217,84 @@ TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** ![状态码](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/状态码.png) +## 七 各种协议与 HTTP 协议之间的关系 -## 七 各种协议与HTTP协议之间的关系 一般面试官会通过这样的问题来考察你对计算机网络知识体系的理解。 -图片来源:《图解HTTP》 +图片来源:《图解 HTTP》 ![各种协议与HTTP协议之间的关系](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019/7/各种协议与HTTP协议之间的关系.png) -## 八 HTTP长连接,短连接 +## 八 HTTP 长连接,短连接 -在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。 +在 HTTP/1.0 中默认使用短连接。也就是说,客户端和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个 HTML 或其他类型的 Web 页中包含有其他的 Web 资源(如 JavaScript 文件、图像文件、CSS 文件等),每遇到这样一个 Web 资源,浏览器就会重新建立一个 HTTP 会话。 -而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码: +而从 HTTP/1.1 起,默认使用长连接,用以保持连接特性。使用长连接的 HTTP 协议,会在响应头加入这行代码: ``` Connection:keep-alive ``` -在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。 +在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive 不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如 Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。 -**HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。** +**HTTP 协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接。** -—— [《HTTP长连接、短连接究竟是什么?》](https://www.cnblogs.com/gotodsp/p/6366163.html) +—— [《HTTP 长连接、短连接究竟是什么?》](https://www.cnblogs.com/gotodsp/p/6366163.html) -## 九 HTTP是不保存状态的协议,如何保存用户状态? +## 九 HTTP 是不保存状态的协议,如何保存用户状态? -HTTP 是一种不保存状态,即无状态(stateless)协议。也就是说 HTTP 协议自身不对请求和响应之间的通信状态进行保存。那么我们保存用户状态呢?Session 机制的存在就是为了解决这个问题,Session 的主要作用就是通过服务端记录用户的状态。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了(一般情况下,服务器会在一定时间内保存这个 Session,过了时间限制,就会销毁这个Session)。 +HTTP 是一种不保存状态,即无状态(stateless)协议。也就是说 HTTP 协议自身不对请求和响应之间的通信状态进行保存。那么我们保存用户状态呢?Session 机制的存在就是为了解决这个问题,Session 的主要作用就是通过服务端记录用户的状态。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了(一般情况下,服务器会在一定时间内保存这个 Session,过了时间限制,就会销毁这个 Session)。 -在服务端保存 Session 的方法很多,最常用的就是内存和数据库(比如是使用内存数据库redis保存)。既然 Session 存放在服务器端,那么我们如何实现 Session 跟踪呢?大部分情况下,我们都是通过在 Cookie 中附加一个 Session ID 来方式来跟踪。 +在服务端保存 Session 的方法很多,最常用的就是内存和数据库(比如是使用内存数据库 redis 保存)。既然 Session 存放在服务器端,那么我们如何实现 Session 跟踪呢?大部分情况下,我们都是通过在 Cookie 中附加一个 Session ID 来方式来跟踪。 **Cookie 被禁用怎么办?** -最常用的就是利用 URL 重写把 Session ID 直接附加在URL路径的后面。 +最常用的就是利用 URL 重写把 Session ID 直接附加在 URL 路径的后面。 ![HTTP是无状态协议](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/HTTP是无状态的.png) -## 十 Cookie的作用是什么?和Session有什么区别? +## 十 Cookie 的作用是什么?和 Session 有什么区别? -Cookie 和 Session都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。 +Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。 - **Cookie 一般用来保存用户信息** 比如①我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;②一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③登录一次网站后访问网站其他页面不需要重新登录。**Session 的主要作用就是通过服务端记录用户的状态。** 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。 +**Cookie 一般用来保存用户信息** 比如 ① 我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;② 一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③ 登录一次网站后访问网站其他页面不需要重新登录。**Session 的主要作用就是通过服务端记录用户的状态。** 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。 Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。 -Cookie 存储在客户端中,而Session存储在服务器上,相对来说 Session 安全性更高。如果要在 Cookie 中存储一些敏感信息,不要直接写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。 +Cookie 存储在客户端中,而 Session 存储在服务器上,相对来说 Session 安全性更高。如果要在 Cookie 中存储一些敏感信息,不要直接写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。 -## 十一 HTTP 1.0和HTTP 1.1的主要区别是什么? +## 十一 HTTP 1.0 和 HTTP 1.1 的主要区别是什么? > 这部分回答引用这篇文章 的一些内容。 -HTTP1.0最早在网页中使用是在1996年,那个时候只是使用一些较为简单的网页上和网络请求上,而HTTP1.1则在1999年才开始广泛应用于现在的各大浏览器网络请求中,同时HTTP1.1也是当前使用最为广泛的HTTP协议。 主要区别主要体现在: +HTTP1.0 最早在网页中使用是在 1996 年,那个时候只是使用一些较为简单的网页上和网络请求上,而 HTTP1.1 则在 1999 年才开始广泛应用于现在的各大浏览器网络请求中,同时 HTTP1.1 也是当前使用最为广泛的 HTTP 协议。 主要区别主要体现在: -1. **长连接** : **在HTTP/1.0中,默认使用的是短连接**,也就是说每次请求都要重新建立一次连接。HTTP 是基于TCP/IP协议的,每一次建立或者断开连接都需要三次握手四次挥手的开销,如果每次请求都要这样的话,开销会比较大。因此最好能维持一个长连接,可以用个长连接来发多个请求。**HTTP 1.1起,默认使用长连接** ,默认开启Connection: keep-alive。 **HTTP/1.1的持续连接有非流水线方式和流水线方式** 。流水线方式是客户在收到HTTP的响应报文之前就能接着发送新的请求报文。与之相对应的非流水线方式是客户在收到前一个响应后才能发送下一个请求。 -1. **错误状态响应码** :在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。 -1. **缓存处理** :在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。 -1. **带宽优化及网络连接的使用** :HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。 +1. **长连接** : **在 HTTP/1.0 中,默认使用的是短连接**,也就是说每次请求都要重新建立一次连接。HTTP 是基于 TCP/IP 协议的,每一次建立或者断开连接都需要三次握手四次挥手的开销,如果每次请求都要这样的话,开销会比较大。因此最好能维持一个长连接,可以用个长连接来发多个请求。**HTTP 1.1 起,默认使用长连接** ,默认开启 Connection: keep-alive。 **HTTP/1.1 的持续连接有非流水线方式和流水线方式** 。流水线方式是客户在收到 HTTP 的响应报文之前就能接着发送新的请求报文。与之相对应的非流水线方式是客户在收到前一个响应后才能发送下一个请求。 +1. **错误状态响应码** :在 HTTP1.1 中新增了 24 个错误状态响应码,如 409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。 +1. **缓存处理** :在 HTTP1.0 中主要使用 header 里的 If-Modified-Since,Expires 来做为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓存策略。 +1. **带宽优化及网络连接的使用** :HTTP1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。 -## 十二 URI和URL的区别是什么? +## 十二 URI 和 URL 的区别是什么? - URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源。 -- URL(Uniform Resource Location) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。 +- URL(Uniform Resource Locator) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。 -URI的作用像身份证号一样,URL的作用更像家庭住址一样。URL是一种具体的URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。 +URI 的作用像身份证号一样,URL 的作用更像家庭住址一样。URL 是一种具体的 URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。 ## 十三 HTTP 和 HTTPS 的区别? -1. **端口** :HTTP的URL由“http://”起始且默认使用端口80,而HTTPS的URL由“https://”起始且默认使用端口443。 -2. **安全性和资源消耗:** HTTP协议运行在TCP之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS是运行在SSL/TLS之上的HTTP协议,SSL/TLS 运行在TCP之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS高,但是 HTTPS 比HTTP耗费更多服务器资源。 - - 对称加密:密钥只有一个,加密解密为同一个密码,且加解密速度快,典型的对称加密算法有DES、AES等; - - 非对称加密:密钥成对出现(且根据公钥无法推知私钥,根据私钥也无法推知公钥),加密解密使用不同密钥(公钥加密需要私钥解密,私钥加密需要公钥解密),相对对称加密速度较慢,典型的非对称加密算法有RSA、DSA等。 +1. **端口** :HTTP 的 URL 由“http://”起始且默认使用端口80,而HTTPS的URL由“https://”起始且默认使用端口443。 +2. **安全性和资源消耗:** HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。 + - 对称加密:密钥只有一个,加密解密为同一个密码,且加解密速度快,典型的对称加密算法有 DES、AES 等; + - 非对称加密:密钥成对出现(且根据公钥无法推知私钥,根据私钥也无法推知公钥),加密解密使用不同密钥(公钥加密需要私钥解密,私钥加密需要公钥解密),相对对称加密速度较慢,典型的非对称加密算法有 RSA、DSA 等。 ## 建议 -非常推荐大家看一下 《图解HTTP》 这本书,这本书页数不多,但是内容很是充实,不管是用来系统的掌握网络方面的一些知识还是说纯粹为了应付面试都有很大帮助。下面的一些文章只是参考。大二学习这门课程的时候,我们使用的教材是 《计算机网络第七版》(谢希仁编著),不推荐大家看这本教材,书非常厚而且知识偏理论,不确定大家能不能心平气和的读完。 +非常推荐大家看一下 《图解 HTTP》 这本书,这本书页数不多,但是内容很是充实,不管是用来系统的掌握网络方面的一些知识还是说纯粹为了应付面试都有很大帮助。下面的一些文章只是参考。大二学习这门课程的时候,我们使用的教材是 《计算机网络第七版》(谢希仁编著),不推荐大家看这本教材,书非常厚而且知识偏理论,不确定大家能不能心平气和的读完。 ## 参考 - [https://blog.csdn.net/qq_16209077/article/details/52718250](https://blog.csdn.net/qq_16209077/article/details/52718250) - [https://blog.csdn.net/zixiaomuwu/article/details/60965466](https://blog.csdn.net/zixiaomuwu/article/details/60965466) -- [https://blog.csdn.net/turn__back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641) +- [https://blog.csdn.net/turn\_\_back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641) - - diff --git a/docs/system-design/authority-certification/images/basis-of-authority-certification/session-cookie.drawio b/docs/system-design/authority-certification/images/basis-of-authority-certification/session-cookie.drawio index 6a1b06b5a8f..c5d46f5767f 100644 --- a/docs/system-design/authority-certification/images/basis-of-authority-certification/session-cookie.drawio +++ b/docs/system-design/authority-certification/images/basis-of-authority-certification/session-cookie.drawio @@ -1 +1 @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/docs/system-design/framework/mybatis/mybatis-interview.md b/docs/system-design/framework/mybatis/mybatis-interview.md index e1195390b1a..db84dee3b5c 100644 --- a/docs/system-design/framework/mybatis/mybatis-interview.md +++ b/docs/system-design/framework/mybatis/mybatis-interview.md @@ -147,7 +147,7 @@ public V get(Object key) { 注:我出的。 -答:MyBatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页,可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。 +答:**(1)** MyBatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页;**(2)** 可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,**(3)** 也可以使用分页插件来完成物理分页。 分页插件的基本原理是使用 MyBatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。 diff --git "a/docs/system-design/framework/spring/Spring\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" "b/docs/system-design/framework/spring/Spring\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" index 774d4eb0a53..529406be945 100644 --- "a/docs/system-design/framework/spring/Spring\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" +++ "b/docs/system-design/framework/spring/Spring\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" @@ -1,131 +1,180 @@ -这篇文章主要是想通过一些问题,加深大家对于 Spring 的理解,所以不会涉及太多的代码!这篇文章整理了挺长时间,下面的很多问题我自己在使用 Spring 的过程中也并没有注意,自己也是临时查阅了很多资料和书籍补上的。网上也有一些很多关于 Spring 常见问题/面试题整理的文章,我感觉大部分都是互相 copy,而且很多问题也不是很好,有些回答也存在问题。所以,自己花了一周的业余时间整理了一下,希望对大家有帮助。 +这篇文章主要是想通过一些问题,加深大家对于 Spring 的理解,所以不会涉及太多的代码! -## 1. 什么是 Spring 框架? +下面的很多问题我自己在使用 Spring 的过程中也并没有注意,自己也是临时查阅了很多资料和书籍补上的。网上也有一些很多关于 Spring 常见问题/面试题整理的文章,我感觉大部分都是互相 copy,而且很多问题也不是很好,有些回答也存在问题。所以,自己花了一周的业余时间整理了一下,希望对大家有帮助。 -Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。Spring 官网:。 +## 什么是 Spring 框架? -我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。比如:Core Container 中的 Core 组件是Spring 所有组件的核心,Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。 +Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。 -Spring 官网列出的 Spring 的 6 个特征: +Spring 翻译过来就是春天的意思,可见其目标和使命就是为 Java 程序员带来春天啊!感动! -- **核心技术** :依赖注入(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL。 -- **测试** :模拟对象,TestContext框架,Spring MVC 测试,WebTestClient。 -- **数据访问** :事务,DAO支持,JDBC,ORM,编组XML。 -- **Web支持** : Spring MVC和Spring WebFlux Web框架。 -- **集成** :远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。 -- **语言** :Kotlin,Groovy,动态语言。 +> 题外话 : 语言的流行通常需要一个杀手级的应用,Spring 就是 Java 生态的一个杀手级的应用框架。 -## 2. 列举一些重要的Spring模块? +我们一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。 -下图对应的是 Spring4.x 版本。目前最新的5.x版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。 +比如说 Spring 自带 IoC(Inverse of Control:控制反转) 和 AOP(Aspect-Oriented Programming:面向切面编程)、可以很方便地对数据库进行访问、可以很方便地集成第三方组件(电子邮件,任务,调度,缓存等等)、对单元测试支持比较好、支持 RESTful Java 应用程序的开发。 -![Spring主要模块](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/Spring主要模块.png) +![](https://img-blog.csdnimg.cn/38ef122122de4375abcd27c3de8f60b4.png) -- **Spring Core:** 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注入功能。 -- **Spring Aspects** : 该模块为与AspectJ的集成提供支持。 -- **Spring AOP** :提供了面向切面的编程实现。 -- **Spring JDBC** : Java数据库连接。 -- **Spring JMS** :Java消息服务。 -- **Spring ORM** : 用于支持Hibernate等ORM工具。 -- **Spring Web** : 为创建Web应用程序提供支持。 -- **Spring Test** : 提供了对 JUnit 和 TestNG 测试的支持。 +Spring 最核心的思想就是不重新造轮子,开箱即用! -## 3. @RestController vs @Controller +Spring 提供的核心功能主要是 IoC 和 AOP。学习 Spring ,一定要把 IoC 和 AOP 的核心思想搞懂! -**`Controller` 返回一个页面** +- Spring 官网: +- Github 地址: https://github.com/spring-projects/spring-framework -单独使用 `@Controller` 不加 `@ResponseBody`的话一般使用在要返回一个视图的情况,这种情况属于比较传统的Spring MVC 的应用,对应于前后端不分离的情况。 +## 列举一些重要的 Spring 模块? -![SpringMVC 传统工作流程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/SpringMVC传统工作流程.png) +下图对应的是 Spring4.x 版本。目前最新的 5.x 版本中 Web 模块的 Portlet 组件已经被废弃掉,同时增加了用于异步响应式处理的 WebFlux 组件。 -**`@RestController` 返回JSON 或 XML 形式数据** +![Spring主要模块](https://images.xiaozhuanlan.com/photo/2019/e0c60b4606711fc4a0b6faf03230247a.png) -但`@RestController`只返回对象,对象数据直接以 JSON 或 XML 形式写入 HTTP 响应(Response)中,这种情况属于 RESTful Web服务,这也是目前日常开发所接触的最常用的情况(前后端分离)。 +**Spring Core** -![SpringMVC+RestController](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/SpringMVCRestController.png) +核心模块, Spring 其他所有的功能基本都需要依赖于该类库,主要提供 IoC 依赖注入功能的支持。 -**`@Controller +@ResponseBody` 返回JSON 或 XML 形式数据** +**Spring Aspects** -如果你需要在Spring4之前开发 RESTful Web服务的话,你需要使用`@Controller` 并结合`@ResponseBody`注解,也就是说`@Controller` +`@ResponseBody`= `@RestController`(Spring 4 之后新加的注解)。 +该模块为与 AspectJ 的集成提供支持。 -> `@ResponseBody` 注解的作用是将 `Controller` 的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到HTTP 响应(Response)对象的 body 中,通常用来返回 JSON 或者 XML 数据,返回 JSON 数据的情况比较多。 +**Spring AOP** -![Spring3.xMVC RESTfulWeb服务工作流程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/Spring3.xMVCRESTfulWeb服务工作流程.png) +提供了面向切面的编程实现。 -Reference: +**Spring Data Access/Integration :** -- https://dzone.com/articles/spring-framework-restcontroller-vs-controller (图片来源) -- https://javarevisited.blogspot.com/2017/08/difference-between-restcontroller-and-controller-annotations-spring-mvc-rest.html?m=1 +Spring Data Access/Integration 由 5 个模块组成: -## 4. Spring IOC & AOP +- spring-jdbc : 提供了对数据库访问的抽象 JDBC。不同的数据库都有自己独立的 API 用于操作数据库,而 Java 程序只需要和 JDBC API 交互,这样就屏蔽了数据库的影响。 +- spring-tx : 提供对事务的支持。 +- spring-orm : 提供对 Hibernate 等 ORM 框架的支持。 +- spring-oxm : 提供对 Castor 等 OXM 框架的支持。 +- spring-jms : Java 消息服务。 -### 4.1 谈谈自己对于 Spring IoC 和 AOP 的理解 +**Spring Web** -#### IoC +Spring Web 由 4 个模块组成: -IoC(Inverse of Control:控制反转)是一种**设计思想**,就是 **将原本在程序中手动创建对象的控制权,交由Spring框架来管理。** IoC 在其他语言中也有应用,并非 Spring 特有。 **IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。** +- spring-web :对 Web 功能的实现提供一些最基础的支持。 +- spring-webmvc : 提供对 Spring MVC 的实现。 +- spring-websocket : 提供了对 WebSocket 的支持,WebSocket 可以让客户端和服务端进行双向通信。 +- spring-webflux :提供对 WebFlux 的支持。WebFlux 是 Spring Framework 5.0 中引入的新的响应式框架。与 Spring MVC 不同,它不需要 Servlet API,是完全异步. -将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 **IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。** 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。 +**Spring Test** -Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。 +Spring 团队提倡测试驱动开发(TDD)。有了控制反转 (IoC)的帮助,单元测试和集成测试变得更简单。 + +Spring 的测试模块对 JUnit(单元测试框架)、TestNG(类似 JUnit)、Mockito(主要用来 Mock 对象)、PowerMock(解决 Mockito 的问题比如无法模拟 final, static, private 方法)等等常用的测试框架支持的都比较好。 + +## Spring IOC & AOP + +### 谈谈自己对于 Spring IoC 的了解 + +**IoC(Inverse of Control:控制反转)** 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spirng 特有,在其他语言中也有应用。 -推荐阅读:https://www.zhihu.com/question/23277575/answer/169698662 +**为什么叫控制反转?** -**Spring IoC的初始化过程:** +- **控制** :指的是对象创建(实例化、管理)的权力 +- **反转** :控制权交给外部环境(Spring 框架、IoC 容器) -![Spring IoC的初始化过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/SpringIOC初始化过程.png) +![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/frc-365faceb5697f04f31399937c059c162.png) -IoC源码阅读 +将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 -- https://javadoop.com/post/spring-ioc +在实际项目中一个 Service 类可能依赖了很多其他的类,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。 -#### AOP +在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。 -AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,**却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来**,便于**减少系统的重复代码**,**降低模块间的耦合度**,并**有利于未来的可拓展性和可维护性**。 +Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。 + +相关阅读: + +- [IoC 源码阅读](https://javadoop.com/post/spring-ioc) +- [面试被问了几百遍的 IoC 和 AOP ,还在傻傻搞不清楚?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486938&idx=1&sn=c99ef0233f39a5ffc1b98c81e02dfcd4&chksm=cea24211f9d5cb07fa901183ba4d96187820713a72387788408040822ffb2ed575d28e953ce7&token=1736772241&lang=zh_CN#rd) -**Spring AOP就是基于动态代理的**,如果要代理的对象,实现了某个接口,那么Spring AOP会使用**JDK Proxy**,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用**Cglib**生成一个被代理对象的子类来作为代理,如下图所示: +### 谈谈自己对于 AOP 的了解 -![SpringAOPProcess](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/SpringAOPProcess.jpg) +AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码**,**降低模块间的耦合度,并有利于未来的可拓展性和可维护性。 -当然你也可以使用 AspectJ,Spring AOP 已经集成了AspectJ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。 +Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 **JDK Proxy**,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 **Cglib** ,这时候 Spring AOP 会使用 **Cglib** 生成一个被代理对象的子类来作为代理,如下图所示: -使用 AOP 之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样大大简化了代码量。我们需要增加新功能时也方便,这样也提高了系统扩展性。日志功能、事务管理等等场景都用到了 AOP 。 +![SpringAOPProcess](https://images.xiaozhuanlan.com/photo/2019/926dfc549b06d280a37397f9fd49bf9d.jpg) -### 4.2 Spring AOP 和 AspectJ AOP 有什么区别? +当然你也可以使用 **AspectJ** !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。 + +### Spring AOP 和 AspectJ AOP 有什么区别? **Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。** Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。 - Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单, +Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单, -如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比Spring AOP 快很多。 +如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。 -## 5. Spring bean +## Spring bean -### 5.1 Spring 中的 bean 的作用域有哪些? +### 什么是 bean? -- singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。 -- prototype : 每次请求都会创建一个新的 bean 实例。 -- request : 每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效。 -- session : 每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效。 -- global-session: 全局session作用域,仅仅在基于portlet的web应用中才有意义,Spring5已经没有了。Portlet是能够生成语义代码(例如:HTML)片段的小型Java Web插件。它们基于portlet容器,可以像servlet一样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话 +简单来说,bean 代指的就是那些被 IoC 容器所管理的对象。 -### 5.2 Spring 中的单例 bean 的线程安全问题了解吗? +我们需要告诉 IoC 容器帮助我们管理哪些对象,这个是通过配置元数据的定义的。配置元数据可以是 XML 文件、注解或者 Java 配置类。 -的确是存在安全问题的。因为,当多个线程操作同一个对象的时候,对这个对象的成员变量的写操作会存在线程安全问题。 +```xml + + + + +``` + +下图简单地展示了 IoC 容器如何使用配置元数据来管理对象。 + +![](https://img-blog.csdnimg.cn/062b422bd7ac4d53afd28fb74b2bc94d.png) -但是,一般情况下,我们常用的 `Controller`、`Service`、`Dao` 这些 Bean 是无状态的。无状态的 Bean 不能保存数据,因此是线程安全的。 +`org.springframework.beans`和 `org.springframework.context` 这两个包是 IoC 实现的基础,如果想要研究 IoC 相关的源码的话,可以去看看 -常见的有 2 种解决办法: +### bean 的作用域有哪些? -1. 在类中定义一个 `ThreadLocal` 成员变量,将需要的可变成员变量保存在 `ThreadLocal` 中(推荐的一种方式)。 -2. 改变 Bean 的作用域为 “prototype”:每次请求都会创建一个新的 bean 实例,自然不会存在线程安全问题。 +Spring 中 Bean 的作用域通常由下面几种: +- **singleton** : 唯一 bean 实例,Spring 中的 bean 默认都是单例的,对单例设计模式的应用。 +- **prototype** : 每次请求都会创建一个新的 bean 实例。 +- **request** : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。 +- **session** : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。 +- **global-session** : 全局 session 作用域,仅仅在基于 portlet 的 web 应用中才有意义,Spring5 已经没有了。Portlet 是能够生成语义代码(例如:HTML)片段的小型 Java Web 插件。它们基于 portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。 -### 5.3 @Component 和 @Bean 的区别是什么? +**如何配置 bean 的作用域呢?** -1. 作用对象不同: `@Component` 注解作用于类,而`@Bean`注解作用于方法。 -2. `@Component`通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用 `@ComponentScan` 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。`@Bean` 注解通常是我们在标有该注解的方法中定义产生这个 bean,`@Bean`告诉了Spring这是某个类的示例,当我需要用它的时候还给我。 -3. `@Bean` 注解比 `Component` 注解的自定义性更强,而且很多地方我们只能通过 `@Bean` 注解来注册bean。比如当我们引用第三方库中的类需要装配到 `Spring`容器时,则只能通过 `@Bean`来实现。 +xml 方式: + +```xml + +``` + +注解方式: + +```java +@Bean +@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public Person personPrototype() { + return new Person(); +} +``` + +### 单例 bean 的线程安全问题了解吗? + +大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。 + +常见的有两种解决办法: + +1. 在 bean 中尽量避免定义可变的成员变量。 +2. 在类中定义一个 `ThreadLocal` 成员变量,将需要的可变成员变量保存在 `ThreadLocal` 中(推荐的一种方式)。 + +不过,大部分 bean 实际都是无状态(没有实例变量)的(比如 Dao、Service),这种情况下, bean 是线程安全的。 + +### @Component 和 @Bean 的区别是什么? + +1. `@Component` 注解作用于类,而`@Bean`注解作用于方法。 +2. `@Component`通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 `@ComponentScan` 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。`@Bean` 注解通常是我们在标有该注解的方法中定义产生这个 bean,`@Bean`告诉了 Spring 这是某个类的示例,当我需要用它的时候还给我。 +3. `@Bean` 注解比 `Component` 注解的自定义性更强,而且很多地方我们只能通过 `@Bean` 注解来注册 bean。比如当我们引用第三方库中的类需要装配到 `Spring`容器时,则只能通过 `@Bean`来实现。 `@Bean`注解使用示例: @@ -140,7 +189,7 @@ public class AppConfig { } ``` - 上面的代码相当于下面的 xml 配置 +上面的代码相当于下面的 xml 配置 ```xml @@ -164,163 +213,225 @@ public OneService getService(status) { } ``` -### 5.4 将一个类声明为Spring的 bean 的注解有哪些? +### 将一个类声明为 bean 的注解有哪些? 我们一般使用 `@Autowired` 注解自动装配 bean,要想把类标识成可用于 `@Autowired` 注解自动装配的 bean 的类,采用以下注解可实现: -- `@Component` :通用的注解,可标注任意类为 `Spring` 组件。如果一个Bean不知道属于哪个层,可以使用`@Component` 注解标注。 +- `@Component` :通用的注解,可标注任意类为 `Spring` 组件。如果一个 Bean 不知道属于哪个层,可以使用`@Component` 注解标注。 - `@Repository` : 对应持久层即 Dao 层,主要用于数据库相关操作。 -- `@Service` : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。 -- `@Controller` : 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service 层返回数据给前端页面。 +- `@Service` : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。 +- `@Controller` : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。 -### 5.5 Spring 中的 bean 生命周期? +### bean 的生命周期? -这部分网上有很多文章都讲到了,下面的内容整理自: ~~https://yemengying.com/2016/07/14/spring-bean-life-cycle/~~ (原作者可能不再维护这个博客,连接无法访问,可通过其 Github 仓库访问 ) ,除了这篇文章,再推荐一篇很不错的文章 : 。 +> 下面的内容整理自: ,除了这篇文章,再推荐一篇很不错的文章 : 。 - Bean 容器找到配置文件中 Spring Bean 的定义。 -- Bean 容器利用 Java Reflection API 创建一个Bean的实例。 +- Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。 - 如果涉及到一些属性值 利用 `set()`方法设置一些属性值。 -- 如果 Bean 实现了 `BeanNameAware` 接口,调用 `setBeanName()`方法,传入Bean的名字。 +- 如果 Bean 实现了 `BeanNameAware` 接口,调用 `setBeanName()`方法,传入 Bean 的名字。 - 如果 Bean 实现了 `BeanClassLoaderAware` 接口,调用 `setBeanClassLoader()`方法,传入 `ClassLoader`对象的实例。 +- 如果 Bean 实现了 `BeanFactoryAware` 接口,调用 `setBeanClassLoader()`方法,传入 `ClassLoade` r 对象的实例。 - 与上面的类似,如果实现了其他 `*.Aware`接口,就调用相应的方法。 - 如果有和加载这个 Bean 的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessBeforeInitialization()` 方法 -- 如果Bean实现了`InitializingBean`接口,执行`afterPropertiesSet()`方法。 -- 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。 -- 如果有和加载这个 Bean的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessAfterInitialization()` 方法 +- 如果 Bean 实现了`InitializingBean`接口,执行`afterPropertiesSet()`方法。 +- 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。 +- 如果有和加载这个 Bean 的 Spring 容器相关的 `BeanPostProcessor` 对象,执行`postProcessAfterInitialization()` 方法 - 当要销毁 Bean 的时候,如果 Bean 实现了 `DisposableBean` 接口,执行 `destroy()` 方法。 - 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。 图示: -![Spring Bean 生命周期](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/48376272.jpg) +![Spring Bean 生命周期](https://images.xiaozhuanlan.com/photo/2019/24bc2bad3ce28144d60d9e0a2edf6c7f.jpg) 与之比较类似的中文版本: -![Spring Bean 生命周期](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/5496407.jpg) +![Spring Bean 生命周期](https://images.xiaozhuanlan.com/photo/2019/b5d264565657a5395c2781081a7483e1.jpg) + +## Spring MVC + +### 说说自己对于 Spring MVC 了解? + +MVC 是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。 + +![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/image-20210809181452421.png) + +网上有很多人说 MVC 不是设计模式,只是软件设计规范,我个人更倾向于 MVC 同样是众多设计模式中的一种。**[java-design-patterns](https://github.com/iluwatar/java-design-patterns)** 项目中就有关于 MVC 的相关介绍。 + +![](https://img-blog.csdnimg.cn/159b3d3e70dd45e6afa81bf06d09264e.png) + +想要真正理解 Spring MVC,我们先来看看 Model 1 和 Model 2 这两个没有 Spring MVC 的时代。 + +**Model 1 时代** -## 6. Spring MVC +很多学 Java 后端比较晚的朋友可能并没有接触过 Model 1 时代下的 JavaWeb 应用开发。在 Model1 模式下,整个 Web 应用几乎全部用 JSP 页面组成,只用少量的 JavaBean 来处理数据库连接、访问等操作。 -### 6.1 说说自己对于 Spring MVC 了解? +这个模式下 JSP 即是控制层(Controller)又是表现层(View)。显而易见,这种模式存在很多问题。比如控制逻辑和表现逻辑混杂在一起,导致代码重用率极低;再比如前端和后端相互依赖,难以进行测试维护并且开发效率极低。 -谈到这个问题,我们不得不提提之前 Model1 和 Model2 这两个没有 Spring MVC 的时代。 +![mvc-mode1](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/mvc-mode1.png) -- **Model1 时代** : 很多学 Java 后端比较晚的朋友可能并没有接触过 Model1 模式下的 JavaWeb 应用开发。在 Model1 模式下,整个 Web 应用几乎全部用 JSP 页面组成,只用少量的 JavaBean 来处理数据库连接、访问等操作。这个模式下 JSP 既是控制层又是表现层。显而易见,这种模式存在很多问题。比如①将控制逻辑和表现逻辑混杂在一起,导致代码重用率极低;②前端和后端相互依赖,难以进行测试并且开发效率极低; -- **Model2 时代** :学过 Servlet 并做过相关 Demo 的朋友应该了解“Java Bean(Model)+ JSP(View,)+Servlet(Controller) ”这种开发模式,这就是早期的 JavaWeb MVC 开发模式。Model:系统涉及的数据,也就是 dao 和 bean。View:展示模型中的数据,只是用来展示。Controller:处理用户请求都发送给 ,返回数据给 JSP 并展示给用户。 +**Model 2 时代** -Model2 模式下还存在很多问题,Model2的抽象和封装程度还远远不够,使用Model2进行开发时不可避免地会重复造轮子,这就大大降低了程序的可维护性和复用性。于是很多JavaWeb开发相关的 MVC 框架应运而生比如Struts2,但是 Struts2 比较笨重。随着 Spring 轻量级开发框架的流行,Spring 生态圈出现了 Spring MVC 框架, Spring MVC 是当前最优秀的 MVC 框架。相比于 Struts2 , Spring MVC 使用更加简单和方便,开发效率更高,并且 Spring MVC 运行速度更快。 +学过 Servlet 并做过相关 Demo 的朋友应该了解“Java Bean(Model)+ JSP(View)+Servlet(Controller) ”这种开发模式,这就是早期的 JavaWeb MVC 开发模式。 -MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的Web层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台页面)。 +- Model:系统涉及的数据,也就是 dao 和 bean。 +- View:展示模型中的数据,只是用来展示。 +- Controller:处理用户请求都发送给 ,返回数据给 JSP 并展示给用户。 -**Spring MVC 的简单原理图如下:** +![](https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/mvc-model2.png) -![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/60679444.jpg) +Model2 模式下还存在很多问题,Model2 的抽象和封装程度还远远不够,使用 Model2 进行开发时不可避免地会重复造轮子,这就大大降低了程序的可维护性和复用性。 -### 6.2 SpringMVC 工作原理了解吗? +于是,很多 JavaWeb 开发相关的 MVC 框架应运而生比如 Struts2,但是 Struts2 比较笨重。 -**原理如下图所示:** -![SpringMVC运行原理](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/49790288.jpg) +**Spring MVC 时代** -上图的一个笔误的小问题:Spring MVC 的入口函数也就是前端控制器 `DispatcherServlet` 的作用是接收请求,响应结果。 +随着 Spring 轻量级开发框架的流行,Spring 生态圈出现了 Spring MVC 框架, Spring MVC 是当前最优秀的 MVC 框架。相比于 Struts2 , Spring MVC 使用更加简单和方便,开发效率更高,并且 Spring MVC 运行速度更快。 + +MVC 是一种设计模式,Spring MVC 是一款很优秀的 MVC 框架。Spring MVC 可以帮助我们进行更简洁的 Web 层的开发,并且它天生与 Spring 框架集成。Spring MVC 下我们一般把后端项目分为 Service 层(处理业务)、Dao 层(数据库操作)、Entity 层(实体类)、Controller 层(控制层,返回数据给前台页面)。 + +### SpringMVC 工作原理了解吗? + +**Spring MVC 原理如下图所示:** + +> SpringMVC 工作原理的图解我没有自己画,直接图省事在网上找了一个非常清晰直观的,原出处不明。 + +![](https://img-blog.csdnimg.cn/img_convert/de6d2b213f112297298f3e223bf08f28.png) **流程说明(重要):** 1. 客户端(浏览器)发送请求,直接请求到 `DispatcherServlet`。 2. `DispatcherServlet` 根据请求信息调用 `HandlerMapping`,解析请求对应的 `Handler`。 3. 解析到对应的 `Handler`(也就是我们平常说的 `Controller` 控制器)后,开始由 `HandlerAdapter` 适配器处理。 -4. `HandlerAdapter` 会根据 `Handler `来调用真正的处理器来处理请求,并处理相应的业务逻辑。 +4. `HandlerAdapter` 会根据 `Handler`来调用真正的处理器开处理请求,并处理相应的业务逻辑。 5. 处理器处理完业务后,会返回一个 `ModelAndView` 对象,`Model` 是返回的数据对象,`View` 是个逻辑上的 `View`。 6. `ViewResolver` 会根据逻辑 `View` 查找实际的 `View`。 7. `DispaterServlet` 把返回的 `Model` 传给 `View`(视图渲染)。 8. 把 `View` 返回给请求者(浏览器) -## 7. Spring 框架中用到了哪些设计模式? +## Spring 框架中用到了哪些设计模式? -关于下面一些设计模式的详细介绍,可以看笔主前段时间的原创文章[《面试官:“谈谈Spring中都用到了那些设计模式?”。》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485303&idx=1&sn=9e4626a1e3f001f9b0d84a6fa0cff04a&chksm=cea248bcf9d5c1aaf48b67cc52bac74eb29d6037848d6cf213b0e5466f2d1fda970db700ba41&token=255050878&lang=zh_CN#rd) 。 +关于下面一些设计模式的详细介绍,可以看笔主前段时间的原创文章[《面试官:“谈谈 Spring 中都用到了那些设计模式?”。》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485303&idx=1&sn=9e4626a1e3f001f9b0d84a6fa0cff04a&chksm=cea248bcf9d5c1aaf48b67cc52bac74eb29d6037848d6cf213b0e5466f2d1fda970db700ba41&token=255050878&lang=zh_CN#rd) 。 -- **工厂设计模式** : Spring使用工厂模式通过 `BeanFactory`、`ApplicationContext` 创建 bean 对象。 +- **工厂设计模式** : Spring 使用工厂模式通过 `BeanFactory`、`ApplicationContext` 创建 bean 对象。 - **代理设计模式** : Spring AOP 功能的实现。 - **单例设计模式** : Spring 中的 Bean 默认都是单例的。 - **模板方法模式** : Spring 中 `jdbcTemplate`、`hibernateTemplate` 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。 - **包装器设计模式** : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。 - **观察者模式:** Spring 事件驱动模型就是观察者模式很经典的一个应用。 -- **适配器模式** :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配`Controller`。 +- **适配器模式** : Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配`Controller`。 - ...... -## 8. Spring 事务 +## Spring 事务 -### 8.1 Spring 管理事务的方式有几种? +Spring/SpringBoot 模块下专门有一篇是讲 Spring 事务的,总结的非常详细,通俗易懂。 -1. 编程式事务,在代码中硬编码。(不推荐使用) -2. 声明式事务,在配置文件中配置(推荐使用) +### Spring 管理事务的方式有几种? -**声明式事务又分为两种:** +- **编程式事务** : 在代码中硬编码(不推荐使用) : 通过 `TransactionTemplate`或者 `TransactionManager` 手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。 +- **声明式事务** : 在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于`@Transactional` 的全注解方式使用最多) -1. 基于XML的声明式事务 -2. 基于注解的声明式事务 +### Spring 事务中哪几种事务传播行为? -### 8.2 Spring 事务中的隔离级别有哪几种? +**事务传播行为是为了解决业务层方法之间互相调用的事务问题**。 -**TransactionDefinition 接口中定义了五个表示隔离级别的常量:** +当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。 -- **TransactionDefinition.ISOLATION_DEFAULT:** 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别. -- **TransactionDefinition.ISOLATION_READ_UNCOMMITTED:** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读** -- **TransactionDefinition.ISOLATION_READ_COMMITTED:** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生** -- **TransactionDefinition.ISOLATION_REPEATABLE_READ:** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生。** -- **TransactionDefinition.ISOLATION_SERIALIZABLE:** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 +正确的事务传播行为可能的值如下: -### 8.3 Spring 事务中哪几种事务传播行为? +**1.`TransactionDefinition.PROPAGATION_REQUIRED`** -**支持当前事务的情况:** +使用的最多的一个事务传播行为,我们平时经常使用的`@Transactional`注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 -- **TransactionDefinition.PROPAGATION_REQUIRED:** 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 -- **TransactionDefinition.PROPAGATION_SUPPORTS:** 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 -- **TransactionDefinition.PROPAGATION_MANDATORY:** 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性) +**`2.TransactionDefinition.PROPAGATION_REQUIRES_NEW`** -**不支持当前事务的情况:** +创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,`Propagation.REQUIRES_NEW`修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。 -- **TransactionDefinition.PROPAGATION_REQUIRES_NEW:** 创建一个新的事务,如果当前存在事务,则把当前事务挂起。 -- **TransactionDefinition.PROPAGATION_NOT_SUPPORTED:** 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 -- **TransactionDefinition.PROPAGATION_NEVER:** 以非事务方式运行,如果当前存在事务,则抛出异常。 +**3.`TransactionDefinition.PROPAGATION_NESTED`** -**其他情况:** +如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于`TransactionDefinition.PROPAGATION_REQUIRED`。 -- **TransactionDefinition.PROPAGATION_NESTED:** 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。 +**4.`TransactionDefinition.PROPAGATION_MANDATORY`** -### 8.4 @Transactional(rollbackFor = Exception.class)注解了解吗? +如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性) -我们知道:Exception分为运行时异常RuntimeException和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。 +这个使用的很少。 -当`@Transactional`注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。 +若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚: + +- **`TransactionDefinition.PROPAGATION_SUPPORTS`**: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 +- **`TransactionDefinition.PROPAGATION_NOT_SUPPORTED`**: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 +- **`TransactionDefinition.PROPAGATION_NEVER`**: 以非事务方式运行,如果当前存在事务,则抛出异常。 + +### Spring 事务中的隔离级别有哪几种? + +和事务传播行为这块一样,为了方便使用,Spring 也相应地定义了一个枚举类:`Isolation` + +```java +public enum Isolation { -在`@Transactional`注解中如果不配置`rollbackFor`属性,那么事务只会在遇到`RuntimeException`的时候才会回滚,加上`rollbackFor=Exception.class`,可以让事务在遇到非运行时异常时也回滚。 + DEFAULT(TransactionDefinition.ISOLATION_DEFAULT), -关于 `@Transactional ` 注解推荐阅读的文章: + READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED), -- [透彻的掌握 Spring 中@transactional 的使用](https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html) + READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), -## 9. JPA + REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), + + SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); + + private final int value; + + Isolation(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + +} +``` -### 9.1 如何使用JPA在数据库中非持久化一个字段? +下面我依次对每一种事务隔离级别进行介绍: + +- **`TransactionDefinition.ISOLATION_DEFAULT`** :使用后端数据库默认的隔离级别,MySQL 默认采用的 `REPEATABLE_READ` 隔离级别 Oracle 默认采用的 `READ_COMMITTED` 隔离级别. +- **`TransactionDefinition.ISOLATION_READ_UNCOMMITTED`** :最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读** +- **`TransactionDefinition.ISOLATION_READ_COMMITTED`** : 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生** +- **`TransactionDefinition.ISOLATION_REPEATABLE_READ`** : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生。** +- **`TransactionDefinition.ISOLATION_SERIALIZABLE`** : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 + +### @Transactional(rollbackFor = Exception.class)注解了解吗? + +`Exception` 分为运行时异常 `RuntimeException` 和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。 + +当 `@Transactional` 注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。 + +在 `@Transactional` 注解中如果不配置`rollbackFor`属性,那么事物只会在遇到`RuntimeException`的时候才会回滚,加上 `rollbackFor=Exception.class`,可以让事物在遇到非运行时异常时也回滚。 + +## JPA + +### 如何使用 JPA 在数据库中非持久化一个字段? 假如我们有有下面一个类: ```java Entity(name="USER") public class User { - + @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "ID") private Long id; - + @Column(name="USER_NAME") private String userName; - + @Column(name="PASSWORD") private String password; - + private String secrect; - + } ``` @@ -336,25 +447,13 @@ String transient4; // not persistent because of @Transient 一般使用后面两种方式比较多,我个人使用注解的方式比较多。 - ## 参考 - 《Spring 技术内幕》 -- +- - - - https://www.cnblogs.com/clwydjgs/p/9317849.html - - - -- - -## 公众号 - -如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 - -**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取! - -**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。 - -![公众号](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/javaguide1.jpg) diff --git a/docs/system-design/high-availability/limit-request.md b/docs/system-design/high-availability/limit-request.md index e7e64accc04..80511532c44 100644 --- a/docs/system-design/high-availability/limit-request.md +++ b/docs/system-design/high-availability/limit-request.md @@ -22,14 +22,20 @@ #### 漏桶算法 -我们可以把发请求的动作比作成注水到桶中,我们处理请求的过程可以比喻为漏桶漏水。我们往桶中以任意速率流入水,以一定速率流出水。当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。如果想要实现这个算法的话也很简单,准备一个队列用来保存请求,然后我们定期从队列中拿请求来执行就好了。 +我们可以把发请求的动作比作成注水到桶中,我们处理请求的过程可以比喻为 **漏桶漏水** 。我们往桶中以任意速率流入水,以一定速率流出水。当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。 + +如果想要实现这个算法的话也很简单,准备一个队列用来保存请求,然后我们定期从队列中拿请求来执行就好了。 ![漏桶算法](https://static001.infoq.cn/resource/image/75/03/75938d1010138ce66e38c6ed0392f103.png) #### 令牌桶算法 -令牌桶算法也比较简单。和漏桶算法算法一样,我们的主角还是桶(这限流算法和桶过不去啊)。不过现在桶里装的是令牌了,请求在被处理之前需要拿到一个令牌,请求处理完毕之后将这个令牌丢弃(删除)。我们根据限流大小,按照一定的速率往桶里添加令牌。 +令牌桶算法也比较简单。和漏桶算法算法一样,我们的主角还是桶(这限流算法和桶过不去啊)。不过现在桶里装的是令牌了,请求在被处理之前需要拿到一个令牌,请求处理完毕之后将这个令牌丢弃(删除)。 + +我们根据限流大小,按照一定的速率往桶里添加令牌即可! ![令牌桶算法](https://static001.infoq.cn/resource/image/ec/93/eca0e5eaa35dac938c673fecf2ec9a93.png) -### +**漏桶算法 vs 令牌桶算法** : + + diff --git "a/docs/system-design/micro-service/\345\210\206\345\270\203\345\274\217id\347\224\237\346\210\220\346\226\271\346\241\210\346\200\273\347\273\223.md" "b/docs/system-design/micro-service/\345\210\206\345\270\203\345\274\217id\347\224\237\346\210\220\346\226\271\346\241\210\346\200\273\347\273\223.md" index 48f962d6c3c..1d644ada2a3 100644 --- "a/docs/system-design/micro-service/\345\210\206\345\270\203\345\274\217id\347\224\237\346\210\220\346\226\271\346\241\210\346\200\273\347\273\223.md" +++ "b/docs/system-design/micro-service/\345\210\206\345\270\203\345\274\217id\347\224\237\346\210\220\346\226\271\346\241\210\346\200\273\347\273\223.md" @@ -152,7 +152,7 @@ Leaf中的snowflake模式和原始snowflake算法的不同点,也主要在work ### 总结 -总得来说,上面两种都是自动生成workId,以让系统更加稳定以及减少人工成功。 +总得来说,上面两种都是自动生成workId,以让系统更加稳定以及减少人工成本。 ## Redis @@ -171,7 +171,7 @@ OK RDB持久化相当于定时打一个快照进行持久化,如果打完快照后,连续自增了几次,还没来得及做下一次快照持久化,这个时候Redis挂掉了,重启Redis后会出现ID重复。 -AOF持久化相当于对每条写命令进行持久化,如果Redis挂掉了,不会出现ID重复的现象,但是会由于incr命令过得,导致重启恢复数据时间过长。 +AOF持久化相当于对每条写命令进行持久化,如果Redis挂掉了,不会出现ID重复的现象,但是会由于incr命令过多,导致重启恢复数据时间过长。 ## 公众号