diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..144625d302627aa1a7b60c63679aa397e7345762
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,29 @@
+name: Docs Test
+
+on:
+ - push
+ - pull_request
+
+jobs:
+ test-docs:
+ name: Test docs
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v2
+ with:
+ run_install: true
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ cache: pnpm
+
+ - name: Build test
+ env:
+ NODE_OPTIONS: --max_old_space_size=4096
+ run: pnpm docs:build
diff --git a/.gitignore b/.gitignore
index 2dc9c784aa890f7a320223df9dde17b2804c1f05..e094687fd0c0caf0822772425d6b4b4c82520767 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,17 @@
-/node_modules
-/package-lock.json
-/dist
+node_modules/
+# *.drawio
+*.drawio.bkp
.DS_Store
+# VS Code Config file
+.vscode/
+# VuePress Cache
+**/.vuepress/.cache/
+# VuePress Temp
+**/.vuepress/.temp/
+# VuePress Output
+dist/
+# Build files
+packages/*/lib/
+traversal-folder-replace-string.py
+format-markdown.py
+package-lock.json
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 0000000000000000000000000000000000000000..523f31ae8c89f809bfd15adb820510dfbc54c3ab
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+pnpm nano-staged
diff --git a/.markdownlint.json b/.markdownlint.json
new file mode 100644
index 0000000000000000000000000000000000000000..b6f98c5112dceff24e11d9f69ca542fb7dbbf8b5
--- /dev/null
+++ b/.markdownlint.json
@@ -0,0 +1,20 @@
+{
+ "default": true,
+ "MD003": {
+ "style": "atx"
+ },
+ "MD004": {
+ "style": "dash"
+ },
+ "MD013": false,
+ "MD024": {
+ "allow_different_nesting": true
+ },
+ "MD035": {
+ "style": "---"
+ },
+ "MD040": false,
+ "MD045": false,
+ "MD046": false,
+ "MD049": false
+}
diff --git a/.markdownlintignore b/.markdownlintignore
new file mode 100644
index 0000000000000000000000000000000000000000..d5d67721c60866768c6d12673134ed6c3f08a4f8
--- /dev/null
+++ b/.markdownlintignore
@@ -0,0 +1,4 @@
+**/node_modules/**
+
+# markdown snippets
+*.snippet.md
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000000000000000000000000000000000000..eef448660d8331ba763a1d8e4f46c4ed43052a9c
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,15 @@
+# Vuepress Cache
+**/.vuepress/.cache/**
+# Vuepress Temp
+**/.vuepress/.temp/**
+# Vuepress Output
+dist/
+
+# Node modules
+node_modules/
+
+# pnpm lock file
+pnpm-lock.yaml
+
+index.html
+sw.js
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
index f8e670983e331dad35e0c6b1d48cb06dd80d7884..a68c996f2d82cd043aff84331a9d223bd5d0d7d7 100755
--- a/README.md
+++ b/README.md
@@ -1,367 +1,444 @@
-👏 重大更新!!!重磅!
-
-- JavaGuide 在线阅读版(新版,推荐👍):https://javaguide.cn/
-- JavaGuide 在线阅读版(老版):https://snailclimb.gitee.io/javaguide/#/
-
-👉 [朋友开源的面试八股文系列](https://github.com/csguide-dabai/interview-guide)。
-
-> 1. **介绍**:关于 JavaGuide 的相关介绍请看:[关于 JavaGuide 的一些说明](https://www.yuque.com/snailclimb/dr6cvl/mr44yt) 。
-> 2. **贡献指南** :欢迎参与 [JavaGuide的维护工作](https://github.com/Snailclimb/JavaGuide/issues/1235),这是一件非常有意义的事情。
-> 3. **PDF版本** : [《JavaGuide 面试突击版》PDF 版本](#公众号) 。
-> 4. **图解计算机基础** :[图解计算机基础 PDF 下载](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100021725&idx=1&sn=2db9664ca25363139a81691043e9fd8f&chksm=4ea19a1679d61300d8990f7e43bfc7f476577a81b712cf0f9c6f6552a8b219bc081efddb5c54#rd) 。
-> 5. **知识星球** : 简历指导/Java学习/面试指导/面试小册。欢迎加入[我的知识星球](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100015911&idx=1&sn=2e8a0f5acb749ecbcbb417aa8a4e18cc&chksm=4ea1b0ec79d639fae37df1b86f196e8ce397accfd1dd2004bcadb66b4df5f582d90ae0d62448#rd) 。
-> 6. **面试专版** :准备面试的小伙伴可以考虑面试专版:[《Java面试进阶指北 》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7) (质量很高,专为面试打造,星球用户免费)
-> 7. **转载须知** :以下所有文章如非文首说明皆为我(Guide哥)的原创,转载在文首注明出处,如发现恶意抄袭/搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!⛽️
-
-
-
-
-
-
-
-
-
-
-
-
-Sponsor
-
-
-
-
-
-
-
-
-
-
-
-
-## Java
-
-### 基础
-
-**知识点/面试题** : (必看:+1: ):[Java 基础知识点/面试题总结](docs/java/basis/java基础知识总结.md)
-
-**重要知识点详解:**
-
-- [什么是反射机制?反射机制的应用场景有哪些?](docs/java/basis/反射机制详解.md)
-- [代理模式详解:静态代理+JDK/CGLIB 动态代理实战](docs/java/basis/代理模式详解.md)
-- [常见的 IO 模型有哪些?Java 中的 BIO、NIO、AIO 有啥区别?](docs/java/basis/java基础知识总结)
-
-### 集合
-
-1. **[Java 集合常见问题总结](docs/java/collection/java集合框架基础知识&面试题总结.md)** (必看 :+1:)
-2. [Java 容器使用注意事项总结](docs/java/collection/java集合使用注意事项总结.md)
-3. **源码分析** :[ArrayList 源码+扩容机制分析](docs/java/collection/arraylist-source-code.md) 、[HashMap(JDK1.8)源码+底层数据结构分析](docs/java/collection/hashmap-source-code.md) 、[ConcurrentHashMap 源码+底层数据结构分析](docs/java/collection/concurrent-hash-map-source-code.md)
-
-### 并发
-
-**知识点/面试题:** (必看 :+1:)
-
-1. **[Java 并发基础常见面试题总结](docs/java/concurrent/java并发基础常见面试题总结.md)**
-2. **[Java 并发进阶常见面试题总结](docs/java/concurrent/java并发进阶常见面试题总结.md)**
-
-**重要知识点详解:**
-
-1. **线程池**:[Java 线程池学习总结](./docs/java/concurrent/java线程池学习总结.md)、[拿来即用的 Java 线程池最佳实践](./docs/java/concurrent/拿来即用的java线程池最佳实践.md)
-2. [ThreadLocal 关键字解析](docs/java/concurrent/threadlocal.md)
-3. [Java 并发容器总结](docs/java/concurrent/并发容器总结.md)
-4. [Atomic 原子类总结](docs/java/concurrent/atomic原子类总结.md)
-5. [AQS 原理以及 AQS 同步组件总结](docs/java/concurrent/aqs原理以及aqs同步组件总结.md)
-6. [CompletableFuture入门](docs/java/concurrent/completablefuture-intro.md)
-
-### JVM (必看 :+1:)
-
-JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8 ](https://docs.oracle.com/javase/specs/jvms/se8/html/index.html) 和周志明老师的[《深入理解Java虚拟机(第3版)》](https://book.douban.com/subject/34907497/) (强烈建议阅读多遍!)。
-
-1. **[Java 内存区域](docs/java/jvm/内存区域.md)**
-2. **[JVM 垃圾回收](docs/java/jvm/jvm垃圾回收.md)**
-3. [JDK 监控和故障处理工具](docs/java/jvm/jdk监控和故障处理工具总结.md)
-4. [类文件结构](docs/java/jvm/类文件结构.md)
-5. **[类加载过程](docs/java/jvm/类加载过程.md)**
-6. [类加载器](docs/java/jvm/类加载器.md)
-7. **[【待完成】最重要的 JVM 参数总结(翻译完善了一半)](docs/java/jvm/jvm参数指南.md)**
-9. **[【加餐】大白话带你认识 JVM](docs/java/jvm/[加餐]大白话带你认识jvm.md)**
-
-### 新特性
-
-1. **Java 8** :[Java 8 新特性总结](docs/java/new-features/Java8新特性总结.md)、[Java8常用新特性总结](docs/java/new-features/java8-common-new-features.md)
-2. **Java9~Java15** : [一文带你看遍 JDK9~15 的重要新特性!](./docs/java/new-features/java新特性总结.md)
-
-### 小技巧
-
-1. [JAD 反编译](docs/java/tips/JAD反编译tricks.md)
-2. [手把手教你定位常见 Java 性能问题](./docs/java/tips/locate-performance-problems/手把手教你定位常见Java性能问题.md)
-
-## 计算机基础
-
-👉 **[图解计算机基础 PDF 下载](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100021725&idx=1&sn=2db9664ca25363139a81691043e9fd8f&chksm=4ea19a1679d61300d8990f7e43bfc7f476577a81b712cf0f9c6f6552a8b219bc081efddb5c54#rd)** 。
-
-### 操作系统
-
-1. [操作系统常见问题总结!](docs/cs-basics/operating-system/basis.md)
-2. [后端程序员必备的 Linux 基础知识总结](docs/cs-basics/operating-system/linux.md)
-3. [Shell 编程入门](docs/cs-basics/operating-system/Shell.md)
-
-### 网络
-
-1. [计算机网络常见面试题](docs/cs-basics/network/计算机网络.md)
-2. [计算机网络基础知识总结](docs/cs-basics/network/计算机网络知识总结.md)
-
-### 数据结构
-
-**图解数据结构:**
-
-1. [线性数据结构 :数组、链表、栈、队列](docs/cs-basics/data-structure/线性数据结构.md)
-2. [图](docs/cs-basics/data-structure/图.md)
-3. [堆](docs/cs-basics/data-structure/堆.md)
-4. [树](docs/cs-basics/data-structure/树.md) :重点关注[红黑树](docs/cs-basics/data-structure/红黑树.md)、B-,B+,B*树、LSM树
-
-其他常用数据结构 :
-
-1. [布隆过滤器](docs/cs-basics/data-structure/bloom-filter.md)
-
-### 算法
-
-算法这部分内容非常重要,如果你不知道如何学习算法的话,可以看下我写的:
-
-- [算法学习书籍+资源推荐](https://www.zhihu.com/question/323359308/answer/1545320858) 。
-- [如何刷Leetcode?](https://www.zhihu.com/question/31092580/answer/1534887374)
-
-**常见算法问题总结** :
-
-- [几道常见的字符串算法题总结 ](docs/cs-basics/algorithms/几道常见的字符串算法题.md)
-- [几道常见的链表算法题总结 ](docs/cs-basics/algorithms/几道常见的链表算法题.md)
-- [剑指 offer 部分编程题](docs/cs-basics/algorithms/剑指offer部分编程题.md)
-
-另外,[GeeksforGeeks]( https://www.geeksforgeeks.org/fundamentals-of-algorithms/) 这个网站总结了常见的算法 ,比较全面系统。
-
-## 数据库
-
-### MySQL
-
-**总结:**
-
-1. [数据库基础知识总结](docs/database/数据库基础知识.md)
-2. **[MySQL知识点总结](docs/database/mysql/mysql知识点&面试题总结.md)** (必看 :+1:)
-4. [一千行 MySQL 学习笔记](docs/database/mysql/a-thousand-lines-of-mysql-study-notes.md)
-5. [MySQL 高性能优化规范建议](docs/database/mysql/mysql-high-performance-optimization-specification-recommendations.md)
-
-**重要知识点:**
-
-1. [MySQL数据库索引总结](docs/database/mysql/mysql-index.md)
-2. [事务隔离级别(图文详解)](docs/database/mysql/transaction-isolation-level.md)
-3. [MySQL三大日志(binlog、redo log和undo log)详解](docs/database/mysql/mysql-logs.md)
-4. [InnoDB存储引擎对MVCC的实现](docs/database/mysql/innodb-implementation-of-mvcc.md)
-5. [一条 SQL 语句在 MySQL 中如何被执行的?](docs/database/mysql/how-sql-executed-in-mysql.md)
-6. [字符集详解:为什么不建议在MySQL中使用 utf8 ?](docs/database/字符集.md)
-7. [关于数据库中如何存储时间的一点思考](docs/database/mysql/some-thoughts-on-database-storage-time.md)
-
-### Redis
-
-1. [Redis 常见问题总结](docs/database/redis/redis-all.md)
-2. [3种常用的缓存读写策略](docs/database/redis/3-commonly-used-cache-read-and-write-strategies.md)
-
-## 搜索引擎
-
-用于提高搜索效率,功能和浏览器搜索引擎类似。比较常见的搜索引擎是 Elasticsearch(推荐) 和 Solr。
-
-## 系统设计
-
-### 系统设计必备基础
-
-#### RESTful API
-
-我们在进行后端开发的时候,主要的工作就是为前端或者其他后端服务提供 API 比如查询用户数据的 API 。RESTful API 是一种基于 REST 构建的 API,它是一种被设计的更好使用的 API。
-
-相关阅读:[RestFul API 简明教程](docs/system-design/basis/RESTfulAPI.md)
-
-#### 命名
-
-编程过程中,一定要重视命名。因为好的命名即是注释,别人一看到你的命名就知道你的变量、方法或者类是做什么的!
-
-相关阅读: [Java 命名之道](docs/system-design/naming.md) 。
-
-### 常用框架
-
-如果你没有接触过 Java Web 开发的话,可以先看一下我总结的 [《J2EE 基础知识》](docs/system-design/J2EE基础知识.md) 。虽然,这篇文章中的很多内容已经淘汰,但是可以让你对 Java 后台技术发展有更深的认识。
-
-#### Spring/SpringBoot (必看 :+1:)
-
-**知识点/面试题:**
-
-1. **[Spring 常见问题总结](docs/system-design/framework/spring/Spring常见问题总结.md)**
-2. **[SpringBoot 入门指南](https://github.com/Snailclimb/springboot-guide)**
-
-**重要知识点详解:**
-
-1. **[Spring/Spring Boot 常用注解总结!安排!](./docs/system-design/framework/spring/Spring&SpringBoot常用注解总结.md)**
-2. **[Spring 事务总结](docs/system-design/framework/spring/Spring事务总结.md)**
-3. [Spring 中都用到了那些设计模式?](docs/system-design/framework/spring/Spring设计模式总结.md)
-4. **[SpringBoot 自动装配原理?”](docs/system-design/framework/spring/SpringBoot自动装配原理.md)**
-
-#### MyBatis
-
-[MyBatis 常见面试题总结](docs/system-design/framework/mybatis/mybatis-interview.md)
-
-#### Spring Cloud
-
-[ 大白话入门 Spring Cloud](docs/system-design/framework/springcloud/springcloud-intro.md)
-
-### 安全
-
-#### 认证授权
-
-**[《认证授权基础》](docs/system-design/security/basis-of-authority-certification.md)** 这篇文章中我会介绍认证授权常见概念: **Authentication**,**Authorization** 以及 **Cookie**、**Session**、Token、**OAuth 2**、**SSO** 。如果你不清楚这些概念的话,建议好好阅读一下这篇文章。
-
-- **JWT** :JWT(JSON Web Token)是一种身份认证的方式,JWT 本质上就一段签名的 JSON 格式的数据。由于它是带有签名的,因此接收者便可以验证它的真实性。相关阅读:
- - [JWT 优缺点分析以及常见问题解决方案](docs/system-design/security/jwt优缺点分析以及常见问题解决方案.md)
- - [适合初学者入门 Spring Security With JWT 的 Demo](https://github.com/Snailclimb/spring-security-jwt-guide)
-
-- **SSO(单点登录)** :**SSO(Single Sign On)** 即单点登录说的是用户登陆多个子系统的其中一个就有权访问与其相关的其他系统。举个例子我们在登陆了京东金融之后,我们同时也成功登陆京东的京东超市、京东家电等子系统。相关阅读:[**SSO 单点登录看这篇就够了!**](docs/system-design/security/sso-intro.md)
-
-#### 数据脱敏
-
-数据脱敏说的就是我们根据特定的规则对敏感信息数据进行变形,比如我们把手机号、身份证号某些位数使用 * 来代替。
-
-### 定时任务
-
-最近有朋友问到定时任务相关的问题。于是,我简单写了一篇文章总结一下定时任务的一些概念以及一些常见的定时任务技术选型:[《Java定时任务大揭秘》](./docs/system-design/定时任务.md)
-
-## 分布式
-
-### CAP 理论和 BASE 理论
-
-CAP 也就是 Consistency(一致性)、Availability(可用性)、Partition Tolerance(分区容错性) 这三个单词首字母组合。
-
-**BASE** 是 **Basically Available(基本可用)** 、**Soft-state(软状态)** 和 **Eventually Consistent(最终一致性)** 三个短语的缩写。BASE 理论是对 CAP 中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于 CAP 定理逐步演化而来的,它大大降低了我们对系统的要求。
-
-相关阅读:[CAP 理论和 BASE 理论解读](docs/distributed-system/理论&算法/cap&base理论.md)
-
-### Paxos 算法和 Raft 算法
-
-**Paxos 算法**诞生于 1990 年,这是一种解决分布式系统一致性的经典算法 。但是,由于 Paxos 算法非常难以理解和实现,不断有人尝试简化这一算法。到了2013 年才诞生了一个比 Paxos 算法更易理解和实现的分布式一致性算法—**Raft 算法**。
-
-### RPC
-
-RPC 让调用远程服务调用像调用本地方法那样简单。
-
-Dubbo 是一款国产的 RPC 框架,由阿里开源。相关阅读:
-
-- [Dubbo 常见问题总结](docs/distributed-system/rpc/dubbo.md)
-- [服务之间的调用为啥不直接用 HTTP 而用 RPC?](docs/distributed-system/rpc/why-use-rpc.md)
-
-### API 网关
-
-网关主要用于请求转发、安全认证、协议转换、容灾。
-
-相关阅读:
-
-- [为什么要网关?你知道有哪些常见的网关系统?](docs/distributed-system/api-gateway.md)
-- [百亿规模API网关服务Shepherd的设计与实现](https://tech.meituan.com/2021/05/20/shepherd-api-gateway.html)
-
-### 分布式 id
-
-在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。比如数据量太大之后,往往需要对数据进行分库分表,分库分表后需要有一个唯一 ID 来标识一条数据或消息,数据库的自增 ID 显然不能满足需求。相关阅读:[为什么要分布式 id ?分布式 id 生成方案有哪些?](docs/distributed-system/distributed-id.md)
-
-### 分布式事务
-
-**分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。**
-
-简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
-
-### 分布式协调
-
-**ZooKeeper** :
-
-> 前两篇文章可能有内容重合部分,推荐都看一遍。
-
-1. [【入门】ZooKeeper 相关概念总结](docs/distributed-system/分布式协调/zookeeper/zookeeper-intro.md)
-2. [【进阶】ZooKeeper 相关概念总结](docs/distributed-system/分布式协调/zookeeper/zookeeper-plus.md)
-3. [【实战】ZooKeeper 实战](docs/distributed-system/分布式协调/zookeeper/zookeeper-in-action.md)
-
-## 高性能
-
-### 消息队列
-
-消息队列在分布式系统中主要是为了解耦和削峰。相关阅读: [消息队列常见问题总结](docs/high-performance/message-queue/message-queue.md)。
-
-1. **RabbitMQ** : [RabbitMQ 入门](docs/high-performance/message-queue/rabbitmq-intro.md)
-2. **RocketMQ** : [RocketMQ 入门](docs/high-performance/message-queue/rocketmq-intro)、[RocketMQ 的几个简单问题与答案](docs/high-performance/message-queue/rocketmq-questions.md)
-3. **Kafka** :[Kafka 常见问题总结](docs/high-performance/message-queue/kafka知识点&面试题总结.md)
-
-### 读写分离&分库分表
-
-读写分离主要是为了将数据库的读和写操作分不到不同的数据库节点上。主服务器负责写,从服务器负责读。另外,一主一从或者一主多从都可以。
-
-读写分离可以大幅提高读性能,小幅提高写的性能。因此,读写分离更适合单机并发读请求比较多的场景。
-
-分库分表是为了解决由于库、表数据量过大,而导致数据库性能持续下降的问题。
-
-常见的分库分表工具有:`sharding-jdbc`(当当)、`TSharding`(蘑菇街)、`MyCAT`(基于 Cobar)、`Cobar`(阿里巴巴)...。 推荐使用 `sharding-jdbc`。 因为,`sharding-jdbc` 是一款轻量级 `Java` 框架,以 `jar` 包形式提供服务,不要我们做额外的运维工作,并且兼容性也很好。
-
-相关阅读: [读写分离&分库分表常见问题总结](docs/high-performance/读写分离&分库分表.md)
-
-### 负载均衡
-
-负载均衡系统通常用于将任务比如用户请求处理分配到多个服务器处理以提高网站、应用或者数据库的性能和可靠性。
-
-常见的负载均衡系统包括 3 种:
-
-1. **DNS 负载均衡** :一般用来实现地理级别的均衡。
-2. **硬件负载均衡** : 通过单独的硬件设备比如 F5 来实现负载均衡功能(硬件的价格一般很贵)。
-3. **软件负载均衡** :通过负载均衡软件比如 Nginx 来实现负载均衡功能。
-
-## 高可用
-
-高可用描述的是一个系统在大部分时间都是可用的,可以为我们提供服务的。高可用代表系统即使在发生硬件故障或者系统升级的时候,服务仍然是可用的 。
-
-相关阅读: **《[如何设计一个高可用系统?要考虑哪些地方?](docs/high-availability/高可用系统设计.md)》** 。
-
-### 限流
-
-限流是从用户访问压力的角度来考虑如何应对系统故障。
-
-限流为了对服务端的接口接受请求的频率进行限制,防止服务挂掉。比如某一接口的请求限制为 100 个每秒, 对超过限制的请求放弃处理或者放到队列中等待处理。限流可以有效应对突发请求过多。相关阅读:[何为限流?限流算法有哪些?](docs/high-availability/limit-request.md)
-
-### 降级
-
-降级是从系统功能优先级的角度考虑如何应对系统故障。
-
-服务降级指的是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
-
-### 熔断
-
-熔断和降级是两个比较容易混淆的概念,两者的含义并不相同。
-
-降级的目的在于应对系统自身的故障,而熔断的目的在于应对当前系统依赖的外部系统或者第三方系统的故障。
-
-### 排队
-
-另类的一种限流,类比于现实世界的排队。玩过英雄联盟的小伙伴应该有体会,每次一有活动,就要经历一波排队才能进入游戏。
-
-### 集群
-
-相同的服务部署多份,避免单点故障。
-
-### 超时和重试机制
-
-**一旦用户的请求超过某个时间得不到响应就结束此次请求并抛出异常。** 如果不进行超时设置可能会导致请求响应速度慢,甚至导致请求堆积进而让系统无法在处理请求。
-
-另外,重试的次数一般设为 3 次,再多次的重试没有好处,反而会加重服务器压力(部分场景使用失败重试机制会不太适合)。
-
-### 灾备设计和异地多活
-
-**灾备** = 容灾+备份。
-
-- **备份** : 将系统所产生的的所有重要数据多备份几份。
-- **容灾** : 在异地建立两个完全相同的系统。当某个地方的系统突然挂掉,整个应用系统可以切换到另一个,这样系统就可以正常提供服务了。
-
-**异地多活** 描述的是将服务部署在异地并且服务同时对外提供服务。和传统的灾备设计的最主要区别在于“多活”,即所有站点都是同时在对外提供服务的。异地多活是为了应对突发状况比如火灾、地震等自然或者认为灾害。
-
-相关阅读:
-
-- [搞懂异地多活,看这篇就够了](https://mp.weixin.qq.com/s/T6mMDdtTfBuIiEowCpqu6Q)
-- [四步构建异地多活](https://mp.weixin.qq.com/s/hMD-IS__4JE5_nQhYPYSTg)
-- [《从零开始学架构》— 28 | 业务高可用的保障:异地多活架构](http://gk.link/a/10pKZ)
+推荐你通过在线阅读网站进行阅读,体验更好,速度更快!地址:[javaguide.cn](https://javaguide.cn/)。
+
+[ ](https://sourl.cn/e7ee87)
+
+
+
+[](https://github.com/Snailclimb/JavaGuide)
+
+[](https://javaguide.cn/)
+
+
+
+
+[GitHub](https://github.com/Snailclimb/JavaGuide) | [Gitee](https://gitee.com/SnailClimb/JavaGuide)
+
+
+
+> - **面试专版**:准备 Java 面试的小伙伴可以考虑面试专版:**[《Java 面试指北 》](./zhuanlan/java-mian-shi-zhi-bei.md)** (质量很高,专为面试打造,配合 JavaGuide 食用)。
+> - **知识星球**:专属面试小册/一对一交流/简历修改/专属求职指南,欢迎加入 **[JavaGuide 知识星球](./about-the-author/zhishixingqiu-two-years.md)**(点击链接即可查看星球的详细介绍,一定确定自己真的需要再加入)。
+> - **使用建议** :有水平的面试官都是顺着项目经历挖掘技术问题。一定不要死记硬背技术八股文!详细的学习建议请参考:[JavaGuide 使用建议](./javaguide/use-suggestion.md)。
+> - **求个Star**:如果觉得 JavaGuide 的内容对你有帮助的话,还请点个免费的 Star,这是对我最大的鼓励,感谢各位一起同行,共勉!Github 地址:[https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide) 。
+> - **转载须知**:以下所有文章如非文首说明为转载皆为 JavaGuide 原创,转载请在文首注明出处。如发现恶意抄袭/搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!
+
+
+
+
+
+
+
+## 项目相关
+
+- [项目介绍](https://javaguide.cn/javaguide/intro.html)
+- [使用建议](https://javaguide.cn/javaguide/use-suggestion.html)
+- [贡献指南](https://javaguide.cn/javaguide/contribution-guideline.html)
+- [常见问题](https://javaguide.cn/javaguide/faq.html)
+
+## Java
+
+### 基础
+
+**知识点/面试题总结** : (必看:+1: ):
+
+- [Java 基础常见知识点&面试题总结(上)](./docs/java/basis/java-basic-questions-01.md)
+- [Java 基础常见知识点&面试题总结(中)](./docs/java/basis/java-basic-questions-02.md)
+- [Java 基础常见知识点&面试题总结(下)](./docs/java/basis/java-basic-questions-03.md)
+
+**重要知识点详解**:
+
+- [为什么 Java 中只有值传递?](./docs/java/basis/why-there-only-value-passing-in-java.md)
+- [Java 序列化详解](./docs/java/basis/serialization.md)
+- [泛型&通配符详解](./docs/java/basis/generics-and-wildcards.md)
+- [Java 反射机制详解](./docs/java/basis/reflection.md)
+- [Java 代理模式详解](./docs/java/basis/proxy.md)
+- [BigDecimal 详解](./docs/java/basis/bigdecimal.md)
+- [Java 魔法类 Unsafe 详解](./docs/java/basis/unsafe.md)
+- [Java SPI 机制详解](./docs/java/basis/spi.md)
+- [Java 语法糖详解](./docs/java/basis/syntactic-sugar.md)
+
+### 集合
+
+**知识点/面试题总结**:
+
+- [Java 集合常见知识点&面试题总结(上)](./docs/java/collection/java-collection-questions-01.md) (必看 :+1:)
+- [Java 集合常见知识点&面试题总结(下)](./docs/java/collection/java-collection-questions-02.md) (必看 :+1:)
+- [Java 容器使用注意事项总结](./docs/java/collection/java-collection-precautions-for-use.md)
+
+**源码分析**:
+
+- [ArrayList 核心源码+扩容机制分析](./docs/java/collection/arraylist-source-code.md)
+- [LinkedList 核心源码分析](./docs/java/collection/linkedlist-source-code.md)
+- [HashMap 核心源码+底层数据结构分析](./docs/java/collection/hashmap-source-code.md)
+- [ConcurrentHashMap 核心源码+底层数据结构分析](./docs/java/collection/concurrent-hash-map-source-code.md)
+- [LinkedHashMap 核心源码分析](./docs/java/collection/linkedhashmap-source-code.md)
+- [CopyOnWriteArrayList 核心源码分析](./docs/java/collection/copyonwritearraylist-source-code.md)
+- [ArrayBlockingQueue 核心源码分析](./docs/java/collection/arrayblockingqueue-source-code.md)
+- [PriorityQueue 核心源码分析](./docs/java/collection/priorityqueue-source-code.md)
+
+### IO
+
+- [IO 基础知识总结](./docs/java/io/io-basis.md)
+- [IO 设计模式总结](./docs/java/io/io-design-patterns.md)
+- [IO 模型详解](./docs/java/io/io-model.md)
+- [NIO 核心知识总结](./docs/java/io/nio-basis.md)
+
+### 并发
+
+**知识点/面试题总结** : (必看 :+1:)
+
+- [Java 并发常见知识点&面试题总结(上)](./docs/java/concurrent/java-concurrent-questions-01.md)
+- [Java 并发常见知识点&面试题总结(中)](./docs/java/concurrent/java-concurrent-questions-02.md)
+- [Java 并发常见知识点&面试题总结(下)](./docs/java/concurrent/java-concurrent-questions-03.md)
+
+**重要知识点详解**:
+
+- [JMM(Java 内存模型)详解](./docs/java/concurrent/jmm.md)
+- **线程池**:[Java 线程池详解](./docs/java/concurrent/java-thread-pool-summary.md)、[Java 线程池最佳实践](./docs/java/concurrent/java-thread-pool-best-practices.md)
+- [ThreadLocal 详解](./docs/java/concurrent/threadlocal.md)
+- [Java 并发容器总结](./docs/java/concurrent/java-concurrent-collections.md)
+- [Atomic 原子类总结](./docs/java/concurrent/atomic-classes.md)
+- [AQS 详解](./docs/java/concurrent/aqs.md)
+- [CompletableFuture 详解](./docs/java/concurrent/completablefuture-intro.md)
+
+### JVM (必看 :+1:)
+
+JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8 ](https://docs.oracle.com/javase/specs/jvms/se8/html/index.html) 和周志明老师的[《深入理解 Java 虚拟机(第 3 版)》](https://book.douban.com/subject/34907497/) (强烈建议阅读多遍!)。
+
+- **[Java 内存区域](./docs/java/jvm/memory-area.md)**
+- **[JVM 垃圾回收](./docs/java/jvm/jvm-garbage-collection.md)**
+- [类文件结构](./docs/java/jvm/class-file-structure.md)
+- **[类加载过程](./docs/java/jvm/class-loading-process.md)**
+- [类加载器](./docs/java/jvm/classloader.md)
+- [【待完成】最重要的 JVM 参数总结(翻译完善了一半)](./docs/java/jvm/jvm-parameters-intro.md)
+- [【加餐】大白话带你认识 JVM](./docs/java/jvm/jvm-intro.md)
+- [JDK 监控和故障处理工具](./docs/java/jvm/jdk-monitoring-and-troubleshooting-tools.md)
+
+### 新特性
+
+- **Java 8**:[Java 8 新特性总结(翻译)](./docs/java/new-features/java8-tutorial-translate.md)、[Java8 常用新特性总结](./docs/java/new-features/java8-common-new-features.md)
+- [Java 9 新特性概览](./docs/java/new-features/java9.md)
+- [Java 10 新特性概览](./docs/java/new-features/java10.md)
+- [Java 11 新特性概览](./docs/java/new-features/java11.md)
+- [Java 12 & 13 新特性概览](./docs/java/new-features/java12-13.md)
+- [Java 14 & 15 新特性概览](./docs/java/new-features/java14-15.md)
+- [Java 16 新特性概览](./docs/java/new-features/java16.md)
+- [Java 17 新特性概览](./docs/java/new-features/java17.md)
+- [Java 18 新特性概览](./docs/java/new-features/java18.md)
+- [Java 19 新特性概览](./docs/java/new-features/java19.md)
+- [Java 20 新特性概览](./docs/java/new-features/java20.md)
+
+## 计算机基础
+
+### 操作系统
+
+- [操作系统常见知识点&面试题总结(上)](./docs/cs-basics/operating-system/operating-system-basic-questions-01.md)
+- [操作系统常见知识点&面试题总结(下)](./docs/cs-basics/operating-system/operating-system-basic-questions-02.md)
+- **Linux**:
+ - [后端程序员必备的 Linux 基础知识总结](./docs/cs-basics/operating-system/linux-intro.md)
+ - [Shell 编程基础知识总结](./docs/cs-basics/operating-system/shell-intro.md)
+
+### 网络
+
+**知识点/面试题总结**:
+
+- [计算机网络常见知识点&面试题总结(上)](./docs/cs-basics/network/other-network-questions.md)
+- [计算机网络常见知识点&面试题总结(下)](./docs/cs-basics/network/other-network-questions2.md)
+- [谢希仁老师的《计算机网络》内容总结(补充)](./docs/cs-basics/network/computer-network-xiexiren-summary.md)
+
+**重要知识点详解**:
+
+- [OSI 和 TCP/IP 网络分层模型详解(基础)](./docs/cs-basics/network/osi-and-tcp-ip-model.md)
+- [应用层常见协议总结(应用层)](./docs/cs-basics/network/application-layer-protocol.md)
+- [HTTP vs HTTPS(应用层)](./docs/cs-basics/network/http-vs-https.md)
+- [HTTP 1.0 vs HTTP 1.1(应用层)](./docs/cs-basics/network/http1.0-vs-http1.1.md)
+- [HTTP 常见状态码(应用层)](./docs/cs-basics/network/http-status-codes.md)
+- [DNS 域名系统详解(应用层)](./docs/cs-basics/network/dns.md)
+- [TCP 三次握手和四次挥手(传输层)](./docs/cs-basics/network/tcp-connection-and-disconnection.md)
+- [TCP 传输可靠性保障(传输层)](./docs/cs-basics/network/tcp-reliability-guarantee.md)
+- [ARP 协议详解(网络层)](./docs/cs-basics/network/arp.md)
+- [NAT 协议详解(网络层)](./docs/cs-basics/network/nat.md)
+- [网络攻击常见手段总结(安全)](./docs/cs-basics/network/network-attack-means.md)
+
+### 数据结构
+
+**图解数据结构:**
+
+- [线性数据结构 :数组、链表、栈、队列](./docs/cs-basics/data-structure/linear-data-structure.md)
+- [图](./docs/cs-basics/data-structure/graph.md)
+- [堆](./docs/cs-basics/data-structure/heap.md)
+- [树](./docs/cs-basics/data-structure/tree.md):重点关注[红黑树](./docs/cs-basics/data-structure/red-black-tree.md)、B-,B+,B\*树、LSM 树
+
+其他常用数据结构:
+
+- [布隆过滤器](./docs/cs-basics/data-structure/bloom-filter.md)
+
+### 算法
+
+算法这部分内容非常重要,如果你不知道如何学习算法的话,可以看下我写的:
+
+- [算法学习书籍+资源推荐](https://www.zhihu.com/question/323359308/answer/1545320858) 。
+- [如何刷 Leetcode?](https://www.zhihu.com/question/31092580/answer/1534887374)
+
+**常见算法问题总结**:
+
+- [几道常见的字符串算法题总结 ](./docs/cs-basics/algorithms/string-algorithm-problems.md)
+- [几道常见的链表算法题总结 ](./docs/cs-basics/algorithms/linkedlist-algorithm-problems.md)
+- [剑指 offer 部分编程题](./docs/cs-basics/algorithms/the-sword-refers-to-offer.md)
+- [十大经典排序算法](./docs/cs-basics/algorithms/10-classical-sorting-algorithms.md)
+
+另外,[GeeksforGeeks](https://www.geeksforgeeks.org/fundamentals-of-algorithms/) 这个网站总结了常见的算法 ,比较全面系统。
+
+## 数据库
+
+### 基础
+
+- [数据库基础知识总结](./docs/database/basis.md)
+- [NoSQL 基础知识总结](./docs/database/nosql.md)
+- [字符集详解](./docs/database/character-set.md)
+- SQL :
+ - [SQL 语法基础知识总结](./docs/database/sql/sql-syntax-summary.md)
+ - [SQL 常见面试题总结](./docs/database/sql/sql-questions-01.md)
+
+### MySQL
+
+**知识点/面试题总结:**
+
+- **[MySQL 常见知识点&面试题总结](./docs/database/mysql/mysql-questions-01.md)** (必看 :+1:)
+- [MySQL 高性能优化规范建议总结](./docs/database/mysql/mysql-high-performance-optimization-specification-recommendations.md)
+
+**重要知识点:**
+
+- [MySQL 索引详解](./docs/database/mysql/mysql-index.md)
+- [MySQL 事务隔离级别图文详解)](./docs/database/mysql/transaction-isolation-level.md)
+- [MySQL 三大日志(binlog、redo log 和 undo log)详解](./docs/database/mysql/mysql-logs.md)
+- [InnoDB 存储引擎对 MVCC 的实现](./docs/database/mysql/innodb-implementation-of-mvcc.md)
+- [SQL 语句在 MySQL 中的执行过程](./docs/database/mysql/how-sql-executed-in-mysql.md)
+- [MySQL 查询缓存详解](./docs/database/mysql/mysql-query-cache.md)
+- [MySQL 执行计划分析](./docs/database/mysql/mysql-query-execution-plan.md)
+- [MySQL 自增主键一定是连续的吗](./docs/database/mysql/mysql-auto-increment-primary-key-continuous.md)
+- [MySQL 时间类型数据存储建议](./docs/database/mysql/some-thoughts-on-database-storage-time.md)
+- [MySQL 隐式转换造成索引失效](./docs/database/mysql/index-invalidation-caused-by-implicit-conversion.md)
+
+### Redis
+
+**知识点/面试题总结** : (必看:+1: ):
+
+- [Redis 常见知识点&面试题总结(上)](./docs/database/redis/redis-questions-01.md)
+- [Redis 常见知识点&面试题总结(下)](./docs/database/redis/redis-questions-02.md)
+
+**重要知识点:**
+
+- [3 种常用的缓存读写策略详解](./docs/database/redis/3-commonly-used-cache-read-and-write-strategies.md)
+- [Redis 5 种基本数据结构详解](./docs/database/redis/redis-data-structures-01.md)
+- [Redis 3 种特殊数据结构详解](./docs/database/redis/redis-data-structures-02.md)
+- [Redis 持久化机制详解](./docs/database/redis/redis-persistence.md)
+- [Redis 内存碎片详解](./docs/database/redis/redis-memory-fragmentation.md)
+- [Redis 常见阻塞原因总结](./docs/database/redis/redis-common-blocking-problems-summary.md)
+- [Redis 集群详解](./docs/database/redis/redis-cluster.md)
+
+### MongoDB
+
+- [MongoDB 常见知识点&面试题总结(上)](./docs/database/mongodb/mongodb-questions-01.md)
+- [MongoDB 常见知识点&面试题总结(下)](./docs/database/mongodb/mongodb-questions-02.md)
+
+## 搜索引擎
+
+[Elasticsearch 常见面试题总结(付费)](./docs/database/elasticsearch/elasticsearch-questions-01.md)
+
+
+
+## 开发工具
+
+### Maven
+
+[Maven 核心概念总结](./docs/tools/maven/maven-core-concepts.md)
+
+### Gradle
+
+[Gradle 核心概念总结](./docs/tools/gradle/gradle-core-concepts.md)(可选,目前国内还是使用 Maven 普遍一些)
+
+### Docker
+
+- [Docker 核心概念总结](./docs/tools/docker/docker-intro.md)
+- [Docker 实战](./docs/tools/docker/docker-in-action.md)
+
+### Git
+
+- [Git 核心概念总结](./docs/tools/git/git-intro.md)
+- [GitHub 实用小技巧总结](./docs/tools/git/github-tips.md)
+
+## 系统设计
+
+- [系统设计常见面试题总结](./docs/system-design/system-design-questions.md)
+- [设计模式常见面试题总结](./docs/system-design/design-pattern.md)
+
+### 基础
+
+- [RestFul API 简明教程](./docs/system-design/basis/RESTfulAPI.md)
+- [软件工程简明教程简明教程](./docs/system-design/basis/software-engineering.md)
+- [代码命名指南](./docs/system-design/basis/naming.md)
+- [代码重构指南](./docs/system-design/basis/refactoring.md)
+- [单元测试指南](./docs/system-design/basis/unit-test.md)
+
+### 常用框架
+
+#### Spring/SpringBoot (必看 :+1:)
+
+**知识点/面试题总结** :
+
+- [Spring 常见知识点&面试题总结](./docs/system-design/framework/spring/spring-knowledge-and-questions-summary.md)
+- [SpringBoot 常见知识点&面试题总结](./docs/system-design/framework/spring/springboot-knowledge-and-questions-summary.md)
+- [Spring/Spring Boot 常用注解总结](./docs/system-design/framework/spring/spring-common-annotations.md)
+- [SpringBoot 入门指南](https://github.com/Snailclimb/springboot-guide)
+
+**重要知识点详解**:
+
+- [Spring 事务详解](./docs/system-design/framework/spring/spring-transaction.md)
+- [Spring 中的设计模式详解](./docs/system-design/framework/spring/spring-design-patterns-summary.md)
+- [SpringBoot 自动装配原理详解](./docs/system-design/framework/spring/spring-boot-auto-assembly-principles.md)
+
+#### MyBatis
+
+[MyBatis 常见面试题总结](./docs/system-design/framework/mybatis/mybatis-interview.md)
+
+### 安全
+
+#### 认证授权
+
+- [认证授权基础概念详解](./docs/system-design/security/basis-of-authority-certification.md)
+- [JWT 基础概念详解](./docs/system-design/security/jwt-intro.md)
+- [JWT 优缺点分析以及常见问题解决方案](./docs/system-design/security/advantages-and-disadvantages-of-jwt.md)
+- [SSO 单点登录详解](./docs/system-design/security/sso-intro.md)
+- [权限系统设计详解](./docs/system-design/security/design-of-authority-system.md)
+- [常见加密算法总结](./docs/system-design/security/encryption-algorithms.md)
+
+#### 数据脱敏
+
+数据脱敏说的就是我们根据特定的规则对敏感信息数据进行变形,比如我们把手机号、身份证号某些位数使用 \* 来代替。
+
+#### 敏感词过滤
+
+[敏感词过滤方案总结](./docs/system-design/security/sentive-words-filter.md)
+
+### 定时任务
+
+[Java 定时任务详解](./docs/system-design/schedule-task.md)
+
+### Web 实时消息推送
+
+[Web 实时消息推送详解](./docs/system-design/web-real-time-message-push.md)
+
+## 分布式
+
+### 理论&算法&协议
+
+- [CAP 理论和 BASE 理论解读](https://javaguide.cn/distributed-system/protocol/cap-and-base-theorem.html)
+- [Paxos 算法解读](https://javaguide.cn/distributed-system/protocol/paxos-algorithm.html)
+- [Raft 算法解读](https://javaguide.cn/distributed-system/protocol/raft-algorithm.html)
+- [Gossip 协议详解](https://javaguide.cn/distributed-system/protocol/gossip-protocl.html)
+
+### RPC
+
+- [RPC 基础知识总结](https://javaguide.cn/distributed-system/rpc/rpc-intro.html)
+- [Dubbo 常见知识点&面试题总结](https://javaguide.cn/distributed-system/rpc/dubbo.html)
+
+### ZooKeeper
+
+> 这两篇文章可能有内容重合部分,推荐都看一遍。
+
+- [ZooKeeper 相关概念总结(入门)](https://javaguide.cn/distributed-system/distributed-process-coordination/zookeeper/zookeeper-intro.html)
+- [ZooKeeper 相关概念总结(进阶)](https://javaguide.cn/distributed-system/distributed-process-coordination/zookeeper/zookeeper-plus.html)
+
+### API 网关
+
+- [API 网关基础知识总结](https://javaguide.cn/distributed-system/api-gateway.html)
+- [Spring Cloud Gateway 常见知识点&面试题总结](./docs/distributed-system/spring-cloud-gateway-questions.md)
+
+### 分布式 ID
+
+- [分布式ID介绍&实现方案总结](https://javaguide.cn/distributed-system/distributed-id.html)
+- [分布式 ID 设计指南](https://javaguide.cn/distributed-system/distributed-id-design.html)
+
+### 分布式锁
+
+- [分布式锁介绍](https://javaguide.cn/distributed-system/distributed-lock.html)
+- [分布式锁常见实现方案总结](https://javaguide.cn/distributed-system/distributed-lock-implementations.html)
+
+### 分布式事务
+
+[分布式事务常见知识点&面试题总结](https://javaguide.cn/distributed-system/distributed-transaction.html)
+
+### 分布式配置中心
+
+[分布式配置中心常见知识点&面试题总结](./docs/distributed-system/distributed-configuration-center.md)
+
+## 高性能
+
+### 数据库读写分离&分库分表
+
+[数据库读写分离和分库分表常见知识点&面试题总结](./docs/high-performance/read-and-write-separation-and-library-subtable.md)
+
+### 负载均衡
+
+[负载均衡常见知识点&面试题总结](./docs/high-performance/load-balancing.md)
+
+### SQL 优化
+
+[常见 SQL 优化手段总结](./docs/high-performance/sql-optimization.md)
+
+### CDN
+
+[CDN(内容分发网络)常见知识点&面试题总结](./docs/high-performance/cdn.md)
+
+### 消息队列
+
+- [消息队列基础知识总结](./docs/high-performance/message-queue/message-queue.md)
+- [Disruptor 常见知识点&面试题总结](./docs/high-performance/message-queue/disruptor-questions.md)
+- [RabbitMQ 常见知识点&面试题总结](./docs/high-performance/message-queue/rabbitmq-questions.md)
+- [RocketMQ 常见知识点&面试题总结](./docs/high-performance/message-queue/rocketmq-questions.md)
+- [Kafka 常见知识点&面试题总结](./docs/high-performance/message-queue/kafka-questions-01.md)
+
+## 高可用
+
+[高可用系统设计指南](./docs/high-availability/high-availability-system-design.md)
+
+### 冗余设计
+
+[冗余设计详解](./docs/high-availability/redundancy.md)
+
+### 限流
+
+[服务限流详解](./docs/high-availability/limit-request.md)
+
+### 降级&熔断
+
+[降级&熔断详解](./docs/high-availability/fallback-and-circuit-breaker.md)
+
+### 超时&重试
+
+[超时&重试详解](./docs/high-availability/timeout-and-retry.md)
+
+### 集群
+
+相同的服务部署多份,避免单点故障。
+
+### 灾备设计和异地多活
+
+**灾备** = 容灾 + 备份。
+
+- **备份**:将系统所产生的的所有重要数据多备份几份。
+- **容灾**:在异地建立两个完全相同的系统。当某个地方的系统突然挂掉,整个应用系统可以切换到另一个,这样系统就可以正常提供服务了。
+
+**异地多活** 描述的是将服务部署在异地并且服务同时对外提供服务。和传统的灾备设计的最主要区别在于“多活”,即所有站点都是同时在对外提供服务的。异地多活是为了应对突发状况比如火灾、地震等自然或者人为灾害。
+
+## Star 趋势
+
+
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+
+
+
+
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
deleted file mode 100644
index b9ce3f6d372ac424109c941f9dee73781646ad71..0000000000000000000000000000000000000000
--- a/docs/.vuepress/config.js
+++ /dev/null
@@ -1,383 +0,0 @@
-const { config } = require("vuepress-theme-hope");
-
-module.exports = config({
- title: "JavaGuide",
- description: "Java学习&&面试指南",
- dest: "./dist",
- head: [
- [
- "script",
- { src: "https://cdn.jsdelivr.net/npm/react/umd/react.production.min.js" },
- ],
- [
- "script",
- {
- src: "https://cdn.jsdelivr.net/npm/react-dom/umd/react-dom.production.min.js",
- },
- ],
- ["script", { src: "https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js" }],
- [
- "script",
- { src: "https://cdn.jsdelivr.net/npm/@babel/standalone/babel.min.js" },
- ],
- // 添加百度统计
- [
- "script",{},
- `var _hmt = _hmt || [];
- (function() {
- var hm = document.createElement("script");
- hm.src = "https://hm.baidu.com/hm.js?5dd2e8c97962d57b7b8fea1737c01743";
- var s = document.getElementsByTagName("script")[0];
- s.parentNode.insertBefore(hm, s);
- })();`
- ]
- ],
-
- themeConfig: {
- logo: "/logo.png",
- hostname: "https://javaguide.cn/",
- author: "Guide哥",
- repo: "https://github.com/Snailclimb/JavaGuide",
- nav: [
- { text: "Java面试指南", icon: "java", link: "/", },
- { text: "Java面试指北", icon: "java", link: "https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7?#%20%E3%80%8A%E3%80%8AJava%E9%9D%A2%E8%AF%95%E8%BF%9B%E9%98%B6%E6%8C%87%E5%8C%97%20%20%E6%89%93%E9%80%A0%E4%B8%AA%E4%BA%BA%E7%9A%84%E6%8A%80%E6%9C%AF%E7%AB%9E%E4%BA%89%E5%8A%9B%E3%80%8B%E3%80%8B", },
- {
- text: "Java精选", icon: "file", icon: "java",
- items: [
- { text: "Java书单精选", icon: "book", link: "https://gitee.com/SnailClimb/awesome-cs" },
- { text: "Java学习路线", icon: "luxianchaxun", link: "https://zhuanlan.zhihu.com/p/379041500" },
- { text: "Java开源项目精选", icon: "git", link: "https://gitee.com/SnailClimb/awesome-java" }
- ],
- },
- { text: "IDEA指南", icon: "intellijidea", link: "/idea-tutorial/", },
- { text: "开发工具", icon: "Tools", link: "/tools/", },
- {
- text: "PDF资源", icon: "pdf",
- items: [
- { text: "JavaGuide面试突击版", link: "https://t.1yb.co/Fy1e", },
- { text: "消息队列常见知识点&面试题总结", link: "https://t.1yb.co/Fy0u", },
- { text: "图解计算机基础!", link: "https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100021725&idx=1&sn=2db9664ca25363139a81691043e9fd8f&chksm=4ea19a1679d61300d8990f7e43bfc7f476577a81b712cf0f9c6f6552a8b219bc081efddb5c54#rd" }
- ],
- },
- {
- text: "关于作者", icon: "zuozhe", link: "/about-the-author/"
- },
- ],
- sidebar: {
- "/about-the-author/": [
- "internet-addiction-teenager", "feelings-after-one-month-of-induction-training"
- ],
- // 应该把更精确的路径放置在前边
- '/tools/': [
- {
- title: "数据库",
- icon: "database",
- prefix: "database/",
- collapsable: false,
- children: ["CHINER", "DBeaver", "screw"]
- },
- {
- title: "Git",
- icon: "git",
- prefix: "git/",
- collapsable: false,
- children: ["git-intro", "github技巧"]
- },
- {
- title: "Docker",
- icon: "docker1",
- prefix: "docker/",
- collapsable: false,
- children: ["docker", "docker从入门到实战"]
- },
- ],
- '/idea-tutorial/':
- [
- {
- title: "IDEA小技巧",
- icon: "creative",
- prefix: "idea-tips/",
- collapsable: false,
- children: [
- "idea-refractor-intro",
- "idea-plug-in-development-intro",
- "idea-source-code-reading-skills",
- ]
- },
- {
- title: "IDEA插件推荐",
- icon: "plugin",
- collapsable: false,
- prefix: "idea-plugins/",
- children: [
- "shortcut-key", "idea-themes", "improve-code", "interface-beautification",
- "camel-case", "code-glance", "code-statistic",
- "git-commit-template", "gson-format", "idea-features-trainer", "jclasslib",
- "maven-helper", "rest-devlop", "save-actions", "sequence-diagram", "translation",
- "others"
- ]
- },
- ],
- // 必须放在最后面
- '/': [{
- title: "Java", icon: "java", prefix: "java/",
- children: [
- {
- title: "基础", prefix: "basis/",
- children: [
- "java基础知识总结",
- {
- title: "重要知识点",
- children: ["反射机制详解", "代理模式详解", "io模型详解"],
- },],
- },
- {
- title: "容器", prefix: "collection/",
- children: [
- "java集合框架基础知识&面试题总结", "java集合使用注意事项",
- {
- title: "源码分析",
- children: ["arraylist-source-code", "hashmap-source-code", "concurrent-hash-map-source-code"],
- },],
- },
- {
- title: "并发编程", prefix: "concurrent/",
- children: [
- "java并发基础常见面试题总结", "java并发进阶常见面试题总结",
- {
- title: "重要知识点",
- children: ["java线程池学习总结", "并发容器总结", "拿来即用的java线程池最佳实践", "aqs原理以及aqs同步组件总结", "reentrantlock",
- "atomic原子类总结", "threadlocal", "completablefuture-intro"],
- },
- ],
- },
- {
- title: "JVM", prefix: "jvm/",
- children: ["memory-area", "jvm-garbage-collection", "class-file-structure", "class-loading-process", "classloader", "jvm-parameters-intro", "jvm-intro", "jdk-monitoring-and-troubleshooting-tools"],
- },
- {
- title: "新特性", prefix: "new-features/",
- children: ["java8-common-new-features", "java8-tutorial-translate", "java新特性总结"],
- },
- {
- title: "小技巧", prefix: "tips/",
- children: ["locate-performance-problems/手把手教你定位常见Java性能问题", "jad"],
- },
- ],
- },
- {
- title: "计算机基础", icon: "computer", prefix: "cs-basics/",
- children: [
- {
- title: "计算机网络", prefix: "network/", icon: "network",
- children: [
- "计算机网络常见面试题", "谢希仁老师的《计算机网络》内容总结", "HTTPS中的TLS"
- ],
- },
- {
- title: "操作系统", prefix: "operating-system/", icon: "caozuoxitong",
- children: [
- "操作系统常见面试题&知识点总结", "linux-intro", "shell-intro"
- ],
- },
- {
- title: "数据结构", prefix: "data-structure/", icon: "people-network-full",
- children: [
- "线性数据结构", "图", "堆", "树", "红黑树", "bloom-filter"
- ],
- },
- {
- title: "算法", prefix: "algorithms/", icon: "suanfaku",
- children: [
- "几道常见的字符串算法题", "几道常见的链表算法题", "剑指offer部分编程题"
- ],
- },
- ],
-
- },
- {
- title: "数据库", icon: "database", prefix: "database/",
- children: [
- "数据库基础知识",
- "字符集",
- {
- title: "MySQL", prefix: "mysql/",
- children: [
- "mysql知识点&面试题总结",
- "a-thousand-lines-of-mysql-study-notes",
- "mysql-high-performance-optimization-specification-recommendations",
- "mysql-index", "mysql-logs", "transaction-isolation-level",
- "innodb-implementation-of-mvcc", "how-sql-executed-in-mysql",
- "some-thoughts-on-database-storage-time"
- ],
- },
- {
- title: "Redis", prefix: "redis/",
- children: ["redis知识点&面试题总结", "3-commonly-used-cache-read-and-write-strategies"],
- },
- ],
- },
- {
- title: "系统设计", icon: "xitongsheji", prefix: "system-design/",
- children: [
- {
- title: "基础", prefix: "basis/", icon: "jibendebasic",
- children: [
- "RESTfulAPI",
- "naming",
- ],
- },
- {
- title: "常用框架", prefix: "framework/", icon: "framework",
- children: [{
- title: "Spring", prefix: "spring/",
- children: ["Spring常见问题总结", "Spring&SpringBoot常用注解总结", "Spring事务总结", "Spring设计模式总结", "SpringBoot自动装配原理"]
- },
- "mybatis/mybatis-interview", "netty",
- {
- title: "SpringCloud", prefix: "springcloud/",
- children: ["springcloud-intro"]
- },
- ],
- },
- {
- title: "安全", prefix: "security/", icon: "security-fill",
- children: ["basis-of-authority-certification", "jwt优缺点分析以及常见问题解决方案", "sso-intro", "数据脱敏"]
- },
- "定时任务"
- ],
- },
- {
- title: "分布式", icon: "distributed-network", prefix: "distributed-system/",
- children: [
- {
- title: "理论&算法", prefix: "理论&算法/",
- children: ["cap&base理论", "paxos&raft算法"],
- },
- "api-gateway", "distributed-id",
- {
- title: "rpc", prefix: "rpc/",
- children: ["dubbo", "why-use-rpc"]
- },
- "distributed-transaction",
- {
- title: "分布式协调", prefix: "分布式协调/",
- children: ["zookeeper/zookeeper-intro", "zookeeper/zookeeper-plus", "zookeeper/zookeeper-in-action"]
- },
- ],
- }, {
- title: "高性能", icon: "gaojixiaozuzhibeifen", prefix: "high-performance/",
- children: [
- "读写分离&分库分表", "负载均衡",
- {
- title: "消息队列", prefix: "message-queue/",
- children: ["message-queue", "kafka知识点&面试题总结", "rocketmq-intro", "rocketmq-questions", "rabbitmq-intro"],
- },
- ],
- }, {
- title: "高可用", icon: "CalendarAvailability-1", prefix: "high-availability/",
- children: [
- "高可用系统设计", "limit-request", "降级&熔断", "超时和重试机制", "集群", "灾备设计和异地多活", "性能测试"
- ],
- }],
- },
- blog: {
- intro: "/intro/",
- sidebarDisplay: "mobile",
- links: {
- Zhihu: "https://www.zhihu.com/people/javaguide",
- Github: "https://github.com/Snailclimb",
- Gitee: "https://gitee.com/SnailClimb",
- },
- },
-
- footer: {
- display: true,
- content: '鄂ICP备2020015769号-1 ',
- },
-
- copyright: {
- status: "global",
- },
-
- git: {
- timezone: "Asia/Shanghai",
- },
-
- mdEnhance: {
- enableAll: true,
- presentation: {
- plugins: [
- "highlight",
- "math",
- "search",
- "notes",
- "zoom",
- "anything",
- "audio",
- "chalkboard",
- ],
- },
- },
-
- pwa: {
- favicon: "/favicon.ico",
- cachePic: true,
- apple: {
- icon: "/assets/icon/apple-icon-152.png",
- statusBarColor: "black",
- },
- msTile: {
- image: "/assets/icon/ms-icon-144.png",
- color: "#ffffff",
- },
- manifest: {
- icons: [
- {
- src: "/assets/icon/chrome-mask-512.png",
- sizes: "512x512",
- purpose: "maskable",
- type: "image/png",
- },
- {
- src: "/assets/icon/chrome-mask-192.png",
- sizes: "192x192",
- purpose: "maskable",
- type: "image/png",
- },
- {
- src: "/assets/icon/chrome-512.png",
- sizes: "512x512",
- type: "image/png",
- },
- {
- src: "/assets/icon/chrome-192.png",
- sizes: "192x192",
- type: "image/png",
- },
- ],
- shortcuts: [
- {
- name: "Guide",
- short_name: "Guide",
- url: "/guide/",
- icons: [
- {
- src: "/assets/icon/guide-maskable.png",
- sizes: "192x192",
- purpose: "maskable",
- type: "image/png",
- },
- {
- src: "/assets/icon/guide-monochrome.png",
- sizes: "192x192",
- purpose: "monochrome",
- type: "image/png",
- },
- ],
- },
- ],
- },
- },
- },
-});
diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b8b2364da30f49a19bd32651ac833c34bf71ddf1
--- /dev/null
+++ b/docs/.vuepress/config.ts
@@ -0,0 +1,86 @@
+import { defineUserConfig } from "vuepress";
+import { searchPlugin } from "@vuepress/plugin-search";
+import theme from "./theme.js";
+
+export default defineUserConfig({
+ dest: "./dist",
+
+ title: "JavaGuide(Java面试 + 学习指南)",
+ description:
+ "「Java学习指北 + Java面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。准备 Java 面试,复习 Java 知识点,首选 JavaGuide! ",
+ lang: "zh-CN",
+
+ head: [
+ // meta
+ ["meta", { name: "robots", content: "all" }],
+ ["meta", { name: "author", content: "Guide" }],
+ [
+ "meta",
+ {
+ "http-equiv": "Cache-Control",
+ content: "no-cache, no-store, must-revalidate",
+ },
+ ],
+ ["meta", { "http-equiv": "Pragma", content: "no-cache" }],
+ ["meta", { "http-equiv": "Expires", content: "0" }],
+ [
+ "meta",
+ {
+ name: "keywords",
+ content:
+ "Java基础, 多线程, JVM, 虚拟机, 数据库, MySQL, Spring, Redis, MyBatis, 系统设计, 分布式, RPC, 高可用, 高并发",
+ },
+ ],
+ ["meta", { name: "apple-mobile-web-app-capable", content: "yes" }],
+ // 添加百度统计
+ [
+ "script",
+ {},
+ `var _hmt = _hmt || [];
+ (function() {
+ var hm = document.createElement("script");
+ hm.src = "https://hm.baidu.com/hm.js?5dd2e8c97962d57b7b8fea1737c01743";
+ var s = document.getElementsByTagName("script")[0];
+ s.parentNode.insertBefore(hm, s);
+ })();`,
+ ],
+ ],
+
+ theme,
+
+ plugins: [
+ searchPlugin({
+ // https://v2.vuepress.vuejs.org/zh/reference/plugin/search.html
+ // 排除首页
+ isSearchable: (page) => page.path !== "/",
+ maxSuggestions: 10,
+ hotKeys: ["s", "/"],
+ // 用于在页面的搜索索引中添加额外字段
+ getExtraFields: () => [],
+ locales: {
+ "/": {
+ placeholder: "搜索",
+ },
+ },
+ }),
+ // searchProPlugin({
+ // indexContent: true,
+ // indexOptions: {
+ // tokenize: (text, fieldName) =>
+ // fieldName === "id" ? [text] : cut(text, true),
+ // },
+ // customFields: [
+ // {
+ // getter: ({ frontmatter }) =>
+ // frontmatter.category ?? null,
+ // formatter: "分类: $content",
+ // },
+ // ],
+ // suggestDelay: 60,
+ // }),
+ ],
+
+ pagePatterns: ["**/*.md", "!**/*.snippet.md", "!.vuepress", "!node_modules"],
+
+ shouldPrefetch: false,
+});
diff --git a/docs/.vuepress/navbar.ts b/docs/.vuepress/navbar.ts
new file mode 100644
index 0000000000000000000000000000000000000000..88d85c94049bef976fdc8bcbf8395981e9e8283a
--- /dev/null
+++ b/docs/.vuepress/navbar.ts
@@ -0,0 +1,45 @@
+import { navbar } from "vuepress-theme-hope";
+
+export default navbar([
+ { text: "面试指南", icon: "java", link: "/home.md" },
+ { text: "开源项目", icon: "github", link: "/open-source-project/" },
+ { text: "技术书籍", icon: "book", link: "/books/" },
+ {
+ text: "程序人生",
+ icon: "article",
+ link: "/high-quality-technical-articles/",
+ },
+ {
+ text: "知识星球",
+ icon: "planet",
+ children: [
+ {
+ text: "星球介绍",
+ icon: "about",
+ link: "/about-the-author/zhishixingqiu-two-years.md",
+ },
+ {
+ text: "星球专属优质专栏",
+ icon: "about",
+ link: "/zhuanlan/",
+ },
+ {
+ text: "星球优质主题汇总",
+ icon: "star",
+ link: "https://www.yuque.com/snailclimb/rpkqw1/ncxpnfmlng08wlf1",
+ },
+ ],
+ },
+ {
+ text: "网站相关",
+ icon: "about",
+ children: [
+ { text: "关于作者", icon: "zuozhe", link: "/about-the-author/" },
+ {
+ text: "更新历史",
+ icon: "history",
+ link: "/timeline/",
+ },
+ ],
+ },
+]);
diff --git a/docs/.vuepress/public/assets/icon/apple-icon-152.png b/docs/.vuepress/public/assets/icon/apple-icon-152.png
index 3eabbeb1dc3aeba231cc33f7f53849fed9d1fca1..f53c6c55b049ea917c283acd84bd5a5f62a289c3 100644
Binary files a/docs/.vuepress/public/assets/icon/apple-icon-152.png and b/docs/.vuepress/public/assets/icon/apple-icon-152.png differ
diff --git a/docs/.vuepress/public/assets/icon/chrome-192.png b/docs/.vuepress/public/assets/icon/chrome-192.png
index 851ad3a224d44f9ea9b59a0cf6890b30244216e6..5709628031cd66a7dc5d1a758601d8ffc862cab9 100644
Binary files a/docs/.vuepress/public/assets/icon/chrome-192.png and b/docs/.vuepress/public/assets/icon/chrome-192.png differ
diff --git a/docs/.vuepress/public/assets/icon/chrome-512.png b/docs/.vuepress/public/assets/icon/chrome-512.png
index 2fb9f40be375d7bd6ecca77368ead35d7388de07..2db62c2910783cadb4f99c15be74bb0134f763dc 100644
Binary files a/docs/.vuepress/public/assets/icon/chrome-512.png and b/docs/.vuepress/public/assets/icon/chrome-512.png differ
diff --git a/docs/.vuepress/public/assets/icon/chrome-mask-192.png b/docs/.vuepress/public/assets/icon/chrome-mask-192.png
index 530977a9e69c9c0dbcbdf8a68fdc366cf0addd84..77c39a2a828560cdfa70eec967cf4e5892ed767d 100644
Binary files a/docs/.vuepress/public/assets/icon/chrome-mask-192.png and b/docs/.vuepress/public/assets/icon/chrome-mask-192.png differ
diff --git a/docs/.vuepress/public/assets/icon/chrome-mask-512.png b/docs/.vuepress/public/assets/icon/chrome-mask-512.png
index a4f90ae484baa44df8f026740ffe280a26d21541..b8349f4ea1d00b8bdc5a2dc905376803ffaa317c 100644
Binary files a/docs/.vuepress/public/assets/icon/chrome-mask-512.png and b/docs/.vuepress/public/assets/icon/chrome-mask-512.png differ
diff --git a/docs/.vuepress/public/assets/icon/guide-maskable.png b/docs/.vuepress/public/assets/icon/guide-maskable.png
index 75449b6098bce400671eb4eac78c4ef687f431b2..230798a3c891267ce4d89b234b158784e17b7813 100644
Binary files a/docs/.vuepress/public/assets/icon/guide-maskable.png and b/docs/.vuepress/public/assets/icon/guide-maskable.png differ
diff --git a/docs/.vuepress/public/assets/icon/guide-monochrome.png b/docs/.vuepress/public/assets/icon/guide-monochrome.png
index 5b1dc406d6adadea7bd0cc4176ff80fcd9aa21c6..e12403e2ec73bab4044d8f7ced3df530cccd18ea 100644
Binary files a/docs/.vuepress/public/assets/icon/guide-monochrome.png and b/docs/.vuepress/public/assets/icon/guide-monochrome.png differ
diff --git a/docs/.vuepress/public/assets/icon/ms-icon-144.png b/docs/.vuepress/public/assets/icon/ms-icon-144.png
index 2464124422891d245517885bd71c2b49a7d29164..681cde6fccaaa520b994c680289341a09a800811 100644
Binary files a/docs/.vuepress/public/assets/icon/ms-icon-144.png and b/docs/.vuepress/public/assets/icon/ms-icon-144.png differ
diff --git a/docs/.vuepress/public/logo.png b/docs/.vuepress/public/logo.png
index 7675a8b5aa3ed8820b2cb30ddf34ccf470e80851..6e7fb462bd8b89e23a8a7e61f2bf3b326fa53716 100644
Binary files a/docs/.vuepress/public/logo.png and b/docs/.vuepress/public/logo.png differ
diff --git a/docs/.vuepress/public/logo.svg b/docs/.vuepress/public/logo.svg
index fdfe9e6c1ce98840dcb5273be7a416b02b8591c5..da6b683d5f18f4cc43418dbb42e74e856239e7e3 100644
--- a/docs/.vuepress/public/logo.svg
+++ b/docs/.vuepress/public/logo.svg
@@ -1,317 +1 @@
-
-
-
-
+
\ No newline at end of file
diff --git a/docs/.vuepress/public/me.png b/docs/.vuepress/public/me.png
index cfa3a6ea375d524d741f22766e1f7b22aa706d92..be8f2106c6cabd0937341ffcffe6c92f04927524 100644
Binary files a/docs/.vuepress/public/me.png and b/docs/.vuepress/public/me.png differ
diff --git a/docs/.vuepress/sidebar/about-the-author.ts b/docs/.vuepress/sidebar/about-the-author.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4ed42a239b00b3c0f8d88eda92d676ab78523d20
--- /dev/null
+++ b/docs/.vuepress/sidebar/about-the-author.ts
@@ -0,0 +1,27 @@
+import { arraySidebar } from "vuepress-theme-hope";
+
+export const aboutTheAuthor = arraySidebar([
+ {
+ text: "个人经历",
+ icon: "experience",
+ collapsible: false,
+ children: [
+ "internet-addiction-teenager",
+ "my-college-life",
+ "javaguide-100k-star",
+ "feelings-after-one-month-of-induction-training",
+ "feelings-of-half-a-year-from-graduation-to-entry",
+ ],
+ },
+ {
+ text: "杂谈",
+ icon: "chat",
+ collapsible: false,
+ children: [
+ "writing-technology-blog-six-years",
+ "my-article-was-stolen-and-made-into-video-and-it-became-popular",
+ "dog-that-copies-other-people-essay",
+ "zhishixingqiu-two-years",
+ ],
+ },
+]);
diff --git a/docs/.vuepress/sidebar/books.ts b/docs/.vuepress/sidebar/books.ts
new file mode 100644
index 0000000000000000000000000000000000000000..152d08c15849a90eaec4a17eb668c24216f7a034
--- /dev/null
+++ b/docs/.vuepress/sidebar/books.ts
@@ -0,0 +1,35 @@
+import { arraySidebar } from "vuepress-theme-hope";
+
+export const books = arraySidebar([
+ {
+ text: "计算机基础",
+ link: "cs-basics",
+ icon: "computer",
+ },
+ {
+ text: "数据库",
+ link: "database",
+ icon: "database",
+ },
+ {
+ text: "搜索引擎",
+ link: "search-engine",
+ icon: "search",
+ },
+ {
+ text: "Java",
+ link: "java",
+ icon: "java",
+ },
+ {
+ text: "软件质量",
+ link: "software-quality",
+ icon: "highavailable",
+ },
+
+ {
+ text: "分布式",
+ link: "distributed-system",
+ icon: "distributed-network",
+ },
+]);
diff --git a/docs/.vuepress/sidebar/high-quality-technical-articles.ts b/docs/.vuepress/sidebar/high-quality-technical-articles.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d84ec0475b8399e59bfafdced4351f2fd9708aed
--- /dev/null
+++ b/docs/.vuepress/sidebar/high-quality-technical-articles.ts
@@ -0,0 +1,66 @@
+import { arraySidebar } from "vuepress-theme-hope";
+
+export const highQualityTechnicalArticles = arraySidebar([
+ {
+ text: "练级攻略",
+ icon: "et-performance",
+ prefix: "advanced-programmer/",
+ collapsible: false,
+ children: [
+ "the-growth-strategy-of-the-technological-giant",
+ "ten-years-of-dachang-growth-road",
+ "seven-tips-for-becoming-an-advanced-programmer",
+ "20-bad-habits-of-bad-programmers",
+ "thinking-about-technology-and-business-after-five-years-of-work",
+ ],
+ },
+ {
+ text: "个人经历",
+ icon: "experience",
+ prefix: "personal-experience/",
+ collapsible: false,
+ children: [
+ "four-year-work-in-tencent-summary",
+ "two-years-of-back-end-develop--experience-in-didi-and-toutiao",
+ "8-years-programmer-work-summary",
+ "huawei-od-275-days",
+ ],
+ },
+ {
+ text: "程序员",
+ icon: "code",
+ prefix: "programmer/",
+ collapsible: false,
+ children: [
+ "how-do-programmers-publish-a-technical-book",
+ "efficient-book-publishing-and-practice-guide",
+ ],
+ },
+ {
+ text: "面试",
+ icon: "interview",
+ prefix: "interview/",
+ collapsible: true,
+ children: [
+ "the-experience-of-get-offer-from-over-20-big-companies",
+ "the-experience-and-thinking-of-an-interview-experienced-by-an-older-programmer",
+ "technical-preliminary-preparation",
+ "screen-candidates-for-packaging",
+ "summary-of-spring-recruitment",
+ "my-personal-experience-in-2021",
+ "how-to-examine-the-technical-ability-of-programmers-in-the-first-test-of-technology",
+ "some-secrets-about-alibaba-interview",
+ ],
+ },
+ {
+ text: "工作",
+ icon: "work",
+ prefix: "work/",
+ collapsible: true,
+ children: [
+ "get-into-work-mode-quickly-when-you-join-a-company",
+ "32-tips-improving-career",
+ "employee-performance",
+ ],
+ },
+]);
diff --git a/docs/.vuepress/sidebar/index.ts b/docs/.vuepress/sidebar/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5b7696edaa03c541907c9c770ef51c9e52bc183b
--- /dev/null
+++ b/docs/.vuepress/sidebar/index.ts
@@ -0,0 +1,572 @@
+import { sidebar } from "vuepress-theme-hope";
+
+import { aboutTheAuthor } from "./about-the-author.js";
+import { books } from "./books.js";
+import { highQualityTechnicalArticles } from "./high-quality-technical-articles.js";
+import { openSourceProject } from "./open-source-project.js";
+
+export default sidebar({
+ // 应该把更精确的路径放置在前边
+ "/open-source-project/": openSourceProject,
+ "/books/": books,
+ "/about-the-author/": aboutTheAuthor,
+ "/high-quality-technical-articles/": highQualityTechnicalArticles,
+ "/zhuanlan/": [
+ "java-mian-shi-zhi-bei",
+ "back-end-interview-high-frequency-system-design-and-scenario-questions",
+ "handwritten-rpc-framework",
+ "source-code-reading",
+ ],
+ // 必须放在最后面
+ "/": [
+ {
+ text: "必看",
+ icon: "star",
+ collapsible: true,
+ prefix: "javaguide/",
+ children: ["intro", "use-suggestion", "contribution-guideline", "faq"],
+ },
+ {
+ text: "面试准备",
+ icon: "interview",
+ collapsible: true,
+ prefix: "interview-preparation/",
+ children: [
+ "teach-you-how-to-prepare-for-the-interview-hand-in-hand",
+ "resume-guide",
+ "key-points-of-interview",
+ "project-experience-guide",
+ "interview-experience",
+ "self-test-of-common-interview-questions",
+ ],
+ },
+ {
+ text: "Java",
+ icon: "java",
+ collapsible: true,
+ prefix: "java/",
+ children: [
+ {
+ text: "基础",
+ prefix: "basis/",
+ icon: "basic",
+ children: [
+ "java-basic-questions-01",
+ "java-basic-questions-02",
+ "java-basic-questions-03",
+ {
+ text: "重要知识点",
+ icon: "star",
+ collapsible: true,
+ children: [
+ "why-there-only-value-passing-in-java",
+ "serialization",
+ "generics-and-wildcards",
+ "reflection",
+ "proxy",
+ "bigdecimal",
+ "unsafe",
+ "spi",
+ "syntactic-sugar",
+ ],
+ },
+ ],
+ },
+ {
+ text: "集合",
+ prefix: "collection/",
+ icon: "container",
+ children: [
+ "java-collection-questions-01",
+ "java-collection-questions-02",
+ "java-collection-precautions-for-use",
+ {
+ text: "源码分析",
+ icon: "star",
+ collapsible: true,
+ children: [
+ "arraylist-source-code",
+ "linkedlist-source-code",
+ "hashmap-source-code",
+ "concurrent-hash-map-source-code",
+ "linkedhashmap-source-code",
+ "copyonwritearraylist-source-code",
+ "arrayblockingqueue-source-code",
+ "priorityqueue-source-code",
+ ],
+ },
+ ],
+ },
+ {
+ text: "并发编程",
+ prefix: "concurrent/",
+ icon: "et-performance",
+ children: [
+ "java-concurrent-questions-01",
+ "java-concurrent-questions-02",
+ "java-concurrent-questions-03",
+ {
+ text: "重要知识点",
+ icon: "star",
+ collapsible: true,
+ children: [
+ "optimistic-lock-and-pessimistic-lock",
+ "jmm",
+ "java-thread-pool-summary",
+ "java-thread-pool-best-practices",
+ "java-concurrent-collections",
+ "aqs",
+ "atomic-classes",
+ "threadlocal",
+ "completablefuture-intro",
+ ],
+ },
+ ],
+ },
+ {
+ text: "IO",
+ prefix: "io/",
+ icon: "code",
+ collapsible: true,
+ children: ["io-basis", "io-design-patterns", "io-model", "nio-basis"],
+ },
+ {
+ text: "JVM",
+ prefix: "jvm/",
+ icon: "virtual_machine",
+ collapsible: true,
+ children: [
+ "memory-area",
+ "jvm-garbage-collection",
+ "class-file-structure",
+ "class-loading-process",
+ "classloader",
+ "jvm-parameters-intro",
+ "jdk-monitoring-and-troubleshooting-tools",
+ "jvm-in-action",
+ ],
+ },
+ {
+ text: "新特性",
+ prefix: "new-features/",
+ icon: "featured",
+ collapsible: true,
+ children: [
+ "java8-common-new-features",
+ "java8-tutorial-translate",
+ "java9",
+ "java10",
+ "java11",
+ "java12-13",
+ "java14-15",
+ "java16",
+ "java17",
+ "java18",
+ "java19",
+ "java20",
+ ],
+ },
+ ],
+ },
+ {
+ text: "计算机基础",
+ icon: "computer",
+ prefix: "cs-basics/",
+ collapsible: true,
+ children: [
+ {
+ text: "网络",
+ prefix: "network/",
+ icon: "network",
+ children: [
+ "other-network-questions",
+ "other-network-questions2",
+ "computer-network-xiexiren-summary",
+ {
+ text: "重要知识点",
+ icon: "star",
+ collapsible: true,
+ children: [
+ "osi-and-tcp-ip-model",
+ "application-layer-protocol",
+ "http-vs-https",
+ "http1.0-vs-http1.1",
+ "http-status-codes",
+ "dns",
+ "tcp-connection-and-disconnection",
+ "tcp-reliability-guarantee",
+ "arp",
+ "nat",
+ "network-attack-means",
+ ],
+ },
+ ],
+ },
+ {
+ text: "操作系统",
+ prefix: "operating-system/",
+ icon: "caozuoxitong",
+ children: [
+ "operating-system-basic-questions-01",
+ "operating-system-basic-questions-02",
+ {
+ text: "Linux",
+ collapsible: true,
+ icon: "linux",
+ children: ["linux-intro", "shell-intro"],
+ },
+ ],
+ },
+ {
+ text: "数据结构",
+ prefix: "data-structure/",
+ icon: "people-network-full",
+ collapsible: true,
+ children: [
+ "linear-data-structure",
+ "graph",
+ "heap",
+ "tree",
+ "red-black-tree",
+ "bloom-filter",
+ ],
+ },
+ {
+ text: "算法",
+ prefix: "algorithms/",
+ icon: "suanfaku",
+ collapsible: true,
+ children: [
+ "string-algorithm-problems",
+ "linkedlist-algorithm-problems",
+ "the-sword-refers-to-offer",
+ "10-classical-sorting-algorithms",
+ ],
+ },
+ ],
+ },
+ {
+ text: "数据库",
+ icon: "database",
+ prefix: "database/",
+ collapsible: true,
+ children: [
+ {
+ text: "基础",
+ icon: "basic",
+ children: [
+ "basis",
+ "nosql",
+ "character-set",
+ {
+ text: "SQL",
+ icon: "SQL",
+ prefix: "sql/",
+ collapsible: true,
+ children: [
+ "sql-syntax-summary",
+ "sql-questions-01",
+ "sql-questions-02",
+ "sql-questions-03",
+ "sql-questions-04",
+ "sql-questions-05"
+ ],
+ },
+ ],
+ },
+ {
+ text: "MySQL",
+ prefix: "mysql/",
+ icon: "mysql",
+ children: [
+ "mysql-questions-01",
+ "mysql-high-performance-optimization-specification-recommendations",
+ {
+ text: "重要知识点",
+ icon: "star",
+ collapsible: true,
+ children: [
+ "mysql-index",
+ {
+ text: "MySQL三大日志详解",
+ link: "mysql-logs",
+ },
+ "transaction-isolation-level",
+ "innodb-implementation-of-mvcc",
+ "how-sql-executed-in-mysql",
+ "mysql-query-cache",
+ "mysql-query-execution-plan",
+ "mysql-auto-increment-primary-key-continuous",
+ "some-thoughts-on-database-storage-time",
+ "index-invalidation-caused-by-implicit-conversion",
+ ],
+ },
+ ],
+ },
+ {
+ text: "Redis",
+ prefix: "redis/",
+ icon: "redis",
+ children: [
+ "cache-basics",
+ "redis-questions-01",
+ "redis-questions-02",
+ {
+ text: "重要知识点",
+ icon: "star",
+ collapsible: true,
+ children: [
+ "3-commonly-used-cache-read-and-write-strategies",
+ "redis-data-structures-01",
+ "redis-data-structures-02",
+ "redis-persistence",
+ "redis-memory-fragmentation",
+ "redis-common-blocking-problems-summary",
+ "redis-cluster",
+ ],
+ },
+ ],
+ },
+ {
+ text: "Elasticsearch",
+ prefix: "elasticsearch/",
+ icon: "elasticsearch",
+ collapsible: true,
+ children: ["elasticsearch-questions-01"],
+ },
+ {
+ text: "MongoDB",
+ prefix: "mongodb/",
+ icon: "mongodb",
+ collapsible: true,
+ children: ["mongodb-questions-01", "mongodb-questions-02"],
+ },
+ ],
+ },
+ {
+ text: "开发工具",
+ icon: "tool",
+ prefix: "tools/",
+ collapsible: true,
+ children: [
+ {
+ text: "Maven",
+ icon: "configuration",
+ prefix: "maven/",
+ children: ["maven-core-concepts"],
+ },
+ {
+ text: "Gradle",
+ icon: "gradle",
+ prefix: "gradle/",
+ children: ["gradle-core-concepts"],
+ },
+ {
+ text: "Git",
+ icon: "git",
+ prefix: "git/",
+ children: ["git-intro", "github-tips"],
+ },
+ {
+ text: "Docker",
+ icon: "docker1",
+ prefix: "docker/",
+ children: ["docker-intro", "docker-in-action"],
+ },
+ {
+ text: "IDEA",
+ icon: "intellijidea",
+ link: "https://gitee.com/SnailClimb/awesome-idea-tutorial",
+ },
+ ],
+ },
+ {
+ text: "常用框架",
+ prefix: "system-design/framework/",
+ icon: "component",
+ collapsible: true,
+ children: [
+ {
+ text: "Spring&Spring Boot",
+ icon: "bxl-spring-boot",
+ prefix: "spring/",
+ children: [
+ "spring-knowledge-and-questions-summary",
+ "springboot-knowledge-and-questions-summary",
+ "spring-common-annotations",
+ {
+ text: "重要知识点",
+ icon: "star",
+ collapsible: true,
+ children: [
+ "spring-transaction",
+ "spring-design-patterns-summary",
+ "spring-boot-auto-assembly-principles",
+ ],
+ },
+ ],
+ },
+ "mybatis/mybatis-interview",
+ "netty",
+ ],
+ },
+ {
+ text: "系统设计",
+ icon: "design",
+ prefix: "system-design/",
+ collapsible: true,
+ children: [
+ {
+ text: "基础",
+ prefix: "basis/",
+ icon: "basic",
+ collapsible: true,
+ children: [
+ "RESTfulAPI",
+ "software-engineering",
+ "naming",
+ "refactoring",
+ {
+ text: "单元测试指南",
+ link: "unit-test",
+ },
+ ],
+ },
+ {
+ text: "安全",
+ prefix: "security/",
+ icon: "security-fill",
+ collapsible: true,
+ children: [
+ "basis-of-authority-certification",
+ "jwt-intro",
+ "advantages-and-disadvantages-of-jwt",
+ "sso-intro",
+ "design-of-authority-system",
+ "encryption-algorithms",
+ "sentive-words-filter",
+ "data-desensitization",
+ ],
+ },
+ "system-design-questions",
+ "design-pattern",
+ "schedule-task",
+ "web-real-time-message-push",
+ ],
+ },
+ {
+ text: "分布式",
+ icon: "distributed-network",
+ prefix: "distributed-system/",
+ collapsible: true,
+ children: [
+ {
+ text: "理论&算法&协议",
+ icon: "suanfaku",
+ prefix: "protocol/",
+ collapsible: true,
+ children: [
+ "cap-and-base-theorem",
+ "paxos-algorithm",
+ "raft-algorithm",
+ "gossip-protocl",
+ ],
+ },
+ {
+ text: "API网关",
+ icon: "gateway",
+ children: ["api-gateway", "spring-cloud-gateway-questions"],
+ },
+ {
+ text: "分布式ID",
+ icon: "id",
+ children: ["distributed-id", "distributed-id-design"],
+ },
+ {
+ text: "分布式锁",
+ icon: "lock",
+ children: ["distributed-lock", "distributed-lock-implementations"],
+ },
+ {
+ text: "RPC",
+ prefix: "rpc/",
+ icon: "network",
+ collapsible: true,
+ children: ["rpc-intro", "dubbo"],
+ },
+ {
+ text: "ZooKeeper",
+ prefix: "distributed-process-coordination/zookeeper/",
+ icon: "framework",
+ collapsible: true,
+ children: ["zookeeper-intro", "zookeeper-plus"],
+ },
+ {
+ text: "分布式事务",
+ icon: "transanction",
+ collapsible: true,
+ children: ["distributed-transaction"],
+ },
+ {
+ text: "分布式配置中心",
+ icon: "configuration",
+ collapsible: true,
+ children: ["distributed-configuration-center"],
+ },
+ ],
+ },
+ {
+ text: "高性能",
+ icon: "et-performance",
+ prefix: "high-performance/",
+ collapsible: true,
+ children: [
+ {
+ text: "CDN",
+ icon: "cdn",
+ children: ["cdn"],
+ },
+ {
+ text: "负载均衡",
+ icon: "fuzaijunheng",
+ children: ["load-balancing"],
+ },
+ {
+ text: "数据库优化",
+ icon: "mysql",
+ children: [
+ "read-and-write-separation-and-library-subtable",
+ "sql-optimization",
+ ],
+ },
+ {
+ text: "消息队列",
+ prefix: "message-queue/",
+ icon: "MQ",
+ collapsible: true,
+ children: [
+ "message-queue",
+ "disruptor-questions",
+ "kafka-questions-01",
+ "rocketmq-questions",
+ "rabbitmq-questions",
+ ],
+ },
+ ],
+ },
+ {
+ text: "高可用",
+ icon: "highavailable",
+ prefix: "high-availability/",
+ collapsible: true,
+ children: [
+ "high-availability-system-design",
+ "redundancy",
+ "limit-request",
+ "fallback-and-circuit-breaker",
+ "timeout-and-retry",
+ "performance-test",
+ ],
+ },
+ ],
+});
diff --git a/docs/.vuepress/sidebar/open-source-project.ts b/docs/.vuepress/sidebar/open-source-project.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6d4b71bb4626f6770816b868884ef019ec29a382
--- /dev/null
+++ b/docs/.vuepress/sidebar/open-source-project.ts
@@ -0,0 +1,39 @@
+import { arraySidebar } from "vuepress-theme-hope";
+
+export const openSourceProject = arraySidebar([
+ {
+ text: "技术教程",
+ link: "tutorial",
+ icon: "book",
+ },
+ {
+ text: "实战项目",
+ link: "practical-project",
+ icon: "project",
+ },
+ {
+ text: "系统设计",
+ link: "system-design",
+ icon: "design",
+ },
+ {
+ text: "工具类库",
+ link: "tool-library",
+ icon: "codelibrary-fill",
+ },
+ {
+ text: "开发工具",
+ link: "tools",
+ icon: "tool",
+ },
+ {
+ text: "机器学习",
+ link: "machine-learning",
+ icon: "a-MachineLearning",
+ },
+ {
+ text: "大数据",
+ link: "big-data",
+ icon: "big-data",
+ },
+]);
diff --git a/docs/.vuepress/styles/index.scss b/docs/.vuepress/styles/index.scss
new file mode 100644
index 0000000000000000000000000000000000000000..a895ab82939d371bb71d5e1da60b97f02b6b7746
--- /dev/null
+++ b/docs/.vuepress/styles/index.scss
@@ -0,0 +1,5 @@
+body {
+ @media (min-width: 1440px) {
+ font-size: 16px;
+ }
+}
diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl
deleted file mode 100644
index e67a4cf03811d72e788c2e1dc2f7d95340be2993..0000000000000000000000000000000000000000
--- a/docs/.vuepress/styles/index.styl
+++ /dev/null
@@ -1,2 +0,0 @@
-// import icon
-@import '//at.alicdn.com/t/font_2922463_74fu8o5xg3.css'
\ No newline at end of file
diff --git a/docs/.vuepress/styles/palette.scss b/docs/.vuepress/styles/palette.scss
new file mode 100644
index 0000000000000000000000000000000000000000..fe23e2c0311aabc447084b44bee63aaa75cfd76b
--- /dev/null
+++ b/docs/.vuepress/styles/palette.scss
@@ -0,0 +1,3 @@
+$theme-color: #2980b9;
+$sidebar-width: 20rem;
+$sidebar-mobile-width: 16rem;
diff --git a/docs/.vuepress/theme.ts b/docs/.vuepress/theme.ts
new file mode 100644
index 0000000000000000000000000000000000000000..179f0a425e26bb53d2abea014f83cd08a6f8d8fd
--- /dev/null
+++ b/docs/.vuepress/theme.ts
@@ -0,0 +1,80 @@
+import { getDirname, path } from "@vuepress/utils";
+import { hopeTheme } from "vuepress-theme-hope";
+
+import navbar from "./navbar.js";
+import sidebar from "./sidebar/index.js";
+
+const __dirname = getDirname(import.meta.url);
+
+export default hopeTheme({
+ hostname: "https://javaguide.cn/",
+ logo: "/logo.png",
+ favicon: "/favicon.ico",
+
+ iconAssets: "//at.alicdn.com/t/c/font_2922463_kweia6fbo9.css",
+
+ author: {
+ name: "Guide",
+ url: "https://javaguide.cn/article/",
+ },
+
+ repo: "https://github.com/Snailclimb/JavaGuide",
+ docsDir: "docs",
+ // 纯净模式:https://theme-hope.vuejs.press/zh/guide/interface/pure.html
+ pure: true,
+ breadcrumb: false,
+ navbar,
+ sidebar,
+ footer:
+ '鄂ICP备2020015769号-1 ',
+ displayFooter: true,
+
+ pageInfo: [
+ "Author",
+ "Category",
+ "Tag",
+ // "Date",
+ "Original",
+ "Word",
+ "ReadingTime",
+ ],
+
+ blog: {
+ intro: "/about-the-author/",
+ sidebarDisplay: "mobile",
+ medias: {
+ Zhihu: "https://www.zhihu.com/people/javaguide",
+ Github: "https://github.com/Snailclimb",
+ Gitee: "https://gitee.com/SnailClimb",
+ },
+ },
+
+ plugins: {
+ blog: true,
+ copyright: true,
+ mdEnhance: {
+ align: true,
+ codetabs: true,
+ container: true,
+ figure: true,
+ include: {
+ resolvePath: (file, cwd) => {
+ if (file.startsWith("@"))
+ return path.resolve(
+ __dirname,
+ "../snippets",
+ file.replace("@", "./")
+ );
+
+ return path.resolve(cwd, file);
+ },
+ },
+ tasklist: true,
+ },
+ feed: {
+ atom: true,
+ json: true,
+ rss: true,
+ },
+ },
+});
diff --git a/docs/about-the-author/dog-that-copies-other-people-essay.md b/docs/about-the-author/dog-that-copies-other-people-essay.md
new file mode 100644
index 0000000000000000000000000000000000000000..653b616eaab7b9931ef5be0c61c03ed48cccbcbd
--- /dev/null
+++ b/docs/about-the-author/dog-that-copies-other-people-essay.md
@@ -0,0 +1,56 @@
+---
+title: 抄袭狗,你冬天睡觉脚必冷!!!
+category: 走近作者
+tag:
+ - 杂谈
+---
+
+抄袭狗真的太烦了。。。
+
+听朋友说我的文章在知乎又被盗了,原封不动地被别人用来引流。
+
+
+
+而且!!!这还不是最气的。
+
+这人还在文末注明的原出处还不是我的。。。
+
+
+
+也就是说 CSDN 有另外一位抄袭狗盗了我的这篇文章并声明了原创,知乎抄袭狗又原封不动地搬运了这位 CSDN 抄袭狗的文章。
+
+真可谓离谱他妈给离谱开门,离谱到家了。
+
+
+
+我打开知乎抄袭狗注明的原出处链接,好家伙,一模一样的内容,还表明了原创。
+
+
+
+看了一下 CSDN 这位抄袭狗的文章,好家伙,把我高赞回答搬运了一个遍。。。真是很勤奋了。。。
+
+CSDN 我就不想多说了,就一大型文章垃圾场,都是各种不规范转载,各种收费下载的垃圾资源。这号称国内流量最大的技术网站贼恶心,吃香太难看,能不用就不要用吧!
+
+像我自己平时用 Google 搜索的时候,都是直接屏蔽掉 CSDN 这个站点的。只需要下载一个叫做 Personal Blocklist 的 Chrome 插件,然后将 blog.csdn.net 添加进黑名单就可以了。
+
+
+
+我的文章基本被盗完了,关键是我自己发没有什么流量,反而是盗我文章的那些人比我这个原作者流量还大。
+
+这是什么世道,是人性的扭曲还是道德的沦丧?
+
+不过,也没啥,CSDN 这垃圾网站不去发文也无妨。
+
+看看 CSDN 热榜上的文章都是一些什么垃圾,不是各种广告就是一些毫无质量的拼凑文。
+
+
+
+当然了,也有极少部分的高质量文章,比如涛哥、二哥、冰河、微观技术等博主的文章。
+
+还有很多视频平台(比如抖音、哔哩哔哩)上面有很多博主直接把别人的原创拿来做个视频,用来引流或者吸粉。
+
+今天提到的这篇被盗的文章曾经就被一个培训机构拿去做成了视频用来引流。
+
+
+
+作为个体,咱也没啥办法,只能遇到一个举报一个。。。
diff --git a/docs/about-the-author/feelings-after-one-month-of-induction-training.md b/docs/about-the-author/feelings-after-one-month-of-induction-training.md
index 9f81220150fb63c02a86876a51781c6d958884f7..8ecb6eac45570c2a74bd5bdfaf9ae1adb50c09e0 100644
--- a/docs/about-the-author/feelings-after-one-month-of-induction-training.md
+++ b/docs/about-the-author/feelings-after-one-month-of-induction-training.md
@@ -1,19 +1,24 @@
-# 入职培训一个月后的感受
+---
+title: 入职培训一个月后的感受
+category: 走近作者
+tag:
+ - 个人经历
+---
不知不觉已经入职一个多月了,在入职之前我没有在某个公司实习过或者工作过,所以很多东西刚入职工作的我来说还是比较新颖的。学校到职场的转变,带来了角色的转变,其中的差别因人而异。对我而言,在学校的时候课堂上老师课堂上教的东西,自己会根据自己的兴趣选择性接受,甚至很多课程你不想去上的话,还可以逃掉。到了公司就不一样了,公司要求你会的技能你不得不学,除非你不想干了。在学校的时候大部分人编程的目的都是为了通过考试或者找到一份好工作,真正靠自己兴趣支撑起来的很少,到了工作岗位之后我们编程更多的是因为工作的要求,相比于学校的来说会一般会更有挑战而且压力更大。在学校的时候,我们最重要的就是对自己负责,我们不断学习知识去武装自己,但是到了公司之后我们不光要对自己负责,更要对公司负责,毕竟公司出钱请你过来,不是让你一直 on beach 的。
-刚来公司的时候,因为公司要求,我换上了 Mac 电脑。由于之前一直用的是 Windows 系统,所以非常不习惯。刚开始用 Mac 系统的时候笨手笨脚,自己会很明显的感觉自己的编程效率降低了至少 3 成。当时内心还是挺不爽的,心里也总是抱怨为什么不直接用 Windows 系统或者 Linux 系统。不过也挺奇怪,大概一个星期之后,自己就开始慢慢适应使用 Mac 进行编程,甚至非常喜欢。我这里不想对比 Mac 和 Windows 编程体验哪一个更好,我觉得还是因人而异,相同价位的 Mac 的配置相比于 Windows确实要被甩几条街。不过 Mac 的编程和使用体验确实不错,当然你也可以选择使用 Linux 进行日常开发,相信一定很不错。 另外,Mac 不能玩一些主流网络游戏,对于一些克制不住自己想玩游戏的朋友是一个不错的选择。
+刚来公司的时候,因为公司要求,我换上了 Mac 电脑。由于之前一直用的是 Windows 系统,所以非常不习惯。刚开始用 Mac 系统的时候笨手笨脚,自己会很明显的感觉自己的编程效率降低了至少 3 成。当时内心还是挺不爽的,心里也总是抱怨为什么不直接用 Windows 系统或者 Linux 系统。不过也挺奇怪,大概一个星期之后,自己就开始慢慢适应使用 Mac 进行编程,甚至非常喜欢。我这里不想对比 Mac 和 Windows 编程体验哪一个更好,我觉得还是因人而异,相同价位的 Mac 的配置相比于 Windows 确实要被甩几条街。不过 Mac 的编程和使用体验确实不错,当然你也可以选择使用 Linux 进行日常开发,相信一定很不错。 另外,Mac 不能玩一些主流网络游戏,对于一些克制不住自己想玩游戏的朋友是一个不错的选择。
-不得不说 ThoughtWorks 的培训机制还是很不错的。应届生入职之后一般都会安排培训,与往年不同的是,今年的培训多了中国本地班(TWU-C)。作为本地班的第一期学员,说句心里话还是很不错。8周的培训,除了工作需要用到的基本技术比如ES6、SpringBoot等等之外,还会增加一些新员工基本技能的培训比如如何高效开会、如何给别人正确的提 Feedback、如何对代码进行重构、如何进行 TDD 等等。培训期间不定期的有活动,比如Weekend Trip、 City Tour、Cake time等等。最后三周还会有一个实际的模拟项目,这个项目基本和我们正式工作的实际项目差不多,我个人感觉很不错。目前这个项目已经正式完成了一个迭代,我觉得在做项目的过程中,收获最大的不是项目中使用的技术,而是如何进行团队合作、如何正确使用 Git 团队协同开发、一个完成的迭代是什么样子的、做项目的过程中可能遇到那些问题、一个项目运作的完整流程等等。
+不得不说 ThoughtWorks 的培训机制还是很不错的。应届生入职之后一般都会安排培训,与往年不同的是,今年的培训多了中国本地班(TWU-C)。作为本地班的第一期学员,说句心里话还是很不错。8 周的培训,除了工作需要用到的基本技术比如 ES6、SpringBoot 等等之外,还会增加一些新员工基本技能的培训比如如何高效开会、如何给别人正确的提 Feedback、如何对代码进行重构、如何进行 TDD 等等。培训期间不定期的有活动,比如 Weekend Trip、 City Tour、Cake time 等等。最后三周还会有一个实际的模拟项目,这个项目基本和我们正式工作的实际项目差不多,我个人感觉很不错。目前这个项目已经正式完成了一个迭代,我觉得在做项目的过程中,收获最大的不是项目中使用的技术,而是如何进行团队合作、如何正确使用 Git 团队协同开发、一个完成的迭代是什么样子的、做项目的过程中可能遇到那些问题、一个项目运作的完整流程等等。
-ThoughtWorks 非常提倡分享、提倡帮助他人成长,这一点在公司的这段时间深有感触。培训期间,我们每个人会有一个 Trainer 负责,Trainer 就是日常带我们上课和做项目的同事,一个 Trainer 大概会负责5-6个人。Trainer不定期都会给我们最近表现的 Feedback( 反馈) ,我个人觉得这个并不是这是走走形式,Trainer 们都很负责,很多时候都是在下班之后找我们聊天。同事们也都很热心,如果你遇到问题,向别人询问,其他人如果知道的话一般都会毫无保留的告诉你,如果遇到大部分都不懂的问题,甚至会组织一次技术 Session 分享。上周五我在我们小组内进行了一次关于 Feign 远程调用的技术分享,因为 team 里面大家对这部分知识都不太熟悉,但是后面的项目进展大概率会用到这部分知识。我刚好研究了这部分内容,所以就分享给了组内的其他同事,以便于项目更好的进行。
+ThoughtWorks 非常提倡分享、提倡帮助他人成长,这一点在公司的这段时间深有感触。培训期间,我们每个人会有一个 Trainer 负责,Trainer 就是日常带我们上课和做项目的同事,一个 Trainer 大概会负责 5 - 6 个人。Trainer 不定期都会给我们最近表现的 Feedback (反馈) ,我个人觉得这个并不是这是走走形式,Trainer 们都很负责,很多时候都是在下班之后找我们聊天。同事们也都很热心,如果你遇到问题,向别人询问,其他人如果知道的话一般都会毫无保留的告诉你,如果遇到大部分都不懂的问题,甚至会组织一次技术 Session 分享。上周五我在我们小组内进行了一次关于 Feign 远程调用的技术分享,因为 team 里面大家对这部分知识都不太熟悉,但是后面的项目进展大概率会用到这部分知识。我刚好研究了这部分内容,所以就分享给了组内的其他同事,以便于项目更好的进行。
- 另外,ThoughtWorks 也是一家非常提倡 Feedback( 反馈) 文化的公司,反馈是告诉人们我们对他们的表现的看法以及他们应该如何更好地做到这一点。刚开始我并没有太在意,慢慢地自己确实感觉到正确的进行反馈对他人会有很大的帮助。因为人在做很多事情的时候,会很难发现别人很容易看到的一些小问题。就比如一个很有趣的现象一样,假如我们在做项目的时候没有测试这个角色,如果你完成了自己的模块,并且自己对这个模块测试了很多遍,你发现已经没啥问题了。但是,到了实际使用的时候会很大概率出现你之前从来没有注意的问题。解释这个问题的说法是:每个人的视野或多或少都是有盲点的,这与我们的关注点息息相关。对于自己做的东西,很多地方自己测试很多遍都不会发现,但是如果让其他人帮你进行测试的话,就很大可能会发现很多显而易见的问题。
+另外,ThoughtWorks 也是一家非常提倡 Feedback (反馈) 文化的公司,反馈是告诉人们我们对他们的表现的看法以及他们应该如何更好地做到这一点。刚开始我并没有太在意,慢慢地自己确实感觉到正确的进行反馈对他人会有很大的帮助。因为人在做很多事情的时候,会很难发现别人很容易看到的一些小问题。就比如一个很有趣的现象一样,假如我们在做项目的时候没有测试这个角色,如果你完成了自己的模块,并且自己对这个模块测试了很多遍,你发现已经没啥问题了。但是,到了实际使用的时候会很大概率出现你之前从来没有注意的问题。解释这个问题的说法是:每个人的视野或多或少都是有盲点的,这与我们的关注点息息相关。对于自己做的东西,很多地方自己测试很多遍都不会发现,但是如果让其他人帮你进行测试的话,就很大可能会发现很多显而易见的问题。

-工作之后,平时更新公众号、专栏还有维护 Github 的时间变少了。实际上,很多时候下班回来后,都有自己的时间来干自己的事情,但是自己也总是找工作太累或者时间比较零散的接口来推掉了。到了今天,翻看 Github 突然发现 14 天前别人在 Github 上给我提的 pr 我还没有处理。这一点确实是自己没有做好的地方,没有合理安排好自己的时间。实际上自己有很多想写的东西,后面会慢慢将他们提上日程。工作之后,更加发现下班后的几个小时如何度过确实很重要 ,如果你觉得自己没有完成好自己白天该做的工作的话,下班后你可以继续忙白天没有忙完的工作,如果白天的工作对于你游刃有余的话,下班回来之后,你大可去干自己感兴趣的事情,学习自己感兴趣的技术。做任何事情都要基于自身的基础,切不可好高骛远。
+工作之后,平时更新公众号、专栏还有维护 Github 的时间变少了。实际上,很多时候下班回来后,都有自己的时间来干自己的事情,但是自己也总是找工作太累或者时间比较零散的接口来推掉了。到了今天,翻看 Github 突然发现 14 天前别人在 Github 上给我提的 PR 我还没有处理。这一点确实是自己没有做好的地方,没有合理安排好自己的时间。实际上自己有很多想写的东西,后面会慢慢将他们提上日程。工作之后,更加发现下班后的几个小时如何度过确实很重要 ,如果你觉得自己没有完成好自己白天该做的工作的话,下班后你可以继续忙白天没有忙完的工作,如果白天的工作对于你游刃有余的话,下班回来之后,你大可去干自己感兴趣的事情,学习自己感兴趣的技术。做任何事情都要基于自身的基础,切不可好高骛远。
-工作之后身边也会有很多厉害的人,多从他人身上学习我觉得是每个职场人都应该做的。这一届和我们一起培训的同事中,有一些技术很厉害的,也有一些技术虽然不是那么厉害,但是组织能力以及团队协作能力特别厉害的。有一个特别厉害的同事,在我们还在学 SpringBoot 各种语法的时候,他自己利用业余时间写了一个简化版的 SpringBoot ,涵盖了 Spring 的一些常用注解比如 `@RestController`、`@Autowried`、`@Pathvairable`、`@RestquestParam`等等(已经联系这位同事,想让他开源一下,后面会第一时间同步到公众号,期待一下吧!)。我觉得这位同事对于编程是真的有兴趣,他好像从初中就开始接触编程了,对于各种底层知识也非常感兴趣,自己写过实现过很多比较底层的东西。他的梦想是在 Github 上造一个 20k Star 以上的轮子。我相信以这位同事的能力一定会达成目标的,在这里祝福这位同事,希望他可以尽快实现这个目标。
+工作之后身边也会有很多厉害的人,多从他人身上学习我觉得是每个职场人都应该做的。这一届和我们一起培训的同事中,有一些技术很厉害的,也有一些技术虽然不是那么厉害,但是组织能力以及团队协作能力特别厉害的。有一个特别厉害的同事,在我们还在学 SpringBoot 各种语法的时候,他自己利用业余时间写了一个简化版的 SpringBoot ,涵盖了 Spring 的一些常用注解比如 `@RestController`、`@Autowried`、`@Pathvairable`、`@RestquestParam`等等(已经联系这位同事,想让他开源一下,后面会第一时间同步到公众号,期待一下吧!)。我觉得这位同事对于编程是真的有兴趣,他好像从初中就开始接触编程了,对于各种底层知识也非常感兴趣,自己写过实现过很多比较底层的东西。他的梦想是在 Github 上造一个 20k Star 以上的轮子。我相信以这位同事的能力一定会达成目标的,在这里祝福这位同事,希望他可以尽快实现这个目标。
-这是我入职一个多月之后的个人感受,很多地方都是一带而过,后面我会抽时间分享自己在公司或者业余学到的比较有用的知识给各位,希望看过的人都能有所收获。
\ No newline at end of file
+这是我入职一个多月之后的个人感受,很多地方都是一带而过,后面我会抽时间分享自己在公司或者业余学到的比较有用的知识给各位,希望看过的人都能有所收获。
diff --git a/docs/about-the-author/feelings-of-half-a-year-from-graduation-to-entry.md b/docs/about-the-author/feelings-of-half-a-year-from-graduation-to-entry.md
new file mode 100644
index 0000000000000000000000000000000000000000..327715f2b737bcaf137eddd42a9eb66055976e42
--- /dev/null
+++ b/docs/about-the-author/feelings-of-half-a-year-from-graduation-to-entry.md
@@ -0,0 +1,56 @@
+---
+title: 从毕业到入职半年的感受
+category: 走近作者
+tag:
+ - 个人经历
+---
+
+如果大家看过我之前的介绍的话,就会知道我是 19 年毕业的几百万应届毕业生中的一员。这篇文章主要讲了一下我入职大半年的感受,文中有很多自己的主观感受,如果你们有任何不认同的地方都可以直接在评论区说出来,会很尊重其他人的想法。
+
+简单说一下自己的情况吧!我目前是在一家外企,每天的工作和大部分人一样就是做开发。毕业到现在,差不多也算是工作半年多了,也已经过了公司 6 个月的试用期。目前在公司做过两个偏向于业务方向的项目,其中一个正在做。你很难想象我在公司做的两个业务项目的后端都没有涉及到分布式/微服务,没有接触到 Redis、Kafka 等等比较“高大上”的技术在项目中的实际运用。
+
+第一个项目做的是公司的内部项目——员工成长系统。抛去员工成长系统这个名字,实际上这个系统做的就是绩效考核比如你在某个项目组的表现。这个项目的技术是 Spring Boot+ JPA + Spring Security + K8S + Docker + React。第二个目前正在做的是一个集成游戏 (cocos)、Web 管理端 (Spring Boot + Vue) 和小程序 (Taro) 项目。
+
+是的,我在工作中的大部分时间都和 CRUD 有关,每天也会写前端页面。之前我认识的一个朋友 ,他听说我做的项目中大部分内容都是写业务代码之后就非常纳闷,他觉得单纯写业务代码得不到提升?what?你一个应届生,连业务代码都写不好你给我说这个!所以,**我就很纳闷不知道为什么现在很多连业务代码都写不好的人为什么人听到 CRUD 就会反感?至少我觉得在我工作这段时间我的代码质量得到了提升、定位问题的能力有了很大的改进、对于业务有了更深的认识,自己也可以独立完成一些前端的开发了。**
+
+其实,我个人觉得能把业务代码写好也没那么容易,抱怨自己天天做 CRUD 工作之前,看看自己 CRUD 的代码写好没。再换句话说,单纯写 CRUD 的过程中你搞懂了哪些你常用的注解或者类吗?这就像一个只会 `@Service`、`@Autowired`、`@RestController`等等最简单的注解的人说我已经掌握了 Spring Boot 一样。
+
+不知道什么时候开始大家都会觉得有实际使用 Redis、MQ 的经验就很牛逼了,这可能和当前的面试环境有关系。你需要和别人有差异,你想进大厂的话,好像就必须要这些技术比较在行,好吧,没有好像,自信点来说对于大部分求职者这些技术都是默认你必备的了。
+
+**实话实说,我在大学的时候就陷入过这个“伪命题”中**。在大学的时候,我大二因为加入了一个学校的偏技术方向的校媒才接触到 Java ,当时我们学习 Java 的目的就是开发一个校园通。 大二的时候,编程相当于才入门水平的我才接触 Java,花了一段时间才掌握 Java 基础。然后,就开始学习安卓开发。
+
+到了大三上学期,我才真正确定要走 Java 后台的方向,找 Java 后台的开发工作。学习了 3 个月左右的 WEB 开发基础之后,我就开始学习分布式方面内容比如 Redis、Dubbo 这些。我当时是通过看书 + 视频 + 博客的方式学习的,自学过程中通过看视频自己做过两个完整的项目,一个普通的业务系统,一个是分布式的系统。**我当时以为自己做完之后就很牛逼了,我觉得普通的 CRUD 工作已经不符合我当前的水平了。哈哈!现在看来,当时的我过于哈皮!**
+
+这不!到了大三暑假跟着老师一起做项目的时候就出问题了。大三的时候,我们跟着老师做的是一个绩效考核系统,业务复杂程度中等。这个项目的技术用的是:SSM + Shiro + JSP。当时,做这个项目的时候我遇到各种问题,各种我以为我会写的代码都不会写了,甚至我写一个简单的 CRUD 都要花费好几天的时间。所以,那时候我都是边复习边学习边写代码。虽然很累,但是,那时候学到了很多,也让我在技术面前变得更加踏实。我觉得这“**这个项目已经没有维护的可能性**”这句话是我对我过的这个项目最大的否定了。
+
+技术千变万化,掌握最核心的才是王道。我们前几年可能还在用 Spring 基于传统的 XML 开发,现在几乎大家都会用 Spring Boot 这个开发利器来提升开发速度,再比如几年前我们使用消息队列可能还在用 ActiveMQ,到今天几乎都没有人用它了,现在比较常用的就是 Rocket MQ、Kafka 。技术更新换代这么快的今天,你是无法把每一个框架/工具都学习一遍的。
+
+**很多初学者上来就想通过做项目学习,特别是在公司,我觉得这个是不太可取的。** 如果的 Java 基础或者 Spring Boot 基础不好的话,建议自己先提前学习一下之后再开始看视频或者通过其他方式做项目。 **还有一点就是,我不知道为什么大家都会说边跟着项目边学习做的话效果最好,我觉得这个要加一个前提是你对这门技术有基本的了解或者说你对编程有了一定的了解。**
+
+**划重点!!!在自己基础没打牢的情况下,单纯跟着视频做一点用没有。你会发现你看完视频之后,让你自己写代码的时候又不会写了。**
+
+不知道其他公司的程序员是怎么样的?我感觉技术积累很大程度在乎平时,单纯依靠工作绝大部分情况只会加快自己做需求的熟练度,当然,写多了之后或多或少也会提升你对代码质量的认识(前提是你有这个意识)。
+
+工作之余,我会利用业余时间来学习自己想学的东西。工作中的例子就是我刚进公司的第一个项目用到了 Spring Security + JWT ,因为当时自己对于这个技术不太了解,然后就在工作之外大概花了一周的时间学习写了一个 Demo 分享了出来,GitHub 地址: 。以次为契机,我还分享了
+
+- [《一问带你区分清楚 Authentication、Authorization 以及 Cookie、Session、Token》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485626&idx=1&sn=3247aa9000693dd692de8a04ccffeec1&chksm=cea24771f9d5ce675ea0203633a95b68bfe412dc6a9d05f22d221161147b76161d1b470d54b3&token=684071313&lang=zh_CN&scene=21#wechat_redirect)
+- [JWT 身份认证优缺点分析以及常见问题解决方案](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485655&idx=1&sn=583eeeb081ea21a8ec6347c72aa223d6&chksm=cea2471cf9d5ce0aa135f2fb9aa32d98ebb3338292beaccc1aae43d1178b16c0125eb4139ca4&token=1737409938&lang=zh_CN#rd)
+
+另外一个最近的例子是因为肺炎疫情在家的这段时间,自学了 Kafka,并且正在准备写一系列的入门文章,目前已经完成了:
+
+1. 大白话 Kafka 入门;
+2. Kafka 安装和基本功能体验;
+3. Spring Boot 整合 Kafka 发送和接受消息;
+4. Spring Boot 整合 Kafka 发送和接受消息的一些事务、错误消息处理等等。
+
+还没完成的:
+
+1. Kafka 高级特性比如工作流程、Kafka 为什么快等等的分析;
+
+2. 源码阅读分析;
+
+3. ......
+
+**所以,我觉得技术的积累和沉淀很大程度在乎工作之外的时间(大佬和一些本身就特别厉害的除外)。**
+
+**未来还有很长的路要走,即使再有精力也学不完你想学的所有技术,适当取舍、适当妥协,适当娱乐。**
diff --git a/docs/about-the-author/internet-addiction-teenager.md b/docs/about-the-author/internet-addiction-teenager.md
index 52458b4a0c018918d35c8e2844cb0202ce35eb7a..d4a34b8fc271316cfde407a3fe735a57655ed71c 100644
--- a/docs/about-the-author/internet-addiction-teenager.md
+++ b/docs/about-the-author/internet-addiction-teenager.md
@@ -1,104 +1,140 @@
-# 我曾经也是网瘾少年
+---
+title: 我曾经也是网瘾少年
+category: 走近作者
+tag:
+ - 个人经历
+---
-聊到高考,无数人都似乎有很多话说。今天就假借高考的名义,**简单**来聊聊我的求学经历吧!因为我自己的求学经历真的还不算平淡,甚至有点魔幻,所以还是有很多话想要说的。这篇文章大概会从我的初中一直介绍到大学,每一部分我都不会花太多篇幅。实际上,每一段经历我都可以增加很多“有趣”的经历,考虑到篇幅问题,以后有机会再慢慢说吧!
+> 这篇文章写入 2021 年高考前夕。
-整个初中我都属于有点网瘾少年的状态,不过初三的时候稍微克制一些。到了高二下学期的时候,自己才对游戏没有真的没有那么沉迷了。
+聊到高考,无数人都似乎有很多话说。今天就假借高考的名义,简单来聊聊我的高中求学经历吧!
-另外,关于大学的详细经历我已经在写了。想要知道我是如何从一个普通的不能再普通的少年慢慢成长起来的朋友不要错过~
+说实话,我自己的高中求学经历真的还不算平淡,甚至有点魔幻,所以还是有很多话想要说的。
-
+这篇文章大概会从我的初中一直介绍到高中,每一部分我都不会花太多篇幅,就简单聊聊吧!
**以下所有内容皆是事实,没有任何夸大的地方,稍微有一点点魔幻。**
-## 01 刚开始接触电脑
+## 刚开始接触电脑
-最开始接触电脑是在我五年级的时候,那时候家里没电脑,都是在黑网吧玩的。我现在已经记不清当时是被哥哥还是姐姐带进网吧的了。
+最开始接触电脑是在我刚上五年级的时候,那时候家里没电脑,刚开始上网都是在黑网吧玩的。
-起初的时候,自己就是玩玩流行蝴蝶剑、单机摩托之类的单机游戏。但是,也没有到沉迷的地步,只是觉得这东西确实挺好玩的。
+在黑网吧上网的经历也是一波三折,经常会遇到警察来检查或者碰到大孩子骚扰。在黑网吧上网的一年多中,我一共两次碰到警察来检查,主要是看有没有未成年人(当时黑网吧里几乎全是未成年人),实际感觉像是要问黑网吧老板要点好处。碰到大孩子骚扰的次数就比较多,大孩子经常抢我电脑,还威胁我把身上所有的钱给他们。我当时一个人也比较怂,被打了几次之后,就尽量避开大孩子来玩的时间去黑网吧,身上也只带很少的钱。小时候的性格就比较独立,在外遇到事情我一般也不会给家里人说。
-
+我现在已经记不太清当时是被我哥还是我姐带进网吧的,好像是我姐。
-开始有网瘾是在小学毕业的时候,在我玩了一款叫做 **QQ 飞车**的游戏之后(好像是六年级就开始玩了)。我艹,当时真的被这游戏吸引了。**每天上课都幻想自己坐在车里面飘逸,没错,当时就觉得秋名山车神就是我啦!**
+起初的时候,自己就是玩玩流行蝴蝶剑、单机摩托之类的单机游戏。但是,也没有到沉迷的地步,只是觉得这东西确实挺好玩的,一玩就可以玩一下午,恋恋不舍。
-我记得,那时候上网还不要身份证,10 元办一张网卡就行了,网费也是一元一小时。但凡,我口袋里有余钱,我都会和我的小伙伴奔跑到网吧一起玩 QQ 飞车。Guide 的青回啊!说到这,我情不自禁地打开自己的 Windows 电脑,下载了 Wegame ,然后下载了 QQ 飞车。
+
+
+## 小学毕业后开始有网瘾
+
+开始有网瘾是在小学毕业的时候,在我玩了一款叫做 **QQ 飞车** 的游戏之后(好像是六年级末就开始玩了)。我艹,当时真的被这游戏吸引了。**每天上课都幻想自己坐在车里面飘逸,没错,当时就觉得秋名山车神就是我啦!**
+
+我当时技术还是挺不错的,整个网吧玩这个游戏的貌似还没有可以打败我的(我们当时经常会开放切磋)。
+
+QQ 飞车这款戏当时还挺火的,很多 90 后的小伙伴应该比较熟悉。
+
+我记得,那时候上网还不要身份证,10 元办一张网卡就行了,网费也是一元一小时。我就经常不吃早饭,攒钱用来上网。只要口袋里有钱,我都会和我的小伙伴奔跑到网吧一起玩 QQ 飞车。青回啊!
+
+> 说到这,我情不自禁地打开自己的 Windows 电脑,下载了 Wegame ,然后下载了 QQ 飞车。
到了初二的时候,就没玩 QQ 飞车了。我的等级也永久定格在了 **120** 级,这个等级在当时那个升级难的一匹的年代,算的上非常高的等级了。
-
+
-## 02 初二网瘾爆发
+## 初二网瘾爆发
-网瘾爆发是在上了初中之后。初二的时候,最为猖狂,自己当时真的是太痴迷 **穿越火线** 了,每天上课都在想像自己拿起枪横扫地方阵营的场景。除了周末在网吧度过之外,我经常每天早上还会起早去玩别人包夜留下的机子,毕竟那时候上学也没什么钱嘛!
+网瘾爆发是在上了初中之后。初二的时候,最为猖狂,自己当时真的是太痴迷于 **穿越火线** 这款游戏了,比 QQ 飞车还要更痴迷一些。每天上课都在想像自己拿起枪横扫地方阵营的场景,心完全不在学习上。
-
+我经常每天早上起早去玩别人包夜留下的机子,毕竟那时候上学也没什么钱嘛!我几乎每个周五晚上都会趁家人睡着之后,偷偷跑出去通宵。整个初二我通宵了无数次,我的眼睛就是这样近视的。
-那时候成绩挺差的。这样说吧!我当时在很普通的一个县级市的高中,全年级有 500 来人,我基本都是在 280 名左右。
+有网瘾真的很可怕,为了上网什么都敢做。当时我家住在顶楼的隔热层,我每次晚上偷偷出去上网,为了不被家里人发现,要从我的房间的窗户爬出去,穿过几栋楼,经过几间无人居住的顶楼隔热层之后再下楼。现在想想,还是比较危险的。而且,我天生比较怕黑。当时为了上网,每次穿过这么多没人居住的顶层隔热层都没怕过。你让我现在再去,我都不敢,实在是佩服当年的自己的啊!
-而且,整个初二我都没有学物理。因为开学不久的一次物理课,物理老师误会我在上课吃东西还狡辩,闪了我一巴掌。从此,我上物理课就睡觉,平常的物理考试就交白卷。那时候心里一直记仇,想着以后自己长大了把这个物理暴打他一顿。
+
-初中时候的觉悟是在初三上学期的时候,当时就突然意识到自己马上就要升高中了。为了让自己能在家附近上学,因为当时我家就在我们当地的二中附近(_附近网吧多是主要原因,哈哈_)。年级前 80 的话基本才有可能考得上二中。**经过努力,初三上学期的第一次月考我直接从 280 多名进不到了年级 50 多名。当时,还因为进步太大,被当做进步之星在讲台上给整个年级做演讲。**那也是我第一次在这么多人面前讲话,挺紧张的,但是挺爽的。
+周五晚上通宵完之后,我会睡到中午,然后下午继续去网吧玩。到了周日,基本都是直接从早上 8 点玩到晚上 9 点 10 点。那时候精力是真旺盛,真的完全不会感觉比较累,反而乐在其中。
-**其实在初三的时候,我的网瘾还是很大。不过,我去玩游戏的前提都是自己把所有任务做完,并且上课听讲也很认真。** 我参加高中提前考试前的一个晚上,我半夜12点乘着妈妈睡着,跑去了网吧玩CF到凌晨 3点多回来。那一次我被抓了现行,到家之后发现妈妈就坐在客厅等我,训斥一顿后,我就保证以后不再晚上偷偷跑出去了(*其实整个初二我通宵了无数次,每个周五晚上都回去通宵*)。
+我的最终军衔停留在了两个钻石,玩过的小伙伴应该清楚这在当时要玩多少把(现在升级比较简单)。
-_这里要说明一点:我的智商我自己有自知之明的,属于比较普通的水平吧! 前进很大的主要原因是自己基础还行,特别是英语和物理。英语是因为自己喜欢,加上小学就学了很多初中的英语课程。 物理的话就很奇怪,虽然初二也不怎么听物理课,也不会物理,但是到了初三之后自己就突然开窍了。真的!我现在都感觉很奇怪。然后,到了高中之后,我的英语和物理依然是我最好的两门课。大学的兼职,我出去做家教都是教的高中物理。_
+那时候成绩挺差的。这样说吧!我当时在很普通的一个县级市的高中,全年级有 500 来人,我基本都是在 280 名左右。而且,整个初二我都没有学物理,上物理课就睡觉,考试就交白卷。
-后面,自己阴差阳错参加我们那个县级市的提前招生考试,然后就到了我们当地的二中,也没有参加中考。
+为什么对物理这么抵触呢?这是因为开学不久的一次物理课,物理老师误会我在上课吃东西还狡辩,扇了我一巴掌。那时候心里一直记仇到大学,想着以后自己早晚有时间把这个物理老师暴打一顿。
-## 03 高中生活
+## 初三开启学习模式
-上了高中的之后,我上课就偷偷看小说,神印王座、斗罗大陆很多小说都是当时看的。中午和晚上回家之后,就在家里玩几把 DNF,当时家里也买了电脑。没记错的话,到我卸载 DNF 的时候已经练了 4 个满级的号。大量时间投入在游戏和小说上,我成功把自己从学校最好的小班玩到奥赛班,然后再到平行班。有点魔幻吧!
+初三上学期的时候突然觉悟,像是开窍了一样,当时就突然意识到自己马上就要升高中了,要开始好好搞搞学习了。
-高中觉悟是在高二下学期的时候,当时是真的觉悟了,就突然觉得游戏不香了,觉得 DNF 也不好玩了。我妈妈当时还很诧异,还奇怪地问我:“怎么不玩游戏了?”(*我妈属于不怎么管我玩游戏的,她觉得这东西还是要靠自觉*)。
+诶,其实也不算是开窍,主要还是为了让自己能在家附近上学,这样上网容易一些。因为当时我家就在我们当地的二中附近,附近有特别特别多的网吧,上网特别特别容易,加上我又能走读。
-*当时,自己就感觉这游戏没啥意思了。内心的真实写照是:“我练了再多的满级的DNF账号有啥用啊?以后有钱了,直接氪金不久能很牛逼嘛!” 就突然觉悟了!*
+像我初中在的那个学校,年级前 80 的话基本才有可能考得上二中。经过努力,初三上学期的第一次月考,我直接从 280 多名进步到了年级 50 多名,有机会考入二中。当时还因为进步太大,被当作 **进步之星** 在讲台上给整个年级做演讲,分享经验。这也是我第一次在这么多人面前讲话,挺紧张的,但是挺爽的,在暗恋对象面前赚足了面子。
-然后,我就开始牟足劲学习。当时,理科平行班大概有 7 个,每次考试都是平行班之间会单独拍一个名次。 后面的话,自己基本每次都能在平行班得第一,并且很多时候都是领先第二名个 30 来分。因为成绩还算亮眼,高三上学期快结束的时候,我就向年级主任申请去了奥赛班。
+其实在初三的时候,我的网瘾还是很大。不过,我去玩游戏的前提都是自己把所有任务做完,并且上课听讲也相对比较认真的听。
-## 04 高考前的失眠
+初三那会,我通宵的次数变少了一些,但会经常晚上趁着家人睡觉了,偷偷跑出去玩到凌晨 2 点多回来。
-> **失败之后,不要抱怨外界因素,自始至终实际都是自己的问题,自己不够强大!** 然后,高考前的失眠也是我自己问题,要怪只能怪自己,别的没有任何接口。
+当时,我们当地的高中有一个政策是每个学校的成绩比较优秀的学生可以参加 **高中提前招生考试** ,只要考上了就不用参加中考了。我当时也有幸参加了这次考试并成功进入了我们当地的二中。
-我的高考经历其实还蛮坎坷的,毫不夸张的说,高考那今天可能是我到现在为止,经历的最难熬的时候,特别是在晚上。
+在我参加高中提前考试前的一个晚上,我半夜 12 点趁着妈妈睡着,跑去了网吧玩 CF 到凌晨 3 点多回来。就那一次我被抓了现行,到家之后发现妈妈就坐在客厅等我,训斥一顿后,我就保证以后不再晚上偷偷跑出去了。
-我在高考那几天晚上都经历了失眠,想睡都睡不着那种痛苦想必很多人或许都体验过。
+> 这里要说明一点:我的智商我自己有自知之明的,属于比较普通的水平吧!前进很大的主要原因是自己基础还行,特别是英语和物理。英语是因为自己喜欢,加上小学就学了很多初中的英语课程。物理的话就很奇怪,虽然初二也不怎么听物理课,也不会物理,但是到了初三之后自己就突然开窍了。真的!我现在都感觉很奇怪。然后,到了高中之后,我的英语和物理依然是我最好的两门课。大学的兼职,我出去做家教都是教的高中物理。
-其实我在之前是从来没有过失眠的经历的。高考前夕,因为害怕自己睡不着,所以,我提前让妈妈去买了几瓶老师推荐的安神补脑液。我到现在还记得这个安神补脑液是敖东牌的。
+## 高中从小班掉到平行班
+
+由于参加了高中提前招生考试,我提前 4 个月就来到了高中,进入了小班,开始学习高中的课程。
+
+上了高中的之后,我上课就偷偷看小说,神印王座、斗罗大陆、斗破苍穹很多小说都是当时看的。中午和晚上回家之后,就在家里玩几把 DNF。当时家里也买了电脑,姥爷给买的,是对自己顺利进入二中的奖励。到我卸载 DNF 的时候,已经练了 4 个满级的号,两个接近满级的号。
+
+当时我的空间专门有一个相册里面放的全是 DNF 的一些照片和截图,无比痴迷于练级和刷图。
-高考那几天的失眠,我觉得可能和我喝了老师推荐的安神补脑液有关系,又或者是我自己太过于紧张了。因为那几天睡觉总会感觉有很多蚂蚁在身上爬一样,然后还起了一些小痘痘。
+在高中待了不到一个月,我上体育课的时候不小心把腿摔断了,这也是我第一次感受到骨头断裂的头疼,实在是太难受了!
-然后,这里要格外说明一点,避免引起误导: **睡不着本身就是自身的问题,上述言论并没有责怪这个补脑液的意思。** 另外, 这款安神补脑液我去各个平台都查了一下,发现大家对他的评价都挺好,和我们老师当时推荐的理由差不多。如果大家需要改善睡眠的话,可以咨询相关医生之后尝试一下。
+于是,我就开始休学养病。直到高中正式开学一个月之后,我才去上学,也没有参加军训。
-## 05 还算充实的大学生活
+由于我耽误了几个月的课程,因此没办法再进入小班,只能转到奥赛班。到了奥赛班之后,我继续把时间和经历都投入在游戏和小说上,于是我的成绩在奥赛班快接近倒数了。等到高二分班的时候,我成功被踢出奥赛班来到了最普通的平行班。
-高考成绩出来之后,比一本线高了 20 多分。自己挺不满意的,因为比平时考差了太多。加上自己泪点很低,就哭了一上午之后。后面,自我安慰说以后到了大学好好努力也是一样的。然后,我的第一志愿学校就报了长江大学,第一志愿专业就报了计算机专业。
+**我成功把自己从学校最好的小班玩到奥赛班,然后再到平行班。有点魔幻吧!**
-后面,就开始了自己还算充实的大学生活。
+## 高二开始奋起直追
-大一的时候,满腔热血,对于高考结果的不满意,化作了我每天早起的动力。雷打不动,每天早上 6点左右就出去背英语单词。这也奠定了我后面的四六级都是一次过,并且六级的成绩还算不错。大一那年的暑假,我还去了孝感当了主管,几乎从无到有办了 5 个家教点。不过,其中两个家教点的话,是去年都已经办过的,没有其他几个那么费心。
+高中觉悟是在高二下学期的时候,当时是真的觉悟了,就突然觉得游戏不香了,觉得 DNF 也不好玩了,什么杀怪打装备不过是虚无,练了再多满级的 DNF 账号也屁用没有,没钱都是浮云。
+
+我妈妈当时还很诧异,还奇怪地问我:“怎么不玩游戏了?”(我妈属于不怎么管我玩游戏的,她觉得这东西还是要靠自觉)。
+
+于是,我便开始牟足劲学习,每天都沉迷学习无法自拔(豪不夸张),乐在其中。虽然晚自习上完回到家已经差不多 11 点了,但也并不感觉累,反而感觉很快乐,很充实。
+
+**我的付出也很快得到了回报,我顺利返回了奥赛班。** 当时,理科平行班大概有 7 个,每次考试都是平行班之间会单独排一个名次,小班和奥赛班不和我们一起排名次。后面的话,自己基本每次都能在平行班得第一,并且很多时候都是领先第二名 30 来分。由于成绩还算亮眼,高三上学期快结束的时候,我就向年级主任申请去了奥赛班。
+
+## 高考前的失眠
+
+> **失败之后,不要抱怨外界因素,自始至终实际都是自己的问题,自己不够强大!** 然后,高考前的失眠也是我自己问题,要怪只能怪自己,别的没有任何接口。
+
+我的高考经历其实还蛮坎坷的,毫不夸张的说,高考那今天可能是我到现在为止,经历的最难熬的时候,特别是在晚上。
+
+我在高考那几天晚上都经历了失眠,想睡都睡不着那种痛苦想必很多人或许都体验过。
+
+其实我在之前是从来没有过失眠的经历的。高考前夕,因为害怕自己睡不着,所以,我提前让妈妈去买了几瓶老师推荐的安神补脑液。我到现在还记得这个安神补脑液是敖东牌的。
-
+
-大二的时候,加了学校一个偏技术方向的传媒组织(做网站、APP 之类的工作),后面成功当了副站长。在大二的时候,我才开始因为组织需要而接触 Java,不过当时主要学的是安卓开发。
+高考那几天的失眠,我觉得可能和我喝了老师推荐的安神补脑液有关系,又或者是我自己太过于紧张了。因为那几天睡觉总会感觉有很多蚂蚁在身上爬一样,身上还起了一些小痘痘。
-
+然后,这里要格外说明一点,避免引起误导:**睡不着本身就是自身的问题,上述言论并没有责怪这个补脑液的意思。** 另外, 这款安神补脑液我去各个平台都查了一下,发现大家对他的评价都挺好,和我们老师当时推荐的理由差不多。如果大家需要改善睡眠的话,可以咨询相关医生之后尝试一下。
-大三的时候,正式确定自己要用 Java 语言找工作,并且要走 Java 后台(当时感觉安卓后台在求职时长太不吃香了)。我每天都在寝室学习 Java 后台开发,自己看视频,看书,做项目。我的开源项目 JavaGuide 和公众号都是这一年创建的。这一年,我大部分时间都是在寝室学习。带上耳机之后,即使室友在玩游戏或者追剧,都不会对我有什么影响。
+## 大学生活
-我记得当时自己独立做项目的时候,遇到了很多问题。**就很多时候,你看书很容易就明白的东西,等到你实践的时候,总是会遇到一些小问题。我一般都是通过 Google 搜索解决的,用好搜索引擎真的能解决自己 99% 的问题。**
+大学生活过的还是挺丰富的,我会偶尔通宵敲代码,也会偶尔半夜发疯跑出去和同学一起走走古城墙、去网吧锤一夜的 LOL。
-
+大学生活专门写过一篇文章介绍:[害,毕业三年了!](./my-college-life.md) 。
-大四的时候,开始找工作。我是参加的秋招,开始的较晚,基本很多公司都没有 HC 了。这点需要 diss 一下学校了,你其他地方都很好,但是,大四的时候就不要再上课点名了吧!然后,**希望国内的学校尽量能多给学生点机会吧!很多人连春招和秋招都不清楚,毕业了连实习都没实习过。**
+## 总结
-## 06 一些心里话
+整个初中我都属于有点网瘾少年的状态,不过初三的时候稍微克制一些。到了高二下学期的时候,自己才对游戏真的没有那么沉迷了。
-关于大学要努力学习专业知识、多去读书馆这类的鸡汤,Guide 就不多说了。就谈几条自己写这篇文章的时候,想到了一些心理话吧!
+对游戏不那么沉迷,也是因为自己意识到游戏终究只是消遣,学习才是当时最重要的事情。而且,我的游戏技术又不厉害,又不能靠游戏吃饭,什么打怪升级到最后不过是电脑中的二进制数据罢了!
-1. **不要抱怨学校** :高考之后,不论你是 985、211 还是普通一本,再或者是 二本、三本,都不重要了,好好享受高考之后的生活。如果你觉得自己考的不满意的话,就去复读,没必要天天抱怨,复读的一年在你的人生长河里根本算不了什么的!
-2. **克制** :大学的时候,克制住自己,诱惑太多了。你不去上课,在寝室睡到中午,都没人管你。你的努力不要只是感动自己!追求形式的努力不过是你打得幌子而已。到了社会之后,这个说法依然适用! 说一个真实的发生在我身边的事情吧!高中的时候有一个特别特别特别努力的同班同学,家里的条件也很差,大学之前没有接触过手机和游戏。后来到了大学之后,因为接触了手机还有手机游戏,每天沉迷,不去上课。最后,直接就导致大学没读完就离开了。我听完我的好朋友给我说了之后,非常非常非常诧异!真的太可惜了!
-3. **不要总抱怨自己迷茫,多和优秀的学长学姐沟通交流。**
-4. **不知道做什么的时候,就把手头的事情做好比如你的专业课学习。**
+**这玩意必须你自己意识到,不然,单纯靠父母监督真的很难改变!如果心不在学习上面的话,那同时是不可能学好的!**
-*不论以前的自己是什么样,自己未来变成什么样自己是可以决定的,未来的路也终究还是要自己走。大环境下,大部分人都挺难的,当 996 成为了常态,Life Balance 是不可能的了。我们只能试着寻求一种平衡,试着去热爱自己现在所做的事情。*
+我真的很反对父母过于干涉孩子的生活,强烈谴责很多父母把自己孩子的网瘾归咎于网络游戏,把自己孩子的暴力归咎于影视媒体。
-**往后余生,爱家人,亦爱自己;好好生活,不忧不恼。**
+**时刻把自己的孩子保护起来不是一件靠谱的事情,他终究要独自面对越来越多的诱惑。到了大学,很多被父母保护太好的孩子就直接废了。他们没有独立意识,没有抗拒诱惑的定力!**
diff --git a/docs/about-the-author/javaguide-100k-star.md b/docs/about-the-author/javaguide-100k-star.md
new file mode 100644
index 0000000000000000000000000000000000000000..e89060dbe272dc0f9090fe49cdb64824345a365b
--- /dev/null
+++ b/docs/about-the-author/javaguide-100k-star.md
@@ -0,0 +1,42 @@
+---
+title: JavaGuide 开源项目 100K Star 了!
+category: 走近作者
+tag:
+ - 个人经历
+---
+
+2021-03-21,晚上 12 点,肝完了我正在做的一个项目的前端的某块功能,我随手打开了[我的 GitHub 主页](https://github.com/Snailclimb)。
+
+好家伙!几天没注意,[JavaGuide](https://github.com/Snailclimb/JavaGuide) 这个项目直接上了 100K star。
+
+
+
+其实,这个真没啥好嘚瑟的。因为,教程类的含金量其实是比较低的,Star 数量比较多主要也是因为受众面比较广,大家觉得不错,点个 star 就相当于收藏了。很多特别优秀的框架,star 数量可能只有几 K。所以,单纯看 star 数量没啥意思,就当看个笑话吧!
+
+
+
+维护这个项目的过程中,也被某些人 diss 过:“md 项目,没啥含金量,给国人丢脸!”。
+
+对于说这类话的人,我觉得对我没啥影响,就持续完善,把 JavaGuide 做的更好吧!其实,国外的很多项目也是纯 MD 啊!就比如外国的朋友发起的 awesome 系列、求职面试系列。无需多说,行动自证!凎!
+
+开源非常重要的一点就是协作。如果你开源了一个项目之后,就不再维护,别人给你提交 issue/pr,你都不处理,那开源也没啥意义了!
+
+我的公号的小伙伴都是通过这个项目关注我的,趁着午休,简单复盘一下,也算是对关注这个项目的小伙伴负责。
+
+我在大三开始准备秋招面试的时候,创建了 JavaGuide 这个项目,**2018-05-07** 这一天我提交了**第 1 个 commit**。
+
+到今天(2021-03-23)为止,这个仓库已经累计有 **2933** 次 commit,累计有 **207** 位朋友参与到了项目中来。
+
+
+
+累计有 **511** 个 **issue** 和 **575** 个 **PR**。所有的 PR 都已经被处理,仅有 15 个左右的 issue 我还未抽出时间处理。
+
+
+
+其实,相比于 star 数量,你看看仓库的 issue 和 PR 更能说明你的项目是否有价值。
+
+那些到处骗 star 甚至是 刷 star 的行为,我就不多说了,有点丢人。人家觉得你的项目还不错,能提供价值,自然就给你点 star 了。
+
+**未来几年,我还是会持续完善 JavaGuide。**
+
+**希望自己以后能开源一些有价值的轮子吧!继续加油!**
diff --git a/docs/about-the-author/my-article-was-stolen-and-made-into-video-and-it-became-popular.md b/docs/about-the-author/my-article-was-stolen-and-made-into-video-and-it-became-popular.md
new file mode 100644
index 0000000000000000000000000000000000000000..2fa306d2fe90ed4e20344e6c8f16737d0044482b
--- /dev/null
+++ b/docs/about-the-author/my-article-was-stolen-and-made-into-video-and-it-became-popular.md
@@ -0,0 +1,72 @@
+---
+title: 某培训机构盗我文章做成视频还上了B站热门
+category: 走近作者
+tag:
+ - 杂谈
+---
+
+时间回到 2021-02-25,我在刷哔哩哔哩的时候发现,哔哩哔哩某 UP 主(某培训机构),擅自将我在知乎的一个回答做成了视频。
+
+原滋原味啊!我艹。甚至,连我开头的自我调侃还加上了!真的牛皮!
+
+你盗我原创,视频你用心做好点也行啊!至少也可以让这么优质的内容得到传播嘛!
+
+结果,好家伙,视频做的像坨屎一样,配音也贼违和!
+
+麻烦这个培训机构看到这篇文章之后可以考虑换一个人做类似恶心的事情哈!这人完全没脑子啊!
+
+
+
+
+
+
+
+
+
+我随便找了一个视频看,发现也还是盗用别人的原创。
+
+
+
+
+
+其他的视频就不用多看了,是否还是剽窃别人的原创,原封不动地做成视频,大家心里应该有数。
+
+他们这样做的目的就是一个:**引流到自己的 QQ 群,然后忽悠你买课程。**
+
+我并不认为是这完全都是培训机构的问题。培训机构的员工为了流量而做这种恶心的事情,也导致了现在这种事情被越来越频繁地发生。
+
+所以,你会发现,哔哩哔哩和知乎上有越来越多培训机构的小号,到处剽窃原创,盗发。
+
+我身边很多原创号主的文章都经常被某些培训机构盗发。
+
+有时候真的会比较生气,毕竟你自己辛辛苦苦的原创,别人复制粘贴一下就白嫖了!
+
+但是,我相信,这种靠剽窃别人原创来吸引流量的行为,终究只是跳梁小丑的行为罢了!
+
+只有那些用心输出内容的创作者,才能走的更远,更安稳!
+
+后来,我在我的公众号上发了一篇名为[《好家伙!某培训机构盗我文章做成视频还上了热门》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247500005&idx=1&sn=7351e22619654492d3cf567bff9d87f0&chksm=cea18f2ef9d606384e0265b9318e004646c03b8a69f2801698d2f9e0e6bdfec0a1185ac3ab17&token=2146952532&lang=zh_CN&scene=21#wechat_redirect) 的文章,吐槽自己的原创被某机构白嫖。
+
+谁能想到,培训机构的人竟然找人来让我删文章了!讲真,这俩人是真的奇葩啊!
+
+
+
+
+
+还让我格局大点?我去你丫的!明明就是我的原创,你自己不删,反而找人联系我删除!有脑子不?
+
+其实,我这人是比较好说话的,现实生活中脾气也是出了名的好(前提是没有触犯到我的原则的情况)。
+
+搞笑的是!他们在让我删文的同时,他们 B 站盗发的视频还都在,还在继续为他们引流。
+
+
+
+
+
+如果他们把账号注销了,我或许还能考虑放一手。但是,文章是肯定不会删的。
+
+现在,看后续情况吧!我随时可以动用法律来维护自己的权益,只是看我想不想,毕竟也挺麻烦对吧!
+
+大家不用担心,这都是小事,我女朋友就是学法律的,国内的某法学双一流学校。
+
+咱不怕事!凎!!!
diff --git a/docs/about-the-author/my-college-life.md b/docs/about-the-author/my-college-life.md
new file mode 100644
index 0000000000000000000000000000000000000000..a24e899eea4629d11edc9491abb23da3e7fa71e5
--- /dev/null
+++ b/docs/about-the-author/my-college-life.md
@@ -0,0 +1,387 @@
+---
+title: 害,毕业三年了!
+category: 走近作者
+star: 1
+tag:
+ - 个人经历
+---
+
+> 关于初高中的生活,可以看 2020 年我写的 [我曾经也是网瘾少年](./internet-addiction-teenager.md) 这篇文章。
+
+2019 年 6 月份毕业,距今已经过去了 3 年。趁着高考以及应届生毕业之际,简单聊聊自己的大学生活。
+
+下面是正文。
+
+我本科毕业于荆州校区的长江大学,一所不起眼的双非一本。
+
+在这里度过的四年大学生活还是过的挺开心的,直到现在,我依然非常怀念!
+
+在学校的这几年的生活,总体来说,还算是比较丰富多彩的。我会偶尔通宵敲代码,也会偶尔半夜发疯跑出去和同学一起走走古城墙、去网吧锤一夜的 LOL。
+
+写下这篇杂文,记录自己逝去的大学生活!希望未来继续砥砺前行,不忘初心!
+
+## 大一
+
+大一那会,我没有把精力放在学习编程上,大部分时间都在参加课外活动。
+
+或许是因为来到了一座新鲜的城市,对周围的一切都充满了兴趣。又或许是因为当时的我还比较懵懂,也没有任何学习方向。
+
+这一年,我和班里的一群新同学去逛了荆州的很多地方比如荆州博物馆、长江大桥、张居正故居、关帝庙。
+
+
+
+即使如此,我当时还是对未来充满了希望,憧憬着工作之后的生活。
+
+我还记得当时我们 6 个室友那会一起聊天的时候,其他 5 个室友都觉得说未来找工作能找一个 6k 的就很不错了。我当时就说:“怎么得至少也要 8k 吧!”。他们无言,觉得我的想法太天真。
+
+其实,我当时内心想的是至少是月薪 1w 起步,只是不太好意思直接说出来。
+
+我不爱出风头,性格有点内向。刚上大学那会,内心还是有一点不自信,干什么事情都畏畏缩缩,还是迫切希望改变自己的!
+
+于是,凭借着一腔热血,我尝试了很多我之前从未尝试过的事情:**露营**、**户外烧烤**、**公交车演讲**、**环跑古城墙**、**徒步旅行**、**异地求生**、**圣诞节卖苹果**、**元旦晚会演出**...。
+
+下面这些都是我和社团的小伙伴利用课外时间自己做的,在圣诞节那周基本都卖完了。我记得,为了能够多卖一些,我们还挨个去每一个寝室推销了一遍。
+
+
+
+我还参加了大一元旦晚会,不过,那次演出我还是没放开,说实话,感觉没有表现出应该有的那味。
+
+
+
+经过这次演出之后,我发现我是真的没有表演的天赋,很僵硬。并且,这种僵硬呆板是自己付出努力之后也没办法改变的。
+
+下图是某一次社团聚餐,我喝的有点小醉之后,被朋友拍下的。
+
+
+
+那时候,还经常和和社团的几位小伙伴一起去夜走荆州古城墙。
+
+
+
+不知道社团的大家现在过得怎么样呢?
+
+虽然这些经历对于我未来的工作和发展其实没有任何帮助,但却让我的大学生活更加完整,经历了更多有趣的事情,有了更多可以回忆的经历。
+
+我的室友们都窝在寝室玩游戏、玩手机的时候,我很庆幸自己做了这些事情。
+
+个人感觉,大一的时候参加一些不错的社团活动,认识一些志同道合的朋友还是很不错的!
+
+**参加课外活动之余,CS 专业的小伙伴,尽量早一点养成一个好的编程习惯,学好一门编程语言,然后平时没事就刷刷算法题。**
+
+### 办补习班
+
+大一暑假的时候,我作为负责人,在孝感的小乡镇上办过 5 个补习班(本来是 7 个,后来砍掉了 2 个) 。
+
+从租房子、租借桌椅再到招生基本都是从零开始做的。
+
+每个周末我都会从荆州坐车跑到孝感,在各个县城之间来回跑。绝大部分时候,只有我一个人,偶尔也会有几个社团的小伙伴陪我一起。
+
+
+
+记忆犹新,那一年孝感也是闹洪水,还挺严重的。
+
+
+
+有一次我差点回不去学校参加期末考试。虽然没有备考,但是也没有挂过任何一门课,甚至很多科目考的还不错。不过,这还是对我绩点产生了比较大的影响,导致我后面没有机会拿到奖学金。
+
+
+
+这次比较赶时间,所以就坐的是火车回学校。在火车上竟然还和别人撞箱子了!
+
+
+
+当时去小乡镇上的时候,自己最差的时候住过 15 元的旅馆。真的是 15 元,你没看错。就那种老旧民房的小破屋,没有独卫,床上用品也很不卫生,还不能洗澡。
+
+下面这个还是我住过最豪华的一个,因为当时坐客车去了孝感之后,突然下大雨,我就在车站附近找了一个相对便宜点的。
+
+
+
+为了以更低的价钱租到房子,我经常和房东砍价砍的面红耳赤。
+
+说句心里话,这些都是我不太愿意去做的事情,我本身属于比较爱面子而且不那么自信的人。
+
+当时,我需要在各个乡镇来回跑,每天就直接顶着太阳晒 。每次吃饭都特别香,随便炒个蔬菜都能吃几碗米饭。
+
+我本身是比较挑食的,这次经历让我真正体会到人饿了之后吃嘛嘛香!
+
+我一个人给 6 个老师加上 10 来个学生和房东们一家做了一个多月的饭,我的厨艺也因此得到了很大的锻炼。
+
+
+
+这些学生有小学的,也有初中的,都比较听话。有很多还是留守儿童,爸爸妈妈在外打工,跟着爷爷奶奶一起生活。
+
+加上我的话,我们一共有 4 位老师,我主要讲的是初中和高中的物理课。
+
+学生们都挺听话,没有出现和我们几个老师闹过矛盾。只有两个调皮的小学生被我训斥之后,怀恨在心,写下了一些让我忍俊不禁的话!哈哈哈哈!太可爱了!
+
+
+
+离开之前的前一天的晚上,我和老师们商量请一些近点的同学们来吃饭。我们一大早就出去买菜了,下图是做成后的成品。虽然是比较简单的一顿饭,但我们吃的特别香。
+
+
+
+那天晚上还有几个家长专门跑过来看我做饭,家长们说他们的孩子非常喜欢我做的饭,哈哈哈!我表面淡然说自己做的不好,实则内心暗暗自喜,就很“闷骚”的一个人,哈哈哈!
+
+不知道这些学生们,现在怎么样呢?怀念啊!
+
+培训班结束,我回家之后,我爸妈都以为我是逃荒回来的。
+
+### 自己赚钱去孤儿院
+
+大一尾声的时候,还做了一件非常有意义的事情。我和我的朋友们去了一次孤儿院(荆州私立孤儿教养院)。这个孤儿院曾经还被多家电视台报道过,目前也被百度百科收录。
+
+
+
+孤儿院的孩子们,大多是一些无父无母或者本身有一些疾病被父母遗弃的孩子。
+
+去之前,我们买了很多小孩子的玩具、文具、零食这些东西。这些钱的来源也比较有意义,都是我和社团的一些小伙伴自己去外面兼职赚的一些钱。
+
+
+
+勿以善小而不为!引用《爱的风险》这首歌的一句歌词:“只要人人都献出一点爱,世界将变成美好的人间” 。
+
+我想看看这个孤儿院的现状,于是在网上有搜了一下,看到了去年 1 月份荆州新闻网的一份报道。
+
+
+
+孤儿教养院创办 33 年来,累计收养孤儿 85 人,其中有 5 人参军入伍报效祖国,20 人上大学,有的早已参加工作并成家立业。
+
+叔叔也慢慢老了,白发越来越多。有点心酸,想哭,希望有机会再回去看看您!一定会的!
+
+
+
+### 徒步旅行
+
+大一那会还有一件让我印象非常深刻的事情——徒步旅行。
+
+我和一群社团的小伙伴,徒步走了接近 45 公里。我们从学校的西校区,徒步走到了枝江那边的一个沙滩。
+
+
+
+是真的全程步行,这还是我第一次走这么远。
+
+走到目的地的时候,我的双腿已经不听使唤,脚底被磨了很多水泡。
+
+我们在沙滩上露营,烧烤,唱歌跳舞,一直到第二天早上才踏上回学校的路程。
+
+
+
+## 大二
+
+到了大二,我开始把自己的重点转移到编程知识的学习上。
+
+不过,我遇到一个让我比较纠结的问题:社团里玩的最好的几个朋友为了能让社团能继续延续下去,希望我和他们一起来继续带这个团队。
+
+但是,我当时已经规划好了自己大二要做的事情,真的想把精力都放在编程学习上,想要好好沉淀一下自己的技术。
+
+迫于无奈,我最终还是妥协,选择了和朋友一起带社团。毕竟,遇到几个真心的朋友属实不易!
+
+### 带社团
+
+带社团确实需要花费很多业余时间,除了每周要从东校区打车到西校区带着他们跑步之外,我们还需要经常带着他们组织一些活动。
+
+比如我们一起去了长江边上烧烤露营。
+
+
+
+再比如我们一起去环跑了古城墙。
+
+
+
+大学那会,我还是非常热爱运动的!
+
+
+
+大二那会,我就已经环跑了 3 次古城墙。
+
+
+
+### 加入长大在线
+
+在大二的时候,我还加入了学校党委宣传部下的组织——长大在线。这是一个比较偏技术性质的组织,主要负责帮学校做做网站、APP 啥的。
+
+在百度上,还能搜索到长大在线的词条。
+
+
+
+莫名其妙还被发了一个记者证,哈哈哈!
+
+
+
+我选的是安卓组,然后我就开始了学习安卓开发的旅程。
+
+刚加入这个组织的时候,我连 HTML、CSS、JS、Java、Linux 这些名词都不知道啥意思。
+
+再到后面,我留下来当了副站长,继续为组织服务了大半年多。
+
+
+
+### 第一次参加比赛
+
+那会也比较喜欢去参加一些学校的比赛,也获得过一些不错的名次,让我印象最深的是一次 PPT 大赛,这也是我第一次参加学校的比赛。
+
+参加比赛之前,自己也是一个 PPT 小白,苦心学了一周多之后,我的一个作品竟然顺利获得了第一名。
+
+
+
+也正是因为这次比赛,我免费拥有了自己的第一个机械键盘,这个键盘陪我度过了后面的大学生活。
+
+### 确定技术方向
+
+在大二上学期末,我最终确定了自己以后要走的技术方向是走 Java 后端。于是,我就开始制定学习计划,开始了自己的 Java 后端领域的打怪升级之路。
+
+每次忙到很晚,一个人走在校园的时候还是很爽的!非常喜欢这种安静的感觉。
+
+
+
+当时身体素质真好,熬夜之后第二天照常起来上课学习。现在熬个夜,后面两天直接就废了!
+
+到了大三,我基本把 Java 后端领域一些必备的技术都给过了一遍,还用自己学的东西做了两个实战项目。
+
+由于缺少正确的人指导,我当时学的时候也走了很多弯路,浪费了不少时间(我很羡慕大家能有我,就很厚脸皮!)。
+
+那个时候还贼自恋,没事就喜欢自拍一张。
+
+
+
+国庆节的时候也不回家,继续在学校刷 Java 视频和书籍。
+
+我记得那次国庆节的时候效率还是非常高的,学习起来也特别有动力。
+
+
+
+## 大三
+
+整个大三,我依然没有周末,基本没有什么娱乐时间。绝大部分时间都是一个人在寝室默默学习,平时偶尔也会去图书馆和办公室。
+
+虽然室友经常会玩游戏和看剧什么的,但是我对我并没有什么影响。一个人戴上耳机之后,世界仿佛都是自己的。
+
+和很多大佬可能不太一样,比起图书馆和办公室,我在寝室的学习效率更高一些。
+
+### JavaGuide 诞生
+
+我的开源项目 JavaGuide 和公众号都是这一年启动的。
+
+
+
+目前的话,JavaGuide 也已经 100k star ,我的公众号也已经有 15w+ 的关注。
+
+
+
+### 接私活赚钱
+
+一些机遇也让我这一年也接了一些私活赚钱。为了能够顺利交付,偶尔也会熬夜。当时的心态是即使熬夜也还是很开心、充实。每次想到自己通过技术赚到了钱,就会非常有动力。
+
+我也曾写过文章分享过接私活的经历:[唠唠嗑!大学那会接私活赚了 3w+](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247499539&idx=1&sn=ff153f9bd98bb3109b1f14e58ed9a785&chksm=cea1b0d8f9d639cee4744f845042df6b1fc319f4383b87eba76a944c2648c81a51c28d25e3b6&token=2114015135&lang=zh_CN#rd) 。
+
+不过,我接的几个私活也是比较杂的,并不太适合作为简历上的项目经历。
+
+于是,为了能让简历上的项目经历看着更好看一些,我自己也找了两个项目做。一个是我跟着视频一起做的,是一个商城类型的项目。另外一个是自己根据自己的想法做的,是一个视频网站类型的项目。
+
+商城类型的项目大概的架构图如下(没有找到当时自己画的原图):
+
+
+
+那会商城项目貌似也已经烂大街了,用的人比较多。为了让自己的商城项目更有竞争力,对照着视频教程做完之后,我加入了很多自己的元素比如更换消息队列 ActiveMQ 为 Kafka、增加二级缓存。
+
+在暑假的时候,还和同学老师一起做了一个员工绩效管理的企业真实项目。这个项目和我刚进公司做的项目,非常非常相似,不过公司做得可能更高级点 ,代码质量也要更高一些。实在是太巧了!
+
+我记得当时自己独立做项目的时候,遇到了很多问题。**就很多时候,你看书很容易就明白的东西,等到你实践的时候,总是会遇到一些小问题。我一般都是通过 Google 搜索解决的,用好搜索引擎真的能解决自己 99% 的问题。**
+
+### 参加软件设计大赛
+
+大三这一年也有遗憾吧!我和几位志同道合的朋友一起参加过一个软件设计大赛,我们花了接近两个月做的系统顺利进入了复赛。
+
+不过,我后面因为自己个人觉得再花时间做这个系统学不到什么东西还浪费时间就直接退出了。然后,整个团队就散了。
+
+其实,先来回头看也是可以学到东西的,自己当时的心态有点飘了吧,心态有一些好高骛远。
+
+现在想来,还是挺对不起那些一起奋斗到深夜的小伙伴。
+
+人生就是这样,一生很长,任何时候你回头看过去的自己,肯定都会有让自己后悔的事情。
+
+### 放弃读研
+
+当时,我也有纠结过是否读研,毕竟学校确实一般,读个研确实能够镀点金,提升一下学历。
+
+不过,我最终还是放弃了读研。当时比较自信,心里就觉得自己不需要读研也能够找到好工作。
+
+### 实习
+
+大三还找了一家离学校不远的公司实习,一位老学长创办的。不过,说实话哈,总体实习体验很差,没有学到什么东西不说,还耽误了自己很多已经计划好的事情。
+
+我记得当时这个公司很多项目还是在用 JSP,用的技术很老。如果是老项目还好,我看几个月前启动的项目也还是用的 JSP,就很离谱。。。
+
+当时真的很难受,而且一来就想着让你上手干活,活还贼多,干不完还想让你免费加班。。。
+
+当时也没办法,因为荆州实在是找不到其他公司可以让你实习,你又没办法跑到其他城市去实习。这也是放弃选择一二线城市的学校带来的问题吧!
+
+## 大四
+
+### 开始找工作
+
+找实习找工作时候,才知道大学所在的城市的重要性。
+
+由于,我的学校在荆州,而且本身学校就很一般,因此,基本没有什么比较好的企业来招人。
+
+当时,唯一一个还算可以的就是苏宁,不过,我遇到的那个苏宁的 HR 还挺恶心的,第一轮面试的时候就开始压薪资了,问我能不能加班。然后,我也就对苏宁没有了想法。
+
+秋招我犯了一个比较严重的问题,那就是投递简历开始的太晚。我是把学校的项目差不多做完之后,才开始在网上投递简历。这个时候,暑假差不多已经结束了,秋招基本已经尾声了。
+
+可能也和学校环境有一些关系,当时,身边的同学没有参加秋招的。大三暑假的时候,都跑去搞学院组织的实习。我是留在学校做项目,没有去参加那次实习。
+
+我觉得学校还是非常有必要提醒学生们把握住秋招这次不错的机会的!
+
+在网上投递了一些简历之后,很多笔试我觉得做的还可以的都没有回应。
+
+我有点慌了!于是,我就从荆州来到武汉,想在武大华科这些不错的学校参加一些宣讲会。
+
+到了武汉之后,我花了一天时间找了一个蛋壳公寓住下。第二天,我就跑去武汉理工大学参加宣讲会。
+
+
+
+当天,我就面试了自己求职过程中的第一家公司—**玄武科技**。
+
+就是这样一家中小型的公司,当时来求职面试的很多都是武大华科的学生。不过,他们之中一定有很多人和我一样,就是单纯来刷一波经验,找找信心。
+
+整个过程也就持续了 3 天左右,我就顺利的拿下了玄武科技的 offer。不过,最终没有签约。
+
+### 拿到 Offer
+
+来武汉之前,我实际上已经在网上投递了 **ThoughtWorks**,并且,作业也已经通过了。
+
+当时,我对 ThoughtWorks 是最有好感的,内心的想法就是:“拿下了 ThoughtWorks,就不再面试其他公司了”。
+
+奈何 ThoughtWorks 的进度太慢,担心之余,才来武汉面试其他公司留个保底。
+
+不过,我最终如愿以偿获得了 ThoughtWorks 的 offer。
+
+
+
+面试 ThoughtWorks 的过程就不多说了,我在[《结束了我短暂的秋招,说点自己的感受》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484842&idx=1&sn=4489dfab0ef2479122b71407855afc71&chksm=cea24a61f9d5c3774a8ed67c5fcc3234cb0741fbe831152986e5d1c8fb4f36a003f4fb2f247e&scene=178&cur_album_id=1323354342556057602#rd)这篇文章中有提到。
+
+## 几点建议
+
+说几点自己的建议,虽然我不优秀,但毕竟你可以更优秀:
+
+1. 确定好自己的方向,搞清你是要考研还是要找工作。如果你要考研的话,好好上每一门可能是考研的科目,平时有时间也要敲代码,最好也能做一个项目,对你复试还有能力提升都有帮助。找工作的话,尽早确定好自己的方向,心里有一个规划,搞清自己的优势和劣势。
+2. 尽可能早一点以求职为导向来学习,这样更有针对性,并且可以大概率减己处在迷茫的时间,很大程度上还可以让自己少走很多弯路。
+3. 自学很重要,养成自学的习惯,学会学习。
+4. 不要觉得逃课就是坏学生。我大学逃了很多课,逃课的大部分时间都是在学自己觉得更重要的东西,逃的大部分也是不那么重要并且不会影响我毕业的课。
+5. 大学恋爱还是相对来说很纯粹的,遇到合适的可以尝试去了解一下, 别人不喜欢你的话不要死缠烂打,这种东西强求不来。你不得不承认,你了解一个人欲望还是始于他的长相而并不是有趣的灵魂。
+6. 管理自己的身材,没事去跑跑步,别当油腻男。
+7. 别太看重绩点。我觉得绩点对于找工作还有考研实际的作用都可以忽略不计,不过不挂科还是比较重要的。但是,绩点确实在奖学金评选和保研名额选取上占有最大的分量。
+8. 别太功利性。做事情以及学习知识都不要奢求它能立马带给你什么,坚持和功利往往是成反比的。
+9. ......
+
+## 后记
+
+我们在找工作的过程中难免会遇到卡学历的情况,特别是我们这种学校本身就比较一般的。我觉得这真的不可厚非,没有什么不公平,要怪就只能怪自己没有考上好的学校。
+
+**考虑到招聘成本和时间,公司一定更愿意在学校本身比较好的人中选拔人才。**
+
+我也曾抱怨过自己为什么不在 211 或者 985 的学校。但,其实静下心来想一想,本来考不上 211 或者 985 就是自己的问题,而且在我们计算机这个领域,学历本身就相对于其他专业稍微要更加公平一点。
+
+我身边专科、三本毕业就进大厂的人也比比皆是。我这句话真不是鸡汤,为了鼓励一些学校出身不太好的朋友。
+
+**多行动,少抱怨。**
diff --git a/docs/about-the-author/readme.md b/docs/about-the-author/readme.md
index cdf185586d734a23af03c891062cc97628c25b31..54e14e2e68d540cd73f7bd51423e482739f468d7 100644
--- a/docs/about-the-author/readme.md
+++ b/docs/about-the-author/readme.md
@@ -1,6 +1,11 @@
-# 个人介绍 Q&A
+---
+title: 个人介绍 Q&A
+category: 走近作者
+---
-大家好,我是 Gudie哥!这篇文章我会通过 Q&A 的形式简单介绍一下我自己。
+
+
+这篇文章我会通过 Q&A 的形式简单介绍一下我自己。
## 我是什么时候毕业的?
@@ -8,45 +13,54 @@
我的学校背景是比较差的,高考失利,勉强过了一本线 20 来分,去了荆州的一所很普通的双非一本。不过,还好我没有因为学校而放弃自己,反倒是比身边的同学都要更努力,整个大学还算过的比较充实。
-下面这张是当时拍的毕业照:
+下面这张是当时拍的毕业照(后排最中间的就是我):
-
+
-## 为什么要做 JavaGuide 这个项目?
+## 我坚持写了多久博客?
-我从大二坚持写作,坚持分享让我收获了 30w+ 的读者以及一笔不错的副业收入。
+时间真快啊!我自己是从大二开始写博客的。那时候就是随意地在博客平台上发发自己的学习笔记和自己写的程序。就比如 [谢希仁老师的《计算机网络》内容总结](../cs-basics/network/computer-network-xiexiren-summary.md) 这篇文章就是我在大二学习计算机网络这门课的时候对照着教材总结的。
-2018 年,我还在读大三的时候,JavaGuide 开源项目&公众号诞生了。很难想到,日后,他们会陪伴我度过这么长的时间。
+身边也有很多小伙伴经常问我:“我现在写博客还晚么?”
-开源 JavaGuide 初始想法源于自己的个人那一段比较迷茫的学习经历。主要目的是为了通过这个开源平台来帮助一些在学习 Java 以及面试过程中遇到问题的小伙伴。
+我觉得哈!如果你想做什么事情,尽量少问迟不迟,多问自己值不值得,只要你觉得有意义,就尽快开始做吧!人生很奇妙,我们每一步的重大决定,都会对自己未来的人生轨迹产生影响。是好还是坏,也只有我们自己知道了!
-- **对于 Java 初学者来说:** 本文档倾向于给你提供一个比较详细的学习路径,让你对于 Java 整体的知识体系有一个初步认识。另外,本文的一些文章也是你学习和复习 Java 知识不错的实践;
-- **对于非 Java 初学者来说:** 本文档更适合回顾知识,准备面试,搞清面试应该把重心放在那些问题上。要搞清楚这个道理:提前知道那些面试常见,不是为了背下来应付面试,而是为了让你可以更有针对的学习重点。
+对我自己来说,坚持写博客这一项决定对我人生轨迹产生的影响是非常正面的!所以,我也推荐大家养成坚持写博客的习惯。
-## 如何看待 JavaGuide 的 star 数量很多?
+## 我在大学期间赚了多少钱?
-[JavaGuide](https://github.com/Snailclimb) 目前已经是 Java 领域 star 数量最多的几个项目之一,登顶过很多次 Github Trending。
+在校期间,我还通过办培训班、接私活、技术培训、编程竞赛等方式变现 20w+,成功实现“经济独立”。我用自己赚的钱去了重庆、三亚、恩施、青岛等地旅游,还给家里补贴了很多,减轻了父母的负担。
-不过,这个真心没啥好嘚瑟的。因为,教程类的含金量其实是比较低的,Star 数量比较多主要也是因为受众面比较广,大家觉得不错,点个 star 就相当于收藏了。很多特别优秀的框架,star 数量可能只有几 K。所以,单纯看 star 数量没啥意思,就当看个笑话吧!
+下面这张是我大一下学期办补习班的时候拍的(离开前的最后一顿饭):
-维护这个项目的过程中,也被某些人 diss 过:“md 项目,没啥含金量,给国人丢脸!”。
+
-对于说这类话的人,我觉得对我没啥影响,就持续完善,把 JavaGuide 做的更好吧!其实,国外的很多项目也是纯 MD 啊!就比如外国的朋友发起的 awesome 系列、求职面试系列。无需多说,行动自证!凎!
+下面这张是我大三去三亚的时候拍的:
-开源非常重要的一点就是协作。如果你开源了一个项目之后,就不再维护,别人给你提交 issue/pr,你都不处理,那开源也没啥意义了!
+
-## 我在大学期间赚了多少钱?
+其实,我在大学就这么努力地开始赚钱,也主要是因为家庭条件太一般,父母赚钱都太辛苦了!也正是因为我自己迫切地想要减轻父母的负担,所以才会去尝试这么多赚钱的方法。
-在校期间,我还通过接私活、技术培训、编程竞赛等方式变现 20w+,成功实现“经济独立”。我用自己赚的钱去了重庆、三亚、恩施、青岛等地旅游,还给家里补贴了很多,减轻的父母的负担。
+我发现做咱们程序员这行的,很多人的家庭条件都挺一般的,选择这个行业的很大原因不是因为自己喜欢,而是为了多赚点钱。
-如果你也想通过接私活变现的话,可以在我的公众号后台回复“**接私活**”来了解详细情况。
+如果你也想通过接私活变现的话,可以在我的公众号后台回复“**接私活**”来了解一些我的个人经验分享。

-## 为什么自称 Guide哥?
+## 为什么自称 Guide?
+
+可能是因为我的项目名字叫做 JavaGuide , 所以导致有很多人称呼我为 **Guide 哥**。
+
+后面,为了读者更方便称呼,我就将自己的笔名改成了 **Guide**。
+
+我早期写文章用的笔名是 SnailClimb 。很多人不知道这个名字是啥意思,给大家拆解一下就清楚了。SnailClimb=Snail(蜗牛)+Climb(攀登)。我从小就非常喜欢听周杰伦的歌曲,特别是他的《蜗牛》🐌 这首歌曲,另外,当年我高考发挥的算是比较失常,上了大学之后还算是比较“奋青”,所以,我就给自己起的笔名叫做 SnailClimb ,寓意自己要不断向上攀登,嘿嘿 😁
+
+
+
+## 后记
-可能是因为我的项目名字叫做 JavaGudie ,所以导致有很多人称呼我为 **Guide哥**。
+凡心所向,素履所往,生如逆旅,一苇以航。
-后面,为了读者更方便称呼,我就将自己的笔名改成了 **Guide哥**。
+生活本就是有苦有甜。共勉!
-我早期写文章用的笔名是 SnailClimb 。很多人不知道这个名字是啥意思,给大家拆解一下就清楚了。SnailClimb=Snail(蜗牛)+Climb(攀登)。我从小就非常喜欢听周杰伦的歌曲,特别是他的《蜗牛》🐌 这首歌曲,另外,当年我高考发挥的算是比较失常,上了大学之后还算是比较“奋青”,所以,我就给自己起的笔名叫做 SnailClimb ,寓意自己要不断向上攀登,哈哈
+
diff --git a/docs/about-the-author/writing-technology-blog-six-years.md b/docs/about-the-author/writing-technology-blog-six-years.md
new file mode 100644
index 0000000000000000000000000000000000000000..9e18a67d8c4767eb0b671c6a7b89a1db1c093211
--- /dev/null
+++ b/docs/about-the-author/writing-technology-blog-six-years.md
@@ -0,0 +1,173 @@
+---
+title: 坚持写技术博客六年了!
+category: 走近作者
+tag:
+ - 杂谈
+---
+
+坚持写技术博客已经有六年了,也算是一个小小的里程碑了。
+
+一开始,我写技术博客就是简单地总结自己课堂上学习的课程比如网络、操作系统。渐渐地,我开始撰写一些更为系统化的知识点详解和面试常见问题总结。
+
+
+
+许多人都想写技术博客,但却不清楚这对他们有何好处。有些人开始写技术博客,却不知道如何坚持下去,也不知道该写些什么。这篇文章我会认真聊聊我对记录技术博客的一些看法和心得,或许可以帮助你解决这些问题。
+
+## 写技术博客有哪些好处?
+
+### 学习效果更好,加深知识点的认识
+
+**费曼学习法** 大家应该已经比较清楚了,这是一个经过实践证明非常有效的学习方式。费曼学习法的命名源自 Richard Feynman,这位物理学家曾获得过诺贝尔物理学奖,也曾参与过曼哈顿计划。
+
+所谓费曼学习法,就是当你学习了一个新知识之后,想象自己是一个老师:用最简单、最浅显直白的话复述、表达复杂深奥的知识,最好不要使用行业术语,让非行业内的人也能听懂。为了达到这种效果,最好想象你是在给一个 80 多岁或 8 岁的小孩子上课,甚至他们都能听懂。
+
+
+
+看书、看视频这类都属于是被动学习,学习效果比较差。费曼学习方法属于主动学习,学习效果非常好。
+
+**写技术博客实际就是教别人的一种方式。** 不过,记录技术博客的时候是可以有专业术语(除非你的文章群体是非技术人员),只是你需要用自己的话表述出来,尽量让别人一看就懂。**切忌照搬书籍或者直接复制粘贴其他人的总结!**
+
+如果我们被动的学习某个知识点,可能大部分时候都是仅仅满足自己能够会用的层面,你并不会深究其原理,甚至很多关键概念都没搞懂。
+
+如果你是要将你所学到的知识总结成一篇博客的话,一定会加深你对这个知识点的思考。很多时候,你为了将一个知识点讲清楚,你回去查阅很多资料,甚至需要查看很多源码,这些细小的积累在潜移默化中加深了你对这个知识点的认识。
+
+甚至,我还经常会遇到这种情况:**写博客的过程中,自己突然意识到自己对于某个知识点的理解存在错误。**
+
+**写博客本身就是一个对自己学习到的知识进行总结、回顾、思考的过程。记录博客也是对于自己学习历程的一种记录。随着时间的流逝、年龄的增长,这又何尝不是一笔宝贵的精神财富呢?**
+
+知识星球的一位球友还提到写技术博客有助于完善自己的知识体系:
+
+
+
+### 帮助别人的同时获得成就感
+
+就像我们程序员希望自己的产品能够得到大家的认可和喜欢一样。我们写技术博客在某一方面当然也是为了能够得到别人的认可。
+
+**当你写的东西对别人产生帮助的时候,你会产生成就感和幸福感。**
+
+
+
+这种成就感和幸福感会作为 **正向反馈** ,继续激励你写博客。
+
+但是,即使受到很多读者的赞赏,也要保持谦虚学习的太多。人外有人,比你技术更厉害的读者多了去,一定要虚心学习!
+
+当然,你可以可能会受到很多非议。可能会有很多人说你写的文章没有深度,还可能会有很多人说你闲的蛋疼,你写的东西网上/书上都有。
+
+**坦然对待这些非议,做好自己,走好自己的路就好!用行动自证!**
+
+### 可能会有额外的收入
+
+写博客可能还会为你带来经济收入。输出价值的同时,还能够有合理的经济收入,这是最好的状态!
+
+为什么说是可能呢? **因为就目前来看,大部分人还是很难短期通过写博客有收入。我也不建议大家一开始写博客就奔着赚钱的目的,这样功利性太强了,效果可能反而不好。就比如说你坚持了写了半年发现赚不到钱,那你可能就会坚持不下去了。**
+
+我自己从大二开始写博客,大三下学期开始将自己的文章发布到公众号上,一直到大四下学期,才通过写博客赚到属于自己的第一笔钱。
+
+第一笔钱是通过微信公众号接某培训机构的推广获得的。没记错的话,当时通过这个推广为自己带来了大约 **500** 元的收入。虽然这不是很多,但对于还在上大学的我来说,这笔钱非常宝贵。那时我才知道,原来写作真的可以赚钱,这也让我更有动力去分享自己的写作。可惜的是,在接了两次这家培训机构的广告之后,它就倒闭了。
+
+之后,很长一段时间我都没有接到过广告。直到网易的课程合作找上门,一篇文章 1000 元,每个月接近一篇,发了接近两年,这也算是我在大学期间比较稳定的一份收入来源了。
+
+
+
+老粉应该大部分都是通过 JavaGuide 这个项目认识我的,这是我在大三开始准备秋招面试时创建的一个项目。没想到这个项目竟然火了一把,一度霸占了 GitHub 榜单。可能当时国内这类开源文档教程类项目太少了,所以这个项目受欢迎程度非常高。
+
+
+
+项目火了之后,有一个国内比较大的云服务公司找到我,说是要赞助 JavaGuide 这个项目。我既惊又喜,担心别人是骗子,反复确认合同之后,最终确定以每月 1000 元的费用在我的项目首页加上对方公司的 banner。
+
+随着时间的推移,以及自己后来写了一些比较受欢迎、比较受众的文章,我的博客知名度也有所提升,通过写博客的收入也增加了不少。
+
+### 增加个人影响力
+
+写技术博客是一种展示自己技术水平和经验的方式,能够让更多的人了解你的专业领域知识和技能。持续分享优质的技术文章,一定能够在技术领域增加个人影响力,这一点是毋庸置疑的。
+
+有了个人影响力之后,不论是对你后面找工作,还是搞付费知识分享或者出书,都非常有帮助。
+
+拿我自己来说,已经很多知名出版社的编辑找过我,协商出一本的书的事情。这种机会应该也是很多人梦寐以求的。不过,我都一一拒绝了,因为觉得自己远远没有达到能够写书的水平。
+
+
+
+其实不出书最主要的原因还是自己嫌麻烦,整个流程的事情太多了。我自己又是比较佛系随性的人,平时也不想把时间都留给工作。
+
+## 怎样才能坚持写技术博客?
+
+**不可否认,人都是有懒性的,这是人的本性。我们需要一个目标/动力来 Push 一下自己。**
+
+就技术写作而言,你的目标可以以技术文章的数量为标准,比如:
+
+- 一年写多少篇技术文章。我个人觉得一年的范围还是太长了,不太容易定一个比较合适的目标。
+- 每月输出一篇高质量的技术文章。这个相对容易实现一些,每月一篇,一年也有十二篇了,也很不错了。
+
+不过,以技术文章的数量为目标有点功利化,文章的质量同样很重要。一篇高质量的技术文可能需要花费一周甚至半个月的业余时间才能写完。一定要避免自己刻意追求数量,而忽略质量,迷失技术写作的本心。
+
+我个人给自己定的目标是:**每个月至少写一篇原创技术文章或者认真修改完善过去写的三篇技术文章** (像开源项目推荐、开源项目学习、个人经验分享、面经分享等等类型的文章不会被记入)。
+
+我的目标对我来说比较容易完成,因此不会出现为了完成目标而应付任务的情况。在我状态比较好,工作也不是很忙的时候,还会经常超额完成任务。下图是我今年 3 月份完成的任务(任务管理工具:Microsoft To-Do)。除了 gossip 协议是去年写的之外,其他都是 3 月份完成的。
+
+
+
+如果觉得以文章数量为标准过于功利的话,也可以比较随性地按照自己的节奏来写作。不过,一般这种情况下,你很可能过段时间就忘了还有这件事,开始慢慢抵触写博客。
+
+写完一篇技术文章之后,我们不光要同步到自己的博客,还要分发到国内一些常见的技术社区比如博客园、掘金。**分发到其他平台的原因是获得关注进而收获正向反馈(动力来源之一)与建议,这是技术写作能坚持下去的非常重要的一步,一定要重视!!!**
+
+说实话,当你写完一篇自认为还不错的文章的幸福感和成就感还是有的。**但是,让自己去做这件事情还是比较痛苦的。** 就好比你让自己出去玩很简单,为了达到这个目的,你可以有各种借口。但是,想要自己老老实实学习,还是需要某个外力来督促自己的。
+
+## 写哪些方向的博客比较好?
+
+通常来说,写下面这些方向的博客会比较好:
+
+1. **详细讲解某个知识点**:一定要有自己的思考而不是东拼西凑。不仅要介绍知识点的基本概念和原理,还需要适当结合实际案例和应用场景进行举例说明。
+2. **问题排查/性能优化经历**:需要详细描述清楚具体的场景以及解决办法。一定要有足够的细节描述,包括出现问题的具体场景、问题的根本原因、解决问题的思路和具体步骤等等。同时,要注重实践性和可操作性,帮助读者更好地学习理解。
+3. **源码阅读记录**:从一个功能点出发描述其底层源码实现,谈谈你从源码中学到了什么。
+
+最重要的是一定要重视 Markdown 规范,不然内容再好也会显得不专业。
+
+详见 [Markdown 规范](../javaguide/contribution-guideline.md) (很重要,尽量按照规范来,对你工作中写文档会非常有帮助)
+
+## 有没有什么写作技巧分享?
+
+### 句子不要过长
+
+句子不要过长,尽量使用短句(但也不要太短),这样读者更容易阅读和理解。
+
+### 尽量让文章更加生动有趣
+
+尽量让文章更加生动有趣,比如你可以适当举一些形象的例子、用一些有趣的段子、歇后语或者网络热词。
+
+不过,这个也主要看你的文章风格。
+
+### 使用简单明了的语言
+
+避免使用阅读者可能无法理解的行话或复杂语言。
+
+注重清晰度和说服力,保持简单。简单的写作是有说服力的,一个五句话的好论点会比一百句话的精彩论点更能打动人。为什么格言、箴言这类文字容易让人接受,与简洁、直白也有些关系。
+
+### 使用视觉效果
+
+图表、图像等视觉效果可以让朴素的文本内容更容易理解。记得在适当的地方使用视觉效果来增强你的文章的表现力。
+
+
+
+### 技术文章配图色彩要鲜明
+
+下面是同样内容的两张图,都是通过 drawio 画的,小伙伴们更喜欢哪一张呢?
+
+我相信大部分小伙伴都会选择后面一个色彩更鲜明的!
+
+色彩的调整不过花费了我不到 30s 的时间,带来的阅读体验的上升却是非常之大!
+
+
+
+### 确定你的读者
+
+写作之前,思考一下你的文章的主要受众全体是谁。受众群体确定之后,你可以根据受众的需求和理解水平调整你的写作风格和内容难易程度。
+
+### 审查和修改
+
+在发表之前一定要审查和修改你的文章。这将帮助你发现错误、澄清任何令人困惑的信息并提高文档的整体质量。
+
+**好文是改出来的,切记!!!**
+
+## 总结
+
+总的来说,写技术博客是一件利己利彼的事情。你可能会从中收获到很多东西,你写的东西也可能对别人也有很大的帮助。但是,写技术博客还是比较耗费自己时间的,你需要和工作以及生活做好权衡。
diff --git a/docs/about-the-author/zhishixingqiu-two-years.md b/docs/about-the-author/zhishixingqiu-two-years.md
new file mode 100644
index 0000000000000000000000000000000000000000..89eb42d4d1f09d33ee7f847d950050ea3d12efb2
--- /dev/null
+++ b/docs/about-the-author/zhishixingqiu-two-years.md
@@ -0,0 +1,148 @@
+---
+title: 我的知识星球快 3 岁了!
+category: 知识星球
+star: 2
+---
+
+
+
+时间过的真快,知识星球我已经平稳运行了 3 年有余了!
+
+在 2019 年 12 月 29 号,经过了大概一年左右的犹豫期,我正式确定要开始做一个自己的星球。
+
+
+
+截止到今天,星球已经有 2.2w+ 的同学加入。虽然比不上很多大佬,但这于我来说也算是小有成就了,真的很满足了!我确信自己是一个普通人,能做成这些,也不过是在兴趣和运气的加持下赶上了时代而已。
+
+**我有自己的原则,不割韭菜,用心做内容,真心希望帮助到他人!**
+
+## 什么是知识星球?
+
+简单来说,知识星球就是一个私密交流圈子,主要用途是知识创作者连接铁杆读者/粉丝。相比于微信群,知识星球内容沉淀、信息管理更高效。
+
+
+
+## 我的知识星球能为你提供什么?
+
+努力做一个最优质的 Java 面试交流星球!加入到我的星球之后,你将获得:
+
+1. 6 个高质量的专栏永久阅读,内容涵盖面试,源码解析,项目实战等内容!价值远超门票!
+2. 多本原创 PDF 版本面试手册。
+3. 免费的简历修改服务(已经累计帮助 4000+ 位球友修改简历)。
+4. 一对一免费提问交流(专属建议,走心回答)。
+5. 专属求职指南和建议,帮助你逆袭大厂!
+6. 海量 Java 优质面试资源分享!价值远超门票!
+7. 读书交流,学习交流,让我们一起努力创造一个纯粹的学习交流社区。
+8. 不定期福利:节日抽奖、送书送课、球友线下聚会等等。
+9. ......
+
+其中的任何一项服务单独拎出来价值都远超星球门票了。
+
+这里再送一个 **30** 元的星球专属优惠券吧,数量有限(价格即将上调。老用户续费半价 ,微信扫码即可续费)!
+
+
+
+### 专属专栏
+
+星球更新了 **《Java 面试指北》**、**《Java 必读源码系列》**(目前已经整理了 Dubbo 2.6.x、Netty 4.x、SpringBoot2.1 的源码)、 **《从零开始写一个 RPC 框架》**(已更新完)、**《Kafka 常见面试题/知识点总结》** 等多个优质专栏。
+
+
+
+《Java 面试指北》内容概览:
+
+
+
+进入星球之后,这些专栏即可免费永久阅读,永久同步更新!
+
+### PDF 面试手册
+
+免费赠送多本优质 PDF 面试手册。
+
+
+
+### 优质精华主题沉淀
+
+星球沉淀了几年的优质精华主题,内容涵盖面经、面试题、工具网站、技术资源、程序员进阶攻略等内容,干货非常多。
+
+
+
+
+
+加入星球之后,记得抽时间把星球精华主题看看,相信你一定会有所收货!
+
+### 简历修改
+
+一到面试季,我平均一天晚上至少要看 15 ~30 份简历。过了面试季的话,找我看简历的话会稍微少一些。要不然的话,是真心顶不住!
+
+
+
+简单统计了一下,到目前为止,我至少帮助 **6000+** 位球友提供了免费的简历修改服务。
+
+
+
+我会针对每一份简历给出详细的修改完善建议,用心修改,深受好评!
+
+
+
+### 一对一提问
+
+你可以和我进行一对一免费提问交流,我会很走心地回答你的问题。到目前为止,已经累计回答了 **2000+** 个读者的提问。
+
+
+
+
+
+### 学习打卡
+
+星球的学习打卡活动可以督促自己和其他球友们一起学习交流。
+
+
+
+看球友们的打卡也能有收货,最重要的是这个学习氛围对于自己自律非常有帮助!
+
+
+
+
+
+### 读书活动
+
+定期会举办读书活动(奖励丰厚),我会带着大家一起读一些优秀的技术书籍!
+
+
+
+每一期读书活动的获奖率都非常非常非常高!直接超过门票价!!!
+
+### 不定时福利
+
+不定时地在星球送书、送专栏、发红包,福利多多,
+
+
+
+## 是否收费?
+
+星球是需要付费才能进入的。 **为什么要收费呢?**
+
+1. 维护好星球是一件费时费力的事情,每到面试季,我经常凌晨还在看简历和回答球友问题。市面上单单一次简历修改服务也至少需要 200+,而简历修改也只是我的星球提供的服务的冰山一角。除此之外,我还要抽时间写星球专属的一些专栏,单单是这些专栏的价值就远超星球门票了。
+2. 星球提供的服务比较多,如果我是免费提供这些服务的话,是肯定忙不过来的。付费这个门槛可以帮我筛选出真正需要帮助的那批人。
+3. 免费的东西才是最贵的,加入星球之后无任何其他需要付费的项目,统统免费!
+4. 合理的收费是对我付出劳动的一种正向激励,促进我继续输出!同时,这份收入还可以让我们家人过上更好的生活。虽然累点,但也是值得的!
+
+另外,这个是一年的,到明年这个时候结束,差不过够用了。如果服务结束的时候你还需要星球服务的话,可以添加我的微信(**javaguide1024**)领取一个续费优惠卷,半价基础再减 10,记得备注 **“续费”** 。
+
+## 如何加入?
+
+三年前,星球的定价是 **50/年** (星球规定的最低定价),我还附送了 33 元优惠券。扣除了星球手续费,发了各种福利之后,就是纯粹在做公益。感兴趣的小伙伴可以看看我在 2020-01-03 发的头条:[做了一个很久没敢做的事情](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486049&idx=1&sn=e0161b409e8f164251bdaa0c83a476bc&chksm=cea245aaf9d5ccbcafdb95a546d959508814085620aabdbb4385c4b8cea6e50bf157c3697041&token=1614894361&lang=zh_CN#rd),考古一下。
+
+随着时间推移,星球沉淀的干货资源越来越多,我花在星球上的时间也越来越多。于是,我将星球的定价慢慢调整为了 **166/年**!后续会将星球的价格调整为 **196/年**,想要加入的小伙伴一定要尽早。
+
+这里再送一个 **30** 元的星球专属优惠券吧,数量有限(价格即将上调。老用户续费半价 ,微信扫码即可续费)!
+
+
+
+进入星球之后,记得查看 **[星球使用指南](https://t.zsxq.com/0d18KSarv)** (一定要看!!!) 和 **[星球优质主题汇总](https://www.yuque.com/snailclimb/rpkqw1/ncxpnfmlng08wlf1)** 。另外,建议你添加一下我的个人微信( **javaguide1024** ,备注 **“星球”** ,生活号,球友专属),方便后续交流沟通。
+
+
+
+**无任何套路,无任何潜在收费项。用心做内容,不割韭菜!**
+
+不过, **一定要确定需要再进** 。并且, **三天之内觉得内容不满意可以全额退款** 。
diff --git a/docs/books/cs-basics.md b/docs/books/cs-basics.md
new file mode 100644
index 0000000000000000000000000000000000000000..e67ac115964d23b7621fba0f48a5127aa70e145e
--- /dev/null
+++ b/docs/books/cs-basics.md
@@ -0,0 +1,274 @@
+---
+title: 计算机基础必读经典书籍
+category: 计算机书籍
+icon: "computer"
+head:
+ - - meta
+ - name: keywords
+ content: 计算机基础书籍精选
+---
+
+考虑到很多同学比较喜欢看视频,因此,这部分内容我不光会推荐书籍,还会顺便推荐一些我觉得不错的视频教程和各大高校的 Project。
+
+## 操作系统
+
+**为什么要学习操作系统?**
+
+**从对个人能力方面提升来说**,操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。比如说我们开发的系统使用的缓存(比如 Redis)和操作系统的高速缓存就很像。CPU 中的高速缓存有很多种,不过大部分都是为了解决 CPU 处理速度和内存处理速度不对等的问题。我们还可以把内存可以看作外存的高速缓存,程序运行的时候我们把外存的数据复制到内存,由于内存的处理速度远远高于外存,这样提高了处理速度。同样地,我们使用的 Redis 缓存就是为了解决程序处理速度和访问常规关系型数据库速度不对等的问题。高速缓存一般会按照局部性原理(2-8 原则)根据相应的淘汰算法保证缓存中的数据是经常会被访问的。我们平常使用的 Redis 缓存很多时候也会按照 2-8 原则去做,很多淘汰算法都和操作系统中的类似。既说了 2-8 原则,那就不得不提命中率了,这是所有缓存概念都通用的。简单来说也就是你要访问的数据有多少能直接在缓存中直接找到。命中率高的话,一般表明你的缓存设计比较合理,系统处理速度也相对较快。
+
+**从面试角度来说**,尤其是校招,对于操作系统方面知识的考察是非常非常多的。
+
+**简单来说,学习操作系统能够提高自己思考的深度以及对技术的理解力,并且,操作系统方面的知识也是面试必备。**
+
+如果你要系统地学习操作系统的话,最硬核最权威的书籍是 **[《操作系统导论》](https://book.douban.com/subject/33463930/)** 。你可以再配套一个 **[《深入理解计算机系统》](https://book.douban.com/subject/1230413/)** 加深你对计算机系统本质的认识,美滋滋!
+
+
+
+另外,去年新出的一本国产的操作系统书籍也很不错:**[《现代操作系统:原理与实现》](https://book.douban.com/subject/35208251/)** (夏老师和陈老师团队的力作,值得推荐)。
+
+
+
+如果你比较喜欢动手,对于理论知识比较抵触的话,我推荐你看看 **[《30 天自制操作系统》](https://book.douban.com/subject/11530329/)** ,这本书会手把手教你编写一个操作系统。
+
+纸上学来终觉浅 绝知此事要躬行!强烈推荐 CS 专业的小伙伴一定要多多实践!!!
+
+
+
+其他相关书籍推荐:
+
+- **[《自己动手写操作系统》](https://book.douban.com/subject/1422377/)**:不光会带着你详细分析操作系统原理的基础,还会用丰富的实例代码,一步一步地指导你用 C 语言和汇编语言编写出一个具备操作系统基本功能的操作系统框架。
+- **[《现代操作系统》](https://book.douban.com/subject/3852290/)**:内容很不错,不过,翻译的一般。如果你是精读本书的话,建议把课后习题都做了。
+- **[《操作系统真象还原》](https://book.douban.com/subject/26745156/)**:这本书的作者毕业于北京大学,前百度运维高级工程师。因为在大学期间曾重修操作系统这一科,后对操作系统进行深入研究,著下此书。
+- **[《深度探索 Linux 操作系统》](https://book.douban.com/subject/25743846/)**:跟着这本书的内容走,可以让你对如何制作一套完善的 GNU/Linux 系统有了清晰的认识。
+- **[《操作系统设计与实现》](https://book.douban.com/subject/2044818/)**:操作系统的权威教学教材。
+- **[《Orange'S:一个操作系统的实现》](https://book.douban.com/subject/3735649/)**:从只有二十行的引导扇区代码出发,一步一步地向读者呈现一个操作系统框架的完成过程。配合《操作系统设计与实现》一起食用更佳!
+
+如果你比较喜欢看视频的话,推荐哈工大李治军老师主讲的慕课 [《操作系统》](https://www.icourse163.org/course/HIT-1002531008),内容质量吊打一众国家精品课程。
+
+课程的大纲如下:
+
+
+
+主要讲了一个基本操作系统中的六个基本模块:CPU 管理、内存管理、外设管理、磁盘管理与文件系统、用户接口和启动模块 。
+
+课程难度还是比较大的,尤其是课后的 lab。如果大家想要真正搞懂操作系统底层原理的话,对应的 lab 能做尽量做一下。正如李治军老师说的那样:“纸上得来终觉浅,绝知此事要躬行”。
+
+
+
+如果你能独立完成几个 lab 的话,我相信你对操作系统的理解绝对要上升几个台阶。当然了,如果你仅仅是为了突击面试的话,那就不需要做 lab 了。
+
+说点心里话,我本人非常喜欢李治军老师讲的课,我觉得他是国内不可多得的好老师。他知道我们国内的教程和国外的差距在哪里,也知道国内的学生和国外学生的差距在哪里,他自己在努力着通过自己的方式来缩小这个差距。真心感谢,期待李治军老师的下一个课程。
+
+
+
+还有下面这个国外的课程 [《深入理解计算机系统 》](https://www.bilibili.com/video/av31289365?from=search&seid=16298868573410423104) 也很不错。
+
+
+
+## 计算机网络
+
+计算机网络是一门系统性比较强的计算机专业课,各大名校的计算机网络课程打磨的应该都比较成熟。
+
+要想学好计算机网络,首先要了解的就是 OSI 七层模型或 TCP/IP 五层模型,即应用层(应用层、表示层、会话层)、传输层、网络层、数据链路层、物理层。
+
+
+
+关于这门课,首先强烈推荐参考书是**机械工业出版社的《计算机网络——自顶向下方法》**。该书目录清晰,按照 TCP/IP 五层模型逐层讲解,对每层涉及的技术都展开了详细讨论,基本上高校里开设的课程的教学大纲就是这本书的目录了。
+
+
+
+如果你觉得上面这本书看着比较枯燥的话,我强烈推荐+安利你看看下面这两本非常有趣的网络相关的书籍:
+
+- [《图解 HTTP》](https://book.douban.com/subject/25863515/ "《图解 HTTP》"):讲漫画一样的讲 HTTP,很有意思,不会觉得枯燥,大概也涵盖也 HTTP 常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究 HTTP 相关知识的话,读这本书的话应该来说就差不多了。
+- [《网络是怎样连接的》](https://book.douban.com/subject/26941639/ "《网络是怎样连接的》"):从在浏览器中输入网址开始,一路追踪了到显示出网页内容为止的整个过程,以图配文,讲解了网络的全貌,并重点介绍了实际的网络设备和软件是如何工作的。
+
+
+
+除了理论知识之外,学习计算机网络非常重要的一点就是:“**动手实践**”。这点和我们编程差不多。
+
+GitHub 上就有一些名校的计算机网络试验/Project:
+
+- [哈工大计算机网络实验](https://github.com/rccoder/HIT-Computer-Network)
+- [《计算机网络-自顶向下方法(原书第 6 版)》编程作业,Wireshark 实验文档的翻译和解答。](https://github.com/moranzcw/Computer-Networking-A-Top-Down-Approach-NOTES)
+- [计算机网络的期末 Project,用 Python 编写的聊天室](https://github.com/KevinWang15/network-pj-chatroom)
+- [CMU 的计算机网络课程](https://computer-networks.github.io/sp19/lectures.html)
+
+我知道,还有很多小伙伴可能比较喜欢边看视频边学习。所以,我这里再推荐几个顶好的计算机网络视频讲解。
+
+**1、[哈工大的计算机网络课程](http://www.icourse163.org/course/HIT-154005)**:国家精品课程,截止目前已经开了 10 次课了。大家对这门课的评价都非常高!所以,非常推荐大家看一下!
+
+
+
+**2、[王道考研的计算机网络](https://www.bilibili.com/video/BV19E411D78Q?from=search&seid=17198507506906312317)**:非常适合 CS 专业考研的小朋友!这个视频目前在哔哩哔哩上已经有 1.6w+ 的点赞。
+
+
+
+## 算法
+
+先来看三本入门书籍。 这三本入门书籍中的任何一本拿来作为入门学习都非常好。
+
+1. [《我的第一本算法书》](https://book.douban.com/subject/30357170/)
+2. [《算法图解》](https://book.douban.com/subject/26979890/)
+3. [《啊哈!算法》](https://book.douban.com/subject/25894685/)
+
+
+
+我个人比较倾向于 **[《我的第一本算法书》](https://book.douban.com/subject/30357170/)** 这本书籍,虽然它相比于其他两本书集它的豆瓣评分略低一点。我觉得它的配图以及讲解是这三本书中最优秀,唯一比较明显的问题就是没有代码示例。但是,我觉得这不影响它是一本好的算法书籍。因为本身下面这三本入门书籍的目的就不是通过代码来让你的算法有多厉害,只是作为一本很好的入门书籍让你进入算法学习的大门。
+
+再推荐几本比较经典的算法书籍。
+
+**[《算法》](https://book.douban.com/subject/19952400/)**
+
+
+
+这本书内容非常清晰易懂,适合数据结构和算法小白阅读。书中把一些常用的数据结构和算法都介绍到了!
+
+我在大二的时候被我们的一个老师强烈安利过!自己也在当时购买了一本放在宿舍,到离开大学的时候自己大概看了一半多一点。因为内容实在太多了!另外,这本书还提供了详细的 Java 代码,非常适合学习 Java 的朋友来看,可以说是 Java 程序员的必备书籍之一了。
+
+> **下面这些书籍都是经典中的经典,但是阅读起来难度也比较大,不做太多阐述,神书就完事了!**
+>
+> **如果你仅仅是准备算法面试的话,不建议你阅读下面这些书籍。**
+
+**[《编程珠玑》](https://book.douban.com/subject/3227098/)**
+
+
+
+经典名著,ACM 冠军、亚军这种算法巨佬都强烈推荐的一本书籍。这本书的作者也非常厉害,Java 之父 James Gosling 就是他的学生。
+
+很多人都说这本书不是教你具体的算法,而是教你一种编程的思考方式。这种思考方式不仅仅在编程领域适用,在其他同样适用。
+
+**[《算法设计手册》](https://book.douban.com/subject/4048566/)**
+
+
+
+这是一本被 GitHub 上的爆火的计算机自学项目 [Teach Yourself Computer Science](https://link.zhihu.com/?target=https%3A//teachyourselfcs.com/) 强烈推荐的一本算法书籍。
+
+类似的神书还有 [《算法导论》](https://book.douban.com/subject/20432061/)、[《计算机程序设计艺术(第 1 卷)》](https://book.douban.com/subject/1130500/) 。
+
+**如果说你要准备面试的话,下面这几本书籍或许对你有帮助!**
+
+**[《剑指 Offer》](https://book.douban.com/subject/6966465/)**
+
+
+
+这本面试宝典上面涵盖了很多经典的算法面试题,如果你要准备大厂面试的话一定不要错过这本书。
+
+《剑指 Offer》 对应的算法编程题部分的开源项目解析:[CodingInterviews](https://link.zhihu.com/?target=https%3A//github.com/gatieme/CodingInterviews) 。
+
+**[《程序员代码面试指南(第 2 版)》](https://book.douban.com/subject/30422021/)**
+
+
+
+《程序员代码面试指南(第 2 版)》里的大部分题目相比于《剑指 offer》 来说要难很多,题目涵盖面相比于《剑指 offer》也更加全面。全书一共有将近 300 道真实出现过的经典代码面试题。
+
+视频的话,推荐北京大学的国家精品课程—**[程序设计与算法(二)算法基础](https://www.icourse163.org/course/PKU-1001894005)**,讲的非常好!
+
+
+
+这个课程把七种基本的通用算法(枚举、二分、递归、分治、动态规划、搜索、贪心)都介绍到了。各种复杂算法问题的解决,都可能用到这些基本的思想。并且,这个课程的一部分的例题和 ACM 国际大学生程序设计竞赛中的中等题相当,如果你能够解决这些问题,那你的算法能力将超过绝大部分的高校计算机专业本科毕业生。
+
+## 数据结构
+
+其实,上面提到的很多算法类书籍(比如 **《算法》** 和 **《算法导论》**)都详细地介绍了常用的数据结构。
+
+我这里再另外补充基本和数据结构相关的书籍。
+
+**[《大话数据结构》](https://book.douban.com/subject/6424904/)**
+
+
+
+入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。
+
+**[《数据结构与算法分析:Java 语言描述》](https://book.douban.com/subject/3351237/)**
+
+
+
+质量很高,介绍了常用的数据结构和算法。
+
+类似的还有 **[《数据结构与算法分析:C 语言描述》](https://book.douban.com/subject/1139426/)**、**[《数据结构与算法分析:C++ 描述》](https://book.douban.com/subject/1971825/)**
+
+
+
+视频的话推荐你看浙江大学的国家精品课程—**[《数据结构》](https://www.icourse163.org/course/ZJU-93001#/info)** 。
+
+姥姥的数据结构讲的非常棒!不过,还是有一些难度的,尤其是课后练习题。
+
+## 计算机专业基础课
+
+数学和英语属于通用课,一般在大一和大二两学年就可以全部修完,大二大三逐渐接触专业课。通用课作为许多高中生升入大学的第一门课,算是高中阶段到本科阶段的一个过渡,从职业生涯重要性上来说,远不及专业课重要,但是在本科阶段的学习生活规划中,有着非常重要的地位。由于通用课的课程多,学分重,占据了本科阶段绩点的主要部分,影响到学生在前两年的专业排名,也影响到大三结束时的推免资格分配,也就是保研。而从升学角度来看,对于攻读研究生和博士生的小伙伴来说,数学和英语这两大基础课,还是十分有用的。
+
+### 数学
+
+#### 微积分(高等数学)
+
+微积分,即传说中的高数,成为了无数新大一心中的痛。但好在,大学的课程考核没那么严格,期末想要拿高分,也不至于像高中那样刷题刷的那么狠。微积分对于计算机专业学生的重要性,主要体现在计算机图形学中的函数变换,机器学习中的梯度算法,信号处理等领域。
+
+微积分的知识体系包括微分和积分两部分,一般会先学微分,再学积分,也有的学校把高数分为两个学期。微分就是高中的导数的升级版,对于大一萌新来说还算比较友好。积分恰好是微分的逆运算,思想上对大一萌新来说比较新,一时半会可能接受不了。不过这门课所有的高校都有开设,而且大部分的名校都有配套的网课,教材也都打磨的非常出色,结合网课和教材的“啃书”学习模式,这门课一定不会落下。
+
+书籍的话,推荐《普林斯顿微积分读本》。这本书详细讲解了微积分基础、极限、连续、微分、导数的应用、积分、无穷级数、泰勒级数与幂级数等内容。
+
+
+
+#### 线性代数(高等代数)
+
+线性代数的思维模式就更加复杂了一些,它定义了一个全新的数学世界,所有的符号、定理都是全新的,唯一能尝试的去理解的方式,大概就是用几何的方式去理解线性代数了。由于线性代数和几何学有着密不可分的关系,比如空间变换的理论支撑就是线性代数,因此,网上有着各种“可视化学习线性代数”的学习资源,帮助理解线性代数的意义,有助于公式的记忆。
+
+
+
+书籍的话,推荐中科大李尚志老师的 **[《线性代数学习指导》](https://book.douban.com/subject/26390093/)** 。
+
+
+
+#### 概率论与数理统计
+
+对于计算机专业的小伙伴来说,这门课可能是概率论更有用一点,而非数理统计。可能某些学校只开设概率论课程,也可能数理统计也教,但仅仅是皮毛。概率论的学习路线和微积分相似,就是一个个公式辅以实例,不像线性代数那么抽象,比较贴近生活。在现在的就业形势下,概率论与数理统计专业的学生,应该是数学专业最好就业的了,他们通常到岗位上会做一些数据分析的工作,因此,**这门课程确实是数据分析的重要前置课程,概率论在机器学习中的重要性也就不言而喻了。**
+
+书籍的话,推荐 **[《概率论与数理统计教程》](https://book.douban.com/subject/34897672/)** 。这本书共八章,前四章为概率论部分,主要叙述各种概率分布及其性质,后四章为数理统计部分,主要叙述各种参数估计与假设检验。
+
+
+
+#### 离散数学(集合论、图论、近世代数等)
+
+离散数学是计算机专业的专属数学,但实际上对于本科毕业找工作的小伙伴来说,离散数学还并没有发挥它的巨大作用。离散数学的作用主要在在图研究等领域,理论性极强,需要读研深造的小伙伴尽可能地扎实掌握。
+
+### 英语
+
+英语算是大学里面比较灵活的一项技能了,有的人会说,“英语学的越好,对个人发展越有利”,此话说的没错,但是对于一些有着明确发展目标的小伙伴,可能英语技能并不在他们的技能清单内。接下来的这些话只针对计算机专业的小伙伴们哦。
+
+英语课在大学本科一般只有前两年开设,小伙伴们可以记住,**想用英语课来提升自己的英语水平的,可以打消这个念头了。** 英语水平的提高全靠自己平时的积累和练习,以及有针对性的刷题。
+
+**英语的大学四六级一定要过。** 这是必备技能,绝大部分就业岗位都要看四六级水平的,最起码要通过的。四级比高中英语稍微难一些,一般的小伙伴可能会卡在六级上,六级需要针对性的训练一下,因为大学期间能接触英语的实在太少了,每学期一门英语课是不足以保持自己的英语水平的。对于一些来自于偏远地区,高中英语基础薄弱的,考四六级会更加吃力。建议考前集中训练一下历年真题,辅以背一下高频词汇,四六级通过只需要 425 分,这个分数线还是比较容易达到的。稍微好一点的小伙伴可能冲一下 500 分,要是能考到 600 分的话,那是非常不错的水平了,算是简历上比较有亮点的一项。
+
+英语的雅思托福考试只限于想要出国的小伙伴,以及应聘岗位对英语能力有特殊要求的。雅思托福考试裸考不容易通过,花钱去比较靠谱的校外补课班应该是一个比较好的选择。
+
+对于计算机专业的小伙伴来说,英语能力还是比较重要的,虽然应聘的时候不会因为没有雅思托福成绩卡人,但是你起码要能够:
+
+- **熟练使用英文界面的软件、系统等**
+- **对于外网的一些博客、bug 解决方案等,阅读无压力**
+- **熟练阅读英文文献**
+- **具备一定的英文论文的撰写能力**
+
+毕竟计算机语言就是字符语言,听说读写中最起码要满足**读写**这两项不过分吧。
+
+### 编译原理
+
+编译原理相比于前面介绍的专业课,地位显得不那么重要了。编译原理的重要性主要体现在:
+
+- 底层语言、引擎或高级语言的开发,如 MySQL,Java 等
+- 操作系统或嵌入式系统的开发
+- 词法、语法、语义的思想,以及自动机思想
+
+**编译原理的重要前置课程就是形式语言与自动机,自动机的思想在词法分析当中有着重要应用,学习了这门课后,应该就会发现许多场景下,自动机算法的妙用了。**
+
+总的来说,这门课对于各位程序员的职业发展来说,相对不那么重要,但是从难度上来说,学习这门课可以对编程思想有一个较好的巩固。学习资源的话,除了课堂上的幻灯片课件以外,还可以把 《编译原理》 这本书作为参考书,用以辅助自己学不懂的地方(大家口中的龙书,想要啃下来还是有一定难度的)。
+
+
+
+其他书籍推荐:
+
+- **[《现代编译原理》](https://book.douban.com/subject/30191414/)**:编译原理的入门书。
+- **[《编译器设计》](https://book.douban.com/subject/20436488/)**:覆盖了编译器从前端到后端的全部主题。
+
+我上面推荐的书籍的难度还是比较高的,真心很难坚持看完。这里强烈推荐[哈工大的编译原理视频课程](https://www.icourse163.org/course/HIT-1002123007),真心不错,还是国家精品课程,关键还是又漂亮有温柔的美女老师讲的!
+
+
diff --git a/docs/books/database.md b/docs/books/database.md
new file mode 100644
index 0000000000000000000000000000000000000000..87f92d24184d40d418428793be1d064aa9508ec9
--- /dev/null
+++ b/docs/books/database.md
@@ -0,0 +1,106 @@
+---
+title: 数据库必读经典书籍
+category: 计算机书籍
+icon: "database"
+head:
+ - - meta
+ - name: keywords
+ content: 数据库书籍精选
+---
+
+## 数据库基础
+
+数据库基础这块,如果你觉得书籍比较枯燥,自己坚持不下来的话,我推荐你可以先看看一些不错的视频,北京师范大学的[《数据库系统原理》](https://www.icourse163.org/course/BNU-1002842007)、哈尔滨工业大学的[《数据库系统(下):管理与技术》](https://www.icourse163.org/course/HIT-1001578001)就很不错。
+
+[《数据库系统原理》](https://www.icourse163.org/course/BNU-1002842007)这个课程的老师讲的非常详细,而且每一小节的作业设计的也与所讲知识很贴合,后面还有很多配套实验。
+
+
+
+如果你比较喜欢动手,对于理论知识比较抵触的话,推荐你看看[《如何开发一个简单的数据库》](https://cstack.github.io/db_tutorial/) ,这个 project 会手把手教你编写一个简单的数据库。
+
+
+
+GitHub 上也已经有大佬用 Java 实现过一个简易的数据库,介绍的挺详细的,感兴趣的朋友可以去看看。地址:[https://github.com/alchemystar/Freedom](https://github.com/alchemystar/Freedom) 。
+
+除了这个用 Java 写的之外,**[db_tutorial](https://github.com/cstack/db_tutorial)** 这个项目是国外的一个大佬用 C 语言写的,朋友们也可以去瞅瞅。
+
+**只要利用好搜索引擎,你可以找到各种语言实现的数据库玩具。**
+
+
+
+**纸上学来终觉浅 绝知此事要躬行!强烈推荐 CS 专业的小伙伴一定要多多实践!!!**
+
+### 《数据库系统概念》
+
+[《数据库系统概念》](https://book.douban.com/subject/10548379/)这本书涵盖了数据库系统的全套概念,知识体系清晰,是学习数据库系统非常经典的教材!不是参考书!
+
+
+
+### 《数据库系统实现》
+
+如果你也想要研究 MySQL 底层原理的话,我推荐你可以先阅读一下[《数据库系统实现》](https://book.douban.com/subject/4838430/)。
+
+
+
+不管是 MySQL 还是 Oracle ,它们总体的架子是差不多的,不同的是其内部的实现比如数据库索引的数据结构、存储引擎的实现方式等等。
+
+这本书有些地方还是翻译的比较蹩脚,有能力看英文版的还是建议上手英文版。
+
+《数据库系统实现》 这本书是斯坦福的教材,另外还有一本[《数据库系统基础教程》](https://book.douban.com/subject/3923575/)是前置课程,可以带你入门数据库。
+
+## MySQL
+
+我们网站或者 APP 的数据都是需要使用数据库来存储数据的。
+
+一般企业项目开发中,使用 MySQL 比较多。如果你要学习 MySQL 的话,可以看下面这 3 本书籍:
+
+- **[《MySQL 必知必会》](https://book.douban.com/subject/3354490/)**:非常薄!非常适合 MySQL 新手阅读,很棒的入门教材。
+- **[《高性能 MySQL》](https://book.douban.com/subject/23008813/)**:MySQL 领域的经典之作!学习 MySQL 必看!属于进阶内容,主要教你如何更好地使用 MySQL 。既有有理论,又有实践!如果你没时间都看一遍的话,我建议第 5 章(创建高性能的索引)、第 6 章(查询性能优化) 你一定要认真看一下。
+- **[《MySQL 技术内幕》](https://book.douban.com/subject/24708143/)**:你想深入了解 MySQL 存储引擎的话,看这本书准没错!
+
+
+
+视频的话,你可以看看动力节点的 [《MySQL 数据库教程视频》](https://www.bilibili.com/video/BV1fx411X7BD)。这个视频基本上把 MySQL 的相关一些入门知识给介绍完了。
+
+另外,强推一波 **[《MySQL 是怎样运行的》](https://book.douban.com/subject/35231266/)** 这本书,内容很适合拿来准备面试。讲的很细节,但又不枯燥,内容非常良心!
+
+
+
+## PostgreSQL
+
+和 MySQL 一样,PostgreSQL 也是开源免费且功能强大的关系型数据库。PostgreSQL 的 Slogan 是“**世界上最先进的开源关系型数据库**” 。
+
+
+
+最近几年,由于 PostgreSQL 的各种新特性过于优秀,使用 PostgreSQL 代替 MySQL 的项目越来越多了。
+
+如果你还在纠结是否尝试一下 PostgreSQL 的话,建议你看看这个知乎话题:[PostgreSQL 与 MySQL 相比,优势何在? - 知乎](https://www.zhihu.com/question/20010554) 。
+
+### 《PostgreSQL 指南:内幕探索》
+
+[《PostgreSQL 指南:内幕探索》](https://book.douban.com/subject/33477094/)这本书主要介绍了 PostgreSQL 内部的工作原理,包括数据库对象的逻辑组织与物理实现,进程与内存的架构。
+
+刚工作那会需要用到 PostgreSQL ,看了大概 1/3 的内容,感觉还不错。
+
+
+
+### 《PostgreSQL 技术内幕:查询优化深度探索》
+
+[《PostgreSQL 技术内幕:查询优化深度探索》](https://book.douban.com/subject/30256561/)这本书主要讲了 PostgreSQL 在查询优化上的一些技术实现细节,可以让你对 PostgreSQL 的查询优化器有深层次的了解。
+
+
+
+## Redis
+
+**Redis 就是一个使用 C 语言开发的数据库**,不过与传统数据库不同的是 **Redis 的数据是存在内存中的** ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。
+
+如果你要学习 Redis 的话,强烈推荐下面这两本书:
+
+- [《Redis 设计与实现》](https://book.douban.com/subject/25900156/) :主要是 Redis 理论知识相关的内容,比较全面。我之前写过一篇文章 [《7 年前,24 岁,出版了一本 Redis 神书》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247507030&idx=1&sn=0a5fd669413991b30163ab6f5834a4ad&chksm=cea1939df9d61a8b93925fae92f4cee0838c449534e60731cfaf533369831192e296780b32a6&token=709354671&lang=zh_CN&scene=21#wechat_redirect) 来介绍这本书。
+- [《Redis 核心原理与实践》](https://book.douban.com/subject/26612779/):主要是结合源码来分析 Redis 的重要知识点比如各种数据结构和高级特性。
+
+
+
+另外,[《Redis 开发与运维》](https://book.douban.com/subject/26971561/) 这本书也非常不错,既有基础介绍,又有一线开发运维经验分享。
+
+
diff --git a/docs/books/distributed-system.md b/docs/books/distributed-system.md
new file mode 100644
index 0000000000000000000000000000000000000000..d8f6e64b1cec72bdebc8f39b99a4a58031bce2ea
--- /dev/null
+++ b/docs/books/distributed-system.md
@@ -0,0 +1,91 @@
+---
+title: 分布式必读经典书籍
+category: 计算机书籍
+icon: "distributed-network"
+---
+
+## 《深入理解分布式系统》
+
+
+
+**[《深入理解分布式系统》](https://book.douban.com/subject/35794814/)** 是今年 3 月份刚出的一本分布式中文原创书籍,主要讲的是分布式领域的基本概念、常见挑战以及共识算法。
+
+作者用了大量篇幅来介绍分布式领域中非常重要的共识算法,并且还会基于 Go 语言带着你从零实现了一个共识算法的鼻祖 Paxos 算法。
+
+实话说,我还没有开始看这本书。但是!这本书的作者的博客上的分布式相关的文章我几乎每一篇都认真看过。
+
+作者从 2019 年开始构思《深入理解分布式系统》,2020 年开始动笔,花了接近两年的时间才最终交稿。
+
+
+
+作者专门写了一篇文章来介绍这本书的背后的故事,感兴趣的小伙伴可以自行查阅: 。
+
+最后,放上这本书的代码仓库和勘误地址: 。
+
+## 《数据密集型应用系统设计》
+
+
+
+强推一波 **[《Designing Data-Intensive Application》](https://book.douban.com/subject/30329536/)** (DDIA,数据密集型应用系统设计),值得读很多遍!豆瓣有接近 90% 的人看了这本书之后给了五星好评。
+
+这本书主要讲了分布式数据库、数据分区、事务、分布式系统等内容。
+
+书中介绍的大部分概念你可能之前都听过,但是在看了书中的内容之后,你可能会豁然开朗:“哇塞!原来是这样的啊!这不是某技术的原理么?”。
+
+这本书我之前专门写过知乎回答介绍和推荐,没看过的朋友可以看看:[有哪些你看了以后大呼过瘾的编程书?](https://www.zhihu.com/question/50408698/answer/2278198495) 。
+
+另外,如果你在阅读这本书的时候感觉难度比较大,很多地方读不懂的话,我这里推荐一下《深入理解分布式系统》作者写的[《DDIA 逐章精读》小册](https://ddia.qtmuniao.com)。
+
+## 《深入理解分布式事务》
+
+
+
+**[《深入理解分布式事务》](https://book.douban.com/subject/35626925/)** 这本书是的其中一位作者是 Apache ShenYu(incubating)网关创始人、Hmily、RainCat、Myth 等分布式事务框架的创始人。
+
+学习分布式事务的时候,可以参考一下这本书。虽有一些小错误以及逻辑不通顺的地方,但对于各种分布式事务解决方案的介绍,总体来说还是不错的。
+
+## 《从 Paxos 到 Zookeeper》
+
+
+
+**[《从 Paxos 到 Zookeeper》](https://book.douban.com/subject/26292004/)** 是一本带你入门分布式理论的好书。这本书主要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了 Paxos 和 ZAB 协议。
+
+## 《微服务架构设计模式》
+
+
+
+**[《微服务架构设计模式》](https://book.douban.com/subject/33425123/)** 的作者 Chris Richardson 被评为世界十大软件架构师之一、微服务架构先驱。这本书主要讲的是如何开发和部署生产级别的微服务架构应用,示例代码使用 Java 语言和 Spring 框架。
+
+## 《凤凰架构》
+
+
+
+**[《凤凰架构》](https://book.douban.com/subject/35492898/)** 这本书是周志明老师多年架构和研发经验的总结,内容非常干货,深度与广度并存,理论结合实践!
+
+正如书名的副标题“构建可靠的大型分布式系统”所说的那样,这本书的主要内容就是讲:“如何构建一套可靠的分布式大型软件系统” ,涵盖了下面这些方面的内容:
+
+- 软件架构从单体到微服务再到无服务的演进之路。
+- 架构师应该在架构设计时应该注意哪些问题,有哪些比较好的实践。
+- 分布式的基石比如常见的分布式共识算法 Paxos、Multi Paxos。
+- 不可变基础设施比如虚拟化容器、服务网格。
+- 向微服务迈进的避坑指南。
+
+这本书我推荐过很多次了。详见历史文章:
+
+- [周志明老师的又一神书!发现宝藏!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247505254&idx=1&sn=04faf3093d6002354f06fffbfc2954e0&chksm=cea19aadf9d613bbba7ed0e02ccc4a9ef3a30f4d83530e7ad319c2cc69cd1770e43d1d470046&scene=178&cur_album_id=1646812382221926401#rd)
+- [Java 领域的又一神书!周志明老师 YYDS!](https://mp.weixin.qq.com/s/9nbzfZGAWM9_qIMp1r6uUQ)
+
+## 《架构解密》
+
+
+
+[《架构解密》](https://book.douban.com/subject/35093373/)这本书和我渊源颇深,在大三的时候,我曾经在图书馆借阅过这本书的第一版,大概了花了不到一周就看完了。
+
+这本书的第二版在 2020 年就已经出来了,总共也才 15 个评价,算得上是一本非常小众的技术书籍了。
+
+书籍质量怎么说呢,各个知识点介绍的都比较泛,匆忙结束,一共 9 章,总共 331 页。如果你只是想初步了解一些分布式相关的概念的话,可以看看这本书,快速概览一波分布式相关的技术。
+
+## 其他
+
+- [《分布式系统 : 概念与设计》](https://book.douban.com/subject/21624776/):偏教材类型,内容全而无趣,可作为参考书籍;
+- [《分布式架构原理与实践》](https://book.douban.com/subject/35689350/):2021 年出版的,没什么热度,我也还没看过。
diff --git a/docs/books/java.md b/docs/books/java.md
new file mode 100644
index 0000000000000000000000000000000000000000..3a34084be648c44350f0871a09057d3010d79efe
--- /dev/null
+++ b/docs/books/java.md
@@ -0,0 +1,260 @@
+---
+title: Java 必读经典书籍
+category: 计算机书籍
+icon: "java"
+---
+
+## Java 基础
+
+**[《Head First Java》](https://book.douban.com/subject/2000732/)**
+
+
+
+《Head First Java》这本书的内容很轻松有趣,可以说是我学习编程初期最喜欢的几本书之一了。同时,这本书也是我的 Java 启蒙书籍。我在学习 Java 的初期多亏了这本书的帮助,自己才算是跨进 Java 语言的大门。
+
+我觉得我在 Java 这块能够坚持下来,这本书有很大的功劳。我身边的的很多朋友学习 Java 初期都是看的这本书。
+
+有很多小伙伴就会问了:**这本书适不适合编程新手阅读呢?**
+
+我个人觉得这本书还是挺适合编程新手阅读的,毕竟是 “Head First” 系列。
+
+**[《Java 核心技术卷 1 + 卷 2》](https://book.douban.com/subject/34898994/)**
+
+
+
+这两本书也非常不错。不过,这两本书的内容很多,全看的话比较费时间。我现在是把这两本书当做工具书来用,就比如我平时写文章的时候,碰到一些 Java 基础方面的问题,经常就翻看这两本来当做参考!
+
+我当时在大学的时候就买了两本放在寝室,没事的时候就翻翻。建议有点 Java 基础之后再读,介绍的还是比较深入和全面的,非常推荐。
+
+**[《Java 编程思想》](https://book.douban.com/subject/2130190/)**
+
+
+
+另外,这本书的作者去年新出版了[《On Java》](https://book.douban.com/subject/35751619/),我更推荐这本,内容更新,介绍了 Java 的 3 个长期支持版(Java 8、11、17)。
+
+
+
+毕竟,这是市面上目前唯一一本介绍了 Java 的 3 个长期支持版(Java 8、11、17)的技术书籍。
+
+**[《Java 8 实战》](https://book.douban.com/subject/26772632/)**
+
+
+
+Java 8 算是一个里程碑式的版本,现在一般企业还是用 Java 8 比较多。掌握 Java 8 的一些新特性比如 Lambda、Stream API 还是挺有必要的。这块的话,我推荐 **[《Java 8 实战》](https://book.douban.com/subject/26772632/)** 这本书。
+
+ **[《Java编程的逻辑》](https://book.douban.com/subject/30133440/)**
+
+
+
+一本非常低调的好书,相比于入门书来说,内容更有深度。适合初学者,同时也适合大家拿来复习 Java 基础知识。
+
+## Java 并发
+
+**[《Java 并发编程之美》](https://book.douban.com/subject/30351286/)**
+
+
+
+这本书还是非常适合我们用来学习 Java 多线程的,讲解非常通俗易懂,作者从并发编程基础到实战都是信手拈来。
+
+另外,这本书的作者加多自身也会经常在网上发布各种技术文章。这本书也是加多大佬这么多年在多线程领域的沉淀所得的结果吧!他书中的内容基本都是结合代码讲解,非常有说服力!
+
+**[《实战 Java 高并发程序设计》](https://book.douban.com/subject/30358019/)**
+
+
+
+这个是我第二本要推荐的书籍,比较适合作为多线程入门/进阶书籍来看。这本书内容同样是理论结合实战,对于每个知识点的讲解也比较通俗易懂,整体结构也比较清。
+
+**[《深入浅出 Java 多线程》](https://github.com/RedSpider1/concurrent)**
+
+
+
+这本开源书籍是几位大厂的大佬开源的。这几位作者为了写好《深入浅出 Java 多线程》这本书阅读了大量的 Java 多线程方面的书籍和博客,然后再加上他们的经验总结、Demo 实例、源码解析,最终才形成了这本书。
+
+这本书的质量也是非常过硬!给作者们点个赞!这本书有统一的排版规则和语言风格、清晰的表达方式和逻辑。并且每篇文章初稿写完后,作者们就会互相审校,合并到主分支时所有成员会再次审校,最后再通篇修订了三遍。
+
+在线阅读: 。
+
+**[《Java 并发实现原理:JDK 源码剖析》](https://book.douban.com/subject/35013531/)**
+
+
+
+这本书主要是对 Java Concurrent 包中一些比较重要的源码进行了讲解,另外,像 JMM、happen-before、CAS 等等比较重要的并发知识这本书也都会一并介绍到。
+
+不论是你想要深入研究 Java 并发,还是说要准备面试,你都可以看看这本书。
+
+## JVM
+
+**[《深入理解 Java 虚拟机》](https://book.douban.com/subject/34907497/)**
+
+
+
+这本书就一句话形容:**国产书籍中的战斗机,实实在在的优秀!** (真心希望国内能有更多这样的优质书籍出现!加油!💪)
+
+这本书的第 3 版 2019 年底已经出来了,新增了很多实在的内容比如 ZGC 等新一代 GC 的原理剖析。目前豆瓣上是 9.5 的高分,🐂 不 🐂 我就不多说了!
+
+不论是你面试还是你想要在 Java 领域学习的更深,你都离不开这本书籍。这本书不光要看,你还要多看几遍,里面都是干货。这本书里面还有一些需要自己实践的东西,我建议你也跟着实践一下。
+
+类似的书籍还有 **[《实战 Java 虚拟机》](https://book.douban.com/subject/26354292/)**、**[《虚拟机设计与实现:以 JVM 为例》](https://book.douban.com/subject/34935105/)** ,这两本都是非常不错的!
+
+
+
+
+
+如果你对实战比较感兴趣,想要自己动手写一个简易的 JVM 的话,可以看看 **[《自己动手写 Java 虚拟机》](https://book.douban.com/subject/26802084/)** 这本书。
+
+
+
+书中的代码是基于 Go 语言实现的,搞懂了原理之后,你可以使用 Java 语言模仿着写一个,也算是练练手! 如果你当前没有能力独立使用 Java 语言模仿着写一个的话,你也可以在网上找到很多基于 Java 语言版本的实现,比如[《zachaxy 的手写 JVM 系列》](https://zachaxy.github.io/tags/JVM/) 。
+
+这本书目前在豆瓣有 8.2 的评分,我个人觉得张秀宏老师写的挺好的,这本书值得更高的评分。
+
+另外,R 大在豆瓣发的[《从表到里学习 JVM 实现》](https://www.douban.com/doulist/2545443/)这篇文章中也推荐了很多不错的 JVM 相关的书籍,推荐小伙伴们去看看。
+
+再推荐两个视频给喜欢看视频学习的小伙伴。
+
+第 1 个是尚硅谷的宋红康老师讲的[《JVM 全套教程》](https://www.bilibili.com/video/BV1PJ411n7xZ)。这个课程的内容非常硬,一共有接近 400 小节。
+
+课程的内容分为 3 部分:
+
+1. 《内存与垃圾回收篇》
+2. 《字节码与类的加载篇》
+3. 《性能监控与调优篇》
+
+第 2 个是你假笨大佬的 **[《JVM 参数【Memory 篇】》](https://club.perfma.com/course/438755/list)** 教程,很厉害了!
+
+
+
+## 常用工具
+
+非常重要!非常重要!特别是 Git 和 Docker。
+
+- **IDEA**:熟悉基本操作以及常用快捷。相关资料: [《IntelliJ IDEA 简体中文专题教程》](https://github.com/judasn/IntelliJ-IDEA-Tutorial) 。
+- **Maven**:强烈建议学习常用框架之前可以提前花几天时间学习一下**Maven**的使用。(到处找 Jar 包,下载 Jar 包是真的麻烦费事,使用 Maven 可以为你省很多事情)。相关阅读:[Maven核心概念总结](https://javaguide.cn/tools/maven/maven-core-concepts.html)。
+- **Git**:基本的 Git 技能也是必备的,试着在学习的过程中将自己的代码托管在 Github 上。相关阅读:[Git核心概念总结](https://javaguide.cn/tools/git/git-intro.html)。
+- **Docker**:学着用 Docker 安装学习中需要用到的软件比如 MySQL ,这样方便很多,可以为你节省不少时间。相关资料:[《Docker - 从入门到实践》](https://yeasy.gitbook.io/docker_practice/) 。
+
+除了这些工具之外,我强烈建议你一定要搞懂 GitHub 的使用。一些使用 GitHub 的小技巧,你可以看[Github实用小技巧总结](https://javaguide.cn/tools/git/github-tips.html)这篇文章。
+
+## 常用框架
+
+框架部分建议找官方文档或者博客来看。
+
+### Spring/SpringBoot
+
+**Spring 和 SpringBoot 真的很重要!**
+
+一定要搞懂 AOP 和 IOC 这两个概念。Spring 中 bean 的作用域与生命周期、SpringMVC 工作原理详解等等知识点都是非常重要的,一定要搞懂。
+
+企业中做 Java 后端,你一定离不开 SpringBoot ,这个是必备的技能了!一定一定一定要学好!
+
+像 SpringBoot 和一些常见技术的整合你也要知识怎么做,比如 SpringBoot 整合 MyBatis、 ElasticSearch、SpringSecurity、Redis 等等。
+
+下面是一些比较推荐的书籍/专栏。
+
+**[《Spring 实战》](https://book.douban.com/subject/34949443/)**
+
+
+
+不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于 Spring 的一个概览,只有一些基本概念的介绍和示例,涵盖了 Spring 的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习 Spring,这才刚刚开始”。
+
+**[《Spring 5 高级编程》](https://book.douban.com/subject/30452637/)**
+
+
+
+对于 Spring5 的新特性介绍的比较详细,也说不上好。另外,感觉全书翻译的有一点蹩脚的味道,还有一点枯燥。全书的内容比较多,我一般拿来当做工具书参考。
+
+**[《Spring Boot 编程思想(核心篇)》](https://book.douban.com/subject/33390560/)**
+
+
+
+_稍微有点啰嗦,但是原理介绍的比较清楚。_
+
+SpringBoot 解析,不适合初学者。我是去年入手的,现在就看了几章,后面没看下去。书很厚,感觉很多很多知识点的讲解过于啰嗦和拖沓,不过,这本书对于 SpringBoot 内部原理讲解的还是很清楚。
+
+**[《Spring Boot 实战》](https://book.douban.com/subject/26857423/)**
+
+
+
+比较一般的一本书,可以简单拿来看一下。
+
+### MyBatis
+
+MyBatis 国内用的挺多的,我的建议是不需要花太多时间在上面。当然了,MyBatis 的源码还是非常值得学习的,里面有很多不错的编码实践。这里推荐两本讲解 MyBatis 源码的书籍。
+
+**[《手写 MyBatis:渐进式源码实践》](https://book.douban.com/subject/36243250/)**
+
+
+
+我的好朋友小傅哥出版的一本书。这本书以实践为核心,摒弃 MyBatis 源码中繁杂的内容,聚焦于 MyBaits 中的核心逻辑,简化代码实现过程,以渐进式的开发方式,逐步实现 MyBaits 中的核心功能。
+
+这本书的配套项目的仓库地址: 。
+
+**[《通用源码阅读指导书――MyBatis 源码详解》](https://book.douban.com/subject/35138963/)**
+
+
+
+这本书通过 MyBatis 开源代码讲解源码阅读的流程和方法!一共对 MyBatis 源码中的 300 多个类进行了详细解析,包括其背景知识、组织方式、逻辑结构、实现细节。
+
+这本书的配套示例仓库地址: 。
+
+### Netty
+
+**[《Netty 实战》](https://book.douban.com/subject/27038538/)**
+
+
+
+这本书可以用来入门 Netty ,内容从 BIO 聊到了 NIO、之后才详细介绍为什么有 Netty、Netty 为什么好用以及 Netty 重要的知识点讲解。
+
+这本书基本把 Netty 一些重要的知识点都介绍到了,而且基本都是通过实战的形式讲解。
+
+**[《Netty 进阶之路:跟着案例学 Netty》](https://book.douban.com/subject/30381214/)**
+
+
+
+内容都是关于使用 Netty 的实践案例比如内存泄露这些东西。如果你觉得你的 Netty 已经完全入门了,并且你想要对 Netty 掌握的更深的话,推荐你看一下这本书。
+
+**[《跟闪电侠学 Netty:Netty 即时聊天实战与底层原理》](https://book.douban.com/subject/35752082/)**
+
+
+
+2022 年 3 月出版的一本书。这本书分为上下两篇,上篇通过一个即时聊天系统的实战案例带你入门 Netty,下篇通过 Netty 源码分析带你搞清 Netty 比较重要的底层原理。
+
+## 性能调优
+
+**[《Java 性能权威指南》](https://book.douban.com/subject/26740520/)**
+
+
+
+_希望能有更多这 Java 性能优化方面的好书!_
+
+O'Reilly 家族书,性能调优的入门书,我个人觉得性能调优是每个 Java 从业者必备知识。
+
+这本书介绍的实战内容很不错,尤其是 JVM 调优,缺点也比较明显,就是内容稍微有点老。市面上这种书很少。这本书不适合初学者,建议对 Java 语言已经比价掌握了再看。另外,阅读之前,最好先看看周志明大佬的《深入理解 Java 虚拟机》。
+
+## 网站架构
+
+看过很多网站架构方面的书籍,比如《大型网站技术架构:核心原理与案例分析》、《亿级流量网站架构核心技术》、《架构修炼之道——亿级网关、平台开放、分布式、微服务、容错等核心技术修炼实践》等等。
+
+目前我觉得能推荐的只有李运华老师的 **[《从零开始学架构》](https://book.douban.com/subject/30335935/)** 和 余春龙老师的 **[《软件架构设计:大型网站技术架构与业务架构融合之道》](https://book.douban.com/subject/30443578/ "《软件架构设计:大型网站技术架构与业务架构融合之道》")** 。
+
+
+
+《从零开始学架构》这本书对应的有一个极客时间的专栏—《从零开始学架构》,里面的很多内容都是这个专栏里面的,两者买其一就可以了。我看了很小一部分,内容挺全面的,是一本真正在讲如何做架构的书籍。
+
+
+
+事务与锁、分布式(CAP、分布式事务......)、高并发、高可用 《软件架构设计:大型网站技术架构与业务架构融合之道》 这本书都有介绍到。
+
+## 面试
+
+**《JavaGuide 面试突击版》**
+
+
+
+
+
+[JavaGuide](https://javaguide.cn/) 的面试版本,涵盖了 Java 后端方面的大部分知识点比如 集合、JVM、多线程还有数据库 MySQL 等内容。
+
+公众号后台回复:“**面试突击**” 即可免费获取,无任何套路。
+
+
diff --git a/docs/books/readme.md b/docs/books/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..a268e1a488475a5dddbb6cc19c52adf99d388748
--- /dev/null
+++ b/docs/books/readme.md
@@ -0,0 +1,19 @@
+---
+title: 技术书籍精选
+category: 计算机书籍
+---
+
+
+
+精选优质计算机书籍。
+
+开源的目的是为了大家能一起完善,如果你觉得内容有任何需要完善/补充的地方,欢迎大家在项目 [issues 区](https://github.com/CodingDocs/awesome-cs/issues) 推荐自己认可的技术书籍,让我们共同维护一个优质的技术书籍精选集!
+
+- GitHub 地址:[https://github.com/CodingDocs/awesome-cs](https://github.com/CodingDocs/awesome-cs)
+- Gitee 地址:[https://gitee.com/SnailClimb/awesome-cs](https://gitee.com/SnailClimb/awesome-cs)
+
+如果内容对你有帮助的话,欢迎给本项目点个 Star。我会用我的业余时间持续完善这份书单,感谢!
+
+本项目推荐的大部分书籍的 PDF 版本我已经整理到了云盘里,你可以在公众号“**GitHub 掘金计划**” 后台回复“**书籍**”获取到。
+
+
diff --git a/docs/books/search-engine.md b/docs/books/search-engine.md
new file mode 100644
index 0000000000000000000000000000000000000000..84124efa7efd5ddc7ec1b7baf8c35eb726fbe5b1
--- /dev/null
+++ b/docs/books/search-engine.md
@@ -0,0 +1,27 @@
+---
+title: 搜索引擎必读经典书籍
+category: 计算机书籍
+icon: "search"
+---
+
+## Lucene
+
+Elasticsearch 在 Apache Lucene 的基础上开发而成,学习 ES 之前,建议简单了解一下 Lucene 的相关概念。
+
+**[《Lucene 实战》](https://book.douban.com/subject/6440615/)** 是国内为数不多的中文版本讲 Lucene 的书籍,适合用来学习和了解 Lucene 相关的概念和常见操作。
+
+
+
+## Elasticsearch
+
+极客时间的[《Elasticsearch 核心技术与实战》](http://gk.link/a/10bcT "《Elasticsearch 核心技术与实战》")这门课程基于 Elasticsearch 7.1 版本讲解,还算比较新。并且,作者是 eBay 资深技术专家,有 20 年的行业经验,课程质量有保障!
+
+
+
+如果你想看书的话,可以考虑一下 **[《Elasticsearch 实战》](https://book.douban.com/subject/30380439/)** 这本书。不过,需要说明的是,这本书中的 Elasticsearch 版本比较老,你可以将其作为一个参考书籍来看,有一些原理性的东西可以在上面找找答案。
+
+
+
+如果你想进一步深入研究 Elasticsearch 原理的话,可以看看张超老师的 **[《Elasticsearch 源码解析与优化实战》](https://book.douban.com/subject/30386800/)** 这本书。这是市面上唯一一本写 Elasticsearch 源码的书。
+
+
diff --git a/docs/books/software-quality.md b/docs/books/software-quality.md
new file mode 100644
index 0000000000000000000000000000000000000000..5cfce79dfaab943cde8b36f1ec3bbcc93710d09f
--- /dev/null
+++ b/docs/books/software-quality.md
@@ -0,0 +1,131 @@
+---
+title: 软件质量必读经典书籍
+category: 计算机书籍
+icon: "highavailable"
+head:
+ - - meta
+ - name: keywords
+ content: 软件质量书籍精选
+---
+
+下面推荐都是我看过并且我觉得值得推荐的书籍。
+
+不过,这些书籍都比较偏理论,只能帮助你建立一个写优秀代码的意识标准。 如果你想要编写更高质量的代码、更高质量的软件,还是应该多去看优秀的源码,多去学习优秀的代码实践。
+
+## 代码整洁之道
+
+**[《重构》](https://book.douban.com/subject/30468597/)**
+
+
+
+必看书籍!无需多言。编程书籍领域的瑰宝。
+
+世界顶级、国宝级别的 Martin Fowler 的书籍,可以说是软件开发领域最经典的几本书之一。目前已经出了第二版。
+
+这是一本值得你看很多遍的书籍。
+
+**[《Clean Code》](https://book.douban.com/subject/4199741/)**
+
+
+
+《Clean Code》是 Bob 大叔的一本经典著作,强烈建议小伙伴们一定要看看。
+
+Bob 大叔将自己对整洁代码的理解浓缩在了这本书中,真可谓是对后生的一大馈赠。
+
+**[《Effective Java 》](https://book.douban.com/subject/30412517/)**
+
+
+
+《Effective Java 》这本书是 Java 领域国宝级别的书,非常经典。Java 程序员必看!
+
+这本书主要介绍了在 Java 编程中很多极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。这篇文章能够非常实际地帮助你写出更加清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。
+
+**[《代码大全》](https://book.douban.com/subject/1477390/)**
+
+
+
+其实,《代码大全(第 2 版)》这本书我本身是不太想推荐给大家了。但是,看在它的豆瓣评分这么高的份上,还是拿出来说说吧!
+
+这也是一本非常经典的书籍,第二版对第一版进行了重写。
+
+我简单地浏览过全书的内容,感觉内容总体比较虚,对于大部分程序员的作用其实不大。如果你想要切实地提高自己的代码质量,《Clean Code》和 《编写可读代码的艺术》我觉得都要比《代码大全》这本书更好。
+
+不过,最重要的还是要多看优秀的源码,多学习优秀的代码实践。
+
+**[《编写可读代码的艺术》](https://book.douban.com/subject/10797189/)**
+
+
+
+《编写可读代码的艺术》这本书要表达的意思和《Clean Code》很像,你看它俩的目录就可以看出来了。
+
+
+
+在我看来,如果你看过 《Clean Code》 的话,就不需要再看这本书了。当然,如果你有时间和精力,也可以快速过一遍。
+
+另外,我这里还要推荐一个叫做 **[write-readable-code](https://github.com/biezhi/write-readable-code)** 的仓库。这个仓库的作者免费分享了一系列基于《编写可读代码的艺术》这本书的视频。这一系列视频会基于 Java 语言来教你如何优化咱们的代码。
+
+在实践中学习的效果肯定会更好!推荐小伙伴们都抓紧学起来啊!
+
+
+
+## 程序员职业素养
+
+**[《The Clean Coder》](https://book.douban.com/subject/26919457/)**
+
+
+
+《 The Clean Coder》是 Bob 大叔的又一经典著作。
+
+《Clean Code》和《 The Clean Coder》这两本书在国内都翻译为 《代码整洁之道》,我觉得这个翻译还是不够优雅的。
+
+另外,两者的内容差异也很大。《Clean Code》这本书从代码层面来讲解如何提高自己的代码质量。而《The Clean Coder》这本书则是从如何成为一名更优秀的开发者的角度来写的,比如这书会教你如何在自己的领域更专业、如何说不、如何做时间管理、如何处理压力等等。
+
+## 架构整洁之道
+
+**[《架构整洁之道》](https://book.douban.com/subject/30333919/)**
+
+
+
+你没看错,《架构整洁之道》这本书又是 Bob 大叔的经典之作。
+
+这本书我强烈安利!认真读完之后,我保证你对编程本质、编程语言的本质、软件设计、架构设计可以有进一步的认识。
+
+国内的很多书籍和专栏都借鉴了《架构整洁之道》 这本书。毫不夸张地说,《架构整洁之道》就是架构领域最经典的书籍之一。
+
+正如作者说的那样:
+
+> 如果深入研究计算机编程的本质,我们就会发现这 50 年来,计算机编程基本没有什么大的变化。编程语言稍微进步了一点,工具的质量大大提升了,但是计算机程序的基本构造没有什么变化。
+>
+> 虽然我们有了新的编程语言、新的编程框架、新的编程范式,但是软件架构的规则仍然和 1946 年阿兰·图灵写下第一行机器代码的时候一样。
+>
+> 这本书就是为了把这些永恒不变的软件架构规则展现出来。
+
+## 项目管理
+
+**[《人月神话》](https://book.douban.com/subject/1102259/)**
+
+
+
+这本书主要描述了软件开发的基本定律:**一个需要 10 天才能干完的活,不可能让 10 个人在 1 天干完!**
+
+看书名的第一眼,感觉不像是技术类的书籍。但是,就是这样一个看似和编程不沾边的书名,却成了编程领域长久相传的经典。
+
+**这本书对于现代软件尤其是复杂软件的开发的规范化有深刻的意义。**
+
+**[《领域驱动设计:软件核心复杂性应对之道》](https://book.douban.com/subject/5344973/)**
+
+
+
+这本领域驱动设计方面的经典之作一直被各种推荐,但是我还来及读。
+
+## 其他
+
+- [《代码的未来》](https://book.douban.com/subject/24536403/):这本书的作者是 Ruby 之父松本行弘,算是一本年代比较久远的书籍(13 年出版),不过,还是非常值得一读。这本书的内容主要介绍是编程/编程语言的本质。我个人还是比较喜欢松本行弘的文字风格,并且,你看他的文章也确实能够有所收获。
+- [《深入浅出设计模式》](https://book.douban.com/subject/1488876/):比较有趣的风格,适合设计模式入门。
+- [《软件架构设计:大型网站技术架构与业务架构融合之道》](https://book.douban.com/subject/30443578/):内容非常全面。适合面试前突击一些比较重要的理论知识,也适合拿来扩充/完善自己的技术广度。
+- [《微服务架构设计模式》](https://book.douban.com/subject/33425123/):这本书是世界十大软件架构师之一、微服务架构先驱 Chris Richardson 亲笔撰写,豆瓣评分 9.6。示例代码使用 Java 语言和 Spring 框架。帮助你设计、实现、测试和部署基于微服务的应用程序。
+
+最后再推荐两个相关的文档:
+
+- **阿里巴巴 Java 开发手册**:
+- **Google Java 编程风格指南**:
diff --git a/docs/cs-basics/algorithms/10-classical-sorting-algorithms.md b/docs/cs-basics/algorithms/10-classical-sorting-algorithms.md
new file mode 100644
index 0000000000000000000000000000000000000000..9b2951eef7a560016ca8dc631e3a820d04a77891
--- /dev/null
+++ b/docs/cs-basics/algorithms/10-classical-sorting-algorithms.md
@@ -0,0 +1,739 @@
+---
+title: 十大经典排序算法总结
+category: 计算机基础
+tag:
+ - 算法
+---
+
+> 本文转自:,JavaGuide 对其做了补充完善。
+
+## 引言
+
+所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。一个优秀的算法可以节省大量的资源。在各个领域中考虑到数据的各种限制和规范,要得到一个符合实际的优秀算法,得经过大量的推理和分析。
+
+两年前,我曾在[博客园](https://www.cnblogs.com/guoyaohua/)发布过一篇[《十大经典排序算法最强总结(含 JAVA 代码实现)》](https://www.cnblogs.com/guoyaohua/p/8600214.html)博文,简要介绍了比较经典的十大排序算法,不过在之前的博文中,仅给出了 Java 版本的代码实现,并且有一些细节上的错误。所以,今天重新写一篇文章,深入了解下十大经典排序算法的原理及实现。
+
+## 简介
+
+排序算法可以分为:
+
+- **内部排序**:数据记录在内存中进行排序。
+- **[外部排序](https://zh.wikipedia.org/wiki/外排序)**:因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
+
+常见的内部排序算法有:**插入排序**、**希尔排序**、**选择排序**、**冒泡排序**、**归并排序**、**快速排序**、**堆排序**、**基数排序**等,本文只讲解内部排序算法。用一张图概括:
+
+
+
+上图存在错误:
+
+1. 插入排序的最好时间复杂度为 O(n) 而不是 O(n^2) 。
+2. 希尔排序的平均时间复杂度为 O(nlogn)
+
+**图片名词解释:**
+
+- **n**:数据规模
+- **k**:“桶” 的个数
+- **In-place**:占用常数内存,不占用额外内存
+- **Out-place**:占用额外内存
+
+### 术语说明
+
+- **稳定**:如果 A 原本在 B 前面,而 A=B,排序之后 A 仍然在 B 的前面。
+- **不稳定**:如果 A 原本在 B 的前面,而 A=B,排序之后 A 可能会出现在 B 的后面。
+- **内排序**:所有排序操作都在内存中完成。
+- **外排序**:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行。
+- **时间复杂度**:定性描述一个算法执行所耗费的时间。
+- **空间复杂度**:定性描述一个算法执行所需内存的大小。
+
+### 算法分类
+
+十种常见排序算法可以分类两大类别:**比较类排序**和**非比较类排序**。
+
+
+
+常见的**快速排序**、**归并排序**、**堆排序**以及**冒泡排序**等都属于**比较类排序算法**。比较类排序是通过比较来决定元素间的相对次序,由于其时间复杂度不能突破 `O(nlogn)`,因此也称为非线性时间比较类排序。在冒泡排序之类的排序中,问题规模为 `n`,又因为需要比较 `n` 次,所以平均时间复杂度为 `O(n²)`。在**归并排序**、**快速排序**之类的排序中,问题规模通过**分治法**消减为 `logn` 次,所以时间复杂度平均 `O(nlogn)`。
+
+比较类排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。
+
+而**计数排序**、**基数排序**、**桶排序**则属于**非比较类排序算法**。非比较排序不通过比较来决定元素间的相对次序,而是通过确定每个元素之前,应该有多少个元素来排序。由于它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。 非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度 `O(n)`。
+
+非比较排序时间复杂度底,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。
+
+## 冒泡排序 (Bubble Sort)
+
+冒泡排序是一种简单的排序算法。它重复地遍历要排序的序列,依次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历序列的工作是重复地进行直到没有再需要交换为止,此时说明该序列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢 “浮” 到数列的顶端。
+
+### 算法步骤
+
+1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
+2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
+3. 针对所有的元素重复以上的步骤,除了最后一个;
+4. 重复步骤 1~3,直到排序完成。
+
+### 图解算法
+
+
+
+### 代码实现
+
+```java
+/**
+ * 冒泡排序
+ * @param arr
+ * @return arr
+ */
+public static int[] bubbleSort(int[] arr) {
+ for (int i = 1; i < arr.length; i++) {
+ // Set a flag, if true, that means the loop has not been swapped,
+ // that is, the sequence has been ordered, the sorting has been completed.
+ boolean flag = true;
+ for (int j = 0; j < arr.length - i; j++) {
+ if (arr[j] > arr[j + 1]) {
+ int tmp = arr[j];
+ arr[j] = arr[j + 1];
+ arr[j + 1] = tmp;
+ // Change flag
+ flag = false;
+ }
+ }
+ if (flag) {
+ break;
+ }
+ }
+ return arr;
+}
+```
+
+**此处对代码做了一个小优化,加入了 `is_sorted` Flag,目的是将算法的最佳时间复杂度优化为 `O(n)`,即当原输入序列就是排序好的情况下,该算法的时间复杂度就是 `O(n)`。**
+
+### 算法分析
+
+- **稳定性**:稳定
+- **时间复杂度**:最佳:O(n) ,最差:O(n2), 平均:O(n2)
+- **空间复杂度**:O(1)
+- **排序方式**:In-place
+
+## 选择排序 (Selection Sort)
+
+选择排序是一种简单直观的排序算法,无论什么数据进去都是 `O(n²)` 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。它的工作原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
+
+### 算法步骤
+
+1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
+2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
+3. 重复第 2 步,直到所有元素均排序完毕。
+
+### 图解算法
+
+
+
+### 代码实现
+
+```java
+/**
+ * 选择排序
+ * @param arr
+ * @return arr
+ */
+public static int[] selectionSort(int[] arr) {
+ for (int i = 0; i < arr.length - 1; i++) {
+ int minIndex = i;
+ for (int j = i + 1; j < arr.length; j++) {
+ if (arr[j] < arr[minIndex]) {
+ minIndex = j;
+ }
+ }
+ if (minIndex != i) {
+ int tmp = arr[i];
+ arr[i] = arr[minIndex];
+ arr[minIndex] = tmp;
+ }
+ }
+ return arr;
+}
+```
+
+### 算法分析
+
+- **稳定性**:不稳定
+- **时间复杂度**:最佳:O(n2) ,最差:O(n2), 平均:O(n2)
+- **空间复杂度**:O(1)
+- **排序方式**:In-place
+
+## 插入排序 (Insertion Sort)
+
+插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用 in-place 排序(即只需用到 `O(1)` 的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
+
+插入排序的代码实现虽然没有冒泡排序和选择排序那么简单粗暴,但它的原理应该是最容易理解的了,因为只要打过扑克牌的人都应该能够秒懂。插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
+
+插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。
+
+### 算法步骤
+
+1. 从第一个元素开始,该元素可以认为已经被排序;
+2. 取出下一个元素,在已经排序的元素序列中从后向前扫描;
+3. 如果该元素(已排序)大于新元素,将该元素移到下一位置;
+4. 重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置;
+5. 将新元素插入到该位置后;
+6. 重复步骤 2~5。
+
+### 图解算法
+
+
+
+### 代码实现
+
+```java
+/**
+ * 插入排序
+ * @param arr
+ * @return arr
+ */
+public static int[] insertionSort(int[] arr) {
+ for (int i = 1; i < arr.length; i++) {
+ int preIndex = i - 1;
+ int current = arr[i];
+ while (preIndex >= 0 && current < arr[preIndex]) {
+ arr[preIndex + 1] = arr[preIndex];
+ preIndex -= 1;
+ }
+ arr[preIndex + 1] = current;
+ }
+ return arr;
+}
+```
+
+### 算法分析
+
+- **稳定性**:稳定
+- **时间复杂度**:最佳:O(n) ,最差:O(n2), 平均:O(n2)
+- **空间复杂度**:O(1)
+- **排序方式**:In-place
+
+## 希尔排序 (Shell Sort)
+
+希尔排序是希尔 (Donald Shell) 于 1959 年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为递减增量排序算法,同时该算法是冲破 `O(n²)` 的第一批算法之一。
+
+希尔排序的基本思想是:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录 “基本有序” 时,再对全体记录进行依次直接插入排序。
+
+### 算法步骤
+
+我们来看下希尔排序的基本步骤,在此我们选择增量 `gap=length/2`,缩小增量继续以 `gap = gap/2` 的方式,这种增量选择我们可以用一个序列来表示,`{n/2, (n/2)/2, ..., 1}`,称为**增量序列**。希尔排序的增量序列的选择与证明是个数学难题,我们选择的这个增量序列是比较常用的,也是希尔建议的增量,称为希尔增量,但其实这个增量序列不是最优的。此处我们做示例使用希尔增量。
+
+先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
+
+- 选择一个增量序列 `{t1, t2, …, tk}`,其中 `(ti>tj, i 0) {
+ for (int i = gap; i < n; i++) {
+ int current = arr[i];
+ int preIndex = i - gap;
+ // Insertion sort
+ while (preIndex >= 0 && arr[preIndex] > current) {
+ arr[preIndex + gap] = arr[preIndex];
+ preIndex -= gap;
+ }
+ arr[preIndex + gap] = current;
+
+ }
+ gap /= 2;
+ }
+ return arr;
+}
+```
+
+### 算法分析
+
+- **稳定性**:不稳定
+- **时间复杂度**:最佳:O(nlogn), 最差:O(n^2) 平均:O(nlogn)
+- **空间复杂度**:`O(1)`
+
+## 归并排序 (Merge Sort)
+
+归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法 (Divide and Conquer) 的一个非常典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为 2 - 路归并。
+
+和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是 `O(nlogn)` 的时间复杂度。代价是需要额外的内存空间。
+
+### 算法步骤
+
+归并排序算法是一个递归过程,边界条件为当输入序列仅有一个元素时,直接返回,具体过程如下:
+
+1. 如果输入内只有一个元素,则直接返回,否则将长度为 `n` 的输入序列分成两个长度为 `n/2` 的子序列;
+2. 分别对这两个子序列进行归并排序,使子序列变为有序状态;
+3. 设定两个指针,分别指向两个已经排序子序列的起始位置;
+4. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间(用于存放排序结果),并移动指针到下一位置;
+5. 重复步骤 3 ~4 直到某一指针达到序列尾;
+6. 将另一序列剩下的所有元素直接复制到合并序列尾。
+
+### 图解算法
+
+
+
+### 代码实现
+
+```java
+/**
+ * 归并排序
+ *
+ * @param arr
+ * @return arr
+ */
+public static int[] mergeSort(int[] arr) {
+ if (arr.length <= 1) {
+ return arr;
+ }
+ int middle = arr.length / 2;
+ int[] arr_1 = Arrays.copyOfRange(arr, 0, middle);
+ int[] arr_2 = Arrays.copyOfRange(arr, middle, arr.length);
+ return merge(mergeSort(arr_1), mergeSort(arr_2));
+}
+
+/**
+ * Merge two sorted arrays
+ *
+ * @param arr_1
+ * @param arr_2
+ * @return sorted_arr
+ */
+public static int[] merge(int[] arr_1, int[] arr_2) {
+ int[] sorted_arr = new int[arr_1.length + arr_2.length];
+ int idx = 0, idx_1 = 0, idx_2 = 0;
+ while (idx_1 < arr_1.length && idx_2 < arr_2.length) {
+ if (arr_1[idx_1] < arr_2[idx_2]) {
+ sorted_arr[idx] = arr_1[idx_1];
+ idx_1 += 1;
+ } else {
+ sorted_arr[idx] = arr_2[idx_2];
+ idx_2 += 1;
+ }
+ idx += 1;
+ }
+ if (idx_1 < arr_1.length) {
+ while (idx_1 < arr_1.length) {
+ sorted_arr[idx] = arr_1[idx_1];
+ idx_1 += 1;
+ idx += 1;
+ }
+ } else {
+ while (idx_2 < arr_2.length) {
+ sorted_arr[idx] = arr_2[idx_2];
+ idx_2 += 1;
+ idx += 1;
+ }
+ }
+ return sorted_arr;
+}
+```
+
+### 算法分析
+
+- **稳定性**:稳定
+- **时间复杂度**:最佳:O(nlogn), 最差:O(nlogn), 平均:O(nlogn)
+- **空间复杂度**:O(n)
+
+## 快速排序 (Quick Sort)
+
+快速排序用到了分治思想,同样的还有归并排序。乍看起来快速排序和归并排序非常相似,都是将问题变小,先排序子串,最后合并。不同的是快速排序在划分子问题的时候经过多一步处理,将划分的两组数据划分为一大一小,这样在最后合并的时候就不必像归并排序那样再进行比较。但也正因为如此,划分的不定性使得快速排序的时间复杂度并不稳定。
+
+快速排序的基本思想:通过一趟排序将待排序列分隔成独立的两部分,其中一部分记录的元素均比另一部分的元素小,则可分别对这两部分子序列继续进行排序,以达到整个序列有序。
+
+### 算法步骤
+
+快速排序使用[分治法](https://zh.wikipedia.org/wiki/分治法)(Divide and conquer)策略来把一个序列分为较小和较大的 2 个子序列,然后递回地排序两个子序列。具体算法描述如下:
+
+1. 从序列中**随机**挑出一个元素,做为 “基准”(`pivot`);
+2. 重新排列序列,将所有比基准值小的元素摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个操作结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
+3. 递归地把小于基准值元素的子序列和大于基准值元素的子序列进行快速排序。
+
+### 图解算法
+
+
+
+### 代码实现
+
+> 来源:[使用 Java 实现快速排序(详解)](https://segmentfault.com/a/1190000040022056)
+
+```java
+public static int partition(int[] array, int low, int high) {
+ int pivot = array[high];
+ int pointer = low;
+ for (int i = low; i < high; i++) {
+ if (array[i] <= pivot) {
+ int temp = array[i];
+ array[i] = array[pointer];
+ array[pointer] = temp;
+ pointer++;
+ }
+ System.out.println(Arrays.toString(array));
+ }
+ int temp = array[pointer];
+ array[pointer] = array[high];
+ array[high] = temp;
+ return pointer;
+}
+public static void quickSort(int[] array, int low, int high) {
+ if (low < high) {
+ int position = partition(array, low, high);
+ quickSort(array, low, position - 1);
+ quickSort(array, position + 1, high);
+ }
+}
+```
+
+### 算法分析
+
+- **稳定性**:不稳定
+- **时间复杂度**:最佳:O(nlogn), 最差:O(nlogn),平均:O(nlogn)
+- **空间复杂度**:O(logn)
+
+## 堆排序 (Heap Sort)
+
+堆排序是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足**堆的性质**:即**子结点的值总是小于(或者大于)它的父节点**。
+
+### 算法步骤
+
+1. 将初始待排序列 `(R1, R2, ……, Rn)` 构建成大顶堆,此堆为初始的无序区;
+2. 将堆顶元素 `R[1]` 与最后一个元素 `R[n]` 交换,此时得到新的无序区 `(R1, R2, ……, Rn-1)` 和新的有序区 (Rn), 且满足 `R[1, 2, ……, n-1]<=R[n]`;
+3. 由于交换后新的堆顶 `R[1]` 可能违反堆的性质,因此需要对当前无序区 `(R1, R2, ……, Rn-1)` 调整为新堆,然后再次将 R [1] 与无序区最后一个元素交换,得到新的无序区 `(R1, R2, ……, Rn-2)` 和新的有序区 `(Rn-1, Rn)`。不断重复此过程直到有序区的元素个数为 `n-1`,则整个排序过程完成。
+
+### 图解算法
+
+
+
+### 代码实现
+
+```java
+// Global variable that records the length of an array;
+static int heapLen;
+
+/**
+ * Swap the two elements of an array
+ * @param arr
+ * @param i
+ * @param j
+ */
+private static void swap(int[] arr, int i, int j) {
+ int tmp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = tmp;
+}
+
+/**
+ * Build Max Heap
+ * @param arr
+ */
+private static void buildMaxHeap(int[] arr) {
+ for (int i = arr.length / 2 - 1; i >= 0; i--) {
+ heapify(arr, i);
+ }
+}
+
+/**
+ * Adjust it to the maximum heap
+ * @param arr
+ * @param i
+ */
+private static void heapify(int[] arr, int i) {
+ int left = 2 * i + 1;
+ int right = 2 * i + 2;
+ int largest = i;
+ if (right < heapLen && arr[right] > arr[largest]) {
+ largest = right;
+ }
+ if (left < heapLen && arr[left] > arr[largest]) {
+ largest = left;
+ }
+ if (largest != i) {
+ swap(arr, largest, i);
+ heapify(arr, largest);
+ }
+}
+
+/**
+ * Heap Sort
+ * @param arr
+ * @return
+ */
+public static int[] heapSort(int[] arr) {
+ // index at the end of the heap
+ heapLen = arr.length;
+ // build MaxHeap
+ buildMaxHeap(arr);
+ for (int i = arr.length - 1; i > 0; i--) {
+ // Move the top of the heap to the tail of the heap in turn
+ swap(arr, 0, i);
+ heapLen -= 1;
+ heapify(arr, 0);
+ }
+ return arr;
+}
+```
+
+### 算法分析
+
+- **稳定性**:不稳定
+- **时间复杂度**:最佳:O(nlogn), 最差:O(nlogn), 平均:O(nlogn)
+- **空间复杂度**:O(1)
+
+## 计数排序 (Counting Sort)
+
+计数排序的核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,**计数排序要求输入的数据必须是有确定范围的整数**。
+
+计数排序 (Counting sort) 是一种稳定的排序算法。计数排序使用一个额外的数组 `C`,其中第 `i` 个元素是待排序数组 `A` 中值等于 `i` 的元素的个数。然后根据数组 `C` 来将 `A` 中的元素排到正确的位置。**它只能对整数进行排序**。
+
+### 算法步骤
+
+1. 找出数组中的最大值 `max`、最小值 `min`;
+2. 创建一个新数组 `C`,其长度是 `max-min+1`,其元素默认值都为 0;
+3. 遍历原数组 `A` 中的元素 `A[i]`,以 `A[i]-min` 作为 `C` 数组的索引,以 `A[i]` 的值在 `A` 中元素出现次数作为 `C[A[i]-min]` 的值;
+4. 对 `C` 数组变形,**新元素的值是该元素与前一个元素值的和**,即当 `i>1` 时 `C[i] = C[i] + C[i-1]`;
+5. 创建结果数组 `R`,长度和原始数组一样。
+6. **从后向前**遍历原始数组 `A` 中的元素 `A[i]`,使用 `A[i]` 减去最小值 `min` 作为索引,在计数数组 `C` 中找到对应的值 `C[A[i]-min]`,`C[A[i]-min]-1` 就是 `A[i]` 在结果数组 `R` 中的位置,做完上述这些操作,将 `count[A[i]-min]` 减小 1。
+
+### 图解算法
+
+
+
+### 代码实现
+
+```java
+/**
+ * Gets the maximum and minimum values in the array
+ *
+ * @param arr
+ * @return
+ */
+private static int[] getMinAndMax(int[] arr) {
+ int maxValue = arr[0];
+ int minValue = arr[0];
+ for (int i = 0; i < arr.length; i++) {
+ if (arr[i] > maxValue) {
+ maxValue = arr[i];
+ } else if (arr[i] < minValue) {
+ minValue = arr[i];
+ }
+ }
+ return new int[] { minValue, maxValue };
+}
+
+/**
+ * Counting Sort
+ *
+ * @param arr
+ * @return
+ */
+public static int[] countingSort(int[] arr) {
+ if (arr.length < 2) {
+ return arr;
+ }
+ int[] extremum = getMinAndMax(arr);
+ int minValue = extremum[0];
+ int maxValue = extremum[1];
+ int[] countArr = new int[maxValue - minValue + 1];
+ int[] result = new int[arr.length];
+
+ for (int i = 0; i < arr.length; i++) {
+ countArr[arr[i] - minValue] += 1;
+ }
+ for (int i = 1; i < countArr.length; i++) {
+ countArr[i] += countArr[i - 1];
+ }
+ for (int i = arr.length - 1; i >= 0; i--) {
+ int idx = countArr[arr[i] - minValue] - 1;
+ result[idx] = arr[i];
+ countArr[arr[i] - minValue] -= 1;
+ }
+ return result;
+}
+```
+
+## 算法分析
+
+当输入的元素是 `n` 个 `0` 到 `k` 之间的整数时,它的运行时间是 `O(n+k)`。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组 `C` 的长度取决于待排序数组中数据的范围(等于待排序数组的**最大值与最小值的差加上 1**),这使得计数排序对于数据范围很大的数组,需要大量额外内存空间。
+
+- **稳定性**:稳定
+- **时间复杂度**:最佳:`O(n+k)` 最差:`O(n+k)` 平均:`O(n+k)`
+- **空间复杂度**:`O(k)`
+
+## 桶排序 (Bucket Sort)
+
+桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
+
+1. 在额外空间充足的情况下,尽量增大桶的数量
+2. 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
+
+桶排序的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行。
+
+### 算法步骤
+
+1. 设置一个 BucketSize,作为每个桶所能放置多少个不同数值;
+2. 遍历输入数据,并且把数据依次映射到对应的桶里去;
+3. 对每个非空的桶进行排序,可以使用其它排序方法,也可以递归使用桶排序;
+4. 从非空桶里把排好序的数据拼接起来。
+
+### 图解算法
+
+
+
+### 代码实现
+
+```java
+/**
+ * Gets the maximum and minimum values in the array
+ * @param arr
+ * @return
+ */
+private static int[] getMinAndMax(List arr) {
+ int maxValue = arr.get(0);
+ int minValue = arr.get(0);
+ for (int i : arr) {
+ if (i > maxValue) {
+ maxValue = i;
+ } else if (i < minValue) {
+ minValue = i;
+ }
+ }
+ return new int[] { minValue, maxValue };
+}
+
+/**
+ * Bucket Sort
+ * @param arr
+ * @return
+ */
+public static List bucketSort(List arr, int bucket_size) {
+ if (arr.size() < 2 || bucket_size == 0) {
+ return arr;
+ }
+ int[] extremum = getMinAndMax(arr);
+ int minValue = extremum[0];
+ int maxValue = extremum[1];
+ int bucket_cnt = (maxValue - minValue) / bucket_size + 1;
+ List> buckets = new ArrayList<>();
+ for (int i = 0; i < bucket_cnt; i++) {
+ buckets.add(new ArrayList());
+ }
+ for (int element : arr) {
+ int idx = (element - minValue) / bucket_size;
+ buckets.get(idx).add(element);
+ }
+ for (int i = 0; i < buckets.size(); i++) {
+ if (buckets.get(i).size() > 1) {
+ buckets.set(i, sort(buckets.get(i), bucket_size / 2));
+ }
+ }
+ ArrayList result = new ArrayList<>();
+ for (List bucket : buckets) {
+ for (int element : bucket) {
+ result.add(element);
+ }
+ }
+ return result;
+}
+```
+
+### 算法分析
+
+- **稳定性**:稳定
+- **时间复杂度**:最佳:`O(n+k)` 最差:`O(n²)` 平均:`O(n+k)`
+- **空间复杂度**:`O(n+k)`
+
+## 基数排序 (Radix Sort)
+
+基数排序也是非比较的排序算法,对元素中的每一位数字进行排序,从最低位开始排序,复杂度为 `O(n×k)`,`n` 为数组长度,`k` 为数组中元素的最大的位数;
+
+基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
+
+### 算法步骤
+
+1. 取得数组中的最大数,并取得位数,即为迭代次数 `N`(例如:数组中最大数值为 1000,则 `N=4`);
+2. `A` 为原始数组,从最低位开始取每个位组成 `radix` 数组;
+3. 对 `radix` 进行计数排序(利用计数排序适用于小范围数的特点);
+4. 将 `radix` 依次赋值给原数组;
+5. 重复 2~4 步骤 `N` 次
+
+### 图解算法
+
+
+
+### 代码实现
+
+```java
+/**
+ * Radix Sort
+ *
+ * @param arr
+ * @return
+ */
+public static int[] radixSort(int[] arr) {
+ if (arr.length < 2) {
+ return arr;
+ }
+ int N = 1;
+ int maxValue = arr[0];
+ for (int element : arr) {
+ if (element > maxValue) {
+ maxValue = element;
+ }
+ }
+ while (maxValue / 10 != 0) {
+ maxValue = maxValue / 10;
+ N += 1;
+ }
+ for (int i = 0; i < N; i++) {
+ List> radix = new ArrayList<>();
+ for (int k = 0; k < 10; k++) {
+ radix.add(new ArrayList());
+ }
+ for (int element : arr) {
+ int idx = (element / (int) Math.pow(10, i)) % 10;
+ radix.get(idx).add(element);
+ }
+ int idx = 0;
+ for (List l : radix) {
+ for (int n : l) {
+ arr[idx++] = n;
+ }
+ }
+ }
+ return arr;
+}
+```
+
+### 算法分析
+
+- **稳定性**:稳定
+- **时间复杂度**:最佳:`O(n×k)` 最差:`O(n×k)` 平均:`O(n×k)`
+- **空间复杂度**:`O(n+k)`
+
+**基数排序 vs 计数排序 vs 桶排序**
+
+这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
+
+- 基数排序:根据键值的每位数字来分配桶
+- 计数排序:每个桶只存储单一键值
+- 桶排序:每个桶存储一定范围的数值
+
+## 参考文章
+
+-
+-
+-
diff --git "a/docs/cs-basics/algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\351\223\276\350\241\250\347\256\227\346\263\225\351\242\230.md" b/docs/cs-basics/algorithms/linkedlist-algorithm-problems.md
similarity index 74%
rename from "docs/cs-basics/algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\351\223\276\350\241\250\347\256\227\346\263\225\351\242\230.md"
rename to docs/cs-basics/algorithms/linkedlist-algorithm-problems.md
index 1b64653cd9f317b98319ca344b02f4b32aa124b4..678fed36269e70d4e74dfb760860a14079577b4f 100644
--- "a/docs/cs-basics/algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\351\223\276\350\241\250\347\256\227\346\263\225\351\242\230.md"
+++ b/docs/cs-basics/algorithms/linkedlist-algorithm-problems.md
@@ -1,4 +1,9 @@
-# 几道常见的链表算法题
+---
+title: 几道常见的链表算法题
+category: 计算机基础
+tag:
+ - 算法
+---
## 1. 两数相加
@@ -6,7 +11,7 @@
> Leetcode:给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。
>
->你可以假设除了数字 0 之外,这两个数字都不会以零开头。
+> 你可以假设除了数字 0 之外,这两个数字都不会以零开头。
示例:
@@ -18,20 +23,20 @@
### 问题分析
-Leetcode官方详细解答地址:
+Leetcode 官方详细解答地址:
- https://leetcode-cn.com/problems/add-two-numbers/solution/
+https://leetcode-cn.com/problems/add-two-numbers/solution/
-> 要对头结点进行操作时,考虑创建哑节点dummy,使用dummy->next表示真正的头节点。这样可以避免处理头节点为空的边界问题。
+> 要对头结点进行操作时,考虑创建哑节点 dummy,使用 dummy->next 表示真正的头节点。这样可以避免处理头节点为空的边界问题。
我们使用变量来跟踪进位,并从包含最低有效位的表头开始模拟逐
位相加的过程。
-
+
### Solution
-**我们首先从最低有效位也就是列表 l1和 l2 的表头开始相加。注意需要考虑到进位的情况!**
+**我们首先从最低有效位也就是列表 l1 和 l2 的表头开始相加。注意需要考虑到进位的情况!**
```java
/**
@@ -71,8 +76,8 @@ public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
## 2. 翻转链表
-
### 题目描述
+
> 剑指 offer:输入一个链表,反转链表后,输出链表的所有元素。

@@ -83,7 +88,6 @@ public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
### Solution
-
```java
public class ListNode {
int val;
@@ -97,7 +101,7 @@ public class ListNode {
```java
/**
- *
+ *
* @author Snailclimb
* @date 2018年9月19日
* @Description: TODO
@@ -157,18 +161,17 @@ public class Solution {
1
```
-## 3. 链表中倒数第k个节点
+## 3. 链表中倒数第 k 个节点
### 题目描述
-> 剑指offer: 输入一个链表,输出该链表中倒数第k个结点。
+> 剑指 offer: 输入一个链表,输出该链表中倒数第 k 个结点。
### 问题分析
-> **链表中倒数第k个节点也就是正数第(L-K+1)个节点,知道了只一点,这一题基本就没问题!**
-
-首先两个节点/指针,一个节点 node1 先开始跑,指针 node1 跑到 k-1 个节点后,另一个节点 node2 开始跑,当 node1 跑到最后时,node2 所指的节点就是倒数第k个节点也就是正数第(L-K+1)个节点。
+> **链表中倒数第 k 个节点也就是正数第(L-K+1)个节点,知道了只一点,这一题基本就没问题!**
+首先两个节点/指针,一个节点 node1 先开始跑,指针 node1 跑到 k-1 个节点后,另一个节点 node2 开始跑,当 node1 跑到最后时,node2 所指的节点就是倒数第 k 个节点也就是正数第(L-K+1)个节点。
### Solution
@@ -216,9 +219,7 @@ public class Solution {
}
```
-
-## 4. 删除链表的倒数第N个节点
-
+## 4. 删除链表的倒数第 N 个节点
> Leetcode:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
@@ -243,8 +244,7 @@ public class Solution {
### 问题分析
-
-我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)个结点,其中 L是列表的长度。只要我们找到列表的长度 L,这个问题就很容易解决。
+我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)个结点,其中 L 是列表的长度。只要我们找到列表的长度 L,这个问题就很容易解决。

@@ -291,19 +291,11 @@ public class Solution {
}
```
-**复杂度分析:**
-
-- **时间复杂度 O(L)** :该算法对列表进行了两次遍历,首先计算了列表的长度 LL 其次找到第 (L - n)(L−n) 个结点。 操作执行了 2L-n2L−n 步,时间复杂度为 O(L)O(L)。
-- **空间复杂度 O(1)** :我们只用了常量级的额外空间。
-
-
-
**进阶——一次遍历法:**
+> 链表中倒数第 N 个节点也就是正数第(L - n + 1)个节点。
-> 链表中倒数第N个节点也就是正数第(L-N+1)个节点。
-
-其实这种方法就和我们上面第四题找“链表中倒数第k个节点”所用的思想是一样的。**基本思路就是:** 定义两个节点 node1、node2;node1 节点先跑,node1节点 跑到第 n+1 个节点的时候,node2 节点开始跑.当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点(L代表总链表长度,也就是倒数第 n+1 个节点)
+其实这种方法就和我们上面第四题找“链表中倒数第 k 个节点”所用的思想是一样的。**基本思路就是:** 定义两个节点 node1、node2;node1 节点先跑,node1 节点 跑到第 n+1 个节点的时候,node2 节点开始跑.当 node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L - n ) 个节点(L 代表总链表长度,也就是倒数第 n + 1 个节点)
```java
/**
@@ -340,25 +332,21 @@ public class Solution {
}
```
-
-
-
-
## 5. 合并两个排序的链表
### 题目描述
-> 剑指offer:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
+> 剑指 offer:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
### 问题分析
-我们可以这样分析:
+我们可以这样分析:
-1. 假设我们有两个链表 A,B;
-2. A的头节点A1的值与B的头结点B1的值比较,假设A1小,则A1为头节点;
-3. A2再和B1比较,假设B1小,则,A1指向B1;
-4. A2再和B2比较
-就这样循环往复就行了,应该还算好理解。
+1. 假设我们有两个链表 A,B;
+2. A 的头节点 A1 的值与 B 的头结点 B1 的值比较,假设 A1 小,则 A1 为头节点;
+3. A2 再和 B1 比较,假设 B1 小,则,A1 指向 B1;
+4. A2 再和 B2 比较
+ 就这样循环往复就行了,应该还算好理解。
考虑通过递归的方式实现!
@@ -378,21 +366,20 @@ public class ListNode {
}*/
//https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
public class Solution {
-public ListNode Merge(ListNode list1,ListNode list2) {
- if(list1 == null){
- return list2;
- }
- if(list2 == null){
- return list1;
- }
- if(list1.val <= list2.val){
- list1.next = Merge(list1.next, list2);
- return list1;
- }else{
- list2.next = Merge(list1, list2.next);
- return list2;
- }
- }
+ public ListNode Merge(ListNode list1, ListNode list2) {
+ if (list1 == null) {
+ return list2;
+ }
+ if (list2 == null) {
+ return list1;
+ }
+ if (list1.val <= list2.val) {
+ list1.next = Merge(list1.next, list2);
+ return list1;
+ } else {
+ list2.next = Merge(list1, list2.next);
+ return list2;
+ }
+ }
}
```
-
diff --git "a/docs/cs-basics/algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\227\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" b/docs/cs-basics/algorithms/string-algorithm-problems.md
similarity index 66%
rename from "docs/cs-basics/algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\227\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md"
rename to docs/cs-basics/algorithms/string-algorithm-problems.md
index 37176650df7b5b3d1cb4ef69efbadf4a8ef551fd..518d9b6c50890af08a3b07fbd5e6e55637a45a1f 100644
--- "a/docs/cs-basics/algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\227\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md"
+++ b/docs/cs-basics/algorithms/string-algorithm-problems.md
@@ -1,36 +1,37 @@
-# 几道常见的字符串算法题
-
-> 授权转载!
+---
+title: 几道常见的字符串算法题
+category: 计算机基础
+tag:
+ - 算法
+---
+
+> 作者:wwwxmu
>
-> - 本文作者:wwwxmu
-> - 原文地址:https://www.weiweiblog.cn/13string/
-
+> 原文地址:https://www.weiweiblog.cn/13string/
## 1. KMP 算法
-谈到字符串问题,不得不提的就是 KMP 算法,它是用来解决字符串查找的问题,可以在一个字符串(S)中查找一个子串(W)出现的位置。KMP 算法把字符匹配的时间复杂度缩小到 O(m+n) ,而空间复杂度也只有O(m)。因为“暴力搜索”的方法会反复回溯主串,导致效率低下,而KMP算法可以利用已经部分匹配这个有效信息,保持主串上的指针不回溯,通过修改子串的指针,让模式串尽量地移动到有效的位置。
+谈到字符串问题,不得不提的就是 KMP 算法,它是用来解决字符串查找的问题,可以在一个字符串(S)中查找一个子串(W)出现的位置。KMP 算法把字符匹配的时间复杂度缩小到 O(m+n) ,而空间复杂度也只有 O(m)。因为“暴力搜索”的方法会反复回溯主串,导致效率低下,而 KMP 算法可以利用已经部分匹配这个有效信息,保持主串上的指针不回溯,通过修改子串的指针,让模式串尽量地移动到有效的位置。
具体算法细节请参考:
-- **字符串匹配的KMP算法:** http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
-- **从头到尾彻底理解KMP:** https://blog.csdn.net/v_july_v/article/details/7041827
-- **如何更好的理解和掌握 KMP 算法?:** https://www.zhihu.com/question/21923021
-- **KMP 算法详细解析:** https://blog.sengxian.com/algorithms/kmp
-- **图解 KMP 算法:** http://blog.jobbole.com/76611/
-- **汪都能听懂的KMP字符串匹配算法【双语字幕】:** https://www.bilibili.com/video/av3246487/?from=search&seid=17173603269940723925
-- **KMP字符串匹配算法1:** https://www.bilibili.com/video/av11866460?from=search&seid=12730654434238709250
+- [从头到尾彻底理解 KMP:](https://blog.csdn.net/v_july_v/article/details/7041827)
+- [如何更好的理解和掌握 KMP 算法?](https://www.zhihu.com/question/21923021)
+- [KMP 算法详细解析](https://blog.sengxian.com/algorithms/kmp)
+- [图解 KMP 算法](http://blog.jobbole.com/76611/)
+- [汪都能听懂的 KMP 字符串匹配算法【双语字幕】](https://www.bilibili.com/video/av3246487/?from=search&seid=17173603269940723925)
+- [KMP 字符串匹配算法 1](https://www.bilibili.com/video/av11866460?from=search&seid=12730654434238709250)
-**除此之外,再来了解一下BM算法!**
-
-> BM算法也是一种精确字符串匹配算法,它采用从右向左比较的方法,同时应用到了两种启发式规则,即坏字符规则 和好后缀规则 ,来决定向右跳跃的距离。基本思路就是从右往左进行字符匹配,遇到不匹配的字符后从坏字符表和好后缀表找一个最大的右移值,将模式串右移继续匹配。
-《字符串匹配的KMP算法》:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
+**除此之外,再来了解一下 BM 算法!**
+> BM 算法也是一种精确字符串匹配算法,它采用从右向左比较的方法,同时应用到了两种启发式规则,即坏字符规则 和好后缀规则 ,来决定向右跳跃的距离。基本思路就是从右往左进行字符匹配,遇到不匹配的字符后从坏字符表和好后缀表找一个最大的右移值,将模式串右移继续匹配。
+> 《字符串匹配的 KMP 算法》:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
## 2. 替换空格
-> 剑指offer:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
+> 剑指 offer:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为 We Are Happy.则经过替换之后的字符串为 We%20Are%20Happy。
-这里我提供了两种方法:①常规方法;②利用 API 解决。
+这里我提供了两种方法:① 常规方法;② 利用 API 解决。
```java
//https://www.weiweiblog.cn/replacespace/
@@ -68,9 +69,15 @@ public class Solution {
```
+对于替换固定字符(比如空格)的情况,第二种方法其实可以使用 `replace` 方法替换,性能更好!
+
+```java
+str.toString().replace(" ","%20");
+```
+
## 3. 最长公共前缀
-> Leetcode: 编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。
+> Leetcode: 编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。
示例 1:
@@ -87,8 +94,7 @@ public class Solution {
解释: 输入不存在公共前缀。
```
-
-思路很简单!先利用Arrays.sort(strs)为数组排序,再将数组第一个元素和最后一个元素的字符从前往后对比即可!
+思路很简单!先利用 Arrays.sort(strs)为数组排序,再将数组第一个元素和最后一个元素的字符从前往后对比即可!
```java
public class Main {
@@ -118,7 +124,7 @@ public class Main {
}
- private static boolean chechStrs(String[] strs) {
+ private static boolean checkStrs(String[] strs) {
boolean flag = false;
if (strs != null) {
// 遍历strs检查元素值
@@ -150,12 +156,10 @@ public class Main {
### 4.1. 最长回文串
-> LeetCode: 给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。在构造过程中,请注意区分大小写。比如`"Aa"`不能当做一个回文字符串。注
-意:假设字符串的长度不会超过 1010。
+> LeetCode: 给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。在构造过程中,请注意区分大小写。比如`"Aa"`不能当做一个回文字符串。注
+> 意:假设字符串的长度不会超过 1010。
-
-
-> 回文串:“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。——百度百科 地址:https://baike.baidu.com/item/%E5%9B%9E%E6%96%87%E4%B8%B2/1274921?fr=aladdin
+> 回文串:“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。——百度百科 地址:https://baike.baidu.com/item/%E5%9B%9E%E6%96%87%E4%B8%B2/1274921?fr=aladdin
示例 1:
@@ -175,7 +179,7 @@ public class Main {
- 字符出现次数为双数的组合
- **字符出现次数为偶数的组合+单个字符中出现次数最多且为奇数次的字符** (参见 **[issue665](https://github.com/Snailclimb/JavaGuide/issues/665)** )
-统计字符出现的次数即可,双数才能构成回文。因为允许中间一个数单独出现,比如“abcba”,所以如果最后有字母落单,总长度可以加 1。首先将字符串转变为字符数组。然后遍历该数组,判断对应字符是否在hashset中,如果不在就加进去,如果在就让count++,然后移除该字符!这样就能找到出现次数为双数的字符个数。
+统计字符出现的次数即可,双数才能构成回文。因为允许中间一个数单独出现,比如“abcba”,所以如果最后有字母落单,总长度可以加 1。首先将字符串转变为字符数组。然后遍历该数组,判断对应字符是否在 hashset 中,如果不在就加进去,如果在就让 count++,然后移除该字符!这样就能找到出现次数为双数的字符个数。
```java
//https://leetcode-cn.com/problems/longest-palindrome/description/
@@ -200,7 +204,6 @@ class Solution {
}
```
-
### 4.2. 验证回文串
> LeetCode: 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 说明:本题中,我们将空字符串定义为有效的回文串。
@@ -245,10 +248,9 @@ class Solution {
}
```
-
### 4.3. 最长回文子串
-> Leetcode: LeetCode: 最长回文子串 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。
+> Leetcode: LeetCode: 最长回文子串 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
@@ -298,10 +300,10 @@ class Solution {
### 4.4. 最长回文子序列
> LeetCode: 最长回文子序列
-给定一个字符串s,找到其中最长的回文子序列。可以假设s的最大长度为1000。
-**最长回文子序列和上一题最长回文子串的区别是,子串是字符串中连续的一个序列,而子序列是字符串中保持相对位置的字符序列,例如,"bbbb"可以是字符串"bbbab"的子序列但不是子串。**
+> 给定一个字符串 s,找到其中最长的回文子序列。可以假设 s 的最大长度为 1000。
+> **最长回文子序列和上一题最长回文子串的区别是,子串是字符串中连续的一个序列,而子序列是字符串中保持相对位置的字符序列,例如,"bbbb"可以是字符串"bbbab"的子序列但不是子串。**
-给定一个字符串s,找到其中最长的回文子序列。可以假设s的最大长度为1000。
+给定一个字符串 s,找到其中最长的回文子序列。可以假设 s 的最大长度为 1000。
示例 1:
@@ -311,6 +313,7 @@ class Solution {
输出:
4
```
+
一个可能的最长回文子序列为 "bbbb"。
示例 2:
@@ -324,7 +327,7 @@ class Solution {
一个可能的最长回文子序列为 "bb"。
-**动态规划:** dp[i][j] = dp[i+1][j-1] + 2 if s.charAt(i) == s.charAt(j) otherwise, dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1])
+**动态规划:** dp[i][j] = dp[i+1][j-1] + 2 if s.charAt(i) == s.charAt(j) otherwise, dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1])
```java
class Solution {
@@ -348,19 +351,21 @@ class Solution {
## 5. 括号匹配深度
> 爱奇艺 2018 秋招 Java:
->一个合法的括号匹配序列有以下定义:
->1. 空串""是一个合法的括号匹配序列
->2. 如果"X"和"Y"都是合法的括号匹配序列,"XY"也是一个合法的括号匹配序列
->3. 如果"X"是一个合法的括号匹配序列,那么"(X)"也是一个合法的括号匹配序列
->4. 每个合法的括号序列都可以由以上规则生成。
+> 一个合法的括号匹配序列有以下定义:
+>
+> 1. 空串""是一个合法的括号匹配序列
+> 2. 如果"X"和"Y"都是合法的括号匹配序列,"XY"也是一个合法的括号匹配序列
+> 3. 如果"X"是一个合法的括号匹配序列,那么"(X)"也是一个合法的括号匹配序列
+> 4. 每个合法的括号序列都可以由以上规则生成。
> 例如: "","()","()()","((()))"都是合法的括号序列
->对于一个合法的括号序列我们又有以下定义它的深度:
->1. 空串""的深度是0
->2. 如果字符串"X"的深度是x,字符串"Y"的深度是y,那么字符串"XY"的深度为max(x,y)
->3. 如果"X"的深度是x,那么字符串"(X)"的深度是x+1
+> 对于一个合法的括号序列我们又有以下定义它的深度:
+>
+> 1. 空串""的深度是 0
+> 2. 如果字符串"X"的深度是 x,字符串"Y"的深度是 y,那么字符串"XY"的深度为 max(x,y)
+> 3. 如果"X"的深度是 x,那么字符串"(X)"的深度是 x+1
-> 例如: "()()()"的深度是1,"((()))"的深度是3。牛牛现在给你一个合法的括号序列,需要你计算出其深度。
+> 例如: "()()()"的深度是 1,"((()))"的深度是 3。牛牛现在给你一个合法的括号序列,需要你计算出其深度。
```
输入描述:
@@ -386,7 +391,7 @@ import java.util.Scanner;
/**
* https://www.nowcoder.com/test/8246651/summary
- *
+ *
* @author Snailclimb
* @date 2018年9月6日
* @Description: TODO 求给定合法括号序列的深度
@@ -412,7 +417,7 @@ public class Main {
## 6. 把字符串转换成整数
-> 剑指offer: 将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。
+> 剑指 offer: 将一个字符串转换成一个整数(实现 Integer.valueOf(string)的功能,但是 string 不符合数字要求时返回 0),要求不能使用字符串转换整数的库函数。 数值为 0 或者字符串不是一个合法的数值则返回 0。
```java
//https://www.weiweiblog.cn/strtoint/
diff --git "a/docs/cs-basics/algorithms/\345\211\221\346\214\207offer\351\203\250\345\210\206\347\274\226\347\250\213\351\242\230.md" b/docs/cs-basics/algorithms/the-sword-refers-to-offer.md
similarity index 67%
rename from "docs/cs-basics/algorithms/\345\211\221\346\214\207offer\351\203\250\345\210\206\347\274\226\347\250\213\351\242\230.md"
rename to docs/cs-basics/algorithms/the-sword-refers-to-offer.md
index 790422342fc01d52b6f188f5e208effc5308fead..03ae14440a290d6d67d2e93776564f484699f95b 100644
--- "a/docs/cs-basics/algorithms/\345\211\221\346\214\207offer\351\203\250\345\210\206\347\274\226\347\250\213\351\242\230.md"
+++ b/docs/cs-basics/algorithms/the-sword-refers-to-offer.md
@@ -1,15 +1,20 @@
-# 剑指offer部分编程题
+---
+title: 剑指offer部分编程题
+category: 计算机基础
+tag:
+ - 算法
+---
## 斐波那契数列
-**题目描述:**
+**题目描述:**
-大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。
+大家都知道斐波那契数列,现在要求输入一个整数 n,请你输出斐波那契数列的第 n 项。
n<=39
**问题分析:**
-可以肯定的是这一题通过递归的方式是肯定能做出来,但是这样会有一个很大的问题,那就是递归大量的重复计算会导致内存溢出。另外可以使用迭代法,用fn1和fn2保存计算过程中的结果,并复用起来。下面我会把两个方法示例代码都给出来并给出两个方法的运行时间对比。
+可以肯定的是这一题通过递归的方式是肯定能做出来,但是这样会有一个很大的问题,那就是递归大量的重复计算会导致内存溢出。另外可以使用迭代法,用 fn1 和 fn2 保存计算过程中的结果,并复用起来。下面我会把两个方法示例代码都给出来并给出两个方法的运行时间对比。
**示例代码:**
@@ -52,24 +57,24 @@ public int Fibonacci(int n) {
**题目描述:**
-一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
+一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
**问题分析:**
正常分析法:
-> a.如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);
-> b.假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2)
-> c.由a,b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2)
+> a.如果两种跳法,1 阶或者 2 阶,那么假定第一次跳的是一阶,那么剩下的是 n-1 个台阶,跳法是 f(n-1);
+> b.假定第一次跳的是 2 阶,那么剩下的是 n-2 个台阶,跳法是 f(n-2)
+> c.由 a,b 假设可以得出总跳法为: f(n) = f(n-1) + f(n-2)
> d.然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2
找规律分析法:
-> f(1) = 1, f(2) = 2, f(3) = 3, f(4) = 5, 可以总结出f(n) = f(n-1) + f(n-2)的规律。但是为什么会出现这样的规律呢?假设现在6个台阶,我们可以从第5跳一步到6,这样的话有多少种方案跳到5就有多少种方案跳到6,另外我们也可以从4跳两步跳到6,跳到4有多少种方案的话,就有多少种方案跳到6,其他的不能从3跳到6什么的啦,所以最后就是f(6) = f(5) + f(4);这样子也很好理解变态跳台阶的问题了。
+> f(1) = 1, f(2) = 2, f(3) = 3, f(4) = 5, 可以总结出 f(n) = f(n-1) + f(n-2)的规律。但是为什么会出现这样的规律呢?假设现在 6 个台阶,我们可以从第 5 跳一步到 6,这样的话有多少种方案跳到 5 就有多少种方案跳到 6,另外我们也可以从 4 跳两步跳到 6,跳到 4 有多少种方案的话,就有多少种方案跳到 6,其他的不能从 3 跳到 6 什么的啦,所以最后就是 f(6) = f(5) + f(4);这样子也很好理解变态跳台阶的问题了。
**所以这道题其实就是斐波那契数列的问题。**
-代码只需要在上一题的代码稍做修改即可。和上一题唯一不同的就是这一题的初始元素变为 1 2 3 5 8.....而上一题为1 1 2 3 5 .......。另外这一题也可以用递归做,但是递归效率太低,所以我这里只给出了迭代方式的代码。
+代码只需要在上一题的代码稍做修改即可。和上一题唯一不同的就是这一题的初始元素变为 1 2 3 5 8.....而上一题为 1 1 2 3 5 .......。另外这一题也可以用递归做,但是递归效率太低,所以我这里只给出了迭代方式的代码。
**示例代码:**
@@ -98,20 +103,20 @@ int jumpFloor(int number) {
**题目描述:**
-一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
+一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级……它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
**问题分析:**
-假设n>=2,第一步有n种跳法:跳1级、跳2级、到跳n级
-跳1级,剩下n-1级,则剩下跳法是f(n-1)
-跳2级,剩下n-2级,则剩下跳法是f(n-2)
+假设 n>=2,第一步有 n 种跳法:跳 1 级、跳 2 级、到跳 n 级
+跳 1 级,剩下 n-1 级,则剩下跳法是 f(n-1)
+跳 2 级,剩下 n-2 级,则剩下跳法是 f(n-2)
......
-跳n-1级,剩下1级,则剩下跳法是f(1)
-跳n级,剩下0级,则剩下跳法是f(0)
-所以在n>=2的情况下:
+跳 n-1 级,剩下 1 级,则剩下跳法是 f(1)
+跳 n 级,剩下 0 级,则剩下跳法是 f(0)
+所以在 n>=2 的情况下:
f(n)=f(n-1)+f(n-2)+...+f(1)
-因为f(n-1)=f(n-2)+f(n-3)+...+f(1)
-所以f(n)=2*f(n-1) 又f(1)=1,所以可得**f(n)=2^(number-1)**
+因为 f(n-1)=f(n-2)+f(n-3)+...+f(1)
+所以 f(n)=2\*f(n-1) 又 f(1)=1,所以可得**f(n)=2^(number-1)**
**示例代码:**
@@ -123,11 +128,11 @@ int JumpFloorII(int number) {
**补充:**
-java中有三种移位运算符:
+java 中有三种移位运算符:
-1. “<<” : **左移运算符**,等同于乘2的n次方
-2. “>>”: **右移运算符**,等同于除2的n次方
-3. “>>>” : **无符号右移运算符**,不管移动前最高位是0还是1,右移后左侧产生的空位部分都以0来填充。与>>类似。
+1. “<<” : **左移运算符**,等同于乘 2 的 n 次方
+2. “>>”: **右移运算符**,等同于除 2 的 n 次方
+3. “>>>” : **无符号右移运算符**,不管移动前最高位是 0 还是 1,右移后左侧产生的空位部分都以 0 来填充。与>>类似。
```java
int a = 16;
@@ -135,7 +140,6 @@ int b = a << 2;//左移2,等同于16 * 2的2次方,也就是16 * 4
int c = a >> 2;//右移2,等同于16 / 2的2次方,也就是16 / 4
```
-
## 二维数组查找
**题目描述:**
@@ -147,8 +151,8 @@ int c = a >> 2;//右移2,等同于16 / 2的2次方,也就是16 / 4
这一道题还是比较简单的,我们需要考虑的是如何做,效率最快。这里有一种很好理解的思路:
> 矩阵是有序的,从左下角来看,向上数字递减,向右数字递增,
-> 因此从左下角开始查找,当要查找数字比左下角数字大时。右移
-> 要查找数字比左下角数字小时,上移。这样找的速度最快。
+> 因此从左下角开始查找,当要查找数字比左下角数字大时。右移
+> 要查找数字比左下角数字小时,上移。这样找的速度最快。
**示例代码:**
@@ -175,11 +179,11 @@ public boolean Find(int target, int [][] array) {
**题目描述:**
-请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
+请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为 We Are Happy.则经过替换之后的字符串为 We%20Are%20Happy。
**问题分析:**
-这道题不难,我们可以通过循环判断字符串的字符是否为空格,是的话就利用append()方法添加追加“%20”,否则还是追加原字符。
+这道题不难,我们可以通过循环判断字符串的字符是否为空格,是的话就利用 append()方法添加追加“%20”,否则还是追加原字符。
或者最简单的方法就是利用:replaceAll(String regex,String replacement)方法了,一行代码就可以解决。
@@ -198,7 +202,7 @@ public String replaceSpace(StringBuffer str) {
out.append(b);
}
}
- return out.toString();
+ return out.toString();
}
```
@@ -208,7 +212,7 @@ public String replaceSpace(StringBuffer str) {
public String replaceSpace(StringBuffer str) {
//return str.toString().replaceAll(" ", "%20");
//public String replaceAll(String regex,String replacement)
- //用给定的替换替换与给定的regular expression匹配的此字符串的每个子字符串。
+ //用给定的替换替换与给定的regular expression匹配的此字符串的每个子字符串。
//\ 转义字符. 如果你要使用 "\" 本身, 则应该使用 "\\". String类型中的空格用“\s”表示,所以我这里猜测"\\s"就是代表空格的意思
return str.toString().replaceAll("\\s", "%20");
}
@@ -218,25 +222,22 @@ public String replaceSpace(StringBuffer str) {
**题目描述:**
-给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
+给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent。求 base 的 exponent 次方。
**问题解析:**
这道题算是比较麻烦和难一点的一个了。我这里采用的是**二分幂**思想,当然也可以采用**快速幂**。
-更具剑指offer书中细节,该题的解题思路如下:
-1.当底数为0且指数<0时,会出现对0求倒数的情况,需进行错误处理,设置一个全局变量;
-2.判断底数是否等于0,由于base为double型,所以不能直接用==判断
-3.优化求幂函数(二分幂)。
-当n为偶数,a^n =(a^n/2)*(a^n/2);
-当n为奇数,a^n = a^[(n-1)/2] * a^[(n-1)/2] * a。时间复杂度O(logn)
+更具剑指 offer 书中细节,该题的解题思路如下:1.当底数为 0 且指数<0 时,会出现对 0 求倒数的情况,需进行错误处理,设置一个全局变量; 2.判断底数是否等于 0,由于 base 为 double 型,所以不能直接用==判断 3.优化求幂函数(二分幂)。
+当 n 为偶数,a^n =(a^n/2)_(a^n/2);
+当 n 为奇数,a^n = a^[(n-1)/2] _ a^[(n-1)/2] \* a。时间复杂度 O(logn)
**时间复杂度**:O(logn)
**示例代码:**
```java
-public class Solution {
- boolean invalidInput=false;
+public class Solution {
+ boolean invalidInput=false;
public double Power(double base, int exponent) {
//如果底数等于0并且指数小于0
//由于base为double型,不能直接用==判断
@@ -281,7 +282,7 @@ public class Solution {
}
```
-当然这一题也可以采用笨方法:累乘。不过这种方法的时间复杂度为O(n),这样没有前一种方法效率高。
+当然这一题也可以采用笨方法:累乘。不过这种方法的时间复杂度为 O(n),这样没有前一种方法效率高。
```java
// 使用累乘
@@ -306,17 +307,17 @@ public double powerAnother(double base, int exponent) {
**问题解析:**
这道题有挺多种解法的,给大家介绍一种我觉得挺好理解的方法:
-我们首先统计奇数的个数假设为n,然后新建一个等长数组,然后通过循环判断原数组中的元素为偶数还是奇数。如果是则从数组下标0的元素开始,把该奇数添加到新数组;如果是偶数则从数组下标为n的元素开始把该偶数添加到新数组中。
+我们首先统计奇数的个数假设为 n,然后新建一个等长数组,然后通过循环判断原数组中的元素为偶数还是奇数。如果是则从数组下标 0 的元素开始,把该奇数添加到新数组;如果是偶数则从数组下标为 n 的元素开始把该偶数添加到新数组中。
**示例代码:**
-时间复杂度为O(n),空间复杂度为O(n)的算法
+时间复杂度为 O(n),空间复杂度为 O(n)的算法
```java
public class Solution {
public void reOrderArray(int [] array) {
//如果数组长度等于0或者等于1,什么都不做直接返回
- if(array.length==0||array.length==1)
+ if(array.length==0||array.length==1)
return;
//oddCount:保存奇数个数
//oddBegin:奇数从数组头部开始添加
@@ -330,7 +331,7 @@ public class Solution {
for(int i=0;i stack1 = new Stack();
Stack stack2 = new Stack();
-
+
//当执行push操作时,将元素添加到stack1
public void push(int node) {
stack1.push(node);
}
-
+
public int pop() {
//如果两个队列都为空则抛出异常,说明用户没有push进任何元素
if(stack1.empty()&&stack2.empty()){
@@ -616,37 +617,37 @@ public class Solution {
**题目描述:**
-输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
+输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列,但 4,3,5,1,2 就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
**题目分析:**
-这道题想了半天没有思路,参考了Alias的答案,他的思路写的也很详细应该很容易看懂。
+这道题想了半天没有思路,参考了 Alias 的答案,他的思路写的也很详细应该很容易看懂。
作者:Alias
https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106
来源:牛客网
-【思路】借用一个辅助的栈,遍历压栈顺序,先讲第一个放入栈中,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1≠4,所以我们继续压栈,直到相等以后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。
+【思路】借用一个辅助的栈,遍历压栈顺序,先讲第一个放入栈中,这里是 1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是 4,很显然 1≠4,所以我们继续压栈,直到相等以后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。
举例:
-入栈1,2,3,4,5
+入栈 1,2,3,4,5
-出栈4,5,3,2,1
+出栈 4,5,3,2,1
-首先1入辅助栈,此时栈顶1≠4,继续入栈2
+首先 1 入辅助栈,此时栈顶 1≠4,继续入栈 2
-此时栈顶2≠4,继续入栈3
+此时栈顶 2≠4,继续入栈 3
-此时栈顶3≠4,继续入栈4
+此时栈顶 3≠4,继续入栈 4
-此时栈顶4=4,出栈4,弹出序列向后一位,此时为5,,辅助栈里面是1,2,3
+此时栈顶 4 = 4,出栈 4,弹出序列向后一位,此时为 5,,辅助栈里面是 1,2,3
-此时栈顶3≠5,继续入栈5
+此时栈顶 3≠5,继续入栈 5
-此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3
+此时栈顶 5=5,出栈 5,弹出序列向后一位,此时为 3,,辅助栈里面是 1,2,3
….
-依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。
+依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。
**考察内容:**
@@ -678,4 +679,4 @@ public class Solution {
return s.empty();
}
}
-```
\ No newline at end of file
+```
diff --git a/docs/cs-basics/data-structure/bloom-filter.md b/docs/cs-basics/data-structure/bloom-filter.md
index d013be7471f6f0549d6d288452fdb2d9a64bb7e4..f50def1d6661cf60fd36334da8340e489f2e8b67 100644
--- a/docs/cs-basics/data-structure/bloom-filter.md
+++ b/docs/cs-basics/data-structure/bloom-filter.md
@@ -1,11 +1,10 @@
---
+title: 布隆过滤器
category: 计算机基础
tag:
- 数据结构
---
-# 布隆过滤器
-
海量数据处理以及缓存穿透这两个场景让我认识了 布隆过滤器 ,我查阅了一些资料来了解它,但是很多现成资料并不满足我的需求,所以就决定自己总结一篇关于布隆过滤器的文章。希望通过这篇文章让更多人了解布隆过滤器,并且会实际去使用它!
下面我们将分为几个方面来介绍布隆过滤器:
@@ -21,11 +20,11 @@ tag:
首先,我们需要了解布隆过滤器的概念。
-布隆过滤器(Bloom Filter)是一个叫做 Bloom 的老哥于 1970 年提出的。我们可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。相比于我们平时常用的的 List、Map 、Set 等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。理论情况下添加到集合中的元素越多,误报的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。
+布隆过滤器(Bloom Filter)是一个叫做 Bloom 的老哥于 1970 年提出的。我们可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。相比于我们平时常用的的 List、Map、Set 等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。理论情况下添加到集合中的元素越多,误报的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。
-
+Bloom Filter 会使用一个较大的 bit 数组来保存所有的数据,数组中的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1(代表 false 或者 true),这也是 Bloom Filter 节省内存的核心所在。这样来算的话,申请一个 100w 个元素的位数组只占用 1000000Bit / 8 = 125000 Byte = 125000/1024 KB ≈ 122KB 的空间。
-位数组中的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1。这样申请一个 100w 个元素的位数组只占用 1000000Bit / 8 = 125000 Byte = 125000/1024 kb ≈ 122kb 的空间。
+
总结:**一个名叫 Bloom 的人提出了一种来检索元素是否在给定大集合中的数据结构,这种数据结构是高效且性能很好的,但缺点是具有一定的错误识别率和删除难度。并且,理论情况下,添加到集合中的元素越多,误报的可能性就越大。**
@@ -41,9 +40,9 @@ tag:
1. 对给定元素再次进行相同的哈希计算;
2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
-举个简单的例子:
+Bloom Filter 的简单原理图如下:
-
+
如图所示,当字符串存储要加入到布隆过滤器中时,该字符串首先由多个哈希函数生成不同的哈希值,然后将对应的位数组的下标设置为 1(当位数组初始化时,所有位置均为 0)。当第二次存储相同字符串时,因为先前的对应位置已设置为 1,所以很容易知道此值已经存在(去重非常方便)。
@@ -55,9 +54,10 @@ tag:
## 布隆过滤器使用场景
-1. 判断给定数据是否存在:比如判断一个数字是否存在于包含大量数字的数字集中(数字集很大,5 亿以上!)、 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤、黑名单功能等等。
-2. 去重:比如爬给定网址的时候对已经爬取过的 URL 去重。
+1. 判断给定数据是否存在:比如判断一个数字是否存在于包含大量数字的数字集中(数字集很大,上亿)、 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤(判断一个邮件地址是否在垃圾邮件列表中)、黑名单功能(判断一个IP地址或手机号码是否在黑名单中)等等。
+2. 去重:比如爬给定网址的时候对已经爬取过的 URL 去重、对巨量的 QQ号/订单号去重。
+去重场景也需要用到判断给定数据是否存在,因此布隆过滤器主要是为了解决海量数据的存在性问题。
## 编码实战
@@ -241,14 +241,14 @@ System.out.println(filter.mightContain(2));
### 介绍
-Redis v4.0 之后有了 Module(模块/插件) 功能,Redis Modules 让 Redis 可以使用外部模块扩展其功能 。布隆过滤器就是其中的 Module。详情可以查看 Redis 官方对 Redis Modules 的介绍 :https://redis.io/modules
+Redis v4.0 之后有了 Module(模块/插件) 功能,Redis Modules 让 Redis 可以使用外部模块扩展其功能 。布隆过滤器就是其中的 Module。详情可以查看 Redis 官方对 Redis Modules 的介绍:https://redis.io/modules
-另外,官网推荐了一个 RedisBloom 作为 Redis 布隆过滤器的 Module,地址:https://github.com/RedisBloom/RedisBloom
+另外,官网推荐了一个 RedisBloom 作为 Redis 布隆过滤器的 Module,地址:https://github.com/RedisBloom/RedisBloom
其他还有:
-* redis-lua-scaling-bloom-filter(lua 脚本实现):https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter
-* pyreBloom(Python 中的快速 Redis 布隆过滤器) :https://github.com/seomoz/pyreBloom
-* ......
+- redis-lua-scaling-bloom-filter(lua 脚本实现):https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter
+- pyreBloom(Python 中的快速 Redis 布隆过滤器):https://github.com/seomoz/pyreBloom
+- ......
RedisBloom 提供了多种语言的客户端支持,包括:Python、Java、JavaScript 和 PHP。
@@ -258,27 +258,29 @@ RedisBloom 提供了多种语言的客户端支持,包括:Python、Java、Ja
**具体操作如下:**
-```
+```bash
➜ ~ docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
➜ ~ docker exec -it redis-redisbloom bash
root@21396d02c252:/data# redis-cli
127.0.0.1:6379>
```
+**注意:当前rebloom镜像已经被废弃,官方推荐使用[redis-stack](https://hub.docker.com/r/redis/redis-stack)**
+
### 常用命令一览
-> 注意: key : 布隆过滤器的名称,item : 添加的元素。
+> 注意:key : 布隆过滤器的名称,item : 添加的元素。
1. **`BF.ADD`**:将元素添加到布隆过滤器中,如果该过滤器尚不存在,则创建该过滤器。格式:`BF.ADD {key} {item}`。
2. **`BF.MADD`** : 将一个或多个元素添加到“布隆过滤器”中,并创建一个尚不存在的过滤器。该命令的操作方式`BF.ADD`与之相同,只不过它允许多个输入并返回多个值。格式:`BF.MADD {key} {item} [item ...]` 。
3. **`BF.EXISTS`** : 确定元素是否在布隆过滤器中存在。格式:`BF.EXISTS {key} {item}`。
-4. **`BF.MEXISTS`** : 确定一个或者多个元素是否在布隆过滤器中存在格式:`BF.MEXISTS {key} {item} [item ...]`。
+4. **`BF.MEXISTS`**:确定一个或者多个元素是否在布隆过滤器中存在格式:`BF.MEXISTS {key} {item} [item ...]`。
-另外, `BF. RESERVE` 命令需要单独介绍一下:
+另外, `BF.RESERVE` 命令需要单独介绍一下:
这个命令的格式如下:
-`BF. RESERVE {key} {error_rate} {capacity} [EXPANSION expansion]` 。
+`BF.RESERVE {key} {error_rate} {capacity} [EXPANSION expansion]` 。
下面简单介绍一下每个参数的具体含义:
@@ -288,7 +290,7 @@ root@21396d02c252:/data# redis-cli
可选参数:
-* expansion:如果创建了一个新的子过滤器,则其大小将是当前过滤器的大小乘以`expansion`。默认扩展值为 2。这意味着每个后续子过滤器将是前一个子过滤器的两倍。
+- expansion:如果创建了一个新的子过滤器,则其大小将是当前过滤器的大小乘以`expansion`。默认扩展值为 2。这意味着每个后续子过滤器将是前一个子过滤器的两倍。
### 实际使用
diff --git "a/docs/cs-basics/data-structure/\345\233\276.md" b/docs/cs-basics/data-structure/graph.md
similarity index 48%
rename from "docs/cs-basics/data-structure/\345\233\276.md"
rename to docs/cs-basics/data-structure/graph.md
index cb9f55c1f8859c60729a3838f4745f216e7e1732..7c5db08038ad3a9e49c943ebb6e6ba1a35c580ff 100644
--- "a/docs/cs-basics/data-structure/\345\233\276.md"
+++ b/docs/cs-basics/data-structure/graph.md
@@ -1,15 +1,10 @@
---
+title: 图
category: 计算机基础
tag:
- 数据结构
---
-# 图
-
-> 开头还是求点赞,求转发!原创优质公众号,希望大家能让更多人看到我们的文章。
->
-> 图片都是我们手绘的,可以说非常用心了!
-
图是一种较为复杂的非线性结构。 **为啥说其较为复杂呢?**
根据前面的内容,我们知道:
@@ -19,35 +14,39 @@ tag:
但是,图形结构的元素之间的关系是任意的。
-**何为图呢?** 简单来说,图就是由顶点的有穷非空集合和顶点之间的边组成的集合。通常表示为:**G(V,E)**,其中,G表示一个图,V表示顶点的集合,E表示边的集合。
+**何为图呢?** 简单来说,图就是由顶点的有穷非空集合和顶点之间的边组成的集合。通常表示为:**G(V,E)**,其中,G 表示一个图,V 表示顶点的集合,E 表示边的集合。
下图所展示的就是图这种数据结构,并且还是一张有向图。
-
+
图在我们日常生活中的例子很多!比如我们在社交软件上好友关系就可以用图来表示。
## 图的基本概念
### 顶点
+
图中的数据元素,我们称之为顶点,图至少有一个顶点(非空有穷集合)
对应到好友关系图,每一个用户就代表一个顶点。
### 边
+
顶点之间的关系用边表示。
对应到好友关系图,两个用户是好友的话,那两者之间就存在一条边。
### 度
+
度表示一个顶点包含多少条边,在有向图中,还分为出度和入度,出度表示从该顶点出去的边的条数,入度表示进入该顶点的边的条数。
对应到好友关系图,度就代表了某个人的好友数量。
### 无向图和有向图
-边表示的是顶点之间的关系,有的关系是双向的,比如同学关系,A是B的同学,那么B也肯定是A的同学,那么在表示A和B的关系时,就不用关注方向,用不带箭头的边表示,这样的图就是无向图。
-有的关系是有方向的,比如父子关系,师生关系,微博的关注关系,A是B的爸爸,但B肯定不是A的爸爸,A关注B,B不一定关注A。在这种情况下,我们就用带箭头的边表示二者的关系,这样的图就是有向图。
+边表示的是顶点之间的关系,有的关系是双向的,比如同学关系,A 是 B 的同学,那么 B 也肯定是 A 的同学,那么在表示 A 和 B 的关系时,就不用关注方向,用不带箭头的边表示,这样的图就是无向图。
+
+有的关系是有方向的,比如父子关系,师生关系,微博的关注关系,A 是 B 的爸爸,但 B 肯定不是 A 的爸爸,A 关注 B,B 不一定关注 A。在这种情况下,我们就用带箭头的边表示二者的关系,这样的图就是有向图。
### 无权图和带权图
@@ -55,21 +54,25 @@ tag:
对于一个关系,如果我们既关心关系的有无,也关心关系的强度,比如描述地图上两个城市的关系,需要用到距离,那么就用带权图来表示,带权图中的每一条边一个数值表示权值,代表关系的强度。
-
+下图就是一个带权有向图。
+
+
## 图的存储
+
### 邻接矩阵存储
+
邻接矩阵将图用二维矩阵存储,是一种较为直观的表示方式。
-如果第i个顶点和第j个顶点之间有关系,且关系权值为n,则 `A[i][j]=n` 。
+如果第 i 个顶点和第 j 个顶点之间有关系,且关系权值为 n,则 `A[i][j]=n` 。
-在无向图中,我们只关心关系的有无,所以当顶点i和顶点j有关系时,`A[i][j]`=1,当顶点i和顶点j没有关系时,`A[i][j]`=0。如下图所示:
+在无向图中,我们只关心关系的有无,所以当顶点 i 和顶点 j 有关系时,`A[i][j]`=1,当顶点 i 和顶点 j 没有关系时,`A[i][j]`=0。如下图所示:
-
+
-值得注意的是:**无向图的邻接矩阵是一个对称矩阵,因为在无向图中,顶点i和顶点j有关系,则顶点j和顶点i必有关系。**
+值得注意的是:**无向图的邻接矩阵是一个对称矩阵,因为在无向图中,顶点 i 和顶点 j 有关系,则顶点 j 和顶点 i 必有关系。**
-
+
邻接矩阵存储的方式优点是简单直接(直接使用一个二维数组即可),并且,在获取两个定点之间的关系的时候也非常高效(直接获取指定位置的数组元素的值即可)。但是,这种存储方式的缺点也比较明显,那就是比较浪费空间,
@@ -77,83 +80,79 @@ tag:
针对上面邻接矩阵比较浪费内存空间的问题,诞生了图的另外一种存储方法—**邻接表** 。
-邻接链表使用一个链表来存储某个顶点的所有后继相邻顶点。对于图中每个顶点Vi,把所有邻接于Vi的顶点Vj链成一个单链表,这个单链表称为顶点Vi的 **邻接表**。如下图所示:
+邻接链表使用一个链表来存储某个顶点的所有后继相邻顶点。对于图中每个顶点 Vi,把所有邻接于 Vi 的顶点 Vj 链成一个单链表,这个单链表称为顶点 Vi 的 **邻接表**。如下图所示:
+
-
-
-
-
-
-
+
大家可以数一数邻接表中所存储的元素的个数以及图中边的条数,你会发现:
-- 在无向图中,邻接表元素个数等于边的条数的两倍,如左图所示的无向图中,边的条数为7,邻接表存储的元素个数为14。
-- 在有向图中,邻接表元素个数等于边的条数,如右图所示的有向图中,边的条数为8,邻接表存储的元素个数为8。
+- 在无向图中,邻接表元素个数等于边的条数的两倍,如左图所示的无向图中,边的条数为 7,邻接表存储的元素个数为 14。
+- 在有向图中,邻接表元素个数等于边的条数,如右图所示的有向图中,边的条数为 8,邻接表存储的元素个数为 8。
## 图的搜索
+
### 广度优先搜索
+
广度优先搜索就像水面上的波纹一样一层一层向外扩展,如下图所示:
-
+
**广度优先搜索的具体实现方式用到了之前所学过的线性数据结构——队列** 。具体过程如下图所示:
-**第1步:**
+**第 1 步:**
-
+
-**第2步:**
+**第 2 步:**
-
+
-**第3步:**
+**第 3 步:**
-
+
-**第4步:**
+**第 4 步:**
-
+
-**第5步:**
+**第 5 步:**
-
+
-**第6步:**
+**第 6 步:**
-
+
### 深度优先搜索
深度优先搜索就是“一条路走到黑”,从源顶点开始,一直走到没有后继节点,才回溯到上一顶点,然后继续“一条路走到黑”,如下图所示:
-
-
+
**和广度优先搜索类似,深度优先搜索的具体实现用到了另一种线性数据结构——栈** 。具体过程如下图所示:
-**第1步:**
-
-
+**第 1 步:**
-**第2步:**
+
-
+**第 2 步:**
-**第3步:**
+
-
+**第 3 步:**
-**第4步:**
+
-
+**第 4 步:**
-**第5步:**
+
-
+**第 5 步:**
-**第6步:**
+
-
+**第 6 步:**
+
diff --git "a/docs/cs-basics/data-structure/\345\240\206.md" b/docs/cs-basics/data-structure/heap.md
similarity index 65%
rename from "docs/cs-basics/data-structure/\345\240\206.md"
rename to docs/cs-basics/data-structure/heap.md
index f86308fafe4a9704f473d09849f11fd043c4e988..55a17569ba5acaf13d7acea0c4541ccae7e59eaa 100644
--- "a/docs/cs-basics/data-structure/\345\240\206.md"
+++ b/docs/cs-basics/data-structure/heap.md
@@ -21,54 +21,62 @@ tag:
大家可以尝试判断下面给出的图是否是堆?
-
+
-第1个和第2个是堆。第1个是最大堆,每个节点都比子树中所有节点大。第2个是最小堆,每个节点都比子树中所有节点小。
+第 1 个和第 2 个是堆。第 1 个是最大堆,每个节点都比子树中所有节点大。第 2 个是最小堆,每个节点都比子树中所有节点小。
-第3个不是,第三个中,根结点1比2和15小,而15却比3大,19比5大,不满足堆的性质。
+第 3 个不是,第三个中,根结点 1 比 2 和 15 小,而 15 却比 3 大,19 比 5 大,不满足堆的性质。
## 堆的用途
+
当我们只关心所有数据中的最大值或者最小值,存在多次获取最大值或者最小值,多次插入或删除数据时,就可以使用堆。
有小伙伴可能会想到用有序数组,初始化一个有序数组时间复杂度是 `O(nlog(n))`,查找最大值或者最小值时间复杂度都是 `O(1)`,但是,涉及到更新(插入或删除)数据时,时间复杂度为 `O(n)`,即使是使用复杂度为 `O(log(n))` 的二分法找到要插入或者删除的数据,在移动数据时也需要 `O(n)` 的时间复杂度。
-**相对于有序数组而言,堆的主要优势在于更新数据效率较高。** 堆的初始化时间复杂度为 `O(nlog(n))`,堆可以做到`O(1)`时间复杂度取出最大值或者最小值,`O(log(n))`时间复杂度插入或者删除数据,具体操作在后续章节详细介绍。
+**相对于有序数组而言,堆的主要优势在于插入和删除数据效率较高。** 因为堆是基于完全二叉树实现的,所以在插入和删除数据时,只需要在二叉树中上下移动节点,时间复杂度为 `O(log(n))`,相比有序数组的 `O(n)`,效率更高。
+
+不过,需要注意的是:Heap 初始化的时间复杂度为 `O(n)`,而非`O(nlogn)`。
## 堆的分类
堆分为 **最大堆** 和 **最小堆**。二者的区别在于节点的排序方式。
-- **最大堆** :堆中的每一个节点的值都大于等于子树中所有节点的值
-- **最小堆** :堆中的每一个节点的值都小于等于子树中所有节点的值
-如下图所示,图1是最大堆,图2是最小堆
+- **最大堆**:堆中的每一个节点的值都大于等于子树中所有节点的值
+- **最小堆**:堆中的每一个节点的值都小于等于子树中所有节点的值
-
+如下图所示,图 1 是最大堆,图 2 是最小堆
+
## 堆的存储
-之前介绍树的时候说过,由于完全二叉树的优秀性质,利用数组存储二叉树即节省空间,又方便索引(若根结点的序号为1,那么对于树中任意节点i,其左子节点序号为 `2*i`,右子节点序号为 `2*i+1`)。
+
+之前介绍树的时候说过,由于完全二叉树的优秀性质,利用数组存储二叉树即节省空间,又方便索引(若根结点的序号为 1,那么对于树中任意节点 i,其左子节点序号为 `2*i`,右子节点序号为 `2*i+1`)。
为了方便存储和索引,(二叉)堆可以用完全二叉树的形式进行存储。存储的方式如下图所示:
-
+
## 堆的操作
-堆的更新操作主要包括两种 : **插入元素** 和 **删除堆顶元素**。操作过程需要着重掌握和理解。
+
+堆的更新操作主要包括两种 : **插入元素** 和 **删除堆顶元素**。操作过程需要着重掌握和理解。
+
> 在进入正题之前,再重申一遍,堆是一个公平的公司,有能力的人自然会走到与他能力所匹配的位置
+
### 插入元素
+
> 插入元素,作为一个新入职的员工,初来乍到,这个员工需要从基层做起
**1.将要插入的元素放到最后**
-
+
> 有能力的人会逐渐升职加薪,是金子总会发光的!!!
-**2.从底向上,如果父结点比该元素大,则该节点和父结点交换,直到无法交换**
+**2.从底向上,如果父结点比该元素小,则该节点和父结点交换,直到无法交换**
-
+
-
+
### 删除堆顶元素
@@ -83,46 +91,40 @@ tag:
> 在堆这个公司中,会出现老大离职的现象,老大离职之后,他的位置就空出来了
-首先删除堆顶元素,使得数组中下标为1的位置空出。
-
-
-
-
+首先删除堆顶元素,使得数组中下标为 1 的位置空出。
+
> 那么他的位置由谁来接替呢,当然是他的直接下属了,谁能力强就让谁上呗
-比较根结点的左子节点和右子节点,也就是下标为2,3的数组元素,将较大的元素填充到根结点(下标为1)的位置。
-
-
+比较根结点的左子节点和右子节点,也就是下标为 2,3 的数组元素,将较大的元素填充到根结点(下标为 1)的位置。
+
> 这个时候又空出一个位置了,老规矩,谁有能力谁上
一直循环比较空出位置的左右子节点,并将较大者移至空位,直到堆的最底部
-
+
这个时候已经完成了自底向上的堆化,没有元素可以填补空缺了,但是,我们可以看到数组中出现了“气泡”,这会导致存储空间的浪费。接下来我们试试自顶向下堆化。
#### 自顶向下堆化
+
自顶向下的堆化用一个词形容就是“石沉大海”,那么第一件事情,就是把石头抬起来,从海面扔下去。这个石头就是堆的最后一个元素,我们将最后一个元素移动到堆顶。
-
+
然后开始将这个石头沉入海底,不停与左右子节点的值进行比较,和较大的子节点交换位置,直到无法交换位置。
-
-
-
-
+
+
### 堆的操作总结
-- **插入元素** :先将元素放至数组末尾,再自底向上堆化,将末尾元素上浮
-- **删除堆顶元素** :删除堆顶元素,将末尾元素放至堆顶,再自顶向下堆化,将堆顶元素下沉。也可以自底向上堆化,只是会产生“气泡”,浪费存储空间。最好采用自顶向下堆化的方式。
-
+- **插入元素**:先将元素放至数组末尾,再自底向上堆化,将末尾元素上浮
+- **删除堆顶元素**:删除堆顶元素,将末尾元素放至堆顶,再自顶向下堆化,将堆顶元素下沉。也可以自底向上堆化,只是会产生“气泡”,浪费存储空间。最好采用自顶向下堆化的方式。
## 堆排序
@@ -135,24 +137,24 @@ tag:
如果你已经足够了解堆化的过程,那么建堆的过程掌握起来就比较容易了。建堆的过程就是一个对所有非叶节点的自顶向下堆化过程。
-首先要了解哪些是非叶节点,最后一个节点的父结点及它之前的元素,都是非叶节点。也就是说,如果节点个数为n,那么我们需要对n/2到1的节点进行自顶向下(沉底)堆化。
+首先要了解哪些是非叶节点,最后一个节点的父结点及它之前的元素,都是非叶节点。也就是说,如果节点个数为 n,那么我们需要对 n/2 到 1 的节点进行自顶向下(沉底)堆化。
具体过程如下图:
-
+
-将初始的无序数组抽象为一棵树,图中的节点个数为6,所以4,5,6节点为叶节点,1,2,3节点为非叶节点,所以要对1-3号节点进行自顶向下(沉底)堆化,注意,顺序是从后往前堆化,从3号节点开始,一直到1号节点。
-3号节点堆化结果:
+将初始的无序数组抽象为一棵树,图中的节点个数为 6,所以 4,5,6 节点为叶节点,1,2,3 节点为非叶节点,所以要对 1-3 号节点进行自顶向下(沉底)堆化,注意,顺序是从后往前堆化,从 3 号节点开始,一直到 1 号节点。
+3 号节点堆化结果:
-
+
-2号节点堆化结果:
+2 号节点堆化结果:
-
+
-1号节点堆化结果:
+1 号节点堆化结果:
-
+
至此,数组所对应的树已经成为了一个最大堆,建堆完成!
@@ -173,26 +175,26 @@ tag:
取出第一个元素并堆化:
-
+
取出第二个元素并堆化:
-
+
取出第三个元素并堆化:
-
+
取出第四个元素并堆化:
-
+
取出第五个元素并堆化:
-
+
取出第六个元素并堆化:
-
+
堆排序完成!
diff --git "a/docs/cs-basics/data-structure/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204.md" b/docs/cs-basics/data-structure/linear-data-structure.md
similarity index 85%
rename from "docs/cs-basics/data-structure/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204.md"
rename to docs/cs-basics/data-structure/linear-data-structure.md
index 17a61ddc386b48e614fa1aa92d1b276aa177d37c..ef9c7dffc0b3245c951ce92c40d27a1dea12aeee 100644
--- "a/docs/cs-basics/data-structure/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204.md"
+++ b/docs/cs-basics/data-structure/linear-data-structure.md
@@ -1,15 +1,10 @@
---
+title: 线性数据结构
category: 计算机基础
tag:
- 数据结构
---
-# 线性数据结构 :数组、链表、栈、队列
-
-> 开头还是求点赞,求转发!原创优质公众号,希望大家能让更多人看到我们的文章。
->
-> 图片都是我们手绘的,可以说非常用心了!
-
## 1. 数组
**数组(Array)** 是一种很常见的数据结构。它由相同类型的元素(element)组成,并且是使用一块连续的内存来存储。
@@ -25,7 +20,7 @@ tag:
删除:O(n)//最坏的情况发生在删除数组的开头发生并需要移动第一元素后面所有的元素时
```
-
+
## 2. 链表
@@ -56,25 +51,25 @@ tag:
**单链表** 单向链表只有一个方向,结点只有一个后继指针 next 指向后面的节点。因此,链表这种数据结构通常在物理内存上是不连续的。我们习惯性地把第一个结点叫作头结点,链表通常有一个不保存任何值的 head 节点(头结点),通过头结点我们可以遍历整个链表。尾结点通常指向 null。
-
+
#### 2.2.2. 循环链表
**循环链表** 其实是一种特殊的单链表,和单链表不同的是循环链表的尾结点不是指向 null,而是指向链表的头结点。
-
+
#### 2.2.3. 双向链表
**双向链表** 包含两个指针,一个 prev 指向前一个节点,一个 next 指向后一个节点。
-
+
#### 2.2.4. 双向循环链表
**双向循环链表** 最后一个节点的 next 指向 head,而 head 的 prev 指向最后一个节点,构成一个环。
-
+
### 2.3. 应用场景
@@ -92,7 +87,7 @@ tag:
### 3.1. 栈简介
-**栈** (stack)只允许在有序的线性数据集合的一端(称为栈顶 top)进行加入数据(push)和移除数据(pop)。因而按照 **后进先出(LIFO, Last In First Out)** 的原理运作。**在栈中,push 和 pop 的操作都发生在栈顶。**
+**栈 (Stack)** 只允许在有序的线性数据集合的一端(称为栈顶 top)进行加入数据(push)和移除数据(pop)。因而按照 **后进先出(LIFO, Last In First Out)** 的原理运作。**在栈中,push 和 pop 的操作都发生在栈顶。**
栈常用一维数组或链表来实现,用数组实现的栈叫作 **顺序栈** ,用链表实现的栈叫作 **链式栈** 。
@@ -123,7 +118,7 @@ tag:
> 1. 左括号必须用相同类型的右括号闭合。
> 2. 左括号必须以正确的顺序闭合。
>
-> 比如 "()"、"()[]{}"、"{[]}" 都是有效字符串,而 "(]" 、"([)]" 则不是。
+> 比如 "()"、"()[]{}"、"{[]}" 都是有效字符串,而 "(]"、"([)]" 则不是。
这个问题实际是 Leetcode 的一道题目,我们可以利用栈 `Stack` 来解决这个问题。
@@ -263,7 +258,7 @@ myStack.pop();//报错:java.lang.IllegalArgumentException: Stack is empty.
### 4.1. 队列简介
-**队列** 是 **先进先出( FIFO,First In, First Out)** 的线性表。在具体应用中通常用链表或者数组来实现,用数组实现的队列叫作 **顺序队列** ,用链表实现的队列叫作 **链式队列** 。**队列只允许在后端(rear)进行插入操作也就是 入队 enqueue,在前端(front)进行删除操作也就是出队 dequeue**
+**队列(Queue)** 是 **先进先出 (FIFO,First In, First Out)** 的线性表。在具体应用中通常用链表或者数组来实现,用数组实现的队列叫作 **顺序队列** ,用链表实现的队列叫作 **链式队列** 。**队列只允许在后端(rear)进行插入操作也就是入队 enqueue,在前端(front)进行删除操作也就是出队 dequeue**
队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加。
@@ -273,7 +268,7 @@ myStack.pop();//报错:java.lang.IllegalArgumentException: Stack is empty.
插入删除:O(1)//后端插入前端删除元素
```
-
+
### 4.2. 队列分类
@@ -287,7 +282,7 @@ myStack.pop();//报错:java.lang.IllegalArgumentException: Stack is empty.
> 为了避免当只有一个元素的时候,队头和队尾重合使处理变得麻烦,所以引入两个指针,front 指针指向对头元素,rear 指针指向队列最后一个元素的下一个位置,这样当 front 等于 rear 时,此队列不是还剩一个元素,而是空队列。——From 《大话数据结构》
-
+
#### 4.2.2. 循环队列
@@ -295,21 +290,19 @@ myStack.pop();//报错:java.lang.IllegalArgumentException: Stack is empty.
还是用上面的图,我们将 rear 指针指向数组下标为 0 的位置就不会有越界问题了。当我们再向队列中添加元素的时候, rear 向后移动。
-
+
顺序队列中,我们说 `front==rear` 的时候队列为空,循环队列中则不一样,也可能为满,如上图所示。解决办法有两种:
1. 可以设置一个标志变量 `flag`,当 `front==rear` 并且 `flag=0` 的时候队列为空,当`front==rear` 并且 `flag=1` 的时候队列为满。
-2. 队列为空的时候就是 `front==rear` ,队列满的时候,我们保证数组还有一个空闲的位置,rear 就指向这个空闲位置,如下图所示,那么现在判断队列是否为满的条件就是: `(rear+1) % QueueSize= front` 。
-
-
+2. 队列为空的时候就是 `front==rear` ,队列满的时候,我们保证数组还有一个空闲的位置,rear 就指向这个空闲位置,如下图所示,那么现在判断队列是否为满的条件就是:`(rear+1) % QueueSize==front` 。
### 4.3. 常见应用场景
当我们需要按照一定顺序来处理数据的时候可以考虑使用队列这个数据结构。
- **阻塞队列:** 阻塞队列可以看成在队列基础上加了阻塞操作的队列。当队列为空的时候,出队操作阻塞,当队列满的时候,入队操作阻塞。使用阻塞队列我们可以很容易实现“生产者 - 消费者“模型。
-- **线程池中的请求/任务队列:** 线程池中没有空闲线程时,新的任务请求线程资源时,线程池该如何处理呢?答案是将这些请求放在队列中,当有空闲线程的时候,会循环中反复从队列中获取任务来执行。队列分为无界队列(基于链表)和有界队列(基于数组)。无界队列的特点就是可以一直入列,除非系统资源耗尽,比如 :`FixedThreadPool` 使用无界队列 `LinkedBlockingQueue`。但是有界队列就不一样了,当队列满的话后面再有任务/请求就会拒绝,在 Java 中的体现就是会抛出`java.util.concurrent.RejectedExecutionException` 异常。
+- **线程池中的请求/任务队列:** 线程池中没有空闲线程时,新的任务请求线程资源时,线程池该如何处理呢?答案是将这些请求放在队列中,当有空闲线程的时候,会循环中反复从队列中获取任务来执行。队列分为无界队列(基于链表)和有界队列(基于数组)。无界队列的特点就是可以一直入列,除非系统资源耗尽,比如:`FixedThreadPool` 使用无界队列 `LinkedBlockingQueue`。但是有界队列就不一样了,当队列满的话后面再有任务/请求就会拒绝,在 Java 中的体现就是会抛出`java.util.concurrent.RejectedExecutionException` 异常。
- Linux 内核进程队列(按优先级排队)
- 现实生活中的派对,播放器上的播放列表;
- 消息队列
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\233\276.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\233\276.png"
deleted file mode 100644
index 10547ded08d0ee2a364214c2c1b647ae298f4249..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\233\276.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.drawio"
deleted file mode 100644
index 72381b7ae284083c0df4e776769c7d51da8a6bad..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.png"
deleted file mode 100644
index 46b67b62e85ddf0ce2afcd6c0fe3cd5099f63489..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.drawio"
deleted file mode 100644
index f4ad6803c4f7c9cfa1d5a4d1d3449e13dfd66986..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.png"
deleted file mode 100644
index 758f84e4a101d3b3c859ddeb60e98ddb061f82ff..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.drawio"
deleted file mode 100644
index bbe5df1038248c25440aa87e35d94ecdaf0ee093..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.png"
deleted file mode 100644
index 4b254eb807742eae3aac13ab2a424627753f9d1f..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.drawio"
deleted file mode 100644
index 620445a760678ae030887913c5a710bc5e7450ca..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.png"
deleted file mode 100644
index 072bda258e056664d34c3ff4f379caf848350256..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.drawio"
deleted file mode 100644
index 8dc681fa499b670e25f88cb9840461a2506609b7..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.png"
deleted file mode 100644
index 37255d9bae65561c9585078e067d067b0aceba85..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.drawio"
deleted file mode 100644
index d1072768e6736af6282ffb42d71f346d112f66d3..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-7V1bk6O4Gf01PE4XN3F5tD12UpXdqs1MqjLzSBtss6GNF+NuO78+EhYYIWFwWwIN0dbUNggQoHM+HenwgTVr8Xb+WxYcdr+nYZRoph6eNeurZpqGoTvwDyq5XEt8Xb8WbLM4xDvdCr7H/41wYbnbKQ6jI7FjnqZJHh/IwnW630frnCgLsiz9IHfbpAl51kOwjaiC7+sgoUv/HYf57lrqme6t/O9RvN2VZzYc/7rlLSh3xndy3AVh+lErspaatcjSNL8uvZ0XUYIar2yX63Grlq3VhWXRPu9zgH6ax8Fb+u1yOv4z3oI//5F++/IF1/IeJCd8w5rpJLC++Su65PyC28H564Suc75J9/mXY4HSDO5ggsP5thEubdFfvawDXsxrWYgboarRhFcHQYQr849dnEffD8EabfmAPIJlu/wtgWsGXAyOhyuym/gchegi4iRZpEmaFRVZm03krNew/Jhn6X+i2pbQ9V8h38qTv0dZHp1bm8+oQIFsjtK3KM8ucBd8gOVgHDGRXRuvf9RogYt2NUaUZQEm4raq+YYVXMBwPQCdyYCu2cT7cIZiAK7t031ENiu87+zyA67o5crP+srXM7F2KdfOcf6jrAEuF8e8ALx2OwitlMdcrysKqUhrtD289vSUraM7N417kzzItlF+Zz+LjWUNK8DAqizLoiTI43fyclkA4jP8kcbwRiqqOJb/4vvANT0XmMDyDJI4pv6i67bpGqbt2o7hk9VfmwDXWA/n5kkM5wU4VDUVO60X26IuoTzJtf2okxQ8rJrr89S0RPQq5hR7FQeQvYpnjNyr2CKgs6YIHTAa0PkjQwdEQGdPEbqmlo8OnSMCOmOK0DWjbvRhmPvkMIzj4KicH3UNjpwxB0fA815Mrxq3mOTgyDOYWx8eHHnGi2tUwx+XHBx5NnPrQIMjTx7CgJ6EaQnJYQhjN4ZIrt8I5b6kAGZHRYKB958Evpx6lfOrPlOvapJXzfhqR92Z5nEkmd2TZGBUkjVkpbKpHiVZsyLPGpZk5emfZ1nFrJ+1LR0sqxGrj5nAkWV9jQF7VO1r9EC+/VmWgY6KRLOM5Rs+25f15Nh4PZn1S3CsObdpdkC95dLp6BJFc8yUhWPy9WKjauV0GMbwKZe+5nuav9KWQPPggqstV9p8ofkzinxwEpuTjCNnvpiR9WkyLgqSeLuHq2tIkAiWz9GUOF4HyQxveIvDMGmbk2fpaR+iGXjBPmQF4MdmJi97xCNxMXSXnmSXz7nqBDP1di49Nck22m3JMH6/eRvXouMh2PeyTXSWbVJzXu5aYFXZ9XRUMXFhUlKHA1VcqxHCgGaKzeiKxDGl3QUVz5QWn1sxhcUUfWymtJuu4pliKqbcYUrDExy9T2F5vEMxpcXFV0xhMWX0PoVl7g7FlJa0G8UUBlPc0fuUHm7wcRcc0GIevCaNuS2r0Y5wulROBVCzrSGLgniPWr84Zp0mSXA4xkVl1z12cRL+FlzSU16eplyj5hXFOr42Q6Oe/oVB5G2YT/+ctRe9bvjA6NsNGHs+/WvaY/ySsHrYrSWM8DbzOEi+Res82G/7IEojFmbp4V+lO4AKDmhyHGXLd9iCx3JKSIdjnh7wxiTalMe+pnmevuGVDLdWVWnRVGAO/8F7XCCjDcC7WcB147YO/6Hds3yR7iHyQVzgGAXH/CM6dtOoDyPuhM9dB2RYHtCGqM2VCEV2a3AjQgpbbZMUztcO9qTRngfugsCqpbu2gcXqe8WBxXIWVwquDrjOJCyjocfKLlTK2aGcng0kU06WpaeUU7hymt2p0cPygDbsDK5E+KW74u5HicP2vSzPTGF1FytZZLNHoqKSzSZqhtGccfZMFRaHY4/8QaWbAsLblUw3aQNJzTjJIJFHNy2WTaRmnF1wSSKdFm3uKOnslk5dNum0WL6Pkk7h0ml1vz8wLA9oB8lSfTERJBJJp2CfaIpYyaKbrFwspZuPPuMcXTYFmz5KNu+Fj0SySTtIqismg0Qi2RRsE00RK1lkU6UG8XjAObZs2izPR8mmeNmULDXIpt0jXXXFRJDII5u2YItoilhJIpu2ygviMNv0xs4LslVe0CiyaUuWF2TT5pEyackgkUg2WRaRer7ZBZcsyqlSgzhMOMdXTpUZNI5ySpYZZNP+kcqoJYNEHuUEgl2iKWIliWwClRbEIy2o78d3xeEo2PNRunkvfuTRTUAbSCqjlgwSiXSTZROpGWcXXLJIp8oM4uHVjq6cKjNoHOWULDMIqMygriCRSDlVZtDDWMkimyoziIdRO7ZsOoI9HyWb98JHHtl0aANJGbVkkMgjm45gl2iKWEkim47KDOJh1PpjP+B0VGrQKLrpSJYa5KjUoK4gkUg3VWrQZ+CSRTpVahAHo3Z85VSpQeMop2SpQQ5tIKkZJxkk8iinK9gmmiJWksimq1KDOBi1o8umK9jzUbJ5L3zkkU2XNpDUK5xkkEgkm+qDQQ9jJYtsSp8WNIYsui45m2R9up31c58CcVJ5P/d18YkI/dxPu5bv/vCH+gEDSNre9Ak4upN3hoVDGTlDR96FhHQ05B/I/ZliIHabM4PC4Qk2Z1QgtgWi74wbiN4D3s4EA9HrzssZFg7l0YwUiEb5qZbRoH8gr2eKkdhtvAwLh0rPGSsSfXPkSHzAuJliJHbn3AwLh0O1PvmLypCku3Sb7oPkt7RoI9T0f0Z5fsFWWXDKUxKYpiMJWyS7/MD1FSs/0QoMBbz69Vzf+PVCtHIUbqPWNsZFeRnbbbfZ9kG6LEqCPH4nT8BqZHzoHyjEa36b33jNzmj4aMf0lK0jfNQNKqoir/m+XrOi6x1SFRWYV/fzBA1ceWnAMLS5cKLt275PcsI3eHECNF5Gaf7CuGhOeBPjBHeoDb2Jte6/gM+hTX/sgq5LNOC+ArwDcIMj4M3fCxwecJ9+NC0N4Bw7+raf2+Qs/q7DSfypikTTgE7sk4YGosS/7ZfLOYv/5znREH/XHZgT9KxMGk5wpEHbd8N4zwt4dQ1URaJpQL8tIw0NRHUNbZ9g5T0v4NU1eEN3DfSnrqThBEcatL2pyrlr8HlZBlRFomkA5KWBqK6h7aMfnLuGz3Oi0TX4A1sGvsRuoiQzyKZl4HO0DBh1iQZcYt+QY9y3JcPy1gJew0SqItE0mJpV2Gd8IGbqQGkBr2GiP/QwUbmJD2sB4KgFdF2CATd0RqLb0tPmSw1OI5dA81xtPtOWK22+0PwZxY48Ouck3GSuPH5Sy3h4GyTxdg9X1xF6qA8L0JPZeB0kM7zhLQ7D4vEx65kxycomxfAtPZOVb5UZgOUzeMOi0/LL922JtHxRWfmQeWyoVgikpavNPM2bK6hQiI4OlcmACgbTSoOj7VtU+RoUYX+FFmZOsQlCONM8E8EJ/z/3MZzegg5Kq7YNaHNd8xwLHQeh92xU5MOiJSqZz7WZW5zE0OYOVbenLeHJF9pshk4yWyJGoUtz0XKdYLjm61F+cZSNLgpdEdBmoHZDAFULa7hdIr0JHusVzQBP5N96GbRzcc3oXuEpvqKj+JP5mjIiGZXt8hOQ96jsWsagXGa9HVlCZVRLFTEWZrVEG5GTxc3rVgtgDItbj3w9NIAqx1S3od3yVjpfn7L3ouUMqh0/MRakXqRbrSz43wMTP+4DOmA01cP99IAOgO66hA/opH/J8ul3Xx/tQx06Fgd97xVeQX9M/i9TZwW9D42DgabKeExgvW/pJGUbwC3rqnN0/jql17xia7MpuslakbNFf63yYHg11+Ov5VzZ9Su/EY9DTyYGPPCK5yT7Z+A0RXL8/lnwy5eqf74bDBJFp8Gww1RfSoTJEGjB1SxFSncbGsMW3P2ehhHa438=
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.png"
deleted file mode 100644
index 8b0b528bde5d20cef3a64c4be93e24b6eb4b44a8..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.drawio"
deleted file mode 100644
index 4e40d1522e7916f4a804f0ec47e0c551a9dc5304..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-3Vrfc9o4EP5r/FiP9dPyI1C4PrQznWbuLu2bwAp4zljUVgL0rz8JS8YWJqEJhDQ8gLRarWR9n1baNQEaLTd/lXy1+CJTkQcwSjcB+hhACEBE9Y+RbGtJEkW1YF5mqVXaC26yX8IKndp9loqqo6ikzFW26gpnsijETHVkvCzluqt2J/PuqCs+FweCmxnPD6X/Zqla1FIG4738k8jmCzcyoEndsuRO2T5JteCpXLdEaBygUSmlqkvLzUjkZvHcutT9Jkdam4mVolCndPjnVrHB3+vP6MvP9bf1YvTrB5p8sFYeeH5vHziANNf2hlMzZbW160B/3pt5Du9koT5UO5QGWgGS1WbfqEtz8xs5G3oyUye0i9BYhHp2GkRdGa4XmRI3Kz4zLWvNIy1bqGWua0AXebWqkb3LNiI1k8jyfCRzWe4Mobs7QWczLa9UKf8TrZY0Tqaab27wB1EqsTm6fKABRbNZyKVQ5Var2A6QkLqLJTLBFtd1ixZWtGgxwsm4JeK8sbzHShcsXL8BHeyBzl/iIh2YPaBrhSxEd1n1c5fbW12JXOV7u/Jx06ltXW2TqVtnQZd3fUJia/tOpuL61PMS6cFO89Zez13elzPxyENbb6J4ORfqET3Uj2ULK9KDlZOVIucqe+hOtw9AO8JXmekHaahCEA6ThMSQxURXGOgSB0ZhFGEYA4hjTEHSNV8vgbXY3s7+IACEhLbMII+eKMToYA5ulHoBD0bZEbFZr+dzE13CrcD36FYI7roVCq7sVvAloEPvETocedAlV4aOXAI6/B6h8w/zq0NHLwEdeI/Q+bvu6vew+IX3sDPejlyA9NTtiF7zdoQZCiFrLi4QdC8uFLBu8zOvRzENY9Dcf+KkOwgGfa2vdDtib4cx5ETGHNmTr8MY5N2RSOLt5VNJgcEThi4MfPJC4F3w5SKsU4KvJsxrYr5Wr0cCvTOSDJ9IMnJVknnnShw9k2S+IYpel2QugHg5yxpmfW+1PMGyFrFOSSeckWWnpgbwVQ8/zwPF+Lksw08YOsIyDTvfttRWRqF6ZMJ+fEo7WUZdqC2el8J9acmXOsoTCXw9N4n+CAL7kZPv3U4+i8kT/vbSbhK+FY69PRd51YP4/TCsLwvqMcyEwtmM55/5VORfZZWpTBa6aSqVkkuNvlMY5NncNCjpReXVgq+MseVmbt7AhVNeZbOQl3Ugzks1KOa70aIQxCyOccIQ00FXjA0CQ0PxRsE4u5RXCxPd74x7Eb4gAO/ieJN+6ET+5tNE/u5FGezLBUwmSH/OkwsAiIaMdUDGFDSiFl8RoCEkh5yFaC8/e2YA9OVSr0kACDBkKGKQERjrb+oTAMcoYSABNGY00lxBfxghnKuwZEDsMDdESA8N0MWyQ+B4UrZa8SI4JbkX9SX3gjENdPiajIJxHAxxMIDBOAkGIBgOgjEx32wSjCfBcBQkg8iW2Ag0JdyUYFNq5+nr6R1JGmqIlEfDDq72uGzTxYq45fFMQyrKHoIvszTNj6UjS3lfpDs2Rj41z0AfSLsnBut5GdObXIS/Tx9d3b/7r4+c/T8o0Ph/
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.png"
deleted file mode 100644
index 784add44411bc3d6dc609b8792e2c5c832379030..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\345\271\277\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.drawio"
deleted file mode 100644
index 055cf29683baa54a25dcff8972d41489a48c4fb6..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.png"
deleted file mode 100644
index 5d2a50290dac828eadcf6aab77f196d4fbfc985e..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.drawio"
deleted file mode 100644
index 053d6656491066fcb1a42f8a920e0e7c685bf4e9..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.png"
deleted file mode 100644
index 2a669ee2066b253674055629781bc22879851e95..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\227\240\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.drawio"
deleted file mode 100644
index d090804e9127436d10c65aaf9759b0506dcc0ae9..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.png"
deleted file mode 100644
index 4ea176bdc2760758484337648dd6338a233eed50..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250\347\232\204\345\211\257\346\234\254.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250\347\232\204\345\211\257\346\234\254.drawio"
deleted file mode 100644
index f4a1af667631c071eb2229e2de119bcd092d5f61..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\347\237\251\351\230\265\345\255\230\345\202\250\347\232\204\345\211\257\346\234\254.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-3VlJl9soEP41HDNP+3KUbLnnvUnykvRhOrlhCUtKY+HGqC3n1wcksBbHiqfHjpfng6migKKWrwABc7KsHihcZR9IgjAwtKQC5hQYhq5rDv8TnG3D8TWtYaQ0T6RQy3jMfyDJVGJlnqB1T5ARglm+6jNjUhQoZj0epJRs+mILgvurrmCK9hiPMcT73H/zhGUN1zPclv83ytNMraw7ftOzhEpY7mSdwYRsOiwzAuaEEsKa1rKaICyMp+zSjJsd6N0pRlHBjhngf3yqXrzvwQspzRhZ0xn7uHgnZ1mzrdowSvj+JUkoy0hKCoijlhtSUhYJErNqnGpl3hOy4kydM78jxrbSmbBkhLMytsSyF1U5e+q0v4qp/rIlNa3kzDWxVUTB6PapS3RGCbIdVlNq3IIUTCpi1HSO8YRgQuvdmosFcuKY89eMkmfU6dED8eM9+3ZWRiMljdGIcU0Zr5CmiI05wWgEhek7K0g3PiCyRHxLXIAiDFn+2g9NKCM83cm1QcAbMg7+Q0wYl4yJNg6+dnp+HRO34FvvqlwrtX6FuJQrAcPBXP9w3vO481IKWKot/G5dmzjgAoa9qtpO3krFv6bm4CrNFXM/hjDmmC1iZZPlDD2uYG3fDS8b/UiA61UD5Iu8EhF1vFsT159r2phbXxFlqBp1hOw1HAnbsm5ZnqQ3nSogWVmnACjeyV1njWZlQYrTpuFboFm/HWi2j0xf56rS1z5H+hr3mL623U/fHX2p9HXO4TrzHl1n6X3XOeaFXef+UeS990Oxd5PI650jfa17TN/hweni6evvWfQsp6Txq8qBM9IV56m6lN7YDUcVjz91Tr6R6+rR3rwu2NXH36cuWXuH3nxzLb7GKLBPHQVy6CeSc53bU7p6HVa1whgUgUZROWoQSzs1/kd4Geco6/o9lvXhqfzi7yHG+IPIVT5dt2Dwluofl/S11l2/0seS86BGQCncdgRWAg3Wh0HFGV79/cF3kd/Ja+Pyar4D8rzRaPxWpNp+e/+cfaY/4uD5kfzzZZHPlrH6TtMFqsgGYQSCCEQWCGcg0EHkAK6bPwGRAby6IYFIRBIQ38hUVLSQxSPH901zH8UecpaVczGnFwHfA5EPPBf4fBVPLCqWs4HnAb/7ftQsJBFPajHdQSo9BlN161eYKhbzQ+BHohFYwJueZJOfprPfas/3yy0QuvV+Z8B3pC7c+MIULgjMjk1c0fDd2hcTEAa18KxuuELA2z+fcgRmfZTpJ7E85XQzXrIgztOCkzFPacT5ocDzPIY4kB3LPEnwoRrTB0MM5wiHMH5Oa/5w8RaLdEvSHZAJ69+JLpDeoNJY+5Vm91G1W2qGh4cjSg0n2w+wTa62n7HN6Cc=
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.drawio"
deleted file mode 100644
index a2ee5e47f227a9ee299f22c7d18431ef0f74eadd..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.png"
deleted file mode 100644
index 6fd510403971121c7a63c1dc7455400e851ad1d3..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\234\211\345\220\221\345\233\276\347\232\204\351\202\273\346\216\245\350\241\250\345\255\230\345\202\250.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.drawio"
deleted file mode 100644
index bceea69589d052d806e4e8974b265755c99f81fc..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.png"
deleted file mode 100644
index ef97be64bb5a085aae57dcd9a9c7ace8fc0a7330..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2421.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.drawio"
deleted file mode 100644
index a25027440d05bca518d52f3ef7548151c35d0169..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.png"
deleted file mode 100644
index a7074d37b2808a9bd5e469913dfb21b01583ab97..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2422.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.drawio"
deleted file mode 100644
index 0c727214ab0c1a0597d91a62c6d7b81b198a40fc..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.png"
deleted file mode 100644
index 063d9ca8a6c90bcd76bfb0abc08e9ec653f82ca1..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2423.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.drawio"
deleted file mode 100644
index b115c61ee2da92a3c96b08f441ef158ec6f354be..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.png"
deleted file mode 100644
index 72456379dc57f4638e8f404755d13d6f470ce6c6..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2424.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.drawio"
deleted file mode 100644
index a5a6d89bd82a139554e4b8389d9559cee4fe29d6..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.png"
deleted file mode 100644
index 0a9d4084789a045c3d02ad9e4f1cc1f57d83cd85..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2425.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.drawio"
deleted file mode 100644
index dcf6c68ef1813da5e32f4f82551722d56356dfdb..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.png"
deleted file mode 100644
index 5393b0c06feb75f7858254bd493fba0e42258bad..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\2426.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.drawio" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.drawio"
deleted file mode 100644
index ae77016546086d9780c0aaa63139630b11262ab4..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-3VpLk6M2EP41HMclifcRv5JDtmoqU6nNHhkjYzYYsUKewfn1kYzEy2Dj8TOew1hqdUsgfV833aDpk3X+G/XT1TcS4FhDIMg1faohBCGw+I+QbAuJC0AhCGkUSKVK8Bb9i6VQqW2iAGcNRUZIzKK0KVyQJMEL1pD5lJLPptqSxM1VUz/Ee4K3hR/vS79HAVsVUgfZlfx3HIUrtTK03GJk7StleSfZyg/IZ02kzzR9QglhRWudT3AsNk/tS2E37xktL4zihA0xCN0cvE1oDl+nKZn+lX3/Mwxf5CwZ26obxgG/f9kllK1ISBI/nlXS8WJDP7CYFPIOJZsk2PUA71UGfxCSSpWfmLGtPFl/wwgXrdg6lqM4j9jf0ly0f4j2yJS9aV4bmm5VJ2F0K4xewAhAJSgsLegoQWW86zWsXzGN1phhKoUZo+QfPCExobt90Odznf/xkWKHxLb0brzaRbKhC3xgt5EEsE9DzA7ouSU8OK8w4ddJt9yO4thn0UfzOnwJ8LDUk6Yepf62ppCSKGFZbeZXIeAKiqu2BKpkKnRaeDqir+tuXZ83iitQvdqtVKIdRk/Aq9zBDz/eyG3QkBXzvRy/N2Bs/doIXo2XJGEv2Q57HldAZppXg7wVil+g5uCX9K6E+8SIY+50BAE+VxHDb6m/O+tP7veaiPaztPBEyygXzBgvoziuIWu5xNZi0YW5wHbfASgX/8CU4fww6vZRIg1Kv6OO05D9z5obk6JVzYMpWReuamd7+tHpHUfX3uIk8ITP5r2EJLjlKBTpQYPwx8gu3Qs8wb1ckPEqjB2lvNl9mLXDMjsOS8lO8wx7VDZcd+S6po0c20Sm7gC7CR3E3SwwkA2RYRsWdPXmAsUuyDkPeAzDtkamVZunBVB9ZOjVRcDWKsUW7q1yKc9iPE4kLOH5o47Ow5FQGg2lxU3CnTkQ+xB0g38wqs/yS+Y1Qgp6xpBiwmZIKR/S7xVSrIfhbD28wC8xFj4CY+3zotVtCGtfg7D6MxJWbz2i8+z7voR1HoawzSD7P6asO5Cy9pmU/VJOiawmAA1wOKc04EH96+SU7jX8ifGM/qSdU97dnyi4XPbs4DOeXTsW3L0eAGHH2Z1SELigEx1amOvLVG6UphvOCDll/oxUhlw+jncPn5qmmwYc2bBMxO1mmo5ao47dXOXKaTrsqgDeCTVDQy+6J2j0VkiFbovQQ2GhO0cmuvbJn11APOdNQ+stQ/kc2PPod0GUDc3Jel4a3AZlqP0yAHwRZe2J+Ey3RZlxKZRdIME4VsW7IMoGF6p78ogbObOWD9KNrzozeGSiHpidmgC1ExqVEF01oYFdJc1zPeVABN/PTw6tNt8VwEY7g2q7t8HvUtARh3ttP2k9CsYe0EfeNRQ/D8T6C71Z6ieD8nvQld9rM1sbe5qHZGM812aO5kHNc4TEM4RwZmn8Aded7HSMnbKjjfkoVza18UxzyhLPO61m3rdyxcxiQlP8d7j5XBtPNNcDsuVMjLKlly1UturliOLGeyoSDOesSbJmRUGSsF5+kCI/jsKEdxecE+KTmLEoNUQLP/bkwDoKgriv1tEsygZ+tirrtdr5hYsSv6omqPo1OnVWLhDop05P5YJ3qw+yChxXn7Xps/8A
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.png" "b/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.png"
deleted file mode 100644
index 95655ea4138e7aa2dfdf6a0dde8cd48a9dacc0a9..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\345\233\276/\346\267\261\345\272\246\344\274\230\345\205\210\346\220\234\347\264\242\345\233\276\347\244\272.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2401.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2401.png"
index 63381b522c160d3386a62e30fdd8f77fd2c56387..f150d59686893e80e75c1460de1ad949dc9698fc 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2401.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2401.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2402.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2402.png"
index c23bbfa8cda8ec5ccc9fee60dd0c777b07ed1810..46a46a0a927b7f3a2664ab639129a1f1f5057f75 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2402.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2402.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2403.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2403.png"
index 46fdb57bd9c909968bc9b2fe585d950cfeb8b9be..2a6d06d3b1b308d960cb7b9828e162402b58ca03 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2403.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2403.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2404.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2404.png"
index a2e3a937acfe1abd4a5719e0863fe22e0e6fa133..fa36b2b1b307a582d5af7d203e35df9fb4117cf2 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2404.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2404.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2405.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2405.png"
index 1129eeac76573ad9c2d3d47d75e1d4348719dc64..586f6c1ed02482fe56f9a8186e718734246a6a95 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2405.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2405.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2406.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2406.png"
index d18889c3e55c3611d8077901fc79cdd515af8c2f..e13ec00d80f7b96ceea9d96f252bdd33c6c26cf0 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2406.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\210\240\351\231\244\345\240\206\351\241\266\345\205\203\347\264\2406.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206-\346\217\222\345\205\245\345\205\203\347\264\2401.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206-\346\217\222\345\205\245\345\205\203\347\264\2401.png"
index 424fe3d91c04e9824bb86342c7e4662d543a7d6f..e8268a9754a3b505edf0d45336004d66e90a49c0 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206-\346\217\222\345\205\245\345\205\203\347\264\2401.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206-\346\217\222\345\205\245\345\205\203\347\264\2401.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206-\346\217\222\345\205\245\345\205\203\347\264\2402.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206-\346\217\222\345\205\245\345\205\203\347\264\2402.png"
index f58aa672c3abf5f87f357c6e3664fe7dc4a7945e..d670321d03ea1293c42491473d13eea650b92122 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206-\346\217\222\345\205\245\345\205\203\347\264\2402.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206-\346\217\222\345\205\245\345\205\203\347\264\2402.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206-\346\217\222\345\205\245\345\205\203\347\264\2403.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206-\346\217\222\345\205\245\345\205\203\347\264\2403.png"
index 24977de86a4a49a17f53178e4d8659ae2400d2ae..37ef1fc15628b8592c3fbe4e5ab46b7017db6c30 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206-\346\217\222\345\205\245\345\205\203\347\264\2403.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206-\346\217\222\345\205\245\345\205\203\347\264\2403.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\2061.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\2061.png"
index fa08e776ec081c3c54a7f47d8018b972c19e1df5..488741f77ab70cd02b49d9e056ce1ef2695f2396 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\2061.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\2061.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\2062.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\2062.png"
index ec8a89d0943bffe3d4d2272ecb26a4ba7597cf51..4b7e63f73ae242594b7aa33cf16a1eef5ddaa7ee 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\2062.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\2062.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2171.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2171.png"
index 655aa65a38cfc1090d785cfb46afb170de75fa78..74fc70615378c74cbc04d21782f44030d20a0951 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2171.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2171.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2172.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2172.png"
index fe60da909c5409cd8108b6ffa50069df1a9c5900..dcb57d6a57e6ff9333f9e3eeccb42c1c04910511 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2172.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2172.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2173.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2173.png"
index 61633b1d3d9c828c9703c96ae034a13cc68177bb..bd028d955ee2652aedbd113537a443bd9ce1c145 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2173.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2173.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2174.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2174.png"
index e502b808f20923d176ec5f0ac59e60941db5dba6..4705d9db82eeb5b6ef3fbf7f825ab22950bd915b 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2174.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2174.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2175.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2175.png"
index 9d2878583050b9436109eb5907bdb9157b2efbd4..87f8816fdfffb615e5d86936e56fc7414a32b68e 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2175.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2175.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2176.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2176.png"
index 85bf1308cfba1e4b7d829f65d9fadf098b81c702..8f20179d7e232e620b279082015daa0032c696b4 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2176.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\346\216\222\345\272\2176.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\347\232\204\345\255\230\345\202\250.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\347\232\204\345\255\230\345\202\250.png"
index de77a9af2abf236868dccfa7d95f83354587e858..d7a4d6a85a502e0949aea9557071ecc172c14c60 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\347\232\204\345\255\230\345\202\250.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\240\206\347\232\204\345\255\230\345\202\250.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2061.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2061.png"
index f69153d0c066bbe8eeabda69eb83e1dfa953cf66..f97602b81aedf97deab4a8dce769f9ab2f331ef7 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2061.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2061.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2062.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2062.png"
index fcbf71ca5a9aa941716e3a543ef7eba433a535c3..e9038d64de029262b9dbce03dfafb74cd990fbc7 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2062.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2062.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2063.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2063.png"
index c4f890b18f8f3e0bb50f484e16424b023ab718c4..8d8b3ff271d32f3b8ce70e65186eab257aaad5f7 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2063.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2063.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2064.png" "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2064.png"
index 6d6c57fdc0c0cec41a1766abe890bed37b9f56c2..b0265b8d1b1bf1a3fa23364df36b2404bc884b9e 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2064.png" and "b/docs/cs-basics/data-structure/pictures/\345\240\206/\345\273\272\345\240\2064.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\344\270\255\345\272\217\351\201\215\345\216\206.drawio" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\344\270\255\345\272\217\351\201\215\345\216\206.drawio"
deleted file mode 100644
index f8fe81d2c06ef9658666cea3abc0481d47ebae6d..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\346\240\221/\344\270\255\345\272\217\351\201\215\345\216\206.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-7Vtbk6I4FP41eZwukgBJHr1g727tVk1VV+30PNKSVmbQuIit7q/fBAISBLVbFHfa8sHkJORyzpcv5xwR4MFs8xj7i+lfIuARQFawAXgIEILQcuWXkmwzCWMkE0ziMNCddoKn8F+uhZaWrsKAL42OiRBREi5M4VjM53ycGDI/jsXa7PYqInPWhT/he4KnsR/tS7+FQTLNpBSRnfw3Hk6m+czQZVnLzM87650sp34g1iUR9gAexEIkWWm2GfBIKS/XS/bcqKG1WFjM58kpD6yenT8ev/39O3VCf/C2ZpuF7X/Ro7z50UpvGCA3kuP1X9SSk63Wg/vPSq2z/yrmyZdlaqWe7ICcxWbXKEsT9d3Lx5CLecmFWgnFiEiuThpRVvrraZjwp4U/Vi1riSMpmyazSNagLPrLRWbZ13DDA7WIMIoGIhJxOhB+feXueCzlyyQWP3mpJSDsxbKKyd94nPBNo/pgYRSJZi5mPIm3sot+gGBtRw1kzHR9XYKFFk1LiMhlvgbipBh5ZytZ0OZ6h+lQjemqKp4HPXUGZG0u5txUq9x3vH0uV76XK0O1b6uobXUtm4MHe6emoke5DrGKx/zABrA+zn484ckxjO7bpaR3p0bvuSzmkZ+Eb+Zy64yhZ/gqQrmRndktYpidWBV7ZtvUT5WPX3Ug5jwgaiMCkU1sN1+JHtYm1GzF5iyZkvZmSYFT6OTjWMKNNLBc+POPM0GJTDLBoJ4aClk23a/BGJSajGF3zRj2tazc/0RWdq0bs7JzLSsPP5GVHWRa2SEdW9m9lpUfP5GVmXtjVibn+nibMHkulb/nPp0s7xw8Vcn9u8IrLCpX9grt/4NX6FaAQuAHvUKKqIk4hq7q+NFzAVYbRFhH4JLD0irBEh6EZQcAc7oEGKneN+yjYYdDD4UdrNOwg52JvhZR4Z6ICtwlKiiCDy7GDrapdEgQNI3pUPjAIGO27AZtNw9c301J2D2AGIdada1XAkxOs5d3e7xP5Pa49MbcHliXljzX7zl8wdT7Pccusi78HqvelldyfMw8FWQmcCB8INAhiBIHOZhWhj/5zrJqh8nvLFq7hGsxEGpkIEU0LTKQfYSBsukaGEiSQWIeCZNB9JEp040W+VE4mcvqWCKYS3lfUUs49qOebpiFQRA1cVssVvNAMdmwLW5ilfSKtc9Ndg3e0cW4qTlX2jIA0B0AilOcyuXUOQCa06gtA8C9AyD1R28NAM0Z1pYBAO8ASC/8CgBo1wBw971R6QI+6arWoqkHESdTMRFzP/pTiIU2yA+eJFv9zoO/SkRDbsV6cDpKxuVvXHwwGXeys3meOeqypBcJCkefKCik1q0Fha3lKt+V2n5/Cr3N80dOPICdpqWgZQZsxIwKXYjrWt8bFbKDmUwJzy7zUnWZzIu4BM7dJZAthNyYS5APfHkAkDsAFBlUg4LOAdD8JmXLAMB3AKhLxb41ADQnBs/zQod9rzcalF/DOOhm/jIWJpXf2N3OLYz3/c1LhH35j+NZ1HdtlzPH8VGXM+vYVdCHarJwngP6Huh5wLOBjNV6EHguYBZgA+AhQNOCycnjInbanToZXzGG8f5BfAyT6epFjUk9wCjwGKAEMDkLVZOq6RxAKWCoiY31KoYFp8en0AK0a+8FORnrA+apQs8GdNjKJr8OR0dXL/crNSA9EbXfEWCuXotUvlIFAT1c0glRBUZSWwxAv5d2HqUFojrkx/rmOCzyX3jU98c/J6m8OrlUjT7D0Nb1UijeTz8theLQZEIb7zNhQXtnUqGs7v6SkoVHuz/2YO8/
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\344\270\255\345\272\217\351\201\215\345\216\206.png" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\344\270\255\345\272\217\351\201\215\345\216\206.png"
deleted file mode 100644
index 3ad5782c8bde94ea9d99f36987b69e2578f20024..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\346\240\221/\344\270\255\345\272\217\351\201\215\345\216\206.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\344\270\255\345\272\217\351\201\215\345\216\2062.drawio" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\344\270\255\345\272\217\351\201\215\345\216\2062.drawio"
deleted file mode 100644
index 31e3097a914f4b43ea6c1037a6533db832c3fde5..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\346\240\221/\344\270\255\345\272\217\351\201\215\345\216\2062.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-5ZhNc5swEIZ/jY/uYLDBHGPsuIdmpmMfmhxlkEGtYKkQBvLrK1niq46bJm0Zpj6hfSWtpH12NSMmlheXW4bS6AECTCemEZQTaz0xzdnMsMVHKpVSXNdRQshIoAe1wp48Yy0aWs1JgLPeQA5AOUn7og9Jgn3e0xBjUPSHHYH2V01RiC+EvY/opfqFBDxS6tJ0Wv0jJmFUrzyzXdUTo3qwPkkWoQCKjmRtJpbHALhqxaWHqQxeHRc17/5Kb7MxhhP+OxN22UPg7CLH2z06MV1Pt0bxPNVeTojm+sAT06bC3+ogt8wrHQf7ey73uTpCwqfZmdKdGGAu0rLtFK1Qfu9qH2Izh1rUQWg8mmJ3AqIwVkVEON6nyJc9hcgjoUU8psKaiSbKUkX2SEocyE0QSj2gwM6OrOMR274v9Iwz+IY7PYHjHgyjWfyEGcfl1fDNGigimzHEmLNKDNET7DojdSLPa7vopIWWok5G1BrSiRg2nltWoqFxvQGdeRVdlqLk/fQ6CaAE72WcjaaW+z8oO+7IKFtDUV7dEOW5NTLK86Eor2+IsjUfGeXFUJS3N0R56bjjomwPRXlzQ5QX5shq2bmMaCAeDtpMIJGhZZAngQzaWoYBGI8ghATRTwCpDu9XzHmlnz0o59APvogOqx7l/A+L2nzq9q1L7VxZlbbey0ie4deExJEhZz5+/U3BEQsxf23cJXGGKeLk1N/HX+e3HKpK72+oSh1jZFXqDlOlJeGdIhXWU12Vot2WqDT+fYX+QUXpqZ+BCI/ty/enu7e5i2sX6kbQs7p/IN7qSF0ZF47O6JvzvJQNwmx/pKjh7e8oa/MD
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\344\270\255\345\272\217\351\201\215\345\216\2062.png" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\344\270\255\345\272\217\351\201\215\345\216\2062.png"
deleted file mode 100644
index fe6956b9d09c3503aaa88c0d3ed833d0f9b9d1a6..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\346\240\221/\344\270\255\345\272\217\351\201\215\345\216\2062.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\205\210\345\272\217\351\201\215\345\216\206.drawio" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\345\205\210\345\272\217\351\201\215\345\216\206.drawio"
deleted file mode 100644
index b92e5c29cc9a4f78293af95b04fa5a75122075f2..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\205\210\345\272\217\351\201\215\345\216\206.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-7Vtbk6I4FP41eZwuIDfyKIq9D7tVU9u1tTOPKGllGo2L2Or8+k0kIEFQ2xvOtOWDyUnI5ZwvJ985IoDdyeo5CWbjv0TIY+BY4QrAHnAc27aI/FKSdSZhjGaCURKFutNW8BL95FpoaekiCvnc6JgKEafRzBQOxXTKh6khC5JELM1uryI2Z50FI74jeBkG8a703yhMx5nUdehW/gePRuN8ZpuwrGUS5J31TubjIBTLkgj6AHYTIdKsNFl1eayUl+sle67f0FosLOHT9JgHEuvvH2+rZPbTI6O56/T+WTrPX/Qo70G80BsGDonleN5ALTldaz2Q/xZqnd6rmKZf5hsrdWQHB89W20ZZGqnvTj6GXMwgF2olFCM6cnXSiLLiLcdRyl9mwVC1LCWOpGycTmJZs2UxmM8yy75GKx6qRURx3BWxSDYDwddXToZDKZ+niXjjpZaQsoFlFZO/8yTlq0b12YVRJJq5mPA0Wcsu+gGCtB01kCHW9WUJFlo0LiEilwUaiKNi5K2tZEGb6wOmc2pMV1XxNOyoMyBrUzHlplrlvpP1t3Lle7nSU/u2itpa17I5eLhzaip6lOsQi2TI92wA6uMcJCOeHsLorl1Kesc1es9lCY+DNHo3l1tnDD3DVxHJjWzNblPD7IRU7JltUz9VPn6VgaiFnxwXOdR2EEUkX4keFkHXbIXmLJmSdmbZAKfQyelYgo1uYD4Lpqd7gpIzyQTdetdQyLLpfg+PQZnpMVDbHgPdysreJ7Iytu/MyvhWVu59IisjaFoZw5atTG5l5edPZGWX3pmV6bkcbxWl30rl7zmnk+UtwVOVnN8VrLCo3JgVol+BFeIKUAg9lRVC10Qcdm5K/NxzAVYbRFgH4JLD0irB0t4LyxYAhtsEGKneN/hEgBHi7gs7cKthBzsTfRdEBTkSFbBNVFBoPxEIMUQulhe4bRoTI/uJ2YwhV7YgYtETXRIiexCDkVXXeiPA5Fz7+rTH/0S0B7M7oz12XVryXN6z/4Kp5z2HLrI2eI9Vb8sbER/XuDBsZl4n1H6iNqaSSGOJIrcy/NF3ll07TD4Jql3CrTyQ0+iBlKO5oAeyD3igbLoGDySdQWoeCdOD6CNTdjdaFMTRaCqrQ4lgLuWeci3RMIg7umEShWHc5NsSsZiGypP1LpV2typpd7Lrm1AN3p2r+abmXOmFAeA8AKCMS0wAoNYB0JxGvTAA8AMAGz56bwBozrBeGADwAQDl8qv0FLUNALLLRiUFfNFVrUVTDyJJx2IkpkH8pxAzbZAfPE3X+p2HYJGKhtyK9YRbSsblb1ycmIw7mmyeZ466LOlVgsL+JwoKqX1vQeHFcpUfSm1/PIV+yfNHjzyAraalmBmvUTMoxBTWtX40KHT3JjIlOttMS9UlMq/CCMiDEcgW4t4ZI8gHvj4A6AMAyhmgewNA84uUFwYAegAAlH4PuxsANOcFzyOhHa/nd/vltzD2sszfxsKk+hN76xaGu3TzGlFf/tt4FvTdmnHmOD7IOLOObcV8Tk0SzsfA80HHBz4CMlTr2MAngFmAdYHvAHdTMH3ysAidtqdOhleMQbh7EJ+jdLwYqDFdHzAX+Ay4FDA5i6smVdNh4LqAOU3eWK+iV/j05Bi3YKPae0FOxjzAfFXoIOD2LrLJr73+wdXL/UoNSCai9tsHjOi1SOUrVVDQgSWdUFVgdGOLLvA6m879TYGqDjmVuzsfFgcDHnvB8G20kVcnl6rRZ9hGul6KxL3N50KReOVdkCIfVvKEhds70xXK6vYfKVl4tP1fD/T/Bw==
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\205\210\345\272\217\351\201\215\345\216\206.png" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\345\205\210\345\272\217\351\201\215\345\216\206.png"
deleted file mode 100644
index 5c80cedf7d6561c8aa8c55d7762d4ea5edd17b02..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\205\210\345\272\217\351\201\215\345\216\206.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\220\216\345\272\217\351\201\215\345\216\206.drawio" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\345\220\216\345\272\217\351\201\215\345\216\206.drawio"
deleted file mode 100644
index c324e80144770d60ad69d68891ac0df8ce59bc39..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\220\216\345\272\217\351\201\215\345\216\206.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-7VvLluI2EP0aLacPlqzXEoPpLJKcySHJPHZurAZnDCLGdEN/fSQsGwtsoHmZTHNYIJVkPaqurqoKA1BnvHhMgunoNxmKGMBWuACoCyB0nBZRX1qyzCSc00wwTKLQdFoL+tGbMMKWkc6jUMysjqmUcRpNbeFATiZikFqyIEnkq93tWcb2rNNgKLYE/UEQb0u/RGE6yqQM0rX8FxENR/nMDuFZyzjIO5udzEZBKF9LIuQD1EmkTLPSeNERsVZerpfsuV5Na7GwREzSQx6I2vhvtYG/fv/jz7cvuP+9P38bfTKjvATx3GwYQBKr8bwnveR0afRA/p3rdXrPcpJ+mq2s1FYdIJ4u1o2qNNTf7XwMtZinXGiUUIwI1eqUEVXFex1FqehPg4FueVU4UrJROo5VzVHFYDbNLPscLUSoFxHFcUfGMlkNhJ6fBRkMlHyWJvKHKLWElD+1WsXkLyJJxaJWfU5hFIVmIcciTZaqi3mA5HY0QIbU1F9LsDCiUQkRuSwwQBwWI69tpQrGXO8wHaww3aaKJ2FbnwFVm8iJsNWq9p0sv5Yr38qVrt53q6gtTS2bQ4Rbp2ZDj2odcp4MxI4NIHOcg2Qo0n0Y3bZLSe+4Qu+5LBFxkEYv9nKrjGFm+CwjtZHC7DjnK2N2vGnPbJvmqfLx2xiIMPwAmQupA13qknwlZliEmd2K7FkyJW3NsgJOoZPjsYRqaWA2DSbHM0GJTDJBp5oaClk23c/BGJTajIGaZgz3Wlb2PpCVXX5jVsbXsnL3I1nZsa3s4oatTK5l5ccPZGWGb8zK9FQfbxGlX0vlb7lPp8prB09Xcv+u8AqLypW9Qvd/4RVuAAXzI71C6jAbcRRe1fFjpwKsMoho7YFLDstWCZbOTlg2ADDcJMDI5n1Djw07XLYr7KCNhh38RPSdERXkQFSgJlFBHeeBIISRy7C6wB3bmC5xHrjDuctUi0ta9EhKgmQHYtS4TSImPxaX93v8D+T3YHpjfo9TlZc81fHZfcNUOz77brImHJ9WtS2v5PkQ68ZwuH2fcOeBOphCRjHEiG0MfygF4eph8kmql3AtBoK1DKSJ5owMRPcwUDZdDQMpMkjtI2EziDkyZboxoiCOhhNVHSgECyX3NLVEgyBum4ZxFIZxHbclcj4JNZN1z8VNzOamgnRKeHcr8A4vxk31ydIzAwDdAaCN69oAQI0DoD6PemYAkDsAVg7prQGgPsV6ZgA4dwCAUkp9HYM0DACy7Y0qF7BvqkaLth5kko7kUE6C+Fcpp8Yg/4g0XZqXHoJ5KmuSK60H3FA2Ln/l4shs3MHO5mnmqEqTXiQo7H2goJDwWwsKz5asfFdu+/059HOeP3rgAWw0L8XseI3aQaHLkdXKjkxMsZ2pTAXPRhNTVbnMi/gE7t0n0OxEbswnyAe+PADwHQCaDeCtAaD+XcozAwDeAaDvLnRrAKjPDJ7mhnZ9r/fYKb9Vu9PP/GksTDZ/ZW/cwmjb4bxE3Jf/PJ6Ffdf2OXMc7/U5s45NRX2wIg3nY+D5oO0D3wUqWGs7wCdAxTG8A3wI2Kpgc/KgCJ7Wp04FWJwjtH0QH6N0NH/SYzIfcAZ8DpQzy9UsTE+qp8OAMcBhHRubVXQLTk8OoQXHrbwX1GTcA9zXhbYLWPcsm/zc7e1dvdqv0oBHV/vtAU7MWpTytSooaKOSTqgucLqyRQd47VXn3qpAdYc8Yrw5DouDJxF7weDHcCXfnFypxpxhxzX1UizurT6XicUR3GbCgvZOpEJVXf8pJQuP1n/tQf5/
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\220\216\345\272\217\351\201\215\345\216\206.png" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\345\220\216\345\272\217\351\201\215\345\216\206.png"
deleted file mode 100644
index 87bf512ec50915b7f8a179632d20115dff4e2a81..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\220\216\345\272\217\351\201\215\345\216\206.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221.drawio" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221.drawio"
deleted file mode 100644
index 107a75dd04374fbb4733ac93b026632b206cfeab..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-5VpLc+I4EP41OoaynpaOPJzsZatSNYedOW0ZWwHvGMwYM8D8+m3bsrGMCQwhhAyVQ9QtoUf3p+5PDYgOZ5un1F9M/05CHSPihBtER4gQjB0B/3LNttRIRUrFJI1CM2in+BL90kbpGO0qCvXSGpglSZxFC1sZJPO5DjJL56dpsraHvSSxverCn+g9xZfAj/e1/0RhNjWnIO5O/5eOJtNqZSxU2TPzq8HmJMupHybrhop6iA7TJMnK1mwz1HFuvMou5eceD/TWG0v1PDvlA5GOgn9/JWv68PTfdumwH6Px8sHM8tOPV+bAiIgY5huMoTHJG/Dfny2gMR8vF6VcDHhJYF04VrY1thI/VknV8bAsPNmHAYQvNrvOalanmgY2XM5Ur1apGzsg1kIEDgb+B2GwnkaZ/rLwg7xnDRAE3TSbxSBhaPrLRQmKl2ijw3xvURwPkzhJi4noy4sWQQD6ZZYm33WjJ3TV2HHqxZtmrmym00xvGipj9iedzHSWbmGI6aXCQMDcAVLJ6waijGraAFOl8w2GJ/XMOzdDw3j6N7xOOrzeNvE87OfXB6R5Mte2WcEU6fZrU/jWFEb5uZ1a2hrpgrZfJqs00K+ckJpQ4acTnR3Hvw6tKLDvyYaneIenKl2qYz+Lftqxo8t9ZoXnJCouUQUUZgOF8xYCynObTzXvensi1ZrIaU1UGmZvogJN9bHPBxg9KaxcIIyQOwojTArLqZR/cBhhB728XPjz8526hxXc7c5aVy73Z3iZUOe2vMyvdZfpHd1lzG0vM/rBXhbX8jK/Iy8zzG7Ly+5bid8myr422t8qogftHevLhYr01VSxFm6NKrI/gyqKM6kid6SNUUauShXlWyHZ+RZxjgCsArLTADJ+Fci3CEl+W5Bs57SzXy9S9ohkxMWEuUwQTFuMqLv7SoBVlwLszUZEcSL86E3Bj1PVE5RyyiQH8oGxDUasegorxST0MOG450GTMfsNxtl1H9ZVWfT931zsjt5cFN8YG8ddddm3ErXX81t3WDqWR28xK1bOvJG4RB0rXWFlJzMX91zMXSJdDiFMtqY/OWXSzmmqRVjnFq4Vs8jBmHXht6W4o7clF7cWs7qKvm9n8pcnRjX372GXNx+yPaeWD8RIEJ51GoHBdGp058ez6l58Mp7FlOwpIYjCDgOqJbkNQ+H0uMvyb0g5kVxwcV5A49QmWuzaROtwcfvCQcu9o6DV/lrqo2vb+HBx+8J0Wt4Rna5/8tCuTX2Ym7uq2+e92Xv805SZjhNl1plYTkhd/CMTEJbwlFc113WlXWTnGBK50ygPnZmBsBA9Lg5WoTilPUZ3u7gyp36PSv7VkLkHuONQ5Z8BmZRdBZntcsVe7f+dwSc6wOdxNPBQ30MeQ4NH1MfIEwjSvRoijyBZNGy6FNSI2SVPQJVSlO7n06com67G+ZzSQ0oiTyHpIgWryHzRfDmOpESKHGJTZhejOjOnp2R3zDqzOyymBkh5eaPPkBxd5JDPo8eju4fzggWANObnfURKmL2A8XNTuKhPGzZx84ZyC18M0aBfDH4sGm4+oMq4jagBOT6zQ4V9wU0oaUYDo/LjaDIHMYDrnj+PBjljiAI/7puOWRSG8SEWkyareZhzliLYxP5YxwM/+D4p9O3FwTTmp6KQw0q5EYAGxd+FSoPuccpa851mSCG/T2ZA3P0ctLysux/VUu9/
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221.png" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221.png"
deleted file mode 100644
index bc0fe0dce147c5daf157e6d114dee76bc49ed8a1..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\256\214\345\205\250\344\272\214\345\217\211\346\240\221.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.drawio" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.drawio"
deleted file mode 100644
index 11133b532ad662decfd4aa5af81e1e29b3930534..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-7VnJdpswFP0aL+OjiWmZOEm7SIdzsmjSHQbZcAqIynKM+/UVRmIysR2SYKfuCr0r8TTcq6eHGOFJnH3ibhp8YT6NRgj42QhfjxCCEJjykSPrAnEcqwDmPPRVowq4D/9QBQKFLkOfLhoNBWORCNMm6LEkoZ5oYC7nbNVsNmNRs9fUndMt4N5zo230R+iLoEBtZFX4ZxrOA90zNJ2iJnZ1YzWTReD6bFWD8M0ITzhjoijF2YRG+eLpdSneu32mthwYp4k45IU7eumBOPrJb52v09m3AKzusgvl5cmNlmrCI2RG0t/VVBbmeUE+3TiVhWS6SAt702DGZL9yWmKt1sr8vWS64mKxYfJSNkBGmlWV2ivQbuSAC09lbxqujQA1OkJyYpJ/aVytglDQ+9T18pqVlKDEAhFH0oKy6C7SQhSzMKN+PrYwiiYsYnzjCM9m1PQ8iS8EZ79orca3nCkAZedPlAuaPbvysORTbgTKYir4WjZRL2BTSUDtAaTtVU1RCgpqYtKYqzQ8Lz1XNMuCYvoFrKMO1ttLnPiX+faRVsIS2lxWOW++fqgbj3XjOp83KK21svquPfW3tmhr5eXI2ZJ7dMeUsYodLp9TsW9DbDNZY8roYEpjnEauCJ+aw+2iT/XwnYWbTaSFQppCMYyWAoppqrfqe73tyGk5Ai1HxTpsOdqoqZx2f4Hhg8LKG4QRdEZhhLTUUarlWGGEPMvyInWT/qRuaQV201liRXf/BsslXafCsjHUXsZntJcRaLJM0JFZNodi2Tgjlg10Yixbr038slA81MqPOtGT5Srryw2d9JWpYmmcfKpIPmSqaPZMFQ1gNzVK0KCpov1aSXZ+i4A9AtNCBjUhw51C/hCSNI4pSdI+00hPSRJsj5FNkAURsYiJIG5lRN3VAwnWeRfBnnRENA+UHz6q/Bw8NjE2pDzkuYvKm0etRuCMHeg4xEYQEhNYPbXZErmBh/2y1vei7//RRc7oo6t9Q3f0RA12Xcy+NlPbfcB1Z2r7DtIPcSxC0M3+QKma0TivoNM8zUw4tqBhIdsyZDSxW+4PvvGzO93oTnDnEIaKWWiomGWeUcxqXxSVd7xHi1ldt77/Y1b3ddr+mIVON2YRuDOcvE3MImRnYOwds6RZ/XQtmle/rvHNXw==
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.png" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.png"
deleted file mode 100644
index 673f3e32bebb906c60c73e8196336c7ec1d58753..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\346\240\221/\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\346\226\234\346\240\221.drawio" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\346\226\234\346\240\221.drawio"
deleted file mode 100644
index 6eeaa610144239f439b47f65c6ed6ed76c65413d..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\346\240\221/\346\226\234\346\240\221.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-5VhNc9owEP01HMnY+rCtY0LSdibNTKc5NOnN2AJ7KixXiGD667vGsi0bSGlKcTI5sfskrbT73grBCE8WxUcV5smdjLkYIScuRvh6hJDrOh58lMimQhjzK2Cu0thMaoH79Bc3oGPQVRrzZWeillLoNO+CkcwyHukOFiol191pMym6u+bhnO8A91EodtFvaayTCg2Q3+KfeDpP6p1dj1Uji7CebDJZJmEs1xaEb0Z4oqTUlbUoJlyUxavrUq37cGC0OZjimT5mwfT2s7i+U1/V7ePDOLv8TokYj02Up1CsTMIj5AmIdzUFY14a8BkucjCy6TKv/O2EmYR9IS29MbXyfq5kPTBebpm8hAmI5kU7WEd16jBw4CpSs1sNWydAnY0QJAb8g3O1TlLN7/MwKkfWIEHAEr0Q4Llghsu8EsUsLXhcni0VYiKFVNtAeDbjXhQBvtRK/uDWSOyzqeM0mz9xpXlxsPJuwyc0ApcLrtUGppgFxDcSMD2AqPHXlqIMlFhiqrHQaHjeRG5pBsMw/Resoz2s90ucxZdl+4CXyYx3ywp5q82D7TzaznWZt9N4G+O9tPY83mnRXuXh5HKlIv5MytjcHaGac/2nhthl0mKK7mGqxhQXoU6fusfdR5/Z4YtMt01UC4V2hUJxTwFVmmaV3eu9QNTpBiJ9KVV12Am0VVOT9ssFho+6Vk5wjbjv6BqhPXVgPPA1Qg6y/G+k7mgF7afzAPNvm2UP91rXHZhleq5exu+ol323xzIbmGXvXCyT98Qy632f+wOz7A/y8ONFqh8s+9Gy2yWl8yqeiuTIpyJ+VU9FcqqnIjvvUzE4lSQdW5LOkZJ0LUm6b1+SdEhJUoYvPIwpJgGF2991u69Vn10wlzESwAjxHP+FciXBBQoIfIHCz2riod4mqDeKz6plNsj1OqgyvbegTM95VpnBaZRJn1Um/k/KBLf9D7Ga3v4Ti29+Aw==
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\346\226\234\346\240\221.png" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\346\226\234\346\240\221.png"
deleted file mode 100644
index af12915808e840ae9dbfa768391fa68e8052ea49..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\346\240\221/\346\226\234\346\240\221.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\346\273\241\344\272\214\345\217\211\346\240\221.drawio" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\346\273\241\344\272\214\345\217\211\346\240\221.drawio"
deleted file mode 100644
index 8379028d2a9a84c94199755c75e3f9c191338da6..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\346\240\221/\346\273\241\344\272\214\345\217\211\346\240\221.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-5VnLcpswFP0aL+PRG1gmTpq2M5nJTBZNuiNGNkwxogLHOF9fYSSMME5c2/WjXqF7JK4e5+jqCnp4MCnupZ+GDyLgcQ+BoOjh2x5CEAKmHiUyrxDPcypgLKNAN1oCT9E71yDQ6DQKeGY1zIWI8yi1waFIEj7MLcyXUszsZiMR272m/pivAE9DP15Ff0RBHlaoi5wl/pVH49D0DJlX1Ux801jPJAv9QMwaEL7r4YEUIq9Kk2LA43LxzLpU731ZU1sPTPIk3+SFZ4cW19/kzzALJmPy8P39HlxdaS9vfjzVE+4hFit/N6+qMC4L6ulPUlVIXrO0shcNRkL1q6aVz/Vasd9TYSqusgWT16oBommxrDRegXGjBlx5qnszcGMEyOoIqYkp/pVxMwujnD+l/rCsmSkJKizMJ7GyoCr6WVqJYhQVPCjHFsXxQMRCLhzh0Yiz4VDhWS7FL96oCRzvFYC68zcuc16sXXlY86k2AhcTnsu5aqJfwExLQO8BZOxZQ1EaChtiMpivNTyuPS9pVgXN9F+wjjpYby9xElyX20dZiUi4vaxq3nL+3DRemsZtOW9QW3Ntbbv2PFjZoq2VVyMXUznkH0wZ69jhyzHPP9sQq0w2mKIdTBlM8tjPozd7uF306R4eRbTYREYoxBYKpS0FVNPUbzX3etuR13IEWo6qdVhxtFBTPe3tBYY3Cit7CCPogsIIcZlFKqZHDiNkLctZ6ifbk7qiFdhNZ41V3f0fLCMMTotleqi9jC9oL0Nqs0zwkVlmh2KZXhDLBJLTYtnZNfErovy5UX4xiZ4qL7O+0jBJX50q1sbJp4rkLFNFtmWqSIFra5Sgg6aK7q6S7LyLgE8EZoQMGkKGHwr5LCRJjyrJ9pm29e3FdfvIJciBiDiEIYhbGVF39YEE6+1LsOcTEdmG8sPHlB/FXp9hTDFxqUo+ILTFCL2+Bz2PuKqGMOBsJ01C7DsYJYe9WJvPov/+zkUu6M6F4Yll47Dru+yuidrH51t3WPrsHD2LUxGCbvYPdCwC67iCnn2YObDvQOog16EqhLkt9xsfmbjTjemEdA7hUDFr/XeiPd8t2QXdLSk7tZjV9Qlh90x+/4lRnfv3oUObF9k+qO01MVIZj1xGasG41Nge4xncNKAdNc8intv3GEMeBESlWi61ZchAnzqk/ENKkUsZZdsFNIrtRIvsLdFS5vKna9V8+esa3/0B
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\346\273\241\344\272\214\345\217\211\346\240\221.png" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\346\273\241\344\272\214\345\217\211\346\240\221.png"
deleted file mode 100644
index c0f30c04c5640bde27ab2dac80df439886143ef3..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\346\240\221/\346\273\241\344\272\214\345\217\211\346\240\221.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\351\223\276\345\274\217\345\255\230\345\202\250\344\272\214\345\217\211\346\240\221.drawio" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\351\223\276\345\274\217\345\255\230\345\202\250\344\272\214\345\217\211\346\240\221.drawio"
deleted file mode 100644
index 37585459182161ebafa19d63f94eef4a1ba9e6c7..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\346\240\221/\351\223\276\345\274\217\345\255\230\345\202\250\344\272\214\345\217\211\346\240\221.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-7Zxbb9owFMc/DY+bcg95LPQyaUya2kntHl1iiFcTZ8bc+ul3HBxIloXCgMQPFkjEx5fE/h38z3EMPXc4Wz9wlCXfWIxpz7Hidc+97TmObVsBfEjLZmuJonBrmHISq0J7wxN5x8poKeuCxHheKSgYo4JkVeOYpSkei4oNcc5W1WITRqtnzdAU1wxPY0Tr1mcSi2Rr7Tvh3v4Fk2lSnNkOom3ODBWFVU/mCYrZqmRy73rukDMmtkez9RBTOXjFuGzr3Tfk7i6M41QcU2HgfX9xfi98h44e3r/es+dF9PbJUdcmNkWHcQz9V8mUpfAx4GyRxlg2Y0GKcZGwKUsRHTGWgdEG4y8sxEbRQwvBwJSIGVW5cIV88yLrf/aL5E/VXJ64XVdSG5WaC87e8JBRxvOLcy0rCPw+5ExYKkp2fyhfYN92R/ahcZSUac4WfIwPDU1fuRviUywOFfR2NOFrgNkMQx+gIscUCbKsXglS/jjdldsjgwNF7QSCriF4NkE76JKgZwge/modQbDfJUDfADwboOt0STAwBA/PjcdMop0SDA3BswmWbks7IKjEeonoQp3pFglUwwq30Zk8FOhVmsp0VgkR+ClD+WisIB7JRx5xoXh6EgUECgKRFHNVacwoRdmc5K3lsMYJofEIbdhCFOcpUjmuwqlkbUTJNIXjMVCRTQ6WmAsC4cONypiROM6vc0IoLYF27KF145/hGvJEeH3YOeooVQWvCK9UVOb1VXq1j3HsInBJSvFNaF2JftQIGnolCKKPEN2hdHoM8zrSmLPsR+H+0pAxIoHdLWHA5spWJqQmDJHPCzKT4klR95UJwWYqwdXY7BrNB8YfwBuGaijnCR96M4S0vU/DWxbnwDcFBwB/lG1gNBcrPBfX84pT7p0K1zjSM9xreUbhqqWJYSRhXNJf8rUDtPeXs12BAYgJzcP9BGYAnLbMNPo30xLEoFWGdo3hY35aA/F0iOsqwM6YOkawWxPsMNRNsO36wpNR7JYmhqZQSxvJ9oxknw7V1UyzfaPZl6Ooi2gHRrRbE+3+X6Ltel7Xol1fJjOi3dbM0PCQSRvRri/AGdH+EGqomWhHRrQvR1ET0XbqK2BGtK8l2pF2kfbuQY8R7dZFu+mRmC6i7dQX4Yxofwi1YQbobH53jWhfjqIuol1fAzOifS3R9m3dIm2nvinMiHZbM0PDhlxtRLu+CGdE+0OovmaiHRrRvhxFXUTbbEJrT7SDqCrau1/KdCfaZhdad6Kt+Ta0omEj2qdA1Wwfmmv2oV2Qoiai7dbXwIxoX020tds57pqNaJ2JdtOvtrQRbbMR7T+garYRzTUb0S5I8eqiDcn9nxrkeaW/hnDv/gA=
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\351\223\276\345\274\217\345\255\230\345\202\250\344\272\214\345\217\211\346\240\221.png" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\351\223\276\345\274\217\345\255\230\345\202\250\344\272\214\345\217\211\346\240\221.png"
deleted file mode 100644
index c0ce15b72e4aa9f5f9a812e1cb54b755341978e5..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\346\240\221/\351\223\276\345\274\217\345\255\230\345\202\250\344\272\214\345\217\211\346\240\221.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\351\241\272\345\272\217\345\255\230\345\202\250.drawio" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\351\241\272\345\272\217\345\255\230\345\202\250.drawio"
deleted file mode 100644
index 1ddbb02dc6e39e25e35caa8e9ebd47eb3a26e5de..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\346\240\221/\351\241\272\345\272\217\345\255\230\345\202\250.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\351\241\272\345\272\217\345\255\230\345\202\250.png" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\351\241\272\345\272\217\345\255\230\345\202\250.png"
deleted file mode 100644
index 33f3c6e3ff387ebe945d27614e720c0f7ae53639..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\346\240\221/\351\241\272\345\272\217\345\255\230\345\202\250.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\351\241\272\345\272\217\345\255\230\345\202\2502.drawio" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\351\241\272\345\272\217\345\255\230\345\202\2502.drawio"
deleted file mode 100644
index 8048a1a724c6a244504718b929e00b01152c3710..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/data-structure/pictures/\346\240\221/\351\241\272\345\272\217\345\255\230\345\202\2502.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/cs-basics/data-structure/pictures/\346\240\221/\351\241\272\345\272\217\345\255\230\345\202\2502.png" "b/docs/cs-basics/data-structure/pictures/\346\240\221/\351\241\272\345\272\217\345\255\230\345\202\2502.png"
deleted file mode 100644
index 70c6da26d2211bd57684c47072085e03841c28fd..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/data-structure/pictures/\346\240\221/\351\241\272\345\272\217\345\255\230\345\202\2502.png" and /dev/null differ
diff --git "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\215\225\351\223\276\350\241\2502.png" "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\215\225\351\223\276\350\241\2502.png"
index 9fe82753c809e2d06b5049394b5b7998959185a1..1fe9f712584aca6e65c50d0fcad4864704b48be9 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\215\225\351\223\276\350\241\2502.png" and "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\215\225\351\223\276\350\241\2502.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\345\276\252\347\216\257\351\223\276\350\241\250.png" "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\345\276\252\347\216\257\351\223\276\350\241\250.png"
index 9d134bcbea0f675788238856fa64c71e412f1232..a10587da1382671fb5c4fead07a8674785379188 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\345\276\252\347\216\257\351\223\276\350\241\250.png" and "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\345\276\252\347\216\257\351\223\276\350\241\250.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\351\223\276\350\241\250.png" "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\351\223\276\350\241\250.png"
index ffb3b3efa0e3324f8ad556b0b1ed2a8d3a2f20ab..bf1fe71bff796ce102cf0b4f9a0a47031b811f23 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\351\223\276\350\241\250.png" and "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\217\214\345\220\221\351\223\276\350\241\250.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\276\252\347\216\257\351\230\237\345\210\227-\345\240\206\346\273\241.png" "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\276\252\347\216\257\351\230\237\345\210\227-\345\240\206\346\273\241.png"
index 225c46d779712475fbe27efe68c2be02097597cd..23cf43107dce669edb6e3c421e80354064b44dbf 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\276\252\347\216\257\351\230\237\345\210\227-\345\240\206\346\273\241.png" and "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\345\276\252\347\216\257\351\230\237\345\210\227-\345\240\206\346\273\241.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204.png" "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204.png"
index 923e8c042d8041cd0086178e354a2301d0425255..663ab2e9bcd0d732cbc5102193374ec24bd3da04 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204.png" and "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210.png" "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210.png"
index 2d704d564c63f450ffa4bb2ddeb37c5e51f31d75..c1b36ef62d09ec8890eb4b36f09808fb384c6b47 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210.png" and "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\345\200\222\351\200\200\345\222\214\345\211\215\350\277\233.png" "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\345\200\222\351\200\200\345\222\214\345\211\215\350\277\233.png"
index 07bdb36a2c6b876727f99bb0588e746e98903ccf..84e117a51f4973d93947a7a24ee53c0db9b87436 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\345\200\222\351\200\200\345\222\214\345\211\215\350\277\233.png" and "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\210\345\256\236\347\216\260\346\265\217\350\247\210\345\231\250\345\200\222\351\200\200\345\222\214\345\211\215\350\277\233.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227.png" "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227.png"
index 2fa16dcdb3a374038ba9029d112bfee06eaada26..fd8e334d1131a84105e2c8992f05a03621755c00 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227.png" and "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227.png" differ
diff --git "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\241\272\345\272\217\351\230\237\345\210\227\345\201\207\346\272\242\345\207\272.png" "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\241\272\345\272\217\351\230\237\345\210\227\345\201\207\346\272\242\345\207\272.png"
index f3b01def26620f5d7cbc86c1f414cdfbf60b9ab8..9a324873490ed3f221cc3c8066742330ff9013ab 100644
Binary files "a/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\241\272\345\272\217\351\230\237\345\210\227\345\201\207\346\272\242\345\207\272.png" and "b/docs/cs-basics/data-structure/pictures/\347\272\277\346\200\247\346\225\260\346\215\256\347\273\223\346\236\204/\351\241\272\345\272\217\351\230\237\345\210\227\345\201\207\346\272\242\345\207\272.png" differ
diff --git "a/docs/cs-basics/data-structure/\347\272\242\351\273\221\346\240\221.md" b/docs/cs-basics/data-structure/red-black-tree.md
similarity index 70%
rename from "docs/cs-basics/data-structure/\347\272\242\351\273\221\346\240\221.md"
rename to docs/cs-basics/data-structure/red-black-tree.md
index ce18d6e01380c2e87d1cb0dbb19c89d5019efbd2..11043f45d0cbc0cdeaae984d78df06d571af527a 100644
--- "a/docs/cs-basics/data-structure/\347\272\242\351\273\221\346\240\221.md"
+++ b/docs/cs-basics/data-structure/red-black-tree.md
@@ -10,13 +10,12 @@ tag:
1. 每个节点非红即黑;
2. 根节点总是黑色的;
-3. 每个叶子节点都是黑色的空节点(NIL节点);
+3. 每个叶子节点都是黑色的空节点(NIL 节点);
4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。
-**红黑树的应用** :TreeMap、TreeSet以及JDK1.8的HashMap底层都用到了红黑树。
+**红黑树的应用**:TreeMap、TreeSet 以及 JDK1.8 的 HashMap 底层都用到了红黑树。
**为什么要用红黑树?** 简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。详细了解可以查看 [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐)
-**相关阅读** :[《红黑树深入剖析及Java实现》](https://zhuanlan.zhihu.com/p/24367771)(美团点评技术团队)
-
+**相关阅读**:[《红黑树深入剖析及 Java 实现》](https://zhuanlan.zhihu.com/p/24367771)(美团点评技术团队)
diff --git "a/docs/cs-basics/data-structure/\346\240\221.md" b/docs/cs-basics/data-structure/tree.md
similarity index 65%
rename from "docs/cs-basics/data-structure/\346\240\221.md"
rename to docs/cs-basics/data-structure/tree.md
index 34f7d49dba9f47b62088d8f142921ea368d71b91..6b016ce5a23ce767432f544f74558af680aa0488 100644
--- "a/docs/cs-basics/data-structure/\346\240\221.md"
+++ b/docs/cs-basics/data-structure/tree.md
@@ -1,11 +1,10 @@
---
+title: 树
category: 计算机基础
tag:
- 数据结构
---
-# 树
-
树就是一种类似现实生活中的树的数据结构(倒置的树)。任何一颗非空树只有一个根节点。
一棵树具有以下特点:
@@ -20,16 +19,18 @@ tag:
如上图所示,通过上面这张图说明一下树中的常用概念:
-- **节点** :树中的每个元素都可以统称为节点。
-- **根节点** :顶层节点或者说没有父节点的节点。上图中 A 节点就是根节点。
-- **父节点** :若一个节点含有子节点,则这个节点称为其子节点的父节点。上图中的 B 节点是 D 节点、E 节点的父节点。
-- **子节点** :一个节点含有的子树的根节点称为该节点的子节点。上图中 D 节点、E 节点是 B 节点的子节点。
-- **兄弟节点** :具有相同父节点的节点互称为兄弟节点。上图中 D 节点、E 节点的共同父节点是 B 节点,故 D 和 E 为兄弟节点。
-- **叶子节点** :没有子节点的节点。上图中的 D、F、H、I 都是叶子节点。
-- **节点的高度** :该节点到叶子节点的最长路径所包含的边数。
-- **节点的深度** :根节点到该节点的路径所包含的边数
-- **节点的层数** :节点的深度+1。
-- **树的高度** :根节点的高度。
+- **节点**:树中的每个元素都可以统称为节点。
+- **根节点**:顶层节点或者说没有父节点的节点。上图中 A 节点就是根节点。
+- **父节点**:若一个节点含有子节点,则这个节点称为其子节点的父节点。上图中的 B 节点是 D 节点、E 节点的父节点。
+- **子节点**:一个节点含有的子树的根节点称为该节点的子节点。上图中 D 节点、E 节点是 B 节点的子节点。
+- **兄弟节点**:具有相同父节点的节点互称为兄弟节点。上图中 D 节点、E 节点的共同父节点是 B 节点,故 D 和 E 为兄弟节点。
+- **叶子节点**:没有子节点的节点。上图中的 D、F、H、I 都是叶子节点。
+- **节点的高度**:该节点到叶子节点的最长路径所包含的边数。
+- **节点的深度**:根节点到该节点的路径所包含的边数
+- **节点的层数**:节点的深度+1。
+- **树的高度**:根节点的高度。
+
+> 关于树的深度和高度的定义可以看 stackoverflow 上的这个问题:[What is the difference between tree depth and height?](https://stackoverflow.com/questions/2603692/what-is-the-difference-between-tree-depth-and-height) 。
## 二叉树的分类
@@ -37,13 +38,15 @@ tag:
**二叉树** 的分支通常被称作“**左子树**”或“**右子树**”。并且,**二叉树** 的分支具有左右次序,不能随意颠倒。
-**二叉树** 的第 i 层至多拥有 `2^(i-1)` 个节点,深度为 k 的二叉树至多总共有 `2^k-1` 个节点
+**二叉树** 的第 i 层至多拥有 `2^(i-1)` 个节点,深度为 k 的二叉树至多总共有 `2^(k+1)-1` 个节点(满二叉树的情况),至少有 2^(k) 个节点(关于节点的深度的定义国内争议比较多,我个人比较认可维基百科对[节点深度的定义]())。
+
+
### 满二叉树
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是 **满二叉树**。也就是说,如果一个二叉树的层数为 K,且结点总数是(2^k) -1 ,则它就是 **满二叉树**。如下图所示:
-
+
### 完全二叉树
@@ -51,7 +54,7 @@ tag:
大家可以想象为一棵树从根结点开始扩展,扩展完左子节点才能开始扩展右子节点,每扩展完一层,才能继续扩展下一层。如下图所示:
-
+
完全二叉树有一个很好的性质:**父结点和子节点的序号有着对应关系。**
@@ -68,7 +71,7 @@ tag:
在给大家展示平衡二叉树之前,先给大家看一棵树:
-
+
**你管这玩意儿叫树???**
@@ -82,7 +85,7 @@ tag:
但是,如果二叉树退化为一个链表了,那么那么树所具有的优秀性质就难以表现出来,效率也会大打折,为了避免这样的情况,我们希望每个做 “家长”(父结点) 的,都 **一碗水端平**,分给左儿子和分给右儿子的尽可能一样多,相差最多不超过一层,如下图所示:
-
+
## 二叉树的存储
@@ -102,19 +105,19 @@ tag:
那就直接引用对象呗(别问我对象哪里找)
-
+
### 顺序存储
-顺序存储就是利用数组进行存储,数组中的每一个位置仅存储节点的 data,不存储左右子节点的指针,子节点的索引通过数组下标完成。根结点的序号为 1,对于每个节点 Node,假设它存储在数组中下标为 i 的位置,那么它的左子节点就存储在 2 _ i 的位置,它的右子节点存储在下标为 2 _ i+1 的位置。
+顺序存储就是利用数组进行存储,数组中的每一个位置仅存储节点的 data,不存储左右子节点的指针,子节点的索引通过数组下标完成。根结点的序号为 1,对于每个节点 Node,假设它存储在数组中下标为 i 的位置,那么它的左子节点就存储在 2i 的位置,它的右子节点存储在下标为 2i+1 的位置。
一棵完全二叉树的数组顺序存储如下图所示:
-
+
大家可以试着填写一下存储如下二叉树的数组,比较一下和完全二叉树的顺序存储有何区别:
-
+
可以看到,如果我们要存储的二叉树不是完全二叉树,在数组中就会出现空隙,导致内存利用率降低
@@ -122,7 +125,7 @@ tag:
### 先序遍历
-
+
二叉树的先序遍历,就是先输出根结点,再遍历左子树,最后遍历右子树,遍历左子树和右子树的时候,同样遵循先序遍历的规则,也就是说,我们可以递归实现先序遍历。
@@ -141,11 +144,11 @@ public void preOrder(TreeNode root){
### 中序遍历
-
+
二叉树的中序遍历,就是先递归中序遍历左子树,再输出根结点的值,再递归中序遍历右子树,大家可以想象成一巴掌把树压扁,父结点被拍到了左子节点和右子节点的中间,如下图所示:
-
+
代码如下:
@@ -162,7 +165,7 @@ public void inOrder(TreeNode root){
### 后序遍历
-
+
二叉树的后序遍历,就是先递归后序遍历左子树,再递归后序遍历右子树,最后输出根结点的值
@@ -173,8 +176,8 @@ public void postOrder(TreeNode root){
if(root == null){
return;
}
- postOrder(root.left);
postOrder(root.right);
+ postOrder(root.left);
system.out.println(root.data);
}
-```
\ No newline at end of file
+```
diff --git "a/docs/cs-basics/network/HTTPS\344\270\255\347\232\204TLS.md" "b/docs/cs-basics/network/HTTPS\344\270\255\347\232\204TLS.md"
deleted file mode 100644
index f665eb22da4a6561706f1e85fc6bfe0576f6f454..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/network/HTTPS\344\270\255\347\232\204TLS.md"
+++ /dev/null
@@ -1,124 +0,0 @@
----
-title: HTTPS中的TLS
-category: 计算机基础
-tag:
- - 计算机网络
----
-
-# 1. SSL 与 TLS
-
-SSL:(Secure Socket Layer) 安全套接层,于 1994 年由网景公司设计,并于 1995 年发布了 3.0 版本
-TLS:(Transport Layer Security)传输层安全性协议,是 IETF 在 SSL3.0 的基础上设计的协议
-以下全部使用 TLS 来表示
-
-# 2. 从网络协议的角度理解 HTTPS
-
-![此图并不准确][1]
-HTTP:HyperText Transfer Protocol 超文本传输协议
-HTTPS:Hypertext Transfer Protocol Secure 超文本传输安全协议
-TLS:位于 HTTP 和 TCP 之间的协议,其内部有 TLS握手协议、TLS记录协议
-HTTPS 经由 HTTP 进行通信,但利用 TLS 来保证安全,即 HTTPS = HTTP + TLS
-
-# 3. 从密码学的角度理解 HTTPS
-
-HTTPS 使用 TLS 保证安全,这里的“安全”分两部分,一是传输内容加密、二是服务端的身份认证
-
-## 3.1. TLS 工作流程
-
-![此图并不准确][2]
-此为服务端单向认证,还有客户端/服务端双向认证,流程类似,只不过客户端也有自己的证书,并发送给服务器进行验证
-
-## 3.2. 密码基础
-
-### 3.2.1. 伪随机数生成器
-
-为什么叫伪随机数,因为没有真正意义上的随机数,具体可以参考 Random/TheadLocalRandom
-它的主要作用在于生成对称密码的秘钥、用于公钥密码生成秘钥对
-
-### 3.2.2. 消息认证码
-
-消息认证码主要用于验证消息的完整性与消息的认证,其中消息的认证指“消息来自正确的发送者”
-
->消息认证码用于验证和认证,而不是加密
-
-![消息认证码过程][3]
-
-1. 发送者与接收者事先共享秘钥
-2. 发送者根据发送消息计算 MAC 值
-3. 发送者发送消息和 MAC 值
-4. 接收者根据接收到的消息计算 MAC 值
-5. 接收者根据自己计算的 MAC 值与收到的 MAC 对比
-6. 如果对比成功,说明消息完整,并来自于正确的发送者
-
-### 3.2.3. 数字签名
-
-消息认证码的缺点在于**无法防止否认**,因为共享秘钥被 client、server 两端拥有,server 可以伪造 client 发送给自己的消息(自己给自己发送消息),为了解决这个问题,我们需要它们有各自的秘钥不被第二个知晓(这样也解决了共享秘钥的配送问题)
-
-![数字签名过程][4]
-
->数字签名和消息认证码都**不是为了加密**
->可以将单向散列函数获取散列值的过程理解为使用 md5 摘要算法获取摘要的过程
-
-使用自己的私钥对自己所认可的消息生成一个该消息专属的签名,这就是数字签名,表明我承认该消息来自自己
-注意:**私钥用于加签,公钥用于解签,每个人都可以解签,查看消息的归属人**
-
-### 3.2.4. 公钥密码
-
-公钥密码也叫非对称密码,由公钥和私钥组成,它最开始是为了解决秘钥的配送传输安全问题,即,我们不配送私钥,只配送公钥,私钥由本人保管
-它与数字签名相反,公钥密码的私钥用于解密、公钥用于加密,每个人都可以用别人的公钥加密,但只有对应的私钥才能解开密文
-client:明文 + 公钥 = 密文
-server:密文 + 私钥 = 明文
-注意:**公钥用于加密,私钥用于解密,只有私钥的归属者,才能查看消息的真正内容**
-
-### 3.2.5. 证书
-
-证书:全称公钥证书(Public-Key Certificate, PKC),里面保存着归属者的基本信息,以及证书过期时间、归属者的公钥,并由认证机构(Certification Authority, **CA**)施加数字签名,表明,某个认证机构认定该公钥的确属于此人
-
->想象这个场景:你想在支付宝页面交易,你需要支付宝的公钥进行加密通信,于是你从百度上搜索关键字“支付宝公钥”,你获得了支什宝的公钥,这个时候,支什宝通过中间人攻击,让你访问到了他们支什宝的页面,最后你在这个支什宝页面完美的使用了支什宝的公钥完成了与支什宝的交易
->![证书过程][5]
-
-在上面的场景中,你可以理解支付宝证书就是由支付宝的公钥、和给支付宝颁发证书的企业的数字签名组成
-任何人都可以给自己或别人的公钥添加自己的数字签名,表明:我拿我的尊严担保,我的公钥/别人的公钥是真的,至于信不信那是另一回事了
-
-### 3.2.6. 密码小结
-
-| 密码 | 作用 | 组成 |
-| :-- | :-- | :-- |
-| 消息认证码 | 确认消息的完整、并对消息的来源认证 | 共享秘钥+消息的散列值 |
-| 数字签名 | 对消息的散列值签名 | 公钥+私钥+消息的散列值 |
-| 公钥密码 | 解决秘钥的配送问题 | 公钥+私钥+消息 |
-| 证书 | 解决公钥的归属问题 | 公钥密码中的公钥+数字签名 |
-
-## 3.3. TLS 使用的密码技术
-
-1. 伪随机数生成器:秘钥生成随机性,更难被猜测
-2. 对称密码:对称密码使用的秘钥就是由伪随机数生成,相较于非对称密码,效率更高
-3. 消息认证码:保证消息信息的完整性、以及验证消息信息的来源
-4. 公钥密码:证书技术使用的就是公钥密码
-5. 数字签名:验证证书的签名,确定由真实的某个 CA 颁发
-6. 证书:解决公钥的真实归属问题,降低中间人攻击概率
-
-## 3.4. TLS 总结
-
-TLS 是一系列密码工具的框架,作为框架,它也是非常的灵活,体现在每个工具套件它都可以替换,即:客户端与服务端之间协商密码套件,从而更难的被攻破,例如使用不同方式的对称密码,或者公钥密码、数字签名生成方式、单向散列函数技术的替换等
-
-# 4. RSA 简单示例
-
-RSA 是一种公钥密码算法,我们简单的走一遍它的加密解密过程
-加密算法:密文 = (明文^E) mod N,其中公钥为{E,N},即”求明文的E次方的对 N 的余数“
-解密算法:明文 = (密文^D) mod N,其中秘钥为{D,N},即”求密文的D次方的对 N 的余数“
-例:我们已知公钥为{5,323},私钥为{29,323},明文为300,请写出加密和解密的过程:
->加密:密文 = 123 ^ 5 mod 323 = 225
->解密:明文 = 225 ^ 29 mod 323 = [[(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 4) mod 323]] mod 323 = (4 * 4 * 4 * 4 * 4 * 290) mod 323 = 123
-
-# 5. 参考
-
-1. SSL加密发生在哪里:
-2. TLS工作流程:
-3. 《图解密码技术》: 豆瓣评分 9.5
-
-[1]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/%E4%B8%83%E5%B1%82.png
-[2]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/tls%E6%B5%81%E7%A8%8B.png
-[3]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/%E6%B6%88%E6%81%AF%E8%AE%A4%E8%AF%81%E7%A0%81%E8%BF%87%E7%A8%8B.png
-[4]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D%E8%BF%87%E7%A8%8B.png
-[5]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/dns%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB.png
\ No newline at end of file
diff --git a/docs/cs-basics/network/application-layer-protocol.md b/docs/cs-basics/network/application-layer-protocol.md
new file mode 100644
index 0000000000000000000000000000000000000000..fd8ceec55a1d4f8cee2ad0d8803e962624c1f678
--- /dev/null
+++ b/docs/cs-basics/network/application-layer-protocol.md
@@ -0,0 +1,113 @@
+---
+title: 应用层常见协议总结(应用层)
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+## HTTP:超文本传输协议
+
+**超文本传输协议(HTTP,HyperText Transfer Protocol)** 是一种用于传输超文本和多媒体内容的协议,主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。
+
+HTTP 使用客户端-服务器模型,客户端向服务器发送 HTTP Request(请求),服务器响应请求并返回 HTTP Response(响应),整个过程如下图所示。
+
+
+
+HTTP 协议基于 TCP 协议,发送 HTTP 请求之前首先要建立 TCP 连接也就是要经历 3 次握手。目前使用的 HTTP 协议大部分都是 1.1。在 1.1 的协议里面,默认是开启了 Keep-Alive 的,这样的话建立的连接就可以在多次请求中被复用了。
+
+另外, HTTP 协议是”无状态”的协议,它无法记录客户端用户的状态,一般我们都是通过 Session 来记录客户端用户的状态。
+
+## SMTP:简单邮件传输(发送)协议
+
+**简单邮件传输(发送)协议(SMTP,Simple Mail Transfer Protocol)** 基于 TCP 协议,是一种用于发送电子邮件的协议
+
+
+
+注意 ⚠️:**接受邮件的协议不是 SMTP 而是 POP3 协议。**
+
+SMTP 协议这块涉及的内容比较多,下面这两个问题比较重要:
+
+1. 电子邮件的发送过程
+2. 如何判断邮箱是真正存在的?
+
+**电子邮件的发送过程?**
+
+比如我的邮箱是“dabai@cszhinan.com”,我要向“xiaoma@qq.com”发送邮件,整个过程可以简单分为下面几步:
+
+1. 通过 **SMTP** 协议,我将我写好的邮件交给 163 邮箱服务器(邮局)。
+2. 163 邮箱服务器发现我发送的邮箱是 qq 邮箱,然后它使用 SMTP 协议将我的邮件转发到 qq 邮箱服务器。
+3. qq 邮箱服务器接收邮件之后就通知邮箱为“xiaoma@qq.com”的用户来收邮件,然后用户就通过 **POP3/IMAP** 协议将邮件取出。
+
+**如何判断邮箱是真正存在的?**
+
+很多场景(比如邮件营销)下面我们需要判断我们要发送的邮箱地址是否真的存在,这个时候我们可以利用 SMTP 协议来检测:
+
+1. 查找邮箱域名对应的 SMTP 服务器地址
+2. 尝试与服务器建立连接
+3. 连接成功后尝试向需要验证的邮箱发送邮件
+4. 根据返回结果判定邮箱地址的真实性
+
+推荐几个在线邮箱是否有效检测工具:
+
+1. https://verify-email.org/
+2. http://tool.chacuo.net/mailverify
+3. https://www.emailcamel.com/
+
+## POP3/IMAP:邮件接收的协议
+
+这两个协议没必要多做阐述,只需要了解 **POP3 和 IMAP 两者都是负责邮件接收的协议** 即可(二者也是基于 TCP 协议)。另外,需要注意不要将这两者和 SMTP 协议搞混淆了。**SMTP 协议只负责邮件的发送,真正负责接收的协议是 POP3/IMAP。**
+
+IMAP 协议是比 POP3 更新的协议,它在功能和性能上都更加强大。IMAP 支持邮件搜索、标记、分类、归档等高级功能,而且可以在多个设备之间同步邮件状态。几乎所有现代电子邮件客户端和服务器都支持 IMAP。
+
+## FTP:文件传输协议
+
+**FTP 协议** 基于 TCP 协议,是一种用于在计算机之间传输文件的协议,可以屏蔽操作系统和文件存储方式。
+
+FTP 是基于客户—服务器(C/S)模型而设计的,在客户端与 FTP 服务器之间建立两个连接。如果我们要基于 FTP 协议开发一个文件传输的软件的话,首先需要搞清楚 FTP 的原理。关于 FTP 的原理,很多书籍上已经描述的非常详细了:
+
+> FTP 的独特的优势同时也是与其它客户服务器程序最大的不同点就在于它在两台通信的主机之间使用了两条 TCP 连接(其它客户服务器应用程序一般只有一条 TCP 连接):
+>
+> 1. 控制连接:用于传送控制信息(命令和响应)
+> 2. 数据连接:用于数据传送;
+>
+> 这种将命令和数据分开传送的思想大大提高了 FTP 的效率。
+
+
+
+注意 ⚠️:FTP 是一种不安全的协议,因为它在传输过程中不会对数据进行加密。因此,FTP 传输的文件可能会被窃听或篡改。建议在传输敏感数据时使用更安全的协议,如 SFTP(一种基于 SSH 协议的安全文件传输协议,用于在网络上安全地传输文件)。
+
+## Telnet:远程登陆协议
+
+**Telnet 协议** 基于 TCP 协议,用于通过一个终端登陆到其他服务器。Telnet 协议的最大缺点之一是所有数据(包括用户名和密码)均以明文形式发送,这有潜在的安全风险。这就是为什么如今很少使用 Telnet,而是使用一种称为 SSH 的非常安全的网络传输协议的主要原因。
+
+
+
+## SSH:安全的网络传输协议
+
+**SSH(Secure Shell)** 基于 TCP 协议,通过加密和认证机制实现安全的访问和文件传输等业务。
+
+SSH 的经典用途是登录到远程电脑中执行命令。除此之外,SSH 也支持隧道协议、端口映射和 X11 连接。借助 SFTP 或 SCP 协议,SSH 还可以传输文件。
+
+SSH 使用客户端-服务器模型,默认端口是 22。SSH 是一个守护进程,负责实时监听客户端请求,并进行处理。大多数现代操作系统都提供了 SSH。
+
+
+
+## RTP:实时传输协议
+
+RTP(Real-time Transport Protocol,实时传输协议)通常基于 UDP 协议,但也支持 TCP 协议。它提供了端到端的实时传输数据的功能,但不包含资源预留存、不保证实时传输质量,这些功能由 WebRTC 实现。
+
+RTP 协议分为两种子协议:
+
+- **RTP(Real-time Transport Protocol,实时传输协议)**:传输具有实时特性的数据。
+- **RTCP(RTP Control Protocol,RTP 控制协议)**:提供实时传输过程中的统计信息(如网络延迟、丢包率等),WebRTC 正是根据这些信息处理丢包
+
+## DNS:域名系统
+
+DNS(Domain Name System,域名管理系统)基于 UDP 协议,用于解决域名和 IP 地址的映射问题。
+
+
+
+## 参考
+
+- 《计算机网络自顶向下方法》(第七版)
+- RTP 协议介绍:https://mthli.xyz/rtp-introduction/
diff --git a/docs/cs-basics/network/arp.md b/docs/cs-basics/network/arp.md
new file mode 100644
index 0000000000000000000000000000000000000000..ba485ba2e5463c44a3503513481959bc3a68dcf8
--- /dev/null
+++ b/docs/cs-basics/network/arp.md
@@ -0,0 +1,103 @@
+---
+title: ARP 协议详解(网络层)
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+每当我们学习一个新的网络协议的时候,都要把他结合到 OSI 七层模型中,或者是 TCP/IP 协议栈中来学习,一是要学习该协议在整个网络协议栈中的位置,二是要学习该协议解决了什么问题,地位如何?三是要学习该协议的工作原理,以及一些更深入的细节。
+
+**ARP 协议**,可以说是在协议栈中属于一个**偏底层的、非常重要的、又非常简单的**通信协议。
+
+开始阅读这篇文章之前,你可以先看看下面几个问题:
+
+1. **ARP 协议在协议栈中的位置?** ARP 协议在协议栈中的位置非常重要,在理解了它的工作原理之后,也很难说它到底是网络层协议,还是链路层协议,因为它恰恰串联起了网络层和链路层。国外的大部分教程通常将 ARP 协议放在网络层。
+2. **ARP 协议解决了什么问题,地位如何?** ARP 协议,全称 **地址解析协议(Address Resolution Protocol)**,它解决的是网络层地址和链路层地址之间的转换问题。因为一个 IP 数据报在物理上传输的过程中,总是需要知道下一跳(物理上的下一个目的地)该去往何处,但 IP 地址属于逻辑地址,而 MAC 地址才是物理地址,ARP 协议解决了 IP 地址转 MAC 地址的一些问题。
+3. **ARP 工作原理?** 只希望大家记住几个关键词:**ARP 表、广播问询、单播响应**。
+
+## MAC 地址
+
+在介绍 ARP 协议之前,有必要介绍一下 MAC 地址。
+
+MAC 地址的全称是 **媒体访问控制地址(Media Access Control Address)**。如果说,互联网中每一个资源都由 IP 地址唯一标识(IP 协议内容),那么一切网络设备都由 MAC 地址唯一标识。
+
+
+
+可以理解为,MAC 地址是一个网络设备真正的身份证号,IP 地址只是一种不重复的定位方式(比如说住在某省某市某街道的张三,这种逻辑定位是 IP 地址,他的身份证号才是他的 MAC 地址),也可以理解为 MAC 地址是身份证号,IP 地址是邮政地址。MAC 地址也有一些别称,如 LAN 地址、物理地址、以太网地址等。
+
+> 还有一点要知道的是,不仅仅是网络资源才有 IP 地址,网络设备也有 IP 地址,比如路由器。但从结构上说,路由器等网络设备的作用是组成一个网络,而且通常是内网,所以它们使用的 IP 地址通常是内网 IP,内网的设备在与内网以外的设备进行通信时,需要用到 NAT 协议。
+
+MAC 地址的长度为 6 字节(48 比特),地址空间大小有 280 万亿之多($2^{48}$),MAC 地址由 IEEE 统一管理与分配,理论上,一个网络设备中的网卡上的 MAC 地址是永久的。不同的网卡生产商从 IEEE 那里购买自己的 MAC 地址空间(MAC 的前 24 比特),也就是前 24 比特由 IEEE 统一管理,保证不会重复。而后 24 比特,由各家生产商自己管理,同样保证生产的两块网卡的 MAC 地址不会重复。
+
+MAC 地址具有可携带性、永久性,身份证号永久地标识一个人的身份,不论他到哪里都不会改变。而 IP 地址不具有这些性质,当一台设备更换了网络,它的 IP 地址也就可能发生改变,也就是它在互联网中的定位发生了变化。
+
+最后,记住,MAC 地址有一个特殊地址:FF-FF-FF-FF-FF-FF(全 1 地址),该地址表示广播地址。
+
+## ARP 协议工作原理
+
+ARP 协议工作时有一个大前提,那就是 **ARP 表**。
+
+在一个局域网内,每个网络设备都自己维护了一个 ARP 表,ARP 表记录了某些其他网络设备的 IP 地址-MAC 地址映射关系,该映射关系以 `` 三元组的形式存储。其中,TTL 为该映射关系的生存周期,典型值为 20 分钟,超过该时间,该条目将被丢弃。
+
+ARP 的工作原理将分两种场景讨论:
+
+1. **同一局域网内的 MAC 寻址**;
+2. **从一个局域网到另一个局域网中的网络设备的寻址**。
+
+### 同一局域网内的 MAC 寻址
+
+假设当前有如下场景:IP 地址为`137.196.7.23`的主机 A,想要给同一局域网内的 IP 地址为`137.196.7.14`主机 B,发送 IP 数据报文。
+
+> 再次强调,当主机发送 IP 数据报文时(网络层),仅知道目的地的 IP 地址,并不清楚目的地的 MAC 地址,而 ARP 协议就是解决这一问题的。
+
+为了达成这一目标,主机 A 将不得不通过 ARP 协议来获取主机 B 的 MAC 地址,并将 IP 报文封装成链路层帧,发送到下一跳上。在该局域网内,关于此将按照时间顺序,依次发生如下事件:
+
+1. 主机 A 检索自己的 ARP 表,发现 ARP 表中并无主机 B 的 IP 地址对应的映射条目,也就无从知道主机 B 的 MAC 地址。
+
+2. 主机 A 将构造一个 ARP 查询分组,并将其广播到所在的局域网中。
+
+ ARP 分组是一种特殊报文,ARP 分组有两类,一种是查询分组,另一种是响应分组,它们具有相同的格式,均包含了发送和接收的 IP 地址、发送和接收的 MAC 地址。当然了,查询分组中,发送的 IP 地址,即为主机 A 的 IP 地址,接收的 IP 地址即为主机 B 的 IP 地址,发送的 MAC 地址也是主机 A 的 MAC 地址,但接收的 MAC 地址绝不会是主机 B 的 MAC 地址(因为这正是我们要问询的!),而是一个特殊值——`FF-FF-FF-FF-FF-FF`,之前说过,该 MAC 地址是广播地址,也就是说,查询分组将广播给该局域网内的所有设备。
+
+3. 主机 A 构造的查询分组将在该局域网内广播,理论上,每一个设备都会收到该分组,并检查查询分组的接收 IP 地址是否为自己的 IP 地址,如果是,说明查询分组已经到达了主机 B,否则,该查询分组对当前设备无效,丢弃之。
+
+4. 主机 B 收到了查询分组之后,验证是对自己的问询,接着构造一个 ARP 响应分组,该分组的目的地只有一个——主机 A,发送给主机 A。同时,主机 B 提取查询分组中的 IP 地址和 MAC 地址信息,在自己的 ARP 表中构造一条主机 A 的 IP-MAC 映射记录。
+
+ ARP 响应分组具有和 ARP 查询分组相同的构造,不同的是,发送和接受的 IP 地址恰恰相反,发送的 MAC 地址为发送者本身,目标 MAC 地址为查询分组的发送者,也就是说,ARP 响应分组只有一个目的地,而非广播。
+
+5. 主机 A 终将收到主机 B 的响应分组,提取出该分组中的 IP 地址和 MAC 地址后,构造映射信息,加入到自己的 ARP 表中。
+
+
+
+在整个过程中,有几点需要补充说明的是:
+
+1. 主机 A 想要给主机 B 发送 IP 数据报,如果主机 B 的 IP-MAC 映射信息已经存在于主机 A 的 ARP 表中,那么主机 A 无需广播,只需提取 MAC 地址并构造链路层帧发送即可。
+2. ARP 表中的映射信息是有生存周期的,典型值为 20 分钟。
+3. 目标主机接收到了问询主机构造的问询报文后,将先把问询主机的 IP-MAC 映射存进自己的 ARP 表中,这样才能获取到响应的目标 MAC 地址,顺利的发送响应分组。
+
+总结来说,ARP 协议是一个**广播问询,单播响应**协议。
+
+### 不同局域网内的 MAC 寻址
+
+更复杂的情况是,发送主机 A 和接收主机 B 不在同一个子网中,假设一个一般场景,两台主机所在的子网由一台路由器联通。这里需要注意的是,一般情况下,我们说网络设备都有一个 IP 地址和一个 MAC 地址,这里说的网络设备,更严谨的说法应该是一个接口。路由器作为互联设备,具有多个接口,每个接口同样也应该具备不重复的 IP 地址和 MAC 地址。因此,在讨论 ARP 表时,路由器的多个接口都各自维护一个 ARP 表,而非一个路由器只维护一个 ARP 表。
+
+接下来,回顾同一子网内的 MAC 寻址,如果主机 A 发送一个广播问询分组,那么 A 所在的子网内所有设备(接口)都将会捕获该分组,因为该分组的目的 IP 与发送主机A的IP在同一个子网中。但是当目的IP与A不在同一子网时,A所在子网内将不会有设备成功接收该分组。那么,主机 A 应该发送怎样的查询分组呢?整个过程按照时间顺序发生的事件如下:
+
+1. 主机 A 查询 ARP 表,期望寻找到目标路由器的本子网接口的 MAC 地址。
+
+ 目标路由器指的是,根据目的主机 B 的 IP 地址,分析出 B 所在的子网,能够把报文转发到 B 所在子网的那个路由器。
+
+2. 主机 A 未能找到目标路由器的本子网接口的 MAC 地址,将采用 ARP 协议,问询到该 MAC 地址,由于目标接口与主机 A 在同一个子网内,该过程与同一局域网内的 MAC 寻址相同。
+
+3. 主机 A 获取到目标接口的 MAC 地址,先构造 IP 数据报,其中源 IP 是 A 的 IP 地址,目的 IP 地址是 B 的 IP 地址,再构造链路层帧,其中源 MAC 地址是 A 的 MAC 地址,目的 MAC 地址是**本子网内与路由器连接的接口的 MAC 地址**。主机 A 将把这个链路层帧,以单播的方式,发送给目标接口。
+
+4. 目标接口接收到了主机 A 发过来的链路层帧,解析,根据目的 IP 地址,查询转发表,将该 IP 数据报转发到与主机 B 所在子网相连的接口上。
+
+ 到此,该帧已经从主机 A 所在的子网,转移到了主机 B 所在的子网了。
+
+5. 路由器接口查询 ARP 表,期望寻找到主机 B 的 MAC 地址。
+
+6. 路由器接口如未能找到主机 B 的 MAC 地址,将采用 ARP 协议,广播问询,单播响应,获取到主机 B 的 MAC 地址。
+
+7. 路由器接口将对 IP 数据报重新封装成链路层帧,目标 MAC 地址为主机 B 的 MAC 地址,单播发送,直到目的地。
+
+
diff --git "a/docs/cs-basics/network/\350\260\242\345\270\214\344\273\201\350\200\201\345\270\210\347\232\204\343\200\212\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\343\200\213\345\206\205\345\256\271\346\200\273\347\273\223.md" b/docs/cs-basics/network/computer-network-xiexiren-summary.md
similarity index 60%
rename from "docs/cs-basics/network/\350\260\242\345\270\214\344\273\201\350\200\201\345\270\210\347\232\204\343\200\212\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\343\200\213\345\206\205\345\256\271\346\200\273\347\273\223.md"
rename to docs/cs-basics/network/computer-network-xiexiren-summary.md
index df0b07537fd6e74229174afd6b691933be2d5f95..216cbdc276a14f77aa09f8e9087bb5286096072f 100644
--- "a/docs/cs-basics/network/\350\260\242\345\270\214\344\273\201\350\200\201\345\270\210\347\232\204\343\200\212\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\343\200\213\345\206\205\345\256\271\346\200\273\347\273\223.md"
+++ b/docs/cs-basics/network/computer-network-xiexiren-summary.md
@@ -1,89 +1,55 @@
---
-title: 谢希仁老师的《计算机网络》内容总结
+title: 《计算机网络》(谢希仁)内容总结
category: 计算机基础
tag:
- 计算机网络
---
+本文是我在大二学习计算机网络期间整理, 大部分内容都来自于谢希仁老师的[《计算机网络》第七版 ](https://www.elias.ltd/usr/local/etc/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%EF%BC%88%E7%AC%AC7%E7%89%88%EF%BC%89%E8%B0%A2%E5%B8%8C%E4%BB%81.pdf)这本书。为了内容更容易理解,我对之前的整理进行了一波重构,并配上了一些相关的示意图便于理解。
-本文是我在大二学习计算机网络期间整理, 大部分内容都来自于谢希仁老师的《计算机网络》这本书。
-
-为了内容更容易理解,我对之前的整理进行了一波重构,并配上了一些相关的示意图便于理解。
-
-
-
-
-
-
-
-- [1. 计算机网络概述](#1-计算机网络概述)
- - [1.1. 基本术语](#11-基本术语)
- - [1.2. 重要知识点总结](#12-重要知识点总结)
-- [2. 物理层(Physical Layer)](#2-物理层physical-layer)
- - [2.1. 基本术语](#21-基本术语)
- - [2.2. 重要知识点总结](#22-重要知识点总结)
- - [2.3. 补充](#23-补充)
- - [2.3.1. 物理层主要做啥?](#231-物理层主要做啥)
- - [2.3.2. 几种常用的信道复用技术](#232-几种常用的信道复用技术)
- - [2.3.3. 几种常用的宽带接入技术,主要是 ADSL 和 FTTx](#233-几种常用的宽带接入技术主要是-adsl-和-fttx)
-- [3. 数据链路层(Data Link Layer)](#3-数据链路层data-link-layer)
- - [3.1. 基本术语](#31-基本术语)
- - [3.2. 重要知识点总结](#32-重要知识点总结)
- - [3.3. 补充](#33-补充)
-- [4. 网络层(Network Layer)](#4-网络层network-layer)
- - [4.1. 基本术语](#41-基本术语)
- - [4.2. 重要知识点总结](#42-重要知识点总结)
-- [5. 传输层(Transport Layer)](#5-传输层transport-layer)
- - [5.1. 基本术语](#51-基本术语)
- - [5.2. 重要知识点总结](#52-重要知识点总结)
- - [5.3. 补充(重要)](#53-补充重要)
-- [6. 应用层(Application Layer)](#6-应用层application-layer)
- - [6.1. 基本术语](#61-基本术语)
- - [6.2. 重要知识点总结](#62-重要知识点总结)
- - [6.3. 补充(重要)](#63-补充重要)
-
-
+
+相关问题:[如何评价谢希仁的计算机网络(第七版)? - 知乎](https://www.zhihu.com/question/327872966) 。
## 1. 计算机网络概述
### 1.1. 基本术语
-1. **结点 (node)** :网络中的结点可以是计算机,集线器,交换机或路由器等。
+1. **结点 (node)**:网络中的结点可以是计算机,集线器,交换机或路由器等。
2. **链路(link )** : 从一个结点到另一个结点的一段物理线路。中间没有任何其他交点。
-3. **主机(host)** :连接在因特网上的计算机。
-4. **ISP(Internet Service Provider)** :因特网服务提供者(提供商)。
+3. **主机(host)**:连接在因特网上的计算机。
+4. **ISP(Internet Service Provider)**:因特网服务提供者(提供商)。
-
+
-5. **IXP(Internet eXchange Point)** : 互联网交换点 IXP 的主要作用就是允许两个网络直接相连并交换分组,而不需要再通过第三个网络来转发分组。
+5. **IXP(Internet eXchange Point)**:互联网交换点 IXP 的主要作用就是允许两个网络直接相连并交换分组,而不需要再通过第三个网络来转发分组。
-
+
https://labs.ripe.net/Members/fergalc/ixp-traffic-during-stratos-skydive
-6. **RFC(Request For Comments)** :意思是“请求评议”,包含了关于 Internet 几乎所有的重要的文字资料。
-7. **广域网 WAN(Wide Area Network)** :任务是通过长距离运送主机发送的数据。
+6. **RFC(Request For Comments)**:意思是“请求评议”,包含了关于 Internet 几乎所有的重要的文字资料。
+7. **广域网 WAN(Wide Area Network)**:任务是通过长距离运送主机发送的数据。
8. **城域网 MAN(Metropolitan Area Network)**:用来将多个局域网进行互连。
-9. **局域网 LAN(Local Area Network)** : 学校或企业大多拥有多个互连的局域网。
+9. **局域网 LAN(Local Area Network)**:学校或企业大多拥有多个互连的局域网。
-
+
http://conexionesmanwman.blogspot.com/
-10. **个人区域网 PAN(Personal Area Network)** :在个人工作的地方把属于个人使用的电子设备用无线技术连接起来的网络 。
+10. **个人区域网 PAN(Personal Area Network)**:在个人工作的地方把属于个人使用的电子设备用无线技术连接起来的网络 。
-
+
https://www.itrelease.com/2018/07/advantages-and-disadvantages-of-personal-area-network-pan/
-12. **分组(packet )** :因特网中传送的数据单元。由首部 header 和数据段组成。分组又称为包,首部可称为包头。
-13. **存储转发(store and forward )** :路由器收到一个分组,先检查分组是否正确,并过滤掉冲突包错误。确定包正确后,取出目的地址,通过查找表找到想要发送的输出端口地址,然后将该包发送出去。
+12. **分组(packet )**:因特网中传送的数据单元。由首部 header 和数据段组成。分组又称为包,首部可称为包头。
+13. **存储转发(store and forward )**:路由器收到一个分组,先检查分组是否正确,并过滤掉冲突包错误。确定包正确后,取出目的地址,通过查找表找到想要发送的输出端口地址,然后将该包发送出去。
-
+
-14. **带宽(bandwidth)** :在计算机网络中,表示在单位时间内从网络中的某一点到另一点所能通过的“最高数据率”。常用来表示网络的通信线路所能传送数据的能力。单位是“比特每秒”,记为 b/s。
-15. **吞吐量(throughput )** :表示在单位时间内通过某个网络(或信道、接口)的数据量。吞吐量更经常地用于对现实世界中的网络的一种测量,以便知道实际上到底有多少数据量能够通过网络。吞吐量受网络的带宽或网络的额定速率的限制。
+14. **带宽(bandwidth)**:在计算机网络中,表示在单位时间内从网络中的某一点到另一点所能通过的“最高数据率”。常用来表示网络的通信线路所能传送数据的能力。单位是“比特每秒”,记为 b/s。
+15. **吞吐量(throughput )**:表示在单位时间内通过某个网络(或信道、接口)的数据量。吞吐量更经常地用于对现实世界中的网络的一种测量,以便知道实际上到底有多少数据量能够通过网络。吞吐量受网络的带宽或网络的额定速率的限制。
### 1.2. 重要知识点总结
@@ -98,43 +64,43 @@ tag:
9. 网络协议即协议,是为进行网络中的数据交换而建立的规则。计算机网络的各层以及其协议集合,称为网络的体系结构。
10. **五层体系结构由应用层,运输层,网络层(网际层),数据链路层,物理层组成。运输层最主要的协议是 TCP 和 UDP 协议,网络层最重要的协议是 IP 协议。**
-
+
下面的内容会介绍计算机网络的五层体系结构:**物理层+数据链路层+网络层(网际层)+运输层+应用层**。
## 2. 物理层(Physical Layer)
-
+
### 2.1. 基本术语
1. **数据(data)** :运送消息的实体。
-2. **信号(signal)** :数据的电气的或电磁的表现。或者说信号是适合在传输介质上传输的对象。
-3. **码元( code)** :在使用时间域(或简称为时域)的波形来表示数字信号时,代表不同离散数值的基本波形。
+2. **信号(signal)**:数据的电气的或电磁的表现。或者说信号是适合在传输介质上传输的对象。
+3. **码元( code)**:在使用时间域(或简称为时域)的波形来表示数字信号时,代表不同离散数值的基本波形。
4. **单工(simplex )** : 只能有一个方向的通信而没有反方向的交互。
-5. **半双工(half duplex )** :通信的双方都可以发送信息,但不能双方同时发送(当然也就不能同时接收)。
+5. **半双工(half duplex )**:通信的双方都可以发送信息,但不能双方同时发送(当然也就不能同时接收)。
6. **全双工(full duplex)** : 通信的双方可以同时发送和接收信息。
-
+
7. **失真**:失去真实性,主要是指接受到的信号和发送的信号不同,有磨损和衰减。影响失真程度的因素:1.码元传输速率 2.信号传输距离 3.噪声干扰 4.传输媒体质量
-
+
8. **奈氏准则** : 在任何信道中,码元的传输的效率是有上限的,传输速率超过此上限,就会出现严重的码间串扰问题,使接收端对码元的判决(即识别)成为不可能。
-9. **香农定理** :在带宽受限且有噪声的信道中,为了不产生误差,信息的数据传输速率有上限值。
+9. **香农定理**:在带宽受限且有噪声的信道中,为了不产生误差,信息的数据传输速率有上限值。
10. **基带信号(baseband signal)** : 来自信源的信号。指没有经过调制的数字信号或模拟信号。
-11. **带通(频带)信号(bandpass signal)** :把基带信号经过载波调制后,把信号的频率范围搬移到较高的频段以便在信道中传输(即仅在一段频率范围内能够通过信道),这里调制过后的信号就是带通信号。
+11. **带通(频带)信号(bandpass signal)**:把基带信号经过载波调制后,把信号的频率范围搬移到较高的频段以便在信道中传输(即仅在一段频率范围内能够通过信道),这里调制过后的信号就是带通信号。
12. **调制(modulation )** : 对信号源的信息进行处理后加到载波信号上,使其变为适合在信道传输的形式的过程。
13. **信噪比(signal-to-noise ratio )** : 指信号的平均功率和噪声的平均功率之比,记为 S/N。信噪比(dB)=10\*log10(S/N)。
-14. **信道复用(channel multiplexing )** :指多个用户共享同一个信道。(并不一定是同时)。
+14. **信道复用(channel multiplexing )**:指多个用户共享同一个信道。(并不一定是同时)。
-
+
-15. **比特率(bit rate )** :单位时间(每秒)内传送的比特数。
-16. **波特率(baud rate)** :单位时间载波调制状态改变的次数。针对数据信号对载波的调制速率。
-17. **复用(multiplexing)** :共享信道的方法。
-18. **ADSL(Asymmetric Digital Subscriber Line )** :非对称数字用户线。
+15. **比特率(bit rate )**:单位时间(每秒)内传送的比特数。
+16. **波特率(baud rate)**:单位时间载波调制状态改变的次数。针对数据信号对载波的调制速率。
+17. **复用(multiplexing)**:共享信道的方法。
+18. **ADSL(Asymmetric Digital Subscriber Line )**:非对称数字用户线。
19. **光纤同轴混合网(HFC 网)** :在目前覆盖范围很广的有线电视网的基础上开发的一种居民宽带接入网
### 2.2. 重要知识点总结
@@ -159,11 +125,11 @@ tag:
#### 2.3.2. 几种常用的信道复用技术
-1. **频分复用(FDM)** :所有用户在同样的时间占用不同的带宽资源。
-2. **时分复用(TDM)** :所有用户在不同的时间占用同样的频带宽度(分时不分频)。
-3. **统计时分复用 (Statistic TDM)** :改进的时分复用,能够明显提高信道的利用率。
-4. **码分复用(CDM)** : 用户使用经过特殊挑选的不同码型,因此各用户之间不会造成干扰。这种系统发送的信号有很强的抗干扰能力,其频谱类似于白噪声,不易被敌人发现。
-5. **波分复用( WDM)** :波分复用就是光的频分复用。
+1. **频分复用(FDM)**:所有用户在同样的时间占用不同的带宽资源。
+2. **时分复用(TDM)**:所有用户在不同的时间占用同样的频带宽度(分时不分频)。
+3. **统计时分复用 (Statistic TDM)**:改进的时分复用,能够明显提高信道的利用率。
+4. **码分复用(CDM)**:用户使用经过特殊挑选的不同码型,因此各用户之间不会造成干扰。这种系统发送的信号有很强的抗干扰能力,其频谱类似于白噪声,不易被敌人发现。
+5. **波分复用( WDM)**:波分复用就是光的频分复用。
#### 2.3.3. 几种常用的宽带接入技术,主要是 ADSL 和 FTTx
@@ -171,24 +137,24 @@ tag:
## 3. 数据链路层(Data Link Layer)
-
+
### 3.1. 基本术语
-1. **链路(link)** :一个结点到相邻结点的一段物理链路。
-2. **数据链路(data link)** :把实现控制数据运输的协议的硬件和软件加到链路上就构成了数据链路。
-3. **循环冗余检验 CRC(Cyclic Redundancy Check)** :为了保证数据传输的可靠性,CRC 是数据链路层广泛使用的一种检错技术。
-4. **帧(frame)** :一个数据链路层的传输单元,由一个数据链路层首部和其携带的封包所组成协议数据单元。
-5. **MTU(Maximum Transfer Uint )** :最大传送单元。帧的数据部分的的长度上限。
-6. **误码率 BER(Bit Error Rate )** :在一段时间内,传输错误的比特占所传输比特总数的比率。
-7. **PPP(Point-to-Point Protocol )** :点对点协议。即用户计算机和 ISP 进行通信时所使用的数据链路层协议。以下是 PPP 帧的示意图:
- 
-8. **MAC 地址(Media Access Control 或者 Medium Access Control)** :意译为媒体访问控制,或称为物理地址、硬件地址,用来定义网络设备的位置。在 OSI 模型中,第三层网络层负责 IP 地址,第二层数据链路层则负责 MAC 地址。因此一个主机会有一个 MAC 地址,而每个网络位置会有一个专属于它的 IP 地址 。地址是识别某个系统的重要标识符,“名字指出我们所要寻找的资源,地址指出资源所在的地方,路由告诉我们如何到达该处。”
+1. **链路(link)**:一个结点到相邻结点的一段物理链路。
+2. **数据链路(data link)**:把实现控制数据运输的协议的硬件和软件加到链路上就构成了数据链路。
+3. **循环冗余检验 CRC(Cyclic Redundancy Check)**:为了保证数据传输的可靠性,CRC 是数据链路层广泛使用的一种检错技术。
+4. **帧(frame)**:一个数据链路层的传输单元,由一个数据链路层首部和其携带的封包所组成协议数据单元。
+5. **MTU(Maximum Transfer Uint )**:最大传送单元。帧的数据部分的的长度上限。
+6. **误码率 BER(Bit Error Rate )**:在一段时间内,传输错误的比特占所传输比特总数的比率。
+7. **PPP(Point-to-Point Protocol )**:点对点协议。即用户计算机和 ISP 进行通信时所使用的数据链路层协议。以下是 PPP 帧的示意图:
+ 
+8. **MAC 地址(Media Access Control 或者 Medium Access Control)**:意译为媒体访问控制,或称为物理地址、硬件地址,用来定义网络设备的位置。在 OSI 模型中,第三层网络层负责 IP 地址,第二层数据链路层则负责 MAC 地址。因此一个主机会有一个 MAC 地址,而每个网络位置会有一个专属于它的 IP 地址 。地址是识别某个系统的重要标识符,“名字指出我们所要寻找的资源,地址指出资源所在的地方,路由告诉我们如何到达该处。”
-
+
-9. **网桥(bridge)** :一种用于数据链路层实现中继,连接两个或多个局域网的网络互连设备。
-10. **交换机(switch )** :广义的来说,交换机指的是一种通信系统中完成信息交换的设备。这里工作在数据链路层的交换机指的是交换式集线器,其实质是一个多接口的网桥
+9. **网桥(bridge)**:一种用于数据链路层实现中继,连接两个或多个局域网的网络互连设备。
+10. **交换机(switch )**:广义的来说,交换机指的是一种通信系统中完成信息交换的设备。这里工作在数据链路层的交换机指的是交换式集线器,其实质是一个多接口的网桥
### 3.2. 重要知识点总结
@@ -214,22 +180,22 @@ tag:
## 4. 网络层(Network Layer)
-
+
### 4.1. 基本术语
1. **虚电路(Virtual Circuit)** : 在两个终端设备的逻辑或物理端口之间,通过建立的双向的透明传输通道。虚电路表示这只是一条逻辑上的连接,分组都沿着这条逻辑连接按照存储转发方式传送,而并不是真正建立了一条物理连接。
2. **IP(Internet Protocol )** : 网际协议 IP 是 TCP/IP 体系中两个最主要的协议之一,是 TCP/IP 体系结构网际层的核心。配套的有 ARP,RARP,ICMP,IGMP。
3. **ARP(Address Resolution Protocol)** : 地址解析协议。地址解析协议 ARP 把 IP 地址解析为硬件地址。
-4. **ICMP(Internet Control Message Protocol )** :网际控制报文协议 (ICMP 允许主机或路由器报告差错情况和提供有关异常情况的报告)。
-5. **子网掩码(subnet mask )** :它是一种用来指明一个 IP 地址的哪些位标识的是主机所在的子网以及哪些位标识的是主机的位掩码。子网掩码不能单独存在,它必须结合 IP 地址一起使用。
+4. **ICMP(Internet Control Message Protocol )**:网际控制报文协议 (ICMP 允许主机或路由器报告差错情况和提供有关异常情况的报告)。
+5. **子网掩码(subnet mask )**:它是一种用来指明一个 IP 地址的哪些位标识的是主机所在的子网以及哪些位标识的是主机的位掩码。子网掩码不能单独存在,它必须结合 IP 地址一起使用。
6. **CIDR( Classless Inter-Domain Routing )**:无分类域间路由选择 (特点是消除了传统的 A 类、B 类和 C 类地址以及划分子网的概念,并使用各种长度的“网络前缀”(network-prefix)来代替分类地址中的网络号和子网号)。
-7. **默认路由(default route)** :当在路由表中查不到能到达目的地址的路由时,路由器选择的路由。默认路由还可以减小路由表所占用的空间和搜索路由表所用的时间。
-8. **路由选择算法(Virtual Circuit)** :路由选择协议的核心部分。因特网采用自适应的,分层次的路由选择协议。
+7. **默认路由(default route)**:当在路由表中查不到能到达目的地址的路由时,路由器选择的路由。默认路由还可以减小路由表所占用的空间和搜索路由表所用的时间。
+8. **路由选择算法(Virtual Circuit)**:路由选择协议的核心部分。因特网采用自适应的,分层次的路由选择协议。
### 4.2. 重要知识点总结
-1. **TCP/IP 协议中的网络层向上只提供简单灵活的,无连接的,尽最大努力交付的数据报服务。网络层不提供服务质量的承诺,不保证分组交付的时限所传送的分组可能出错,丢失,重复和失序。进程之间通信的可靠性由运输层负责**
+1. **TCP/IP 协议中的网络层向上只提供简单灵活的,无连接的,尽最大努力交付的数据报服务。网络层不提供服务质量的承诺,不保证分组交付的时限,所传送的分组可能出错、丢失、重复和失序。进程之间通信的可靠性由运输层负责**
2. 在互联网的交付有两种,一是在本网络直接交付不用经过路由器,另一种是和其他网络的间接交付,至少经过一个路由器,但最后一次一定是直接交付
3. 分类的 IP 地址由网络号字段(指明网络)和主机号字段(指明主机)组成。网络号字段最前面的类别指明 IP 地址的类别。IP 地址是一种分等级的地址结构。IP 地址管理机构分配 IP 地址时只分配网络号,主机号由得到该网络号的单位自行分配。路由器根据目的主机所连接的网络号来转发分组。一个路由器至少连接到两个网络,所以一个路由器至少应当有两个不同的 IP 地址
4. IP 数据报分为首部和数据两部分。首部的前一部分是固定长度,共 20 字节,是所有 IP 数据包必须具有的(源地址,目的地址,总长度等重要地段都固定在首部)。一些长度可变的可选字段固定在首部的后面。IP 首部中的生存时间给出了 IP 数据报在互联网中所能经过的最大路由器数。可防止 IP 数据报在互联网中无限制的兜圈子。
@@ -242,22 +208,22 @@ tag:
## 5. 传输层(Transport Layer)
-
+
### 5.1. 基本术语
-1. **进程(process)** :指计算机中正在运行的程序实体。
-2. **应用进程互相通信** :一台主机的进程和另一台主机中的一个进程交换数据的过程(另外注意通信真正的端点不是主机而是主机中的进程,也就是说端到端的通信是应用进程之间的通信)。
-3. **传输层的复用与分用** :复用指发送方不同的进程都可以通过同一个运输层协议传送数据。分用指接收方的运输层在剥去报文的首部后能把这些数据正确的交付到目的应用进程。
-4. **TCP(Transmission Control Protocol)** :传输控制协议。
-5. **UDP(User Datagram Protocol)** :用户数据报协议。
+1. **进程(process)**:指计算机中正在运行的程序实体。
+2. **应用进程互相通信**:一台主机的进程和另一台主机中的一个进程交换数据的过程(另外注意通信真正的端点不是主机而是主机中的进程,也就是说端到端的通信是应用进程之间的通信)。
+3. **传输层的复用与分用**:复用指发送方不同的进程都可以通过同一个运输层协议传送数据。分用指接收方的运输层在剥去报文的首部后能把这些数据正确的交付到目的应用进程。
+4. **TCP(Transmission Control Protocol)**:传输控制协议。
+5. **UDP(User Datagram Protocol)**:用户数据报协议。
-
+
-6. **端口(port)** :端口的目的是为了确认对方机器的哪个进程在与自己进行交互,比如 MSN 和 QQ 的端口不同,如果没有端口就可能出现 QQ 进程和 MSN 交互错误。端口又称协议端口号。
-7. **停止等待协议(stop-and-wait)** :指发送方每发送完一个分组就停止发送,等待对方确认,在收到确认之后在发送下一个分组。
+6. **端口(port)**:端口的目的是为了确认对方机器的哪个进程在与自己进行交互,比如 MSN 和 QQ 的端口不同,如果没有端口就可能出现 QQ 进程和 MSN 交互错误。端口又称协议端口号。
+7. **停止等待协议(stop-and-wait)**:指发送方每发送完一个分组就停止发送,等待对方确认,在收到确认之后在发送下一个分组。
8. **流量控制** : 就是让发送方的发送速率不要太快,既要让接收方来得及接收,也不要使网络发生拥塞。
-9. **拥塞控制** :防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。
+9. **拥塞控制**:防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。
### 5.2. 重要知识点总结
@@ -274,7 +240,7 @@ tag:
11. 停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
12. 为了提高传输效率,发送方可以不使用低效率的停止等待协议,而是采用流水线传输。流水线传输就是发送方可连续发送多个分组,不必每发完一个分组就停下来等待对方确认。这样可使信道上一直有数据不间断的在传送。这种传输方式可以明显提高信道利用率。
13. 停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为自动重传请求 ARQ。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。连续 ARQ 协议可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。
-14. TCP 报文段的前 20 个字节是固定的,后面有 4n 字节是根据需要增加的选项。因此,TCP 首部的最小长度是 20 字节。
+14. TCP 报文段的前 20 个字节是固定的,其后有 40 字节长度的可选字段。如果加入可选字段后首部长度不是 4 的整数倍字节,需要在再在之后用 0 填充。因此,TCP 首部的长度取值为 20+4n 字节,最长为 60 字节。
15. **TCP 使用滑动窗口机制。发送窗口里面的序号表示允许发送的序号。发送窗口后沿的后面部分表示已发送且已收到确认,而发送窗口前沿的前面部分表示不允许发送。发送窗口后沿的变化情况有两种可能,即不动(没有收到新的确认)和前移(收到了新的确认)。发送窗口的前沿通常是不断向前移动的。一般来说,我们总是希望数据传输更快一些。但如果发送方把数据发送的过快,接收方就可能来不及接收,这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收。**
16. 在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
17. **为了进行拥塞控制,TCP 发送方要维持一个拥塞窗口 cwnd 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。**
@@ -295,48 +261,46 @@ tag:
## 6. 应用层(Application Layer)
-
+
### 6.1. 基本术语
-1. **域名系统(DNS)** :域名系统(DNS,Domain Name System)将人类可读的域名 (例如,www.baidu.com) 转换为机器可读的 IP 地址 (例如,220.181.38.148)。我们可以将其理解为专为互联网设计的电话薄。
+1. **域名系统(DNS)**:域名系统(DNS,Domain Name System)将人类可读的域名 (例如,www.baidu.com) 转换为机器可读的 IP 地址 (例如,220.181.38.148)。我们可以将其理解为专为互联网设计的电话薄。
-
+
https://www.seobility.net/en/wiki/HTTP_headers
-2. **文件传输协议(FTP)** :FTP 是 File Transfer Protocol(文件传输协议)的英文简称,而中文简称为“文传协议”。用于 Internet 上的控制文件的双向传输。同时,它也是一个应用程序(Application)。基于不同的操作系统有不同的 FTP 应用程序,而所有这些应用程序都遵守同一种协议以传输文件。在 FTP 的使用当中,用户经常遇到两个概念:"下载"(Download)和"上传"(Upload)。 "下载"文件就是从远程主机拷贝文件至自己的计算机上;"上传"文件就是将文件从自己的计算机中拷贝至远程主机上。用 Internet 语言来说,用户可通过客户机程序向(从)远程主机上传(下载)文件。
+2. **文件传输协议(FTP)**:FTP 是 File Transfer Protocol(文件传输协议)的英文简称,而中文简称为“文传协议”。用于 Internet 上的控制文件的双向传输。同时,它也是一个应用程序(Application)。基于不同的操作系统有不同的 FTP 应用程序,而所有这些应用程序都遵守同一种协议以传输文件。在 FTP 的使用当中,用户经常遇到两个概念:"下载"(Download)和"上传"(Upload)。 "下载"文件就是从远程主机拷贝文件至自己的计算机上;"上传"文件就是将文件从自己的计算机中拷贝至远程主机上。用 Internet 语言来说,用户可通过客户机程序向(从)远程主机上传(下载)文件。
-
+
-3. **简单文件传输协议(TFTP)** :TFTP(Trivial File Transfer Protocol,简单文件传输协议)是 TCP/IP 协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。端口号为 69。
-4. **远程终端协议(TELNET)** :Telnet 协议是 TCP/IP 协议族中的一员,是 Internet 远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用者的电脑上使用 telnet 程序,用它连接到服务器。终端使用者可以在 telnet 程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个 telnet 会话,必须输入用户名和密码来登录服务器。Telnet 是常用的远程控制 Web 服务器的方法。
-5. **万维网(WWW)** :WWW 是环球信息网的缩写,(亦作“Web”、“WWW”、“'W3'”,英文全称为“World Wide Web”),中文名字为“万维网”,"环球网"等,常简称为 Web。分为 Web 客户端和 Web 服务器程序。WWW 可以让 Web 客户端(常用浏览器)访问浏览 Web 服务器上的页面。是一个由许多互相链接的超文本组成的系统,通过互联网访问。在这个系统中,每个有用的事物,称为一样“资源”;并且由一个全局“统一资源标识符”(URI)标识;这些资源通过超文本传输协议(Hypertext Transfer Protocol)传送给用户,而后者通过点击链接来获得资源。万维网联盟(英语:World Wide Web Consortium,简称 W3C),又称 W3C 理事会。1994 年 10 月在麻省理工学院(MIT)计算机科学实验室成立。万维网联盟的创建者是万维网的发明者蒂姆·伯纳斯-李。万维网并不等同互联网,万维网只是互联网所能提供的服务其中之一,是靠着互联网运行的一项服务。
+3. **简单文件传输协议(TFTP)**:TFTP(Trivial File Transfer Protocol,简单文件传输协议)是 TCP/IP 协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。端口号为 69。
+4. **远程终端协议(TELNET)**:Telnet 协议是 TCP/IP 协议族中的一员,是 Internet 远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用者的电脑上使用 telnet 程序,用它连接到服务器。终端使用者可以在 telnet 程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个 telnet 会话,必须输入用户名和密码来登录服务器。Telnet 是常用的远程控制 Web 服务器的方法。
+5. **万维网(WWW)**:WWW 是环球信息网的缩写,(亦作“Web”、“WWW”、“'W3'”,英文全称为“World Wide Web”),中文名字为“万维网”,"环球网"等,常简称为 Web。分为 Web 客户端和 Web 服务器程序。WWW 可以让 Web 客户端(常用浏览器)访问浏览 Web 服务器上的页面。是一个由许多互相链接的超文本组成的系统,通过互联网访问。在这个系统中,每个有用的事物,称为一样“资源”;并且由一个全局“统一资源标识符”(URI)标识;这些资源通过超文本传输协议(Hypertext Transfer Protocol)传送给用户,而后者通过点击链接来获得资源。万维网联盟(英语:World Wide Web Consortium,简称 W3C),又称 W3C 理事会。1994 年 10 月在麻省理工学院(MIT)计算机科学实验室成立。万维网联盟的创建者是万维网的发明者蒂姆·伯纳斯-李。万维网并不等同互联网,万维网只是互联网所能提供的服务其中之一,是靠着互联网运行的一项服务。
6. **万维网的大致工作工程:**
-
+
-7. **统一资源定位符(URL)** :统一资源定位符是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
-8. **超文本传输协议(HTTP)** :超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。1960 年美国人 Ted Nelson 构思了一种通过计算机处理文本信息的方法,并称之为超文本(hypertext),这成为了 HTTP 超文本传输协议标准架构的发展根基。
+7. **统一资源定位符(URL)**:统一资源定位符是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的 URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。
+8. **超文本传输协议(HTTP)**:超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。1960 年美国人 Ted Nelson 构思了一种通过计算机处理文本信息的方法,并称之为超文本(hypertext),这成为了 HTTP 超文本传输协议标准架构的发展根基。
HTTP 协议的本质就是一种浏览器与服务器之间约定好的通信格式。HTTP 的原理如下图所示:
-
+
-10. **代理服务器(Proxy Server)** : 代理服务器(Proxy Server)是一种网络实体,它又称为万维网高速缓存。 代理服务器把最近的一些请求和响应暂存在本地磁盘中。当新请求到达时,若代理服务器发现这个请求与暂时存放的的请求相同,就返回暂存的响应,而不需要按 URL 的地址再次去互联网访问该资源。代理服务器可在客户端或服务器工作,也可以在中间系统工作。
+10. **代理服务器(Proxy Server)**:代理服务器(Proxy Server)是一种网络实体,它又称为万维网高速缓存。 代理服务器把最近的一些请求和响应暂存在本地磁盘中。当新请求到达时,若代理服务器发现这个请求与暂时存放的的请求相同,就返回暂存的响应,而不需要按 URL 的地址再次去互联网访问该资源。代理服务器可在客户端或服务器工作,也可以在中间系统工作。
11. **简单邮件传输协议(SMTP)** : SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。 SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。 通过 SMTP 协议所指定的服务器,就可以把 E-mail 寄到收信人的服务器上了,整个过程只要几分钟。SMTP 服务器则是遵循 SMTP 协议的发送邮件服务器,用来发送或中转发出的电子邮件。
-
+
-https://www.campaignmonitor.com/resources/knowledge-base/what-is-the-code-that-makes-bcc-or-cc-operate-in-an-email/
+
https://www.campaignmonitor.com/resources/knowledge-base/what-is-the-code-that-makes-bcc-or-cc-operate-in-an-email/
11. **搜索引擎** :搜索引擎(Search Engine)是指根据一定的策略、运用特定的计算机程序从互联网上搜集信息,在对信息进行组织和处理后,为用户提供检索服务,将用户检索相关的信息展示给用户的系统。搜索引擎包括全文索引、目录索引、元搜索引擎、垂直搜索引擎、集合式搜索引擎、门户搜索引擎与免费链接列表等。
-
-
-12. **垂直搜索引擎** :垂直搜索引擎是针对某一个行业的专业搜索引擎,是搜索引擎的细分和延伸,是对网页库中的某类专门的信息进行一次整合,定向分字段抽取出需要的数据进行处理后再以某种形式返回给用户。垂直搜索是相对通用搜索引擎的信息量大、查询不准确、深度不够等提出来的新的搜索引擎服务模式,通过针对某一特定领域、某一特定人群或某一特定需求提供的有一定价值的信息和相关服务。其特点就是“专、精、深”,且具有行业色彩,相比较通用搜索引擎的海量信息无序化,垂直搜索引擎则显得更加专注、具体和深入。
+12. **垂直搜索引擎**:垂直搜索引擎是针对某一个行业的专业搜索引擎,是搜索引擎的细分和延伸,是对网页库中的某类专门的信息进行一次整合,定向分字段抽取出需要的数据进行处理后再以某种形式返回给用户。垂直搜索是相对通用搜索引擎的信息量大、查询不准确、深度不够等提出来的新的搜索引擎服务模式,通过针对某一特定领域、某一特定人群或某一特定需求提供的有一定价值的信息和相关服务。其特点就是“专、精、深”,且具有行业色彩,相比较通用搜索引擎的海量信息无序化,垂直搜索引擎则显得更加专注、具体和深入。
13. **全文索引** :全文索引技术是目前搜索引擎的关键技术。试想在 1M 大小的文件中搜索一个词,可能需要几秒,在 100M 的文件中可能需要几十秒,如果在更大的文件中搜索那么就需要更大的系统开销,这样的开销是不现实的。所以在这样的矛盾下出现了全文索引技术,有时候有人叫倒排文档技术。
-14. **目录索引** :目录索引( search index/directory),顾名思义就是将网站分门别类地存放在相应的目录中,因此用户在查询信息时,可选择关键词搜索,也可按分类目录逐层查找。
+14. **目录索引**:目录索引( search index/directory),顾名思义就是将网站分门别类地存放在相应的目录中,因此用户在查询信息时,可选择关键词搜索,也可按分类目录逐层查找。
### 6.2. 重要知识点总结
diff --git a/docs/cs-basics/network/dns.md b/docs/cs-basics/network/dns.md
new file mode 100644
index 0000000000000000000000000000000000000000..21eae6a0ff35c477b56a44454a7fc9ee4a27ab82
--- /dev/null
+++ b/docs/cs-basics/network/dns.md
@@ -0,0 +1,102 @@
+---
+title: DNS 域名系统详解(应用层)
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+DNS(Domain Name System)域名管理系统,是当用户使用浏览器访问网址之后,使用的第一个重要协议。DNS 要解决的是**域名和 IP 地址的映射问题**。
+
+
+
+在实际使用中,有一种情况下,浏览器是可以不必动用 DNS 就可以获知域名和 IP 地址的映射的。浏览器在本地会维护一个`hosts`列表,一般来说浏览器要先查看要访问的域名是否在`hosts`列表中,如果有的话,直接提取对应的 IP 地址记录,就好了。如果本地`hosts`列表内没有域名-IP 对应记录的话,那么 DNS 就闪亮登场了。
+
+目前 DNS 的设计采用的是分布式、层次数据库结构,**DNS 是应用层协议,基于 UDP 协议之上,端口为 53** 。
+
+
+
+DNS 服务器自底向上可以依次分为以下几个层级(所有 DNS 服务器都属于以下四个类别之一):
+
+- 根 DNS 服务器。根 DNS 服务器提供 TLD 服务器的 IP 地址。目前世界上只有 13 组根服务器,我国境内目前仍没有根服务器。
+- 顶级域 DNS 服务器(TLD 服务器)。顶级域是指域名的后缀,如`com`、`org`、`net`和`edu`等。国家也有自己的顶级域,如`uk`、`fr`和`ca`。TLD 服务器提供了权威 DNS 服务器的 IP 地址。
+- 权威 DNS 服务器。在因特网上具有公共可访问主机的每个组织机构必须提供公共可访问的 DNS 记录,这些记录将这些主机的名字映射为 IP 地址。
+- 本地 DNS 服务器。每个 ISP(互联网服务提供商)都有一个自己的本地 DNS 服务器。当主机发出 DNS 请求时,该请求被发往本地 DNS 服务器,它起着代理的作用,并将该请求转发到 DNS 层次结构中。严格说来,不属于 DNS 层级结构。
+
+## DNS 工作流程
+
+以下图为例,介绍 DNS 的查询解析过程。DNS 的查询解析过程分为两种模式:
+
+- **迭代**
+- **递归**
+
+下图是实践中常采用的方式,从请求主机到本地 DNS 服务器的查询是递归的,其余的查询时迭代的。
+
+
+
+现在,主机`cis.poly.edu`想知道`gaia.cs.umass.edu`的 IP 地址。假设主机`cis.poly.edu`的本地 DNS 服务器为`dns.poly.edu`,并且`gaia.cs.umass.edu`的权威 DNS 服务器为`dns.cs.umass.edu`。
+
+1. 首先,主机`cis.poly.edu`向本地 DNS 服务器`dns.poly.edu`发送一个 DNS 请求,该查询报文包含被转换的域名`gaia.cs.umass.edu`。
+2. 本地 DNS 服务器`dns.poly.edu`检查本机缓存,发现并无记录,也不知道`gaia.cs.umass.edu`的 IP 地址该在何处,不得不向根服务器发送请求。
+3. 根服务器注意到请求报文中含有`edu`顶级域,因此告诉本地 DNS,你可以向`edu`的 TLD DNS 发送请求,因为目标域名的 IP 地址很可能在那里。
+4. 本地 DNS 获取到了`edu`的 TLD DNS 服务器地址,向其发送请求,询问`gaia.cs.umass.edu`的 IP 地址。
+5. `edu`的 TLD DNS 服务器仍不清楚请求域名的 IP 地址,但是它注意到该域名有`umass.edu`前缀,因此返回告知本地 DNS,`umass.edu`的权威服务器可能记录了目标域名的 IP 地址。
+6. 这一次,本地 DNS 将请求发送给权威 DNS 服务器`dns.cs.umass.edu`。
+7. 终于,由于`gaia.cs.umass.edu`向权威 DNS 服务器备案过,在这里有它的 IP 地址记录,权威 DNS 成功地将 IP 地址返回给本地 DNS。
+8. 最后,本地 DNS 获取到了目标域名的 IP 地址,将其返回给请求主机。
+
+除了迭代式查询,还有一种递归式查询如下图,具体过程和上述类似,只是顺序有所不同。
+
+
+
+另外,DNS 的缓存位于本地 DNS 服务器。由于全世界的根服务器甚少,只有 400 多台,分为 13 组,且顶级域的数量也在一个可数的范围内,因此本地 DNS 通常已经缓存了很多 TLD DNS 服务器,所以在实际查找过程中,无需访问根服务器。根服务器通常是被跳过的,不请求的。
+
+## DNS 报文格式
+
+DNS 的报文格式如下图所示:
+
+
+
+DNS 报文分为查询和回答报文,两种形式的报文结构相同。
+
+- 标识符。16 比特,用于标识该查询。这个标识符会被复制到对查询的回答报文中,以便让客户用它来匹配发送的请求和接收到的回答。
+- 标志。1 比特的”查询/回答“标识位,`0`表示查询报文,`1`表示回答报文;1 比特的”权威的“标志位(当某 DNS 服务器是所请求名字的权威 DNS 服务器时,且是回答报文,使用”权威的“标志);1 比特的”希望递归“标志位,显式地要求执行递归查询;1 比特的”递归可用“标志位,用于回答报文中,表示 DNS 服务器支持递归查询。
+- 问题数、回答 RR 数、权威 RR 数、附加 RR 数。分别指示了后面 4 类数据区域出现的数量。
+- 问题区域。包含正在被查询的主机名字,以及正被询问的问题类型。
+- 回答区域。包含了对最初请求的名字的资源记录。**在回答报文的回答区域中可以包含多条 RR,因此一个主机名能够有多个 IP 地址。**
+- 权威区域。包含了其他权威服务器的记录。
+- 附加区域。包含了其他有帮助的记录。
+
+## DNS 记录
+
+DNS 服务器在响应查询时,需要查询自己的数据库,数据库中的条目被称为**资源记录(Resource Record,RR)**。RR 提供了主机名到 IP 地址的映射。RR 是一个包含了`Name`, `Value`, `Type`, `TTL`四个字段的四元组。
+
+
+
+`TTL`是该记录的生存时间,它决定了资源记录应当从缓存中删除的时间。
+
+`Name`和`Value`字段的取值取决于`Type`:
+
+
+
+- 如果`Type=A`,则`Name`是主机名信息,`Value` 是该主机名对应的 IP 地址。这样的 RR 记录了一条主机名到 IP 地址的映射。
+- 如果 `Type=AAAA` (与 `A` 记录非常相似),唯一的区别是 A 记录使用的是 IPv4,而 `AAAA` 记录使用的是 IPv6。
+- 如果`Type=CNAME` (Canonical Name Record,真实名称记录) ,则`Value`是别名为`Name`的主机对应的规范主机名。`Value`值才是规范主机名。`CNAME` 记录将一个主机名映射到另一个主机名。`CNAME` 记录用于为现有的 `A` 记录创建别名。下文有示例。
+- 如果`Type=NS`,则`Name`是个域,而`Value`是个知道如何获得该域中主机 IP 地址的权威 DNS 服务器的主机名。通常这样的 RR 是由 TLD 服务器发布的。
+- 如果`Type=MX` ,则`Value`是个别名为`Name`的邮件服务器的规范主机名。既然有了 `MX` 记录,那么邮件服务器可以和其他服务器使用相同的别名。为了获得邮件服务器的规范主机名,需要请求 `MX` 记录;为了获得其他服务器的规范主机名,需要请求 `CNAME` 记录。
+
+`CNAME`记录总是指向另一则域名,而非 IP 地址。假设有下述 DNS zone:
+
+```
+NAME TYPE VALUE
+--------------------------------------------------
+bar.example.com. CNAME foo.example.com.
+foo.example.com. A 192.0.2.23
+```
+
+当用户查询 `bar.example.com` 的时候,DNS Server 实际返回的是 `foo.example.com` 的 IP 地址。
+
+## 参考
+
+- DNS 服务器类型:https://www.cloudflare.com/zh-cn/learning/dns/dns-server-types/
+- DNS Message Resource Record Field Formats:http://www.tcpipguide.com/free/t_DNSMessageResourceRecordFieldFormats-2.htm
+- Understanding Different Types of Record in DNS Server:https://www.mustbegeek.com/understanding-different-types-of-record-in-dns-server/
diff --git a/docs/cs-basics/network/http-status-codes.md b/docs/cs-basics/network/http-status-codes.md
new file mode 100644
index 0000000000000000000000000000000000000000..4cacb50cd42f496d85095ea9e34a2be045d12922
--- /dev/null
+++ b/docs/cs-basics/network/http-status-codes.md
@@ -0,0 +1,70 @@
+---
+title: HTTP 常见状态码总结(应用层)
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+HTTP 状态码用于描述 HTTP 请求的结果,比如 2xx 就代表请求被成功处理。
+
+
+
+### 1xx Informational(信息性状态码)
+
+相比于其他类别状态码来说,1xx 你平时你大概率不会碰到,所以这里直接跳过。
+
+### 2xx Success(成功状态码)
+
+- **200 OK**:请求被成功处理。比如我们发送一个查询用户数据的 HTTP 请求到服务端,服务端正确返回了用户数据。这个是我们平时最常见的一个 HTTP 状态码。
+- **201 Created**:请求被成功处理并且在服务端创建了一个新的资源。比如我们通过 POST 请求创建一个新的用户。
+- **202 Accepted**:服务端已经接收到了请求,但是还未处理。
+- **204 No Content**:服务端已经成功处理了请求,但是没有返回任何内容。
+
+这里格外提一下 204 状态码,平时学习/工作中见到的次数并不多。
+
+[HTTP RFC 2616 对 204 状态码的描述](https://tools.ietf.org/html/rfc2616#section-10.2.5)如下:
+
+> The server has fulfilled the request but does not need to return an
+> entity-body, and might want to return updated metainformation. The
+> response MAY include new or updated metainformation in the form of
+> entity-headers, which if present SHOULD be associated with the
+> requested variant.
+>
+> If the client is a user agent, it SHOULD NOT change its document view
+> from that which caused the request to be sent. This response is
+> primarily intended to allow input for actions to take place without
+> causing a change to the user agent's active document view, although
+> any new or updated metainformation SHOULD be applied to the document
+> currently in the user agent's active view.
+>
+> The 204 response MUST NOT include a message-body, and thus is always
+> terminated by the first empty line after the header fields.
+
+简单来说,204 状态码描述的是我们向服务端发送 HTTP 请求之后,只关注处理结果是否成功的场景。也就是说我们需要的就是一个结果:true/false。
+
+举个例子:你要追一个女孩子,你问女孩子:“我能追你吗?”,女孩子回答:“好!”。我们把这个女孩子当做是服务端就很好理解 204 状态码了。
+
+### 3xx Redirection(重定向状态码)
+
+- **301 Moved Permanently**:资源被永久重定向了。比如你的网站的网址更换了。
+- **302 Found**:资源被临时重定向了。比如你的网站的某些资源被暂时转移到另外一个网址。
+
+### 4xx Client Error(客户端错误状态码)
+
+- **400 Bad Request**:发送的 HTTP 请求存在问题。比如请求参数不合法、请求方法错误。
+- **401 Unauthorized**:未认证却请求需要认证之后才能访问的资源。
+- **403 Forbidden**:直接拒绝 HTTP 请求,不处理。一般用来针对非法请求。
+- **404 Not Found**:你请求的资源未在服务端找到。比如你请求某个用户的信息,服务端并没有找到指定的用户。
+- **409 Conflict**:表示请求的资源与服务端当前的状态存在冲突,请求无法被处理。
+
+### 5xx Server Error(服务端错误状态码)
+
+- **500 Internal Server Error**:服务端出问题了(通常是服务端出 Bug 了)。比如你服务端处理请求的时候突然抛出异常,但是异常并未在服务端被正确处理。
+- **502 Bad Gateway**:我们的网关将请求转发到服务端,但是服务端返回的却是一个错误的响应。
+
+### 参考
+
+- https://www.restapitutorial.com/httpstatuscodes.html
+- https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
+- https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+- https://segmentfault.com/a/1190000018264501
diff --git a/docs/cs-basics/network/http-vs-https.md b/docs/cs-basics/network/http-vs-https.md
new file mode 100644
index 0000000000000000000000000000000000000000..987422925778fdf5a77acf81efaa6e4a28f86368
--- /dev/null
+++ b/docs/cs-basics/network/http-vs-https.md
@@ -0,0 +1,140 @@
+---
+title: HTTP vs HTTPS(应用层)
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+## HTTP 协议
+
+### HTTP 协议介绍
+
+HTTP 协议,全称超文本传输协议(Hypertext Transfer Protocol)。顾名思义,HTTP 协议就是用来规范超文本的传输,超文本,也就是网络上的包括文本在内的各式各样的消息,具体来说,主要是来规范浏览器和服务器端的行为的。
+
+并且,HTTP 是一个无状态(stateless)协议,也就是说服务器不维护任何有关客户端过去所发请求的消息。这其实是一种懒政,有状态协议会更加复杂,需要维护状态(历史信息),而且如果客户或服务器失效,会产生状态的不一致,解决这种不一致的代价更高。
+
+### HTTP 协议通信过程
+
+HTTP 是应用层协议,它以 TCP(传输层)作为底层协议,默认端口为 80. 通信过程主要如下:
+
+1. 服务器在 80 端口等待客户的请求。
+2. 浏览器发起到服务器的 TCP 连接(创建套接字 Socket)。
+3. 服务器接收来自浏览器的 TCP 连接。
+4. 浏览器(HTTP 客户端)与 Web 服务器(HTTP 服务器)交换 HTTP 消息。
+5. 关闭 TCP 连接。
+
+### HTTP 协议优点
+
+扩展性强、速度快、跨平台支持性好。
+
+## HTTPS 协议
+
+### HTTPS 协议介绍
+
+HTTPS 协议(Hyper Text Transfer Protocol Secure),是 HTTP 的加强安全版本。HTTPS 是基于 HTTP 的,也是用 TCP 作为底层协议,并额外使用 SSL/TLS 协议用作加密和安全认证。默认端口号是 443.
+
+HTTPS 协议中,SSL 通道通常使用基于密钥的加密算法,密钥长度通常是 40 比特或 128 比特。
+
+### HTTPS 协议优点
+
+保密性好、信任度高。
+
+## HTTPS 的核心—SSL/TLS 协议
+
+HTTPS 之所以能达到较高的安全性要求,就是结合了 SSL/TLS 和 TCP 协议,对通信数据进行加密,解决了 HTTP 数据透明的问题。接下来重点介绍一下 SSL/TLS 的工作原理。
+
+### SSL 和 TLS 的区别?
+
+**SSL 和 TLS 没有太大的区别。**
+
+SSL 指安全套接字协议(Secure Sockets Layer),首次发布与 1996 年。SSL 的首次发布其实已经是他的 3.0 版本,SSL 1.0 从未面世,SSL 2.0 则具有较大的缺陷(DROWN 缺陷——Decrypting RSA with Obsolete and Weakened eNcryption)。很快,在 1999 年,SSL 3.0 进一步升级,**新版本被命名为 TLS 1.0**。因此,TLS 是基于 SSL 之上的,但由于习惯叫法,通常把 HTTPS 中的核心加密协议混称为 SSL/TLS。
+
+### SSL/TLS 的工作原理
+
+#### 非对称加密
+
+SSL/TLS 的核心要素是**非对称加密**。非对称加密采用两个密钥——一个公钥,一个私钥。在通信时,私钥仅由解密者保存,公钥由任何一个想与解密者通信的发送者(加密者)所知。可以设想一个场景,
+
+> 在某个自助邮局,每个通信信道都是一个邮箱,每一个邮箱所有者都在旁边立了一个牌子,上面挂着一把钥匙:这是我的公钥,发送者请将信件放入我的邮箱,并用公钥锁好。
+>
+> 但是公钥只能加锁,并不能解锁。解锁只能由邮箱的所有者——因为只有他保存着私钥。
+>
+> 这样,通信信息就不会被其他人截获了,这依赖于私钥的保密性。
+
+
+
+非对称加密的公钥和私钥需要采用一种复杂的数学机制生成(密码学认为,为了较高的安全性,尽量不要自己创造加密方案)。公私钥对的生成算法依赖于单向陷门函数。
+
+> 单向函数:已知单向函数 f,给定任意一个输入 x,易计算输出 y=f(x);而给定一个输出 y,假设存在 f(x)=y,很难根据 f 来计算出 x。
+>
+> 单向陷门函数:一个较弱的单向函数。已知单向陷门函数 f,陷门 h,给定任意一个输入 x,易计算出输出 y=f(x;h);而给定一个输出 y,假设存在 f(x;h)=y,很难根据 f 来计算出 x,但可以根据 f 和 h 来推导出 x。
+
+
+
+上图就是一个单向函数(不是单项陷门函数),假设有一个绝世秘籍,任何知道了这个秘籍的人都可以把苹果汁榨成苹果,那么这个秘籍就是“陷门”了吧。
+
+在这里,函数 f 的计算方法相当于公钥,陷门 h 相当于私钥。公钥 f 是公开的,任何人对已有输入,都可以用 f 加密,而要想根据加密信息还原出原信息,必须要有私钥才行。
+
+#### 对称加密
+
+使用 SSL/TLS 进行通信的双方需要使用非对称加密方案来通信,但是非对称加密设计了较为复杂的数学算法,在实际通信过程中,计算的代价较高,效率太低,因此,SSL/TLS 实际对消息的加密使用的是对称加密。
+
+> 对称加密:通信双方共享唯一密钥 k,加解密算法已知,加密方利用密钥 k 加密,解密方利用密钥 k 解密,保密性依赖于密钥 k 的保密性。
+
+
+
+对称加密的密钥生成代价比公私钥对的生成代价低得多,那么有的人会问了,为什么 SSL/TLS 还需要使用非对称加密呢?因为对称加密的保密性完全依赖于密钥的保密性。在双方通信之前,需要商量一个用于对称加密的密钥。我们知道网络通信的信道是不安全的,传输报文对任何人是可见的,密钥的交换肯定不能直接在网络信道中传输。因此,使用非对称加密,对对称加密的密钥进行加密,保护该密钥不在网络信道中被窃听。这样,通信双方只需要一次非对称加密,交换对称加密的密钥,在之后的信息通信中,使用绝对安全的密钥,对信息进行对称加密,即可保证传输消息的保密性。
+
+#### 公钥传输的信赖性
+
+SSL/TLS 介绍到这里,了解信息安全的朋友又会想到一个安全隐患,设想一个下面的场景:
+
+> 客户端 C 和服务器 S 想要使用 SSL/TLS 通信,由上述 SSL/TLS 通信原理,C 需要先知道 S 的公钥,而 S 公钥的唯一获取途径,就是把 S 公钥在网络信道中传输。要注意网络信道通信中有几个前提:
+>
+> 1. 任何人都可以捕获通信包
+> 2. 通信包的保密性由发送者设计
+> 3. 保密算法设计方案默认为公开,而(解密)密钥默认是安全的
+>
+> 因此,假设 S 公钥不做加密,在信道中传输,那么很有可能存在一个攻击者 A,发送给 C 一个诈包,假装是 S 公钥,其实是诱饵服务器 AS 的公钥。当 C 收获了 AS 的公钥(却以为是 S 的公钥),C 后续就会使用 AS 公钥对数据进行加密,并在公开信道传输,那么 A 将捕获这些加密包,用 AS 的私钥解密,就截获了 C 本要给 S 发送的内容,而 C 和 S 二人全然不知。
+>
+> 同样的,S 公钥即使做加密,也难以避免这种信任性问题,C 被 AS 拐跑了!
+
+
+
+为了公钥传输的信赖性问题,第三方机构应运而生——证书颁发机构(CA,Certificate Authority)。CA 默认是受信任的第三方。CA 会给各个服务器颁发证书,证书存储在服务器上,并附有 CA 的**电子签名**(见下节)。
+
+当客户端(浏览器)向服务器发送 HTTPS 请求时,一定要先获取目标服务器的证书,并根据证书上的信息,检验证书的合法性。一旦客户端检测到证书非法,就会发生错误。客户端获取了服务器的证书后,由于证书的信任性是由第三方信赖机构认证的,而证书上又包含着服务器的公钥信息,客户端就可以放心的信任证书上的公钥就是目标服务器的公钥。
+
+#### 数字签名
+
+好,到这一小节,已经是 SSL/TLS 的尾声了。上一小节提到了数字签名,数字签名要解决的问题,是防止证书被伪造。第三方信赖机构 CA 之所以能被信赖,就是 **靠数字签名技术** 。
+
+数字签名,是 CA 在给服务器颁发证书时,使用散列+加密的组合技术,在证书上盖个章,以此来提供验伪的功能。具体行为如下:
+
+> CA 知道服务器的公钥,对证书采用散列技术生成一个摘要。CA 使用 CA 私钥对该摘要进行加密,并附在证书下方,发送给服务器。
+>
+> 现在服务器将该证书发送给客户端,客户端需要验证该证书的身份。客户端找到第三方机构 CA,获知 CA 的公钥,并用 CA 公钥对证书的签名进行解密,获得了 CA 生成的摘要。
+>
+> 客户端对证书数据(包含服务器的公钥)做相同的散列处理,得到摘要,并将该摘要与之前从签名中解码出的摘要做对比,如果相同,则身份验证成功;否则验证失败。
+
+
+
+总结来说,带有证书的公钥传输机制如下:
+
+1. 设有服务器 S,客户端 C,和第三方信赖机构 CA。
+2. S 信任 CA,CA 是知道 S 公钥的,CA 向 S 颁发证书。并附上 CA 私钥对消息摘要的加密签名。
+3. S 获得 CA 颁发的证书,将该证书传递给 C。
+4. C 获得 S 的证书,信任 CA 并知晓 CA 公钥,使用 CA 公钥对 S 证书上的签名解密,同时对消息进行散列处理,得到摘要。比较摘要,验证 S 证书的真实性。
+5. 如果 C 验证 S 证书是真实的,则信任 S 的公钥(在 S 证书中)。
+
+
+
+对于数字签名,我这里讲的比较简单,如果你没有搞清楚的话,强烈推荐你看看[数字签名及数字证书原理](https://www.bilibili.com/video/BV18N411X7ty/)这个视频,这是我看过最清晰的讲解。
+
+
+
+## 总结
+
+- **端口号**:HTTP 默认是 80,HTTPS 默认是 443。
+- **URL 前缀**:HTTP 的 URL 前缀是 `http://`,HTTPS 的 URL 前缀是 `https://`。
+- **安全性和资源消耗**:HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。
diff --git a/docs/cs-basics/network/http1.0-vs-http1.1.md b/docs/cs-basics/network/http1.0-vs-http1.1.md
new file mode 100644
index 0000000000000000000000000000000000000000..cc27c0e0974acb420e5ec6ef70575113c11a4293
--- /dev/null
+++ b/docs/cs-basics/network/http1.0-vs-http1.1.md
@@ -0,0 +1,105 @@
+---
+title: HTTP 1.0 vs HTTP 1.1(应用层)
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+这篇文章会从下面几个维度来对比 HTTP 1.0 和 HTTP 1.1:
+
+- 响应状态码
+- 缓存处理
+- 连接方式
+- Host 头处理
+- 带宽优化
+
+## 响应状态码
+
+HTTP/1.0 仅定义了 16 种状态码。HTTP/1.1 中新加入了大量的状态码,光是错误响应状态码就新增了 24 种。比如说,`100 (Continue)`——在请求大资源前的预热请求,`206 (Partial Content)`——范围请求的标识码,`409 (Conflict)`——请求与当前资源的规定冲突,`410 (Gone)`——资源已被永久转移,而且没有任何已知的转发地址。
+
+## 缓存处理
+
+缓存技术通过避免用户与源服务器的频繁交互,节约了大量的网络带宽,降低了用户接收信息的延迟。
+
+### HTTP/1.0
+
+HTTP/1.0 提供的缓存机制非常简单。服务器端使用`Expires`标签来标志(时间)一个响应体,在`Expires`标志时间内的请求,都会获得该响应体缓存。服务器端在初次返回给客户端的响应体中,有一个`Last-Modified`标签,该标签标记了被请求资源在服务器端的最后一次修改。在请求头中,使用`If-Modified-Since`标签,该标签标志一个时间,意为客户端向服务器进行问询:“该时间之后,我要请求的资源是否有被修改过?”通常情况下,请求头中的`If-Modified-Since`的值即为上一次获得该资源时,响应体中的`Last-Modified`的值。
+
+如果服务器接收到了请求头,并判断`If-Modified-Since`时间后,资源确实没有修改过,则返回给客户端一个`304 not modified`响应头,表示”缓冲可用,你从浏览器里拿吧!”。
+
+如果服务器判断`If-Modified-Since`时间后,资源被修改过,则返回给客户端一个`200 OK`的响应体,并附带全新的资源内容,表示”你要的我已经改过的,给你一份新的”。
+
+
+
+
+
+### HTTP/1.1
+
+HTTP/1.1 的缓存机制在 HTTP/1.0 的基础上,大大增加了灵活性和扩展性。基本工作原理和 HTTP/1.0 保持不变,而是增加了更多细致的特性。其中,请求头中最常见的特性就是`Cache-Control`,详见 MDN Web 文档 [Cache-Control](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control).
+
+## 连接方式
+
+**HTTP/1.0 默认使用短连接** ,也就是说,客户端和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个 HTML 或其他类型的 Web 页中包含有其他的 Web 资源(如 JavaScript 文件、图像文件、CSS 文件等),每遇到这样一个 Web 资源,浏览器就会重新建立一个 TCP 连接,这样就会导致有大量的“握手报文”和“挥手报文”占用了带宽。
+
+**为了解决 HTTP/1.0 存在的资源浪费的问题, HTTP/1.1 优化为默认长连接模式 。** 采用长连接模式的请求报文会通知服务端:“我向你请求连接,并且连接成功建立后,请不要关闭”。因此,该 TCP 连接将持续打开,为后续的客户端-服务端的数据交互服务。也就是说在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。
+
+如果 TCP 连接一直保持的话也是对资源的浪费,因此,一些服务器软件(如 Apache)还会支持超时时间的时间。在超时时间之内没有新的请求达到,TCP 连接才会被关闭。
+
+有必要说明的是,HTTP/1.0 仍提供了长连接选项,即在请求头中加入`Connection: Keep-alive`。同样的,在 HTTP/1.1 中,如果不希望使用长连接选项,也可以在请求头中加入`Connection: close`,这样会通知服务器端:“我不需要长连接,连接成功后即可关闭”。
+
+**HTTP 协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接。**
+
+**实现长连接需要客户端和服务端都支持长连接。**
+
+## Host 头处理
+
+域名系统(DNS)允许多个主机名绑定到同一个 IP 地址上,但是 HTTP/1.0 并没有考虑这个问题,假设我们有一个资源 URL 是http://example1.org/home.html,HTTP/1.0的请求报文中,将会请求的是`GET /home.html HTTP/1.0`.也就是不会加入主机名。这样的报文送到服务器端,服务器是理解不了客户端想请求的真正网址。
+
+因此,HTTP/1.1 在请求头中加入了`Host`字段。加入`Host`字段的报文头部将会是:
+
+```
+GET /home.html HTTP/1.1
+Host: example1.org
+```
+
+这样,服务器端就可以确定客户端想要请求的真正的网址了。
+
+## 带宽优化
+
+### 范围请求
+
+HTTP/1.1 引入了范围请求(range request)机制,以避免带宽的浪费。当客户端想请求一个文件的一部分,或者需要继续下载一个已经下载了部分但被终止的文件,HTTP/1.1 可以在请求中加入`Range`头部,以请求(并只能请求字节型数据)数据的一部分。服务器端可以忽略`Range`头部,也可以返回若干`Range`响应。
+
+如果一个响应包含部分数据的话,那么将带有`206 (Partial Content)`状态码。该状态码的意义在于避免了 HTTP/1.0 代理缓存错误地把该响应认为是一个完整的数据响应,从而把他当作为一个请求的响应缓存。
+
+在范围响应中,`Content-Range`头部标志指示出了该数据块的偏移量和数据块的长度。
+
+### 状态码 100
+
+HTTP/1.1 中新加入了状态码`100`。该状态码的使用场景为,存在某些较大的文件请求,服务器可能不愿意响应这种请求,此时状态码`100`可以作为指示请求是否会被正常响应,过程如下图:
+
+
+
+
+
+然而在 HTTP/1.0 中,并没有`100 (Continue)`状态码,要想触发这一机制,可以发送一个`Expect`头部,其中包含一个`100-continue`的值。
+
+### 压缩
+
+许多格式的数据在传输时都会做预压缩处理。数据的压缩可以大幅优化带宽的利用。然而,HTTP/1.0 对数据压缩的选项提供的不多,不支持压缩细节的选择,也无法区分端到端(end-to-end)压缩或者是逐跳(hop-by-hop)压缩。
+
+HTTP/1.1 则对内容编码(content-codings)和传输编码(transfer-codings)做了区分。内容编码总是端到端的,传输编码总是逐跳的。
+
+HTTP/1.0 包含了`Content-Encoding`头部,对消息进行端到端编码。HTTP/1.1 加入了`Transfer-Encoding`头部,可以对消息进行逐跳传输编码。HTTP/1.1 还加入了`Accept-Encoding`头部,是客户端用来指示他能处理什么样的内容编码。
+
+## 总结
+
+1. **连接方式** : HTTP 1.0 为短连接,HTTP 1.1 支持长连接。
+1. **状态响应码** : HTTP/1.1 中新加入了大量的状态码,光是错误响应状态码就新增了 24 种。比如说,`100 (Continue)`——在请求大资源前的预热请求,`206 (Partial Content)`——范围请求的标识码,`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. **Host 头处理** : HTTP/1.1 在请求头中加入了`Host`字段。
+
+## 参考资料
+
+[Key differences between HTTP/1.0 and HTTP/1.1](http://www.ra.ethz.ch/cdstore/www8/data/2136/pdf/pd1.pdf)
diff --git a/docs/cs-basics/network/images/Cut-Trough-Switching_0.gif b/docs/cs-basics/network/images/Cut-Trough-Switching_0.gif
deleted file mode 100644
index 170dc3bbb191e67a42ff3e12258b0e86e48289e7..0000000000000000000000000000000000000000
Binary files a/docs/cs-basics/network/images/Cut-Trough-Switching_0.gif and /dev/null differ
diff --git a/docs/cs-basics/network/images/arp/2008410143049281.png b/docs/cs-basics/network/images/arp/2008410143049281.png
new file mode 100644
index 0000000000000000000000000000000000000000..759fb441f6cce4a4cafe638a7868b7ccc46aab4f
Binary files /dev/null and b/docs/cs-basics/network/images/arp/2008410143049281.png differ
diff --git a/docs/cs-basics/network/images/arp/arp_different_lan.png b/docs/cs-basics/network/images/arp/arp_different_lan.png
new file mode 100644
index 0000000000000000000000000000000000000000..8cfe44450766ac8be79480527d55217a3d01835e
Binary files /dev/null and b/docs/cs-basics/network/images/arp/arp_different_lan.png differ
diff --git a/docs/cs-basics/network/images/arp/arp_same_lan.png b/docs/cs-basics/network/images/arp/arp_same_lan.png
new file mode 100644
index 0000000000000000000000000000000000000000..9137cb7ed3e000e375e945a22a470c801362b112
Binary files /dev/null and b/docs/cs-basics/network/images/arp/arp_same_lan.png differ
diff --git a/docs/cs-basics/network/images/http-vs-https/HTTP1.0cache1.png b/docs/cs-basics/network/images/http-vs-https/HTTP1.0cache1.png
new file mode 100644
index 0000000000000000000000000000000000000000..a57c774d40fdd7df431658018ea1b7d784d87e9c
Binary files /dev/null and b/docs/cs-basics/network/images/http-vs-https/HTTP1.0cache1.png differ
diff --git a/docs/cs-basics/network/images/http-vs-https/HTTP1.0cache2.png b/docs/cs-basics/network/images/http-vs-https/HTTP1.0cache2.png
new file mode 100644
index 0000000000000000000000000000000000000000..f74da56cf96d7a952c44e8270d24b3c3ae2f3cd6
Binary files /dev/null and b/docs/cs-basics/network/images/http-vs-https/HTTP1.0cache2.png differ
diff --git a/docs/cs-basics/network/images/http-vs-https/HTTP1.1continue1.png b/docs/cs-basics/network/images/http-vs-https/HTTP1.1continue1.png
new file mode 100644
index 0000000000000000000000000000000000000000..5943d22ce0efbb3d9163d1ccc28fd892bb0f58a9
Binary files /dev/null and b/docs/cs-basics/network/images/http-vs-https/HTTP1.1continue1.png differ
diff --git a/docs/cs-basics/network/images/http-vs-https/HTTP1.1continue2.png b/docs/cs-basics/network/images/http-vs-https/HTTP1.1continue2.png
new file mode 100644
index 0000000000000000000000000000000000000000..5181160087a6034f490a3b7e0ca7099162434111
Binary files /dev/null and b/docs/cs-basics/network/images/http-vs-https/HTTP1.1continue2.png differ
diff --git a/docs/cs-basics/network/images/http-vs-https/OWF.png b/docs/cs-basics/network/images/http-vs-https/OWF.png
new file mode 100644
index 0000000000000000000000000000000000000000..426c7e29457910944a577bb0e37b47a8921189b1
Binary files /dev/null and b/docs/cs-basics/network/images/http-vs-https/OWF.png differ
diff --git a/docs/cs-basics/network/images/http-vs-https/attack1.png b/docs/cs-basics/network/images/http-vs-https/attack1.png
new file mode 100644
index 0000000000000000000000000000000000000000..fd6e9dff4e959bf4bc512c47e8f86c240bf0da8a
Binary files /dev/null and b/docs/cs-basics/network/images/http-vs-https/attack1.png differ
diff --git a/docs/cs-basics/network/images/http-vs-https/digital-signature.png b/docs/cs-basics/network/images/http-vs-https/digital-signature.png
new file mode 100644
index 0000000000000000000000000000000000000000..391b83f3a8814199e60c71bd3b74e263dddb81b2
Binary files /dev/null and b/docs/cs-basics/network/images/http-vs-https/digital-signature.png differ
diff --git a/docs/cs-basics/network/images/http-vs-https/public-key-cryptography.png b/docs/cs-basics/network/images/http-vs-https/public-key-cryptography.png
new file mode 100644
index 0000000000000000000000000000000000000000..a6c4143ff7ef5af9f4e2058f2af2c496d736d727
Binary files /dev/null and b/docs/cs-basics/network/images/http-vs-https/public-key-cryptography.png differ
diff --git a/docs/cs-basics/network/images/http-vs-https/public-key-transmission.png b/docs/cs-basics/network/images/http-vs-https/public-key-transmission.png
new file mode 100644
index 0000000000000000000000000000000000000000..ab49670ec42f815233cc9dfaeafe8fc4dde989a0
Binary files /dev/null and b/docs/cs-basics/network/images/http-vs-https/public-key-transmission.png differ
diff --git a/docs/cs-basics/network/images/http-vs-https/symmetric-encryption.png b/docs/cs-basics/network/images/http-vs-https/symmetric-encryption.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc9cfbc0e5d2b7032b553e3c84274ba8a848e3ab
Binary files /dev/null and b/docs/cs-basics/network/images/http-vs-https/symmetric-encryption.png differ
diff --git a/docs/cs-basics/network/images/isp.png b/docs/cs-basics/network/images/isp.png
deleted file mode 100644
index fe5c0e96adbaa2ae67ba83171595e83ec9e53abe..0000000000000000000000000000000000000000
Binary files a/docs/cs-basics/network/images/isp.png and /dev/null differ
diff --git a/docs/cs-basics/network/images/network-model/nerwork-layer-protocol.png b/docs/cs-basics/network/images/network-model/nerwork-layer-protocol.png
new file mode 100644
index 0000000000000000000000000000000000000000..a94274cce32f1aa2d33345fc303b7428b06f4616
Binary files /dev/null and b/docs/cs-basics/network/images/network-model/nerwork-layer-protocol.png differ
diff --git "a/docs/cs-basics/network/images/\344\270\203\345\261\202\344\275\223\347\263\273\347\273\223\346\236\204\345\233\276.png" "b/docs/cs-basics/network/images/\344\270\203\345\261\202\344\275\223\347\263\273\347\273\223\346\236\204\345\233\276.png"
deleted file mode 100644
index a2d24300fcb43dcf910faa4e49a4c8432b3c8a50..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/network/images/\344\270\203\345\261\202\344\275\223\347\263\273\347\273\223\346\236\204\345\233\276.png" and /dev/null differ
diff --git "a/docs/cs-basics/network/images/\344\274\240\350\276\223\345\261\202.png" "b/docs/cs-basics/network/images/\344\274\240\350\276\223\345\261\202.png"
deleted file mode 100644
index 192af24536bec85ea0148831cf550b885f157233..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/network/images/\344\274\240\350\276\223\345\261\202.png" and /dev/null differ
diff --git "a/docs/cs-basics/network/images/\345\272\224\347\224\250\345\261\202.png" "b/docs/cs-basics/network/images/\345\272\224\347\224\250\345\261\202.png"
deleted file mode 100644
index 31e1e447b73fcdcbb5002e7bdb20538e9aa4761e..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/network/images/\345\272\224\347\224\250\345\261\202.png" and /dev/null differ
diff --git "a/docs/cs-basics/network/images/\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.png" "b/docs/cs-basics/network/images/\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.png"
deleted file mode 100644
index c2b51a7c589cbb745f3f6148bc42366e843901bd..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/network/images/\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.png" and /dev/null differ
diff --git "a/docs/cs-basics/network/images/\347\211\251\347\220\206\345\261\202.png" "b/docs/cs-basics/network/images/\347\211\251\347\220\206\345\261\202.png"
deleted file mode 100644
index abb979261c1aa9c91576c6c8aa359a777c6281b1..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/network/images/\347\211\251\347\220\206\345\261\202.png" and /dev/null differ
diff --git "a/docs/cs-basics/network/images/\347\275\221\347\273\234\345\261\202.png" "b/docs/cs-basics/network/images/\347\275\221\347\273\234\345\261\202.png"
deleted file mode 100644
index 376479d7989a8b5ac90fabb91ebab8da85d7a779..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/network/images/\347\275\221\347\273\234\345\261\202.png" and /dev/null differ
diff --git "a/docs/cs-basics/network/images/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223/\344\270\207\347\273\264\347\275\221\347\232\204\345\244\247\350\207\264\345\267\245\344\275\234\345\267\245\347\250\213.png" "b/docs/cs-basics/network/images/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223/\344\270\207\347\273\264\347\275\221\347\232\204\345\244\247\350\207\264\345\267\245\344\275\234\345\267\245\347\250\213.png"
deleted file mode 100644
index 6af03daa96ab5f86ad06ab8babd4de477d779d73..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/network/images/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223/\344\270\207\347\273\264\347\275\221\347\232\204\345\244\247\350\207\264\345\267\245\344\275\234\345\267\245\347\250\213.png" and /dev/null differ
diff --git a/docs/cs-basics/network/nat.md b/docs/cs-basics/network/nat.md
new file mode 100644
index 0000000000000000000000000000000000000000..814df5e01391067fbf232c739eebdb15f46c5b87
--- /dev/null
+++ b/docs/cs-basics/network/nat.md
@@ -0,0 +1,58 @@
+---
+title: NAT 协议详解(网络层)
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+## 应用场景
+
+**NAT 协议(Network Address Translation)** 的应用场景如同它的名称——网络地址转换,应用于内部网到外部网的地址转换过程中。具体地说,在一个小的子网(局域网,Local Area Network,LAN)内,各主机使用的是同一个 LAN 下的 IP 地址,但在该 LAN 以外,在广域网(Wide Area Network,WAN)中,需要一个统一的 IP 地址来标识该 LAN 在整个 Internet 上的位置。
+
+这个场景其实不难理解。随着一个个小型办公室、家庭办公室(Small Office, Home Office, SOHO)的出现,为了管理这些 SOHO,一个个子网被设计出来,从而在整个 Internet 中的主机数量将非常庞大。如果每个主机都有一个“绝对唯一”的 IP 地址,那么 IPv4 地址的表达能力可能很快达到上限($2^{32}$)。因此,实际上,SOHO 子网中的 IP 地址是“相对的”,这在一定程度上也缓解了 IPv4 地址的分配压力。
+
+SOHO 子网的“代理人”,也就是和外界的窗口,通常由路由器扮演。路由器的 LAN 一侧管理着一个小子网,而它的 WAN 接口才是真正参与到 Internet 中的接口,也就有一个“绝对唯一的地址”。NAT 协议,正是在 LAN 中的主机在与 LAN 外界通信时,起到了地址转换的关键作用。
+
+## 细节
+
+
+
+假设当前场景如上图。中间是一个路由器,它的右侧组织了一个 LAN,网络号为`10.0.0/24`。LAN 侧接口的 IP 地址为`10.0.0.4`,并且该子网内有至少三台主机,分别是`10.0.0.1`,`10.0.0.2`和`10.0.0.3`。路由器的左侧连接的是 WAN,WAN 侧接口的 IP 地址为`138.76.29.7`。
+
+首先,针对以上信息,我们有如下事实需要说明:
+
+1. 路由器的右侧子网的网络号为`10.0.0/24`,主机号为`10.0.0/8`,三台主机地址,以及路由器的 LAN 侧接口地址,均由 DHCP 协议规定。而且,该 DHCP 运行在路由器内部(路由器自维护一个小 DHCP 服务器),从而为子网内提供 DHCP 服务。
+2. 路由器的 WAN 侧接口地址同样由 DHCP 协议规定,但该地址是路由器从 ISP(网络服务提供商)处获得,也就是该 DHCP 通常运行在路由器所在区域的 DHCP 服务器上。
+
+现在,路由器内部还运行着 NAT 协议,从而为 LAN-WAN 间通信提供地址转换服务。为此,一个很重要的结构是 **NAT 转换表**。为了说明 NAT 的运行细节,假设有以下请求发生:
+
+1. 主机`10.0.0.1`向 IP 地址为`128.119.40.186`的 Web 服务器(端口 80)发送了 HTTP 请求(如请求页面)。此时,主机`10.0.0.1`将随机指派一个端口,如`3345`,作为本次请求的源端口号,将该请求发送到路由器中(目的地址将是`128.119.40.186`,但会先到达`10.0.0.4`)。
+2. `10.0.0.4`即路由器的 LAN 接口收到`10.0.0.1`的请求。路由器将为该请求指派一个新的源端口号,如`5001`,并将请求报文发送给 WAN 接口`138.76.29.7`。同时,在 NAT 转换表中记录一条转换记录**138.76.29.7:5001——10.0.0.1:3345**。
+3. 请求报文到达 WAN 接口,继续向目的主机`128.119.40.186`发送。
+
+之后,将会有如下响应发生:
+
+1. 主机`128.119.40.186`收到请求,构造响应报文,并将其发送给目的地`138.76.29.7:5001`。
+2. 响应报文到达路由器的 WAN 接口。路由器查询 NAT 转换表,发现`138.76.29.7:5001`在转换表中有记录,从而将其目的地址和目的端口转换成为`10.0.0.1:3345`,再发送到`10.0.0.4`上。
+3. 被转换的响应报文到达路由器的 LAN 接口,继而被转发至目的地`10.0.0.1`。
+
+
+
+🐛 修正(参见:[issue#2009](https://github.com/Snailclimb/JavaGuide/issues/2009)):上图第四步的 Dest 值应该为 `10.0.0.1:3345` 而不是~~`138.76.29.7:5001`~~,这里笔误了。
+
+## 划重点
+
+针对以上过程,有以下几个重点需要强调:
+
+1. 当请求报文到达路由器,并被指定了新端口号时,由于端口号有 16 位,因此,通常来说,一个路由器管理的 LAN 中的最大主机数 $≈65500$($2^{16}$ 的地址空间),但通常 SOHO 子网内不会有如此多的主机数量。
+2. 对于目的服务器来说,从来不知道“到底是哪个主机给我发送的请求”,它只知道是来自`138.76.29.7:5001`的路由器转发的请求。因此,可以说,**路由器在 WAN 和 LAN 之间起到了屏蔽作用,**所有内部主机发送到外部的报文,都具有同一个 IP 地址(不同的端口号),所有外部发送到内部的报文,也都只有一个目的地(不同端口号),是经过了 NAT 转换后,外部报文才得以正确地送达内部主机。
+3. 在报文穿过路由器,发生 NAT 转换时,如果 LAN 主机 IP 已经在 NAT 转换表中注册过了,则不需要路由器新指派端口,而是直接按照转换记录穿过路由器。同理,外部报文发送至内部时也如此。
+
+总结 NAT 协议的特点,有以下几点:
+
+1. NAT 协议通过对 WAN 屏蔽 LAN,有效地缓解了 IPv4 地址分配压力。
+2. LAN 主机 IP 地址的变更,无需通告 WAN。
+3. WAN 的 ISP 变更接口地址时,无需通告 LAN 内主机。
+4. LAN 主机对 WAN 不可见,不可直接寻址,可以保证一定程度的安全性。
+
+然而,NAT 协议由于其独特性,存在着一些争议。比如,可能你已经注意到了,**NAT 协议在 LAN 以外,标识一个内部主机时,使用的是端口号,因为 IP 地址都是相同的。**这种将端口号作为主机寻址的行为,可能会引发一些误会。此外,路由器作为网络层的设备,修改了传输层的分组内容(修改了源 IP 地址和端口号),同样是不规范的行为。但是,尽管如此,NAT 协议作为 IPv4 时代的产物,极大地方便了一些本来棘手的问题,一直被沿用至今。
diff --git a/docs/cs-basics/network/network-attack-means.md b/docs/cs-basics/network/network-attack-means.md
new file mode 100644
index 0000000000000000000000000000000000000000..f3e785cc3bb9bbc051dbffbcd134b442d30450e1
--- /dev/null
+++ b/docs/cs-basics/network/network-attack-means.md
@@ -0,0 +1,468 @@
+---
+title: 网络攻击常见手段总结
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+> 本文整理完善自[TCP/IP 常见攻击手段 - 暖蓝笔记 - 2021](https://mp.weixin.qq.com/s/AZwWrOlLxRSSi-ywBgZ0fA)这篇文章。
+
+这篇文章的内容主要是介绍 TCP/IP 常见攻击手段,尤其是 DDoS 攻击,也会补充一些其他的常见网络攻击手段。
+
+## IP 欺骗
+
+### IP 是什么?
+
+在网络中,所有的设备都会分配一个地址。这个地址就仿佛小蓝的家地址「**多少号多少室**」,这个号就是分配给整个子网的,「**室**」对应的号码即分配给子网中计算机的,这就是网络中的地址。「号」对应的号码为网络号,「**室**」对应的号码为主机号,这个地址的整体就是 **IP 地址**。
+
+### 通过 IP 地址我们能知道什么?
+
+通过 IP 地址,我们就可以知道判断访问对象服务器的位置,从而将消息发送到服务器。一般发送者发出的消息首先经过子网的集线器,转发到最近的路由器,然后根据路由位置访问下一个路由器的位置,直到终点
+
+**IP 头部格式** :
+
+
+
+### IP 欺骗技术是什么?
+
+骗呗,拐骗,诱骗!
+
+IP 欺骗技术就是**伪造**某台主机的 IP 地址的技术。通过 IP 地址的伪装使得某台主机能够**伪装**另外的一台主机,而这台主机往往具有某种特权或者被另外的主机所信任。
+
+假设现在有一个合法用户 **(1.1.1.1)** 已经同服务器建立正常的连接,攻击者构造攻击的 TCP 数据,伪装自己的 IP 为 **1.1.1.1**,并向服务器发送一个带有 RSI 位的 TCP 数据段。服务器接收到这样的数据后,认为从 **1.1.1.1** 发送的连接有错误,就会清空缓冲区中建立好的连接。
+
+这时,如果合法用户 **1.1.1.1** 再发送合法数据,服务器就已经没有这样的连接了,该用户就必须从新开始建立连接。攻击时,伪造大量的 IP 地址,向目标发送 RST 数据,使服务器不对合法用户服务。虽然 IP 地址欺骗攻击有着相当难度,但我们应该清醒地意识到,这种攻击非常广泛,入侵往往从这种攻击开始。
+
+
+
+### 如何缓解 IP 欺骗?
+
+虽然无法预防 IP 欺骗,但可以采取措施来阻止伪造数据包渗透网络。**入口过滤** 是防范欺骗的一种极为常见的防御措施,如 BCP38(通用最佳实践文档)所示。入口过滤是一种数据包过滤形式,通常在[网络边缘](https://www.cloudflare.com/learning/serverless/glossary/what-is-edge-computing/)设备上实施,用于检查传入的 IP 数据包并确定其源标头。如果这些数据包的源标头与其来源不匹配或者看上去很可疑,则拒绝这些数据包。一些网络还实施出口过滤,检查退出网络的 IP 数据包,确保这些数据包具有合法源标头,以防止网络内部用户使用 IP 欺骗技术发起出站恶意攻击。
+
+## SYN Flood(洪水)
+
+### SYN Flood 是什么?
+
+SYN Flood 是互联网上最原始、最经典的 DDoS(Distributed Denial of Service,分布式拒绝服务)攻击之一,旨在耗尽可用服务器资源,致使服务器无法传输合法流量
+
+SYN Flood 利用了 TCP 协议的三次握手机制,攻击者通常利用工具或者控制僵尸主机向服务器发送海量的变源 IP 地址或变源端口的 TCP SYN 报文,服务器响应了这些报文后就会生成大量的半连接,当系统资源被耗尽后,服务器将无法提供正常的服务。
+增加服务器性能,提供更多的连接能力对于 SYN Flood 的海量报文来说杯水车薪,防御 SYN Flood 的关键在于判断哪些连接请求来自于真实源,屏蔽非真实源的请求以保障正常的业务请求能得到服务。
+
+
+
+### TCP SYN Flood 攻击原理是什么?
+
+**TCP SYN Flood** 攻击利用的是 **TCP** 的三次握手(**SYN -> SYN/ACK -> ACK**),假设连接发起方是 A,连接接受方是 B,即 B 在某个端口(**Port**)上监听 A 发出的连接请求,过程如下图所示,左边是 A,右边是 B。
+
+
+
+A 首先发送 **SYN**(Synchronization)消息给 B,要求 B 做好接收数据的准备;B 收到后反馈 **SYN-ACK**(Synchronization-Acknowledgement) 消息给 A,这个消息的目的有两个:
+
+- 向 A 确认已做好接收数据的准备,
+- 同时要求 A 也做好接收数据的准备,此时 B 已向 A 确认好接收状态,并等待 A 的确认,连接处于**半开状态(Half-Open)**,顾名思义只开了一半;A 收到后再次发送 **ACK** (Acknowledgement) 消息给 B,向 B 确认也做好了接收数据的准备,至此三次握手完成,「**连接**」就建立了,
+
+大家注意到没有,最关键的一点在于双方是否都按对方的要求进入了**可以接收消息**的状态。而这个状态的确认主要是双方将要使用的**消息序号(**SequenceNum),**TCP** 为保证消息按发送顺序抵达接收方的上层应用,需要用**消息序号**来标记消息的发送先后顺序的。
+
+**TCP**是「**双工**」(Duplex)连接,同时支持双向通信,也就是双方同时可向对方发送消息,其中 **SYN** 和 **SYN-ACK** 消息开启了 A→B 的单向通信通道(B 获知了 A 的消息序号);**SYN-ACK** 和 **ACK** 消息开启了 B→A 单向通信通道(A 获知了 B 的消息序号)。
+
+上面讨论的是双方在诚实守信,正常情况下的通信。
+
+但实际情况是,网络可能不稳定会丢包,使握手消息不能抵达对方,也可能是对方故意不按规矩来,故意延迟或不发送握手确认消息。
+
+假设 B 通过某 **TCP** 端口提供服务,B 在收到 A 的 **SYN** 消息时,积极的反馈了 **SYN-ACK** 消息,使连接进入**半开状态**,因为 B 不确定自己发给 A 的 **SYN-ACK** 消息或 A 反馈的 ACK 消息是否会丢在半路,所以会给每个待完成的半开连接都设一个**Timer**,如果超过时间还没有收到 A 的 **ACK** 消息,则重新发送一次 **SYN-ACK** 消息给 A,直到重试超过一定次数时才会放弃。
+
+
+
+B 为帮助 A 能顺利连接,需要**分配内核资源**维护半开连接,那么当 B 面临海量的连接 A 时,如上图所示,**SYN Flood** 攻击就形成了。攻击方 A 可以控制肉鸡向 B 发送大量 SYN 消息但不响应 ACK 消息,或者干脆伪造 SYN 消息中的 **Source IP**,使 B 反馈的 **SYN-ACK** 消息石沉大海,导致 B 被大量注定不能完成的半开连接占据,直到资源耗尽,停止响应正常的连接请求。
+
+### SYN Flood 的常见形式有哪些?
+
+**恶意用户可通过三种不同方式发起 SYN Flood 攻击**:
+
+1. **直接攻击:** 不伪造 IP 地址的 SYN 洪水攻击称为直接攻击。在此类攻击中,攻击者完全不屏蔽其 IP 地址。由于攻击者使用具有真实 IP 地址的单一源设备发起攻击,因此很容易发现并清理攻击者。为使目标机器呈现半开状态,黑客将阻止个人机器对服务器的 SYN-ACK 数据包做出响应。为此,通常采用以下两种方式实现:部署防火墙规则,阻止除 SYN 数据包以外的各类传出数据包;或者,对传入的所有 SYN-ACK 数据包进行过滤,防止其到达恶意用户机器。实际上,这种方法很少使用(即便使用过也不多见),因为此类攻击相当容易缓解 – 只需阻止每个恶意系统的 IP 地址。哪怕攻击者使用僵尸网络(如 [Mirai 僵尸网络](https://www.cloudflare.com/learning/ddos/glossary/mirai-botnet/)),通常也不会刻意屏蔽受感染设备的 IP。
+2. **欺骗攻击:** 恶意用户还可以伪造其发送的各个 SYN 数据包的 IP 地址,以便阻止缓解措施并加大身份暴露难度。虽然数据包可能经过伪装,但还是可以通过这些数据包追根溯源。此类检测工作很难开展,但并非不可实现;特别是,如果 Internet 服务提供商 (ISP) 愿意提供帮助,则更容易实现。
+3. **分布式攻击(DDoS):** 如果使用僵尸网络发起攻击,则追溯攻击源头的可能性很低。随着混淆级别的攀升,攻击者可能还会命令每台分布式设备伪造其发送数据包的 IP 地址。哪怕攻击者使用僵尸网络(如 Mirai 僵尸网络),通常也不会刻意屏蔽受感染设备的 IP。
+
+### 如何缓解 SYN Flood?
+
+#### 扩展积压工作队列
+
+目标设备安装的每个操作系统都允许具有一定数量的半开连接。若要响应大量 SYN 数据包,一种方法是增加操作系统允许的最大半开连接数目。为成功扩展最大积压工作,系统必须额外预留内存资源以处理各类新请求。如果系统没有足够的内存,无法应对增加的积压工作队列规模,将对系统性能产生负面影响,但仍然好过拒绝服务。
+
+#### 回收最先创建的 TCP 半开连接
+
+另一种缓解策略是在填充积压工作后覆盖最先创建的半开连接。这项策略要求完全建立合法连接的时间低于恶意 SYN 数据包填充积压工作的时间。当攻击量增加或积压工作规模小于实际需求时,这项特定的防御措施将不奏效。
+
+#### SYN Cookie
+
+此策略要求服务器创建 Cookie。为避免在填充积压工作时断开连接,服务器使用 SYN-ACK 数据包响应每一项连接请求,而后从积压工作中删除 SYN 请求,同时从内存中删除请求,保证端口保持打开状态并做好重新建立连接的准备。如果连接是合法请求并且已将最后一个 ACK 数据包从客户端机器发回服务器,服务器将重建(存在一些限制)SYN 积压工作队列条目。虽然这项缓解措施势必会丢失一些 TCP 连接信息,但好过因此导致对合法用户发起拒绝服务攻击。
+
+## UDP Flood(洪水)
+
+### UDP Flood 是什么?
+
+**UDP Flood** 也是一种拒绝服务攻击,将大量的用户数据报协议(**UDP**)数据包发送到目标服务器,目的是压倒该设备的处理和响应能力。防火墙保护目标服务器也可能因 **UDP** 泛滥而耗尽,从而导致对合法流量的拒绝服务。
+
+### UDP Flood 攻击原理是什么?
+
+**UDP Flood** 主要通过利用服务器响应发送到其中一个端口的 **UDP** 数据包所采取的步骤。在正常情况下,当服务器在特定端口接收到 **UDP** 数据包时,会经过两个步骤:
+
+- 服务器首先检查是否正在运行正在侦听指定端口的请求的程序。
+- 如果没有程序在该端口接收数据包,则服务器使用 **ICMP**(ping)数据包进行响应,以通知发送方目的地不可达。
+
+举个例子。假设今天要联系酒店的小蓝,酒店客服接到电话后先查看房间的列表来确保小蓝在客房内,随后转接给小蓝。
+
+首先,接待员接收到呼叫者要求连接到特定房间的电话。接待员然后需要查看所有房间的清单,以确保客人在房间中可用,并愿意接听电话。碰巧的是,此时如果突然间所有的电话线同时亮起来,那么他们就会很快就变得不堪重负了。
+
+当服务器接收到每个新的 **UDP** 数据包时,它将通过步骤来处理请求,并利用该过程中的服务器资源。发送 **UDP** 报文时,每个报文将包含源设备的 **IP** 地址。在这种类型的 **DDoS** 攻击期间,攻击者通常不会使用自己的真实 **IP** 地址,而是会欺骗 **UDP** 数据包的源 **IP** 地址,从而阻止攻击者的真实位置被暴露并潜在地饱和来自目标的响应数据包服务器。
+
+由于目标服务器利用资源检查并响应每个接收到的 **UDP** 数据包的结果,当接收到大量 **UDP** 数据包时,目标的资源可能会迅速耗尽,导致对正常流量的拒绝服务。
+
+
+
+### 如何缓解 UDP Flooding?
+
+大多数操作系统部分限制了 **ICMP** 报文的响应速率,以中断需要 ICMP 响应的 **DDoS** 攻击。这种缓解的一个缺点是在攻击过程中,合法的数据包也可能被过滤。如果 **UDP Flood** 的容量足够高以使目标服务器的防火墙的状态表饱和,则在服务器级别发生的任何缓解都将不足以应对目标设备上游的瓶颈。
+
+## HTTP Flood(洪水)
+
+### HTTP Flood 是什么?
+
+HTTP Flood 是一种大规模的 DDoS(Distributed Denial of Service,分布式拒绝服务)攻击,旨在利用 HTTP 请求使目标服务器不堪重负。目标因请求而达到饱和,且无法响应正常流量后,将出现拒绝服务,拒绝来自实际用户的其他请求。
+
+
+
+### HTTP Flood 的攻击原理是什么?
+
+HTTP 洪水攻击是“第 7 层”DDoS 攻击的一种。第 7 层是 OSI 模型的应用程序层,指的是 HTTP 等互联网协议。HTTP 是基于浏览器的互联网请求的基础,通常用于加载网页或通过互联网发送表单内容。缓解应用程序层攻击特别复杂,因为恶意流量和正常流量很难区分。
+
+为了获得最大效率,恶意行为者通常会利用或创建僵尸网络,以最大程度地扩大攻击的影响。通过利用感染了恶意软件的多台设备,攻击者可以发起大量攻击流量来进行攻击。
+
+HTTP 洪水攻击有两种:
+
+- **HTTP GET 攻击**:在这种攻击形式下,多台计算机或其他设备相互协调,向目标服务器发送对图像、文件或其他资产的多个请求。当目标被传入的请求和响应所淹没时,来自正常流量源的其他请求将被拒绝服务。
+- **HTTP POST 攻击**:一般而言,在网站上提交表单时,服务器必须处理传入的请求并将数据推送到持久层(通常是数据库)。与发送 POST 请求所需的处理能力和带宽相比,处理表单数据和运行必要数据库命令的过程相对密集。这种攻击利用相对资源消耗的差异,直接向目标服务器发送许多 POST 请求,直到目标服务器的容量饱和并拒绝服务为止。
+
+### 如何防护 HTTP Flood?
+
+如前所述,缓解第 7 层攻击非常复杂,而且通常要从多方面进行。一种方法是对发出请求的设备实施质询,以测试它是否是机器人,这与在线创建帐户时常用的 CAPTCHA 测试非常相似。通过提出 JavaScript 计算挑战之类的要求,可以缓解许多攻击。
+
+其他阻止 HTTP 洪水攻击的途径包括使用 Web 应用程序防火墙 (WAF)、管理 IP 信誉数据库以跟踪和有选择地阻止恶意流量,以及由工程师进行动态分析。Cloudflare 具有超过 2000 万个互联网设备的规模优势,能够分析来自各种来源的流量并通过快速更新的 WAF 规则和其他防护策略来缓解潜在的攻击,从而消除应用程序层 DDoS 流量。
+
+## DNS Flood(洪水)
+
+### DNS Flood 是什么?
+
+域名系统(DNS)服务器是互联网的“电话簿“;互联网设备通过这些服务器来查找特定 Web 服务器以便访问互联网内容。DNS Flood 攻击是一种分布式拒绝服务(DDoS)攻击,攻击者用大量流量淹没某个域的 DNS 服务器,以尝试中断该域的 DNS 解析。如果用户无法找到电话簿,就无法查找到用于调用特定资源的地址。通过中断 DNS 解析,DNS Flood 攻击将破坏网站、API 或 Web 应用程序响应合法流量的能力。很难将 DNS Flood 攻击与正常的大流量区分开来,因为这些大规模流量往往来自多个唯一地址,查询该域的真实记录,模仿合法流量。
+
+### DNS Flood 的攻击原理是什么?
+
+
+
+域名系统的功能是将易于记忆的名称(例如 example.com)转换成难以记住的网站服务器地址(例如 192.168.0.1),因此成功攻击 DNS 基础设施将导致大多数人无法使用互联网。DNS Flood 攻击是一种相对较新的基于 DNS 的攻击,这种攻击是在高带宽[物联网(IoT)](https://www.cloudflare.com/learning/ddos/glossary/internet-of-things-iot/)[僵尸网络](https://www.cloudflare.com/learning/ddos/what-is-a-ddos-botnet/)(如 [Mirai](https://www.cloudflare.com/learning/ddos/glossary/mirai-botnet/))兴起后激增的。DNS Flood 攻击使用 IP 摄像头、DVR 盒和其他 IoT 设备的高带宽连接直接淹没主要提供商的 DNS 服务器。来自 IoT 设备的大量请求淹没 DNS 提供商的服务,阻止合法用户访问提供商的 DNS 服务器。
+
+DNS Flood 攻击不同于 [DNS 放大攻击](https://www.cloudflare.com/zh-cn/learning/ddos/dns-amplification-ddos-attack/)。与 DNS Flood 攻击不同,DNS 放大攻击反射并放大不安全 DNS 服务器的流量,以便隐藏攻击的源头并提高攻击的有效性。DNS 放大攻击使用连接带宽较小的设备向不安全的 DNS 服务器发送无数请求。这些设备对非常大的 DNS 记录发出小型请求,但在发出请求时,攻击者伪造返回地址为目标受害者。这种放大效果让攻击者能借助有限的攻击资源来破坏较大的目标。
+
+### 如何防护 DNS Flood?
+
+DNS Flood 对传统上基于放大的攻击方法做出了改变。借助轻易获得的高带宽僵尸网络,攻击者现能针对大型组织发动攻击。除非被破坏的 IoT 设备得以更新或替换,否则抵御这些攻击的唯一方法是使用一个超大型、高度分布式的 DNS 系统,以便实时监测、吸收和阻止攻击流量。
+
+## TCP 重置攻击
+
+在 **TCP** 重置攻击中,攻击者通过向通信的一方或双方发送伪造的消息,告诉它们立即断开连接,从而使通信双方连接中断。正常情况下,如果客户端收发现到达的报文段对于相关连接而言是不正确的,**TCP** 就会发送一个重置报文段,从而导致 **TCP** 连接的快速拆卸。
+
+**TCP** 重置攻击利用这一机制,通过向通信方发送伪造的重置报文段,欺骗通信双方提前关闭 TCP 连接。如果伪造的重置报文段完全逼真,接收者就会认为它有效,并关闭 **TCP** 连接,防止连接被用来进一步交换信息。服务端可以创建一个新的 **TCP** 连接来恢复通信,但仍然可能会被攻击者重置连接。万幸的是,攻击者需要一定的时间来组装和发送伪造的报文,所以一般情况下这种攻击只对长连接有杀伤力,对于短连接而言,你还没攻击呢,人家已经完成了信息交换。
+
+从某种意义上来说,伪造 **TCP** 报文段是很容易的,因为 **TCP/IP** 都没有任何内置的方法来验证服务端的身份。有些特殊的 IP 扩展协议(例如 `IPSec`)确实可以验证身份,但并没有被广泛使用。客户端只能接收报文段,并在可能的情况下使用更高级别的协议(如 `TLS`)来验证服务端的身份。但这个方法对 **TCP** 重置包并不适用,因为 **TCP** 重置包是 **TCP** 协议本身的一部分,无法使用更高级别的协议进行验证。
+
+## 模拟攻击
+
+> 以下实验是在 `OSX` 系统中完成的,其他系统请自行测试。
+
+现在来总结一下伪造一个 **TCP** 重置报文要做哪些事情:
+
+- 嗅探通信双方的交换信息。
+- 截获一个 `ACK` 标志位置位 1 的报文段,并读取其 `ACK` 号。
+- 伪造一个 TCP 重置报文段(`RST` 标志位置为 1),其序列号等于上面截获的报文的 `ACK` 号。这只是理想情况下的方案,假设信息交换的速度不是很快。大多数情况下为了增加成功率,可以连续发送序列号不同的重置报文。
+- 将伪造的重置报文发送给通信的一方或双方,时其中断连接。
+
+为了实验简单,我们可以使用本地计算机通过 `localhost` 与自己通信,然后对自己进行 TCP 重置攻击。需要以下几个步骤:
+
+- 在两个终端之间建立一个 TCP 连接。
+- 编写一个能嗅探通信双方数据的攻击程序。
+- 修改攻击程序,伪造并发送重置报文。
+
+下面正式开始实验。
+
+> 建立 TCP 连接
+
+可以使用 netcat 工具来建立 TCP 连接,这个工具很多操作系统都预装了。打开第一个终端窗口,运行以下命令:
+
+```bash
+$ nc -nvl 8000
+```
+
+这个命令会启动一个 TCP 服务,监听端口为 `8000`。接着再打开第二个终端窗口,运行以下命令:
+
+```bash
+$ nc 127.0.0.1 8000
+```
+
+该命令会尝试与上面的服务建立连接,在其中一个窗口输入一些字符,就会通过 TCP 连接发送给另一个窗口并打印出来。
+
+
+
+> 嗅探流量
+
+编写一个攻击程序,使用 Python 网络库 `scapy` 来读取两个终端窗口之间交换的数据,并将其打印到终端上。代码比较长,下面为一部份,完整代码后台回复 TCP 攻击,代码的核心是调用 `scapy` 的嗅探方法:
+
+
+
+这段代码告诉 `scapy` 在 `lo0` 网络接口上嗅探数据包,并记录所有 TCP 连接的详细信息。
+
+- **iface** : 告诉 scapy 在 `lo0`(localhost)网络接口上进行监听。
+- **lfilter** : 这是个过滤器,告诉 scapy 忽略所有不属于指定的 TCP 连接(通信双方皆为 `localhost`,且端口号为 `8000`)的数据包。
+- **prn** : scapy 通过这个函数来操作所有符合 `lfilter` 规则的数据包。上面的例子只是将数据包打印到终端,下文将会修改函数来伪造重置报文。
+- **count** : scapy 函数返回之前需要嗅探的数据包数量。
+
+> 发送伪造的重置报文
+
+下面开始修改程序,发送伪造的 TCP 重置报文来进行 TCP 重置攻击。根据上面的解读,只需要修改 prn 函数就行了,让其检查数据包,提取必要参数,并利用这些参数来伪造 TCP 重置报文并发送。
+
+例如,假设该程序截获了一个从(`src_ip`, `src_port`)发往 (`dst_ip`, `dst_port`)的报文段,该报文段的 ACK 标志位已置为 1,ACK 号为 `100,000`。攻击程序接下来要做的是:
+
+- 由于伪造的数据包是对截获的数据包的响应,所以伪造数据包的源 `IP/Port` 应该是截获数据包的目的 `IP/Port`,反之亦然。
+- 将伪造数据包的 `RST` 标志位置为 1,以表示这是一个重置报文。
+- 将伪造数据包的序列号设置为截获数据包的 ACK 号,因为这是发送方期望收到的下一个序列号。
+- 调用 `scapy` 的 `send` 方法,将伪造的数据包发送给截获数据包的发送方。
+
+对于我的程序而言,只需将这一行取消注释,并注释这一行的上面一行,就可以全面攻击了。按照步骤 1 的方法设置 TCP 连接,打开第三个窗口运行攻击程序,然后在 TCP 连接的其中一个终端输入一些字符串,你会发现 TCP 连接被中断了!
+
+> 进一步实验
+
+1. 可以继续使用攻击程序进行实验,将伪造数据包的序列号加减 1 看看会发生什么,是不是确实需要和截获数据包的 `ACK` 号完全相同。
+2. 打开 `Wireshark`,监听 lo0 网络接口,并使用过滤器 `ip.src == 127.0.0.1 && ip.dst == 127.0.0.1 && tcp.port == 8000` 来过滤无关数据。你可以看到 TCP 连接的所有细节。
+3. 在连接上更快速地发送数据流,使攻击更难执行。
+
+## 中间人攻击
+
+猪八戒要向小蓝表白,于是写了一封信给小蓝,结果第三者小黑拦截到了这封信,把这封信进行了篡改,于是乎在他们之间进行搞破坏行动。这个马文才就是中间人,实施的就是中间人攻击。好我们继续聊聊什么是中间人攻击。
+
+### 什么是中间人?
+
+攻击中间人攻击英文名叫 Man-in-the-MiddleAttack,简称「MITM 攻击」。指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方 直接对话,但事实上整个会话都被攻击者完全控制。我们画一张图:
+
+
+
+从这张图可以看到,中间人其实就是攻击者。通过这种原理,有很多实现的用途,比如说,你在手机上浏览不健康网站的时候,手机就会提示你,此网站可能含有病毒,是否继续访问还是做其他的操作等等。
+
+### 中间人攻击的原理是什么?
+
+举个例子,我和公司签了一个一份劳动合同,一人一份合同。不晓得哪个可能改了合同内容,不知道真假了,怎么搞?只好找专业的机构来鉴定,自然就要花钱。
+
+在安全领域有句话:**我们没有办法杜绝网络犯罪,只好想办法提高网络犯罪的成本**。既然没法杜绝这种情况,那我们就想办法提高作案的成本,今天我们就简单了解下基本的网络安全知识,也是面试中的高频面试题了。
+
+为了避免双方说活不算数的情况,双方引入第三家机构,将合同原文给可信任的第三方机构,只要这个机构不监守自盗,合同就相对安全。
+
+**如果第三方机构内部不严格或容易出现纰漏?**
+
+虽然我们将合同原文给第三方机构了,为了防止内部人员的更改,需要采取什么措施呢
+
+一种可行的办法是引入 **摘要算法** 。即合同和摘要一起,为了简单的理解摘要。大家可以想象这个摘要为一个函数,这个函数对原文进行了加密,会产生一个唯一的散列值,一旦原文发生一点点变化,那么这个散列值将会变化。
+
+#### 有哪些常用的摘要算法呢?
+
+目前比较常用的加密算法有消息摘要算法和安全散列算法(**SHA**)。**MD5** 是将任意长度的文章转化为一个 128 位的散列值,可是在 2004 年,**MD5** 被证实了容易发生碰撞,即两篇原文产生相同的摘要。这样的话相当于直接给黑客一个后门,轻松伪造摘要。
+
+所以在大部分的情况下都会选择 **SHA 算法** 。
+
+**出现内鬼了怎么办?**
+
+看似很安全的场面了,理论上来说杜绝了篡改合同的做法。主要某个员工同时具有修改合同和摘要的权利,那搞事儿就是时间的问题了,毕竟没哪个系统可以完全的杜绝员工接触敏感信息,除非敏感信息都不存在。所以能不能考虑将合同和摘要分开存储呢
+
+**那如何确保员工不会修改合同呢?**
+
+这确实蛮难的,不过办法总比困难多。我们将合同放在双方手中,摘要放在第三方机构,篡改难度进一步加大
+
+**那么员工万一和某个用户串通好了呢?**
+
+看来放在第三方的机构还是不好使,同样存在不小风险。所以还需要寻找新的方案,这就出现了 **数字签名和证书**。
+
+#### 数字证书和签名有什么用?
+
+同样的,举个例子。Sum 和 Mike 两个人签合同。Sum 首先用 **SHA** 算法计算合同的摘要,然后用自己私钥将摘要加密,得到数字签名。Sum 将合同原文、签名,以及公钥三者都交给 Mike
+
+
+
+如果 Sum 想要证明合同是 Mike 的,那么就要使用 Mike 的公钥,将这个签名解密得到摘要 x,然后 Mike 计算原文的 sha 摘要 Y,随后对比 x 和 y,如果两者相等,就认为数据没有被篡改
+
+在这样的过程中,Mike 是不能更改 Sum 的合同,因为要修改合同不仅仅要修改原文还要修改摘要,修改摘要需要提供 Mike 的私钥,私钥即 Sum 独有的密码,公钥即 Sum 公布给他人使用的密码
+
+总之,公钥加密的数据只能私钥可以解密。私钥加密的数据只有公钥可以解密,这就是 **非对称加密** 。
+
+隐私保护?不是吓唬大家,信息是透明的兄 die,不过尽量去维护个人的隐私吧,今天学习对称加密和非对称加密。
+
+大家先读读这个字"钥",是读"yao",我以前也是,其实读"yue"
+
+#### 什么是对称加密?
+
+对称加密,顾名思义,加密方与解密方使用同一钥匙(秘钥)。具体一些就是,发送方通过使用相应的加密算法和秘钥,对将要发送的信息进行加密;对于接收方而言,使用解密算法和相同的秘钥解锁信息,从而有能力阅读信息。
+
+
+
+#### 常见的对称加密算法有哪些?
+
+**DES**
+
+DES 使用的密钥表面上是 64 位的,然而只有其中的 56 位被实际用于算法,其余 8 位可以被用于奇偶校验,并在算法中被丢弃。因此,**DES** 的有效密钥长度为 56 位,通常称 **DES** 的密钥长度为 56 位。假设秘钥为 56 位,采用暴力破 Jie 的方式,其秘钥个数为 2 的 56 次方,那么每纳秒执行一次解密所需要的时间差不多 1 年的样子。当然,没人这么干。**DES** 现在已经不是一种安全的加密方法,主要因为它使用的 56 位密钥过短。
+
+
+
+**IDEA**
+
+国际数据加密算法(International Data Encryption Algorithm)。秘钥长度 128 位,优点没有专利的限制。
+
+**AES**
+
+当 DES 被破解以后,没过多久推出了 **AES** 算法,提供了三种长度供选择,128 位、192 位和 256,为了保证性能不受太大的影响,选择 128 即可。
+
+**SM1 和 SM4**
+
+之前几种都是国外的,我们国内自行研究了国密 **SM1 **和 **SM4**。其中 S 都属于国家标准,算法公开。优点就是国家的大力支持和认可
+
+**总结**:
+
+
+
+#### 常见的非对称加密算法有哪些?
+
+在对称加密中,发送方与接收方使用相同的秘钥。那么在非对称加密中则是发送方与接收方使用的不同的秘钥。其主要解决的问题是防止在秘钥协商的过程中发生泄漏。比如在对称加密中,小蓝将需要发送的消息加密,然后告诉你密码是 123balala,ok,对于其他人而言,很容易就能劫持到密码是 123balala。那么在非对称的情况下,小蓝告诉所有人密码是 123balala,对于中间人而言,拿到也没用,因为没有私钥。所以,非对称密钥其实主要解决了密钥分发的难题。如下图
+
+
+
+其实我们经常都在使用非对称加密,比如使用多台服务器搭建大数据平台 hadoop,为了方便多台机器设置免密登录,是不是就会涉及到秘钥分发。再比如搭建 docker 集群也会使用相关非对称加密算法。
+
+常见的非对称加密算法:
+
+- RSA(RSA 加密算法,RSA Algorithm):优势是性能比较快,如果想要较高的加密难度,需要很长的秘钥。
+
+- ECC:基于椭圆曲线提出。是目前加密强度最高的非对称加密算法
+- SM2:同样基于椭圆曲线问题设计。最大优势就是国家认可和大力支持。
+
+总结:
+
+
+
+#### 常见的散列算法有哪些?
+
+这个大家应该更加熟悉了,比如我们平常使用的 MD5 校验,在很多时候,我并不是拿来进行加密,而是用来获得唯一性 ID。在做系统的过程中,存储用户的各种密码信息,通常都会通过散列算法,最终存储其散列值。
+
+**MD5**
+
+MD5 可以用来生成一个 128 位的消息摘要,它是目前应用比较普遍的散列算法,具体的应用场景你可以自行 参阅。虽然,因为算法的缺陷,它的唯一性已经被破解了,但是大部分场景下,这并不会构成安全问题。但是,如果不是长度受限(32 个字符),我还是不推荐你继续使用 **MD5** 的。
+
+**SHA**
+
+安全散列算法。**SHA** 分为 **SHA1** 和 **SH2** 两个版本。该算法的思想是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文,也可以简单的理解为取一串输入码(称为预映射或信息),并把它们转化为长度较短、位数固定的输出序列即散列值(也称为信息摘要或信息认证代码)的过程。
+
+**SM3**
+
+国密算法**SM3**。加密强度和 SHA-256 想不多。主要是收到国家的支持。
+
+**总结**:
+
+
+
+**大部分情况下使用对称加密,具有比较不错的安全性。如果需要分布式进行秘钥分发,考虑非对称。如果不需要可逆计算则散列算法。** 因为这段时间有这方面需求,就看了一些这方面的资料,入坑信息安全,就怕以后洗发水都不用买。谢谢大家查看!
+
+#### 第三方机构和证书机制有什么用?
+
+问题还有,此时如果 Sum 否认给过 Mike 的公钥和合同,不久 gg 了
+
+所以需要 Sum 过的话做过的事儿需要足够的信誉,这就引入了 **第三方机构和证书机制** 。
+
+证书之所以会有信用,是因为证书的签发方拥有信用。所以如果 Sum 想让 Mike 承认自己的公钥,Sum 不会直接将公钥给 Mike ,而是提供由第三方机构,含有公钥的证书。如果 Mike 也信任这个机构,法律都认可,那 ik,信任关系成立
+
+
+
+如上图所示,Sum 将自己的申请提交给机构,产生证书的原文。机构用自己的私钥签名 Sum 的申请原文(先根据原文内容计算摘要,再用私钥加密),得到带有签名信息的证书。Mike 拿到带签名信息的证书,通过第三方机构的公钥进行解密,获得 Sum 证书的摘要、证书的原文。有了 Sum 证书的摘要和原文,Mike 就可以进行验签。验签通过,Mike 就可以确认 Sum 的证书的确是第三方机构签发的。
+
+用上面这样一个机制,合同的双方都无法否认合同。这个解决方案的核心在于需要第三方信用服务机构提供信用背书。这里产生了一个最基础的信任链,如果第三方机构的信任崩溃,比如被黑客攻破,那整条信任链条也就断裂了
+
+为了让这个信任条更加稳固,就需要环环相扣,打造更长的信任链,避免单点信任风险
+
+
+
+上图中,由信誉最好的根证书机构提供根证书,然后根证书机构去签发二级机构的证书;二级机构去签发三级机构的证书;最后有由三级机构去签发 Sum 证书。
+
+如果要验证 Sum 证书的合法性,就需要用三级机构证书中的公钥去解密 Sum 证书的数字签名。
+
+如果要验证三级机构证书的合法性,就需要用二级机构的证书去解密三级机构证书的数字签名。
+
+如果要验证二级结构证书的合法性,就需要用根证书去解密。
+
+以上,就构成了一个相对长一些的信任链。如果其中一方想要作弊是非常困难的,除非链条中的所有机构同时联合起来,进行欺诈。
+
+### 中间人攻击如何避免?
+
+既然知道了中间人攻击的原理也知道了他的危险,现在我们看看如何避免。相信我们都遇到过下面这种状况:
+
+
+
+出现这个界面的很多情况下,都是遇到了中间人攻击的现象,需要对安全证书进行及时地监测。而且大名鼎鼎的 github 网站,也曾遭遇过中间人攻击:
+
+想要避免中间人攻击的方法目前主要有两个:
+
+- 客户端不要轻易相信证书:因为这些证书极有可能是中间人。
+- App 可以提前预埋证书在本地:意思是我们本地提前有一些证书,这样其他证书就不能再起作用了。
+
+## DDOS
+
+通过上面的描述,总之即好多种攻击都是 **DDOS** 攻击,所以简单总结下这个攻击相关内容。
+
+其实,像全球互联网各大公司,均遭受过大量的 **DDoS**。
+
+2018 年,GitHub 在一瞬间遭到高达 1.35Tbps 的带宽攻击。这次 DDoS 攻击几乎可以堪称是互联网有史以来规模最大、威力最大的 DDoS 攻击了。在 GitHub 遭到攻击后,仅仅一周后,DDoS 攻击又开始对 Google、亚马逊甚至 Pornhub 等网站进行了 DDoS 攻击。后续的 DDoS 攻击带宽最高也达到了 1Tbps。
+
+### DDoS 攻击究竟是什么?
+
+DDos 全名 Distributed Denial of Service,翻译成中文就是**分布式拒绝服务**。指的是处于不同位置的多个攻击者同时向一个或数个目标发动攻击,是一种分布的、协同的大规模攻击方式。单一的 DoS 攻击一般是采用一对一方式的,它利用网络协议和操作系统的一些缺陷,采用**欺骗和伪装**的策略来进行网络攻击,使网站服务器充斥大量要求回复的信息,消耗网络带宽或系统资源,导致网络或系统不胜负荷以至于瘫痪而停止提供正常的网络服务。
+
+> 举个例子
+
+我开了一家有五十个座位的重庆火锅店,由于用料上等,童叟无欺。平时门庭若市,生意特别红火,而对面二狗家的火锅店却无人问津。二狗为了对付我,想了一个办法,叫了五十个人来我的火锅店坐着却不点菜,让别的客人无法吃饭。
+
+上面这个例子讲的就是典型的 DDoS 攻击,一般来说是指攻击者利用“肉鸡”对目标网站在较短的时间内发起大量请求,大规模消耗目标网站的主机资源,让它无法正常服务。在线游戏、互联网金融等领域是 DDoS 攻击的高发行业。
+
+攻击方式很多,比如 **ICMP Flood**、**UDP Flood**、**NTP Flood**、**SYN Flood**、**CC 攻击**、**DNS Query Flood**等等。
+
+### 如何应对 DDoS 攻击?
+
+#### 高防服务器
+
+还是拿开的重庆火锅店举例,高防服务器就是我给重庆火锅店增加了两名保安,这两名保安可以让保护店铺不受流氓骚扰,并且还会定期在店铺周围巡逻防止流氓骚扰。
+
+高防服务器主要是指能独立硬防御 50Gbps 以上的服务器,能够帮助网站拒绝服务攻击,定期扫描网络主节点等,这东西是不错,就是贵~
+
+#### 黑名单
+
+面对火锅店里面的流氓,我一怒之下将他们拍照入档,并禁止他们踏入店铺,但是有的时候遇到长得像的人也会禁止他进入店铺。这个就是设置黑名单,此方法秉承的就是“错杀一千,也不放一百”的原则,会封锁正常流量,影响到正常业务。
+
+#### DDoS 清洗
+
+**DDos** 清洗,就是我发现客人进店几分钟以后,但是一直不点餐,我就把他踢出店里。
+
+**DDoS** 清洗会对用户请求数据进行实时监控,及时发现 **DOS** 攻击等异常流量,在不影响正常业务开展的情况下清洗掉这些异常流量。
+
+#### CDN 加速
+
+CDN 加速,我们可以这么理解:为了减少流氓骚扰,我干脆将火锅店开到了线上,承接外卖服务,这样流氓找不到店在哪里,也耍不来流氓了。
+
+在现实中,CDN 服务将网站访问流量分配到了各个节点中,这样一方面隐藏网站的真实 IP,另一方面即使遭遇 **DDoS** 攻击,也可以将流量分散到各个节点中,防止源站崩溃。
+
+## 参考
+
+- HTTP 洪水攻击 - CloudFlare:https://www.cloudflare.com/zh-cn/learning/ddos/http-flood-ddos-attack/
+- SYN 洪水攻击:https://www.cloudflare.com/zh-cn/learning/ddos/syn-flood-ddos-attack/
+- 什么是 IP 欺骗?:https://www.cloudflare.com/zh-cn/learning/ddos/glossary/ip-spoofing/
+- 什么是 DNS 洪水?| DNS 洪水 DDoS 攻击:https://www.cloudflare.com/zh-cn/learning/ddos/dns-flood-ddos-attack/
diff --git a/docs/cs-basics/network/osi-and-tcp-ip-model.md b/docs/cs-basics/network/osi-and-tcp-ip-model.md
new file mode 100644
index 0000000000000000000000000000000000000000..57c74108fc1b7dc19756d370e0985bf528cc05cd
--- /dev/null
+++ b/docs/cs-basics/network/osi-and-tcp-ip-model.md
@@ -0,0 +1,193 @@
+---
+title: OSI 和 TCP/IP 网络分层模型详解(基础)
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+## OSI 七层模型
+
+**OSI 七层模型** 是国际标准化组织提出一个网络分层模型,其大体结构以及每一层提供的功能如下图所示:
+
+
+
+每一层都专注做一件事情,并且每一层都需要使用下一层提供的功能比如传输层需要使用网络层提供的路由和寻址功能,这样传输层才知道把数据传输到哪里去。
+
+**OSI 的七层体系结构概念清楚,理论也很完整,但是它比较复杂而且不实用,而且有些功能在多个层中重复出现。**
+
+上面这种图可能比较抽象,再来一个比较生动的图片。下面这个图片是我在国外的一个网站上看到的,非常赞!
+
+
+
+**既然 OSI 七层模型这么厉害,为什么干不过 TCP/IP 四 层模型呢?**
+
+的确,OSI 七层模型当时一直被一些大公司甚至一些国家政府支持。这样的背景下,为什么会失败呢?我觉得主要有下面几方面原因:
+
+1. OSI 的专家缺乏实际经验,他们在完成 OSI 标准时缺乏商业驱动力
+2. OSI 的协议实现起来过分复杂,而且运行效率很低
+3. OSI 制定标准的周期太长,因而使得按 OSI 标准生产的设备无法及时进入市场(20 世纪 90 年代初期,虽然整套的 OSI 国际标准都已经制定出来,但基于 TCP/IP 的互联网已经抢先在全球相当大的范围成功运行了)
+4. OSI 的层次划分不太合理,有些功能在多个层次中重复出现。
+
+OSI 七层模型虽然失败了,但是却提供了很多不错的理论基础。为了更好地去了解网络分层,OSI 七层模型还是非常有必要学习的。
+
+最后再分享一个关于 OSI 七层模型非常不错的总结图片!
+
+
+
+## TCP/IP 四层模型
+
+**TCP/IP 四层模型** 是目前被广泛采用的一种模型,我们可以将 TCP / IP 模型看作是 OSI 七层模型的精简版本,由以下 4 层组成:
+
+1. 应用层
+2. 传输层
+3. 网络层
+4. 网络接口层
+
+需要注意的是,我们并不能将 TCP/IP 四层模型 和 OSI 七层模型完全精确地匹配起来,不过可以简单将两者对应起来,如下图所示:
+
+
+
+### 应用层(Application layer)
+
+**应用层位于传输层之上,主要提供两个终端设备上的应用程序之间信息交换的服务,它定义了信息交换的格式,消息会交给下一层传输层来传输。** 我们把应用层交互的数据单元称为报文。
+
+
+
+应用层协议定义了网络通信规则,对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多,如支持 Web 应用的 HTTP 协议,支持电子邮件的 SMTP 协议等等。
+
+**应用层常见协议**:
+
+
+
+- **HTTP(Hypertext Transfer Protocol,超文本传输协议)**:基于 TCP 协议,是一种用于传输超文本和多媒体内容的协议,主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。
+- **SMTP(Simple Mail Transfer Protocol,简单邮件发送协议)**:基于 TCP 协议,是一种用于发送电子邮件的协议。注意 ⚠️:SMTP 协议只负责邮件的发送,而不是接收。要从邮件服务器接收邮件,需要使用 POP3 或 IMAP 协议。
+- **POP3/IMAP(邮件接收协议)**:基于 TCP 协议,两者都是负责邮件接收的协议。IMAP 协议是比 POP3 更新的协议,它在功能和性能上都更加强大。IMAP 支持邮件搜索、标记、分类、归档等高级功能,而且可以在多个设备之间同步邮件状态。几乎所有现代电子邮件客户端和服务器都支持 IMAP。
+- **FTP(File Transfer Protocol,文件传输协议)** : 基于 TCP 协议,是一种用于在计算机之间传输文件的协议,可以屏蔽操作系统和文件存储方式。注意 ⚠️:FTP 是一种不安全的协议,因为它在传输过程中不会对数据进行加密。建议在传输敏感数据时使用更安全的协议,如 SFTP。
+- **Telnet(远程登陆协议)**:基于 TCP 协议,用于通过一个终端登陆到其他服务器。Telnet 协议的最大缺点之一是所有数据(包括用户名和密码)均以明文形式发送,这有潜在的安全风险。这就是为什么如今很少使用 Telnet,而是使用一种称为 SSH 的非常安全的网络传输协议的主要原因。
+- **SSH(Secure Shell Protocol,安全的网络传输协议)**:基于 TCP 协议,通过加密和认证机制实现安全的访问和文件传输等业务
+- **RTP(Real-time Transport Protocol,实时传输协议)**:通常基于 UDP 协议,但也支持 TCP 协议。它提供了端到端的实时传输数据的功能,但不包含资源预留存、不保证实时传输质量,这些功能由 WebRTC 实现。
+- **DNS(Domain Name System,域名管理系统)**: 基于 UDP 协议,用于解决域名和 IP 地址的映射问题。
+
+关于这些协议的详细介绍请看 [应用层常见协议总结(应用层)](./application-layer-protocol.md) 这篇文章。
+
+### 传输层(Transport layer)
+
+**传输层的主要任务就是负责向两台终端设备进程之间的通信提供通用的数据传输服务。** 应用进程利用该服务传送应用层报文。“通用的”是指并不针对某一个特定的网络应用,而是多种应用可以使用同一个运输层服务。
+
+**传输层常见协议**:
+
+
+
+- **TCP(Transmisson Control Protocol,传输控制协议 )**:提供 **面向连接** 的,**可靠** 的数据传输服务。
+- **UDP(User Datagram Protocol,用户数据协议)**:提供 **无连接** 的,**尽最大努力** 的数据传输服务(不保证数据传输的可靠性),简单高效。
+
+### 网络层(Network layer)
+
+**网络层负责为分组交换网上的不同主机提供通信服务。** 在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 IP 协议,因此分组也叫 IP 数据报,简称数据报。
+
+⚠️ 注意:**不要把运输层的“用户数据报 UDP”和网络层的“IP 数据报”弄混**。
+
+**网络层的还有一个任务就是选择合适的路由,使源主机运输层所传下来的分组,能通过网络层中的路由器找到目的主机。**
+
+这里强调指出,网络层中的“网络”二字已经不是我们通常谈到的具体网络,而是指计算机网络体系结构模型中第三层的名称。
+
+互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Internet Protocol)和许多路由选择协议,因此互联网的网络层也叫做 **网际层** 或 **IP 层**。
+
+**网络层常见协议**:
+
+
+
+- **IP(Internet Protocol,网际协议)**:TCP/IP 协议中最重要的协议之一,主要作用是定义数据包的格式、对数据包进行路由和寻址,以便它们可以跨网络传播并到达正确的目的地。目前 IP 协议主要分为两种,一种是过去的 IPv4,另一种是较新的 IPv6,目前这两种协议都在使用,但后者已经被提议来取代前者。
+- **ARP(Address Resolution Protocol,地址解析协议)**:ARP 协议解决的是网络层地址和链路层地址之间的转换问题。因为一个 IP 数据报在物理上传输的过程中,总是需要知道下一跳(物理上的下一个目的地)该去往何处,但 IP 地址属于逻辑地址,而 MAC 地址才是物理地址,ARP 协议解决了 IP 地址转 MAC 地址的一些问题。
+- **ICMP(Internet Control Message Protocol,互联网控制报文协议)**:一种用于传输网络状态和错误消息的协议,常用于网络诊断和故障排除。例如,Ping 工具就使用了 ICMP 协议来测试网络连通性。
+- **NAT(Network Address Translation,网络地址转换协议)**:NAT 协议的应用场景如同它的名称——网络地址转换,应用于内部网到外部网的地址转换过程中。具体地说,在一个小的子网(局域网,LAN)内,各主机使用的是同一个 LAN 下的 IP 地址,但在该 LAN 以外,在广域网(WAN)中,需要一个统一的 IP 地址来标识该 LAN 在整个 Internet 上的位置。
+- **OSPF(Open Shortest Path First,开放式最短路径优先)** ):一种内部网关协议(Interior Gateway Protocol,IGP),也是广泛使用的一种动态路由协议,基于链路状态算法,考虑了链路的带宽、延迟等因素来选择最佳路径。
+- **RIP(Routing Information Protocol,路由信息协议)**:一种内部网关协议(Interior Gateway Protocol,IGP),也是一种动态路由协议,基于距离向量算法,使用固定的跳数作为度量标准,选择跳数最少的路径作为最佳路径。
+- **BGP(Border Gateway Protocol,边界网关协议)**:一种用来在路由选择域之间交换网络层可达性信息(Network Layer Reachability Information,NLRI)的路由选择协议,具有高度的灵活性和可扩展性。
+
+### 网络接口层(Network interface layer)
+
+我们可以把网络接口层看作是数据链路层和物理层的合体。
+
+1. 数据链路层(data link layer)通常简称为链路层( 两台主机之间的数据传输,总是在一段一段的链路上传送的)。**数据链路层的作用是将网络层交下来的 IP 数据报组装成帧,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。**
+2. **物理层的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异**
+
+网络接口层重要功能和协议如下图所示:
+
+
+
+### 总结
+
+简单总结一下每一层包含的协议和核心技术:
+
+
+
+**应用层协议** :
+
+- HTTP(Hypertext Transfer Protocol,超文本传输协议)
+- SMTP(Simple Mail Transfer Protocol,简单邮件发送协议)
+- POP3/IMAP(邮件接收协议)
+- FTP(File Transfer Protocol,文件传输协议)
+- Telnet(远程登陆协议)
+- SSH(Secure Shell Protocol,安全的网络传输协议)
+- RTP(Real-time Transport Protocol,实时传输协议)
+- DNS(Domain Name System,域名管理系统)
+- ......
+
+**传输层协议** :
+
+- TCP 协议
+ - 报文段结构
+ - 可靠数据传输
+ - 流量控制
+ - 拥塞控制
+- UDP 协议
+ - 报文段结构
+ - RDT(可靠数据传输协议)
+
+**网络层协议** :
+
+- IP(Internet Protocol,网际协议)
+- ARP(Address Resolution Protocol,地址解析协议)
+- ICMP 协议(控制报文协议,用于发送控制消息)
+- NAT(Network Address Translation,网络地址转换协议)
+- OSPF(Open Shortest Path First,开放式最短路径优先)
+- RIP(Routing Information Protocol,路由信息协议)
+- BGP(Border Gateway Protocol,边界网关协议)
+- ......
+
+**网络接口层** :
+
+- 差错检测技术
+- 多路访问协议(信道复用技术)
+- CSMA/CD 协议
+- MAC 协议
+- 以太网技术
+- ......
+
+## 网络分层的原因
+
+在这篇文章的最后,我想聊聊:“为什么网络要分层?”。
+
+说到分层,我们先从我们平时使用框架开发一个后台程序来说,我们往往会按照每一层做不同的事情的原则将系统分为三层(复杂的系统分层会更多):
+
+1. Repository(数据库操作)
+2. Service(业务操作)
+3. Controller(前后端数据交互)
+
+**复杂的系统需要分层,因为每一层都需要专注于一类事情。网络分层的原因也是一样,每一层只专注于做一类事情。**
+
+好了,再来说回:“为什么网络要分层?”。我觉得主要有 3 方面的原因:
+
+1. **各层之间相互独立**:各层之间相互独立,各层之间不需要关心其他层是如何实现的,只需要知道自己如何调用下层提供好的功能就可以了(可以简单理解为接口调用)**。这个和我们对开发时系统进行分层是一个道理。**
+2. **提高了整体灵活性**:每一层都可以使用最适合的技术来实现,你只需要保证你提供的功能以及暴露的接口的规则没有改变就行了。**这个和我们平时开发系统的时候要求的高内聚、低耦合的原则也是可以对应上的。**
+3. **大问题化小**:分层可以将复杂的网络问题分解为许多比较小的、界线比较清晰简单的小问题来处理和解决。这样使得复杂的计算机网络系统变得易于设计,实现和标准化。 **这个和我们平时开发的时候,一般会将系统功能分解,然后将复杂的问题分解为容易理解的更小的问题是相对应的,这些较小的问题具有更好的边界(目标和接口)定义。**
+
+我想到了计算机世界非常非常有名的一句话,这里分享一下:
+
+> 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决,计算机整个体系从上到下都是按照严格的层次结构设计的。
+
+## 参考
+
+- TCP/IP model vs OSI model:https://fiberbit.com.tw/tcpip-model-vs-osi-model/
+- Data Encapsulation and the TCP/IP Protocol Stack:https://docs.oracle.com/cd/E19683-01/806-4075/ipov-32/index.html
diff --git a/docs/cs-basics/network/other-network-questions.md b/docs/cs-basics/network/other-network-questions.md
new file mode 100644
index 0000000000000000000000000000000000000000..77b3c0a09d53920abb0809478376368c79a13084
--- /dev/null
+++ b/docs/cs-basics/network/other-network-questions.md
@@ -0,0 +1,313 @@
+---
+title: 计算机网络常见面试题总结(上)
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+
+
+上篇主要是计算机网络基础和应用层相关的内容。
+
+## 计算机网络基础
+
+### 网络分层模型
+
+#### OSI 七层模型是什么?每一层的作用是什么?
+
+**OSI 七层模型** 是国际标准化组织提出一个网络分层模型,其大体结构以及每一层提供的功能如下图所示:
+
+
+
+每一层都专注做一件事情,并且每一层都需要使用下一层提供的功能比如传输层需要使用网络层提供的路由和寻址功能,这样传输层才知道把数据传输到哪里去。
+
+**OSI 的七层体系结构概念清楚,理论也很完整,但是它比较复杂而且不实用,而且有些功能在多个层中重复出现。**
+
+上面这种图可能比较抽象,再来一个比较生动的图片。下面这个图片是我在国外的一个网站上看到的,非常赞!
+
+
+
+#### TCP/IP 四层模型是什么?每一层的作用是什么?
+
+**TCP/IP 四层模型** 是目前被广泛采用的一种模型,我们可以将 TCP / IP 模型看作是 OSI 七层模型的精简版本,由以下 4 层组成:
+
+1. 应用层
+2. 传输层
+3. 网络层
+4. 网络接口层
+
+需要注意的是,我们并不能将 TCP/IP 四层模型 和 OSI 七层模型完全精确地匹配起来,不过可以简单将两者对应起来,如下图所示:
+
+
+
+关于每一层作用的详细介绍,请看 [OSI 和 TCP/IP 网络分层模型详解(基础)](./osi-and-tcp-ip-model.md) 这篇文章。
+
+#### 为什么网络要分层?
+
+说到分层,我们先从我们平时使用框架开发一个后台程序来说,我们往往会按照每一层做不同的事情的原则将系统分为三层(复杂的系统分层会更多):
+
+1. Repository(数据库操作)
+2. Service(业务操作)
+3. Controller(前后端数据交互)
+
+**复杂的系统需要分层,因为每一层都需要专注于一类事情。网络分层的原因也是一样,每一层只专注于做一类事情。**
+
+好了,再来说回:“为什么网络要分层?”。我觉得主要有 3 方面的原因:
+
+1. **各层之间相互独立**:各层之间相互独立,各层之间不需要关心其他层是如何实现的,只需要知道自己如何调用下层提供好的功能就可以了(可以简单理解为接口调用)**。这个和我们对开发时系统进行分层是一个道理。**
+2. **提高了整体灵活性**:每一层都可以使用最适合的技术来实现,你只需要保证你提供的功能以及暴露的接口的规则没有改变就行了。**这个和我们平时开发系统的时候要求的高内聚、低耦合的原则也是可以对应上的。**
+3. **大问题化小**:分层可以将复杂的网络问题分解为许多比较小的、界线比较清晰简单的小问题来处理和解决。这样使得复杂的计算机网络系统变得易于设计,实现和标准化。 **这个和我们平时开发的时候,一般会将系统功能分解,然后将复杂的问题分解为容易理解的更小的问题是相对应的,这些较小的问题具有更好的边界(目标和接口)定义。**
+
+我想到了计算机世界非常非常有名的一句话,这里分享一下:
+
+> 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决,计算机整个体系从上到下都是按照严格的层次结构设计的。
+
+### 常见网络协议
+
+#### 应用层有哪些常见的协议?
+
+
+
+- **HTTP(Hypertext Transfer Protocol,超文本传输协议)**:基于 TCP 协议,是一种用于传输超文本和多媒体内容的协议,主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。
+- **SMTP(Simple Mail Transfer Protocol,简单邮件发送协议)**:基于 TCP 协议,是一种用于发送电子邮件的协议。注意 ⚠️:SMTP 协议只负责邮件的发送,而不是接收。要从邮件服务器接收邮件,需要使用 POP3 或 IMAP 协议。
+- **POP3/IMAP(邮件接收协议)**:基于 TCP 协议,两者都是负责邮件接收的协议。IMAP 协议是比 POP3 更新的协议,它在功能和性能上都更加强大。IMAP 支持邮件搜索、标记、分类、归档等高级功能,而且可以在多个设备之间同步邮件状态。几乎所有现代电子邮件客户端和服务器都支持 IMAP。
+- **FTP(File Transfer Protocol,文件传输协议)** : 基于 TCP 协议,是一种用于在计算机之间传输文件的协议,可以屏蔽操作系统和文件存储方式。注意 ⚠️:FTP 是一种不安全的协议,因为它在传输过程中不会对数据进行加密。建议在传输敏感数据时使用更安全的协议,如 SFTP。
+- **Telnet(远程登陆协议)**:基于 TCP 协议,用于通过一个终端登陆到其他服务器。Telnet 协议的最大缺点之一是所有数据(包括用户名和密码)均以明文形式发送,这有潜在的安全风险。这就是为什么如今很少使用 Telnet,而是使用一种称为 SSH 的非常安全的网络传输协议的主要原因。
+- **SSH(Secure Shell Protocol,安全的网络传输协议)**:基于 TCP 协议,通过加密和认证机制实现安全的访问和文件传输等业务
+- **RTP(Real-time Transport Protocol,实时传输协议)**:通常基于 UDP 协议,但也支持 TCP 协议。它提供了端到端的实时传输数据的功能,但不包含资源预留存、不保证实时传输质量,这些功能由 WebRTC 实现。
+- **DNS(Domain Name System,域名管理系统)**: 基于 UDP 协议,用于解决域名和 IP 地址的映射问题。
+
+关于这些协议的详细介绍请看 [应用层常见协议总结(应用层)](./application-layer-protocol.md) 这篇文章。
+
+#### 传输层有哪些常见的协议?
+
+
+
+- **TCP(Transmission Control Protocol,传输控制协议 )**:提供 **面向连接** 的,**可靠** 的数据传输服务。
+- **UDP(User Datagram Protocol,用户数据协议)**:提供 **无连接** 的,**尽最大努力** 的数据传输服务(不保证数据传输的可靠性),简单高效。
+
+#### 网络层有哪些常见的协议?
+
+
+
+- **IP(Internet Protocol,网际协议)**:TCP/IP 协议中最重要的协议之一,属于网络层的协议,主要作用是定义数据包的格式、对数据包进行路由和寻址,以便它们可以跨网络传播并到达正确的目的地。目前 IP 协议主要分为两种,一种是过去的 IPv4,另一种是较新的 IPv6,目前这两种协议都在使用,但后者已经被提议来取代前者。
+- **ARP(Address Resolution Protocol,地址解析协议)**:ARP 协议解决的是网络层地址和链路层地址之间的转换问题。因为一个 IP 数据报在物理上传输的过程中,总是需要知道下一跳(物理上的下一个目的地)该去往何处,但 IP 地址属于逻辑地址,而 MAC 地址才是物理地址,ARP 协议解决了 IP 地址转 MAC 地址的一些问题。
+- **ICMP(Internet Control Message Protocol,互联网控制报文协议)**:一种用于传输网络状态和错误消息的协议,常用于网络诊断和故障排除。例如,Ping 工具就使用了 ICMP 协议来测试网络连通性。
+- **NAT(Network Address Translation,网络地址转换协议)**:NAT 协议的应用场景如同它的名称——网络地址转换,应用于内部网到外部网的地址转换过程中。具体地说,在一个小的子网(局域网,LAN)内,各主机使用的是同一个 LAN 下的 IP 地址,但在该 LAN 以外,在广域网(WAN)中,需要一个统一的 IP 地址来标识该 LAN 在整个 Internet 上的位置。
+- **OSPF(Open Shortest Path First,开放式最短路径优先)** ):一种内部网关协议(Interior Gateway Protocol,IGP),也是广泛使用的一种动态路由协议,基于链路状态算法,考虑了链路的带宽、延迟等因素来选择最佳路径。
+- **RIP(Routing Information Protocol,路由信息协议)**:一种内部网关协议(Interior Gateway Protocol,IGP),也是一种动态路由协议,基于距离向量算法,使用固定的跳数作为度量标准,选择跳数最少的路径作为最佳路径。
+- **BGP(Border Gateway Protocol,边界网关协议)**:一种用来在路由选择域之间交换网络层可达性信息(Network Layer Reachability Information,NLRI)的路由选择协议,具有高度的灵活性和可扩展性。
+
+## HTTP
+
+### 从输入 URL 到页面展示到底发生了什么?(非常重要)
+
+> 类似的问题:打开一个网页,整个过程会使用哪些协议?
+
+图解(图片来源:《图解 HTTP》):
+
+
+
+> 上图有一个错误,请注意,是 OSPF 不是 OPSF。 OSPF(Open Shortest Path First,ospf)开放最短路径优先协议, 是由 Internet 工程任务组开发的路由选择协议
+
+总体来说分为以下几个过程:
+
+1. DNS 解析
+2. TCP 连接
+3. 发送 HTTP 请求
+4. 服务器处理请求并返回 HTTP 报文
+5. 浏览器解析渲染页面
+6. 连接结束
+
+具体可以参考下面这两篇文章:
+
+- [从输入 URL 到页面加载发生了什么?](https://segmentfault.com/a/1190000006879700)
+- [浏览器从输入网址到页面展示的过程](https://cloud.tencent.com/developer/article/1879758)
+
+### HTTP 状态码有哪些?
+
+HTTP 状态码用于描述 HTTP 请求的结果,比如 2xx 就代表请求被成功处理。
+
+
+
+关于 HTTP 状态码更详细的总结,可以看我写的这篇文章:[HTTP 常见状态码总结(应用层)](./http-status-codes.md)。
+
+### HTTP Header 中常见的字段有哪些?
+
+| 请求头字段名 | 说明 | 示例 |
+| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------- |
+| Accept | 能够接受的回应内容类型(Content-Types)。 | Accept: text/plain |
+| Accept-Charset | 能够接受的字符集 | Accept-Charset: utf-8 |
+| Accept-Datetime | 能够接受的按照时间来表示的版本 | Accept-Datetime: Thu, 31 May 2007 20:35:00 GMT |
+| Accept-Encoding | 能够接受的编码方式列表。参考 HTTP 压缩。 | Accept-Encoding: gzip, deflate |
+| Accept-Language | 能够接受的回应内容的自然语言列表。 | Accept-Language: en-US |
+| Authorization | 用于超文本传输协议的认证的认证信息 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
+| Cache-Control | 用来指定在这次的请求/响应链中的所有缓存机制 都必须 遵守的指令 | Cache-Control: no-cache |
+| Connection | 该浏览器想要优先使用的连接类型 | Connection: keep-alive Connection: Upgrade |
+| Content-Length | 以 八位字节数组 (8 位的字节)表示的请求体的长度 | Content-Length: 348 |
+| Content-MD5 | 请求体的内容的二进制 MD5 散列值,以 Base64 编码的结果 | Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== |
+| Content-Type | 请求体的 多媒体类型 (用于 POST 和 PUT 请求中) | Content-Type: application/x-www-form-urlencoded |
+| Cookie | 之前由服务器通过 Set- Cookie (下文详述)发送的一个 超文本传输协议 Cookie | Cookie: \$Version=1; Skin=new; |
+| Date | 发送该消息的日期和时间(按照 RFC 7231 中定义的"超文本传输协议日期"格式来发送) | Date: Tue, 15 Nov 1994 08:12:31 GMT |
+| Expect | 表明客户端要求服务器做出特定的行为 | Expect: 100-continue |
+| From | 发起此请求的用户的邮件地址 | From: [user@example.com](mailto:user@example.com) |
+| Host | 服务器的域名(用于虚拟主机 ),以及服务器所监听的传输控制协议端口号。如果所请求的端口是对应的服务的标准端口,则端口号可被省略。 | Host: en.wikipedia.org:80 |
+| If-Match | 仅当客户端提供的实体与服务器上对应的实体相匹配时,才进行对应的操作。主要作用时,用作像 PUT 这样的方法中,仅当从用户上次更新某个资源以来,该资源未被修改的情况下,才更新该资源。 | If-Match: “737060cd8c284d8af7ad3082f209582d” |
+| If-Modified-Since | 允许在对应的内容未被修改的情况下返回 304 未修改( 304 Not Modified ) | If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT |
+| If-None-Match | 允许在对应的内容未被修改的情况下返回 304 未修改( 304 Not Modified ) | If-None-Match: “737060cd8c284d8af7ad3082f209582d” |
+| If-Range | 如果该实体未被修改过,则向我发送我所缺少的那一个或多个部分;否则,发送整个新的实体 | If-Range: “737060cd8c284d8af7ad3082f209582d” |
+| If-Unmodified-Since | 仅当该实体自某个特定时间已来未被修改的情况下,才发送回应。 | If-Unmodified-Since: Sat, 29 Oct 1994 19:43:31 GMT |
+| Max-Forwards | 限制该消息可被代理及网关转发的次数。 | Max-Forwards: 10 |
+| Origin | 发起一个针对 跨来源资源共享 的请求。 | Origin: [http://www.example-social-network.com](http://www.example-social-network.com/) |
+| Pragma | 与具体的实现相关,这些字段可能在请求/回应链中的任何时候产生多种效果。 | Pragma: no-cache |
+| Proxy-Authorization | 用来向代理进行认证的认证信息。 | Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
+| Range | 仅请求某个实体的一部分。字节偏移以 0 开始。参见字节服务。 | Range: bytes=500-999 |
+| Referer | 表示浏览器所访问的前一个页面,正是那个页面上的某个链接将浏览器带到了当前所请求的这个页面。 | Referer: [http://en.wikipedia.org/wiki/Main_Page](https://en.wikipedia.org/wiki/Main_Page) |
+| TE | 浏览器预期接受的传输编码方式:可使用回应协议头 Transfer-Encoding 字段中的值; | TE: trailers, deflate |
+| Upgrade | 要求服务器升级到另一个协议。 | Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 |
+| User-Agent | 浏览器的浏览器身份标识字符串 | User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0 |
+| Via | 向服务器告知,这个请求是由哪些代理发出的。 | Via: 1.0 fred, 1.1 example.com (Apache/1.1) |
+| Warning | 一个一般性的警告,告知,在实体内容体中可能存在错误。 | Warning: 199 Miscellaneous warning |
+
+### HTTP 和 HTTPS 有什么区别?(重要)
+
+
+
+- **端口号**:HTTP 默认是 80,HTTPS 默认是 443。
+- **URL 前缀**:HTTP 的 URL 前缀是 `http://`,HTTPS 的 URL 前缀是 `https://`。
+- **安全性和资源消耗**:HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。
+- **SEO(搜索引擎优化)**:搜索引擎通常会更青睐使用 HTTPS 协议的网站,因为 HTTPS 能够提供更高的安全性和用户隐私保护。使用 HTTPS 协议的网站在搜索结果中可能会被优先显示,从而对 SEO 产生影响。
+
+关于 HTTP 和 HTTPS 更详细的对比总结,可以看我写的这篇文章:[HTTP vs HTTPS(应用层)](./http-vs-https.md) 。
+
+### HTTP/1.0 和 HTTP/1.1 有什么区别?
+
+
+
+- **连接方式** : HTTP/1.0 为短连接,HTTP/1.1 支持长连接。
+- **状态响应码** : HTTP/1.1 中新加入了大量的状态码,光是错误响应状态码就新增了 24 种。比如说,`100 (Continue)`——在请求大资源前的预热请求,`206 (Partial Content)`——范围请求的标识码,`409 (Conflict)`——请求与当前资源的规定冲突,`410 (Gone)`——资源已被永久转移,而且没有任何已知的转发地址。
+- **缓存机制** : 在 HTTP/1.0 中主要使用 Header 里的 If-Modified-Since,Expires 来做为缓存判断的标准,HTTP/1.1 则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓存策略。
+- **带宽**:HTTP/1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP/1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
+- **Host 头(Host Header)处理** :HTTP/1.1 引入了 Host 头字段,允许在同一 IP 地址上托管多个域名,从而支持虚拟主机的功能。而 HTTP/1.0 没有 Host 头字段,无法实现虚拟主机。
+
+关于 HTTP/1.0 和 HTTP/1.1 更详细的对比总结,可以看我写的这篇文章:[HTTP/1.0 vs HTTP/1.1(应用层)](./http1.0-vs-http1.1.md) 。
+
+### HTTP/1.1 和 HTTP/2.0 有什么区别?
+
+
+
+- **IO 多路复用(Multiplexing)**:HTTP/2.0 在同一连接上可以同时传输多个请求和响应(可以看作是 HTTP/1.1 中长链接的升级版本)。HTTP/1.1 则使用串行方式,每个请求和响应都需要独立的连接。这使得 HTTP/2.0 在处理多个请求时更加高效,减少了网络延迟和提高了性能。
+- **二进制帧(Binary Frames)**:HTTP/2.0 使用二进制帧进行数据传输,而 HTTP/1.1 则使用文本格式的报文。二进制帧更加紧凑和高效,减少了传输的数据量和带宽消耗。
+- **头部压缩(Header Compression)**:HTTP/1.1 支持`Body`压缩,`Header`不支持压缩。HTTP/2.0 支持对`Header`压缩,减少了网络开销。
+- **服务器推送(Server Push)**:HTTP/2.0 支持服务器推送,可以在客户端请求一个资源时,将其他相关资源一并推送给客户端,从而减少了客户端的请求次数和延迟。而 HTTP/1.1 需要客户端自己发送请求来获取相关资源。
+
+### HTTP/2.0 和 HTTP/3.0 有什么区别?
+
+
+
+- **传输协议**:HTTP/2.0 是基于 TCP 协议实现的,HTTP/3.0 新增了 QUIC(Quick UDP Internet Connections) 协议来实现可靠的传输,提供与 TLS/SSL 相当的安全性,具有较低的连接和传输延迟。你可以将 QUIC 看作是 UDP 的升级版本,在其基础上新增了很多功能比如加密、重传等等。HTTP/3.0 之前名为 HTTP-over-QUIC,从这个名字中我们也可以发现,HTTP/3 最大的改造就是使用了 QUIC。
+- **连接建立**:HTTP/2.0 需要经过经典的 TCP 三次握手过程(由于安全的 HTTPS 连接建立还需要 TLS 握手,共需要大约 3 个 RTT)。由于 QUIC 协议的特性(TLS 1.3,TLS 1.3 除了支持 1 个 RTT 的握手,还支持 0 个 RTT 的握手)连接建立仅需 0-RTT 或者 1-RTT。这意味着 QUIC 在最佳情况下不需要任何的额外往返时间就可以建立新连接。
+- **队头阻塞**:HTTP/2.0 多请求复用一个 TCP 连接,一旦发生丢包,就会阻塞住所有的 HTTP 请求。由于 QUIC 协议的特性,HTTP/3.0 在一定程度上解决了队头阻塞(Head-of-Line blocking, 简写:HOL blocking)问题,一个连接建立多个不同的数据流,这些数据流之间独立互不影响,某个数据流发生丢包了,其数据流不受影响(本质上是多路复用+轮询)。
+- **错误恢复**:HTTP/3.0 具有更好的错误恢复机制,当出现丢包、延迟等网络问题时,可以更快地进行恢复和重传。而 HTTP/2.0 则需要依赖于 TCP 的错误恢复机制。
+- **安全性**:HTTP/2.0 和 HTTP/3.0 在安全性上都有较高的要求,支持加密通信,但在实现上有所不同。HTTP/2.0 使用 TLS 协议进行加密,而 HTTP/3.0 基于 QUIC 协议,包含了内置的加密和身份验证机制,可以提供更强的安全性。
+
+### HTTP 是不保存状态的协议, 如何保存用户状态?
+
+HTTP 是一种不保存状态,即无状态(stateless)协议。也就是说 HTTP 协议自身不对请求和响应之间的通信状态进行保存。那么我们保存用户状态呢?Session 机制的存在就是为了解决这个问题,Session 的主要作用就是通过服务端记录用户的状态。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了(一般情况下,服务器会在一定时间内保存这个 Session,过了时间限制,就会销毁这个 Session)。
+
+在服务端保存 Session 的方法很多,最常用的就是内存和数据库(比如是使用内存数据库 redis 保存)。既然 Session 存放在服务器端,那么我们如何实现 Session 跟踪呢?大部分情况下,我们都是通过在 Cookie 中附加一个 Session ID 来方式来跟踪。
+
+**Cookie 被禁用怎么办?**
+
+最常用的就是利用 URL 重写把 Session ID 直接附加在 URL 路径的后面。
+
+### URI 和 URL 的区别是什么?
+
+- URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源。
+- URL(Uniform Resource Locator) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。
+
+URI 的作用像身份证号一样,URL 的作用更像家庭住址一样。URL 是一种具体的 URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。
+
+### Cookie 和 Session 有什么区别?
+
+准确点来说,这个问题属于认证授权的范畴,你可以在 [认证授权基础概念详解](../../system-design/security/basis-of-authority-certification.md) 这篇文章中找到详细的答案。
+
+## PING
+
+### PING 命令的作用是什么?
+
+PING 命令是一种常用的网络诊断工具,经常用来测试网络中主机之间的连通性和网络延迟。
+
+这里简单举一个例子,我们来 PING 一下百度。
+
+```bash
+# 发送4个PING请求数据包到 www.baidu.com
+❯ ping -c 4 www.baidu.com
+
+PING www.a.shifen.com (14.119.104.189): 56 data bytes
+64 bytes from 14.119.104.189: icmp_seq=0 ttl=54 time=27.867 ms
+64 bytes from 14.119.104.189: icmp_seq=1 ttl=54 time=28.732 ms
+64 bytes from 14.119.104.189: icmp_seq=2 ttl=54 time=27.571 ms
+64 bytes from 14.119.104.189: icmp_seq=3 ttl=54 time=27.581 ms
+
+--- www.a.shifen.com ping statistics ---
+4 packets transmitted, 4 packets received, 0.0% packet loss
+round-trip min/avg/max/stddev = 27.571/27.938/28.732/0.474 ms
+```
+
+PING 命令的输出结果通常包括以下几部分信息:
+
+1. **ICMP Echo Request(请求报文)信息**:序列号、TTL(Time to Live)值。
+2. **目标主机的域名或 IP 地址**:输出结果的第一行。
+3. **往返时间(RTT,Round-Trip Time)**:从发送 ICMP Echo Request(请求报文)到接收到 ICMP Echo Reply(响应报文)的总时间,用来衡量网络连接的延迟。
+4. **统计结果(Statistics)**:包括发送的 ICMP 请求数据包数量、接收到的 ICMP 响应数据包数量、丢包率、往返时间(RTT)的最小、平均、最大和标准偏差值。
+
+如果 PING 对应的目标主机无法得到正确的响应,则表明这两个主机之间的连通性存在问题。如果往返时间(RTT)过高,则表明网络延迟过高。
+
+### PING 命令的工作原理是什么?
+
+PING 基于网络层的 **ICMP(Internet Control Message Protocol,互联网控制报文协议)**,其主要原理就是通过在网络上发送和接收 ICMP 报文实现的。
+
+ICMP 报文中包含了类型字段,用于标识 ICMP 报文类型。ICMP 报文的类型有很多种,但大致可以分为两类:
+
+- **查询报文类型**:向目标主机发送请求并期望得到响应。
+- **差错报文类型**:向源主机发送错误信息,用于报告网络中的错误情况。
+
+PING 用到的 ICMP Echo Request(类型为 8 ) 和 ICMP Echo Reply(类型为 0) 属于查询报文类型 。
+
+- PING 命令会向目标主机发送 ICMP Echo Request。
+- 如果两个主机的连通性正常,目标主机会返回一个对应的 ICMP Echo Reply。
+
+## DNS
+
+### DNS 的作用是什么?
+
+DNS(Domain Name System)域名管理系统,是当用户使用浏览器访问网址之后,使用的第一个重要协议。DNS 要解决的是**域名和 IP 地址的映射问题**。
+
+
+
+在实际使用中,有一种情况下,浏览器是可以不必动用 DNS 就可以获知域名和 IP 地址的映射的。浏览器在本地会维护一个`hosts`列表,一般来说浏览器要先查看要访问的域名是否在`hosts`列表中,如果有的话,直接提取对应的 IP 地址记录,就好了。如果本地`hosts`列表内没有域名-IP 对应记录的话,那么 DNS 就闪亮登场了。
+
+目前 DNS 的设计采用的是分布式、层次数据库结构,**DNS 是应用层协议,基于 UDP 协议之上,端口为 53** 。
+
+### DNS 服务器有哪些?
+
+DNS 服务器自底向上可以依次分为以下几个层级(所有 DNS 服务器都属于以下四个类别之一):
+
+- 根 DNS 服务器。根 DNS 服务器提供 TLD 服务器的 IP 地址。目前世界上只有 13 组根服务器,我国境内目前仍没有根服务器。
+- 顶级域 DNS 服务器(TLD 服务器)。顶级域是指域名的后缀,如`com`、`org`、`net`和`edu`等。国家也有自己的顶级域,如`uk`、`fr`和`ca`。TLD 服务器提供了权威 DNS 服务器的 IP 地址。
+- 权威 DNS 服务器。在因特网上具有公共可访问主机的每个组织机构必须提供公共可访问的 DNS 记录,这些记录将这些主机的名字映射为 IP 地址。
+- 本地 DNS 服务器。每个 ISP(互联网服务提供商)都有一个自己的本地 DNS 服务器。当主机发出 DNS 请求时,该请求被发往本地 DNS 服务器,它起着代理的作用,并将该请求转发到 DNS 层次结构中。严格说来,不属于 DNS 层级结构
+
+### DNS 解析的过程是什么样的?
+
+整个过程的步骤比较多,我单独写了一篇文章详细介绍:[DNS 域名系统详解(应用层)](./dns.md) 。
+
+## 参考
+
+- 《图解 HTTP》
+- 《计算机网络自顶向下方法》(第七版)
+- 详解 HTTP/2.0 及 HTTPS 协议:
+- HTTP 请求头字段大全| HTTP Request Headers:
+- HTTP1、HTTP2、HTTP3:
+- 如何看待 HTTP/3 ? - 车小胖的回答 - 知乎:
diff --git a/docs/cs-basics/network/other-network-questions2.md b/docs/cs-basics/network/other-network-questions2.md
new file mode 100644
index 0000000000000000000000000000000000000000..ef6acc1ce484ba4ff161862a748ca3e6959c7033
--- /dev/null
+++ b/docs/cs-basics/network/other-network-questions2.md
@@ -0,0 +1,188 @@
+---
+title: 计算机网络常见面试题总结(下)
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+下篇主要是传输层和网络层相关的内容。
+
+## TCP 与 UDP
+
+### TCP 与 UDP 的区别(重要)
+
+1. **是否面向连接**:UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。
+2. **是否是可靠传输**:远地主机在收到 UDP 报文后,不需要给出任何确认,并且不保证数据不丢失,不保证是否顺序到达。TCP 提供可靠的传输服务,TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
+3. **是否有状态**:这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 ,TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情了(**这很渣男!**)。
+4. **传输效率**:由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。
+5. **传输形式**:TCP 是面向字节流的,UDP 是面向报文的。
+6. **首部开销**:TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。
+7. **是否提供广播或多播服务**:TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多;
+8. ......
+
+我把上面总结的内容通过表格形式展示出来了!确定不点个赞嘛?
+
+| | TCP | UDP |
+| ---------------------- | -------------- | ---------- |
+| 是否面向连接 | 是 | 否 |
+| 是否可靠 | 是 | 否 |
+| 是否有状态 | 是 | 否 |
+| 传输效率 | 较慢 | 较快 |
+| 传输形式 | 字节流 | 数据报文段 |
+| 首部开销 | 20 ~ 60 bytes | 8 bytes |
+| 是否提供广播或多播服务 | 否 | 是 |
+
+### 什么时候选择 TCP,什么时候选 UDP?
+
+- **UDP 一般用于即时通信**,比如:语音、 视频、直播等等。这些场景对传输数据的准确性要求不是特别高,比如你看视频即使少个一两帧,实际给人的感觉区别也不大。
+- **TCP 用于对传输准确性要求特别高的场景**,比如文件传输、发送和接收邮件、远程登录等等。
+
+### HTTP 基于 TCP 还是 UDP?
+
+~~**HTTP 协议是基于 TCP 协议的**,所以发送 HTTP 请求之前首先要建立 TCP 连接也就是要经历 3 次握手。~~
+
+🐛 修正(参见 [issue#1915](https://github.com/Snailclimb/JavaGuide/issues/1915)):
+
+HTTP/3.0 之前是基于 TCP 协议的,而 HTTP/3.0 将弃用 TCP,改用 **基于 UDP 的 QUIC 协议** 。
+
+此变化解决了 HTTP/2 中存在的队头阻塞问题。队头阻塞是指在 HTTP/2.0 中,多个 HTTP 请求和响应共享一个 TCP 连接,如果其中一个请求或响应因为网络拥塞或丢包而被阻塞,那么后续的请求或响应也无法发送,导致整个连接的效率降低。这是由于 HTTP/2.0 在单个 TCP 连接上使用了多路复用,受到 TCP 拥塞控制的影响,少量的丢包就可能导致整个 TCP 连接上的所有流被阻塞。HTTP/3.0 在一定程度上解决了队头阻塞问题,一个连接建立多个不同的数据流,这些数据流之间独立互不影响,某个数据流发生丢包了,其数据流不受影响(本质上是多路复用+轮询)。
+
+除了解决队头阻塞问题,HTTP/3.0 还可以减少握手过程的延迟。在 HTTP/2.0 中,如果要建立一个安全的 HTTPS 连接,需要经过 TCP 三次握手和 TLS 握手:
+
+1. TCP 三次握手:客户端和服务器交换 SYN 和 ACK 包,建立一个 TCP 连接。这个过程需要 1.5 个 RTT(round-trip time),即一个数据包从发送到接收的时间。
+2. TLS 握手:客户端和服务器交换密钥和证书,建立一个 TLS 加密层。这个过程需要至少 1 个 RTT(TLS 1.3)或者 2 个 RTT(TLS 1.2)。
+
+所以,HTTP/2.0 的连接建立就至少需要 2.5 个 RTT(TLS 1.3)或者 3.5 个 RTT(TLS 1.2)。而在 HTTP/3.0 中,使用的 QUIC 协议(TLS 1.3,TLS 1.3 除了支持 1 个 RTT 的握手,还支持 0 个 RTT 的握手)连接建立仅需 0-RTT 或者 1-RTT。这意味着 QUIC 在最佳情况下不需要任何的额外往返时间就可以建立新连接。
+
+相关证明可以参考下面这两个链接:
+
+- https://zh.wikipedia.org/zh/HTTP/3
+- https://datatracker.ietf.org/doc/rfc9114/
+
+### 使用 TCP 的协议有哪些?使用 UDP 的协议有哪些?
+
+**运行于 TCP 协议之上的协议**:
+
+1. **HTTP 协议**:超文本传输协议(HTTP,HyperText Transfer Protocol)是一种用于传输超文本和多媒体内容的协议,主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。
+2. **HTTPS 协议**:更安全的超文本传输协议(HTTPS,Hypertext Transfer Protocol Secure),身披 SSL 外衣的 HTTP 协议
+3. **FTP 协议**:文件传输协议 FTP(File Transfer Protocol)是一种用于在计算机之间传输文件的协议,可以屏蔽操作系统和文件存储方式。注意 ⚠️:FTP 是一种不安全的协议,因为它在传输过程中不会对数据进行加密。建议在传输敏感数据时使用更安全的协议,如 SFTP。
+4. **SMTP 协议**:简单邮件传输协议(SMTP,Simple Mail Transfer Protocol)的缩写,是一种用于发送电子邮件的协议。注意 ⚠️:SMTP 协议只负责邮件的发送,而不是接收。要从邮件服务器接收邮件,需要使用 POP3 或 IMAP 协议。
+5. **POP3/IMAP 协议**:两者都是负责邮件接收的协议。IMAP 协议是比 POP3 更新的协议,它在功能和性能上都更加强大。IMAP 支持邮件搜索、标记、分类、归档等高级功能,而且可以在多个设备之间同步邮件状态。几乎所有现代电子邮件客户端和服务器都支持 IMAP。
+6. **Telnet 协议**:用于通过一个终端登陆到其他服务器。Telnet 协议的最大缺点之一是所有数据(包括用户名和密码)均以明文形式发送,这有潜在的安全风险。这就是为什么如今很少使用 Telnet,而是使用一种称为 SSH 的非常安全的网络传输协议的主要原因。
+7. **SSH 协议** : SSH( Secure Shell)是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。SSH 建立在可靠的传输协议 TCP 之上。
+8. ......
+
+**运行于 UDP 协议之上的协议**:
+
+1. **DHCP 协议**:动态主机配置协议,动态配置 IP 地址
+2. **DNS**:**域名系统(DNS,Domain Name System)将人类可读的域名 (例如,www.baidu.com) 转换为机器可读的 IP 地址 (例如,220.181.38.148)。** 我们可以将其理解为专为互联网设计的电话薄。实际上 DNS 同时支持 UDP 和 TCP 协议。
+3. ......
+
+### TCP 三次握手和四次挥手(非常重要)
+
+**相关面试题**:
+
+- 为什么要三次握手?
+- 第 2 次握手传回了 ACK,为什么还要传回 SYN?
+- 为什么要四次挥手?
+- 为什么不能把服务器发送的 ACK 和 FIN 合并起来,变成三次挥手?
+- 如果第二次挥手时服务器的 ACK 没有送达客户端,会怎样?
+- 为什么第四次挥手客户端需要等待 2\*MSL(报文段最长寿命)时间后才进入 CLOSED 状态?
+
+**参考答案**:[TCP 三次握手和四次挥手(传输层)](./tcp-connection-and-disconnection.md) 。
+
+### TCP 如何保证传输的可靠性?(重要)
+
+[TCP 传输可靠性保障(传输层)](./tcp-reliability-guarantee.md)
+
+## IP
+
+### IP 协议的作用是什么?
+
+**IP(Internet Protocol,网际协议)** 是 TCP/IP 协议中最重要的协议之一,属于网络层的协议,主要作用是定义数据包的格式、对数据包进行路由和寻址,以便它们可以跨网络传播并到达正确的目的地。
+
+目前 IP 协议主要分为两种,一种是过去的 IPv4,另一种是较新的 IPv6,目前这两种协议都在使用,但后者已经被提议来取代前者。
+
+### 什么是 IP 地址?IP 寻址如何工作?
+
+每个连入互联网的设备或域(如计算机、服务器、路由器等)都被分配一个 **IP 地址(Internet Protocol address)**,作为唯一标识符。每个 IP 地址都是一个字符序列,如 192.168.1.1(IPv4)、2001:0db8:85a3:0000:0000:8a2e:0370:7334(IPv6) 。
+
+当网络设备发送 IP 数据包时,数据包中包含了 **源 IP 地址** 和 **目的 IP 地址** 。源 IP 地址用于标识数据包的发送方设备或域,而目的 IP 地址则用于标识数据包的接收方设备或域。这类似于一封邮件中同时包含了目的地地址和回邮地址。
+
+网络设备根据目的 IP 地址来判断数据包的目的地,并将数据包转发到正确的目的地网络或子网络,从而实现了设备间的通信。
+
+这种基于 IP 地址的寻址方式是互联网通信的基础,它允许数据包在不同的网络之间传递,从而实现了全球范围内的网络互联互通。IP 地址的唯一性和全局性保证了网络中的每个设备都可以通过其独特的 IP 地址进行标识和寻址。
+
+
+
+### 什么是 IP 地址过滤?
+
+**IP 地址过滤(IP Address Filtering)** 简单来说就是限制或阻止特定 IP 地址或 IP 地址范围的访问。例如,你有一个图片服务突然被某一个 IP 地址攻击,那我们就可以禁止这个 IP 地址访问图片服务。
+
+IP 地址过滤是一种简单的网络安全措施,实际应用中一般会结合其他网络安全措施,如认证、授权、加密等一起使用。单独使用 IP 地址过滤并不能完全保证网络的安全。
+
+### IPv4 和 IPv6 有什么区别?
+
+**IPv4(Internet Protocol version 4)** 是目前广泛使用的 IP 地址版本,其格式是四组由点分隔的数字,例如:123.89.46.72。IPv4 使用 32 位地址作为其 Internet 地址,这意味着共有约 42 亿( 2^32)个可用 IP 地址。
+
+
+
+这么少当然不够用啦!为了解决 IP 地址耗尽的问题,最根本的办法是采用具有更大地址空间的新版本 IP 协议 - **IPv6(Internet Protocol version 6)**。IPv6 地址使用更复杂的格式,该格式使用由单或双冒号分隔的一组数字和字母,例如:2001:0db8:85a3:0000:0000:8a2e:0370:7334 。IPv6 使用 128 位互联网地址,这意味着越有 2^128(3 开头的 39 位数字,恐怖如斯) 个可用 IP 地址。
+
+
+
+除了更大的地址空间之外,IPv6 的优势还包括:
+
+- **无状态地址自动配置(Stateless Address Autoconfiguration,简称 SLAAC)**:主机可以直接通过根据接口标识和网络前缀生成全局唯一的 IPv6 地址,而无需依赖 DHCP(Dynamic Host Configuration Protocol)服务器,简化了网络配置和管理。
+- **NAT(Network Address Translation,网络地址转换) 成为可选项**:IPv6 地址资源充足,可以给全球每个设备一个独立的地址。
+- **对标头结构进行了改进**:IPv6 标头结构相较于 IPv4 更加简化和高效,减少了处理开销,提高了网络性能。
+- **可选的扩展头**:允许在 IPv6 标头中添加不同的扩展头(Extension Headers),用于实现不同类型的功能和选项。
+- **ICMPv6(Internet Control Message Protocol for IPv6)**:IPv6 中的 ICMPv6 相较于 IPv4 中的 ICMP 有了一些改进,如邻居发现、路径 MTU 发现等功能的改进,从而提升了网络的可靠性和性能。
+- ......
+
+### NAT 的作用是什么?
+
+**NAT(Network Address Translation,网络地址转换)** 主要用于在不同网络之间转换 IP 地址。它允许将私有 IP 地址(如在局域网中使用的 IP 地址)映射为公有 IP 地址(在互联网中使用的 IP 地址)或者反向映射,从而实现局域网内的多个设备通过单一公有 IP 地址访问互联网。
+
+NAT 不光可以缓解 IPv4 地址资源短缺的问题,还可以隐藏内部网络的实际拓扑结构,使得外部网络无法直接访问内部网络中的设备,从而提高了内部网络的安全性。
+
+
+
+相关阅读:[NAT 协议详解(网络层)](./nat.md)。
+
+## ARP
+
+### 什么是 Mac 地址?
+
+MAC 地址的全称是 **媒体访问控制地址(Media Access Control Address)**。如果说,互联网中每一个资源都由 IP 地址唯一标识(IP 协议内容),那么一切网络设备都由 MAC 地址唯一标识。
+
+
+
+可以理解为,MAC 地址是一个网络设备真正的身份证号,IP 地址只是一种不重复的定位方式(比如说住在某省某市某街道的张三,这种逻辑定位是 IP 地址,他的身份证号才是他的 MAC 地址),也可以理解为 MAC 地址是身份证号,IP 地址是邮政地址。MAC 地址也有一些别称,如 LAN 地址、物理地址、以太网地址等。
+
+> 还有一点要知道的是,不仅仅是网络资源才有 IP 地址,网络设备也有 IP 地址,比如路由器。但从结构上说,路由器等网络设备的作用是组成一个网络,而且通常是内网,所以它们使用的 IP 地址通常是内网 IP,内网的设备在与内网以外的设备进行通信时,需要用到 NAT 协议。
+
+MAC 地址的长度为 6 字节(48 比特),地址空间大小有 280 万亿之多($2^{48}$),MAC 地址由 IEEE 统一管理与分配,理论上,一个网络设备中的网卡上的 MAC 地址是永久的。不同的网卡生产商从 IEEE 那里购买自己的 MAC 地址空间(MAC 的前 24 比特),也就是前 24 比特由 IEEE 统一管理,保证不会重复。而后 24 比特,由各家生产商自己管理,同样保证生产的两块网卡的 MAC 地址不会重复。
+
+MAC 地址具有可携带性、永久性,身份证号永久地标识一个人的身份,不论他到哪里都不会改变。而 IP 地址不具有这些性质,当一台设备更换了网络,它的 IP 地址也就可能发生改变,也就是它在互联网中的定位发生了变化。
+
+最后,记住,MAC 地址有一个特殊地址:FF-FF-FF-FF-FF-FF(全 1 地址),该地址表示广播地址。
+
+### ARP 协议解决了什么问题地位如何?
+
+ARP 协议,全称 **地址解析协议(Address Resolution Protocol)**,它解决的是网络层地址和链路层地址之间的转换问题。因为一个 IP 数据报在物理上传输的过程中,总是需要知道下一跳(物理上的下一个目的地)该去往何处,但 IP 地址属于逻辑地址,而 MAC 地址才是物理地址,ARP 协议解决了 IP 地址转 MAC 地址的一些问题。
+
+### ARP 协议的工作原理?
+
+[ARP 协议详解(网络层)](./arp.md)
+
+## 复习建议
+
+非常推荐大家看一下 《图解 HTTP》 这本书,这本书页数不多,但是内容很是充实,不管是用来系统的掌握网络方面的一些知识还是说纯粹为了应付面试都有很大帮助。下面的一些文章只是参考。大二学习这门课程的时候,我们使用的教材是 《计算机网络第七版》(谢希仁编著),不推荐大家看这本教材,书非常厚而且知识偏理论,不确定大家能不能心平气和的读完。
+
+## 参考
+
+- 《图解 HTTP》
+- 《计算机网络自顶向下方法》(第七版)
+- 什么是 Internet 协议(IP)?:
+- What Is NAT and What Are the Benefits of NAT Firewalls?:
diff --git a/docs/cs-basics/network/tcp-connection-and-disconnection.md b/docs/cs-basics/network/tcp-connection-and-disconnection.md
new file mode 100644
index 0000000000000000000000000000000000000000..6b3efac77a1557ff223b8fb231a24c3eb53b4813
--- /dev/null
+++ b/docs/cs-basics/network/tcp-connection-and-disconnection.md
@@ -0,0 +1,84 @@
+---
+title: TCP 三次握手和四次挥手(传输层)
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+为了准确无误地把数据送达目标处,TCP 协议采用了三次握手策略。
+
+## 建立连接-TCP 三次握手
+
+
+
+建立一个 TCP 连接需要“三次握手”,缺一不可:
+
+- **一次握手**:客户端发送带有 SYN(SEQ=x) 标志的数据包 -> 服务端,然后客户端进入 **SYN_SEND** 状态,等待服务器的确认;
+- **二次握手**:服务端发送带有 SYN+ACK(SEQ=y,ACK=x+1) 标志的数据包 –> 客户端,然后服务端进入 **SYN_RECV** 状态
+- **三次握手**:客户端发送带有 ACK(ACK=y+1) 标志的数据包 –> 服务端,然后客户端和服务器端都进入**ESTABLISHED** 状态,完成 TCP 三次握手。
+
+当建立了 3 次握手之后,客户端和服务端就可以传输数据啦!
+
+### 为什么要三次握手?
+
+三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。
+
+1. **第一次握手**:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
+2. **第二次握手**:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
+3. **第三次握手**:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
+
+三次握手就能确认双方收发功能都正常,缺一不可。
+
+更详细的解答可以看这个:[TCP 为什么是三次握手,而不是两次或四次? - 车小胖的回答 - 知乎](https://www.zhihu.com/question/24853633/answer/115173386) 。
+
+### 第 2 次握手传回了 ACK,为什么还要传回 SYN?
+
+服务端传回发送端所发送的 ACK 是为了告诉客户端:“我接收到的信息确实就是你所发送的信号了”,这表明从客户端到服务端的通信是正常的。回传 SYN 则是为了建立并确认从服务端到客户端的通信。
+
+> SYN 同步序列编号(Synchronize Sequence Numbers) 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。
+
+## 断开连接-TCP 四次挥手
+
+
+
+断开一个 TCP 连接则需要“四次挥手”,缺一不可:
+
+1. **第一次挥手**:客户端发送一个 FIN(SEQ=x) 标志的数据包->服务端,用来关闭客户端到服务器的数据传送。然后,客户端进入 **FIN-WAIT-1** 状态。
+2. **第二次挥手**:服务器收到这个 FIN(SEQ=X) 标志的数据包,它发送一个 ACK (ACK=x+1)标志的数据包->客户端 。然后,此时服务端进入 **CLOSE-WAIT** 状态,客户端进入 **FIN-WAIT-2** 状态。
+3. **第三次挥手**:服务端关闭与客户端的连接并发送一个 FIN (SEQ=y)标志的数据包->客户端请求关闭连接,然后,服务端进入 **LAST-ACK** 状态。
+4. **第四次挥手**:客户端发送 ACK (ACK=y+1)标志的数据包->服务端并且进入**TIME-WAIT**状态,服务端在收到 ACK (ACK=y+1)标志的数据包后进入 CLOSE 状态。此时,如果客户端等待 **2MSL** 后依然没有收到回复,就证明服务端已正常关闭,随后,客户端也可以关闭连接了。
+
+**只要四次挥手没有结束,客户端和服务端就可以继续传输数据!**
+
+### 为什么要四次挥手?
+
+TCP 是全双工通信,可以双向传输数据。任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。
+
+举个例子:A 和 B 打电话,通话即将结束后。
+
+1. **第一次挥手**:A 说“我没啥要说的了”
+2. **第二次挥手**:B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话
+3. **第三次挥手**:于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”
+4. **第四次挥手**:A 回答“知道了”,这样通话才算结束。
+
+### 为什么不能把服务器发送的 ACK 和 FIN 合并起来,变成三次挥手?
+
+因为服务器收到客户端断开连接的请求时,可能还有一些数据没有发完,这时先回复 ACK,表示接收到了断开连接的请求。等到数据发完之后再发 FIN,断开服务器到客户端的数据传送。
+
+### 如果第二次挥手时服务器的 ACK 没有送达客户端,会怎样?
+
+客户端没有收到 ACK 确认,会重新发送 FIN 请求。
+
+### 为什么第四次挥手客户端需要等待 2\*MSL(报文段最长寿命)时间后才进入 CLOSED 状态?
+
+第四次挥手时,客户端发送给服务器的 ACK 有可能丢失,如果服务端因为某些原因而没有收到 ACK 的话,服务端就会重发 FIN,如果客户端在 2\*MSL 的时间内收到了 FIN,就会重新发送 ACK 并再次等待 2MSL,防止 Server 没有收到 ACK 而不断重发 FIN。
+
+> **MSL(Maximum Segment Lifetime)** : 一个片段在网络中最大的存活时间,2MSL 就是一个发送和一个回复所需的最大时间。如果直到 2MSL,Client 都没有再次收到 FIN,那么 Client 推断 ACK 已经被成功接收,则结束 TCP 连接。
+
+## 参考
+
+- 《计算机网络(第 7 版)》
+
+- 《图解 HTTP》
+
+- TCP and UDP Tutorial:https://www.9tut.com/tcp-and-udp-tutorial
diff --git a/docs/cs-basics/network/tcp-reliability-guarantee.md b/docs/cs-basics/network/tcp-reliability-guarantee.md
new file mode 100644
index 0000000000000000000000000000000000000000..c2f081f2327cda39d539e47b784e0d981a3fa34f
--- /dev/null
+++ b/docs/cs-basics/network/tcp-reliability-guarantee.md
@@ -0,0 +1,116 @@
+---
+title: TCP 传输可靠性保障(传输层)
+category: 计算机基础
+tag:
+ - 计算机网络
+---
+
+## TCP 如何保证传输的可靠性?
+
+1. **基于数据块传输**:应用数据被分割成 TCP 认为最适合发送的数据块,再传输给网络层,数据块被称为报文段或段。
+2. **对失序数据包重新排序以及去重**:TCP 为了保证不发生丢包,就给每个包一个序列号,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据就可以实现数据包去重。
+3. **校验和** : TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
+4. **超时重传** : 当发送方发送数据之后,它启动一个定时器,等待目的端确认收到这个报文段。接收端实体对已成功收到的包发回一个相应的确认信息(ACK)。如果发送端实体在合理的往返时延(RTT)内未收到确认消息,那么对应的数据包就被假设为[已丢失](https://zh.wikipedia.org/wiki/丢包)并进行重传。
+5. **流量控制** : TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议(TCP 利用滑动窗口实现流量控制)。
+6. **拥塞控制** : 当网络拥塞时,减少数据的发送。
+
+## TCP 如何实现流量控制?
+
+**TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。** 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。
+
+**为什么需要流量控制?** 这是因为双方在通信的时候,发送方的速率与接收方的速率是不一定相等,如果发送方的发送速率太快,会导致接收方处理不过来。如果接收方处理不过来的话,就只能把处理不过来的数据存在 **接收缓冲区(Receiving Buffers)** 里(失序的数据包也会被存放在缓存区里)。如果缓存区满了发送方还在狂发数据的话,接收方只能把收到的数据包丢掉。出现丢包问题的同时又疯狂浪费着珍贵的网络资源。因此,我们需要控制发送方的发送速率,让接收方与发送方处于一种动态平衡才好。
+
+这里需要注意的是(常见误区):
+
+- 发送端不等同于客户端
+- 接收端不等同于服务端
+
+TCP 为全双工(Full-Duplex, FDX)通信,双方可以进行双向通信,客户端和服务端既可能是发送端又可能是服务端。因此,两端各有一个发送缓冲区与接收缓冲区,两端都各自维护一个发送窗口和一个接收窗口。接收窗口大小取决于应用、系统、硬件的限制(TCP 传输速率不能大于应用的数据处理速率)。通信双方的发送窗口和接收窗口的要求相同
+
+**TCP 发送窗口可以划分成四个部分**:
+
+1. 已经发送并且确认的 TCP 段(已经发送并确认);
+2. 已经发送但是没有确认的 TCP 段(已经发送未确认);
+3. 未发送但是接收方准备接收的 TCP 段(可以发送);
+4. 未发送并且接收方也并未准备接受的 TCP 段(不可发送)。
+
+**TCP 发送窗口结构图示**:
+
+
+
+- **SND.WND**:发送窗口。
+- **SND.UNA**:Send Unacknowledged 指针,指向发送窗口的第一个字节。
+- **SND.NXT**:Send Next 指针,指向可用窗口的第一个字节。
+
+**可用窗口大小** = `SND.UNA + SND.WND - SND.NXT` 。
+
+**TCP 接收窗口可以划分成三个部分**:
+
+1. 已经接收并且已经确认的 TCP 段(已经接收并确认);
+2. 等待接收且允许发送方发送 TCP 段(可以接收未确认);
+3. 不可接收且不允许发送方发送 TCP 段(不可接收)。
+
+**TCP 接收窗口结构图示**:
+
+
+
+**接收窗口的大小是根据接收端处理数据的速度动态调整的。** 如果接收端读取数据快,接收窗口可能会扩大。 否则,它可能会缩小。
+
+另外,这里的滑动窗口大小只是为了演示使用,实际窗口大小通常会远远大于这个值。
+
+## TCP 的拥塞控制是怎么实现的?
+
+在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
+
+
+
+为了进行拥塞控制,TCP 发送方要维持一个 **拥塞窗口(cwnd)** 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。
+
+TCP 的拥塞控制采用了四种算法,即 **慢开始**、 **拥塞避免**、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。
+
+- **慢开始:** 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd 初始值为 1,每经过一个传播轮次,cwnd 加倍。
+- **拥塞避免:** 拥塞避免算法的思路是让拥塞窗口 cwnd 缓慢增大,即每经过一个往返时间 RTT 就把发送方的 cwnd 加 1.
+- **快重传与快恢复:** 在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。 当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。
+
+## ARQ 协议了解吗?
+
+**自动重传请求**(Automatic Repeat-reQuest,ARQ)是 OSI 模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认信息(Acknowledgements,就是我们常说的 ACK),它通常会重新发送,直到收到确认或者重试超过一定的次数。
+
+ARQ 包括停止等待 ARQ 协议和连续 ARQ 协议。
+
+### 停止等待 ARQ 协议
+
+停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复 ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组;
+
+在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认。
+
+**1) 无差错情况:**
+
+发送方发送分组,接收方在规定时间内收到,并且回复确认.发送方再次发送。
+
+**2) 出现差错情况(超时重传):**
+
+停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 **自动重传请求 ARQ** 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。
+
+**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。
+
+### 连续 ARQ 协议
+
+连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。
+
+**优点:** 信道利用率高,容易实现,即使确认丢失,也不必重传。
+
+**缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5 条 消息,中间第三条丢失(3 号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。
+
+## Reference
+
+1. 《计算机网络(第 7 版)》
+2. 《图解 HTTP》
+3. [https://www.9tut.com/tcp-and-udp-tutorial](https://www.9tut.com/tcp-and-udp-tutorial)
+4. [https://github.com/wolverinn/Waking-Up/blob/master/Computer%20Network.md](https://github.com/wolverinn/Waking-Up/blob/master/Computer%20Network.md)
+5. TCP Flow Control—[https://www.brianstorti.com/tcp-flow-control/](https://www.brianstorti.com/tcp-flow-control/)
+6. TCP 流量控制(Flow Control):https://notfalse.net/24/tcp-flow-control
+7. TCP 之滑动窗口原理 : https://cloud.tencent.com/developer/article/1857363
diff --git "a/docs/cs-basics/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md" "b/docs/cs-basics/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md"
deleted file mode 100644
index ef63192cb18310e36490f27710c5579d3c7903f5..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230.md"
+++ /dev/null
@@ -1,309 +0,0 @@
----
-title: 计算机网络常见面试题
-category: 计算机基础
-tag:
- - 计算机网络
----
-
-## 一 OSI 与 TCP/IP 各层的结构与功能, 都有哪些协议?
-
-学习计算机网络时我们一般采用折中的办法,也就是中和 OSI 和 TCP/IP 的优点,采用一种只有五层协议的体系结构,这样既简洁又能将概念阐述清楚。
-
-
-
-结合互联网的情况,自上而下地,非常简要的介绍一下各层的作用。
-
-### 1.1 应用层
-
-**应用层(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 等。
-
-**HTTP 协议**
-
-> 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW(万维网) 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。(百度百科)
-
-### 1.2 运输层
-
-**运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务**。应用进程利用该服务传送应用层报文。“通用的”是指并不针对某一个特定的网络应用,而是多种应用可以使用同一个运输层服务。由于一台主机可同时运行多个线程,因此运输层有复用和分用的功能。所谓复用就是指多个应用层进程可同时使用下面运输层的服务,分用和复用相反,是运输层把收到的信息分别交付上面应用层中的相应进程。
-
-**运输层主要使用以下两种协议:**
-
-1. **传输控制协议 TCP**(Transmission Control Protocol)--提供**面向连接**的,**可靠的**数据传输服务。
-2. **用户数据协议 UDP**(User Datagram Protocol)--提供**无连接**的,尽最大努力的数据传输服务(**不保证数据传输的可靠性**)。
-
-**TCP 与 UDP 的对比见问题三。**
-
-### 1.3 网络层
-
-**在计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。** 在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 **IP 协议**,因此分组也叫 **IP 数据报** ,简称 **数据报**。
-
-这里要注意:**不要把运输层的“用户数据报 UDP ”和网络层的“ IP 数据报”弄混**。另外,无论是哪一层的数据单元,都可笼统地用“分组”来表示。
-
-这里强调指出,网络层中的“网络”二字已经不是我们通常谈到的具体网络,而是指计算机网络体系结构模型中第三层的名称.
-
-互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Internet Protocol)和许多路由选择协议,因此互联网的网络层也叫做**网际层**或**IP 层**。
-
-### 1.4 数据链路层
-
-**数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。** 在两个相邻节点之间传送数据时,**数据链路层将网络层交下来的 IP 数据报组装成帧**,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。
-
-在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。这样,数据链路层在收到一个帧后,就可从中提出数据部分,上交给网络层。
-控制信息还使接收端能够检测到所收到的帧中有无差错。如果发现差错,数据链路层就简单地丢弃这个出了差错的帧,以避免继续在网络中传送下去白白浪费网络资源。如果需要改正数据在链路层传输时出现差错(这就是说,数据链路层不仅要检错,而且还要纠错),那么就要采用可靠性传输协议来纠正出现的差错。这种方法会使链路层的协议复杂些。
-
-### 1.5 物理层
-
-在物理层上所传送的数据单位是比特。
-
-**物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异,** 使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。
-
-在互联网使用的各种协议中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的 TCP/IP 并不一定单指 TCP 和 IP 这两个具体的协议,而往往表示互联网所使用的整个 TCP/IP 协议族。
-
-### 1.6 总结一下
-
-上面我们对计算机网络的五层体系结构有了初步的了解,下面附送一张七层体系结构图总结一下(图片来源于网络)。
-
-
-
-## 二 TCP 三次握手和四次挥手(面试常客)
-
-为了准确无误地把数据送达目标处,TCP 协议采用了三次握手策略。
-
-### 2.1 TCP 三次握手漫画图解
-
-如下图所示,下面的两个机器人通过 3 次握手确定了对方能正确接收和发送消息(图片来源:《图解 HTTP》)。
-
-
-
-**简单示意图:**
-
-
-
-* 客户端–发送带有 SYN 标志的数据包–一次握手–服务端
-* 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
-* 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端
-
-**详细示意图(图片来源不详)**
-
-
-
-### 2.2 为什么要三次握手
-
-**三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。**
-
-第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
-
-第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
-
-第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
-
-所以三次握手就能确认双发收发功能都正常,缺一不可。
-
-### 2.3 第 2 次握手传回了 ACK,为什么还要传回 SYN?
-
-接收端传回发送端所发送的 ACK 是为了告诉客户端,我接收到的信息确实就是你所发送的信号了,这表明从客户端到服务端的通信是正常的。而回传 SYN 则是为了建立并确认从服务端到客户端的通信。”
-
-> SYN 同步序列编号(Synchronize Sequence Numbers) 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。
-
-### 2.5 为什么要四次挥手
-
-
-
-断开一个 TCP 连接则需要“四次挥手”:
-
-* 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
-* 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加 1 。和 SYN 一样,一个 FIN 将占用一个序号
-* 服务器-关闭与客户端的连接,发送一个 FIN 给客户端
-* 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加 1
-
-任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。
-
-举个例子: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 协议的区别
-
-
-
-UDP 在传送数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 却是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等
-
-TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的传输服务(TCP 的可靠体现在 TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。
-
-## 四 TCP 协议如何保证可靠传输
-
-1. 应用数据被分割成 TCP 认为最适合发送的数据块。
-2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
-3. **校验和:** TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
-4. TCP 的接收端会丢弃重复的数据。
-5. **流量控制:** TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)
-6. **拥塞控制:** 当网络拥塞时,减少数据的发送。
-7. **ARQ 协议:** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
-8. **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
-
-### 4.1 ARQ 协议
-
-**自动重传请求**(Automatic Repeat-reQuest,ARQ)是 OSI 模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ 包括停止等待 ARQ 协议和连续 ARQ 协议。
-
-#### 停止等待 ARQ 协议
-
-停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复 ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组。
-
-在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认。
-
-**优缺点:**
-
-* **优点:** 简单
-* **缺点:** 信道利用率低,等待时间长
-
-**1) 无差错情况:**
-
-发送方发送分组, 接收方在规定时间内收到, 并且回复确认. 发送方再次发送。
-
-**2) 出现差错情况(超时重传):**
-
-停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 **自动重传请求 ARQ** 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。**连续 ARQ 协议** 可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。
-
-**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。
-
-#### 连续 ARQ 协议
-
-连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。
-
-**优缺点:**
-
-* **优点:** 信道利用率高,容易实现,即使确认丢失,也不必重传。
-* **缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5 条 消息,中间第三条丢失(3 号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。
-
-### 4.2 滑动窗口和流量控制
-
-**TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。** 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。
-
-### 4.3 拥塞控制
-
-在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
-
-为了进行拥塞控制,TCP 发送方要维持一个 **拥塞窗口(cwnd)** 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。
-
-TCP 的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。
-
-* **慢开始:** 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd 初始值为 1,每经过一个传播轮次,cwnd 加倍。
-* **拥塞避免:** 拥塞避免算法的思路是让拥塞窗口 cwnd 缓慢增大,即每经过一个往返时间 RTT 就把发送放的 cwnd 加 1.
-* **快重传与快恢复:**
- 在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。 当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。
-
-## 五 在浏览器中输入 url 地址 ->> 显示主页的过程(面试常客)
-
-百度好像最喜欢问这个问题。
-
-> 打开一个网页,整个过程会使用哪些协议?
-
-图解(图片来源:《图解 HTTP》):
-
-
-
-> 上图有一个错误,请注意,是 OSPF 不是 OPSF。 OSPF(Open Shortest Path First,ospf)开放最短路径优先协议, 是由 Internet 工程任务组开发的路由选择协议
-
-总体来说分为以下几个过程:
-
-1. DNS 解析
-2. TCP 连接
-3. 发送 HTTP 请求
-4. 服务器处理请求并返回 HTTP 报文
-5. 浏览器解析渲染页面
-6. 连接结束
-
-具体可以参考下面这篇文章:
-
-* [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700)
-
-## 六 状态码
-
-
-
-## 七 各种协议与 HTTP 协议之间的关系
-
-一般面试官会通过这样的问题来考察你对计算机网络知识体系的理解。
-
-图片来源:《图解 HTTP》
-
-
-
-## 八 HTTP 长连接, 短连接
-
-在 HTTP/1.0 中默认使用短连接。也就是说,客户端和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个 HTML 或其他类型的 Web 页中包含有其他的 Web 资源(如 JavaScript 文件、图像文件、CSS 文件等),每遇到这样一个 Web 资源,浏览器就会重新建立一个 HTTP 会话。
-
-而从 HTTP/1.1 起,默认使用长连接,用以保持连接特性。使用长连接的 HTTP 协议,会在响应头加入这行代码:
-
-```
-Connection:keep-alive
-```
-
-在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive 不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如 Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
-
-**HTTP 协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接。**
-
-—— [《HTTP 长连接、短连接究竟是什么?》](https://www.cnblogs.com/gotodsp/p/6366163.html)
-
-## 九 HTTP 是不保存状态的协议, 如何保存用户状态?
-
-HTTP 是一种不保存状态,即无状态(stateless)协议。也就是说 HTTP 协议自身不对请求和响应之间的通信状态进行保存。那么我们保存用户状态呢?Session 机制的存在就是为了解决这个问题,Session 的主要作用就是通过服务端记录用户的状态。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了(一般情况下,服务器会在一定时间内保存这个 Session,过了时间限制,就会销毁这个 Session)。
-
-在服务端保存 Session 的方法很多,最常用的就是内存和数据库(比如是使用内存数据库 redis 保存)。既然 Session 存放在服务器端,那么我们如何实现 Session 跟踪呢?大部分情况下,我们都是通过在 Cookie 中附加一个 Session ID 来方式来跟踪。
-
-**Cookie 被禁用怎么办?**
-
-最常用的就是利用 URL 重写把 Session ID 直接附加在 URL 路径的后面。
-
-
-
-## 十 Cookie 的作用是什么? 和 Session 有什么区别?
-
-Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
-
-**Cookie 一般用来保存用户信息** 比如 ① 我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;② 一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③ 登录一次网站后访问网站其他页面不需要重新登录。**Session 的主要作用就是通过服务端记录用户的状态。** 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
-
-Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。
-
-Cookie 存储在客户端中,而 Session 存储在服务器上,相对来说 Session 安全性更高。如果要在 Cookie 中存储一些敏感信息,不要直接写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。
-
-## 十一 HTTP 1.0 和 HTTP 1.1 的主要区别是什么?
-
-> 这部分回答引用这篇文章 的一些内容。
-
-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),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
-
-## 十二 URI 和 URL 的区别是什么?
-
-* URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源。
-* URL(Uniform Resource Locator) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。
-
-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 等。
-
-## 建议
-
-非常推荐大家看一下 《图解 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)
-*
diff --git a/docs/cs-basics/operating-system/images/Linux-Logo.png b/docs/cs-basics/operating-system/images/Linux-Logo.png
deleted file mode 100644
index 40e75aaac8aaf83dc8fd4afeb8760f5eb61e63fd..0000000000000000000000000000000000000000
Binary files a/docs/cs-basics/operating-system/images/Linux-Logo.png and /dev/null differ
diff --git "a/docs/cs-basics/operating-system/images/Linux\344\271\213\347\210\266.png" "b/docs/cs-basics/operating-system/images/Linux\344\271\213\347\210\266.png"
deleted file mode 100644
index 33145373b68a91333c7a1e97c9798e5633bf2182..0000000000000000000000000000000000000000
Binary files "a/docs/cs-basics/operating-system/images/Linux\344\271\213\347\210\266.png" and /dev/null differ
diff --git "a/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\345\221\275\344\273\244.png" "b/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\345\221\275\344\273\244.png"
index f59b2e638c6a576bb9ecd224357b3c4327b95cbf..4d1cb5b154e7dc2ad67a593f65c24b72b4366096 100644
Binary files "a/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\345\221\275\344\273\244.png" and "b/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\345\221\275\344\273\244.png" differ
diff --git "a/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\350\247\243\350\257\273.png" "b/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\350\247\243\350\257\273.png"
index 1292c125aca36306f93c2a1259f5f9a78264bef1..af06cc1a60b57258b573bb5c09641d99a9a305d0 100644
Binary files "a/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\350\247\243\350\257\273.png" and "b/docs/cs-basics/operating-system/images/Linux\346\235\203\351\231\220\350\247\243\350\257\273.png" differ
diff --git "a/docs/cs-basics/operating-system/images/Linux\347\233\256\345\275\225\346\240\221.png" "b/docs/cs-basics/operating-system/images/Linux\347\233\256\345\275\225\346\240\221.png"
index beef42034bf048569feabef10f9c81f6bb813850..a0ddc3e16d5c5cb8fe8119521a01af394e4cb224 100644
Binary files "a/docs/cs-basics/operating-system/images/Linux\347\233\256\345\275\225\346\240\221.png" and "b/docs/cs-basics/operating-system/images/Linux\347\233\256\345\275\225\346\240\221.png" differ
diff --git a/docs/cs-basics/operating-system/images/linux.png b/docs/cs-basics/operating-system/images/linux.png
deleted file mode 100644
index 20ead246915b72da2b4ad05e5e71579b781cdf2a..0000000000000000000000000000000000000000
Binary files a/docs/cs-basics/operating-system/images/linux.png and /dev/null differ
diff --git a/docs/cs-basics/operating-system/images/macos.png b/docs/cs-basics/operating-system/images/macos.png
index 332945774ee63a3595eda64c3184d57835b513e6..e05e203506d9bce1e79ea4577c6621069c7f4899 100644
Binary files a/docs/cs-basics/operating-system/images/macos.png and b/docs/cs-basics/operating-system/images/macos.png differ
diff --git a/docs/cs-basics/operating-system/images/unix.png b/docs/cs-basics/operating-system/images/unix.png
index 0afabcd8621beaf4bba1e08fcc9925a17379c898..18d1c24f6f33faa5ca65c583ef576fc917c88eff 100644
Binary files a/docs/cs-basics/operating-system/images/unix.png and b/docs/cs-basics/operating-system/images/unix.png differ
diff --git a/docs/cs-basics/operating-system/images/windows.png b/docs/cs-basics/operating-system/images/windows.png
index c2687dc72f7bdcf63fe05341e53861dd01599be3..b4a3625f408905f45547dc273e0b9e029bc0aae7 100644
Binary files a/docs/cs-basics/operating-system/images/windows.png and b/docs/cs-basics/operating-system/images/windows.png differ
diff --git "a/docs/cs-basics/operating-system/images/\344\277\256\346\224\271\346\226\207\344\273\266\346\235\203\351\231\220.png" "b/docs/cs-basics/operating-system/images/\344\277\256\346\224\271\346\226\207\344\273\266\346\235\203\351\231\220.png"
index de9409410c8468be3277b59daf503256763b5f67..fb9551b11725f87df10ca211bd7f75b8cc0ce745 100644
Binary files "a/docs/cs-basics/operating-system/images/\344\277\256\346\224\271\346\226\207\344\273\266\346\235\203\351\231\220.png" and "b/docs/cs-basics/operating-system/images/\344\277\256\346\224\271\346\226\207\344\273\266\346\235\203\351\231\220.png" differ
diff --git "a/docs/cs-basics/operating-system/images/\346\226\207\344\273\266inode\344\277\241\346\201\257.png" "b/docs/cs-basics/operating-system/images/\346\226\207\344\273\266inode\344\277\241\346\201\257.png"
index b47551e8314b8010b353f967d9f3acaa41eac8a4..4d376fa8472c57657d81f570216a8d4d04af5799 100644
Binary files "a/docs/cs-basics/operating-system/images/\346\226\207\344\273\266inode\344\277\241\346\201\257.png" and "b/docs/cs-basics/operating-system/images/\346\226\207\344\273\266inode\344\277\241\346\201\257.png" differ
diff --git "a/docs/cs-basics/operating-system/images/\347\224\250\346\210\267\346\200\201\344\270\216\345\206\205\346\240\270\346\200\201.png" "b/docs/cs-basics/operating-system/images/\347\224\250\346\210\267\346\200\201\344\270\216\345\206\205\346\240\270\346\200\201.png"
index aa0dafc2f0227a5f1e24e82bf4715f5f429b007d..d429c702cee266404bcd6e6f48b59b9cae801fac 100644
Binary files "a/docs/cs-basics/operating-system/images/\347\224\250\346\210\267\346\200\201\344\270\216\345\206\205\346\240\270\346\200\201.png" and "b/docs/cs-basics/operating-system/images/\347\224\250\346\210\267\346\200\201\344\270\216\345\206\205\346\240\270\346\200\201.png" differ
diff --git a/docs/cs-basics/operating-system/linux-intro.md b/docs/cs-basics/operating-system/linux-intro.md
index 723cd9b7e8846d9dcca2ab01047deff1d8abfd0c..cdc4fb9fe74c753a75b653589dcd2040e08fe29f 100644
--- a/docs/cs-basics/operating-system/linux-intro.md
+++ b/docs/cs-basics/operating-system/linux-intro.md
@@ -1,216 +1,155 @@
---
-title: 后端程序员必备的 Linux 基础知识总结
+title: Linux 基础知识总结
category: 计算机基础
tag:
- 操作系统
- Linux
+head:
+ - - meta
+ - name: description
+ content: 简单介绍一下 Java 程序员必知的 Linux 的一些概念以及常见命令。
---
-简单介绍一下 Java 程序员必知的 Linux 的一些概念以及常见命令。
-
-_如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!笔芯!_
-
-## 1. 从认识操作系统开始
-
-
-
-正式开始 Linux 之前,简单花一点点篇幅科普一下操作系统相关的内容。
-
-### 1.1. 操作系统简介
-
-我通过以下四点介绍什么是操作系统:
-
-1. **操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机的基石。**
-2. **操作系统本质上是一个运行在计算机上的软件程序 ,用于管理计算机硬件和软件资源。** 举例:运行在你电脑上的所有应用程序都通过操作系统来调用系统内存以及磁盘等等硬件。
-3. **操作系统存在屏蔽了硬件层的复杂性。** 操作系统就像是硬件使用的负责人,统筹着各种相关事项。
-4. **操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理**。
-
-> 内核(Kernel)在后文中会提到。
-
-
-
-### 1.2. 操作系统简单分类
-
-#### 1.2.1. Windows
-
-目前最流行的个人桌面操作系统 ,不做多的介绍,大家都清楚。界面简单易操作,软件生态非常好。
-
-_玩玩电脑游戏还是必须要有 Windows 的,所以我现在是一台 Windows 用于玩游戏,一台 Mac 用于平时日常开发和学习使用。_
-
-
-
-#### 1.2.2. Unix
-
-最早的多用户、多任务操作系统 。后面崛起的 Linux 在很多方面都参考了 Unix。
-
-目前这款操作系统已经逐渐逐渐退出操作系统的舞台。
-
-
-
-#### 1.2.3. Linux
+
-**Linux 是一套免费使用、开源的类 Unix 操作系统。** Linux 存在着许多不同的发行版本,但它们都使用了 **Linux 内核** 。
-
-> 严格来讲,Linux 这个词本身只表示 Linux 内核,在 GNU/Linux 系统中,Linux 实际就是 Linux 内核,而该系统的其余部分主要是由 GNU 工程编写和提供的程序组成。单独的 Linux 内核并不能成为一个可以正常工作的操作系统。
->
-> **很多人更倾向使用 “GNU/Linux” 一词来表达人们通常所说的 “Linux”。**
-
-
-
-#### 1.2.4. Mac OS
-
-苹果自家的操作系统,编程体验和 Linux 相当,但是界面、软件生态以及用户体验各方面都要比 Linux 操作系统更好。
-
-
-
-### 1.3. 操作系统的内核(Kernel)
-
-我们先来看看维基百科对于内核的解释,我觉得总结的非常好!
-
-> **内核**(英语:Kernel,又称核心)在计算机科学中是一个用来管理软件发出的数据 I/O(输入与输出)要求的电脑程序,将这些要求转译为数据处理的指令并交由中央处理器(CPU)及电脑中其他电子组件进行处理,是现代操作系统中最基本的部分。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并由内核决定一个程序在什么时候对某部分硬件操作多长时间。 **直接对硬件操作是非常复杂的。所以内核通常提供一种硬件抽象的方法,来完成这些操作。有了这个,通过进程间通信机制及系统调用,应用进程可间接控制所需的硬件资源(特别是处理器及 IO 设备)。**
->
-> 早期计算机系统的设计中,还没有操作系统的内核这个概念。随着计算机系统的发展,操作系统内核的概念才渐渐明晰起来了!
-
-简单概括两点:
-
-1. **操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。**
-2. **操作系统的内核是连接应用程序和硬件的桥梁,决定着操作系统的性能和稳定性。**
-
-### 1.4. 中央处理器(CPU,Central Processing Unit)
-
-关于 CPU 简单概括三点:
-
-1. **CPU 是一台计算机的运算核心(Core)+控制核心( Control Unit),可以称得上是计算机的大脑。**
-2. **CPU 主要包括两个部分:控制器+运算器。**
-3. **CPU 的根本任务就是执行指令,对计算机来说最终都是一串由“0”和“1”组成的序列。**
-
-### 1.5. CPU vs Kernel(内核)
+简单介绍一下 Java 程序员必知的 Linux 的一些概念以及常见命令。
-很多人容易无法区分操作系统的内核(Kernel)和中央处理器(CPU),你可以简单从下面两点来区别:
+## 初探 Linux
-1. 操作系统的内核(Kernel)属于操作系统层面,而 CPU 属于硬件。
-2. CPU 主要提供运算,处理各种指令的能力。内核(Kernel)主要负责系统管理比如内存管理,它屏蔽了对硬件的操作。
+### Linux 简介
-下图清晰说明了应用程序、内核、CPU 这三者的关系。
+通过以下三点可以概括 Linux 到底是什么:
-
+- **类 Unix 系统**:Linux 是一种自由、开放源码的类似 Unix 的操作系统
+- **Linux 本质是指 Linux 内核**:严格来讲,Linux 这个词本身只表示 Linux 内核,单独的 Linux 内核并不能成为一个可以正常工作的操作系统。所以,就有了各种 Linux 发行版。
+- **Linux 之父(林纳斯·本纳第克特·托瓦兹 Linus Benedict Torvalds)**:一个编程领域的传奇式人物,真大佬!我辈崇拜敬仰之楷模。他是 **Linux 内核** 的最早作者,随后发起了这个开源项目,担任 Linux 内核的首要架构师。他还发起了 Git 这个开源项目,并为主要的开发者。
-### 1.6. 系统调用
+
-介绍系统调用之前,我们先来了解一下用户态和系统态。
+### Linux 诞生
-根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:
+1989 年,Linus Torvalds 进入芬兰陆军新地区旅,服 11 个月的国家义务兵役,军衔为少尉,主要服务于计算机部门,任务是弹道计算。服役期间,购买了安德鲁·斯图尔特·塔能鲍姆所著的教科书及 minix 源代码,开始研究操作系统。1990 年,他退伍后回到大学,开始接触 Unix。
-1. **用户态(user mode)** : 用户态运行的进程或可以直接读取用户程序的数据。
-2. **系统态(kernel mode)**: 可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。
+> **Minix** 是一个迷你版本的类 Unix 操作系统,由塔能鲍姆教授为了教学之用而创作,采用微核心设计。它启发了 Linux 内核的创作。
-**说了用户态和系统态之后,那么什么是系统调用呢?**
+1991 年,Linus Torvalds 开源了 Linux 内核。Linux 以一只可爱的企鹅作为标志,象征着敢作敢为、热爱生活。
-我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的系统态级别的子功能咋办呢?那就需要系统调用了!
+
-也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
+### 常见的 Linux 发行版本
-这些系统调用按功能大致可分为如下几类:
+
-- **设备管理** :完成设备的请求或释放,以及设备启动等功能。
-- **文件管理** :完成文件的读、写、创建及删除等功能。
-- **进程控制** :完成进程的创建、撤销、阻塞及唤醒等功能。
-- **进程通信** :完成进程之间的消息传递或信号传递等功能。
-- **内存管理** :完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
+Linus Torvalds 开源的只是 Linux 内核,我们上面也提到了操作系统内核的作用。一些组织或厂商将 Linux 内核与各种软件和文档包装起来,并提供系统安装界面和系统配置、设定与管理工具,就构成了 Linux 的发行版本。
-我在网上找了一个图,通过这个图可以很清晰的说明用户程序、系统调用、内核和硬件之间的关系。(_太难了~木有自己画_)
+> 内核主要负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。
-
+Linux 的发行版本可以大体分为两类:
-## 2. 初探 Linux
+- **商业公司维护的发行版本**:比如 Red Hat 公司维护支持的 Red Hat Enterprise Linux (RHEL)。
+- **社区组织维护的发行版本**:比如基于 Red Hat Enterprise Linux(RHEL)的 CentOS、基于 Debian 的 Ubuntu。
-### 2.1. Linux 简介
+对于初学者学习 Linux ,推荐选择 CentOS,原因如下:
-我们上面已经简单了 Linux,这里只强调三点。
+- CentOS 免费且开放源代码;
+- CentOS 基于 RHEL,功能与 RHEL 高度一致,安全稳定、性能优秀。
-- **类 Unix 系统** : Linux 是一种自由、开放源码的类似 Unix 的操作系统
-- **Linux 本质是指 Linux 内核** : 严格来讲,Linux 这个词本身只表示 Linux 内核,单独的 Linux 内核并不能成为一个可以正常工作的操作系统。所以,就有了各种 Linux 发行版。
-- **Linux 之父(林纳斯·本纳第克特·托瓦兹 Linus Benedict Torvalds)** : 一个编程领域的传奇式人物,真大佬!我辈崇拜敬仰之楷模。他是 **Linux 内核** 的最早作者,随后发起了这个开源项目,担任 Linux 内核的首要架构师。他还发起了 Git 这个开源项目,并为主要的开发者。
+## Linux 文件系统
-
+### Linux 文件系统简介
-### 2.2. Linux 诞生
+在 Linux 操作系统中,一切被操作系统管理的资源,如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文件或目录等,都被视为文件。这是 Linux 系统中一个重要的概念,即"一切都是文件"。
-1989 年,Linus Torvalds 进入芬兰陆军新地区旅,服 11 个月的国家义务兵役,军衔为少尉,主要服务于计算机部门,任务是弹道计算。服役期间,购买了安德鲁·斯图尔特·塔能鲍姆所著的教科书及 minix 源代码,开始研究操作系统。1990 年,他退伍后回到大学,开始接触 Unix。
+这种概念源自 UNIX 哲学,即将所有资源都抽象为文件的方式来进行管理和访问。Linux 的文件系统也借鉴了 UNIX 文件系统的设计理念。这种设计使得 Linux 系统可以通过统一的文件接口来管理和操作不同类型的资源,从而实现了一种统一的文件操作方式。例如,可以使用类似于读写文件的方式来对待网络接口、磁盘驱动器、设备文件等,使得操作和管理这些资源更加统一和简便。
-> **Minix** 是一个迷你版本的类 Unix 操作系统,由塔能鲍姆教授为了教学之用而创作,采用微核心设计。它启发了 Linux 内核的创作。
+这种文件为中心的设计理念为 Linux 系统带来了灵活性和可扩展性,使得 Linux 成为一种强大的操作系统。同时,这也是 Linux 系统的一大特点,深受广大用户和开发者的喜欢和推崇。
-1991 年,Linus Torvalds 开源了 Linux 内核。Linux 以一只可爱的企鹅作为标志,象征着敢作敢为、热爱生活。
+### inode 介绍
-
+inode 是 Linux/Unix 文件系统的基础。那 inode 到是什么?有什么作用呢?
-### 2.3. 常见 Linux 发行版本有哪些?
+通过以下五点可以概括 inode 到底是什么:
-Linus Torvalds 开源的只是 Linux 内核,我们上面也提到了操作系统内核的作用。一些组织或厂商将 Linux 内核与各种软件和文档包装起来,并提供系统安装界面和系统配置、设定与管理工具,就构成了 Linux 的发行版本。
+1. 硬盘的最小存储单位是扇区(Sector),块(block)由多个扇区组成。文件数据存储在块中。块的最常见的大小是 4kb,约为 8 个连续的扇区组成(每个扇区存储 512 字节)。一个文件可能会占用多个 block,但是一个块只能存放一个文件。虽然,我们将文件存储在了块(block)中,但是我们还需要一个空间来存储文件的 **元信息 metadata**:如某个文件被分成几块、每一块在的地址、文件拥有者,创建时间,权限,大小等。这种 **存储文件元信息的区域就叫 inode**,译为索引节点:**i(index)+node**。 **每个文件都有一个唯一的 inode,存储文件的元信息。**
+2. inode 是一种固定大小的数据结构,其大小在文件系统创建时就确定了,并且在文件的生命周期内保持不变。
+3. inode 的访问速度非常快,因为系统可以直接通过 inode 号码定位到文件的元数据信息,无需遍历整个文件系统。
+4. inode 的数量是有限的,每个文件系统只能包含固定数量的 inode。这意味着当文件系统中的 inode 用完时,无法再创建新的文件或目录,即使磁盘上还有可用空间。因此,在创建文件系统时,需要根据文件和目录的预期数量来合理分配 inode 的数量。
+5. 可以使用 `stat` 命令可以查看文件的 inode 信息,包括文件的 inode 号、文件类型、权限、所有者、文件大小、修改时间。
-> 内核主要负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。
+简单来说:inode 就是用来维护某个文件被分成几块、每一块在的地址、文件拥有者,创建时间,权限,大小等信息。
-Linux 的发行版本可以大体分为两类:
+再总结一下 inode 和 block:
-- 商业公司维护的发行版本,以著名的 Red Hat 为代表,比较典型的有 CentOS 。
-- 社区组织维护的发行版本,以 Debian 为代表,比较典型的有 Ubuntu、Debian。
+- **inode**:记录文件的属性信息,可以使用 `stat` 命令查看 inode 信息。
+- **block**:实际文件的内容,如果一个文件大于一个块时候,那么将占用多个 block,但是一个块只能存放一个文件。(因为数据是由 inode 指向的,如果有两个文件的数据存放在同一个块中,就会乱套了)
-对于初学者学习 Linux ,推荐选择 CentOS 。
+
-## 3. Linux 文件系统概览
+可以看出,Linux/Unix 操作系统使用 inode 区分不同的文件。这样做的好处是,即使文件名被修改或删除,文件的 inode 号码不会改变,从而可以避免一些因文件重命名、移动或删除导致的错误。同时,inode 也可以提供更高的文件系统性能,因为 inode 的访问速度非常快,可以直接通过 inode 号码定位到文件的元数据信息,无需遍历整个文件系统。
-### 3.1. Linux 文件系统简介
+不过,使用 inode 号码也使得文件系统在用户和应用程序层面更加抽象和复杂,需要通过系统命令或文件系统接口来访问和管理文件的 inode 信息。
-**在 Linux 操作系统中,所有被操作系统管理的资源,例如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文件或是目录都被看作是一个文件。** 也就是说在 Linux 系统中有一个重要的概念:**一切都是文件**。
+### 硬链接和软链接
-其实这是 UNIX 哲学的一个体现,在 UNIX 系统中,把一切资源都看作是文件,Linux 的文件系统也是借鉴 UNIX 文件系统而来。
+在 Linux/类 Unix 系统上,文件链接(File Link)是一种特殊的文件类型,可以在文件系统中指向另一个文件。常见的文件链接类型有两种:
-### 3.2. inode 介绍
+**1、硬链接(Hard Link)**
-**inode 是 linux/unix 文件系统的基础。那么,inode 是什么?有什么作用呢?**
+- 在 Linux/类 Unix 文件系统中,每个文件和目录都有一个唯一的索引节点(inode)号,用来标识该文件或目录。硬链接通过 inode 节点号建立连接,硬链接和源文件的 inode 节点号相同,两者对文件系统来说是完全平等的(可以看作是互为硬链接,源头是同一份文件),删除其中任何一个对另外一个没有影响,可以通过给文件设置硬链接文件来防止重要文件被误删。
+- 只有删除了源文件和所有对应的硬链接文件,该文件才会被真正删除。
+- 硬链接具有一些限制,不能对目录以及不存在的文件创建硬链接,并且,硬链接也不能跨越文件系统。
+- `ln` 命令用于创建硬链接。
-硬盘的最小存储单位是扇区(Sector),块(block)由多个扇区组成。文件数据存储在块中。块的最常见的大小是 4kb,约为 8 个连续的扇区组成(每个扇区存储 512 字节)。一个文件可能会占用多个 block,但是一个块只能存放一个文件。
+**2、软链接(Symbolic Link 或 Symlink)**
-虽然,我们将文件存储在了块(block)中,但是我们还需要一个空间来存储文件的 **元信息 metadata** :如某个文件被分成几块、每一块在的地址、文件拥有者,创建时间,权限,大小等。这种 **存储文件元信息的区域就叫 inode**,译为索引节点:**i(index)+node**。 每个文件都有一个 inode,存储文件的元信息。
+- 软链接和源文件的 inode 节点号不同,而是指向一个文件路径。
+- 源文件删除后,软链接依然存在,但是指向的是一个无效的文件路径。
+- 软连接类似于 Windows 系统中的快捷方式。
+- 不同于硬链接,可以对目录或者不存在的文件创建软链接,并且,软链接可以跨越文件系统。
+- `ln -s` 命令用于创建软链接。
-可以使用 `stat` 命令可以查看文件的 inode 信息。每个 inode 都有一个号码,Linux/Unix 操作系统不使用文件名来区分文件,而是使用 inode 号码区分不同的文件。
+**硬链接为什么不能跨文件系统?**
-简单来说:inode 就是用来维护某个文件被分成几块、每一块在的地址、文件拥有者,创建时间,权限,大小等信息。
+我们之前提到过,硬链接是通过 inode 节点号建立连接的,而硬链接和源文件共享相同的 inode 节点号。
-简单总结一下:
+然而,每个文件系统都有自己的独立 inode 表,且每个 inode 表只维护该文件系统内的 inode。如果在不同的文件系统之间创建硬链接,可能会导致 inode 节点号冲突的问题,即目标文件的 inode 节点号已经在该文件系统中被使用。
-- **inode** :记录文件的属性信息,可以使用 stat 命令查看 inode 信息。
-- **block** :实际文件的内容,如果一个文件大于一个块时候,那么将占用多个 block,但是一个块只能存放一个文件。(因为数据是由 inode 指向的,如果有两个文件的数据存放在同一个块中,就会乱套了)
+### Linux 文件类型
-
+Linux 支持很多文件类型,其中非常重要的文件类型有: **普通文件**,**目录文件**,**链接文件**,**设备文件**,**管道文件**,**Socket 套接字文件** 等。
-### 3.3. Linux 文件类型
+- **普通文件(-)**:用于存储信息和数据, Linux 用户可以根据访问权限对普通文件进行查看、更改和删除。比如:图片、声音、PDF、text、视频、源代码等等。
+- **目录文件(d,directory file)**:目录也是文件的一种,用于表示和管理系统中的文件,目录文件中包含一些文件名和子目录名。打开目录事实上就是打开目录文件。
+- **符号链接文件(l,symbolic link)**:保留了指向文件的地址而不是文件本身。
+- **字符设备(c,char)**:用来访问字符设备比如键盘。
+- **设备文件(b,block)**:用来访问块设备比如硬盘、软盘。
+- **管道文件(p,pipe)** : 一种特殊类型的文件,用于进程之间的通信。
+- **套接字文件(s,socket)**:用于进程间的网络通信,也可以用于本机之间的非网络通信。
-Linux 支持很多文件类型,其中非常重要的文件类型有: **普通文件**,**目录文件**,**链接文件**,**设备文件**,**管道文件**,**Socket 套接字文件**等。
+每种文件类型都有不同的用途和属性,可以通过命令如`ls`、`file`等来查看文件的类型信息。
-- **普通文件(-)** : 用于存储信息和数据, Linux 用户可以根据访问权限对普通文件进行查看、更改和删除。比如:图片、声音、PDF、text、视频、源代码等等。
-- **目录文件(d,directory file)** :目录也是文件的一种,用于表示和管理系统中的文件,目录文件中包含一些文件名和子目录名。打开目录事实上就是打开目录文件。
-- **符号链接文件(l,symbolic link)** :保留了指向文件的地址而不是文件本身。
-- **字符设备(c,char)** :用来访问字符设备比如键盘。
-- **设备文件(b,block)** : 用来访问块设备比如硬盘、软盘。
-- **管道文件(p,pipe)** : 一种特殊类型的文件,用于进程之间的通信。
-- **套接字(s,socket)** :用于进程间的网络通信,也可以用于本机之间的非网络通信。
+```bash
+# 普通文件(-)
+-rw-r--r-- 1 user group 1024 Apr 14 10:00 file.txt
-### 3.4. Linux 目录树
+# 目录文件(d,directory file)*
+drwxr-xr-x 2 user group 4096 Apr 14 10:00 directory/
-所有可操作的计算机资源都存在于目录树这个结构中,对计算资源的访问,可以看做是对这棵目录树的访问。
+# 套接字文件(s,socket)
+srwxrwxrwx 1 user group 0 Apr 14 10:00 socket
+```
-**Linux 的目录结构如下:**
+### Linux 目录树
-Linux 文件系统的结构层次鲜明,就像一棵倒立的树,最顶层是其根目录:
-
+Linux 使用一种称为目录树的层次结构来组织文件和目录。目录树由根目录(/)作为起始点,向下延伸,形成一系列的目录和子目录。每个目录可以包含文件和其他子目录。结构层次鲜明,就像一棵倒立的树。
+
**常见目录说明:**
- **/bin:** 存放二进制可执行文件(ls、cat、mkdir 等),常用命令一般都在这里;
- **/etc:** 存放系统管理和配置文件;
- **/home:** 存放所有用户文件的根目录,是用户主目录的基点,比如用户 user 的主目录就是/home/user,可以用~user 表示;
-- **/usr :** 用于存放系统应用程序;
+- **/usr:** 用于存放系统应用程序;
- **/opt:** 额外安装的可选应用程序包所放置的位置。一般情况下,我们可以把 tomcat 等都安装到这里;
- **/proc:** 虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息;
- **/root:** 超级用户(系统管理员)的主目录(特权阶级^o^);
@@ -218,47 +157,58 @@ Linux 文件系统的结构层次鲜明,就像一棵倒立的树,最顶层
- **/dev:** 用于存放设备文件;
- **/mnt:** 系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统;
- **/boot:** 存放用于系统引导时使用的各种文件;
-- **/lib :** 存放着和系统运行相关的库文件 ;
+- **/lib 和/lib64:** 存放着和系统运行相关的库文件 ;
- **/tmp:** 用于存放各种临时文件,是公用的临时文件存储点;
- **/var:** 用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启动日志等。)等;
- **/lost+found:** 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows 下叫什么.chk)就在这里。
-## 4. Linux 基本命令
+## Linux 常用命令
-下面只是给出了一些比较常用的命令。推荐一个 Linux 命令快查网站,非常不错,大家如果遗忘某些命令或者对某些命令不理解都可以在这里得到解决。
+下面只是给出了一些比较常用的命令。
-Linux 命令大全:[http://man.linuxde.net/](http://man.linuxde.net/)
+推荐一个 Linux 命令快查网站,非常不错,大家如果遗忘某些命令或者对某些命令不理解都可以在这里得到解决。Linux 命令在线速查手册:https://wangchujiang.com/linux-command/ 。
-### 4.1. 目录切换命令
+
-- **`cd usr`:** 切换到该目录下 usr 目录
-- **`cd ..(或cd../)`:** 切换到上一层目录
-- **`cd /`:** 切换到系统根目录
-- **`cd ~`:** 切换到用户主目录
+另外,[shell.how](https://www.shell.how/) 这个网站可以用来解释常见命令的意思,对你学习 Linux 基本命令以及其他常用命令(如 Git、NPM)。
+
+
+
+### 目录切换
+
+- `cd usr`:切换到该目录下 usr 目录
+- `cd ..(或cd../)`:切换到上一层目录
+- `cd /`:切换到系统根目录
+- `cd ~`:切换到用户主目录
- **`cd -`:** 切换到上一个操作所在目录
-### 4.2. 目录的操作命令(增删改查)
+### 目录操作
-- **`mkdir 目录名称`:** 增加目录。
-- **`ls/ll`**(ll 是 ls -l 的别名,ll 命令可以看到该目录下的所有目录和文件的详细信息):查看目录信息。
-- **`find 目录 参数`:** 寻找目录(查)。示例:① 列出当前目录及子目录下所有文件和文件夹: `find .`;② 在`/home`目录下查找以.txt 结尾的文件名:`find /home -name "*.txt"` ,忽略大小写: `find /home -iname "*.txt"` ;③ 当前目录及子目录下查找所有以.txt 和.pdf 结尾的文件:`find . \( -name "*.txt" -o -name "*.pdf" \)`或`find . -name "*.txt" -o -name "*.pdf"`。
-- **`mv 目录名称 新目录名称`:** 修改目录的名称(改)。注意:mv 的语法不仅可以对目录进行重命名而且也可以对各种文件,压缩包等进行 重命名的操作。mv 命令用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中。后面会介绍到 mv 命令的另一个用法。
-- **`mv 目录名称 目录的新位置`:** 移动目录的位置---剪切(改)。注意:mv 语法不仅可以对目录进行剪切操作,对文件和压缩包等都可执行剪切操作。另外 mv 与 cp 的结果不同,mv 好像文件“搬家”,文件个数并未增加。而 cp 对文件进行复制,文件个数增加了。
-- **`cp -r 目录名称 目录拷贝的目标位置`:** 拷贝目录(改),-r 代表递归拷贝 。注意:cp 命令不仅可以拷贝目录还可以拷贝文件,压缩包等,拷贝文件和压缩包时不 用写-r 递归。
-- **`rm [-rf] 目录` :** 删除目录(删)。注意:rm 不仅可以删除目录,也可以删除其他文件或压缩包,为了增强大家的记忆, 无论删除任何目录或文件,都直接使用`rm -rf` 目录/文件/压缩包。
+- `ls`:显示目录中的文件和子目录的列表。例如:`ls /home`,显示 `/home` 目录下的文件和子目录列表。
+- `ll`:`ll` 是 `ls -l` 的别名,ll 命令可以看到该目录下的所有目录和文件的详细信息
+- `mkdir [选项] 目录名`:创建新目录(增)。例如:`mkdir -m 755 my_directory`,创建一个名为 `my_directory` 的新目录,并将其权限设置为 755,即所有用户对该目录有读、写和执行的权限。
+- `find [路径] [表达式]`:在指定目录及其子目录中搜索文件或目录(查),非常强大灵活。例如:① 列出当前目录及子目录下所有文件和文件夹: `find .`;② 在`/home`目录下查找以 `.txt` 结尾的文件名:`find /home -name "*.txt"` ,忽略大小写: `find /home -i name "*.txt"` ;③ 当前目录及子目录下查找所有以 `.txt` 和 `.pdf` 结尾的文件:`find . \( -name "*.txt" -o -name "*.pdf" \)`或`find . -name "*.txt" -o -name "*.pdf"`。
+- `pwd`:显示当前工作目录的路径。
+- `rmdir [选项] 目录名`:删除空目录(删)。例如:`rmdir -p my_directory`,删除名为 `my_directory` 的空目录,并且会递归删除`my_directory`的空父目录,直到遇到非空目录或根目录。
+- `rm [选项] 文件或目录名`:删除文件/目录(删)。例如:`rm -r my_directory`,删除名为 `my_directory` 的目录,`-r`(recursive,递归) 表示会递归删除指定目录及其所有子目录和文件。
+- `cp [选项] 源文件/目录 目标文件/目录`:复制文件或目录(移)。例如:`cp file.txt /home/file.txt`,将 `file.txt` 文件复制到 `/home` 目录下,并重命名为 `file.txt`。`cp -r source destination`,将 `source` 目录及其下的所有子目录和文件复制到 `destination` 目录下,并保留源文件的属性和目录结构。
+- `mv [选项] 源文件/目录 目标文件/目录`:移动文件或目录(移),也可以用于重命名文件或目录。例如:`mv file.txt /home/file.txt`,将 `file.txt` 文件移动到 `/home` 目录下,并重命名为 `file.txt`。`mv` 与 `cp` 的结果不同,`mv` 好像文件“搬家”,文件个数并未增加。而 `cp` 对文件进行复制,文件个数增加了。
-### 4.3. 文件的操作命令(增删改查)
+### 文件操作
-- **`touch 文件名称`:** 文件的创建(增)。
-- **`cat/more/less/tail 文件名称`** :文件的查看(查) 。命令 `tail -f 文件` 可以对某个文件进行动态监控,例如 tomcat 的日志文件, 会随着程序的运行,日志会变化,可以使用 `tail -f catalina-2016-11-11.log` 监控 文 件的变化 。
-- **`vim 文件`:** 修改文件的内容(改)。vim 编辑器是 Linux 中的强大组件,是 vi 编辑器的加强版,vim 编辑器的命令和快捷方式有很多,但此处不一一阐述,大家也无需研究的很透彻,使用 vim 编辑修改文件的方式基本会使用就可以了。在实际开发中,使用 vim 编辑器主要作用就是修改配置文件,下面是一般步骤: `vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q!` (输入 wq 代表写入内容并退出,即保存;输入 q!代表强制退出不保存)。
-- **`rm -rf 文件`:** 删除文件(删)。
+像 `mv`、`cp`、`rm` 等文件和目录都适用的命令,这里就不重复列举了。
-### 4.4. 压缩文件的操作命令
+- `touch [选项] 文件名..`:创建新文件或更新已存在文件(增)。例如:`touch file1.txt file2.txt file3.txt` ,创建 3 个文件。
+- `ln [选项] <源文件> <硬链接/软链接文件>`:创建硬链接/软链接。例如:`ln -s file.txt file_link`,创建名为 `file_link` 的软链接,指向 `file.txt` 文件。`-s` 选项代表的就是创建软链接,s 即 symbolic(软链接又名符号链接) 。
+- `cat/more/less/tail 文件名`:文件的查看(查) 。命令 `tail -f 文件` 可以对某个文件进行动态监控,例如 Tomcat 的日志文件, 会随着程序的运行,日志会变化,可以使用 `tail -f catalina-2016-11-11.log` 监控 文 件的变化 。
+- `vim 文件名`:修改文件的内容(改)。vim 编辑器是 Linux 中的强大组件,是 vi 编辑器的加强版,vim 编辑器的命令和快捷方式有很多,但此处不一一阐述,大家也无需研究的很透彻,使用 vim 编辑修改文件的方式基本会使用就可以了。在实际开发中,使用 vim 编辑器主要作用就是修改配置文件,下面是一般步骤:`vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q!` (输入 wq 代表写入内容并退出,即保存;输入 q!代表强制退出不保存)。
+
+### 文件压缩
**1)打包并压缩文件:**
-Linux 中的打包文件一般是以.tar 结尾的,压缩的命令一般是以.gz 结尾的。而一般情况下打包和压缩是一起进行的,打包并压缩后的文件的后缀名一般.tar.gz。
+Linux 中的打包文件一般是以 `.tar` 结尾的,压缩的命令一般是以 `.gz` 结尾的。而一般情况下打包和压缩是一起进行的,打包并压缩后的文件的后缀名一般 `.tar.gz`。
+
命令:`tar -zcvf 打包压缩后的文件名 要打包压缩的文件` ,其中:
- z:调用 gzip 压缩命令进行压缩
@@ -266,20 +216,26 @@ Linux 中的打包文件一般是以.tar 结尾的,压缩的命令一般是以
- v:显示运行过程
- f:指定文件名
-比如:假如 test 目录下有三个文件分别是:aaa.txt bbb.txt ccc.txt,如果我们要打包 test 目录并指定压缩后的压缩包名称为 test.tar.gz 可以使用命令:**`tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt` 或 `tar -zcvf test.tar.gz /test/`**
+比如:假如 test 目录下有三个文件分别是:`aaa.txt`、 `bbb.txt`、`ccc.txt`,如果我们要打包 `test` 目录并指定压缩后的压缩包名称为 `test.tar.gz` 可以使用命令:`tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt` 或 `tar -zcvf test.tar.gz /test/` 。
**2)解压压缩包:**
命令:`tar [-xvf] 压缩文件`
-其中:x:代表解压
+其中 x 代表解压
示例:
-- 将 /test 下的 test.tar.gz 解压到当前目录下可以使用命令:**`tar -xvf test.tar.gz`**
-- 将 /test 下的 test.tar.gz 解压到根目录/usr 下:**`tar -xvf test.tar.gz -C /usr`**(- C 代表指定解压的位置)
+- 将 `/test` 下的 `test.tar.gz` 解压到当前目录下可以使用命令:`tar -xvf test.tar.gz`
+- 将 /test 下的 test.tar.gz 解压到根目录/usr 下:`tar -xvf test.tar.gz -C /usr`(`-C` 代表指定解压的位置)
-### 4.5. Linux 的权限命令
+### 文件传输
+
+- `scp [选项] 源文件 远程文件` (scp 即 secure copy,安全复制):用于通过 SSH 协议进行安全的文件传输,可以实现从本地到远程主机的上传和从远程主机到本地的下载。例如:`scp -r my_directory user@remote:/home/user` ,将本地目录`my_directory`上传到远程服务器 `/home/user` 目录下。`scp -r user@remote:/home/user/my_directory` ,将远程服务器的 `/home/user` 目录下的`my_directory`目录下载到本地。需要注意的是,`scp` 命令需要在本地和远程系统之间建立 SSH 连接进行文件传输,因此需要确保远程服务器已经配置了 SSH 服务,并且具有正确的权限和认证方式。
+- `rsync [选项] 源文件 远程文件` : 可以在本地和远程系统之间高效地进行文件复制,并且能够智能地处理增量复制,节省带宽和时间。例如:`rsync -r my_directory user@remote:/home/user`,将本地目录`my_directory`上传到远程服务器 `/home/user` 目录下。
+- `ftp` (File Transfer Protocol):提供了一种简单的方式来连接到远程 FTP 服务器并进行文件上传、下载、删除等操作。使用之前需要先连接登录远程 FTP 服务器,进入 FTP 命令行界面后,可以使用 `put` 命令将本地文件上传到远程主机,可以使用`get`命令将远程主机的文件下载到本地,可以使用 `delete` 命令删除远程主机的文件。这里就不进行演示了。
+
+### 文件权限
操作系统中每个文件都拥有特定的权限、所属用户和所属组。权限是操作系统用来限制资源访问的机制,在 Linux 中权限一般分为读(readable)、写(writable)和执行(excutable),分为三组。分别对应文件的属主(owner),属组(group)和其他用户(other),通过这样的机制来限制哪些用户、哪些组可以对特定的文件进行什么样的操作。
@@ -287,19 +243,19 @@ Linux 中的打包文件一般是以.tar 结尾的,压缩的命令一般是以
示例:在随意某个目录下`ls -l`
-
+
第一列的内容的信息解释如下:
-
+
> 下面将详细讲解文件的类型、Linux 中权限以及文件有所有者、所在组、其它组具体是什么?
**文件的类型:**
-- d: 代表目录
-- -: 代表文件
-- l: 代表软链接(可以认为是 window 中的快捷方式)
+- d:代表目录
+- -:代表文件
+- l:代表软链接(可以认为是 window 中的快捷方式)
**Linux 中权限分为以下几种:**
@@ -327,13 +283,13 @@ Linux 中的打包文件一般是以.tar 结尾的,压缩的命令一般是以
| w | 可以创建和删除目录下文件 |
| x | 可以使用 cd 进入目录 |
-需要注意的是: **超级用户可以无视普通用户的权限,即使文件目录权限是 000,依旧可以访问。**
+需要注意的是:**超级用户可以无视普通用户的权限,即使文件目录权限是 000,依旧可以访问。**
**在 linux 中的每个用户必须属于一个组,不能独立于组外。在 linux 中每个文件有所有者、所在组、其它组的概念。**
-- **所有者(u)** :一般为文件的创建者,谁创建了该文件,就天然的成为该文件的所有者,用 `ls ‐ahl` 命令可以看到文件的所有者 也可以使用 chown 用户名 文件名来修改文件的所有者 。
-- **文件所在组(g)** :当某个用户创建了一个文件后,这个文件的所在组就是该用户所在的组用 `ls ‐ahl`命令可以看到文件的所有组也可以使用 chgrp 组名 文件名来修改文件所在的组。
-- **其它组(o)** :除开文件的所有者和所在组的用户外,系统的其它用户都是文件的其它组。
+- **所有者(u)**:一般为文件的创建者,谁创建了该文件,就天然的成为该文件的所有者,用 `ls ‐ahl` 命令可以看到文件的所有者 也可以使用 chown 用户名 文件名来修改文件的所有者 。
+- **文件所在组(g)**:当某个用户创建了一个文件后,这个文件的所在组就是该用户所在的组用 `ls ‐ahl`命令可以看到文件的所有组也可以使用 chgrp 组名 文件名来修改文件所在的组。
+- **其它组(o)**:除开文件的所有者和所在组的用户外,系统的其它用户都是文件的其它组。
> 我们再来看看如何修改文件/目录的权限。
@@ -343,7 +299,7 @@ Linux 中的打包文件一般是以.tar 结尾的,压缩的命令一般是以
**`chmod u=rwx,g=rw,o=r aaa.txt`** 或者 **`chmod 764 aaa.txt`**
-
+
**补充一个比较常用的东西:**
@@ -354,7 +310,7 @@ Linux 中的打包文件一般是以.tar 结尾的,压缩的命令一般是以
3. 把 zookeeper 这个脚本添加到开机启动项里面,命令是:`chkconfig --add zookeeper`
4. 如果想看看是否添加成功,命令是:`chkconfig --list`
-### 4.6. Linux 用户管理
+### 用户管理
Linux 系统是一个多用户多任务的分时操作系统,任何一个要使用系统资源的用户,都必须首先向系统管理员申请一个账号,然后以这个账号的身份进入系统。
@@ -362,18 +318,13 @@ Linux 系统是一个多用户多任务的分时操作系统,任何一个要
**Linux 用户管理相关命令:**
-- `useradd 选项 用户名`:添加用户账号
-- `userdel 选项 用户名`:删除用户帐号
-- `usermod 选项 用户名`:修改帐号
-- `passwd 用户名`:更改或创建用户的密码
-- `passwd -S 用户名` :显示用户账号密码信息
-- `passwd -d 用户名`: 清除用户密码
-
-`useradd` 命令用于 Linux 中创建的新的系统用户。`useradd`可用来建立用户帐号。帐号建好之后,再用`passwd`设定帐号的密码.而可用`userdel`删除帐号。使用`useradd`指令所建立的帐号,实际上是保存在 `/etc/passwd`文本文件中。
+- `useradd [选项] 用户名`:创建用户账号。使用`useradd`指令所建立的帐号,实际上是保存在 `/etc/passwd`文本文件中。
+- `userdel [选项] 用户名`:删除用户帐号。
+- `usermod [选项] 用户名`:修改用户账号的属性和配置比如用户名、用户 ID、家目录。
+- `passwd [选项] 用户名`: 设置用户的认证信息,包括用户密码、密码过期时间等。。例如:`passwd -S 用户名` ,显示用户账号密码信息。`passwd -d 用户名`: 清除用户密码,会导致用户无法登录。`passwd 用户名`,修改用户密码,随后系统会提示输入新密码并确认密码。
+- `su [选项] 用户名`(su 即 Switch User,切换用户):在当前登录的用户和其他用户之间切换身份。
-`passwd`命令用于设置用户的认证信息,包括用户密码、密码过期时间等。系统管理者则能用它管理系统用户的密码。只有管理者可以指定用户名称,一般用户只能变更自己的密码。
-
-### 4.7. Linux 系统用户组的管理
+### 用户组管理
每个用户都有一个用户组,系统可以对一个用户组中的所有用户进行集中管理。不同 Linux 系统对用户组的规定有所不同,如 Linux 下的用户属于与它同名的用户组,这个用户组在创建用户时同时创建。
@@ -381,32 +332,98 @@ Linux 系统是一个多用户多任务的分时操作系统,任何一个要
**Linux 系统用户组的管理相关命令:**
-- `groupadd 选项 用户组` :增加一个新的用户组
-- `groupdel 用户组`:要删除一个已有的用户组
-- `groupmod 选项 用户组` : 修改用户组的属性
+- `groupadd [选项] 用户组` :增加一个新的用户组。
+- `groupdel 用户组`:要删除一个已有的用户组。
+- `groupmod [选项] 用户组` : 修改用户组的属性。
+
+### 系统状态
-### 4.8. 其他常用命令
+- `top [选项]`:用于实时查看系统的 CPU 使用率、内存使用率、进程信息等。
+- `htop [选项]`:类似于 `top`,但提供了更加交互式和友好的界面,可让用户交互式操作,支持颜色主题,可横向或纵向滚动浏览进程列表,并支持鼠标操作。
+- `uptime [选项]`:用于查看系统总共运行了多长时间、系统的平均负载等信息。
+- `vmstat [间隔时间] [重复次数]`:vmstat (Virtual Memory Statistics) 的含义为显示虚拟内存状态,但是它可以报告关于进程、内存、I/O 等系统整体运行状态。
+- `free [选项]`:用于查看系统的内存使用情况,包括已用内存、可用内存、缓冲区和缓存等。
+- `df [选项] [文件系统]`:用于查看系统的磁盘空间使用情况,包括磁盘空间的总量、已使用量和可用量等,可以指定文件系统上。例如:`df -a`,查看全部文件系统。
+- `du [选项] [文件]`:用于查看指定目录或文件的磁盘空间使用情况,可以指定不同的选项来控制输出格式和单位。
+- `sar [选项] [时间间隔] [重复次数]`:用于收集、报告和分析系统的性能统计信息,包括系统的 CPU 使用、内存使用、磁盘 I/O、网络活动等详细信息。它的特点是可以连续对系统取样,获得大量的取样数据。取样数据和分析的结果都可以存入文件,使用它时消耗的系统资源很小。
+- `ps [选项]`:用于查看系统中的进程信息,包括进程的 ID、状态、资源使用情况等。`ps -ef`/`ps -aux`:这两个命令都是查看当前系统正在运行进程,两者的区别是展示格式不同。如果想要查看特定的进程可以使用这样的格式:`ps aux|grep redis` (查看包括 redis 字符串的进程),也可使用 `pgrep redis -a`。
+- `systemctl [命令] [服务名称]`:用于管理系统的服务和单元,可以查看系统服务的状态、启动、停止、重启等。
-- **`pwd`:** 显示当前所在位置
+### 网络通信
+
+- `ping [选项] 目标主机`:测试与目标主机的网络连接。
+- `ifconfig` 或 `ip`:用于查看系统的网络接口信息,包括网络接口的 IP 地址、MAC 地址、状态等。
+- `netstat [选项]`:用于查看系统的网络连接状态和网络统计信息,可以查看当前的网络连接情况、监听端口、网络协议等。
+- `ss [选项]`:比 `netstat` 更好用,提供了更快速、更详细的网络连接信息。
+
+### 其他
- `sudo + 其他命令`:以系统管理者的身份执行指令,也就是说,经由 sudo 所执行的指令就好像是 root 亲自执行。
+- `grep 要搜索的字符串 要搜索的文件 --color`:搜索命令,--color 代表高亮显示。
+- `kill -9 进程的pid`:杀死进程(-9 表示强制终止)先用 ps 查找进程,然后用 kill 杀掉。
+- `shutdown`:`shutdown -h now`:指定现在立即关机;`shutdown +5 "System will shutdown after 5 minutes"`:指定 5 分钟后关机,同时送出警告信息给登入用户。
+- `reboot`:`reboot`:重开机。`reboot -w`:做个重开机的模拟(只有纪录并不会真的重开机)。
+
+## Linux 环境变量
+
+在 Linux 系统中,环境变量是用来定义系统运行环境的一些参数,比如每个用户不同的主目录(HOME)。
+
+### 环境变量分类
+
+按照作用域来分,环境变量可以简单的分成:
+
+- 用户级别环境变量 : `~/.bashrc`、`~/.bash_profile`。
+- 系统级别环境变量 : `/etc/bashrc`、`/etc/environment`、`/etc/profile`、`/etc/profile.d`。
+
+上述配置文件执行先后顺序为:`/etc/environment` –> `/etc/profile` –> `/etc/profile.d` –> `~/.bash_profile` –> `/etc/bashrc` –> `~/.bashrc`
+
+如果要修改系统级别环境变量文件,需要管理员具备对该文件的写入权限。
+
+建议用户级别环境变量在 `~/.bash_profile`中配置,系统级别环境变量在 `/etc/profile.d` 中配置。
+
+按照生命周期来分,环境变量可以简单的分成:
+
+- 永久的:需要用户修改相关的配置文件,变量永久生效。
+- 临时的:用户利用 `export` 命令,在当前终端下声明环境变量,关闭 shell 终端失效。
+
+### 读取环境变量
+
+通过 `export` 命令可以输出当前系统定义的所有环境变量。
+
+```bash
+# 列出当前的环境变量值
+export -p
+```
+
+除了 `export` 命令之外, `env` 命令也可以列出所有环境变量。
+
+`echo` 命令可以输出指定环境变量的值。
+
+```bash
+# 输出当前的PATH环境变量的值
+echo $PATH
+# 输出当前的HOME环境变量的值
+echo $HOME
+```
+
+### 环境变量修改
-- **`grep 要搜索的字符串 要搜索的文件 --color`:** 搜索命令,--color 代表高亮显示
+通过 `export`命令可以修改指定的环境变量。不过,这种方式修改环境变量仅仅对当前 shell 终端生效,关闭 shell 终端就会失效。修改完成之后,立即生效。
-- **`ps -ef`/`ps -aux`:** 这两个命令都是查看当前系统正在运行进程,两者的区别是展示格式不同。如果想要查看特定的进程可以使用这样的格式:**`ps aux|grep redis`** (查看包括 redis 字符串的进程),也可使用 `pgrep redis -a`。
+```bash
+export CLASSPATH=./JAVA_HOME/lib;$JAVA_HOME/jre/lib
+```
- 注意:如果直接用 ps((Process Status))命令,会显示所有进程的状态,通常结合 grep 命令查看某进程的状态。
+通过 `vim` 命令修改环境变量配置文件。这种方式修改环境变量永久有效。
-- **`kill -9 进程的pid`:** 杀死进程(-9 表示强制终止。)
+```bash
+vim ~/.bash_profile
+```
- 先用 ps 查找进程,然后用 kill 杀掉
+如果修改的是系统级别环境变量则对所有用户生效,如果修改的是用户级别环境变量则仅对当前用户生效。
-- **网络通信命令:**
- - 查看当前系统的网卡信息:ifconfig
- - 查看与某台机器的连接情况:ping
- - 查看当前系统的端口使用:netstat -an
-- **net-tools 和 iproute2 :**
- `net-tools`起源于 BSD 的 TCP/IP 工具箱,后来成为老版本 LinuxLinux 中配置网络功能的工具。但自 2001 年起,Linux 社区已经对其停止维护。同时,一些 Linux 发行版比如 Arch Linux 和 CentOS/RHEL 7 则已经完全抛弃了 net-tools,只支持`iproute2`。linux ip 命令类似于 ifconfig,但功能更强大,旨在替代它。更多详情请阅读[如何在 Linux 中使用 IP 命令和示例](https://linoxide.com/linux-command/use-ip-command-linux)
-- **`shutdown`:** `shutdown -h now`: 指定现在立即关机;`shutdown +5 "System will shutdown after 5 minutes"`:指定 5 分钟后关机,同时送出警告信息给登入用户。
+修改完成之后,需要 `source` 命令让其生效或者关闭 shell 终端重新登录。
-- **`reboot`:** **`reboot`:** 重开机。**`reboot -w`:** 做个重开机的模拟(只有纪录并不会真的重开机)。
+```bash
+source /etc/profile
+```
diff --git a/docs/cs-basics/operating-system/operating-system-basic-questions-01.md b/docs/cs-basics/operating-system/operating-system-basic-questions-01.md
new file mode 100644
index 0000000000000000000000000000000000000000..7498b83ebf4f6b18f8142d177f636dadff894e2d
--- /dev/null
+++ b/docs/cs-basics/operating-system/operating-system-basic-questions-01.md
@@ -0,0 +1,459 @@
+---
+title: 操作系统常见面试题总结(上)
+category: 计算机基础
+tag:
+ - 操作系统
+head:
+ - - meta
+ - name: keywords
+ content: 操作系统,进程,进程通信方式,死锁,操作系统内存管理,块表,多级页表,虚拟内存,页面置换算法
+ - - meta
+ - name: description
+ content: 很多读者抱怨计算操作系统的知识点比较繁杂,自己也没有多少耐心去看,但是面试的时候又经常会遇到。所以,我带着我整理好的操作系统的常见问题来啦!这篇文章总结了一些我觉得比较重要的操作系统相关的问题比如进程管理、内存管理、虚拟内存等等。
+---
+
+
+
+很多读者抱怨计算操作系统的知识点比较繁杂,自己也没有多少耐心去看,但是面试的时候又经常会遇到。所以,我带着我整理好的操作系统的常见问题来啦!这篇文章总结了一些我觉得比较重要的操作系统相关的问题比如 **用户态和内核态、系统调用、进程和线程、死锁、内存管理、虚拟内存、文件系统**等等。
+
+这篇文章只是对一些操作系统比较重要概念的一个概览,深入学习的话,建议大家还是老老实实地去看书。另外, 这篇文章的很多内容参考了《现代操作系统》第三版这本书,非常感谢。
+
+开始本文的内容之前,我们先聊聊为什么要学习操作系统。
+
+- **从对个人能力方面提升来说**:操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。比如说我们开发的系统使用的缓存(比如 Redis)和操作系统的高速缓存就很像。CPU 中的高速缓存有很多种,不过大部分都是为了解决 CPU 处理速度和内存处理速度不对等的问题。我们还可以把内存看作外存的高速缓存,程序运行的时候我们把外存的数据复制到内存,由于内存的处理速度远远高于外存,这样提高了处理速度。同样地,我们使用的 Redis 缓存就是为了解决程序处理速度和访问常规关系型数据库速度不对等的问题。高速缓存一般会按照局部性原理(2-8 原则)根据相应的淘汰算法保证缓存中的数据是经常会被访问的。我们平常使用的 Redis 缓存很多时候也会按照 2-8 原则去做,很多淘汰算法都和操作系统中的类似。既说了 2-8 原则,那就不得不提命中率了,这是所有缓存概念都通用的。简单来说也就是你要访问的数据有多少能直接在缓存中直接找到。命中率高的话,一般表明你的缓存设计比较合理,系统处理速度也相对较快。
+- **从面试角度来说**:尤其是校招,对于操作系统方面知识的考察是非常非常多的。
+
+**简单来说,学习操作系统能够提高自己思考的深度以及对技术的理解力,并且,操作系统方面的知识也是面试必备。**
+
+## 操作系统基础
+
+
+
+### 什么是操作系统?
+
+通过以下四点可以概括操作系统到底是什么:
+
+1. 操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机的基石。
+2. 操作系统本质上是一个运行在计算机上的软件程序 ,主要用于管理计算机硬件和软件资源。 举例:运行在你电脑上的所有应用程序都通过操作系统来调用系统内存以及磁盘等等硬件。
+3. 操作系统存在屏蔽了硬件层的复杂性。 操作系统就像是硬件使用的负责人,统筹着各种相关事项。
+4. 操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。
+
+很多人容易把操作系统的内核(Kernel)和中央处理器(CPU,Central Processing Unit)弄混。你可以简单从下面两点来区别:
+
+1. 操作系统的内核(Kernel)属于操作系统层面,而 CPU 属于硬件。
+2. CPU 主要提供运算,处理各种指令的能力。内核(Kernel)主要负责系统管理比如内存管理,它屏蔽了对硬件的操作。
+
+下图清晰说明了应用程序、内核、CPU 这三者的关系。
+
+
+
+### 操作系统主要有哪些功能?
+
+从资源管理的角度来看,操作系统有 6 大功能:
+
+1. **进程和线程的管理**:进程的创建、撤销、阻塞、唤醒,进程间的通信等。
+2. **存储管理**:内存的分配和管理、外存(磁盘等)的分配和管理等。
+3. **文件管理**:文件的读、写、创建及删除等。
+4. **设备管理**:完成设备(输入输出设备和外部存储设备等)的请求或释放,以及设备启动等功能。
+5. **网络管理**:操作系统负责管理计算机网络的使用。网络是计算机系统中连接不同计算机的方式,操作系统需要管理计算机网络的配置、连接、通信和安全等,以提供高效可靠的网络服务。
+6. **安全管理**:用户的身份认证、访问控制、文件加密等,以防止非法用户对系统资源的访问和操作。
+
+### 常见的操作系统有哪些?
+
+#### Windows
+
+目前最流行的个人桌面操作系统 ,不做多的介绍,大家都清楚。界面简单易操作,软件生态非常好。
+
+_玩玩电脑游戏还是必须要有 Windows 的,所以我现在是一台 Windows 用于玩游戏,一台 Mac 用于平时日常开发和学习使用。_
+
+
+
+#### Unix
+
+最早的多用户、多任务操作系统 。后面崛起的 Linux 在很多方面都参考了 Unix。
+
+目前这款操作系统已经逐渐逐渐退出操作系统的舞台。
+
+
+
+#### Linux
+
+**Linux 是一套免费使用、开源的类 Unix 操作系统。** Linux 存在着许多不同的发行版本,但它们都使用了 **Linux 内核** 。
+
+> 严格来讲,Linux 这个词本身只表示 Linux 内核,在 GNU/Linux 系统中,Linux 实际就是 Linux 内核,而该系统的其余部分主要是由 GNU 工程编写和提供的程序组成。单独的 Linux 内核并不能成为一个可以正常工作的操作系统。
+>
+> **很多人更倾向使用 “GNU/Linux” 一词来表达人们通常所说的 “Linux”。**
+
+
+
+#### Mac OS
+
+苹果自家的操作系统,编程体验和 Linux 相当,但是界面、软件生态以及用户体验各方面都要比 Linux 操作系统更好。
+
+
+
+### 用户态和内核态
+
+#### 什么是用户态和内核态?
+
+根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:
+
+- **用户态(User Mode)** : 用户态运行的进程可以直接读取用户程序的数据,拥有较低的权限。当应用程序需要执行某些需要特殊权限的操作,例如读写磁盘、网络通信等,就需要向操作系统发起系统调用请求,进入内核态。
+- **内核态(Kernel Mode)**:内核态运行的进程几乎可以访问计算机的任何资源包括系统的内存空间、设备、驱动程序等,不受限制,拥有非常高的权限。当操作系统接收到进程的系统调用请求时,就会从用户态切换到内核态,执行相应的系统调用,并将结果返回给进程,最后再从内核态切换回用户态。
+
+
+
+内核态相比用户态拥有更高的特权级别,因此能够执行更底层、更敏感的操作。不过,由于进入内核态需要付出较高的开销(需要进行一系列的上下文切换和权限检查),应该尽量减少进入内核态的次数,以提高系统的性能和稳定性。
+
+#### 为什么要有用户态和内核态?只有一个内核态不行么?
+
+- 在 CPU 的所有指令中,有一些指令是比较危险的比如内存分配、设置时钟、IO 处理等,如果所有的程序都能使用这些指令的话,会对系统的正常运行造成灾难性地影响。因此,我们需要限制这些危险指令只能内核态运行。这些只能由操作系统内核态执行的指令也被叫做 **特权指令** 。
+- 如果计算机系统中只有一个内核态,那么所有程序或进程都必须共享系统资源,例如内存、CPU、硬盘等,这将导致系统资源的竞争和冲突,从而影响系统性能和效率。并且,这样也会让系统的安全性降低,毕竟所有程序或进程都具有相同的特权级别和访问权限。
+
+因此,同时具有用户态和内核态主要是为了保证计算机系统的安全性、稳定性和性能。
+
+#### 用户态和内核态是如何切换的?
+
+
+
+用户态切换到内核态的 3 种方式:
+
+1. **系统调用(Trap)**:用户态进程 **主动** 要求切换到内核态的一种方式,主要是为了使用内核态才能做的事情比如读取磁盘资源。系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现。
+2. **中断(Interrupt)**:当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
+3. **异常(Exception)**:当 CPU 在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
+
+在系统的处理上,中断和异常类似,都是通过中断向量表来找到相应的处理程序进行处理。区别在于,中断来自处理器外部,不是由任何一条专门的指令造成,而异常是执行当前指令的结果。
+
+### 系统调用
+
+#### 什么是系统调用?
+
+我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的内核态级别的子功能咋办呢?那就需要系统调用了!
+
+也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
+
+
+
+这些系统调用按功能大致可分为如下几类:
+
+- 设备管理:完成设备(输入输出设备和外部存储设备等)的请求或释放,以及设备启动等功能。
+- 文件管理:完成文件的读、写、创建及删除等功能。
+- 进程管理:进程的创建、撤销、阻塞、唤醒,进程间的通信等功能。
+- 内存管理:完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
+
+系统调用和普通库函数调用非常相似,只是系统调用由操作系统内核提供,运行于内核态,而普通的库函数调用由函数库或用户自己提供,运行于用户态。
+
+总结:系统调用是应用程序与操作系统之间进行交互的一种方式,通过系统调用,应用程序可以访问操作系统底层资源例如文件、设备、网络等。
+
+#### 系统调用的过程了解吗?
+
+系统调用的过程可以简单分为以下几个步骤:
+
+1. 用户态的程序发起系统调用,因为系统调用中涉及一些特权指令(只能由操作系统内核态执行的指令),用户态程序权限不足,因此会中断执行,也就是 Trap(Trap 是一种中断)。
+2. 发生中断后,当前 CPU 执行的程序会中断,跳转到中断处理程序。内核程序开始执行,也就是开始处理系统调用。
+3. 内核处理完成后,主动触发 Trap,这样会再次发生中断,切换回用户态工作。
+
+
+
+## 进程和线程
+
+### 什么是进程和线程?
+
+- **进程(Process)** 是指计算机中正在运行的一个程序实例。举例:你打开的微信就是一个进程。
+- **线程(Thread)** 也被称为轻量级进程,更加轻量。多个线程可以在同一个进程中同时执行,并且共享进程的资源比如内存空间、文件句柄、网络连接等。举例:你打开的微信里就有一个线程专门用来拉取别人发你的最新的消息。
+
+### 进程和线程的区别是什么?
+
+下图是 Java 内存区域,我们从 JVM 的角度来说一下线程和进程之间的关系吧!
+
+
+
+从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**。
+
+**总结:**
+
+- 线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。
+- 线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。
+- 线程执行开销小,但不利于资源的管理和保护;而进程正相反。
+
+### 有了进程为什么还需要线程?
+
+- 进程切换是一个开销很大的操作,线程切换的成本较低。
+- 线程更轻量,一个进程可以创建多个线程。
+- 多个线程可以并发处理不同的任务,更有效地利用了多处理器和多核计算机。而进程只能在一个时间干一件事,如果在执行过程中遇到阻塞问题比如 IO 阻塞就会挂起直到结果返回。
+- 同一进程内的线程共享内存和文件,因此它们之间相互通信无须调用内核。
+
+### 为什么要使用多线程?
+
+先从总体上来说:
+
+- **从计算机底层来说:** 线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
+- **从当代互联网发展趋势来说:** 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
+
+再深入到计算机底层来探讨:
+
+- **单核时代**:在单核时代多线程主要是为了提高单进程利用 CPU 和 IO 系统的效率。 假设只运行了一个 Java 进程的情况,当我们请求 IO 的时候,如果 Java 进程中只有一个线程,此线程被 IO 阻塞则整个进程被阻塞。CPU 和 IO 设备只有一个在运行,那么可以简单地说系统整体效率只有 50%。当使用多线程的时候,一个线程被 IO 阻塞,其他线程还可以继续使用 CPU。从而提高了 Java 进程利用系统资源的整体效率。
+- **多核时代**: 多核时代多线程主要是为了提高进程利用多核 CPU 的能力。举个例子:假如我们要计算一个复杂的任务,我们只用一个线程的话,不论系统有几个 CPU 核心,都只会有一个 CPU 核心被利用到。而创建多个线程,这些线程可以被映射到底层多个 CPU 上执行,在任务中的多个线程没有资源竞争的情况下,任务执行的效率会有显著性的提高,约等于(单核时执行时间/CPU 核心数)。
+
+### 线程间的同步的方式有哪些?
+
+线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。
+
+下面是几种常见的线程同步的方式:
+
+1. **互斥锁(Mutex)**:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 `synchronized` 关键词和各种 `Lock` 都是这种机制。
+2. **读写锁(Read-Write Lock)**:允许多个线程同时读取共享资源,但只有一个线程可以对共享资源进行写操作。
+3. **信号量(Semaphore)**:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
+4. **屏障(Barrier)**:屏障是一种同步原语,用于等待多个线程到达某个点再一起继续执行。当一个线程到达屏障时,它会停止执行并等待其他线程到达屏障,直到所有线程都到达屏障后,它们才会一起继续执行。比如 Java 中的 `CyclicBarrier` 是这种机制。
+5. **事件(Event)** :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作。
+
+### PCB 是什么?包含哪些信息?
+
+**PCB(Process Control Block)** 即进程控制块,是操作系统中用来管理和跟踪进程的数据结构,每个进程都对应着一个独立的 PCB。你可以将 PCB 视为进程的大脑。
+
+当操作系统创建一个新进程时,会为该进程分配一个唯一的进程 ID,并且为该进程创建一个对应的进程控制块。当进程执行时,PCB 中的信息会不断变化,操作系统会根据这些信息来管理和调度进程。
+
+PCB 主要包含下面几部分的内容:
+
+- 进程的描述信息,包括进程的名称、标识符等等;
+- 进程的调度信息,包括进程阻塞原因、进程状态(就绪、运行、阻塞等)、进程优先级(标识进程的重要程度)等等;
+- 进程对资源的需求情况,包括 CPU 时间、内存空间、I/O 设备等等。
+- 进程打开的文件信息,包括文件描述符、文件类型、打开模式等等。
+- 处理机的状态信息(由处理机的各种寄存器中的内容组成的),包括通用寄存器、指令计数器、程序状态字 PSW、用户栈指针。
+- ......
+
+### 进程有哪几种状态?
+
+我们一般把进程大致分为 5 种状态,这一点和线程很像!
+
+- **创建状态(new)**:进程正在被创建,尚未到就绪状态。
+- **就绪状态(ready)**:进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。
+- **运行状态(running)**:进程正在处理器上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。
+- **阻塞状态(waiting)**:又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
+- **结束状态(terminated)**:进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。
+
+
+
+### 进程间的通信方式有哪些?
+
+> 下面这部分总结参考了:[《进程间通信 IPC (InterProcess Communication)》](https://www.jianshu.com/p/c1015f5ffa74) 这篇文章,推荐阅读,总结的非常不错。
+
+1. **管道/匿名管道(Pipes)**:用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
+2. **有名管道(Named Pipes)** : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循 **先进先出(First In First Out)** 。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
+3. **信号(Signal)**:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
+4. **消息队列(Message Queuing)**:消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显式地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。**消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺点。**
+5. **信号量(Semaphores)**:信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
+6. **共享内存(Shared memory)**:使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
+7. **套接字(Sockets)** : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
+
+### 进程的调度算法有哪些?
+
+
+
+这是一个很重要的知识点!为了确定首先执行哪个进程以及最后执行哪个进程以实现最大 CPU 利用率,计算机科学家已经定义了一些算法,它们是:
+
+- **先到先服务调度算法(FCFS,First Come, First Served)** : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
+- **短作业优先的调度算法(SJF,Shortest Job First)** : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
+- **时间片轮转调度算法(RR,Round-Robin)** : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
+- **多级反馈队列调度算法(MFQ,Multi-level Feedback Queue)**:前面介绍的几种进程调度的算法都有一定的局限性。如**短进程优先的调度算法,仅照顾了短进程而忽略了长进程** 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前**被公认的一种较好的进程调度算法**,UNIX 操作系统采取的便是这种调度算法。
+- **优先级调度算法(Priority)**:为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
+
+### 什么是僵尸进程和孤儿进程?
+
+在 Unix/Linux 系统中,子进程通常是通过 fork()系统调用创建的,该调用会创建一个新的进程,该进程是原有进程的一个副本。子进程和父进程的运行是相互独立的,它们各自拥有自己的 PCB,即使父进程结束了,子进程仍然可以继续运行。
+
+当一个进程调用 exit()系统调用结束自己的生命时,内核会释放该进程的所有资源,包括打开的文件、占用的内存等,但是该进程对应的 PCB 依然存在于系统中。这些信息只有在父进程调用 wait()或 waitpid()系统调用时才会被释放,以便让父进程得到子进程的状态信息。
+
+这样的设计可以让父进程在子进程结束时得到子进程的状态信息,并且可以防止出现“僵尸进程”(即子进程结束后 PCB 仍然存在但父进程无法得到状态信息的情况)。
+
+- **僵尸进程**:子进程已经终止,但是其父进程仍在运行,且父进程没有调用 wait()或 waitpid()等系统调用来获取子进程的状态信息,释放子进程占用的资源,导致子进程的 PCB 依然存在于系统中,但无法被进一步使用。这种情况下,子进程被称为“僵尸进程”。避免僵尸进程的产生,父进程需要及时调用 wait()或 waitpid()系统调用来回收子进程。
+- **孤儿进程**:一个进程的父进程已经终止或者不存在,但是该进程仍在运行。这种情况下,该进程就是孤儿进程。孤儿进程通常是由于父进程意外终止或未及时调用 wait()或 waitpid()等系统调用来回收子进程导致的。为了避免孤儿进程占用系统资源,操作系统会将孤儿进程的父进程设置为 init 进程(进程号为 1),由 init 进程来回收孤儿进程的资源。
+
+### 如何查看是否有僵尸进程?
+
+Linux 下可以使用 Top 命令查找,`zombie` 值表示僵尸进程的数量,为 0 则代表没有僵尸进程。
+
+
+
+下面这个命令可以定位僵尸进程以及该僵尸进程的父进程:
+
+```bash
+ps -A -ostat,ppid,pid,cmd |grep -e '^[Zz]'
+```
+
+## 死锁
+
+### 什么是死锁?
+
+死锁(Deadlock)描述的是这样一种情况:多个进程/线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于进程/线程被无限期地阻塞,因此程序不可能正常终止。
+
+### 能列举一个操作系统发生死锁的例子吗?
+
+假设有两个进程 A 和 B,以及两个资源 X 和 Y,它们的分配情况如下:
+
+| 进程 | 占用资源 | 需求资源 |
+| ---- | -------- | -------- |
+| A | X | Y |
+| B | Y | X |
+
+此时,进程 A 占用资源 X 并且请求资源 Y,而进程 B 已经占用了资源 Y 并请求资源 X。两个进程都在等待对方释放资源,无法继续执行,陷入了死锁状态。
+
+### 产生死锁的四个必要条件是什么?
+
+1. **互斥**:资源必须处于非共享模式,即一次只有一个进程可以使用。如果另一进程申请该资源,那么必须等待直到该资源被释放为止。
+2. **占有并等待**:一个进程至少应该占有一个资源,并等待另一资源,而该资源被其他进程所占有。
+3. **非抢占**:资源不能被抢占。只能在持有资源的进程完成任务后,该资源才会被释放。
+4. **循环等待**:有一组等待进程 `{P0, P1,..., Pn}`, `P0` 等待的资源被 `P1` 占有,`P1` 等待的资源被 `P2` 占有,......,`Pn-1` 等待的资源被 `Pn` 占有,`Pn` 等待的资源被 `P0` 占有。
+
+**注意 ⚠️**:这四个条件是产生死锁的 **必要条件** ,也就是说只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
+
+下面是百度百科对必要条件的解释:
+
+> 如果没有事物情况 A,则必然没有事物情况 B,也就是说如果有事物情况 B 则一定有事物情况 A,那么 A 就是 B 的必要条件。从逻辑学上看,B 能推导出 A,A 就是 B 的必要条件,等价于 B 是 A 的充分条件。
+
+### 能写一个模拟产生死锁的代码吗?
+
+下面通过一个实际的例子来模拟下图展示的线程死锁:
+
+
+
+```java
+public class DeadLockDemo {
+ private static Object resource1 = new Object();//资源 1
+ private static Object resource2 = new Object();//资源 2
+
+ public static void main(String[] args) {
+ new Thread(() -> {
+ synchronized (resource1) {
+ System.out.println(Thread.currentThread() + "get resource1");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println(Thread.currentThread() + "waiting get resource2");
+ synchronized (resource2) {
+ System.out.println(Thread.currentThread() + "get resource2");
+ }
+ }
+ }, "线程 1").start();
+
+ new Thread(() -> {
+ synchronized (resource2) {
+ System.out.println(Thread.currentThread() + "get resource2");
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ System.out.println(Thread.currentThread() + "waiting get resource1");
+ synchronized (resource1) {
+ System.out.println(Thread.currentThread() + "get resource1");
+ }
+ }
+ }, "线程 2").start();
+ }
+}
+```
+
+Output
+
+```text
+Thread[线程 1,5,main]get resource1
+Thread[线程 2,5,main]get resource2
+Thread[线程 1,5,main]waiting get resource2
+Thread[线程 2,5,main]waiting get resource1
+```
+
+线程 A 通过 `synchronized (resource1)` 获得 `resource1` 的监视器锁,然后通过`Thread.sleep(1000);`让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 `resource2` 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。
+
+### 解决死锁的方法
+
+解决死锁的方法可以从多个角度去分析,一般的情况下,有**预防,避免,检测和解除四种**。
+
+- **预防** 是采用某种策略,**限制并发进程对资源的请求**,从而使得死锁的必要条件在系统执行的任何时间上都不满足。
+
+- **避免**则是系统在分配资源时,根据资源的使用情况**提前做出预测**,从而**避免死锁的发生**
+
+- **检测**是指系统设有**专门的机构**,当死锁发生时,该机构能够检测死锁的发生,并精确地确定与死锁有关的进程和资源。
+- **解除** 是与检测相配套的一种措施,用于**将进程从死锁状态下解脱出来**。
+
+#### 死锁的预防
+
+死锁四大必要条件上面都已经列出来了,很显然,只要破坏四个必要条件中的任何一个就能够预防死锁的发生。
+
+破坏第一个条件 **互斥条件**:使得资源是可以同时访问的,这是种简单的方法,磁盘就可以用这种方法管理,但是我们要知道,有很多资源 **往往是不能同时访问的** ,所以这种做法在大多数的场合是行不通的。
+
+破坏第三个条件 **非抢占**:也就是说可以采用 **剥夺式调度算法**,但剥夺式调度方法目前一般仅适用于 **主存资源** 和 **处理器资源** 的分配,并不适用于所有的资源,会导致 **资源利用率下降**。
+
+所以一般比较实用的 **预防死锁的方法**,是通过考虑破坏第二个条件和第四个条件。
+
+**1、静态分配策略**
+
+静态分配策略可以破坏死锁产生的第二个条件(占有并等待)。所谓静态分配策略,就是指一个进程必须在执行前就申请到它所需要的全部资源,并且知道它所要的资源都得到满足之后才开始执行。进程要么占有所有的资源然后开始执行,要么不占有资源,不会出现占有一些资源等待一些资源的情况。
+
+静态分配策略逻辑简单,实现也很容易,但这种策略 **严重地降低了资源利用率**,因为在每个进程所占有的资源中,有些资源是在比较靠后的执行时间里采用的,甚至有些资源是在额外的情况下才使用的,这样就可能造成一个进程占有了一些 **几乎不用的资源而使其他需要该资源的进程产生等待** 的情况。
+
+**2、层次分配策略**
+
+层次分配策略破坏了产生死锁的第四个条件(循环等待)。在层次分配策略下,所有的资源被分成了多个层次,一个进程得到某一次的一个资源后,它只能再申请较高一层的资源;当一个进程要释放某层的一个资源时,必须先释放所占用的较高层的资源,按这种策略,是不可能出现循环等待链的,因为那样的话,就出现了已经申请了较高层的资源,反而去申请了较低层的资源,不符合层次分配策略,证明略。
+
+#### 死锁的避免
+
+上面提到的 **破坏** 死锁产生的四个必要条件之一就可以成功 **预防系统发生死锁** ,但是会导致 **低效的进程运行** 和 **资源使用率** 。而死锁的避免相反,它的角度是允许系统中**同时存在四个必要条件** ,只要掌握并发进程中与每个进程有关的资源动态申请情况,做出 **明智和合理的选择** ,仍然可以避免死锁,因为四大条件仅仅是产生死锁的必要条件。
+
+我们将系统的状态分为 **安全状态** 和 **不安全状态** ,每当在未申请者分配资源前先测试系统状态,若把系统资源分配给申请者会产生死锁,则拒绝分配,否则接受申请,并为它分配资源。
+
+> 如果操作系统能够保证所有的进程在有限的时间内得到需要的全部资源,则称系统处于安全状态,否则说系统是不安全的。很显然,系统处于安全状态则不会发生死锁,系统若处于不安全状态则可能发生死锁。
+
+那么如何保证系统保持在安全状态呢?通过算法,其中最具有代表性的 **避免死锁算法** 就是 Dijkstra 的银行家算法,银行家算法用一句话表达就是:当一个进程申请使用资源的时候,**银行家算法** 通过先 **试探** 分配给该进程资源,然后通过 **安全性算法** 判断分配后系统是否处于安全状态,若不安全则试探分配作废,让该进程继续等待,若能够进入到安全的状态,则就 **真的分配资源给该进程**。
+
+银行家算法详情可见:[《一句话+一张图说清楚——银行家算法》](https://blog.csdn.net/qq_33414271/article/details/80245715) 。
+
+操作系统教程书中讲述的银行家算法也比较清晰,可以一看.
+
+死锁的避免(银行家算法)改善了 **资源使用率低的问题** ,但是它要不断地检测每个进程对各类资源的占用和申请情况,以及做 **安全性检查** ,需要花费较多的时间。
+
+#### 死锁的检测
+
+对资源的分配加以限制可以 **预防和避免** 死锁的发生,但是都不利于各进程对系统资源的**充分共享**。解决死锁问题的另一条途径是 **死锁检测和解除** (这里突然联想到了乐观锁和悲观锁,感觉死锁的检测和解除就像是 **乐观锁** ,分配资源时不去提前管会不会发生死锁了,等到真的死锁出现了再来解决嘛,而 **死锁的预防和避免** 更像是悲观锁,总是觉得死锁会出现,所以在分配资源的时候就很谨慎)。
+
+这种方法对资源的分配不加以任何限制,也不采取死锁避免措施,但系统 **定时地运行一个 “死锁检测”** 的程序,判断系统内是否出现死锁,如果检测到系统发生了死锁,再采取措施去解除它。
+
+##### 进程-资源分配图
+
+操作系统中的每一刻时刻的**系统状态**都可以用**进程-资源分配图**来表示,进程-资源分配图是描述进程和资源申请及分配关系的一种有向图,可用于**检测系统是否处于死锁状态**。
+
+用一个方框表示每一个资源类,方框中的黑点表示该资源类中的各个资源,每个键进程用一个圆圈表示,用 **有向边** 来表示**进程申请资源和资源被分配的情况**。
+
+图中 2-21 是**进程-资源分配图**的一个例子,其中共有三个资源类,每个进程的资源占有和申请情况已清楚地表示在图中。在这个例子中,由于存在 **占有和等待资源的环路** ,导致一组进程永远处于等待资源的状态,发生了 **死锁**。
+
+
+
+进程-资源分配图中存在环路并不一定是发生了死锁。因为循环等待资源仅仅是死锁发生的必要条件,而不是充分条件。图 2-22 便是一个有环路而无死锁的例子。虽然进程 P1 和进程 P3 分别占用了一个资源 R1 和一个资源 R2,并且因为等待另一个资源 R2 和另一个资源 R1 形成了环路,但进程 P2 和进程 P4 分别占有了一个资源 R1 和一个资源 R2,它们申请的资源得到了满足,在有限的时间里会归还资源,于是进程 P1 或 P3 都能获得另一个所需的资源,环路自动解除,系统也就不存在死锁状态了。
+
+##### 死锁检测步骤
+
+知道了死锁检测的原理,我们可以利用下列步骤编写一个 **死锁检测** 程序,检测系统是否产生了死锁。
+
+1. 如果进程-资源分配图中无环路,则此时系统没有发生死锁
+2. 如果进程-资源分配图中有环路,且每个资源类仅有一个资源,则系统中已经发生了死锁。
+3. 如果进程-资源分配图中有环路,且涉及到的资源类有多个资源,此时系统未必会发生死锁。如果能在进程-资源分配图中找出一个 **既不阻塞又非独立的进程** ,该进程能够在有限的时间内归还占有的资源,也就是把边给消除掉了,重复此过程,直到能在有限的时间内 **消除所有的边** ,则不会发生死锁,否则会发生死锁。(消除边的过程类似于 **拓扑排序**)
+
+#### 死锁的解除
+
+当死锁检测程序检测到存在死锁发生时,应设法让其解除,让系统从死锁状态中恢复过来,常用的解除死锁的方法有以下四种:
+
+1. **立即结束所有进程的执行,重新启动操作系统**:这种方法简单,但以前所在的工作全部作废,损失很大。
+2. **撤销涉及死锁的所有进程,解除死锁后继续运行**:这种方法能彻底打破**死锁的循环等待**条件,但将付出很大代价,例如有些进程可能已经计算了很长时间,由于被撤销而使产生的部分结果也被消除了,再重新执行时还要再次进行计算。
+3. **逐个撤销涉及死锁的进程,回收其资源直至死锁解除。**
+4. **抢占资源**:从涉及死锁的一个或几个进程中抢占资源,把夺得的资源再分配给涉及死锁的进程直至死锁解除。
+
+## 参考
+
+- 《计算机操作系统—汤小丹》第四版
+- 《深入理解计算机系统》
+- 《重学操作系统》
+- 操作系统为什么要分用户态和内核态:https://blog.csdn.net/chen134225/article/details/81783980
+- 从根上理解用户态与内核态:https://juejin.cn/post/6923863670132850701
+- 什么是僵尸进程与孤儿进程:https://blog.csdn.net/a745233700/article/details/120715371
diff --git a/docs/cs-basics/operating-system/operating-system-basic-questions-02.md b/docs/cs-basics/operating-system/operating-system-basic-questions-02.md
new file mode 100644
index 0000000000000000000000000000000000000000..2b2e014ab2469f0dc54c2b8ee00db742d49c96ca
--- /dev/null
+++ b/docs/cs-basics/operating-system/operating-system-basic-questions-02.md
@@ -0,0 +1,411 @@
+---
+title: 操作系统常见面试题总结(下)
+category: 计算机基础
+tag:
+ - 操作系统
+head:
+ - - meta
+ - name: keywords
+ content: 操作系统,进程,进程通信方式,死锁,操作系统内存管理,块表,多级页表,虚拟内存,页面置换算法
+ - - meta
+ - name: description
+ content: 很多读者抱怨计算操作系统的知识点比较繁杂,自己也没有多少耐心去看,但是面试的时候又经常会遇到。所以,我带着我整理好的操作系统的常见问题来啦!这篇文章总结了一些我觉得比较重要的操作系统相关的问题比如进程管理、内存管理、虚拟内存等等。
+---
+
+## 内存管理
+
+### 内存管理主要做了什么?
+
+
+
+操作系统的内存管理非常重要,主要负责下面这些事情:
+
+- **内存的分配与回收**:对进程所需的内存进行分配和释放,malloc 函数:申请内存,free 函数:释放内存。
+- **地址转换**:将程序中的虚拟地址转换成内存中的物理地址。
+- **内存扩充**:当系统没有足够的内存时,利用虚拟内存技术或自动覆盖技术,从逻辑上扩充内存。
+- **内存映射**:将一个文件直接映射到进程的进程空间中,这样可以通过内存指针用读写内存的办法直接存取文件内容,速度更快。
+- **内存优化**:通过调整内存分配策略和回收算法来优化内存使用效率。
+- **内存安全**:保证进程之间使用内存互不干扰,避免一些恶意程序通过修改内存来破坏系统的安全性。
+- ......
+
+### 什么是内存碎片?
+
+内存碎片是由内存的申请和释放产生的,通常分为下面两种:
+
+- **内部内存碎片(Internal Memory Fragmentation,简称为内存碎片)**:已经分配给进程使用但未被使用的内存。导致内部内存碎片的主要原因是,当采用固定比例比如 2 的幂次方进行内存分配时,进程所分配的内存可能会比其实际所需要的大。举个例子,一个进程只需要 65 字节的内存,但为其分配了 128(2^7) 大小的内存,那 63 字节的内存就成为了内部内存碎片。
+- **外部内存碎片(External Memory Fragmentation,简称为外部碎片)**:由于未分配的连续内存区域太小,以至于不能满足任意进程所需要的内存分配请求,这些小片段且不连续的内存空间被称为外部碎片。也就是说,外部内存碎片指的是那些并未分配给进程但又不能使用的内存。我们后面介绍的分段机制就会导致外部内存碎片。
+
+
+
+内存碎片会导致内存利用率下降,如何减少内存碎片是内存管理要非常重视的一件事情。
+
+### 常见的内存管理方式有哪些?
+
+内存管理方式可以简单分为下面两种:
+
+- **连续内存管理**:为一个用户程序分配一个连续的内存空间,内存利用率一般不高。
+- **非连续内存管理**:允许一个程序使用的内存分布在离散或者说不相邻的内存中,相对更加灵活一些。
+
+#### 连续内存管理
+
+**块式管理** 是早期计算机操作系统的一种连续内存管理方式,存在严重的内存碎片问题。块式管理会将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,我们称之为内部内存碎片。除了内部内存碎片之外,由于两个内存块之间可能还会有外部内存碎片,这些不连续的外部内存碎片由于太小了无法再进行分配。
+
+在 Linux 系统中,连续内存管理采用了 **伙伴系统(Buddy System)算法** 来实现,这是一种经典的连续内存分配算法,可以有效解决外部内存碎片的问题。伙伴系统的主要思想是将内存按 2 的幂次划分(每一块内存大小都是 2 的幂次比如 2^6=64 KB),并将相邻的内存块组合成一对伙伴(注意:**必须是相邻的才是伙伴**)。
+
+当进行内存分配时,伙伴系统会尝试找到大小最合适的内存块。如果找到的内存块过大,就将其一分为二,分成两个大小相等的伙伴块。如果还是大的话,就继续切分,直到到达合适的大小为止。
+
+假设两块相邻的内存块都被释放,系统会将这两个内存块合并,进而形成一个更大的内存块,以便后续的内存分配。这样就可以减少内存碎片的问题,提高内存利用率。
+
+
+
+虽然解决了外部内存碎片的问题,但伙伴系统仍然存在内存利用率不高的问题(内部内存碎片)。这主要是因为伙伴系统只能分配大小为 2^n 的内存块,因此当需要分配的内存大小不是 2^n 的整数倍时,会浪费一定的内存空间。举个例子:如果要分配 65 大小的内存快,依然需要分配 2^7=128 大小的内存块。
+
+
+
+对于内部内存碎片的问题,Linux 采用 **SLAB** 进行解决。由于这部分内容不是本篇文章的重点,这里就不详细介绍了。
+
+#### 非连续内存管理
+
+非连续内存管理存在下面 3 种方式:
+
+- **段式管理**:以段(—段连续的物理内存)的形式管理/分配物理内存。应用程序的虚拟地址空间被分为大小不等的段,段是有实际意义的,每个段定义了一组逻辑信息,例如有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。
+- **页式管理**:把物理内存分为连续等长的物理页,应用程序的虚拟地址空间划也被分为连续等长的虚拟页,现代操作系统广泛使用的一种内存管理方式。
+- **段页式管理机制**:结合了段式管理和页式管理的一种内存管理机制,把物理内存先分成若干段,每个段又继续分成若干大小相等的页。
+
+### 虚拟内存
+
+#### 什么是虚拟内存?有什么用?
+
+**虚拟内存(Virtual Memory)** 是计算机系统内存管理非常重要的一个技术,本质上来说它只是逻辑存在的,是一个假想出来的内存空间,主要作用是作为进程访问主存(物理内存)的桥梁并简化内存管理。
+
+
+
+总结来说,虚拟内存主要提供了下面这些能力:
+
+- **隔离进程**:物理内存通过虚拟地址空间访问,虚拟地址空间与进程一一对应。每个进程都认为自己拥有了整个物理内存,进程之间彼此隔离,一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。
+- **提升物理内存利用率**:有了虚拟地址空间后,操作系统只需要将进程当前正在使用的部分数据或指令加载入物理内存。
+- **简化内存管理**:进程都有一个一致且私有的虚拟地址空间,程序员不用和真正的物理内存打交道,而是借助虚拟地址空间访问物理内存,从而简化了内存管理。
+- **多个进程共享物理内存**:进程在运行过程中,会加载许多操作系统的动态库。这些库对于每个进程而言都是公用的,它们在内存中实际只会加载一份,这部分称为共享内存。
+- **提高内存使用安全性**:控制进程对物理内存的访问,隔离不同进程的访问权限,提高系统的安全性。
+- **提供更大的可使用内存空间**:可以让程序拥有超过系统物理内存大小的可用内存空间。这是因为当物理内存不够用时,可以利用磁盘充当,将物理内存页(通常大小为 4 KB)保存到磁盘文件(会影响读写速度),数据或代码页会根据需要在物理内存与磁盘之间移动。
+
+#### 没有虚拟内存有什么问题?
+
+如果没有虚拟内存的话,程序直接访问和操作的都是物理内存,看似少了一层中介,但多了很多问题。
+
+**具体有什么问题呢?** 这里举几个例子说明(参考虚拟内存提供的能力回答这个问题):
+
+1. 用户程序可以访问任意物理内存,可能会不小心操作到系统运行必需的内存,进而造成操作系统崩溃,严重影响系统的安全。
+2. 同时运行多个程序容易崩溃。比如你想同时运行一个微信和一个 QQ 音乐,微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就可能会造成微信这个程序会崩溃。
+3. 程序运行过程中使用的所有数据或指令都要载入物理内存,根据局部性原理,其中很大一部分可能都不会用到,白白占用了宝贵的物理内存资源。
+4. ......
+
+#### 什么是虚拟地址和物理地址?
+
+**物理地址(Physical Address)** 是真正的物理内存中地址,更具体点来说是内存地址寄存器中的地址。程序中访问的内存地址不是物理地址,而是 **虚拟地址(Virtual Address)** 。
+
+也就是说,我们编程开发的时候实际就是在和虚拟地址打交道。比如在 C 语言中,指针里面存储的数值就可以理解成为内存里的一个地址,这个地址也就是我们说的虚拟地址。
+
+操作系统一般通过 CPU 芯片中的一个重要组件 **MMU(Memory Management Unit,内存管理单元)** 将虚拟地址转换为物理地址,这个过程被称为 **地址翻译/地址转换(Address Translation)** 。
+
+
+
+通过 MMU 将虚拟地址转换为物理地址后,再通过总线传到物理内存设备,进而完成相应的物理内存读写请求。
+
+MMU 将虚拟地址翻译为物理地址的主要机制有两种: **分段机制** 和 **分页机制** 。
+
+#### 什么是虚拟地址空间和物理地址空间?
+
+- 虚拟地址空间是虚拟地址的集合,是虚拟内存的范围。每一个进程都有一个一致且私有的虚拟地址空间。
+- 物理地址空间是物理地址的集合,是物理内存的范围。
+
+#### 虚拟地址与物理内存地址是如何映射的?
+
+MMU 将虚拟地址翻译为物理地址的主要机制有 3 种:
+
+1. 分段机制
+2. 分页机制
+3. 段页机制
+
+其中,现代操作系统广泛采用分页机制,需要重点关注!
+
+### 分段机制
+
+**分段机制(Segmentation)** 以段(—段 **连续** 的物理内存)的形式管理/分配物理内存。应用程序的虚拟地址空间被分为大小不等的段,段是有实际意义的,每个段定义了一组逻辑信息,例如有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。
+
+#### 段表有什么用?地址翻译过程是怎样的?
+
+分段管理通过 **段表(Segment Table)** 映射虚拟地址和物理地址。
+
+分段机制下的虚拟地址由两部分组成:
+
+- **段号**:标识着该虚拟地址属于整个虚拟地址空间中的哪一个段。
+- **段内偏移量**:相对于该段起始地址的偏移量。
+
+具体的地址翻译过程如下:
+
+1. MMU 首先解析得到虚拟地址中的段号;
+2. 通过段号去该应用程序的段表中取出对应的段信息(找到对应的段表项);
+3. 从段信息中取出该段的起始地址(物理地址)加上虚拟地址中的段内偏移量得到最终的物理地址。
+
+
+
+段表中还存有诸如段长(可用于检查虚拟地址是否超出合法范围)、段类型(该段的类型,例如代码段、数据段等)等信息。
+
+**通过段号一定要找到对应的段表项吗?得到最终的物理地址后对应的物理内存一定存在吗?**
+
+不一定。段表项可能并不存在:
+
+- **段表项被删除**:软件错误、软件恶意行为等情况可能会导致段表项被删除。
+- **段表项还未创建**:如果系统内存不足或者无法分配到连续的物理内存块就会导致段表项无法被创建。
+
+#### 分段机制为什么会导致内存外部碎片?
+
+分段机制容易出现外部内存碎片,即在段与段之间留下碎片空间(不足以映射给虚拟地址空间中的段)。从而造成物理内存资源利用率的降低。
+
+举个例子:假设可用物理内存为 5G 的系统使用分段机制分配内存。现在有 4 个进程,每个进程的内存占用情况如下:
+
+- 进程 1:0~1G(第 1 段)
+- 进程 2:1~3G(第 2 段)
+- 进程 3:3~4.5G(第 3 段)
+- 进程 4:4.5~5G(第 4 段)
+
+此时,我们关闭了进程 1 和进程 4,则第 1 段和第 4 段的内存会被释放,空闲物理内存还有 1.5G。由于这 1.5G 物理内存并不是连续的,导致没办法将空闲的物理内存分配给一个需要 1.5G 物理内存的进程。
+
+
+
+### 分页机制
+
+**分页机制(Paging)** 把主存(物理内存)分为连续等长的物理页,应用程序的虚拟地址空间划也被分为连续等长的虚拟页。现代操作系统广泛采用分页机制。
+
+**注意:这里的页是连续等长的,不同于分段机制下不同长度的段。**
+
+在分页机制下,应用程序虚拟地址空间中的任意虚拟页可以被映射到物理内存中的任意物理页上,因此可以实现物理内存资源的离散分配。分页机制按照固定页大小分配物理内存,使得物理内存资源易于管理,可有效避免分段机制中外部内存碎片的问题。
+
+#### 页表有什么用?地址翻译过程是怎样的?
+
+分页管理通过 **页表(Page Table)** 映射虚拟地址和物理地址。我这里画了一张基于单级页表进行地址翻译的示意图。
+
+
+
+在分页机制下,每个应用程序都会有一个对应的页表。
+
+分页机制下的虚拟地址由两部分组成:
+
+- **页号**:通过虚拟页号可以从页表中取出对应的物理页号;
+- **页内偏移量**:物理页起始地址+页内偏移量=物理内存地址。
+
+具体的地址翻译过程如下:
+
+1. MMU 首先解析得到虚拟地址中的虚拟页号;
+2. 通过虚拟页号去该应用程序的页表中取出对应的物理页号(找到对应的页表项);
+3. 用该物理页号对应的物理页起始地址(物理地址)加上虚拟地址中的页内偏移量得到最终的物理地址。
+
+
+
+页表中还存有诸如访问标志(标识该页面有没有被访问过)、脏数据标识位等信息。
+
+**通过虚拟页号一定要找到对应的物理页号吗?找到了物理页号得到最终的物理地址后对应的物理页一定存在吗?**
+
+不一定!可能会存在 **页缺失** 。也就是说,物理内存中没有对应的物理页或者物理内存中有对应的物理页但虚拟页还未和物理页建立映射(对应的页表项不存在)。关于页缺失的内容,后面会详细介绍到。
+
+#### 单级页表有什么问题?为什么需要多级页表?
+
+以 32 位的环境为例,虚拟地址空间范围共有 2^32(4G)。假设 一个页的大小是 2^12(4KB),那页表项共有 4G / 4K = 2^20 个。每个页表项为一个地址,占用 4 字节,2^20 * 2^2/1024*1024= 4MB。也就是说一个程序啥都不干,页表大小就得占用 4M。
+
+系统运行的应用程序多起来的话,页表的开销还是非常大的。而且,绝大部分应用程序可能只能用到页表中的几项,其他的白白浪费了。
+
+为了解决这个问题,操作系统引入了 **多级页表** ,多级页表对应多个页表,每个页表也前一个页表相关联。32 位系统一般为二级页表,64 位系统一般为四级页表。
+
+这里以二级页表为例进行介绍:二级列表分为一级页表和二级页表。一级页表共有 1024 个页表项,一级页表又关联二级页表,二级页表同样共有 1024 个页表项。二级页表中的一级页表项是一对多的关系,二级页表按需加载(只会用到很少一部分二级页表),进而节省空间占用。
+
+假设只需要 2 个二级页表,那两级页表的内存占用情况为: 4KB(一级页表占用) + 4KB \* 2(二级页表占用) = 12 KB。
+
+
+
+多级页表属于时间换空间的典型场景,利用增加页表查询的次数减少页表占用的空间。
+
+#### TLB 有什么用?使用 TLB 之后的地址翻译流程是怎样的?
+
+为了提高虚拟地址到物理地址的转换速度,操作系统在 **页表方案** 基础之上引入了 **转址旁路缓存(Translation Lookasjde Buffer,TLB,也被称为快表)** 。
+
+
+
+在主流的 AArch64 和 x86-64 体系结构下,TLB 属于 (Memory Management Unit,内存管理单元) 内部的单元,本质上就是一块高速缓存(Cache),缓存了虚拟页号到物理页号的映射关系,你可以将其简单看作是存储着键(虚拟页号)值(物理页号)对的哈希表。
+
+使用 TLB 之后的地址翻译流程是这样的:
+
+1. 用虚拟地址中的虚拟页号作为 key 去 TLB 中查询;
+2. 如果能查到对应的物理页的话,就不用再查询页表了,这种情况称为 TLB 命中(TLB hit)。
+3. 如果不能查到对应的物理页的话,还是需要去查询主存中的页表,同时将页表中的该映射表项添加到 TLB 中,这种情况称为 TLB 未命中(TLB miss)。
+4. 当 TLB 填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。
+
+
+
+由于页表也在主存中,因此在没有 TLB 之前,每次读写内存数据时 CPU 要访问两次主存。有了 TLB 之后,对于存在于 TLB 中的页表数据只需要访问一次主存即可。
+
+TLB 的设计思想非常简单,但命中率往往非常高,效果很好。这就是因为被频繁访问的页就是其中的很小一部分。
+
+看完了之后你会发现快表和我们平时经常在开发系统中使用的缓存(比如 Redis)很像,的确是这样的,操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。
+
+#### 换页机制有什么用?
+
+换页机制的思想是当物理内存不够用的时候,操作系统选择将一些物理页的内容放到磁盘上去,等要用到的时候再将它们读取到物理内存中。也就是说,换页机制利用磁盘这种较低廉的存储设备扩展的物理内存。
+
+这也就解释了一个日常使用电脑常见的问题:为什么操作系统中所有进程运行所需的物理内存即使比真实的物理内存要大一些,这些进程也是可以正常运行的,只是运行速度会变慢。
+
+这同样是一种时间换空间的策略,你用 CPU 的计算时间,页的调入调出花费的时间,换来了一个虚拟的更大的物理内存空间来支持程序的运行。
+
+#### 什么是页缺失?
+
+根据维基百科:
+
+> 页缺失(Page Fault,又名硬错误、硬中断、分页错误、寻页缺失、缺页中断、页故障等)指的是当软件试图访问已映射在虚拟地址空间中,但是目前并未被加载在物理内存中的一个分页时,由 MMU 所发出的中断。
+
+常见的页缺失有下面这两种:
+
+- **硬性页缺失(Hard Page Fault)**:物理内存中没有对应的物理页。于是,Page Fault Handler 会指示 CPU 从已经打开的磁盘文件中读取相应的内容到物理内存,而后交由 MMU 建立相应的虚拟页和物理页的映射关系。
+- **软性页缺失(Soft Page Fault)**:物理内存中有对应的物理页,但虚拟页还未和物理页建立映射。于是,Page Fault Handler 会指示 MMU 建立相应的虚拟页和物理页的映射关系。
+
+发生上面这两种缺页错误的时候,应用程序访问的是有效的物理内存,只是出现了物理页缺失或者虚拟页和物理页的映射关系未建立的问题。如果应用程序访问的是无效的物理内存的话,还会出现 **无效缺页错误(Invalid Page Fault)** 。
+
+#### 常见的页面置换算法有哪些?
+
+当发生硬性页缺失时,如果物理内存中没有空闲的物理页面可用的话。操作系统就必须将物理内存中的一个物理页淘汰出去,这样就可以腾出空间来加载新的页面了。
+
+用来选择淘汰哪一个物理页的规则叫做 **页面置换算法** ,我们可以把页面置换算法看成是淘汰物物理页的规则。
+
+页缺失太频繁的发生会非常影响性能,一个好的页面置换算法应该是可以减少页缺失出现的次数。
+
+常见的页面置换算法有下面这 5 种(其他还有很多页面置换算法都是基于这些算法改进得来的):
+
+
+
+1. **最佳页面置换算法(OPT,Optimal)**:优先选择淘汰的页面是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若干页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现,只是理论最优的页面置换算法,可以作为衡量其他置换算法优劣的标准。
+2. **先进先出页面置换算法(FIFO,First In First Out)** : 最简单的一种页面置换算法,总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。该算法易于实现和理解,一般只需要通过一个 FIFO 队列即可需求。不过,它的性能并不是很好。
+3. **最近最久未使用页面置换算法(LRU ,Least Recently Used)**:LRU 算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。LRU 算法是根据各页之前的访问情况来实现,因此是易于实现的。OPT 算法是根据各页未来的访问情况来实现,因此是不可实现的。
+4. **最少使用页面置换算法(LFU,Least Frequently Used)** : 和 LRU 算法比较像,不过该置换算法选择的是之前一段时间内使用最少的页面作为淘汰页。
+5. **时钟页面置换算法(Clock)**:可以认为是一种最近未使用算法,即逐出的页面都是最近没有使用的那个。
+
+**FIFO 页面置换算法性能为何不好?**
+
+主要原因主要有二:
+
+1. **经常访问或者需要长期存在的页面会被频繁调入调出**:较早调入的页往往是经常被访问或者需要长期存在的页,这些页会被反复调入和调出。
+2. **存在 Belady 现象**:被置换的页面并不是进程不会访问的,有时就会出现分配的页面数增多但缺页率反而提高的异常现象。出现该异常的原因是因为 FIFO 算法只考虑了页面进入内存的顺序,而没有考虑页面访问的频率和紧迫性。
+
+**哪一种页面置换算法实际用的比较多?**
+
+LRU 算法是实际使用中应用的比较多,也被认为是最接近 OPT 的页面置换算法。
+
+不过,需要注意的是,实际应用中这些算法会被做一些改进,就比如 InnoDB Buffer Pool( InnoDB 缓冲池,MySQL 数据库中用于管理缓存页面的机制)就改进了传统的 LRU 算法,使用了一种称为"Adaptive LRU"的算法(同时结合了 LRU 和 LFU 算法的思想)。
+
+### 分页机制和分段机制有哪些共同点和区别?
+
+**共同点**:
+
+- 都是非连续内存管理的方式。
+- 都采用了地址映射的方法,将虚拟地址映射到物理地址,以实现对内存的管理和保护。
+
+**区别**:
+
+- 分页机制以页面为单位进行内存管理,而分段机制以段为单位进行内存管理。页的大小是固定的,由操作系统决定,通常为 2 的幂次方。而段的大小不固定,取决于我们当前运行的程序。
+- 页是物理单位,即操作系统将物理内存划分成固定大小的页面,每个页面的大小通常是 2 的幂次方,例如 4KB、8KB 等等。而段则是逻辑单位,是为了满足程序对内存空间的逻辑需求而设计的,通常根据程序中数据和代码的逻辑结构来划分。
+- 分段机制容易出现外部内存碎片,即在段与段之间留下碎片空间(不足以映射给虚拟地址空间中的段)。分页机制解决了外部内存碎片的问题,但仍然可能会出现内部内存碎片。
+- 分页机制采用了页表来完成虚拟地址到物理地址的映射,页表通过一级页表和二级页表来实现多级映射;而分段机制则采用了段表来完成虚拟地址到物理地址的映射,每个段表项中记录了该段的起始地址和长度信息。
+- 分页机制对程序没有任何要求,程序只需要按照虚拟地址进行访问即可;而分段机制需要程序员将程序分为多个段,并且显式地使用段寄存器来访问不同的段。
+
+### 段页机制
+
+结合了段式管理和页式管理的一种内存管理机制,把物理内存先分成若干段,每个段又继续分成若干大小相等的页。
+
+在段页式机制下,地址翻译的过程分为两个步骤:
+
+1. 段式地址映射。
+2. 页式地址映射。
+
+### 局部性原理
+
+要想更好地理解虚拟内存技术,必须要知道计算机中著名的 **局部性原理(Locality Principle)**。另外,局部性原理既适用于程序结构,也适用于数据结构,是非常重要的一个概念。
+
+局部性原理是指在程序执行过程中,数据和指令的访问存在一定的空间和时间上的局部性特点。其中,时间局部性是指一个数据项或指令在一段时间内被反复使用的特点,空间局部性是指一个数据项或指令在一段时间内与其相邻的数据项或指令被反复使用的特点。
+
+在分页机制中,页表的作用是将虚拟地址转换为物理地址,从而完成内存访问。在这个过程中,局部性原理的作用体现在两个方面:
+
+- **时间局部性**:由于程序中存在一定的循环或者重复操作,因此会反复访问同一个页或一些特定的页,这就体现了时间局部性的特点。为了利用时间局部性,分页机制中通常采用缓存机制来提高页面的命中率,即将最近访问过的一些页放入缓存中,如果下一次访问的页已经在缓存中,就不需要再次访问内存,而是直接从缓存中读取。
+- **空间局部性**:由于程序中数据和指令的访问通常是具有一定的空间连续性的,因此当访问某个页时,往往会顺带访问其相邻的一些页。为了利用空间局部性,分页机制中通常采用预取技术来预先将相邻的一些页读入内存缓存中,以便在未来访问时能够直接使用,从而提高访问速度。
+
+总之,局部性原理是计算机体系结构设计的重要原则之一,也是许多优化算法的基础。在分页机制中,利用时间局部性和空间局部性,采用缓存和预取技术,可以提高页面的命中率,从而提高内存访问效率
+
+## 文件系统
+
+### 文件系统主要做了什么?
+
+文件系统主要负责管理和组织计算机存储设备上的文件和目录,其功能包括以下几个方面:
+
+1. **存储管理**:将文件数据存储到物理存储介质中,并且管理空间分配,以确保每个文件都有足够的空间存储,并避免文件之间发生冲突。
+2. **文件管理**:文件的创建、删除、移动、重命名、压缩、加密、共享等等。
+3. **目录管理**:目录的创建、删除、移动、重命名等等。
+4. **文件访问控制**:管理不同用户或进程对文件的访问权限,以确保用户只能访问其被授权访问的文件,以保证文件的安全性和保密性。
+
+### 硬链接和软链接有什么区别?
+
+在 Linux/类 Unix 系统上,文件链接(File Link)是一种特殊的文件类型,可以在文件系统中指向另一个文件。常见的文件链接类型有两种:
+
+**1、硬链接(Hard Link)**
+
+- 在 Linux/类 Unix 文件系统中,每个文件和目录都有一个唯一的索引节点(inode)号,用来标识该文件或目录。硬链接通过 inode 节点号建立连接,硬链接和源文件的 inode 节点号相同,两者对文件系统来说是完全平等的(可以看作是互为硬链接,源头是同一份文件),删除其中任何一个对另外一个没有影响,可以通过给文件设置硬链接文件来防止重要文件被误删。
+- 只有删除了源文件和所有对应的硬链接文件,该文件才会被真正删除。
+- 硬链接具有一些限制,不能对目录以及不存在的文件创建硬链接,并且,硬链接也不能跨越文件系统。
+- `ln` 命令用于创建硬链接。
+
+**2、软链接(Symbolic Link 或 Symlink)**
+
+- 软链接和源文件的 inode 节点号不同,而是指向一个文件路径。
+- 源文件删除后,软链接依然存在,但是指向的是一个无效的文件路径。
+- 软连接类似于 Windows 系统中的快捷方式。
+- 不同于硬链接,可以对目录或者不存在的文件创建软链接,并且,软链接可以跨越文件系统。
+- `ln -s` 命令用于创建软链接。
+
+### 硬链接为什么不能跨文件系统?
+
+我们之前提到过,硬链接是通过 inode 节点号建立连接的,而硬链接和源文件共享相同的 inode 节点号。
+
+然而,每个文件系统都有自己的独立 inode 表,且每个 inode 表只维护该文件系统内的 inode。如果在不同的文件系统之间创建硬链接,可能会导致 inode 节点号冲突的问题,即目标文件的 inode 节点号已经在该文件系统中被使用。
+
+### 提高文件系统性能的方式有哪些?
+
+- **优化硬件**:使用高速硬件设备(如 SSD、NVMe)替代传统的机械硬盘,使用 RAID(Redundant Array of Inexpensive Disks)等技术提高磁盘性能。
+- **选择合适的文件系统选型**:不同的文件系统具有不同的特性,对于不同的应用场景选择合适的文件系统可以提高系统性能。
+- **运用缓存**:访问磁盘的效率比较低,可以运用缓存来减少磁盘的访问次数。不过,需要注意缓存命中率,缓存命中率过低的话,效果太差。
+- **避免磁盘过度使用**:注意磁盘的使用率,避免将磁盘用满,尽量留一些剩余空间,以免对文件系统的性能产生负面影响。
+- **对磁盘进行合理的分区**:合理的磁盘分区方案,能够使文件系统在不同的区域存储文件,从而减少文件碎片,提高文件读写性能。
+
+### 常见的磁盘调度算法有哪些?
+
+磁盘调度算法是操作系统中对磁盘访问请求进行排序和调度的算法,其目的是提高磁盘的访问效率。
+
+一次磁盘读写操作的时间由磁盘寻道/寻找时间、延迟时间和传输时间决定。磁盘调度算法可以通过改变到达磁盘请求的处理顺序,减少磁盘寻道时间和延迟时间。
+
+常见的磁盘调度算法有下面这 6 种(其他还有很多磁盘调度算法都是基于这些算法改进得来的):
+
+
+
+1. **先来先服务算法(First-Come First-Served,FCFS)**:按照请求到达磁盘调度器的顺序进行处理,先到达的请求的先被服务。FCFS 算法实现起来比较简单,不存在算法开销。不过,由于没有考虑磁头移动的路径和方向,平均寻道时间较长。同时,该算法容易出现饥饿问题,即一些后到的磁盘请求可能需要等待很长时间才能得到服务。
+2. **最短寻道时间优先算法(Shortest Seek Time First,SSTF)**:也被称为最佳服务优先(Shortest Service Time First,SSTF)算法,优先选择距离当前磁头位置最近的请求进行服务。SSTF 算法能够最小化磁头的寻道时间,但容易出现饥饿问题,即磁头附近的请求不断被服务,远离磁头的请求长时间得不到响应。实际应用中,需要优化一下该算法的实现,避免出现饥饿问题。
+3. **扫描算法(SCAN)**:也被称为电梯(Elevator)算法,基本思想和电梯非常类似。磁头沿着一个方向扫描磁盘,如果经过的磁道有请求就处理,直到到达磁盘的边界,然后改变移动方向,依此往复。SCAN 算法能够保证所有的请求得到服务,解决了饥饿问题。但是,如果磁头从一个方向刚扫描完,请求才到的话。这个请求就需要等到磁头从相反方向过来之后才能得到处理。
+4. **循环扫描算法(Circular Scan,C-SCAN)**:SCAN 算法的变体,只在磁盘的一侧进行扫描,并且只按照一个方向扫描,直到到达磁盘边界,然后回到磁盘起点,重新开始循环。
+5. **边扫描边观察算法(LOOK)**:SCAN 算法中磁头到了磁盘的边界才改变移动方向,这样可能会做很多无用功,因为磁头移动方向上可能已经没有请求需要处理了。LOOK 算法对 SCAN 算法进行了改进,如果磁头移动方向上已经没有别的请求,就可以立即改变磁头移动方向,依此往复。也就是边扫描边观察指定方向上还有无请求,因此叫 LOOK。
+6. **均衡循环扫描算法(C-LOOK)**:C-SCAN 只有到达磁盘边界时才能改变磁头移动方向,并且磁头返回时也需要返回到磁盘起点,这样可能会做很多无用功。C-LOOK 算法对 C-SCAN 算法进行了改进,如果磁头移动的方向上已经没有磁道访问请求了,就可以立即让磁头返回,并且磁头只需要返回到有磁道访问请求的位置即可。
+
+## 参考
+
+- 《计算机操作系统—汤小丹》第四版
+- 《深入理解计算机系统》
+- 《重学操作系统》
+- 《现代操作系统原理与实现》
+- 王道考研操作系统知识点整理:https://wizardforcel.gitbooks.io/wangdaokaoyan-os/content/13.html
+- 内存管理之伙伴系统与 SLAB:https://blog.csdn.net/qq_44272681/article/details/124199068
+- 为什么 Linux 需要虚拟内存:https://draveness.me/whys-the-design-os-virtual-memory/
+- 程序员的自我修养(七):内存缺页错误:https://liam.page/2017/09/01/page-fault/
+- 虚拟内存的那点事儿:https://juejin.cn/post/6844903507594575886
diff --git a/docs/cs-basics/operating-system/shell-intro.md b/docs/cs-basics/operating-system/shell-intro.md
index 074f7bfbe556608afc9cab76083151f7f14253b7..a1481faf0e4c102a036707af60f7e58f04fe185f 100644
--- a/docs/cs-basics/operating-system/shell-intro.md
+++ b/docs/cs-basics/operating-system/shell-intro.md
@@ -1,24 +1,30 @@
---
-title: Shell 编程入门
+title: Shell 编程基础知识总结
category: 计算机基础
tag:
- 操作系统
- Linux
+head:
+ - - meta
+ - name: description
+ content: Shell 编程在我们的日常开发工作中非常实用,目前 Linux 系统下最流行的运维自动化语言就是 Shell 和 Python 了。这篇文章我会简单总结一下 Shell 编程基础知识,带你入门 Shell 编程!
---
-# Shell 编程入门
+Shell 编程在我们的日常开发工作中非常实用,目前 Linux 系统下最流行的运维自动化语言就是 Shell 和 Python 了。
+
+这篇文章我会简单总结一下 Shell 编程基础知识,带你入门 Shell 编程!
## 走进 Shell 编程的大门
-### 为什么要学Shell?
+### 为什么要学 Shell?
学一个东西,我们大部分情况都是往实用性方向着想。从工作角度来讲,学习 Shell 是为了提高我们自己工作效率,提高产出,让我们在更少的时间完成更多的事情。
-很多人会说 Shell 编程属于运维方面的知识了,应该是运维人员来做,我们做后端开发的没必要学。我觉得这种说法大错特错,相比于专门做Linux运维的人员来说,我们对 Shell 编程掌握程度的要求要比他们低,但是shell编程也是我们必须要掌握的!
+很多人会说 Shell 编程属于运维方面的知识了,应该是运维人员来做,我们做后端开发的没必要学。我觉得这种说法大错特错,相比于专门做 Linux 运维的人员来说,我们对 Shell 编程掌握程度的要求要比他们低,但是 Shell 编程也是我们必须要掌握的!
-目前Linux系统下最流行的运维自动化语言就是Shell和Python了。
+目前 Linux 系统下最流行的运维自动化语言就是 Shell 和 Python 了。
-两者之间,Shell几乎是IT企业必须使用的运维自动化编程语言,特别是在运维工作中的服务监控、业务快速部署、服务启动停止、数据备份及处理、日志分析等环节里,shell是不可缺的。Python 更适合处理复杂的业务逻辑,以及开发复杂的运维软件工具,实现通过web访问等。Shell是一个命令解释器,解释执行用户所输入的命令和程序。一输入命令,就立即回应的交互的对话方式。
+两者之间,Shell 几乎是 IT 企业必须使用的运维自动化编程语言,特别是在运维工作中的服务监控、业务快速部署、服务启动停止、数据备份及处理、日志分析等环节里,shell 是不可缺的。Python 更适合处理复杂的业务逻辑,以及开发复杂的运维软件工具,实现通过 web 访问等。Shell 是一个命令解释器,解释执行用户所输入的命令和程序。一输入命令,就立即回应的交互的对话方式。
另外,了解 shell 编程也是大部分互联网公司招聘后端开发人员的要求。下图是我截取的一些知名互联网公司对于 Shell 编程的要求。
@@ -26,22 +32,20 @@ tag:
### 什么是 Shell?
-简单来说“Shell编程就是对一堆Linux命令的逻辑化处理”。
+简单来说“Shell 编程就是对一堆 Linux 命令的逻辑化处理”。
-W3Cschool 上的一篇文章是这样介绍 Shell的,如下图所示。
+W3Cschool 上的一篇文章是这样介绍 Shell 的,如下图所示。

-
### Shell 编程的 Hello World
-学习任何一门编程语言第一件事就是输出HelloWorld了!下面我会从新建文件到shell代码编写来说下Shell 编程如何输出Hello World。
+学习任何一门编程语言第一件事就是输出 HelloWorld 了!下面我会从新建文件到 shell 代码编写来说下 Shell 编程如何输出 Hello World。
-
-(1)新建一个文件 helloworld.sh :`touch helloworld.sh`,扩展名为 sh(sh代表Shell)(扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用 php 好了)
+(1)新建一个文件 helloworld.sh :`touch helloworld.sh`,扩展名为 sh(sh 代表 Shell)(扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用 php 好了)
(2) 使脚本具有执行权限:`chmod +x helloworld.sh`
-(3) 使用 vim 命令修改helloworld.sh文件:`vim helloworld.sh`(vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。))
+(3) 使用 vim 命令修改 helloworld.sh 文件:`vim helloworld.sh`(vim 文件------>进入文件----->命令模式------>按 i 进入编辑模式----->编辑文件 ------->按 Esc 进入底行模式----->输入:wq/q! (输入 wq 代表写入内容并退出,即保存;输入 q!代表强制退出不保存。))
helloworld.sh 内容如下:
@@ -51,39 +55,37 @@ helloworld.sh 内容如下:
echo "helloworld!"
```
-shell中 # 符号表示注释。**shell 的第一行比较特殊,一般都会以#!开始来指定使用的 shell 类型。在linux中,除了bash shell以外,还有很多版本的shell, 例如zsh、dash等等...不过bash shell还是我们使用最多的。**
-
+shell 中 # 符号表示注释。**shell 的第一行比较特殊,一般都会以#!开始来指定使用的 shell 类型。在 linux 中,除了 bash shell 以外,还有很多版本的 shell, 例如 zsh、dash 等等...不过 bash shell 还是我们使用最多的。**
(4) 运行脚本:`./helloworld.sh` 。(注意,一定要写成 `./helloworld.sh` ,而不是 `helloworld.sh` ,运行其它二进制的程序也一样,直接写 `helloworld.sh` ,linux 系统会去 PATH 里寻找有没有叫 helloworld.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 `helloworld.sh` 是会找不到命令的,要用`./helloworld.sh` 告诉系统说,就在当前目录找。)

-
## Shell 变量
### Shell 编程中的变量介绍
-
-**Shell编程中一般分为三种变量:**
+**Shell 编程中一般分为三种变量:**
1. **我们自己定义的变量(自定义变量):** 仅在当前 Shell 实例中有效,其他 Shell 启动的程序不能访问局部变量。
-2. **Linux已定义的环境变量**(环境变量, 例如:`PATH`, `HOME` 等..., 这类变量我们可以直接使用),使用 `env` 命令可以查看所有的环境变量,而set命令既可以查看环境变量也可以查看自定义变量。
-3. **Shell变量** :Shell变量是由 Shell 程序设置的特殊变量。Shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 Shell 的正常运行
+2. **Linux 已定义的环境变量**(环境变量, 例如:`PATH`, `HOME` 等..., 这类变量我们可以直接使用),使用 `env` 命令可以查看所有的环境变量,而 set 命令既可以查看环境变量也可以查看自定义变量。
+3. **Shell 变量**:Shell 变量是由 Shell 程序设置的特殊变量。Shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 Shell 的正常运行
**常用的环境变量:**
-> PATH 决定了shell将到哪些目录中寻找命令或程序
-HOME 当前用户主目录
-HISTSIZE 历史记录数
-LOGNAME 当前用户的登录名
-HOSTNAME 指主机的名称
-SHELL 当前用户Shell类型
-LANGUAGE 语言相关的环境变量,多语言可以修改此环境变量
-MAIL 当前用户的邮件存放目录
-PS1 基本提示符,对于root用户是#,对于普通用户是$
+
+> PATH 决定了 shell 将到哪些目录中寻找命令或程序
+> HOME 当前用户主目录
+> HISTSIZE 历史记录数
+> LOGNAME 当前用户的登录名
+> HOSTNAME 指主机的名称
+> SHELL 当前用户 Shell 类型
+> LANGUAGE 语言相关的环境变量,多语言可以修改此环境变量
+> MAIL 当前用户的邮件存放目录
+> PS1 基本提示符,对于 root 用户是#,对于普通用户是\$
**使用 Linux 已定义的环境变量:**
-比如我们要看当前用户目录可以使用:`echo $HOME`命令;如果我们要看当前用户Shell类型 可以使用`echo $SHELL`命令。可以看出,使用方法非常简单。
+比如我们要看当前用户目录可以使用:`echo $HOME`命令;如果我们要看当前用户 Shell 类型 可以使用`echo $SHELL`命令。可以看出,使用方法非常简单。
**使用自己定义的变量:**
@@ -94,34 +96,35 @@ hello="hello world"
echo $hello
echo "helloworld!"
```
-
+
**Shell 编程中的变量名的命名的注意事项:**
-
-- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头,但是可以使用下划线(_)开头。
-- 中间不能有空格,可以使用下划线(_)。
+- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头,但是可以使用下划线(\_)开头。
+- 中间不能有空格,可以使用下划线(\_)。
- 不能使用标点符号。
-- 不能使用bash里的关键字(可用help命令查看保留关键字)。
-
+- 不能使用 bash 里的关键字(可用 help 命令查看保留关键字)。
### Shell 字符串入门
-字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号。这点和Java中有所不同。
+字符串是 shell 编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号。这点和 Java 中有所不同。
+
+在单引号中所有的特殊符号,如$和反引号都没有特殊含义。在双引号中,除了"$"、"\\"、反引号和感叹号(需开启 `history expansion`),其他的字符没有特殊含义。
**单引号字符串:**
```shell
#!/bin/bash
name='SnailClimb'
-hello='Hello, I am '$name'!'
+hello='Hello, I am $name!'
echo $hello
```
+
输出内容:
```
-Hello, I am SnailClimb!
+Hello, I am $name!
```
**双引号字符串:**
@@ -129,7 +132,7 @@ Hello, I am SnailClimb!
```shell
#!/bin/bash
name='SnailClimb'
-hello="Hello, I am "$name"!"
+hello="Hello, I am $name!"
echo $hello
```
@@ -139,7 +142,6 @@ echo $hello
Hello, I am SnailClimb!
```
-
### Shell 字符串常见操作
**拼接字符串:**
@@ -161,7 +163,6 @@ echo $greeting_2 $greeting_3

-
**获取字符串长度:**
```shell
@@ -175,6 +176,7 @@ expr length "$name";
```
输出结果:
+
```
10
10
@@ -186,6 +188,7 @@ expr length "$name";
expr 5+6 // 直接输出 5+6
expr 5 + 6 // 输出 11
```
+
对于某些运算符,还需要我们使用符号`\`进行转义,否则就会提示语法错误。
```shell
@@ -197,7 +200,6 @@ expr 5 \* 6 // 输出30
简单的字符串截取:
-
```shell
#从字符串第 1 个字符开始往后截取 10 个字符
str="SnailClimb is a great man"
@@ -225,8 +227,7 @@ s5=${var##*/} #linux-shell-variable.html
### Shell 数组
-bash支持一维数组(不支持多维数组),并且没有限定数组的大小。我下面给了大家一个关于数组操作的 Shell 代码示例,通过该示例大家可以知道如何创建数组、获取数组长度、获取/删除特定位置的数组元素、删除整个数组以及遍历数组。
-
+bash 支持一维数组(不支持多维数组),并且没有限定数组的大小。我下面给了大家一个关于数组操作的 Shell 代码示例,通过该示例大家可以知道如何创建数组、获取数组长度、获取/删除特定位置的数组元素、删除整个数组以及遍历数组。
```shell
#!/bin/bash
@@ -241,17 +242,16 @@ echo $length2 #输出:5
# 输出数组第三个元素
echo ${array[2]} #输出:3
unset array[1]# 删除下标为1的元素也就是删除第二个元素
-for i in ${array[@]};do echo $i ;done # 遍历数组,输出: 1 3 4 5
+for i in ${array[@]};do echo $i ;done # 遍历数组,输出:1 3 4 5
unset array; # 删除数组中的所有元素
for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没有任何输出内容
```
-
## Shell 基本运算符
> 说明:图片来自《菜鸟教程》
- Shell 编程支持下面几种运算符
+Shell 编程支持下面几种运算符
- 算数运算符
- 关系运算符
@@ -273,14 +273,13 @@ val=`expr $a + $b`
echo "Total value : $val"
```
-
### 关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

-通过一个简单的示例演示关系运算符的使用,下面shell程序的作用是当score=100的时候输出A否则输出B。
+通过一个简单的示例演示关系运算符的使用,下面 shell 程序的作用是当 score=100 的时候输出 A 否则输出 B。
```shell
#!/bin/bash
@@ -315,7 +314,6 @@ echo $a;
### 布尔运算符
-

这里就不做演示了,应该挺简单的。
@@ -337,6 +335,7 @@ else
echo "a 不等于 b"
fi
```
+
输出:
```
@@ -349,7 +348,7 @@ a 不等于 b
使用方式很简单,比如我们定义好了一个文件路径`file="/usr/learnshell/test.sh"` 如果我们想判断这个文件是否可读,可以这样`if [ -r $file ]` 如果想判断这个文件是否可写,可以这样`-w $file`,是不是很简单。
-## shell流程控制
+## shell 流程控制
### if 条件语句
@@ -376,7 +375,7 @@ fi
a 小于 b
```
-相信大家通过上面的示例就已经掌握了 shell 编程中的 if 条件语句。不过,还要提到的一点是,不同于我们常见的 Java 以及 PHP 中的 if 条件语句,shell if 条件语句中不能包含空语句也就是什么都不做的语句。
+相信大家通过上面的示例就已经掌握了 shell 编程中的 if 条件语句。不过,还要提到的一点是,不同于我们常见的 Java 以及 PHP 中的 if 条件语句,shell if 条件语句中不能包含空语句也就是什么都不做的语句。
### for 循环语句
@@ -396,23 +395,23 @@ done
```shell
#!/bin/bash
for i in {0..9};
-do
+do
echo $RANDOM;
done
```
-**输出1到5:**
+**输出 1 到 5:**
-通常情况下 shell 变量调用需要加 $,但是 for 的 (()) 中不需要,下面来看一个例子:
+通常情况下 shell 变量调用需要加 \$,但是 for 的 (()) 中不需要,下面来看一个例子:
```shell
#!/bin/bash
-for((i=1;i<=5;i++));do
+length=5
+for((i=1;i<=length;i++));do
echo $i;
done;
```
-
### while 语句
**基本的 while 循环语句:**
@@ -427,7 +426,7 @@ do
done
```
-**while循环可用于读取键盘信息:**
+**while 循环可用于读取键盘信息:**
```shell
echo '按下 退出'
@@ -477,7 +476,6 @@ echo "-----函数执行完毕-----"
-----函数执行完毕-----
```
-
### 有返回值的函数
**输入两个数字之后相加并返回结果:**
@@ -499,9 +497,9 @@ echo "输入的两个数字之和为 $?"
输出结果:
```
-输入第一个数字:
+输入第一个数字:
1
-输入第二个数字:
+输入第二个数字:
2
两个数字分别为 1 和 2 !
输入的两个数字之和为 3
diff --git "a/docs/cs-basics/operating-system/\346\223\215\344\275\234\347\263\273\347\273\237\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230&\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223.md" "b/docs/cs-basics/operating-system/\346\223\215\344\275\234\347\263\273\347\273\237\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230&\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223.md"
deleted file mode 100644
index d2db9fed5459e6544a56cd3379bb505ae8c15611..0000000000000000000000000000000000000000
--- "a/docs/cs-basics/operating-system/\346\223\215\344\275\234\347\263\273\347\273\237\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230&\347\237\245\350\257\206\347\202\271\346\200\273\347\273\223.md"
+++ /dev/null
@@ -1,373 +0,0 @@
----
-title: 操作系统常见面试题总结
-category: 计算机基础
-tag:
- - 操作系统
----
-
-大家好,我是 Guide 哥!
-
-很多读者抱怨计算操作系统的知识点比较繁杂,自己也没有多少耐心去看,但是面试的时候又经常会遇到。所以,我带着我整理好的操作系统的常见问题来啦!这篇文章总结了一些我觉得比较重要的操作系统相关的问题比如**进程管理**、**内存管理**、**虚拟内存**等等。
-
-文章形式通过大部分比较喜欢的面试官和求职者之间的对话形式展开。另外,Guide哥 也只是在大学的时候学习过操作系统,不过基本都忘了,为了写这篇文章这段时间看了很多相关的书籍和博客。如果文中有任何需要补充和完善的地方,你都可以在 issue 中指出!
-
-这篇文章只是对一些操作系统比较重要概念的一个概览,深入学习的话,建议大家还是老老实实地去看书。另外, 这篇文章的很多内容参考了《现代操作系统》第三版这本书,非常感谢。
-
-开始本文的内容之前,我们先聊聊为什么要学习操作系统。
-
-- **从对个人能力方面提升来说** :操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。比如说我们开发的系统使用的缓存(比如 Redis)和操作系统的高速缓存就很像。CPU 中的高速缓存有很多种,不过大部分都是为了解决 CPU 处理速度和内存处理速度不对等的问题。我们还可以把内存看作外存的高速缓存,程序运行的时候我们把外存的数据复制到内存,由于内存的处理速度远远高于外存,这样提高了处理速度。同样地,我们使用的 Redis 缓存就是为了解决程序处理速度和访问常规关系型数据库速度不对等的问题。高速缓存一般会按照局部性原理(2-8 原则)根据相应的淘汰算法保证缓存中的数据是经常会被访问的。我们平常使用的 Redis 缓存很多时候也会按照 2-8 原则去做,很多淘汰算法都和操作系统中的类似。既说了 2-8 原则,那就不得不提命中率了,这是所有缓存概念都通用的。简单来说也就是你要访问的数据有多少能直接在缓存中直接找到。命中率高的话,一般表明你的缓存设计比较合理,系统处理速度也相对较快。
-- **从面试角度来说** :尤其是校招,对于操作系统方面知识的考察是非常非常多的。
-
-**简单来说,学习操作系统能够提高自己思考的深度以及对技术的理解力,并且,操作系统方面的知识也是面试必备。**
-
-关于如何学习操作系统,可以看这篇回答:[https://www.zhihu.com/question/270998611/answer/1640198217](https://www.zhihu.com/question/270998611/answer/1640198217)。
-
-## 一 操作系统基础
-
-面试官顶着蓬松的假发向我走来,只见他一手拿着厚重的 Thinkpad ,一手提着他那淡黄的长裙。
-
-
-
-### 1.1 什么是操作系统?
-
-👨💻**面试官** : 先来个简单问题吧!**什么是操作系统?**
-
-🙋 **我** :我通过以下四点向您介绍一下什么是操作系统吧!
-
-1. **操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机的基石。**
-2. **操作系统本质上是一个运行在计算机上的软件程序 ,用于管理计算机硬件和软件资源。** 举例:运行在你电脑上的所有应用程序都通过操作系统来调用系统内存以及磁盘等等硬件。
-3. **操作系统存在屏蔽了硬件层的复杂性。** 操作系统就像是硬件使用的负责人,统筹着各种相关事项。
-4. **操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理**。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性。
-
-
-
-### 1.2 系统调用
-
-👨💻**面试官** :**什么是系统调用呢?** 能不能详细介绍一下。
-
-🙋 **我** :介绍系统调用之前,我们先来了解一下用户态和系统态。
-
-根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:
-
-1. 用户态(user mode) : 用户态运行的进程可以直接读取用户程序的数据。
-2. 系统态(kernel mode):可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。
-
-说了用户态和系统态之后,那么什么是系统调用呢?
-
-我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的系统态级别的子功能咋办呢?那就需要系统调用了!
-
-也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
-
-这些系统调用按功能大致可分为如下几类:
-
-- 设备管理。完成设备的请求或释放,以及设备启动等功能。
-- 文件管理。完成文件的读、写、创建及删除等功能。
-- 进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。
-- 进程通信。完成进程之间的消息传递或信号传递等功能。
-- 内存管理。完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
-
-## 二 进程和线程
-
-### 2.1 进程和线程的区别
-
-👨💻**面试官**: 好的!我明白了!那你再说一下: **进程和线程的区别**。
-
-🙋 **我:** 好的! 下图是 Java 内存区域,我们从 JVM 的角度来说一下线程和进程之间的关系吧!
-
-> 如果你对 Java 内存区域 (运行时数据区) 这部分知识不太了解的话可以阅读一下这篇文章:[《可能是把 Java 内存区域讲的最清楚的一篇文章》](https://snailclimb.gitee.io/javaguide/#/docs/java/jvm/Java内存区域)
-
-
-
-从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**。
-
-**总结:** 线程是进程划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
-
-### 2.2 进程有哪几种状态?
-
-👨💻**面试官** : 那你再说说**进程有哪几种状态?**
-
-🙋 **我** :我们一般把进程大致分为 5 种状态,这一点和[线程](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/Multithread/JavaConcurrencyBasicsCommonInterviewQuestionsSummary.md#6-%E8%AF%B4%E8%AF%B4%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E5%92%8C%E7%8A%B6%E6%80%81)很像!
-
-- **创建状态(new)** :进程正在被创建,尚未到就绪状态。
-- **就绪状态(ready)** :进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。
-- **运行状态(running)** :进程正在处理器上上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。
-- **阻塞状态(waiting)** :又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
-- **结束状态(terminated)** :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。
-
-> 订正:下图中 running 状态被 interrupt 向 ready 状态转换的箭头方向反了。
-
-
-
-### 2.3 进程间的通信方式
-
-👨💻**面试官** :**进程间的通信常见的的有哪几种方式呢?**
-
-🙋 **我** :大概有 7 种常见的进程间的通信方式。
-
-> 下面这部分总结参考了:[《进程间通信 IPC (InterProcess Communication)》](https://www.jianshu.com/p/c1015f5ffa74) 这篇文章,推荐阅读,总结的非常不错。
-
-1. **管道/匿名管道(Pipes)** :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
-1. **有名管道(Names Pipes)** : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循**先进先出(first in first out)**。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
-1. **信号(Signal)** :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
-1. **消息队列(Message Queuing)** :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。**消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。**
-1. **信号量(Semaphores)** :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
-1. **共享内存(Shared memory)** :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
-1. **套接字(Sockets)** : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
-
-### 2.4 线程间的同步的方式
-
-👨💻**面试官** :**那线程间的同步的方式有哪些呢?**
-
-🙋 **我** :线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。操作系统一般有下面三种线程同步的方式:
-
-1. **互斥量(Mutex)**:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
-1. **信号量(Semphares)** :它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量
-1. **事件(Event)** :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操
-
-### 2.5 进程的调度算法
-
-👨💻**面试官** :**你知道操作系统中进程的调度算法有哪些吗?**
-
-🙋 **我** :嗯嗯!这个我们大学的时候学过,是一个很重要的知识点!
-
-为了确定首先执行哪个进程以及最后执行哪个进程以实现最大 CPU 利用率,计算机科学家已经定义了一些算法,它们是:
-
-- **先到先服务(FCFS)调度算法** : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
-- **短作业优先(SJF)的调度算法** : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
-- **时间片轮转调度算法** : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
-- **多级反馈队列调度算法** :前面介绍的几种进程调度的算法都有一定的局限性。如**短进程优先的调度算法,仅照顾了短进程而忽略了长进程** 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前**被公认的一种较好的进程调度算法**,UNIX 操作系统采取的便是这种调度算法。
-- **优先级调度** : 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
-
-### 2.6 什么是死锁
-
-👨💻**面试官** :**你知道什么是死锁吗?**
-
-🙋 **我** :多个进程可以竞争有限数量的资源。当一个进程申请资源时,如果这时没有可用资源,那么这个进程进入等待状态。有时,如果所申请的资源被其他等待进程占有,那么该等待进程有可能再也无法改变状态。这种情况成为**死锁**。
-
-### 2.7 死锁的四个条件
-
-👨💻**面试官** :**产生死锁的四个必要条件是什么?**
-
-🙋 **我** :如果系统中以下四个条件同时成立,那么就能引起死锁:
-
-- **互斥**:资源必须处于非共享模式,即一次只有一个进程可以使用。如果另一进程申请该资源,那么必须等待直到该资源被释放为止。
-- **占有并等待**:一个进程至少应该占有一个资源,并等待另一资源,而该资源被其他进程所占有。
-- **非抢占**:资源不能被抢占。只能在持有资源的进程完成任务后,该资源才会被释放。
-- **循环等待**:有一组等待进程 `{P0, P1,..., Pn}`, `P0` 等待的资源被 `P1` 占有,`P1` 等待的资源被 `P2` 占有,......,`Pn-1` 等待的资源被 `Pn` 占有,`Pn` 等待的资源被 `P0` 占有。
-
-注意,只有四个条件同时成立时,死锁才会出现。
-
-## 三 操作系统内存管理基础
-
-### 3.1 内存管理介绍
-
-👨💻 **面试官**: **操作系统的内存管理主要是做什么?**
-
-🙋 **我:** 操作系统的内存管理主要负责内存的分配与回收(malloc 函数:申请内存,free 函数:释放内存),另外地址转换也就是将逻辑地址转换成相应的物理地址等功能也是操作系统内存管理做的事情。
-
-### 3.2 常见的几种内存管理机制
-
-👨💻 **面试官**: **操作系统的内存管理机制了解吗?内存管理有哪几种方式?**
-
-🙋 **我:** 这个在学习操作系统的时候有了解过。
-
-简单分为**连续分配管理方式**和**非连续分配管理方式**这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间,常见的如 **块式管理** 。同样地,非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,常见的如**页式管理** 和 **段式管理**。
-
-1. **块式管理** : 远古时代的计算机操系统的内存管理方式。将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,我们称之为碎片。
-2. **页式管理** :把主存分为大小相等且固定的一页一页的形式,页较小,相对相比于块式管理的划分力度更大,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址。
-3. **段式管理** : 页式管理虽然提高了内存利用率,但是页式管理其中的页实际并无任何实际意义。 段式管理把主存分为一段段的,每一段的空间又要比一页的空间小很多 。但是,最重要的是段是有实际意义的,每个段定义了一组逻辑信息,例如,有主程序段 MAIN、子程序段 X、数据段 D 及栈段 S 等。 段式管理通过段表对应逻辑地址和物理地址。
-
-👨💻**面试官** : 回答的还不错!不过漏掉了一个很重要的 **段页式管理机制** 。段页式管理机制结合了段式管理和页式管理的优点。简单来说段页式管理机制就是把主存先分成若干段,每个段又分成若干页,也就是说 **段页式管理机制** 中段与段之间以及段的内部的都是离散的。
-
-🙋 **我** :谢谢面试官!刚刚把这个给忘记了~
-
-### 3.3 快表和多级页表
-
-👨💻**面试官** : 页表管理机制中有两个很重要的概念:快表和多级页表,这两个东西分别解决了页表管理中很重要的两个问题。你给我简单介绍一下吧!
-
-🙋 **我** :在分页内存管理中,很重要的两点是:
-
-1. 虚拟地址到物理地址的转换要快。
-2. 解决虚拟地址空间大,页表也会很大的问题。
-
-#### 快表
-
-为了解决虚拟地址到物理地址的转换速度,操作系统在 **页表方案** 基础之上引入了 **快表** 来加速虚拟地址到物理地址的转换。我们可以把快表理解为一种特殊的高速缓冲存储器(Cache),其中的内容是页表的一部分或者全部内容。作为页表的 Cache,它的作用与页表相似,但是提高了访问速率。由于采用页表做地址转换,读写内存数据时 CPU 要访问两次主存。有了快表,有时只要访问一次高速缓冲存储器,一次主存,这样可加速查找并提高指令执行速度。
-
-使用快表之后的地址转换流程是这样的:
-
-1. 根据虚拟地址中的页号查快表;
-2. 如果该页在快表中,直接从快表中读取相应的物理地址;
-3. 如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中;
-4. 当快表填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。
-
-看完了之后你会发现快表和我们平时经常在我们开发的系统使用的缓存(比如 Redis)很像,的确是这样的,操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。
-
-#### 多级页表
-
-引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间,特别是那些根本就不需要的页表就不需要保留在内存中。多级页表属于时间换空间的典型场景,具体可以查看下面这篇文章
-
-- 多级页表如何节约内存:[https://www.polarxiong.com/archives/多级页表如何节约内存.html](https://www.polarxiong.com/archives/多级页表如何节约内存.html)
-
-#### 总结
-
-为了提高内存的空间性能,提出了多级页表的概念;但是提到空间性能是以浪费时间性能为基础的,因此为了补充损失的时间性能,提出了快表(即 TLB)的概念。 不论是快表还是多级页表实际上都利用到了程序的局部性原理,局部性原理在后面的虚拟内存这部分会介绍到。
-
-### 3.4 分页机制和分段机制的共同点和区别
-
-👨💻**面试官** : **分页机制和分段机制有哪些共同点和区别呢?**
-
-🙋 **我** :
-
-1. **共同点** :
- - 分页机制和分段机制都是为了提高内存利用率,较少内存碎片。
- - 页和段都是离散存储的,所以两者都是离散分配内存的方式。但是,每个页和段中的内存是连续的。
-2. **区别** :
- - 页的大小是固定的,由操作系统决定;而段的大小不固定,取决于我们当前运行的程序。
- - 分页仅仅是为了满足操作系统内存管理的需求,而段是逻辑信息的单位,在程序中可以体现为代码段,数据段,能够更好满足用户的需要。
-
-### 3.5 逻辑(虚拟)地址和物理地址
-
-👨💻**面试官** :你刚刚还提到了**逻辑地址和物理地址**这两个概念,我不太清楚,你能为我解释一下不?
-
-🙋 **我:** em...好的嘛!我们编程一般只有可能和逻辑地址打交道,比如在 C 语言中,指针里面存储的数值就可以理解成为内存里的一个地址,这个地址也就是我们说的逻辑地址,逻辑地址由操作系统决定。物理地址指的是真实物理内存中地址,更具体一点来说就是内存地址寄存器中的地址。物理地址是内存单元真正的地址。
-
-### 3.6 CPU 寻址了解吗?为什么需要虚拟地址空间?
-
-👨💻**面试官** :**CPU 寻址了解吗?为什么需要虚拟地址空间?**
-
-🙋 **我** :这部分我真不清楚!
-
-于是面试完之后我默默去查阅了相关文档!留下了没有技术的泪水。。。
-
-> 这部分内容参考了 Microsoft 官网的介绍,地址:
-
-现代处理器使用的是一种称为 **虚拟寻址(Virtual Addressing)** 的寻址方式。**使用虚拟寻址,CPU 需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。** 实际上完成虚拟地址转换为物理地址转换的硬件是 CPU 中含有一个被称为 **内存管理单元(Memory Management Unit, MMU)** 的硬件。如下图所示:
-
-
-
-**为什么要有虚拟地址空间呢?**
-
-先从没有虚拟地址空间的时候说起吧!没有虚拟地址空间的时候,**程序都是直接访问和操作的都是物理内存** 。但是这样有什么问题呢?
-
-1. 用户程序可以访问任意内存,寻址内存的每个字节,这样就很容易(有意或者无意)破坏操作系统,造成操作系统崩溃。
-2. 想要同时运行多个程序特别困难,比如你想同时运行一个微信和一个 QQ 音乐都不行。为什么呢?举个简单的例子:微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就造成了微信这个程序就会崩溃。
-
-**总结来说:如果直接把物理地址暴露出来的话会带来严重问题,比如可能对操作系统造成伤害以及给同时运行多个程序造成困难。**
-
-通过虚拟地址访问内存有以下优势:
-
-- 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
-- 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为 4 KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
-- 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。
-
-## 四 虚拟内存
-
-### 4.1 什么是虚拟内存(Virtual Memory)?
-
-👨💻**面试官** :再问你一个常识性的问题!**什么是虚拟内存(Virtual Memory)?**
-
-🙋 **我** :这个在我们平时使用电脑特别是 Windows 系统的时候太常见了。很多时候我们使用点开了很多占内存的软件,这些软件占用的内存可能已经远远超出了我们电脑本身具有的物理内存。**为什么可以这样呢?** 正是因为 **虚拟内存** 的存在,通过 **虚拟内存** 可以让程序可以拥有超过系统物理内存大小的可用内存空间。另外,**虚拟内存为每个进程提供了一个一致的、私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉(每个进程拥有一片连续完整的内存空间)**。这样会更加有效地管理内存并减少出错。
-
-**虚拟内存**是计算机系统内存管理的一种技术,我们可以手动设置自己电脑的虚拟内存。不要单纯认为虚拟内存只是“使用硬盘空间来扩展内存“的技术。**虚拟内存的重要意义是它定义了一个连续的虚拟地址空间**,并且 **把内存扩展到硬盘空间**。推荐阅读:[《虚拟内存的那点事儿》](https://juejin.im/post/59f8691b51882534af254317)
-
-维基百科中有几句话是这样介绍虚拟内存的。
-
-> **虚拟内存** 使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,对真正的物理内存(例如 RAM)的使用也更有效率。目前,大多数操作系统都使用了虚拟内存,如 Windows 家族的“虚拟内存”;Linux 的“交换空间”等。From:
-
-### 4.2 局部性原理
-
-👨💻**面试官** :要想更好地理解虚拟内存技术,必须要知道计算机中著名的**局部性原理**。另外,局部性原理既适用于程序结构,也适用于数据结构,是非常重要的一个概念。
-
-🙋 **我** :局部性原理是虚拟内存技术的基础,正是因为程序运行具有局部性原理,才可以只装入部分程序到内存就开始运行。
-
-> 以下内容摘自《计算机操作系统教程》 第 4 章存储器管理。
-
-早在 1968 年的时候,就有人指出我们的程序在执行的时候往往呈现局部性规律,也就是说在某个较短的时间段内,程序执行局限于某一小部分,程序访问的存储空间也局限于某个区域。
-
-局部性原理表现在以下两个方面:
-
-1. **时间局部性** :如果程序中的某条指令一旦执行,不久以后该指令可能再次执行;如果某数据被访问过,不久以后该数据可能再次被访问。产生时间局部性的典型原因,是由于在程序中存在着大量的循环操作。
-2. **空间局部性** :一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也将被访问,即程序在一段时间内所访问的地址,可能集中在一定的范围之内,这是因为指令通常是顺序存放、顺序执行的,数据也一般是以向量、数组、表等形式簇聚存储的。
-
-时间局部性是通过将近来使用的指令和数据保存到高速缓存存储器中,并使用高速缓存的层次结构实现。空间局部性通常是使用较大的高速缓存,并将预取机制集成到高速缓存控制逻辑中实现。虚拟内存技术实际上就是建立了 “内存一外存”的两级存储器的结构,利用局部性原理实现髙速缓存。
-
-### 4.3 虚拟存储器
-
-> **勘误:虚拟存储器又叫做虚拟内存,都是 Virtual Memory 的翻译,属于同一个概念。**
-
-👨💻**面试官** :~~都说了虚拟内存了。你再讲讲**虚拟存储器**把!~~
-
-🙋 **我** :
-
-> 这部分内容来自:[王道考研操作系统知识点整理](https://wizardforcel.gitbooks.io/wangdaokaoyan-os/content/13.html)。
-
-基于局部性原理,在程序装入时,可以将程序的一部分装入内存,而将其他部分留在外存,就可以启动程序执行。由于外存往往比内存大很多,所以我们运行的软件的内存大小实际上是可以比计算机系统实际的内存大小大的。在程序执行过程中,当所访问的信息不在内存时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换到外存上,从而腾出空间存放将要调入内存的信息。这样,计算机好像为用户提供了一个比实际内存大的多的存储器——**虚拟存储器**。
-
-实际上,我觉得虚拟内存同样是一种时间换空间的策略,你用 CPU 的计算时间,页的调入调出花费的时间,换来了一个虚拟的更大的空间来支持程序的运行。不得不感叹,程序世界几乎不是时间换空间就是空间换时间。
-
-### 4.4 虚拟内存的技术实现
-
-👨💻**面试官** :**虚拟内存技术的实现呢?**
-
-🙋 **我** :**虚拟内存的实现需要建立在离散分配的内存管理方式的基础上。** 虚拟内存的实现有以下三种方式:
-
-1. **请求分页存储管理** :建立在分页管理之上,为了支持虚拟存储器功能而增加了请求调页功能和页面置换功能。请求分页是目前最常用的一种实现虚拟存储器的方法。请求分页存储管理系统中,在作业开始运行之前,仅装入当前要执行的部分段即可运行。假如在作业运行的过程中发现要访问的页面不在内存,则由处理器通知操作系统按照对应的页面置换算法将相应的页面调入到主存,同时操作系统也可以将暂时不用的页面置换到外存中。
-2. **请求分段存储管理** :建立在分段存储管理之上,增加了请求调段功能、分段置换功能。请求分段储存管理方式就如同请求分页储存管理方式一样,在作业开始运行之前,仅装入当前要执行的部分段即可运行;在执行过程中,可使用请求调入中断动态装入要访问但又不在内存的程序段;当内存空间已满,而又需要装入新的段时,根据置换功能适当调出某个段,以便腾出空间而装入新的段。
-3. **请求段页式存储管理**
-
-**这里多说一下?很多人容易搞混请求分页与分页存储管理,两者有何不同呢?**
-
-请求分页存储管理建立在分页管理之上。他们的根本区别是是否将程序全部所需的全部地址空间都装入主存,这也是请求分页存储管理可以提供虚拟内存的原因,我们在上面已经分析过了。
-
-它们之间的根本区别在于是否将一作业的全部地址空间同时装入主存。请求分页存储管理不要求将作业全部地址空间同时装入主存。基于这一点,请求分页存储管理可以提供虚存,而分页存储管理却不能提供虚存。
-
-不管是上面那种实现方式,我们一般都需要:
-
-1. 一定容量的内存和外存:在载入程序的时候,只需要将程序的一部分装入内存,而将其他部分留在外存,然后程序就可以执行了;
-2. **缺页中断**:如果**需执行的指令或访问的数据尚未在内存**(称为缺页或缺段),则由处理器通知操作系统将相应的页面或段**调入到内存**,然后继续执行程序;
-3. **虚拟地址空间** :逻辑地址到物理地址的变换。
-
-### 4.5 页面置换算法
-
-👨💻**面试官** :虚拟内存管理很重要的一个概念就是页面置换算法。那你说一下 **页面置换算法的作用?常见的页面置换算法有哪些?**
-
-🙋 **我** :
-
-> 这个题目经常作为笔试题出现,网上已经给出了很不错的回答,我这里只是总结整理了一下。
-
-地址映射过程中,若在页面中发现所要访问的页面不在内存中,则发生缺页中断 。
-
-> **缺页中断** 就是要访问的**页**不在主存,需要操作系统将其调入主存后再进行访问。 在这个时候,被内存映射的文件实际上成了一个分页交换文件。
-
-当发生缺页中断时,如果当前内存中并没有空闲的页面,操作系统就必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。用来选择淘汰哪一页的规则叫做页面置换算法,我们可以把页面置换算法看成是淘汰页面的规则。
-
-- **OPT 页面置换算法(最佳页面置换算法)** :最佳(Optimal, OPT)置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若千页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。一般作为衡量其他置换算法的方法。
-- **FIFO(First In First Out) 页面置换算法(先进先出页面置换算法)** : 总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。
-- **LRU (Least Recently Used)页面置换算法(最近最久未使用页面置换算法)** :LRU算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。
-- **LFU (Least Frequently Used)页面置换算法(最少使用页面置换算法)** : 该置换算法选择在之前时期使用最少的页面作为淘汰页。
-
-## Reference
-
-- 《计算机操作系统—汤小丹》第四版
-- [《深入理解计算机系统》](https://book.douban.com/subject/1230413/)
-- [https://zh.wikipedia.org/wiki/输入输出内存管理单元](https://zh.wikipedia.org/wiki/输入输出内存管理单元)
-- [https://baike.baidu.com/item/快表/19781679](https://baike.baidu.com/item/快表/19781679)
-- https://www.jianshu.com/p/1d47ed0b46d5
--
--
--
-- 王道考研操作系统知识点整理: https://wizardforcel.gitbooks.io/wangdaokaoyan-os/content/13.html
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/database/Redis/images/redis-all/redis-list.drawio b/docs/database/Redis/images/redis-all/redis-list.drawio
deleted file mode 100644
index afa767154b78e800813cbfe4c672ac94cbdcff60..0000000000000000000000000000000000000000
--- a/docs/database/Redis/images/redis-all/redis-list.drawio
+++ /dev/null
@@ -1 +0,0 @@
-7VlNc5swFPw1PiaDENjmmNjpx6Gdjt1J2lNHAzKoEYgRcmzn11cYyRgJT1w3DnScU3grIaHd5b2HM4CTdP2Rozz5wiJMB64TrQdwOnBdEEAo/5TIpkICx6mAmJNITaqBOXnGCtTTliTCRWOiYIwKkjfBkGUZDkUDQ5yzVXPagtHmrjmKsQXMQ0Rt9IFEIqnQsTuq8U+YxIneGQyDaiRFerI6SZGgiK32IHg3gBPOmKiu0vUE05I8zUt134cDo7sH4zgTx9wwu/+V/H4g/myezsD35/vZ56/ulVrlCdGlOrB6WLHRDHC2zCJcLgIG8HaVEIHnOQrL0ZXUXGKJSKkaLh6xCBMVLFgmlKJgJGO1F+YCrw8eAuyokZ7CLMWCb+QUdcOVp9hUdnKhile1OL62WLInzEhhSPkh3i1dUyYvFGt/waBrMbi9BD3lUfPm2bwFLbT556INttPWc/sB035d0+i10wj7TaM77hmNw5fTIM6im7KeyCikqChI2ORMHp1vfsjA0cHPMrh2fR1P1/uj042KjiAbR1aRMqiWVRHxGIuXUr0tSSNxHqacY4oEeWo+RpsOaodvjMgHrPM2gKbk4+YaBVvyEKvb9suYuZLpHdcwRUWEtdDWF7tzn26V0f9ula4s4A0N4cbOtX+aB3zfWupNPaCb01cxAXjPF0cofGq68AJjIT8wXXdusxzRY/fbLJ0VDbPbOj1j2PXnrVOG/Z3A82WRWFaQXZUwujHB2SOeMMq4RDKW4VJLQqkBIUrirHSQVA9L/Lbs0Yj8lL1RAymJonKb1vavbhCP9M0/dYA+OFDF95zltTjLLPav1gECu5PmOcvf5aloD7qWx7fkoRf8+siiYggEuhbI/oSil/v+2Pp0/gLZ3y18u20/BTqDJp5O8VoSp2tJxvYrgxcXpIjV351PERnWv7RXPV39/wp49wc=
\ No newline at end of file
diff --git a/docs/database/Redis/images/redis-all/redis-list.png b/docs/database/Redis/images/redis-all/redis-list.png
deleted file mode 100644
index 4fb4e36cb494ee9554a156f2bd295d6f2f983864..0000000000000000000000000000000000000000
Binary files a/docs/database/Redis/images/redis-all/redis-list.png and /dev/null differ
diff --git a/docs/database/Redis/images/redis-all/redis-rollBack.png b/docs/database/Redis/images/redis-all/redis-rollBack.png
deleted file mode 100644
index 91f7f46d66dba0c55398d5b7aad42254dd65adde..0000000000000000000000000000000000000000
Binary files a/docs/database/Redis/images/redis-all/redis-rollBack.png and /dev/null differ
diff --git a/docs/database/Redis/images/redis-all/redis-vs-memcached.png b/docs/database/Redis/images/redis-all/redis-vs-memcached.png
deleted file mode 100644
index 23844d67e6fc949920643ec78628e2e37a909c45..0000000000000000000000000000000000000000
Binary files a/docs/database/Redis/images/redis-all/redis-vs-memcached.png and /dev/null differ
diff --git a/docs/database/Redis/images/redis-all/redis4.0-more-thread.png b/docs/database/Redis/images/redis-all/redis4.0-more-thread.png
deleted file mode 100644
index e7e19e52e17e12050cfa110bf2c9fa973d7dc299..0000000000000000000000000000000000000000
Binary files a/docs/database/Redis/images/redis-all/redis4.0-more-thread.png and /dev/null differ
diff --git "a/docs/database/Redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png" "b/docs/database/Redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png"
deleted file mode 100644
index fc280fffabaeaf8d984449b62e61ef21c84f1d06..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png" and /dev/null differ
diff --git "a/docs/database/Redis/images/redis-all/redis\344\272\213\345\212\241.png" "b/docs/database/Redis/images/redis-all/redis\344\272\213\345\212\241.png"
deleted file mode 100644
index eb0c404cafd1197235fb37563611f5c5b13eaa6a..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/redis-all/redis\344\272\213\345\212\241.png" and /dev/null differ
diff --git "a/docs/database/Redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png" "b/docs/database/Redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png"
deleted file mode 100644
index 27df6ead8e4c8a576b53d777d0d9fa8e29586f17..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png" and /dev/null differ
diff --git a/docs/database/Redis/images/redis-all/try-redis.png b/docs/database/Redis/images/redis-all/try-redis.png
deleted file mode 100644
index cd21a6518e4de4a25fd740d029e3222c1dabd41c..0000000000000000000000000000000000000000
Binary files a/docs/database/Redis/images/redis-all/try-redis.png and /dev/null differ
diff --git a/docs/database/Redis/images/redis-all/what-is-redis.png b/docs/database/Redis/images/redis-all/what-is-redis.png
deleted file mode 100644
index 913881ac6cf59e9c3c56a58db545048d4f7c47ca..0000000000000000000000000000000000000000
Binary files a/docs/database/Redis/images/redis-all/what-is-redis.png and /dev/null differ
diff --git "a/docs/database/Redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png" "b/docs/database/Redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png"
deleted file mode 100644
index 2c73bd90276521694b6a7e7c2fe78fbbd0517b31..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png" and /dev/null differ
diff --git "a/docs/database/Redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png" "b/docs/database/Redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png"
deleted file mode 100644
index a2c2ed6906fa5e1e558301dc1c36d7bbeee181fd..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png" and /dev/null differ
diff --git "a/docs/database/Redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png" "b/docs/database/Redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png"
deleted file mode 100644
index 648a404af8c93ae61d9e9797d2cbae6b4f3776da..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png" and /dev/null differ
diff --git "a/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png" "b/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png"
deleted file mode 100644
index 11860ae1f024368ecd8173d94d5235a9c0c2fc08..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png" and /dev/null differ
diff --git "a/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png" "b/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png"
deleted file mode 100644
index e7298c15ed6a483fa565fd6a6ba7841eabd9ff3b..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png" and /dev/null differ
diff --git "a/docs/database/Redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png" "b/docs/database/Redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png"
deleted file mode 100644
index 5aff414baa44e30e307d103ba1eea8eb56aa1b54..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png" and /dev/null differ
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio"
deleted file mode 100644
index bc4c6d0cca713d2e0db2aaac480477ec795407d3..0000000000000000000000000000000000000000
--- "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-7Vpbc+o2EP41ekzHl/j2iAmk02nmdA6dpn3qCFsYTWSLY0Qg/fWVbMnYluE4B4jTQh4Se6WVVt+32pU2BvY43T3mcLV8ojEiwDLiHbAfgGWZluPzP0LyVkoCwygFSY5j2WkvmOF/kBSqbhsco3WjI6OUMLxqCiOaZShiDRnMc7ptdltQ0px1BROkCWYRJLr0GcdsWUp9y9vLf0Y4WaqZTTcoW1KoOsuVrJcwptuayJ4Ae5xTysqndDdGRICncCn1pgdaK8NylLE+Ck/Pf/xthHPraf7NePliLeLZ1+c7OcorJBu5YGkse1MI5HSTxUgMYgI73C4xQ7MVjETrlnPOZUuWEtm8wISMKaF5oWsvFgsrirh8zXL6gmotsTt3HVe0KFiEekRTzLs/GPw5IXC9ls/SSpQztDu4fLMClXsjoili+RvvIhVs5VHSEX3fKd+3e1pNxdWyRqkrZVB6UlINvQebP0i834G9db3YW0Njb2vYx5BBDX++ZNYEuQlmRjN0BHlIcJJxWcRxQrwxFChiHlxGsiHFcSzm6qR2T75RTaxiUME3zZgMmFbQIq1O5jkIvG8RqN5rBLod/NmX4u9e4y8TVN3468dflYWH4s+5mtjnGJ8t77jXi/3gece75Z13EWi3845OYFfcUjyfnT//lndO4W/wvBNcTexzg8+Wd9Ql4BrBHzzxmPpN/5Z5jjDoWZ/sxmPq5YJb6nkHgYOnHlOvOfxfw5/vf7rcoxcMrgb84XOPftu/5Z4jDAbtcunguUcvGdxyzzsIHD736HUHjTyUxSPxDzNBgIBGANKIeC38mgjbXIJ2mP0pe4vnvwSiPzny7WEnAS5e3hpoo1j7L1wLa24q3eQROrbIbk5qmDsdmCtZjghk+LVpRhcRcobfKOYGHiw1BSrjqCFK86XWns4eA7VqGQzmCWLaQIVfVMs+wVX0Esd/zFWGcgG/vevbzPV1gXYG/3AX6FElublAF3Nu+9Lxo1GgXUDQBrqwC6h1NFzAJUV2t/bK7reN+HwhBJN7EPrAD8DEAf4U+GPxwNcQmGASgMAG4QRMfDAywcivqdVdSgmF59ytC9cZ8Q62vdrVNdyknJDP8wACp3N4UnadClNlfymM8esJk8pB5vnpY3DrCluU9PSTlBSd9RBVv7IcOi/Vdrptn+f8ZPt+w/s9Vz8/OeZHHqAsvXz2PZcSFN5JNoQ/ELRgh/1hvYJZH68yne7twPcf3w5jsQt49uD37ckUhGMQjGYZxGRMcDrnnWEqSM7m61XTFcvZj3toKRXmnGDnBTCrBQQHjMYFFHzhngxFoVetu/z9iNlyM6/w+QW+wkfxkdkPwcGlJSK99/HF92e9vNDOxEKJ8hExEzsrEK8EzhEJYfSSFGO3AwxXrl3VjOKn0qJ5jPIOjSlMMREzPMIcpjSLlRkSGENS/aUyxTNKPiOcJb/TlTwiSMGvwgsakpAyRtOm7KsMA2eKQZ6qWakM7OhlFKsrBt1fLAbpFeDbsazXscw0vnep6nsuMw1v2LO5pVeRs41Q+9jAM2R1RefgcuUV/rr/QrfkcP+dsz35Fw==
\ No newline at end of file
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png"
deleted file mode 100644
index f8b9589d6d7814b49aef01013b7ce61d38b304db..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png" and /dev/null differ
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio"
deleted file mode 100644
index 6fddf10f064eb3ecd2446ba3c5663e373533521d..0000000000000000000000000000000000000000
--- "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-7LzXsuTYkSX6NWl254FtAAIqHqG11ngZgwporfH1A5zMLFaRySZ5L7unZ26fqjwHsYGt3Zcv9+2Iby+qPbgpGgqlT7PmGwSkx7cX/Q2CwPfrdf95Ss7vJW8A+F6QT2X646E/F1jllf0o/PnYWqbZ/IcHl75vlnL4Y2HSd12WLH8oi6ap3//42Kdv/tjrEOXZXxVYSdT8dalXpkvxvRSHsD+X81mZFz97BtH39ztt9PPhHzOZiyjt998VvZhvL2rq++X7VXtQWfMs3s91+V6P/Rt3fxvYlHXLP1LhaIIYQnDfkqb05QKximXAn34u8xY1648Zf4PQ5m6QjO+L/Ln4WTAPUffMYzl/LA46rs/gyaRv+unbi7hvTnn8/yD30O5mqfv3Hy7/x3P9NHfvQrf86RO1ZXN+r3a3FbXD183XC77/ttk0lXt2r2E2/dXN39r8qzvD8gwwm8rPP1Epz/opL6N/osZStrdMQkCX7c+c+/ZZmH+49rzMfZf/7Qo/xv/7tZq/9OJZKRD7N2Q4/uLu9x15bnf91EbNb7ebbFmy6U/3ziXl3eWvHlmyY/lT1JR59/12k32WP94su/RLxJ67wO/6/rq5TFE3f+4mfzbeZb89sPdT+se+f189jpI6n/q1S//0FwIEIchvi/EX1//jj72nWdJP0VL23S+6T8t5aKIfElZ2Tfm7e5+mj5bf1/kpyz+FHvq3bwz+jWC/keQ3BvmGs9/e6DcG/kbi33D6qwT/RgLfGOzbm/iGwz8eftM/leXWw+/68t869F9dh/7fCuJfisxvo+ni+fugqC9B+ZKVR3TIbzhDfxcn4hsBPDJD0s//txTdIkSiz603cD/19Qz67f3+0QCBJFFSZD8EkPh7UnYX/w69oT8IHPQozl2+F+WSWbdyPsX7bbzvsmJpm/sTeF8+S/bDGv9Yp7+0Nj8M0JZNd4u/K/phfbisb7NlOu9Hftx9IT+MzQ8q8EJ/fN7/bFihn/Sg+J1RhX+URT9sef5b0382d/fFD4v3z1g/8G9av/9W1P96ivp/sLH7D7ZWf8XZwH/7DXHuSt/B427tN/z4hW274Qb5MmnoY+GIG4PuB6hvOPWrh39v/8hvJPxVi7lx6gvU7mfgLywjv72Z38Y2/fsw9TcQ7V+CXk0UZw35G85T33X4bu/Ffv38awAO/wt8Q/4a30D0F/iG/wvwTaedpuK5M2cg0KMIasRg+k+/gLcf9oT6EgXq2xv7sakk9mO/34QYbRH3uF2/2LmnFv3t/SUT97Dvpn7W+l7j2fTXIwZ/Y99+t0fzMvV19nMrvuT63riyaf6i6Ie60sm9MTc+vshnQ8rbQyN+3GjLNH26+aVQfO139izSvyMHPzv/C6l5Pv9OVMiv//5FtvD1+jfkD9ICY/+gNYT+BdKSMkbzbv5nOG1tTP/PbKnwvPqVtPzlLj7LNvyNuf7mg0fxz8eBv7FSf3NZoNffpwgg9ItFAf/jOMLfX5Xbsx+ey7L9Cib8JqDyI216P5ffIZ+O+2Xp238Xjj5fP78Q8qV/pDmah+9Bjk95PEJNfnVJ/CwFfpbc12m0RLfJ+P4RYofHCFOlS2rmDkhc3hP3j2o5BePk95Xy/KIFigjuv9TiSWh2XzgL0DCGa8L+CqXUx/oGkRBo8xtmWrAxyqgaWkBkUmbSg4Nxhm2JSs7KcgUjOJyvxapZyJbVWbkyOC93D2vQqM+UBGpgegVFHd3zAfXtjfkLht1NP/sEsdn3PzN+X5Dvq9s0PPy6SYKvzb//YBt+/w6R93tbP8g248j17mgxtCyKMijt4GCTk+znej1YIV/ttDbKhOiZ2lLhrHQIS9TuBkhToTiDRshYLpoRYAxLEBRlYRCbnCnLJOJ4aUaGsO5iVUkJzC5qwjqEMF6KnsktWxBILd0x46sYDqOFrJmcsmXB1JI9NvLa8M6vYoYpKZqWTc3ZY6KuDQ7CwqggmPoutnlDA+CQYGaL87AgIgWmPinC7hINEJ7ijPMmIyQFoj5Pxu4UDhBEhp3Xp9gkhTzfT8BuFG4XRYZcVsjrDVK8i4EDoBeNM0iZKZoV4nKLFJU8Z5BbkqinmH8mzRkWISpzwSAXuVCU+ffXggNu00zmkRWX/oAP0jpr+sU43VZvxkfDMORzvrebXbIRvFkuic7YO7WsVMjaiapY7VZBclo/p/02Pi3uLFJ8SyVrtW2pPPMqY7iRrWq8ao3j6gq+77kbqMU+PqGRZ/l3n2AoZ0BUfe56JN5SpnAd9j5gDfG07JTHrfCsfP+r73+mXA9wi8bOsR+Lbx0yo2+ZL9e4XOocmFau691PIaOQzNO4heeujLYgx+Inpu7qJFChNrtdyHhDADkfdFMh02vTBa0+6UsciJdD4zlqbZ2abS264IvJN4V6hGBc51ao9kznokISv4fmld79gMFbyprq5XZhHpDiM8DTxMOT58FP+lnHfroGH3CYdvMQ360CUM3XnesEDffPPcaT2AbGE3sjSXeGWvyOy7MahPwA8GCCxo+UtaiELPqItTiWiQM+vj0y+ERy3pz4fGWuKl4i+xocKn0fWJU5dUWZFdoIIsGsn3LBGcj6tPrdpfJon3RP2X8LEqnEVQDcosPDevXAiJB1V8bdV9NqIOLFGPVI+bzh4/oEUX5Df0iLfAMTatb1xh2QLQoFIDvNxusgp3Ex/iwEIuDDaM5mmwoiPpfus2VNVe4FxWUE/o6DCoXgC4OfLVYaP7fjktzILvPk5UL8yK/orknvNhL1OzCc44gEL7NJaBmPcE+VFu4oOerqP035yMMDLtdWnuZ7Qg3MwsViniJ8Cnf2RnXIvF4jj9T5ym65C7r2p2as7qnSg0SHFewirONan6A9EpiD4x7cUu4H7T3TyZcjBhplvDpzCYodfERuZEX1bTetAxZbGFjBIzp9XHSdpViIElnR7PILFXOAv8pbMqUn+uFuikx6Xt2HZ2vel6YgF+jrimEX3lSMUB5JcdDdH1f0Vr8bYjt/OacAyBD9BCX53rQipC0KZItozrqW1/xNw5YqI/TbEZVXtr18+5blx/pR6Svq49Ii/RowOEZGq4LcdsPlJWntyG1OWBRD3wPFUVMdlJS4nF1JmkkTiVd/6zOo651lmGkxN9P5lqdPykrZ4V436wwS/Bl7sPGQ0SBjGtQpYgMezHA6YFv2Ho8oKLsSYRZR4nOq8wiY8RYuoTCWntIskcdXNJk9jtFtuU7yk75Bo7mtTG0Er0oVQFAcIdm4J6FH1TUh+m3vWYpM/agPb3BjzhdvYeKjz3VnXpaZwqzvP4+3SZNEVc9GwipgNAg13PLAE2MXKhk2uWAWUPSiPlQ1VpGpHI4dfzdAfD3nAhry92UTmBunIxF5trw9EHyTKxf2NX5vEauN6dHoGU0dKDaJlErdpa8QUmRF8yzqI0i1jphMJTe8X9pDWsAsH2eqx03vJTKn9Dq4BrrbFanex6vJ/dhc6oCZoal6A1jzgXjp2uWYOUpyjqi0mBSdpQ+kZZlvfM9FXigxjbPJNDCT/cBI66NmfO69lwq42QVhhmfDzOG4Nb1xi8LHOA55qjVHFnnm/JQdL6g3hjlzgsW383wbCDj1p6KomAZIRMDHGSoAcnsH4NezlLjxOuPFjSiDsCGOJCpodDEDLjCSRPu3g57X3sKr20p8c2hz7Nz2X6AP9uXITUFbtkZVJzbfDSWwNvePYuZ6hU8vvheeDw/mHtdtxE6U7aX9MTSkkqMCKAfGrels/2AyKpWVfvOpGeJeVMNFr+YzghN6c9H1RpOjwRU7x8/OdegixMPMq7yNlovPVtZ2mpFgUFgFba+BjjbWO4RFPDoZToUWOYjE4zDzduIN2VcG0X8r5LsBaj3dzG1aW92UmOMydfqeT3OQWRYHLWLKlS4h+rhq1lthxlAIuzHxOnfsqxGC2nQCzIHx1rEDCd2ZLRvFzcI+iktgfb0pzshhnGALDxNsmzdjGEYtiLQLRnyJDQWSQMF4Sx9Z+n5hU6alN1YmkRSrQu5WuWbmgYlO6u3M5KN0gYqetGGes44OI01lQDYD+/y7HloT4FJpW1kBmoDZNsnE6FAsFgaMMDafwhHyHlILn55KzkjLj5IxY0XshUKukXKJqFEsC50INFVu3xtcX0tx3WMoldcaSm3cB58Hg6Vb84jR5ltYs8oXDu8SB8s3X/gUQBOQWxmuJEhrcxomhtoftTRmEXpt8vuRKWupvvCNy4uNFBoJLztsrRXv6oj9oYPzcssSMEjItCC2pYgys4b3Ijcqo0bp633AIP/24fFpazp1P3XmkEiHZK5EqCCuvezCYQ+1qB04eTuQ1QopL9thi8mJs00KTDr5Ngtkq6lHfAkMGmjghx3yxWkMcgcW08zK0ELpcnPjgeLf/jBE2qlkizoYYgGc3B8fPGlwaq3i9eE3YtjRC6mb9Q0R7Oq4VIElGkWXb3WYnPzUmmbz8DPhhKqpceNWm+nsm7bzjTBjNflFjWADNnX8uk1IXEqYjzbGEKNWFkECUa1kYS2jxcz9SXP9o0MFZ+hTZxGMrrrJPkMEOpqy9lB10SpwDAqwiNT49llKTATrxx7WsqwO0oUgWaOuU3mmsJyPTJt70oOpEB55gfF2z+Nth5rPSyWpZkU7broUpe3L0oBHKq230KUGPE73NRc3ibS+DaOQc2q7yWZx9u9+w1wJ2w3EOETwJbQs5J+PK4bMTJIZNCyuwFSaI6yq0amGz6iOKAVLQsDeIhIAvgbWRZi8W5I20DeDG6cPJrPhxgyGkOIDF8pi5cfSFZHxho5tuRxhb1ck4tPjszSo9jCN4NkP+GCPvTQCWMP3oIkFtkk4aOCH3ni0AXw1qMmh9XRj8yv2zHi8zRhLCuJs4A3JQ443NbDpww8ZJdkJ7ffaqo0DydCzZvRX5/Xqx9DePUmw5WclIFPh/REOnRNiPCuk+8cwSLS1E3nnFxOkq2QQQHGKarZtg3r9ncCMN9PoVIQBv6hAwDm14ZNWyVSv6+TOoxgBwL02t6pPg7ZK8MU+2GgWInYzR95ue8dICqCUF77Q6ON86xVDOWKycHO99g4GfwRfmfohyU2sEgumdoyPRAgPXbGlNXK0nk31Zx8hWyCzDxcesnDpwv0MfptRJzVUhtGM2QpMNetdErfMSBhl9WH5m/OuW6NGeArapZthVY8Tl5Q29KhFeTteRsEba3dDAwcrN6O9aenju2lRIdeZYpWdZW4eKChXCH1qFHsxq3k9HoGQMBnJwOFCN6ctYEX3yK+wv3qTsZbGfp4j6wL2FUnAJqVoXraeiEo5AJaSy0pv4ilqfRb48mIbvJ21Nuseycs+myuUHMmiq/CM+3E9bvAh72XVSvl1viex2PDbSN3THUnjWMCCFCWCf7Yegu7dfFtM6Ipo0NUjXb08xqMBpmyaDL/dUOgxbP4UITnT2mHnWIiMTdBSnDSM8AstqK3zDoc8CLVXOVSos3ehCsKvJOyCuiRATHxDj+u7bmt3K4xpmC+nWhVfLQRqIU/pIquMMZ1NToBRy4NmkV9jP2TY4N2+XOBfc3Go11A2w6i7sXXbUzKFtqnLrIHGrG4sl1S3qlkpbAB4/Gk2wdr3R/HeHiWQGGZZdmbsrXRl5k4oILkwn6cJ70E7pqnFfRm1clKy25fSei11YQzFXQsQnOWB/+t1xFAh4m8r84r6E72YydLM4O32Mp6fFtWjB5UZvETrRt+svhgWF9aULUi54kO2DvliM6axG3F2K+g6nNGgr+069DBxrllU7rUdlylHSo+dpMxOEaNmIHNpLcfLm0MFuvbKqg4JquPZgS8/0IkWhBkNb+6iN0/F1an1JtzQ+yN8fXOzC0MM2dvzCIrOufQ1zCxBjcIpzbQZEztN9exDfxQzebkBJTz+pJ6nuNty1yFFgwu01JtOLp5ajGOVLXpRlWPvvXygYMw7+f6zW7Wmjd+fq68byr3b5b1lmHwVIcWIqeENb+/GVtHl9BVMxXwzEOVguxjMXFNvxdMTCkpzMd365O5HCWwTQ7B8tQWCyd2GJmeDEjn7oiATaO5WqUguLHJLsrCaQikLP5LwfpzWgFRbHXBFP+ER7qES1HQJ8O15UUyoRLdz6BpADaUxziS+8x7b220lVaVkX01qQ1FXY8IN3nPZXWSaN81NmUxhupTqE8/1WINhkb3ISd9FthfhaN2PWVXQMbLQB3WNutR96MssaygyP6MBIK+wCCfbkloAUAp6RlQPPvTGxJhfdHMFmS+/q2MvW2YMhICsqdK6+rbqSCHL9IL1pF4D1uddFk9siEkiMixgwMunQwJyIDXXoH0ZWIYSFjJR1imCU/MJJcEd9vkZI4P7TXT3Brx0q4gUG9Fu79IreRvTJw7sBWO5bXB0znWese5q3S5Sc4ZZx9ge7L5U7DAHfH5kh1/sykK8VRy0TkTw3DyI/DGdV4zpVJrwBXNA1nGDiMhOn0IONsvz+fNNmWRVkkRRiDpbHInrBW6DOUbBygqN+ToedAHMm69lZx7AKeQJnNEa/PLETQplvmRCmSf7VjZaLCOdfiEk3VKEjDg2xtMpKhOykKk17lIMAYuadQGWECfQQjxaZnTlZHmU+N3izDG9e3l1O41yXajo21XalqbjzqQyaXnYuHjVK3yRr34yQqyZKC2I/IeuXarMTwWUCtpeXEyXiyq4TJNWNRZ9U5fyrXByqXEIWGF6t80mgNLPLh88H+un7njX3cbnPW0n4Eb4HvZuyjkNxTCTTji40sqj86FPXrYstzD1gtVFIXKyl4l2KphsqSPAkbg3N64Ypzs3sF/0rKt5lLd0ynsJ/RTt4LYzN+iNLLeRgLah24vm1MLbUNwuSJO2h8AbQOpIuoIPWN9jWjfPt8Pv0fnZWNdwSMjwmAVIDW3nGeu1ls1DT5UHCVEYHGMnE8tsCTlgKx+bhCq6rDPGcbNJhC4OSqvYlD4C2j09dPK2hRB3sbW6KvULOMn9Mx/69kZkyg+QGdH9DjeJ3g6ZFpuYcHcSN0pEE6UAkscoUZAzyKqFAb5lMFzOVx3HCNKSdp8S2areXOkJnAAcHpdZnASHRXaT0773Ky4HRk/PqidvfjW+hlhnLISVrFGjq9P1GCBDyhJT+S5/eTlBrahfbVffUzoX5kg1sn4v5uf5FXdwCQK63VmtZrhWJEatVvJ+mZbytNOrGtcbG/NNySPsEj0B6DF1u5xcoXXpdMxVuqUXx1SjE5SY2+lLcmwhLynbuZfSTZX7N6HZwCtQlU/ARCNKUCT7Kr7LZ7A9yKFWpV9J2LyYj/1nGWZNumRQqK4VyE47gEJDqlhVOJEgWITXDqSwJmmYQURleOMAp01dYLP5RFeC5tw7XMamzoiFpWaRC5pMXYqlay6/V/LXKQ9twMOv2rFuWHASk4JvduJ/NIgQ4hlCbnaZz+hjE9cuhljCLPlzlDwlx5lBVPc+GtvlbmnOMcbr/WI7MvUKLPIiVlglrMt/C9tNnOJEV9ZnJvVGf5J6c8TuFTMcrj6GSeHSIKpBrODbfUDNjDZvhuOG5PnU2A6jPmVftu9xNHymtC+etq66zhpOJl1GOEiRbvdrjf2Na4wl7w4WCt6w1DBp+NJ7WFI7bfNVo7+NPd5alubYYmLNL7CyPkAwTIxjYPwqiKqkmPNyiSL/eSMvs2zUj7IbQpIQZB25xqAeJrOlfKsa9W1DuE4IMhUskifuQtu4Ze3UftsxB+RgXmK5JyibvTgZLZ4999MHWIYGeIL4c3XPjL2or+AqMVp8j1T8+7YnkgMlPdFl7pshO2hAUnID48wczY4enKhQmACstUclzx60kunZkmq0QjFzpvbj0viHsIVQHwm4n5FNj15PLMdy3kk6NjiKjIZJzHDhZPG5jNTFr4zi2UR8QlWk98Peh/GB6WN15TOwCWborl7BMDCqfhAEkBgxzORzLnb/MbrO6ntQ1Dq0thswAKtr52DPaNBrtlzSG9RauR2KwaQ4lS6TWYoZoSZwhq/FjBMT/aTQVBpA2zwi71BrKbw9Dv1tJMnSOUAm2ROqP/v+IkdbAWDeT+SrNJl8wKZQ9itlvKbAqcEuXYGstki2jUQK3C8bG4UPPRrCh9vcYDVDoj5TdDiNVTMMWr+ZEN7e+09ynDGlyKoaCqChI7eLV3JwLCczgEHWskouyH5Zk87BISE1PU30puGI3rx+TIIhwjfttHrtmE5Q9+UsAEQUOTAKFkxW8kK4kbXZM70LAkyVnYC425p0PTodK71fv2GBKGj/duNXNk/Vcuc+fIhbN0ln82EgIEol1fzZ4NsDju7HRGZpRHovdrt2Gal2sxWhMsoQiVBO89PxkSm0aTOneAcdhCKwJbPmXQ1goMeIGiq5OUUiNppm7cwoFopMpNs4jfZyOxDEcR2iXVFGGEaKFad2KpBcZxaUH9EcC2L+46yogrhgpJEgliJ1aNKNRIrOcvABuk0CBFIGLgvAli0C3V6cj25l5UiEqfmI8WSBgIOEzQ/XI317jLVFROdHeG+moYTpLHWn/UTVfCDyNd8FXQKmlGbTqGSeUjiDH5VpxxwXBSEhg+tNtTfn+LRGb3MNn8ubHNl5bBoxln/FT9AxRWqlPVnGyTxH8fzy9NnYus3noyH3I3CMZK4lj+OSODzSPaQWvEGWjD/3Ly3++C9UCASQqRkt2Y7DDg3LEkSHtDd/MktCnFcMecIK8yQgxE2uj/3WqBdx+1Fp8v54DR7mNz8MA9J+Nq83LUGaH48G+6rUCyLDjARyjSAMEreLlOLIx3OBIKdcIYRZ66mU3z1qa7Z9VVFyUayVnhge0ACfPr3PBx/uSoxTUi4RwGCEx89J3Lom2xQ9VQxSqpWZGj7Lc07Ge7r/vis4z1mbSzgYiD79xfL6qO82Saf2VSGRho+6373xvu6Dw4cLmNriPMJ5mAD69Dbx20Ng7yrPKZmUawm6ZOqu4zQYZPS7dL878mzynKoSluNqpoRQgSA8p7q/Shj4dw/g/34WwfnHVIC/ezr+r8gZ+OXpOPqLDBPsKyHk9eSCEPS3N/5fNBfkV9ke1o9B/iK5/v/jjv1McIB/pn38bs8w8D8xywP635/lgUD/QKIU/Cs5Rv+j5PgfWJX/a7M8bKD+c5ZHiu6QaXYsDZY76dqM31DHew9GMt+gqWKs9WShWXEk47Eae3mMWNCvZaCavaWdbwKVqC1QS8oYPIfl54f/tYr87MmWYl9BqbB5ha/P7ccfEIaB9nX7idcQdte65+3B7MSyMAGRVsFuKbscmArD4qlZkrJJ7IZ/IBzB2IeaEUxS0DutCnwLr8tNSmyRYjV1oi9GJ0ilo6/I8e7pi3NB8hlSIJSY51lcIiAjGArDeHCb68qOk8xp6CqSRhQhtCcjJJjHt4JHUYjGt06+kDthhqWkvC96v/sg8Bd7jIJRBgKxHIuQhQVMiHWfxdYJMISp1kKAQYqu3H3wZ8JrQ2GReV5eDGAiLZ8LN/VCVnlyjKfjwq6iHT/onbDFHYeeuBRBxYJhvtYge/p4JpeaFEOTJLuKPX0pOmEqNX1lvpfn1D05itfAAinZPF/tAmkE2UhuOxmUlZ78nNwJudDQWdEyNNfD04UnJI1/cYYCmN7yc9K91gqWHr6evD5PyFB8bxOkP5t39JRY55Ff8a8n6G8vTCq6IHJ1oD3iINuSkkjePgIYAE8glVMxbubq05g+0HtFjdgC+2YPnTYb2uR1j/V9OukT0GhgKzmipXKElqI7UnOi+p4qageFEVIIhOG0E8M1aq3WVsYD+VI+qfoVKQyBXEUkIIQTxzpJpoNWOkS6imjq/CJay15BO+EQpKkDg+LJl+UvMlRvVpN9WoCZqiCMkYDKUmdsU2QcUqJmIQq1CE6yryeSmbGR7/c2zPT8PvE6cH6Fxdt3Zy2kXFxCIqf7xuaRfC8XA/O220YeZL/mCwbyJIzzTG4zu4sgJa9wUaQbP5Q0ydUfd0H2lcIinog/a8G4Qrz1cVp3l7NeJSfCqLmguYVMqEGLKrgCDRrEz3hCK4VnNAJtPhMdyhdl67sn9SziQDyj2z/Tmwfyufn44AgjcGRYcIF6LcmzOp1N3vfTbEQGEMRnuFOehUGEgZRUNQDBdx5XRIHpa4wZmMwq6hy9nZHPyvYQsYfvr61qhNdHFUgiN66vtshIE3I5JSpbTAlXDBnhgYI6k5EiuB9Ab44LypYRPpXXseYKzLjYQMLF6phospnKWGQgHq5ERTgERqn38S2rLLXT03NgZqoiwevo8aKomjrcWUSzUbm5mT+kdiaqzVAThlHAAvlugeTD8spsV9UVp6UzLX4E0rcgRJZsDtp14p/M+NiI7mk1B9vvt8Nj+IBGmqUXkoB/5lZ3o815Hfe+3N1lDfoOe9TEXziOtS+RktQWU1Xf3xHifGY4oPFxEUSo3xLpnIyycSHmoGvMPH5abGFDJTDXEh5jOcpNHClDJN4AkhTUs/s9S1BMcdyGIovk5Jr4KVEPipMEZ3K1ftwjVb0sY2FW41V3wYIRdNbq99pjFuChH8rRprIubkmwOCtuRhhf3tI6jfXr8FYVW1Ewr06xbvWTxNTVmK3E6IdXA+12k4JqLmNkJBgsO0mtxm+Pj9p+BqtPnPotk3y7DozG0VJov0vIqgYagBwZXMNxYTyCFSv9CW32HpE1LEy7Z79ux+iVjDC7Bs7sO6jXQiqttW2cy/lgxM5gBVXyfAnzkMRcpqqp+sQfbCDs48vwjSQ/Z+V1aBTdtyBOkvDH6CXuBmNbfUg2MQcfi6i5oJnq5px8n+zkPUjuMXh0LuIZZeUNV02+uvBAkZ4UkvTgLpJ6cWVf5kYpwK7i7Jr1vbCi5AM2eHqQTuOdecrbT3UDIuyisbjt3UlKm+lEJwjr/GIYnHRkqJFU7zFTkwUP5jV2zdcpwKc9CZ164vexk3/OtG5nYTJVcmfxY1hXl9PVRzcWm0J8Sb9CMhnDT9+Wm2f0BIgpBylXnsPrtQFRUmJs6nTPWa3u5ypY2SoLF8yaVegEpQgWfyM2w30lLpF4xSizd1YAZLyBpvT9VvKdj5WCiqRx+EVpFBafH6h4MiPeCwDMznlh9/wQW6UJzF5zJ+FHCOg66BDID19NpHBw70/nUIS0xvRKLPL7yfowjO5dBlOq1oxNlGqKflohDCGcJvyDv1hIai1iiAQXgxhoSettbid1yw3yJT0OpHXwqlAOdS55JbsJ1t6/xLLd4ecYQDhAUWrafDSeWx9xbY/Wc9NYVoTt9gyDS4pVhjnzSA+sPX5OP42jB2vHZjSi4qHnoHf1IgqhBP8s+LIqNPyJlS2EQMy+fHdsIalSE5ImpFYKJfZkGCPhRYSU17mYPhlJJ1+a0L4bAyySAWnNohAMPksGjBpxo1PO0gpAkUDUGipubvSWMaFx2ucUqQZRKxQMigBBynY7NwAtx5llH1fdUHf5Luesjyq6K9qXuBcV7O26Dy1rN30hMkMIgizN3otlA6eh6rhyzlAst4FrMOkJcvZAnlltCJLjnTUwMz3+0dMJekTvOcTKN5RTHvznSboBAE5rFAdfJsUMWD7SnyM4Gyk/iHBctC5wUrlFpkYJFEXOT/hUgoHVloTXiG3Jk0+6TGoPGGZfnFz/2ui3wMHwYaTTbqDvLBOywa8I9/IsSlQI0eOU9wyd2yPsGyQJVzy/nBcKXxgrouBmDM8pwITYkKTuaaDrYAyI8JOQC34FbAVMf1B8fgIbmrRPmIF8UN56/PmPP5YDs71iNBY7qfSHZ7JnhsCt+X4SCp2HH1oFSSQPpTk9UH/xgrDnD0FB9w+7BZ9XrHzCo/bzOBQzuW8WbNpbPWH81bBEO6b0g9VopiuoGUdRwbC0OrCdC+TS981JSJ3imdKACkeoyqyL1QqmMB2EQ6acNpslme2wy5I6LBPLBbCJPk6h5lxqeX4j7lHIYu9LO2qntQQxtg6lVucnkAmTFFu5F2SvpT6jQYUr3iOg5g18w/uNMJBx64BNfaR3r0cpoDzZBa5t5FZ9VIVz3gjcNZuyO5hms1VLJBuiB55rHBNy/1reJDMTpQQuFx8ugdg+Z+7xrbwehppq0QDNu2vEvjcRplO80mblS8VSYxF1uezamw/lQ1S7QFdqQC7mp8mMvoR7QS2FN6FodmXwREDUnCmQTuctxEx5E0ZzVlWq4JqByBaZnOT90YV7NWOevA5J+SS12VjBob3eCVOVvsoh4Br5mRyz1lNbkcXr9dqeeHBU4glww1cimOKhN2unch0490kdB/2u40LJwF0jIU8KDQ11/dWZ2NDkrYGGoFJ7afIuCdUMpoHTmM845E7FDN3+MYWYnv0Lqs3goYRBgdzsmMyDj1HItfxEtbK9cQbUP610YAHAp+du/nwY3xbay7R0Dc84YyQr6uq96sW9MMbCN8sAB+i6XjevmfV9Dsk32emqAuRrMjIJu48SbOGx95hOuQE8gpHIjo3zVHWYh+ZkXlUP8AWT7t578LwVQ7YDmqIdKbXnxkDe+JFwGxmLEkoCPimXJyr11REzXScHJUW5wk2RcjdIfQELVW1yH4dpvPY4dxYwFMigaOjIFmmr5OMxP05TEmnOzWHyJGlwtkhRtlUyKjnMafG67Fys2LknhNrC9TFRT2RscCDjMxUutfs8Hgnk1aMFS+N9U6k4egoZ7VKJX8HixpOpqLZ5u1uY7S78Vx5RzYEtu8gfc8V0SIE+/iFdpNZ4N2c/k/7GX5FJZ7XH6ZI1nkij4Vr+6XaTfrwftNAZ6RJn23c5UqvxaNUBDhWYvKOTaIy27lKL3UyNyVrpVqNUBjXS9IUFRumEfCRCTZbUCPXiUg2Gxdq12FvqXjqlR098N5yTE7rtLdOTk2fnK93r8A3qniWRT1ZxtC6fMcqe1UyVUilYpTYpnLjMualheWaw0VlWwSjiiQYGnTFaVfNm4dQJrlA8W2ZffeBXWdVXxUkIxz0TujYHMbFwBCH8BgZDlnHZQlP28gpqnxBpihFuR/ZTrEzGADrHDAMEKrmcRNNNJ9j28+I6mWRq4lMWZNDctl0YUI9NxUh9h+ep7k6dANWQhCMUUSJHZt/xCImEpKfFDLIB4t3xAUYg18bAlM8s885YCYWqDIfasZIJ3lC8LKqwcSzidxaCayEucRPtaKTNN5OUbufOCsn10Zx4rCUPlHFHTGjPbIwZr1cJrkg0FIg2H0yJGbfMOHhwnTbuiFXcyJUknl3wksBBWNPH3+sZBC98OlnXTNoaMnFTSfzYGv/REaMlqkAIpawDi9Z7d2FDxIVGyuTtZuyQDncjYOdiszSPK/oqfdgldj8bxgt8WMhOuU884yY84bbUIpbB0nHwUWXeSEG8SDRCOHYWD0uAvKUA6o+1VR1ye+IrI7IV/lYlNbELps4iEb7pX/DhV3H8uMR2FRk86zBbMLawkqZOIeCEGwlNtfSFkL1VObZIeket4/uTu82qMruOnDp4fiLsBMBEzUhCDvqCJTVaZ2mHxb4iNFslmoPuowAcJuQDtMqutRQV7iynrNRRZuD4Kl+eMAPwPJVzeYCVVyYhaN5OPuNjj3MxEw1vTUNk6GgUCArxIE057Y028vdi3jhcnohWkQ/L3Ag4KAVBVG5TWRaKcUA75XlXJbzIXGCyhzK5t04+WSskojq1KbOn5bAGg06la7cXWTDKkOyMFLj1gKCvggAaIk09fudKBiEoaHpTznmjjktWjYlL0/H5+DF3U53s7Z8097JGKngOBuj0cS5TT9tXwXltx0yI8iwuBVg+1DW8yPx1RWjzARh7oN9e/tLPoV9CMiXE3iafl3pqRpKMiofdeJyK7dlw9R5mllXw1MFgTsMxSyN5TMhPJJ7lxGYvvMv/Sk3aecrmOajCYpLraqGUcXi+PbuyFFMDkVvwBbdKi3JORcoMG9jG0uaTnRe2+zY0XOpHQroQlglUPKkIUjlJwrZWsI9ktxfrUvaXhutwbK4FX1XijUri9xqTx7y8d7dq9fRT2kxhThMKxqniMFan3mZOPT5uY3grPMqOVa02IpeZ1xNCghOOS2nL53N8WJdVQrnM8xaOK2kqMsUCGCR1jIL0FPs6SBpv3qSTgE/CGmSxsgu1ZjUittuaB4hYKsFswJwh5V39cUfmvaCUpV69lNM07GZ/bLicV8GYGTsb1aoiV7tZzG0xLIQODh6iEnKP13cVGTCllg08olGhgTdHvTtWRaeYpX7iFP8VH/UnvyFPnVQ/aynikaqyYWs1r3nMKD13eFsMRISgGGZOc5gUwT2JHnKsFLdHH0ENZUoxUj2hJpPhswJ6MbHuGMHNkVJZ0jCqGwJYeE5NJMEz4iE1lP4mhbdLrOAzZSBRjTw5yBOffI416G7WRYg5hqjAC7bxkARu//ITGmQIxZbnejrnQfo9lPIj1EgO3oick+lmIW6pJeTlnzp4cuVikiNUKgaIfORgtWTO2jvOKKjCrapURhODwifBF1AzT62YZ4ag4pUExMfcqrKPo9gh6Q9nYTWirGmolVtHCtsVtYlp/ritq3CcTHKAEgHsva6zCVy1W90GPgZMKzobDQVS1aSA4uLg4vESRRawS01XcAOJhyvqk/j03xylrXrfsabz4Rz6Y2zuVWpgMDBecPtnh8YYn9u+f0VliIpq77kxuXV7OjldZdYW7yb7eFAfUBMmyuqulCx498nPIz3N+EpmOcfaN+cOl02CUTx9tX1tVjMOxoEpMk0Zqx+NnkqeTNgvrpN3t+G0mx0VJmYyNVO7XjOYoEeGP2Qd+1x4dey7hDihUFwvnen0cbE2f/EgQ6Rii5PVTEJCjYL9NZ4aK6/4OYsfn1Ge0pc83Z5OQdFvfMcZiC0ynduESXv5gQjH+cvqJgBS1LGwSDpEOKGH/eqYoNLKu+elDHvuh2HfsY6q3Tyn6OrQddkxBDK6/Q6zvqBBovB7/256wNT8TgZr8eVQ+We5J+4+ie+P7zACVnH4maJd9AXD0MIE1hNHYCVls12p7f3qHNnQuXLt9WQqtD6b8eDbKA0zd98oAV6KeGRRsRpVrnU/cldADEQHt29uDbFM2qpunTOP5xj2lagnIk9CzzNZAo3A9IzQMmAjCuu8PUnxjU65dSkFHeWzVxVrcsvKFYoVlVtFhTaTWU/q8jriMhCP5Mg043xPFU2EH2VWwOgE9V5lYAK13oLAObK5+3Td8dQ1FJk3jqBuu7fYoCIPCx8rnHXDuxLx8TfjW+9oCHPIV/YQ6BVeo5BhRAotR6Oz/AlfZcKNfHC4NYZ4r33Ob59KyIu1F6WBsUxq7pL8bAtZsT95e9UgniJEJWmd+rh8pV1+9MRAhKLeUOBN8nl141jhAYMCjKHG3FTySpYVny1hVlBW4BlcotazEtOmHbXV5LZrHV+RBEYcs23c8lgzLy2SUxVSon9ioKdoaixyX7ALMt6TecKQIkEFkwXUEyN4ndJYZJIUmhQ4FiGNvpGHJMvbUmk5uDIzXiL7T4aRDg4hUUvY5VFJVjoiXt/879G2V0Tybbng80zYrGGCLSMHQD4CWOq8ZVXMALfqjm52Gvw5rS6tQdMpxuQQM50sKpbHlDJxRp5bwaL5C5j3UOCDE9Y3VihMHFMXYuHw4FpF5PCFvNyHed+iuTjNd9iWgrExGE9aGi1cBjFmEnRMjE48rxmFGDkKRNOih8bimAY5kL07TGeJzJNdZXKy0wgcRYkC7SrnurUUI01IiOR45J3HUUHr1K00cDmJhvOWCF7YB23dbGU4FprAAYMbuGn3e+nc0NeK5DljQTJVrkZBIm6fhXWbBGV3U6nlBgZWrX/OW5zc9dyr95+zh9WvLBgtqZz47zb+VhtPtEcR8YNgU3ocvtIHCFI0HYSZajHP868Egn8ihwD6G+e2/1QOwS/PXv/D3rCHf5FD8NdfRnJ/JJ6sgv97kwn+8a37mUzw/qute/9nfmHELzbuPzuV4E9/+Z1Sv0olAH8pzsh/kDi//n+cSmBSv/vCiAzeP67p8xfSLXFZth+AcBx/z8VR6L0X9rryd8K9GcGxzfEj0eftXFDllgsRaal9SUb5wlNuDgSmNXnkSZilwr96wL8pCX7h58y4tT4RAADorUpYmNrcZvt121AIyl5Zlh7IS38IWb1BE4Ic2LV9Ehv6tOkLvnbY0FmBoKzzboAIJEPgGtYgcogEDZggJIIkZ/rMCUMm+J55slhJn2DOKIk3eizveQ+WpJphBbME83wRwSujXcZwCItJosqWL8kgCXytuQmGe4IhUKAtndbBSUIUdfVVgE8SJTCR3BMy3949XPWj2oWQlxz4m2Xe9P540OVsvpw3syLF88ViZNWjzwsT7TAGWeRWoz8GtEFOBrhq+nMCiYTVhaGo1ikfuje3hCgH+1mnqAGloLb8MIQUpR52gRCldA/jCswmyM5LAq6CXHlePGMF5qMWwtI7pMK1u0ftwvN+I+uLoUkItDMzPqWwZ7XEqFEy8BK9mrYbzrQLgYTuE70pHDsJcsISPMxgJiQmFE55TgN4IL498pu4PcwfS+u3bgOXpoFr4XgEaRnx2jRE1gViWR2DMnq0cPSMaS5ySy3xkRE9AccjbgQFcHEXzX0FJUFPr+L8pAKe2DkdbYr4KfVIitKgsmQ0ymHhw9OaObfe94xYXXUVGw5Zgsi5xDqV7AVm40oLNMlXsgO9Cz9qn/XFxfnDHWXUDeNzAONlHDq5sJSLtrNt/IEnKmcC9y8t3EKnKfzARO4ltR4WzYByL2NsoRGNSppS5mLORSusvLs8+gLdTil4/HNMESis944QTk7CibLrtJeG6D4wUui593NVFgl1aYt+7LTAQrOEU2rHlAdvzRV60PBpsvebQyNCtWiRPmOssrkqj9VG/8IDlYJ7aSYOToqvYwN8h2QKsIUXFWBu0gfkO1m3EZjgKLXFC4HqUM4ITxAxeGLp5NpzQ6w9ybRnoM1gppK+elopTxtUzdvx4lWGe92YbfYAURBltGr5BCFSy7eT0w5VXVovUITk8+swhw5yPb9Qv8WVVq30zyjaZ7rQgeWQOK/bYcxFvjrBGlfNrEUk3MeCw1nolldEHQxMKjZzzp3Yhu1nbWLzvm9HMRR5qgqrHLazIvkCW2LCIiRACIxx3OieDOAuhKIVEytLC2+bKLnn3CcAqOAFP8epe6QKp1JxWLo951OofrTxgMK4q2TFdCCx7a1vJX5b6JoDYfZimD2iNtpdnhd8j/MJWAUI8Fnk3vC06R5yPr3AXFZOGA10zs0lyniCztju5kzDPN/8oE2um3O0gRx4QJ3D+JKogw3IHNCSN0fMcqyc+S1uBEXx3lDj/SFRW4IcBna3d+RiSXYgybBnvtho82DyTLHW2+jlkHxaV4g9IvSEOguDguhjDgjgeVXg1aGoo004+3guGEOPiJ+Hr6EHlYmzbY/ucyMYzvA6uXK+kafmR/B4MhQWZ6VaN1q8Lm6aEwl5PwsT6SQLTkSb5QMrjP0Kw5ATsAig6D0kcSkmA5rcXY9dciIv5NZvLXB3i6lKHYecPi5qiez7dHzmx33+vg84hVilrkCF/pGygAc2LogjAiMJCtIhKOT4eVcYuVBmXPI5eHxw+kYkxDJo6QEK27ilSxH5hAUnkIanEGuUikBrbbKpqFbPmjfuCJaE9vNOMcQsySyZB8fFI1tND/45kq3BYzmwDR4uJDnBLmq02aJck5AnuGSdG33+at/f81wahAGCmiJD04KlAKsWxCqc1iOsJxOANYC701MdjtuOsPmIawJWaedrptvCoiz6/Xkc061Rh0xlO9jY2Zi8BQw8XTHo0MLJCpuyyNsAJSbgJnJoDJ0kkiphA5C6xUz/pCgz0z4a571Hwbsb88txJCkiwG4sUXXBxl2uW8AEsok9YQNglXLd1R6r0EvFLl/rSZIoDj5ssSIVWRkxBGmmLv5AWMb5XFn/emGwZUiWg9QNWhzh92eEhFLx9AEzKUg+T1oB4hKeIA9C/ZFYws6fcBI/f47zJaNYp9AC4zv5Q5jsx37aGW7g4+ISBmmNj9IEm1ZLhEi16V4hrKsRrZ2yjm9pDSs3JWESSZ9zzSvoE/HzHH6v+22pkGYaHNKi16gor/TrnXQD7Ib0RsX2QMGL3+nS77IZ4A221btSEiZPRoSO4BoUR6iPsz9NJSlJksgztDidoeeQwWYOGyYOqcMpFXFuhG5AtaPhD5WFpnDj4GsRdPHsZyILG9g3U0ZtFoYsJddQuEexiOHDJuEF5gFDDDr4NndflnRyGVKLfc58+d2+WocoCcQUVFk7LFjeSwrfI3q3cHkgFoLqcx6EgrFX5al6wnhuTcKE5LBMwZW32chmb/JHBlyhWG1sp+6JgTIKJgozMrno/sKTZ2u4zQJgOheNWVJEd8QJ2ltn+ezGCLakgUDI57UFuHOtxn5eDaBLbQOLclNcijBpnijV57jeGJIxtNCgyh8TP75w4CvgdyvNyJCN3EgUAW4T9uBlgxU4Zjba7Aw9y4jONNNbmZWlJjzfKeN/Oml9wl41ObfHIDuUt67yvVehwZgIto6XvyZEUVWsVKvsQRVpHt12V813jnhwx3tNdUww935H70ADLcJ77O7oyiNemuJnDc/OOI/bWnKzR492/xAV5aPzfVmi1wJYqyXQNXuee9QopA0j2rVgSACnz/kn4oYLZQU5Z9S9ybTCLkUS8KrgU/HpOqCVCxOQDSUsUsWCi88+N9+VUOX52qnboi6GYLCTcSPY826CvHlZB1miqCzSTZoWqQhjBwGfM6IRxMx37OYGEbkdDQ3xGrvTRDut1araTezgu4MVA3spwSC+vZmiE3sIOeWpxAQUsAms+nSAF8LWz4wHCfCreAGILNcGTpwWE/k3Au3y/nmJh0YHHzoKcOaWskyNtdXpUK+rkfeEwgwvwbJBnhL62nrfuWHFvHj3tSQSQR+r3GjWA3bPqanAL2b0AjvT90ENnQiFqom5CMRwrInkQDw8l50izzWjqKxS9WqCkRYPNt3ZrUQkurV2EnvJkI4aXwghZ3PwtmnBmVkFY1BHVPbEHMqicSpEsLgOwQlNDaccofyv9q5r2XHciH6N30kx6pE5iaSYwxtzELNIieTXG9Cd8e56Z+yt8tqucrnqvuAKJVINoPscnEbDmWeLB3glurcWp/i+zjaqr8v1yAra1gNEsfTuG2EaNnAtr/r/s37yrARhCTkXcs32HZc7vJm9VUI1DRe5qAOlO++EFOKvTzouAjiuyBLnXFg5QKfaBR9G3/Oc+qvIHssNlipwsjCDyLuhA8bwRikLIUybmuHahI7isjCbddp3h1XGgVup7Vv8czxniK8ID2KKJApheWxTyB78S9XUpWksDbvg3ONVjlHuOsGDBn5dDR8p6h629MC+NsrLRXXJLBYFPF+NTyxaFz5UDJgjO+/Rs7i9t6+eX38Qw8VykYGYpCmB90lP9fSmGHo7NmWqfPv0651O13B0LZt9KrPWQr/v5H1IJ3Yuq0X4Xu60d3B4XBfqEmwrT0SG2JTlDRNjxnXbtk7CF0YNULjSiQ2+qAn9kX43Cc+2r2pYMOhNOhS8jt13s7dxV/q8DXOZ2DvBKZWFP9S+bGn7nqJVi10BLimlLD2oiD1mkWUmIu2Eqxk0TnYvbwgnCNJVAZjJhUdcWRTdNgBHerwVG5+ZlMIOiJ5oi+ARHiH0NfWbNYcv84lYfPG70xz8pEkxQDXehZ2L1zhznrLKQcDk66d+EjWLF+xDzHkfkDqBuK51y++PXC7Rd7HakRdE974cDk2B4T7oICYV9qbMyZdtAj9dJ34t5q1hBJauvKH2KgaX7mBgEZigSmlDlofhsZvDRMJMoVBMn4ElnIf3mE49TepF0EhxzldYaQvu0M9dx/d5MfZtuF5enbycGd9ktxYSkDTiK0fUnm2AsCpz8uLRdLf+1j/xyS8F/pV685QTlgE4zQ5Cp87Ma7icRLNHo49eJKWXMGplJEtnzjAhMaPPVPaSLLdYxgxsQak71r5eJRR6VMIMp1BeK/3spCitfeCxuQM7qWWPH29BhaWHOFpUzmGTZg+gj1NgnHQ8ajgHa7PfK7apwRvShuU9OjgReaz0By4VUkepvOf8eT6Z2+rS2g1Rb743GWKJ22P/IfOM2t2Cx0KEjXLYux5imBslqzFuR8Qqs09zrZkiZIcuD7sl2tXRJWJfs7jJ7KfaCB1Syif9NGTbs0x4ot9f48mp10B7TtDOBUBC5hJqjwCnmqA62s1c5AyK6JHRSkyrhTnMJAuDHfDCSHykNsIZYWhslWBb894RFe0grmQa8hJVvHDoC5SJPvWaIRfCOGRf1FqCRlJCxuca0YwuEX80fbCQAHmGEaMogWV9CvU4wUcT8cx4+c7Xr+eavY8JSd5czOILMdAb81yBG4WCsDFX6aOXTFPyNyLbvLAx25VvguvdClv1TYUH65B5xEXkeiszGs2H/QjmQL232lOwmXYXguhy7ha+qyLNucbd7iTk4ss0Jokpl9XZWqJfPwbMro6RxdJ8+bNH+5CjhM+7PmA2vt7JkwnuYDpdXOR6d3UEfUbak+1tHeF1HSXL84FyiUZxytDhTJZf993GSb2Vj6IRqVUZMhcBYNM8n5Rpv036PF9H5yct/loA1Dktc9BHQ/AVSYjKh7jAVUWEJsXPHw5pmwswjUDqt+qdHO8IesRwUGLPfRDqvC0PoWTEjmNyWm4Dj/3ifSH5ORwwHQRDymPcLXp42a4IsZuoG44aOrsdUligR3LDzb56cs8mMYI1oPL+JLAR+FHMdlMHAiyKYLFrG11M10MNKB3LCVbftROr9PdEgnnytCowy6e7AJa/XJDbDqh+s6aR5PF9lWpm71whJDb8DokJSFaLjFXvF/QqRE0xL76y9obUluOBUnCXtSZzhna8G+nRvVLpqpRI+OY0q1S33et+Z8bGo5dOiNXPCVT91uFbSl/pCJbqGm4BTdSp+pbimaIMQN1TGEvQHZZc9bCjhokwLcy7RMTkQgHorDd5KVmFccC4edryoXUF5qd5hzA50zTj+6SuRn5aGxsBf96HeiqQQ9YidgnMfznTMSDJgijx3CZYUZQWfpSFWrmhBYXA3TfkfGEaMW0lkV/SYp7cpOdsdQpLhMh61GVp5jk6iWvTyFnlAncXpxismlNB9ts14kkbvQ7hmxUqNo67DM1zdmRYkXupQXw6pmvXGINnQbWQ1cbotvO2IlParrupv/Z4ZYdLjnenxSp2FYiClFqJGQ343uWXC79hCfeazYpRlYIDfQFbvKpwiWqrzFubBognnH2sed2ogXkwmsY1XSlH5FdagBxhBQ/PAoDuRam9mWneYHuo2nhrn8l23kbtOhWMqHrvEO69pnaFJDP6UjjlPI9PNsiLceJgRQAmbUYeNWTenpe06I2DysmZfaSIDkJisaY5Vz1zk3/Nt5V1gJWDcxtZiX/VKqyOoQmGAJZiwNtg3qXq1ESWwdwEa0Zod//0a7W0Tq6Eg6auvh60VZhUY1aKQ2XdCXh4dTDKHfoywcayYEnQrdx3Ntl2BizfV9hOMI9xpMxHNhkA+6yA3e+nb5l0sGeM0hjOSKzImO8S0pqOIBJ4DaJ2tb5oRIvlo+/SujJhuItkWCWkwn2GY0zWwiCWaJFL2vZs3FJts+gv4TmtrqE+OpSIaOU1wB1a9brR5qwwFfdQcFTqy4fHcDyRK0Zo7xajsOhk6Is+vRnmT96VnkRppHeaqzirvC3sfOiQgLN59bazw4ZVUMr0/a+KlPhPFKGfipTY7w/N/ljVQf9dIiX1A5HyS5LEP5cg4H9h/pcPOv/xEfuuTaI/OOhMUv9BdfL3dxAUeVV8/+HjstZjNQ5JJ/zy378z3C99biNU0z4j2BbrenwzZ7Kt42/H9+uZ8EG/ETp/cFfAc9yWrPgHM+77bXfJUhXrP9Mbfz8OS9Ela/P67Xv86UYm/xtGBqZcjvDXjQh+GZhw35r8/u3Lv1rHt9afODiXPzg4P1kk/+rgwHD8t4sDP5/96vpFTPgr
\ No newline at end of file
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png"
deleted file mode 100644
index c976cc99b912f4ce1049482ff6bf27d6817e60e6..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png" and /dev/null differ
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio"
deleted file mode 100644
index 7f7bfd71641b9b8e6822b9368fdca5827fa92ddf..0000000000000000000000000000000000000000
--- "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png"
deleted file mode 100644
index f8f457c7490d10907baa89f5786aa57faf2de3f4..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png" and /dev/null differ
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio"
deleted file mode 100644
index 7626c8d1f5004745fcbd532b7a6a90c6f3b2a9e1..0000000000000000000000000000000000000000
--- "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png" "b/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png"
deleted file mode 100644
index ecdbd6d2c2e2a773b0013f527e180497b63277e2..0000000000000000000000000000000000000000
Binary files "a/docs/database/Redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png" and /dev/null differ
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/basis.md
similarity index 40%
rename from "docs/database/\346\225\260\346\215\256\345\272\223\345\237\272\347\241\200\347\237\245\350\257\206.md"
rename to docs/database/basis.md
index 92d30998a71152999db0e45a4fc1ce8a409575fd..d8cd9db61bafd8c1ac86906a90d2f633080a626a 100644
--- "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/basis.md
@@ -1,105 +1,103 @@
---
-title: 数据库基础知识
+title: 数据库基础知识总结
category: 数据库
tag:
- 数据库基础
---
+
+
数据库知识基础,这部分内容一定要理解记忆。虽然这部分内容只是理论知识,但是非常重要,这是后面学习 MySQL 数据库的基础。PS: 这部分内容由于涉及太多概念性内容,所以参考了维基百科和百度百科相应的介绍。
## 什么是数据库, 数据库管理系统, 数据库系统, 数据库管理员?
-* **数据库** : 数据库(DataBase 简称 DB)就是信息的集合或者说数据库是由数据库管理系统管理的数据的集合。
-* **数据库管理系统** : 数据库管理系统(Database Management System 简称 DBMS)是一种操纵和管理数据库的大型软件,通常用于建立、使用和维护数据库。
-* **数据库系统** : 数据库系统(Data Base System,简称 DBS)通常由软件、数据库和数据管理员(DBA)组成。
-* **数据库管理员** : 数据库管理员(Database Administrator, 简称 DBA)负责全面管理和控制数据库系统。
-
-数据库系统基本构成如下图所示:
-
-
+- **数据库** : 数据库(DataBase 简称 DB)就是信息的集合或者说数据库是由数据库管理系统管理的数据的集合。
+- **数据库管理系统** : 数据库管理系统(Database Management System 简称 DBMS)是一种操纵和管理数据库的大型软件,通常用于建立、使用和维护数据库。
+- **数据库系统** : 数据库系统(Data Base System,简称 DBS)通常由软件、数据库和数据管理员(DBA)组成。
+- **数据库管理员** : 数据库管理员(Database Administrator, 简称 DBA)负责全面管理和控制数据库系统。
## 什么是元组, 码, 候选码, 主码, 外码, 主属性, 非主属性?
-* **元组** : 元组(tuple)是关系数据库中的基本概念,关系是一张表,表中的每行(即数据库中的每条记录)就是一个元组,每列就是一个属性。 在二维表里,元组也称为行。
-* **码** :码就是能唯一标识实体的属性,对应表中的列。
-* **候选码** : 若关系中的某一属性或属性组的值能唯一的标识一个元组,而其任何、子集都不能再标识,则称该属性组为候选码。例如:在学生实体中,“学号”是能唯一的区分学生实体的,同时又假设“姓名”、“班级”的属性组合足以区分学生实体,那么{学号}和{姓名,班级}都是候选码。
-* **主码** : 主码也叫主键。主码是从候选码中选出来的。 一个实体集中只能有一个主码,但可以有多个候选码。
-* **外码** : 外码也叫外键。如果一个关系中的一个属性是另外一个关系中的主码则这个属性为外码。
-* **主属性** : 候选码中出现过的属性称为主属性。比如关系 工人(工号,身份证号,姓名,性别,部门). 显然工号和身份证号都能够唯一标示这个关系,所以都是候选码。工号、身份证号这两个属性就是主属性。如果主码是一个属性组,那么属性组中的属性都是主属性。
-* **非主属性:** 不包含在任何一个候选码中的属性称为非主属性。比如在关系——学生(学号,姓名,年龄,性别,班级)中,主码是“学号”,那么其他的“姓名”、“年龄”、“性别”、“班级”就都可以称为非主属性。
+- **元组**:元组(tuple)是关系数据库中的基本概念,关系是一张表,表中的每行(即数据库中的每条记录)就是一个元组,每列就是一个属性。 在二维表里,元组也称为行。
+- **码**:码就是能唯一标识实体的属性,对应表中的列。
+- **候选码**:若关系中的某一属性或属性组的值能唯一的标识一个元组,而其任何、子集都不能再标识,则称该属性组为候选码。例如:在学生实体中,“学号”是能唯一的区分学生实体的,同时又假设“姓名”、“班级”的属性组合足以区分学生实体,那么{学号}和{姓名,班级}都是候选码。
+- **主码** : 主码也叫主键。主码是从候选码中选出来的。 一个实体集中只能有一个主码,但可以有多个候选码。
+- **外码** : 外码也叫外键。如果一个关系中的一个属性是另外一个关系中的主码则这个属性为外码。
+- **主属性**:候选码中出现过的属性称为主属性。比如关系 工人(工号,身份证号,姓名,性别,部门). 显然工号和身份证号都能够唯一标示这个关系,所以都是候选码。工号、身份证号这两个属性就是主属性。如果主码是一个属性组,那么属性组中的属性都是主属性。
+- **非主属性:** 不包含在任何一个候选码中的属性称为非主属性。比如在关系——学生(学号,姓名,年龄,性别,班级)中,主码是“学号”,那么其他的“姓名”、“年龄”、“性别”、“班级”就都可以称为非主属性。
-## 主键和外键有什么区别?
+## 什么是 ER 图?
-* **主键(主码)** :主键用于唯一标识一个元组,不能有重复,不允许为空。一个表只能有一个主键。
-* **外键(外码)** :外键用来和其他表建立联系用,外键是另一表的主键,外键是可以有重复的,可以是空值。一个表可以有多个外键。
+我们做一个项目的时候一定要试着画 ER 图来捋清数据库设计,这个也是面试官问你项目的时候经常会被问到的。
-## 为什么不推荐使用外键与级联?
+**ER 图** 全称是 Entity Relationship Diagram(实体联系图),提供了表示实体类型、属性和联系的方法。
-对于外键和级联,阿里巴巴开发手册这样说到:
+ER 图由下面 3 个要素组成:
-> 【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
->
-> 说明: 以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群; 级联更新是强阻塞,存在数据库更新风暴的风 险; 外键影响数据库的插入速度
+- **实体**:通常是现实世界的业务对象,当然使用一些逻辑对象也可以。比如对于一个校园管理系统,会涉及学生、教师、课程、班级等等实体。在 ER 图中,实体使用矩形框表示。
+- **属性**:即某个实体拥有的属性,属性用来描述组成实体的要素,对于产品设计来说可以理解为字段。在 ER 图中,属性使用椭圆形表示。
+- **联系**:即实体与实体之间的关系,在 ER 图中用菱形表示,这个关系不仅有业务关联关系,还能通过数字表示实体之间的数量对照关系。例如,一个班级会有多个学生就是一种实体间的联系。
-为什么不要用外键呢?大部分人可能会这样回答:
+下图是一个学生选课的 ER 图,每个学生可以选若干门课程,同一门课程也可以被若干人选择,所以它们之间的关系是多对多(M: N)。另外,还有其他两种实体之间的关系是:1 对 1(1:1)、1 对多(1: N)。
-> 1. **增加了复杂性:** a. 每次做DELETE 或者UPDATE都必须考虑外键约束,会导致开发的时候很痛苦, 测试数据极为不方便; b. 外键的主从关系是定的,假如那天需求有变化,数据库中的这个字段根本不需要和其他表有关联的话就会增加很多麻烦。
-> 2. **增加了额外工作**: 数据库需要增加维护外键的工作,比如当我们做一些涉及外键字段的增,删,更新操作之后,需要触发相关操作去检查,保证数据的的一致性和正确性,这样会不得不消耗资源;(个人觉得这个不是不用外键的原因,因为即使你不使用外键,你在应用层面也还是要保证的。所以,我觉得这个影响可以忽略不计。)
-> 3. 外键还会因为需要请求对其他表内部加锁而容易出现死锁情况;
-> 4. **对分库分表不友好** :因为分库分表下外键是无法生效的。
-> 5. ......
+
-我个人觉得上面这种回答不是特别的全面,只是说了外键存在的一个常见的问题。实际上,我们知道外键也是有很多好处的,比如:
+## 数据库范式了解吗?
-1. 保证了数据库数据的一致性和完整性;
-2. 级联操作方便,减轻了程序代码量;
-3. ......
+数据库范式有 3 种:
-所以说,不要一股脑的就抛弃了外键这个概念,既然它存在就有它存在的道理,如果系统不涉及分库分表,并发量不是很高的情况还是可以考虑使用外键的。
+- 1NF(第一范式):属性不可再分。
+- 2NF(第二范式):1NF 的基础之上,消除了非主属性对于码的部分函数依赖。
+- 3NF(第三范式):3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。
+### 1NF(第一范式)
-## 什么是 ER 图?
+属性(对应于表中的字段)不能再被分割,也就是这个字段只能是一个值,不能再分为多个其他的字段了。**1NF 是所有关系型数据库的最基本要求** ,也就是说关系型数据库中创建的表一定满足第一范式。
-> 我们做一个项目的时候一定要试着画 ER 图来捋清数据库设计,这个也是面试官问你项目的时候经常会被问道的。
+### 2NF(第二范式)
-**E-R 图** 也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。 它是描述现实世界关系概念模型的有效方法。 是表示概念关系模型的一种方式。
+2NF 在 1NF 的基础之上,消除了非主属性对于码的部分函数依赖。如下图所示,展示了第一范式到第二范式的过渡。第二范式在第一范式的基础上增加了一个列,这个列称为主键,非主属性都依赖于主键。
-下图是一个学生选课的 ER 图,每个学生可以选若干门课程,同一门课程也可以被若干人选择,所以它们之间的关系是多对多(M: N)。另外,还有其他两种关系是:1 对 1(1:1)、1 对多(1: N)。
+
-
+一些重要的概念:
-我们试着将上面的 ER 图转换成数据库实际的关系模型(实际设计中,我们通常会将任课教师也作为一个实体来处理):
+- **函数依赖(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(第三范式)
-## 数据库范式了解吗?
-
-**1NF(第一范式)**
+3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。符合 3NF 要求的数据库设计,**基本**上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。比如在关系 R(学号 , 姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖,所以该表的设计,不符合 3NF 的要求。
-属性(对应于表中的字段)不能再被分割,也就是这个字段只能是一个值,不能再分为多个其他的字段了。**1NF 是所有关系型数据库的最基本要求** ,也就是说关系型数据库中创建的表一定满足第一范式。
+## 主键和外键有什么区别?
-**2NF(第二范式)**
+- **主键(主码)**:主键用于唯一标识一个元组,不能有重复,不允许为空。一个表只能有一个主键。
+- **外键(外码)**:外键用来和其他表建立联系用,外键是另一表的主键,外键是可以有重复的,可以是空值。一个表可以有多个外键。
-2NF 在 1NF 的基础之上,消除了非主属性对于码的部分函数依赖。如下图所示,展示了第一范式到第二范式的过渡。第二范式在第一范式的基础上增加了一个列,这个列称为主键,非主属性都依赖于主键。
+## 为什么不推荐使用外键与级联?
-
+对于外键和级联,阿里巴巴开发手册这样说到:
-一些重要的概念:
+> 【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
+>
+> 说明: 以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群; 级联更新是强阻塞,存在数据库更新风暴的风 险; 外键影响数据库的插入速度
-* **函数依赖(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(第三范式)**
+1. **增加了复杂性:** a. 每次做 DELETE 或者 UPDATE 都必须考虑外键约束,会导致开发的时候很痛苦, 测试数据极为不方便; b. 外键的主从关系是定的,假如那天需求有变化,数据库中的这个字段根本不需要和其他表有关联的话就会增加很多麻烦。
+2. **增加了额外工作**:数据库需要增加维护外键的工作,比如当我们做一些涉及外键字段的增,删,更新操作之后,需要触发相关操作去检查,保证数据的的一致性和正确性,这样会不得不消耗资源;(个人觉得这个不是不用外键的原因,因为即使你不使用外键,你在应用层面也还是要保证的。所以,我觉得这个影响可以忽略不计。)
+3. **对分库分表不友好**:因为分库分表下外键是无法生效的。
+4. ......
-3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。符合 3NF 要求的数据库设计,**基本**上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。比如在关系 R(学号 , 姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖,所以该表的设计,不符合 3NF 的要求。
+我个人觉得上面这种回答不是特别的全面,只是说了外键存在的一个常见的问题。实际上,我们知道外键也是有很多好处的,比如:
-**总结**
+1. 保证了数据库数据的一致性和完整性;
+2. 级联操作方便,减轻了程序代码量;
+3. ......
-* 1NF:属性不可再分。
-* 2NF:1NF 的基础之上,消除了非主属性对于码的部分函数依赖。
-* 3NF:3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。
+所以说,不要一股脑的就抛弃了外键这个概念,既然它存在就有它存在的道理,如果系统不涉及分库分表,并发量不是很高的情况还是可以考虑使用外键的。
## 什么是存储过程?
@@ -109,30 +107,38 @@ tag:
阿里巴巴 Java 开发手册里要求禁止使用存储过程。
-
+
## drop、delete 与 truncate 区别?
### 用法不同
-* drop(丢弃数据): `drop table 表名` ,直接将表都删除掉,在删除表的时候使用。
-* truncate (清空数据) : `truncate table 表名` ,只删除表中的数据,再插入数据的时候自增长 id 又从 1 开始,在清空表中数据的时候使用。
-* delete(删除数据) : `delete from 表名 where 列名=值`,删除某一列的数据,如果不加 where 子句和`truncate table 表名`作用类似。
+- `drop`(丢弃数据): `drop table 表名` ,直接将表都删除掉,在删除表的时候使用。
+- `truncate` (清空数据) : `truncate table 表名` ,只删除表中的数据,再插入数据的时候自增长 id 又从 1 开始,在清空表中数据的时候使用。
+- `delete`(删除数据) : `delete from 表名 where 列名=值`,删除某一行的数据,如果不加 `where` 子句和`truncate table 表名`作用类似。
-truncate 和不带 where 子句的 delete、以及 drop 都会删除表内的数据,但是 **truncate 和 delete 只删除数据不删除表的结构(定义),执行 drop 语句,此表的结构也会删除,也就是执行 drop 之后对应的表不复存在。**
+`truncate` 和不带 `where`子句的 `delete`、以及 `drop` 都会删除表内的数据,但是 **`truncate` 和 `delete` 只删除数据不删除表的结构(定义),执行 `drop` 语句,此表的结构也会删除,也就是执行`drop` 之后对应的表不复存在。**
### 属于不同的数据库语言
-truncate 和 drop 属于 DDL(数据定义语言)语句,操作立即生效,原数据不放到 rollback segment 中,不能回滚,操作不触发 trigger。而 delete 语句是 DML (数据库操作语言)语句,这个操作会放到 rollback segement 中,事务提交之后才生效。
+`truncate` 和 `drop` 属于 DDL(数据定义语言)语句,操作立即生效,原数据不放到 rollback segment 中,不能回滚,操作不触发 trigger。而 `delete` 语句是 DML (数据库操作语言)语句,这个操作会放到 rollback segment 中,事务提交之后才生效。
**DML 语句和 DDL 语句区别:**
-* DML 是数据库操作语言(Data Manipulation Language)的缩写,是指对数据库中表记录的操作,主要包括表记录的插入(insert)、更新(update)、删除(delete)和查询(select),是开发人员日常使用最频繁的操作。
-* DDL (Data Definition Language)是数据定义语言的缩写,简单来说,就是对数据库内部的对象进行创建、删除、修改的操作语言。它和 DML 语言的最大区别是 DML 只是对表内部数据的操作,而不涉及到表的定义、结构的修改,更不会涉及到其他对象。DDL 语句更多的被数据库管理员(DBA)所使用,一般的开发人员很少使用。
+- DML 是数据库操作语言(Data Manipulation Language)的缩写,是指对数据库中表记录的操作,主要包括表记录的插入、更新、删除和查询,是开发人员日常使用最频繁的操作。
+- DDL (Data Definition Language)是数据定义语言的缩写,简单来说,就是对数据库内部的对象进行创建、删除、修改的操作语言。它和 DML 语言的最大区别是 DML 只是对表内部数据的操作,而不涉及到表的定义、结构的修改,更不会涉及到其他对象。DDL 语句更多的被数据库管理员(DBA)所使用,一般的开发人员很少使用。
+
+另外,由于`select`不会对表进行破坏,所以有的地方也会把`select`单独区分开叫做数据库查询语言 DQL(Data Query Language)。
### 执行速度不同
-一般来说:drop>truncate>delete(这个我没有设计测试过)。
+一般来说:`drop` > `truncate` > `delete`(这个我没有实际测试过)。
+
+- `delete`命令执行的时候会产生数据库的`binlog`日志,而日志记录是需要消耗时间的,但是也有个好处方便数据回滚恢复。
+- `truncate`命令执行的时候不会产生数据库日志,因此比`delete`要快。除此之外,还会把表的自增值重置和索引恢复到初始大小等。
+- `drop`命令会把表占用的空间全部释放掉。
+
+Tips:你应该更多地关注在使用场景上,而不是执行效率。
## 数据库设计通常分为哪几步?
@@ -145,6 +151,6 @@ truncate 和 drop 属于 DDL(数据定义语言)语句,操作立即生效,
## 参考
-*
-*
-*
+-
+-
+-
diff --git "a/docs/database/\345\255\227\347\254\246\351\233\206.md" b/docs/database/character-set.md
similarity index 41%
rename from "docs/database/\345\255\227\347\254\246\351\233\206.md"
rename to docs/database/character-set.md
index 1cca2575e6efecc0075388865fd04f9e713cb3e9..feb08c3989df6d3ab12403b118821d26df4b4caa 100644
--- "a/docs/database/\345\255\227\347\254\246\351\233\206.md"
+++ b/docs/database/character-set.md
@@ -1,14 +1,13 @@
---
-title: 字符集
+title: 字符集详解
category: 数据库
tag:
- 数据库基础
---
-
MySQL 字符编码集中有两套 UTF-8 编码实现:**`utf8`** 和 **`utf8mb4`**。
-如果使用 **`utf8`** 的话,存储emoji 符号和一些比较复杂的汉字、繁体字就会出错。
+如果使用 **`utf8`** 的话,存储 emoji 符号和一些比较复杂的汉字、繁体字就会出错。
为什么会这样呢?这篇文章可以从源头给你解答。
@@ -18,7 +17,7 @@ MySQL 字符编码集中有两套 UTF-8 编码实现:**`utf8`** 和 **`utf8mb4
**计算机只能存储二进制的数据,那英文、汉字、表情等字符应该如何存储呢?**
-我们要将这些字符和二级制的数据一一对应起来,比如说字符“a”对应“01100001”,反之,“01100001”对应 “a”。我们将字符对应二进制数据的过程称为"**字符编码**",反之,二进制数据解析成字符的过程称为“**字符解码**”。
+我们要将这些字符和二进制的数据一一对应起来,比如说字符“a”对应“01100001”,反之,“01100001”对应 “a”。我们将字符对应二进制数据的过程称为"**字符编码**",反之,二进制数据解析成字符的过程称为“**字符解码**”。
## 有哪些常见的字符集?
@@ -41,7 +40,7 @@ ASCII 字符集至今为止共定义了 128 个字符,其中有 33 个控制
由于,ASCII 码可以表示的字符实在是太少了。后来,人们对其进行了扩展得到了 **ASCII 扩展字符集** 。ASCII 扩展字符集使用 8 位(bits)表示一个字符,所以,ASCII 扩展字符集可以定义 256(2^8)个字符。
-
+
### GB2312
@@ -65,7 +64,7 @@ GB18030 完全兼容 GB2312 和 GBK 字符集,纳入中国国内少数民族
BIG5 主要针对的是繁体中文,收录了 13000 多个汉字。
-### Unicode & UTF-8编码
+### Unicode & UTF-8 编码
为了更加适合本国语言,诞生了很多种字符集。
@@ -73,13 +72,13 @@ BIG5 主要针对的是繁体中文,收录了 13000 多个汉字。
就比如说你使用 UTF-8 编码方式打开 GB2312 编码格式的文件就会出现乱码。示例:“牛”这个汉字 GB2312 编码后的十六进制数值为 “C5A3”,而 “C5A3” 用 UTF-8 解码之后得到的却是 “ţ”。
-你可以通过这个网站在线进行编码和解码:https://www.haomeili.net/HanZi/ZiFuBianMaZhuanHuan
+你可以通过这个网站在线进行编码和解码:
-
+
-这样我们就搞懂了乱码的本质: **编码和解码时用了不同或者不兼容的字符集** 。
+这样我们就搞懂了乱码的本质:**编码和解码时用了不同或者不兼容的字符集** 。
-
+
为了解决这个问题,人们就想:“如果我们能够有一种字符集将世界上所有的字符都纳入其中就好了!”。
@@ -95,17 +94,180 @@ UTF-8 可以根据不同的符号自动选择编码的长短,像英文字符
UTF-32 的规则最简单,不过缺陷也比较明显,对于英文字母这类字符消耗的空间是 UTF-8 的 4 倍之多。
-**UTF-8** 是目前使用最广的一种字符编码,。
+**UTF-8** 是目前使用最广的一种字符编码。
-
+
## MySQL 字符集
MySQL 支持很多种字符编码的方式,比如 UTF-8、GB2312、GBK、BIG5。
-你可以通过 `SHOW CHARSET` 命令来查看。
+### 查看支持的字符集
+
+你可以通过 `SHOW CHARSET` 命令来查看,支持 like 和 where 子句。
+
+
+
+### 默认字符集
+
+在 MySQL5.7 中,默认字符集是 `latin1` ;在 MySQL8.0 中,默认字符集是 `utf8mb4`
+
+### 字符集的层次级别
+
+MySQL 中的字符集有以下的层次级别:
+
+- `server`(MySQL 实例级别)
+- `database`(库级别)
+- `table`(表级别)
+- `column`(字段级别)
+
+它们的优先级可以简单的认为是从上往下依次增大,也即 `column` 的优先级会大于 `table` 等其余层次的。如指定 MySQL 实例级别字符集是`utf8mb4`,指定某个表字符集是`latin1`,那么这个表的所有字段如果不指定的话,编码就是`latin1`。
+
+#### server
+
+不同版本的 MySQL 其 `server` 级别的字符集默认值不同,在 MySQL5.7 中,其默认值是 `latin1` ;在 MySQL8.0 中,其默认值是 `utf8mb4` 。
+
+当然也可以通过在启动 `mysqld` 时指定 `--character-set-server` 来设置 `server` 级别的字符集。
+
+```bash
+mysqld
+mysqld --character-set-server=utf8mb4
+mysqld --character-set-server=utf8mb4 \
+ --collation-server=utf8mb4_0900_ai_ci
+```
+
+或者如果你是通过源码构建的方式启动的 MySQL,你可以在 `cmake` 命令中指定选项:
+
+```sh
+cmake . -DDEFAULT_CHARSET=latin1
+或者
+cmake . -DDEFAULT_CHARSET=latin1 \
+ -DDEFAULT_COLLATION=latin1_german1_ci
+```
+
+此外,你也可以在运行时改变 `character_set_server` 的值,从而达到修改 `server` 级别的字符集的目的。
+
+`server` 级别的字符集是 MySQL 服务器的全局设置,它不仅会作为创建或修改数据库时的默认字符集(如果没有指定其他字符集),还会影响到客户端和服务器之间的连接字符集,具体可以查看 [MySQL Connector/J 8.0 - 6.7 Using Character Sets and Unicode](https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-charsets.html)。
+
+#### database
+
+`database` 级别的字符集是我们在创建数据库和修改数据库时指定的:
+
+```sql
+CREATE DATABASE db_name
+ [[DEFAULT] CHARACTER SET charset_name]
+ [[DEFAULT] COLLATE collation_name]
+
+ALTER DATABASE db_name
+ [[DEFAULT] CHARACTER SET charset_name]
+ [[DEFAULT] COLLATE collation_name]
+```
+
+如前面所说,如果在执行上述语句时未指定字符集,那么 MySQL 将会使用 `server` 级别的字符集。
+
+可以通过下面的方式查看某个数据库的字符集:
+
+```sql
+USE db_name;
+SELECT @@character_set_database, @@collation_database;
+```
+
+```sql
+SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
+FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = 'db_name';
+```
+
+#### table
+
+`table` 级别的字符集是在创建表和修改表时指定的:
+
+```sql
+CREATE TABLE tbl_name (column_list)
+ [[DEFAULT] CHARACTER SET charset_name]
+ [COLLATE collation_name]]
+
+ALTER TABLE tbl_name
+ [[DEFAULT] CHARACTER SET charset_name]
+ [COLLATE collation_name]
+```
+
+如果在创建表和修改表时未指定字符集,那么将会使用 `database` 级别的字符集。
+
+#### column
+
+`column` 级别的字符集同样是在创建表和修改表时指定的,只不过它是定义在列中。下面是个例子:
+
+```sql
+CREATE TABLE t1
+(
+ col1 VARCHAR(5)
+ CHARACTER SET latin1
+ COLLATE latin1_german1_ci
+);
+```
+
+如果未指定列级别的字符集,那么将会使用表级别的字符集。
+
+### 连接字符集
+
+前面说到了字符集的层次级别,它们是和存储相关的。而连接字符集涉及的是和 MySQL 服务器的通信。
+
+连接字符集与下面这几个变量息息相关:
+
+- `character_set_client` :描述了客户端发送给服务器的 SQL 语句使用的是什么字符集。
+- `character_set_connection` :描述了服务器接收到 SQL 语句时使用什么字符集进行翻译。
+- `character_set_results` :描述了服务器返回给客户端的结果使用的是什么字符集。
+
+它们的值可以通过下面的 SQL 语句查询:
+
+```sql
+SELECT * FROM performance_schema.session_variables
+WHERE VARIABLE_NAME IN (
+'character_set_client', 'character_set_connection',
+'character_set_results', 'collation_connection'
+) ORDER BY VARIABLE_NAME;
+```
+
+```sql
+SHOW SESSION VARIABLES LIKE 'character\_set\_%';
+```
+
+如果要想修改前面提到的几个变量的值,有以下方式:
+
+1、修改配置文件
+
+```properties
+[mysql]
+# 只针对MySQL客户端程序
+default-character-set=utf8mb4
+```
+
+2、使用 SQL 语句
+
+```sql
+set names utf8mb4
+# 或者一个个进行修改
+# SET character_set_client = utf8mb4;
+# SET character_set_results = utf8mb4;
+# SET collation_connection = utf8mb4;
+```
+
+### jdbc 对连接字符集的影响
+
+不知道你们有没有碰到过存储 emoji 表情正常,但是使用类似 Navicat 之类的软件的进行查询的时候,发现 emoji 表情变成了问号的情况。这个问题很有可能就是 jdbc 驱动引起的。
+
+根据前面的内容,我们知道连接字符集也是会影响我们存储的数据的,而 jdbc 驱动会影响连接字符集。
+
+`mysql-connector-java` (jdbc 驱动)主要通过这几个属性影响连接字符集:
+
+- `characterEncoding`
+- `characterSetResults`
+
+以 `DataGrip 2023.1.2` 来说,在它配置数据源的高级对话框中,可以看到 `characterSetResults` 的默认值是 `utf8` ,在使用 `mysql-connector-java 8.0.25` 时,连接字符集最后会被设置成 `utf8mb3` 。那么这种情况下 emoji 表情就会被显示为问号,并且当前版本驱动还不支持把 `characterSetResults` 设置为 `utf8mb4` ,不过换成 `mysql-connector-java driver 8.0.29` 却是允许的。
+
+具体可以看一下 StackOverflow 的 [DataGrip MySQL stores emojis correctly but displays them as?](https://stackoverflow.com/questions/54815419/datagrip-mysql-stores-emojis-correctly-but-displays-them-as)这个回答。
-
+### UTF-8 使用
通常情况下,我们建议使用 UTF-8 作为默认的字符编码方式。
@@ -113,12 +275,12 @@ MySQL 支持很多种字符编码的方式,比如 UTF-8、GB2312、GBK、BIG5
MySQL 字符编码集中有两套 UTF-8 编码实现:
-- **`utf8`** : `utf8`编码只支持`1-3`个字节 。 在 `utf8` 编码中,中文是占 3 个字节,其他数字、英文、符号占一个字节。但 emoji 符号占 4 个字节,一些较复杂的文字、繁体字也是 4 个字节。
-- **`utf8mb4`** : UTF-8 的完整实现,正版!最多支持使用 4 个字节表示字符,因此,可以用来存储 emoji 符号。
+- **`utf8`**:`utf8`编码只支持`1-3`个字节 。 在 `utf8` 编码中,中文是占 3 个字节,其他数字、英文、符号占一个字节。但 emoji 符号占 4 个字节,一些较复杂的文字、繁体字也是 4 个字节。
+- **`utf8mb4`**:UTF-8 的完整实现,正版!最多支持使用 4 个字节表示字符,因此,可以用来存储 emoji 符号。
**为什么有两套 UTF-8 编码实现呢?** 原因如下:
-
+
因此,如果你需要存储`emoji`类型的数据或者一些比较复杂的文字、繁体字到 MySQL 数据库的话,数据库的编码一定要指定为`utf8mb4` 而不是`utf8` ,要不然存储的时候就会报错了。
@@ -128,10 +290,10 @@ MySQL 字符编码集中有两套 UTF-8 编码实现:
```sql
CREATE TABLE `user` (
- `id` varchar(66) CHARACTER SET utf8mb4 NOT NULL,
- `name` varchar(33) CHARACTER SET utf8mb4 NOT NULL,
- `phone` varchar(33) CHARACTER SET utf8mb4 DEFAULT NULL,
- `password` varchar(100) CHARACTER SET utf8mb4 DEFAULT NULL
+ `id` varchar(66) CHARACTER SET utf8mb3 NOT NULL,
+ `name` varchar(33) CHARACTER SET utf8mb3 NOT NULL,
+ `phone` varchar(33) CHARACTER SET utf8mb3 DEFAULT NULL,
+ `password` varchar(100) CHARACTER SET utf8mb3 DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```
@@ -140,7 +302,7 @@ CREATE TABLE `user` (
```sql
INSERT INTO `user` (`id`, `name`, `phone`, `password`)
VALUES
- ('A00003', 'guide哥😘😘😘', '181631312312', '123456');
+ ('A00003', 'guide哥😘😘😘', '181631312312', '123456');
```
@@ -152,9 +314,12 @@ Incorrect string value: '\xF0\x9F\x98\x98\xF0\x9F...' for column 'name' at row 1
## 参考
-- 字符集和字符编码(Charset & Encoding): https://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html
-- 十分钟搞清字符集和字符编码:http://cenalulu.github.io/linux/character-encoding/
-- Unicode-维基百科:https://zh.wikipedia.org/wiki/Unicode
-- GB2312-维基百科:https://zh.wikipedia.org/wiki/GB_2312
-- UTF-8-维基百科:https://zh.wikipedia.org/wiki/UTF-8
-- GB18030-维基百科: https://zh.wikipedia.org/wiki/GB_18030
\ No newline at end of file
+- 字符集和字符编码(Charset & Encoding):
+- 十分钟搞清字符集和字符编码:
+- Unicode-维基百科:
+- GB2312-维基百科:
+- UTF-8-维基百科:
+- GB18030-维基百科:
+- MySQL8 文档:
+- MySQL5.7 文档:
+- MySQL Connector/J 文档:
diff --git a/docs/database/elasticsearch/elasticsearch-questions-01.md b/docs/database/elasticsearch/elasticsearch-questions-01.md
new file mode 100644
index 0000000000000000000000000000000000000000..be3817a594947c8bd7a6320061a7fdbf2abdde45
--- /dev/null
+++ b/docs/database/elasticsearch/elasticsearch-questions-01.md
@@ -0,0 +1,13 @@
+---
+title: Elasticsearch常见面试题总结(付费)
+category: 数据库
+tag:
+ - NoSQL
+ - Elasticsearch
+---
+
+**Elasticsearch** 相关的面试题为我的[知识星球](../../about-the-author/zhishixingqiu-two-years.md)(点击链接即可查看详细介绍以及加入方法)专属内容,已经整理到了[《Java 面试指北》](../../zhuanlan/java-mian-shi-zhi-bei.md)中。
+
+
+
+
diff --git a/docs/database/mongodb/mongodb-questions-01.md b/docs/database/mongodb/mongodb-questions-01.md
new file mode 100644
index 0000000000000000000000000000000000000000..699d11bd9410a3cc67955f909f491c2a9020e177
--- /dev/null
+++ b/docs/database/mongodb/mongodb-questions-01.md
@@ -0,0 +1,284 @@
+---
+title: MongoDB常见面试题总结(上)
+category: 数据库
+tag:
+ - NoSQL
+ - MongoDB
+---
+
+> 少部分内容参考了 MongoDB 官方文档的描述,在此说明一下。
+
+## MongoDB 基础
+
+### MongoDB 是什么?
+
+MongoDB 是一个基于 **分布式文件存储** 的开源 NoSQL 数据库系统,由 **C++** 编写的。MongoDB 提供了 **面向文档** 的存储方式,操作起来比较简单和容易,支持“**无模式**”的数据建模,可以存储比较复杂的数据类型,是一款非常流行的 **文档类型数据库** 。
+
+在高负载的情况下,MongoDB 天然支持水平扩展和高可用,可以很方便地添加更多的节点/实例,以保证服务性能和可用性。在许多场景下,MongoDB 可以用于代替传统的关系型数据库或键/值存储方式,皆在为 Web 应用提供可扩展的高可用高性能数据存储解决方案。
+
+### MongoDB 的存储结构是什么?
+
+MongoDB 的存储结构区别于传统的关系型数据库,主要由如下三个单元组成:
+
+- **文档(Document)**:MongoDB 中最基本的单元,由 BSON 键值对(key-value)组成,类似于关系型数据库中的行(Row)。
+- **集合(Collection)**:一个集合可以包含多个文档,类似于关系型数据库中的表(Table)。
+- **数据库(Database)**:一个数据库中可以包含多个集合,可以在 MongoDB 中创建多个数据库,类似于关系型数据库中的数据库(Database)。
+
+也就是说,MongoDB 将数据记录存储为文档 (更具体来说是[BSON 文档](https://www.mongodb.com/docs/manual/core/document/#std-label-bson-document-format)),这些文档在集合中聚集在一起,数据库中存储一个或多个文档集合。
+
+**SQL 与 MongoDB 常见术语对比**:
+
+| SQL | MongoDB |
+| ------------------------ | ------------------------------- |
+| 表(Table) | 集合(Collection) |
+| 行(Row) | 文档(Document) |
+| 列(Col) | 字段(Field) |
+| 主键(Primary Key) | 对象 ID(Objectid) |
+| 索引(Index) | 索引(Index) |
+| 嵌套表(Embedded Table) | 嵌入式文档(Embedded Document) |
+| 数组(Array) | 数组(Array) |
+
+#### 文档
+
+MongoDB 中的记录就是一个 BSON 文档,它是由键值对组成的数据结构,类似于 JSON 对象,是 MongoDB 中的基本数据单元。字段的值可能包括其他文档、数组和文档数组。
+
+
+
+文档的键是字符串。除了少数例外情况,键可以使用任意 UTF-8 字符。
+
+- 键不能含有 `\0`(空字符)。这个字符用来表示键的结尾。
+- `.` 和 `$` 有特别的意义,只有在特定环境下才能使用。
+- 以下划线`_`开头的键是保留的(不是严格要求的)。
+
+**BSON [bee·sahn]** 是 Binary [JSON](http://json.org/)的简称,是 JSON 文档的二进制表示,支持将文档和数组嵌入到其他文档和数组中,还包含允许表示不属于 JSON 规范的数据类型的扩展。有关 BSON 规范的内容,可以参考 [bsonspec.org](http://bsonspec.org/),另见[BSON 类型](https://www.mongodb.com/docs/manual/reference/bson-types/)。
+
+根据维基百科对 BJSON 的介绍,BJSON 的遍历速度优于 JSON,这也是 MongoDB 选择 BSON 的主要原因,但 BJSON 需要更多的存储空间。
+
+> 与 JSON 相比,BSON 着眼于提高存储和扫描效率。BSON 文档中的大型元素以长度字段为前缀以便于扫描。在某些情况下,由于长度前缀和显式数组索引的存在,BSON 使用的空间会多于 JSON。
+
+
+
+#### 集合
+
+MongoDB 集合存在于数据库中,**没有固定的结构**,也就是 **无模式** 的,这意味着可以往集合插入不同格式和类型的数据。不过,通常情况下,插入集合中的数据都会有一定的关联性。
+
+
+
+集合不需要事先创建,当第一个文档插入或者第一个索引创建时,如果该集合不存在,则会创建一个新的集合。
+
+集合名可以是满足下列条件的任意 UTF-8 字符串:
+
+- 集合名不能是空字符串`""`。
+- 集合名不能含有 `\0` (空字符),这个字符表示集合名的结尾。
+- 集合名不能以"system."开头,这是为系统集合保留的前缀。例如 `system.users` 这个集合保存着数据库的用户信息,`system.namespaces` 集合保存着所有数据库集合的信息。
+- 集合名必须以下划线或者字母符号开始,并且不能包含 `$`。
+
+#### 数据库
+
+数据库用于存储所有集合,而集合又用于存储所有文档。一个 MongoDB 中可以创建多个数据库,每一个数据库都有自己的集合和权限。
+
+MongoDB 预留了几个特殊的数据库。
+
+- **admin** : admin 数据库主要是保存 root 用户和角色。例如,system.users 表存储用户,system.roles 表存储角色。一般不建议用户直接操作这个数据库。将一个用户添加到这个数据库,且使它拥有 admin 库上的名为 dbAdminAnyDatabase 的角色权限,这个用户自动继承所有数据库的权限。一些特定的服务器端命令也只能从这个数据库运行,比如关闭服务器。
+- **local** : local 数据库是不会被复制到其他分片的,因此可以用来存储本地单台服务器的任意 collection。一般不建议用户直接使用 local 库存储任何数据,也不建议进行 CRUD 操作,因为数据无法被正常备份与恢复。
+- **config** : 当 MongoDB 使用分片设置时,config 数据库可用来保存分片的相关信息。
+- **test** : 默认创建的测试库,连接 [mongod](https://mongoing.com/docs/reference/program/mongod.html) 服务时,如果不指定连接的具体数据库,默认就会连接到 test 数据库。
+
+数据库名可以是满足以下条件的任意 UTF-8 字符串:
+
+- 不能是空字符串`""`。
+- 不得含有`' '`(空格)、`.`、`$`、`/`、`\`和 `\0` (空字符)。
+- 应全部小写。
+- 最多 64 字节。
+
+数据库名最终会变成文件系统里的文件,这也就是有如此多限制的原因。
+
+### MongoDB 有什么特点?
+
+- **数据记录被存储为文档**:MongoDB 中的记录就是一个 BSON 文档,它是由键值对组成的数据结构,类似于 JSON 对象,是 MongoDB 中的基本数据单元。
+- **模式自由**:集合的概念类似 MySQL 里的表,但它不需要定义任何模式,能够用更少的数据对象表现复杂的领域模型对象。
+- **支持多种查询方式**:MongoDB 查询 API 支持读写操作 (CRUD)以及数据聚合、文本搜索和地理空间查询。
+- **支持 ACID 事务**:NoSQL 数据库通常不支持事务,为了可扩展和高性能进行了权衡。不过,也有例外,MongoDB 就支持事务。与关系型数据库一样,MongoDB 事务同样具有 ACID 特性。MongoDB 单文档原生支持原子性,也具备事务的特性。MongoDB 4.0 加入了对多文档事务的支持,但只支持复制集部署模式下的事务,也就是说事务的作用域限制为一个副本集内。MongoDB 4.2 引入了分布式事务,增加了对分片集群上多文档事务的支持,并合并了对副本集上多文档事务的现有支持。
+- **高效的二进制存储**:存储在集合中的文档,是以键值对的形式存在的。键用于唯一标识一个文档,一般是 ObjectId 类型,值是以 BSON 形式存在的。BSON = Binary JSON, 是在 JSON 基础上加了一些类型及元数据描述的格式。
+- **自带数据压缩功能**:存储同样的数据所需的资源更少。
+- **支持 mapreduce**:通过分治的方式完成复杂的聚合任务。不过,从 MongoDB 5.0 开始,map-reduce 已经不被官方推荐使用了,替代方案是 [聚合管道](https://www.mongodb.com/docs/manual/core/aggregation-pipeline/)。聚合管道提供比 map-reduce 更好的性能和可用性。
+- **支持多种类型的索引**:MongoDB 支持多种类型的索引,包括单字段索引、复合索引、多键索引、哈希索引、文本索引、 地理位置索引等,每种类型的索引有不同的使用场合。
+- **支持 failover**:提供自动故障恢复的功能,主节点发生故障时,自动从从节点中选举出一个新的主节点,确保集群的正常使用,这对于客户端来说是无感知的。
+- **支持分片集群**:MongoDB 支持集群自动切分数据,让集群存储更多的数据,具备更强的性能。在数据插入和更新时,能够自动路由和存储。
+- **支持存储大文件**:MongoDB 的单文档存储空间要求不超过 16MB。对于超过 16MB 的大文件,MongoDB 提供了 GridFS 来进行存储,通过 GridFS,可以将大型数据进行分块处理,然后将这些切分后的小文档保存在数据库中。
+
+### MongoDB 适合什么应用场景?
+
+**MongoDB 的优势在于其数据模型和存储引擎的灵活性、架构的可扩展性以及对强大的索引支持。**
+
+选用 MongoDB 应该充分考虑 MongoDB 的优势,结合实际项目的需求来决定:
+
+- 随着项目的发展,使用类 JSON 格式(BSON)保存数据是否满足项目需求?MongoDB 中的记录就是一个 BSON 文档,它是由键值对组成的数据结构,类似于 JSON 对象,是 MongoDB 中的基本数据单元。
+- 是否需要大数据量的存储?是否需要快速水平扩展?MongoDB 支持分片集群,可以很方便地添加更多的节点(实例),让集群存储更多的数据,具备更强的性能。
+- 是否需要更多类型索引来满足更多应用场景?MongoDB 支持多种类型的索引,包括单字段索引、复合索引、多键索引、哈希索引、文本索引、 地理位置索引等,每种类型的索引有不同的使用场合。
+- ......
+
+## MongoDB 存储引擎
+
+### MongoDB 支持哪些存储引擎?
+
+存储引擎(Storage Engine)是数据库的核心组件,负责管理数据在内存和磁盘中的存储方式。
+
+与 MySQL 一样,MongoDB 采用的也是 **插件式的存储引擎架构** ,支持不同类型的存储引擎,不同的存储引擎解决不同场景的问题。在创建数据库或集合时,可以指定存储引擎。
+
+> 插件式的存储引擎架构可以实现 Server 层和存储引擎层的解耦,可以支持多种存储引擎,如 MySQL 既可以支持 B-Tree 结构的 InnoDB 存储引擎,还可以支持 LSM 结构的 RocksDB 存储引擎。
+
+在存储引擎刚出来的时候,默认是使用 MMAPV1 存储引擎,MongoDB4.x 版本不再支持 MMAPv1 存储引擎。
+
+现在主要有下面这两种存储引擎:
+
+- **WiredTiger 存储引擎**:自 MongoDB 3.2 以后,默认的存储引擎为 [WiredTiger 存储引擎](https://www.mongodb.com/docs/manual/core/wiredtiger/) 。非常适合大多数工作负载,建议用于新部署。WiredTiger 提供文档级并发模型、检查点和数据压缩(后文会介绍到)等功能。
+- **In-Memory 存储引擎**:[In-Memory 存储引擎](https://www.mongodb.com/docs/manual/core/inmemory/)在 MongoDB Enterprise 中可用。它不是将文档存储在磁盘上,而是将它们保留在内存中以获得更可预测的数据延迟。
+
+此外,MongoDB 3.0 提供了 **可插拔的存储引擎 API** ,允许第三方为 MongoDB 开发存储引擎,这点和 MySQL 也比较类似。
+
+### WiredTiger 基于 LSM Tree 还是 B+ Tree?
+
+目前绝大部分流行的数据库存储引擎都是基于 B/B+ Tree 或者 LSM(Log Structured Merge) Tree 来实现的。对于 NoSQL 数据库来说,绝大部分(比如 HBase、Cassandra、RocksDB)都是基于 LSM 树,MongoDB 不太一样。
+
+上面也说了,自 MongoDB 3.2 以后,默认的存储引擎为 WiredTiger 存储引擎。在 WiredTiger 引擎官网上,我们发现 WiredTiger 使用的是 B+ 树作为其存储结构:
+
+```
+WiredTiger maintains a table's data in memory using a data structure called a B-Tree ( B+ Tree to be specific), referring to the nodes of a B-Tree as pages. Internal pages carry only keys. The leaf pages store both keys and values.
+```
+
+此外,WiredTiger 还支持 [LSM(Log Structured Merge)](https://source.wiredtiger.com/3.1.0/lsm.html) 树作为存储结构,MongoDB 在使用 WiredTiger 作为存储引擎时,默认使用的是 B+ 树。
+
+如果想要了解 MongoDB 使用 B 树的原因,可以看看这篇文章:[为什么 MongoDB 使用 B 树?](https://mp.weixin.qq.com/s/mMWdpbYRiT6LQcdaj4hgXQ)。
+
+使用 B+ 树时,WiredTiger 以 **page** 为基本单位往磁盘读写数据。B+ 树的每个节点为一个 page,共有三种类型的 page:
+
+- **root page(根节点)**:B+ 树的根节点。
+- **internal page(内部节点)**:不实际存储数据的中间索引节点。
+- **leaf page(叶子节点)**:真正存储数据的叶子节点,包含一个页头(page header)、块头(block header)和真正的数据(key/value),其中页头定义了页的类型、页中实际载荷数据的大小、页中记录条数等信息;块头定义了此页的 checksum、块在磁盘上的寻址位置等信息。
+
+其整体结构如下图所示:
+
+
+
+如果想要深入研究学习 WiredTiger 存储引擎,推荐阅读 MongoDB 中文社区的 [WiredTiger 存储引擎系列](https://mongoing.com/archives/category/wiredtiger%e5%ad%98%e5%82%a8%e5%bc%95%e6%93%8e%e7%b3%bb%e5%88%97)。
+
+## MongoDB 聚合
+
+### MongoDB 聚合有什么用?
+
+实际项目中,我们经常需要将多个文档甚至是多个集合汇总到一起计算分析(比如求和、取最大值)并返回计算后的结果,这个过程被称为 **聚合操作** 。
+
+根据官方文档介绍,我们可以使用聚合操作来:
+
+- 将来自多个文档的值组合在一起。
+- 对集合中的数据进行的一系列运算。
+- 分析数据随时间的变化。
+
+### MongoDB 提供了哪几种执行聚合的方法?
+
+MongoDB 提供了两种执行聚合的方法:
+
+- **聚合管道(Aggregation Pipeline)**:执行聚合操作的首选方法。
+- **单一目的聚合方法(Single purpose aggregation methods)**:也就是单一作用的聚合函数比如 `count()`、`distinct()`、`estimatedDocumentCount()`。
+
+绝大部分文章中还提到了 **map-reduce** 这种聚合方法。不过,从 MongoDB 5.0 开始,map-reduce 已经不被官方推荐使用了,替代方案是 [聚合管道](https://www.mongodb.com/docs/manual/core/aggregation-pipeline/)。聚合管道提供比 map-reduce 更好的性能和可用性。
+
+MongoDB 聚合管道由多个阶段组成,每个阶段在文档通过管道时转换文档。每个阶段接收前一个阶段的输出,进一步处理数据,并将其作为输入数据发送到下一个阶段。
+
+每个管道的工作流程是:
+
+1. 接受一系列原始数据文档
+2. 对这些文档进行一系列运算
+3. 结果文档输出给下一个阶段
+
+
+
+**常用阶段操作符**:
+
+| 操作符 | 简述 |
+| --------- | ---------------------------------------------------------------------------------------------------- |
+| \$match | 匹配操作符,用于对文档集合进行筛选 |
+| \$project | 投射操作符,用于重构每一个文档的字段,可以提取字段,重命名字段,甚至可以对原有字段进行操作后新增字段 |
+| \$sort | 排序操作符,用于根据一个或多个字段对文档进行排序 |
+| \$limit | 限制操作符,用于限制返回文档的数量 |
+| \$skip | 跳过操作符,用于跳过指定数量的文档 |
+| \$count | 统计操作符,用于统计文档的数量 |
+| \$group | 分组操作符,用于对文档集合进行分组 |
+| \$unwind | 拆分操作符,用于将数组中的每一个值拆分为单独的文档 |
+| \$lookup | 连接操作符,用于连接同一个数据库中另一个集合,并获取指定的文档,类似于 populate |
+
+更多操作符介绍详见官方文档:
+
+阶段操作符用于 `db.collection.aggregate` 方法里面,数组参数中的第一层。
+
+```sql
+db.collection.aggregate( [ { 阶段操作符:表述 }, { 阶段操作符:表述 }, ... ] )
+```
+
+下面是 MongoDB 官方文档中的一个例子:
+
+```sql
+db.orders.aggregate([
+ # 第一阶段:$match阶段按status字段过滤文档,并将status等于"A"的文档传递到下一阶段。
+ { $match: { status: "A" } },
+ # 第二阶段:$group阶段按cust_id字段将文档分组,以计算每个cust_id唯一值的金额总和。
+ { $group: { _id: "$cust_id", total: { $sum: "$amount" } } }
+])
+```
+
+## MongoDB 事务
+
+> MongoDB 事务想要搞懂原理还是比较花费时间的,我自己也没有搞太明白。因此,我这里只是简单介绍一下 MongoDB 事务,想要了解原理的小伙伴,可以自行搜索查阅相关资料。
+>
+> 这里推荐几篇文章,供大家参考:
+>
+> - [技术干货| MongoDB 事务原理](https://mongoing.com/archives/82187)
+> - [MongoDB 一致性模型设计与实现](https://developer.aliyun.com/article/782494)
+> - [MongoDB 官方文档对事务的介绍](https://www.mongodb.com/docs/upcoming/core/transactions/)
+
+我们在介绍 NoSQL 数据的时候也说过,NoSQL 数据库通常不支持事务,为了可扩展和高性能进行了权衡。不过,也有例外,MongoDB 就支持事务。
+
+与关系型数据库一样,MongoDB 事务同样具有 ACID 特性:
+
+- **原子性**(`Atomicity`):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
+- **一致性**(`Consistency`):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
+- **隔离性**(`Isolation`):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。WiredTiger 存储引擎支持读未提交( read-uncommitted )、读已提交( read-committed )和快照( snapshot )隔离,MongoDB 启动时默认选快照隔离。在不同隔离级别下,一个事务的生命周期内,可能出现脏读、不可重复读、幻读等现象。
+- **持久性**(`Durability`):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
+
+关于事务的详细介绍这篇文章就不多说了,感兴趣的可以看看我写的[MySQL 常见面试题总结](../mysql/mysql-questions-01.md)这篇文章,里面有详细介绍到。
+
+MongoDB 单文档原生支持原子性,也具备事务的特性。当谈论 MongoDB 事务的时候,通常指的是 **多文档** 。MongoDB 4.0 加入了对多文档 ACID 事务的支持,但只支持复制集部署模式下的 ACID 事务,也就是说事务的作用域限制为一个副本集内。MongoDB 4.2 引入了 **分布式事务** ,增加了对分片集群上多文档事务的支持,并合并了对副本集上多文档事务的现有支持。
+
+根据官方文档介绍:
+
+> 从 MongoDB 4.2 开始,分布式事务和多文档事务在 MongoDB 中是一个意思。分布式事务是指分片集群和副本集上的多文档事务。从 MongoDB 4.2 开始,多文档事务(无论是在分片集群还是副本集上)也称为分布式事务。
+
+在大多数情况下,多文档事务比单文档写入会产生更大的性能成本。对于大部分场景来说, [非规范化数据模型(嵌入式文档和数组)](https://www.mongodb.com/docs/upcoming/core/data-model-design/#std-label-data-modeling-embedding) 依然是最佳选择。也就是说,适当地对数据进行建模可以最大限度地减少对多文档事务的需求。
+
+**注意**:
+
+- 从 MongoDB 4.2 开始,多文档事务支持副本集和分片集群,其中:主节点使用 WiredTiger 存储引擎,同时从节点使用 WiredTiger 存储引擎或 In-Memory 存储引擎。在 MongoDB 4.0 中,只有使用 WiredTiger 存储引擎的副本集支持事务。
+- 在 MongoDB 4.2 及更早版本中,你无法在事务中创建集合。从 MongoDB 4.4 开始,您可以在事务中创建集合和索引。有关详细信息,请参阅 [在事务中创建集合和索引](https://www.mongodb.com/docs/upcoming/core/transactions/#std-label-transactions-create-collections-indexes)。
+
+## MongoDB 数据压缩
+
+借助 WiredTiger 存储引擎( MongoDB 3.2 后的默认存储引擎),MongoDB 支持对所有集合和索引进行压缩。压缩以额外的 CPU 为代价最大限度地减少存储使用。
+
+默认情况下,WiredTiger 使用 [Snappy](https://github.com/google/snappy) 压缩算法(谷歌开源,旨在实现非常高的速度和合理的压缩,压缩比 3 ~ 5 倍)对所有集合使用块压缩,对所有索引使用前缀压缩。
+
+除了 Snappy 之外,对于集合还有下面这些压缩算法:
+
+- [zlib](https://github.com/madler/zlib):高度压缩算法,压缩比 5 ~ 7 倍
+- [Zstandard](https://github.com/facebook/zstd)(简称 zstd):Facebook 开源的一种快速无损压缩算法,针对 zlib 级别的实时压缩场景和更好的压缩比,提供更高的压缩率和更低的 CPU 使用率,MongoDB 4.2 开始可用。
+
+WiredTiger 日志也会被压缩,默认使用的也是 Snappy 压缩算法。如果日志记录小于或等于 128 字节,WiredTiger 不会压缩该记录。
+
+## 参考
+
+- MongoDB 官方文档(主要参考资料,以官方文档为准):
+- 《MongoDB 权威指南》
+- 技术干货| MongoDB 事务原理 - MongoDB 中文社区:
+- Transactions - MongoDB 官方文档:
+- WiredTiger Storage Engine - MongoDB 官方文档:
+- WiredTiger 存储引擎之一:基础数据结构分析:
diff --git a/docs/database/mongodb/mongodb-questions-02.md b/docs/database/mongodb/mongodb-questions-02.md
new file mode 100644
index 0000000000000000000000000000000000000000..851981a8fd0839d861bf620dc024d3f1c90c7d29
--- /dev/null
+++ b/docs/database/mongodb/mongodb-questions-02.md
@@ -0,0 +1,273 @@
+---
+title: MongoDB常见面试题总结(下)
+category: 数据库
+tag:
+ - NoSQL
+ - MongoDB
+---
+
+## MongoDB 索引
+
+### MongoDB 索引有什么用?
+
+和关系型数据库类似,MongoDB 中也有索引。索引的目的主要是用来提高查询效率,如果没有索引的话,MongoDB 必须执行 **集合扫描** ,即扫描集合中的每个文档,以选择与查询语句匹配的文档。如果查询存在合适的索引,MongoDB 可以使用该索引来限制它必须检查的文档数量。并且,MongoDB 可以使用索引中的排序返回排序后的结果。
+
+虽然索引可以显著缩短查询时间,但是使用索引、维护索引是有代价的。在执行写入操作时,除了要更新文档之外,还必须更新索引,这必然会影响写入的性能。因此,当有大量写操作而读操作少时,或者不考虑读操作的性能时,都不推荐建立索引。
+
+### MongoDB 支持哪些类型的索引?
+
+**MongoDB 支持多种类型的索引,包括单字段索引、复合索引、多键索引、哈希索引、文本索引、 地理位置索引等,每种类型的索引有不同的使用场合。**
+
+- **单字段索引:** 建立在单个字段上的索引,索引创建的排序顺序无所谓,MongoDB 可以头/尾开始遍历。
+- **复合索引:** 建立在多个字段上的索引,也可以称之为组合索引、联合索引。
+- **多键索引**:MongoDB 的一个字段可能是数组,在对这种字段创建索引时,就是多键索引。MongoDB 会为数组的每个值创建索引。就是说你可以按照数组里面的值做条件来查询,这个时候依然会走索引。
+- **哈希索引**:按数据的哈希值索引,用在哈希分片集群上。
+- **文本索引:** 支持对字符串内容的文本搜索查询。文本索引可以包含任何值为字符串或字符串元素数组的字段。一个集合只能有一个文本搜索索引,但该索引可以覆盖多个字段。MongoDB 虽然支持全文索引,但是性能低下,暂时不建议使用。
+- **地理位置索引:** 基于经纬度的索引,适合 2D 和 3D 的位置查询。
+- **唯一索引**:确保索引字段不会存储重复值。如果集合已经存在了违反索引的唯一约束的文档,则后台创建唯一索引会失败。
+- **TTL 索引**:TTL 索引提供了一个过期机制,允许为每一个文档设置一个过期时间,当一个文档达到预设的过期时间之后就会被删除。
+- ......
+
+### 复合索引中字段的顺序有影响吗?
+
+复合索引中字段的顺序非常重要,例如下图中的复合索引由`{userid:1, score:-1}`组成,则该复合索引首先按照`userid`升序排序;然后再每个`userid`的值内,再按照`score`降序排序。
+
+
+
+在复合索引中,按照何种方式排序,决定了该索引在查询中是否能被应用到。
+
+走复合索引的排序:
+
+```sql
+db.s2.find().sort({"userid": 1, "score": -1})
+db.s2.find().sort({"userid": -1, "score": 1})
+```
+
+不走复合索引的排序:
+
+```sql
+db.s2.find().sort({"userid": 1, "score": 1})
+db.s2.find().sort({"userid": -1, "score": -1})
+db.s2.find().sort({"score": 1, "userid": -1})
+db.s2.find().sort({"score": 1, "userid": 1})
+db.s2.find().sort({"score": -1, "userid": -1})
+db.s2.find().sort({"score": -1, "userid": 1})
+```
+
+我们可以通过 explain 进行分析:
+
+```sql
+db.s2.find().sort({"score": -1, "userid": 1}).explain()
+```
+
+### 复合索引遵循左前缀原则吗?
+
+**MongoDB 的复合索引遵循左前缀原则**:拥有多个键的索引,可以同时得到所有这些键的前缀组成的索引,但不包括除左前缀之外的其他子集。比如说,有一个类似 `{a: 1, b: 1, c: 1, ..., z: 1}` 这样的索引,那么实际上也等于有了 `{a: 1}`、`{a: 1, b: 1}`、`{a: 1, b: 1, c: 1}` 等一系列索引,但是不会有 `{b: 1}` 这样的非左前缀的索引。
+
+### 什么是 TTL 索引?
+
+TTL 索引提供了一个过期机制,允许为每一个文档设置一个过期时间 `expireAfterSeconds` ,当一个文档达到预设的过期时间之后就会被删除。TTL 索引除了有 `expireAfterSeconds` 属性外,和普通索引一样。
+
+数据过期对于某些类型的信息很有用,比如机器生成的事件数据、日志和会话信息,这些信息只需要在数据库中保存有限的时间。
+
+**TTL 索引运行原理**:
+
+- MongoDB 会开启一个后台线程读取该 TTL 索引的值来判断文档是否过期,但不会保证已过期的数据会立马被删除,因后台线程每 60 秒触发一次删除任务,且如果删除的数据量较大,会存在上一次的删除未完成,而下一次的任务已经开启的情况,导致过期的数据也会出现超过了数据保留时间 60 秒以上的现象。
+- 对于副本集而言,TTL 索引的后台进程只会在 Primary 节点开启,在从节点会始终处于空闲状态,从节点的数据删除是由主库删除后产生的 oplog 来做同步。
+
+**TTL 索引限制**:
+
+- TTL 索引是单字段索引。复合索引不支持 TTL
+- `_id`字段不支持 TTL 索引。
+- 无法在上限集合(Capped Collection)上创建 TTL 索引,因为 MongoDB 无法从上限集合中删除文档。
+- 如果某个字段已经存在非 TTL 索引,那么在该字段上无法再创建 TTL 索引。
+
+### 什么是覆盖索引查询?
+
+根据官方文档介绍,覆盖查询是以下的查询:
+
+- 所有的查询字段是索引的一部分。
+- 结果中返回的所有字段都在同一索引中。
+- 查询中没有字段等于`null`。
+
+由于所有出现在查询中的字段是索引的一部分, MongoDB 无需在整个数据文档中检索匹配查询条件和返回使用相同索引的查询结果。因为索引存在于内存中,从索引中获取数据比通过扫描文档读取数据要快得多。
+
+举个例子:我们有如下 `users` 集合:
+
+```json
+{
+ "_id": ObjectId("53402597d852426020000002"),
+ "contact": "987654321",
+ "dob": "01-01-1991",
+ "gender": "M",
+ "name": "Tom Benzamin",
+ "user_name": "tombenzamin"
+}
+```
+
+我们在 `users` 集合中创建联合索引,字段为 `gender` 和 `user_name` :
+
+```sql
+db.users.ensureIndex({gender:1,user_name:1})
+```
+
+现在,该索引会覆盖以下查询:
+
+```sql
+db.users.find({gender:"M"},{user_name:1,_id:0})
+```
+
+为了让指定的索引覆盖查询,必须显式地指定 `_id: 0` 来从结果中排除 `_id` 字段,因为索引不包括 `_id` 字段。
+
+## MongoDB 高可用
+
+### 复制集群
+
+#### 什么是复制集群?
+
+MongoDB 的复制集群又称为副本集群,是一组维护相同数据集合的 mongod 进程。
+
+客户端连接到整个 Mongodb 复制集群,主节点机负责整个复制集群的写,从节点可以进行读操作,但默认还是主节点负责整个复制集群的读。主节点发生故障时,自动从从节点中选举出一个新的主节点,确保集群的正常使用,这对于客户端来说是无感知的。
+
+通常来说,一个复制集群包含 1 个主节点(Primary),多个从节点(Secondary)以及零个或 1 个仲裁节点(Arbiter)。
+
+- **主节点**:整个集群的写操作入口,接收所有的写操作,并将集合所有的变化记录到操作日志中,即 oplog。主节点挂掉之后会自动选出新的主节点。
+- **从节点**:从主节点同步数据,在主节点挂掉之后选举新节点。不过,从节点可以配置成 0 优先级,阻止它在选举中成为主节点。
+- **仲裁节点**:这个是为了节约资源或者多机房容灾用,只负责主节点选举时投票不存数据,保证能有节点获得多数赞成票。
+
+下图是一个典型的三成员副本集群:
+
+
+
+主节点与备节点之间是通过 **oplog(操作日志)** 来同步数据的。oplog 是 local 库下的一个特殊的 **上限集合(Capped Collection)** ,用来保存写操作所产生的增量日志,类似于 MySQL 中 的 Binlog。
+
+> 上限集合类似于定长的循环队列,数据顺序追加到集合的尾部,当集合空间达到上限时,它会覆盖集合中最旧的文档。上限集合的数据将会被顺序写入到磁盘的固定空间内,所以,I/O 速度非常快,如果不建立索引,性能更好。
+
+
+
+当主节点上的一个写操作完成后,会向 oplog 集合写入一条对应的日志,而从节点则通过这个 oplog 不断拉取到新的日志,在本地进行回放以达到数据同步的目的。
+
+副本集最多有一个主节点。 如果当前主节点不可用,一个选举会抉择出新的主节点。MongoDB 的节点选举规则能够保证在 Primary 挂掉之后选取的新节点一定是集群中数据最全的一个。
+
+#### 为什么要用复制集群?
+
+- **实现 failover**:提供自动故障恢复的功能,主节点发生故障时,自动从从节点中选举出一个新的主节点,确保集群的正常使用,这对于客户端来说是无感知的。
+- **实现读写分离**:我们可以设置从节点上可以读取数据,主节点负责写入数据,这样的话就实现了读写分离,减轻了主节点读写压力过大的问题。MongoDB 4.0 之前版本如果主库压力不大,不建议读写分离,因为写会阻塞读,除非业务对响应时间不是非常关注以及读取历史数据接受一定时间延迟。
+
+### 分片集群
+
+#### 什么是分片集群?
+
+分片集群是 MongoDB 的分布式版本,相较副本集,分片集群数据被均衡的分布在不同分片中, 不仅大幅提升了整个集群的数据容量上限,也将读写的压力分散到不同分片,以解决副本集性能瓶颈的难题。
+
+MongoDB 的分片集群由如下三个部分组成(下图来源于[官方文档对分片集群的介绍](https://www.mongodb.com/docs/manual/sharding/)):
+
+
+
+- **Config Servers**:配置服务器,本质上是一个 MongoDB 的副本集,负责存储集群的各种元数据和配置,如分片地址、Chunks 等
+- **Mongos**:路由服务,不存具体数据,从 Config 获取集群配置讲请求转发到特定的分片,并且整合分片结果返回给客户端。
+- **Shard**:每个分片是整体数据的一部分子集,从 MongoDB3.6 版本开始,每个 Shard 必须部署为副本集(replica set)架构
+
+#### 为什么要用分片集群?
+
+随着系统数据量以及吞吐量的增长,常见的解决办法有两种:垂直扩展和水平扩展。
+
+垂直扩展通过增加单个服务器的能力来实现,比如磁盘空间、内存容量、CPU 数量等;水平扩展则通过将数据存储到多个服务器上来实现,根据需要添加额外的服务器以增加容量。
+
+类似于 Redis Cluster,MongoDB 也可以通过分片实现 **水平扩展** 。水平扩展这种方式更灵活,可以满足更大数据量的存储需求,支持更高吞吐量。并且,水平扩展所需的整体成本更低,仅仅需要相对较低配置的单机服务器即可,代价是增加了部署的基础设施和维护的复杂性。
+
+也就是说当你遇到如下问题时,可以使用分片集群解决:
+
+- 存储容量受单机限制,即磁盘资源遭遇瓶颈。
+- 读写能力受单机限制,可能是 CPU、内存或者网卡等资源遭遇瓶颈,导致读写能力无法扩展。
+
+#### 什么是分片键?
+
+**分片键(Shard Key)** 是数据分区的前提, 从而实现数据分发到不同服务器上,减轻服务器的负担。也就是说,分片键决定了集合内的文档如何在集群的多个分片间的分布状况。
+
+分片键就是文档里面的一个字段,但是这个字段不是普通的字段,有一定的要求:
+
+- 它必须在所有文档中都出现。
+- 它必须是集合的一个索引,可以是单索引或复合索引的前缀索引,不能是多索引、文本索引或地理空间位置索引。
+- MongoDB 4.2 之前的版本,文档的分片键字段值不可变。MongoDB 4.2 版本开始,除非分片键字段是不可变的 `_id` 字段,否则您可以更新文档的分片键值。MongoDB 5.0 版本开始,实现了实时重新分片(live resharding),可以实现分片键的完全重新选择。
+- 它的大小不能超过 512 字节。
+
+#### 如何选择分片键?
+
+选择合适的片键对 sharding 效率影响很大,主要基于如下四个因素(摘自[分片集群使用注意事项 - - 腾讯云文档](https://cloud.tencent.com/document/product/240/44611)):
+
+- **取值基数** 取值基数建议尽可能大,如果用小基数的片键,因为备选值有限,那么块的总数量就有限,随着数据增多,块的大小会越来越大,导致水平扩展时移动块会非常困难。 例如:选择年龄做一个基数,范围最多只有 100 个,随着数据量增多,同一个值分布过多时,导致 chunck 的增长超出 chuncksize 的范围,引起 jumbo chunk,从而无法迁移,导致数据分布不均匀,性能瓶颈。
+- **取值分布** 取值分布建议尽量均匀,分布不均匀的片键会造成某些块的数据量非常大,同样有上面数据分布不均匀,性能瓶颈的问题。
+- **查询带分片** 查询时建议带上分片,使用分片键进行条件查询时,mongos 可以直接定位到具体分片,否则 mongos 需要将查询分发到所有分片,再等待响应返回。
+- **避免单调递增或递减** 单调递增的 sharding key,数据文件挪动小,但写入会集中,导致最后一篇的数据量持续增大,不断发生迁移,递减同理。
+
+综上,在选择片键时要考虑以上 4 个条件,尽可能满足更多的条件,才能降低 MoveChunks 对性能的影响,从而获得最优的性能体验。
+
+#### 分片策略有哪些?
+
+MongoDB 支持两种分片算法来满足不同的查询需求(摘自[MongoDB 分片集群介绍 - 阿里云文档](https://help.aliyun.com/document_detail/64561.html?spm=a2c4g.11186623.0.0.3121565eQhUGGB#h2--shard-key-3)):
+
+**1、基于范围的分片**:
+
+
+
+MongoDB 按照分片键(Shard Key)的值的范围将数据拆分为不同的块(Chunk),每个块包含了一段范围内的数据。当分片键的基数大、频率低且值非单调变更时,范围分片更高效。
+
+- 优点:Mongos 可以快速定位请求需要的数据,并将请求转发到相应的 Shard 节点中。
+- 缺点:可能导致数据在 Shard 节点上分布不均衡,容易造成读写热点,且不具备写分散性。
+- 适用场景:分片键的值不是单调递增或单调递减、分片键的值基数大且重复的频率低、需要范围查询等业务场景。
+
+**2、基于 Hash 值的分片**
+
+
+
+MongoDB 计算单个字段的哈希值作为索引值,并以哈希值的范围将数据拆分为不同的块(Chunk)。
+
+- 优点:可以将数据更加均衡地分布在各 Shard 节点中,具备写分散性。
+- 缺点:不适合进行范围查询,进行范围查询时,需要将读请求分发到所有的 Shard 节点。
+- 适用场景:分片键的值存在单调递增或递减、片键的值基数大且重复的频率低、需要写入的数据随机分发、数据读取随机性较大等业务场景。
+
+除了上述两种分片策略,您还可以配置 **复合片键** ,例如由一个低基数的键和一个单调递增的键组成。
+
+#### 分片数据如何存储?
+
+**Chunk(块)** 是 MongoDB 分片集群的一个核心概念,其本质上就是由一组 Document 组成的逻辑数据单元。每个 Chunk 包含一定范围片键的数据,互不相交且并集为全部数据,即离散数学中**划分**的概念。
+
+分片集群不会记录每条数据在哪个分片上,而是记录 Chunk 在哪个分片上一级这个 Chunk 包含哪些数据。
+
+默认情况下,一个 Chunk 的最大值默认为 64MB(可调整,取值范围为 1~1024 MB。如无特殊需求,建议保持默认值),进行数据插入、更新、删除时,如果此时 Mongos 感知到了目标 Chunk 的大小或者其中的数据量超过上限,则会触发 **Chunk 分裂**。
+
+
+
+数据的增长会让 Chunk 分裂得越来越多。这个时候,各个分片上的 Chunk 数量可能会不平衡。Mongos 中的 **均衡器(Balancer)** 组件就会执行自动平衡,尝试使各个 Shard 上 Chunk 的数量保持均衡,这个过程就是 **再平衡(Rebalance)**。默认情况下,数据库和集合的 Rebalance 是开启的。
+
+如下图所示,随着数据插入,导致 Chunk 分裂,让 AB 两个分片有 3 个 Chunk,C 分片只有一个,这个时候就会把 B 分配的迁移一个到 C 分片实现集群数据均衡。
+
+
+
+> Balancer 是 MongoDB 的一个运行在 Config Server 的 Primary 节点上(自 MongoDB 3.4 版本起)的后台进程,它监控每个分片上 Chunk 数量,并在某个分片上 Chunk 数量达到阈值进行迁移。
+
+Chunk 只会分裂,不会合并,即使 chunkSize 的值变大。
+
+Rebalance 操作是比较耗费系统资源的,我们可以通过在业务低峰期执行、预分片或者设置 Rebalance 时间窗等方式来减少其对 MongoDB 正常使用所带来的影响。
+
+#### Chunk 迁移原理是什么?
+
+关于 Chunk 迁移原理的详细介绍,推荐阅读 MongoDB 中文社区的[一文读懂 MongoDB chunk 迁移](https://mongoing.com/archives/77479)这篇文章。
+
+## 学习资料推荐
+
+- [MongoDB 中文手册|官方文档中文版](https://docs.mongoing.com/)(推荐):基于 4.2 版本,不断与官方最新版保持同步。
+- [MongoDB 初学者教程——7 天学习 MongoDB](https://mongoing.com/archives/docs/mongodb%e5%88%9d%e5%ad%a6%e8%80%85%e6%95%99%e7%a8%8b/mongodb%e5%a6%82%e4%bd%95%e5%88%9b%e5%bb%ba%e6%95%b0%e6%8d%ae%e5%ba%93%e5%92%8c%e9%9b%86%e5%90%88):快速入门。
+- [SpringBoot 整合 MongoDB 实战 - 2022](https://www.cnblogs.com/dxflqm/p/16643981.html):很不错的一篇 MongoDB 入门文章,主要围绕 MongoDB 的 Java 客户端使用进行基本的增删改查操作介绍。
+
+## 参考
+
+- MongoDB 官方文档(主要参考资料,以官方文档为准):
+- 《MongoDB 权威指南》
+- Indexes - MongoDB 官方文档:
+- MongoDB - 索引知识 - 程序员翔仔 - 2022:
+- MongoDB - 索引:
+- Sharding - MongoDB 官方文档:
+- MongoDB 分片集群介绍 - 阿里云文档:
+- 分片集群使用注意事项 - - 腾讯云文档:
diff --git a/docs/database/mysql/a-thousand-lines-of-mysql-study-notes.md b/docs/database/mysql/a-thousand-lines-of-mysql-study-notes.md
index 53350ec5ae75b31fe53de2789d877f83b25913ff..4dc48353eebaa8cfd6ced9a3990cfc4cb1d8ce98 100644
--- a/docs/database/mysql/a-thousand-lines-of-mysql-study-notes.md
+++ b/docs/database/mysql/a-thousand-lines-of-mysql-study-notes.md
@@ -6,28 +6,30 @@ tag:
---
> 原文地址:https://shockerli.net/post/1000-line-mysql-note/ ,JavaGuide 对本文进行了简答排版,新增了目录。
-> 作者:格物
非常不错的总结,强烈建议保存下来,需要的时候看一看。
### 基本操作
-```mysql
+```sql
/* Windows服务 */
--- 启动MySQL
- net start mysql
+-- 启动 MySQL
+ net start mysql
-- 创建Windows服务
- sc create mysql binPath= mysqld_bin_path(注意:等号与值之间有空格)
+ sc create mysql binPath= mysqld_bin_path(注意:等号与值之间有空格)
/* 连接与断开服务器 */
-mysql -h 地址 -P 端口 -u 用户名 -p 密码
-SHOW PROCESSLIST -- 显示哪些线程正在运行
-SHOW VARIABLES -- 显示系统变量信息
+-- 连接 MySQL
+ mysql -h 地址 -P 端口 -u 用户名 -p 密码
+-- 显示哪些线程正在运行
+ SHOW PROCESSLIST
+-- 显示系统变量信息
+ SHOW VARIABLES
```
### 数据库操作
-```mysql
-/* 数据库操作 */ ------------------
+```sql
+/* 数据库操作 */
-- 查看当前数据库
SELECT DATABASE();
-- 显示当前时间、用户名、数据库版本
@@ -48,9 +50,10 @@ SHOW VARIABLES -- 显示系统变量信息
同时删除该数据库相关的目录及其目录内容
```
-### 表的操作
+### 表的操作
-```mysql
+```sql
+/* 表的操作 */
-- 创建表
CREATE [TEMPORARY] TABLE[ IF NOT EXISTS] [库名.]表名 ( 表的结构定义 )[ 表选项]
每个字段必须有数据类型
@@ -131,13 +134,13 @@ SHOW VARIABLES -- 显示系统变量信息
### 数据操作
-```mysql
+```sql
/* 数据操作 */ ------------------
-- 增
INSERT [INTO] 表名 [(字段列表)] VALUES (值列表)[, (值列表), ...]
-- 如果要插入的值列表包含所有字段并且顺序一致,则可以省略字段列表。
-- 可同时插入多条数据记录!
- REPLACE 与 INSERT 完全一样,可互换。
+ REPLACE与INSERT类似,唯一的区别是对于匹配的行,现有行(与主键/唯一键比较)的数据会被替换,如果没有现有行,则插入新行。
INSERT [INTO] 表名 SET 字段名=值[, 字段名=值, ...]
-- 查
SELECT 字段列表 FROM 表名[ 其他子句]
@@ -153,7 +156,7 @@ SHOW VARIABLES -- 显示系统变量信息
### 字符集编码
-```mysql
+```sql
/* 字符集编码 */ ------------------
-- MySQL、数据库、表、字段均可设置编码
-- 数据编码与客户端编码不需一致
@@ -176,7 +179,7 @@ SET NAMES GBK; -- 相当于完成以上三个设置
### 数据类型(列类型)
-```mysql
+```sql
/* 数据类型(列类型) */ ------------------
1. 数值类型
-- a. 整型 ----------
@@ -278,7 +281,7 @@ set(val1, val2, val3...)
### 列属性(列约束)
-```mysql
+```sql
/* 列属性(列约束) */ ------------------
1. PRIMARY 主键
- 能唯一标识记录的字段,可以作为主键。
@@ -333,7 +336,7 @@ set(val1, val2, val3...)
### 建表规范
-```mysql
+```sql
/* 建表规范 */ ------------------
-- Normal Format, NF
- 每个表保存一个实体信息
@@ -350,9 +353,9 @@ set(val1, val2, val3...)
将一个实体信息的数据放在一个表内实现。
```
-### SELECT
+### SELECT
-```mysql
+```sql
/* SELECT */ ------------------
SELECT [ALL|DISTINCT] select_expr FROM -> WHERE -> GROUP BY [合计函数] -> HAVING -> ORDER BY -> LIMIT
a. select_expr
@@ -415,22 +418,22 @@ h. DISTINCT, ALL 选项
默认为 all, 全部记录
```
-### UNION
+### UNION
-```mysql
+```sql
/* UNION */ ------------------
- 将多个select查询的结果组合成一个结果集合。
- SELECT ... UNION [ALL|DISTINCT] SELECT ...
- 默认 DISTINCT 方式,即所有返回的行都是唯一的
- 建议,对每个SELECT查询加上小括号包裹。
- ORDER BY 排序时,需加上 LIMIT 进行结合。
- 需要各select查询的字段数量一样。
- 每个select查询的字段列表(数量、类型)应一致,因为结果中的字段名以第一条select语句为准。
+ 将多个select查询的结果组合成一个结果集合。
+ SELECT ... UNION [ALL|DISTINCT] SELECT ...
+ 默认 DISTINCT 方式,即所有返回的行都是唯一的
+ 建议,对每个SELECT查询加上小括号包裹。
+ ORDER BY 排序时,需加上 LIMIT 进行结合。
+ 需要各select查询的字段数量一样。
+ 每个select查询的字段列表(数量、类型)应一致,因为结果中的字段名以第一条select语句为准。
```
### 子查询
-```mysql
+```sql
/* 子查询 */ ------------------
- 子查询需用括号包裹。
-- from型
@@ -464,7 +467,7 @@ h. DISTINCT, ALL 选项
### 连接查询(join)
-```mysql
+```sql
/* 连接查询(join) */ ------------------
将多个表的字段进行连接,可以指定连接条件。
-- 内连接(inner join)
@@ -491,9 +494,9 @@ h. DISTINCT, ALL 选项
select info.id, info.name, info.stu_num, extra_info.hobby, extra_info.sex from info, extra_info where info.stu_num = extra_info.stu_id;
```
-### TRUNCATE
+### TRUNCATE
-```mysql
+```sql
/* TRUNCATE */ ------------------
TRUNCATE [TABLE] tbl_name
清空数据
@@ -507,7 +510,7 @@ TRUNCATE [TABLE] tbl_name
### 备份与还原
-```mysql
+```sql
/* 备份与还原 */ ------------------
备份,将数据的结构与表内数据保存起来。
利用 mysqldump 指令完成。
@@ -533,7 +536,7 @@ mysqldump [options] --all--database
### 视图
-```mysql
+```sql
什么是视图:
视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。
视图具有表结构文件,但不存在数据文件。
@@ -565,9 +568,9 @@ CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] VIEW view_name
UNDEFINED 未定义(默认),指的是MySQL自主去选择相应的算法。
```
-### 事务(transaction)
+### 事务(transaction)
-```mysql
+```sql
事务是指逻辑上的一组操作,组成这组操作的各个单元,要不全成功要不全失败。
- 支持连续SQL的集体成功或集体撤销。
- 事务是数据库在数据完整性方面的一个功能。
@@ -681,7 +684,7 @@ end
3. Replace 语法 如果有记录,则执行 before insert, before delete, after delete, after insert
```
-### SQL编程
+### SQL 编程
```mysql
/* SQL编程 */ ------------------
@@ -710,7 +713,7 @@ select into 可以将表中查询获得的数据赋给变量。
--// 控制结构 ----------
-- if语句
if search_condition then
- statement_list
+ statement_list
[elseif search_condition then
statement_list]
...
@@ -950,4 +953,3 @@ OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...
6. SQL对大小写不敏感
7. 清除已有语句:\c
```
-
diff --git a/docs/database/mysql/how-sql-executed-in-mysql.md b/docs/database/mysql/how-sql-executed-in-mysql.md
index 07404d0e930b49ec357437b7583b37c0a2708400..cdcde73a462e417d269ebcc5f44106f103223b34 100644
--- a/docs/database/mysql/how-sql-executed-in-mysql.md
+++ b/docs/database/mysql/how-sql-executed-in-mysql.md
@@ -1,13 +1,13 @@
---
-title: 一条 SQL 语句在 MySQL 中如何被执行的?
+title: SQL语句在MySQL中的执行过程
category: 数据库
tag:
- MySQL
---
-本文来自[木木匠](https://github.com/kinglaw1204)投稿。
+> 本文来自[木木匠](https://github.com/kinglaw1204)投稿。
-本篇文章会分析下一个 sql 语句在 MySQL 中的执行流程,包括 sql 的查询在 MySQL 内部会怎么流转,sql 语句的更新是怎么完成的。
+本篇文章会分析下一个 SQL 语句在 MySQL 中的执行流程,包括 SQL 的查询在 MySQL 内部会怎么流转,SQL 语句的更新是怎么完成的。
在分析之前我会先带着你看看 MySQL 的基础架构,知道了 MySQL 由那些组件组成以及这些组件的作用是什么,可以帮助我们理解和解决这些问题。
@@ -15,7 +15,7 @@ tag:
### 1.1 MySQL 基本架构概览
-下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到用户的 SQL 语句在 MySQL 内部是如何执行的。
+下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到用户的 SQL 语句在 MySQL 内部是如何执行的。
先简单介绍一下下图涉及的一些组件的基本作用帮助大家理解这幅图,在 1.2 节中会详细介绍到这些组件的作用。
@@ -23,14 +23,14 @@ tag:
- **查询缓存:** 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
- **分析器:** 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
- **优化器:** 按照 MySQL 认为最优的方案去执行。
-
+- **执行器:** 执行语句,然后从存储引擎返回数据。 -
-
+
-简单来说 MySQL 主要分为 Server 层和存储引擎层:
+简单来说 MySQL 主要分为 Server 层和存储引擎层:
-- **Server 层**:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块。
-- **存储引擎**: 主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。**现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了。**
+- **Server 层**:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binlog 日志模块。
+- **存储引擎**:主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。**现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5 版本开始就被当做默认存储引擎了。**
### 1.2 Server 层基本组件介绍
@@ -38,13 +38,13 @@ tag:
连接器主要和身份认证和权限相关的功能相关,就好比一个级别很高的门卫一样。
-主要负责用户登录数据库,进行用户的身份认证,包括校验账户密码,权限等操作,如果用户账户密码已通过,连接器会到权限表中查询该用户的所有权限,之后在这个连接里的权限逻辑判断都是会依赖此时读取到的权限数据,也就是说,后续只要这个连接不断开,即时管理员修改了该用户的权限,该用户也是不受影响的。
+主要负责用户登录数据库,进行用户的身份认证,包括校验账户密码,权限等操作,如果用户账户密码已通过,连接器会到权限表中查询该用户的所有权限,之后在这个连接里的权限逻辑判断都是会依赖此时读取到的权限数据,也就是说,后续只要这个连接不断开,即使管理员修改了该用户的权限,该用户也是不受影响的。
#### 2) 查询缓存(MySQL 8.0 版本后移除)
查询缓存主要用来缓存我们所执行的 SELECT 语句以及该语句的结果集。
-连接建立后,执行查询语句的时候,会先查询缓存,MySQL 会先校验这个 sql 是否执行过,以 Key-Value 的形式缓存在内存中,Key 是查询预计,Value 是结果集。如果缓存 key 被命中,就会直接返回给客户端,如果没有命中,就会执行后续的操作,完成后也会把结果缓存起来,方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限,是否有该表的查询条件。
+连接建立后,执行查询语句的时候,会先查询缓存,MySQL 会先校验这个 SQL 是否执行过,以 Key-Value 的形式缓存在内存中,Key 是查询语句,Value 是结果集。如果缓存 key 被命中,就会直接返回给客户端,如果没有命中,就会执行后续的操作,完成后也会把结果缓存起来,方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限,是否有该表的查询条件。
MySQL 查询不建议使用缓存,因为查询缓存失效在实际业务场景中可能会非常频繁,假如你对一个表更新的话,这个表上的所有的查询缓存都会被清空。对于不经常更新的数据来说,使用缓存还是可以的。
@@ -58,11 +58,11 @@ MySQL 没有命中缓存,那么就会进入分析器,分析器主要是用
**第一步,词法分析**,一条 SQL 语句有多个字符串组成,首先要提取关键字,比如 select,提出查询的表,提出字段名,提出查询条件等等。做完这些操作后,就会进入第二步。
-**第二步,语法分析**,主要就是判断你输入的 sql 是否正确,是否符合 MySQL 的语法。
+**第二步,语法分析**,主要就是判断你输入的 SQL 是否正确,是否符合 MySQL 的语法。
完成这 2 步之后,MySQL 就准备开始执行了,但是如何执行,怎么执行是最好的结果呢?这个时候就需要优化器上场了。
-#### 4) 优化器
+#### 4) 优化器
优化器的作用就是它认为的最优的执行方案去执行(有时候可能也不是最优,这篇文章涉及对这部分知识的深入讲解),比如多个索引的时候该如何选择索引,多表查询的时候如何选择关联顺序等。
@@ -72,11 +72,11 @@ MySQL 没有命中缓存,那么就会进入分析器,分析器主要是用
当选择了执行方案后,MySQL 就准备开始执行了,首先执行前会校验该用户有没有权限,如果没有权限,就会返回错误信息,如果有权限,就会去调用引擎的接口,返回接口执行的结果。
-## 二 语句分析
+## 二 语句分析
### 2.1 查询语句
-说了以上这么多,那么究竟一条 sql 语句是如何执行的呢?其实我们的 sql 可以分为两种,一种是查询,一种是更新(增加,更新,删除)。我们先分析下查询语句,语句如下:
+说了以上这么多,那么究竟一条 SQL 语句是如何执行的呢?其实我们的 SQL 可以分为两种,一种是查询,一种是更新(增加,修改,删除)。我们先分析下查询语句,语句如下:
```sql
select * from tb_student A where A.age='18' and A.name=' 张三 ';
@@ -84,29 +84,31 @@ select * from tb_student A where A.age='18' and A.name=' 张三 ';
结合上面的说明,我们分析下这个语句的执行流程:
-* 先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 sql 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。
-* 通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id='1'。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。
-* 接下来就是优化器进行确定执行方案,上面的 sql 语句,可以有两种执行方案:
-
+- 先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 SQL 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。
+- 通过分析器进行词法分析,提取 SQL 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id='1'。然后判断这个 SQL 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。
+- 接下来就是优化器进行确定执行方案,上面的 SQL 语句,可以有两种执行方案:
+
a.先查询学生表中姓名为“张三”的学生,然后判断是否年龄是 18。
b.先找出学生中年龄 18 岁的学生,然后再查询姓名为“张三”的学生。
- 那么优化器根据自己的优化算法进行选择执行效率最好的一个方案(优化器认为,有时候不一定最好)。那么确认了执行计划后就准备开始执行了。
-* 进行权限校验,如果没有权限就会返回错误信息,如果有权限就会调用数据库引擎接口,返回引擎的执行结果。
+ 那么优化器根据自己的优化算法进行选择执行效率最好的一个方案(优化器认为,有时候不一定最好)。那么确认了执行计划后就准备开始执行了。
+
+- 进行权限校验,如果没有权限就会返回错误信息,如果有权限就会调用数据库引擎接口,返回引擎的执行结果。
### 2.2 更新语句
-以上就是一条查询 sql 的执行流程,那么接下来我们看看一条更新语句如何执行的呢?sql 语句如下:
+以上就是一条查询 SQL 的执行流程,那么接下来我们看看一条更新语句如何执行的呢?SQL 语句如下:
```
update tb_student A set A.age='19' where A.name=' 张三 ';
```
+
我们来给张三修改下年龄,在实际数据库肯定不会设置年龄这个字段的,不然要被技术负责人打的。其实这条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候肯定要记录日志啦,这就会引入日志模块了,MySQL 自带的日志模块是 **binlog(归档日志)** ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 **redo log(重做日志)**,我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:
-* 先查询到张三这一条数据,如果有缓存,也是会用到缓存。
-* 然后拿到查询的语句,把 age 改为 19,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。
-* 执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。
-* 更新完成。
+- 先查询到张三这一条数据,如果有缓存,也是会用到缓存。
+- 然后拿到查询的语句,把 age 改为 19,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。
+- 执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。
+- 更新完成。
**这里肯定有同学会问,为什么要用两个日志模块,用一个日志模块不行吗?**
@@ -114,25 +116,25 @@ update tb_student A set A.age='19' where A.name=' 张三 ';
并不是说只用一个日志模块不可以,只是 InnoDB 引擎就是通过 redo log 来支持事务的。那么,又会有同学问,我用两个日志模块,但是不要这么复杂行不行,为什么 redo log 要引入 prepare 预提交状态?这里我们用反证法来说明下为什么要这么做?
-* **先写 redo log 直接提交,然后写 binlog**,假设写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 bingog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。
-* **先写 binlog,然后写 redo log**,假设写完了 binlog,机器异常重启了,由于没有 redo log,本机是无法恢复这一条记录的,但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。
+- **先写 redo log 直接提交,然后写 binlog**,假设写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 binlog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。
+- **先写 binlog,然后写 redo log**,假设写完了 binlog,机器异常重启了,由于没有 redo log,本机是无法恢复这一条记录的,但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。
-如果采用 redo log 两阶段提交的方式就不一样了,写完 binglog 后,然后再提交 redo log 就会防止出现上述的问题,从而保证了数据的一致性。那么问题来了,有没有一个极端的情况呢?假设 redo log 处于预提交状态,binglog 也已经写完了,这个时候发生了异常重启会怎么样呢?
+如果采用 redo log 两阶段提交的方式就不一样了,写完 binlog 后,然后再提交 redo log 就会防止出现上述的问题,从而保证了数据的一致性。那么问题来了,有没有一个极端的情况呢?假设 redo log 处于预提交状态,binlog 也已经写完了,这个时候发生了异常重启会怎么样呢?
这个就要依赖于 MySQL 的处理机制了,MySQL 的处理过程如下:
-* 判断 redo log 是否完整,如果判断是完整的,就立即提交。
-* 如果 redo log 只是预提交但不是 commit 状态,这个时候就会去判断 binlog 是否完整,如果完整就提交 redo log, 不完整就回滚事务。
+- 判断 redo log 是否完整,如果判断是完整的,就立即提交。
+- 如果 redo log 只是预提交但不是 commit 状态,这个时候就会去判断 binlog 是否完整,如果完整就提交 redo log, 不完整就回滚事务。
这样就解决了数据一致性的问题。
## 三 总结
-* MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。
-* 引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。
-* 查询语句的执行流程如下:权限校验(如果命中缓存)--->查询缓存--->分析器--->优化器--->权限校验--->执行器--->引擎
-* 更新语句执行流程如下:分析器---->权限校验---->执行器--->引擎---redo log(prepare 状态)--->binlog--->redo log(commit状态)
+- MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。
+- 引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。
+- 查询语句的执行流程如下:权限校验(如果命中缓存)--->查询缓存--->分析器--->优化器--->权限校验--->执行器--->引擎
+- 更新语句执行流程如下:分析器---->权限校验---->执行器--->引擎---redo log(prepare 状态)--->binlog--->redo log(commit 状态)
## 四 参考
-* 《MySQL 实战45讲》
-* MySQL 5.6参考手册:
+- 《MySQL 实战 45 讲》
+- MySQL 5.6 参考手册:
diff --git a/docs/database/mysql/images/ACID.drawio b/docs/database/mysql/images/ACID.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..e8805b8d9584c4098a6155585eb434c9bd34ee06
--- /dev/null
+++ b/docs/database/mysql/images/ACID.drawio
@@ -0,0 +1 @@
+7Zhdb5swFIZ/jS9b8R24BAJdp1Wamotply6Yj9VgZkyT7tfPBjvAIFK7LcqqNZHAfo99bJ/zxLIDzLA63FDYFHckRRgYWnoA5hYYhq4ZHn8J5XlQPM0chJyWqWw0CrvyB1I9pdqVKWpnDRkhmJXNXExIXaOEzTRIKdnPm2UEz0dtYI4Wwi6BeKl+KVNWDKprbEb9AyrzQo2sO3LBDzB5zCnpajkeMMzYiePYHcwVVL7kQtsCpmQ/kcwImCElhA2l6hAiLGKrwjb0i09Yj/OmqGYv6fD5/ttDl3wtnor7T9S9tRq/C6/swcsTxB1Sy+gny55VgPolIuFEB2awL0qGdg1MhHXPkeBawSoszVmJcUgwoX1f0wp9y9twvWWUPCJlqUmNhKgioonKI2JJISs5hm0ryxmp2cTl8JF6DKsSC/A+IhZQWNYtn/sdqYm070hH+5kWjHGeDNv0+YOHSDxEg/Y6JyTHCDZle52Qqjckbd80zgbvvDj1bxuBHGGZAZmUJ0QZOkwkmZEbRCrEKHepKasp6Xj+pb4fWTRtqRUTDg1LilDynx99jwzwgsTgFUjoK0g4mMmIzthwvndEGa7a/qfN46vpXnMYjbyUi7cPohgEIXBdENnAjYAXi4K/BZ4GIge4GvA3qo2nxgRDnpSTv0pnZovvKTpPcfdyat8+nZs5nYa2pFM3Vuh0zgWncSY4wwmcFghcQWPEnxsQWJeBM4XIzZI/3zrfPISm9a9BaJ4JwtsJhB7wfOBx9jbAd0AQjBCeYm+Q2wbWvz+DNcIHj+fYfrPMSN4JX9lmvUsTbp2J8O2EcM4zL+j9fusB177QGcBN0DuEa9vsxSF0FhD64e12kX2+PjZP8fpxbpJ3KUFc5jWvJjxqiOuBiFbJr4a+NFRlmophVpkaqVMMyMut8Z+dEo8EqDuMvSTHW7vCvB4cXh0vzL1t8q+EGf0E
\ No newline at end of file
diff --git a/docs/database/mysql/images/AID-C.drawio b/docs/database/mysql/images/AID-C.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..4ac724c8404d197020920876445cd820a0980d08
--- /dev/null
+++ b/docs/database/mysql/images/AID-C.drawio
@@ -0,0 +1 @@
+5ZjZcpswFIafRpfJAGK9BBvStM1Fm+mS3skglkYgCvKWp68EwoCNM0k6HmdqZwZL/znazvmkIAM4yzc3FSrTOxphAjQl2gA4B5qmKprDv4SybRVHga2QVFkknXrhPnvCXUupLrMI1yNHRilhWTkWQ1oUOGQjDVUVXY/dYkrGo5YowQfCfYjIofoji1jaqrZm9foHnCVpN7JqygUvUPiYVHRZyPGABgMzCAK7Neeo60sutE5RRNcDCfoAzipKWVvKNzNMRGy7sLXtgiPW3bwrXLAXNSBfy+/et6cCXX26ib/o33+pqyvZS822XTxwxMMjq7RiKU1ogYjfq16zZix6VXit9/lMaclFlYu/MWNbmWu0ZJRLKcuJtOJNxn6K5teGrD0MLPON7LmpbLtKwartoJGoPgxtfbOm1rWrHzELU1mJacEClGdEWD9i5lUoK2q+/jtaUGm/p8sqFPNOGeMIagZ0+YNHVTyEQ32dUJoQjMqsvg5p3hjCunEN4rZ3Xhz2b2jecIQ2LqpYcM0q+rgDjzPhHaa1y1E3sWO51OTuQVWC2TN+eusnEj0YQEJzg2mOeQC5Q4UJYtlqvE+Q3G7Jzq9Hjhckda8gUM56hcgSdxtpD8keOBGydZoxfF+iJhZrfiiN4dpts4PsJwTVdUdCRsiMElo1I0B95uqOtcvHwGI2H5m5gd5+3gVTR4lZ4YrhzbM5llYNykNNHuKaIg+tdX8kQkNq6eA41HTlRFzACS5MwmRER4CYf5a0M1zVze7i8VVUp9z0Rl5KxLcL/AB4M2DbwDeA7QMnEAV3DhwF+CawFeBanY/TjQnaPHWd/Buie/DFhvh7E3wvhP2/QNTWx4hah4iq2gSi5qkI1U9E6GxAqA48WyDp86cFPP08hEYI23E4SWho40V8WSRCY0yioZ6bRONEJN4OSHSA4wKHA2gB1wSe15N4DMBWrktUvH0GU5i3PZ7iII5jLZzEPDIXpmFeGubjdwJonhtz80SYzweYc6h5QW1OXgfYxpleCewQT5O4sA3dUC6LRB2+t3/91gGJ7u38gAC+PLZ3ORmls6AF3su9lBDJkoJXQx40zHVPBCsLEXGlIc+iqLmMT3E1vqAPrpzahb0zQmsPHO0QHGfqVvN6bni1/ymnsQ1+L4P+Xw==
\ No newline at end of file
diff --git a/docs/database/mysql/images/concurrency-consistency-issues-dirty-reading.drawio b/docs/database/mysql/images/concurrency-consistency-issues-dirty-reading.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..6e4e61ba50ce11034b2f1ec63240edd91e21534b
--- /dev/null
+++ b/docs/database/mysql/images/concurrency-consistency-issues-dirty-reading.drawio
@@ -0,0 +1 @@
+7Vpbk6I4FP41eWxLQG6P4KV3e2d2ZrZramYfIwZINxIWYqv96zeBIGKi7Uwp2jOWVZqchJPL+c53TiLAGM5X9znM4o9khhKg92crYIyArpuuzr65YF0JbMOsBFGOZ5VIawSP+BUJYV9IF3iGilZHSkhCcdYWBiRNUUBbMpjnZNnuFpKkPWoGIyQJHgOYyNJveEbjSurodiP/A+EorkfWLLdqmcLgOcrJIhXjAd2YWJPJxKma57DWJRZaxHBGllsiYwyMYU4IrUrz1RAlfGvrbauem+xp3cw7Ryk95oE//x4YTj8YYPr11f7y9OmfuWPdibUUdF3vB5qx7RFVktOYRCSFybiR+uWaEdfaZ7WmzwdCMibUmPAJUboWtoYLSpgopvNEtLIJ5+vv4vmy8i+v9My6OlptN47WohaSlAqlminqEzjHCe/wgKifQ5wWbDkfSUrq/mSRB/yJmFKGKN00PPbFNol/8Q5FLyIkShDMcNELyLxsCIqy6ySstLPitn5T98UIsg2EWYp62H0bX0Md5hGiB/oNqn7cKlsDCAvfIzJHbIdYhxwlkOKXNqih8I1o06/BBysIiPwAXITeF5gsUI36XfwkCXNdjpNljCl6zGC5D0tGHm0UwCKr/DnEK44mP8RJMiQJyUtFRhgiKwiYvKA5eUZ1S0pS9L6w8IJyilYHrSdaN1whuFSzRH3ZMFMtirdIqZad3N7Gzd7ntLejXZm96/nc4kHX8WBwZDwwryseuBJBeHWQ2MHQBzhliWObERIcpawcsN1CzNd97jmYpWaeaJjj2ayCGCrwK5yW+rjlM4KZpbhy0wfmSIGFhA/nb1K0LZ4RSdp1wuWgV0rEssmKxda0MksV4dz1ezzKtEmnqh0NGKH8MzdBo9lU6awfJ2FYMFTvom0zv58H4EDGH/vRZRprSEp7O1RJkSnUf7PIZFg7kcmWI5OtiEyDE0SmEcqGeP0AA/8B+/lf3/wv0+fa0N0Gpl3TKrn35/jelPleuW69I3pXDm7K6d/YBK4P3DEYW8BnBe8SZH8kucumO5pB20bdj8iDTGu3SdE4CdFaKp3nJ1oZCpxoNfn+4Ea0P0S05u6Rr0OiVRraVvj8APgecHzu/I4HPE3O8Nhqadu0ahNu2VuIjucFFZbaFP9LIkQzdhFiSghRHRKNcyHEOQIh8j3BDSGdIUR3ukOIMjSqT4Dv5BbhhEle7QZvnuo1s6M07+A0b9d83Vzr6ooY3+01n/OeHfSKMfE2IyjOfeqOtpKHLkYRt/z/TPm/uxu7L5z/a4oDwC0YnO8/n4sHg80S2rc8LIf0hmViPwSuXUomwGeFSSnxHuALvOfvbgDdSnhmP81bILH+W/A3G0qj3BWlFZkl+pqZrcqNrNtZKSp/xzbwR8DVeMHzgetuhnpiQ5WvifSC9ErPFXtupA4AecsL/PJT6qSMy0kqlBbPiAYxaO4i3z349V22G8jg1xwF+rVzHVWMS2RCpzly7L8vPiK/MDrKJg5N8vavjexeihtqCQZ73cuyO7tMZNXm1bXqkrl5PdAY/w8=
\ No newline at end of file
diff --git a/docs/database/mysql/images/concurrency-consistency-issues-dirty-reading.png b/docs/database/mysql/images/concurrency-consistency-issues-dirty-reading.png
new file mode 100644
index 0000000000000000000000000000000000000000..db90c6ea22c0f473b20b86bf5dd931955ddcf13d
Binary files /dev/null and b/docs/database/mysql/images/concurrency-consistency-issues-dirty-reading.png differ
diff --git a/docs/database/mysql/images/concurrency-consistency-issues-missing-modifications.drawio b/docs/database/mysql/images/concurrency-consistency-issues-missing-modifications.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..68c79b9da73d4df96647df36f287af81a70bb849
--- /dev/null
+++ b/docs/database/mysql/images/concurrency-consistency-issues-missing-modifications.drawio
@@ -0,0 +1 @@
+7VrbcqM4EP0aPcZl7vjR+DI7U3PZ3dTW7j4qWIASgRiQYztfvxJIXAzOkKnx4JqlUuVILalB3UenW10AYxUf32UwjT7RHSJAn++OwFgDXbfnC/4rBKdS4BhaKQgzvCtFDcE9fkFSOJfSPd6hvDWRUUoYTttCnyYJ8llLBrOMHtrTAkraT01hiDqCex+SrvRvvGNRKXV1p5b/hnAYqSdrttzwA/SfwozuE/k8oBtbe7vduuVwDJUuudE8gjt6aIiMDTBWGaWsbMXHFSLCtMps5brthdHqvTOUsCEL3n82DXfum5j99eL88fjlz9i17+RecnZS9kA7bh7ZpRmLaEgTSDa11Cv2jITWOe/Vcz5SmnKhxoWPiLGT9DXcM8pFEYuJHOUvnJ3+keuLzr+iM7NUd31sDq5PshfQhEmlmiX7WxhjIiZ8QMzLIE5yvp1PNKFqPt1nvlgRMcYRpVvGkv9wI4kfMSGfhZSGBMEU5zOfxsWAnxdTt0GpnTeb+i3dk08orSdMdtEpUpSr97jkCYV9mIWIvTLPrKDDTySiMeIW4usyRCDDz+33gPJshNW8Gh+8ISHyBrhIvc+Q7JFC/Tl+COFHV+DkEGGG7lNYbPvAyaONApin5XkO8FGgyQswIStKaFYoMoIA2b7P5TnL6BNSIwlN0A1j4RllDB1fR0PXe3JBxRWSSzVb9g81MylR1CAlJfvh/jYmf1/T37Z7Y/5WQXmKB6PHA3NgPLBGjQeLDkEsVZA4w9BH+MATxzYjEBwmvO1z4yB+1j1xmDBPzZZyIMa7XQkxlOMX+FDoE55PKeaeEsotD1jrHiwQ8TivStEaPCOTtNuBSw+FyGRX7rjOIZtAeuUAXyScu/lMRJk26ZS9wYCRyn8XLqg1W3061XIaBDkH8Tnaqvf7fgCaXfzxf3qXxmqS0r4dqjqRKdD/Z5HJsM8ik9ONTE5PZDKvFpnsKTLdSGSyBkYmY8zIZPUSg9a9707E8CZisM6vKGMTg9O9omxM4C2B64GNBdwlWGrdjIQbgLVd2+/Chr+laHjS0oelNiP9kgjRjHOEWIMuNca1EOIOQAifYBOBiIeMt0JWmWLCzBiY0d2RMdNDGVO6MVJhVB+Yb2jjlkb1Ls1MtbLr1Ub1nsTj59bKjIkiboUihhbLtFGrZQrBU7XsVqplF66odbVMU4Wt7y2PXb8Epk01sOtEHFNZtkpKx66BuVPEuZWIM7QIpjmjJqVTGexKZTD3/MI6Njf01MGm68ePc7hzXqEY+/qhttAqa1nAtcByVdS3VmDhFJIt8HhjW0iWH+AzfCc+uWsWvBogsb/uxQdphVPu8sKL3BNzzUiPhSHVeFUl2zjAW4OFJhpLDywW1aMe+aOKr/tmftLB4m0U0y7kvT1ANmS/cQq84q/QyTiX00QqzZ8Q8yNFlb8C+PXFGfjNLvg1twf92tvRz7v1N5Fljlx/d2ps/gM=
\ No newline at end of file
diff --git a/docs/database/mysql/images/concurrency-consistency-issues-missing-modifications.png b/docs/database/mysql/images/concurrency-consistency-issues-missing-modifications.png
new file mode 100644
index 0000000000000000000000000000000000000000..1718e7ce0dc3a98a8f1db60d4c9372cf6e9e7bd0
Binary files /dev/null and b/docs/database/mysql/images/concurrency-consistency-issues-missing-modifications.png differ
diff --git a/docs/database/mysql/images/concurrency-consistency-issues-phantom-read.drawio b/docs/database/mysql/images/concurrency-consistency-issues-phantom-read.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..350470274c5ed0eee91eb83f7e26adeead4c3295
--- /dev/null
+++ b/docs/database/mysql/images/concurrency-consistency-issues-phantom-read.drawio
@@ -0,0 +1 @@
+7Vptb+I4EP41lvZOKsIJeftIgHRV7UordU/78uXkBudl68TZxBS4X3+2YxNCQkvvSqG7CAnsx/bYmXk8M3YA5iRbXZeoSD7SOSbAGM5XwJwCw4BwaPMfgaxrxDGtGojLdK46NcBt+g9W4FChi3SOq1ZHRilhadEGQ5rnOGQtDJUlXba7RZS0Zy1QjDvAbYhIF/2SzllSo67hNPh7nMaJnhnaXt1yh8L7uKSLXM0HDDOwgyBw6+YMaVnqQasEzelyCzJnwJyUlLK6lK0mmAjdarXV44I9rZt1lzhnhwy4uV0Ff/3tf86+uHH86frm+3v680pJeUBkofQBZjZwA+DxggVcC4wtgXgW8IeyaQrGM/VAbK2ViOdcp6pKS5bQmOaIzBrUl4rCYilDXmv6fKC04CDk4A/M2FoRBC0Y5VDCMqJa+VOW669qvKx8E5WBpavT1XbjdK1qBN1h4m9sNaGElnLR2lqmH9GcqXnhSNUDlKVEyLjBzC9Rmlf8iT/SnOr+dFGGYkTCGGeqYZlj/sWVL75Eh2oQUxoTjIq0GoQ0kw1hJbsGUS2dF7flW4avZqgVLLS619gKqvQ69lnYUHsKlTFmj/QzN5TkWx3TDHMl8nFqn18NB+bIVrJKTBBLH9prQ2ofxpuxG3GfaMpX3XShUVTxtWyRlRe2Zm0gSeFn0Nno0rlDVUK4axGUXCYpw7cFkupbcu/WJhyqitrfROlKENePUkK26BNF2A5DjlespPdYt+Q0xzucss6IUw+4ZHj1OKv2sgBaypcpXw9tVV82nlNDyZbT1FgfR1oUeK69zYu9j2lvxzwze496wtUI+GPg+jJcjcEYwg4FuAJY29b9Jtyyt4IQSeOcV0OuMMxxX6gz5cnDWDVk6XxO9pGrHfN+SYbYOwRxrIMIYh6LINYBBOn6iAtBXosghntigthvKXc9V0a8SGLqHpiYwj0EOzgL/V98cXoyDJsIb1EVKG8xyf65EGc5aYWrSpqNq34IR8VKak6381IsfkNOK/buzz+0QL6+Wmbd3EvUD+I0s5O3HOyCSsxXhe6kPEGvQuTlUmGWD6zp2z4s9XgpdWWgnnhzQG6xdb+TeOwsBA3Xbvu1cz8ZuZdM+TVPRoZz4kzZe2tx7hwo8CJxTR9Ang5s1ikDm75/vUS23ymyeU9GNgeOWp5MXxH/19CmF270Sj1+4IPdK+58kfHC5l6+4WHjAOHTUbAT9CLjNwt65m7Qc7tBz+kJeqNjBT1NskuW8zr3gSfPcmDPBbB6bTWRFz8T4DkSCYDPC4FExjfoAV2L9446QN2VB8U7c0+8AzMH+FPgQVEY+8DzNlP94FPJV5yDMO9w8TzunfaExh4im6q+tQt8+ZEyGY8PNFdCq3vMwkS7yl+B/ObuVdaoS37o9rAfHo39o1Mk+edizJdJ1q1Dk3XjpMl6915bJTHdtx2XJOZZ29rq3FAfLYnh1ebvHnV+2/ynxpz9Cw==
\ No newline at end of file
diff --git a/docs/database/mysql/images/concurrency-consistency-issues-phantom-read.png b/docs/database/mysql/images/concurrency-consistency-issues-phantom-read.png
new file mode 100644
index 0000000000000000000000000000000000000000..4bea3c329532ff04bf9e177416695d80240ec6bc
Binary files /dev/null and b/docs/database/mysql/images/concurrency-consistency-issues-phantom-read.png differ
diff --git a/docs/database/mysql/images/concurrency-consistency-issues-unrepeatable-read.drawio b/docs/database/mysql/images/concurrency-consistency-issues-unrepeatable-read.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..212d68f7f921a50e246909ddff7e29c99e3ac5a8
--- /dev/null
+++ b/docs/database/mysql/images/concurrency-consistency-issues-unrepeatable-read.drawio
@@ -0,0 +1 @@
+7Vptc5s4EP41+hiPQYDxR/BLern27nq5m14/3cggQImMKMiOnV9fCYQBQxy3jR3SejyDpZVYIe2jfXYFAE6Wm+sUJdEH5mMK9KG/AXAKdH1kQHGVgq0SQLMQhCnxC5FWCW7JI1bCoZKuiI+zRkfOGOUkaQo9FsfY4w0ZSlP20OwWMNocNUEhbgluPUTb0k/E51EhtfVRJX+HSRiVI2vWuGhZIO8+TNkqVuMBHc6t+XxuF81LVOpSE80i5LOHmgjOAJykjPGitNxMMJVLWy5bcd/8idbdc6c45sfc8NsfBrSHnkH4v4+jj3d//r20rSs1l4xvy/XAvlgeVWUpj1jIYkRnldTN54yl1qGoVX3eM5YIoSaEd5jzrbI1WnEmRBFfUtUqHjjd/qfuzyufZWVgltXppt443apawGKulGqmqs/RklDZ4QZzN0UkzsR0PrCYlf3ZKvXkHRHnAlG6CR1xEYskL7JDNggZCylGCckGHlvmDV6Wd50HhXZRrOs3dVeN0LaBMktWDvvUwpdQR2mI+YF+RtFPWqU2gLLwNWZLLFZIdEgxRZysm6BGam+Eu34VPkRBQeQb4KL0rhFd4RL1+/ihVGxdiZOHiHB8m6B8HR6E82iiAGVJsZ8DspFocgNC6YRRluaKYBBgy/OEPOMpu8dlS8xi/LawsMYpx5uD1lOtO1+hfKlmqfpD5ZlKUVRzSqXsxe0NL/Y+pb1trWf2Lp/nwgfn5gPjSD4w+8UH45aDcEqS2MPQe7QQgWPTI1ASxqLsidXCYq+7cucQEZo5qmFJfL+AGM7II1rk+qTlE0aEpaRy0wXmtAMLVA7n7kK0mp9RQVo/4XJwV7Ycyy4qVkvTiCy7HM7VcCBZpul0itrRgFHK/5ImqDSbXTrL21kQZALV+2jbPd/3A9Bo40/86W03Vjkp7XmqajFToP9izAStPWYatZlp1MFMxgsw05c7///P0EE4SG+tNWf/DBm/0joij/4y05vwPnXgwh8iL/NI8oK9Ii+z03do7ZT44ju+yXeYr+g7Og09amcxMwO4DrBdMDOB7QBHawctYra8adpuE9bsrUTHxzVdWGo6rZ8SIRrcR4h5VN4DT4UQ+wiEtAnogpCzIUS3z4eQKU4mZHuDPPeGuOnvn9yPi/snkpqehh/7kPh+Zi9R/yy1az1LTPX2jr6cXJ3upFLv4PhTnVx15wfGW9qgv1h+oHUkCN1W7FeGoHWnCJfjhZc+XtDtV04RtI4c4cIXp3vTcU6+6DT4bgr1kF9E+iZwJnnsPwHjUS6ZA1cU5rnEuUFrdC2/WAC6RWXwv0gbILG+rOT7/NwoV1luRWGJoQaTTb6QZbsohfn/bATcKRhrsuC4YDzeDXUnhso/jhh4cU9TjydorAPIUNVru8DNf7lOLnw5i5XS7B5zLypd5c8Afn28B36jDX7N7kC/drJoybxES32Ilg4GQc/nXKPXipYOPvflQPWF/Ydlni1aEtXqi7TiJV311R+cfQU=
\ No newline at end of file
diff --git a/docs/database/mysql/images/concurrency-consistency-issues-unrepeatable-read.png b/docs/database/mysql/images/concurrency-consistency-issues-unrepeatable-read.png
new file mode 100644
index 0000000000000000000000000000000000000000..c734138cf8efece11298cf31cec17c5d62e1d9d7
Binary files /dev/null and b/docs/database/mysql/images/concurrency-consistency-issues-unrepeatable-read.png differ
diff --git a/docs/database/mysql/images/mvvc/0e906b95-c916-4f30-beda-9cb3e49746bf.png b/docs/database/mysql/images/mvvc/0e906b95-c916-4f30-beda-9cb3e49746bf.png
new file mode 100644
index 0000000000000000000000000000000000000000..8f57daf4a980ce8eff33eb00c3852f2e4e3fe175
Binary files /dev/null and b/docs/database/mysql/images/mvvc/0e906b95-c916-4f30-beda-9cb3e49746bf.png differ
diff --git a/docs/database/mysql/images/mvvc/317e91e1-1ee1-42ad-9412-9098d5c6a9ad.png b/docs/database/mysql/images/mvvc/317e91e1-1ee1-42ad-9412-9098d5c6a9ad.png
new file mode 100644
index 0000000000000000000000000000000000000000..e483cd541e726feeb340ac8208a2269443aad530
Binary files /dev/null and b/docs/database/mysql/images/mvvc/317e91e1-1ee1-42ad-9412-9098d5c6a9ad.png differ
diff --git a/docs/database/mysql/images/mvvc/528559e9-dae8-4d14-b78d-a5b657c88391.png b/docs/database/mysql/images/mvvc/528559e9-dae8-4d14-b78d-a5b657c88391.png
new file mode 100644
index 0000000000000000000000000000000000000000..7bdfdd94c43b40cf6811dbbf4a25fb795b31e314
Binary files /dev/null and b/docs/database/mysql/images/mvvc/528559e9-dae8-4d14-b78d-a5b657c88391.png differ
diff --git a/docs/database/mysql/images/mvvc/6a276e7a-b0da-4c7b-bdf7-c0c7b7b3b31c.png b/docs/database/mysql/images/mvvc/6a276e7a-b0da-4c7b-bdf7-c0c7b7b3b31c.png
new file mode 100644
index 0000000000000000000000000000000000000000..80db77036b22311cd8ceb4e4e703c85979ad95af
Binary files /dev/null and b/docs/database/mysql/images/mvvc/6a276e7a-b0da-4c7b-bdf7-c0c7b7b3b31c.png differ
diff --git a/docs/database/mysql/images/mvvc/6f82703c-36a1-4458-90fe-d7f4edbac71a.png b/docs/database/mysql/images/mvvc/6f82703c-36a1-4458-90fe-d7f4edbac71a.png
new file mode 100644
index 0000000000000000000000000000000000000000..d1806960f03da6014242fa34148fc4cc612c6f60
Binary files /dev/null and b/docs/database/mysql/images/mvvc/6f82703c-36a1-4458-90fe-d7f4edbac71a.png differ
diff --git a/docs/database/mysql/images/mvvc/6fb2b9a1-5f14-4dec-a797-e4cf388ed413.png b/docs/database/mysql/images/mvvc/6fb2b9a1-5f14-4dec-a797-e4cf388ed413.png
new file mode 100644
index 0000000000000000000000000000000000000000..e6dc897c6391a79fe522046bbc3957148a067cb4
Binary files /dev/null and b/docs/database/mysql/images/mvvc/6fb2b9a1-5f14-4dec-a797-e4cf388ed413.png differ
diff --git a/docs/database/mysql/images/mvvc/79ed6142-7664-4e0b-9023-cf546586aa39.png b/docs/database/mysql/images/mvvc/79ed6142-7664-4e0b-9023-cf546586aa39.png
new file mode 100644
index 0000000000000000000000000000000000000000..078ca7f673143724407730911559217809eee7cf
Binary files /dev/null and b/docs/database/mysql/images/mvvc/79ed6142-7664-4e0b-9023-cf546586aa39.png differ
diff --git a/docs/database/mysql/images/mvvc/8778836b-34a8-480b-b8c7-654fe207a8c2.png b/docs/database/mysql/images/mvvc/8778836b-34a8-480b-b8c7-654fe207a8c2.png
new file mode 100644
index 0000000000000000000000000000000000000000..cc95b4ae7354173ef76b3cbb0e0bc844a4be0c50
Binary files /dev/null and b/docs/database/mysql/images/mvvc/8778836b-34a8-480b-b8c7-654fe207a8c2.png differ
diff --git a/docs/database/mysql/images/mvvc/a3fd1ec6-8f37-42fa-b090-7446d488fd04.png b/docs/database/mysql/images/mvvc/a3fd1ec6-8f37-42fa-b090-7446d488fd04.png
new file mode 100644
index 0000000000000000000000000000000000000000..240a64ad58ce1dec167b31d6dc14342b82cbf53a
Binary files /dev/null and b/docs/database/mysql/images/mvvc/a3fd1ec6-8f37-42fa-b090-7446d488fd04.png differ
diff --git a/docs/database/mysql/images/mvvc/c52ff79f-10e6-46cb-b5d4-3c9cbcc1934a.png b/docs/database/mysql/images/mvvc/c52ff79f-10e6-46cb-b5d4-3c9cbcc1934a.png
new file mode 100644
index 0000000000000000000000000000000000000000..7529d9ce8b5378d8aba8e8b144a830dd1f2beaef
Binary files /dev/null and b/docs/database/mysql/images/mvvc/c52ff79f-10e6-46cb-b5d4-3c9cbcc1934a.png differ
diff --git a/docs/database/mysql/images/mvvc/cbbedbc5-0e3c-4711-aafd-7f3d68a4ed4e.png b/docs/database/mysql/images/mvvc/cbbedbc5-0e3c-4711-aafd-7f3d68a4ed4e.png
new file mode 100644
index 0000000000000000000000000000000000000000..f3608c42b511324861303b73657a6c226b031a04
Binary files /dev/null and b/docs/database/mysql/images/mvvc/cbbedbc5-0e3c-4711-aafd-7f3d68a4ed4e.png differ
diff --git a/docs/database/mysql/images/mvvc/trans_visible.png b/docs/database/mysql/images/mvvc/trans_visible.png
new file mode 100644
index 0000000000000000000000000000000000000000..f0df5216c027dc54f236c4dcdaf1f2318d2040f7
Binary files /dev/null and b/docs/database/mysql/images/mvvc/trans_visible.png differ
diff --git "a/docs/database/mysql/images/\344\272\213\345\212\241\347\244\272\346\204\217\345\233\276.drawio" "b/docs/database/mysql/images/\344\272\213\345\212\241\347\244\272\346\204\217\345\233\276.drawio"
new file mode 100644
index 0000000000000000000000000000000000000000..4f649c952cca408ba472acc6a6efad134e1e4315
--- /dev/null
+++ "b/docs/database/mysql/images/\344\272\213\345\212\241\347\244\272\346\204\217\345\233\276.drawio"
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git "a/docs/database/mysql/images/\346\225\260\346\215\256\345\272\223\344\272\213\345\212\241\347\244\272\346\204\217\345\233\276.drawio" "b/docs/database/mysql/images/\346\225\260\346\215\256\345\272\223\344\272\213\345\212\241\347\244\272\346\204\217\345\233\276.drawio"
new file mode 100644
index 0000000000000000000000000000000000000000..6ab55e057465e94f8f16c206baadaf07ef2854a8
--- /dev/null
+++ "b/docs/database/mysql/images/\346\225\260\346\215\256\345\272\223\344\272\213\345\212\241\347\244\272\346\204\217\345\233\276.drawio"
@@ -0,0 +1 @@
+7Zpbc5s4GIZ/jS6TscCAuAQb0sm0s9tmOtvuTUcBGdPIyAtyYu+vX0kIc3TidJOYepLMJNKnE0iP3k8HgDlbba9yvF5+YjGhwJjEW2DOgWHAieGKf9KyKy3uxCwNSZ7GOlNtuEn/JVVJbd2kMSlaGTljlKfrtjFiWUYi3rLhPGcP7WwLRtutrnFCeoabCNO+9a805svSigyntn8gabKsWoa2fuFbHN0lOdtkuj1gmKEdhiEqk1e4qku/aLHEMXtomMwAmLOcMV6GVtsZobJvq24ry4UHUvfPnZOMH1OAJjfh359XhXPNrY9WEH0prr9cyAKymntMN6R6D/W0fFf1kHpHImuBwPQfliknN2scydQHwYSwLfmK6uQFy7geZGiJ+P6tJzJyR3i01JFFSumMUZarVsy5FaD5VGbiObsjVUrGMqKrDfEqpZKya8L9HKdZIZ7zE8tY1Szb5OqplpwLeAzL9MQf0R/yj8xQXCaMJZTgdVpcRmylEqJCZQ0XZe0i2KzfMnzdQr+79Qjck5yTbcOku/+KsBXhuahysq2mRllCzxRHRx8a2CFtWzaQM21txBr1ZF9zPdwioEf8GaNv98aaxGJy6CjL+ZIlLMM0qK1+TYMcxDrPR8bWmoGfhPOdhgBvOGsTIvov333T5VXku4xcWlV0vm0mzncVPAdJKompZq8xblyKqtlDg6Llk+M8IfyRfFaZT47Yo/DlhGKe3rfl7sVRMgd0xKZc93ULMvufDasSLgqFiej5CXTW2zpRhBL1P7CAPwNiXoiAmEBeCIIp8D2AVBLygAerlkA5bvuiPbgpFV6FHCFibWmKMUGL6JA0PaJw46XweNGCVlu19v6sIVvQGJAt57VUC55Eto53bOclRy8uM7ronyxVuqAxc6ZtzGDX6ZV6qEt1CNo/xv+AyjmJL9ym/Fsj3PCEIlY7Qhmp/OA7iK8LYkfvTPM4EL08x7tGtrXMUBxux+4Ab1hWe/n+RP7quep5UD7Bi84K66V3B23HulgsjOgXHGtCcVGck5PdQ9ZVv4aT3e+Ym04Wuq/mZWFvqEflZUc87k9KmXPkEh+Naonv9MTg5vNHENjANQGaq2X5HLizPjdiEvD2MA/v8RvioE2YpkkmopHoYiLsvpxSaYSppxNWaRzTQyv6No1ngdYzJKVz2gDRwHHDgKIYryYofd/xpqcN9QHDd9A8Xxg+bTgLWp4UInSkEFWuZyRKhI5Toj5w70o0AiUyzFMr0eQ4frJ3fsbIjwlPzU//sPNS/fwmvOSMC5Fmshb38ZP18+AHdfRn6OLlTfk54L8sgCzgIRC4AJkqYAPkAs8BAZLH3GimLEieg5dn3274myB35oh1D2fgyV2c20NM+beZBEr6N6SuVFTA87T367ImYBQZoLT4U+BZ76yNgrXuAaV1YtaM/nLqpe7+BJOhUjt15SdofL/7e0vUHLsjaye/+9u/w2vcM7s+cAMJnS8C3tuztkAR+ZXj8PNgzenI2uQNWfPx2vpw95XAmJtft1v7jx8/0osB1PQSbabYmAHXUZYQ+CIQKot3je/xlfymraLlNj+KSltQqXDsgemo3SiUAU+A6e6b+imaUp/PXUZj3apSfEuov/9SbuCrrspz2zremA+++m1vWFrwwxHB3yN9YD4chH8Kp22hHdjiwiGnDp8Pv4jWHx2WF4X1l51m8B8=
\ No newline at end of file
diff --git a/docs/database/mysql/index-invalidation-caused-by-implicit-conversion.md b/docs/database/mysql/index-invalidation-caused-by-implicit-conversion.md
new file mode 100644
index 0000000000000000000000000000000000000000..b433a40e57114b2622fa0084989b038c2c569970
--- /dev/null
+++ b/docs/database/mysql/index-invalidation-caused-by-implicit-conversion.md
@@ -0,0 +1,160 @@
+---
+title: MySQL隐式转换造成索引失效
+category: 数据库
+tag:
+ - MySQL
+ - 性能优化
+---
+
+> 本次测试使用的 MySQL 版本是 `5.7.26`,随着 MySQL 版本的更新某些特性可能会发生改变,本文不代表所述观点和结论于 MySQL 所有版本均准确无误,版本差异请自行甄别。
+>
+> 原文:https://www.guitu18.com/post/2019/11/24/61.html
+
+## 前言
+
+数据库优化是一个任重而道远的任务,想要做优化必须深入理解数据库的各种特性。在开发过程中我们经常会遇到一些原因很简单但造成的后果却很严重的疑难杂症,这类问题往往还不容易定位,排查费时费力最后发现是一个很小的疏忽造成的,又或者是因为不了解某个技术特性产生的。
+
+于数据库层面,最常见的恐怕就是索引失效了,且一开始因为数据量小还不易被发现。但随着业务的拓展数据量的提升,性能问题慢慢的就体现出来了,处理不及时还很容易造成雪球效应,最终导致数据库卡死甚至瘫痪。造成索引失效的原因可能有很多种,相关技术博客已经有太多了,今天我要记录的是**隐式转换造成的索引失效**。
+
+## 数据准备
+
+首先使用存储过程生成 1000 万条测试数据,
+测试表一共建立了 7 个字段(包括主键),`num1`和`num2`保存的是和`ID`一样的顺序数字,其中`num2`是字符串类型。
+`type1`和`type2`保存的都是主键对 5 的取模,目的是模拟实际应用中常用类似 type 类型的数据,但是`type2`是没有建立索引的。
+`str1`和`str2`都是保存了一个 20 位长度的随机字符串,`str1`不能为`NULL`,`str2`允许为`NULL`,相应的生成测试数据的时候我也会在`str2`字段生产少量`NULL`值(每 100 条数据产生一个`NULL`值)。
+
+```sql
+-- 创建测试数据表
+DROP TABLE IF EXISTS test1;
+CREATE TABLE `test1` (
+ `id` int(11) NOT NULL,
+ `num1` int(11) NOT NULL DEFAULT '0',
+ `num2` varchar(11) NOT NULL DEFAULT '',
+ `type1` int(4) NOT NULL DEFAULT '0',
+ `type2` int(4) NOT NULL DEFAULT '0',
+ `str1` varchar(100) NOT NULL DEFAULT '',
+ `str2` varchar(100) DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `num1` (`num1`),
+ KEY `num2` (`num2`),
+ KEY `type1` (`type1`),
+ KEY `str1` (`str1`),
+ KEY `str2` (`str2`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- 创建存储过程
+DROP PROCEDURE IF EXISTS pre_test1;
+DELIMITER //
+CREATE PROCEDURE `pre_test1`()
+BEGIN
+ DECLARE i INT DEFAULT 0;
+ SET autocommit = 0;
+ WHILE i < 10000000 DO
+ SET i = i + 1;
+ SET @str1 = SUBSTRING(MD5(RAND()),1,20);
+ -- 每100条数据str2产生一个null值
+ IF i % 100 = 0 THEN
+ SET @str2 = NULL;
+ ELSE
+ SET @str2 = @str1;
+ END IF;
+ INSERT INTO test1 (`id`, `num1`, `num2`,
+ `type1`, `type2`, `str1`, `str2`)
+ VALUES (CONCAT('', i), CONCAT('', i),
+ CONCAT('', i), i%5, i%5, @str1, @str2);
+ -- 事务优化,每一万条数据提交一次事务
+ IF i % 10000 = 0 THEN
+ COMMIT;
+ END IF;
+ END WHILE;
+END;
+// DELIMITER ;
+-- 执行存储过程
+CALL pre_test1();
+```
+
+数据量比较大,还涉及使用`MD5`生成随机字符串,所以速度有点慢,稍安勿躁,耐心等待即可。
+
+1000 万条数据,我用了 33 分钟才跑完(实际时间跟你电脑硬件配置有关)。这里贴几条生成的数据,大致长这样。
+
+
+
+## SQL 测试
+
+先来看这组 SQL,一共四条,我们的测试数据表`num1`是`int`类型,`num2`是`varchar`类型,但是存储的数据都是跟主键`id`一样的顺序数字,两个字段都建立有索引。
+
+```sql
+1: SELECT * FROM `test1` WHERE num1 = 10000;
+2: SELECT * FROM `test1` WHERE num1 = '10000';
+3: SELECT * FROM `test1` WHERE num2 = 10000;
+4: SELECT * FROM `test1` WHERE num2 = '10000';
+```
+
+这四条 SQL 都是有针对性写的,12 查询的字段是 int 类型,34 查询的字段是`varchar`类型。12 或 34 查询的字段虽然都相同,但是一个条件是数字,一个条件是用引号引起来的字符串。这样做有什么区别呢?先不看下边的测试结果你能猜出这四条 SQL 的效率顺序吗?
+
+经测试这四条 SQL 最后的执行结果却相差很大,其中 124 三条 SQL 基本都是瞬间出结果,大概在 0.001~0.005 秒,在千万级的数据量下这样的结果可以判定这三条 SQL 性能基本没差别了。但是第三条 SQL,多次测试耗时基本在 4.5~4.8 秒之间。
+
+为什么 34 两条 SQL 效率相差那么大,但是同样做对比的 12 两条 SQL 却没什么差别呢?查看一下执行计划,下边分别 1234 条 SQL 的执行计划数据:
+
+
+
+可以看到,124 三条 SQL 都能使用到索引,连接类型都为`ref`,扫描行数都为 1,所以效率非常高。再看看第三条 SQL,没有用上索引,所以为全表扫描,`rows`直接到达 1000 万了,所以性能差别才那么大。
+
+仔细观察你会发现,34 两条 SQL 查询的字段`num2`是`varchar`类型的,查询条件等号右边加引号的第 4 条 SQL 是用到索引的,那么是查询的数据类型和字段数据类型不一致造成的吗?如果是这样那 12 两条 SQL 查询的字段`num1`是`int`类型,但是第 2 条 SQL 查询条件右边加了引号为什么还能用上索引呢。
+
+查阅 MySQL 相关文档发现是隐式转换造成的,看一下官方的描述:
+
+> 官方文档:[12.2 Type Conversion in Expression Evaluation](https://dev.mysql.com/doc/refman/5.7/en/type-conversion.html?spm=5176.100239.blogcont47339.5.1FTben)
+>
+> 当操作符与不同类型的操作数一起使用时,会发生类型转换以使操作数兼容。某些转换是隐式发生的。例如,MySQL 会根据需要自动将字符串转换为数字,反之亦然。以下规则描述了比较操作的转换方式:
+>
+> 1. 两个参数至少有一个是`NULL`时,比较的结果也是`NULL`,特殊的情况是使用`<=>`对两个`NULL`做比较时会返回`1`,这两种情况都不需要做类型转换
+> 2. 两个参数都是字符串,会按照字符串来比较,不做类型转换
+> 3. 两个参数都是整数,按照整数来比较,不做类型转换
+> 4. 十六进制的值和非数字做比较时,会被当做二进制串
+> 5. 有一个参数是`TIMESTAMP`或`DATETIME`,并且另外一个参数是常量,常量会被转换为`timestamp`
+> 6. 有一个参数是`decimal`类型,如果另外一个参数是`decimal`或者整数,会将整数转换为`decimal`后进行比较,如果另外一个参数是浮点数,则会把`decimal`转换为浮点数进行比较
+> 7. **所有其他情况下,两个参数都会被转换为浮点数再进行比较**
+
+根据官方文档的描述,我们的第 23 两条 SQL 都发生了隐式转换,第 2 条 SQL 的查询条件`num1 = '10000'`,左边是`int`类型右边是字符串,第 3 条 SQL 相反,那么根据官方转换规则第 7 条,左右两边都会转换为浮点数再进行比较。
+
+先看第 2 条 SQL:`SELECT * FROM`test1`WHERE num1 = '10000';` **左边为 int 类型**`10000`,转换为浮点数还是`10000`,右边字符串类型`'10000'`,转换为浮点数也是`10000`。两边的转换结果都是唯一确定的,所以不影响使用索引。
+
+第 3 条 SQL:`SELECT * FROM`test1`WHERE num2 = 10000;` **左边是字符串类型**`'10000'`,转浮点数为 10000 是唯一的,右边`int`类型`10000`转换结果也是唯一的。但是,因为左边是检索条件,`'10000'`转到`10000`虽然是唯一,但是其他字符串也可以转换为`10000`,比如`'10000a'`,`'010000'`,`'10000'`等等都能转为浮点数`10000`,这样的情况下,是不能用到索引的。
+
+关于这个**隐式转换**我们可以通过查询测试验证一下,先插入几条数据,其中`num2='10000a'`、`'010000'`和`'10000'`:
+
+```sql
+INSERT INTO `test1` (`id`, `num1`, `num2`, `type1`, `type2`, `str1`, `str2`) VALUES ('10000001', '10000', '10000a', '0', '0', '2df3d9465ty2e4hd523', '2df3d9465ty2e4hd523');
+INSERT INTO `test1` (`id`, `num1`, `num2`, `type1`, `type2`, `str1`, `str2`) VALUES ('10000002', '10000', '010000', '0', '0', '2df3d9465ty2e4hd523', '2df3d9465ty2e4hd523');
+INSERT INTO `test1` (`id`, `num1`, `num2`, `type1`, `type2`, `str1`, `str2`) VALUES ('10000003', '10000', ' 10000', '0', '0', '2df3d9465ty2e4hd523', '2df3d9465ty2e4hd523');
+```
+
+然后使用第三条 SQL 语句`SELECT * FROM`test1`WHERE num2 = 10000;`进行查询:
+
+
+
+从结果可以看到,后面插入的三条数据也都匹配上了。那么这个字符串隐式转换的规则是什么呢?为什么`num2='10000a'`、`'010000'`和`'10000'`这三种情形都能匹配上呢?查阅相关资料发现规则如下:
+
+1. **不以数字开头**的字符串都将转换为`0`。如`'abc'`、`'a123bc'`、`'abc123'`都会转化为`0`;
+2. **以数字开头的**字符串转换时会进行截取,从第一个字符截取到第一个非数字内容为止。比如`'123abc'`会转换为`123`,`'012abc'`会转换为`012`也就是`12`,`'5.3a66b78c'`会转换为`5.3`,其他同理。
+
+现对以上规则做如下测试验证:
+
+
+
+如此也就印证了之前的查询结果了。
+
+再次写一条 SQL 查询 str1 字段:`SELECT * FROM`test1`WHERE str1 = 1234;`
+
+
+
+## 分析和总结
+
+通过上面的测试我们发现 MySQL 使用操作符的一些特性:
+
+1. 当操作符**左右两边的数据类型不一致**时,会发生**隐式转换**。
+2. 当 where 查询操作符**左边为数值类型**时发生了隐式转换,那么对效率影响不大,但还是不推荐这么做。
+3. 当 where 查询操作符**左边为字符类型**时发生了隐式转换,那么会导致索引失效,造成全表扫描效率极低。
+4. 字符串转换为数值类型时,非数字开头的字符串会转化为`0`,以数字开头的字符串会截取从第一个字符到第一个非数字内容为止的值为转化结果。
+
+所以,我们在写 SQL 时一定要养成良好的习惯,查询的字段是什么类型,等号右边的条件就写成对应的类型。特别当查询的字段是字符串时,等号右边的条件一定要用引号引起来标明这是一个字符串,否则会造成索引失效触发全表扫描。
diff --git a/docs/database/mysql/innodb-implementation-of-mvcc.md b/docs/database/mysql/innodb-implementation-of-mvcc.md
index 96fa1f7982b501c328234d22e908e4a9e2c0a5b1..3712827f0bc2af18bb9190dee08cd8a1b81e547a 100644
--- a/docs/database/mysql/innodb-implementation-of-mvcc.md
+++ b/docs/database/mysql/innodb-implementation-of-mvcc.md
@@ -9,7 +9,7 @@ tag:
### 一致性非锁定读
-对于 [**一致性非锁定读(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)
@@ -74,7 +74,7 @@ private:
**事务可见性示意图**([图源](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)):
-
+
### undo-log
@@ -83,23 +83,23 @@ private:
- 当事务回滚时用于将数据恢复到修改前的样子
- 另一个作用是 `MVCC` ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 `undo log` 读取之前的版本数据,以此实现非锁定读
-**在 `InnoDB` 存储引擎中 `undo log` 分为两种: `insert undo log` 和 `update undo log`:**
+**在 `InnoDB` 存储引擎中 `undo log` 分为两种:`insert undo log` 和 `update undo log`:**
-1. **`insert undo log`** :指在 `insert` 操作中产生的 `undo log`。因为 `insert` 操作的记录只对事务本身可见,对其他事务不可见,故该 `undo log` 可以在事务提交后直接删除。不需要进行 `purge` 操作
+1. **`insert undo log`**:指在 `insert` 操作中产生的 `undo log`。因为 `insert` 操作的记录只对事务本身可见,对其他事务不可见,故该 `undo log` 可以在事务提交后直接删除。不需要进行 `purge` 操作
**`insert` 时的数据初始状态:**
-
+
-2. **`update undo log`** :`update` 或 `delete` 操作中产生的 `undo log`。该 `undo log`可能需要提供 `MVCC` 机制,因此不能在事务提交时就进行删除。提交时放入 `undo log` 链表,等待 `purge线程` 进行最后的删除
+2. **`update undo log`**:`update` 或 `delete` 操作中产生的 `undo log`。该 `undo log`可能需要提供 `MVCC` 机制,因此不能在事务提交时就进行删除。提交时放入 `undo log` 链表,等待 `purge线程` 进行最后的删除
**数据第一次被修改时:**
-
+
**数据第二次被修改时:**
-
+
不同事务或者相同事务的对同一记录行的修改,会使该记录行的 `undo log` 成为一条链表,链首就是最新的记录,链尾就是最早的旧记录。
@@ -107,9 +107,9 @@ private:
在 `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)
+[具体的比较算法](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)):
-
+
1. 如果记录 DB_TRX_ID < m_up_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之前就提交了,所以该记录行的值对当前事务是可见的
@@ -138,13 +138,13 @@ private:
举个例子:
-
+
### 在 RC 下 ReadView 生成情况
-1. **`假设时间线来到 T4 ,那么此时数据行 id = 1 的版本链为`:**
+**1. 假设时间线来到 T4 ,那么此时数据行 id = 1 的版本链为:**
- 
+
由于 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
@@ -152,9 +152,9 @@ private:
- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见
- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花`
-2. **`时间线来到 T6 ,数据的版本链为`:**
+**2. 时间线来到 T6 ,数据的版本链为:**
- 
+
因为在 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
@@ -162,9 +162,9 @@ private:
- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为 101,满足 101 < m_up_limit_id,记录可见,所以在 `T6` 时间点查询到数据为 `name = 李四`,与时间 T4 查询到的结果不一致,不可重复读!
-3. **`时间线来到 T9 ,数据的版本链为`:**
+**3. 时间线来到 T9 ,数据的版本链为:**
-
+
重新生成 `Read View`, 这时事务 101 和 102 都已经提交,所以 **m_ids** 为空,则 m_up_limit_id = m_low_limit_id = 104,最新版本事务 ID 为 102,满足 102 < m_low_limit_id,可见,查询结果为 `name = 赵六`
@@ -172,11 +172,11 @@ private:
### 在 RR 下 ReadView 生成情况
-**在可重复读级别下,只会在事务开始后第一次读取数据时生成一个 Read View(m_ids 列表)**
+在可重复读级别下,只会在事务开始后第一次读取数据时生成一个 Read View(m_ids 列表)
-1. **`在 T4 情况下的版本链为`:**
+**1. 在 T4 情况下的版本链为:**
-
+
在当前执行 `select` 语句时生成一个 `Read View`,此时 **`m_ids`:[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
@@ -186,11 +186,11 @@ private:
- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见
- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花`
-2. **`时间点 T6 情况下`:**
+**2. 时间点 T6 情况下:**
- 
+
- 在 RR 级别下只会生成一次`Read View`,所以此时依然沿用 **`m_ids` :[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
+在 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` 存在列表中,那么这个记录不可见
@@ -200,11 +200,11 @@ private:
- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花`
-3. **时间点 T9 情况下:**
+**3. 时间点 T9 情况下:**
-
+
-此时情况跟 T6 完全一样,由于已经生成了 `Read View`,此时依然沿用 **`m_ids` :[101,102]** ,所以查询结果依然是 `name = 菜花`
+此时情况跟 T6 完全一样,由于已经生成了 `Read View`,此时依然沿用 **`m_ids`:[101,102]** ,所以查询结果依然是 `name = 菜花`
## MVCC➕Next-key-Lock 防止幻读
diff --git a/docs/database/mysql/mysql-auto-increment-primary-key-continuous.md b/docs/database/mysql/mysql-auto-increment-primary-key-continuous.md
new file mode 100644
index 0000000000000000000000000000000000000000..92ecdf84dc527931ae49c6dce1b0da23f73d2068
--- /dev/null
+++ b/docs/database/mysql/mysql-auto-increment-primary-key-continuous.md
@@ -0,0 +1,219 @@
+---
+title: MySQL自增主键一定是连续的吗
+category: 数据库
+tag:
+ - MySQL
+ - 大厂面试
+---
+
+> 作者:飞天小牛肉
+>
+> 原文:https://mp.weixin.qq.com/s/qci10h9rJx_COZbHV3aygQ
+
+众所周知,自增主键可以让聚集索引尽量地保持递增顺序插入,避免了随机查询,从而提高了查询效率。
+
+但实际上,MySQL 的自增主键并不能保证一定是连续递增的。
+
+下面举个例子来看下,如下所示创建一张表:
+
+
+
+## 自增值保存在哪里?
+
+使用 `insert into test_pk values(null, 1, 1)` 插入一行数据,再执行 `show create table` 命令来看一下表的结构定义:
+
+
+
+上述表的结构定义存放在后缀名为 `.frm` 的本地文件中,在 MySQL 安装目录下的 data 文件夹下可以找到这个 `.frm` 文件:
+
+
+
+从上述表结构可以看到,表定义里面出现了一个 `AUTO_INCREMENT=2`,表示下一次插入数据时,如果需要自动生成自增值,会生成 id = 2。
+
+但需要注意的是,自增值并不会保存在这个表结构也就是 `.frm` 文件中,不同的引擎对于自增值的保存策略不同:
+
+1)MyISAM 引擎的自增值保存在数据文件中
+
+2)InnoDB 引擎的自增值,其实是保存在了内存里,并没有持久化。第一次打开表的时候,都会去找自增值的最大值 `max(id)`,然后将 `max(id)+1` 作为这个表当前的自增值。
+
+举个例子:我们现在表里当前数据行里最大的 id 是 1,AUTO_INCREMENT=2,对吧。这时候,我们删除 id=1 的行,AUTO_INCREMENT 还是 2。
+
+
+
+但如果马上重启 MySQL 实例,重启后这个表的 AUTO_INCREMENT 就会变成 1。 也就是说,MySQL 重启可能会修改一个表的 AUTO_INCREMENT 的值。
+
+
+
+
+
+以上,是在我本地 MySQL 5.x 版本的实验,实际上,**到了 MySQL 8.0 版本后,自增值的变更记录被放在了 redo log 中,提供了自增值持久化的能力** ,也就是实现了“如果发生重启,表的自增值可以根据 redo log 恢复为 MySQL 重启前的值”
+
+也就是说对于上面这个例子来说,重启实例后这个表的 AUTO_INCREMENT 仍然是 2。
+
+理解了 MySQL 自增值到底保存在哪里以后,我们再来看看自增值的修改机制,并以此引出第一种自增值不连续的场景。
+
+## 自增值不连续的场景
+
+### 自增值不连续场景 1
+
+在 MySQL 里面,如果字段 id 被定义为 AUTO_INCREMENT,在插入一行数据的时候,自增值的行为如下:
+
+- 如果插入数据时 id 字段指定为 0、null 或未指定值,那么就把这个表当前的 AUTO_INCREMENT 值填到自增字段;
+- 如果插入数据时 id 字段指定了具体的值,就直接使用语句里指定的值。
+
+根据要插入的值和当前自增值的大小关系,自增值的变更结果也会有所不同。假设某次要插入的值是 `insert_num`,当前的自增值是 `autoIncrement_num`:
+
+- 如果 `insert_num < autoIncrement_num`,那么这个表的自增值不变
+- 如果 `insert_num >= autoIncrement_num`,就需要把当前自增值修改为新的自增值
+
+也就是说,如果插入的 id 是 100,当前的自增值是 90,`insert_num >= autoIncrement_num`,那么自增值就会被修改为新的自增值即 101
+
+一定是这样吗?
+
+非也~
+
+了解过分布式 id 的小伙伴一定知道,为了避免两个库生成的主键发生冲突,我们可以让一个库的自增 id 都是奇数,另一个库的自增 id 都是偶数
+
+这个奇数偶数其实是通过 `auto_increment_offset` 和 `auto_increment_increment` 这两个参数来决定的,这俩分别用来表示自增的初始值和步长,默认值都是 1。
+
+所以,上面的例子中生成新的自增值的步骤实际是这样的:从 `auto_increment_offset` 开始,以 `auto_increment_increment` 为步长,持续叠加,直到找到第一个大于 100 的值,作为新的自增值。
+
+所以,这种情况下,自增值可能会是 102,103 等等之类的,就会导致不连续的主键 id。
+
+更遗憾的是,即使在自增初始值和步长这两个参数都设置为 1 的时候,自增主键 id 也不一定能保证主键是连续的
+
+### 自增值不连续场景 2
+
+举个例子,我们现在往表里插入一条 (null,1,1) 的记录,生成的主键是 1,AUTO_INCREMENT= 2,对吧
+
+
+
+这时我再执行一条插入 `(null,1,1)` 的命令,很显然会报错 `Duplicate entry`,因为我们设置了一个唯一索引字段 `a`:
+
+
+
+但是,你会惊奇的发现,虽然插入失败了,但自增值仍然从 2 增加到了 3!
+
+这是为啥?
+
+我们来分析下这个 insert 语句的执行流程:
+
+1. 执行器调用 InnoDB 引擎接口准备插入一行记录 (null,1,1);
+2. InnoDB 发现用户没有指定自增 id 的值,则获取表 `test_pk` 当前的自增值 2;
+3. 将传入的记录改成 (2,1,1);
+4. 将表的自增值改成 3;
+5. 继续执行插入数据操作,由于已经存在 a=1 的记录,所以报 Duplicate key error,语句返回
+
+可以看到,自增值修改的这个操作,是在真正执行插入数据的操作之前。
+
+这个语句真正执行的时候,因为碰到唯一键 a 冲突,所以 id = 2 这一行并没有插入成功,但也没有将自增值再改回去。所以,在这之后,再插入新的数据行时,拿到的自增 id 就是 3。也就是说,出现了自增主键不连续的情况。
+
+至此,我们已经罗列了两种自增主键不连续的情况:
+
+1. 自增初始值和自增步长设置不为 1
+2. 唯一键冲突
+
+除此之外,事务回滚也会导致这种情况
+
+### 自增值不连续场景 3
+
+我们现在表里有一行 `(1,1,1)` 的记录,AUTO_INCREMENT = 3:
+
+
+
+我们先插入一行数据 `(null, 2, 2)`,也就是 (3, 2, 2) 嘛,并且 AUTO_INCREMENT 变为 4:
+
+
+
+再去执行这样一段 SQL:
+
+
+
+虽然我们插入了一条 (null, 3, 3) 记录,但是使用 rollback 进行回滚了,所以数据库中是没有这条记录的:
+
+
+
+在这种事务回滚的情况下,自增值并没有同样发生回滚!如下图所示,自增值仍然固执地从 4 增加到了 5:
+
+
+
+所以这时候我们再去插入一条数据(null, 3, 3)的时候,主键 id 就会被自动赋为 `5` 了:
+
+
+
+那么,为什么在出现唯一键冲突或者回滚的时候,MySQL 没有把表的自增值改回去呢?回退回去的话不就不会发生自增 id 不连续了吗?
+
+事实上,这么做的主要原因是为了提高性能。
+
+我们直接用反证法来验证:假设 MySQL 在事务回滚的时候会把自增值改回去,会发生什么?
+
+现在有两个并行执行的事务 A 和 B,在申请自增值的时候,为了避免两个事务申请到相同的自增 id,肯定要加锁,然后顺序申请,对吧。
+
+1. 假设事务 A 申请到了 id = 1, 事务 B 申请到 id=2,那么这时候表 t 的自增值是 3,之后继续执行。
+2. 事务 B 正确提交了,但事务 A 出现了唯一键冲突,也就是 id = 1 的那行记录插入失败了,那如果允许事务 A 把自增 id 回退,也就是把表的当前自增值改回 1,那么就会出现这样的情况:表里面已经有 id = 2 的行,而当前的自增 id 值是 1。
+3. 接下来,继续执行的其他事务就会申请到 id=2。这时,就会出现插入语句报错“主键冲突”。
+
+
+
+而为了解决这个主键冲突,有两种方法:
+
+1. 每次申请 id 之前,先判断表里面是否已经存在这个 id,如果存在,就跳过这个 id
+2. 把自增 id 的锁范围扩大,必须等到一个事务执行完成并提交,下一个事务才能再申请自增 id
+
+很显然,上述两个方法的成本都比较高,会导致性能问题。而究其原因呢,是我们假设的这个 “允许自增 id 回退”。
+
+因此,InnoDB 放弃了这个设计,语句执行失败也不回退自增 id。也正是因为这样,所以才只保证了自增 id 是递增的,但不保证是连续的。
+
+综上,已经分析了三种自增值不连续的场景,还有第四种场景:批量插入数据。
+
+### 自增值不连续场景 4
+
+对于批量插入数据的语句,MySQL 有一个批量申请自增 id 的策略:
+
+1. 语句执行过程中,第一次申请自增 id,会分配 1 个;
+2. 1 个用完以后,这个语句第二次申请自增 id,会分配 2 个;
+3. 2 个用完以后,还是这个语句,第三次申请自增 id,会分配 4 个;
+4. 依此类推,同一个语句去申请自增 id,每次申请到的自增 id 个数都是上一次的两倍。
+
+注意,这里说的批量插入数据,不是在普通的 insert 语句里面包含多个 value 值!!!,因为这类语句在申请自增 id 的时候,是可以精确计算出需要多少个 id 的,然后一次性申请,申请完成后锁就可以释放了。
+
+而对于 `insert … select`、replace … select 和 load data 这种类型的语句来说,MySQL 并不知道到底需要申请多少 id,所以就采用了这种批量申请的策略,毕竟一个一个申请的话实在太慢了。
+
+举个例子,假设我们现在这个表有下面这些数据:
+
+
+
+我们创建一个和当前表 `test_pk` 有相同结构定义的表 `test_pk2`:
+
+
+
+然后使用 `insert...select` 往 `teset_pk2` 表中批量插入数据:
+
+
+
+可以看到,成功导入了数据。
+
+再来看下 `test_pk2` 的自增值是多少:
+
+
+
+如上分析,是 8 而不是 6
+
+具体来说,insert…select 实际上往表中插入了 5 行数据 (1 1)(2 2)(3 3)(4 4)(5 5)。但是,这五行数据是分三次申请的自增 id,结合批量申请策略,每次申请到的自增 id 个数都是上一次的两倍,所以:
+
+- 第一次申请到了一个 id:id=1
+- 第二次被分配了两个 id:id=2 和 id=3
+- 第三次被分配到了 4 个 id:id=4、id = 5、id = 6、id=7
+
+由于这条语句实际只用上了 5 个 id,所以 id=6 和 id=7 就被浪费掉了。之后,再执行 `insert into test_pk2 values(null,6,6)`,实际上插入的数据就是(8,6,6):
+
+
+
+## 小结
+
+本文总结下自增值不连续的 4 个场景:
+
+1. 自增初始值和自增步长设置不为 1
+2. 唯一键冲突
+3. 事务回滚
+4. 批量插入(如 `insert...select` 语句)
diff --git a/docs/database/mysql/mysql-high-performance-optimization-specification-recommendations.md b/docs/database/mysql/mysql-high-performance-optimization-specification-recommendations.md
index 7c2b7def8ad61a79774630cb195b2771029750d9..cd0079be2db227759e383d5f7e69ac2495fa17b4 100644
--- a/docs/database/mysql/mysql-high-performance-optimization-specification-recommendations.md
+++ b/docs/database/mysql/mysql-high-performance-optimization-specification-recommendations.md
@@ -1,47 +1,50 @@
---
-title: MySQL 高性能优化规范建议
+title: MySQL高性能优化规范建议总结
category: 数据库
tag:
- MySQL
---
-> 作者: 听风,原文地址: 。JavaGuide 已获得作者授权。
+> 作者: 听风 原文地址: 。
+>
+> JavaGuide 已获得作者授权,并对原文内容进行了完善。
-## 数据库命令规范
+## 数据库命名规范
- 所有数据库对象名称必须使用小写字母并用下划线分割
- 所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来)
- 数据库对象的命名要能做到见名识意,并且最后不要超过 32 个字符
-- 临时库表必须以 tmp_为前缀并以日期为后缀,备份表必须以 bak_为前缀并以日期 (时间戳) 为后缀
+- 临时库表必须以 `tmp_` 为前缀并以日期为后缀,备份表必须以 `bak_` 为前缀并以日期 (时间戳) 为后缀
- 所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)
-------
-
## 数据库基本设计规范
-### 1. 所有表必须使用 Innodb 存储引擎
+### 所有表必须使用 InnoDB 存储引擎
-没有特殊要求(即 Innodb 无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用 Innodb 存储引擎(MySQL5.5 之前默认使用 Myisam,5.6 以后默认的为 Innodb)。
+没有特殊要求(即 InnoDB 无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用 InnoDB 存储引擎(MySQL5.5 之前默认使用 Myisam,5.6 以后默认的为 InnoDB)。
-Innodb 支持事务,支持行级锁,更好的恢复性,高并发下性能更好。
+InnoDB 支持事务,支持行级锁,更好的恢复性,高并发下性能更好。
-### 2. 数据库和表的字符集统一使用 UTF8
+### 数据库和表的字符集统一使用 UTF8
兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效,如果数据库中有存储 emoji 表情的需要,字符集需要采用 utf8mb4 字符集。
-参考文章:[MySQL 字符集不一致导致索引失效的一个真实案例](https://blog.csdn.net/horses/article/details/107243447)
+参考文章:
+
+- [MySQL 字符集不一致导致索引失效的一个真实案例](https://blog.csdn.net/horses/article/details/107243447)
+- [MySQL 字符集详解](../character-set.md)
-### 3. 所有表和字段都需要添加注释
+### 所有表和字段都需要添加注释
使用 comment 从句添加表和列的备注,从一开始就进行数据字典的维护
-### 4. 尽量控制单表数据量的大小,建议控制在 500 万以内。
+### 尽量控制单表数据量的大小,建议控制在 500 万以内
500 万并不是 MySQL 数据库的限制,过大会造成修改表结构,备份,恢复都会有很大的问题。
可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小
-### 5. 谨慎使用 MySQL 分区表
+### 谨慎使用 MySQL 分区表
分区表在物理上表现为多个文件,在逻辑上表现为一个表;
@@ -49,97 +52,90 @@ Innodb 支持事务,支持行级锁,更好的恢复性,高并发下性能
建议采用物理分表的方式管理大数据。
-### 6.尽量做到冷热数据分离,减小表的宽度
-
-> MySQL 限制每个表最多存储 4096 列,并且每一行数据的大小不能超过 65535 字节。
-
-减少磁盘 IO,保证热数据的内存缓存命中率(表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的 IO);
+### 经常一起使用的列放到一个表中
-更有效的利用缓存,避免读入无用的冷数据;
+避免更多的关联操作。
-经常一起使用的列放到一个表中(避免更多的关联操作)。
+### 禁止在表中建立预留字段
-### 7. 禁止在表中建立预留字段
+- 预留字段的命名很难做到见名识义。
+- 预留字段无法确认存储的数据类型,所以无法选择合适的类型。
+- 对预留字段类型的修改,会对表进行锁定。
-预留字段的命名很难做到见名识义。
+### 禁止在数据库中存储文件(比如图片)这类大的二进制数据
-预留字段无法确认存储的数据类型,所以无法选择合适的类型。
+在数据库中存储文件会严重影响数据库性能,消耗过多存储空间。
-对预留字段类型的修改,会对表进行锁定。
+文件(比如图片)这类大的二进制数据通常存储于文件服务器,数据库只存储文件地址信息。
-### 8. 禁止在数据库中存储图片,文件等大的二进制数据
+### 不要被数据库范式所束缚
-通常文件很大,会短时间内造成数据量快速增长,数据库进行数据库读取时,通常会进行大量的随机 IO 操作,文件很大时,IO 操作很耗时。
+一般来说,设计关系数据库时需要满足第三范式,但为了满足第三范式,我们可能会拆分出多张表。而在进行查询时需要对多张表进行关联查询,有时为了提高查询效率,会降低范式的要求,在表中保存一定的冗余信息,也叫做反范式。但要注意反范式一定要适度。
-通常存储于文件服务器,数据库只存储文件地址信息
+### 禁止在线上做数据库压力测试
-### 9. 禁止在线上做数据库压力测试
+### 禁止从开发环境,测试环境直接连接生产环境数据库
-### 10. 禁止从开发环境,测试环境直接连接生产环境数据库
-
-------
+安全隐患极大,要对生产环境抱有敬畏之心!
## 数据库字段设计规范
-### 1. 优先选择符合存储需要的最小的数据类型
-
-**原因:**
+### 优先选择符合存储需要的最小的数据类型
-列的字段越大,建立索引时所需要的空间也就越大,这样一页中所能存储的索引节点的数量也就越少也越少,在遍历时所需要的 IO 次数也就越多,索引的性能也就越差。
+存储字节越小,占用也就空间越小,性能也越好。
-**方法:**
+**a.某些字符串可以转换成数字类型存储比如可以将 IP 地址转换成整型数据。**
-**a.将字符串转换成数字类型存储,如:将 IP 地址转换成整形数据**
+数字是连续的,性能更好,占用空间也更小。
MySQL 提供了两个方法来处理 ip 地址
-- inet_aton 把 ip 转为无符号整型 (4-8 位)
-- inet_ntoa 把整型的 ip 转为地址
-
-插入数据前,先用 inet_aton 把 ip 地址转为整型,可以节省空间,显示数据时,使用 inet_ntoa 把整型的 ip 地址转为地址显示即可。
+- `INET_ATON()`:把 ip 转为无符号整型 (4-8 位)
+- `INET_NTOA()` :把整型的 ip 转为地址
-**b.对于非负型的数据 (如自增 ID,整型 IP) 来说,要优先使用无符号整型来存储**
+插入数据前,先用 `INET_ATON()` 把 ip 地址转为整型,显示数据时,使用 `INET_NTOA()` 把整型的 ip 地址转为地址显示即可。
-**原因:**
+**b.对于非负型的数据 (如自增 ID,整型 IP,年龄) 来说,要优先使用无符号整型来存储。**
无符号相对于有符号可以多出一倍的存储空间
-```
+```sql
SIGNED INT -2147483648~2147483647
UNSIGNED INT 0~4294967295
```
-VARCHAR(N) 中的 N 代表的是字符数,而不是字节数,使用 UTF8 存储 255 个汉字 Varchar(255)=765 个字节。**过大的长度会消耗更多的内存。**
+**c.小数值类型(比如年龄、状态表示如 0/1)优先使用 TINYINT 类型。**
-### 2. 避免使用 TEXT,BLOB 数据类型,最常见的 TEXT 类型可以存储 64k 的数据
+### 避免使用 TEXT,BLOB 数据类型,最常见的 TEXT 类型可以存储 64k 的数据
-**a. 建议把 BLOB 或是 TEXT 列分离到单独的扩展表中**
+**a. 建议把 BLOB 或是 TEXT 列分离到单独的扩展表中。**
MySQL 内存临时表不支持 TEXT、BLOB 这样的大数据类型,如果查询中包含这样的数据,在排序等操作时,就不能使用内存临时表,必须使用磁盘临时表进行。而且对于这种数据,MySQL 还是要进行二次查询,会使 sql 性能变得很差,但是不是说一定不能使用这样的数据类型。
-如果一定要使用,建议把 BLOB 或是 TEXT 列分离到单独的扩展表中,查询时一定不要使用 select * 而只需要取出必要的列,不需要 TEXT 列的数据时不要对该列进行查询。
+如果一定要使用,建议把 BLOB 或是 TEXT 列分离到单独的扩展表中,查询时一定不要使用 `select *`而只需要取出必要的列,不需要 TEXT 列的数据时不要对该列进行查询。
**2、TEXT 或 BLOB 类型只能使用前缀索引**
-因为[MySQL](https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247487885&idx=1&sn=65b1bf5f7d4505502620179669a9c2df&chksm=ebd62ea1dca1a7b7bf884bcd9d538d78ba064ee03c09436ca8e57873b1d98a55afd6d7884cfc&scene=21#wechat_redirect) 对索引字段长度是有限制的,所以 TEXT 类型只能使用前缀索引,并且 TEXT 列上是不能有默认值的
-
-### 3. 避免使用 ENUM 类型
+因为 MySQL 对索引字段长度是有限制的,所以 TEXT 类型只能使用前缀索引,并且 TEXT 列上是不能有默认值的
-修改 ENUM 值需要使用 ALTER 语句
+### 避免使用 ENUM 类型
-ENUM 类型的 ORDER BY 操作效率低,需要额外操作
+- 修改 ENUM 值需要使用 ALTER 语句;
+- ENUM 类型的 ORDER BY 操作效率低,需要额外操作;
+- ENUM 数据类型存在一些限制比如建议不要使用数值作为 ENUM 的枚举值。
-禁止使用数值作为 ENUM 的枚举值
+相关阅读:[是否推荐使用 MySQL 的 enum 类型? - 架构文摘 - 知乎](https://www.zhihu.com/question/404422255/answer/1661698499) 。
-### 4. 尽可能把所有列定义为 NOT NULL
+### 尽可能把所有列定义为 NOT NULL
-**原因:**
+除非有特别的原因使用 NULL 值,应该总是让字段保持 NOT NULL。
-索引 NULL 列需要额外的空间来保存,所以要占用更多的空间
+- 索引 NULL 列需要额外的空间来保存,所以要占用更多的空间;
+- 进行比较和计算时要对 NULL 值做特别的处理。
-进行比较和计算时要对 NULL 值做特别的处理
+相关阅读:[技术分享 | MySQL 默认值选型(是空,还是 NULL)](https://opensource.actionsky.com/20190710-mysql/) 。
-### 5. 使用 TIMESTAMP(4 个字节) 或 DATETIME 类型 (8 个字节) 存储时间
+### 使用 TIMESTAMP(4 个字节) 或 DATETIME 类型 (8 个字节) 存储时间
TIMESTAMP 存储的时间范围 1970-01-01 00:00:01 ~ 2038-01-19-03:14:07
@@ -152,22 +148,22 @@ TIMESTAMP 占用 4 字节和 INT 相同,但比 INT 可读性高
- 缺点 1:无法用日期函数进行计算和比较
- 缺点 2:用字符串存储日期要占用更多的空间
-### 6. 同财务相关的金额类数据必须使用 decimal 类型
+### 同财务相关的金额类数据必须使用 decimal 类型
-- 非精准浮点:float,double
-- 精准浮点:decimal
+- **非精准浮点**:float,double
+- **精准浮点**:decimal
-Decimal 类型为精准浮点数,在计算时不会丢失精度
+decimal 类型为精准浮点数,在计算时不会丢失精度。占用空间由定义的宽度决定,每 4 个字节可以存储 9 位数字,并且小数点要占用一个字节。并且,decimal 可用于存储比 bigint 更大的整型数据
-占用空间由定义的宽度决定,每 4 个字节可以存储 9 位数字,并且小数点要占用一个字节
+不过, 由于 decimal 需要额外的空间和计算开销,应该尽量只在需要对数据进行精确计算时才使用 decimal 。
-可用于存储比 bigint 更大的整型数据
+### 单表不要包含过多字段
-------
+如果一个表包含过多字段的话,可以考虑将其分解成多个表,必要时增加中间表进行关联。
## 索引设计规范
-### 1. 限制每张表上的索引数量,建议单张表索引不超过 5 个
+### 限制每张表上的索引数量,建议单张表索引不超过 5 个
索引并不是越多越好!索引可以提高效率同样可以降低效率。
@@ -175,32 +171,32 @@ Decimal 类型为精准浮点数,在计算时不会丢失精度
因为 MySQL 优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,就会增加 MySQL 优化器生成执行计划的时间,同样会降低查询性能。
-### 2. 禁止给表中的每一列都建立单独的索引
+### 禁止使用全文索引
+
+全文索引不适用于 OLTP 场景。
+
+### 禁止给表中的每一列都建立单独的索引
5.6 版本之前,一个 sql 只能使用到一个表中的一个索引,5.6 以后,虽然有了合并索引的优化方式,但是还是远远没有使用一个联合索引的查询方式好。
-### 3. 每个 Innodb 表必须有个主键
+### 每个 InnoDB 表必须有个主键
-Innodb 是一种索引组织表:数据的存储的逻辑顺序和索引的顺序是相同的。每个表都可以有多个索引,但是表的存储顺序只能有一种。
+InnoDB 是一种索引组织表:数据的存储的逻辑顺序和索引的顺序是相同的。每个表都可以有多个索引,但是表的存储顺序只能有一种。
-Innodb 是按照主键索引的顺序来组织表的
+InnoDB 是按照主键索引的顺序来组织表的
-- 不要使用更新频繁的列作为主键,不适用多列主键(相当于联合索引)
+- 不要使用更新频繁的列作为主键,不使用多列主键(相当于联合索引)
- 不要使用 UUID,MD5,HASH,字符串列作为主键(无法保证数据的顺序增长)
- 主键建议使用自增 ID 值
-------
-
-### 4. 常见索引列建议
+### 常见索引列建议
- 出现在 SELECT、UPDATE、DELETE 语句的 WHERE 从句中的列
- 包含在 ORDER BY、GROUP BY、DISTINCT 中的字段
- 并不要将符合 1 和 2 中的字段的列都建立一个索引, 通常将 1、2 中的字段建立联合索引效果更好
- 多表 join 的关联列
-------
-
-### 5.如何选择索引列的顺序
+### 如何选择索引列的顺序
建立索引的目的是:希望通过索引进行数据查找,减少随机 IO,增加查询性能 ,索引能过滤出越少的数据,则从磁盘中读入的数据也就越少。
@@ -208,27 +204,23 @@ Innodb 是按照主键索引的顺序来组织表的
- 尽量把字段长度小的列放在联合索引的最左侧(因为字段长度越小,一页能存储的数据量越大,IO 性能也就越好)
- 使用最频繁的列放到联合索引的左侧(这样可以比较少的建立一些索引)
-------
-
-### 6. 避免建立冗余索引和重复索引(增加了查询优化器生成执行计划的时间)
+### 避免建立冗余索引和重复索引(增加了查询优化器生成执行计划的时间)
- 重复索引示例:primary key(id)、index(id)、unique index(id)
- 冗余索引示例:index(a,b,c)、index(a,b)、index(a)
-------
-
-### 7. 对于频繁的查询优先考虑使用覆盖索引
+### 对于频繁的查询优先考虑使用覆盖索引
> 覆盖索引:就是包含了所有查询字段 (where,select,order by,group by 包含的字段) 的索引
**覆盖索引的好处:**
-- **避免 Innodb 表进行索引的二次查询:** Innodb 是以聚集索引的顺序来存储的,对于 Innodb 来说,二级索引在叶子节点中所保存的是行的主键信息,如果是用二级索引查询数据的话,在查找到相应的键值后,还要通过主键进行二次查询才能获取我们真实所需要的数据。而在覆盖索引中,二级索引的键值中可以获取所有的数据,避免了对主键的二次查询 ,减少了 IO 操作,提升了查询效率。
+- **避免 InnoDB 表进行索引的二次查询:** InnoDB 是以聚集索引的顺序来存储的,对于 InnoDB 来说,二级索引在叶子节点中所保存的是行的主键信息,如果是用二级索引查询数据的话,在查找到相应的键值后,还要通过主键进行二次查询才能获取我们真实所需要的数据。而在覆盖索引中,二级索引的键值中可以获取所有的数据,避免了对主键的二次查询 ,减少了 IO 操作,提升了查询效率。
- **可以把随机 IO 变成顺序 IO 加快查询效率:** 由于覆盖索引是按键值的顺序存储的,对于 IO 密集型的范围查找来说,对比随机从磁盘读取每一行的数据 IO 要少的多,因此利用覆盖索引在访问时也可以把磁盘的随机读取的 IO 转变成索引查找的顺序 IO。
-------
+---
-### 8.索引 SET 规范
+### 索引 SET 规范
**尽量避免使用外键约束**
@@ -236,27 +228,13 @@ Innodb 是按照主键索引的顺序来组织表的
- 外键可用于保证数据的参照完整性,但建议在业务端实现
- 外键会影响父表和子表的写操作从而降低性能
-------
-
## 数据库 SQL 开发规范
-### 1. 建议使用预编译语句进行数据库操作
-
-预编译语句可以重复使用这些计划,减少 SQL 编译所需要的时间,还可以解决动态 SQL 所带来的 SQL 注入的问题。
-
-只传参数,比传递 SQL 语句更高效。
+### 优化对性能影响较大的 SQL 语句
-相同语句可以一次解析,多次使用,提高处理效率。
-
-### 2. 避免数据类型的隐式转换
-
-隐式转换会导致索引失效如:
-
-```
-select name,phone from customer where id = '111';
-```
+要找到最需要优化的 SQL 语句。要么是使用最频繁的语句,要么是优化后提高最明显的语句,可以通过查询 MySQL 的慢查询日志来发现需要进行优化的 SQL 语句;
-### 3. 充分利用表上已经存在的索引
+### 充分利用表上已经存在的索引
避免使用双%号的查询条件。如:`a like '%123%'`,(如果无前置%,只有后置%,是可以用到列上的索引的)
@@ -264,47 +242,49 @@ select name,phone from customer where id = '111';
在定义联合索引时,如果 a 列要用到范围查找的话,就要把 a 列放到联合索引的右侧,使用 left join 或 not exists 来优化 not in 操作,因为 not in 也通常会使用索引失效。
-### 4. 数据库设计时,应该要对以后扩展进行考虑
-
-### 5. 程序连接不同的数据库使用不同的账号,禁止跨库查询
-
-- 为数据库迁移和分库分表留出余地
-- 降低业务耦合度
-- 避免权限过大而产生的安全风险
-
-### 6. 禁止使用 SELECT * 必须使用 SELECT <字段列表> 查询
+### 禁止使用 SELECT \* 必须使用 SELECT <字段列表> 查询
-**原因:**
+- `SELECT *` 消耗更多的 CPU 和 IO 以网络带宽资源
+- `SELECT *` 无法使用覆盖索引
+- `SELECT <字段列表>` 可减少表结构变更带来的影响
-- 消耗更多的 CPU 和 IO 以网络带宽资源
-- 无法使用覆盖索引
-- 可减少表结构变更带来的影响
-
-### 7. 禁止使用不含字段列表的 INSERT 语句
+### 禁止使用不含字段列表的 INSERT 语句
如:
-```
-insert into values ('a','b','c');
+```sql
+insert into t values ('a','b','c');
```
应使用:
-```
+```sql
insert into t(c1,c2,c3) values ('a','b','c');
```
-### 8. 避免使用子查询,可以把子查询优化为 join 操作
+### 建议使用预编译语句进行数据库操作
-通常子查询在 in 子句中,且子查询中为简单 SQL(不包含 union、group by、order by、limit 从句) 时,才可以把子查询转化为关联查询进行优化。
+- 预编译语句可以重复使用这些计划,减少 SQL 编译所需要的时间,还可以解决动态 SQL 所带来的 SQL 注入的问题。
+- 只传参数,比传递 SQL 语句更高效。
+- 相同语句可以一次解析,多次使用,提高处理效率。
-**子查询性能差的原因:**
+### 避免数据类型的隐式转换
-子查询的结果集无法使用索引,通常子查询的结果集会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响。特别是对于返回结果集比较大的子查询,其对查询性能的影响也就越大。
+隐式转换会导致索引失效如:
-由于子查询会产生大量的临时表也没有索引,所以会消耗过多的 CPU 和 IO 资源,产生大量的慢查询。
+```sql
+select name,phone from customer where id = '111';
+```
+
+详细解读可以看:[MySQL 中的隐式转换造成的索引失效](./index-invalidation-caused-by-implicit-conversion.md) 这篇文章。
+
+### 避免使用子查询,可以把子查询优化为 join 操作
+
+通常子查询在 in 子句中,且子查询中为简单 SQL(不包含 union、group by、order by、limit 从句) 时,才可以把子查询转化为关联查询进行优化。
+
+**子查询性能差的原因:** 子查询的结果集无法使用索引,通常子查询的结果集会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响。特别是对于返回结果集比较大的子查询,其对查询性能的影响也就越大。由于子查询会产生大量的临时表也没有索引,所以会消耗过多的 CPU 和 IO 资源,产生大量的慢查询。
-### 9. 避免使用 JOIN 关联太多的表
+### 避免使用 JOIN 关联太多的表
对于 MySQL 来说,是存在关联缓存的,缓存的大小可以由 join_buffer_size 参数进行设置。
@@ -314,57 +294,60 @@ insert into t(c1,c2,c3) values ('a','b','c');
同时对于关联操作来说,会产生临时表操作,影响查询效率,MySQL 最多允许关联 61 个表,建议不超过 5 个。
-### 10. 减少同数据库的交互次数
+### 减少同数据库的交互次数
数据库更适合处理批量操作,合并多个相同的操作到一起,可以提高处理效率。
-### 11. 对应同一列进行 or 判断时,使用 in 代替 or
+### 对应同一列进行 or 判断时,使用 in 代替 or
in 的值不要超过 500 个,in 操作可以更有效的利用索引,or 大多数情况下很少能利用到索引。
-### 12. 禁止使用 order by rand() 进行随机排序
+### 禁止使用 order by rand() 进行随机排序
order by rand() 会把表中所有符合条件的数据装载到内存中,然后在内存中对所有数据根据随机生成的值进行排序,并且可能会对每一行都生成一个随机值,如果满足条件的数据集非常大,就会消耗大量的 CPU 和 IO 及内存资源。
推荐在程序中获取一个随机值,然后从数据库中获取数据的方式。
-### 13. WHERE 从句中禁止对列进行函数转换和计算
+### WHERE 从句中禁止对列进行函数转换和计算
对列进行函数转换或计算时会导致无法使用索引
**不推荐:**
-```
+```sql
where date(create_time)='20190101'
```
**推荐:**
-```
+```sql
where create_time >= '20190101' and create_time < '20190102'
```
-### 14. 在明显不会有重复值时使用 UNION ALL 而不是 UNION
+### 在明显不会有重复值时使用 UNION ALL 而不是 UNION
- UNION 会把两个结果集的所有数据放到临时表中后再进行去重操作
- UNION ALL 不会再对结果集进行去重操作
-### 15. 拆分复杂的大 SQL 为多个小 SQL
+### 拆分复杂的大 SQL 为多个小 SQL
- 大 SQL 逻辑上比较复杂,需要占用大量 CPU 进行计算的 SQL
- MySQL 中,一个 SQL 只能使用一个 CPU 进行计算
- SQL 拆分后可以通过并行执行来提高处理效率
-------
+### 程序连接不同的数据库使用不同的账号,禁止跨库查询
+
+- 为数据库迁移和分库分表留出余地
+- 降低业务耦合度
+- 避免权限过大而产生的安全风险
## 数据库操作行为规范
-### 1. 超 100 万行的批量写 (UPDATE,DELETE,INSERT) 操作,要分批多次进行操作
+### 超 100 万行的批量写 (UPDATE,DELETE,INSERT) 操作,要分批多次进行操作
**大批量操作可能会造成严重的主从延迟**
-主从环境中,大批量操作可能会造成严重的主从延迟,大批量的写操作一般都需要执行一定长的时间,
-而只有当主库上执行完成后,才会在其他从库上执行,所以会造成主库与从库长时间的延迟情况
+主从环境中,大批量操作可能会造成严重的主从延迟,大批量的写操作一般都需要执行一定长的时间,而只有当主库上执行完成后,才会在其他从库上执行,所以会造成主库与从库长时间的延迟情况
**binlog 日志为 row 格式时会产生大量的日志**
@@ -376,7 +359,7 @@ where create_time >= '20190101' and create_time < '20190102'
特别是长时间的阻塞会占满所有数据库的可用连接,这会使生产环境中的其他应用无法连接到数据库,因此一定要注意大批量写操作要进行分批
-### 2. 对于大表使用 pt-online-schema-change 修改表结构
+### 对于大表使用 pt-online-schema-change 修改表结构
- 避免大表修改产生的主从延迟
- 避免在对表字段进行修改时进行锁表
@@ -385,12 +368,12 @@ where create_time >= '20190101' and create_time < '20190102'
pt-online-schema-change 它会首先建立一个与原表结构相同的新表,并且在新表上进行表结构的修改,然后再把原表中的数据复制到新表中,并在原表中增加一些触发器。把原表中新增的数据也复制到新表中,在行所有数据复制完成之后,把新表命名成原表,并把原来的表删除掉。把原来一个 DDL 操作,分解成多个小的批次进行。
-### 3. 禁止为程序使用的账号赋予 super 权限
+### 禁止为程序使用的账号赋予 super 权限
- 当达到最大连接数限制时,还运行 1 个有 super 权限的用户连接
- super 权限只能留给 DBA 处理问题的账号使用
-### 4. 对于程序连接数据库账号,遵循权限最小原则
+### 对于程序连接数据库账号,遵循权限最小原则
- 程序使用数据库账号只能在一个 DB 下使用,不准跨库
- 程序使用的账号原则上不准有 drop 权限
diff --git a/docs/database/mysql/mysql-index.md b/docs/database/mysql/mysql-index.md
index 5dd4e5264924d32e53f6e3f29d3ed72643401c18..38ebdd0f373c4469307cbe08179b24204c4e180d 100644
--- a/docs/database/mysql/mysql-index.md
+++ b/docs/database/mysql/mysql-index.md
@@ -1,26 +1,32 @@
---
-title: MySQL 索引详解
+title: MySQL索引详解
category: 数据库
tag:
- MySQL
---
+> 感谢[WT-AHA](https://github.com/WT-AHA)对本文的完善,相关 PR:https://github.com/Snailclimb/JavaGuide/pull/1648 。
+但凡经历过几场面试的小伙伴,应该都清楚,数据库索引这个知识点在面试中出现的频率高到离谱。
-## 何为索引?有什么作用?
+除了对于准备面试来说非常重要之外,善用索引对 SQL 的性能提升非常明显,是一个性价比较高的 SQL 优化手段。
-**索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。**
+## 索引介绍
-索引的作用就相当于目录的作用。打个比方: 我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
+**索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。**
+
+索引的作用就相当于书的目录。打个比方: 我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
+
+索引底层数据结构存在很多种类型,常见的索引结构有: B 树, B+树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyIsam,都使用了 B+树作为索引结构。
## 索引的优缺点
-**优点** :
+**优点**:
- 使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
-**缺点** :
+**缺点**:
- 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
- 索引需要使用物理文件存储,也会耗费一定空间。
@@ -29,43 +35,81 @@ tag:
大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。
-## 索引的底层数据结构
+## 索引底层数据结构选型
-### Hash表 & B+树
+### Hash 表
哈希表是键值对的集合,通过键(key)即可快速取出对应的值(value),因此哈希表可以快速检索数据(接近 O(1))。
-**为何能够通过 key 快速取出 value呢?** 原因在于 **哈希算法**(也叫散列算法)。通过哈希算法,我们可以快速找到 value 对应的 index,找到了 index 也就找到了对应的 value。
+**为何能够通过 key 快速取出 value 呢?** 原因在于 **哈希算法**(也叫散列算法)。通过哈希算法,我们可以快速找到 key 对应的 index,找到了 index 也就找到了对应的 value。
```java
hash = hashfunc(key)
index = hash % array_size
```
+
+但是!哈希算法有个 **Hash 冲突** 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 **链地址法**。链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 `HashMap` 就是通过链地址法来解决哈希冲突的。不过,JDK1.8 以后`HashMap`为了减少链表过长的时候搜索时间过长引入了红黑树。
-
-
-但是!哈希算法有个 **Hash 冲突** 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 **链地址法**。链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 `HashMap` 就是通过链地址法来解决哈希冲突的。不过,JDK1.8 以后`HashMap`为了减少链表过长的时候搜索时间过长引入了红黑树。
-
-
+
为了减少 Hash 冲突的发生,一个好的哈希函数应该“均匀地”将数据分布在整个可能的哈希值集合中。
-既然哈希表这么快,**为什么MySQL 没有使用其作为索引的数据结构呢?**
-
-**1.Hash 冲突问题** :我们上面也提到过Hash 冲突了,不过对于数据库来说这还不算最大的缺点。
-
-**2.Hash 索引不支持顺序和范围查询(Hash 索引不支持顺序和范围查询是它最大的缺点:** 假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。
+既然哈希表这么快,**为什么 MySQL 没有使用其作为索引的数据结构呢?** 主要是因为 Hash 索引不支持顺序和范围查询。假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。并且,每次 IO 只能取一个。
试想一种情况:
```java
-SELECT * FROM tb1 WHERE id < 500;Copy to clipboardErrorCopied
+SELECT * FROM tb1 WHERE id < 500;
```
在这种范围查询中,优势非常大,直接遍历比 500 小的叶子节点就够了。而 Hash 索引是根据 hash 算法来定位的,难不成还要把 1 - 499 的数据,每个都进行一次 hash 计算来定位吗?这就是 Hash 最大的缺点了。
+### 二叉查找树(BST)
+
+二叉查找树(Binary Search Tree)是一种基于二叉树的数据结构,它具有以下特点:
+
+1. 左子树所有节点的值均小于根节点的值。
+2. 右子树所有节点的值均大于根节点的值。
+3. 左右子树也分别为二叉查找树。
+
+当二叉查找树是平衡的时候,也就是树的每个节点的左右子树深度相差不超过 1 的时候,查询的时间复杂度为 O(log2(N)),具有比较高的效率。然而,当二叉查找树不平衡时,例如在最坏情况下(有序插入节点),树会退化成线性链表(也被称为斜树),导致查询效率急剧下降,时间复杂退化为 O(N)。
+
+
+
+也就是说,**二叉查找树的性能非常依赖于它的平衡程度,这就导致其不适合作为 MySQL 底层索引的数据结构。**
+
+为了解决这个问题,并提高查询效率,人们发明了多种在二叉查找树基础上的改进型数据结构,如平衡二叉树、B-Tree、B+Tree 等。
+
+### AVL 树
+
+AVL 树是计算机科学中最早被发明的自平衡二叉查找树,它的名称来自于发明者 G.M. Adelson-Velsky 和 E.M. Landis 的名字缩写。AVL 树的特点是保证任何节点的左右子树高度之差不超过 1,因此也被称为高度平衡二叉树,它的查找、插入和删除在平均和最坏情况下的时间复杂度都是 O(logn)。
+
+
+
+AVL 树采用了旋转操作来保持平衡。主要有四种旋转操作:LL 旋转、RR 旋转、LR 旋转和 RL 旋转。其中 LL 旋转和 RR 旋转分别用于处理左左和右右失衡,而 LR 旋转和 RL 旋转则用于处理左右和右左失衡。
+
+由于 AVL 树需要频繁地进行旋转操作来保持平衡,因此会有较大的计算开销进而降低了查询性能。并且, 在使用 AVL 树时,每个树节点仅存储一个数据,而每次进行磁盘 IO 时只能读取一个节点的数据,如果需要查询的数据分布在多个节点上,那么就需要进行多次磁盘 IO。 **磁盘 IO 是一项耗时的操作,在设计数据库索引时,我们需要优先考虑如何最大限度地减少磁盘 IO 操作的次数。**
+
+实际应用中,AVL 树使用的并不多。
+
+### 红黑树
+
+红黑树是一种自平衡二叉查找树,通过在插入和删除节点时进行颜色变换和旋转操作,使得树始终保持平衡状态,它具有以下特点:
+
+1. 每个节点非红即黑;
+2. 根节点总是黑色的;
+3. 每个叶子节点都是黑色的空节点(NIL 节点);
+4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
+5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。
+
+
+
+和 AVL 树不同的是,红黑树并不追求严格的平衡,而是大致的平衡。正因如此,红黑树的查询效率稍有下降,因为红黑树的平衡性相对较弱,可能会导致树的高度较高,这可能会导致一些数据需要进行多次磁盘 IO 操作才能查询到,这也是 MySQL 没有选择红黑树的主要原因。也正因如此,红黑树的插入和删除操作效率大大提高了,因为红黑树在插入和删除节点时只需进行 O(1) 次数的旋转和变色操作,即可保持基本平衡状态,而不需要像 AVL 树一样进行 O(logn) 次数的旋转操作。
+
+**红黑树的应用还是比较广泛的,TreeMap、TreeSet 以及 JDK1.8 的 HashMap 底层都用到了红黑树。对于数据在内存中的这种情况来说,红黑树的表现是非常优异的。**
+
### B 树& B+树
B 树也称 B-树,全称为 **多路平衡查找树** ,B+ 树是 B 树的一种变体。B 树和 B+树中的 B 是 `Balanced` (平衡)的意思。
@@ -74,193 +118,328 @@ B 树也称 B-树,全称为 **多路平衡查找树** ,B+ 树是 B 树的一
**B 树& B+树两者有何异同呢?**
-- B 树的所有节点既存放键(key) 也存放 数据(data),而 B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。
+- B 树的所有节点既存放键(key) 也存放数据(data),而 B+树只有叶子节点存放 key 和 data,其他内节点只存放 key。
- B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
- B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
+- 在 B 树中进行范围查询时,首先找到要查找的下限,然后对 B 树进行中序遍历,直到找到查找的上限;而 B+树的范围查询,只需要对链表进行遍历即可。
-
+综上,B+树与 B 树相比,具备更少的 IO 次数、更稳定的查询效率和更适于范围查询这些优势。
在 MySQL 中,MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但是,两者的实现方式不太一样。(下面的内容整理自《Java 工程师修炼之道》)
-MyISAM 引擎中,B+Tree 叶节点的 data 域存放的是数据记录的地址。在索引检索的时候,首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
+> MyISAM 引擎中,B+Tree 叶节点的 data 域存放的是数据记录的地址。在索引检索的时候,首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“**非聚簇索引(非聚集索引)**”。
+>
+> InnoDB 引擎中,其数据文件本身就是索引文件。相比 MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按 B+Tree 组织的一个索引结构,树的叶节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。这被称为“**聚簇索引(聚集索引)**”,而其余的索引都作为 **辅助索引** ,辅助索引的 data 域存储相应记录主键的值而不是地址,这也是和 MyISAM 不同的地方。在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
+
+## 索引类型总结
+
+按照数据结构维度划分:
+
+- BTree 索引:MySQL 里默认和最常用的索引类型。只有叶子节点存储 value,非叶子节点只有指针和 key。存储引擎 MyISAM 和 InnoDB 实现 BTree 索引都是使用 B+Tree,但二者实现方式不一样(前面已经介绍了)。
+- 哈希索引:类似键值对的形式,一次即可定位。
+- RTree 索引:一般不会使用,仅支持 geometry 数据类型,优势在于范围查找,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
+- 全文索引:对文本的内容进行分词,进行搜索。目前只有 `CHAR`、`VARCHAR` ,`TEXT` 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
+
+按照底层存储方式角度划分:
-InnoDB 引擎中,其数据文件本身就是索引文件。相比 MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按 B+Tree 组织的一个索引结构,树的叶节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”,而其余的索引都作为辅助索引,辅助索引的 data 域存储相应记录主键的值而不是地址,这也是和 MyISAM 不同的地方。在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
+- 聚簇索引(聚集索引):索引结构和数据一起存放的索引,InnoDB 中的主键索引就属于聚簇索引。
+- 非聚簇索引(非聚集索引):索引结构和数据分开存放的索引,二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
-## 索引类型
+按照应用维度划分:
-### 主键索引(Primary Key)
+- 主键索引:加速查询 + 列值唯一(不可以有 NULL)+ 表中只有一个。
+- 普通索引:仅加速查询。
+- 唯一索引:加速查询 + 列值唯一(可以有 NULL)。
+- 覆盖索引:一个索引包含(或者说覆盖)所有需要查询的字段的值。
+- 联合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。
+- 全文索引:对文本的内容进行分词,进行搜索。目前只有 `CHAR`、`VARCHAR` ,`TEXT` 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
+
+MySQL 8.x 中实现的索引新特性:
+
+- 隐藏索引:也称为不可见索引,不会被优化器使用,但是仍然需要维护,通常会软删除和灰度发布的场景中使用。主键不能设置为隐藏(包括显式设置或隐式设置)。
+- 降序索引:之前的版本就支持通过 desc 来指定索引为降序,但实际上创建的仍然是常规的升序索引。直到 MySQL 8.x 版本才开始真正支持降序索引。另外,在 MySQL 8.x 版本中,不再对 GROUP BY 语句进行隐式排序。
+- 函数索引:从 MySQL 8.0.13 版本开始支持在索引中使用函数或者表达式的值,也就是在索引中可以包含函数或者表达式。
+
+## 主键索引(Primary Key)
数据表的主键列使用的就是主键索引。
一张数据表有只能有一个主键,并且主键不能为 null,不能重复。
-在 MySQL 的 InnoDB 的表中,当没有显示的指定表的主键时,InnoDB 会自动先检查表中是否有唯一索引的字段,如果有,则选择该字段为默认的主键,否则 InnoDB 将会自动创建一个 6Byte 的自增主键。
+在 MySQL 的 InnoDB 的表中,当没有显示的指定表的主键时,InnoDB 会自动先检查表中是否有唯一索引且不允许存在 null 值的字段,如果有,则选择该字段为默认的主键,否则 InnoDB 将会自动创建一个 6Byte 的自增主键。
-### 二级索引(辅助索引)
+
-**二级索引又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。**
+## 二级索引
+
+**二级索引(Secondary Index)又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。**
唯一索引,普通索引,前缀索引等索引属于二级索引。
-**PS:不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,也可以自行搜索。**
+PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,也可以自行搜索。
-1. **唯一索引(Unique Key)** :唯一索引也是一种约束。**唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。** 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
-2. **普通索引(Index)** :**普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。**
-3. **前缀索引(Prefix)** :前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,
+1. **唯一索引(Unique Key)**:唯一索引也是一种约束。**唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL,一张表允许创建多个唯一索引。** 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
+2. **普通索引(Index)**:**普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。**
+3. **前缀索引(Prefix)**:前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,
因为只取前几个字符。
-4. **全文索引(Full Text)** :全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。
+4. **全文索引(Full Text)**:全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。
二级索引:
-
-## 聚集索引与非聚集索引
+
-### 聚集索引
+## 聚簇索引与非聚簇索引
-**聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。**
+### 聚簇索引(聚集索引)
-在 Mysql 中,InnoDB 引擎的表的 `.ibd`文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
+#### 聚簇索引介绍
-#### 聚集索引的优点
+**聚簇索引(Clustered Index)即索引结构和数据一起存放的索引,并不是一种单独的索引类型。InnoDB 中的主键索引就属于聚簇索引。**
-聚集索引的查询速度非常的快,因为整个 B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。
+在 MySQL 中,InnoDB 引擎的表的 `.ibd`文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
-#### 聚集索引的缺点
+#### 聚簇索引的优缺点
-1. **依赖于有序的数据** :因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
-2. **更新代价大** : 如果对索引列的数据被修改时,那么对应的索引也将会被修改,
- 而且况聚集索引的叶子节点还存放着数据,修改代价肯定是较大的,
- 所以对于主键索引来说,主键一般都是不可被修改的。
+**优点**:
-### 非聚集索引
+- **查询速度非常快**:聚簇索引的查询速度非常的快,因为整个 B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。相比于非聚簇索引, 聚簇索引少了一次读取数据的 IO 操作。
+- **对排序查找和范围查找优化**:聚簇索引对于主键的排序查找和范围查找速度非常快。
-**非聚集索引即索引结构和数据分开存放的索引。**
+**缺点**:
-**二级索引属于非聚集索引。**
+- **依赖于有序的数据**:因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
+- **更新代价大**:如果对索引列的数据被修改时,那么对应的索引也将会被修改,而且聚簇索引的叶子节点还存放着数据,修改代价肯定是较大的,所以对于主键索引来说,主键一般都是不可被修改的。
-> MYISAM 引擎的表的.MYI 文件包含了表的索引,
-> 该表的索引(B+树)的每个叶子非叶子节点存储索引,
-> 叶子节点存储索引和索引对应数据的指针,指向.MYD 文件的数据。
->
-> **非聚集索引的叶子节点并不一定存放数据的指针,
-> 因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。**
+### 非聚簇索引(非聚集索引)
+
+#### 非聚簇索引介绍
-#### 非聚集索引的优点
+**非聚簇索引(Non-Clustered Index)即索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。**
-**更新代价比聚集索引要小** 。非聚集索引的更新代价就没有聚集索引那么大了,非聚集索引的叶子节点是不存放数据的
+非聚簇索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。
-#### 非聚集索引的缺点
+#### 非聚簇索引的优缺点
-1. 跟聚集索引一样,非聚集索引也依赖于有序的数据
-2. **可能会二次查询(回表)** :这应该是非聚集索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。
+**优点**:
+
+更新代价比聚簇索引要小 。非聚簇索引的更新代价就没有聚簇索引那么大了,非聚簇索引的叶子节点是不存放数据的
+
+**缺点**:
+
+- **依赖于有序的数据**:跟聚簇索引一样,非聚簇索引也依赖于有序的数据
+- **可能会二次查询(回表)**:这应该是非聚簇索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。
这是 MySQL 的表的文件截图:
-
+
-聚集索引和非聚集索引:
+聚簇索引和非聚簇索引:
-
+
-### 非聚集索引一定回表查询吗(覆盖索引)?
+#### 非聚簇索引一定回表查询吗(覆盖索引)?
-**非聚集索引不一定回表查询。**
+**非聚簇索引不一定回表查询。**
-> 试想一种情况,用户准备使用 SQL 查询用户名,而用户名字段正好建立了索引。
+试想一种情况,用户准备使用 SQL 查询用户名,而用户名字段正好建立了索引。
-```text
+```sql
SELECT name FROM table WHERE name='guang19';
```
-> 那么这个索引的 key 本身就是 name,查到对应的 name 直接返回就行了,无需回表查询。
+那么这个索引的 key 本身就是 name,查到对应的 name 直接返回就行了,无需回表查询。
-**即使是 MYISAM 也是这样,虽然 MYISAM 的主键索引确实需要回表,
-因为它的主键索引的叶子节点存放的是指针。但是如果 SQL 查的就是主键呢?**
+即使是 MYISAM 也是这样,虽然 MYISAM 的主键索引确实需要回表,因为它的主键索引的叶子节点存放的是指针。但是!**如果 SQL 查的就是主键呢?**
-```text
+```sql
SELECT id FROM table WHERE id=1;
```
主键索引本身的 key 就是主键,查到返回就行了。这种情况就称之为覆盖索引了。
-## 覆盖索引
+## 覆盖索引和联合索引
-如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。我们知道在 InnoDB 存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次。这样就会比较慢覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!
+### 覆盖索引
-**覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了,
-而无需回表查询。**
+如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为 **覆盖索引(Covering Index)** 。我们知道在 InnoDB 存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就会比较慢。而覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!
-> 如主键索引,如果一条 SQL 需要查询主键,那么正好根据主键索引就可以查到主键。
->
-> 再如普通索引,如果一条 SQL 需要查询 name,name 字段正好有索引,
+**覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了,而无需回表查询。**
+
+> 如主键索引,如果一条 SQL 需要查询主键,那么正好根据主键索引就可以查到主键。再如普通索引,如果一条 SQL 需要查询 name,name 字段正好有索引,
> 那么直接根据这个索引就可以查到数据,也无需回表。
-覆盖索引:
-
+
-## 创建索引的注意事项
+我们这里简单演示一下覆盖索引的效果。
-**1.选择合适的字段创建索引:**
+1、创建一个名为 `cus_order` 的表,来实际测试一下这种排序方式。为了测试方便, `cus_order` 这张表只有 `id`、`score`、`name`这 3 个字段。
-- **不为 NULL 的字段** :索引字段的数据应该尽量不为 NULL,因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL,建议使用 0,1,true,false 这样语义较为清晰的短值或短字符作为替代。
-- **被频繁查询的字段** :我们创建索引的字段应该是查询操作非常频繁的字段。
-- **被作为条件查询的字段** :被作为 WHERE 条件查询的字段,应该被考虑建立索引。
-- **频繁需要排序的字段** :索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
-- **被经常频繁用于连接的字段** :经常用于连接的字段可能是一些外键列,对于外键列并不一定要建立外键,只是说该列涉及到表与表的关系。对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。
+```sql
+CREATE TABLE `cus_order` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `score` int(11) NOT NULL,
+ `name` varchar(11) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=100000 DEFAULT CHARSET=utf8mb4;
+```
-**2.被频繁更新的字段应该慎重建立索引。**
+2、定义一个简单的存储过程(PROCEDURE)来插入 100w 测试数据。
-虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。
-如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。
+```sql
+DELIMITER ;;
+CREATE DEFINER=`root`@`%` PROCEDURE `BatchinsertDataToCusOder`(IN start_num INT,IN max_num INT)
+BEGIN
+ DECLARE i INT default start_num;
+ WHILE i < max_num DO
+ insert into `cus_order`(`id`, `score`, `name`)
+ values (i,RAND() * 1000000,CONCAT('user', i));
+ SET i = i + 1;
+ END WHILE;
+ END;;
+DELIMITER ;
+```
-**3.尽可能的考虑建立联合索引而不是单列索引。**
+存储过程定义完成之后,我们执行存储过程即可!
-因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。
+```sql
+CALL BatchinsertDataToCusOder(1, 1000000); # 插入100w+的随机数据
+```
-**4.注意避免冗余索引** 。
+等待一会,100w 的测试数据就插入完成了!
-冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city )和(name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
+3、创建覆盖索引并使用 `EXPLAIN` 命令分析。
-**5.考虑在字符串类型的字段上使用前缀索引代替普通索引。**
+为了能够对这 100w 数据按照 `score` 进行排序,我们需要执行下面的 SQL 语句。
-前缀索引仅限于字符串类型,较普通索引会占用更小的空间,所以可以考虑使用前缀索引带替普通索引。
+```sql
+SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC;#降序排序
+```
-## 使用索引的一些建议
+使用 `EXPLAIN` 命令分析这条 SQL 语句,通过 `Extra` 这一列的 `Using filesort` ,我们发现是没有用到覆盖索引的。
-- 对于中到大型表索引都是非常有效的,但是特大型表的话维护开销会很大,不适合建索引
-- 避免 where 子句中对字段施加函数,这会造成无法命中索引。
-- 在使用 InnoDB 时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。
-- 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用
-- 在使用 limit offset 查询缓慢时,可以借助索引来提高性能
+
-## MySQL 如何为表字段添加索引?
+不过这也是理所应当,毕竟我们现在还没有创建索引呢!
-1.添加 PRIMARY KEY(主键索引)
+我们这里以 `score` 和 `name` 两个字段建立联合索引:
```sql
-ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
+ALTER TABLE `cus_order` ADD INDEX id_score_name(score, name);
```
-2.添加 UNIQUE(唯一索引)
+创建完成之后,再用 `EXPLAIN` 命令分析再次分析这条 SQL 语句。
-```sqlite
-ALTER TABLE `table_name` ADD UNIQUE ( `column` )
-```
+
-3.添加 INDEX(普通索引)
+通过 `Extra` 这一列的 `Using index` ,说明这条 SQL 语句成功使用了覆盖索引。
-```sql
-ALTER TABLE `table_name` ADD INDEX index_name ( `column` )
-```
+关于 `EXPLAIN` 命令的详细介绍请看:[MySQL 执行计划分析](./mysql-query-execution-plan.md)这篇文章。
+
+### 联合索引
+
+使用表中的多个字段创建索引,就是 **联合索引**,也叫 **组合索引** 或 **复合索引**。
-4.添加 FULLTEXT(全文索引)
+以 `score` 和 `name` 两个字段建立联合索引:
```sql
-ALTER TABLE `table_name` ADD FULLTEXT ( `column`)
+ALTER TABLE `cus_order` ADD INDEX id_score_name(score, name);
```
-5.添加多列索引
+### 最左前缀匹配原则
+
+最左前缀匹配原则指的是,在使用联合索引时,**MySQL** 会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配,如果查询条件中存在与联合索引中最左侧字段相匹配的字段,则就会使用该字段过滤一批数据,直至联合索引中全部字段匹配完成,或者在执行过程中遇到范围查询(如 **`>`**、**`<`**)才会停止匹配。对于 **`>=`**、**`<=`**、**`BETWEEN`**、**`like`** 前缀匹配的范围查询,并不会停止匹配。所以,我们在使用联合索引时,可以将区分度高的字段放在最左边,这也可以过滤更多数据。
+
+相关阅读:[联合索引的最左匹配原则全网都在说的一个错误结论](https://mp.weixin.qq.com/s/8qemhRg5MgXs1So5YCv0fQ)。
+
+## 索引下推
+
+**索引下推(Index Condition Pushdown)** 是 **MySQL 5.6** 版本中提供的一项索引优化功能,可以在非聚簇索引遍历过程中,对索引中包含的字段先做判断,过滤掉不符合条件的记录,减少回表次数。
+
+## 正确使用索引的一些建议
+
+### 选择合适的字段创建索引
+
+- **不为 NULL 的字段**:索引字段的数据应该尽量不为 NULL,因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL,建议使用 0,1,true,false 这样语义较为清晰的短值或短字符作为替代。
+- **被频繁查询的字段**:我们创建索引的字段应该是查询操作非常频繁的字段。
+- **被作为条件查询的字段**:被作为 WHERE 条件查询的字段,应该被考虑建立索引。
+- **频繁需要排序的字段**:索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
+- **被经常频繁用于连接的字段**:经常用于连接的字段可能是一些外键列,对于外键列并不一定要建立外键,只是说该列涉及到表与表的关系。对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。
+
+### 被频繁更新的字段应该慎重建立索引
+
+虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。 如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。
+
+### 限制每张表上的索引数量
+
+索引并不是越多越好,建议单张表索引不超过 5 个!索引可以提高效率同样可以降低效率。
+
+索引可以增加查询效率,但同样也会降低插入和更新的效率,甚至有些情况下会降低查询效率。
+
+因为 MySQL 优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,就会增加 MySQL 优化器生成执行计划的时间,同样会降低查询性能。
+
+### 尽可能的考虑建立联合索引而不是单列索引
+
+因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。
+
+### 注意避免冗余索引
+
+冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city )和(name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
+
+### 字符串类型的字段使用前缀索引代替普通索引
+
+前缀索引仅限于字符串类型,较普通索引会占用更小的空间,所以可以考虑使用前缀索引带替普通索引。
+
+### 避免索引失效
+
+索引失效也是慢查询的主要原因之一,常见的导致索引失效的情况有下面这些:
+
+- ~~使用 `SELECT *` 进行查询;~~ `SELECT *` 不会直接导致索引失效(如果不走索引大概率是因为 where 查询范围过大导致的),但它可能会带来一些其他的性能问题比如造成网络传输和数据处理的浪费、无法使用索引覆盖;
+- 创建了组合索引,但查询条件未遵守最左匹配原则;
+- 在索引列上进行计算、函数、类型转换等操作;
+- 以 `%` 开头的 LIKE 查询比如 `like '%abc'`;
+- 查询条件中使用 or,且 or 的前后条件中有一个列没有索引,涉及的索引都不会被使用到;
+- 发生[隐式转换](./index-invalidation-caused-by-implicit-conversion.md);
+- ......
+
+### 删除长期未使用的索引
+
+删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗。
+
+MySQL 5.7 可以通过查询 `sys` 库的 `schema_unused_indexes` 视图来查询哪些索引从未被使用。
+
+### 知道如何分析语句是否走索引查询
+
+我们可以使用 `EXPLAIN` 命令来分析 SQL 的 **执行计划** ,这样就知道语句是否命中索引了。执行计划是指一条 SQL 语句在经过 MySQL 查询优化器的优化会后,具体的执行方式。
+
+`EXPLAIN` 并不会真的去执行相关的语句,而是通过 **查询优化器** 对语句进行分析,找出最优的查询方案,并显示对应的信息。
+
+`EXPLAIN` 的输出格式如下:
```sql
-ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
-```
\ No newline at end of file
+mysql> EXPLAIN SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC;
++----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
++----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
+| 1 | SIMPLE | cus_order | NULL | ALL | NULL | NULL | NULL | NULL | 997572 | 100.00 | Using filesort |
++----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
+1 row in set, 1 warning (0.00 sec)
+```
+
+各个字段的含义如下:
+
+| **列名** | **含义** |
+| ------------- | -------------------------------------------- |
+| id | SELECT 查询的序列标识符 |
+| select_type | SELECT 关键字对应的查询类型 |
+| table | 用到的表名 |
+| partitions | 匹配的分区,对于未分区的表,值为 NULL |
+| type | 表的访问方法 |
+| possible_keys | 可能用到的索引 |
+| key | 实际用到的索引 |
+| key_len | 所选索引的长度 |
+| ref | 当使用索引等值查询时,与索引作比较的列或常量 |
+| rows | 预计要读取的行数 |
+| filtered | 按表条件过滤后,留存的记录数的百分比 |
+| Extra | 附加信息 |
+
+篇幅问题,我这里只是简单介绍了一下 MySQL 执行计划,详细介绍请看:[MySQL 执行计划分析](./mysql-query-execution-plan.md)这篇文章。
diff --git a/docs/database/mysql/mysql-logs.md b/docs/database/mysql/mysql-logs.md
index 697cf30f6a1cd9989623672d3b1d8bc51d65ee0e..f388828590597cd5e5cf5b3fb7ba2cb0796d6724 100644
--- a/docs/database/mysql/mysql-logs.md
+++ b/docs/database/mysql/mysql-logs.md
@@ -5,15 +5,13 @@ tag:
- MySQL
---
-
-
> 本文来自公号程序猿阿星投稿,JavaGuide 对其做了补充完善。
## 前言
`MySQL` 日志 主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。其中,比较重要的还要属二进制日志 `binlog`(归档日志)和事务日志 `redo log`(重做日志)和 `undo log`(回滚日志)。
-
+
今天就来聊聊 `redo log`(重做日志)、`binlog`(归档日志)、两阶段提交、`undo log` (回滚日志)。
@@ -23,7 +21,7 @@ tag:
比如 `MySQL` 实例挂了或宕机了,重启时,`InnoDB`存储引擎会使用`redo log`恢复数据,保证数据的持久性与完整性。
-
+
`MySQL` 中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到 `Buffer Pool` 中。
@@ -33,7 +31,9 @@ tag:
然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(`redo log buffer`)里,接着刷盘到 `redo log` 文件里。
-
+
+
+> 图片笔误提示:第 4 步 “清空 redo log buffe 刷盘到 redo 日志中”这句话中的 buffe 应该是 buffer。
理想情况,事务一提交就会进行刷盘操作,但实际上,刷盘的时机是根据策略来进行的。
@@ -43,15 +43,15 @@ tag:
`InnoDB` 存储引擎为 `redo log` 的刷盘策略提供了 `innodb_flush_log_at_trx_commit` 参数,它支持三种策略:
-- **0** :设置为 0 的时候,表示每次事务提交时不进行刷盘操作
-- **1** :设置为 1 的时候,表示每次事务提交时都将进行刷盘操作(默认值)
-- **2** :设置为 2 的时候,表示每次事务提交时都只把 redo log buffer 内容写入 page cache
+- **0**:设置为 0 的时候,表示每次事务提交时不进行刷盘操作
+- **1**:设置为 1 的时候,表示每次事务提交时都将进行刷盘操作(默认值)
+- **2**:设置为 2 的时候,表示每次事务提交时都只把 redo log buffer 内容写入 page cache
`innodb_flush_log_at_trx_commit` 参数默认为 1 ,也就是说当事务提交时会调用 `fsync` 对 redo log 进行刷盘
另外,`InnoDB` 存储引擎有一个后台线程,每隔`1` 秒,就会把 `redo log buffer` 中的内容写到文件系统缓存(`page cache`),然后调用 `fsync` 刷盘。
-
+
也就是说,一个没有提交事务的 `redo log` 记录,也可能会刷盘。
@@ -59,7 +59,7 @@ tag:
因为在事务执行过程 `redo log` 记录是会写入`redo log buffer` 中,这些 `redo log` 记录会被后台线程刷盘。
-
+
除了后台线程每秒`1`次的轮询操作,还有一种情况,当 `redo log buffer` 占用的空间即将达到 `innodb_log_buffer_size` 一半的时候,后台线程会主动刷盘。
@@ -67,13 +67,13 @@ tag:
#### innodb_flush_log_at_trx_commit=0
-
+
为`0`时,如果`MySQL`挂了或宕机可能会有`1`秒数据的丢失。
#### innodb_flush_log_at_trx_commit=1
-
+
为`1`时, 只要事务提交成功,`redo log`记录就一定在硬盘里,不会有任何数据丢失。
@@ -81,7 +81,7 @@ tag:
#### innodb_flush_log_at_trx_commit=2
-
+
为`2`时, 只要事务提交成功,`redo log buffer`中的内容只写入文件系统缓存(`page cache`)。
@@ -95,7 +95,7 @@ tag:
它采用的是环形数组形式,从头开始写,写到末尾又回到头循环写,如下图所示。
-
+
在个**日志文件组**中还有两个重要的属性,分别是 `write pos、checkpoint`
@@ -108,17 +108,17 @@ tag:
`write pos` 和 `checkpoint` 之间的还空着的部分可以用来写入新的 `redo log` 记录。
-
+
如果 `write pos` 追上 `checkpoint` ,表示**日志文件组**满了,这时候不能再写入新的 `redo log` 记录,`MySQL` 得停下来,清空一些记录,把 `checkpoint` 推进一下。
-
+
### redo log 小结
相信大家都知道 `redo log` 的作用和它的刷盘时机、存储形式。
-现在我们来思考一个问题: **只要每次把修改后的数据页直接刷盘不就好了,还有 `redo log` 什么事?**
+现在我们来思考一个问题:**只要每次把修改后的数据页直接刷盘不就好了,还有 `redo log` 什么事?**
它们不都是刷盘么?差别在哪里?
@@ -153,7 +153,7 @@ tag:
可以说`MySQL`数据库的**数据备份、主备、主主、主从**都离不开`binlog`,需要依靠`binlog`来同步数据,保证数据一致性。
-
+
`binlog`会记录所有涉及更新数据的逻辑操作,并且是顺序写。
@@ -167,13 +167,13 @@ tag:
指定`statement`,记录的内容是`SQL`语句原文,比如执行一条`update T set update_time=now() where id=1`,记录的内容如下。
-
+
同步数据时,会执行记录的`SQL`语句,但是有个问题,`update_time=now()`这里会获取当前系统时间,直接执行会导致与原库的数据不一致。
为了解决这种问题,我们需要指定为`row`,记录的内容不再是简单的`SQL`语句了,还包含操作的具体数据,记录内容如下。
-
+
`row`格式记录的内容看不到详细信息,要通过`mysqlbinlog`工具解析出来。
@@ -197,7 +197,7 @@ tag:
`binlog`日志刷盘流程如下
-
+
- **上图的 write,是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快**
- **上图的 fsync,才是将数据持久化到磁盘的操作**
@@ -206,15 +206,15 @@ tag:
为`0`的时候,表示每次提交事务都只`write`,由系统自行判断什么时候执行`fsync`。
-
+
-虽然性能得到提升,但是机器宕机,`page cache`里面的 binglog 会丢失。
+虽然性能得到提升,但是机器宕机,`page cache`里面的 binlog 会丢失。
-为了安全起见,可以设置为`1`,表示每次提交事务都会执行`fsync`,就如同**binlog 日志刷盘流程**一样。
+为了安全起见,可以设置为`1`,表示每次提交事务都会执行`fsync`,就如同 **redo log 日志刷盘流程** 一样。
最后还有一种折中方式,可以设置为`N(N>1)`,表示每次提交事务都`write`,但累积`N`个事务后才`fsync`。
-
+
在出现`IO`瓶颈的场景里,将`sync_binlog`设置成一个比较大的值,可以提升性能。
@@ -230,7 +230,7 @@ tag:
在执行更新语句过程,会记录`redo log`与`binlog`两块日志,以基本的事务为单位,`redo log`在事务执行过程中可以不断写入,而`binlog`只有在提交事务时才写入,所以`redo log`与`binlog`的写入时机不一样。
-
+
回到正题,`redo log`与`binlog`两份日志之间的逻辑不一致,会出现什么问题?
@@ -238,25 +238,25 @@ tag:
假设执行过程中写完`redo log`日志后,`binlog`日志写期间发生了异常,会出现什么情况呢?
-
+
由于`binlog`没写完就异常,这时候`binlog`里面没有对应的修改记录。因此,之后用`binlog`日志恢复数据时,就会少这一次更新,恢复出来的这一行`c`值是`0`,而原库因为`redo log`日志恢复,这一行`c`值是`1`,最终数据不一致。
-
+
为了解决两份日志之间的逻辑一致问题,`InnoDB`存储引擎使用**两阶段提交**方案。
原理很简单,将`redo log`的写入拆成了两个步骤`prepare`和`commit`,这就是**两阶段提交**。
-
+
使用**两阶段提交**后,写入`binlog`时发生异常也不会有影响,因为`MySQL`根据`redo log`日志恢复数据时,发现`redo log`还处于`prepare`阶段,并且没有对应`binlog`日志,就会回滚该事务。
-
+
再看一个场景,`redo log`设置`commit`阶段发生异常,那会不会回滚事务呢?
-
+
并不会回滚事务,它会执行上图框住的逻辑,虽然`redo log`是处于`prepare`阶段,但是能通过事务`id`找到对应的`binlog`日志,所以`MySQL`认为是完整的,就会提交事务恢复数据。
diff --git a/docs/database/mysql/mysql-query-cache.md b/docs/database/mysql/mysql-query-cache.md
new file mode 100644
index 0000000000000000000000000000000000000000..760a197ab4a608c7744da7a2b52b4c066cc18e52
--- /dev/null
+++ b/docs/database/mysql/mysql-query-cache.md
@@ -0,0 +1,206 @@
+---
+title: MySQL查询缓存详解
+category: 数据库
+tag:
+ - MySQL
+head:
+ - - meta
+ - name: keywords
+ content: MySQL查询缓存,MySQL缓存机制中的内存管理
+ - - meta
+ - name: description
+ content: 为了提高完全相同的查询语句的响应速度,MySQL Server 会对查询语句进行 Hash 计算得到一个 Hash 值。MySQL Server 不会对 SQL 做任何处理,SQL 必须完全一致 Hash 值才会一样。得到 Hash 值之后,通过该 Hash 值到查询缓存中匹配该查询的结果。MySQL 中的查询缓存虽然能够提升数据库的查询性能,但是查询同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。
+---
+
+缓存是一个有效且实用的系统性能优化的手段,不论是操作系统还是各种软件和网站或多或少都用到了缓存。
+
+然而,有经验的 DBA 都建议生产环境中把 MySQL 自带的 Query Cache(查询缓存)给关掉。而且,从 MySQL 5.7.20 开始,就已经默认弃用查询缓存了。在 MySQL 8.0 及之后,更是直接删除了查询缓存的功能。
+
+这又是为什么呢?查询缓存真就这么鸡肋么?
+
+带着如下几个问题,我们正式进入本文。
+
+- MySQL 查询缓存是什么?适用范围?
+- MySQL 缓存规则是什么?
+- MySQL 缓存的优缺点是什么?
+- MySQL 缓存对性能有什么影响?
+
+## MySQL 查询缓存介绍
+
+MySQL 体系架构如下图所示:
+
+
+
+为了提高完全相同的查询语句的响应速度,MySQL Server 会对查询语句进行 Hash 计算得到一个 Hash 值。MySQL Server 不会对 SQL 做任何处理,SQL 必须完全一致 Hash 值才会一样。得到 Hash 值之后,通过该 Hash 值到查询缓存中匹配该查询的结果。
+
+- 如果匹配(命中),则将查询的结果集直接返回给客户端,不必再解析、执行查询。
+- 如果没有匹配(未命中),则将 Hash 值和结果集保存在查询缓存中,以便以后使用。
+
+也就是说,**一个查询语句(select)到了 MySQL Server 之后,会先到查询缓存看看,如果曾经执行过的话,就直接返回结果集给客户端。**
+
+
+
+## MySQL 查询缓存管理和配置
+
+通过 `show variables like '%query_cache%'`命令可以查看查询缓存相关的信息。
+
+8.0 版本之前的话,打印的信息可能是下面这样的:
+
+```bash
+mysql> show variables like '%query_cache%';
++------------------------------+---------+
+| Variable_name | Value |
++------------------------------+---------+
+| have_query_cache | YES |
+| query_cache_limit | 1048576 |
+| query_cache_min_res_unit | 4096 |
+| query_cache_size | 599040 |
+| query_cache_type | ON |
+| query_cache_wlock_invalidate | OFF |
++------------------------------+---------+
+6 rows in set (0.02 sec)
+```
+
+8.0 以及之后版本之后,打印的信息是下面这样的:
+
+```bash
+mysql> show variables like '%query_cache%';
++------------------+-------+
+| Variable_name | Value |
++------------------+-------+
+| have_query_cache | NO |
++------------------+-------+
+1 row in set (0.01 sec)
+```
+
+我们这里对 8.0 版本之前`show variables like '%query_cache%';`命令打印出来的信息进行解释。
+
+- **`have_query_cache`:** 该 MySQL Server 是否支持查询缓存,如果是 YES 表示支持,否则则是不支持。
+- **`query_cache_limit`:** MySQL 查询缓存的最大查询结果,查询结果大于该值时不会被缓存。
+- **`query_cache_min_res_unit`:** 查询缓存分配的最小块的大小(字节)。当查询进行的时候,MySQL 把查询结果保存在查询缓存中,但如果要保存的结果比较大,超过 `query_cache_min_res_unit` 的值 ,这时候 MySQL 将一边检索结果,一边进行保存结果,也就是说,有可能在一次查询中,MySQL 要进行多次内存分配的操作。适当的调节 `query_cache_min_res_unit` 可以优化内存。
+- **`query_cache_size`:** 为缓存查询结果分配的内存的数量,单位是字节,且数值必须是 1024 的整数倍。默认值是 0,即禁用查询缓存。
+- **`query_cache_type`:** 设置查询缓存类型,默认为 ON。设置 GLOBAL 值可以设置后面的所有客户端连接的类型。客户端可以设置 SESSION 值以影响他们自己对查询缓存的使用。
+- **`query_cache_wlock_invalidate`**:如果某个表被锁住,是否返回缓存中的数据,默认关闭,也是建议的。
+
+`query_cache_type` 可能的值(修改 `query_cache_type` 需要重启 MySQL Server):
+
+- 0 或 OFF:关闭查询功能。
+- 1 或 ON:开启查询缓存功能,但不缓存 `Select SQL_NO_CACHE` 开头的查询。
+- 2 或 DEMAND:开启查询缓存功能,但仅缓存 `Select SQL_CACHE` 开头的查询。
+
+**建议**:
+
+- `query_cache_size`不建议设置的过大。过大的空间不但挤占实例其他内存结构的空间,而且会增加在缓存中搜索的开销。建议根据实例规格,初始值设置为 10MB 到 100MB 之间的值,而后根据运行使用情况调整。
+- 建议通过调整 `query_cache_size` 的值来开启、关闭查询缓存,因为修改`query_cache_type` 参数需要重启 MySQL Server 生效。
+
+ 8.0 版本之前,`my.cnf` 加入以下配置,重启 MySQL 开启查询缓存
+
+```properties
+query_cache_type=1
+query_cache_size=600000
+```
+
+或者,MySQL 执行以下命令也可以开启查询缓存
+
+```properties
+set global query_cache_type=1;
+set global query_cache_size=600000;
+```
+
+手动清理缓存可以使用下面三个 SQL:
+
+- `flush query cache;`:清理查询缓存内存碎片。
+- `reset query cache;`:从查询缓存中移除所有查询。
+- `flush tables;` 关闭所有打开的表,同时该操作会清空查询缓存中的内容。
+
+## MySQL 缓存机制
+
+### 缓存规则
+
+- 查询缓存会将查询语句和结果集保存到内存(一般是 key-value 的形式,key 是查询语句,value 是查询的结果集),下次再查直接从内存中取。
+- 缓存的结果是通过 sessions 共享的,所以一个 client 查询的缓存结果,另一个 client 也可以使用。
+- SQL 必须完全一致才会导致查询缓存命中(大小写、空格、使用的数据库、协议版本、字符集等必须一致)。检查查询缓存时,MySQL Server 不会对 SQL 做任何处理,它精确的使用客户端传来的查询。
+- 不缓存查询中的子查询结果集,仅缓存查询最终结果集。
+- 不确定的函数将永远不会被缓存, 比如 `now()`、`curdate()`、`last_insert_id()`、`rand()` 等。
+- 不缓存产生告警(Warnings)的查询。
+- 太大的结果集不会被缓存 (< query_cache_limit)。
+- 如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果也不会被缓存。
+- 缓存建立之后,MySQL 的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。
+- MySQL 缓存在分库分表环境下是不起作用的。
+- 不缓存使用 `SQL_NO_CACHE` 的查询。
+- ......
+
+查询缓存 `SELECT` 选项示例:
+
+```sql
+SELECT SQL_CACHE id, name FROM customer;# 会缓存
+SELECT SQL_NO_CACHE id, name FROM customer;# 不会缓存
+```
+
+### 缓存机制中的内存管理
+
+查询缓存是完全存储在内存中的,所以在配置和使用它之前,我们需要先了解它是如何使用内存的。
+
+MySQL 查询缓存使用内存池技术,自己管理内存释放和分配,而不是通过操作系统。内存池使用的基本单位是变长的 block, 用来存储类型、大小、数据等信息。一个结果集的缓存通过链表把这些 block 串起来。block 最短长度为 `query_cache_min_res_unit`。
+
+当服务器启动的时候,会初始化缓存需要的内存,是一个完整的空闲块。当查询结果需要缓存的时候,先从空闲块中申请一个数据块为参数 `query_cache_min_res_unit` 配置的空间,即使缓存数据很小,申请数据块也是这个,因为查询开始返回结果的时候就分配空间,此时无法预知结果多大。
+
+分配内存块需要先锁住空间块,所以操作很慢,MySQL 会尽量避免这个操作,选择尽可能小的内存块,如果不够,继续申请,如果存储完时有空余则释放多余的。
+
+但是如果并发的操作,余下的需要回收的空间很小,小于 `query_cache_min_res_unit`,不能再次被使用,就会产生碎片。
+
+## MySQL 查询缓存的优缺点
+
+**优点:**
+
+- 查询缓存的查询,发生在 MySQL 接收到客户端的查询请求、查询权限验证之后和查询 SQL 解析之前。也就是说,当 MySQL 接收到客户端的查询 SQL 之后,仅仅只需要对其进行相应的权限验证之后,就会通过查询缓存来查找结果,甚至都不需要经过 Optimizer 模块进行执行计划的分析优化,更不需要发生任何存储引擎的交互。
+- 由于查询缓存是基于内存的,直接从内存中返回相应的查询结果,因此减少了大量的磁盘 I/O 和 CPU 计算,导致效率非常高。
+
+**缺点:**
+
+- MySQL 会对每条接收到的 SELECT 类型的查询进行 Hash 计算,然后查找这个查询的缓存结果是否存在。虽然 Hash 计算和查找的效率已经足够高了,一条查询语句所带来的开销可以忽略,但一旦涉及到高并发,有成千上万条查询语句时,hash 计算和查找所带来的开销就必须重视了。
+- 查询缓存的失效问题。如果表的变更比较频繁,则会造成查询缓存的失效率非常高。表的变更不仅仅指表中的数据发生变化,还包括表结构或者索引的任何变化。
+- 查询语句不同,但查询结果相同的查询都会被缓存,这样便会造成内存资源的过度消耗。查询语句的字符大小写、空格或者注释的不同,查询缓存都会认为是不同的查询(因为他们的 Hash 值会不同)。
+- 相关系统变量设置不合理会造成大量的内存碎片,这样便会导致查询缓存频繁清理内存。
+
+## MySQL 查询缓存对性能的影响
+
+在 MySQL Server 中打开查询缓存对数据库的读和写都会带来额外的消耗:
+
+- 读查询开始之前必须检查是否命中缓存。
+- 如果读查询可以缓存,那么执行完查询操作后,会查询结果和查询语句写入缓存。
+- 当向某个表写入数据的时候,必须将这个表所有的缓存设置为失效,如果缓存空间很大,则消耗也会很大,可能使系统僵死一段时间,因为这个操作是靠全局锁操作来保护的。
+- 对 InnoDB 表,当修改一个表时,设置了缓存失效,但是多版本特性会暂时将这修改对其他事务屏蔽,在这个事务提交之前,所有查询都无法使用缓存,直到这个事务被提交,所以长时间的事务,会大大降低查询缓存的命中。
+
+## 总结
+
+MySQL 中的查询缓存虽然能够提升数据库的查询性能,但是查询同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。
+
+查询缓存是一个适用较少情况的缓存机制。如果你的应用对数据库的更新很少,那么查询缓存将会作用显著。比较典型的如博客系统,一般博客更新相对较慢,数据表相对稳定不变,这时候查询缓存的作用会比较明显。
+
+简单总结一下查询缓存的适用场景:
+
+- 表数据修改不频繁、数据较静态。
+- 查询(Select)重复度高。
+- 查询结果集小于 1 MB。
+
+对于一个更新频繁的系统来说,查询缓存缓存的作用是很微小的,在某些情况下开启查询缓存会带来性能的下降。
+
+简单总结一下查询缓存不适用的场景:
+
+- 表中的数据、表结构或者索引变动频繁
+- 重复的查询很少
+- 查询的结果集很大
+
+《高性能 MySQL》这样写到:
+
+> 根据我们的经验,在高并发压力环境中查询缓存会导致系统性能的下降,甚至僵死。如果你一 定要使用查询缓存,那么不要设置太大内存,而且只有在明确收益的时候才使用(数据库内容修改次数较少)。
+
+**确实是这样的!实际项目中,更建议使用本地缓存(比如 Caffeine)或者分布式缓存(比如 Redis) ,性能更好,更通用一些。**
+
+## 参考
+
+- 《高性能 MySQL》
+- MySQL 缓存机制:
+- RDS MySQL 查询缓存(Query Cache)的设置和使用 - 阿里元云数据库 RDS 文档:
+- 8.10.3 The MySQL Query Cache - MySQL 官方文档:
diff --git a/docs/database/mysql/mysql-query-execution-plan.md b/docs/database/mysql/mysql-query-execution-plan.md
new file mode 100644
index 0000000000000000000000000000000000000000..696fcbb407b496419b1e66762144191f0180391e
--- /dev/null
+++ b/docs/database/mysql/mysql-query-execution-plan.md
@@ -0,0 +1,141 @@
+---
+title: MySQL执行计划分析
+category: 数据库
+tag:
+ - MySQL
+head:
+ - - meta
+ - name: keywords
+ content: MySQL基础,MySQL执行计划,EXPLAIN,查询优化器
+ - - meta
+ - name: description
+ content: 执行计划是指一条 SQL 语句在经过MySQL 查询优化器的优化会后,具体的执行方式。优化 SQL 的第一步应该是读懂 SQL 的执行计划。
+---
+
+> 本文来自公号 MySQL 技术,JavaGuide 对其做了补充完善。原文地址:https://mp.weixin.qq.com/s/d5OowNLtXBGEAbT31sSH4g
+
+优化 SQL 的第一步应该是读懂 SQL 的执行计划。本篇文章,我们一起来学习下 MySQL `EXPLAIN` 执行计划相关知识。
+
+## 什么是执行计划?
+
+**执行计划** 是指一条 SQL 语句在经过 **MySQL 查询优化器** 的优化会后,具体的执行方式。
+
+执行计划通常用于 SQL 性能分析、优化等场景。通过 `EXPLAIN` 的结果,可以了解到如数据表的查询顺序、数据查询操作的操作类型、哪些索引可以被命中、哪些索引实际会命中、每个数据表有多少行记录被查询等信息。
+
+## 如何获取执行计划?
+
+MySQL 为我们提供了 `EXPLAIN` 命令,来获取执行计划的相关信息。
+
+需要注意的是,`EXPLAIN` 语句并不会真的去执行相关的语句,而是通过查询优化器对语句进行分析,找出最优的查询方案,并显示对应的信息。
+
+`EXPLAIN` 执行计划支持 `SELECT`、`DELETE`、`INSERT`、`REPLACE` 以及 `UPDATE` 语句。我们一般多用于分析 `SELECT` 查询语句,使用起来非常简单,语法如下:
+
+```sql
+EXPLAIN + SELECT 查询语句;
+```
+
+我们简单来看下一条查询语句的执行计划:
+
+```sql
+mysql> explain SELECT * FROM dept_emp WHERE emp_no IN (SELECT emp_no FROM dept_emp GROUP BY emp_no HAVING COUNT(emp_no)>1);
++----+-------------+----------+------------+-------+-----------------+---------+---------+------+--------+----------+-------------+
+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
++----+-------------+----------+------------+-------+-----------------+---------+---------+------+--------+----------+-------------+
+| 1 | PRIMARY | dept_emp | NULL | ALL | NULL | NULL | NULL | NULL | 331143 | 100.00 | Using where |
+| 2 | SUBQUERY | dept_emp | NULL | index | PRIMARY,dept_no | PRIMARY | 16 | NULL | 331143 | 100.00 | Using index |
++----+-------------+----------+------------+-------+-----------------+---------+---------+------+--------+----------+-------------+
+```
+
+可以看到,执行计划结果中共有 12 列,各列代表的含义总结如下表:
+
+| **列名** | **含义** |
+| ------------- | -------------------------------------------- |
+| id | SELECT 查询的序列标识符 |
+| select_type | SELECT 关键字对应的查询类型 |
+| table | 用到的表名 |
+| partitions | 匹配的分区,对于未分区的表,值为 NULL |
+| type | 表的访问方法 |
+| possible_keys | 可能用到的索引 |
+| key | 实际用到的索引 |
+| key_len | 所选索引的长度 |
+| ref | 当使用索引等值查询时,与索引作比较的列或常量 |
+| rows | 预计要读取的行数 |
+| filtered | 按表条件过滤后,留存的记录数的百分比 |
+| Extra | 附加信息 |
+
+## 如何分析 EXPLAIN 结果?
+
+为了分析 `EXPLAIN` 语句的执行结果,我们需要搞懂执行计划中的重要字段。
+
+### id
+
+SELECT 标识符,是查询中 SELECT 的序号,用来标识整个查询中 SELELCT 语句的顺序。
+
+id 如果相同,从上往下依次执行。id 不同,id 值越大,执行优先级越高,如果行引用其他行的并集结果,则该值可以为 NULL。
+
+### select_type
+
+查询的类型,主要用于区分普通查询、联合查询、子查询等复杂的查询,常见的值有:
+
+- **SIMPLE**:简单查询,不包含 UNION 或者子查询。
+- **PRIMARY**:查询中如果包含子查询或其他部分,外层的 SELECT 将被标记为 PRIMARY。
+- **SUBQUERY**:子查询中的第一个 SELECT。
+- **UNION**:在 UNION 语句中,UNION 之后出现的 SELECT。
+- **DERIVED**:在 FROM 中出现的子查询将被标记为 DERIVED。
+- **UNION RESULT**:UNION 查询的结果。
+
+### table
+
+查询用到的表名,每行都有对应的表名,表名除了正常的表之外,也可能是以下列出的值:
+
+- **``** : 本行引用了 id 为 M 和 N 的行的 UNION 结果;
+- **``** : 本行引用了 id 为 N 的表所产生的的派生表结果。派生表有可能产生自 FROM 语句中的子查询。 -**``** : 本行引用了 id 为 N 的表所产生的的物化子查询结果。
+
+### type(重要)
+
+查询执行的类型,描述了查询是如何执行的。所有值的顺序从最优到最差排序为:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
+
+常见的几种类型具体含义如下:
+
+- **system**:如果表使用的引擎对于表行数统计是精确的(如:MyISAM),且表中只有一行记录的情况下,访问方法是 system ,是 const 的一种特例。
+- **const**:表中最多只有一行匹配的记录,一次查询就可以找到,常用于使用主键或唯一索引的所有字段作为查询条件。
+- **eq_ref**:当连表查询时,前一张表的行在当前这张表中只有一行与之对应。是除了 system 与 const 之外最好的 join 方式,常用于使用主键或唯一索引的所有字段作为连表条件。
+- **ref**:使用普通索引作为查询条件,查询结果可能找到多个符合条件的行。
+- **index_merge**:当查询条件使用了多个索引时,表示开启了 Index Merge 优化,此时执行计划中的 key 列列出了使用到的索引。
+- **range**:对索引列进行范围查询,执行计划中的 key 列表示哪个索引被使用了。
+- **index**:查询遍历了整棵索引树,与 ALL 类似,只不过扫描的是索引,而索引一般在内存中,速度更快。
+- **ALL**:全表扫描。
+
+### possible_keys
+
+possible_keys 列表示 MySQL 执行查询时可能用到的索引。如果这一列为 NULL ,则表示没有可能用到的索引;这种情况下,需要检查 WHERE 语句中所使用的的列,看是否可以通过给这些列中某个或多个添加索引的方法来提高查询性能。
+
+### key(重要)
+
+key 列表示 MySQL 实际使用到的索引。如果为 NULL,则表示未用到索引。
+
+### key_len
+
+key_len 列表示 MySQL 实际使用的索引的最大长度;当使用到联合索引时,有可能是多个列的长度和。在满足需求的前提下越短越好。如果 key 列显示 NULL ,则 key_len 列也显示 NULL 。
+
+### rows
+
+rows 列表示根据表统计信息及选用情况,大致估算出找到所需的记录或所需读取的行数,数值越小越好。
+
+### Extra(重要)
+
+这列包含了 MySQL 解析查询的额外信息,通过这些信息,可以更准确的理解 MySQL 到底是如何执行查询的。常见的值如下:
+
+- **Using filesort**:在排序时使用了外部的索引排序,没有用到表内索引进行排序。
+- **Using temporary**:MySQL 需要创建临时表来存储查询的结果,常见于 ORDER BY 和 GROUP BY。
+- **Using index**:表明查询使用了覆盖索引,不用回表,查询效率非常高。
+- **Using index condition**:表示查询优化器选择使用了索引条件下推这个特性。
+- **Using where**:表明查询使用了 WHERE 子句进行条件过滤。一般在没有使用到索引的时候会出现。
+- **Using join buffer (Block Nested Loop)**:连表查询的方式,表示当被驱动表的没有使用索引的时候,MySQL 会先将驱动表读出来放到 join buffer 中,再遍历被驱动表与驱动表进行查询。
+
+这里提醒下,当 Extra 列包含 Using filesort 或 Using temporary 时,MySQL 的性能可能会存在问题,需要尽可能避免。
+
+## 参考
+
+- https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
+- https://juejin.cn/post/6953444668973514789
diff --git a/docs/database/mysql/mysql-questions-01.md b/docs/database/mysql/mysql-questions-01.md
new file mode 100644
index 0000000000000000000000000000000000000000..de4a63da24e06efd7f84a1afc233048809f6cd2b
--- /dev/null
+++ b/docs/database/mysql/mysql-questions-01.md
@@ -0,0 +1,828 @@
+---
+title: MySQL常见面试题总结
+category: 数据库
+tag:
+ - MySQL
+ - 大厂面试
+head:
+ - - meta
+ - name: keywords
+ content: MySQL基础,MySQL基础架构,MySQL存储引擎,MySQL查询缓存,MySQL事务,MySQL锁等内容。
+ - - meta
+ - name: description
+ content: 一篇文章总结MySQL常见的知识点和面试题,涵盖MySQL基础、MySQL基础架构、MySQL存储引擎、MySQL查询缓存、MySQL事务、MySQL锁等内容。
+---
+
+
+
+## MySQL 基础
+
+### 什么是关系型数据库?
+
+顾名思义,关系型数据库(RDB,Relational Database)就是一种建立在关系模型的基础上的数据库。关系模型表明了数据库中所存储的数据之间的联系(一对一、一对多、多对多)。
+
+关系型数据库中,我们的数据都被存放在了各种表中(比如用户表),表中的每一行就存放着一条数据(比如一个用户的信息)。
+
+
+
+大部分关系型数据库都使用 SQL 来操作数据库中的数据。并且,大部分关系型数据库都支持事务的四大特性(ACID)。
+
+**有哪些常见的关系型数据库呢?**
+
+MySQL、PostgreSQL、Oracle、SQL Server、SQLite(微信本地的聊天记录的存储就是用的 SQLite) ......。
+
+### 什么是 SQL?
+
+SQL 是一种结构化查询语言(Structured Query Language),专门用来与数据库打交道,目的是提供一种从数据库中读写数据的简单有效的方法。
+
+几乎所有的主流关系数据库都支持 SQL ,适用性非常强。并且,一些非关系型数据库也兼容 SQL 或者使用的是类似于 SQL 的查询语言。
+
+SQL 可以帮助我们:
+
+- 新建数据库、数据表、字段;
+- 在数据库中增加,删除,修改,查询数据;
+- 新建视图、函数、存储过程;
+- 对数据库中的数据进行简单的数据分析;
+- 搭配 Hive,Spark SQL 做大数据;
+- 搭配 SQLFlow 做机器学习;
+- ......
+
+### 什么是 MySQL?
+
+
+
+**MySQL 是一种关系型数据库,主要用于持久化存储我们的系统中的一些数据比如用户信息。**
+
+由于 MySQL 是开源免费并且比较成熟的数据库,因此,MySQL 被大量使用在各种系统中。任何人都可以在 GPL(General Public License) 的许可下下载并根据个性化的需要对其进行修改。MySQL 的默认端口号是**3306**。
+
+### MySQL 有什么优点?
+
+这个问题本质上是在问 MySQL 如此流行的原因。
+
+MySQL 主要具有下面这些优点:
+
+1. 成熟稳定,功能完善。
+2. 开源免费。
+3. 文档丰富,既有详细的官方文档,又有非常多优质文章可供参考学习。
+4. 开箱即用,操作简单,维护成本低。
+5. 兼容性好,支持常见的操作系统,支持多种开发语言。
+6. 社区活跃,生态完善。
+7. 事务支持优秀, InnoDB 存储引擎默认使用 REPEATABLE-READ 并不会有任何性能损失,并且,InnoDB 实现的 REPEATABLE-READ 隔离级别其实是可以解决幻读问题发生的。
+8. 支持分库分表、读写分离、高可用。
+
+## MySQL 字段类型
+
+MySQL 字段类型可以简单分为三大类:
+
+- **数值类型**:整型(tinyint、smallint、mediumint、int 和 bigint)、浮点型(float 和 double)、定点型(decimal)
+- **字符串类型**:char、varchar、tinytext、text、mediumtext、longtext、tinyblob、blob、mediumblob 和 longblob 等,最常用的是 char 和 varchar 。
+- **日期时间类型**:year、time、date、datetime 和 timestamp 等。
+
+下面这张图不是我画的,忘记是从哪里保存下来的了,总结的还蛮不错的。
+
+
+
+MySQL 字段类型比较多,我这里会挑选一些日常开发使用很频繁且面试常问的字段类型,以面试问题的形式来详细介绍。如无特殊说明,针对的都是 InnoDB 存储引擎。
+
+另外,推荐阅读一下《高性能 MySQL(第三版)》的第四章,有详细介绍 MySQL 字段类型优化。
+
+### char 和 varchar 的区别是什么?
+
+char 和 varchar 是最常用到的字符串类型,两者的主要区别在于:**char 是定长字符串,varchar 是变长字符串。**
+
+char 在存储时会在右边填充空格以达到指定的长度,检索时会去掉空格;varchar 在存储时需要使用 1 或 2 个额外字节记录字符串的长度,检索时不需要处理。
+
+char 更适合存储长度较短或者长度都差不多的字符串,例如 Bcrypt 算法、MD5 算法加密后的密码、身份证号码。varchar 类型适合存储长度不确定或者差异较大的字符串,例如用户昵称、文章标题等。
+
+char(M) 和 varchar(M) 的 M 都代表能够保存的字符数的最大值,无论是字母、数字还是中文,每个都只占用一个字符。
+
+### varchar(100)和 varchar(10)的区别是什么?
+
+varchar(100)和 varchar(10)都是变长类型,表示能存储最多 100 个字符和 10 个字符。因此,varchar (100) 可以满足更大范围的字符存储需求,有更好的业务拓展性。而 varchar(10)存储超过 10 个字符时,就需要修改表结构才可以。
+
+虽说 varchar(100)和 varchar(10)能存储的字符范围不同,但二者存储相同的字符串,所占用磁盘的存储空间其实是一样的,这也是很多人容易误解的一点。
+
+不过,varchar(100)会消耗更多的内存。这是因为 varchar 类型在内存中操作时,通常会分配固定大小的内存块来保存值,即使用字符类型中定义的长度。例如在进行排序的时候,varcahr(100)是按照 100 这个长度来进行的,也就会消耗更多内存。
+
+### decimal 和 float/double 的区别是什么?
+
+decimal 和 float 的区别是:**decimal 是定点数,float/double 是浮点数。decimal 可以存储精确的小数值,float/double 只能存储近似的小数值。**
+
+decimal 用于存储有精度要求的小数比如与金钱相关的数据,可以避免浮点数带来的精度损失。
+
+在 Java 中,MySQL 的 decimal 类型对应的是 Java 类 `java.math.BigDecimal` 。
+
+### 为什么不推荐使用 text 和 blob?
+
+text 类型类似于 char(0 - 255 字节)、varchar(0 - 65 535 字节),不过其可以存储更长的字符串,也就是长文本数据,比如一篇博客的内容。
+
+| **类型** | **可存储大小** | **用途** |
+| ---------- | ---------------------- | -------------- |
+| TINYTEXT | 0 - 255 字节 | 一般文本字符串 |
+| TEXT | 0 - 65 535 字节 | 长文本字符串 |
+| MEDIUMTEXT | 0 - 16 772 150 字节 | 较大文本数据 |
+| LONGTEXT | 0 - 4 294 967 295 字节 | 极大文本数据 |
+
+blob 类型主要用于存储二进制大对象,例如图片,音视频等文件。
+
+| **类型** | **可存储大小** | **用途** |
+| ---------- | -------------- | ------------------------ |
+| TINYBLOB | 0 - 255 字节 | 短文本二进制字符串 |
+| BLOB | 0 - 65KB | 二进制字符串 |
+| MEDIUMBLOB | 0 - 16MB | 二进制形式的长文本数据 |
+| LONGBLOB | 0 - 4GB | 二进制形式的极大文本数据 |
+
+日常开发中,text 类型用的很少,但偶尔会用,blob 类型就属于是基本不用。如果预期长度范围 varchar 就满足,就避免使用 text。
+
+数据库规范中一般不推荐使用 blob 及 text 类型,二者的部分缺点和限制如下:
+
+- 不能有默认值。
+- 在遇到使用临时表的情况时,无法使用内存临时表,只能在磁盘上创建临时表(《高性能 MySQL》这本书有提到)。
+- 检索效率比 char 和 varchar 低。
+- 不能直接创建索引,需要指定前缀长度。
+- 会消耗大量的网络和 IO 带宽。
+- 可能会导致表上的 DML 操作都变得较慢。
+- ......
+
+### datetime 和 timestamp 的区别是什么?
+
+DateTime 类型没有时区信息,Timestamp 和时区有关。
+
+Timestamp 只需要使用 4 个字节的存储空间,但是 DateTime 需要耗费 8 个字节的存储空间。但是,这样同样造成了一个问题,Timestamp 表示的时间范围更小。
+
+- DateTime:1000-01-01 00:00:00 ~ 9999-12-31 23:59:59
+- Timestamp:1970-01-01 00:00:01 ~ 2037-12-31 23:59:59
+
+关于两者的详细对比,请参考我写的[MySQL 时间类型数据存储建议](./some-thoughts-on-database-storage-time.md)。
+
+### NULL 和 '' 的区别是什么?
+
+`NULL` 跟 `''`(空字符串)是两个完全不一样的值,区别如下:
+
+- `NULL` 代表一个不确定的值,就算是两个 `NULL`,它俩也不一定相等。例如,`SELECT NULL=NULL`的结果为 false,但是在我们使用`DISTINCT`,`GROUP BY`,`ORDER BY`时,`NULL`又被认为是相等的。
+- `''`的长度是 0,是不占用空间的,而`NULL` 是需要占用空间的。
+- `NULL` 会影响聚合函数的结果。例如,`SUM`、`AVG`、`MIN`、`MAX` 等聚合函数会忽略 `NULL` 值。 `COUNT` 的处理方式取决于参数的类型。如果参数是 `*`(`COUNT(*)`),则会统计所有的记录数,包括 `NULL` 值;如果参数是某个字段名(`COUNT(列名)`),则会忽略 `NULL` 值,只统计非空值的个数。
+- 查询 `NULL` 值时,必须使用 `IS NULL` 或 `IS NOT NULLl` 来判断,而不能使用 =、!=、 <、> 之类的比较运算符。而`''`是可以使用这些比较运算符的。
+
+看了上面的介绍之后,相信你对另外一个高频面试题:“为什么MySQL不建议使用 `NULL` 作为列默认值?”也有了答案。
+
+## MySQL 基础架构
+
+> 建议配合 [SQL 语句在 MySQL 中的执行过程](./how-sql-executed-in-mysql.md) 这篇文章来理解 MySQL 基础架构。另外,“一个 SQL 语句在 MySQL 中的执行流程”也是面试中比较常问的一个问题。
+
+下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到客户端的一条 SQL 语句在 MySQL 内部是如何执行的。
+
+
+
+从上图可以看出, MySQL 主要由下面几部分构成:
+
+- **连接器:** 身份认证和权限相关(登录 MySQL 的时候)。
+- **查询缓存:** 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
+- **分析器:** 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
+- **优化器:** 按照 MySQL 认为最优的方案去执行。
+- **执行器:** 执行语句,然后从存储引擎返回数据。 执行语句之前会先判断是否有权限,如果没有权限的话,就会报错。
+- **插件式存储引擎**:主要负责数据的存储和读取,采用的是插件式架构,支持 InnoDB、MyISAM、Memory 等多种存储引擎。
+
+## MySQL 存储引擎
+
+MySQL 核心在于存储引擎,想要深入学习 MySQL,必定要深入研究 MySQL 存储引擎。
+
+### MySQL 支持哪些存储引擎?默认使用哪个?
+
+MySQL 支持多种存储引擎,你可以通过 `SHOW ENGINES` 命令来查看 MySQL 支持的所有存储引擎。
+
+
+
+从上图我们可以查看出, MySQL 当前默认的存储引擎是 InnoDB。并且,所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。
+
+我这里使用的 MySQL 版本是 8.x,不同的 MySQL 版本之间可能会有差别。
+
+MySQL 5.5.5 之前,MyISAM 是 MySQL 的默认存储引擎。5.5.5 版本之后,InnoDB 是 MySQL 的默认存储引擎。
+
+你可以通过 `SELECT VERSION()` 命令查看你的 MySQL 版本。
+
+```bash
+mysql> SELECT VERSION();
++-----------+
+| VERSION() |
++-----------+
+| 8.0.27 |
++-----------+
+1 row in set (0.00 sec)
+```
+
+你也可以通过 `SHOW VARIABLES LIKE '%storage_engine%'` 命令直接查看 MySQL 当前默认的存储引擎。
+
+```bash
+mysql> SHOW VARIABLES LIKE '%storage_engine%';
++---------------------------------+-----------+
+| Variable_name | Value |
++---------------------------------+-----------+
+| default_storage_engine | InnoDB |
+| default_tmp_storage_engine | InnoDB |
+| disabled_storage_engines | |
+| internal_tmp_mem_storage_engine | TempTable |
++---------------------------------+-----------+
+4 rows in set (0.00 sec)
+```
+
+如果你想要深入了解每个存储引擎以及它们之间的区别,推荐你去阅读以下 MySQL 官方文档对应的介绍(面试不会问这么细,了解即可):
+
+- InnoDB 存储引擎详细介绍: 。
+- 其他存储引擎详细介绍: 。
+
+
+
+### MySQL 存储引擎架构了解吗?
+
+MySQL 存储引擎采用的是 **插件式架构** ,支持多种存储引擎,我们甚至可以为不同的数据库表设置不同的存储引擎以适应不同场景的需要。**存储引擎是基于表的,而不是数据库。**
+
+并且,你还可以根据 MySQL 定义的存储引擎实现标准接口来编写一个属于自己的存储引擎。这些非官方提供的存储引擎可以称为第三方存储引擎,区别于官方存储引擎。像目前最常用的 InnoDB 其实刚开始就是一个第三方存储引擎,后面由于过于优秀,其被 Oracle 直接收购了。
+
+MySQL 官方文档也有介绍到如何编写一个自定义存储引擎,地址: 。
+
+### MyISAM 和 InnoDB 有什么区别?
+
+MySQL 5.5 之前,MyISAM 引擎是 MySQL 的默认存储引擎,可谓是风光一时。
+
+虽然,MyISAM 的性能还行,各种特性也还不错(比如全文索引、压缩、空间函数等)。但是,MyISAM 不支持事务和行级锁,而且最大的缺陷就是崩溃后无法安全恢复。
+
+MySQL 5.5 版本之后,InnoDB 是 MySQL 的默认存储引擎。
+
+言归正传!咱们下面还是来简单对比一下两者:
+
+**1.是否支持行级锁**
+
+MyISAM 只有表级锁(table-level locking),而 InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
+
+也就说,MyISAM 一锁就是锁住了整张表,这在并发写的情况下是多么滴憨憨啊!这也是为什么 InnoDB 在并发写的时候,性能更牛皮了!
+
+**2.是否支持事务**
+
+MyISAM 不提供事务支持。
+
+InnoDB 提供事务支持,实现了 SQL 标准定义了四个隔离级别,具有提交(commit)和回滚(rollback)事务的能力。并且,InnoDB 默认使用的 REPEATABLE-READ(可重读)隔离级别是可以解决幻读问题发生的(基于 MVCC 和 Next-Key Lock)。
+
+关于 MySQL 事务的详细介绍,可以看看我写的这篇文章:[MySQL 事务隔离级别详解](./transaction-isolation-level.md)。
+
+**3.是否支持外键**
+
+MyISAM 不支持,而 InnoDB 支持。
+
+外键对于维护数据一致性非常有帮助,但是对性能有一定的损耗。因此,通常情况下,我们是不建议在实际生产项目中使用外键的,在业务代码中进行约束即可!
+
+阿里的《Java 开发手册》也是明确规定禁止使用外键的。
+
+
+
+不过,在代码中进行约束的话,对程序员的能力要求更高,具体是否要采用外键还是要根据你的项目实际情况而定。
+
+总结:一般我们也是不建议在数据库层面使用外键的,应用层面可以解决。不过,这样会对数据的一致性造成威胁。具体要不要使用外键还是要根据你的项目来决定。
+
+**4.是否支持数据库异常崩溃后的安全恢复**
+
+MyISAM 不支持,而 InnoDB 支持。
+
+使用 InnoDB 的数据库在异常崩溃后,数据库重新启动的时候会保证数据库恢复到崩溃前的状态。这个恢复的过程依赖于 `redo log` 。
+
+**5.是否支持 MVCC**
+
+MyISAM 不支持,而 InnoDB 支持。
+
+讲真,这个对比有点废话,毕竟 MyISAM 连行级锁都不支持。MVCC 可以看作是行级锁的一个升级,可以有效减少加锁操作,提高性能。
+
+**6.索引实现不一样。**
+
+虽然 MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但是两者的实现方式不太一样。
+
+InnoDB 引擎中,其数据文件本身就是索引文件。相比 MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按 B+Tree 组织的一个索引结构,树的叶节点 data 域保存了完整的数据记录。
+
+详细区别,推荐你看看我写的这篇文章:[MySQL 索引详解](./mysql-index.md)。
+
+**7.性能有差别。**
+
+InnoDB 的性能比 MyISAM 更强大,不管是在读写混合模式下还是只读模式下,随着 CPU 核数的增加,InnoDB 的读写能力呈线性增长。MyISAM 因为读写不能并发,它的处理能力跟核数没关系。
+
+
+
+**总结**:
+
+- InnoDB 支持行级别的锁粒度,MyISAM 不支持,只支持表级别的锁粒度。
+- MyISAM 不提供事务支持。InnoDB 提供事务支持,实现了 SQL 标准定义了四个隔离级别。
+- MyISAM 不支持外键,而 InnoDB 支持。
+- MyISAM 不支持 MVCC,而 InnoDB 支持。
+- 虽然 MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构,但是两者的实现方式不太一样。
+- MyISAM 不支持数据库异常崩溃后的安全恢复,而 InnoDB 支持。
+- InnoDB 的性能比 MyISAM 更强大。
+
+最后,再分享一张图片给你,这张图片详细对比了常见的几种 MySQL 存储引擎。
+
+
+
+### MyISAM 和 InnoDB 如何选择?
+
+大多数时候我们使用的都是 InnoDB 存储引擎,在某些读密集的情况下,使用 MyISAM 也是合适的。不过,前提是你的项目不介意 MyISAM 不支持事务、崩溃恢复等缺点(可是~我们一般都会介意啊!)。
+
+《MySQL 高性能》上面有一句话这样写到:
+
+> 不要轻易相信“MyISAM 比 InnoDB 快”之类的经验之谈,这个结论往往不是绝对的。在很多我们已知场景中,InnoDB 的速度都可以让 MyISAM 望尘莫及,尤其是用到了聚簇索引,或者需要访问的数据都可以放入内存的应用。
+
+一般情况下我们选择 InnoDB 都是没有问题的,但是某些情况下你并不在乎可扩展能力和并发能力,也不需要事务支持,也不在乎崩溃后的安全恢复问题的话,选择 MyISAM 也是一个不错的选择。但是一般情况下,我们都是需要考虑到这些问题的。
+
+因此,对于咱们日常开发的业务系统来说,你几乎找不到什么理由再使用 MyISAM 作为自己的 MySQL 数据库的存储引擎。
+
+## MySQL 索引
+
+MySQL 索引相关的问题比较多,对于面试和工作都比较重要,于是,我单独抽了一篇文章专门来总结 MySQL 索引相关的知识点和问题:[MySQL 索引详解](./mysql-index.md) 。
+
+## MySQL 查询缓存
+
+执行查询语句的时候,会先查询缓存。不过,MySQL 8.0 版本后移除,因为这个功能不太实用
+
+`my.cnf` 加入以下配置,重启 MySQL 开启查询缓存
+
+```properties
+query_cache_type=1
+query_cache_size=600000
+```
+
+MySQL 执行以下命令也可以开启查询缓存
+
+```properties
+set global query_cache_type=1;
+set global query_cache_size=600000;
+```
+
+如上,**开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。
+
+**查询缓存不命中的情况:**
+
+1. 任何两个查询在任何字符上的不同都会导致缓存不命中。
+2. 如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果也不会被缓存。
+3. 缓存建立之后,MySQL 的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。
+
+**缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。** 因此,开启查询缓存要谨慎,尤其对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小,一般来说其大小设置为几十 MB 比较合适。此外,**还可以通过 `sql_cache` 和 `sql_no_cache` 来控制某个查询语句是否需要缓存:**
+
+```sql
+SELECT sql_no_cache COUNT(*) FROM usr;
+```
+
+## MySQL 日志
+
+MySQL 日志常见的面试题有:
+
+- MySQL 中常见的日志有哪些?
+- 慢查询日志有什么用?
+- binlog 主要记录了什么?
+- redo log 如何保证事务的持久性?
+- 页修改之后为什么不直接刷盘呢?
+- binlog 和 redolog 有什么区别?
+- undo log 如何保证事务的原子性?
+- ......
+
+上诉问题的答案可以在[《Java 面试指北》(付费)](../../zhuanlan/java-mian-shi-zhi-bei.md) 的 **「技术面试题篇」** 中找到。
+
+
+
+## MySQL 事务
+
+### 何谓事务?
+
+我们设想一个场景,这个场景中我们需要插入多条相关联的数据到数据库,不幸的是,这个过程可能会遇到下面这些问题:
+
+- 数据库中途突然因为某些原因挂掉了。
+- 客户端突然因为网络原因连接不上数据库了。
+- 并发访问数据库时,多个线程同时写入数据库,覆盖了彼此的更改。
+- ......
+
+上面的任何一个问题都可能会导致数据的不一致性。为了保证数据的一致性,系统必须能够处理这些问题。事务就是我们抽象出来简化这些问题的首选机制。事务的概念起源于数据库,目前,已经成为一个比较广泛的概念。
+
+**何为事务?** 一言蔽之,**事务是逻辑上的一组操作,要么都执行,要么都不执行。**
+
+事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作,这两个操作必须都成功或者都失败。
+
+1. 将小明的余额减少 1000 元
+2. 将小红的余额增加 1000 元。
+
+事务会把这两个操作就可以看成逻辑上的一个整体,这个整体包含的操作要么都成功,要么都要失败。这样就不会出现小明余额减少而小红的余额却并没有增加的情况。
+
+
+
+### 何谓数据库事务?
+
+大多数情况下,我们在谈论事务的时候,如果没有特指**分布式事务**,往往指的就是**数据库事务**。
+
+数据库事务在我们日常开发中接触的最多了。如果你的项目属于单体架构的话,你接触到的往往就是数据库事务了。
+
+**那数据库事务有什么作用呢?**
+
+简单来说,数据库事务可以保证多个对数据库的操作(也就是 SQL 语句)构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循:**要么全部执行成功,要么全部不执行** 。
+
+```sql
+# 开启一个事务
+START TRANSACTION;
+# 多条 SQL 语句
+SQL1,SQL2...
+## 提交事务
+COMMIT;
+```
+
+
+
+另外,关系型数据库(例如:`MySQL`、`SQL Server`、`Oracle` 等)事务都有 **ACID** 特性:
+
+
+
+1. **原子性**(`Atomicity`):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
+2. **一致性**(`Consistency`):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
+3. **隔离性**(`Isolation`):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
+4. **持久性**(`Durability`):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
+
+🌈 这里要额外补充一点:**只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的!** 想必大家也和我一样,被 ACID 这个概念被误导了很久! 我也是看周志明老师的公开课[《周志明的软件架构课》](https://time.geekbang.org/opencourse/intro/100064201)才搞清楚的(多看好书!!!)。
+
+
+
+另外,DDIA 也就是 [《Designing Data-Intensive Application(数据密集型应用系统设计)》](https://book.douban.com/subject/30329536/) 的作者在他的这本书中如是说:
+
+> Atomicity, isolation, and durability are properties of the database, whereas consis‐
+> tency (in the ACID sense) is a property of the application. The application may rely
+> on the database’s atomicity and isolation properties in order to achieve consistency,
+> but it’s not up to the database alone.
+>
+> 翻译过来的意思是:原子性,隔离性和持久性是数据库的属性,而一致性(在 ACID 意义上)是应用程序的属性。应用可能依赖数据库的原子性和隔离属性来实现一致性,但这并不仅取决于数据库。因此,字母 C 不属于 ACID 。
+
+《Designing Data-Intensive Application(数据密集型应用系统设计)》这本书强推一波,值得读很多遍!豆瓣有接近 90% 的人看了这本书之后给了五星好评。另外,中文翻译版本已经在 GitHub 开源,地址:[https://github.com/Vonng/ddia](https://github.com/Vonng/ddia) 。
+
+
+
+### 并发事务带来了哪些问题?
+
+在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。
+
+#### 脏读(Dirty read)
+
+一个事务读取数据并且对数据进行了修改,这个修改对其他事务来说是可见的,即使当前事务没有提交。这时另外一个事务读取了这个还未提交的数据,但第一个事务突然回滚,导致数据并没有被提交到数据库,那第二个事务读取到的就是脏数据,这也就是脏读的由来。
+
+例如:事务 1 读取某表中的数据 A=20,事务 1 修改 A=A-1,事务 2 读取到 A = 19,事务 1 回滚导致对 A 的修改并未提交到数据库, A 的值还是 20。
+
+
+
+#### 丢失修改(Lost to modify)
+
+在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。
+
+例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 先修改 A=A-1,事务 2 后来也修改 A=A-1,最终结果 A=19,事务 1 的修改被丢失。
+
+
+
+#### 不可重复读(Unrepeatable read)
+
+指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
+
+例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 修改 A=A-1,事务 2 再次读取 A =19,此时读取的结果和第一次读取的结果不同。
+
+
+
+#### 幻读(Phantom read)
+
+幻读与不可重复读类似。它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
+
+例如:事务 2 读取某个范围的数据,事务 1 在这个范围插入了新的数据,事务 2 再次读取这个范围的数据发现相比于第一次读取的结果多了新的数据。
+
+
+
+### 不可重复读和幻读有什么区别?
+
+- 不可重复读的重点是内容修改或者记录减少比如多次读取一条记录发现其中某些记录的值被修改;
+- 幻读的重点在于记录新增比如多次执行同一条查询语句(DQL)时,发现查到的记录增加了。
+
+幻读其实可以看作是不可重复读的一种特殊情况,单独把区分幻读的原因主要是解决幻读和不可重复读的方案不一样。
+
+举个例子:执行 `delete` 和 `update` 操作的时候,可以直接对记录加锁,保证事务安全。而执行 `insert` 操作的时候,由于记录锁(Record Lock)只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁(Gap Lock)。也就是说执行 `insert` 操作的时候需要依赖 Next-Key Lock(Record Lock+Gap Lock) 进行加锁来保证不出现幻读。
+
+### 并发事务的控制方式有哪些?
+
+MySQL 中并发事务的控制方式无非就两种:**锁** 和 **MVCC**。锁可以看作是悲观控制的模式,多版本并发控制(MVCC,Multiversion concurrency control)可以看作是乐观控制的模式。
+
+**锁** 控制方式下会通过锁来显示控制共享资源而不是通过调度手段,MySQL 中主要是通过 **读写锁** 来实现并发控制。
+
+- **共享锁(S 锁)**:又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。
+- **排他锁(X 锁)**:又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条记录加任何类型的锁(锁不兼容)。
+
+读写锁可以做到读读并行,但是无法做到写读、写写并行。另外,根据根据锁粒度的不同,又被分为 **表级锁(table-level locking)** 和 **行级锁(row-level locking)** 。InnoDB 不光支持表级锁,还支持行级锁,默认为行级锁。行级锁的粒度更小,仅对相关的记录上锁即可(对一行或者多行记录加锁),所以对于并发写入操作来说, InnoDB 的性能更高。不论是表级锁还是行级锁,都存在共享锁(Share Lock,S 锁)和排他锁(Exclusive Lock,X 锁)这两类。
+
+**MVCC** 是多版本并发控制方法,即对一份数据会存储多个版本,通过事务的可见性来保证事务能看到自己应该看到的版本。通常会有一个全局的版本分配器来为每一行数据设置版本号,版本号是唯一的。
+
+MVCC 在 MySQL 中实现所依赖的手段主要是: **隐藏字段、read view、undo log**。
+
+- undo log : undo log 用于记录某行数据的多个版本的数据。
+- read view 和 隐藏字段 : 用来判断当前版本数据的可见性。
+
+关于 InnoDB 对 MVCC 的具体实现可以看这篇文章:[InnoDB 存储引擎对 MVCC 的实现](./innodb-implementation-of-mvcc.md) 。
+
+### SQL 标准定义了哪些事务隔离级别?
+
+SQL 标准定义了四个隔离级别:
+
+- **READ-UNCOMMITTED(读取未提交)**:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
+- **READ-COMMITTED(读取已提交)**:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
+- **REPEATABLE-READ(可重复读)**:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
+- **SERIALIZABLE(可串行化)**:最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
+
+---
+
+| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
+| :--------------: | :--: | :--------: | :--: |
+| READ-UNCOMMITTED | √ | √ | √ |
+| READ-COMMITTED | × | √ | √ |
+| REPEATABLE-READ | × | × | √ |
+| SERIALIZABLE | × | × | × |
+
+### MySQL 的隔离级别是基于锁实现的吗?
+
+MySQL 的隔离级别基于锁和 MVCC 机制共同实现的。
+
+SERIALIZABLE 隔离级别是通过锁来实现的,READ-COMMITTED 和 REPEATABLE-READ 隔离级别是基于 MVCC 实现的。不过, SERIALIZABLE 之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ 在当前读情况下需要使用加锁读来保证不会出现幻读。
+
+### MySQL 的默认隔离级别是什么?
+
+MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
+
+```sql
+mysql> SELECT @@tx_isolation;
++-----------------+
+| @@tx_isolation |
++-----------------+
+| REPEATABLE-READ |
++-----------------+
+```
+
+关于 MySQL 事务隔离级别的详细介绍,可以看看我写的这篇文章:[MySQL 事务隔离级别详解](./transaction-isolation-level.md)。
+
+## MySQL 锁
+
+锁是一种常见的并发事务的控制方式。
+
+### 表级锁和行级锁了解吗?有什么区别?
+
+MyISAM 仅仅支持表级锁(table-level locking),一锁就锁整张表,这在并发写的情况下性非常差。InnoDB 不光支持表级锁(table-level locking),还支持行级锁(row-level locking),默认为行级锁。
+
+行级锁的粒度更小,仅对相关的记录上锁即可(对一行或者多行记录加锁),所以对于并发写入操作来说, InnoDB 的性能更高。
+
+**表级锁和行级锁对比**:
+
+- **表级锁:** MySQL 中锁定粒度最大的一种锁(全局锁除外),是针对非索引字段加的锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。不过,触发锁冲突的概率最高,高并发下效率极低。表级锁和存储引擎无关,MyISAM 和 InnoDB 引擎都支持表级锁。
+- **行级锁:** MySQL 中锁定粒度最小的一种锁,是 **针对索引字段加的锁** ,只针对当前操作的行记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。行级锁和存储引擎有关,是在存储引擎层面实现的。
+
+### 行级锁的使用有什么注意事项?
+
+InnoDB 的行锁是针对索引字段加的锁,表级锁是针对非索引字段加的锁。当我们执行 `UPDATE`、`DELETE` 语句时,如果 `WHERE`条件中字段没有命中唯一索引或者索引失效的话,就会导致扫描全表对表中的所有行记录进行加锁。这个在我们日常工作开发中经常会遇到,一定要多多注意!!!
+
+不过,很多时候即使用了索引也有可能会走全表扫描,这是因为 MySQL 优化器的原因。
+
+### InnoDB 有哪几类行锁?
+
+InnoDB 行锁是通过对索引数据页上的记录加锁实现的,MySQL InnoDB 支持三种行锁定方式:
+
+- **记录锁(Record Lock)**:也被称为记录锁,属于单个行记录上的锁。
+- **间隙锁(Gap Lock)**:锁定一个范围,不包括记录本身。
+- **临键锁(Next-Key Lock)**:Record Lock+Gap Lock,锁定一个范围,包含记录本身,主要目的是为了解决幻读问题(MySQL 事务部分提到过)。记录锁只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁。
+
+**在 InnoDB 默认的隔离级别 REPEATABLE-READ 下,行锁默认使用的是 Next-Key Lock。但是,如果操作的索引是唯一索引或主键,InnoDB 会对 Next-Key Lock 进行优化,将其降级为 Record Lock,即仅锁住索引本身,而不是范围。**
+
+一些大厂面试中可能会问到 Next-Key Lock 的加锁范围,这里推荐一篇文章:[MySQL next-key lock 加锁范围是什么? - 程序员小航 - 2021](https://segmentfault.com/a/1190000040129107) 。
+
+### 共享锁和排他锁呢?
+
+不论是表级锁还是行级锁,都存在共享锁(Share Lock,S 锁)和排他锁(Exclusive Lock,X 锁)这两类:
+
+- **共享锁(S 锁)**:又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。
+- **排他锁(X 锁)**:又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条事务加任何类型的锁(锁不兼容)。
+
+排他锁与任何的锁都不兼容,共享锁仅和共享锁兼容。
+
+| | S 锁 | X 锁 |
+| :--- | :----- | :--- |
+| S 锁 | 不冲突 | 冲突 |
+| X 锁 | 冲突 | 冲突 |
+
+由于 MVCC 的存在,对于一般的 `SELECT` 语句,InnoDB 不会加任何锁。不过, 你可以通过以下语句显式加共享锁或排他锁。
+
+```sql
+# 共享锁 可以在 MySQL 5.7 和 MySQL 8.0 中使用
+SELECT ... LOCK IN SHARE MODE;
+# 共享锁 可以在 MySQL 8.0 中使用
+SELECT ... FOR SHARE;
+# 排他锁
+SELECT ... FOR UPDATE;
+```
+
+### 意向锁有什么作用?
+
+如果需要用到表锁的话,如何判断表中的记录没有行锁呢,一行一行遍历肯定是不行,性能太差。我们需要用到一个叫做意向锁的东东来快速判断是否可以对某个表使用表锁。
+
+意向锁是表级锁,共有两种:
+
+- **意向共享锁(Intention Shared Lock,IS 锁)**:事务有意向对表中的某些记录加共享锁(S 锁),加共享锁前必须先取得该表的 IS 锁。
+- **意向排他锁(Intention Exclusive Lock,IX 锁)**:事务有意向对表中的某些记录加排他锁(X 锁),加排他锁之前必须先取得该表的 IX 锁。
+
+**意向锁是由数据引擎自己维护的,用户无法手动操作意向锁,在为数据行加共享/排他锁之前,InooDB 会先获取该数据行所在在数据表的对应意向锁。**
+
+意向锁之间是互相兼容的。
+
+| | IS 锁 | IX 锁 |
+| ----- | ----- | ----- |
+| IS 锁 | 兼容 | 兼容 |
+| IX 锁 | 兼容 | 兼容 |
+
+意向锁和共享锁和排它锁互斥(这里指的是表级别的共享锁和排他锁,意向锁不会与行级的共享锁和排他锁互斥)。
+
+| | IS 锁 | IX 锁 |
+| ---- | ----- | ----- |
+| S 锁 | 兼容 | 互斥 |
+| X 锁 | 互斥 | 互斥 |
+
+《MySQL 技术内幕 InnoDB 存储引擎》这本书对应的描述应该是笔误了。
+
+
+
+### 当前读和快照读有什么区别?
+
+**快照读**(一致性非锁定读)就是单纯的 `SELECT` 语句,但不包括下面这两类 `SELECT` 语句:
+
+```sql
+SELECT ... FOR UPDATE
+# 共享锁 可以在 MySQL 5.7 和 MySQL 8.0 中使用
+SELECT ... LOCK IN SHARE MODE;
+# 共享锁 可以在 MySQL 8.0 中使用
+SELECT ... FOR SHARE;
+```
+
+快照即记录的历史版本,每行记录可能存在多个历史版本(多版本技术)。
+
+快照读的情况下,如果读取的记录正在执行 UPDATE/DELETE 操作,读取操作不会因此去等待记录上 X 锁的释放,而是会去读取行的一个快照。
+
+只有在事务隔离级别 RC(读取已提交) 和 RR(可重读)下,InnoDB 才会使用一致性非锁定读:
+
+- 在 RC 级别下,对于快照数据,一致性非锁定读总是读取被锁定行的最新一份快照数据。
+- 在 RR 级别下,对于快照数据,一致性非锁定读总是读取本事务开始时的行数据版本。
+
+快照读比较适合对于数据一致性要求不是特别高且追求极致性能的业务场景。
+
+**当前读** (一致性锁定读)就是给行记录加 X 锁或 S 锁。
+
+当前读的一些常见 SQL 语句类型如下:
+
+```sql
+# 对读的记录加一个X锁
+SELECT...FOR UPDATE
+# 对读的记录加一个S锁
+SELECT...LOCK IN SHARE MODE
+# 对读的记录加一个S锁
+SELECT...FOR SHARE
+# 对修改的记录加一个X锁
+INSERT...
+UPDATE...
+DELETE...
+```
+
+### 自增锁有了解吗?
+
+> 不太重要的一个知识点,简单了解即可。
+
+关系型数据库设计表的时候,通常会有一列作为自增主键。InnoDB 中的自增主键会涉及一种比较特殊的表级锁— **自增锁(AUTO-INC Locks)** 。
+
+```sql
+CREATE TABLE `sequence_id` (
+ `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+ `stub` char(10) NOT NULL DEFAULT '',
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `stub` (`stub`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+```
+
+更准确点来说,不仅仅是自增主键,`AUTO_INCREMENT`的列都会涉及到自增锁,毕竟非主键也可以设置自增长。
+
+如果一个事务正在插入数据到有自增列的表时,会先获取自增锁,拿不到就可能会被阻塞住。这里的阻塞行为只是自增锁行为的其中一种,可以理解为自增锁就是一个接口,其具体的实现有多种。具体的配置项为 `innodb_autoinc_lock_mode` (MySQL 5.1.22 引入),可以选择的值如下:
+
+| innodb_autoinc_lock_mode | 介绍 |
+| :----------------------- | :----------------------------- |
+| 0 | 传统模式 |
+| 1 | 连续模式(MySQL 8.0 之前默认) |
+| 2 | 交错模式(MySQL 8.0 之后默认) |
+
+交错模式下,所有的“INSERT-LIKE”语句(所有的插入语句,包括:`INSERT`、`REPLACE`、`INSERT…SELECT`、`REPLACE…SELECT`、`LOAD DATA`等)都不使用表级锁,使用的是轻量级互斥锁实现,多条插入语句可以并发执行,速度更快,扩展性也更好。
+
+不过,如果你的 MySQL 数据库有主从同步需求并且 Binlog 存储格式为 Statement 的话,不要将 InnoDB 自增锁模式设置为交叉模式,不然会有数据不一致性问题。这是因为并发情况下插入语句的执行顺序就无法得到保障。
+
+> 如果 MySQL 采用的格式为 Statement ,那么 MySQL 的主从同步实际上同步的就是一条一条的 SQL 语句。
+
+最后,再推荐一篇文章:[为什么 MySQL 的自增主键不单调也不连续](https://draveness.me/whys-the-design-mysql-auto-increment/) 。
+
+## MySQL 性能优化
+
+关于 MySQL 性能优化的建议总结,请看这篇文章:[MySQL 高性能优化规范建议总结](./mysql-high-performance-optimization-specification-recommendations.md) 。
+
+### 能用 MySQL 直接存储文件(比如图片)吗?
+
+可以是可以,直接存储文件对应的二进制数据即可。不过,还是建议不要在数据库中存储文件,会严重影响数据库性能,消耗过多存储空间。
+
+可以选择使用云服务厂商提供的开箱即用的文件存储服务,成熟稳定,价格也比较低。
+
+
+
+也可以选择自建文件存储服务,实现起来也不难,基于 FastDFS、MinIO(推荐) 等开源项目就可以实现分布式文件服务。
+
+**数据库只存储文件地址信息,文件由文件存储服务负责存储。**
+
+相关阅读:[Spring Boot 整合 MinIO 实现分布式文件服务](https://www.51cto.com/article/716978.html) 。
+
+### MySQL 如何存储 IP 地址?
+
+可以将 IP 地址转换成整形数据存储,性能更好,占用空间也更小。
+
+MySQL 提供了两个方法来处理 ip 地址
+
+- `INET_ATON()`:把 ip 转为无符号整型 (4-8 位)
+- `INET_NTOA()` :把整型的 ip 转为地址
+
+插入数据前,先用 `INET_ATON()` 把 ip 地址转为整型,显示数据时,使用 `INET_NTOA()` 把整型的 ip 地址转为地址显示即可。
+
+### 有哪些常见的 SQL 优化手段?
+
+[《Java 面试指北》(付费)](../../zhuanlan/java-mian-shi-zhi-bei.md) 的 **「技术面试题篇」** 有一篇文章详细介绍了常见的 SQL 优化手段,非常全面,清晰易懂!
+
+
+
+### 如何分析 SQL 的性能?
+
+我们可以使用 `EXPLAIN` 命令来分析 SQL 的 **执行计划** 。执行计划是指一条 SQL 语句在经过 MySQL 查询优化器的优化会后,具体的执行方式。
+
+`EXPLAIN` 并不会真的去执行相关的语句,而是通过 **查询优化器** 对语句进行分析,找出最优的查询方案,并显示对应的信息。
+
+`EXPLAIN` 适用于 `SELECT`, `DELETE`, `INSERT`, `REPLACE`, 和 `UPDATE`语句,我们一般分析 `SELECT` 查询较多。
+
+我们这里简单来演示一下 `EXPLAIN` 的使用。
+
+`EXPLAIN` 的输出格式如下:
+
+```sql
+mysql> EXPLAIN SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC;
++----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
++----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
+| 1 | SIMPLE | cus_order | NULL | ALL | NULL | NULL | NULL | NULL | 997572 | 100.00 | Using filesort |
++----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
+1 row in set, 1 warning (0.00 sec)
+```
+
+各个字段的含义如下:
+
+| **列名** | **含义** |
+| ------------- | -------------------------------------------- |
+| id | SELECT 查询的序列标识符 |
+| select_type | SELECT 关键字对应的查询类型 |
+| table | 用到的表名 |
+| partitions | 匹配的分区,对于未分区的表,值为 NULL |
+| type | 表的访问方法 |
+| possible_keys | 可能用到的索引 |
+| key | 实际用到的索引 |
+| key_len | 所选索引的长度 |
+| ref | 当使用索引等值查询时,与索引作比较的列或常量 |
+| rows | 预计要读取的行数 |
+| filtered | 按表条件过滤后,留存的记录数的百分比 |
+| Extra | 附加信息 |
+
+篇幅问题,我这里只是简单介绍了一下 MySQL 执行计划,详细介绍请看:[SQL 的执行计划](./mysql-query-execution-plan.md)这篇文章。
+
+### 读写分离和分库分表了解吗?
+
+读写分离和分库分表相关的问题比较多,于是,我单独写了一篇文章来介绍:[读写分离和分库分表详解](../../high-performance/read-and-write-separation-and-library-subtable.md)。
+
+## MySQL 学习资料推荐
+
+[**书籍推荐**](../../books/database.md#mysql) 。
+
+**文章推荐** :
+
+- [一树一溪的 MySQL 系列教程](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg3NTc3NjM4Nw==&action=getalbum&album_id=2372043523518300162&scene=173&from_msgid=2247484308&from_itemidx=1&count=3&nolastread=1#wechat_redirect)
+- [Yes 的 MySQL 系列教程](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzkxNTE3NjQ3MA==&action=getalbum&album_id=1903249596194095112&scene=173&from_msgid=2247490365&from_itemidx=1&count=3&nolastread=1#wechat_redirect)
+- [写完这篇 我的 SQL 优化能力直接进入新层次 - 变成派大星 - 2022](https://juejin.cn/post/7161964571853815822)
+- [两万字详解!InnoDB 锁专题! - 捡田螺的小男孩 - 2022](https://juejin.cn/post/7094049650428084232)
+- [MySQL 的自增主键一定是连续的吗? - 飞天小牛肉 - 2022](https://mp.weixin.qq.com/s/qci10h9rJx_COZbHV3aygQ)
+- [深入理解 MySQL 索引底层原理 - 腾讯技术工程 - 2020](https://zhuanlan.zhihu.com/p/113917726)
+
+## 参考
+
+- 《高性能 MySQL》第 7 章 MySQL 高级特性
+- 《MySQL 技术内幕 InnoDB 存储引擎》第 6 章 锁
+- Relational Database:
+- 一篇文章看懂 mysql 中 varchar 能存多少汉字、数字,以及 varchar(100)和 varchar(10)的区别:
+- 技术分享 | 隔离级别:正确理解幻读:
+- MySQL Server Logs - MySQL 5.7 Reference Manual:
+- Redo Log - MySQL 5.7 Reference Manual:
+- Locking Reads - MySQL 5.7 Reference Manual:
+- 深入理解数据库行锁与表锁
+- 详解 MySQL InnoDB 中意向锁的作用:
+- 深入剖析 MySQL 自增锁:
+- 在数据库中不可重复读和幻读到底应该怎么分?:
diff --git "a/docs/database/mysql/mysql\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/docs/database/mysql/mysql\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md"
deleted file mode 100644
index b07247b20f594b6d6fcec83fce235f6009422130..0000000000000000000000000000000000000000
--- "a/docs/database/mysql/mysql\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md"
+++ /dev/null
@@ -1,292 +0,0 @@
----
-title: MySQL知识点&面试题总结
-category: 数据库
-tag:
- - MySQL
- - 大厂面试
----
-
-
-## MySQL 基础
-
-### 关系型数据库介绍
-
-顾名思义,关系型数据库就是一种建立在关系模型的基础上的数据库。关系模型表明了数据库中所存储的数据之间的联系(一对一、一对多、多对多)。
-
-关系型数据库中,我们的数据都被存放在了各种表中(比如用户表),表中的每一行就存放着一条数据(比如一个用户的信息)。
-
-
-
-大部分关系型数据库都使用 SQL 来操作数据库中的数据。并且,大部分关系型数据库都支持事务的四大特性(ACID)。
-
-**有哪些常见的关系型数据库呢?**
-
-MySQL、PostgreSQL、Oracle、SQL Server、SQLite(微信本地的聊天记录的存储就是用的 SQLite) ......。
-
-### MySQL 介绍
-
-
-
-**MySQL 是一种关系型数据库,主要用于持久化存储我们的系统中的一些数据比如用户信息。**
-
-由于 MySQL 是开源免费并且比较成熟的数据库,因此,MySQL 被大量使用在各种系统中。任何人都可以在 GPL(General Public License) 的许可下下载并根据个性化的需要对其进行修改。MySQL 的默认端口号是**3306**。
-
-## 存储引擎
-
-### 存储引擎相关的命令
-
-**查看 MySQL 提供的所有存储引擎**
-
-```sql
-mysql> show engines;
-```
-
-
-
-从上图我们可以查看出 MySQL 当前默认的存储引擎是 InnoDB,并且在 5.7 版本所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。
-
-**查看 MySQL 当前默认的存储引擎**
-
-我们也可以通过下面的命令查看默认的存储引擎。
-
-```sql
-mysql> show variables like '%storage_engine%';
-```
-
-**查看表的存储引擎**
-
-```sql
-show table status like "table_name" ;
-```
-
-
-
-### MyISAM 和 InnoDB 的区别
-
-
-
-MySQL 5.5 之前,MyISAM 引擎是 MySQL 的默认存储引擎,可谓是风光一时。
-
-虽然,MyISAM 的性能还行,各种特性也还不错(比如全文索引、压缩、空间函数等)。但是,MyISAM 不支持事务和行级锁,而且最大的缺陷就是崩溃后无法安全恢复。
-
-5.5 版本之后,MySQL 引入了 InnoDB(事务性数据库引擎),MySQL 5.5 版本后默认的存储引擎为 InnoDB。小伙子,一定要记好这个 InnoDB ,你每次使用 MySQL 数据库都是用的这个存储引擎吧?
-
-言归正传!咱们下面还是来简单对比一下两者:
-
-**1.是否支持行级锁**
-
-MyISAM 只有表级锁(table-level locking),而 InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
-
-也就说,MyISAM 一锁就是锁住了整张表,这在并发写的情况下是多么滴憨憨啊!这也是为什么 InnoDB 在并发写的时候,性能更牛皮了!
-
-**2.是否支持事务**
-
-MyISAM 不提供事务支持。
-
-InnoDB 提供事务支持,具有提交(commit)和回滚(rollback)事务的能力。
-
-**3.是否支持外键**
-
-MyISAM 不支持,而 InnoDB 支持。
-
-🌈 拓展一下:
-
-一般我们也是不建议在数据库层面使用外键的,应用层面可以解决。不过,这样会对数据的一致性造成威胁。具体要不要使用外键还是要根据你的项目来决定。
-
-**4.是否支持数据库异常崩溃后的安全恢复**
-
-MyISAM 不支持,而 InnoDB 支持。
-
-使用 InnoDB 的数据库在异常崩溃后,数据库重新启动的时候会保证数据库恢复到崩溃前的状态。这个恢复的过程依赖于 `redo log` 。
-
-🌈 拓展一下:
-
-- MySQL InnoDB 引擎使用 **redo log(重做日志)** 保证事务的**持久性**,使用 **undo log(回滚日志)** 来保证事务的**原子性**。
-- MySQL InnoDB 引擎通过 **锁机制**、**MVCC** 等手段来保证事务的隔离性( 默认支持的隔离级别是 **`REPEATABLE-READ`** )。
-- 保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。
-
-**5.是否支持 MVCC**
-
-MyISAM 不支持,而 InnoDB 支持。
-
-讲真,这个对比有点废话,毕竟 MyISAM 连行级锁都不支持。
-
-MVCC 可以看作是行级锁的一个升级,可以有效减少加锁操作,提供性能。
-
-### 关于 MyISAM 和 InnoDB 的选择问题
-
-大多数时候我们使用的都是 InnoDB 存储引擎,在某些读密集的情况下,使用 MyISAM 也是合适的。不过,前提是你的项目不介意 MyISAM 不支持事务、崩溃恢复等缺点(可是~我们一般都会介意啊!)。
-
-《MySQL 高性能》上面有一句话这样写到:
-
-> 不要轻易相信“MyISAM 比 InnoDB 快”之类的经验之谈,这个结论往往不是绝对的。在很多我们已知场景中,InnoDB 的速度都可以让 MyISAM 望尘莫及,尤其是用到了聚簇索引,或者需要访问的数据都可以放入内存的应用。
-
-一般情况下我们选择 InnoDB 都是没有问题的,但是某些情况下你并不在乎可扩展能力和并发能力,也不需要事务支持,也不在乎崩溃后的安全恢复问题的话,选择 MyISAM 也是一个不错的选择。但是一般情况下,我们都是需要考虑到这些问题的。
-
-因此,对于咱们日常开发的业务系统来说,你几乎找不到什么理由再使用 MyISAM 作为自己的 MySQL 数据库的存储引擎。
-
-## 锁机制与 InnoDB 锁算法
-
-**MyISAM 和 InnoDB 存储引擎使用的锁:**
-
-- MyISAM 采用表级锁(table-level locking)。
-- InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁
-
-**表级锁和行级锁对比:**
-
-- **表级锁:** MySQL 中锁定 **粒度最大** 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM 和 InnoDB 引擎都支持表级锁。
-- **行级锁:** MySQL 中锁定 **粒度最小** 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
-
-**InnoDB 存储引擎的锁的算法有三种:**
-
-- Record lock:记录锁,单个行记录上的锁
-- Gap lock:间隙锁,锁定一个范围,不包括记录本身
-- Next-key lock:record+gap 临键锁,锁定一个范围,包含记录本身
-
-## 查询缓存
-
-执行查询语句的时候,会先查询缓存。不过,MySQL 8.0 版本后移除,因为这个功能不太实用
-
-`my.cnf` 加入以下配置,重启 MySQL 开启查询缓存
-
-```properties
-query_cache_type=1
-query_cache_size=600000
-```
-
-MySQL 执行以下命令也可以开启查询缓存
-
-```properties
-set global query_cache_type=1;
-set global query_cache_size=600000;
-```
-
-如上,**开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。(**查询缓存不命中的情况:(1)**)因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外,(**查询缓存不命中的情况:(2)**)如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果也不会被缓存。
-
-(**查询缓存不命中的情况:(3)**)**缓存建立之后**,MySQL 的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。
-
-**缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。** 因此,开启查询缓存要谨慎,尤其对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小,一般来说其大小设置为几十 MB 比较合适。此外,**还可以通过 sql_cache 和 sql_no_cache 来控制某个查询语句是否需要缓存:**
-
-```sql
-select sql_no_cache count(*) from usr;
-```
-
-## 事务
-
-### 何为事务?
-
-一言蔽之,**事务是逻辑上的一组操作,要么都执行,要么都不执行。**
-
-**可以简单举一个例子不?**
-
-事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是:
-
-1. 将小明的余额减少 1000 元
-2. 将小红的余额增加 1000 元。
-
-事务会把这两个操作就可以看成逻辑上的一个整体,这个整体包含的操作要么都成功,要么都要失败。
-
-这样就不会出现小明余额减少而小红的余额却并没有增加的情况。
-
-### 何为数据库事务?
-
-数据库事务在我们日常开发中接触的最多了。如果你的项目属于单体架构的话,你接触到的往往就是数据库事务了。
-
-平时,我们在谈论事务的时候,如果没有特指**分布式事务**,往往指的就是**数据库事务**。
-
-**那数据库事务有什么作用呢?**
-
-简单来说:数据库事务可以保证多个对数据库的操作(也就是 SQL 语句)构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循:**要么全部执行成功,要么全部不执行** 。
-
-```sql
-# 开启一个事务
-START TRANSACTION;
-# 多条 SQL 语句
-SQL1,SQL2...
-## 提交事务
-COMMIT;
-```
-
-
-
-另外,关系型数据库(例如:`MySQL`、`SQL Server`、`Oracle` 等)事务都有 **ACID** 特性:
-
-
-
-### 何为 ACID 特性呢?
-
-1. **原子性**(`Atomicity`) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
-2. **一致性**(`Consistency`): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
-3. **隔离性**(`Isolation`): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
-4. **持久性**(`Durability`): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
-
-**数据事务的实现原理呢?**
-
-我们这里以 MySQL 的 InnoDB 引擎为例来简单说一下。
-
-MySQL InnoDB 引擎使用 **redo log(重做日志)** 保证事务的**持久性**,使用 **undo log(回滚日志)** 来保证事务的**原子性**。
-
-MySQL InnoDB 引擎通过 **锁机制**、**MVCC** 等手段来保证事务的隔离性( 默认支持的隔离级别是 **`REPEATABLE-READ`** )。
-
-保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。
-
-### 并发事务带来哪些问题?
-
-在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。
-
-- **脏读(Dirty read):** 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
-- **丢失修改(Lost to modify):** 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 修改 A=A-1,事务 2 也修改 A=A-1,最终结果 A=19,事务 1 的修改被丢失。
-- **不可重复读(Unrepeatable read):** 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
-- **幻读(Phantom read):** 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
-
-**不可重复读和幻读区别:**
-
-不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。
-
-### 事务隔离级别有哪些?
-
-SQL 标准定义了四个隔离级别:
-
-- **READ-UNCOMMITTED(读取未提交):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读**。
-- **READ-COMMITTED(读取已提交):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生**。
-- **REPEATABLE-READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生**。
-- **SERIALIZABLE(可串行化):** 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。
-
----
-
-| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
-| :--------------: | :--: | :--------: | :--: |
-| READ-UNCOMMITTED | √ | √ | √ |
-| READ-COMMITTED | × | √ | √ |
-| REPEATABLE-READ | × | × | √ |
-| SERIALIZABLE | × | × | × |
-
-### MySQL 的默认隔离级别是什么?
-
-MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
-
-```sql
-mysql> SELECT @@tx_isolation;
-+-----------------+
-| @@tx_isolation |
-+-----------------+
-| REPEATABLE-READ |
-+-----------------+
-```
-
-~~这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 **REPEATABLE-READ(可重读)** 事务隔离级别下使用的是 Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说 InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)** 已经可以完全保证事务的隔离性要求,即达到了 SQL 标准的 **SERIALIZABLE(可串行化)** 隔离级别。~~
-
-🐛 问题更正:**MySQL InnoDB 的 REPEATABLE-READ(可重读)并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是 Next-Key Locks。**
-
-因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)** 并不会有任何性能损失。
-
-InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALIZABLE(可串行化)** 隔离级别。
-
-🌈 拓展一下(以下内容摘自《MySQL 技术内幕:InnoDB 存储引擎(第 2 版)》7.7 章):
-
-> InnoDB 存储引擎提供了对 XA 事务的支持,并通过 XA 事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源(transactional resources)参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的 ACID 要求又有了提高。另外,在使用分布式事务时,InnoDB 存储引擎的事务隔离级别必须设置为 SERIALIZABLE。
-
-## 参考
-
-- 《高性能 MySQL》
-- https://www.omnisci.com/technical-glossary/relational-database
diff --git a/docs/database/mysql/some-thoughts-on-database-storage-time.md b/docs/database/mysql/some-thoughts-on-database-storage-time.md
index ccad646e5d4067308ab99dfec535c595f7da1710..fe19f18a2d18d8ffc061df42e824dff6e3ee4685 100644
--- a/docs/database/mysql/some-thoughts-on-database-storage-time.md
+++ b/docs/database/mysql/some-thoughts-on-database-storage-time.md
@@ -1,34 +1,30 @@
---
-title: 关于数据库中如何存储时间的一点思考
+title: MySQL时间类型数据存储建议
category: 数据库
tag:
- MySQL
---
+我们平时开发中不可避免的就是要存储时间,比如我们要记录操作表中这条记录的时间、记录转账的交易时间、记录出发时间、用户下单时间等等。你会发现时间这个东西与我们开发的联系还是非常紧密的,用的好与不好会给我们的业务甚至功能带来很大的影响。所以,我们有必要重新出发,好好认识一下这个东西。
+## 不要用字符串存储日期
-我们平时开发中不可避免的就是要存储时间,比如我们要记录操作表中这条记录的时间、记录转账的交易时间、记录出发时间等等。你会发现时间这个东西与我们开发的联系还是非常紧密的,用的好与不好会给我们的业务甚至功能带来很大的影响。所以,我们有必要重新出发,好好认识一下这个东西。
-
-这是一篇短小精悍的文章,仔细阅读一定能学到不少东西!
-
-### 1.切记不要用字符串存储日期
-
-我记得我在大学的时候就这样干过,而且现在很多对数据库不太了解的新手也会这样干,可见,这种存储日期的方式的优点还是有的,就是简单直白,容易上手。
+和绝大部分对数据库不太了解的新手一样,我在大学的时候就这样干过,甚至认为这样是一个不错的表示日期的方法。毕竟简单直白,容易上手。
但是,这是不正确的做法,主要会有下面两个问题:
1. 字符串占用的空间更大!
2. 字符串存储的日期效率比较低(逐个字符进行比对),无法用日期相关的 API 进行计算和比较。
-### 2.Datetime 和 Timestamp 之间抉择
+## Datetime 和 Timestamp 之间的抉择
-Datetime 和 Timestamp 是 MySQL 提供的两种比较相似的保存时间的数据类型。他们两者究竟该如何选择呢?
+Datetime 和 Timestamp 是 MySQL 提供的两种比较相似的保存时间的数据类型,可以精确到秒。他们两者究竟该如何选择呢?
-**通常我们都会首选 Timestamp。** 下面说一下为什么这样做!
+下面我们来简单对比一下二者。
-#### 2.1 DateTime 类型没有时区信息
+### 时区信息
-**DateTime 类型是没有时区信息的(时区无关)** ,DateTime 类型保存的时间都是当前会话所设置的时区对应的时间。这样就会有什么问题呢?当你的时区更换之后,比如你的服务器更换地址或者更换客户端连接时区设置的话,就会导致你从数据库中读出的时间错误。不要小看这个问题,很多系统就是因为这个问题闹出了很多笑话。
+**DateTime 类型是没有时区信息的(时区无关)** ,DateTime 类型保存的时间都是当前会话所设置的时区对应的时间。这样就会有什么问题呢?当你的时区更换之后,比如你的服务器更换地址或者更换客户端连接时区设置的话,就会导致你从数据库中读出的时间错误。
**Timestamp 和时区有关**。Timestamp 类型字段的值会随着服务器时区的变化而变化,自动换算成相应的时间,说简单点就是在不同时区,查询到同一个条记录此字段的值会不一样。
@@ -100,28 +96,32 @@ SET GLOBAL time_zone = '+8:00';
SET GLOBAL time_zone = 'Europe/Helsinki';
```
-#### 2.2 DateTime 类型耗费空间更大
+### 占用空间
+
+下图是 MySQL 日期类型所占的存储空间(官方文档传送门:):
-Timestamp 只需要使用 4 个字节的存储空间,但是 DateTime 需要耗费 8 个字节的存储空间。但是,这样同样造成了一个问题,Timestamp 表示的时间范围更小。
+
-- DateTime :1000-01-01 00:00:00 ~ 9999-12-31 23:59:59
-- Timestamp: 1970-01-01 00:00:01 ~ 2037-12-31 23:59:59
+在 MySQL 5.6.4 之前,DateTime 和 Timestamp 的存储空间是固定的,分别为 8 字节和 4 字节。但是从 MySQL 5.6.4 开始,它们的存储空间会根据毫秒精度的不同而变化,DateTime 的范围是 5~8 字节,Timestamp 的范围是 4~7 字节。
-> Timestamp 在不同版本的 MySQL 中有细微差别。
+### 表示范围
-### 3 再看 MySQL 日期类型存储空间
+Timestamp 表示的时间范围更小,只能到 2038 年:
-下图是 MySQL 5.6 版本中日期类型所占的存储空间:
+- DateTime:1000-01-01 00:00:00.000000 ~ 9999-12-31 23:59:59.499999
+- Timestamp:1970-01-01 00:00:01.000000 ~ 2038-01-19 03:14:07.499999
-
+### 性能
-可以看出 5.6.4 之后的 MySQL 多出了一个需要 0 ~ 3 字节的小数位。DateTime 和 Timestamp 会有几种不同的存储空间占用。
+由于 TIMESTAMP 需要根据时区进行转换,所以从毫秒数转换到 TIMESTAMP 时,不仅要调用一个简单的函数,还要调用操作系统底层的系统函数。这个系统函数为了保证操作系统时区的一致性,需要进行加锁操作,这就降低了效率。
-为了方便,本文我们还是默认 Timestamp 只需要使用 4 个字节的存储空间,但是 DateTime 需要耗费 8 个字节的存储空间。
+DATETIME 不涉及时区转换,所以不会有这个问题。
-### 4.数值型时间戳是更好的选择吗?
+为了避免 TIMESTAMP 的时区转换问题,建议使用指定的时区,而不是依赖于操作系统时区。
-很多时候,我们也会使用 int 或者 bigint 类型的数值也就是时间戳来表示时间。
+## 数值时间戳是更好的选择吗?
+
+很多时候,我们也会使用 int 或者 bigint 类型的数值也就是数值时间戳来表示时间。
这种存储方式的具有 Timestamp 类型的所具有一些优点,并且使用它的进行日期排序以及对比等操作的效率会更高,跨系统也很方便,毕竟只是存放的数值。缺点也很明显,就是数据的可读性太差了,你无法直观的看到具体时间。
@@ -149,14 +149,21 @@ mysql> select FROM_UNIXTIME(1578707612);
1 row in set (0.01 sec)
```
-### 5.总结
+## 总结
+
+MySQL 中时间到底怎么存储才好?Datetime?Timestamp?还是数值时间戳?
+
+并没有一个银弹,很多程序员会觉得数值型时间戳是真的好,效率又高还各种兼容,但是很多人又觉得它表现的不够直观。
-MySQL 中时间到底怎么存储才好?Datetime?Timestamp? 数值保存的时间戳?
+《高性能 MySQL 》这本神书的作者就是推荐 Timestamp,原因是数值表示时间不够直观。下面是原文:
-好像并没有一个银弹,很多程序员会觉得数值型时间戳是真的好,效率又高还各种兼容,但是很多人又觉得它表现的不够直观。这里插一嘴,《高性能 MySQL 》这本神书的作者就是推荐 Timestamp,原因是数值表示时间不够直观。下面是原文:
+
-
+每种方式都有各自的优势,根据实际场景选择最合适的才是王道。下面再对这三种方式做一个简单的对比,以供大家实际开发中选择正确的存放时间的数据类型:
-每种方式都有各自的优势,根据实际场景才是王道。下面再对这三种方式做一个简单的对比,以供大家实际开发中选择正确的存放时间的数据类型:
+| 类型 | 存储空间 | 日期格式 | 日期范围 | 是否带时区信息 |
+| ------------ | -------- | ------------------------------ | ------------------------------------------------------------ | -------------- |
+| DATETIME | 5~8字节 | YYYY-MM-DD hh:mm:ss[.fraction] | 1000-01-01 00:00:00[.000000] ~ 9999-12-31 23:59:59[.999999] | 否 |
+| TIMESTAMP | 4~7字节 | YYYY-MM-DD hh:mm:ss[.fraction] | 1970-01-01 00:00:01[.000000] ~ 2038-01-19 03:14:07[.999999] | 是 |
+| 数值型时间戳 | 4字节 | 全数字如1578707612 | 1970-01-01 00:00:01之后的时间 | 否 |
-
\ No newline at end of file
diff --git a/docs/database/mysql/transaction-isolation-level.md b/docs/database/mysql/transaction-isolation-level.md
index 4be2f5fde44ce682972dcaf161ca42307a3ba306..647b80ee6ebe0106dee2184bebc88b922e56499a 100644
--- a/docs/database/mysql/transaction-isolation-level.md
+++ b/docs/database/mysql/transaction-isolation-level.md
@@ -1,70 +1,36 @@
---
-title: 事务隔离级别(图文详解)
+title: MySQL事务隔离级别详解
category: 数据库
tag:
- MySQL
---
-
> 本文由 [SnailClimb](https://github.com/Snailclimb) 和 [guang19](https://github.com/guang19) 共同完成。
-## 事务隔离级别(图文详解)
-
-### 什么是事务?
-
-事务是逻辑上的一组操作,要么都执行,要么都不执行。
-
-事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
-
-### 事务的特性(ACID)
-
-
-
-
-1. **原子性:** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
-2. **一致性:** 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
-3. **隔离性:** 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
-4. **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
-
-### 并发事务带来的问题
-
-在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。
+关于事务基本概览的介绍,请看这篇文章的介绍:[MySQL 常见知识点&面试题总结](./mysql-questions-01.md#MySQL-事务)
-- **脏读(Dirty read):** 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
-- **丢失修改(Lost to modify):** 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
-- **不可重复读(Unrepeatableread):** 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
-- **幻读(Phantom read):** 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
+## 事务隔离级别总结
-**不可重复度和幻读区别:**
+SQL 标准定义了四个隔离级别:
-不可重复读的重点是修改,幻读的重点在于新增或者删除。
+- **READ-UNCOMMITTED(读取未提交)**:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
+- **READ-COMMITTED(读取已提交)**:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
+- **REPEATABLE-READ(可重复读)**:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
+- **SERIALIZABLE(可串行化)**:最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
-例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。
-
- 例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。
-
-### 事务隔离级别
-
-**SQL 标准定义了四个隔离级别:**
-
-- **READ-UNCOMMITTED(读取未提交):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读**。
-- **READ-COMMITTED(读取已提交):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生**。
-- **REPEATABLE-READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生**。
-- **SERIALIZABLE(可串行化):** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。
-
-----
+---
-| 隔离级别 | 脏读 | 不可重复读 | 幻影读 |
-| :---: | :---: | :---:| :---: |
-| READ-UNCOMMITTED | √ | √ | √ |
-| READ-COMMITTED | × | √ | √ |
-| REPEATABLE-READ | × | × | √ |
-| SERIALIZABLE | × | × | × |
+| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
+| :--------------: | :--: | :--------: | :--: |
+| READ-UNCOMMITTED | √ | √ | √ |
+| READ-COMMITTED | × | √ | √ |
+| REPEATABLE-READ | × | × | √ |
+| SERIALIZABLE | × | × | × |
MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
```sql
-mysql> SELECT @@tx_isolation;
+MySQL> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
@@ -72,23 +38,26 @@ mysql> SELECT @@tx_isolation;
+-----------------+
```
-~~这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 **REPEATABLE-READ(可重读)** 事务隔离级别下使用的是 Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说 InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)** 已经可以完全保证事务的隔离性要求,即达到了 SQL 标准的 **SERIALIZABLE(可串行化)** 隔离级别。~~
+从上面对 SQL 标准定义了四个隔离级别的介绍可以看出,标准的 SQL 隔离级别定义里,REPEATABLE-READ(可重复读)是不可以防止幻读的。
+
+但是!InnoDB 实现的 REPEATABLE-READ 隔离级别其实是可以解决幻读问题发生的,主要有下面两种情况:
-🐛 问题更正:**MySQL InnoDB 的 REPEATABLE-READ(可重读)并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是 Next-Key Locks。**
+- **快照读**:由 MVCC 机制来保证不出现幻读。
+- **当前读**:使用 Next-Key Lock 进行加锁来保证不出现幻读,Next-Key Lock 是行锁(Record Lock)和间隙锁(Gap Lock)的结合,行锁只能锁住已经存在的行,为了避免插入新行,需要依赖间隙锁。
-因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)** 并不会有任何性能损失。
+因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEATABLE-READ** 并不会有任何性能损失。
-InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALIZABLE(可串行化)** 隔离级别。
+InnoDB 存储引擎在分布式事务的情况下一般会用到 SERIALIZABLE 隔离级别。
-🌈 拓展一下(以下内容摘自《MySQL 技术内幕:InnoDB 存储引擎(第 2 版)》7.7 章):
+《MySQL 技术内幕:InnoDB 存储引擎(第 2 版)》7.7 章这样写到:
> InnoDB 存储引擎提供了对 XA 事务的支持,并通过 XA 事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源(transactional resources)参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的 ACID 要求又有了提高。另外,在使用分布式事务时,InnoDB 存储引擎的事务隔离级别必须设置为 SERIALIZABLE。
-### 实际情况演示
+## 实际情况演示
-在下面我会使用 2 个命令行mysql ,模拟多线程(多事务)对同一份数据的脏读问题。
+在下面我会使用 2 个命令行 MySQL ,模拟多线程(多事务)对同一份数据的脏读问题。
-MySQL 命令行的默认配置中事务都是自动提交的,即执行SQL语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务需要使用命令:`START TARNSACTION`。
+MySQL 命令行的默认配置中事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务需要使用命令:`START TRANSACTION`。
我们可以通过下面的命令来设置隔离级别。
@@ -98,49 +67,47 @@ SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTE
我们再来看一下我们在下面实际操作中使用到的一些并发控制语句:
-- `START TARNSACTION` |`BEGIN`:显式地开启一个事务。
+- `START TRANSACTION` |`BEGIN`:显式地开启一个事务。
- `COMMIT`:提交事务,使得对数据库做的所有修改成为永久性。
- `ROLLBACK`:回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。
-#### 脏读(读未提交)
+### 脏读(读未提交)
-
-
-
+![]()
-#### 避免脏读(读已提交)
+### 避免脏读(读已提交)
-
-
-
+
-#### 不可重复读
+### 不可重复读
还是刚才上面的读已提交的图,虽然避免了读未提交,但是却出现了,一个事务还没有结束,就发生了 不可重复读问题。
-
-
-
+
+
+### 可重复读
+
+
+
+### 幻读
-#### 可重复读
+#### 演示幻读出现的情况
-
-
-
+
-#### 防止幻读(可重复读)
+SQL 脚本 1 在第一次查询工资为 500 的记录时只有一条,SQL 脚本 2 插入了一条工资为 500 的记录,提交之后;SQL 脚本 1 在同一个事务中再次使用当前读查询发现出现了两条工资为 500 的记录这种就是幻读。
-
-
-
+#### 解决幻读的方法
-一个事务对数据库进行操作,这种操作的范围是数据库的全部行,然后第二个事务也在对这个数据库操作,这种操作可以是插入一行记录或删除一行记录,那么第一个是事务就会觉得自己出现了幻觉,怎么还有没有处理的记录呢? 或者 怎么多处理了一行记录呢?
+解决幻读的方式有很多,但是它们的核心思想就是一个事务在操作某张表数据的时候,另外一个事务不允许新增或者删除这张表中的数据了。解决幻读的方式主要有以下几种:
-幻读和不可重复读有些相似之处 ,但是不可重复读的重点是修改,幻读的重点在于新增或者删除。
+1. 将事务隔离级别调整为 `SERIALIZABLE` 。
+2. 在可重复读的事务级别下,给事务操作的这张表添加表锁。
+3. 在可重复读的事务级别下,给事务操作的这张表添加 `Next-key Lock(Record Lock+Gap Lock)`。
### 参考
-- 《MySQL技术内幕:InnoDB存储引擎》
--
-- [Mysql 锁:灵魂七拷问](https://tech.youzan.com/seven-questions-about-the-lock-of-mysql/)
+- 《MySQL 技术内幕:InnoDB 存储引擎》
+-
+- [Mysql 锁:灵魂七拷问](https://tech.youzan.com/seven-questions-about-the-lock-of-MySQL/)
- [Innodb 中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html)
diff --git a/docs/database/nosql.md b/docs/database/nosql.md
new file mode 100644
index 0000000000000000000000000000000000000000..fd70056fd2c6dbfbea7c05b1f741d20479a683aa
--- /dev/null
+++ b/docs/database/nosql.md
@@ -0,0 +1,59 @@
+---
+title: NoSQL基础知识总结
+category: 数据库
+tag:
+ - NoSQL
+ - MongoDB
+ - Redis
+---
+
+## NoSQL 是什么?
+
+NoSQL(Not Only SQL 的缩写)泛指非关系型的数据库,主要针对的是键值、文档以及图形类型数据存储。并且,NoSQL 数据库天生支持分布式,数据冗余和数据分片等特性,旨在提供可扩展的高可用高性能数据存储解决方案。
+
+一个常见的误解是 NoSQL 数据库或非关系型数据库不能很好地存储关系型数据。NoSQL 数据库可以存储关系型数据—它们与关系型数据库的存储方式不同。
+
+NoSQL 数据库代表:HBase、Cassandra、MongoDB、Redis。
+
+
+
+## SQL 和 NoSQL 有什么区别?
+
+| | SQL 数据库 | NoSQL 数据库 |
+| :----------- | -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
+| 数据存储模型 | 结构化存储,具有固定行和列的表格 | 非结构化存储。文档:JSON 文档,键值:键值对,宽列:包含行和动态列的表,图:节点和边 |
+| 发展历程 | 开发于 1970 年代,重点是减少数据重复 | 开发于 2000 年代后期,重点是提升可扩展性,减少大规模数据的存储成本 |
+| 例子 | Oracle、MySQL、Microsoft SQL Server、PostgreSQL | 文档:MongoDB、CouchDB,键值:Redis、DynamoDB,宽列:Cassandra、 HBase,图表:Neo4j、 Amazon Neptune、Giraph |
+| ACID 属性 | 提供原子性、一致性、隔离性和持久性 (ACID) 属性 | 通常不支持 ACID 事务,为了可扩展、高性能进行了权衡,少部分支持比如 MongoDB 。不过,MongoDB 对 ACID 事务 的支持和 MySQL 还是有所区别的。 |
+| 性能 | 性能通常取决于磁盘子系统。要获得最佳性能,通常需要优化查询、索引和表结构。 | 性能通常由底层硬件集群大小、网络延迟以及调用应用程序来决定。 |
+| 扩展 | 垂直(使用性能更强大的服务器进行扩展)、读写分离、分库分表 | 横向(增加服务器的方式横向扩展,通常是基于分片机制) |
+| 用途 | 普通企业级的项目的数据存储 | 用途广泛比如图数据库支持分析和遍历连接数据之间的关系、键值数据库可以处理大量数据扩展和极高的状态变化 |
+| 查询语法 | 结构化查询语言 (SQL) | 数据访问语法可能因数据库而异 |
+
+## NoSQL 数据库有什么优势?
+
+NoSQL 数据库非常适合许多现代应用程序,例如移动、Web 和游戏等应用程序,它们需要灵活、可扩展、高性能和功能强大的数据库以提供卓越的用户体验。
+
+- **灵活性:** NoSQL 数据库通常提供灵活的架构,以实现更快速、更多的迭代开发。灵活的数据模型使 NoSQL 数据库成为半结构化和非结构化数据的理想之选。
+- **可扩展性:** NoSQL 数据库通常被设计为通过使用分布式硬件集群来横向扩展,而不是通过添加昂贵和强大的服务器来纵向扩展。
+- **高性能:** NoSQL 数据库针对特定的数据模型和访问模式进行了优化,这与尝试使用关系数据库完成类似功能相比可实现更高的性能。
+- **强大的功能:** NoSQL 数据库提供功能强大的 API 和数据类型,专门针对其各自的数据模型而构建。
+
+## NoSQL 数据库有哪些类型?
+
+NoSQL 数据库主要可以分为下面四种类型:
+
+- **键值**:键值数据库是一种较简单的数据库,其中每个项目都包含键和值。这是极为灵活的 NoSQL 数据库类型,因为应用可以完全控制 value 字段中存储的内容,没有任何限制。Redis 和 DynanoDB 是两款非常流行的键值数据库。
+- **文档**:文档数据库中的数据被存储在类似于 JSON(JavaScript 对象表示法)对象的文档中,非常清晰直观。每个文档包含成对的字段和值。这些值通常可以是各种类型,包括字符串、数字、布尔值、数组或对象等,并且它们的结构通常与开发者在代码中使用的对象保持一致。MongoDB 就是一款非常流行的文档数据库。
+- **图形**:图形数据库旨在轻松构建和运行与高度连接的数据集一起使用的应用程序。图形数据库的典型使用案例包括社交网络、推荐引擎、欺诈检测和知识图形。Neo4j 和 Giraph 是两款非常流行的图形数据库。
+- **宽列**:宽列存储数据库非常适合需要存储大量的数据。Cassandra 和 HBase 是两款非常流行的宽列存储数据库。
+
+下面这张图片来源于 [微软的官方文档 | 关系数据与 NoSQL 数据](https://learn.microsoft.com/en-us/dotnet/architecture/cloud-native/relational-vs-nosql-data)。
+
+
+
+## 参考
+
+- NoSQL 是什么?- MongoDB 官方文档:
+- 什么是 NoSQL? - AWS:
+- NoSQL vs. SQL Databases - MongoDB 官方文档:
diff --git a/docs/database/redis/3-commonly-used-cache-read-and-write-strategies.md b/docs/database/redis/3-commonly-used-cache-read-and-write-strategies.md
index 841067861f2ee7816490b12dfa16d6074481fb02..91427109697334a75fba40c9ba4e6da48b0cb3db 100644
--- a/docs/database/redis/3-commonly-used-cache-read-and-write-strategies.md
+++ b/docs/database/redis/3-commonly-used-cache-read-and-write-strategies.md
@@ -1,103 +1,103 @@
---
-title: 3种常用的缓存读写策略
+title: 3种常用的缓存读写策略详解
category: 数据库
tag:
- Redis
---
-
-看到很多小伙伴简历上写了“**熟练使用缓存**”,但是被我问到“**缓存常用的3种读写策略**”的时候却一脸懵逼。
+看到很多小伙伴简历上写了“**熟练使用缓存**”,但是被我问到“**缓存常用的 3 种读写策略**”的时候却一脸懵逼。
在我看来,造成这个问题的原因是我们在学习 Redis 的时候,可能只是简单了写一些 Demo,并没有去关注缓存的读写策略,或者说压根不知道这回事。
-但是,搞懂3种常见的缓存读写策略对于实际工作中使用缓存以及面试中被问到缓存都是非常有帮助的!
-
-下面我会简单介绍一下自己对于这 3 种缓存读写策略的理解。
+但是,搞懂 3 种常见的缓存读写策略对于实际工作中使用缓存以及面试中被问到缓存都是非常有帮助的!
-另外,**这3 种缓存读写策略各有优劣,不存在最佳,需要我们根据具体的业务场景选择更适合的。**
-
-*个人能力有限。如果文章有任何需要补充/完善/修改的地方,欢迎在评论区指出,共同进步!——爱你们的 Guide 哥*
+**下面介绍到的三种模式各有优劣,不存在最佳模式,根据具体的业务场景选择适合自己的缓存读写模式。**
### Cache Aside Pattern(旁路缓存模式)
**Cache Aside Pattern 是我们平时使用比较多的一个缓存读写模式,比较适合读请求比较多的场景。**
-Cache Aside Pattern 中服务端需要同时维系 DB 和 cache,并且是以 DB 的结果为准。
+Cache Aside Pattern 中服务端需要同时维系 db 和 cache,并且是以 db 的结果为准。
下面我们来看一下这个策略模式下的缓存读写步骤。
-**写** :
+**写**:
-- 先更新 DB
+- 先更新 db
- 然后直接删除 cache 。
简单画了一张图帮助大家理解写的步骤。
-
+
**读** :
- 从 cache 中读取数据,读取到就直接返回
-- cache中读取不到的话,就从 DB 中读取数据返回
+- cache 中读取不到的话,就从 db 中读取数据返回
- 再把数据放到 cache 中。
简单画了一张图帮助大家理解读的步骤。
-
-
+
你仅仅了解了上面这些内容的话是远远不够的,我们还要搞懂其中的原理。
-比如说面试官很可能会追问:“**在写数据的过程中,可以先删除 cache ,后更新 DB 么?**”
+比如说面试官很可能会追问:“**在写数据的过程中,可以先删除 cache ,后更新 db 么?**”
+
+**答案:** 那肯定是不行的!因为这样可能会造成 **数据库(db)和缓存(Cache)数据不一致**的问题。
+
+举例:请求 1 先写数据 A,请求 2 随后读数据 A 的话,就很有可能产生数据不一致性的问题。
+
+这个过程可以简单描述为:
-**答案:** 那肯定是不行的!因为这样可能会造成**数据库(DB)和缓存(Cache)数据不一致**的问题。为什么呢?比如说请求1 先写数据A,请求2随后读数据A的话就很有可能产生数据不一致性的问题。这个过程可以简单描述为:
+> 请求 1 先把 cache 中的 A 数据删除 -> 请求 2 从 db 中读取数据->请求 1 再把 db 中的 A 数据更新
-> 请求1先把cache中的A数据删除 -> 请求2从DB中读取数据->请求1再把DB中的A数据更新。
+当你这样回答之后,面试官可能会紧接着就追问:“**在写数据的过程中,先更新 db,后删除 cache 就没有问题了么?**”
-当你这样回答之后,面试官可能会紧接着就追问:“**在写数据的过程中,先更新DB,后删除cache就没有问题了么?**”
+**答案:** 理论上来说还是可能会出现数据不一致性的问题,不过概率非常小,因为缓存的写入速度是比数据库的写入速度快很多。
-**答案:** 理论上来说还是可能会出现数据不一致性的问题,不过概率非常小,因为缓存的写入速度是比数据库的写入速度快很多!
+举例:请求 1 先读数据 A,请求 2 随后写数据 A,并且数据 A 在请求 1 请求之前不在缓存中的话,也有可能产生数据不一致性的问题。
-比如请求1先读数据 A,请求2随后写数据A,并且数据A不在缓存中的话也有可能产生数据不一致性的问题。这个过程可以简单描述为:
+这个过程可以简单描述为:
-> 请求1从DB读数据A->请求2写更新数据 A 到数据库并把删除cache中的A数据->请求1将数据A写入cache。
+> 请求 1 从 db 读数据 A-> 请求 2 更新 db 中的数据 A(此时缓存中无数据 A ,故不用执行删除缓存操作 ) -> 请求 1 将数据 A 写入 cache
现在我们再来分析一下 **Cache Aside Pattern 的缺陷**。
-**缺陷1:首次请求数据一定不在 cache 的问题**
+**缺陷 1:首次请求数据一定不在 cache 的问题**
-解决办法:可以将热点数据可以提前放入cache 中。
+解决办法:可以将热点数据可以提前放入 cache 中。
-**缺陷2:写操作比较频繁的话导致cache中的数据会被频繁被删除,这样会影响缓存命中率 。**
+**缺陷 2:写操作比较频繁的话导致 cache 中的数据会被频繁被删除,这样会影响缓存命中率 。**
解决办法:
-- 数据库和缓存数据强一致场景 :更新DB的时候同样更新cache,不过我们需要加一个锁/分布式锁来保证更新cache的时候不存在线程安全问题。
-- 可以短暂地允许数据库和缓存数据不一致的场景 :更新DB的时候同样更新cache,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。
+- 数据库和缓存数据强一致场景:更新 db 的时候同样更新 cache,不过我们需要加一个锁/分布式锁来保证更新 cache 的时候不存在线程安全问题。
+- 可以短暂地允许数据库和缓存数据不一致的场景:更新 db 的时候同样更新 cache,但是给缓存加一个比较短的过期时间,这样的话就可以保证即使数据不一致的话影响也比较小。
### Read/Write Through Pattern(读写穿透)
-Read/Write Through Pattern 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。cache 服务负责将此数据读取和写入 DB,从而减轻了应用程序的职责。
+Read/Write Through Pattern 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。cache 服务负责将此数据读取和写入 db,从而减轻了应用程序的职责。
-这种缓存读写策略小伙伴们应该也发现了在平时在开发过程中非常少见。抛去性能方面的影响,大概率是因为我们经常使用的分布式缓存 Redis 并没有提供 cache 将数据写入DB的功能。
+这种缓存读写策略小伙伴们应该也发现了在平时在开发过程中非常少见。抛去性能方面的影响,大概率是因为我们经常使用的分布式缓存 Redis 并没有提供 cache 将数据写入 db 的功能。
**写(Write Through):**
-- 先查 cache,cache 中不存在,直接更新 DB。
-- cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(**同步更新 cache 和 DB**)。
+- 先查 cache,cache 中不存在,直接更新 db。
+- cache 中存在,则先更新 cache,然后 cache 服务自己更新 db(**同步更新 cache 和 db**)。
简单画了一张图帮助大家理解写的步骤。
-
+
-**读(Read Through):**
+**读(Read Through):**
- 从 cache 中读取数据,读取到就直接返回 。
-- 读取不到的话,先从 DB 加载,写入到 cache 后返回响应。
+- 读取不到的话,先从 db 加载,写入到 cache 后返回响应。
简单画了一张图帮助大家理解读的步骤。
-
+
Read-Through Pattern 实际只是在 Cache-Aside Pattern 之上进行了封装。在 Cache-Aside Pattern 下,发生读请求的时候,如果 cache 中不存在对应的数据,是由客户端自己负责把数据写入 cache,而 Read Through Pattern 则是 cache 服务自己来写入缓存的,这对客户端是透明的。
@@ -105,12 +105,12 @@ Read-Through Pattern 实际只是在 Cache-Aside Pattern 之上进行了封装
### Write Behind Pattern(异步缓存写入)
-Write Behind Pattern 和 Read/Write Through Pattern 很相似,两者都是由 cache 服务来负责 cache 和 DB 的读写。
+Write Behind Pattern 和 Read/Write Through Pattern 很相似,两者都是由 cache 服务来负责 cache 和 db 的读写。
-但是,两个又有很大的不同:**Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 则是只更新缓存,不直接更新 DB,而是改为异步批量的方式来更新 DB。**
+但是,两个又有很大的不同:**Read/Write Through 是同步更新 cache 和 db,而 Write Behind 则是只更新缓存,不直接更新 db,而是改为异步批量的方式来更新 db。**
-很明显,这种方式对数据一致性带来了更大的挑战,比如cache数据可能还没异步更新DB的话,cache服务可能就就挂掉了。
+很明显,这种方式对数据一致性带来了更大的挑战,比如 cache 数据可能还没异步更新 db 的话,cache 服务可能就就挂掉了。
-这种策略在我们平时开发过程中也非常非常少见,但是不代表它的应用场景少,比如消息队列中消息的异步写入磁盘、MySQL 的 InnoDB Buffer Pool 机制都用到了这种策略。
+这种策略在我们平时开发过程中也非常非常少见,但是不代表它的应用场景少,比如消息队列中消息的异步写入磁盘、MySQL 的 Innodb Buffer Pool 机制都用到了这种策略。
-Write Behind Pattern 下 DB 的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量。
+Write Behind Pattern 下 db 的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量。
diff --git a/docs/database/redis/cache-basics.md b/docs/database/redis/cache-basics.md
new file mode 100644
index 0000000000000000000000000000000000000000..2a9aee5735418f0654cc1d6864cd449ef15f6541
--- /dev/null
+++ b/docs/database/redis/cache-basics.md
@@ -0,0 +1,12 @@
+---
+title: 缓存基础常见面试题总结(付费)
+category: 数据库
+tag:
+ - Redis
+---
+
+**缓存基础** 相关的面试题为我的 [知识星球](../../about-the-author/zhishixingqiu-two-years.md)(点击链接即可查看详细介绍以及加入方法)专属内容,已经整理到了[《Java 面试指北》](../../zhuanlan/java-mian-shi-zhi-bei.md)中。
+
+
+
+
diff --git a/docs/database/redis/images/aof-rewrite.drawio b/docs/database/redis/images/aof-rewrite.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..d3c8862ffd2cecf4d8f249a150f33052e42880c0
--- /dev/null
+++ b/docs/database/redis/images/aof-rewrite.drawio
@@ -0,0 +1 @@
+7VZNj9MwEP01Pm6VxE3bPTb9ACEWkIoEe3TjSWJw4uK42+7+euxk0nxtVywITlxa+3n8bM97Mwqhq/z8RrNDdqc4SBJ4/EzomgSB73sz++eQxxqZL25rINWCY1AL7MQTIOghehQcyl6gUUoaceiDsSoKiE0PY1qrUz8sUbJ/6oGlMAJ2MZNj9IvgJqvRRTBv8bcg0qw52Z/h+3LWBONLyoxxdepAdEPoSitl6lF+XoF0yWvyUu/bXlm9XExDYX5lQ5gcow98b8TN8cf6M/d3357UDbI8MHnEBy8/bslmRm5nZDEnmymJIhKhhqV5bNKi1bHg4Jh9QqNTJgzsDix2qydrBItlJpe4nKjCoLL+zM2FlCslla64KPg8hLnFS6PVd2hWClUAbt6yXEhnoHdgIs1EUdrb3KlCNeTqqKuzM2OsL4KQLu2PzYT7cQHlJFUqlcAOopzEKq8W4rIK3SY1ux12+cMgwhMwRaANnK/m3r8oaksBVA5GW0qv2dCYAKsgmOP81HrKn4c1lnX95GEgQx+nF+5WajtAtV+hfDBSfiQyFHzpSsjOYsnKUsR9XVsTeHYGZ2G+4oob3zt8EuJsfe6ErR9xUh8JfFSDg8TaazUSv+jksQCdBIfeOL8NpkEyIx7613gu53jCJyXsBS/60kVfX0oHstXXx13dIh0QTemAKBgQGaZTMCOiygKXZ/++K+jIFRpO2lb32BxWtPdsb9t9zxNMirRwhrECgi3jyNWNsP10iQu54NxxRBpK8cT2FZ/zwsG9qXplGJFwPegb4UuFiM0fydqW2/XR9SK4WrU33iToqeH/mVmaEJUkJfwV+ab/2/m/aOfDKg1un2nnwTPtZha+upvbafuNUPuk/dKim58=
\ No newline at end of file
diff --git a/docs/database/redis/images/aof-work-process.drawio b/docs/database/redis/images/aof-work-process.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..a6e4650b664e3a9cf1157402d39ea3ceccb75c26
--- /dev/null
+++ b/docs/database/redis/images/aof-work-process.drawio
@@ -0,0 +1 @@
+7Vptc+MmEP41fIxH6F0fJce69iZpO5N27u5Th0hY1kUWroRj+359AYElLDnxxXHsyWUy48ACC2If9tlFAtZ4vv5UocXslqS4AKaRroF1DUwTQsNl/7hk00g8P2gEWZWnslMruMt/YCk0pHSZp7jWOlJCCpovdGFCyhInVJOhqiIrvduUFPqsC5ThnuAuQUVf+iVP6ayR+qbXyn/DeTZTM0NXPt89Sh6yiixLOR8wrdiN49hvmudI6ZIPWs9QSlYdkTUB1rgihDal+XqMC763atuacfGe1u26K1zSQwag4N+x/8et+c/vf3+d3qR3FN4WV7ZcG92o/cAp2x5ZJRWdkYyUqJi00kg8M+ZaDVZr+9wQsmBCyITfMaUbaWu0pISJZnReyFa24GrzVY4XlW+8MnJU9XrdbbzeyNqUlDRG87zggs+YRhXKy5ot/5aURLbfkWWV8GlnlDIEmY4Vsh+2KfyHd6hHGSFZgdEir0cJmYuGpBZd42mjnRW7+h0zkjPUtCIPW5wwE0bN/vFN22sWKarVyvbZQqEfVRmmT/Qzt+BhhxKTOWZ7xMZVuEA0f9TXgeTpyLb9WoSwggTJTwBGnvZHVCzVsywWmJ2BIRzdoHvmLTTboyLPSlZO2PbgigkecUVzdhxD2TDP07SBGa7zH+he6OPWX5CcWY8rdyLgXCt7NyCD9kXgo+APHG0dw5gUpBLboVyDAgx/arwGAw5OPnHrN7pQ2n+G+3iQ2q+MkdkoOhggUtdffMM7Xch0WjNg7iJoO+XLQQV7oAITBwQQRNdgYoMoAqENJj6IYiFxgB+C0OhBrnVMHGmrWU7x3QIJa68YeelA7ILH5fW8KDr2ShH2p8n2yKuWkpT4IpC2F0Y9z7MXGVBRsGRuy5b1VcuD0JCyWYcDbfdEzsX7YKOLYSPzQDayzslGfs9xrCp26j/I6Gxk5D1HRtAIoO52Lp2dzB7Iwj9jMPFANAaBJejIBZGgLH8MovCkvGQYvuXH75qXLPPSeCk4Ly+1VPRNY6JfkpesA3kJGsMge6mfkeD07B1wupauolmYHHWCULkfK0/rTZl8UN7ZKC94Nv+CvqeBBh5HeUqzzqOBPv50fGgNZGuMDC2ep/FCBIJYsqLvgInLU7XIPxdhTv0EJ+87kXN2fdIQYVpvSZgqs9yNmVwQMFB4MqmPXDCJOSh8gY4QgnDMCwFDkK+a+tz7mujAMHWw977RAT0WQHTxYfsD+PCcAXwYp8JHP6buE1iZhvx+n5NVgeo6T3TL6gHWLhc9H6Cd37gvj5P6xu6Y0hk46Up2JOW4ju5pHHMHIU341ot++oqCHUXGjqI9YRRDBNp0usmg5PAFO8aT6/LMI/sbWn9WaFb8ujFgn4Er/HHxcd4oEO55LdMNA6EesR15L78ZHHDCW3pnEF5nSoO3bU+kwZcFy9O+PJRvck+fFx9H/P33hwVB6YffOp/fcp70W8YImratua2rI/3WG3gqux9eTgKeePji9SELPsL4pHnFL/D60LGtkacnFo7v9BML0xy9aWrhDaQWbkHltmomd/9bEtVwVQtrsk02oLtYiz1S7ayUif/8xsIRWaotLjM8IWGJqifT1SD8jB7RJ/5Nk5r1vjpyTo+/9A6gSJFZZhxsp/rOphKfT42SUs0GGnBsR++CnNmb6kgeRmgHzlJ0uO8dOjp6HLDH03WOh3a2WL1ztiLxJ3RS5oBIKZXWD5gmM1VRX1tdxpX4K5w3c+eWx/YGsnh/IPeDP3/UWLX9MK3xyu3Xf9bkfw==
\ No newline at end of file
diff --git a/docs/database/redis/images/hash-shopping-cart.drawio b/docs/database/redis/images/hash-shopping-cart.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..75d6bda045580a398646169d13cd1b9120996a2e
--- /dev/null
+++ b/docs/database/redis/images/hash-shopping-cart.drawio
@@ -0,0 +1 @@
+7VnZcts2FP0azLQP9nAFyUctdJI2STPjztTJG0RCJGOIUEjIkvL1AUGAGxgvkWQ7rvUi4gAXyzm4FxuwZ6vdmwKt0w80xgRYRrwD9hxYlmkakP9VyL5GgsCrgaTIYlmoBS6z71iChkQ3WYzLXkFGKWHZug9GNM9xxHoYKgq67RdbUtJvdY0SrAGXESI6+l8Ws1SNCwZtxlucJalqGrpOnbNCqrQcSpmimG47kB0Ce1ZQyuqv1W6GScWeIqa2u/hJbtOzAufsPgbX3+fs35Ll766uvvn/fFha5fzLmenKzrG9GjKOOQMySQuW0oTmiIQtOi3oJo9xVa3BU22Z95SuOWhy8CtmbC/lRBtGOZSyFZG5vMfF/krai8TnKnHuquR8182c72VqSXMmKzV9nq77XnX4p5xIqKSbIsK3EGHLyYWKBLNbyjmNcnzOY7rCvH/crsAEseym3w8kJ1/SlGvl4R9SoQeoJTt5g8hGtvQ33v8BQg8EDpj4IITA98GUu5jxbv6npmyrWyXCNs0YvlwjQcuWu29fo2VGyIwSWghbO0bYX0YcL1lBr3EnB0Y+XiwH6sBGnRtcMLy7XR+dT2lgudJ5ZPhwbJnedpxRYWnHD5Xd0SVwNAkOpPn4pDme1yPNHiHNUo7fJe10rLkaa29RmWrM8TGzPj392ZbTHA+mpoQQyZKcJyPOEeb4tGIw43F8IjNWWRyL4DWmRz+gjUeZgyRxoduXxNElcUamsXUqQfzfOezHfO40/nXENQDecw3wnnINgCMBCBImaeppCr9tqMo4KwWBE17AdNa7NpN/JaJQhklsqrp41+rq6syjLiXLJYbR6FISe8HCGCp+rKgYOOcDJwx0J/RHnNA+lRN6J5JSVPiCpXSfn5TBq1f+olf2hXTcJxZSHTxfnfLBTvnclDRP6ZMfX66SQ590R84Pj6ukdUqffMFKDn3y6ZXUbzA0mnEeT6qbO9Ac7nr7/Q7HFf4JMX7gywViGXbDsLqtsw64NtJ57fDm3nLrcO+TgGzhE83ENFay2cP9jTs8CNZnGWnXvfS7uypjUFV93NGqEvo2Qz9A8nvcmLxKDm3nOIJrFT223CPn09AFPBD5UHzYwDd5Dc//6sc9Ugw2rOFhBepRGI5dx50sCo8cPDWNqstkjkwNEAbA94B/8T/SLIB3Kub5j6mYf6IdkKpl0QBcf98FkxkIHTCdgcATyEX1qhBeCGTyF7pBb6qHuca6OKAHoQemcxCY1cdkCoKgaecrb0c8AJ5HeWeTthh2/q6N2/OYpwQtMJmi6DoR+LDxzix2hC3j6wnNpXF5jVmUqoR6SVTTv7OdDMTvOG5gef3XF9vX3cAc2z+aD/cDnmyfQuvFqH1RtsMf
\ No newline at end of file
diff --git a/docs/database/redis/images/memory-fragmentation.drawio b/docs/database/redis/images/memory-fragmentation.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..a7cec6ac44f17eb89629d74bf27f96bc31ef59f0
--- /dev/null
+++ b/docs/database/redis/images/memory-fragmentation.drawio
@@ -0,0 +1 @@
+7VdNc9owEP01OtLBNhj5aBOSTmbaaSeHpL10hC1sTWSLCvHVX9+VvQb8QZs0bWlmOhyQ3sq7631vF0G8ab670WyZvVMJl8QdJjviXRHXdZyhD18W2VdIEEwqINUiwUNH4E584wgOEV2LhK8aB41S0ohlE4xVUfDYNDCmtdo2jy2UbEZdspR3gLuYyS56LxKTVSh1J0f8LRdpVkd2/KCy5Kw+jG+yyliitieQNyPeVCtlqlW+m3Jpi1fXpXru+oz1kJjmhXnKA/l7Z//5y+2nj/cmH6zmi83jgz9ALxsm1/jCxPUl+ItWS1bYrM0eS+F/XdtUo4UqzGBVEhXCAYcugezoaIdVar8pmY1JeEWA8BklNCT04BuSrNxXJ7E+h0iuVusi4TZvB8zbTBh+t2SxtW5BZoBlJpdotvmgbhxq90LKqZJKl768hHG6iO0LGa0e+YnFjymfL6ylZmZoN4/cxBn6TiVbrdCAleLa8N1ZCpwDsdARXOXc6D0cwQfcCWoBm8H1cL89Ssup9ZKdyMpHjKGa04PrI+GwQM6fwb/7e/i/HPmviOzg0mR7/8n+W2R740uTPXrlZF+MupFzaeomPdS1SsmLJLS3G9jFthwi/vGPIt8J84A2u/5k6/dmjLurHZaz3Oz7il5lwJPObalVZshSrXXMf6bMLh0n5R73VLvGNJfMiE0zjT4KMMIHJSDBA9ujYYvtNotV+vjU6XWq7Yi2HI1ajgzTKTcdR6UiDq/96yKhXZFAY1Kf0HHdodCqExK6hM7sggaETjpCgr4xrcZrTNNCFbw1ehFiUqSF1R8IgAMe2S4UcHMO0ZCLJLFheju9OQvacm00f93vTr8mX9T8HRafeiNz/1TzB/28AqNTMhuRaFpOXkCuSQSL6xIJb9mG3di/SvUsnusnjXj/zOW9DAUSmpZDfliKCkNVYSCDwCPh+B8UlNWMZHMuIxY/piXeDn4iNx/3J9eHqPyUPg3MGmVjD7wzI/FF8vOC1rWh77eH9sjPeb78YHv8s1lNoeNfdm/2HQ==
\ No newline at end of file
diff --git a/docs/database/redis/images/redis-all/redis-list.drawio b/docs/database/redis/images/redis-all/redis-list.drawio
deleted file mode 100644
index afa767154b78e800813cbfe4c672ac94cbdcff60..0000000000000000000000000000000000000000
--- a/docs/database/redis/images/redis-all/redis-list.drawio
+++ /dev/null
@@ -1 +0,0 @@
-7VlNc5swFPw1PiaDENjmmNjpx6Gdjt1J2lNHAzKoEYgRcmzn11cYyRgJT1w3DnScU3grIaHd5b2HM4CTdP2Rozz5wiJMB64TrQdwOnBdEEAo/5TIpkICx6mAmJNITaqBOXnGCtTTliTCRWOiYIwKkjfBkGUZDkUDQ5yzVXPagtHmrjmKsQXMQ0Rt9IFEIqnQsTuq8U+YxIneGQyDaiRFerI6SZGgiK32IHg3gBPOmKiu0vUE05I8zUt134cDo7sH4zgTx9wwu/+V/H4g/myezsD35/vZ56/ulVrlCdGlOrB6WLHRDHC2zCJcLgIG8HaVEIHnOQrL0ZXUXGKJSKkaLh6xCBMVLFgmlKJgJGO1F+YCrw8eAuyokZ7CLMWCb+QUdcOVp9hUdnKhile1OL62WLInzEhhSPkh3i1dUyYvFGt/waBrMbi9BD3lUfPm2bwFLbT556INttPWc/sB035d0+i10wj7TaM77hmNw5fTIM6im7KeyCikqChI2ORMHp1vfsjA0cHPMrh2fR1P1/uj042KjiAbR1aRMqiWVRHxGIuXUr0tSSNxHqacY4oEeWo+RpsOaodvjMgHrPM2gKbk4+YaBVvyEKvb9suYuZLpHdcwRUWEtdDWF7tzn26V0f9ula4s4A0N4cbOtX+aB3zfWupNPaCb01cxAXjPF0cofGq68AJjIT8wXXdusxzRY/fbLJ0VDbPbOj1j2PXnrVOG/Z3A82WRWFaQXZUwujHB2SOeMMq4RDKW4VJLQqkBIUrirHSQVA9L/Lbs0Yj8lL1RAymJonKb1vavbhCP9M0/dYA+OFDF95zltTjLLPav1gECu5PmOcvf5aloD7qWx7fkoRf8+siiYggEuhbI/oSil/v+2Pp0/gLZ3y18u20/BTqDJp5O8VoSp2tJxvYrgxcXpIjV351PERnWv7RXPV39/wp49wc=
\ No newline at end of file
diff --git a/docs/database/redis/images/redis-all/redis-list.png b/docs/database/redis/images/redis-all/redis-list.png
deleted file mode 100644
index 4fb4e36cb494ee9554a156f2bd295d6f2f983864..0000000000000000000000000000000000000000
Binary files a/docs/database/redis/images/redis-all/redis-list.png and /dev/null differ
diff --git a/docs/database/redis/images/redis-all/redis-rollBack.png b/docs/database/redis/images/redis-all/redis-rollBack.png
deleted file mode 100644
index 91f7f46d66dba0c55398d5b7aad42254dd65adde..0000000000000000000000000000000000000000
Binary files a/docs/database/redis/images/redis-all/redis-rollBack.png and /dev/null differ
diff --git a/docs/database/redis/images/redis-all/redis-vs-memcached.png b/docs/database/redis/images/redis-all/redis-vs-memcached.png
deleted file mode 100644
index 23844d67e6fc949920643ec78628e2e37a909c45..0000000000000000000000000000000000000000
Binary files a/docs/database/redis/images/redis-all/redis-vs-memcached.png and /dev/null differ
diff --git a/docs/database/redis/images/redis-all/redis4.0-more-thread.png b/docs/database/redis/images/redis-all/redis4.0-more-thread.png
deleted file mode 100644
index e7e19e52e17e12050cfa110bf2c9fa973d7dc299..0000000000000000000000000000000000000000
Binary files a/docs/database/redis/images/redis-all/redis4.0-more-thread.png and /dev/null differ
diff --git "a/docs/database/redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png" "b/docs/database/redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png"
deleted file mode 100644
index fc280fffabaeaf8d984449b62e61ef21c84f1d06..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/redis-all/redis\344\272\213\344\273\266\345\244\204\347\220\206\345\231\250.png" and /dev/null differ
diff --git "a/docs/database/redis/images/redis-all/redis\344\272\213\345\212\241.png" "b/docs/database/redis/images/redis-all/redis\344\272\213\345\212\241.png"
deleted file mode 100644
index eb0c404cafd1197235fb37563611f5c5b13eaa6a..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/redis-all/redis\344\272\213\345\212\241.png" and /dev/null differ
diff --git "a/docs/database/redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png" "b/docs/database/redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png"
deleted file mode 100644
index 27df6ead8e4c8a576b53d777d0d9fa8e29586f17..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/redis-all/redis\350\277\207\346\234\237\346\227\266\351\227\264.png" and /dev/null differ
diff --git a/docs/database/redis/images/redis-all/try-redis.png b/docs/database/redis/images/redis-all/try-redis.png
deleted file mode 100644
index cd21a6518e4de4a25fd740d029e3222c1dabd41c..0000000000000000000000000000000000000000
Binary files a/docs/database/redis/images/redis-all/try-redis.png and /dev/null differ
diff --git a/docs/database/redis/images/redis-all/what-is-redis.png b/docs/database/redis/images/redis-all/what-is-redis.png
deleted file mode 100644
index 913881ac6cf59e9c3c56a58db545048d4f7c47ca..0000000000000000000000000000000000000000
Binary files a/docs/database/redis/images/redis-all/what-is-redis.png and /dev/null differ
diff --git "a/docs/database/redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png" "b/docs/database/redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png"
deleted file mode 100644
index 2c73bd90276521694b6a7e7c2fe78fbbd0517b31..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/redis-all/\344\275\277\347\224\250\347\274\223\345\255\230\344\271\213\345\220\216.png" and /dev/null differ
diff --git "a/docs/database/redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png" "b/docs/database/redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png"
deleted file mode 100644
index a2c2ed6906fa5e1e558301dc1c36d7bbeee181fd..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/redis-all/\345\212\240\345\205\245\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250\345\220\216\347\232\204\347\274\223\345\255\230\345\244\204\347\220\206\346\265\201\347\250\213.png" and /dev/null differ
diff --git "a/docs/database/redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png" "b/docs/database/redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png"
deleted file mode 100644
index 648a404af8c93ae61d9e9797d2cbae6b4f3776da..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/redis-all/\345\215\225\344\275\223\346\236\266\346\236\204.png" and /dev/null differ
diff --git "a/docs/database/redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png" "b/docs/database/redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png"
deleted file mode 100644
index 11860ae1f024368ecd8173d94d5235a9c0c2fc08..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/redis-all/\347\274\223\345\255\230\347\232\204\345\244\204\347\220\206\346\265\201\347\250\213.png" and /dev/null differ
diff --git "a/docs/database/redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png" "b/docs/database/redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png"
deleted file mode 100644
index e7298c15ed6a483fa565fd6a6ba7841eabd9ff3b..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/redis-all/\347\274\223\345\255\230\347\251\277\351\200\217\346\203\205\345\206\265.png" and /dev/null differ
diff --git "a/docs/database/redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png" "b/docs/database/redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png"
deleted file mode 100644
index 5aff414baa44e30e307d103ba1eea8eb56aa1b54..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/redis-all/\351\233\206\344\270\255\345\274\217\347\274\223\345\255\230\346\236\266\346\236\204.png" and /dev/null differ
diff --git a/docs/database/redis/images/redis-aof-write-log-disc.drawio b/docs/database/redis/images/redis-aof-write-log-disc.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..e03fbce3d8ec65692ec1de5d8cf5d7227383eee6
--- /dev/null
+++ b/docs/database/redis/images/redis-aof-write-log-disc.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/database/redis/images/redis-cache-avalanche.drawio b/docs/database/redis/images/redis-cache-avalanche.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..f92d5d3ea3068615b9cdb610545e8bb70be1e88f
--- /dev/null
+++ b/docs/database/redis/images/redis-cache-avalanche.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/database/redis/images/redis-cache-breakdown.drawio b/docs/database/redis/images/redis-cache-breakdown.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..638a0925ad192d3c831bdabd41cce8a38ec01dfe
--- /dev/null
+++ b/docs/database/redis/images/redis-cache-breakdown.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/database/redis/images/redis-cache-penetration.drawio b/docs/database/redis/images/redis-cache-penetration.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..331f92273c08a9cdeea42d67157e536c414b2867
--- /dev/null
+++ b/docs/database/redis/images/redis-cache-penetration.drawio
@@ -0,0 +1 @@
+7Vxrd5pK2/41fNxdw8noRxRN6OtgbXR3m28ECYIHshSj8Ovf654ZVKJp0zbt7u7TrtUG5ngfrvs0TKqZneX+eh08zng2jRaawaZ7zXQ1w9B11sAPailkS5OZsiFeJ1M16Nhwm5SRamSqdZtMo01tYJ5lizx5rDeG2WoVhXmtLVivs1192EO2qO/6GMTRWcNtGCzOWz8l03ymuDCuju03URLPqp31Rkv23AfhPF5n25XaTzPMXqPX6zVl9zKo1lKMbmbBNNudNJldzeyssyyXT8t9J1qQbCuxyXm9F3oPdK+jVf6aCcOR1Zjc3629xWb36f+uMu/Kuf7LsBVxeVEJJJpCPuo1W+ezLM5WwaJ7bG2Dk0fqVSrJ1mgTgohoK4a347x+lj2iUUdjGuV5oQAQbPMMTbN8uVC9i+A+WrQPMu1kC1rXnUYPwXaR0675OptHL7VXujPR8pCt8l6wTBYEyfdR3l4HyWoDLnm2ylS/okO31fv5utFq6hC+iNFFsNkkYTU1265DmjzLc4DUsE0H/0Du9A8N2LyLsyxeRMFjsnkXZkvREW7E0N6DJAyPp6TZRlsRJ/VBSnhRz6ppU9HxknJ18wAzmG+ULaN8jZ3ZOloEefJU3yBQdhQfxh2mfsgSbG0wZfOG3XpnNlrHP3IBZf9XV6y+YB6s4yhXaxzxiYcToo5NArVfgWBduZunYLGNKkt8BukjOAlpu1mSR7ePgZDdDl6tDsQTdBiE5YdksajQARu3Oo7VujrD4ypbCdOYR3k4U1ZwsHh6iQlD6vlVAP0JKHuK1nm0/zzOzuFTTWgBBlc13TeY0v3u6En1yv/NTrxok70MuhpAvhYNjTcGwwXnUofDNIiaD+FLcPgtFG0w67midfOConX9XRVOTnV9Zbyzf5C29W+JXt8dqSC2dfEPzX9nV68TtZx4cfe1t6JyB98WqP4r0UYhRLr7z4xrvUVQQmQOipMBjxReNp+JWQ1WA7BhPkuWvm48HiQFbxvKzr2XrnVNDd6yiQdbcyzNudK6La15pTV7WrepOT2t3a4e0NXQ2rrWPHd6pOE+ZVh1KAeLJF5RggNtR5TGkaNIkBw7qmOZTKfSaqJNUgb3Yj0CsxI4Frfbmu1eyKpeSOiOafKvg/8L7lHltorjYxZ/ahmf8UkvOtO/2DudmUpB35mI/fVsRvbwsIl+SJJ19eWwWhUFyVIUXAckCdR9yDZJnmSEqPssz6GkVyT8z7GYk39uB5tHWQY+JHvy4m2xoVO1sqqFlgryQDMd+Wr0Nk+xZrT3AL/R+XDjG3dF27r/tN+G5aN1f73YBiVLgpuPLHSzp745NaeFbfLCfgqX4RMfze3BbUuOK7w4utY39yve8pYzNr1xGv2ihRnhdlry7b35ftUvvR13nafQvFt5STu7+7RYBTfDlpcOLbybwaePLHBZ4rtx4t3cLcKV/3hvWOjvbvmtF0+Xi8WUvX+KMIZ3nJ3ndnc8HcZ85BR+x4snxn4WmrT/IsW85EO6n999uivxs5zeLDZ3t20W/dNefLh9z+7+mbG+4Zdh4T19SHdPd9d/L8PSaobXPRZ02nOM8Xnq7fv46yeO4ad866czoz8K9X46sQJ3bPFby5imoXHfYfrA7W79hO36KS+iObO46239Mmae64E2q+SrSTEYzWkNT63LBr1JwdOPG38U431sDMCTmJ+0c95hhpg3jzEG/KddY1h2Lb+wmOR5QvwbkMN+4MbbgTth6h3jh1tevjd8l9tYz5iUTszL8RZytUCP2U+HoMmR64M38IE1hrEv52U8nYCOrg2adb6M88Goi/U/pv1R1+inMeaBzhHHmLGN+XuxTjnJeels/dG88FyOv9hzFILfuQG6yn4aFuCP9it5xzIED2KvIRNrXnPI2bIGo0nMO4cxOR8RTQ54o/0moJvRGti/Cz4moIXteWGZPI2xvydpgGxBXxF0mA3eLL83Qf9M8QV9XPOS5kCvkOEEsodcOwz0WRbWgBwg71GMNSaM9PF3qnQ455YvZB9bSg4YPwHPoaFkL/QOLMj3myzpA1N9E3ZVWuQSvuzeX0yZf/2U2LTPUmL7PCW2D6nvaUJs/7iEuHnupylt6GqOQblBsymShCvNaVPC8NyFQx75pfLnuYZOKiDV9Po84lK9Vc/IayX0paOaXzRj+Ar0GGeV86WCqnGhcDZ/VOHc+rmnKN1WS7dfRNj/0CmKweqFhtn6t49QDgXfv34k/K2F9p+j5Lc/SmavrO516zvL++/D7vlhsPGNFTRaYIptl2ZR3GTU1bLVQ9OlqIqutqO1TK1rae2m1nR+hbr7NZA9h+grq/X/WjX+Be+Latxo2Y16KH7T2rxa1PpZpXplqX9q9d+wVl8Gn/abD7de4l2DvjnqQdejehO1GurDAvXtoX5Fbeh6qOUnpueiFi/DHT2jlsR4rvuuGF9Q7TkYoUYsOerJOebO6ZlqUEZ1en80xs/JXozAeNSNDDU81g0xnqMuDZmYX6A2doc0H3O4jdrdHoyGtD/VxqilPXsg9mSoHa0CdTxq4i5ohIwEXXPio5RjUBOjTueuqIn3J/RaVKP2R0Oqh1FDs4InqG1Rc0v6h1SrMi5kwQQ9mGcT7ZizQx2854Im0D4K6fzBGKAWBq27gwxGqGlHlayGGDPf+4J37FVQzS7HYU/U6iSHCfHL6OxE0G5CVi5HneyIfcV5BHSBWlund2CDxuq8kv8tZDGiM5cwPl1Tyt+DPnmJ2ltHTV74qNd5OgZ9w71cC3K5tXTFP/YRcikUFhjRATwwiYGuCbmXfhpjHzqroLrdKwR2qJa/tXZiHujgKea5caHmMfSVvpLbQKzhSXpT8JUOdSlfDj1BxnLv0qe9SzpDEJhjkLEJuUo6yznonNtCpwn0VE6wztyS+9GZTWiKdsIa7AgYAR+OIdfydHW+wsDPXshRYJHkImU8TYRdgIexWPO+Q2dVnsIwPTuWT3o05ZnFF2yLQT5M8CNto5yINYaVnG2iV+pTnM9UsoIc6XxmrLBE51NxpXfo1NK5kMcwx3jSRyl16gHDQ13ZClqgq3JoV9jBPDo3yusydE5lCDyKsx9LnEHdWsCBJ3U3Ai6VToAdOgva0ZmSxN1Y4STeEocK/29kZ8O9ks8FWwO/X2VnXdNf1WyMvcbG4Lsv2Ze02eSz9gX/Fkt9/iT7EueURI87VvI++kmimzAm+SD5Ek109lrzuXnN5yYOyd306dySsKZsyXNJ7l1JF+nmlsZUvujU5jhsbm4Mbmi+A54ciU9jlws6CY8d8CdwNm14SROx+8NNeza9juO75WJzj8iISCpPmrGCOJF0ESHTCZDkmALFhjhl3XOyXli+3J04FJZXeRJY0ZhxEXEYeQA2qMaldOLcVdY2LsWpM9A9OGqQIhZFs9KvrFVItlqbw2JglcK7EafjvUCRRKUuUOnGaKdo2i3k6Xi8V5a6x1ybVxYirF1GAGi/JA0NS27DigpYj0KTOCneVV4EfQZ4kB4y5Sd7OyV4qDROCC1OZAa+J1uJLIEmsjYbFktz6JSdIjxorCx3Di8xriI8yVvuv5hQ5N1yaXWvilrPLMqsWVTxVRZlqAzikkVVOHhuVQoDlyyLMq+TrCFhlDUYvMqQvsuywrplFW9kWT3yKvC8bih4D0TE4oJH/+bRh0YMinqgBfzAg17vCooM4El5XY7+Yak8qMml7uElx+RpLPqaITMFgZOc9qoiE32B8UcefZlAhKBIxJmKmntBa8l3kqcxaBXeRXr9W8KyF+OnLfYVuCe+PeZL+xS6VtiETmg+2QCjrzAUVdX6oYp8J+uvsvj+FvZN2ewoVjwiqwZfvrCfuw0yAeBgrjJDjE2gO4FtZAyk19GcHTOKuYgo4ktQUeEPOks90sVe+IVR16oyLoFF+tIiKKOvRTFl0FWGoh8iPrAFuzcJcwOSB3nCEWXRlXcnDFXZOX3Nge4U9nzya+m8ysp36AMNc/H1y3MJY0MReUGvLjOuu4Z3zRT+QtKvjj0VjX4KfB0yHvlFyqMvWDupC2GzlZ6e97GByLbJ9/iQ89gQWY+KctQHfOSX5py0k6+lLGF3qA6O7Wrs+5TLbBvynsSIGvTVsMoa4C+7On1hI382cIfWoW31P/ZlqX4aYVXl+8l5sNU8Pw6u2t7+jpV+dp7wMZomm1/069HpB4JpsJkdvle8+Fmp8dt8StCff0o4h47OfuZHJd34cxb1255FiWzeH3WRlQxLuk/gj0JEdB+1H2U1wwKRDpkI0eBR9ow+j8bkdGdDjvXsqdu1KDtC5oDxyExKr+qzpinVuKiPUDf7ZnfniTse1bpMP+7JEfEpc5R9U8pcUiemCKv23FO2IO6PnOyF6L+TFYXcz19QloqMRvAUloe9KRso6d7JmOaWf1Nmdljnbi9rvpjuSuiHtRLHRDTUkYlgjRmjzORkPbpjofoWFNV3v40MEzEvr88b03mB5Jey0wMfU9T0Nfooq2EDcc9nvJXyiSkbQqY3lnveDAuROSk6IRd26PsHWePIAU3dI4+r7l7eB5K0iMyopgt2ogvoqaZDdqLDBaqU8LBvOKJ7RvMXeHROeaTMku7LkP7knnR3B3KDbZ7KreCQm6ouBd9yHqvNQ3Z1wCEq0CMf+kSXd3kq+iYWMIW/NWzEJ9gAbmqYik8wZUVz7DtC9XYD+Z3sKeX3jPbV8AwryOZfkodxirPXy3F2QY69sbRVbhx5vGMSp+wEp3dj2BPGefZRn/Ma3tEnTgrUup/o/IrO0IQ8bro7eX53sMdTu8lrduM+11lFY7s6F+m0yukyxNPv/UsD5zekGhfuuPzcVNY6T0c+81H3T4L7q2GKve43jn5ujnvpzt2V1u6IewG25rhaq6kuCOCZ7hcYmqML4HW0ZkuM6WntVgU8i6a3HK1pXQDnG2BSM8yHZhiF4RsC89mNr9/nqp5p1WsqS7+At9YFvFk/7HrW1zmxI/Aswlir9weB/20ENox/HYEXfhuk29TaPYEcW2u1tVZX3DJuEZy+DKFvvVgqI1sdWL2e+I3kPxdLL/x67uFXs3/A3VK8Hv8rA3mX6fj/RZjd/wc=
\ No newline at end of file
diff --git a/docs/database/redis/images/redis-event-handler.drawio b/docs/database/redis/images/redis-event-handler.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..478a954c5994e04eb5a50cba33ba4e8882172536
--- /dev/null
+++ b/docs/database/redis/images/redis-event-handler.drawio
@@ -0,0 +1 @@
+7Vtbc6s2EP41ekyG++XR2DjnnPa0meah7VNHARmTYOSCnNj99ZWEMGDJSZz4QjJMZhxpdQPtp0+7KwTM8WJ9U8Dl/CeOUQYMLV4DcwIMQ9c1h/5jkk0lcT2/EiRFGotKjeAu/Q8JoSakqzRGZaciwTgj6bIrjHCeo4h0ZLAo8HO32gxn3VGXMEGS4C6CmSz9M43JvH4vx28KvqE0mddDO7ZVldzD6DEp8CoXAwLDnDrT6dSrihew7ky8aTmHMX5uicwQmOMCY1KlFusxytjk1vNWtZvuKd0+eIFy8pYGqHzI/7m6/QNOYkxulsFVsfnlSvTyBLMVql+DPyzZ1DPEXxGxTnRgBs/zlKC7JYxY6TPFBJXNySITxTEs59u6M5yTKVykGcPGD0SCAqZ5SXv/iXMsyu/wquB9zQmhKjdsc0R/6EuwH1ahvE4wTjIEl2l5HeEFL4hKXnU6q3qnyXb/thGIEUpS4MetYumUB/Ks1VOACoLWLZGYxRuEF4gUdBCtLvWERgXkdUvkn1sAqrU+b2HH0oQQCtAm274bvdGEUN0BajRkNYY2GNnAd0HoAC9kaSaZMMmuhul7k64aq3kb4wwXVJLjHDF1pVm2I4JZmuQ0G9HZRFQesFlM6eoaiYJFGsdsGCVuGmRpKugIsjC0XkDpCMAxtB3gODJwPAVujJPBRpOwgGJKjyKLCzLHCc5hFjbSHa01dX7FeCm094AI2Qj1wRXBXXDRCSw2f4n2PPM3y1zbdXaybhdONiLXwoTu9AITB9BLWT/IPl2YYvODRYLIC/XE8mV6ehFzBcogSZ+629zREWRKxFPi6BERXQZWltE9fR8VtOAhqblFO3SPjSHyZtFejro4Jo6xwfh2hye2vNHmCVvmCfdkPKFQ58ATl+EJ61I8wZuOigJuWhWWOKVz0+r5lgkaKJt6d8szzR0j9bD6NFE9QYPl7au8H97WHhKTbeGBxN5NYubFSUyhztOT2NcmI/skJCOxgqWpWWHbRcWGolWDk4PZyuqOY7n2y2xlXYCt7D1s9dvAVsdzzSyFT39WtnIkLcvqzeMRi4A1qui40DtRmVtIqIuec4mhmUoi+ERe96uU01KcrfCpa9kHmckw3evuPmfteusViUrcpOqqSyb+20juWLximIOR35d91X2jke8de//9EGe5Emd9p/38TkU89mgBfwRCDwQuGE2FxJuA0AW+BUYeS9BfL2BFwQh4UwmPB4WiJTLrbmqzGXKir72pSWaTwgTXVQFH51S7mifvaqEDfAd4LggtEAQgcHhixHGwldjAo8hwWOWACkMm8X0Gl9NCZGZ8cYg4ft8g4r9u+AwnUjta9J2uFnVToUVHoUXrZPZrHcfpLHXVwmabgMU3AY2v8H0LezikOg8j+F7XntVdBZZ0BZZOdkqly/G5wRfqoS+kazvhENt7pyuka6a/41bZ9nm9IV0Os3Bu0kEwEfxFmYtas9SUDfghe6ADzziE0d5rqqhCNEiPbeR+aVPFd3dOxWoVtYnJPKepoiuCNAqMUEkA/LDl8QwYORNGlObseTEiO8XcB55yRLQ+zaGWkc8Rwb7RsQaMnA8j3sUxonKLKX14jEEqHqEwUaJmwMhZMGIpvtQ6L0YMS7Z5h+jsZaKzYnm9Gp3Vj348+jEI2QOEPh2EnH5ByBkg9Okg1K8vRg35+/MBQn2HUL/OGQ2FvWw4GRHz3wGX8+8K1wVXJdck1YamO8t1U0hTCf/PDpqoUT3mVveY356w2UEki/BMuWT0Az7BG3ZpqR7yvmiau8zzp/4/c+MCZorXrR5oK37V6TrK64agQsO2dT8j3hm8R1mwveuksPbr9eGJfMtVCPgf75NQYOBcdFo+IhLN60x9MarnYda3uw+22Q2EGqobQqpTNf1w94FmmztkVfSzualnhv8D
\ No newline at end of file
diff --git a/docs/database/redis/images/redis-list.drawio b/docs/database/redis/images/redis-list.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..6a12bdc5487ab233151a95b5cd240cba7b1ed221
--- /dev/null
+++ b/docs/database/redis/images/redis-list.drawio
@@ -0,0 +1 @@
+7VlNc9owFPw1HJPBlg32MYE07aGdDnSS5tRR7YetRrY8sgiQX18Zy9iWYEIpXy05Ya3kJ2l3rfc0dNAgmd9znMWfWQi0Y3fDeQcNO7Zt+QjJnwJZlIjv90sg4iRUg2pgTF5BgV2FTkkIeWugYIwKkrXBgKUpBKKFYc7ZrD1swmh71gxHYADjAFMTfSShiEvUs/s1/hFIFFczWz2/7ElwNVjtJI9xyGYNCN110IAzJsqnZD4AWpBX8VK+92FD72phHFKxzQujhx/xr0fijsbJyPr2+jD69MW+UlFeMJ2qDavFikXFAGfTNIQiiNVBt7OYCBhnOCh6Z1JzicUioao7fwYRFPvuysaEpUIpavVl21xxNT1wAfMGpHZwDywBwRdyiOq9chSbyk42Uu1ZLY7bVVjcEKavMKz8EK1C15TJB8XaHzBoGwwuH60D8jghlA4YZXwZGIUYvElQvCQ4e4ZGTy/w4OfkL5mvmHZMpv01RLuHIhqtJ/qQhj0u0VWvbvFTE++sJx79b8Tb3pkR33v7cIY0vCmynGwFFOc5CdosSzb44rtiedl4KhrXtlu1h/Nm73ChWrsd5ALzCMTb+QbCVtY1JWkd55sp50CxIC/tXL1OBzXDV0bkiutsYiFdcq8dI2dTHoB6rZlc9Ui6d2zNFCUzRqClL1b73t0q/X/OKmdiAaenCed1r93dPOC6RqijeqAqmfdiAuv9vNhC4V2PC8fXArm+7rpDm2WLyv/MzHImJjCuILufGGb+OfaRYd5eeDbNY8MKsoQSWv3WKsVSloJWtykIUxKlhYOknCDx26IgI/KCfaM6EhKGxTRrC8a6pNzdN9tXgK61IYs3nOWscZae7PdWAVpm7c0zlr3LU9Lun1oe15CHXvDnI5OKJpB1aoHMKxS93O/H1OfkH5B5b+HLac9TIF2TlmB7EMipzvtKn+6p9fHM7wcmlyqPUfkdTh7ZrP8ZKKu9+v8VdPcb
\ No newline at end of file
diff --git a/docs/database/redis/images/redis4.0-master-slave-replication-replid.drawio b/docs/database/redis/images/redis4.0-master-slave-replication-replid.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..04ed496f736695e1904c41e7fff125405f1f87f2
--- /dev/null
+++ b/docs/database/redis/images/redis4.0-master-slave-replication-replid.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/database/redis/images/why-redis-so-fast.png b/docs/database/redis/images/why-redis-so-fast.png
new file mode 100644
index 0000000000000000000000000000000000000000..279e1955473eaf8fe2bc665e0a0bf0b8e512d8cd
Binary files /dev/null and b/docs/database/redis/images/why-redis-so-fast.png differ
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio"
deleted file mode 100644
index bc4c6d0cca713d2e0db2aaac480477ec795407d3..0000000000000000000000000000000000000000
--- "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-7Vpbc+o2EP41ekzHl/j2iAmk02nmdA6dpn3qCFsYTWSLY0Qg/fWVbMnYluE4B4jTQh4Se6WVVt+32pU2BvY43T3mcLV8ojEiwDLiHbAfgGWZluPzP0LyVkoCwygFSY5j2WkvmOF/kBSqbhsco3WjI6OUMLxqCiOaZShiDRnMc7ptdltQ0px1BROkCWYRJLr0GcdsWUp9y9vLf0Y4WaqZTTcoW1KoOsuVrJcwptuayJ4Ae5xTysqndDdGRICncCn1pgdaK8NylLE+Ck/Pf/xthHPraf7NePliLeLZ1+c7OcorJBu5YGkse1MI5HSTxUgMYgI73C4xQ7MVjETrlnPOZUuWEtm8wISMKaF5oWsvFgsrirh8zXL6gmotsTt3HVe0KFiEekRTzLs/GPw5IXC9ls/SSpQztDu4fLMClXsjoili+RvvIhVs5VHSEX3fKd+3e1pNxdWyRqkrZVB6UlINvQebP0i834G9db3YW0Njb2vYx5BBDX++ZNYEuQlmRjN0BHlIcJJxWcRxQrwxFChiHlxGsiHFcSzm6qR2T75RTaxiUME3zZgMmFbQIq1O5jkIvG8RqN5rBLod/NmX4u9e4y8TVN3468dflYWH4s+5mtjnGJ8t77jXi/3gece75Z13EWi3845OYFfcUjyfnT//lndO4W/wvBNcTexzg8+Wd9Ql4BrBHzzxmPpN/5Z5jjDoWZ/sxmPq5YJb6nkHgYOnHlOvOfxfw5/vf7rcoxcMrgb84XOPftu/5Z4jDAbtcunguUcvGdxyzzsIHD736HUHjTyUxSPxDzNBgIBGANKIeC38mgjbXIJ2mP0pe4vnvwSiPzny7WEnAS5e3hpoo1j7L1wLa24q3eQROrbIbk5qmDsdmCtZjghk+LVpRhcRcobfKOYGHiw1BSrjqCFK86XWns4eA7VqGQzmCWLaQIVfVMs+wVX0Esd/zFWGcgG/vevbzPV1gXYG/3AX6FElublAF3Nu+9Lxo1GgXUDQBrqwC6h1NFzAJUV2t/bK7reN+HwhBJN7EPrAD8DEAf4U+GPxwNcQmGASgMAG4QRMfDAywcivqdVdSgmF59ytC9cZ8Q62vdrVNdyknJDP8wACp3N4UnadClNlfymM8esJk8pB5vnpY3DrCluU9PSTlBSd9RBVv7IcOi/Vdrptn+f8ZPt+w/s9Vz8/OeZHHqAsvXz2PZcSFN5JNoQ/ELRgh/1hvYJZH68yne7twPcf3w5jsQt49uD37ckUhGMQjGYZxGRMcDrnnWEqSM7m61XTFcvZj3toKRXmnGDnBTCrBQQHjMYFFHzhngxFoVetu/z9iNlyM6/w+QW+wkfxkdkPwcGlJSK99/HF92e9vNDOxEKJ8hExEzsrEK8EzhEJYfSSFGO3AwxXrl3VjOKn0qJ5jPIOjSlMMREzPMIcpjSLlRkSGENS/aUyxTNKPiOcJb/TlTwiSMGvwgsakpAyRtOm7KsMA2eKQZ6qWakM7OhlFKsrBt1fLAbpFeDbsazXscw0vnep6nsuMw1v2LO5pVeRs41Q+9jAM2R1RefgcuUV/rr/QrfkcP+dsz35Fw==
\ No newline at end of file
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png"
deleted file mode 100644
index f8b9589d6d7814b49aef01013b7ce61d38b304db..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-read.png" and /dev/null differ
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio"
deleted file mode 100644
index 6fddf10f064eb3ecd2446ba3c5663e373533521d..0000000000000000000000000000000000000000
--- "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png"
deleted file mode 100644
index c976cc99b912f4ce1049482ff6bf27d6817e60e6..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/cache-aside-write.png" and /dev/null differ
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio"
deleted file mode 100644
index 7f7bfd71641b9b8e6822b9368fdca5827fa92ddf..0000000000000000000000000000000000000000
--- "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png"
deleted file mode 100644
index f8f457c7490d10907baa89f5786aa57faf2de3f4..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/read-through.png" and /dev/null differ
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio"
deleted file mode 100644
index 7626c8d1f5004745fcbd532b7a6a90c6f3b2a9e1..0000000000000000000000000000000000000000
--- "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.drawio"
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png" "b/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png"
deleted file mode 100644
index ecdbd6d2c2e2a773b0013f527e180497b63277e2..0000000000000000000000000000000000000000
Binary files "a/docs/database/redis/images/\347\274\223\345\255\230\350\257\273\345\206\231\347\255\226\347\225\245/write-through.png" and /dev/null differ
diff --git a/docs/database/redis/redis-cluster.md b/docs/database/redis/redis-cluster.md
new file mode 100644
index 0000000000000000000000000000000000000000..2db4feda7e36889497f9a4a065c495804e616e3f
--- /dev/null
+++ b/docs/database/redis/redis-cluster.md
@@ -0,0 +1,12 @@
+---
+title: Redis集群详解(付费)
+category: 数据库
+tag:
+ - Redis
+---
+
+**Redis 集群** 相关的面试题为我的 [知识星球](../../about-the-author/zhishixingqiu-two-years.md)(点击链接即可查看详细介绍以及加入方法)专属内容,已经整理到了[《Java 面试指北》](../../zhuanlan/java-mian-shi-zhi-bei.md)中。
+
+
+
+
diff --git a/docs/database/redis/redis-common-blocking-problems-summary.md b/docs/database/redis/redis-common-blocking-problems-summary.md
new file mode 100644
index 0000000000000000000000000000000000000000..facf3e3ac9b0ff9c0e4ef5635f839f8e8b079996
--- /dev/null
+++ b/docs/database/redis/redis-common-blocking-problems-summary.md
@@ -0,0 +1,173 @@
+---
+title: Redis常见阻塞原因总结
+category: 数据库
+tag:
+ - Redis
+---
+
+> 本文整理完善自:https://mp.weixin.qq.com/s/0Nqfq_eQrUb12QH6eBbHXA ,作者:阿 Q 说代码
+
+这篇文章会详细总结一下可能导致 Redis 阻塞的情况,这些情况也是影响 Redis 性能的关键因素,使用 Redis 的时候应该格外注意!
+
+## O(n) 命令
+
+Redis 中的大部分命令都是 O(1)时间复杂度,但也有少部分 O(n) 时间复杂度的命令,例如:
+
+- `KEYS *`:会返回所有符合规则的 key。
+- `HGETALL`:会返回一个 Hash 中所有的键值对。
+- `LRANGE`:会返回 List 中指定范围内的元素。
+- `SMEMBERS`:返回 Set 中的所有元素。
+- `SINTER`/`SUNION`/`SDIFF`:计算多个 Set 的交集/并集/差集。
+- ......
+
+由于这些命令时间复杂度是 O(n),有时候也会全表扫描,随着 n 的增大,执行耗时也会越长,从而导致客户端阻塞。不过, 这些命令并不是一定不能使用,但是需要明确 N 的值。另外,有遍历的需求可以使用 `HSCAN`、`SSCAN`、`ZSCAN` 代替。
+
+除了这些 O(n)时间复杂度的命令可能会导致阻塞之外, 还有一些时间复杂度可能在 O(N) 以上的命令,例如:
+
+- `ZRANGE`/`ZREVRANGE`:返回指定 Sorted Set 中指定排名范围内的所有元素。时间复杂度为 O(log(n)+m),n 为所有元素的数量, m 为返回的元素数量,当 m 和 n 相当大时,O(n) 的时间复杂度更小。
+- `ZREMRANGEBYRANK`/`ZREMRANGEBYSCORE`:移除 Sorted Set 中指定排名范围/指定 score 范围内的所有元素。时间复杂度为 O(log(n)+m),n 为所有元素的数量, m 被删除元素的数量,当 m 和 n 相当大时,O(n) 的时间复杂度更小。
+- ......
+
+## SAVE 创建 RDB 快照
+
+Redis 提供了两个命令来生成 RDB 快照文件:
+
+- `save` : 同步保存操作,会阻塞 Redis 主线程;
+- `bgsave` : fork 出一个子进程,子进程执行,不会阻塞 Redis 主线程,默认选项。
+
+默认情况下,Redis 默认配置会使用 `bgsave` 命令。如果手动使用 `save` 命令生成 RDB 快照文件的话,就会阻塞主线程。
+
+## AOF
+
+### AOF 日志记录阻塞
+
+Redis AOF 持久化机制是在执行完命令之后再记录日志,这和关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复)不同。
+
+
+
+**为什么是在执行完命令之后记录日志呢?**
+
+- 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
+- 在命令执行完之后再记录,不会阻塞当前的命令执行。
+
+这样也带来了风险(我在前面介绍 AOF 持久化的时候也提到过):
+
+- 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
+- **可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)**。
+
+### AOF 刷盘阻塞
+
+开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。
+
+在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是:
+
+1. `appendfsync always`:主线程调用 `write` 执行写操作后,后台线程( `aof_fsync` 线程)立即会调用 `fsync` 函数同步 AOF 文件(刷盘),`fsync` 完成后线程返回,这样会严重降低 Redis 的性能(`write` + `fsync`)。
+2. `appendfsync everysec`:主线程调用 `write` 执行写操作后立即返回,由后台线程( `aof_fsync` 线程)每秒钟调用 `fsync` 函数(系统调用)同步一次 AOF 文件(`write`+`fsync`,`fsync`间隔为 1 秒)
+3. `appendfsync no`:主线程调用 `write` 执行写操作后立即返回,让操作系统决定何时进行同步,Linux 下一般为 30 秒一次(`write`但不`fsync`,`fsync` 的时机由操作系统决定)。
+
+当后台线程( `aof_fsync` 线程)调用 `fsync` 函数同步 AOF 文件时,需要等待,直到写入完成。当磁盘压力太大的时候,会导致 `fsync` 操作发生阻塞,主线程调用 `write` 函数时也会被阻塞。`fsync` 完成后,主线程执行 `write` 才能成功返回。
+
+关于 AOF 工作流程的详细介绍可以查看:[Redis 持久化机制详解](./redis-persistence.md),有助于理解 AOF 刷盘阻塞。
+
+### AOF 重写阻塞
+
+1. fork 出一条子线程来将文件重写,在执行 `BGREWRITEAOF` 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子线程创建新 AOF 文件期间,记录服务器执行的所有写命令。
+2. 当子线程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。
+3. 最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。
+
+阻塞就是出现在第 2 步的过程中,将缓冲区中新数据写到新文件的过程中会产生**阻塞**。
+
+相关阅读:[Redis AOF 重写阻塞问题分析](https://cloud.tencent.com/developer/article/1633077)。
+
+## 大 Key
+
+如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准:string 类型的 value 超过 10 kb,复合类型的 value 包含的元素超过 5000 个(对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。
+
+大 key 造成的阻塞问题如下:
+
+- 客户端超时阻塞:由于 Redis 执行命令是单线程处理,然后在操作大 key 时会比较耗时,那么就会阻塞 Redis,从客户端这一视角看,就是很久很久都没有响应。
+- 引发网络阻塞:每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。
+- 阻塞工作线程:如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。
+
+### 查找大 key
+
+当我们在使用 Redis 自带的 `--bigkeys` 参数查找大 key 时,最好选择在从节点上执行该命令,因为主节点上执行时,会**阻塞**主节点。
+
+- 我们还可以使用 SCAN 命令来查找大 key;
+
+- 通过分析 RDB 文件来找出 big key,这种方案的前提是 Redis 采用的是 RDB 持久化。网上有现成的工具:
+
+- - redis-rdb-tools:Python 语言写的用来分析 Redis 的 RDB 快照文件用的工具
+ - rdb_bigkeys:Go 语言写的用来分析 Redis 的 RDB 快照文件用的工具,性能更好。
+
+### 删除大 key
+
+删除操作的本质是要释放键值对占用的内存空间。
+
+释放内存只是第一步,为了更加高效地管理内存空间,在应用程序释放内存时,**操作系统需要把释放掉的内存块插入一个空闲内存块的链表**,以便后续进行管理和再分配。这个过程本身需要一定时间,而且会**阻塞**当前释放内存的应用程序。
+
+所以,如果一下子释放了大量内存,空闲内存块链表操作时间就会增加,相应地就会造成 Redis 主线程的阻塞,如果主线程发生了阻塞,其他所有请求可能都会超时,超时越来越多,会造成 Redis 连接耗尽,产生各种异常。
+
+删除大 key 时建议采用分批次删除和异步删除的方式进行。
+
+## 清空数据库
+
+清空数据库和上面 bigkey 删除也是同样道理,`flushdb`、`flushall` 也涉及到删除和释放所有的键值对,也是 Redis 的阻塞点。
+
+## 集群扩容
+
+Redis 集群可以进行节点的动态扩容缩容,这一过程目前还处于半自动状态,需要人工介入。
+
+在扩缩容的时候,需要进行数据迁移。而 Redis 为了保证迁移的一致性,迁移所有操作都是同步操作。
+
+执行迁移时,两端的 Redis 均会进入时长不等的阻塞状态,对于小 Key,该时间可以忽略不计,但如果一旦 Key 的内存使用过大,严重的时候会触发集群内的故障转移,造成不必要的切换。
+
+## Swap(内存交换)
+
+**什么是 Swap?** Swap 直译过来是交换的意思,Linux 中的 Swap 常被称为内存交换或者交换分区。类似于 Windows 中的虚拟内存,就是当内存不足的时候,把一部分硬盘空间虚拟成内存使用,从而解决内存容量不足的情况。因此,Swap 分区的作用就是牺牲硬盘,增加内存,解决 VPS 内存不够用或者爆满的问题。
+
+Swap 对于 Redis 来说是非常致命的,Redis 保证高性能的一个重要前提是所有的数据在内存中。如果操作系统把 Redis 使用的部分内存换出硬盘,由于内存与硬盘读写的速度并几个数量级,会导致发生交换后的 Redis 性能急剧下降。
+
+识别 Redis 发生 Swap 的检查方法如下:
+
+1、查询 Redis 进程号
+
+```bash
+reids-cli -p 6383 info server | grep process_id
+process_id: 4476
+```
+
+2、根据进程号查询内存交换信息
+
+```bash
+cat /proc/4476/smaps | grep Swap
+Swap: 0kB
+Swap: 0kB
+Swap: 4kB
+Swap: 0kB
+Swap: 0kB
+.....
+```
+
+如果交换量都是 0KB 或者个别的是 4KB,则正常。
+
+预防内存交换的方法:
+
+- 保证机器充足的可用内存
+- 确保所有 Redis 实例设置最大可用内存(maxmemory),防止极端情况 Redis 内存不可控的增长
+- 降低系统使用 swap 优先级,如`echo 10 > /proc/sys/vm/swappiness`
+
+## CPU 竞争
+
+Redis 是典型的 CPU 密集型应用,不建议和其他多核 CPU 密集型服务部署在一起。当其他进程过度消耗 CPU 时,将严重影响 Redis 的吞吐量。
+
+可以通过`reids-cli --stat`获取当前 Redis 使用情况。通过`top`命令获取进程对 CPU 的利用率等信息 通过`info commandstats`统计信息分析出命令不合理开销时间,查看是否是因为高算法复杂度或者过度的内存优化问题。
+
+## 网络问题
+
+连接拒绝、网络延迟,网卡软中断等网络问题也可能会导致 Redis 阻塞。
+
+## 参考
+
+- Redis 阻塞的 6 大类场景分析与总结:https://mp.weixin.qq.com/s/eaZCEtTjTuEmXfUubVHjew
+- Redis 开发与运维笔记-Redis 的噩梦-阻塞:https://mp.weixin.qq.com/s/TDbpz9oLH6ifVv6ewqgSgA
diff --git a/docs/database/redis/redis-data-structures-01.md b/docs/database/redis/redis-data-structures-01.md
new file mode 100644
index 0000000000000000000000000000000000000000..c0fd83cbfc0ad6ecefa99b8953e2e7d6c387a511
--- /dev/null
+++ b/docs/database/redis/redis-data-structures-01.md
@@ -0,0 +1,501 @@
+---
+title: Redis 5 种基本数据结构详解
+category: 数据库
+tag:
+ - Redis
+head:
+ - - meta
+ - name: keywords
+ content: Redis常见数据结构
+ - - meta
+ - name: description
+ content: Redis基础数据结构总结:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)
+---
+
+Redis 共有 5 种基本数据结构:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
+
+这 5 种数据结构是直接提供给用户使用的,是数据的保存形式,其底层实现主要依赖这 8 种数据结构:简单动态字符串(SDS)、LinkedList(双向链表)、Hash Table(哈希表)、SkipList(跳跃表)、Intset(整数集合)、ZipList(压缩列表)、QuickList(快速列表)。
+
+Redis 基本数据结构的底层数据结构实现如下:
+
+| String | List | Hash | Set | Zset |
+| :----- | :--------------------------- | :------------------ | :-------------- | :---------------- |
+| SDS | LinkedList/ZipList/QuickList | Hash Table、ZipList | ZipList、Intset | ZipList、SkipList |
+
+Redis 3.2 之前,List 底层实现是 LinkedList 或者 ZipList。 Redis 3.2 之后,引入了 LinkedList 和 ZipList 的结合 QuickList,List 的底层实现变为 QuickList。
+
+你可以在 Redis 官网上找到 Redis 数据结构非常详细的介绍:
+
+- [Redis Data Structures](https://redis.com/redis-enterprise/data-structures/)
+- [Redis Data types tutorial](https://redis.io/docs/manual/data-types/data-types-tutorial/)
+
+未来随着 Redis 新版本的发布,可能会有新的数据结构出现,通过查阅 Redis 官网对应的介绍,你总能获取到最靠谱的信息。
+
+
+
+## String(字符串)
+
+### 介绍
+
+String 是 Redis 中最简单同时也是最常用的一个数据结构。
+
+String 是一种二进制安全的数据结构,可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的 base64 编码或者解码或者图片的路径)、序列化后的对象。
+
+
+
+虽然 Redis 是用 C 语言写的,但是 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 **简单动态字符串**(Simple Dynamic String,**SDS**)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。
+
+### 常用命令
+
+| 命令 | 介绍 |
+| ------------------------------ | -------------------------------- |
+| SET key value | 设置指定 key 的值 |
+| SETNX key value | 只有在 key 不存在时设置 key 的值 |
+| GET key | 获取指定 key 的值 |
+| MSET key1 value1 key2 value2 … | 设置一个或多个指定 key 的值 |
+| MGET key1 key2 ... | 获取一个或多个指定 key 的值 |
+| STRLEN key | 返回 key 所储存的字符串值的长度 |
+| INCR key | 将 key 中储存的数字值增一 |
+| DECR key | 将 key 中储存的数字值减一 |
+| EXISTS key | 判断指定 key 是否存在 |
+| DEL key(通用) | 删除指定的 key |
+| EXPIRE key seconds(通用) | 给指定 key 设置过期时间 |
+
+更多 Redis String 命令以及详细使用指南,请查看 Redis 官网对应的介绍: 。
+
+**基本操作**:
+
+```bash
+> SET key value
+OK
+> GET key
+"value"
+> EXISTS key
+(integer) 1
+> STRLEN key
+(integer) 5
+> DEL key
+(integer) 1
+> GET key
+(nil)
+```
+
+**批量设置**:
+
+```bash
+> MSET key1 value1 key2 value2
+OK
+> MGET key1 key2 # 批量获取多个 key 对应的 value
+1) "value1"
+2) "value2"
+```
+
+**计数器(字符串的内容为整数的时候可以使用):**
+
+```bash
+> SET number 1
+OK
+> INCR number # 将 key 中储存的数字值增一
+(integer) 2
+> GET number
+"2"
+> DECR number # 将 key 中储存的数字值减一
+(integer) 1
+> GET number
+"1"
+```
+
+**设置过期时间(默认为永不过期)**:
+
+```bash
+> EXPIRE key 60
+(integer) 1
+> SETEX key 60 value # 设置值并设置过期时间
+OK
+> TTL key
+(integer) 56
+```
+
+### 应用场景
+
+**需要存储常规数据的场景**
+
+- 举例:缓存 session、token、图片地址、序列化后的对象(相比较于 Hash 存储更节省内存)。
+- 相关命令:`SET`、`GET`。
+
+**需要计数的场景**
+
+- 举例:用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数。
+- 相关命令:`SET`、`GET`、 `INCR`、`DECR` 。
+
+**分布式锁**
+
+利用 `SETNX key value` 命令可以实现一个最简易的分布式锁(存在一些缺陷,通常不建议这样实现分布式锁)。
+
+## List(列表)
+
+### 介绍
+
+Redis 中的 List 其实就是链表数据结构的实现。我在 [线性数据结构 :数组、链表、栈、队列](https://javaguide.cn/cs-basics/data-structure/linear-data-structure.html) 这篇文章中详细介绍了链表这种数据结构,我这里就不多做介绍了。
+
+许多高级编程语言都内置了链表的实现比如 Java 中的 `LinkedList`,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 List 的实现为一个 **双向链表**,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
+
+
+
+### 常用命令
+
+| 命令 | 介绍 |
+| --------------------------- | ------------------------------------------ |
+| RPUSH key value1 value2 ... | 在指定列表的尾部(右边)添加一个或多个元素 |
+| LPUSH key value1 value2 ... | 在指定列表的头部(左边)添加一个或多个元素 |
+| LSET key index value | 将指定列表索引 index 位置的值设置为 value |
+| LPOP key | 移除并获取指定列表的第一个元素(最左边) |
+| RPOP key | 移除并获取指定列表的最后一个元素(最右边) |
+| LLEN key | 获取列表元素数量 |
+| LRANGE key start end | 获取列表 start 和 end 之间 的元素 |
+
+更多 Redis List 命令以及详细使用指南,请查看 Redis 官网对应的介绍: 。
+
+**通过 `RPUSH/LPOP` 或者 `LPUSH/RPOP`实现队列**:
+
+```bash
+> RPUSH myList value1
+(integer) 1
+> RPUSH myList value2 value3
+(integer) 3
+> LPOP myList
+"value1"
+> LRANGE myList 0 1
+1) "value2"
+2) "value3"
+> LRANGE myList 0 -1
+1) "value2"
+2) "value3"
+```
+
+**通过 `RPUSH/RPOP`或者`LPUSH/LPOP` 实现栈**:
+
+```bash
+> RPUSH myList2 value1 value2 value3
+(integer) 3
+> RPOP myList2 # 将 list的最右边的元素取出
+"value3"
+```
+
+我专门画了一个图方便大家理解 `RPUSH` , `LPOP` , `lpush` , `RPOP` 命令:
+
+
+
+**通过 `LRANGE` 查看对应下标范围的列表元素**:
+
+```bash
+> RPUSH myList value1 value2 value3
+(integer) 3
+> LRANGE myList 0 1
+1) "value1"
+2) "value2"
+> LRANGE myList 0 -1
+1) "value1"
+2) "value2"
+3) "value3"
+```
+
+通过 `LRANGE` 命令,你可以基于 List 实现分页查询,性能非常高!
+
+**通过 `LLEN` 查看链表长度**:
+
+```bash
+> LLEN myList
+(integer) 3
+```
+
+### 应用场景
+
+**信息流展示**
+
+- 举例:最新文章、最新动态。
+- 相关命令:`LPUSH`、`LRANGE`。
+
+**消息队列**
+
+Redis List 数据结构可以用来做消息队列,只是功能过于简单且存在很多缺陷,不建议这样做。
+
+相对来说,Redis 5.0 新增加的一个数据结构 `Stream` 更适合做消息队列一些,只是功能依然非常简陋。和专业的消息队列相比,还是有很多欠缺的地方比如消息丢失和堆积问题不好解决。
+
+## Hash(哈希)
+
+### 介绍
+
+Redis 中的 Hash 是一个 String 类型的 field-value(键值对) 的映射表,特别适合用于存储对象,后续操作的时候,你可以直接修改这个对象中的某些字段的值。
+
+Hash 类似于 JDK1.8 前的 `HashMap`,内部实现也差不多(数组 + 链表)。不过,Redis 的 Hash 做了更多优化。
+
+
+
+### 常用命令
+
+| 命令 | 介绍 |
+| ----------------------------------------- | -------------------------------------------------------- |
+| HSET key field value | 设置指定哈希表中指定字段的值 |
+| HSETNX key field value | 只有指定字段不存在时设置指定字段的值 |
+| HMSET key field1 value1 field2 value2 ... | 同时将一个或多个 field-value (域-值)对设置到指定哈希表中 |
+| HGET key field | 获取指定哈希表中指定字段的值 |
+| HMGET key field1 field2 ... | 获取指定哈希表中一个或者多个指定字段的值 |
+| HGETALL key | 获取指定哈希表中所有的键值对 |
+| HEXISTS key field | 查看指定哈希表中指定的字段是否存在 |
+| HDEL key field1 field2 ... | 删除一个或多个哈希表字段 |
+| HLEN key | 获取指定哈希表中字段的数量 |
+| HINCRBY key field increment | 对指定哈希中的指定字段做运算操作(正数为加,负数为减) |
+
+更多 Redis Hash 命令以及详细使用指南,请查看 Redis 官网对应的介绍: 。
+
+**模拟对象数据存储**:
+
+```bash
+> HMSET userInfoKey name "guide" description "dev" age 24
+OK
+> HEXISTS userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。
+(integer) 1
+> HGET userInfoKey name # 获取存储在哈希表中指定字段的值。
+"guide"
+> HGET userInfoKey age
+"24"
+> HGETALL userInfoKey # 获取在哈希表中指定 key 的所有字段和值
+1) "name"
+2) "guide"
+3) "description"
+4) "dev"
+5) "age"
+6) "24"
+> HSET userInfoKey name "GuideGeGe"
+> HGET userInfoKey name
+"GuideGeGe"
+> HINCRBY userInfoKey age 2
+(integer) 26
+```
+
+### 应用场景
+
+**对象数据存储场景**
+
+- 举例:用户信息、商品信息、文章信息、购物车信息。
+- 相关命令:`HSET` (设置单个字段的值)、`HMSET`(设置多个字段的值)、`HGET`(获取单个字段的值)、`HMGET`(获取多个字段的值)。
+
+## Set(集合)
+
+### 介绍
+
+Redis 中的 Set 类型是一种无序集合,集合中的元素没有先后顺序但都唯一,有点类似于 Java 中的 `HashSet` 。当你需要存储一个列表数据,又不希望出现重复数据时,Set 是一个很好的选择,并且 Set 提供了判断某个元素是否在一个 Set 集合内的重要接口,这个也是 List 所不能提供的。
+
+你可以基于 Set 轻易实现交集、并集、差集的操作,比如你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。这样的话,Set 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
+
+
+
+### 常用命令
+
+| 命令 | 介绍 |
+| ------------------------------------- | ----------------------------------------- |
+| SADD key member1 member2 ... | 向指定集合添加一个或多个元素 |
+| SMEMBERS key | 获取指定集合中的所有元素 |
+| SCARD key | 获取指定集合的元素数量 |
+| SISMEMBER key member | 判断指定元素是否在指定集合中 |
+| SINTER key1 key2 ... | 获取给定所有集合的交集 |
+| SINTERSTORE destination key1 key2 ... | 将给定所有集合的交集存储在 destination 中 |
+| SUNION key1 key2 ... | 获取给定所有集合的并集 |
+| SUNIONSTORE destination key1 key2 ... | 将给定所有集合的并集存储在 destination 中 |
+| SDIFF key1 key2 ... | 获取给定所有集合的差集 |
+| SDIFFSTORE destination key1 key2 ... | 将给定所有集合的差集存储在 destination 中 |
+| SPOP key count | 随机移除并获取指定集合中一个或多个元素 |
+| SRANDMEMBER key count | 随机获取指定集合中指定数量的元素 |
+
+更多 Redis Set 命令以及详细使用指南,请查看 Redis 官网对应的介绍: 。
+
+**基本操作**:
+
+```bash
+> SADD mySet value1 value2
+(integer) 2
+> SADD mySet value1 # 不允许有重复元素,因此添加失败
+(integer) 0
+> SMEMBERS mySet
+1) "value1"
+2) "value2"
+> SCARD mySet
+(integer) 2
+> SISMEMBER mySet value1
+(integer) 1
+> SADD mySet2 value2 value3
+(integer) 2
+```
+
+- `mySet` : `value1`、`value2` 。
+- `mySet2`:`value2`、`value3` 。
+
+**求交集**:
+
+```bash
+> SINTERSTORE mySet3 mySet mySet2
+(integer) 1
+> SMEMBERS mySet3
+1) "value2"
+```
+
+**求并集**:
+
+```bash
+> SUNION mySet mySet2
+1) "value3"
+2) "value2"
+3) "value1"
+```
+
+**求差集**:
+
+```bash
+> SDIFF mySet mySet2 # 差集是由所有属于 mySet 但不属于 A 的元素组成的集合
+1) "value1"
+```
+
+### 应用场景
+
+**需要存放的数据不能重复的场景**
+
+- 举例:网站 UV 统计(数据量巨大的场景还是 `HyperLogLog`更适合一些)、文章点赞、动态点赞等场景。
+- 相关命令:`SCARD`(获取集合数量) 。
+
+
+
+**需要获取多个数据源交集、并集和差集的场景**
+
+- 举例:共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集)、订阅号推荐(差集+交集) 等场景。
+- 相关命令:`SINTER`(交集)、`SINTERSTORE` (交集)、`SUNION` (并集)、`SUNIONSTORE`(并集)、`SDIFF`(差集)、`SDIFFSTORE` (差集)。
+
+
+
+**需要随机获取数据源中的元素的场景**
+
+- 举例:抽奖系统、随机点名等场景。
+- 相关命令:`SPOP`(随机获取集合中的元素并移除,适合不允许重复中奖的场景)、`SRANDMEMBER`(随机获取集合中的元素,适合允许重复中奖的场景)。
+
+## Sorted Set(有序集合)
+
+### 介绍
+
+Sorted Set 类似于 Set,但和 Set 相比,Sorted Set 增加了一个权重参数 `score`,使得集合中的元素能够按 `score` 进行有序排列,还可以通过 `score` 的范围来获取元素的列表。有点像是 Java 中 `HashMap` 和 `TreeSet` 的结合体。
+
+
+
+### 常用命令
+
+| 命令 | 介绍 |
+| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
+| ZADD key score1 member1 score2 member2 ... | 向指定有序集合添加一个或多个元素 |
+| ZCARD KEY | 获取指定有序集合的元素数量 |
+| ZSCORE key member | 获取指定有序集合中指定元素的 score 值 |
+| ZINTERSTORE destination numkeys key1 key2 ... | 将给定所有有序集合的交集存储在 destination 中,对相同元素对应的 score 值进行 SUM 聚合操作,numkeys 为集合数量 |
+| ZUNIONSTORE destination numkeys key1 key2 ... | 求并集,其它和 ZINTERSTORE 类似 |
+| ZDIFFSTORE destination numkeys key1 key2 ... | 求差集,其它和 ZINTERSTORE 类似 |
+| ZRANGE key start end | 获取指定有序集合 start 和 end 之间的元素(score 从低到高) |
+| ZREVRANGE key start end | 获取指定有序集合 start 和 end 之间的元素(score 从高到底) |
+| ZREVRANK key member | 获取指定有序集合中指定元素的排名(score 从大到小排序) |
+
+更多 Redis Sorted Set 命令以及详细使用指南,请查看 Redis 官网对应的介绍: 。
+
+**基本操作**:
+
+```bash
+> ZADD myZset 2.0 value1 1.0 value2
+(integer) 2
+> ZCARD myZset
+2
+> ZSCORE myZset value1
+2.0
+> ZRANGE myZset 0 1
+1) "value2"
+2) "value1"
+> ZREVRANGE myZset 0 1
+1) "value1"
+2) "value2"
+> ZADD myZset2 4.0 value2 3.0 value3
+(integer) 2
+
+```
+
+- `myZset` : `value1`(2.0)、`value2`(1.0) 。
+- `myZset2`:`value2` (4.0)、`value3`(3.0) 。
+
+**获取指定元素的排名**:
+
+```bash
+> ZREVRANK myZset value1
+0
+> ZREVRANK myZset value2
+1
+```
+
+**求交集**:
+
+```bash
+> ZINTERSTORE myZset3 2 myZset myZset2
+1
+> ZRANGE myZset3 0 1 WITHSCORES
+value2
+5
+```
+
+**求并集**:
+
+```bash
+> ZUNIONSTORE myZset4 2 myZset myZset2
+3
+> ZRANGE myZset4 0 2 WITHSCORES
+value1
+2
+value3
+3
+value2
+5
+```
+
+**求差集**:
+
+```bash
+> ZDIFF 2 myZset myZset2 WITHSCORES
+value1
+2
+```
+
+### 应用场景
+
+**需要随机获取数据源中的元素根据某个权重进行排序的场景**
+
+- 举例:各种排行榜比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、王者荣耀中的段位排行榜、话题热度排行榜等等。
+- 相关命令:`ZRANGE` (从小到大排序)、 `ZREVRANGE` (从大到小排序)、`ZREVRANK` (指定元素排名)。
+
+
+
+[《Java 面试指北》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7) 的「技术面试题篇」就有一篇文章详细介绍如何使用 Sorted Set 来设计制作一个排行榜。
+
+
+
+**需要存储的数据有优先级或者重要程度的场景** 比如优先级任务队列。
+
+- 举例:优先级任务队列。
+- 相关命令:`ZRANGE` (从小到大排序)、 `ZREVRANGE` (从大到小排序)、`ZREVRANK` (指定元素排名)。
+
+## 总结
+
+| 数据类型 | 说明 |
+| -------------------------------- | ------------------------------------------------- |
+| String | 一种二进制安全的数据结构,可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的 base64 编码或者解码或者图片的路径)、序列化后的对象。 |
+| List | Redis 的 List 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 |
+| Set | 一个 String 类型的 field-value(键值对) 的映射表,特别适合用于存储对象,后续操作的时候,你可以直接修改这个对象中的某些字段的值。 |
+| Hash | 无序集合,集合中的元素没有先后顺序但都唯一,有点类似于 Java 中的 `HashSet` 。 |
+| Zset | 和 Set 相比,Sorted Set 增加了一个权重参数 `score`,使得集合中的元素能够按 `score` 进行有序排列,还可以通过 `score` 的范围来获取元素的列表。有点像是 Java 中 `HashMap` 和 `TreeSet` 的结合体。 |
+
+## 参考
+
+- Redis Data Structures: 。
+- Redis Commands: 。
+- Redis Data types tutorial: 。
+- Redis 存储对象信息是用 Hash 还是 String :
diff --git a/docs/database/redis/redis-data-structures-02.md b/docs/database/redis/redis-data-structures-02.md
new file mode 100644
index 0000000000000000000000000000000000000000..7b090c7dcbff5304adfb7b18df5a9bcb2199da2b
--- /dev/null
+++ b/docs/database/redis/redis-data-structures-02.md
@@ -0,0 +1,216 @@
+---
+title: Redis 3 种特殊数据结构详解
+category: 数据库
+tag:
+ - Redis
+head:
+ - - meta
+ - name: keywords
+ content: Redis常见数据结构
+ - - meta
+ - name: description
+ content: Redis特殊数据结构总结:HyperLogLogs(基数统计)、Bitmap (位存储)、Geospatial (地理位置)。
+---
+
+除了 5 种基本的数据结构之外,Redis 还支持 3 种特殊的数据结构:Bitmap、HyperLogLog、GEO。
+
+## Bitmap
+
+### 介绍
+
+Bitmap 存储的是连续的二进制数字(0 和 1),通过 Bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 Bitmap 本身会极大的节省储存空间。
+
+你可以将 Bitmap 看作是一个存储二进制数字(0 和 1)的数组,数组中每个元素的下标叫做 offset(偏移量)。
+
+
+
+### 常用命令
+
+| 命令 | 介绍 |
+| ------------------------------------- | ---------------------------------------------------------------- |
+| SETBIT key offset value | 设置指定 offset 位置的值 |
+| GETBIT key offset | 获取指定 offset 位置的值 |
+| BITCOUNT key start end | 获取 start 和 end 之前值为 1 的元素个数 |
+| BITOP operation destkey key1 key2 ... | 对一个或多个 Bitmap 进行运算,可用运算符有 AND, OR, XOR 以及 NOT |
+
+**Bitmap 基本操作演示**:
+
+```bash
+# SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位
+> SETBIT mykey 7 1
+(integer) 0
+> SETBIT mykey 7 0
+(integer) 1
+> GETBIT mykey 7
+(integer) 0
+> SETBIT mykey 6 1
+(integer) 0
+> SETBIT mykey 8 1
+(integer) 0
+# 通过 bitcount 统计被被设置为 1 的位的数量。
+> BITCOUNT mykey
+(integer) 2
+```
+
+### 应用场景
+
+**需要保存状态信息(0/1 即可表示)的场景**
+
+- 举例:用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
+- 相关命令:`SETBIT`、`GETBIT`、`BITCOUNT`、`BITOP`。
+
+## HyperLogLog
+
+### 介绍
+
+HyperLogLog 是一种有名的基数计数概率算法 ,基于 LogLog Counting(LLC)优化改进得来,并不是 Redis 特有的,Redis 只是实现了这个算法并提供了一些开箱即用的 API。
+
+Redis 提供的 HyperLogLog 占用空间非常非常小,只需要 12k 的空间就能存储接近`2^64`个不同元素。这是真的厉害,这就是数学的魅力么!并且,Redis 对 HyperLogLog 的存储结构做了优化,采用两种方式计数:
+
+- **稀疏矩阵**:计数较少的时候,占用空间很小。
+- **稠密矩阵**:计数达到某个阈值的时候,占用 12k 的空间。
+
+Redis 官方文档中有对应的详细说明:
+
+
+
+基数计数概率算法为了节省内存并不会直接存储元数据,而是通过一定的概率统计方法预估基数值(集合中包含元素的个数)。因此, HyperLogLog 的计数结果并不是一个精确值,存在一定的误差(标准误差为 `0.81%` )。
+
+
+
+HyperLogLog 的使用非常简单,但原理非常复杂。HyperLogLog 的原理以及在 Redis 中的实现可以看这篇文章:[HyperLogLog 算法的原理讲解以及 Redis 是如何应用它的](https://juejin.cn/post/6844903785744056333) 。
+
+再推荐一个可以帮助理解 HyperLogLog 原理的工具:[Sketch of the Day: HyperLogLog — Cornerstone of a Big Data Infrastructure](http://content.research.neustar.biz/blog/hll.html) 。
+
+### 常用命令
+
+HyperLogLog 相关的命令非常少,最常用的也就 3 个。
+
+| 命令 | 介绍 |
+| ----------------------------------------- | -------------------------------------------------------------------------------- |
+| PFADD key element1 element2 ... | 添加一个或多个元素到 HyperLogLog 中 |
+| PFCOUNT key1 key2 | 获取一个或者多个 HyperLogLog 的唯一计数。 |
+| PFMERGE destkey sourcekey1 sourcekey2 ... | 将多个 HyperLogLog 合并到 destkey 中,destkey 会结合多个源,算出对应的唯一计数。 |
+
+**HyperLogLog 基本操作演示**:
+
+```bash
+> PFADD hll foo bar zap
+(integer) 1
+> PFADD hll zap zap zap
+(integer) 0
+> PFADD hll foo bar
+(integer) 0
+> PFCOUNT hll
+(integer) 3
+> PFADD some-other-hll 1 2 3
+(integer) 1
+> PFCOUNT hll some-other-hll
+(integer) 6
+> PFMERGE desthll hll some-other-hll
+"OK"
+> PFCOUNT desthll
+(integer) 6
+```
+
+### 应用场景
+
+**数量量巨大(百万、千万级别以上)的计数场景**
+
+- 举例:热门网站每日/每周/每月访问 ip 数统计、热门帖子 uv 统计、
+- 相关命令:`PFADD`、`PFCOUNT` 。
+
+## Geospatial index
+
+### 介绍
+
+Geospatial index(地理空间索引,简称 GEO) 主要用于存储地理位置信息,基于 Sorted Set 实现。
+
+通过 GEO 我们可以轻松实现两个位置距离的计算、获取指定位置附近的元素等功能。
+
+
+
+### 常用命令
+
+| 命令 | 介绍 |
+| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
+| GEOADD key longitude1 latitude1 member1 ... | 添加一个或多个元素对应的经纬度信息到 GEO 中 |
+| GEOPOS key member1 member2 ... | 返回给定元素的经纬度信息 |
+| GEODIST key member1 member2 M/KM/FT/MI | 返回两个给定元素之间的距离 |
+| GEORADIUS key longitude latitude radius distance | 获取指定位置附近 distance 范围内的其他元素,支持 ASC(由近到远)、DESC(由远到近)、Count(数量) 等参数 |
+| GEORADIUSBYMEMBER key member radius distance | 类似于 GEORADIUS 命令,只是参照的中心点是 GEO 中的元素 |
+
+**基本操作**:
+
+```bash
+> GEOADD personLocation 116.33 39.89 user1 116.34 39.90 user2 116.35 39.88 user3
+3
+> GEOPOS personLocation user1
+116.3299986720085144
+39.89000061669732844
+> GEODIST personLocation user1 user2 km
+1.4018
+```
+
+通过 Redis 可视化工具查看 `personLocation` ,果不其然,底层就是 Sorted Set。
+
+GEO 中存储的地理位置信息的经纬度数据通过 GeoHash 算法转换成了一个整数,这个整数作为 Sorted Set 的 score(权重参数)使用。
+
+
+
+**获取指定位置范围内的其他元素**:
+
+```bash
+> GEORADIUS personLocation 116.33 39.87 3 km
+user3
+user1
+> GEORADIUS personLocation 116.33 39.87 2 km
+> GEORADIUS personLocation 116.33 39.87 5 km
+user3
+user1
+user2
+> GEORADIUSBYMEMBER personLocation user1 5 km
+user3
+user1
+user2
+> GEORADIUSBYMEMBER personLocation user1 2 km
+user1
+user2
+```
+
+`GEORADIUS` 命令的底层原理解析可以看看阿里的这篇文章:[Redis 到底是怎么实现“附近的人”这个功能的呢?](https://juejin.cn/post/6844903966061363207) 。
+
+**移除元素**:
+
+GEO 底层是 Sorted Set ,你可以对 GEO 使用 Sorted Set 相关的命令。
+
+```bash
+> ZREM personLocation user1
+1
+> ZRANGE personLocation 0 -1
+user3
+user2
+> ZSCORE personLocation user2
+4069879562983946
+```
+
+### 应用场景
+
+**需要管理使用地理空间数据的场景**
+
+- 举例:附近的人。
+- 相关命令: `GEOADD`、`GEORADIUS`、`GEORADIUSBYMEMBER` 。
+
+## 总结
+
+| 数据类型 | 说明 |
+| ---------------- | ------------------------------------------------------------ |
+| Bitmap | 你可以将 Bitmap 看作是一个存储二进制数字(0 和 1)的数组,数组中每个元素的下标叫做 offset(偏移量)。通过 Bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 Bitmap 本身会极大的节省储存空间。 |
+| HyperLogLog | Redis 提供的 HyperLogLog 占用空间非常非常小,只需要 12k 的空间就能存储接近`2^64`个不同元素。不过,HyperLogLog 的计数结果并不是一个精确值,存在一定的误差(标准误差为 `0.81%` )。 |
+| Geospatial index | Geospatial index(地理空间索引,简称 GEO) 主要用于存储地理位置信息,基于 Sorted Set 实现。 |
+
+## 参考
+
+- Redis Data Structures:https://redis.com/redis-enterprise/data-structures/ 。
+- 《Redis 深度历险:核心原理与应用实践》1.6 四两拨千斤——HyperLogLog
+- 布隆过滤器,位图,HyperLogLog:https://hogwartsrico.github.io/2020/06/08/BloomFilter-HyperLogLog-BitMap/index.html
diff --git a/docs/database/redis/redis-memory-fragmentation.md b/docs/database/redis/redis-memory-fragmentation.md
new file mode 100644
index 0000000000000000000000000000000000000000..799e2131accd2d48e3206ca8c729acd0abb4abdc
--- /dev/null
+++ b/docs/database/redis/redis-memory-fragmentation.md
@@ -0,0 +1,122 @@
+---
+title: Redis内存碎片详解
+category: 数据库
+tag:
+ - Redis
+---
+
+## 什么是内存碎片?
+
+你可以将内存碎片简单地理解为那些不可用的空闲内存。
+
+举个例子:操作系统为你分配了 32 字节的连续内存空间,而你存储数据实际只需要使用 24 字节内存空间,那这多余出来的 8 字节内存空间如果后续没办法再被分配存储其他数据的话,就可以被称为内存碎片。
+
+
+
+Redis 内存碎片虽然不会影响 Redis 性能,但是会增加内存消耗。
+
+## 为什么会有 Redis 内存碎片?
+
+Redis 内存碎片产生比较常见的 2 个原因:
+
+**1、Redis 存储存储数据的时候向操作系统申请的内存空间可能会大于数据实际需要的存储空间。**
+
+以下是这段 Redis 官方的原话:
+
+> To store user keys, Redis allocates at most as much memory as the `maxmemory` setting enables (however there are small extra allocations possible).
+
+Redis 使用 `zmalloc` 方法(Redis 自己实现的内存分配方法)进行内存分配的时候,除了要分配 `size` 大小的内存之外,还会多分配 `PREFIX_SIZE` 大小的内存。
+
+`zmalloc` 方法源码如下(源码地址:https://github.com/antirez/redis-tools/blob/master/zmalloc.c):
+
+```java
+void *zmalloc(size_t size) {
+ // 分配指定大小的内存
+ void *ptr = malloc(size+PREFIX_SIZE);
+ if (!ptr) zmalloc_oom_handler(size);
+#ifdef HAVE_MALLOC_SIZE
+ update_zmalloc_stat_alloc(zmalloc_size(ptr));
+ return ptr;
+#else
+ *((size_t*)ptr) = size;
+ update_zmalloc_stat_alloc(size+PREFIX_SIZE);
+ return (char*)ptr+PREFIX_SIZE;
+#endif
+}
+```
+
+另外,Redis 可以使用多种内存分配器来分配内存( libc、jemalloc、tcmalloc),默认使用 [jemalloc](https://github.com/jemalloc/jemalloc),而 jemalloc 按照一系列固定的大小(8 字节、16 字节、32 字节......)来分配内存的。jemalloc 划分的内存单元如下图所示:
+
+
+
+当程序申请的内存最接近某个固定值时,jemalloc 会给它分配相应大小的空间,就比如说程序需要申请 17 字节的内存,jemalloc 会直接给它分配 32 字节的内存,这样会导致有 15 字节内存的浪费。不过,jemalloc 专门针对内存碎片问题做了优化,一般不会存在过度碎片化的问题。
+
+**2、频繁修改 Redis 中的数据也会产生内存碎片。**
+
+当 Redis 中的某个数据删除时,Redis 通常不会轻易释放内存给操作系统。
+
+这个在 Redis 官方文档中也有对应的原话:
+
+
+
+文档地址:https://redis.io/topics/memory-optimization 。
+
+## 如何查看 Redis 内存碎片的信息?
+
+使用 `info memory` 命令即可查看 Redis 内存相关的信息。下图中每个参数具体的含义,Redis 官方文档有详细的介绍:https://redis.io/commands/INFO 。
+
+
+
+Redis 内存碎片率的计算公式:`mem_fragmentation_ratio` (内存碎片率)= `used_memory_rss` (操作系统实际分配给 Redis 的物理内存空间大小)/ `used_memory`(Redis 内存分配器为了存储数据实际申请使用的内存空间大小)
+
+也就是说,`mem_fragmentation_ratio` (内存碎片率)的值越大代表内存碎片率越严重。
+
+一定不要误认为`used_memory_rss` 减去 `used_memory`值就是内存碎片的大小!!!这不仅包括内存碎片,还包括其他进程开销,以及共享库、堆栈等的开销。
+
+很多小伙伴可能要问了:“多大的内存碎片率才是需要清理呢?”。
+
+通常情况下,我们认为 `mem_fragmentation_ratio > 1.5` 的话才需要清理内存碎片。 `mem_fragmentation_ratio > 1.5` 意味着你使用 Redis 存储实际大小 2G 的数据需要使用大于 3G 的内存。
+
+如果想要快速查看内存碎片率的话,你还可以通过下面这个命令:
+
+```bash
+> redis-cli -p 6379 info | grep mem_fragmentation_ratio
+```
+
+另外,内存碎片率可能存在小于 1 的情况。这种情况我在日常使用中还没有遇到过,感兴趣的小伙伴可以看看这篇文章 [故障分析 | Redis 内存碎片率太低该怎么办?- 爱可生开源社区](https://mp.weixin.qq.com/s/drlDvp7bfq5jt2M5pTqJCw) 。
+
+## 如何清理 Redis 内存碎片?
+
+Redis4.0-RC3 版本以后自带了内存整理,可以避免内存碎片率过大的问题。
+
+直接通过 `config set` 命令将 `activedefrag` 配置项设置为 `yes` 即可。
+
+```bash
+config set activedefrag yes
+```
+
+具体什么时候清理需要通过下面两个参数控制:
+
+```bash
+# 内存碎片占用空间达到 500mb 的时候开始清理
+config set active-defrag-ignore-bytes 500mb
+# 内存碎片率大于 1.5 的时候开始清理
+config set active-defrag-threshold-lower 50
+```
+
+通过 Redis 自动内存碎片清理机制可能会对 Redis 的性能产生影响,我们可以通过下面两个参数来减少对 Redis 性能的影响:
+
+```bash
+# 内存碎片清理所占用 CPU 时间的比例不低于 20%
+config set active-defrag-cycle-min 20
+# 内存碎片清理所占用 CPU 时间的比例不高于 50%
+config set active-defrag-cycle-max 50
+```
+
+另外,重启节点可以做到内存碎片重新整理。如果你采用的是高可用架构的 Redis 集群的话,你可以将碎片率过高的主节点转换为从节点,以便进行安全重启。
+
+## 参考
+
+- Redis 官方文档:https://redis.io/topics/memory-optimization
+- Redis 核心技术与实战 - 极客时间 - 删除数据后,为什么内存占用率还是很高?:https://time.geekbang.org/column/article/289140
+- Redis 源码解析——内存分配:
diff --git a/docs/database/redis/redis-persistence.md b/docs/database/redis/redis-persistence.md
new file mode 100644
index 0000000000000000000000000000000000000000..499994cb8e4e076104fbd3adae64424d4881e249
--- /dev/null
+++ b/docs/database/redis/redis-persistence.md
@@ -0,0 +1,197 @@
+---
+title: Redis持久化机制详解
+category: 数据库
+tag:
+ - Redis
+head:
+ - - meta
+ - name: keywords
+ content: Redis持久化机制详解
+ - - meta
+ - name: description
+ content: Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持 3 种持久化方式:快照(snapshotting,RDB)、只追加文件(append-only file, AOF)、RDB 和 AOF 的混合持久化(Redis 4.0 新增)。
+---
+
+使用缓存的时候,我们经常需要对内存中的数据进行持久化也就是将内存中的数据写入到硬盘中。大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了做数据同步(比如 Redis 集群的主从节点通过 RDB 文件同步数据)。
+
+Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持 3 种持久化方式:
+
+- 快照(snapshotting,RDB)
+- 只追加文件(append-only file, AOF)
+- RDB 和 AOF 的混合持久化(Redis 4.0 新增)
+
+官方文档地址:https://redis.io/topics/persistence 。
+
+
+
+## RDB 持久化
+
+### 什么是 RDB 持久化?
+
+Redis 可以通过创建快照来获得存储在内存里面的数据在 **某个时间点** 上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
+
+快照持久化是 Redis 默认采用的持久化方式,在 `redis.conf` 配置文件中默认有此下配置:
+
+```clojure
+save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发bgsave命令创建快照。
+
+save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发bgsave命令创建快照。
+
+save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发bgsave命令创建快照。
+```
+
+### RDB 创建快照时会阻塞主线程吗?
+
+Redis 提供了两个命令来生成 RDB 快照文件:
+
+- `save` : 同步保存操作,会阻塞 Redis 主线程;
+- `bgsave` : fork 出一个子进程,子进程执行,不会阻塞 Redis 主线程,默认选项。
+
+> 这里说 Redis 主线程而不是主进程的主要是因为 Redis 启动之后主要是通过单线程的方式完成主要的工作。如果你想将其描述为 Redis 主进程,也没毛病。
+
+## AOF 持久化
+
+### 什么是 AOF 持久化?
+
+与快照持久化相比,AOF 持久化的实时性更好。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化(Redis 6.0 之后已经默认是开启了),可以通过 `appendonly` 参数开启:
+
+```bash
+appendonly yes
+```
+
+开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 `server.aof_buf` 中,然后再写入到 AOF 文件中(此时还在系统内核缓存区未同步到磁盘),最后再根据持久化方式( `fsync`策略)的配置来决定何时将系统内核缓存区的数据同步到硬盘中的。
+
+只有同步到磁盘中才算持久化保存了,否则依然存在数据丢失的风险,比如说:系统内核缓存区的数据还未同步,磁盘机器就宕机了,那这部分数据就算丢失了。
+
+AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 `dir` 参数设置的,默认的文件名是 `appendonly.aof`。
+
+### AOF 工作基本流程是怎样的?
+
+AOF 持久化功能的实现可以简单分为 5 步:
+
+1. **命令追加(append)**:所有的写命令会追加到 AOF 缓冲区中。
+2. **文件写入(write)**:将 AOF 缓冲区的数据写入到 AOF 文件中。这一步需要调用`write`函数(系统调用),`write`将数据写入到了系统内核缓冲区之后直接返回了(延迟写)。注意!!!此时并没有同步到磁盘。
+3. **文件同步(fsync)**:AOF 缓冲区根据对应的持久化方式( `fsync` 策略)向硬盘做同步操作。这一步需要调用 `fsync` 函数(系统调用), `fsync` 针对单个文件操作,对其进行强制硬盘同步,`fsync` 将阻塞直到写入磁盘完成后返回,保证了数据持久化。
+4. **文件重写(rewrite)**:随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
+5. **重启加载(load)**:当 Redis 重启时,可以加载 AOF 文件进行数据恢复。
+
+> Linux 系统直接提供了一些函数用于对文件和设备进行访问和控制,这些函数被称为 **系统调用(syscall)**。
+
+这里对上面提到的一些 Linux 系统调用再做一遍解释:
+
+- `write`:写入系统内核缓冲区之后直接返回(仅仅是写到缓冲区),不会立即同步到硬盘。虽然提高了效率,但也带来了数据丢失的风险。同步硬盘操作通常依赖于系统调度机制,Linux 内核通常为 30s 同步一次,具体值取决于写出的数据量和 I/O 缓冲区的状态。
+- `fsync`:`fsync`用于强制刷新系统内核缓冲区(同步到到磁盘),确保写磁盘操作结束才会返回。
+
+AOF 工作流程图如下:
+
+
+
+### AOF 持久化方式有哪些?
+
+在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是:
+
+1. `appendfsync always`:主线程调用 `write` 执行写操作后,后台线程( `aof_fsync` 线程)立即会调用 `fsync` 函数同步 AOF 文件(刷盘),`fsync` 完成后线程返回,这样会严重降低 Redis 的性能(`write` + `fsync`)。
+2. `appendfsync everysec`:主线程调用 `write` 执行写操作后立即返回,由后台线程( `aof_fsync` 线程)每秒钟调用 `fsync` 函数(系统调用)同步一次 AOF 文件(`write`+`fsync`,`fsync`间隔为 1 秒)
+3. `appendfsync no`:主线程调用 `write` 执行写操作后立即返回,让操作系统决定何时进行同步,Linux 下一般为 30 秒一次(`write`但不`fsync`,`fsync` 的时机由操作系统决定)。
+
+可以看出:**这 3 种持久化方式的主要区别在于 `fsync` 同步 AOF 文件的时机(刷盘)**。
+
+为了兼顾数据和写入性能,可以考虑 `appendfsync everysec` 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能受到的影响较小。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
+
+从 Redis 7.0.0 开始,Redis 使用了 **Multi Part AOF** 机制。顾名思义,Multi Part AOF 就是将原来的单个 AOF 文件拆分成多个 AOF 文件。在 Multi Part AOF 中,AOF 文件被分为三种类型,分别为:
+
+- BASE:表示基础 AOF 文件,它一般由子进程通过重写产生,该文件最多只有一个。
+- INCR:表示增量 AOF 文件,它一般会在 AOFRW 开始执行时被创建,该文件可能存在多个。
+- HISTORY:表示历史 AOF 文件,它由 BASE 和 INCR AOF 变化而来,每次 AOFRW 成功完成时,本次 AOFRW 之前对应的 BASE 和 INCR AOF 都将变为 HISTORY,HISTORY 类型的 AOF 会被 Redis 自动删除。
+
+Multi Part AOF 不是重点,了解即可,详细介绍可以看看阿里开发者的[Redis 7.0 Multi Part AOF 的设计和实现](https://zhuanlan.zhihu.com/p/467217082) 这篇文章。
+
+**相关 issue**:[Redis 的 AOF 方式 #783](https://github.com/Snailclimb/JavaGuide/issues/783)。
+
+### AOF 为什么是在执行完命令之后记录日志?
+
+关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。
+
+
+
+**为什么是在执行完命令之后记录日志呢?**
+
+- 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
+- 在命令执行完之后再记录,不会阻塞当前的命令执行。
+
+这样也带来了风险(我在前面介绍 AOF 持久化的时候也提到过):
+
+- 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
+- 可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)。
+
+### AOF 重写了解吗?
+
+当 AOF 变得太大时,Redis 能够在后台自动重写 AOF 产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。
+
+
+
+> AOF 重写(rewrite) 是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。
+
+由于 AOF 重写会进行大量的写入操作,为了避免对 Redis 正常处理命令请求造成影响,Redis 将 AOF 重写程序放到子进程里执行。
+
+AOF 文件重写期间,Redis 还会维护一个 **AOF 重写缓冲区**,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。
+
+开启 AOF 重写功能,可以调用 `BGREWRITEAOF` 命令手动执行,也可以设置下面两个配置项,让程序自动决定触发时机:
+
+- `auto-aof-rewrite-min-size`:如果 AOF 文件大小小于该值,则不会触发 AOF 重写。默认值为 64 MB;
+- `auto-aof-rewrite-percentage`:执行 AOF 重写时,当前 AOF 大小(aof_current_size)和上一次重写时 AOF 大小(aof_base_size)的比值。如果当前 AOF 文件大小增加了这个百分比值,将触发 AOF 重写。将此值设置为 0 将禁用自动 AOF 重写。默认值为 100。
+
+Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。
+
+Redis 7.0 版本之后,AOF 重写机制得到了优化改进。下面这段内容摘自阿里开发者的[从 Redis7.0 发布看 Redis 的过去与未来](https://mp.weixin.qq.com/s/RnoPPL7jiFSKkx3G4p57Pg) 这篇文章。
+
+> AOF 重写期间的增量数据如何处理一直是个问题,在过去写期间的增量数据需要在内存中保留,写结束后再把这部分增量数据写入新的 AOF 文件中以保证数据完整性。可以看出来 AOF 写会额外消耗内存和磁盘 IO,这也是 Redis AOF 写的痛点,虽然之前也进行过多次改进但是资源消耗的本质问题一直没有解决。
+>
+> 阿里云的 Redis 企业版在最初也遇到了这个问题,在内部经过多次迭代开发,实现了 Multi-part AOF 机制来解决,同时也贡献给了社区并随此次 7.0 发布。具体方法是采用 base(全量数据)+inc(增量数据)独立文件存储的方式,彻底解决内存和 IO 资源的浪费,同时也支持对历史 AOF 文件的保存管理,结合 AOF 文件中的时间信息还可以实现 PITR 按时间点恢复(阿里云企业版 Tair 已支持),这进一步增强了 Redis 的数据可靠性,满足用户数据回档等需求。
+
+**相关 issue**:[Redis AOF 重写描述不准确 #1439](https://github.com/Snailclimb/JavaGuide/issues/1439)。
+
+### AOF 校验机制了解吗?
+
+AOF 校验机制是 Redis 在启动时对 AOF 文件进行检查,以判断文件是否完整,是否有损坏或者丢失的数据。这个机制的原理其实非常简单,就是通过使用一种叫做 **校验和(checksum)** 的数字来验证 AOF 文件。这个校验和是通过对整个 AOF 文件内容进行 CRC64 算法计算得出的数字。如果文件内容发生了变化,那么校验和也会随之改变。因此,Redis 在启动时会比较计算出的校验和与文件末尾保存的校验和(计算的时候会把最后一行保存校验和的内容给忽略点),从而判断 AOF 文件是否完整。如果发现文件有问题,Redis 就会拒绝启动并提供相应的错误信息。AOF 校验机制十分简单有效,可以提高 Redis 数据的可靠性。
+
+类似地,RDB 文件也有类似的校验机制来保证 RDB 文件的正确性,这里就不重复进行介绍了。
+
+## Redis 4.0 对于持久化机制做了什么优化?
+
+由于 RDB 和 AOF 各有优势,于是,Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。
+
+如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
+
+官方文档地址:https://redis.io/topics/persistence
+
+
+
+## 如何选择 RDB 和 AOF?
+
+关于 RDB 和 AOF 的优缺点,官网上面也给了比较详细的说明[Redis persistence](https://redis.io/docs/manual/persistence/),这里结合自己的理解简单总结一下。
+
+**RDB 比 AOF 优秀的地方**:
+
+- RDB 文件存储的内容是经过压缩的二进制数据, 保存着某个时间点的数据集,文件很小,适合做数据的备份,灾难恢复。AOF 文件存储的是每一次写命令,类似于 MySQL 的 binlog 日志,通常会比 RDB 文件大很多。当 AOF 变得太大时,Redis 能够在后台自动重写 AOF。新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。不过, Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。
+- 使用 RDB 文件恢复数据,直接解析还原数据即可,不需要一条一条地执行命令,速度非常快。而 AOF 则需要依次执行每个写命令,速度非常慢。也就是说,与 AOF 相比,恢复大数据集的时候,RDB 速度更快。
+
+**AOF 比 RDB 优秀的地方**:
+
+- RDB 的数据安全性不如 AOF,没有办法实时或者秒级持久化数据。生成 RDB 文件的过程是比较繁重的, 虽然 BGSAVE 子进程写入 RDB 文件的工作不会阻塞主线程,但会对机器的 CPU 资源和内存资源产生影响,严重的情况下甚至会直接把 Redis 服务干宕机。AOF 支持秒级数据丢失(取决 fsync 策略,如果是 everysec,最多丢失 1 秒的数据),仅仅是追加命令到 AOF 文件,操作轻量。
+- RDB 文件是以特定的二进制格式保存的,并且在 Redis 版本演进中有多个版本的 RDB,所以存在老版本的 Redis 服务不兼容新版本的 RDB 格式的问题。
+- AOF 以一种易于理解和解析的格式包含所有操作的日志。你可以轻松地导出 AOF 文件进行分析,你也可以直接操作 AOF 文件来解决一些问题。比如,如果执行`FLUSHALL`命令意外地刷新了所有内容后,只要 AOF 文件没有被重写,删除最新命令并重启即可恢复之前的状态。
+
+**综上**:
+
+- Redis 保存的数据丢失一些也没什么影响的话,可以选择使用 RDB。
+- 不建议单独使用 AOF,因为时不时地创建一个 RDB 快照可以进行数据库备份、更快的重启以及解决 AOF 引擎错误。
+- 如果保存的数据要求安全性比较高的话,建议同时开启 RDB 和 AOF 持久化或者开启 RDB 和 AOF 混合持久化。
+
+## 参考
+
+- 《Redis 设计与实现》
+- Redis persistence - Redis 官方文档:https://redis.io/docs/management/persistence/
+- The difference between AOF and RDB persistence:https://www.sobyte.net/post/2022-04/redis-rdb-and-aof/
+- Redis AOF 持久化详解 - 程序员历小冰:http://remcarpediem.net/article/376c55d8/
+- Redis RDB 与 AOF 持久化 · Analyze:https://wingsxdu.com/posts/database/redis/rdb-and-aof/
diff --git a/docs/database/redis/redis-questions-01.md b/docs/database/redis/redis-questions-01.md
new file mode 100644
index 0000000000000000000000000000000000000000..088bf2dbbe7b04458ca2e5f64073a6b685d046f7
--- /dev/null
+++ b/docs/database/redis/redis-questions-01.md
@@ -0,0 +1,601 @@
+---
+title: Redis常见面试题总结(上)
+category: 数据库
+tag:
+ - Redis
+head:
+ - - meta
+ - name: keywords
+ content: Redis基础,Redis常见数据结构,Redis线程模型,Redis内存管理,Redis事务,Redis性能优化
+ - - meta
+ - name: description
+ content: 一篇文章总结Redis常见的知识点和面试题,涵盖Redis基础、Redis常见数据结构、Redis线程模型、Redis内存管理、Redis事务、Redis性能优化等内容。
+---
+
+
+
+## Redis 基础
+
+### 什么是 Redis?
+
+[Redis](https://redis.io/) 是一个基于 C 语言开发的开源数据库(BSD 许可),与传统数据库不同的是 Redis 的数据是存在内存中的(内存数据库),读写速度非常快,被广泛应用于缓存方向。并且,Redis 存储的是 KV 键值对数据。
+
+为了满足不同的业务场景,Redis 内置了多种数据类型实现(比如 String、Hash、Sorted Set、Bitmap、HyperLogLog、GEO)。并且,Redis 还支持事务、持久化、Lua 脚本、多种开箱即用的集群方案(Redis Sentinel、Redis Cluster)。
+
+Redis 没有外部依赖,Linux 和 OS X 是 Redis 开发和测试最多的两个操作系统,官方推荐生产环境使用 Linux 部署 Redis。
+
+个人学习的话,你可以自己本机安装 Redis 或者通过 Redis 官网提供的[在线 Redis 环境](https://try.redis.io/)(少部分命令无法使用)来实际体验 Redis。
+
+
+
+全世界有非常多的网站使用到了 Redis ,[techstacks.io](https://techstacks.io/) 专门维护了一个[使用 Redis 的热门站点列表](https://techstacks.io/tech/redis) ,感兴趣的话可以看看。
+
+### Redis 为什么这么快?
+
+Redis 内部做了非常多的性能优化,比较重要的有下面 3 点:
+
+1. Redis 基于内存,内存的访问速度是磁盘的上千倍;
+2. Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用(Redis 线程模式后面会详细介绍到);
+3. Redis 内置了多种优化过后的数据结构实现,性能非常高。
+
+下面这张图片总结的挺不错的,分享一下,出自 [Why is Redis so fast?](https://twitter.com/alexxubyte/status/1498703822528544770) 。
+
+
+
+### 分布式缓存常见的技术选型方案有哪些?
+
+分布式缓存的话,比较老牌同时也是使用的比较多的还是 **Memcached** 和 **Redis**。不过,现在基本没有看过还有项目使用 **Memcached** 来做缓存,都是直接用 **Redis**。
+
+Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来,随着 Redis 的发展,大家慢慢都转而使用更加强大的 Redis 了。
+
+另外,腾讯也开源了一款类似于 Redis 的分布式高性能 KV 存储数据库,基于知名的开源项目 [RocksDB](https://github.com/facebook/rocksdb) 作为存储引擎 ,100% 兼容 Redis 协议和 Redis4.0 所有数据模型,名为 [Tendis](https://github.com/Tencent/Tendis)。
+
+关于 Redis 和 Tendis 的对比,腾讯官方曾经发过一篇文章:[Redis vs Tendis:冷热混合存储版架构揭秘](https://mp.weixin.qq.com/s/MeYkfOIdnU6LYlsGb24KjQ) ,可以简单参考一下。
+
+从这个项目的 GitHub 提交记录可以看出,Tendis 开源版几乎已经没有被维护更新了,加上其关注度并不高,使用的公司也比较少。因此,不建议你使用 Tendis 来实现分布式缓存。
+
+### 说一下 Redis 和 Memcached 的区别和共同点
+
+现在公司一般都是用 Redis 来实现缓存,而且 Redis 自身也越来越强大了!不过,了解 Redis 和 Memcached 的区别和共同点,有助于我们在做相应的技术选型的时候,能够做到有理有据!
+
+**共同点**:
+
+1. 都是基于内存的数据库,一般都用来当做缓存使用。
+2. 都有过期策略。
+3. 两者的性能都非常高。
+
+**区别**:
+
+1. **Redis 支持更丰富的数据类型(支持更复杂的应用场景)**。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
+2. **Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memcached 把数据全部存在内存之中。**
+3. **Redis 有灾难恢复机制。** 因为可以把缓存中的数据持久化到磁盘上。
+4. **Redis 在服务器内存使用完之后,可以将不用的数据放到磁盘上。但是,Memcached 在服务器内存使用完之后,就会直接报异常。**
+5. **Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的。**
+6. **Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。** (Redis 6.0 针对网络数据的读写引入了多线程)
+7. **Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且,Redis 支持更多的编程语言。**
+8. **Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。**
+
+相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。
+
+### 为什么要用 Redis/为什么要用缓存?
+
+下面我们主要从“高性能”和“高并发”这两点来回答这个问题。
+
+**1、高性能**
+
+假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。但是,如果说,用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心地将该用户访问的数据存在缓存中。
+
+**这样有什么好处呢?** 那就是保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。
+
+**2、高并发**
+
+一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 Redis 的情况,Redis 集群的话会更高)。
+
+> QPS(Query Per Second):服务器每秒可以执行的查询次数;
+
+由此可见,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高了系统整体的并发。
+
+### 常见的缓存读写策略有哪些?
+
+关于常见的缓存读写策略的详细介绍,可以看我写的这篇文章:[3 种常用的缓存读写策略详解](https://javaguide.cn/database/redis/3-commonly-used-cache-read-and-write-strategies.html) 。
+
+## Redis 应用
+
+### Redis 除了做缓存,还能做什么?
+
+- **分布式锁**:通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。关于 Redis 实现分布式锁的详细介绍,可以看我写的这篇文章:[分布式锁详解](https://javaguide.cn/distributed-system/distributed-lock.html) 。
+- **限流**:一般是通过 Redis + Lua 脚本的方式来实现限流。相关阅读:[《我司用了 6 年的 Redis 分布式限流器,可以说是非常厉害了!》](https://mp.weixin.qq.com/s/kyFAWH3mVNJvurQDt4vchA)。
+- **消息队列**:Redis 自带的 list 数据结构可以作为一个简单的队列使用。Redis 5.0 中增加的 stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。
+- **延时队列**:Redisson 内置了延时队列(基于 sorted set 实现的)。
+- **分布式 Session** :利用 string 或者 hash 保存 Session 数据,所有的服务器都可以访问。
+- **复杂业务场景**:通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 bitmap 统计活跃用户、通过 sorted set 维护排行榜。
+- ......
+
+### 如何基于 Redis 实现分布式锁?
+
+关于 Redis 实现分布式锁的详细介绍,可以看我写的这篇文章:[分布式锁详解](https://javaguide.cn/distributed-system/distributed-lock.html) 。
+
+### Redis 可以做消息队列么?
+
+> 实际项目中也没见谁使用 Redis 来做消息队列,对于这部分知识点大家了解就好了。
+
+先说结论:可以是可以,但不建议使用 Redis 来做消息队列。和专业的消息队列相比,还是有很多欠缺的地方。
+
+**Redis 2.0 之前,如果想要使用 Redis 来做消息队列的话,只能通过 List 来实现。**
+
+通过 `RPUSH/LPOP` 或者 `LPUSH/RPOP`即可实现简易版消息队列:
+
+```bash
+# 生产者生产消息
+> RPUSH myList msg1 msg2
+(integer) 2
+> RPUSH myList msg3
+(integer) 3
+# 消费者消费消息
+> LPOP myList
+"msg1"
+```
+
+不过,通过 `RPUSH/LPOP` 或者 `LPUSH/RPOP`这样的方式存在性能问题,我们需要不断轮询去调用 `RPOP` 或 `LPOP` 来消费消息。当 List 为空时,大部分的轮询的请求都是无效请求,这种方式大量浪费了系统资源。
+
+因此,Redis 还提供了 `BLPOP`、`BRPOP` 这种阻塞式读取的命令(带 B-Bloking 的都是阻塞式),并且还支持一个超时参数。如果 List 为空,Redis 服务端不会立刻返回结果,它会等待 List 中有新数据后在返回或者是等待最多一个超时时间后返回空。如果将超时时间设置为 0 时,即可无限等待,直到弹出消息
+
+```bash
+# 超时时间为 10s
+# 如果有数据立刻返回,否则最多等待10秒
+> BRPOP myList 10
+null
+```
+
+**List 实现消息队列功能太简单,像消息确认机制等功能还需要我们自己实现,最要命的是没有广播机制,消息也只能被消费一次。**
+
+**Redis 2.0 引入了发布订阅 (pub/sub) 功能,解决了 List 实现消息队列没有广播机制的问题。**
+
+
+
+pub/sub 中引入了一个概念叫 **channel(频道)**,发布订阅机制的实现就是基于这个 channel 来做的。
+
+pub/sub 涉及发布者(Publisher)和订阅者(Subscriber,也叫消费者)两个角色:
+
+- 发布者通过 `PUBLISH` 投递消息给指定 channel。
+- 订阅者通过`SUBSCRIBE`订阅它关心的 channel。并且,订阅者可以订阅一个或者多个 channel。
+
+我们这里启动 3 个 Redis 客户端来简单演示一下:
+
+
+
+pub/sub 既能单播又能广播,还支持 channel 的简单正则匹配。不过,消息丢失(客户端断开连接或者 Redis 宕机都会导致消息丢失)、消息堆积(发布者发布消息的时候不会管消费者的具体消费能力如何)等问题依然没有一个比较好的解决办法。
+
+为此,Redis 5.0 新增加的一个数据结构 `Stream` 来做消息队列。`Stream` 支持:
+
+- 发布 / 订阅模式
+- 按照消费者组进行消费
+- 消息持久化( RDB 和 AOF)
+
+`Stream` 使用起来相对要麻烦一些,这里就不演示了。而且,`Stream` 在实际使用中依然会有一些小问题不太好解决比如在 Redis 发生故障恢复后不能保证消息至少被消费一次。
+
+综上,和专业的消息队列相比,使用 Redis 来实现消息队列还是有很多欠缺的地方比如消息丢失和堆积问题不好解决。因此,我们通常建议不要使用 Redis 来做消息队列,你完全可以选择市面上比较成熟的一些消息队列比如 RocketMQ、Kafka。
+
+相关阅读:[Redis 消息队列发展历程 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/gCUT5TcCQRAxYkTJfTRjJw)。
+
+## Redis 数据结构
+
+> 关于 Redis 5 种基础数据结构和 3 种特殊数据结构的详细介绍请看下面这两篇文章:
+>
+> - [Redis 5 种基本数据结构详解](https://javaguide.cn/database/redis/redis-data-structures-01.html)
+> - [Redis 3 种特殊数据结构详解](https://javaguide.cn/database/redis/redis-data-structures-02.html)
+
+### Redis 常用的数据结构有哪些?
+
+- **5 种基础数据结构**:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
+- **3 种特殊数据结构**:HyperLogLogs(基数统计)、Bitmap (位存储)、Geospatial (地理位置)。
+
+### String 的应用场景有哪些?
+
+String 是 Redis 中最简单同时也是最常用的一个数据结构。String 是一种二进制安全的数据结构,可以用来存储任何类型的数据比如字符串、整数、浮点数、图片(图片的 base64 编码或者解码或者图片的路径)、序列化后的对象。
+
+String 的常见应用场景如下:
+
+- 常规数据(比如 session、token、序列化后的对象、图片的路径)的缓存;
+- 计数比如用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数;
+- 分布式锁(利用 `SETNX key value` 命令可以实现一个最简易的分布式锁);
+- ......
+
+关于 String 的详细介绍请看这篇文章:[Redis 5 种基本数据结构详解](https://javaguide.cn/database/redis/redis-data-structures-01.html)。
+
+### String 还是 Hash 存储对象数据更好呢?
+
+- String 存储的是序列化后的对象数据,存放的是整个对象。Hash 是对对象的每个字段单独存储,可以获取部分字段的信息,也可以修改或者添加部分字段,节省网络流量。如果对象中某些字段需要经常变动或者经常需要单独查询对象中的个别字段信息,Hash 就非常适合。
+- String 存储相对来说更加节省内存,缓存相同数量的对象数据,String 消耗的内存约是 Hash 的一半。并且,存储具有多层嵌套的对象时也方便很多。如果系统对性能和资源消耗非常敏感的话,String 就非常适合。
+
+在绝大部分情况,我们建议使用 String 来存储对象数据即可!
+
+### String 的底层实现是什么?
+
+Redis 是基于 C 语言编写的,但 Redis 的 String 类型的底层实现并不是 C 语言中的字符串(即以空字符 `\0` 结尾的字符数组),而是自己编写了 [SDS](https://github.com/antirez/sds)(Simple Dynamic String,简单动态字符串) 来作为底层实现。
+
+SDS 最早是 Redis 作者为日常 C 语言开发而设计的 C 字符串,后来被应用到了 Redis 上,并经过了大量的修改完善以适合高性能操作。
+
+Redis7.0 的 SDS 的部分源码如下(https://github.com/redis/redis/blob/7.0/src/sds.h):
+
+```c
+/* Note: sdshdr5 is never used, we just access the flags byte directly.
+ * However is here to document the layout of type 5 SDS strings. */
+struct __attribute__ ((__packed__)) sdshdr5 {
+ unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
+ char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr8 {
+ uint8_t len; /* used */
+ uint8_t alloc; /* excluding the header and null terminator */
+ unsigned char flags; /* 3 lsb of type, 5 unused bits */
+ char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr16 {
+ uint16_t len; /* used */
+ uint16_t alloc; /* excluding the header and null terminator */
+ unsigned char flags; /* 3 lsb of type, 5 unused bits */
+ char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr32 {
+ uint32_t len; /* used */
+ uint32_t alloc; /* excluding the header and null terminator */
+ unsigned char flags; /* 3 lsb of type, 5 unused bits */
+ char buf[];
+};
+struct __attribute__ ((__packed__)) sdshdr64 {
+ uint64_t len; /* used */
+ uint64_t alloc; /* excluding the header and null terminator */
+ unsigned char flags; /* 3 lsb of type, 5 unused bits */
+ char buf[];
+};
+```
+
+通过源码可以看出,SDS 共有五种实现方式 SDS_TYPE_5(并未用到)、SDS_TYPE_8、SDS_TYPE_16、SDS_TYPE_32、SDS_TYPE_64,其中只有后四种实际用到。Redis 会根据初始化的长度决定使用哪种类型,从而减少内存的使用。
+
+| 类型 | 字节 | 位 |
+| -------- | ---- | --- |
+| sdshdr5 | < 1 | <8 |
+| sdshdr8 | 1 | 8 |
+| sdshdr16 | 2 | 16 |
+| sdshdr32 | 4 | 32 |
+| sdshdr64 | 8 | 64 |
+
+对于后四种实现都包含了下面这 4 个属性:
+
+- `len`:字符串的长度也就是已经使用的字节数
+- `alloc`:总共可用的字符空间大小,alloc-len 就是 SDS 剩余的空间大小
+- `buf[]`:实际存储字符串的数组
+- `flags`:低三位保存类型标志
+
+SDS 相比于 C 语言中的字符串有如下提升:
+
+1. **可以避免缓冲区溢出**:C 语言中的字符串被修改(比如拼接)时,一旦没有分配足够长度的内存空间,就会造成缓冲区溢出。SDS 被修改时,会先根据 len 属性检查空间大小是否满足要求,如果不满足,则先扩展至所需大小再进行修改操作。
+2. **获取字符串长度的复杂度较低**:C 语言中的字符串的长度通常是经过遍历计数来实现的,时间复杂度为 O(n)。SDS 的长度获取直接读取 len 属性即可,时间复杂度为 O(1)。
+3. **减少内存分配次数**:为了避免修改(增加/减少)字符串时,每次都需要重新分配内存(C 语言的字符串是这样的),SDS 实现了空间预分配和惰性空间释放两种优化策略。当 SDS 需要增加字符串时,Redis 会为 SDS 分配好内存,并且根据特定的算法分配多余的内存,这样可以减少连续执行字符串增长操作所需的内存重分配次数。当 SDS 需要减少字符串时,这部分内存不会立即被回收,会被记录下来,等待后续使用(支持手动释放,有对应的 API)。
+4. **二进制安全**:C 语言中的字符串以空字符 `\0` 作为字符串结束的标识,这存在一些问题,像一些二进制文件(比如图片、视频、音频)就可能包括空字符,C 字符串无法正确保存。SDS 使用 len 属性判断字符串是否结束,不存在这个问题。
+
+🤐 多提一嘴,很多文章里 SDS 的定义是下面这样的:
+
+```c
+struct sdshdr {
+ unsigned int len;
+ unsigned int free;
+ char buf[];
+};
+```
+
+这个也没错,Redis 3.2 之前就是这样定义的。后来,由于这种方式的定义存在问题,`len` 和 `free` 的定义用了 4 个字节,造成了浪费。Redis 3.2 之后,Redis 改进了 SDS 的定义,将其划分为了现在的 5 种类型。
+
+### 购物车信息用 String 还是 Hash 存储更好呢?
+
+由于购物车中的商品频繁修改和变动,购物车信息建议使用 Hash 存储:
+
+- 用户 id 为 key
+- 商品 id 为 field,商品数量为 value
+
+
+
+那用户购物车信息的维护具体应该怎么操作呢?
+
+- 用户添加商品就是往 Hash 里面增加新的 field 与 value;
+- 查询购物车信息就是遍历对应的 Hash;
+- 更改商品数量直接修改对应的 value 值(直接 set 或者做运算皆可);
+- 删除商品就是删除 Hash 中对应的 field;
+- 清空购物车直接删除对应的 key 即可。
+
+这里只是以业务比较简单的购物车场景举例,实际电商场景下,field 只保存一个商品 id 是没办法满足需求的。
+
+### 使用 Redis 实现一个排行榜怎么做?
+
+Redis 中有一个叫做 `sorted set` 的数据结构经常被用在各种排行榜的场景,比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、王者荣耀中的段位排行榜、话题热度排行榜等等。
+
+相关的一些 Redis 命令: `ZRANGE` (从小到大排序)、 `ZREVRANGE` (从大到小排序)、`ZREVRANK` (指定元素排名)。
+
+
+
+[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html) 的「技术面试题篇」就有一篇文章详细介绍如何使用 Sorted Set 来设计制作一个排行榜,感兴趣的小伙伴可以看看。
+
+
+
+### Set 的应用场景是什么?
+
+Redis 中 `Set` 是一种无序集合,集合中的元素没有先后顺序但都唯一,有点类似于 Java 中的 `HashSet` 。
+
+Set 的常见应用场景如下:
+
+- 存放的数据不能重复的场景:网站 UV 统计(数据量巨大的场景还是 `HyperLogLog`更适合一些)、文章点赞、动态点赞等等。
+- 需要获取多个数据源交集、并集和差集的场景:共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集)、订阅号推荐(差集+交集) 等等。
+- 需要随机获取数据源中的元素的场景:抽奖系统、随机点名等等。
+
+### 使用 Set 实现抽奖系统怎么做?
+
+如果想要使用 Set 实现一个简单的抽奖系统的话,直接使用下面这几个命令就可以了:
+
+- `SADD key member1 member2 ...`:向指定集合添加一个或多个元素。
+- `SPOP key count`:随机移除并获取指定集合中一个或多个元素,适合不允许重复中奖的场景。
+- `SRANDMEMBER key count` : 随机获取指定集合中指定数量的元素,适合允许重复中奖的场景。
+
+### 使用 Bitmap 统计活跃用户怎么做?
+
+Bitmap 存储的是连续的二进制数字(0 和 1),通过 Bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 Bitmap 本身会极大的节省储存空间。
+
+你可以将 Bitmap 看作是一个存储二进制数字(0 和 1)的数组,数组中每个元素的下标叫做 offset(偏移量)。
+
+
+
+如果想要使用 Bitmap 统计活跃用户的话,可以使用日期(精确到天)作为 key,然后用户 ID 为 offset,如果当日活跃过就设置为 1。
+
+初始化数据:
+
+```bash
+> SETBIT 20210308 1 1
+(integer) 0
+> SETBIT 20210308 2 1
+(integer) 0
+> SETBIT 20210309 1 1
+(integer) 0
+```
+
+统计 20210308~20210309 总活跃用户数:
+
+```bash
+> BITOP and desk1 20210308 20210309
+(integer) 1
+> BITCOUNT desk1
+(integer) 1
+```
+
+统计 20210308~20210309 在线活跃用户数:
+
+```bash
+> BITOP or desk2 20210308 20210309
+(integer) 1
+> BITCOUNT desk2
+(integer) 2
+```
+
+### 使用 HyperLogLog 统计页面 UV 怎么做?
+
+使用 HyperLogLog 统计页面 UV 主要需要用到下面这两个命令:
+
+- `PFADD key element1 element2 ...`:添加一个或多个元素到 HyperLogLog 中。
+- `PFCOUNT key1 key2`:获取一个或者多个 HyperLogLog 的唯一计数。
+
+1、将访问指定页面的每个用户 ID 添加到 `HyperLogLog` 中。
+
+```bash
+PFADD PAGE_1:UV USER1 USER2 ...... USERn
+```
+
+2、统计指定页面的 UV。
+
+```bash
+PFCOUNT PAGE_1:UV
+```
+
+## Redis 持久化机制(重要)
+
+Redis 持久化机制(RDB 持久化、AOF 持久化、RDB 和 AOF 的混合持久化) 相关的问题比较多,也比较重要,于是我单独抽了一篇文章来总结 Redis 持久化机制相关的知识点和问题:[Redis 持久化机制详解](https://javaguide.cn/database/redis/redis-persistence.html) 。
+
+## Redis 线程模型(重要)
+
+对于读写命令来说,Redis 一直是单线程模型。不过,在 Redis 4.0 版本之后引入了多线程来执行一些大键值对的异步删除操作, Redis 6.0 版本之后引入了多线程来处理网络请求(提高网络 IO 读写性能)。
+
+### Redis 单线程模型了解吗?
+
+**Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型** (Netty 的线程模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),这套事件处理模型对应的是 Redis 中的文件事件处理器(file event handler)。由于文件事件处理器(file event handler)是单线程方式运行的,所以我们一般都说 Redis 是单线程模型。
+
+《Redis 设计与实现》有一段话是如是介绍文件事件处理器的,我觉得写得挺不错。
+
+> Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。
+>
+> - 文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
+> - 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
+>
+> **虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字**,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性。
+
+**既然是单线程,那怎么监听大量的客户端连接呢?**
+
+Redis 通过 **IO 多路复用程序** 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。
+
+这样的好处非常明显:**I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗**(和 NIO 中的 `Selector` 组件很像)。
+
+文件事件处理器(file event handler)主要是包含 4 个部分:
+
+- 多个 socket(客户端连接)
+- IO 多路复用程序(支持多个客户端连接的关键)
+- 文件事件分派器(将 socket 关联到相应的事件处理器)
+- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
+
+
+
+相关阅读:[Redis 事件机制详解](http://remcarpediem.net/article/1aa2da89/) 。
+
+### Redis6.0 之前为什么不使用多线程?
+
+虽然说 Redis 是单线程模型,但是,实际上,**Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。**
+
+不过,Redis 4.0 增加的多线程主要是针对一些大键值对的删除操作的命令,使用这些命令就会使用主线程之外的其他线程来“异步处理”。
+
+为此,Redis 4.0 之后新增了`UNLINK`(可以看作是 `DEL` 的异步版本)、`FLUSHALL ASYNC`(清空所有数据库的所有 key,不仅仅是当前 `SELECT` 的数据库)、`FLUSHDB ASYNC`(清空当前 `SELECT` 数据库中的所有 key)等异步命令。
+
+
+
+大体上来说,Redis 6.0 之前主要还是单线程处理。
+
+**那 Redis6.0 之前为什么不使用多线程?** 我觉得主要原因有 3 点:
+
+- 单线程编程容易并且更容易维护;
+- Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
+- 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
+
+相关阅读:[为什么 Redis 选择单线程模型?](https://draveness.me/whys-the-design-redis-single-thread/) 。
+
+### Redis6.0 之后为何引入了多线程?
+
+**Redis6.0 引入多线程主要是为了提高网络 IO 读写性能**,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。
+
+虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。
+
+Redis6.0 的多线程默认是禁用的,只使用主线程。如需开启需要设置 IO 线程数 > 1,需要修改 redis 配置文件 `redis.conf`:
+
+```bash
+io-threads 4 #设置1的话只会开启主线程,官网建议4核的机器建议设置为2或3个线程,8核的建议设置为6个线程
+```
+
+另外:
+
+- io-threads 的个数一旦设置,不能通过 config 动态设置。
+- 当设置 ssl 后,io-threads 将不工作。
+
+开启多线程后,默认只会使用多线程进行 IO 写入 writes,即发送数据给客户端,如果需要开启多线程 IO 读取 reads,同样需要修改 redis 配置文件 `redis.conf` :
+
+```bash
+io-threads-do-reads yes
+```
+
+但是官网描述开启多线程读并不能有太大提升,因此一般情况下并不建议开启
+
+相关阅读:
+
+- [Redis 6.0 新特性-多线程连环 13 问!](https://mp.weixin.qq.com/s/FZu3acwK6zrCBZQ_3HoUgw)
+- [Redis 多线程网络模型全面揭秘](https://segmentfault.com/a/1190000039223696)(推荐)
+
+### Redis 后台线程了解吗?
+
+我们虽然经常说 Redis 是单线程模型(主要逻辑是单线程完成的),但实际还有一些后台线程用于执行一些比较耗时的操作:
+
+- 通过 `bio_close_file` 后台线程来释放 AOF / RDB 等过程中产生的临时文件资源。
+- 通过 `bio_aof_fsync` 后台线程调用 `fsync` 函数将系统内核缓冲区还未同步到到磁盘的数据强制刷到磁盘( AOF 文件)。
+- 通过 `bio_lazy_free`后台线程释放大对象(已删除)占用的内存空间.
+
+在`bio.h` 文件中有定义(Redis 6.0 版本,源码地址:https://github.com/redis/redis/blob/6.0/src/bio.h):
+
+```java
+#ifndef __BIO_H
+#define __BIO_H
+
+/* Exported API */
+void bioInit(void);
+void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3);
+unsigned long long bioPendingJobsOfType(int type);
+unsigned long long bioWaitStepOfType(int type);
+time_t bioOlderJobOfType(int type);
+void bioKillThreads(void);
+
+/* Background job opcodes */
+#define BIO_CLOSE_FILE 0 /* Deferred close(2) syscall. */
+#define BIO_AOF_FSYNC 1 /* Deferred AOF fsync. */
+#define BIO_LAZY_FREE 2 /* Deferred objects freeing. */
+#define BIO_NUM_OPS 3
+
+#endif
+```
+
+关于 Redis 后台线程的详细介绍可以查看 [Redis 6.0 后台线程有哪些?](https://juejin.cn/post/7102780434739626014) 这篇就文章。
+
+## Redis 内存管理
+
+### Redis 给缓存数据设置过期时间有啥用?
+
+一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间。为什么呢?
+
+因为内存是有限的,如果缓存中的所有数据都是一直保存的话,分分钟直接 Out of memory。
+
+Redis 自带了给缓存数据设置过期时间的功能,比如:
+
+```bash
+127.0.0.1:6379> expire key 60 # 数据在 60s 后过期
+(integer) 1
+127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
+OK
+127.0.0.1:6379> ttl key # 查看数据还有多久过期
+(integer) 56
+```
+
+注意:**Redis 中除了字符串类型有自己独有设置过期时间的命令 `setex` 外,其他方法都需要依靠 `expire` 命令来设置过期时间 。另外, `persist` 命令可以移除一个键的过期时间。**
+
+**过期时间除了有助于缓解内存的消耗,还有什么其他用么?**
+
+很多时候,我们的业务场景就是需要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在 1 分钟内有效,用户登录的 token 可能只在 1 天内有效。
+
+如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多。
+
+### Redis 是如何判断数据是否过期的呢?
+
+Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。
+
+
+
+过期字典是存储在 redisDb 这个结构里的:
+
+```c
+typedef struct redisDb {
+ ...
+
+ dict *dict; //数据库键空间,保存着数据库中所有键值对
+ dict *expires // 过期字典,保存着键的过期时间
+ ...
+} redisDb;
+```
+
+### 过期的数据的删除策略了解么?
+
+如果假设你设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢?
+
+常用的过期数据的删除策略就两个(重要!自己造缓存轮子的时候需要格外考虑的东西):
+
+1. **惰性删除**:只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
+2. **定期删除**:每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
+
+定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 **定期删除+惰性/懒汉式删除** 。
+
+但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory 了。
+
+怎么解决这个问题呢?答案就是:**Redis 内存淘汰机制。**
+
+### Redis 内存淘汰机制了解么?
+
+> 相关问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?
+
+Redis 提供 6 种数据淘汰策略:
+
+1. **volatile-lru(least recently used)**:从已设置过期时间的数据集(`server.db[i].expires`)中挑选最近最少使用的数据淘汰。
+2. **volatile-ttl**:从已设置过期时间的数据集(`server.db[i].expires`)中挑选将要过期的数据淘汰。
+3. **volatile-random**:从已设置过期时间的数据集(`server.db[i].expires`)中任意选择数据淘汰。
+4. **allkeys-lru(least recently used)**:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
+5. **allkeys-random**:从数据集(`server.db[i].dict`)中任意选择数据淘汰。
+6. **no-eviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
+
+4.0 版本后增加以下两种:
+
+7. **volatile-lfu(least frequently used)**:从已设置过期时间的数据集(`server.db[i].expires`)中挑选最不经常使用的数据淘汰。
+8. **allkeys-lfu(least frequently used)**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key。
+
+## 参考
+
+- 《Redis 开发与运维》
+- 《Redis 设计与实现》
+- Redis 命令手册:https://www.redis.com.cn/commands.html
+- WHY Redis choose single thread (vs multi threads): [https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153](https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153)
diff --git a/docs/database/redis/redis-questions-02.md b/docs/database/redis/redis-questions-02.md
new file mode 100644
index 0000000000000000000000000000000000000000..f3de97645d6e51660ff8ec9c8b9e06a5f0a38258
--- /dev/null
+++ b/docs/database/redis/redis-questions-02.md
@@ -0,0 +1,754 @@
+---
+title: Redis常见面试题总结(下)
+category: 数据库
+tag:
+ - Redis
+head:
+ - - meta
+ - name: keywords
+ content: Redis基础,Redis常见数据结构,Redis线程模型,Redis内存管理,Redis事务,Redis性能优化
+ - - meta
+ - name: description
+ content: 一篇文章总结Redis常见的知识点和面试题,涵盖Redis基础、Redis常见数据结构、Redis线程模型、Redis内存管理、Redis事务、Redis性能优化等内容。
+---
+
+## Redis 事务
+
+### 什么是 Redis 事务?
+
+你可以将 Redis 中的事务理解为:**Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。**
+
+Redis 事务实际开发中使用的非常少,功能比较鸡肋,不要将其和我们平时理解的关系型数据库的事务混淆了。
+
+除了不满足原子性和持久性之外,事务中的每条命令都会与 Redis 服务器进行网络交互,这是比较浪费资源的行为。明明一次批量执行多个命令就可以了,这种操作实在是看不懂。
+
+因此,Redis 事务是不建议在日常开发中使用的。
+
+### 如何使用 Redis 事务?
+
+Redis 可以通过 **`MULTI`,`EXEC`,`DISCARD` 和 `WATCH`** 等命令来实现事务(Transaction)功能。
+
+```bash
+> MULTI
+OK
+> SET PROJECT "JavaGuide"
+QUEUED
+> GET PROJECT
+QUEUED
+> EXEC
+1) OK
+2) "JavaGuide"
+```
+
+[`MULTI`](https://redis.io/commands/multi) 命令后可以输入多个命令,Redis 不会立即执行这些命令,而是将它们放到队列,当调用了 [`EXEC`](https://redis.io/commands/exec) 命令后,再执行所有的命令。
+
+这个过程是这样的:
+
+1. 开始事务(`MULTI`);
+2. 命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行);
+3. 执行事务(`EXEC`)。
+
+你也可以通过 [`DISCARD`](https://redis.io/commands/discard) 命令取消一个事务,它会清空事务队列中保存的所有命令。
+
+```bash
+> MULTI
+OK
+> SET PROJECT "JavaGuide"
+QUEUED
+> GET PROJECT
+QUEUED
+> DISCARD
+OK
+```
+
+你可以通过[`WATCH`](https://redis.io/commands/watch) 命令监听指定的 Key,当调用 `EXEC` 命令执行事务时,如果一个被 `WATCH` 命令监视的 Key 被 **其他客户端/Session** 修改的话,整个事务都不会被执行。
+
+```bash
+# 客户端 1
+> SET PROJECT "RustGuide"
+OK
+> WATCH PROJECT
+OK
+> MULTI
+OK
+> SET PROJECT "JavaGuide"
+QUEUED
+
+# 客户端 2
+# 在客户端 1 执行 EXEC 命令提交事务之前修改 PROJECT 的值
+> SET PROJECT "GoGuide"
+
+# 客户端 1
+# 修改失败,因为 PROJECT 的值被客户端2修改了
+> EXEC
+(nil)
+> GET PROJECT
+"GoGuide"
+```
+
+不过,如果 **WATCH** 与 **事务** 在同一个 Session 里,并且被 **WATCH** 监视的 Key 被修改的操作发生在事务内部,这个事务是可以被执行成功的(相关 issue:[WATCH 命令碰到 MULTI 命令时的不同效果](https://github.com/Snailclimb/JavaGuide/issues/1714))。
+
+事务内部修改 WATCH 监视的 Key:
+
+```bash
+> SET PROJECT "JavaGuide"
+OK
+> WATCH PROJECT
+OK
+> MULTI
+OK
+> SET PROJECT "JavaGuide1"
+QUEUED
+> SET PROJECT "JavaGuide2"
+QUEUED
+> SET PROJECT "JavaGuide3"
+QUEUED
+> EXEC
+1) OK
+2) OK
+3) OK
+127.0.0.1:6379> GET PROJECT
+"JavaGuide3"
+```
+
+事务外部修改 WATCH 监视的 Key:
+
+```bash
+> SET PROJECT "JavaGuide"
+OK
+> WATCH PROJECT
+OK
+> SET PROJECT "JavaGuide2"
+OK
+> MULTI
+OK
+> GET USER
+QUEUED
+> EXEC
+(nil)
+```
+
+Redis 官网相关介绍 [https://redis.io/topics/transactions](https://redis.io/topics/transactions) 如下:
+
+
+
+### Redis 事务支持原子性吗?
+
+Redis 的事务和我们平时理解的关系型数据库的事务不同。我们知道事务具有四大特性:**1. 原子性**,**2. 隔离性**,**3. 持久性**,**4. 一致性**。
+
+1. **原子性(Atomicity):** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
+2. **隔离性(Isolation):** 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
+3. **持久性(Durability):** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
+4. **一致性(Consistency):** 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
+
+Redis 事务在运行错误的情况下,除了执行过程中出现错误的命令外,其他命令都能正常执行。并且,Redis 事务是不支持回滚(roll back)操作的。因此,Redis 事务其实是不满足原子性的。
+
+Redis 官网也解释了自己为啥不支持回滚。简单来说就是 Redis 开发者们觉得没必要支持回滚,这样更简单便捷并且性能更好。Redis 开发者觉得即使命令执行错误也应该在开发过程中就被发现而不是生产过程中。
+
+
+
+**相关 issue** :
+
+- [issue#452: 关于 Redis 事务不满足原子性的问题](https://github.com/Snailclimb/JavaGuide/issues/452) 。
+- [Issue#491:关于 Redis 没有事务回滚?](https://github.com/Snailclimb/JavaGuide/issues/491)
+
+### Redis 事务支持持久性吗?
+
+Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持 3 种持久化方式:
+
+- 快照(snapshotting,RDB)
+- 只追加文件(append-only file, AOF)
+- RDB 和 AOF 的混合持久化(Redis 4.0 新增)
+
+与 RDB 持久化相比,AOF 持久化的实时性更好。在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( `fsync`策略),它们分别是:
+
+```bash
+appendfsync always #每次有数据修改发生时都会调用fsync函数同步AOF文件,fsync完成后线程返回,这样会严重降低Redis的速度
+appendfsync everysec #每秒钟调用fsync函数同步一次AOF文件
+appendfsync no #让操作系统决定何时进行同步,一般为30秒一次
+```
+
+AOF 持久化的`fsync`策略为 no、everysec 时都会存在数据丢失的情况 。always 下可以基本是可以满足持久性要求的,但性能太差,实际开发过程中不会使用。
+
+因此,Redis 事务的持久性也是没办法保证的。
+
+### 如何解决 Redis 事务的缺陷?
+
+Redis 从 2.6 版本开始支持执行 Lua 脚本,它的功能和事务非常类似。我们可以利用 Lua 脚本来批量执行多条 Redis 命令,这些 Redis 命令会被提交到 Redis 服务器一次性执行完成,大幅减小了网络开销。
+
+一段 Lua 脚本可以视作一条命令执行,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰。
+
+不过,如果 Lua 脚本运行时出错并中途结束,出错之后的命令是不会被执行的。并且,出错之前执行的命令是无法被撤销的,无法实现类似关系型数据库执行失败可以回滚的那种原子性效果。因此, **严格来说的话,通过 Lua 脚本来批量执行 Redis 命令实际也是不完全满足原子性的。**
+
+如果想要让 Lua 脚本中的命令全部执行,必须保证语句语法和命令都是对的。
+
+另外,Redis 7.0 新增了 [Redis functions](https://redis.io/docs/manual/programmability/functions-intro/) 特性,你可以将 Redis functions 看作是比 Lua 更强大的脚本。
+
+## Redis 性能优化(重要)
+
+除了下面介绍的内容之外,再推荐两篇不错的文章:
+
+- [你的 Redis 真的变慢了吗?性能优化如何做 - 阿里开发者](https://mp.weixin.qq.com/s/nNEuYw0NlYGhuKKKKoWfcQ)
+- [Redis 常见阻塞原因总结 - JavaGuide](https://javaguide.cn/database/redis/redis-common-blocking-problems-summary.html)
+
+### 使用批量操作减少网络传输
+
+一个 Redis 命令的执行可以简化为以下 4 步:
+
+1. 发送命令
+2. 命令排队
+3. 命令执行
+4. 返回结果
+
+其中,第 1 步和第 4 步耗费时间之和称为 **Round Trip Time (RTT,往返时间)** ,也就是数据在网络上传输的时间。
+
+使用批量操作可以减少网络传输次数,进而有效减小网络开销,大幅减少 RTT。
+
+另外,除了能减少 RTT 之外,发送一次命令的 socket I/O 成本也比较高(涉及上下文切换,存在`read()`和`write()`系统调用),批量操作还可以减少 socket I/O 成本。这个在官方对 pipeline 的介绍中有提到:https://redis.io/docs/manual/pipelining/ 。
+
+#### 原生批量操作命令
+
+Redis 中有一些原生支持批量操作的命令,比如:
+
+- `MGET`(获取一个或多个指定 key 的值)、`MSET`(设置一个或多个指定 key 的值)、
+- `HMGET`(获取指定哈希表中一个或者多个指定字段的值)、`HMSET`(同时将一个或多个 field-value 对设置到指定哈希表中)、
+- `SADD`(向指定集合添加一个或多个元素)
+- ......
+
+不过,在 Redis 官方提供的分片集群解决方案 Redis Cluster 下,使用这些原生批量操作命令可能会存在一些小问题需要解决。就比如说 `MGET` 无法保证所有的 key 都在同一个 **hash slot**(哈希槽)上,`MGET`可能还是需要多次网络传输,原子操作也无法保证了。不过,相较于非批量操作,还是可以节省不少网络传输次数。
+
+整个步骤的简化版如下(通常由 Redis 客户端实现,无需我们自己再手动实现):
+
+1. 找到 key 对应的所有 hash slot;
+2. 分别向对应的 Redis 节点发起 `MGET` 请求获取数据;
+3. 等待所有请求执行结束,重新组装结果数据,保持跟入参 key 的顺序一致,然后返回结果。
+
+如果想要解决这个多次网络传输的问题,比较常用的办法是自己维护 key 与 slot 的关系。不过这样不太灵活,虽然带来了性能提升,但同样让系统复杂性提升。
+
+> Redis Cluster 并没有使用一致性哈希,采用的是 **哈希槽分区** ,每一个键值对都属于一个 **hash slot**(哈希槽) 。当客户端发送命令请求的时候,需要先根据 key 通过上面的计算公示找到的对应的哈希槽,然后再查询哈希槽和节点的映射关系,即可找到目标 Redis 节点。
+>
+> 我在 [Redis 集群详解(付费)](https://javaguide.cn/database/redis/redis-cluster.html) 这篇文章中详细介绍了 Redis Cluster 这部分的内容,感兴趣地可以看看。
+
+#### pipeline
+
+对于不支持批量操作的命令,我们可以利用 **pipeline(流水线)** 将一批 Redis 命令封装成一组,这些 Redis 命令会被一次性提交到 Redis 服务器,只需要一次网络传输。不过,需要注意控制一次批量操作的 **元素个数**(例如 500 以内,实际也和元素字节数有关),避免网络传输的数据量过大。
+
+与`MGET`、`MSET`等原生批量操作命令一样,pipeline 同样在 Redis Cluster 上使用会存在一些小问题。原因类似,无法保证所有的 key 都在同一个 **hash slot**(哈希槽)上。如果想要使用的话,客户端需要自己维护 key 与 slot 的关系。
+
+原生批量操作命令和 pipeline 的是有区别的,使用的时候需要注意:
+
+- 原生批量操作命令是原子操作,pipeline 是非原子操作。
+- pipeline 可以打包不同的命令,原生批量操作命令不可以。
+- 原生批量操作命令是 Redis 服务端支持实现的,而 pipeline 需要服务端和客户端的共同实现。
+
+顺带补充一下 pipeline 和 Redis 事务的对比:
+
+- 事务是原子操作,pipeline 是非原子操作。两个不同的事务不会同时运行,而 pipeline 可以同时以交错方式执行。
+- Redis 事务中每个命令都需要发送到服务端,而 Pipeline 只需要发送一次,请求次数更少。
+
+> 事务可以看作是一个原子操作,但其实并不满足原子性。当我们提到 Redis 中的原子操作时,主要指的是这个操作(比如事务、Lua 脚本)不会被其他操作(比如其他事务、Lua 脚本)打扰,并不能完全保证这个操作中的所有写命令要么都执行要么都不执行。这主要也是因为 Redis 是不支持回滚操作。
+
+
+
+另外,pipeline 不适用于执行顺序有依赖关系的一批命令。就比如说,你需要将前一个命令的结果给后续的命令使用,pipeline 就没办法满足你的需求了。对于这种需求,我们可以使用 **Lua 脚本** 。
+
+#### Lua 脚本
+
+Lua 脚本同样支持批量操作多条命令。一段 Lua 脚本可以视作一条命令执行,可以看作是 **原子操作** 。也就是说,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰,这是 pipeline 所不具备的。
+
+并且,Lua 脚本中支持一些简单的逻辑处理比如使用命令读取值并在 Lua 脚本中进行处理,这同样是 pipeline 所不具备的。
+
+不过, Lua 脚本依然存在下面这些缺陷:
+
+- 如果 Lua 脚本运行时出错并中途结束,之后的操作不会进行,但是之前已经发生的写操作不会撤销,所以即使使用了 Lua 脚本,也不能实现类似数据库回滚的原子性。
+- Redis Cluster 下 Lua 脚本的原子操作也无法保证了,原因同样是无法保证所有的 key 都在同一个 **hash slot**(哈希槽)上。
+
+### 大量 key 集中过期问题
+
+我在前面提到过:对于过期 key,Redis 采用的是 **定期删除+惰性/懒汉式删除** 策略。
+
+定期删除执行过程中,如果突然遇到大量过期 key 的话,客户端请求必须等待定期清理过期 key 任务线程执行完成,因为这个这个定期任务线程是在 Redis 主线程中执行的。这就导致客户端请求没办法被及时处理,响应速度会比较慢。
+
+**如何解决呢?** 下面是两种常见的方法:
+
+1. 给 key 设置随机过期时间。
+2. 开启 lazy-free(惰性删除/延迟释放) 。lazy-free 特性是 Redis 4.0 开始引入的,指的是让 Redis 采用异步方式延迟释放 key 使用的内存,将该操作交给单独的子线程处理,避免阻塞主线程。
+
+个人建议不管是否开启 lazy-free,我们都尽量给 key 设置随机过期时间。
+
+### Redis bigkey(大 Key)
+
+#### 什么是 bigkey?
+
+简单来说,如果一个 key 对应的 value 所占用的内存比较大,那这个 key 就可以看作是 bigkey。具体多大才算大呢?有一个不是特别精确的参考标准:string 类型的 value 超过 10 kb,复合类型的 value 包含的元素超过 5000 个(对于复合类型的 value 来说,不一定包含的元素越多,占用的内存就越多)。
+
+#### bigkey 有什么危害?
+
+bigkey 除了会消耗更多的内存空间和带宽,还会对性能造成比较大的影响。因此,我们应该尽量避免 Redis 中存在 bigkey。
+
+#### 如何发现 bigkey?
+
+**1、使用 Redis 自带的 `--bigkeys` 参数来查找。**
+
+```bash
+# redis-cli -p 6379 --bigkeys
+
+# Scanning the entire keyspace to find biggest keys as well as
+# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
+# per 100 SCAN commands (not usually needed).
+
+[00.00%] Biggest string found so far '"ballcat:oauth:refresh_auth:f6cdb384-9a9d-4f2f-af01-dc3f28057c20"' with 4437 bytes
+[00.00%] Biggest list found so far '"my-list"' with 17 items
+
+-------- summary -------
+
+Sampled 5 keys in the keyspace!
+Total key length in bytes is 264 (avg len 52.80)
+
+Biggest list found '"my-list"' has 17 items
+Biggest string found '"ballcat:oauth:refresh_auth:f6cdb384-9a9d-4f2f-af01-dc3f28057c20"' has 4437 bytes
+
+1 lists with 17 items (20.00% of keys, avg size 17.00)
+0 hashs with 0 fields (00.00% of keys, avg size 0.00)
+4 strings with 4831 bytes (80.00% of keys, avg size 1207.75)
+0 streams with 0 entries (00.00% of keys, avg size 0.00)
+0 sets with 0 members (00.00% of keys, avg size 0.00)
+0 zsets with 0 members (00.00% of keys, avg size 0.00
+```
+
+从这个命令的运行结果,我们可以看出:这个命令会扫描(Scan) Redis 中的所有 key ,会对 Redis 的性能有一点影响。并且,这种方式只能找出每种数据结构 top 1 bigkey(占用内存最大的 string 数据类型,包含元素最多的复合数据类型)。然而,一个 key 的元素多并不代表占用内存也多,需要我们根据具体的业务情况来进一步判断。
+
+在线上执行该命令时,为了降低对 Redis 的影响,需要指定 `-i` 参数控制扫描的频率。`redis-cli -p 6379 --bigkeys -i 3` 表示扫描过程中每次扫描后休息的时间间隔为 3 秒。
+
+**2、借助开源工具分析 RDB 文件。**
+
+通过分析 RDB 文件来找出 big key。这种方案的前提是你的 Redis 采用的是 RDB 持久化。
+
+网上有现成的代码/工具可以直接拿来使用:
+
+- [redis-rdb-tools](https://github.com/sripathikrishnan/redis-rdb-tools):Python 语言写的用来分析 Redis 的 RDB 快照文件用的工具
+- [rdb_bigkeys](https://github.com/weiyanwei412/rdb_bigkeys) : Go 语言写的用来分析 Redis 的 RDB 快照文件用的工具,性能更好。
+
+**3、借助公有云的 Redis 分析服务。**
+
+如果你用的是公有云的 Redis 服务的话,可以看看其是否提供了 key 分析功能(一般都提供了)。
+
+这里以阿里云 Redis 为例说明,它支持 bigkey 实时分析、发现,文档地址: 。
+
+
+
+#### 如何处理 bigkey?
+
+bigkey 的常见处理以及优化办法如下(这些方法可以配合起来使用):
+
+- **分割 bigkey**:将一个 bigkey 分割为多个小 key。这种方式需要修改业务层的代码,一般不推荐这样做。
+- **手动清理**:Redis 4.0+ 可以使用 `UNLINK` 命令来异步删除一个或多个指定的 key。Redis 4.0 以下可以考虑使用 `SCAN` 命令结合 `DEL` 命令来分批次删除。
+- **采用合适的数据结构**:比如使用 HyperLogLog 统计页面 UV。
+- **开启 lazy-free(惰性删除/延迟释放)** :lazy-free 特性是 Redis 4.0 开始引入的,指的是让 Redis 采用异步方式延迟释放 key 使用的内存,将该操作交给单独的子线程处理,避免阻塞主线程。
+
+### Redis hotkey(热 Key)
+
+#### 什么是 hotkey?
+
+简单来说,如果一个 key 的访问次数比较多且明显多于其他 key 的话,那这个 key 就可以看作是 hotkey。例如在 Redis 实例的每秒处理请求达到 5000 次,而其中某个 key 的每秒访问量就高达 2000 次,那这个 key 就可以看作是 hotkey。
+
+hotkey 出现的原因主要是某个热点数据访问量暴增,如重大的热搜事件、参与秒杀的商品。
+
+#### hotkey 有什么危害?
+
+处理 hotkey 会占用大量的 CPU 和带宽,可能会影响 Redis 实例对其他请求的正常处理。此外,如果突然访问 hotkey 的请求超出了 Redis 的处理能力,Redis 就会直接宕机。这种情况下,大量请求将落到后面的数据库上,可能会导致数据库崩溃。
+
+因此,hotkey 很可能成为系统性能的瓶颈点,需要单独对其进行优化,以确保系统的高可用性和稳定性。
+
+#### 如何发现 hotkey?
+
+**1、使用 Redis 自带的 `--hotkeys` 参数来查找。**
+
+Redis 4.0.3 版本中新增了 `hotkeys` 参数,该参数能够返回所有 key 的被访问次数。
+
+使用该方案的前提条件是 Redis Server 的 `maxmemory-policy` 参数设置为 LFU 算法,不然就会出现如下所示的错误。
+
+```bash
+# redis-cli -p 6379 --hotkeys
+
+# Scanning the entire keyspace to find hot keys as well as
+# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec
+# per 100 SCAN commands (not usually needed).
+
+Error: ERR An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.
+```
+
+Redis 中有两种 LFU 算法:
+
+1. **volatile-lfu(least frequently used)**:从已设置过期时间的数据集(`server.db[i].expires`)中挑选最不经常使用的数据淘汰。
+2. **allkeys-lfu(least frequently used)**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key。
+
+以下是配置文件 `redis.conf` 中的示例:
+
+```properties
+# 使用 volatile-lfu 策略
+maxmemory-policy volatile-lfu
+
+# 或者使用 allkeys-lfu 策略
+maxmemory-policy allkeys-lfu
+```
+
+需要注意的是,`hotkeys` 参数命令也会增加 Redis 实例的 CPU 和内存消耗(全局扫描),因此需要谨慎使用。
+
+**2、使用`MONITOR` 命令。**
+
+`MONITOR` 命令是 Redis 提供的一种实时查看 Redis 的所有操作的方式,可以用于临时监控 Redis 实例的操作情况,包括读写、删除等操作。
+
+由于该命令对 Redis 性能的影响比较大,因此禁止长时间开启 `MONITOR`(生产环境中建议谨慎使用该命令)。
+
+```java
+# redis-cli
+127.0.0.1:6379> MONITOR
+OK
+1683638260.637378 [0 172.17.0.1:61516] "ping"
+1683638267.144236 [0 172.17.0.1:61518] "smembers" "mySet"
+1683638268.941863 [0 172.17.0.1:61518] "smembers" "mySet"
+1683638269.551671 [0 172.17.0.1:61518] "smembers" "mySet"
+1683638270.646256 [0 172.17.0.1:61516] "ping"
+1683638270.849551 [0 172.17.0.1:61518] "smembers" "mySet"
+1683638271.926945 [0 172.17.0.1:61518] "smembers" "mySet"
+1683638274.276599 [0 172.17.0.1:61518] "smembers" "mySet2"
+1683638276.327234 [0 172.17.0.1:61518] "smembers" "mySet"
+```
+
+在发生紧急情况时,我们可以选择在合适的时机短暂执行 `MONITOR` 命令并将输出重定向至文件,在关闭 `MONITOR` 命令后通过对文件中请求进行归类分析即可找出这段时间中的 hotkey。
+
+**3、借助开源项目。**
+
+京东零售的 [hotkey](https://gitee.com/jd-platform-opensource/hotkey) 这个项目不光支持 hotkey 的发现,还支持 hotkey 的处理。
+
+
+
+**4、根据业务情况提前预估。**
+
+可以根据业务情况来预估一些 hotkey,比如参与秒杀活动的商品数据等。不过,我们无法预估所有 hotkey 的出现,比如突发的热点新闻事件等。
+
+**5、业务代码中记录分析。**
+
+在业务代码中添加相应的逻辑对 key 的访问情况进行记录分析。不过,这种方式会让业务代码的复杂性增加,一般也不会采用。
+
+**6、借助公有云的 Redis 分析服务。**
+
+如果你用的是公有云的 Redis 服务的话,可以看看其是否提供了 key 分析功能(一般都提供了)。
+
+这里以阿里云 Redis 为例说明,它支持 hotkey 实时分析、发现,文档地址: 。
+
+
+
+#### 如何解决 hotkey?
+
+hotkey 的常见处理以及优化办法如下(这些方法可以配合起来使用):
+
+- **读写分离**:主节点处理写请求,从节点处理读请求。
+- **使用 Redis Cluster**:将热点数据分散存储在多个 Redis 节点上。
+- **二级缓存**:hotkey 采用二级缓存的方式进行处理,将 hotkey 存放一份到 JVM 本地内存中(可以用 Caffeine)。
+
+除了这些方法之外,如果你使用的公有云的 Redis 服务话,还可以留意其提供的开箱即用的解决方案。
+
+这里以阿里云 Redis 为例说明,它支持通过代理查询缓存功能(Proxy Query Cache)优化热点 Key 问题。
+
+
+
+### 慢查询命令
+
+#### 为什么会有慢查询命令?
+
+我们知道一个 Redis 命令的执行可以简化为以下 4 步:
+
+1. 发送命令
+2. 命令排队
+3. 命令执行
+4. 返回结果
+
+Redis 慢查询统计的是命令执行这一步骤的耗时,慢查询命令也就是那些命令执行时间较长的命令。
+
+Redis 为什么会有慢查询命令呢?
+
+Redis 中的大部分命令都是 O(1)时间复杂度,但也有少部分 O(n) 时间复杂度的命令,例如:
+
+- `KEYS *`:会返回所有符合规则的 key。
+- `HGETALL`:会返回一个 Hash 中所有的键值对。
+- `LRANGE`:会返回 List 中指定范围内的元素。
+- `SMEMBERS`:返回 Set 中的所有元素。
+- `SINTER`/`SUNION`/`SDIFF`:计算多个 Set 的交集/并集/差集。
+- ......
+
+由于这些命令时间复杂度是 O(n),有时候也会全表扫描,随着 n 的增大,执行耗时也会越长。不过, 这些命令并不是一定不能使用,但是需要明确 N 的值。另外,有遍历的需求可以使用 `HSCAN`、`SSCAN`、`ZSCAN` 代替。
+
+除了这些 O(n)时间复杂度的命令可能会导致慢查询之外, 还有一些时间复杂度可能在 O(N) 以上的命令,例如:
+
+- `ZRANGE`/`ZREVRANGE`:返回指定 Sorted Set 中指定排名范围内的所有元素。时间复杂度为 O(log(n)+m),n 为所有元素的数量, m 为返回的元素数量,当 m 和 n 相当大时,O(n) 的时间复杂度更小。
+- `ZREMRANGEBYRANK`/`ZREMRANGEBYSCORE`:移除 Sorted Set 中指定排名范围/指定 score 范围内的所有元素。时间复杂度为 O(log(n)+m),n 为所有元素的数量, m 被删除元素的数量,当 m 和 n 相当大时,O(n) 的时间复杂度更小。
+- ......
+
+#### 如何找到慢查询命令?
+
+在 `redis.conf` 文件中,我们可以使用 `slowlog-log-slower-than` 参数设置耗时命令的阈值,并使用 `slowlog-max-len` 参数设置耗时命令的最大记录条数。
+
+当 Redis 服务器检测到执行时间超过 `slowlog-log-slower-than`阈值的命令时,就会将该命令记录在慢查询日志(slow log) 中,这点和 MySQL 记录慢查询语句类似。当慢查询日志超过设定的最大记录条数之后,Redis 会把最早的执行命令依次舍弃。
+
+⚠️注意:由于慢查询日志会占用一定内存空间,如果设置最大记录条数过大,可能会导致内存占用过高的问题。
+
+ `slowlog-log-slower-than`和`slowlog-max-len`的默认配置如下(可以自行修改):
+
+```nginx
+# The following time is expressed in microseconds, so 1000000 is equivalent
+# to one second. Note that a negative number disables the slow log, while
+# a value of zero forces the logging of every command.
+slowlog-log-slower-than 10000
+
+# There is no limit to this length. Just be aware that it will consume memory.
+# You can reclaim memory used by the slow log with SLOWLOG RESET.
+slowlog-max-len 128
+```
+
+除了修改配置文件之外,你也可以直接通过 `CONFIG` 命令直接设置:
+
+```bash
+# 命令执行耗时超过 10000 微妙(即10毫秒)就会被记录
+CONFIG SET slowlog-log-slower-than 10000
+# 只保留最近 128 条耗时命令
+CONFIG SET slowlog-max-len 128
+```
+
+获取慢查询日志的内容很简单,直接使用`SLOWLOG GET` 命令即可。
+
+```java
+127.0.0.1:6379> SLOWLOG GET #慢日志查询
+ 1) 1) (integer) 5
+ 2) (integer) 1684326682
+ 3) (integer) 12000
+ 4) 1) "KEYS"
+ 2) "*"
+ 5) "172.17.0.1:61152"
+ 6) ""
+ // ...
+```
+
+慢查询日志中的每个条目都由以下六个值组成:
+
+1. 唯一渐进的日志标识符。
+2. 处理记录命令的 Unix 时间戳。
+3. 执行所需的时间量,以微秒为单位。
+4. 组成命令参数的数组。
+5. 客户端 IP 地址和端口。
+6. 客户端名称。
+
+`SLOWLOG GET` 命令默认返回最近 10 条的的慢查询命令,你也自己可以指定返回的慢查询命令的数量 `SLOWLOG GET N`。
+
+下面是其他比较常用的慢查询相关的命令:
+
+```bash
+# 返回慢查询命令的数量
+127.0.0.1:6379> SLOWLOG LEN
+(integer) 128
+# 清空慢查询命令
+127.0.0.1:6379> SLOWLOG RESET
+OK
+```
+
+### Redis 内存碎片
+
+**相关问题**:
+
+1. 什么是内存碎片?为什么会有 Redis 内存碎片?
+2. 如何清理 Redis 内存碎片?
+
+**参考答案**:[Redis 内存碎片详解](https://javaguide.cn/database/redis/redis-memory-fragmentation.html)。
+
+## Redis 生产问题(重要)
+
+### 缓存穿透
+
+#### 什么是缓存穿透?
+
+缓存穿透说简单点就是大量请求的 key 是不合理的,**根本不存在于缓存中,也不存在于数据库中** 。这就导致这些请求直接到了数据库上,根本没有经过缓存这一层,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
+
+
+
+举个例子:某个黑客故意制造一些非法的 key 发起大量请求,导致大量请求落到数据库,结果数据库上也没有查到对应的数据。也就是说这些请求最终都落到了数据库上,对数据库造成了巨大的压力。
+
+#### 有哪些解决办法?
+
+最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
+
+**1)缓存无效 key**
+
+如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下:`SET key value EX 10086` 。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
+
+另外,这里多说一嘴,一般情况下我们是这样设计 key 的:`表名:列名:主键名:主键值` 。
+
+如果用 Java 代码展示的话,差不多是下面这样的:
+
+```java
+public Object getObjectInclNullById(Integer id) {
+ // 从缓存中获取数据
+ Object cacheValue = cache.get(id);
+ // 缓存为空
+ if (cacheValue == null) {
+ // 从数据库中获取
+ Object storageValue = storage.get(key);
+ // 缓存空对象
+ cache.set(key, storageValue);
+ // 如果存储数据为空,需要设置一个过期时间(300秒)
+ if (storageValue == null) {
+ // 必须设置过期时间,否则有被攻击的风险
+ cache.expire(key, 60 * 5);
+ }
+ return storageValue;
+ }
+ return cacheValue;
+}
+```
+
+**2)布隆过滤器**
+
+布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中。我们需要的就是判断 key 是否合法,有没有感觉布隆过滤器就是我们想要找的那个“人”。
+
+具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。
+
+加入布隆过滤器之后的缓存处理流程图如下。
+
+
+
+但是,需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是:**布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。**
+
+_为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理来说!_
+
+我们先来看一下,**当一个元素加入布隆过滤器中的时候,会进行哪些操作:**
+
+1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
+2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。
+
+我们再来看一下,**当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行哪些操作:**
+
+1. 对给定元素再次进行相同的哈希计算;
+2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
+
+然后,一定会出现这样一种情况:**不同的字符串可能哈希出来的位置相同。** (可以适当增加位数组大小或者调整我们的哈希函数来降低概率)
+
+更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://javaguide.cn/cs-basics/data-structure/bloom-filter/) ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。
+
+### 缓存击穿
+
+#### 什么是缓存击穿?
+
+缓存击穿中,请求的 key 对应的是 **热点数据** ,该数据 **存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期)** 。这就可能会导致瞬时大量的请求直接打到了数据库上,对数据库造成了巨大的压力,可能直接就被这么多请求弄宕机了。
+
+
+
+举个例子:秒杀进行过程中,缓存中的某个秒杀商品的数据突然过期,这就导致瞬时大量对该商品的请求直接落到数据库上,对数据库造成了巨大的压力。
+
+#### 有哪些解决办法?
+
+- 设置热点数据永不过期或者过期时间比较长。
+- 针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。
+- 请求数据库写数据到缓存之前,先获取互斥锁,保证只有一个请求会落到数据库上,减少数据库的压力。
+
+#### 缓存穿透和缓存击穿有什么区别?
+
+缓存穿透中,请求的 key 既不存在于缓存中,也不存在于数据库中。
+
+缓存击穿中,请求的 key 对应的是 **热点数据** ,该数据 **存在于数据库中,但不存在于缓存中(通常是因为缓存中的那份数据已经过期)** 。
+
+### 缓存雪崩
+
+#### 什么是缓存雪崩?
+
+我发现缓存雪崩这名字起的有点意思,哈哈。
+
+实际上,缓存雪崩描述的就是这样一个简单的场景:**缓存在同一时间大面积的失效,导致大量的请求都直接落到了数据库上,对数据库造成了巨大的压力。** 这就好比雪崩一样,摧枯拉朽之势,数据库的压力可想而知,可能直接就被这么多请求弄宕机了。
+
+另外,缓存服务宕机也会导致缓存雪崩现象,导致所有的请求都落到了数据库上。
+
+
+
+举个例子:数据库中的大量数据在同一时间过期,这个时候突然有大量的请求需要访问这些过期的数据。这就导致大量的请求直接落到数据库上,对数据库造成了巨大的压力。
+
+#### 有哪些解决办法?
+
+**针对 Redis 服务不可用的情况:**
+
+1. 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
+2. 限流,避免同时处理大量的请求。
+
+**针对热点缓存失效的情况:**
+
+1. 设置不同的失效时间比如随机设置缓存的失效时间。
+2. 缓存永不失效(不太推荐,实用性太差)。
+3. 设置二级缓存。
+
+#### 缓存雪崩和缓存击穿有什么区别?
+
+缓存雪崩和缓存击穿比较像,但缓存雪崩导致的原因是缓存中的大量或者所有数据失效,缓存击穿导致的原因主要是某个热点数据不存在与缓存中(通常是因为缓存中的那份数据已经过期)。
+
+### 如何保证缓存和数据库数据的一致性?
+
+细说的话可以扯很多,但是我觉得其实没太大必要(小声 BB:很多解决方案我也没太弄明白)。我个人觉得引入缓存之后,如果为了短时间的不一致性问题,选择让系统设计变得更加复杂的话,完全没必要。
+
+下面单独对 **Cache Aside Pattern(旁路缓存模式)** 来聊聊。
+
+Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删除 cache 。
+
+如果更新数据库成功,而删除缓存这一步失败的情况的话,简单说两个解决方案:
+
+1. **缓存失效时间变短(不推荐,治标不治本)**:我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
+2. **增加 cache 更新重试机制(常用)**:如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的 key 删除即可。
+
+相关文章推荐:[缓存和数据库一致性问题,看这篇就够了 - 水滴与银弹](https://mp.weixin.qq.com/s?__biz=MzIyOTYxNDI5OA==&mid=2247487312&idx=1&sn=fa19566f5729d6598155b5c676eee62d&chksm=e8beb8e5dfc931f3e35655da9da0b61c79f2843101c130cf38996446975014f958a6481aacf1&scene=178&cur_album_id=1699766580538032128#rd)。
+
+### 哪些情况可能会导致 Redis 阻塞?
+
+单独抽了一篇文章来总结可能会导致 Redis 阻塞的情况:[Redis 常见阻塞原因总结](https://javaguide.cn/database/redis/redis-common-blocking-problems-summary.html)。
+
+## Redis 集群
+
+**Redis Sentinel**:
+
+1. 什么是 Sentinel? 有什么用?
+2. Sentinel 如何检测节点是否下线?主观下线与客观下线的区别?
+3. Sentinel 是如何实现故障转移的?
+4. 为什么建议部署多个 sentinel 节点(哨兵集群)?
+5. Sentinel 如何选择出新的 master(选举机制)?
+6. 如何从 Sentinel 集群中选择出 Leader ?
+7. Sentinel 可以防止脑裂吗?
+
+**Redis Cluster**:
+
+1. 为什么需要 Redis Cluster?解决了什么问题?有什么优势?
+2. Redis Cluster 是如何分片的?
+3. 为什么 Redis Cluster 的哈希槽是 16384 个?
+4. 如何确定给定 key 的应该分布到哪个哈希槽中?
+5. Redis Cluster 支持重新分配哈希槽吗?
+6. Redis Cluster 扩容缩容期间可以提供服务吗?
+7. Redis Cluster 中的节点是怎么进行通信的?
+
+**参考答案**:[Redis 集群详解(付费)](https://javaguide.cn/database/redis/redis-cluster.html)。
+
+## Redis 使用规范
+
+实际使用 Redis 的过程中,我们尽量要准守一些常见的规范,比如:
+
+1. 使用连接池:避免频繁创建关闭客户端连接。
+2. 尽量不使用 O(n)指令,使用 O(n) 命令时要关注 n 的数量:像 `KEYS *`、`HGETALL`、`LRANGE`、`SMEMBERS`、`SINTER`/`SUNION`/`SDIFF`等 O(n) 命令并非不能使用,但是需要明确 n 的值。另外,有遍历的需求可以使用 `HSCAN`、`SSCAN`、`ZSCAN` 代替。
+3. 使用批量操作减少网络传输:原生批量操作命令(比如 `MGET`、`MSET`等等)、pipeline、Lua 脚本。
+4. 尽量不适用 Redis 事务:Redis 事务实现的功能比较鸡肋,可以使用 Lua 脚本代替。
+5. 禁止长时间开启 monitor:对性能影响比较大。
+6. 控制 key 的生命周期:避免 Redis 中存放了太多不经常被访问的数据。
+7. ......
+
+相关文章推荐:[阿里云 Redis 开发规范](https://developer.aliyun.com/article/531067) 。
+
+## 参考
+
+- 《Redis 开发与运维》
+- 《Redis 设计与实现》
+- Redis Transactions :
+- What is Redis Pipeline:
+- 一文详解 Redis 中 BigKey、HotKey 的发现与处理:
+- Redis延迟问题全面排障指南:https://mp.weixin.qq.com/s/mIc6a9mfEGdaNDD3MmfFsg
diff --git "a/docs/database/redis/redis\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/docs/database/redis/redis\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md"
deleted file mode 100644
index f2ef0eb8756c7d391a8386ba180da579fa2bce08..0000000000000000000000000000000000000000
--- "a/docs/database/redis/redis\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md"
+++ /dev/null
@@ -1,799 +0,0 @@
----
-title: Redis知识点&面试题总结
-category: 数据库
-tag:
- - Redis
----
-
-### 简单介绍一下 Redis 呗!
-
-简单来说 **Redis 就是一个使用 C 语言开发的数据库**,不过与传统数据库不同的是 **Redis 的数据是存在内存中的** ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。
-
-另外,**Redis 除了做缓存之外,也经常用来做分布式锁,甚至是消息队列。**
-
-**Redis 提供了多种数据类型来支持不同的业务场景。Redis 还支持事务 、持久化、Lua 脚本、多种集群方案。**
-
-### 分布式缓存常见的技术选型方案有哪些?
-
-分布式缓存的话,使用的比较多的主要是 **Memcached** 和 **Redis**。不过,现在基本没有看过还有项目使用 **Memcached** 来做缓存,都是直接用 **Redis**。
-
-Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来,随着 Redis 的发展,大家慢慢都转而使用更加强大的 Redis 了。
-
-分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用信息的问题。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。
-
-### 说一下 Redis 和 Memcached 的区别和共同点
-
-现在公司一般都是用 Redis 来实现缓存,而且 Redis 自身也越来越强大了!不过,了解 Redis 和 Memcached 的区别和共同点,有助于我们在做相应的技术选型的时候,能够做到有理有据!
-
-**共同点** :
-
-1. 都是基于内存的数据库,一般都用来当做缓存使用。
-2. 都有过期策略。
-3. 两者的性能都非常高。
-
-**区别** :
-
-1. **Redis 支持更丰富的数据类型(支持更复杂的应用场景)**。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
-2. **Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中。**
-3. **Redis 有灾难恢复机制。** 因为可以把缓存中的数据持久化到磁盘上。
-4. **Redis 在服务器内存使用完之后,可以将不用的数据放到磁盘上。但是,Memcached 在服务器内存使用完之后,就会直接报异常。**
-5. **Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的。**
-6. **Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。** (Redis 6.0 引入了多线程 IO )
-7. **Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且,Redis 支持更多的编程语言。**
-8. **Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。**
-
-相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。
-
-### 缓存数据的处理流程是怎样的?
-
-作为暖男一号,我给大家画了一个草图。
-
-
-
-简单来说就是:
-
-1. 如果用户请求的数据在缓存中就直接返回。
-2. 缓存中不存在的话就看数据库中是否存在。
-3. 数据库中存在的话就更新缓存中的数据。
-4. 数据库中不存在的话就返回空数据。
-
-### 为什么要用 Redis/为什么要用缓存?
-
-_简单,来说使用缓存主要是为了提升用户体验以及应对更多的用户。_
-
-下面我们主要从“高性能”和“高并发”这两点来看待这个问题。
-
-
-
-**高性能** :
-
-对照上面 👆 我画的图。我们设想这样的场景:
-
-假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。但是,如果说,用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心地将该用户访问的数据存在缓存中。
-
-**这样有什么好处呢?** 那就是保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。
-
-不过,要保持数据库和缓存中的数据的一致性。 如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
-
-**高并发:**
-
-一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 redis 的情况,redis 集群的话会更高)。
-
-> QPS(Query Per Second):服务器每秒可以执行的查询次数;
-
-由此可见,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高了系统整体的并发。
-
-### Redis 除了做缓存,还能做什么?
-
-- **分布式锁** : 通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。相关阅读:[《分布式锁中的王者方案 - Redisson》](https://mp.weixin.qq.com/s/CbnPRfvq4m1sqo2uKI6qQw)。
-- **限流** :一般是通过 Redis + Lua 脚本的方式来实现限流。相关阅读:[《我司用了 6 年的 Redis 分布式限流器,可以说是非常厉害了!》](https://mp.weixin.qq.com/s/kyFAWH3mVNJvurQDt4vchA)。
-- **消息队列** :Redis 自带的 list 数据结构可以作为一个简单的队列使用。Redis5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。
-- **复杂业务场景** :通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 bitmap 统计活跃用户、通过 sorted set 维护排行榜。
-- ......
-
-### Redis 常见数据结构以及使用场景分析
-
-你可以自己本机安装 redis 或者通过 redis 官网提供的[在线 redis 环境](https://try.redis.io/)。
-
-
-
-#### string
-
-1. **介绍** :string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的,但是 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 **简单动态字符串**(simple dynamic string,**SDS**)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。
-2. **常用命令:** `set,get,strlen,exists,decr,incr,setex` 等等。
-3. **应用场景:** 一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。
-
-下面我们简单看看它的使用!
-
-**普通字符串的基本操作:**
-
-```bash
-127.0.0.1:6379> set key value #设置 key-value 类型的值
-OK
-127.0.0.1:6379> get key # 根据 key 获得对应的 value
-"value"
-127.0.0.1:6379> exists key # 判断某个 key 是否存在
-(integer) 1
-127.0.0.1:6379> strlen key # 返回 key 所储存的字符串值的长度。
-(integer) 5
-127.0.0.1:6379> del key # 删除某个 key 对应的值
-(integer) 1
-127.0.0.1:6379> get key
-(nil)
-```
-
-**批量设置** :
-
-```bash
-127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置 key-value 类型的值
-OK
-127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value
-1) "value1"
-2) "value2"
-```
-
-**计数器(字符串的内容为整数的时候可以使用):**
-
-```bash
-127.0.0.1:6379> set number 1
-OK
-127.0.0.1:6379> incr number # 将 key 中储存的数字值增一
-(integer) 2
-127.0.0.1:6379> get number
-"2"
-127.0.0.1:6379> decr number # 将 key 中储存的数字值减一
-(integer) 1
-127.0.0.1:6379> get number
-"1"
-```
-
-**过期(默认为永不过期)**:
-
-```bash
-127.0.0.1:6379> expire key 60 # 数据在 60s 后过期
-(integer) 1
-127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
-OK
-127.0.0.1:6379> ttl key # 查看数据还有多久过期
-(integer) 56
-```
-
-#### list
-
-1. **介绍** :**list** 即是 **链表**。链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如 Java 中的 **LinkedList**,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 list 的实现为一个 **双向链表**,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
-2. **常用命令:** `rpush,lpop,lpush,rpop,lrange,llen` 等。
-3. **应用场景:** 发布与订阅或者说消息队列、慢查询。
-
-下面我们简单看看它的使用!
-
-**通过 `rpush/lpop` 实现队列:**
-
-```bash
-127.0.0.1:6379> rpush myList value1 # 向 list 的头部(右边)添加元素
-(integer) 1
-127.0.0.1:6379> rpush myList value2 value3 # 向list的头部(最右边)添加多个元素
-(integer) 3
-127.0.0.1:6379> lpop myList # 将 list的尾部(最左边)元素取出
-"value1"
-127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end
-1) "value2"
-2) "value3"
-127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一
-1) "value2"
-2) "value3"
-```
-
-**通过 `rpush/rpop` 实现栈:**
-
-```bash
-127.0.0.1:6379> rpush myList2 value1 value2 value3
-(integer) 3
-127.0.0.1:6379> rpop myList2 # 将 list的头部(最右边)元素取出
-"value3"
-```
-
-我专门画了一个图方便小伙伴们来理解:
-
-
-
-**通过 `lrange` 查看对应下标范围的列表元素:**
-
-```bash
-127.0.0.1:6379> rpush myList value1 value2 value3
-(integer) 3
-127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的list列表, 0 为 start,1为 end
-1) "value1"
-2) "value2"
-127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒数第一
-1) "value1"
-2) "value2"
-3) "value3"
-```
-
-通过 `lrange` 命令,你可以基于 list 实现分页查询,性能非常高!
-
-**通过 `llen` 查看链表长度:**
-
-```bash
-127.0.0.1:6379> llen myList
-(integer) 3
-```
-
-#### hash
-
-1. **介绍** :hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。不过,Redis 的 hash 做了更多优化。另外,hash 是一个 string 类型的 field 和 value 的映射表,**特别适合用于存储对象**,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。
-2. **常用命令:** `hset,hmset,hexists,hget,hgetall,hkeys,hvals` 等。
-3. **应用场景:** 系统中对象数据的存储。
-
-下面我们简单看看它的使用!
-
-```bash
-127.0.0.1:6379> hmset userInfoKey name "guide" description "dev" age "24"
-OK
-127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。
-(integer) 1
-127.0.0.1:6379> hget userInfoKey name # 获取存储在哈希表中指定字段的值。
-"guide"
-127.0.0.1:6379> hget userInfoKey age
-"24"
-127.0.0.1:6379> hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值
-1) "name"
-2) "guide"
-3) "description"
-4) "dev"
-5) "age"
-6) "24"
-127.0.0.1:6379> hkeys userInfoKey # 获取 key 列表
-1) "name"
-2) "description"
-3) "age"
-127.0.0.1:6379> hvals userInfoKey # 获取 value 列表
-1) "guide"
-2) "dev"
-3) "24"
-127.0.0.1:6379> hset userInfoKey name "GuideGeGe" # 修改某个字段对应的值
-127.0.0.1:6379> hget userInfoKey name
-"GuideGeGe"
-```
-
-#### set
-
-1. **介绍 :** set 类似于 Java 中的 `HashSet` 。Redis 中的 set 类型是一种无序集合,集合中的元素没有先后顺序。当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。比如:你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
-2. **常用命令:** `sadd,spop,smembers,sismember,scard,sinterstore,sunion` 等。
-3. **应用场景:** 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景
-
-下面我们简单看看它的使用!
-
-```bash
-127.0.0.1:6379> sadd mySet value1 value2 # 添加元素进去
-(integer) 2
-127.0.0.1:6379> sadd mySet value1 # 不允许有重复元素
-(integer) 0
-127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素
-1) "value1"
-2) "value2"
-127.0.0.1:6379> scard mySet # 查看 set 的长度
-(integer) 2
-127.0.0.1:6379> sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素
-(integer) 1
-127.0.0.1:6379> sadd mySet2 value2 value3
-(integer) 2
-127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交集并存放在 mySet3 中
-(integer) 1
-127.0.0.1:6379> smembers mySet3
-1) "value2"
-```
-
-#### sorted set
-
-1. **介绍:** 和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。
-2. **常用命令:** `zadd,zcard,zscore,zrange,zrevrange,zrem` 等。
-3. **应用场景:** 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。
-
-```bash
-127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 为权重
-(integer) 1
-127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素
-(integer) 2
-127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量
-(integer) 3
-127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重
-"3"
-127.0.0.1:6379> zrange myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元素
-1) "value3"
-2) "value2"
-3) "value1"
-127.0.0.1:6379> zrange myZset 0 1 # 顺序输出某个范围区间的元素,0 为 start 1 为 stop
-1) "value3"
-2) "value2"
-127.0.0.1:6379> zrevrange myZset 0 1 # 逆序输出某个范围区间的元素,0 为 start 1 为 stop
-1) "value1"
-2) "value2"
-```
-
-#### bitmap
-
-1. **介绍:** bitmap 存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 bitmap 本身会极大的节省储存空间。
-2. **常用命令:** `setbit` 、`getbit` 、`bitcount`、`bitop`
-3. **应用场景:** 适合需要保存状态信息(比如是否签到、是否登录...)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
-
-```bash
-# SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位
-127.0.0.1:6379> setbit mykey 7 1
-(integer) 0
-127.0.0.1:6379> setbit mykey 7 0
-(integer) 1
-127.0.0.1:6379> getbit mykey 7
-(integer) 0
-127.0.0.1:6379> setbit mykey 6 1
-(integer) 0
-127.0.0.1:6379> setbit mykey 8 1
-(integer) 0
-# 通过 bitcount 统计被被设置为 1 的位的数量。
-127.0.0.1:6379> bitcount mykey
-(integer) 2
-```
-
-针对上面提到的一些场景,这里进行进一步说明。
-
-**使用场景一:用户行为分析**
-很多网站为了分析你的喜好,需要研究你点赞过的内容。
-
-```bash
-# 记录你喜欢过 001 号小姐姐
-127.0.0.1:6379> setbit beauty_girl_001 uid 1
-```
-
-**使用场景二:统计活跃用户**
-
-使用时间作为 key,然后用户 ID 为 offset,如果当日活跃过就设置为 1
-
-那么我该如何计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只要有一天在线就称为活跃),有请下一个 redis 的命令
-
-```bash
-# 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
-# BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数
-BITOP operation destkey key [key ...]
-```
-
-初始化数据:
-
-```bash
-127.0.0.1:6379> setbit 20210308 1 1
-(integer) 0
-127.0.0.1:6379> setbit 20210308 2 1
-(integer) 0
-127.0.0.1:6379> setbit 20210309 1 1
-(integer) 0
-```
-
-统计 20210308~20210309 总活跃用户数: 1
-
-```bash
-127.0.0.1:6379> bitop and desk1 20210308 20210309
-(integer) 1
-127.0.0.1:6379> bitcount desk1
-(integer) 1
-```
-
-统计 20210308~20210309 在线活跃用户数: 2
-
-```bash
-127.0.0.1:6379> bitop or desk2 20210308 20210309
-(integer) 1
-127.0.0.1:6379> bitcount desk2
-(integer) 2
-```
-
-**使用场景三:用户在线状态**
-
-对于获取或者统计用户在线状态,使用 bitmap 是一个节约空间且效率又高的一种方法。
-
-只需要一个 key,然后用户 ID 为 offset,如果在线就设置为 1,不在线就设置为 0。
-
-### Redis 单线程模型详解
-
-**Redis 基于 Reactor 模式来设计开发了自己的一套高效的事件处理模型** (Netty 的线程模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),这套事件处理模型对应的是 Redis 中的文件事件处理器(file event handler)。由于文件事件处理器(file event handler)是单线程方式运行的,所以我们一般都说 Redis 是单线程模型。
-
-**既然是单线程,那怎么监听大量的客户端连接呢?**
-
-Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。
-
-这样的好处非常明显: **I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗**(和 NIO 中的 `Selector` 组件很像)。
-
-另外, Redis 服务器是一个事件驱动程序,服务器需要处理两类事件:1. 文件事件; 2. 时间事件。
-
-时间事件不需要多花时间了解,我们接触最多的还是 **文件事件**(客户端进行读取写入等操作,涉及一系列网络通信)。
-
-《Redis 设计与实现》有一段话是如是介绍文件事件的,我觉得写得挺不错。
-
-> Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
->
-> 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
->
-> **虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字**,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性。
-
-可以看出,文件事件处理器(file event handler)主要是包含 4 个部分:
-
-- 多个 socket(客户端连接)
-- IO 多路复用程序(支持多个客户端连接的关键)
-- 文件事件分派器(将 socket 关联到相应的事件处理器)
-- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
-
-
-
-《Redis设计与实现:12章》
-
-### Redis 没有使用多线程?为什么不使用多线程?
-
-虽然说 Redis 是单线程模型,但是,实际上,**Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。**
-
-
-
-不过,Redis 4.0 增加的多线程主要是针对一些大键值对的删除操作的命令,使用这些命令就会使用主处理之外的其他线程来“异步处理”。
-
-大体上来说,**Redis 6.0 之前主要还是单线程处理。**
-
-**那,Redis6.0 之前 为什么不使用多线程?**
-
-我觉得主要原因有下面 3 个:
-
-1. 单线程编程容易并且更容易维护;
-2. Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
-3. 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
-
-### Redis6.0 之后为何引入了多线程?
-
-**Redis6.0 引入多线程主要是为了提高网络 IO 读写性能**,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。
-
-虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。
-
-Redis6.0 的多线程默认是禁用的,只使用主线程。如需开启需要修改 redis 配置文件 `redis.conf` :
-
-```bash
-io-threads-do-reads yes
-```
-
-开启多线程后,还需要设置线程数,否则是不生效的。同样需要修改 redis 配置文件 `redis.conf` :
-
-```bash
-io-threads 4 #官网建议4核的机器建议设置为2或3个线程,8核的建议设置为6个线程
-```
-
-推荐阅读:
-
-1. [Redis 6.0 新特性-多线程连环 13 问!](https://mp.weixin.qq.com/s/FZu3acwK6zrCBZQ_3HoUgw)
-2. [为什么 Redis 选择单线程模型](https://draveness.me/whys-the-design-redis-single-thread/)
-
-### Redis 给缓存数据设置过期时间有啥用?
-
-一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间。为什么呢?
-
-因为内存是有限的,如果缓存中的所有数据都是一直保存的话,分分钟直接 Out of memory。
-
-Redis 自带了给缓存数据设置过期时间的功能,比如:
-
-```bash
-127.0.0.1:6379> exp key 60 # 数据在 60s 后过期
-(integer) 1
-127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
-OK
-127.0.0.1:6379> ttl key # 查看数据还有多久过期
-(integer) 56
-```
-
-注意:**Redis 中除了字符串类型有自己独有设置过期时间的命令 `setex` 外,其他方法都需要依靠 `expire` 命令来设置过期时间 。另外, `persist` 命令可以移除一个键的过期时间。 **
-
-**过期时间除了有助于缓解内存的消耗,还有什么其他用么?**
-
-很多时候,我们的业务场景就是需要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在 1 分钟内有效,用户登录的 token 可能只在 1 天内有效。
-
-如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多。
-
-### Redis 是如何判断数据是否过期的呢?
-
-Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。
-
-
-
-过期字典是存储在 redisDb 这个结构里的:
-
-```c
-typedef struct redisDb {
- ...
-
- dict *dict; //数据库键空间,保存着数据库中所有键值对
- dict *expires // 过期字典,保存着键的过期时间
- ...
-} redisDb;
-```
-
-### 过期的数据的删除策略了解么?
-
-如果假设你设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢?
-
-常用的过期数据的删除策略就两个(重要!自己造缓存轮子的时候需要格外考虑的东西):
-
-1. **惰性删除** :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
-2. **定期删除** : 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
-
-定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 **定期删除+惰性/懒汉式删除** 。
-
-但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory 了。
-
-怎么解决这个问题呢?答案就是:**Redis 内存淘汰机制。**
-
-### Redis 内存淘汰机制了解么?
-
-> 相关问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?
-
-Redis 提供 6 种数据淘汰策略:
-
-1. **volatile-lru(least recently used)**:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
-2. **volatile-ttl**:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
-3. **volatile-random**:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
-4. **allkeys-lru(least recently used)**:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
-5. **allkeys-random**:从数据集(server.db[i].dict)中任意选择数据淘汰
-6. **no-eviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
-
-4.0 版本后增加以下两种:
-
-7. **volatile-lfu(least frequently used)**:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
-8. **allkeys-lfu(least frequently used)**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
-
-### Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)
-
-很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
-
-Redis 不同于 Memcached 的很重要一点就是,Redis 支持持久化,而且支持两种不同的持久化操作。**Redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)**。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。
-
-**快照(snapshotting)持久化(RDB)**
-
-Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
-
-快照持久化是 Redis 默认采用的持久化方式,在 Redis.conf 配置文件中默认有此下配置:
-
-```conf
-save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
-
-save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
-
-save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
-```
-
-**AOF(append-only file)持久化**
-
-与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启:
-
-```conf
-appendonly yes
-```
-
-开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。
-
-AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 `appendonly.aof`。
-
-在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
-
-```conf
-appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
-appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
-appendfsync no #让操作系统决定何时进行同步
-```
-
-为了兼顾数据和写入性能,用户可以考虑 `appendfsync everysec` 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
-
-**相关 issue** :[783:Redis 的 AOF 方式](https://github.com/Snailclimb/JavaGuide/issues/783)
-
-**拓展:Redis 4.0 对于持久化机制的优化**
-
-Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。
-
-如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
-
-官方文档地址:https://redis.io/topics/persistence
-
-
-
-**补充内容:AOF 重写**
-
-AOF 重写可以产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。
-
-AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。
-
-在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。
-
-### Redis 事务
-
-Redis 可以通过 **`MULTI`,`EXEC`,`DISCARD` 和 `WATCH`** 等命令来实现事务(transaction)功能。
-
-```bash
-> MULTI
-OK
-> SET USER "Guide哥"
-QUEUED
-> GET USER
-QUEUED
-> EXEC
-1) OK
-2) "Guide哥"
-```
-
-使用 [`MULTI`](https://redis.io/commands/multi) 命令后可以输入多个命令。Redis 不会立即执行这些命令,而是将它们放到队列,当调用了 [`EXEC`](https://redis.io/commands/exec) 命令将执行所有命令。
-
-这个过程是这样的:
-
-1. 开始事务(`MULTI`)。
-2. 命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行)。
-3. 执行事务(`EXEC`)。
-
-你也可以通过 [`DISCARD`](https://redis.io/commands/discard) 命令取消一个事务,它会清空事务队列中保存的所有命令。
-
-```bash
-> MULTI
-OK
-> SET USER "Guide哥"
-QUEUED
-> GET USER
-QUEUED
-> DISCARD
-OK
-```
-
-[`WATCH`](https://redis.io/commands/watch) 命令用于监听指定的键,当调用 `EXEC` 命令执行事务时,如果一个被 `WATCH` 命令监视的键被修改的话,整个事务都不会执行,直接返回失败。
-
-```bash
-> WATCH USER
-OK
-> MULTI
-> SET USER "Guide哥"
-OK
-> GET USER
-Guide哥
-> EXEC
-ERR EXEC without MULTI
-```
-
-Redis 官网相关介绍 [https://redis.io/topics/transactions](https://redis.io/topics/transactions) 如下:
-
-
-
-但是,Redis 的事务和我们平时理解的关系型数据库的事务不同。我们知道事务具有四大特性: **1. 原子性**,**2. 隔离性**,**3. 持久性**,**4. 一致性**。
-
-1. **原子性(Atomicity):** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
-2. **隔离性(Isolation):** 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
-3. **持久性(Durability):** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
-4. **一致性(Consistency):** 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
-
-**Redis 是不支持 roll back 的,因而不满足原子性的(而且不满足持久性)。**
-
-Redis 官网也解释了自己为啥不支持回滚。简单来说就是 Redis 开发者们觉得没必要支持回滚,这样更简单便捷并且性能更好。Redis 开发者觉得即使命令执行错误也应该在开发过程中就被发现而不是生产过程中。
-
-
-
-你可以将 Redis 中的事务就理解为 :**Redis 事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。**
-
-**相关 issue** :
-
-- [issue452: 关于 Redis 事务不满足原子性的问题](https://github.com/Snailclimb/JavaGuide/issues/452) 。
-- [Issue491:关于 redis 没有事务回滚?](https://github.com/Snailclimb/JavaGuide/issues/491)
-
-### 缓存穿透
-
-#### 什么是缓存穿透?
-
-缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
-
-#### 缓存穿透情况的处理流程是怎样的?
-
-如下图所示,用户的请求最终都要跑到数据库中查询一遍。
-
-
-
-#### 有哪些解决办法?
-
-最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
-
-**1)缓存无效 key**
-
-如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下: `SET key value EX 10086` 。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
-
-另外,这里多说一嘴,一般情况下我们是这样设计 key 的: `表名:列名:主键名:主键值` 。
-
-如果用 Java 代码展示的话,差不多是下面这样的:
-
-```java
-public Object getObjectInclNullById(Integer id) {
- // 从缓存中获取数据
- Object cacheValue = cache.get(id);
- // 缓存为空
- if (cacheValue == null) {
- // 从数据库中获取
- Object storageValue = storage.get(key);
- // 缓存空对象
- cache.set(key, storageValue);
- // 如果存储数据为空,需要设置一个过期时间(300秒)
- if (storageValue == null) {
- // 必须设置过期时间,否则有被攻击的风险
- cache.expire(key, 60 * 5);
- }
- return storageValue;
- }
- return cacheValue;
-}
-```
-
-**2)布隆过滤器**
-
-布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中。我们需要的就是判断 key 是否合法,有没有感觉布隆过滤器就是我们想要找的那个“人”。
-
-具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。
-
-加入布隆过滤器之后的缓存处理流程图如下。
-
-
-
-但是,需要注意的是布隆过滤器可能会存在误判的情况。总结来说就是: **布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。**
-
-_为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理来说!_
-
-我们先来看一下,**当一个元素加入布隆过滤器中的时候,会进行哪些操作:**
-
-1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
-2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。
-
-我们再来看一下,**当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行哪些操作:**
-
-1. 对给定元素再次进行相同的哈希计算;
-2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
-
-然后,一定会出现这样一种情况:**不同的字符串可能哈希出来的位置相同。** (可以适当增加位数组大小或者调整我们的哈希函数来降低概率)
-
-更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/cs-basics/data-structure/bloom-filter.md) ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。
-
-### 缓存雪崩
-
-#### 什么是缓存雪崩?
-
-我发现缓存雪崩这名字起的有点意思,哈哈。
-
-实际上,缓存雪崩描述的就是这样一个简单的场景:**缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。** 这就好比雪崩一样,摧枯拉朽之势,数据库的压力可想而知,可能直接就被这么多请求弄宕机了。
-
-举个例子:系统的缓存模块出了问题比如宕机导致不可用。造成系统的所有访问,都要走数据库。
-
-还有一种缓存雪崩的场景是:**有一些被大量访问数据(热点缓存)在某一时刻大面积失效,导致对应的请求直接落到了数据库上。** 这样的情况,有下面几种解决办法:
-
-举个例子 :秒杀开始 12 个小时之前,我们统一存放了一批商品到 Redis 中,设置的缓存过期时间也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致的情况就是,相应的请求直接就落到了数据库上,就像雪崩一样可怕。
-
-#### 有哪些解决办法?
-
-**针对 Redis 服务不可用的情况:**
-
-1. 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
-2. 限流,避免同时处理大量的请求。
-
-**针对热点缓存失效的情况:**
-
-1. 设置不同的失效时间比如随机设置缓存的失效时间。
-2. 缓存永不失效。
-
-### 如何保证缓存和数据库数据的一致性?
-
-细说的话可以扯很多,但是我觉得其实没太大必要(小声 BB:很多解决方案我也没太弄明白)。我个人觉得引入缓存之后,如果为了短时间的不一致性问题,选择让系统设计变得更加复杂的话,完全没必要。
-
-下面单独对 **Cache Aside Pattern(旁路缓存模式)** 来聊聊。
-
-Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删除 cache 。
-
-如果更新数据库成功,而删除缓存这一步失败的情况的话,简单说两个解决方案:
-
-1. **缓存失效时间变短(不推荐,治标不治本)** :我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
-2. **增加 cache 更新重试机制(常用)**: 如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的 key 删除即可。
-
-### 参考
-
-- 《Redis 开发与运维》
-- 《Redis 设计与实现》
-- Redis 命令总结:http://Redisdoc.com/string/set.html
-- 通俗易懂的 Redis 数据结构基础教程:[https://juejin.im/post/5b53ee7e5188251aaa2d2e16](https://juejin.im/post/5b53ee7e5188251aaa2d2e16)
-- WHY Redis choose single thread (vs multi threads): [https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153](https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153)
diff --git a/docs/database/sql/sql-questions-01.md b/docs/database/sql/sql-questions-01.md
new file mode 100644
index 0000000000000000000000000000000000000000..77868beffd0f8e6375696a214e68286381aa03b0
--- /dev/null
+++ b/docs/database/sql/sql-questions-01.md
@@ -0,0 +1,1824 @@
+---
+title: SQL常见面试题总结(1)
+category: 数据库
+tag:
+ - 数据库基础
+ - SQL
+---
+
+> 题目来源于:[牛客题霸 - SQL 必知必会](https://www.nowcoder.com/exam/oj?page=1&tab=SQL%E7%AF%87&topicId=298)
+
+## 检索数据
+
+`SELECT` 用于从数据库中查询数据。
+
+### 从 Customers 表中检索所有的 ID
+
+现有表 `Customers` 如下:
+
+| cust_id |
+| ------- |
+| A |
+| B |
+| C |
+
+编写 SQL 语句,从 `Customers` 表中检索所有的 `cust_id`。
+
+答案:
+
+```sql
+SELECT cust_id
+FROM Customers
+```
+
+### 检索并列出已订购产品的清单
+
+表 `OrderItems` 含有非空的列 `prod_id` 代表商品 id,包含了所有已订购的商品(有些已被订购多次)。
+
+| prod_id |
+| ------- |
+| a1 |
+| a2 |
+| a3 |
+| a4 |
+| a5 |
+| a6 |
+| a7 |
+
+编写 SQL 语句,检索并列出所有已订购商品(`prod_id`)的去重后的清单。
+
+答案:
+
+```sql
+SELECT DISTINCT prod_id
+FROM OrderItems
+```
+
+知识点:`DISTINCT` 用于返回列中的唯一不同值。
+
+### 检索所有列
+
+现在有 `Customers` 表(表中含有列 `cust_id` 代表客户 id,`cust_name` 代表客户姓名)
+
+| cust_id | cust_name |
+| ------- | --------- |
+| a1 | andy |
+| a2 | ben |
+| a3 | tony |
+| a4 | tom |
+| a5 | an |
+| a6 | lee |
+| a7 | hex |
+
+需要编写 SQL 语句,检索所有列。
+
+答案:
+
+```sql
+SELECT cust_id, cust_name
+FROM Customers
+```
+
+## 排序检索数据
+
+`ORDER BY` 用于对结果集按照一个列或者多个列进行排序。默认按照升序对记录进行排序,如果需要按照降序对记录进行排序,可以使用 `DESC` 关键字。
+
+### 检索顾客名称并且排序
+
+有表 `Customers`,`cust_id` 代表客户 id,`cust_name` 代表客户姓名。
+
+| cust_id | cust_name |
+| ------- | --------- |
+| a1 | andy |
+| a2 | ben |
+| a3 | tony |
+| a4 | tom |
+| a5 | an |
+| a6 | lee |
+| a7 | hex |
+
+从 `Customers` 中检索所有的顾客名称(`cust_name`),并按从 Z 到 A 的顺序显示结果。
+
+答案:
+
+```sql
+SELECT cust_name
+FROM Customers
+ORDER BY cust_name DESC
+```
+
+### 对顾客 ID 和日期排序
+
+有 `Orders` 表:
+
+| cust_id | order_num | order_date |
+| ------- | --------- | ------------------- |
+| andy | aaaa | 2021-01-01 00:00:00 |
+| andy | bbbb | 2021-01-01 12:00:00 |
+| bob | cccc | 2021-01-10 12:00:00 |
+| dick | dddd | 2021-01-11 00:00:00 |
+
+编写 SQL 语句,从 `Orders` 表中检索顾客 ID(`cust_id`)和订单号(`order_num`),并先按顾客 ID 对结果进行排序,再按订单日期倒序排列。
+
+答案:
+
+```sql
+# 根据列名排序
+# 注意:是 order_date 降序,而不是 order_num
+SELECT cust_id, order_num
+FROM Orders
+ORDER BY cust_id,order_date DESC
+```
+
+知识点:`order by` 对多列排序的时候,先排序的列放前面,后排序的列放后面。并且,不同的列可以有不同的排序规则。
+
+### 按照数量和价格排序
+
+假设有一个 `OrderItems` 表:
+
+| quantity | item_price |
+| -------- | ---------- |
+| 1 | 100 |
+| 10 | 1003 |
+| 2 | 500 |
+
+编写 SQL 语句,显示 `OrderItems` 表中的数量(`quantity`)和价格(`item_price`),并按数量由多到少、价格由高到低排序。
+
+答案:
+
+```sql
+SELECT quantity, item_price
+FROM OrderItems
+ORDER BY quantity DESC,item_price DESC
+```
+
+### 检查 SQL 语句
+
+有 `Vendors` 表:
+
+| vend_name |
+| --------- |
+| 海底捞 |
+| 小龙坎 |
+| 大龙燚 |
+
+下面的 SQL 语句有问题吗?尝试将它改正确,使之能够正确运行,并且返回结果根据`vend_name` 逆序排列。
+
+```sql
+SELECT vend_name,
+FROM Vendors
+ORDER vend_name DESC
+```
+
+改正后:
+
+```sql
+SELECT vend_name
+FROM Vendors
+ORDER BY vend_name DESC
+```
+
+知识点:
+
+- 逗号作用是用来隔开列与列之间的。
+- ORDER BY 是有 BY 的,需要撰写完整,且位置正确。
+
+## 过滤数据
+
+`WHERE` 可以过滤返回的数据。
+
+下面的运算符可以在 `WHERE` 子句中使用:
+
+| 运算符 | 描述 |
+| :------ | :--------------------------------------------------------- |
+| = | 等于 |
+| <> | 不等于。 **注释:** 在 SQL 的一些版本中,该操作符可被写成 != |
+| > | 大于 |
+| < | 小于 |
+| >= | 大于等于 |
+| <= | 小于等于 |
+| BETWEEN | 在某个范围内 |
+| LIKE | 搜索某种模式 |
+| IN | 指定针对某个列的多个可能值 |
+
+### 返回固定价格的产品
+
+有表 `Products`:
+
+| prod_id | prod_name | prod_price |
+| ------- | -------------- | ---------- |
+| a0018 | sockets | 9.49 |
+| a0019 | iphone13 | 600 |
+| b0018 | gucci t-shirts | 1000 |
+
+【问题】从 `Products` 表中检索产品 ID(`prod_id`)和产品名称(`prod_name`),只返回价格为 9.49 美元的产品。
+
+答案:
+
+```sql
+SELECT prod_id, prod_name
+FROM Products
+WHERE prod_price = 9.49
+```
+
+### 返回更高价格的产品
+
+有表 `Products`:
+
+| prod_id | prod_name | prod_price |
+| ------- | -------------- | ---------- |
+| a0018 | sockets | 9.49 |
+| a0019 | iphone13 | 600 |
+| b0019 | gucci t-shirts | 1000 |
+
+【问题】编写 SQL 语句,从 `Products` 表中检索产品 ID(`prod_id`)和产品名称(`prod_name`),只返回价格为 9 美元或更高的产品。
+
+答案:
+
+```sql
+SELECT prod_id, prod_name
+FROM Products
+WHERE prod_price >= 9
+```
+
+### 返回产品并且按照价格排序
+
+有表 `Products`:
+
+| prod_id | prod_name | prod_price |
+| ------- | --------- | ---------- |
+| a0011 | egg | 3 |
+| a0019 | sockets | 4 |
+| b0019 | coffee | 15 |
+
+【问题】编写 SQL 语句,返回 `Products` 表中所有价格在 3 美元到 6 美元之间的产品的名称(`prod_name`)和价格(`prod_price`),然后按价格对结果进行排序。
+
+答案:
+
+```sql
+SELECT prod_name, prod_price
+FROM Products
+WHERE prod_price BETWEEN 3 AND 6
+ORDER BY prod_price
+
+# 或者
+SELECT prod_name, prod_price
+FROM Products
+WHERE prod_price >= 3 AND prod_price <= 6
+ORDER BY prod_price
+```
+
+### 返回更多的产品
+
+`OrderItems` 表含有:订单号 `order_num`,`quantity`产品数量
+
+| order_num | quantity |
+| --------- | -------- |
+| a1 | 105 |
+| a2 | 1100 |
+| a2 | 200 |
+| a4 | 1121 |
+| a5 | 10 |
+| a2 | 19 |
+| a7 | 5 |
+
+【问题】从 `OrderItems` 表中检索出所有不同且不重复的订单号(`order_num`),其中每个订单都要包含 100 个或更多的产品。
+
+答案:
+
+```sql
+SELECT order_num
+FROM OrderItems
+GROUP BY order_num
+HAVING SUM(quantity) >= 100
+```
+
+## 高级数据过滤
+
+`AND` 和 `OR` 运算符用于基于一个以上的条件对记录进行过滤,两者可以结合使用。`AND` 必须 2 个条件都成立,`OR`只要 2 个条件中的一个成立即可。
+
+### 检索供应商名称
+
+`Vendors` 表有字段供应商名称(`vend_name`)、供应商国家(`vend_country`)、供应商州(`vend_state`)
+
+| vend_name | vend_country | vend_state |
+| --------- | ------------ | ---------- |
+| apple | USA | CA |
+| vivo | CNA | shenzhen |
+| huawei | CNA | xian |
+
+【问题】编写 SQL 语句,从 `Vendors` 表中检索供应商名称(`vend_name`),仅返回加利福尼亚州的供应商(这需要按国家[USA]和州[CA]进行过滤,没准其他国家也存在一个 CA)
+
+答案:
+
+```sql
+SELECT vend_name
+FROM Vendors
+WHERE vend_country = 'USA' AND vend_state = 'CA'
+```
+
+### 检索并列出已订购产品的清单
+
+`OrderItems` 表包含了所有已订购的产品(有些已被订购多次)。
+
+| prod_id | order_num | quantity |
+| ------- | --------- | -------- |
+| BR01 | a1 | 105 |
+| BR02 | a2 | 1100 |
+| BR02 | a2 | 200 |
+| BR03 | a4 | 1121 |
+| BR017 | a5 | 10 |
+| BR02 | a2 | 19 |
+| BR017 | a7 | 5 |
+
+【问题】编写 SQL 语句,查找所有订购了数量至少 100 个的 `BR01`、`BR02` 或 `BR03` 的订单。你需要返回 `OrderItems` 表的订单号(`order_num`)、产品 ID(`prod_id`)和数量(`quantity`),并按产品 ID 和数量进行过滤。
+
+答案:
+
+```sql
+SELECT order_num, prod_id, quantity
+FROM OrderItems
+WHERE prod_id IN ('BR01', 'BR02', 'BR03') AND quantity >= 100
+```
+
+### 返回所有价格在 3 美元到 6 美元之间的产品的名称和价格
+
+有表 `Products`:
+
+| prod_id | prod_name | prod_price |
+| ------- | --------- | ---------- |
+| a0011 | egg | 3 |
+| a0019 | sockets | 4 |
+| b0019 | coffee | 15 |
+
+【问题】编写 SQL 语句,返回所有价格在 3 美元到 6 美元之间的产品的名称(`prod_name`)和价格(`prod_price`),使用 AND 操作符,然后按价格对结果进行升序排序。
+
+答案:
+
+```sql
+SELECT prod_name, prod_price
+FROM Products
+WHERE prod_price >= 3 and prod_price <= 6
+ORDER BY prod_price
+```
+
+### 检查 SQL 语句
+
+供应商表 `Vendors` 有字段供应商名称 `vend_name`、供应商国家 `vend_country`、供应商省份 `vend_state`
+
+| vend_name | vend_country | vend_state |
+| --------- | ------------ | ---------- |
+| apple | USA | CA |
+| vivo | CNA | shenzhen |
+| huawei | CNA | xian |
+
+【问题】修改正确下面 sql,使之正确返回。
+
+```sql
+SELECT vend_name
+FROM Vendors
+ORDER BY vend_name
+WHERE vend_country = 'USA' AND vend_state = 'CA';
+```
+
+修改后:
+
+```sql
+SELECT vend_name
+FROM Vendors
+WHERE vend_country = 'USA' AND vend_state = 'CA'
+ORDER BY vend_name
+```
+
+`ORDER BY` 语句必须放在 `WHERE` 之后。
+
+## 用通配符进行过滤
+
+SQL 通配符必须与 `LIKE` 运算符一起使用
+
+在 SQL 中,可使用以下通配符:
+
+| 通配符 | 描述 |
+| :------------------------------- | :------------------------- |
+| `%` | 代表零个或多个字符 |
+| `_` | 仅替代一个字符 |
+| `[charlist]` | 字符列中的任何单一字符 |
+| `[^charlist]` 或者 `[!charlist]` | 不在字符列中的任何单一字符 |
+
+### 检索产品名称和描述(一)
+
+`Products` 表如下:
+
+| prod_name | prod_desc |
+| --------- | -------------- |
+| a0011 | usb |
+| a0019 | iphone13 |
+| b0019 | gucci t-shirts |
+| c0019 | gucci toy |
+| d0019 | lego toy |
+
+【问题】编写 SQL 语句,从 `Products` 表中检索产品名称(`prod_name`)和描述(`prod_desc`),仅返回描述中包含 `toy` 一词的产品名称。
+
+答案:
+
+```sql
+SELECT prod_name, prod_desc
+FROM Products
+WHERE prod_desc LIKE '%toy%'
+```
+
+### 检索产品名称和描述(二)
+
+`Products` 表如下:
+
+| prod_name | prod_desc |
+| --------- | -------------- |
+| a0011 | usb |
+| a0019 | iphone13 |
+| b0019 | gucci t-shirts |
+| c0019 | gucci toy |
+| d0019 | lego toy |
+
+【问题】编写 SQL 语句,从 `Products` 表中检索产品名称(`prod_name`)和描述(`prod_desc`),仅返回描述中未出现 `toy` 一词的产品,最后按”产品名称“对结果进行排序。
+
+答案:
+
+```sql
+SELECT prod_name, prod_desc
+FROM Products
+WHERE prod_desc NOT LIKE '%toy%'
+ORDER BY prod_name
+```
+
+### 检索产品名称和描述(三)
+
+`Products` 表如下:
+
+| prod_name | prod_desc |
+| --------- | ---------------- |
+| a0011 | usb |
+| a0019 | iphone13 |
+| b0019 | gucci t-shirts |
+| c0019 | gucci toy |
+| d0019 | lego carrots toy |
+
+【问题】编写 SQL 语句,从 `Products` 表中检索产品名称(`prod_name`)和描述(`prod_desc`),仅返回描述中同时出现 `toy` 和 `carrots` 的产品。有好几种方法可以执行此操作,但对于这个挑战题,请使用 `AND` 和两个 `LIKE` 比较。
+
+答案:
+
+```sql
+SELECT prod_name, prod_desc
+FROM Products
+WHERE prod_desc LIKE '%toy%' AND prod_desc LIKE "%carrots%"
+```
+
+### 检索产品名称和描述(四)
+
+`Products` 表如下:
+
+| prod_name | prod_desc |
+| --------- | ---------------- |
+| a0011 | usb |
+| a0019 | iphone13 |
+| b0019 | gucci t-shirts |
+| c0019 | gucci toy |
+| d0019 | lego toy carrots |
+
+【问题】编写 SQL 语句,从 Products 表中检索产品名称(prod_name)和描述(prod_desc),仅返回在描述中以**先后顺序**同时出现 toy 和 carrots 的产品。提示:只需要用带有三个 `%` 符号的 `LIKE` 即可。
+
+答案:
+
+```sql
+SELECT prod_name, prod_desc
+FROM Products
+WHERE prod_desc LIKE '%toy%carrots%'
+```
+
+## 创建计算字段
+
+### 别名
+
+别名的常见用法是在检索出的结果中重命名表的列字段(为了符合特定的报表要求或客户需求)。有表 `Vendors` 代表供应商信息,`vend_id` 供应商 id、`vend_name` 供应商名称、`vend_address` 供应商地址、`vend_city` 供应商城市。
+
+| vend_id | vend_name | vend_address | vend_city |
+| ------- | ------------- | ------------ | --------- |
+| a001 | tencent cloud | address1 | shenzhen |
+| a002 | huawei cloud | address2 | dongguan |
+| a003 | aliyun cloud | address3 | hangzhou |
+| a003 | netease cloud | address4 | guangzhou |
+
+【问题】编写 SQL 语句,从 `Vendors` 表中检索 `vend_id`、`vend_name`、`vend_address` 和 `vend_city`,将 `vend_name` 重命名为 `vname`,将 `vend_city` 重命名为 `vcity`,将 `vend_address` 重命名为 `vaddress`,按供应商名称对结果进行升序排序。
+
+答案:
+
+```sql
+SELECT vend_id, vend_name AS vname, vend_address AS vaddress, vend_city AS vcity
+FROM Vendors
+ORDER BY vname
+# as 可以省略
+SELECT vend_id, vend_name vname, vend_address vaddress, vend_city vcity
+FROM Vendors
+ORDER BY vname
+```
+
+### 打折
+
+我们的示例商店正在进行打折促销,所有产品均降价 10%。`Products` 表包含 `prod_id` 产品 id、`prod_price` 产品价格。
+
+【问题】编写 SQL 语句,从 `Products` 表中返回 `prod_id`、`prod_price` 和 `sale_price`。`sale_price` 是一个包含促销价格的计算字段。提示:可以乘以 0.9,得到原价的 90%(即 10%的折扣)。
+
+答案:
+
+```sql
+SELECT prod_id, prod_price, prod_price * 0.9 AS sale_price
+FROM Products
+```
+
+注意:`sale_price` 是对计算结果的命名,而不是原有的列名。
+
+## 使用函数处理数据
+
+### 顾客登录名
+
+我们的商店已经上线了,正在创建顾客账户。所有用户都需要登录名,默认登录名是其名称和所在城市的组合。
+
+给出 `Customers` 表 如下:
+
+| cust_id | cust_name | cust_contact | cust_city |
+| ------- | --------- | ------------ | --------- |
+| a1 | Andy Li | Andy Li | Oak Park |
+| a2 | Ben Liu | Ben Liu | Oak Park |
+| a3 | Tony Dai | Tony Dai | Oak Park |
+| a4 | Tom Chen | Tom Chen | Oak Park |
+| a5 | An Li | An Li | Oak Park |
+| a6 | Lee Chen | Lee Chen | Oak Park |
+| a7 | Hex Liu | Hex Liu | Oak Park |
+
+【问题】编写 SQL 语句,返回顾客 ID(`cust_id`)、顾客名称(`cust_name`)和登录名(`user_login`),其中登录名全部为大写字母,并由顾客联系人的前两个字符(`cust_contact`)和其所在城市的前三个字符(`cust_city`)组成。提示:需要使用函数、拼接和别名。
+
+答案:
+
+```sql
+SELECT cust_id, cust_name, UPPER(CONCAT(SUBSTRING(cust_contact, 1, 2), SUBSTRING(cust_city, 1, 3))) AS user_login
+FROM Customers
+```
+
+知识点:
+
+- 截取函数`SUBSTRING()`:截取字符串,`substring(str ,n ,m)`(n 表示起始截取位置,m 表示要截取的字符个数)表示返回字符串 str 从第 n 个字符开始截取 m 个字符;
+- 拼接函数`CONCAT()`:将两个或多个字符串连接成一个字符串,select concat(A,B):连接字符串 A 和 B。
+
+- 大写函数 `UPPER()`:将指定字符串转换为大写。
+
+### 返回 2020 年 1 月的所有订单的订单号和订单日期
+
+`Orders` 订单表如下:
+
+| order_num | order_date |
+| --------- | ------------------- |
+| a0001 | 2020-01-01 00:00:00 |
+| a0002 | 2020-01-02 00:00:00 |
+| a0003 | 2020-01-01 12:00:00 |
+| a0004 | 2020-02-01 00:00:00 |
+| a0005 | 2020-03-01 00:00:00 |
+
+【问题】编写 SQL 语句,返回 2020 年 1 月的所有订单的订单号(`order_num`)和订单日期(`order_date`),并按订单日期升序排序
+
+答案:
+
+```sql
+SELECT order_num, order_date
+FROM Orders
+WHERE month(order_date) = '01' AND YEAR(order_date) = '2020'
+ORDER BY order_date
+```
+
+也可以用通配符来做:
+
+```sql
+SELECT order_num, order_date
+FROM Orders
+WHERE order_date LIKE '2020-01%'
+ORDER BY order_date
+```
+
+知识点:
+
+- 日期格式:`YYYY-MM-DD`
+- 时间格式:`HH:MM:SS`
+
+日期和时间处理相关的常用函数:
+
+| 函 数 | 说 明 |
+| --------------- | ------------------------------ |
+| `ADDDATE()` | 增加一个日期(天、周等) |
+| `ADDTIME()` | 增加一个时间(时、分等) |
+| `CURDATE()` | 返回当前日期 |
+| `CURTIME()` | 返回当前时间 |
+| `DATE()` | 返回日期时间的日期部分 |
+| `DATEDIFF` | 计算两个日期之差 |
+| `DATE_FORMAT()` | 返回一个格式化的日期或时间串 |
+| `DAY()` | 返回一个日期的天数部分 |
+| `DAYOFWEEK()` | 对于一个日期,返回对应的星期几 |
+| `HOUR()` | 返回一个时间的小时部分 |
+| `MINUTE()` | 返回一个时间的分钟部分 |
+| `MONTH()` | 返回一个日期的月份部分 |
+| `NOW()` | 返回当前日期和时间 |
+| `SECOND()` | 返回一个时间的秒部分 |
+| `TIME()` | 返回一个日期时间的时间部分 |
+| `YEAR()` | 返回一个日期的年份部分 |
+
+## 汇总数据
+
+汇总数据相关的函数:
+
+| 函 数 | 说 明 |
+| --------- | ---------------- |
+| `AVG()` | 返回某列的平均值 |
+| `COUNT()` | 返回某列的行数 |
+| `MAX()` | 返回某列的最大值 |
+| `MIN()` | 返回某列的最小值 |
+| `SUM()` | 返回某列值之和 |
+
+### 确定已售出产品的总数
+
+`OrderItems` 表代表售出的产品,`quantity` 代表售出商品数量。
+
+| quantity |
+| -------- |
+| 10 |
+| 100 |
+| 1000 |
+| 10001 |
+| 2 |
+| 15 |
+
+【问题】编写 SQL 语句,确定已售出产品的总数。
+
+答案:
+
+```sql
+SELECT Sum(quantity) AS items_ordered
+FROM OrderItems
+```
+
+### 确定已售出产品项 BR01 的总数
+
+`OrderItems` 表代表售出的产品,`quantity` 代表售出商品数量,产品项为 `prod_id`。
+
+| quantity | prod_id |
+| -------- | ------- |
+| 10 | AR01 |
+| 100 | AR10 |
+| 1000 | BR01 |
+| 10001 | BR010 |
+
+【问题】修改创建的语句,确定已售出产品项(`prod_id`)为"BR01"的总数。
+
+答案:
+
+```sql
+SELECT Sum(quantity) AS items_ordered
+FROM OrderItems
+WHERE prod_id = 'BR01'
+```
+
+### 确定 Products 表中价格不超过 10 美元的最贵产品的价格
+
+`Products` 表如下,`prod_price` 代表商品的价格。
+
+| prod_price |
+| ---------- |
+| 9.49 |
+| 600 |
+| 1000 |
+
+【问题】编写 SQL 语句,确定 `Products` 表中价格不超过 10 美元的最贵产品的价格(`prod_price`)。将计算所得的字段命名为 `max_price`。
+
+答案:
+
+```sql
+SELECT Max(prod_price) AS max_price
+FROM Products
+WHERE prod_price <= 10
+```
+
+## 分组数据
+
+`GROUP BY`:
+
+- `GROUP BY` 子句将记录分组到汇总行中。
+- `GROUP BY` 为每个组返回一个记录。
+- `GROUP BY` 通常还涉及聚合`COUNT`,`MAX`,`SUM`,`AVG` 等。
+- `GROUP BY` 可以按一列或多列进行分组。
+- `GROUP BY` 按分组字段进行排序后,`ORDER BY` 可以以汇总字段来进行排序。
+
+`HAVING`:
+
+- `HAVING` 用于对汇总的 `GROUP BY` 结果进行过滤。
+- `HAVING` 必须要与 `GROUP BY` 连用。
+- `WHERE` 和 `HAVING` 可以在相同的查询中。
+
+`HAVING` vs `WHERE`:
+
+- `WHERE`:过滤指定的行,后面不能加聚合函数(分组函数)。
+- `HAVING`:过滤分组,必须要与 `GROUP BY` 连用,不能单独使用。
+
+### 返回每个订单号各有多少行数
+
+`OrderItems` 表包含每个订单的每个产品
+
+| order_num |
+| --------- |
+| a002 |
+| a002 |
+| a002 |
+| a004 |
+| a007 |
+
+【问题】编写 SQL 语句,返回每个订单号(`order_num`)各有多少行数(`order_lines`),并按 `order_lines` 对结果进行升序排序。
+
+答案:
+
+```sql
+SELECT order_num, Count(order_num) AS order_lines
+FROM OrderItems
+GROUP BY order_num
+ORDER BY order_lines
+```
+
+知识点:
+
+1. `count(*)`,`count(列名)`都可以,区别在于,`count(列名)`是统计非 NULL 的行数;
+2. `order by` 最后执行,所以可以使用列别名;
+3. 分组聚合一定不要忘记加上 `group by` ,不然只会有一行结果。
+
+### 每个供应商成本最低的产品
+
+有 `Products` 表,含有字段 `prod_price` 代表产品价格,`vend_id` 代表供应商 id
+
+| vend_id | prod_price |
+| ------- | ---------- |
+| a0011 | 100 |
+| a0019 | 0.1 |
+| b0019 | 1000 |
+| b0019 | 6980 |
+| b0019 | 20 |
+
+【问题】编写 SQL 语句,返回名为 `cheapest_item` 的字段,该字段包含每个供应商成本最低的产品(使用 `Products` 表中的 `prod_price`),然后从最低成本到最高成本对结果进行升序排序。
+
+答案:
+
+```sql
+SELECT vend_id, Min(prod_price) AS cheapest_item
+FROM Products
+GROUP BY vend_id
+ORDER BY cheapest_item
+```
+
+### 返回订单数量总和不小于 100 的所有订单的订单号
+
+`OrderItems` 代表订单商品表,包括:订单号 `order_num` 和订单数量 `quantity`。
+
+| order_num | quantity |
+| --------- | -------- |
+| a1 | 105 |
+| a2 | 1100 |
+| a2 | 200 |
+| a4 | 1121 |
+| a5 | 10 |
+| a2 | 19 |
+| a7 | 5 |
+
+【问题】请编写 SQL 语句,返回订单数量总和不小于 100 的所有订单号,最后结果按照订单号升序排序。
+
+答案:
+
+```sql
+# 直接聚合
+SELECT order_num
+FROM OrderItems
+GROUP BY order_num
+HAVING Sum(quantity) >= 100
+ORDER BY order_num
+
+# 子查询
+SELECT a.order_num
+FROM (SELECT order_num, Sum(quantity) AS sum_num
+ FROM OrderItems
+ GROUP BY order_num
+ HAVING sum_num >= 100) a
+ORDER BY a.order_num
+```
+
+知识点:
+
+- `where`:过滤过滤指定的行,后面不能加聚合函数(分组函数)。
+- `having`:过滤分组,与 `group by` 连用,不能单独使用。
+
+### 计算总和
+
+`OrderItems` 表代表订单信息,包括字段:订单号 `order_num` 和 `item_price` 商品售出价格、`quantity` 商品数量。
+
+| order_num | item_price | quantity |
+| --------- | ---------- | -------- |
+| a1 | 10 | 105 |
+| a2 | 1 | 1100 |
+| a2 | 1 | 200 |
+| a4 | 2 | 1121 |
+| a5 | 5 | 10 |
+| a2 | 1 | 19 |
+| a7 | 7 | 5 |
+
+【问题】编写 SQL 语句,根据订单号聚合,返回订单总价不小于 1000 的所有订单号,最后的结果按订单号进行升序排序。
+
+提示:总价 = item_price 乘以 quantity
+
+答案:
+
+```sql
+SELECT order_num, Sum(item_price * quantity) AS total_price
+FROM OrderItems
+GROUP BY order_num
+HAVING total_price >= 1000
+ORDER BY order_num
+```
+
+### 检查 SQL 语句
+
+`OrderItems` 表含有 `order_num` 订单号
+
+| order_num |
+| --------- |
+| a002 |
+| a002 |
+| a002 |
+| a004 |
+| a007 |
+
+【问题】将下面代码修改正确后执行
+
+```sql
+SELECT order_num, COUNT(*) AS items
+FROM OrderItems
+GROUP BY items
+HAVING COUNT(*) >= 3
+ORDER BY items, order_num;
+```
+
+修改后:
+
+```sql
+SELECT order_num, COUNT(*) AS items
+FROM OrderItems
+GROUP BY order_num
+HAVING items >= 3
+ORDER BY items, order_num;
+```
+
+## 使用子查询
+
+子查询是嵌套在较大查询中的 SQL 查询,也称内部查询或内部选择,包含子查询的语句也称为外部查询或外部选择。简单来说,子查询就是指将一个 `SELECT` 查询(子查询)的结果作为另一个 SQL 语句(主查询)的数据来源或者判断条件。
+
+子查询可以嵌入 `SELECT`、`INSERT`、`UPDATE` 和 `DELETE` 语句中,也可以和 `=`、`<`、`>`、`IN`、`BETWEEN`、`EXISTS` 等运算符一起使用。
+
+子查询常用在 `WHERE` 子句和 `FROM` 子句后边:
+
+- 当用于 `WHERE` 子句时,根据不同的运算符,子查询可以返回单行单列、多行单列、单行多列数据。子查询就是要返回能够作为 WHERE 子句查询条件的值。
+- 当用于 `FROM` 子句时,一般返回多行多列数据,相当于返回一张临时表,这样才符合 `FROM` 后面是表的规则。这种做法能够实现多表联合查询。
+
+> 注意:MySQL 数据库从 4.1 版本才开始支持子查询,早期版本是不支持的。
+
+用于 `WHERE` 子句的子查询的基本语法如下:
+
+```sql
+SELECT column_name [, column_name ]
+FROM table1 [, table2 ]
+WHERE column_name operator
+(SELECT column_name [, column_name ]
+FROM table1 [, table2 ]
+[WHERE])
+```
+
+- 子查询需要放在括号`( )`内。
+- `operator` 表示用于 `WHERE` 子句的运算符,可以是比较运算符(如 `=`, `<`, `>`, `<>` 等)或逻辑运算符(如 `IN`, `NOT IN`, `EXISTS`, `NOT EXISTS` 等),具体根据需求来确定。
+
+用于 `FROM` 子句的子查询的基本语法如下:
+
+```sql
+SELECT column_name [, column_name ]
+FROM (SELECT column_name [, column_name ]
+ FROM table1 [, table2 ]
+ [WHERE]) AS temp_table_name [, ...]
+[JOIN type JOIN table_name ON condition]
+WHERE condition;
+```
+
+- 用于 `FROM` 的子查询返回的结果相当于一张临时表,所以需要使用 AS 关键字为该临时表起一个名字。
+- 子查询需要放在括号 `( )` 内。
+- 可以指定多个临时表名,并使用 `JOIN` 语句连接这些表。
+
+### 返回购买价格为 10 美元或以上产品的顾客列表
+
+`OrderItems` 表示订单商品表,含有字段订单号:`order_num`、订单价格:`item_price`;`Orders` 表代表订单信息表,含有顾客 `id:cust_id` 和订单号:`order_num`
+
+`OrderItems` 表:
+
+| order_num | item_price |
+| --------- | ---------- |
+| a1 | 10 |
+| a2 | 1 |
+| a2 | 1 |
+| a4 | 2 |
+| a5 | 5 |
+| a2 | 1 |
+| a7 | 7 |
+
+`Orders` 表:
+
+| order_num | cust_id |
+| --------- | ------- |
+| a1 | cust10 |
+| a2 | cust1 |
+| a2 | cust1 |
+| a4 | cust2 |
+| a5 | cust5 |
+| a2 | cust1 |
+| a7 | cust7 |
+
+【问题】使用子查询,返回购买价格为 10 美元或以上产品的顾客列表,结果无需排序。
+
+答案:
+
+```sql
+SELECT cust_id
+FROM Orders
+WHERE order_num IN (SELECT order_num
+ FROM OrderItems
+ GROUP BY order_num
+ HAVING Sum(item_price) >= 10)
+```
+
+### 确定哪些订单购买了 prod_id 为 BR01 的产品(一)
+
+表 `OrderItems` 代表订单商品信息表,`prod_id` 为产品 id;`Orders` 表代表订单表有 `cust_id` 代表顾客 id 和订单日期 `order_date`
+
+`OrderItems` 表:
+
+| prod_id | order_num |
+| ------- | --------- |
+| BR01 | a0001 |
+| BR01 | a0002 |
+| BR02 | a0003 |
+| BR02 | a0013 |
+
+`Orders` 表:
+
+| order_num | cust_id | order_date |
+| --------- | ------- | ------------------- |
+| a0001 | cust10 | 2022-01-01 00:00:00 |
+| a0002 | cust1 | 2022-01-01 00:01:00 |
+| a0003 | cust1 | 2022-01-02 00:00:00 |
+| a0013 | cust2 | 2022-01-01 00:20:00 |
+
+【问题】
+
+编写 SQL 语句,使用子查询来确定哪些订单(在 `OrderItems` 中)购买了 `prod_id` 为 "BR01" 的产品,然后从 `Orders` 表中返回每个产品对应的顾客 ID(`cust_id`)和订单日期(`order_date`),按订购日期对结果进行升序排序。
+
+答案:
+
+```sql
+# 写法 1:子查询
+SELECT cust_id,order_date
+FROM Orders
+WHERE order_num IN
+ (SELECT order_num
+ FROM OrderItems
+ WHERE prod_id = 'BR01' )
+ORDER BY order_date;
+
+# 写法 2: 连接表
+SELECT b.cust_id, b.order_date
+FROM OrderItems a,Orders b
+WHERE a.order_num = b.order_num AND a.prod_id = 'BR01'
+ORDER BY order_date
+```
+
+### 返回购买 prod_id 为 BR01 的产品的所有顾客的电子邮件(一)
+
+你想知道订购 BR01 产品的日期,有表 `OrderItems` 代表订单商品信息表,`prod_id` 为产品 id;`Orders` 表代表订单表有 `cust_id` 代表顾客 id 和订单日期 `order_date`;`Customers` 表含有 `cust_email` 顾客邮件和 `cust_id` 顾客 id
+
+`OrderItems` 表:
+
+| prod_id | order_num |
+| ------- | --------- |
+| BR01 | a0001 |
+| BR01 | a0002 |
+| BR02 | a0003 |
+| BR02 | a0013 |
+
+`Orders` 表:
+
+| order_num | cust_id | order_date |
+| --------- | ------- | ------------------- |
+| a0001 | cust10 | 2022-01-01 00:00:00 |
+| a0002 | cust1 | 2022-01-01 00:01:00 |
+| a0003 | cust1 | 2022-01-02 00:00:00 |
+| a0013 | cust2 | 2022-01-01 00:20:00 |
+
+`Customers` 表代表顾客信息,`cust_id` 为顾客 id,`cust_email` 为顾客 email
+
+| cust_id | cust_email |
+| ------- | --------------- |
+| cust10 | cust10@cust.com |
+| cust1 | cust1@cust.com |
+| cust2 | cust2@cust.com |
+
+【问题】返回购买 `prod_id` 为 `BR01` 的产品的所有顾客的电子邮件(`Customers` 表中的 `cust_email`),结果无需排序。
+
+提示:这涉及 `SELECT` 语句,最内层的从 `OrderItems` 表返回 `order_num`,中间的从 `Customers` 表返回 `cust_id`。
+
+答案:
+
+```sql
+# 写法 1:子查询
+SELECT cust_email
+FROM Customers
+WHERE cust_id IN (SELECT cust_id
+ FROM Orders
+ WHERE order_num IN (SELECT order_num
+ FROM OrderItems
+ WHERE prod_id = 'BR01'))
+
+# 写法 2: 连接表(inner join)
+SELECT c.cust_email
+FROM OrderItems a,Orders b,Customers c
+WHERE a.order_num = b.order_num AND b.cust_id = c.cust_id AND a.prod_id = 'BR01'
+
+# 写法 3:连接表(left join)
+SELECT c.cust_email
+FROM Orders a LEFT JOIN
+ OrderItems b ON a.order_num = b.order_num LEFT JOIN
+ Customers c ON a.cust_id = c.cust_id
+WHERE b.prod_id = 'BR01'
+```
+
+### 返回每个顾客不同订单的总金额
+
+我们需要一个顾客 ID 列表,其中包含他们已订购的总金额。
+
+`OrderItems` 表代表订单信息,`OrderItems` 表有订单号:`order_num` 和商品售出价格:`item_price`、商品数量:`quantity`。
+
+| order_num | item_price | quantity |
+| --------- | ---------- | -------- |
+| a0001 | 10 | 105 |
+| a0002 | 1 | 1100 |
+| a0002 | 1 | 200 |
+| a0013 | 2 | 1121 |
+| a0003 | 5 | 10 |
+| a0003 | 1 | 19 |
+| a0003 | 7 | 5 |
+
+`Orders` 表订单号:`order_num`、顾客 id:`cust_id`
+
+| order_num | cust_id |
+| --------- | ------- |
+| a0001 | cust10 |
+| a0002 | cust1 |
+| a0003 | cust1 |
+| a0013 | cust2 |
+
+【问题】
+
+编写 SQL 语句,返回顾客 ID(`Orders` 表中的 `cust_id`),并使用子查询返回 `total_ordered` 以便返回每个顾客的订单总数,将结果按金额从大到小排序。
+
+答案:
+
+```sql
+# 写法 1:子查询
+SELECT o.cust_id AS cust_id, tb.total_ordered AS total_ordered
+FROM (SELECT order_num, Sum(item_price * quantity) AS total_ordered
+ FROM OrderItems
+ GROUP BY order_num) AS tb,
+ Orders o
+WHERE tb.order_num = o.order_num
+ORDER BY total_ordered DESC
+
+# 写法 2:连接表
+SELECT b.cust_id, Sum(a.quantity * a.item_price) AS total_ordered
+FROM OrderItems a,Orders b
+WHERE a.order_num = b.order_num
+GROUP BY cust_id
+ORDER BY total_ordered DESC
+```
+
+### 从 Products 表中检索所有的产品名称以及对应的销售总数
+
+`Products` 表中检索所有的产品名称:`prod_name`、产品 id:`prod_id`
+
+| prod_id | prod_name |
+| ------- | --------- |
+| a0001 | egg |
+| a0002 | sockets |
+| a0013 | coffee |
+| a0003 | cola |
+
+`OrderItems` 代表订单商品表,订单产品:`prod_id`、售出数量:`quantity`
+
+| prod_id | quantity |
+| ------- | -------- |
+| a0001 | 105 |
+| a0002 | 1100 |
+| a0002 | 200 |
+| a0013 | 1121 |
+| a0003 | 10 |
+| a0003 | 19 |
+| a0003 | 5 |
+
+【问题】
+
+编写 SQL 语句,从 `Products` 表中检索所有的产品名称(`prod_name`),以及名为 `quant_sold` 的计算列,其中包含所售产品的总数(在 `OrderItems` 表上使用子查询和 `SUM(quantity)` 检索)。
+
+答案:
+
+```sql
+# 写法 1:子查询
+SELECT p.prod_name, tb.quant_sold
+FROM (SELECT prod_id, Sum(quantity) AS quant_sold
+ FROM OrderItems
+ GROUP BY prod_id) AS tb,
+ Products p
+WHERE tb.prod_id = p.prod_id
+
+# 写法 2:连接表
+SELECT p.prod_name, Sum(o.quantity) AS quant_sold
+FROM Products p,
+ OrderItems o
+WHERE p.prod_id = o.prod_id
+GROUP BY p.prod_name(这里不能用 p.prod_id,会报错)
+```
+
+## 连接表
+
+JOIN 是“连接”的意思,顾名思义,SQL JOIN 子句用于将两个或者多个表联合起来进行查询。
+
+连接表时需要在每个表中选择一个字段,并对这些字段的值进行比较,值相同的两条记录将合并为一条。**连接表的本质就是将不同表的记录合并起来,形成一张新表。当然,这张新表只是临时的,它仅存在于本次查询期间**。
+
+使用 `JOIN` 连接两个表的基本语法如下:
+
+```sql
+SELECT table1.column1, table2.column2...
+FROM table1
+JOIN table2
+ON table1.common_column1 = table2.common_column2;
+```
+
+`table1.common_column1 = table2.common_column2` 是连接条件,只有满足此条件的记录才会合并为一行。您可以使用多个运算符来连接表,例如 =、>、<、<>、<=、>=、!=、`between`、`like` 或者 `not`,但是最常见的是使用 =。
+
+当两个表中有同名的字段时,为了帮助数据库引擎区分是哪个表的字段,在书写同名字段名时需要加上表名。当然,如果书写的字段名在两个表中是唯一的,也可以不使用以上格式,只写字段名即可。
+
+另外,如果两张表的关联字段名相同,也可以使用 `USING`子句来代替 `ON`,举个例子:
+
+```sql
+# join....on
+SELECT c.cust_name, o.order_num
+FROM Customers c
+INNER JOIN Orders o
+ON c.cust_id = o.cust_id
+ORDER BY c.cust_name
+
+# 如果两张表的关联字段名相同,也可以使用USING子句:JOIN....USING()
+SELECT c.cust_name, o.order_num
+FROM Customers c
+INNER JOIN Orders o
+USING(cust_id)
+ORDER BY c.cust_name
+```
+
+**`ON` 和 `WHERE` 的区别**:
+
+- 连接表时,SQL 会根据连接条件生成一张新的临时表。`ON` 就是连接条件,它决定临时表的生成。
+- `WHERE` 是在临时表生成以后,再对临时表中的数据进行过滤,生成最终的结果集,这个时候已经没有 JOIN-ON 了。
+
+所以总结来说就是:**SQL 先根据 ON 生成一张临时表,然后再根据 WHERE 对临时表进行筛选**。
+
+SQL 允许在 `JOIN` 左边加上一些修饰性的关键词,从而形成不同类型的连接,如下表所示:
+
+| 连接类型 | 说明 |
+| ---------------------------------------- | --------------------------------------------------------------------------------------------- |
+| INNER JOIN 内连接 | (默认连接方式)只有当两个表都存在满足条件的记录时才会返回行。 |
+| LEFT JOIN / LEFT OUTER JOIN 左(外)连接 | 返回左表中的所有行,即使右表中没有满足条件的行也是如此。 |
+| RIGHT JOIN / RIGHT OUTER JOIN 右(外)连接 | 返回右表中的所有行,即使左表中没有满足条件的行也是如此。 |
+| FULL JOIN / FULL OUTER JOIN 全(外)连接 | 只要其中有一个表存在满足条件的记录,就返回行。 |
+| SELF JOIN | 将一个表连接到自身,就像该表是两个表一样。为了区分两个表,在 SQL 语句中需要至少重命名一个表。 |
+| CROSS JOIN | 交叉连接,从两个或者多个连接表中返回记录集的笛卡尔积。 |
+
+下图展示了 LEFT JOIN、RIGHT JOIN、INNER JOIN、OUTER JOIN 相关的 7 种用法。
+
+
+
+如果不加任何修饰词,只写 `JOIN`,那么默认为 `INNER JOIN`
+
+对于 `INNER JOIN` 来说,还有一种隐式的写法,称为 “**隐式内连接**”,也就是没有 `INNER JOIN` 关键字,使用 `WHERE` 语句实现内连接的功能
+
+```sql
+# 隐式内连接
+SELECT c.cust_name, o.order_num
+FROM Customers c,Orders o
+WHERE c.cust_id = o.cust_id
+ORDER BY c.cust_name
+
+# 显式内连接
+SELECT c.cust_name, o.order_num
+FROM Customers c
+INNER JOIN Orders o
+USING(cust_id)
+ORDER BY c.cust_name;
+```
+
+### 返回顾客名称和相关订单号
+
+`Customers` 表有字段顾客名称 `cust_name`、顾客 id `cust_id`
+
+| cust_id | cust_name |
+| -------- | --------- |
+| cust10 | andy |
+| cust1 | ben |
+| cust2 | tony |
+| cust22 | tom |
+| cust221 | an |
+| cust2217 | hex |
+
+`Orders` 订单信息表,含有字段 `order_num` 订单号、`cust_id` 顾客 id
+
+| order_num | cust_id |
+| --------- | -------- |
+| a1 | cust10 |
+| a2 | cust1 |
+| a3 | cust2 |
+| a4 | cust22 |
+| a5 | cust221 |
+| a7 | cust2217 |
+
+【问题】编写 SQL 语句,返回 `Customers` 表中的顾客名称(`cust_name`)和 `Orders` 表中的相关订单号(`order_num`),并按顾客名称再按订单号对结果进行升序排序。你可以尝试用两个不同的写法,一个使用简单的等连接语法,另外一个使用 INNER JOIN。
+
+答案:
+
+```sql
+# 隐式内连接
+SELECT c.cust_name, o.order_num
+FROM Customers c,Orders o
+WHERE c.cust_id = o.cust_id
+ORDER BY c.cust_name,o.order_num
+
+# 显式内连接
+SELECT c.cust_name, o.order_num
+FROM Customers c
+INNER JOIN Orders o
+USING(cust_id)
+ORDER BY c.cust_name,o.order_num;
+```
+
+### 返回顾客名称和相关订单号以及每个订单的总价
+
+`Customers` 表有字段,顾客名称:`cust_name`、顾客 id:`cust_id`
+
+| cust_id | cust_name |
+| -------- | --------- |
+| cust10 | andy |
+| cust1 | ben |
+| cust2 | tony |
+| cust22 | tom |
+| cust221 | an |
+| cust2217 | hex |
+
+`Orders` 订单信息表,含有字段,订单号:`order_num`、顾客 id:`cust_id`
+
+| order_num | cust_id |
+| --------- | -------- |
+| a1 | cust10 |
+| a2 | cust1 |
+| a3 | cust2 |
+| a4 | cust22 |
+| a5 | cust221 |
+| a7 | cust2217 |
+
+`OrderItems` 表有字段,商品订单号:`order_num`、商品数量:`quantity`、商品价格:`item_price`
+
+| order_num | quantity | item_price |
+| --------- | -------- | ---------- |
+| a1 | 1000 | 10 |
+| a2 | 200 | 10 |
+| a3 | 10 | 15 |
+| a4 | 25 | 50 |
+| a5 | 15 | 25 |
+| a7 | 7 | 7 |
+
+【问题】除了返回顾客名称和订单号,返回 `Customers` 表中的顾客名称(`cust_name`)和 `Orders` 表中的相关订单号(`order_num`),添加第三列 `OrderTotal`,其中包含每个订单的总价,并按顾客名称再按订单号对结果进行升序排序。
+
+```sql
+# 简单的等连接语法
+SELECT c.cust_name, o.order_num, SUM(quantity * item_price) AS OrderTotal
+FROM Customers c,Orders o,OrderItems oi
+WHERE c.cust_id = o.cust_id AND o.order_num = oi.order_num
+GROUP BY c.cust_name, o.order_num
+ORDER BY c.cust_name, o.order_num
+```
+
+注意,可能有小伙伴会这样写:
+
+```sql
+SELECT c.cust_name, o.order_num, SUM(quantity * item_price) AS OrderTotal
+FROM Customers c,Orders o,OrderItems oi
+WHERE c.cust_id = o.cust_id AND o.order_num = oi.order_num
+GROUP BY c.cust_name
+ORDER BY c.cust_name,o.order_num
+```
+
+这是错误的!只对 `cust_name` 进行聚类确实符合题意,但是不符合 `GROUP BY` 的语法。
+
+select 语句中,如果没有 `GROUP BY` 语句,那么 `cust_name`、`order_num` 会返回若干个值,而 `sum(quantity * item_price)` 只返回一个值,通过 `group by` `cust_name` 可以让 `cust_name` 和 `sum(quantity * item_price)` 一一对应起来,或者说**聚类**,所以同样的,也要对 `order_num` 进行聚类。
+
+> **一句话,select 中的字段要么都聚类,要么都不聚类**
+
+### 确定哪些订单购买了 prod_id 为 BR01 的产品(二)
+
+表 `OrderItems` 代表订单商品信息表,`prod_id` 为产品 id;`Orders` 表代表订单表有 `cust_id` 代表顾客 id 和订单日期 `order_date`
+
+`OrderItems` 表:
+
+| prod_id | order_num |
+| ------- | --------- |
+| BR01 | a0001 |
+| BR01 | a0002 |
+| BR02 | a0003 |
+| BR02 | a0013 |
+
+`Orders` 表:
+
+| order_num | cust_id | order_date |
+| --------- | ------- | ------------------- |
+| a0001 | cust10 | 2022-01-01 00:00:00 |
+| a0002 | cust1 | 2022-01-01 00:01:00 |
+| a0003 | cust1 | 2022-01-02 00:00:00 |
+| a0013 | cust2 | 2022-01-01 00:20:00 |
+
+【问题】
+
+编写 SQL 语句,使用子查询来确定哪些订单(在 `OrderItems` 中)购买了 `prod_id` 为 "BR01" 的产品,然后从 `Orders` 表中返回每个产品对应的顾客 ID(`cust_id`)和订单日期(`order_date`),按订购日期对结果进行升序排序。
+
+提示:这一次使用连接和简单的等连接语法。
+
+```sql
+# 写法 1:子查询
+SELECT cust_id, order_date
+FROM Orders
+WHERE order_num IN (SELECT order_num
+ FROM OrderItems
+ WHERE prod_id = 'BR01')
+ORDER BY order_date
+
+# 写法 2:连接表 inner join
+SELECT cust_id, order_date
+FROM Orders o INNER JOIN
+ (SELECT order_num
+ FROM OrderItems
+ WHERE prod_id = 'BR01') tb ON o.order_num = tb.order_num
+ORDER BY order_date
+
+# 写法 3:写法 2 的简化版
+SELECT cust_id, order_date
+FROM Orders
+INNER JOIN OrderItems USING(order_num)
+WHERE OrderItems.prod_id = 'BR01'
+ORDER BY order_date
+```
+
+### 返回购买 prod_id 为 BR01 的产品的所有顾客的电子邮件(二)
+
+有表 `OrderItems` 代表订单商品信息表,`prod_id` 为产品 id;`Orders` 表代表订单表有 `cust_id` 代表顾客 id 和订单日期 `order_date`;`Customers` 表含有 `cust_email` 顾客邮件和 cust_id 顾客 id
+
+`OrderItems` 表:
+
+| prod_id | order_num |
+| ------- | --------- |
+| BR01 | a0001 |
+| BR01 | a0002 |
+| BR02 | a0003 |
+| BR02 | a0013 |
+
+`Orders` 表:
+
+| order_num | cust_id | order_date |
+| --------- | ------- | ------------------- |
+| a0001 | cust10 | 2022-01-01 00:00:00 |
+| a0002 | cust1 | 2022-01-01 00:01:00 |
+| a0003 | cust1 | 2022-01-02 00:00:00 |
+| a0013 | cust2 | 2022-01-01 00:20:00 |
+
+`Customers` 表代表顾客信息,`cust_id` 为顾客 id,`cust_email` 为顾客 email
+
+| cust_id | cust_email |
+| ------- | --------------- |
+| cust10 | cust10@cust.com |
+| cust1 | cust1@cust.com |
+| cust2 | cust2@cust.com |
+
+【问题】返回购买 `prod_id` 为 BR01 的产品的所有顾客的电子邮件(`Customers` 表中的 `cust_email`),结果无需排序。
+
+提示:涉及到 `SELECT` 语句,最内层的从 `OrderItems` 表返回 `order_num`,中间的从 `Customers` 表返回 `cust_id`,但是必须使用 INNER JOIN 语法。
+
+```sql
+SELECT cust_email
+FROM Customers
+INNER JOIN Orders using(cust_id)
+INNER JOIN OrderItems using(order_num)
+WHERE OrderItems.prod_id = 'BR01'
+```
+
+### 确定最佳顾客的另一种方式(二)
+
+`OrderItems` 表代表订单信息,确定最佳顾客的另一种方式是看他们花了多少钱,`OrderItems` 表有订单号 `order_num` 和 `item_price` 商品售出价格、`quantity` 商品数量
+
+| order_num | item_price | quantity |
+| --------- | ---------- | -------- |
+| a1 | 10 | 105 |
+| a2 | 1 | 1100 |
+| a2 | 1 | 200 |
+| a4 | 2 | 1121 |
+| a5 | 5 | 10 |
+| a2 | 1 | 19 |
+| a7 | 7 | 5 |
+
+`Orders` 表含有字段 `order_num` 订单号、`cust_id` 顾客 id
+
+| order_num | cust_id |
+| --------- | -------- |
+| a1 | cust10 |
+| a2 | cust1 |
+| a3 | cust2 |
+| a4 | cust22 |
+| a5 | cust221 |
+| a7 | cust2217 |
+
+顾客表 `Customers` 有字段 `cust_id` 客户 id、`cust_name` 客户姓名
+
+| cust_id | cust_name |
+| -------- | --------- |
+| cust10 | andy |
+| cust1 | ben |
+| cust2 | tony |
+| cust22 | tom |
+| cust221 | an |
+| cust2217 | hex |
+
+【问题】编写 SQL 语句,返回订单总价不小于 1000 的客户名称和总额(`OrderItems` 表中的 `order_num`)。
+
+提示:需要计算总和(`item_price` 乘以 `quantity`)。按总额对结果进行排序,请使用 `INNER JOIN`语法。
+
+```sql
+SELECT cust_name, SUM(item_price * quantity) AS total_price
+FROM Customers
+INNER JOIN Orders USING(cust_id)
+INNER JOIN OrderItems USING(order_num)
+GROUP BY cust_name
+HAVING total_price >= 1000
+ORDER BY total_price
+```
+
+## 创建高级连接
+
+### 检索每个顾客的名称和所有的订单号(一)
+
+`Customers` 表代表顾客信息含有顾客 id `cust_id` 和 顾客名称 `cust_name`
+
+| cust_id | cust_name |
+| -------- | --------- |
+| cust10 | andy |
+| cust1 | ben |
+| cust2 | tony |
+| cust22 | tom |
+| cust221 | an |
+| cust2217 | hex |
+
+`Orders` 表代表订单信息含有订单号 `order_num` 和顾客 id `cust_id`
+
+| order_num | cust_id |
+| --------- | -------- |
+| a1 | cust10 |
+| a2 | cust1 |
+| a3 | cust2 |
+| a4 | cust22 |
+| a5 | cust221 |
+| a7 | cust2217 |
+
+【问题】使用 INNER JOIN 编写 SQL 语句,检索每个顾客的名称(`Customers` 表中的 `cust_name`)和所有的订单号(`Orders` 表中的 `order_num`),最后根据顾客姓名 `cust_name` 升序返回。
+
+```sql
+SELECT cust_name, order_num
+FROM Customers
+INNER JOIN Orders
+USING(cust_id)
+ORDER BY cust_name
+```
+
+### 检索每个顾客的名称和所有的订单号(二)
+
+`Orders` 表代表订单信息含有订单号 `order_num` 和顾客 id `cust_id`
+
+| order_num | cust_id |
+| --------- | -------- |
+| a1 | cust10 |
+| a2 | cust1 |
+| a3 | cust2 |
+| a4 | cust22 |
+| a5 | cust221 |
+| a7 | cust2217 |
+
+`Customers` 表代表顾客信息含有顾客 id `cust_id` 和 顾客名称 `cust_name`
+
+| cust_id | cust_name |
+| -------- | --------- |
+| cust10 | andy |
+| cust1 | ben |
+| cust2 | tony |
+| cust22 | tom |
+| cust221 | an |
+| cust2217 | hex |
+| cust40 | ace |
+
+【问题】检索每个顾客的名称(`Customers` 表中的 `cust_name`)和所有的订单号(Orders 表中的 `order_num`),列出所有的顾客,即使他们没有下过订单。最后根据顾客姓名 `cust_name` 升序返回。
+
+```sql
+SELECT cust_name, order_num
+FROM Customers
+LEFT JOIN Orders
+USING(cust_id)
+ORDER BY cust_name
+```
+
+### 返回产品名称和与之相关的订单号
+
+`Products` 表为产品信息表含有字段 `prod_id` 产品 id、`prod_name` 产品名称
+
+| prod_id | prod_name |
+| ------- | --------- |
+| a0001 | egg |
+| a0002 | sockets |
+| a0013 | coffee |
+| a0003 | cola |
+| a0023 | soda |
+
+`OrderItems` 表为订单信息表含有字段 `order_num` 订单号和产品 id `prod_id`
+
+| prod_id | order_num |
+| ------- | --------- |
+| a0001 | a105 |
+| a0002 | a1100 |
+| a0002 | a200 |
+| a0013 | a1121 |
+| a0003 | a10 |
+| a0003 | a19 |
+| a0003 | a5 |
+
+【问题】使用外连接(left join、 right join、full join)联结 `Products` 表和 `OrderItems` 表,返回产品名称(`prod_name`)和与之相关的订单号(`order_num`)的列表,并按照产品名称升序排序。
+
+```sql
+SELECT prod_name, order_num
+FROM Products
+LEFT JOIN OrderItems
+USING(prod_id)
+ORDER BY prod_name
+```
+
+### 返回产品名称和每一项产品的总订单数
+
+`Products` 表为产品信息表含有字段 `prod_id` 产品 id、`prod_name` 产品名称
+
+| prod_id | prod_name |
+| ------- | --------- |
+| a0001 | egg |
+| a0002 | sockets |
+| a0013 | coffee |
+| a0003 | cola |
+| a0023 | soda |
+
+`OrderItems` 表为订单信息表含有字段 `order_num` 订单号和产品 id `prod_id`
+
+| prod_id | order_num |
+| ------- | --------- |
+| a0001 | a105 |
+| a0002 | a1100 |
+| a0002 | a200 |
+| a0013 | a1121 |
+| a0003 | a10 |
+| a0003 | a19 |
+| a0003 | a5 |
+
+【问题】
+
+使用 OUTER JOIN 联结 `Products` 表和 `OrderItems` 表,返回产品名称(`prod_name`)和每一项产品的总订单数(不是订单号),并按产品名称升序排序。
+
+```sql
+SELECT prod_name, COUNT(order_num) AS orders
+FROM Products
+LEFT JOIN OrderItems
+USING(prod_id)
+GROUP BY prod_name
+ORDER BY prod_name
+```
+
+### 列出供应商及其可供产品的数量
+
+有 `Vendors` 表含有 `vend_id` (供应商 id)
+
+| vend_id |
+| ------- |
+| a0002 |
+| a0013 |
+| a0003 |
+| a0010 |
+
+有 `Products` 表含有 `vend_id`(供应商 id)和 prod_id(供应产品 id)
+
+| vend_id | prod_id |
+| ------- | -------------------- |
+| a0001 | egg |
+| a0002 | prod_id_iphone |
+| a00113 | prod_id_tea |
+| a0003 | prod_id_vivo phone |
+| a0010 | prod_id_huawei phone |
+
+【问题】列出供应商(`Vendors` 表中的 `vend_id`)及其可供产品的数量,包括没有产品的供应商。你需要使用 OUTER JOIN 和 COUNT()聚合函数来计算 `Products` 表中每种产品的数量,最后根据 vend_id 升序排序。
+
+注意:`vend_id` 列会显示在多个表中,因此在每次引用它时都需要完全限定它。
+
+```sql
+SELECT vend_id, COUNT(prod_id) AS prod_id
+FROM Vendors
+LEFT JOIN Products
+USING(vend_id)
+GROUP BY vend_id
+ORDER BY vend_id
+```
+
+## 组合查询
+
+`UNION` 运算符将两个或更多查询的结果组合起来,并生成一个结果集,其中包含来自 `UNION` 中参与查询的提取行。
+
+`UNION` 基本规则:
+
+- 所有查询的列数和列顺序必须相同。
+- 每个查询中涉及表的列的数据类型必须相同或兼容。
+- 通常返回的列名取自第一个查询。
+
+默认地,`UNION` 操作符选取不同的值。如果允许重复的值,请使用 `UNION ALL`。
+
+```sql
+SELECT column_name(s) FROM table1
+UNION ALL
+SELECT column_name(s) FROM table2;
+```
+
+`UNION` 结果集中的列名总是等于 `UNION` 中第一个 `SELECT` 语句中的列名。
+
+`JOIN` vs `UNION`:
+
+- `JOIN` 中连接表的列可能不同,但在 `UNION` 中,所有查询的列数和列顺序必须相同。
+- `UNION` 将查询之后的行放在一起(垂直放置),但 `JOIN` 将查询之后的列放在一起(水平放置),即它构成一个笛卡尔积。
+
+### 将两个 SELECT 语句结合起来(一)
+
+表 `OrderItems` 包含订单产品信息,字段 `prod_id` 代表产品 id、`quantity` 代表产品数量
+
+| prod_id | quantity |
+| ------- | -------- |
+| a0001 | 105 |
+| a0002 | 100 |
+| a0002 | 200 |
+| a0013 | 1121 |
+| a0003 | 10 |
+| a0003 | 19 |
+| a0003 | 5 |
+| BNBG | 10002 |
+
+【问题】将两个 `SELECT` 语句结合起来,以便从 `OrderItems` 表中检索产品 id(`prod_id`)和 `quantity`。其中,一个 `SELECT` 语句过滤数量为 100 的行,另一个 `SELECT` 语句过滤 id 以 BNBG 开头的产品,最后按产品 id 对结果进行升序排序。
+
+```sql
+SELECT prod_id, quantity
+FROM OrderItems
+WHERE quantity = 100
+UNION
+SELECT prod_id, quantity
+FROM OrderItems
+WHERE prod_id LIKE 'BNBG%'
+```
+
+### 将两个 SELECT 语句结合起来(二)
+
+表 `OrderItems` 包含订单产品信息,字段 `prod_id` 代表产品 id、`quantity` 代表产品数量。
+
+| prod_id | quantity |
+| ------- | -------- |
+| a0001 | 105 |
+| a0002 | 100 |
+| a0002 | 200 |
+| a0013 | 1121 |
+| a0003 | 10 |
+| a0003 | 19 |
+| a0003 | 5 |
+| BNBG | 10002 |
+
+【问题】将两个 `SELECT` 语句结合起来,以便从 `OrderItems` 表中检索产品 id(`prod_id`)和 `quantity`。其中,一个 `SELECT` 语句过滤数量为 100 的行,另一个 `SELECT` 语句过滤 id 以 BNBG 开头的产品,最后按产品 id 对结果进行升序排序。 注意:**这次仅使用单个 SELECT 语句。**
+
+答案:
+
+要求只用一条 select 语句,那就用 `or` 不用 `union` 了。
+
+```sql
+SELECT prod_id, quantity
+FROM OrderItems
+WHERE quantity = 100 OR prod_id LIKE 'BNBG%'
+```
+
+### 组合 Products 表中的产品名称和 Customers 表中的顾客名称
+
+`Products` 表含有字段 `prod_name` 代表产品名称
+
+| prod_name |
+| --------- |
+| flower |
+| rice |
+| ring |
+| umbrella |
+
+Customers 表代表顾客信息,cust_name 代表顾客名称
+
+| cust_name |
+| --------- |
+| andy |
+| ben |
+| tony |
+| tom |
+| an |
+| lee |
+| hex |
+
+【问题】编写 SQL 语句,组合 `Products` 表中的产品名称(`prod_name`)和 `Customers` 表中的顾客名称(`cust_name`)并返回,然后按产品名称对结果进行升序排序。
+
+```sql
+# UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名。
+SELECT prod_name
+FROM Products
+UNION
+SELECT cust_name
+FROM Customers
+ORDER BY prod_name
+```
+
+### 检查 SQL 语句
+
+表 `Customers` 含有字段 `cust_name` 顾客名、`cust_contact` 顾客联系方式、`cust_state` 顾客州、`cust_email` 顾客 `email`
+
+| cust_name | cust_contact | cust_state | cust_email |
+| --------- | ------------ | ---------- | --------------- |
+| cust10 | 8695192 | MI | cust10@cust.com |
+| cust1 | 8695193 | MI | cust1@cust.com |
+| cust2 | 8695194 | IL | cust2@cust.com |
+
+【问题】修正下面错误的 SQL
+
+```sql
+SELECT cust_name, cust_contact, cust_email
+FROM Customers
+WHERE cust_state = 'MI'
+ORDER BY cust_name;
+UNION
+SELECT cust_name, cust_contact, cust_email
+FROM Customers
+WHERE cust_state = 'IL'ORDER BY cust_name;
+```
+
+修正后:
+
+```sql
+SELECT cust_name, cust_contact, cust_email
+FROM Customers
+WHERE cust_state = 'MI'
+UNION
+SELECT cust_name, cust_contact, cust_email
+FROM Customers
+WHERE cust_state = 'IL'
+ORDER BY cust_name;
+```
+
+使用 `union` 组合查询时,只能使用一条 `order by` 字句,他必须位于最后一条 `select` 语句之后
+
+或者直接用 `or` 来做:
+
+```sql
+SELECT cust_name, cust_contact, cust_email
+FROM Customers
+WHERE cust_state = 'MI' or cust_state = 'IL'
+ORDER BY cust_name;
+```
diff --git a/docs/database/sql/sql-questions-02.md b/docs/database/sql/sql-questions-02.md
new file mode 100644
index 0000000000000000000000000000000000000000..2bdbc994b361ba5c7bb95c829c4e12a4b6e2d807
--- /dev/null
+++ b/docs/database/sql/sql-questions-02.md
@@ -0,0 +1,448 @@
+---
+title: SQL常见面试题总结(2)
+category: 数据库
+tag:
+ - 数据库基础
+ - SQL
+---
+
+> 题目来源于:[牛客题霸 - SQL 进阶挑战](https://www.nowcoder.com/exam/oj?page=1&tab=SQL%E7%AF%87&topicId=240)
+
+## 增删改操作
+
+SQL 插入记录的方式汇总:
+
+- **普通插入(全字段)** :`INSERT INTO table_name VALUES (value1, value2, ...)`
+- **普通插入(限定字段)** :`INSERT INTO table_name (column1, column2, ...) VALUES (value1, value2, ...)`
+- **多条一次性插入** :`INSERT INTO table_name (column1, column2, ...) VALUES (value1_1, value1_2, ...), (value2_1, value2_2, ...), ...`
+- **从另一个表导入** :`INSERT INTO table_name SELECT * FROM table_name2 [WHERE key=value]`
+- **带更新的插入** :`REPLACE INTO table_name VALUES (value1, value2, ...)`(注意这种原理是检测到主键或唯一性索引键重复就删除原记录后重新插入)
+
+### 插入记录(一)
+
+**描述**:牛客后台会记录每个用户的试卷作答记录到 `exam_record` 表,现在有两个用户的作答记录详情如下:
+
+- 用户 1001 在 2021 年 9 月 1 日晚上 10 点 11 分 12 秒开始作答试卷 9001,并在 50 分钟后提交,得了 90 分;
+- 用户 1002 在 2021 年 9 月 4 日上午 7 点 1 分 2 秒开始作答试卷 9002,并在 10 分钟后退出了平台。
+
+试卷作答记录表`exam_record`中,表已建好,其结构如下,请用一条语句将这两条记录插入表中。
+
+| Filed | Type | Null | Key | Extra | Default | Comment |
+| ----------- | ---------- | ---- | ---- | -------------- | ------- | -------- |
+| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
+| uid | int(11) | NO | | | (NULL) | 用户 ID |
+| exam_id | int(11) | NO | | | (NULL) | 试卷 ID |
+| start_time | datetime | NO | | | (NULL) | 开始时间 |
+| submit_time | datetime | YES | | | (NULL) | 提交时间 |
+| score | tinyint(4) | YES | | | (NULL) | 得分 |
+
+**答案**:
+
+```sql
+// 存在自增主键,无需手动赋值
+INSERT INTO exam_record (uid, exam_id, start_time, submit_time, score) VALUES
+(1001, 9001, '2021-09-01 22:11:12', '2021-09-01 23:01:12', 90),
+(1002, 9002, '2021-09-04 07:01:02', NULL, NULL);
+```
+
+### 插入记录(二)
+
+**描述**:现有一张试卷作答记录表`exam_record`,结构如下表,其中包含多年来的用户作答试卷记录,由于数据越来越多,维护难度越来越大,需要对数据表内容做精简,历史数据做备份。
+
+表`exam_record`:
+
+| Filed | Type | Null | Key | Extra | Default | Comment |
+| ----------- | ---------- | ---- | ---- | -------------- | ------- | -------- |
+| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
+| uid | int(11) | NO | | | (NULL) | 用户 ID |
+| exam_id | int(11) | NO | | | (NULL) | 试卷 ID |
+| start_time | datetime | NO | | | (NULL) | 开始时间 |
+| submit_time | datetime | YES | | | (NULL) | 提交时间 |
+| score | tinyint(4) | YES | | | (NULL) | 得分 |
+
+我们已经创建了一张新表`exam_record_before_2021`用来备份 2021 年之前的试题作答记录,结构和`exam_record`表一致,请将 2021 年之前的已完成了的试题作答纪录导入到该表。
+
+**答案**:
+
+```sql
+INSERT INTO exam_record_before_2021 (uid, exam_id, start_time, submit_time, score)
+SELECT uid,exam_id,start_time,submit_time,score
+FROM exam_record
+WHERE YEAR(submit_time) < 2021;
+```
+
+### 插入记录(三)
+
+**描述**:现在有一套 ID 为 9003 的高难度 SQL 试卷,时长为一个半小时,请你将 2021-01-01 00:00:00 作为发布时间插入到试题信息表`examination_info`,不管该 ID 试卷是否存在,都要插入成功,请尝试插入它。
+
+试题信息表`examination_info`:
+
+| Filed | Type | Null | Key | Extra | Default | Comment |
+| ------------ | ----------- | ---- | ---- | -------------- | ------- | ------------ |
+| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
+| exam_id | int(11) | NO | UNI | | (NULL) | 试卷 ID |
+| tag | varchar(32) | YES | | | (NULL) | 类别标签 |
+| difficulty | varchar(8) | YES | | | (NULL) | 难度 |
+| duration | int(11) | NO | | | (NULL) | 时长(分钟数) |
+| release_time | datetime | YES | | | (NULL) | 发布时间 |
+
+**答案**:
+
+```sql
+REPLACE INTO examination_info VALUES
+ (NULL, 9003, "SQL", "hard", 90, "2021-01-01 00:00:00");
+```
+
+### 更新记录(一)
+
+**描述**:现在有一张试卷信息表 `examination_info`, 表结构如下图所示:
+
+| Filed | Type | Null | Key | Extra | Default | Comment |
+| ------------ | -------- | ---- | ---- | -------------- | ------- | -------- |
+| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
+| exam_id | int(11) | NO | UNI | | (NULL) | 试卷 ID |
+| tag | char(32) | YES | | | (NULL) | 类别标签 |
+| difficulty | char(8) | YES | | | (NULL) | 难度 |
+| duration | int(11) | NO | | | (NULL) | 时长 |
+| release_time | datetime | YES | | | (NULL) | 发布时间 |
+
+请把**examination_info**表中`tag`为`PYTHON`的`tag`字段全部修改为`Python`。
+
+**思路**:这题有两种解题思路,最容易想到的是直接`update + where`来指定条件更新,第二种就是根据要修改的字段进行查找替换
+
+**答案一**:
+
+```sql
+UPDATE examination_info SET tag = 'Python' WHERE tag='PYTHON'
+```
+
+**答案二**:
+
+```sql
+UPDATE examination_info
+SET tag = REPLACE(tag,'PYTHON','Python')
+
+# REPLACE (目标字段,"查找内容","替换内容")
+```
+
+### 更新记录(二)
+
+**描述**:现有一张试卷作答记录表 exam_record,其中包含多年来的用户作答试卷记录,结构如下表:作答记录表 `exam_record`: **`submit_time`** 为 完成时间 (注意这句话)
+
+| Filed | Type | Null | Key | Extra | Default | Comment |
+| ----------- | ---------- | ---- | ---- | -------------- | ------- | -------- |
+| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
+| uid | int(11) | NO | | | (NULL) | 用户 ID |
+| exam_id | int(11) | NO | | | (NULL) | 试卷 ID |
+| start_time | datetime | NO | | | (NULL) | 开始时间 |
+| submit_time | datetime | YES | | | (NULL) | 提交时间 |
+| score | tinyint(4) | YES | | | (NULL) | 得分 |
+
+**题目要求**:请把 `exam_record` 表中 2021 年 9 月 1 日==之前==开始作答的==未完成==记录全部改为被动完成,即:将完成时间改为'2099-01-01 00:00:00',分数改为 0。
+
+**思路**:注意题干中的关键字(已经高亮) `" xxx 时间 "`之前这个条件, 那么这里马上就要想到要进行时间的比较 可以直接 `xxx_time < "2021-09-01 00:00:00",` 也可以采用`date()`函数来进行比较;第二个条件就是 `"未完成"`, 即完成时间为 NULL,也就是题目中的提交时间 ----- `submit_time 为 NULL`。
+
+**答案**:
+
+```sql
+UPDATE exam_record SET submit_time = '2099-01-01 00:00:00', score = 0 WHERE DATE(start_time) < "2021-09-01" AND submit_time IS null
+```
+
+### 删除记录(一)
+
+**描述**:现有一张试卷作答记录表 `exam_record`,其中包含多年来的用户作答试卷记录,结构如下表:
+
+作答记录表`exam_record:` **`start_time`** 是试卷开始时间`submit_time` 是交卷,即结束时间。
+
+| Filed | Type | Null | Key | Extra | Default | Comment |
+| ----------- | ---------- | ---- | ---- | -------------- | ------- | -------- |
+| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
+| uid | int(11) | NO | | | (NULL) | 用户 ID |
+| exam_id | int(11) | NO | | | (NULL) | 试卷 ID |
+| start_time | datetime | NO | | | (NULL) | 开始时间 |
+| submit_time | datetime | YES | | | (NULL) | 提交时间 |
+| score | tinyint(4) | YES | | | (NULL) | 得分 |
+
+**要求**:请删除`exam_record`表中作答时间小于 5 分钟整且分数不及格(及格线为 60 分)的记录;
+
+**思路**:这一题虽然是练习删除,仔细看确是考察对时间函数的用法,这里提及的分钟数比较,常用的函数有 **`TIMEDIFF`**和**`TIMESTAMPDIFF`** ,两者用法稍有区别,后者更为灵活,这都是看个人习惯。
+
+1. `TIMEDIFF`:两个时间之间的差值
+
+```sql
+TIMEDIFF(time1, time2)
+```
+
+两者参数都是必须的,都是一个时间或者日期时间表达式。如果指定的参数不合法或者是 NULL,那么函数将返回 NULL。
+
+对于这题而言,可以用在 minute 函数里面,因为 TIMEDIFF 计算出来的是时间的差值,在外面套一个 MINUTE 函数,计算出来的就是分钟数。
+
+2. `TIMESTAMPDIFF`:用于计算两个日期的时间差
+
+```sql
+TIMESTAMPDIFF(unit,datetime_expr1,datetime_expr2)
+# 参数说明
+#unit: 日期比较返回的时间差单位,常用可选值如下:
+SECOND:秒
+MINUTE:分钟
+HOUR:小时
+DAY:天
+WEEK:星期
+MONTH:月
+QUARTER:季度
+YEAR:年
+# TIMESTAMPDIFF函数返回datetime_expr2 - datetime_expr1的结果(人话: 后面的 - 前面的 即2-1),其中datetime_expr1和datetime_expr2可以是DATE或DATETIME类型值(人话:可以是“2023-01-01”, 也可以是“2023-01-01- 00:00:00”)
+```
+
+ 这题需要进行分钟的比较,那么就是 TIMESTAMPDIFF(MINUTE, 开始时间, 结束时间) < 5
+
+**答案**:
+
+```sql
+DELETE FROM exam_record WHERE MINUTE (TIMEDIFF(submit_time , start_time)) < 5 AND score < 60
+```
+
+```sql
+DELETE FROM exam_record WHERE TIMESTAMPDIFF(MINUTE, start_time, submit_time) < 5 AND score < 60
+```
+
+### 删除记录(二)
+
+**描述**:现有一张试卷作答记录表`exam_record`,其中包含多年来的用户作答试卷记录,结构如下表:
+
+作答记录表`exam_record`:`start_time` 是试卷开始时间,`submit_time` 是交卷时间,即结束时间,如果未完成的话,则为空。
+
+| Filed | Type | Null | Key | Extra | Default | Comment |
+| ----------- | ---------- | :--: | ---- | -------------- | ------- | -------- |
+| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
+| uid | int(11) | NO | | | (NULL) | 用户 ID |
+| exam_id | int(11) | NO | | | (NULL) | 试卷 ID |
+| start_time | datetime | NO | | | (NULL) | 开始时间 |
+| submit_time | datetime | YES | | | (NULL) | 提交时间 |
+| score | tinyint(4) | YES | | | (NULL) | 分数 |
+
+**要求**:请删除`exam_record`表中未完成作答==或==作答时间小于 5 分钟整的记录中,开始作答时间最早的 3 条记录。
+
+**思路**:这题比较简单,但是要注意题干中给出的信息,结束时间,如果未完成的话,则为空,这个其实就是一个条件
+
+还有一个条件就是小于 5 分钟,跟上题类似,但是这里是**或**,即两个条件满足一个就行;另外就是稍微考察到了排序和 limit 的用法。
+
+**答案**:
+
+```sql
+DELETE FROM exam_record WHERE submit_time IS null OR TIMESTAMPDIFF(MINUTE, start_time, submit_time) < 5
+ORDER BY start_time
+LIMIT 3
+# 默认就是asc, desc是降序排列
+```
+
+### 删除记录(三)
+
+**描述**:现有一张试卷作答记录表 exam_record,其中包含多年来的用户作答试卷记录,结构如下表:
+
+| Filed | Type | Null | Key | Extra | Default | Comment |
+| ----------- | ---------- | :--: | ---- | -------------- | ------- | -------- |
+| id | int(11) | NO | PRI | auto_increment | (NULL) | 自增 ID |
+| uid | int(11) | NO | | | (NULL) | 用户 ID |
+| exam_id | int(11) | NO | | | (NULL) | 试卷 ID |
+| start_time | datetime | NO | | | (NULL) | 开始时间 |
+| submit_time | datetime | YES | | | (NULL) | 提交时间 |
+| score | tinyint(4) | YES | | | (NULL) | 分数 |
+
+**要求**:请删除`exam_record`表中所有记录,==并重置自增主键==
+
+**思路**:这题考察对三种删除语句的区别,注意高亮部分,要求重置主键;
+
+- `DROP`: 清空表,删除表结构,不可逆
+- `TRUNCATE`: 格式化表,不删除表结构,不可逆
+- `DELETE`:删除数据,可逆
+
+这里选用`TRUNCATE`的原因是:TRUNCATE 只能作用于表;`TRUNCATE`会清空表中的所有行,但表结构及其约束、索引等保持不变;`TRUNCATE`会重置表的自增值;使用`TRUNCATE`后会使表和索引所占用的空间会恢复到初始大小。
+
+这题也可以采用`DELETE`来做,但是在删除后,还需要手动`ALTER`表结构来设置主键初始值;
+
+同理也可以采用`DROP`来做,直接删除整张表,包括表结构,然后再新建表即可。
+
+**答案**:
+
+```sql
+TRUNCATE exam_record;
+```
+
+## 表与索引操作
+
+### 创建一张新表
+
+**描述**:现有一张用户信息表,其中包含多年来在平台注册过的用户信息,随着牛客平台的不断壮大,用户量飞速增长,为了高效地为高活跃用户提供服务,现需要将部分用户拆分出一张新表。
+
+原来的用户信息表:
+
+| Filed | Type | Null | Key | Default | Extra | Comment |
+| ------------- | ----------- | ---- | ---- | ----------------- | -------------- | -------- |
+| id | int(11) | NO | PRI | (NULL) | auto_increment | 自增 ID |
+| uid | int(11) | NO | UNI | (NULL) | | 用户 ID |
+| nick_name | varchar(64) | YES | | (NULL) | | 昵称 |
+| achievement | int(11) | YES | | 0 | | 成就值 |
+| level | int(11) | YES | | (NULL) | | 用户等级 |
+| job | varchar(32) | YES | | (NULL) | | 职业方向 |
+| register_time | datetime | YES | | CURRENT_TIMESTAMP | | 注册时间 |
+
+作为数据分析师,请**创建一张优质用户信息表 user_info_vip**,表结构和用户信息表一致。
+
+你应该返回的输出如下表格所示,请写出建表语句将表格中所有限制和说明记录到表里。
+
+| Filed | Type | Null | Key | Default | Extra | Comment |
+| ------------- | ----------- | ---- | ---- | ----------------- | -------------- | -------- |
+| id | int(11) | NO | PRI | (NULL) | auto_increment | 自增 ID |
+| uid | int(11) | NO | UNI | (NULL) | | 用户 ID |
+| nick_name | varchar(64) | YES | | (NULL) | | 昵称 |
+| achievement | int(11) | YES | | 0 | | 成就值 |
+| level | int(11) | YES | | (NULL) | | 用户等级 |
+| job | varchar(32) | YES | | (NULL) | | 职业方向 |
+| register_time | datetime | YES | | CURRENT_TIMESTAMP | | 注册时间 |
+
+**思路**:如果这题给出了旧表的名称,可直接`create table 新表 as select * from 旧表;` 但是这题并没有给出旧表名称,所以需要自己创建,注意默认值和键的创建即可,比较简单。(注意:如果是在牛客网上面执行,请注意 comment 中要和题目中的 comment 保持一致,包括大小写,否则不通过,还有字符也要设置)
+
+答案:
+
+```sql
+CREATE TABLE IF NOT EXISTS user_info_vip(
+ id INT(11) PRIMARY KEY AUTO_INCREMENT COMMENT'自增ID',
+ uid INT(11) UNIQUE NOT NULL COMMENT '用户ID',
+ nick_name VARCHAR(64) COMMENT'昵称',
+ achievement INT(11) DEFAULT 0 COMMENT '成就值',
+ level INT(11) COMMENT '用户等级',
+ job VARCHAR(32) COMMENT '职业方向',
+ register_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间'
+)CHARACTER SET UTF8
+```
+
+### 修改表
+
+**描述**: 现有一张用户信息表`user_info`,其中包含多年来在平台注册过的用户信息。
+
+**用户信息表 `user_info`:**
+
+| Filed | Type | Null | Key | Default | Extra | Comment |
+| ------------- | ----------- | ---- | ---- | ----------------- | -------------- | -------- |
+| id | int(11) | NO | PRI | (NULL) | auto_increment | 自增 ID |
+| uid | int(11) | NO | UNI | (NULL) | | 用户 ID |
+| nick_name | varchar(64) | YES | | (NULL) | | 昵称 |
+| achievement | int(11) | YES | | 0 | | 成就值 |
+| level | int(11) | YES | | (NULL) | | 用户等级 |
+| job | varchar(32) | YES | | (NULL) | | 职业方向 |
+| register_time | datetime | YES | | CURRENT_TIMESTAMP | | 注册时间 |
+
+**要求:**请在用户信息表,字段 `level` 的后面增加一列最多可保存 15 个汉字的字段 `school`;并将表中 `job` 列名改为 `profession`,同时 `varchar` 字段长度变为 10;`achievement` 的默认值设置为 0。
+
+**思路**:首先做这题之前,需要了解 ALTER 语句的基本用法:
+
+- 添加一列:`ALTER TABLE 表名 ADD COLUMN 列名 类型 【first | after 字段名】;`(first : 在某列之前添加,after 反之)
+- 修改列的类型或约束:`ALTER TABLE 表名 MODIFY COLUMN 列名 新类型 【新约束】;`
+- 修改列名:`ALTER TABLE 表名 change COLUMN 旧列名 新列名 类型;`
+- 删除列:`ALTER TABLE 表名 drop COLUMN 列名;`
+- 修改表名:`ALTER TABLE 表名 rename 【to】 新表名;`
+- 将某一列放到第一列:`ALTER TABLE 表名 MODIFY COLUMN 列名 类型 first;`
+
+`COLUMN` 关键字其实可以省略不写,这里基于规范还是罗列出来了。
+
+在修改时,如果有多个修改项,可以写到一起,但要注意格式
+
+**答案**:
+
+```sql
+ALTER TABLE user_info
+ ADD school VARCHAR(15) AFTER level,
+ CHANGE job profession VARCHAR(10),
+ MODIFY achievement INT(11) DEFAULT 0;
+```
+
+### 删除表
+
+**描述**:现有一张试卷作答记录表 `exam_record`,其中包含多年来的用户作答试卷记录。一般每年都会为 `exam_record` 表建立一张备份表 `exam_record_{YEAR},{YEAR}` 为对应年份。
+
+现在随着数据越来越多,存储告急,请你把很久前的(2011 到 2014 年)备份表都删掉(如果存在的话)。
+
+**思路**:这题很简单,直接删就行,如果嫌麻烦,可以将要删除的表用逗号隔开,写到一行;这里肯定会有小伙伴问:如果要删除很多张表呢?放心,如果要删除很多张表,可以写脚本来进行删除。
+
+**答案**:
+
+```sql
+DROP TABLE IF EXISTS exam_record_2011;
+DROP TABLE IF EXISTS exam_record_2012;
+DROP TABLE IF EXISTS exam_record_2013;
+DROP TABLE IF EXISTS exam_record_2014;
+```
+
+### 创建索引
+
+**描述**:现有一张试卷信息表 `examination_info`,其中包含各种类型试卷的信息。为了对表更方便快捷地查询,需要在 `examination_info` 表创建以下索引,
+
+规则如下:在 `duration` 列创建普通索引 `idx_duration`、在 `exam_id` 列创建唯一性索引 `uniq_idx_exam_id`、在 `tag` 列创建全文索引 `full_idx_tag`。
+
+根据题意,将返回如下结果:
+
+| examination_info | 0 | PRIMARY | 1 | id | A | 0 | | | | BTREE |
+| ---------------- | ---- | ---------------- | ---- | -------- | ---- | ---- | ---- | ---- | ---- | -------- |
+| examination_info | 0 | uniq_idx_exam_id | 1 | exam_id | A | 0 | | | YES | BTREE |
+| examination_info | 1 | idx_duration | 1 | duration | A | 0 | | | | BTREE |
+| examination_info | 1 | full_idx_tag | 1 | tag | | 0 | | | YES | FULLTEXT |
+
+备注:后台会通过 `SHOW INDEX FROM examination_info` 语句来对比输出结果
+
+**思路**:做这题首先需要了解常见的索引类型:
+
+- B-Tree 索引:B-Tree(或称为平衡树)索引是最常见和默认的索引类型。它适用于各种查询条件,可以快速定位到符合条件的数据。B-Tree 索引适用于普通的查找操作,支持等值查询、范围查询和排序。
+- 唯一索引:唯一索引与普通的 B-Tree 索引类似,不同之处在于它要求被索引的列的值是唯一的。这意味着在插入或更新数据时,MySQL 会验证索引列的唯一性。
+- 主键索引:主键索引是一种特殊的唯一索引,它用于唯一标识表中的每一行数据。每个表只能有一个主键索引,它可以帮助提高数据的访问速度和数据完整性。
+- 全文索引:全文索引用于在文本数据中进行全文搜索。它支持在文本字段中进行关键字搜索,而不仅仅是简单的等值或范围查找。全文索引适用于需要进行全文搜索的应用场景。
+
+```sql
+-- 示例:
+-- 添加B-Tree索引:
+ CREATE INDEX idx_name(索引名) ON 表名 (字段名); -- idx_name为索引名,以下都是
+-- 创建唯一索引:
+ CREATE UNIQUE INDEX idx_name ON 表名 (字段名);
+-- 创建一个主键索引:
+ ALTER TABLE 表名 ADD PRIMARY KEY (字段名);
+-- 创建一个全文索引
+ ALTER TABLE 表名 ADD FULLTEXT INDEX idx_name (字段名);
+
+-- 通过以上示例,可以看出create 和 alter 都可以添加索引
+```
+
+有了以上的基础知识之后,该题答案也就浮出水面了。
+
+**答案**:
+
+```sql
+ALTER TABLE examination_info
+ ADD INDEX idx_duration(duration),
+ ADD UNIQUE INDEX uniq_idx_exam_id(exam_id),
+ ADD FULLTEXT INDEX full_idx_tag(tag);
+```
+
+### 删除索引
+
+**描述**:请删除`examination_info`表上的唯一索引 uniq_idx_exam_id 和全文索引 full_idx_tag。
+
+**思路**:该题考察删除索引的基本语法:
+
+```sql
+-- 使用 DROP INDEX 删除索引
+DROP INDEX idx_name ON 表名;
+
+-- 使用 ALTER TABLE 删除索引
+ALTER TABLE employees DROP INDEX idx_email;
+```
+
+这里需要注意的是:在 MySQL 中,一次删除多个索引的操作是不支持的。每次删除索引时,只能指定一个索引名称进行删除。
+
+而且 **DROP** 命令需要慎用!!!
+
+**答案**:
+
+```sql
+DROP INDEX uniq_idx_exam_id ON examination_info;
+DROP INDEX full_idx_tag ON examination_info;
+```
diff --git a/docs/database/sql/sql-questions-03.md b/docs/database/sql/sql-questions-03.md
new file mode 100644
index 0000000000000000000000000000000000000000..2511e0ab3c1ce072de4222ea9bb236404383f85d
--- /dev/null
+++ b/docs/database/sql/sql-questions-03.md
@@ -0,0 +1,1299 @@
+---
+title: SQL常见面试题总结(3)
+category: 数据库
+tag:
+ - 数据库基础
+ - SQL
+---
+
+> 题目来源于:[牛客题霸 - SQL 进阶挑战](https://www.nowcoder.com/exam/oj?page=1&tab=SQL%E7%AF%87&topicId=240)
+
+较难或者困难的题目可以根据自身实际情况和面试需要来决定是否要跳过。
+
+## 聚合函数
+
+### SQL 类别高难度试卷得分的截断平均值(较难)
+
+**描述**: 牛客的运营同学想要查看大家在 SQL 类别中高难度试卷的得分情况。
+
+请你帮她从`exam_record`数据表中计算所有用户完成 SQL 类别高难度试卷得分的截断平均值(去掉一个最大值和一个最小值后的平均值)。
+
+示例数据:`examination_info`(`exam_id` 试卷 ID, tag 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间)
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2020-01-01 10:00:00 |
+| 2 | 9002 | 算法 | medium | 80 | 2020-08-02 10:00:00 |
+
+示例数据:`exam_record`(uid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分)
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2020-01-02 09:01:01 | 2020-01-02 09:21:01 | 80 |
+| 2 | 1001 | 9001 | 2021-05-02 10:01:01 | 2021-05-02 10:30:01 | 81 |
+| 3 | 1001 | 9001 | 2021-06-02 19:01:01 | 2021-06-02 19:31:01 | 84 |
+| 4 | 1001 | 9002 | 2021-09-05 19:01:01 | 2021-09-05 19:40:01 | 89 |
+| 5 | 1001 | 9001 | 2021-09-02 12:01:01 | (NULL) | (NULL) |
+| 6 | 1001 | 9002 | 2021-09-01 12:01:01 | (NULL) | (NULL) |
+| 7 | 1002 | 9002 | 2021-02-02 19:01:01 | 2021-02-02 19:30:01 | 87 |
+| 8 | 1002 | 9001 | 2021-05-05 18:01:01 | 2021-05-05 18:59:02 | 90 |
+| 9 | 1003 | 9001 | 2021-09-07 12:01:01 | 2021-09-07 10:31:01 | 50 |
+| 10 | 1004 | 9001 | 2021-09-06 10:01:01 | (NULL) | (NULL) |
+
+根据输入你的查询结果如下:
+
+| tag | difficulty | clip_avg_score |
+| ---- | ---------- | -------------- |
+| SQL | hard | 81.7 |
+
+从`examination_info`表可知,试卷 9001 为高难度 SQL 试卷,该试卷被作答的得分有[80,81,84,90,50],去除最高分和最低分后为[80,81,84],平均分为 81.6666667,保留一位小数后为 81.7
+
+**输入描述:**
+
+输入数据中至少有 3 个有效分数
+
+**思路一:** 要找出高难度 sql 试卷,肯定需要联 examination_info 这张表,然后找出高难度的课程,由 examination_info 得知,高难度 sql 的 exam_id 为 9001,那么等下就以 exam_id = 9001 作为条件去查询;
+
+先找出 9001 号考试 `select * from exam_record where exam_id = 9001`
+
+然后,找出最高分 `select max(score) 最高分 from exam_record where exam_id = 9001`
+
+接着,找出最低分 `select min(score) 最低分 from exam_record where exam_id = 9001`
+
+在查询出来的分数结果集当中,去掉最高分和最低分,最直观能想到的就是 NOT IN 或者 用 NOT EXISTS 也行,这里以 NOT IN 来做
+
+首先将主体写出来`select tag, difficulty, round(avg(score), 1) clip_avg_score from examination_info info INNER JOIN exam_record record`
+
+**小 tips** : MYSQL 的 `ROUND()` 函数 ,`ROUND(X)`返回参数 X 最近似的整数 `ROUND(X,D)`返回 X ,其值保留到小数点后 D 位,第 D 位的保留方式为四舍五入。
+
+再将上面的 "碎片" 语句拼凑起来即可, 注意在 NOT IN 中两个子查询用 UNION ALL 来关联,用 union 把 max 和 min 的结果集中在一行当中,这样形成一列多行的效果。
+
+**答案一:**
+
+```sql
+SELECT tag, difficulty, ROUND(AVG(score), 1) clip_avg_score
+ FROM examination_info info INNER JOIN exam_record record
+ WHERE info.exam_id = record.exam_id
+ AND record.exam_id = 9001
+ AND record.score NOT IN(
+ SELECT MAX(score)
+ FROM exam_record
+ WHERE exam_id = 9001
+ UNION ALL
+ SELECT MIN(score)
+ FROM exam_record
+ WHERE exam_id = 9001
+ )
+```
+
+这是最直观,也是最容易想到的解法,但是还有待改进,这算是投机取巧过关,其实严格按照题目要求应该这么写:
+
+```sql
+SELECT tag,
+ difficulty,
+ ROUND(AVG(score), 1) clip_avg_score
+FROM examination_info info
+INNER JOIN exam_record record
+WHERE info.exam_id = record.exam_id
+ AND record.exam_id =
+ (SELECT examination_info.exam_id
+ FROM examination_info
+ WHERE tag = 'SQL'
+ AND difficulty = 'hard' )
+ AND record.score NOT IN
+ (SELECT MAX(score)
+ FROM exam_record
+ WHERE exam_id =
+ (SELECT examination_info.exam_id
+ FROM examination_info
+ WHERE tag = 'SQL'
+ AND difficulty = 'hard' )
+ UNION ALL SELECT MIN(score)
+ FROM exam_record
+ WHERE exam_id =
+ (SELECT examination_info.exam_id
+ FROM examination_info
+ WHERE tag = 'SQL'
+ AND difficulty = 'hard' ) )
+```
+
+然而你会发现,重复的语句非常多,所以可以利用`WITH`来抽取公共部分
+
+**`WITH` 子句介绍**:
+
+`WITH` 子句,也称为公共表表达式(Common Table Expression,CTE),是在 SQL 查询中定义临时表的方式。它可以让我们在查询中创建一个临时命名的结果集,并且可以在同一查询中引用该结果集。
+
+基本用法:
+
+```sql
+WITH cte_name (column1, column2, ..., columnN) AS (
+ -- 查询体
+ SELECT ...
+ FROM ...
+ WHERE ...
+)
+-- 主查询
+SELECT ...
+FROM cte_name
+WHERE ...
+```
+
+`WITH` 子句由以下几个部分组成:
+
+- `cte_name`: 给临时表起一个名称,可以在主查询中引用。
+- `(column1, column2, ..., columnN)`: 可选,指定临时表的列名。
+- `AS`: 必需,表示开始定义临时表。
+- `CTE 查询体`: 实际的查询语句,用于定义临时表中的数据。
+
+`WITH` 子句的主要用途之一是增强查询的可读性和可维护性,尤其在涉及多个嵌套子查询或需要重复使用相同的查询逻辑时。通过将这些逻辑放在一个命名的临时表中,我们可以更清晰地组织查询,并消除重复代码。
+
+此外,`WITH` 子句还可以在复杂的查询中实现递归查询。递归查询允许我们在单个查询中执行对同一表的多次迭代,逐步构建结果集。这在处理层次结构数据、组织结构和树状结构等场景中非常有用。
+
+**小细节**:MySQL 5.7 版本以及之前的版本不支持在 `WITH` 子句中直接使用别名。
+
+下面是改进后的答案:
+
+```sql
+WITH t1 AS
+ (SELECT record.*,
+ info.tag,
+ info.difficulty
+ FROM exam_record record
+ INNER JOIN examination_info info ON record.exam_id = info.exam_id
+ WHERE info.tag = "SQL"
+ AND info.difficulty = "hard" )
+SELECT tag,
+ difficulty,
+ ROUND(AVG(score), 1)
+FROM t1
+WHERE score NOT IN
+ (SELECT max(score)
+ FROM t1
+ UNION SELECT min(score)
+ FROM t1)
+```
+
+**思路二:**
+
+- 筛选 SQL 高难度试卷:`where tag="SQL" and difficulty="hard"`
+- 计算截断平均值:`(和-最大值-最小值) / (总个数-2)`:
+ - `(sum(score) - max(score) - min(score)) / (count(score) - 2)`
+ - 有一个缺点就是,如果最大值和最小值有多个,这个方法就很难筛选出来, 但是题目中说了----->**`去掉一个最大值和一个最小值后的平均值`**, 所以这里可以用这个公式。
+
+**答案二:**
+
+```sql
+SELECT info.tag,
+ info.difficulty,
+ ROUND((SUM(record.score)- MIN(record.score)- MAX(record.score)) / (COUNT(record.score)- 2), 1) AS clip_avg_score
+FROM examination_info info,
+ exam_record record
+WHERE info.exam_id = record.exam_id
+ AND info.tag = "SQL"
+ AND info.difficulty = "hard";
+```
+
+### 统计作答次数
+
+有一个试卷作答记录表 `exam_record`,请从中统计出总作答次数 `total_pv`、试卷已完成作答数 `complete_pv`、已完成的试卷数 `complete_exam_cnt`。
+
+示例数据 `exam_record` 表(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2020-01-02 09:01:01 | 2020-01-02 09:21:01 | 80 |
+| 2 | 1001 | 9001 | 2021-05-02 10:01:01 | 2021-05-02 10:30:01 | 81 |
+| 3 | 1001 | 9001 | 2021-06-02 19:01:01 | 2021-06-02 19:31:01 | 84 |
+| 4 | 1001 | 9002 | 2021-09-05 19:01:01 | 2021-09-05 19:40:01 | 89 |
+| 5 | 1001 | 9001 | 2021-09-02 12:01:01 | (NULL) | (NULL) |
+| 6 | 1001 | 9002 | 2021-09-01 12:01:01 | (NULL) | (NULL) |
+| 7 | 1002 | 9002 | 2021-02-02 19:01:01 | 2021-02-02 19:30:01 | 87 |
+| 8 | 1002 | 9001 | 2021-05-05 18:01:01 | 2021-05-05 18:59:02 | 90 |
+| 9 | 1003 | 9001 | 2021-09-07 12:01:01 | 2021-09-07 10:31:01 | 50 |
+| 10 | 1004 | 9001 | 2021-09-06 10:01:01 | (NULL) | (NULL) |
+
+示例输出:
+
+| total_pv | complete_pv | complete_exam_cnt |
+| -------- | ----------- | ----------------- |
+| 11 | 7 | 2 |
+
+解释:表示截止当前,有 11 次试卷作答记录,已完成的作答次数为 7 次(中途退出的为未完成状态,其交卷时间和份数为 NULL),已完成的试卷有 9001 和 9002 两份。
+
+**思路**: 这题一看到统计次数,肯定第一时间就要想到用`COUNT`这个函数来解决,问题是要统计不同的记录,该怎么来写?使用子查询就能解决这个题目(这题用 case when 也能写出来,解法类似,逻辑不同而已);首先在做这个题之前,让我们先来了解一下`COUNT`的基本用法;
+
+`COUNT()` 函数的基本语法如下所示:
+
+```sql
+COUNT(expression)
+```
+
+其中,`expression` 可以是列名、表达式、常量或通配符。下面是一些常见的用法示例:
+
+1. 计算表中所有行的数量:
+
+```sql
+SELECT COUNT(*) FROM table_name;
+```
+
+2. 计算特定列非空(不为 NULL)值的数量:
+
+```sql
+SELECT COUNT(column_name) FROM table_name;
+```
+
+3. 计算满足条件的行数:
+
+```sql
+SELECT COUNT(*) FROM table_name WHERE condition;
+```
+
+4. 结合 `GROUP BY` 使用,计算分组后每个组的行数:
+
+```sql
+SELECT column_name, COUNT(*) FROM table_name GROUP BY column_name;
+```
+
+5. 计算不同列组合的唯一组合数:
+
+```sql
+SELECT COUNT(DISTINCT column_name1, column_name2) FROM table_name;
+```
+
+在使用 `COUNT()` 函数时,如果不指定任何参数或者使用 `COUNT(*)`,将会计算所有行的数量。而如果使用列名,则只会计算该列非空值的数量。
+
+另外,`COUNT()` 函数的结果是一个整数值。即使结果是零,也不会返回 NULL,这点需要谨记。
+
+**答案**:
+
+```sql
+SELECT
+ count(*) total_pv,
+ ( SELECT count(*) FROM exam_record WHERE submit_time IS NOT NULL ) complete_pv,
+ ( SELECT COUNT( DISTINCT exam_id, score IS NOT NULL OR NULL ) FROM exam_record ) complete_exam_cnt
+FROM
+ exam_record
+```
+
+这里着重说一下`COUNT( DISTINCT exam_id, score IS NOT NULL OR NULL )`这一句,判断 score 是否为 null ,如果是即为真,如果不是返回 null;注意这里如果不加 `or null` 在不是 null 的情况下只会返回 false 也就是返回 0;
+
+`COUNT`本身是不可以对多列求行数的,`distinct`的加入是的多列成为一个整体,可以求出现的行数了;`count distinct`在计算时只返回非 null 的行, 这个也要注意;
+
+另外通过本题 get 到了------>count 加条件常用句式`count( 列判断 or null)`
+
+### 得分不小于平均分的最低分
+
+**描述**: 请从试卷作答记录表中找到 SQL 试卷得分不小于该类试卷平均得分的用户最低得分。
+
+示例数据 exam_record 表(uid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2020-01-02 09:01:01 | 2020-01-02 09:21:01 | 80 |
+| 2 | 1002 | 9001 | 2021-09-05 19:01:01 | 2021-09-05 19:40:01 | 89 |
+| 3 | 1002 | 9002 | 2021-09-02 12:01:01 | (NULL) | (NULL) |
+| 4 | 1002 | 9003 | 2021-09-01 12:01:01 | (NULL) | (NULL) |
+| 5 | 1002 | 9001 | 2021-02-02 19:01:01 | 2021-02-02 19:30:01 | 87 |
+| 6 | 1002 | 9002 | 2021-05-05 18:01:01 | 2021-05-05 18:59:02 | 90 |
+| 7 | 1003 | 9002 | 2021-02-06 12:01:01 | (NULL) | (NULL) |
+| 8 | 1003 | 9003 | 2021-09-07 10:01:01 | 2021-09-07 10:31:01 | 86 |
+| 9 | 1004 | 9003 | 2021-09-06 12:01:01 | (NULL) | (NULL) |
+
+`examination_info` 表(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间)
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2020-01-01 10:00:00 |
+| 2 | 9002 | SQL | easy | 60 | 2020-02-01 10:00:00 |
+| 3 | 9003 | 算法 | medium | 80 | 2020-08-02 10:00:00 |
+
+示例输出数据:
+
+| min_score_over_avg |
+| ------------------ |
+| 87 |
+
+**解释**:试卷 9001 和 9002 为 SQL 类别,作答这两份试卷的得分有[80,89,87,90],平均分为 86.5,不小于平均分的最小分数为 87
+
+**思路**:这类题目第一眼看确实很复杂, 因为不知道从哪入手,但是当我们仔细读题审题后,要学会抓住题干中的关键信息。以本题为例:`请从试卷作答记录表中找到SQL试卷得分不小于该类试卷平均得分的用户最低得分。`你能一眼从中提取哪些有效信息来作为解题思路?
+
+第一条:找到==SQL==试卷得分
+
+第二条:该类试卷==平均得分==
+
+第三条:该类试卷的==用户最低得分==
+
+然后中间的 “桥梁” 就是==不小于==
+
+将条件拆分后,先逐步完成
+
+```sql
+-- 找出tag为‘SQL’的得分 【80, 89,87,90】
+-- 再算出这一组的平均得分
+select ROUND(AVG(score), 1) from examination_info info INNER JOIN exam_record record
+ where info.exam_id = record.exam_id
+ and tag= 'SQL'
+```
+
+然后再找出该类试卷的最低得分,接着将结果集`【80, 89,87,90】` 去和平均分数作比较,方可得出最终答案。
+
+**答案**:
+
+```sql
+SELECT MIN(score) AS min_score_over_avg
+FROM examination_info info
+INNER JOIN exam_record record
+WHERE info.exam_id = record.exam_id
+ AND tag= 'SQL'
+ AND score >=
+ (SELECT ROUND(AVG(score), 1)
+ FROM examination_info info
+ INNER JOIN exam_record record
+ WHERE info.exam_id = record.exam_id
+ AND tag= 'SQL' )
+```
+
+其实这类题目给出的要求看似很 “绕”,但其实仔细梳理一遍,将大条件拆分成小条件,逐个拆分完以后,最后将所有条件拼凑起来。反正只要记住:**抓主干,理分支**,问题便迎刃而解。
+
+## 分组查询
+
+### 平均活跃天数和月活人数
+
+**描述**:用户在牛客试卷作答区作答记录存储在表 `exam_record` 中,内容如下:
+
+`exam_record` 表(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分)
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2021-07-02 09:01:01 | 2021-07-02 09:21:01 | 80 |
+| 2 | 1002 | 9001 | 2021-09-05 19:01:01 | 2021-09-05 19:40:01 | 81 |
+| 3 | 1002 | 9002 | 2021-09-02 12:01:01 | (NULL) | (NULL) |
+| 4 | 1002 | 9003 | 2021-09-01 12:01:01 | (NULL) | (NULL) |
+| 5 | 1002 | 9001 | 2021-07-02 19:01:01 | 2021-07-02 19:30:01 | 82 |
+| 6 | 1002 | 9002 | 2021-07-05 18:01:01 | 2021-07-05 18:59:02 | 90 |
+| 7 | 1003 | 9002 | 2021-07-06 12:01:01 | (NULL) | (NULL) |
+| 8 | 1003 | 9003 | 2021-09-07 10:01:01 | 2021-09-07 10:31:01 | 86 |
+| 9 | 1004 | 9003 | 2021-09-06 12:01:01 | (NULL) | (NULL) |
+| 10 | 1002 | 9003 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 81 |
+| 11 | 1005 | 9001 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 88 |
+| 12 | 1006 | 9002 | 2021-09-02 12:11:01 | 2021-09-02 12:31:01 | 89 |
+| 13 | 1007 | 9002 | 2020-09-02 12:11:01 | 2020-09-02 12:31:01 | 89 |
+
+请计算 2021 年每个月里试卷作答区用户平均月活跃天数 `avg_active_days` 和月度活跃人数 `mau`,上面数据的示例输出如下:
+
+| month | avg_active_days | mau |
+| ------ | --------------- | ---- |
+| 202107 | 1.50 | 2 |
+| 202109 | 1.25 | 4 |
+
+**解释**:2021 年 7 月有 2 人活跃,共活跃了 3 天(1001 活跃 1 天,1002 活跃 2 天),平均活跃天数 1.5;2021 年 9 月有 4 人活跃,共活跃了 5 天,平均活跃天数 1.25,结果保留 2 位小数。
+
+注:此处活跃指有==交卷==行为。
+
+**思路**:读完题先注意高亮部分;一般求天数和月活跃人数马上就要想到相关的日期函数;这一题我们同样来进行拆分,把问题细化再解决;首先求活跃人数,肯定要用到`COUNT()`,那这里首先就有一个坑,不知道大家注意了没有?用户 1002 在 9 月份做了两种不同的试卷,所以这里要注意去重,不然在统计的时候,活跃人数是错的;第二个就是要知道日期的格式化,如上表,题目要求以`202107`这种日期格式展现,要用到`DATE_FORMAT`来进行格式化。
+
+基本用法:
+
+`DATE_FORMAT(date_value, format)`
+
+- `date_value` 参数是待格式化的日期或时间值。
+- `format` 参数是指定的日期或时间格式(这个和 Java 里面的日期格式一样)。
+
+**答案**:
+
+```sql
+SELECT DATE_FORMAT(submit_time, '%Y%m') MONTH,
+ round(count(DISTINCT UID, DATE_FORMAT(submit_time, '%Y%m%d')) / count(DISTINCT UID), 2) avg_active_days,
+ COUNT(DISTINCT UID) mau
+FROM exam_record
+WHERE YEAR (submit_time) = 2021
+GROUP BY MONTH
+```
+
+这里多说一句, 使用`COUNT(DISTINCT uid, DATE_FORMAT(submit_time, '%Y%m%d'))` 可以统计在 `uid` 列和 `submit_time` 列按照年份、月份和日期进行格式化后的组合值的数量。
+
+### 月总刷题数和日均刷题数
+
+**描述**:现有一张题目练习记录表 `practice_record`,示例内容如下:
+
+| id | uid | question_id | submit_time | score |
+| ---- | ---- | ----------- | ------------------- | ----- |
+| 1 | 1001 | 8001 | 2021-08-02 11:41:01 | 60 |
+| 2 | 1002 | 8001 | 2021-09-02 19:30:01 | 50 |
+| 3 | 1002 | 8001 | 2021-09-02 19:20:01 | 70 |
+| 4 | 1002 | 8002 | 2021-09-02 19:38:01 | 70 |
+| 5 | 1003 | 8002 | 2021-08-01 19:38:01 | 80 |
+
+请从中统计出 2021 年每个月里用户的月总刷题数 `month_q_cnt` 和日均刷题数 `avg_day_q_cnt`(按月份升序排序)以及该年的总体情况,示例数据输出如下:
+
+| submit_month | month_q_cnt | avg_day_q_cnt |
+| ------------ | ----------- | ------------- |
+| 202108 | 2 | 0.065 |
+| 202109 | 3 | 0.100 |
+| 2021 汇总 | 5 | 0.161 |
+
+**解释**:2021 年 8 月共有 2 次刷题记录,日均刷题数为 2/31=0.065(保留 3 位小数);2021 年 9 月共有 3 次刷题记录,日均刷题数为 3/30=0.100;2021 年共有 5 次刷题记录(年度汇总平均无实际意义,这里我们按照 31 天来算 5/31=0.161)
+
+> 牛客已经采用最新的 Mysql 版本,如果您运行结果出现错误:ONLY_FULL_GROUP_BY,意思是:对于 GROUP BY 聚合操作,如果在 SELECT 中的列,没有在 GROUP BY 中出现,那么这个 SQL 是不合法的,因为列不在 GROUP BY 从句中,也就是说查出来的列必须在 group by 后面出现否则就会报错,或者这个字段出现在聚合函数里面。
+
+**思路:**
+
+看到实例数据就要马上联想到相关的函数,比如`submit_month`就要用到`DATE_FORMAT`来格式化日期。然后查出每月的刷题数量。
+
+每月的刷题数量
+
+```sql
+SELECT MONTH ( submit_time ), COUNT( question_id )
+FROM
+ practice_record
+GROUP BY
+ MONTH (submit_time)
+```
+
+接着第三列这里要用到`DAY(LAST_DAY(date_value))`函数来查找给定日期的月份中的天数。
+
+示例代码如下:
+
+```sql
+SELECT DAY(LAST_DAY('2023-07-08')) AS days_in_month;
+-- 输出:31
+
+SELECT DAY(LAST_DAY('2023-02-01')) AS days_in_month;
+-- 输出:28 (闰年中的二月份)
+
+SELECT DAY(LAST_DAY(NOW())) AS days_in_current_month;
+-- 输出:31 (当前月份的天数)
+```
+
+使用 `LAST_DAY()` 函数获取给定日期的当月最后一天,然后使用 `DAY()` 函数提取该日期的天数。这样就能获得指定月份的天数。
+
+需要注意的是,`LAST_DAY()` 函数返回的是日期值,而 `DAY()` 函数用于提取日期值中的天数部分。
+
+有了上述的分析之后,即可马上写出答案,这题复杂就复杂在处理日期上,其中的逻辑并不难。
+
+**答案**:
+
+```sql
+SELECT DATE_FORMAT(submit_time, '%Y%m') submit_month,
+ count(question_id) month_q_cnt,
+ ROUND(COUNT(question_id) / DAY (LAST_DAY(submit_time)), 3) avg_day_q_cnt
+FROM practice_record
+WHERE DATE_FORMAT(submit_time, '%Y') = '2021'
+GROUP BY submit_month
+UNION ALL
+SELECT '2021汇总' AS submit_month,
+ count(question_id) month_q_cnt,
+ ROUND(COUNT(question_id) / 31, 3) avg_day_q_cnt
+FROM practice_record
+WHERE DATE_FORMAT(submit_time, '%Y') = '2021'
+ORDER BY submit_month
+```
+
+在实例数据输出中因为最后一行需要得出汇总数据,所以这里要 `UNION ALL`加到结果集中;别忘了最后要排序!
+
+### 未完成试卷数大于 1 的有效用户(较难)
+
+**描述**:现有试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分),示例数据如下:
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2021-07-02 09:01:01 | 2021-07-02 09:21:01 | 80 |
+| 2 | 1002 | 9001 | 2021-09-05 19:01:01 | 2021-09-05 19:40:01 | 81 |
+| 3 | 1002 | 9002 | 2021-09-02 12:01:01 | (NULL) | (NULL) |
+| 4 | 1002 | 9003 | 2021-09-01 12:01:01 | (NULL) | (NULL) |
+| 5 | 1002 | 9001 | 2021-07-02 19:01:01 | 2021-07-02 19:30:01 | 82 |
+| 6 | 1002 | 9002 | 2021-07-05 18:01:01 | 2021-07-05 18:59:02 | 90 |
+| 7 | 1003 | 9002 | 2021-07-06 12:01:01 | (NULL) | (NULL) |
+| 8 | 1003 | 9003 | 2021-09-07 10:01:01 | 2021-09-07 10:31:01 | 86 |
+| 9 | 1004 | 9003 | 2021-09-06 12:01:01 | (NULL) | (NULL) |
+| 10 | 1002 | 9003 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 81 |
+| 11 | 1005 | 9001 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 88 |
+| 12 | 1006 | 9002 | 2021-09-02 12:11:01 | 2021-09-02 12:31:01 | 89 |
+| 13 | 1007 | 9002 | 2020-09-02 12:11:01 | 2020-09-02 12:31:01 | 89 |
+
+还有一张试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间),示例数据如下:
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2020-01-01 10:00:00 |
+| 2 | 9002 | SQL | easy | 60 | 2020-02-01 10:00:00 |
+| 3 | 9003 | 算法 | medium | 80 | 2020-08-02 10:00:00 |
+
+请统计 2021 年每个未完成试卷作答数大于 1 的有效用户的数据(有效用户指完成试卷作答数至少为 1 且未完成数小于 5),输出用户 ID、未完成试卷作答数、完成试卷作答数、作答过的试卷 tag 集合,按未完成试卷数量由多到少排序。示例数据的输出结果如下:
+
+| uid | incomplete_cnt | complete_cnt | detail |
+| ---- | -------------- | ------------ | ------------------------------------------------------------ |
+| 1002 | 2 | 4 | 2021-09-01:算法;2021-07-02:SQL;2021-09-02:SQL;2021-09-05:SQL;2021-07-05:SQL |
+
+**解释**:2021 年的作答记录中,除了 1004,其他用户均满足有效用户定义,但只有 1002 未完成试卷数大于 1,因此只输出 1002,detail 中是 1002 作答过的试卷{日期:tag}集合,日期和 tag 间用 **:** 连接,多元素间用 **;** 连接。
+
+**思路:**
+
+仔细读题后,分析出:首先要联表,因为后面要输出`tag`;
+
+筛选出 2021 年的数据
+
+```sql
+SELECT *
+FROM exam_record er
+LEFT JOIN examination_info ei ON er.exam_id = ei.exam_id
+WHERE YEAR (er.start_time)= 2021
+```
+
+根据 uid 进行分组,然后对每个用户进行条件进行判断,题目中要求`完成试卷数至少为1,未完成试卷数要大于1,小于5`
+
+那么等会儿写 sql 的时候条件应该是:`未完成 > 1 and 已完成 >=1 and 未完成 < 5`
+
+因为最后要用到字符串的拼接,而且还要组合拼接,这个可以用`GROUP_CONCAT`函数,下面简单介绍一下该函数的用法:
+
+基本格式:
+
+```sql
+GROUP_CONCAT([DISTINCT] expr [ORDER BY {unsigned_integer | col_name | expr} [ASC | DESC] [, ...]] [SEPARATOR sep])
+```
+
+- `expr`:要连接的列或表达式。
+- `DISTINCT`:可选参数,用于去重。当指定了 `DISTINCT`,相同的值只会出现一次。
+- `ORDER BY`:可选参数,用于排序连接后的值。可以选择升序 (`ASC`) 或降序 (`DESC`) 排序。
+- `SEPARATOR sep`:可选参数,用于设置连接后的值的分隔符。(本题要用这个参数设置 ; 号 )
+
+`GROUP_CONCAT()` 函数常用于 `GROUP BY` 子句中,将一组行的值连接为一个字符串,并在结果集中以聚合的形式返回。
+
+**答案**:
+
+```sql
+SELECT a.uid,
+ SUM(CASE
+ WHEN a.submit_time IS NULL THEN 1
+ END) AS incomplete_cnt,
+ SUM(CASE
+ WHEN a.submit_time IS NOT NULL THEN 1
+ END) AS complete_cnt,
+ GROUP_CONCAT(DISTINCT CONCAT(DATE_FORMAT(a.start_time, '%Y-%m-%d'), ':', b.tag)
+ ORDER BY start_time SEPARATOR ";") AS detail
+FROM exam_record a
+LEFT JOIN examination_info b ON a.exam_id = b.exam_id
+WHERE YEAR (a.start_time)= 2021
+GROUP BY a.uid
+HAVING incomplete_cnt > 1
+AND complete_cnt >= 1
+AND incomplete_cnt < 5
+ORDER BY incomplete_cnt DESC
+```
+
+- `SUM(CASE WHEN a.submit_time IS NULL THEN 1 END)` 统计了每个用户未完成的记录数量。
+- `SUM(CASE WHEN a.submit_time IS NOT NULL THEN 1 END)` 统计了每个用户已完成的记录数量。
+- `GROUP_CONCAT(DISTINCT CONCAT(DATE_FORMAT(a.start_time, '%Y-%m-%d'), ':', b.tag) ORDER BY a.start_time SEPARATOR ';')` 将每个用户的考试日期和标签以逗号分隔的形式连接成一个字符串,并按考试开始时间进行排序。
+
+## 嵌套子查询
+
+### 月均完成试卷数不小于 3 的用户爱作答的类别(较难)
+
+**描述**:现有试卷作答记录表 `exam_record`(`uid`:用户 ID, `exam_id`:试卷 ID, `start_time`:开始作答时间, `submit_time`:交卷时间,没提交的话为 NULL, `score`:得分),示例数据如下:
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2021-07-02 09:01:01 | (NULL) | (NULL) |
+| 2 | 1002 | 9003 | 2021-09-01 12:01:01 | 2021-09-01 12:21:01 | 60 |
+| 3 | 1002 | 9002 | 2021-09-02 12:01:01 | 2021-09-02 12:31:01 | 70 |
+| 4 | 1002 | 9001 | 2021-09-05 19:01:01 | 2021-09-05 19:40:01 | 81 |
+| 5 | 1002 | 9002 | 2021-07-06 12:01:01 | (NULL) | (NULL) |
+| 6 | 1003 | 9003 | 2021-09-07 10:01:01 | 2021-09-07 10:31:01 | 86 |
+| 7 | 1003 | 9003 | 2021-09-08 12:01:01 | 2021-09-08 12:11:01 | 40 |
+| 8 | 1003 | 9001 | 2021-09-08 13:01:01 | (NULL) | (NULL) |
+| 9 | 1003 | 9002 | 2021-09-08 14:01:01 | (NULL) | (NULL) |
+| 10 | 1003 | 9003 | 2021-09-08 15:01:01 | (NULL) | (NULL) |
+| 11 | 1005 | 9001 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 88 |
+| 12 | 1005 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 88 |
+| 13 | 1005 | 9002 | 2021-09-02 12:11:01 | 2021-09-02 12:31:01 | 89 |
+
+试卷信息表 `examination_info`(`exam_id`:试卷 ID, `tag`:试卷类别, `difficulty`:试卷难度, `duration`:考试时长, `release_time`:发布时间),示例数据如下:
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2020-01-01 10:00:00 |
+| 2 | 9002 | C++ | easy | 60 | 2020-02-01 10:00:00 |
+| 3 | 9003 | 算法 | medium | 80 | 2020-08-02 10:00:00 |
+
+请从表中统计出 “当月均完成试卷数”不小于 3 的用户们爱作答的类别及作答次数,按次数降序输出,示例输出如下:
+
+| tag | tag_cnt |
+| ---- | ------- |
+| C++ | 4 |
+| SQL | 2 |
+| 算法 | 1 |
+
+**解释**:用户 1002 和 1005 在 2021 年 09 月的完成试卷数目均为 3,其他用户均小于 3;然后用户 1002 和 1005 作答过的试卷 tag 分布结果按作答次数降序排序依次为 C++、SQL、算法。
+
+**思路**:这题考察联合子查询,重点在于`月均回答>=3`, 但是个人认为这里没有表述清楚,应该直接说查 9 月的就容易理解多了;这里不是每个月都要>=3 或者是所有答题次数/答题月份。不要理解错误了。
+
+先查询出哪些用户月均答题大于三次
+
+```sql
+SELECT UID
+FROM exam_record record
+GROUP BY UID,
+ MONTH (start_time)
+HAVING count(submit_time) >= 3
+```
+
+有了这一步之后再进行深入,只要能理解上一步(我的意思是不被题目中的月均所困扰),然后再套一个子查询,查哪些用户包含其中,然后查出题目中所需的列即可。记得排序!!
+
+```sql
+SELECT tag,
+ count(start_time) AS tag_cnt
+FROM exam_record record
+INNER JOIN examination_info info ON record.exam_id = info.exam_id
+WHERE UID IN
+ (SELECT UID
+ FROM exam_record record
+ GROUP BY UID,
+ MONTH (start_time)
+ HAVING count(submit_time) >= 3)
+GROUP BY tag
+ORDER BY tag_cnt DESC
+```
+
+### 试卷发布当天作答人数和平均分
+
+**描述**:现有用户信息表 `user_info`(`uid` 用户 ID,`nick_name` 昵称, `achievement` 成就值, `level` 等级, `job` 职业方向, `register_time` 注册时间),示例数据如下:
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | --------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 号 | 3100 | 7 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 2100 | 6 | 算法 | 2020-01-01 10:00:00 |
+| 3 | 1003 | 牛客 3 号 | 1500 | 5 | 算法 | 2020-01-01 10:00:00 |
+| 4 | 1004 | 牛客 4 号 | 1100 | 4 | 算法 | 2020-01-01 10:00:00 |
+| 5 | 1005 | 牛客 5 号 | 1600 | 6 | C++ | 2020-01-01 10:00:00 |
+| 6 | 1006 | 牛客 6 号 | 3000 | 6 | C++ | 2020-01-01 10:00:00 |
+
+**释义**:用户 1001 昵称为牛客 1 号,成就值为 3100,用户等级是 7 级,职业方向为算法,注册时间 2020-01-01 10:00:00
+
+试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间) 示例数据如下:
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2021-09-01 06:00:00 |
+| 2 | 9002 | C++ | easy | 60 | 2020-02-01 10:00:00 |
+| 3 | 9003 | 算法 | medium | 80 | 2020-08-02 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分) 示例数据如下:
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2021-07-02 09:01:01 | 2021-09-01 09:41:01 | 70 |
+| 2 | 1002 | 9003 | 2021-09-01 12:01:01 | 2021-09-01 12:21:01 | 60 |
+| 3 | 1002 | 9002 | 2021-09-02 12:01:01 | 2021-09-02 12:31:01 | 70 |
+| 4 | 1002 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:40:01 | 80 |
+| 5 | 1002 | 9003 | 2021-08-01 12:01:01 | 2021-08-01 12:21:01 | 60 |
+| 6 | 1002 | 9002 | 2021-08-02 12:01:01 | 2021-08-02 12:31:01 | 70 |
+| 7 | 1002 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:40:01 | 85 |
+| 8 | 1002 | 9002 | 2021-07-06 12:01:01 | (NULL) | (NULL) |
+| 9 | 1003 | 9002 | 2021-09-07 10:01:01 | 2021-09-07 10:31:01 | 86 |
+| 10 | 1003 | 9003 | 2021-09-08 12:01:01 | 2021-09-08 12:11:01 | 40 |
+| 11 | 1003 | 9003 | 2021-09-01 13:01:01 | 2021-09-01 13:41:01 | 70 |
+| 12 | 1003 | 9001 | 2021-09-08 14:01:01 | (NULL) | (NULL) |
+| 13 | 1003 | 9002 | 2021-09-08 15:01:01 | (NULL) | (NULL) |
+| 14 | 1005 | 9001 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 90 |
+| 15 | 1005 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 88 |
+| 16 | 1005 | 9002 | 2021-09-02 12:11:01 | 2021-09-02 12:31:01 | 89 |
+
+请计算每张 SQL 类别试卷发布后,当天 5 级以上的用户作答的人数 `uv` 和平均分 `avg_score`,按人数降序,相同人数的按平均分升序,示例数据结果输出如下:
+
+| exam_id | uv | avg_score |
+| ------- | ---- | --------- |
+| 9001 | 3 | 81.3 |
+
+解释:只有一张 SQL 类别的试卷,试卷 ID 为 9001,发布当天(2021-09-01)有 1001、1002、1003、1005 作答过,但是 1003 是 5 级用户,其他 3 位为 5 级以上,他们三的得分有[70,80,85,90],平均分为 81.3(保留 1 位小数)。
+
+**思路**:这题看似很复杂,但是先逐步将“外边”条件拆分,然后合拢到一起,答案就出来,多表查询反正记住:由外向里,抽丝剥茧。
+
+先把三种表连起来,同时给定一些条件,比如题目中要求`等级> 5`的用户,那么可以先查出来
+
+```sql
+SELECT DISTINCT u_info.uid
+FROM examination_info e_info
+INNER JOIN exam_record record
+INNER JOIN user_info u_info
+WHERE e_info.exam_id = record.exam_id
+ AND u_info.uid = record.uid
+ AND u_info.LEVEL > 5
+```
+
+接着注意题目中要求:`每张sql类别试卷发布后,当天作答用户`,注意其中的==当天==,那我们马上就要想到要用到时间的比较。
+
+对试卷发布日期和开始考试日期进行比较:`DATE(e_info.release_time) = DATE(record.start_time)`;不用担心`submit_time` 为 null 的问题,后续在 where 中会给过滤掉。
+
+**答案**:
+
+```sql
+SELECT record.exam_id AS exam_id,
+ COUNT(DISTINCT u_info.uid) AS uv,
+ ROUND(SUM(record.score) / COUNT(u_info.uid), 1) AS avg_score
+FROM examination_info e_info
+INNER JOIN exam_record record
+INNER JOIN user_info u_info
+WHERE e_info.exam_id = record.exam_id
+ AND u_info.uid = record.uid
+ AND DATE (e_info.release_time) = DATE (record.start_time)
+ AND submit_time IS NOT NULL
+ AND tag = 'SQL'
+ AND u_info.LEVEL > 5
+GROUP BY record.exam_id
+ORDER BY uv DESC,
+ avg_score ASC
+```
+
+注意最后的分组排序!先按人数排,若一致,按平均分排。
+
+### 作答试卷得分大于过 80 的人的用户等级分布
+
+**描述**:
+
+现有用户信息表 `user_info`(`uid` 用户 ID,`nick_name` 昵称, `achievement` 成就值, `level` 等级, `job` 职业方向, `register_time` 注册时间):
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | --------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 号 | 3100 | 7 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 2100 | 6 | 算法 | 2020-01-01 10:00:00 |
+| 3 | 1003 | 牛客 3 号 | 1500 | 5 | 算法 | 2020-01-01 10:00:00 |
+| 4 | 1004 | 牛客 4 号 | 1100 | 4 | 算法 | 2020-01-01 10:00:00 |
+| 5 | 1005 | 牛客 5 号 | 1600 | 6 | C++ | 2020-01-01 10:00:00 |
+| 6 | 1006 | 牛客 6 号 | 3000 | 6 | C++ | 2020-01-01 10:00:00 |
+
+试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2021-09-01 06:00:00 |
+| 2 | 9002 | C++ | easy | 60 | 2021-09-01 06:00:00 |
+| 3 | 9003 | 算法 | medium | 80 | 2021-09-01 10:00:00 |
+
+试卷作答信息表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2021-09-01 09:01:01 | 2021-09-01 09:41:01 | 79 |
+| 2 | 1002 | 9003 | 2021-09-01 12:01:01 | 2021-09-01 12:21:01 | 60 |
+| 3 | 1002 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 70 |
+| 4 | 1002 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:40:01 | 80 |
+| 5 | 1002 | 9003 | 2021-08-01 12:01:01 | 2021-08-01 12:21:01 | 60 |
+| 6 | 1002 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 70 |
+| 7 | 1002 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:40:01 | 85 |
+| 8 | 1002 | 9002 | 2021-09-01 12:01:01 | (NULL) | (NULL) |
+| 9 | 1003 | 9002 | 2021-09-07 10:01:01 | 2021-09-07 10:31:01 | 86 |
+| 10 | 1003 | 9003 | 2021-09-08 12:01:01 | 2021-09-08 12:11:01 | 40 |
+| 11 | 1003 | 9003 | 2021-09-01 13:01:01 | 2021-09-01 13:41:01 | 81 |
+| 12 | 1003 | 9001 | 2021-09-01 14:01:01 | (NULL) | (NULL) |
+| 13 | 1003 | 9002 | 2021-09-08 15:01:01 | (NULL) | (NULL) |
+| 14 | 1005 | 9001 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 90 |
+| 15 | 1005 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 88 |
+| 16 | 1005 | 9002 | 2021-09-02 12:11:01 | 2021-09-02 12:31:01 | 89 |
+
+统计作答 SQL 类别的试卷得分大于过 80 的人的用户等级分布,按数量降序排序(保证数量都不同)。示例数据结果输出如下:
+
+| level | level_cnt |
+| ----- | --------- |
+| 6 | 2 |
+| 5 | 1 |
+
+解释:9001 为 SQL 类试卷,作答该试卷大于 80 分的人有 1002、1003、1005 共 3 人,6 级两人,5 级一人。
+
+**思路:**这题和上一题都是一样的数据,只是查询条件改变了而已,上一题理解了,这题分分钟做出来。
+
+**答案**:
+
+```sql
+SELECT u_info.LEVEL AS LEVEL,
+ count(u_info.uid) AS level_cnt
+FROM examination_info e_info
+INNER JOIN exam_record record
+INNER JOIN user_info u_info
+WHERE e_info.exam_id = record.exam_id
+ AND u_info.uid = record.uid
+ AND record.score > 80
+ AND submit_time IS NOT NULL
+ AND tag = 'SQL'
+GROUP BY LEVEL
+ORDER BY level_cnt DESC
+```
+
+## 合并查询
+
+### 每个题目和每份试卷被作答的人数和次数
+
+**描述**:
+
+现有试卷作答记录表 exam_record(uid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2021-09-01 09:01:01 | 2021-09-01 09:41:01 | 81 |
+| 2 | 1002 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 70 |
+| 3 | 1002 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:40:01 | 80 |
+| 4 | 1002 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 70 |
+| 5 | 1004 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:40:01 | 85 |
+| 6 | 1002 | 9002 | 2021-09-01 12:01:01 | (NULL) | (NULL) |
+
+题目练习表 practice_record(uid 用户 ID, question_id 题目 ID, submit_time 提交时间, score 得分):
+
+| id | uid | question_id | submit_time | score |
+| ---- | ---- | ----------- | ------------------- | ----- |
+| 1 | 1001 | 8001 | 2021-08-02 11:41:01 | 60 |
+| 2 | 1002 | 8001 | 2021-09-02 19:30:01 | 50 |
+| 3 | 1002 | 8001 | 2021-09-02 19:20:01 | 70 |
+| 4 | 1002 | 8002 | 2021-09-02 19:38:01 | 70 |
+| 5 | 1003 | 8001 | 2021-08-02 19:38:01 | 70 |
+| 6 | 1003 | 8001 | 2021-08-02 19:48:01 | 90 |
+| 7 | 1003 | 8002 | 2021-08-01 19:38:01 | 80 |
+
+请统计每个题目和每份试卷被作答的人数和次数,分别按照"试卷"和"题目"的 uv & pv 降序显示,示例数据结果输出如下:
+
+| tid | uv | pv |
+| ---- | ---- | ---- |
+| 9001 | 3 | 3 |
+| 9002 | 1 | 3 |
+| 8001 | 3 | 5 |
+| 8002 | 2 | 2 |
+
+**解释**:“试卷”有 3 人共练习 3 次试卷 9001,1 人作答 3 次 9002;“刷题”有 3 人刷 5 次 8001,有 2 人刷 2 次 8002
+
+**思路**:这题的难点和易错点在于`UNOIN`和`ORDER BY` 同时使用的问题
+
+有以下几种情况:使用`union`和多个`order by`不加括号,报错!
+
+`order by`在`union`连接的子句中不起作用;
+
+比如不加括号:
+
+```sql
+SELECT exam_id AS tid,
+ COUNT(DISTINCT UID) AS uv,
+ COUNT(UID) AS pv
+FROM exam_record
+GROUP BY exam_id
+ORDER BY uv DESC,
+ pv DESC
+UNION
+SELECT question_id AS tid,
+ COUNT(DISTINCT UID) AS uv,
+ COUNT(UID) AS pv
+FROM practice_record
+GROUP BY question_id
+ORDER BY uv DESC,
+ pv DESC
+```
+
+直接报语法错误,如果没有括号,只能有一个`order by`
+
+还有一种`order by`不起作用的情况,但是能在子句的子句中起作用,这里的解决方案就是在外面再套一层查询。
+
+**答案**:
+
+```sql
+SELECT *
+FROM
+ (SELECT exam_id AS tid,
+ COUNT(DISTINCT exam_record.uid) uv,
+ COUNT(*) pv
+ FROM exam_record
+ GROUP BY exam_id
+ ORDER BY uv DESC, pv DESC) t1
+UNION
+SELECT *
+FROM
+ (SELECT question_id AS tid,
+ COUNT(DISTINCT practice_record.uid) uv,
+ COUNT(*) pv
+ FROM practice_record
+ GROUP BY question_id
+ ORDER BY uv DESC, pv DESC) t2;
+```
+
+### 分别满足两个活动的人
+
+**描述**: 为了促进更多用户在牛客平台学习和刷题进步,我们会经常给一些既活跃又表现不错的用户发放福利。假使以前我们有两拨运营活动,分别给每次试卷得分都能到 85 分的人(activity1)、至少有一次用了一半时间就完成高难度试卷且分数大于 80 的人(activity2)发了福利券。
+
+现在,需要你一次性将这两个活动满足的人筛选出来,交给运营同学。请写出一个 SQL 实现:输出 2021 年里,所有每次试卷得分都能到 85 分的人以及至少有一次用了一半时间就完成高难度试卷且分数大于 80 的人的 id 和活动号,按用户 ID 排序输出。
+
+现有试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2021-09-01 06:00:00 |
+| 2 | 9002 | C++ | easy | 60 | 2021-09-01 06:00:00 |
+| 3 | 9003 | 算法 | medium | 80 | 2021-09-01 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2021-09-01 09:01:01 | 2021-09-01 09:31:00 | 81 |
+| 2 | 1002 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 70 |
+| 3 | 1003 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:40:01 | **86** |
+| 4 | 1003 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 89 |
+| 5 | 1004 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:30:01 | 85 |
+
+示例数据输出结果:
+
+| uid | activity |
+| ---- | --------- |
+| 1001 | activity2 |
+| 1003 | activity1 |
+| 1004 | activity1 |
+| 1004 | activity2 |
+
+**解释**:用户 1001 最小分数 81 不满足活动 1,但 29 分 59 秒完成了 60 分钟长的试卷得分 81,满足活动 2;1003 最小分数 86 满足活动 1,完成时长都大于试卷时长的一半,不满足活动 2;用户 1004 刚好用了一半时间(30 分钟整)完成了试卷得分 85,满足活动 1 和活动 2。
+
+**思路**: 这一题需要涉及到时间的减法,需要用到 `TIMESTAMPDIFF()` 函数计算两个时间戳之间的分钟差值。
+
+下面我们来看一下基本用法
+
+示例:
+
+```sql
+TIMESTAMPDIFF(MINUTE, start_time, end_time)
+```
+
+`TIMESTAMPDIFF()` 函数的第一个参数是时间单位,这里我们选择 `MINUTE` 表示返回分钟差值。第二个参数是较早的时间戳,第三个参数是较晚的时间戳。函数会返回它们之间的分钟差值
+
+了解了这个函数的用法之后,我们再回过头来看`activity1`的要求,求分数大于 85 即可,那我们还是先把这个写出来,后续思路就会清晰很多
+
+```sql
+SELECT DISTINCT UID
+FROM exam_record
+WHERE score >= 85
+ AND YEAR (start_time) = '2021'
+```
+
+根据条件 2,接着写出`在一半时间内完成高难度试卷且分数大于80的人`
+
+```sql
+SELECT UID
+FROM examination_info info
+INNER JOIN exam_record record
+WHERE info.exam_id = record.exam_id
+ AND (TIMESTAMPDIFF(MINUTE, start_time, submit_time)) < (info.duration / 2)
+ AND difficulty = 'hard'
+ AND score >= 80
+```
+
+然后再把两者`UNION` 起来即可。(这里特别要注意括号问题和`order by`位置,具体用法在上一篇中已提及)
+
+**答案**:
+
+```sql
+SELECT DISTINCT UID UID,
+ 'activity1' activity
+FROM exam_record
+WHERE UID not in
+ (SELECT UID
+ FROM exam_record
+ WHERE score<85
+ AND YEAR(submit_time) = 2021 )
+UNION
+SELECT DISTINCT UID UID,
+ 'activity2' activity
+FROM exam_record e_r
+LEFT JOIN examination_info e_i ON e_r.exam_id = e_i.exam_id
+WHERE YEAR(submit_time) = 2021
+ AND difficulty = 'hard'
+ AND TIMESTAMPDIFF(SECOND, start_time, submit_time) <= duration *30
+ AND score>80
+ORDER BY UID
+```
+
+## 连接查询
+
+### 满足条件的用户的试卷完成数和题目练习数(困难)
+
+**描述**:
+
+现有用户信息表 user_info(uid 用户 ID,nick_name 昵称, achievement 成就值, level 等级, job 职业方向, register_time 注册时间):
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | --------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 号 | 3100 | 7 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 2300 | 7 | 算法 | 2020-01-01 10:00:00 |
+| 3 | 1003 | 牛客 3 号 | 2500 | 7 | 算法 | 2020-01-01 10:00:00 |
+| 4 | 1004 | 牛客 4 号 | 1200 | 5 | 算法 | 2020-01-01 10:00:00 |
+| 5 | 1005 | 牛客 5 号 | 1600 | 6 | C++ | 2020-01-01 10:00:00 |
+| 6 | 1006 | 牛客 6 号 | 2000 | 6 | C++ | 2020-01-01 10:00:00 |
+
+试卷信息表 examination_info(exam_id 试卷 ID, tag 试卷类别, difficulty 试卷难度, duration 考试时长, release_time 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2021-09-01 06:00:00 |
+| 2 | 9002 | C++ | hard | 60 | 2021-09-01 06:00:00 |
+| 3 | 9003 | 算法 | medium | 80 | 2021-09-01 10:00:00 |
+
+试卷作答记录表 exam_record(uid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ----- |
+| 1 | 1001 | 9001 | 2021-09-01 09:01:01 | 2021-09-01 09:31:00 | 81 |
+| 2 | 1002 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 81 |
+| 3 | 1003 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:40:01 | 86 |
+| 4 | 1003 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:51 | 89 |
+| 5 | 1004 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:30:01 | 85 |
+| 6 | 1005 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:02 | 85 |
+| 7 | 1006 | 9003 | 2021-09-07 10:01:01 | 2021-09-07 10:21:01 | 84 |
+| 8 | 1006 | 9001 | 2021-09-07 10:01:01 | 2021-09-07 10:21:01 | 80 |
+
+题目练习记录表 practice_record(uid 用户 ID, question_id 题目 ID, submit_time 提交时间, score 得分):
+
+| id | uid | question_id | submit_time | score |
+| ---- | ---- | ----------- | ------------------- | ----- |
+| 1 | 1001 | 8001 | 2021-08-02 11:41:01 | 60 |
+| 2 | 1002 | 8001 | 2021-09-02 19:30:01 | 50 |
+| 3 | 1002 | 8001 | 2021-09-02 19:20:01 | 70 |
+| 4 | 1002 | 8002 | 2021-09-02 19:38:01 | 70 |
+| 5 | 1004 | 8001 | 2021-08-02 19:38:01 | 70 |
+| 6 | 1004 | 8002 | 2021-08-02 19:48:01 | 90 |
+| 7 | 1001 | 8002 | 2021-08-02 19:38:01 | 70 |
+| 8 | 1004 | 8002 | 2021-08-02 19:48:01 | 90 |
+| 9 | 1004 | 8002 | 2021-08-02 19:58:01 | 94 |
+| 10 | 1004 | 8003 | 2021-08-02 19:38:01 | 70 |
+| 11 | 1004 | 8003 | 2021-08-02 19:48:01 | 90 |
+| 12 | 1004 | 8003 | 2021-08-01 19:38:01 | 80 |
+
+请你找到高难度 SQL 试卷得分平均值大于 80 并且是 7 级的红名大佬,统计他们的 2021 年试卷总完成次数和题目总练习次数,只保留 2021 年有试卷完成记录的用户。结果按试卷完成数升序,按题目练习数降序。
+
+示例数据输出如下:
+
+| uid | exam_cnt | question_cnt |
+| ---- | -------- | ------------ |
+| 1001 | 1 | 2 |
+| 1003 | 2 | 0 |
+
+解释:用户 1001、1003、1004、1006 满足高难度 SQL 试卷得分平均值大于 80,但只有 1001、1003 是 7 级红名大佬;1001 完成了 1 次试卷 1001,练习了 2 次题目;1003 完成了 2 次试卷 9001、9002,未练习题目(因此计数为 0)
+
+**思路:**
+
+先将条件进行初步筛选,比如先查出做过高难度 sql 试卷的用户
+
+```sql
+SELECT
+ record.uid
+FROM
+ exam_record record
+ INNER JOIN examination_info e_info ON record.exam_id = e_info.exam_id
+ JOIN user_info u_info ON record.uid = u_info.uid
+WHERE
+ e_info.tag = 'SQL'
+ AND e_info.difficulty = 'hard'
+```
+
+然后根据题目要求,接着再往里叠条件即可;
+
+但是这里又要注意:
+
+第一:不能`YEAR(submit_time)= 2021`这个条件放到最后,要在`ON`条件里,因为左连接存在返回左表全部行,右表为 null 的情形,放在 `JOIN`条件的 `ON` 子句中的目的是为了确保在连接两个表时,只有满足年份条件的记录会进行连接。这样可以避免其他年份的记录被包含在结果中。即 1001 做过 2021 年的试卷,但没有练习过,如果把条件放到最后,就会排除掉这种情况。
+
+第二,必须是`COUNT(distinct er.exam_id) exam_cnt, COUNT(distinct pr.id) question_cnt,`要加 distinct,因为有左连接产生很多重复值。
+
+**答案**:
+
+```sql
+SELECT er.uid AS UID,
+ count(DISTINCT er.exam_id) AS exam_cnt,
+ count(DISTINCT pr.id) AS question_cnt
+FROM exam_record er
+LEFT JOIN practice_record pr ON er.uid = pr.uid
+AND YEAR (er.submit_time)= 2021
+AND YEAR (pr.submit_time)= 2021
+WHERE er.uid IN
+ (SELECT er.uid
+ FROM exam_record er
+ LEFT JOIN examination_info ei ON er.exam_id = ei.exam_id
+ LEFT JOIN user_info ui ON er.uid = ui.uid
+ WHERE tag = 'SQL'
+ AND difficulty = 'hard'
+ AND LEVEL = 7
+ GROUP BY er.uid
+ HAVING avg(score) > 80)
+GROUP BY er.uid
+ORDER BY exam_cnt,
+ question_cnt DESC
+```
+
+可能细心的小伙伴会发现,为什么明明将条件限制了`tag = 'SQL' AND difficulty = 'hard'`,但是用户 1003 仍然能查出两条考试记录,其中一条的考试`tag`为 `C++`; 这是由于`LEFT JOIN`的特性,即使没有与右表匹配的行,左表的所有记录仍然会被保留。
+
+### 每个 6/7 级用户活跃情况(困难)
+
+**描述**:
+
+现有用户信息表 `user_info`(`uid` 用户 ID,`nick_name` 昵称, `achievement` 成就值, `level` 等级, `job` 职业方向, `register_time` 注册时间):
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | --------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 号 | 3100 | 7 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 2300 | 7 | 算法 | 2020-01-01 10:00:00 |
+| 3 | 1003 | 牛客 3 号 | 2500 | 7 | 算法 | 2020-01-01 10:00:00 |
+| 4 | 1004 | 牛客 4 号 | 1200 | 5 | 算法 | 2020-01-01 10:00:00 |
+| 5 | 1005 | 牛客 5 号 | 1600 | 6 | C++ | 2020-01-01 10:00:00 |
+| 6 | 1006 | 牛客 6 号 | 2600 | 7 | C++ | 2020-01-01 10:00:00 |
+
+试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2021-09-01 06:00:00 |
+| 2 | 9002 | C++ | easy | 60 | 2021-09-01 06:00:00 |
+| 3 | 9003 | 算法 | medium | 80 | 2021-09-01 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| uid | exam_id | start_time | submit_time | score |
+| ---- | ------- | ------------------- | ------------------- | ------ |
+| 1001 | 9001 | 2021-09-01 09:01:01 | 2021-09-01 09:31:00 | 78 |
+| 1001 | 9001 | 2021-09-01 09:01:01 | 2021-09-01 09:31:00 | 81 |
+| 1005 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:30:01 | 85 |
+| 1005 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:02 | 85 |
+| 1006 | 9003 | 2021-09-07 10:01:01 | 2021-09-07 10:21:59 | 84 |
+| 1006 | 9001 | 2021-09-07 10:01:01 | 2021-09-07 10:21:01 | 81 |
+| 1002 | 9001 | 2020-09-01 13:01:01 | 2020-09-01 13:41:01 | 81 |
+| 1005 | 9001 | 2021-09-01 14:01:01 | (NULL) | (NULL) |
+
+题目练习记录表 `practice_record`(`uid` 用户 ID, `question_id` 题目 ID, `submit_time` 提交时间, `score` 得分):
+
+| uid | question_id | submit_time | score |
+| ---- | ----------- | ------------------- | ----- |
+| 1001 | 8001 | 2021-08-02 11:41:01 | 60 |
+| 1004 | 8001 | 2021-08-02 19:38:01 | 70 |
+| 1004 | 8002 | 2021-08-02 19:48:01 | 90 |
+| 1001 | 8002 | 2021-08-02 19:38:01 | 70 |
+| 1004 | 8002 | 2021-08-02 19:48:01 | 90 |
+| 1006 | 8002 | 2021-08-04 19:58:01 | 94 |
+| 1006 | 8003 | 2021-08-03 19:38:01 | 70 |
+| 1006 | 8003 | 2021-08-02 19:48:01 | 90 |
+| 1006 | 8003 | 2020-08-01 19:38:01 | 80 |
+
+请统计每个 6/7 级用户总活跃月份数、2021 年活跃天数、2021 年试卷作答活跃天数、2021 年答题活跃天数,按照总活跃月份数、2021 年活跃天数降序排序。由示例数据结果输出如下:
+
+| uid | act_month_total | act_days_2021 | act_days_2021_exam |
+| ---- | --------------- | ------------- | ------------------ |
+| 1006 | 3 | 4 | 1 |
+| 1001 | 2 | 2 | 1 |
+| 1005 | 1 | 1 | 1 |
+| 1002 | 1 | 0 | 0 |
+| 1003 | 0 | 0 | 0 |
+
+**解释**:6/7 级用户共有 5 个,其中 1006 在 202109、202108、202008 共 3 个月活跃过,2021 年活跃的日期有 20210907、20210804、20210803、20210802 共 4 天,2021 年在试卷作答区 20210907 活跃 1 天,在题目练习区活跃了 3 天。
+
+**思路:**
+
+这题的关键在于`CASE WHEN THEN`的使用,不然要写很多的`left join` 因为会产生很多的结果集。
+
+`CASE WHEN THEN`语句是一种条件表达式,用于在 SQL 中根据条件执行不同的操作或返回不同的结果。
+
+语法结构如下:
+
+```sql
+CASE
+ WHEN condition1 THEN result1
+ WHEN condition2 THEN result2
+ ...
+ ELSE result
+END
+```
+
+在这个结构中,可以根据需要添加多个`WHEN`子句,每个`WHEN`子句后面跟着一个条件(condition)和一个结果(result)。条件可以是任何逻辑表达式,如果满足条件,将返回对应的结果。
+
+最后的`ELSE`子句是可选的,用于指定当所有前面的条件都不满足时的默认返回结果。如果没有提供`ELSE`子句,则默认返回`NULL`。
+
+例如:
+
+```sql
+SELECT score,
+ CASE
+ WHEN score >= 90 THEN '优秀'
+ WHEN score >= 80 THEN '良好'
+ WHEN score >= 60 THEN '及格'
+ ELSE '不及格'
+ END AS grade
+FROM student_scores;
+```
+
+在上述示例中,根据学生成绩(score)的不同范围,使用 CASE WHEN THEN 语句返回相应的等级(grade)。如果成绩大于等于 90,则返回"优秀";如果成绩大于等于 80,则返回"良好";如果成绩大于等于 60,则返回"及格";否则返回"不及格"。
+
+那了解到了上述的用法之后,回过头看看该题,要求列出不同的活跃天数。
+
+```sql
+count(distinct act_month) as act_month_total,
+count(distinct case when year(act_time)='2021'then act_day end) as act_days_2021,
+count(distinct case when year(act_time)='2021' and tag='exam' then act_day end) as act_days_2021_exam,
+count(distinct case when year(act_time)='2021' and tag='question'then act_day end) as act_days_2021_question
+```
+
+这里的 tag 是先给标记,方便对查询进行区分,将考试和答题分开。
+
+找出试卷作答区的用户
+
+```sql
+SELECT
+ uid,
+ exam_id AS ans_id,
+ start_time AS act_time,
+ date_format( start_time, '%Y%m' ) AS act_month,
+ date_format( start_time, '%Y%m%d' ) AS act_day,
+ 'exam' AS tag
+ FROM
+ exam_record
+```
+
+紧接着就是答题作答区的用户
+
+```sql
+SELECT
+ uid,
+ question_id AS ans_id,
+ submit_time AS act_time,
+ date_format( submit_time, '%Y%m' ) AS act_month,
+ date_format( submit_time, '%Y%m%d' ) AS act_day,
+ 'question' AS tag
+ FROM
+ practice_record
+```
+
+最后将两个结果进行`UNION` 最后别忘了将结果进行排序 (这题有点类似于分治法的思想)
+
+**答案**:
+
+```sql
+SELECT user_info.uid,
+ count(DISTINCT act_month) AS act_month_total,
+ count(DISTINCT CASE
+ WHEN YEAR (act_time)= '2021' THEN act_day
+ END) AS act_days_2021,
+ count(DISTINCT CASE
+ WHEN YEAR (act_time)= '2021'
+ AND tag = 'exam' THEN act_day
+ END) AS act_days_2021_exam,
+ count(DISTINCT CASE
+ WHEN YEAR (act_time)= '2021'
+ AND tag = 'question' THEN act_day
+ END) AS act_days_2021_question
+FROM
+ (SELECT UID,
+ exam_id AS ans_id,
+ start_time AS act_time,
+ date_format(start_time, '%Y%m') AS act_month,
+ date_format(start_time, '%Y%m%d') AS act_day,
+ 'exam' AS tag
+ FROM exam_record
+ UNION ALL SELECT UID,
+ question_id AS ans_id,
+ submit_time AS act_time,
+ date_format(submit_time, '%Y%m') AS act_month,
+ date_format(submit_time, '%Y%m%d') AS act_day,
+ 'question' AS tag
+ FROM practice_record) total
+RIGHT JOIN user_info ON total.uid = user_info.uid
+WHERE user_info.LEVEL IN (6,
+ 7)
+GROUP BY user_info.uid
+ORDER BY act_month_total DESC,
+ act_days_2021 DESC
+```
diff --git a/docs/database/sql/sql-questions-04.md b/docs/database/sql/sql-questions-04.md
new file mode 100644
index 0000000000000000000000000000000000000000..45ab6072d9f8becfe8508b7bb81a47ba6c8650ff
--- /dev/null
+++ b/docs/database/sql/sql-questions-04.md
@@ -0,0 +1,831 @@
+---
+title: SQL常见面试题总结(4)
+category: 数据库
+tag:
+ - 数据库基础
+ - SQL
+---
+
+> 题目来源于:[牛客题霸 - SQL 进阶挑战](https://www.nowcoder.com/exam/oj?page=1&tab=SQL%E7%AF%87&topicId=240)
+
+较难或者困难的题目可以根据自身实际情况和面试需要来决定是否要跳过。
+
+## 专用窗口函数
+
+MySQL 8.0 版本引入了窗口函数的支持,下面是 MySQL 中常见的窗口函数及其用法:
+
+1. `ROW_NUMBER()`: 为查询结果集中的每一行分配一个唯一的整数值。
+
+```sql
+SELECT col1, col2, ROW_NUMBER() OVER (ORDER BY col1) AS row_num
+FROM table;
+```
+
+2. `RANK()`: 计算每一行在排序结果中的排名。
+
+```sql
+SELECT col1, col2, RANK() OVER (ORDER BY col1 DESC) AS ranking
+FROM table;
+```
+
+3. `DENSE_RANK()`: 计算每一行在排序结果中的排名,保留相同的排名。
+
+```sql
+SELECT col1, col2, DENSE_RANK() OVER (ORDER BY col1 DESC) AS ranking
+FROM table;
+```
+
+4. `NTILE(n)`: 将结果分成 n 个基本均匀的桶,并为每个桶分配一个标识号。
+
+```sql
+SELECT col1, col2, NTILE(4) OVER (ORDER BY col1) AS bucket
+FROM table;
+```
+
+5. `SUM()`, `AVG()`,`COUNT()`, `MIN()`, `MAX()`: 这些聚合函数也可以与窗口函数结合使用,计算窗口内指定列的汇总、平均值、计数、最小值和最大值。
+
+```sql
+SELECT col1, col2, SUM(col1) OVER () AS sum_col
+FROM table;
+```
+
+6. `LEAD()` 和 `LAG()`: LEAD 函数用于获取当前行之后的某个偏移量的行的值,而 LAG 函数用于获取当前行之前的某个偏移量的行的值。
+
+```sql
+SELECT col1, col2, LEAD(col1, 1) OVER (ORDER BY col1) AS next_col1,
+ LAG(col1, 1) OVER (ORDER BY col1) AS prev_col1
+FROM table;
+```
+
+7. `FIRST_VALUE()` 和 `LAST_VALUE()`: FIRST_VALUE 函数用于获取窗口内指定列的第一个值,LAST_VALUE 函数用于获取窗口内指定列的最后一个值。
+
+```sql
+SELECT col1, col2, FIRST_VALUE(col2) OVER (PARTITION BY col1 ORDER BY col2) AS first_val,
+ LAST_VALUE(col2) OVER (PARTITION BY col1 ORDER BY col2) AS last_val
+FROM table;
+```
+
+窗口函数通常需要配合 OVER 子句一起使用,用于定义窗口的大小、排序规则和分组方式。
+
+### 每类试卷得分前三名
+
+**描述**:
+
+现有试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2021-09-01 06:00:00 |
+| 2 | 9002 | SQL | hard | 60 | 2021-09-01 06:00:00 |
+| 3 | 9003 | 算法 | medium | 80 | 2021-09-01 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, score 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2021-09-01 09:01:01 | 2021-09-01 09:31:00 | 78 |
+| 2 | 1002 | 9001 | 2021-09-01 09:01:01 | 2021-09-01 09:31:00 | 81 |
+| 3 | 1002 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 81 |
+| 4 | 1003 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:40:01 | 86 |
+| 5 | 1003 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:51 | 89 |
+| 6 | 1004 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:30:01 | 85 |
+| 7 | 1005 | 9003 | 2021-09-01 12:01:01 | 2021-09-01 12:31:02 | 85 |
+| 8 | 1006 | 9003 | 2021-09-07 10:01:01 | 2021-09-07 10:21:01 | 84 |
+| 9 | 1003 | 9003 | 2021-09-08 12:01:01 | 2021-09-08 12:11:01 | 40 |
+| 10 | 1003 | 9002 | 2021-09-01 14:01:01 | (NULL) | (NULL) |
+
+找到每类试卷得分的前 3 名,如果两人最大分数相同,选择最小分数大者,如果还相同,选择 uid 大者。由示例数据结果输出如下:
+
+| tid | uid | ranking |
+| ---- | ---- | ------- |
+| SQL | 1003 | 1 |
+| SQL | 1004 | 2 |
+| SQL | 1002 | 3 |
+| 算法 | 1005 | 1 |
+| 算法 | 1006 | 2 |
+| 算法 | 1003 | 3 |
+
+**解释**:有作答得分记录的试卷 tag 有 SQL 和算法,SQL 试卷用户 1001、1002、1003、1004 有作答得分,最高得分分别为 81、81、89、85,最低得分分别为 78、81、86、40,因此先按最高得分排名再按最低得分排名取前三为 1003、1004、1002。
+
+**答案**:
+
+```sql
+SELECT tag,
+ UID,
+ ranking
+FROM
+ (SELECT b.tag AS tag,
+ a.uid AS UID,
+ ROW_NUMBER() OVER (PARTITION BY b.tag
+ ORDER BY b.tag,
+ max(a.score) DESC,
+ min(a.score) DESC,
+ a.uid DESC) AS ranking
+ FROM exam_record a
+ LEFT JOIN examination_info b ON a.exam_id = b.exam_id
+ GROUP BY b.tag,
+ a.uid) t
+WHERE ranking <= 3
+```
+
+### 第二快/慢用时之差大于试卷时长一半的试卷(较难)
+
+**描述**:
+
+现有试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2021-09-01 06:00:00 |
+| 2 | 9002 | C++ | hard | 60 | 2021-09-01 06:00:00 |
+| 3 | 9003 | 算法 | medium | 80 | 2021-09-01 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2021-09-01 09:01:01 | 2021-09-01 09:51:01 | 78 |
+| 2 | 1001 | 9002 | 2021-09-01 09:01:01 | 2021-09-01 09:31:00 | 81 |
+| 3 | 1002 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:01 | 81 |
+| 4 | 1003 | 9001 | 2021-09-01 19:01:01 | 2021-09-01 19:59:01 | 86 |
+| 5 | 1003 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:31:51 | 89 |
+| 6 | 1004 | 9002 | 2021-09-01 19:01:01 | 2021-09-01 19:30:01 | 85 |
+| 7 | 1005 | 9001 | 2021-09-01 12:01:01 | 2021-09-01 12:31:02 | 85 |
+| 8 | 1006 | 9001 | 2021-09-07 10:01:01 | 2021-09-07 10:21:01 | 84 |
+| 9 | 1003 | 9001 | 2021-09-08 12:01:01 | 2021-09-08 12:11:01 | 40 |
+| 10 | 1003 | 9002 | 2021-09-01 14:01:01 | (NULL) | (NULL) |
+| 11 | 1005 | 9001 | 2021-09-01 14:01:01 | (NULL) | (NULL) |
+| 12 | 1003 | 9003 | 2021-09-08 15:01:01 | (NULL) | (NULL) |
+
+找到第二快和第二慢用时之差大于试卷时长的一半的试卷信息,按试卷 ID 降序排序。由示例数据结果输出如下:
+
+| exam_id | duration | release_time |
+| ------- | -------- | ------------------- |
+| 9001 | 60 | 2021-09-01 06:00:00 |
+
+**解释**:试卷 9001 被作答用时有 50 分钟、50 分钟、30 分 1 秒、11 分钟、10 分钟,第二快和第二慢用时之差为 50 分钟-11 分钟=39 分钟,试卷时长为 60 分钟,因此满足大于试卷时长一半的条件,输出试卷 ID、时长、发布时间。
+
+**思路:**
+
+第一步,找到每张试卷完成时间的顺序排名和倒序排名 也就是表 a;
+
+第二步,与通过试卷信息表 b 建立内连接,并根据试卷 id 分组,利用`having`筛选排名为第二个数据,将秒转化为分钟并进行比较,最后再根据试卷 id 倒序排序就行
+
+**答案**:
+
+```sql
+SELECT a.exam_id,
+ b.duration,
+ b.release_time
+FROM
+ (SELECT exam_id,
+ row_number() OVER (PARTITION BY exam_id
+ ORDER BY timestampdiff(SECOND, start_time, submit_time) DESC) rn1,
+ row_number() OVER (PARTITION BY exam_id
+ ORDER BY timestampdiff(SECOND, start_time, submit_time) ASC) rn2,
+ timestampdiff(SECOND, start_time, submit_time) timex
+ FROM exam_record
+ WHERE score IS NOT NULL ) a
+INNER JOIN examination_info b ON a.exam_id = b.exam_id
+GROUP BY a.exam_id
+HAVING (max(IF (rn1 = 2, a.timex, 0))- max(IF (rn2 = 2, a.timex, 0)))/ 60 > b.duration / 2
+ORDER BY a.exam_id DESC
+```
+
+### 连续两次作答试卷的最大时间窗(较难)
+
+**描述**
+
+现有试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ----- |
+| 1 | 1006 | 9003 | 2021-09-07 10:01:01 | 2021-09-07 10:21:02 | 84 |
+| 2 | 1006 | 9001 | 2021-09-01 12:11:01 | 2021-09-01 12:31:01 | 89 |
+| 3 | 1006 | 9002 | 2021-09-06 10:01:01 | 2021-09-06 10:21:01 | 81 |
+| 4 | 1005 | 9002 | 2021-09-05 10:01:01 | 2021-09-05 10:21:01 | 81 |
+| 5 | 1005 | 9001 | 2021-09-05 10:31:01 | 2021-09-05 10:51:01 | 81 |
+
+请计算在 2021 年至少有两天作答过试卷的人中,计算该年连续两次作答试卷的最大时间窗 `days_window`,那么根据该年的历史规律他在 `days_window` 天里平均会做多少套试卷,按最大时间窗和平均做答试卷套数倒序排序。由示例数据结果输出如下:
+
+| uid | days_window | avg_exam_cnt |
+| ---- | ----------- | ------------ |
+| 1006 | 6 | 2.57 |
+
+**解释**:用户 1006 分别在 20210901、20210906、20210907 作答过 3 次试卷,连续两次作答最大时间窗为 6 天(1 号到 6 号),他 1 号到 7 号这 7 天里共做了 3 张试卷,平均每天 3/7=0.428571 张,那么 6 天里平均会做 0.428571\*6=2.57 张试卷(保留两位小数);用户 1005 在 20210905 做了两张试卷,但是只有一天的作答记录,过滤掉。
+
+**思路:**
+
+上面这个解释中提示要对作答记录去重,千万别被骗了,不要去重!去重就通不过测试用例。注意限制时间是 2021 年;
+
+而且要注意时间差要+1 天;还要注意==没交卷也算在内==!!!! (反正感觉这题描述不清,出的不是很好)
+
+**答案**:
+
+```sql
+SELECT UID,
+ max(datediff(next_time, start_time)) + 1 AS days_window,
+ round(count(start_time)/(datediff(max(start_time), min(start_time))+ 1) * (max(datediff(next_time, start_time))+ 1), 2) AS avg_exam_cnt
+FROM
+ (SELECT UID,
+ start_time,
+ lead(start_time, 1) OVER (PARTITION BY UID
+ ORDER BY start_time) AS next_time
+ FROM exam_record
+ WHERE YEAR (start_time) = '2021' ) a
+GROUP BY UID
+HAVING count(DISTINCT date(start_time)) > 1
+ORDER BY days_window DESC,
+ avg_exam_cnt DESC
+```
+
+### 近三个月未完成为 0 的用户完成情况
+
+**描述**:
+
+现有试卷作答记录表 `exam_record`(`uid`:用户 ID, `exam_id`:试卷 ID, `start_time`:开始作答时间, `submit_time`:交卷时间,为空的话则代表未完成, `score`:得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1006 | 9003 | 2021-09-06 10:01:01 | 2021-09-06 10:21:02 | 84 |
+| 2 | 1006 | 9001 | 2021-08-02 12:11:01 | 2021-08-02 12:31:01 | 89 |
+| 3 | 1006 | 9002 | 2021-06-06 10:01:01 | 2021-06-06 10:21:01 | 81 |
+| 4 | 1006 | 9002 | 2021-05-06 10:01:01 | 2021-05-06 10:21:01 | 81 |
+| 5 | 1006 | 9001 | 2021-05-01 12:01:01 | (NULL) | (NULL) |
+| 6 | 1001 | 9001 | 2021-09-05 10:31:01 | 2021-09-05 10:51:01 | 81 |
+| 7 | 1001 | 9003 | 2021-08-01 09:01:01 | 2021-08-01 09:51:11 | 78 |
+| 8 | 1001 | 9002 | 2021-07-01 09:01:01 | 2021-07-01 09:31:00 | 81 |
+| 9 | 1001 | 9002 | 2021-07-01 12:01:01 | 2021-07-01 12:31:01 | 81 |
+| 10 | 1001 | 9002 | 2021-07-01 12:01:01 | (NULL) | (NULL) |
+
+找到每个人近三个有试卷作答记录的月份中没有试卷是未完成状态的用户的试卷作答完成数,按试卷完成数和用户 ID 降序排名。由示例数据结果输出如下:
+
+| uid | exam_complete_cnt |
+| ---- | ----------------- |
+| 1006 | 3 |
+
+**解释**:用户 1006 近三个有作答试卷的月份为 202109、202108、202106,作答试卷数为 3,全部完成;用户 1001 近三个有作答试卷的月份为 202109、202108、202107,作答试卷数为 5,完成试卷数为 4,因为有未完成试卷,故过滤掉。
+
+**思路:**
+
+1. `找到每个人近三个有试卷作答记录的月份中没有试卷是未完成状态的用户的试卷作答完成数`首先看这句话,肯定要先根据人进行分组
+2. 最近三个月,可以采用连续重复排名,倒序排列,排名<=3
+3. 统计作答数
+4. 拼装剩余条件
+5. 排序
+
+**答案**:
+
+```sql
+SELECT UID,
+ count(score) exam_complete_cnt
+FROM
+ (SELECT *, DENSE_RANK() OVER (PARTITION BY UID
+ ORDER BY date_format(start_time, '%Y%m') DESC) dr
+ FROM exam_record) t1
+WHERE dr <= 3
+GROUP BY UID
+HAVING count(dr)= count(score)
+ORDER BY exam_complete_cnt DESC,
+ UID DESC
+```
+
+### 未完成率较高的 50%用户近三个月答卷情况(困难)
+
+**描述**:
+
+现有用户信息表 `user_info`(`uid` 用户 ID,`nick_name` 昵称, `achievement` 成就值, `level` 等级, `job` 职业方向, `register_time` 注册时间):
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | ----------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 号 | 3200 | 7 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 2500 | 6 | 算法 | 2020-01-01 10:00:00 |
+| 3 | 1003 | 牛客 3 号 ♂ | 2200 | 5 | 算法 | 2020-01-01 10:00:00 |
+
+试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ------ | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2020-01-01 10:00:00 |
+| 2 | 9002 | SQL | hard | 80 | 2020-01-01 10:00:00 |
+| 3 | 9003 | 算法 | hard | 80 | 2020-01-01 10:00:00 |
+| 4 | 9004 | PYTHON | medium | 70 | 2020-01-01 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ----- |
+| 1 | 1001 | 9001 | 2020-01-01 09:01:01 | 2020-01-01 09:21:59 | 90 |
+| 15 | 1002 | 9001 | 2020-01-01 18:01:01 | 2020-01-01 18:59:02 | 90 |
+| 13 | 1001 | 9001 | 2020-01-02 10:01:01 | 2020-01-02 10:31:01 | 89 |
+| 2 | 1002 | 9001 | 2020-01-20 10:01:01 | | |
+| 3 | 1002 | 9001 | 2020-02-01 12:11:01 | | |
+| 5 | 1001 | 9001 | 2020-03-01 12:01:01 | | |
+| 6 | 1002 | 9001 | 2020-03-01 12:01:01 | 2020-03-01 12:41:01 | 90 |
+| 4 | 1003 | 9001 | 2020-03-01 19:01:01 | | |
+| 7 | 1002 | 9001 | 2020-05-02 19:01:01 | 2020-05-02 19:32:00 | 90 |
+| 14 | 1001 | 9002 | 2020-01-01 12:11:01 | | |
+| 8 | 1001 | 9002 | 2020-01-02 19:01:01 | 2020-01-02 19:59:01 | 69 |
+| 9 | 1001 | 9002 | 2020-02-02 12:01:01 | 2020-02-02 12:20:01 | 99 |
+| 10 | 1002 | 9002 | 2020-02-02 12:01:01 | | |
+| 11 | 1002 | 9002 | 2020-02-02 12:01:01 | 2020-02-02 12:43:01 | 81 |
+| 12 | 1002 | 9002 | 2020-03-02 12:11:01 | | |
+| 17 | 1001 | 9002 | 2020-05-05 18:01:01 | | |
+| 16 | 1002 | 9003 | 2020-05-06 12:01:01 | | |
+
+请统计 SQL 试卷上未完成率较高的 50%用户中,6 级和 7 级用户在有试卷作答记录的近三个月中,每个月的答卷数目和完成数目。按用户 ID、月份升序排序。
+
+由示例数据结果输出如下:
+
+| uid | start_month | total_cnt | complete_cnt |
+| ---- | ----------- | --------- | ------------ |
+| 1002 | 202002 | 3 | 1 |
+| 1002 | 202003 | 2 | 1 |
+| 1002 | 202005 | 2 | 1 |
+
+解释:各个用户对 SQL 试卷的未完成数、作答总数、未完成率如下:
+
+| uid | incomplete_cnt | total_cnt | incomplete_rate |
+| ---- | -------------- | --------- | --------------- |
+| 1001 | 3 | 7 | 0.4286 |
+| 1002 | 4 | 8 | 0.5000 |
+| 1003 | 1 | 1 | 1.0000 |
+
+1001、1002、1003 分别排在 1.0、0.5、0.0 的位置,因此较高的 50%用户(排位<=0.5)为 1002、1003;
+
+1003 不是 6 级或 7 级;
+
+有试卷作答记录的近三个月为 202005、202003、202002;
+
+这三个月里 1002 的作答题数分别为 3、2、2,完成数目分别为 1、1、1。
+
+**思路:**
+
+注意点:这题注意求的是所有的答题次数和完成次数,而 sql 类别的试卷是限制未完成率排名,6, 7 级用户限制的是做题记录。
+
+先求出未完成率的排名
+
+```sql
+SELECT UID,
+ count(submit_time IS NULL
+ OR NULL)/ count(start_time) AS num,
+ PERCENT_RANK() OVER (
+ ORDER BY count(submit_time IS NULL
+ OR NULL)/ count(start_time)) AS ranking
+FROM exam_record
+LEFT JOIN examination_info USING (exam_id)
+WHERE tag = 'SQL'
+GROUP BY UID
+```
+
+再求出最近三个月的练习记录
+
+```sql
+SELECT UID,
+ date_format(start_time, '%Y%m') AS month_d,
+ submit_time,
+ exam_id,
+ dense_rank() OVER (PARTITION BY UID
+ ORDER BY date_format(start_time, '%Y%m') DESC) AS ranking
+FROM exam_record
+LEFT JOIN user_info USING (UID)
+WHERE LEVEL IN (6,7)
+```
+
+**答案**:
+
+```sql
+SELECT t1.uid,
+ t1.month_d,
+ count(*) AS total_cnt,
+ count(t1.submit_time) AS complete_cnt
+FROM-- 先求出未完成率的排名
+
+ (SELECT UID,
+ count(submit_time IS NULL OR NULL)/ count(start_time) AS num,
+ PERCENT_RANK() OVER (
+ ORDER BY count(submit_time IS NULL OR NULL)/ count(start_time)) AS ranking
+ FROM exam_record
+ LEFT JOIN examination_info USING (exam_id)
+ WHERE tag = 'SQL'
+ GROUP BY UID) t
+INNER JOIN
+ (-- 再求出近三个月的练习记录
+ SELECT UID,
+ date_format(start_time, '%Y%m') AS month_d,
+ submit_time,
+ exam_id,
+ dense_rank() OVER (PARTITION BY UID
+ ORDER BY date_format(start_time, '%Y%m') DESC) AS ranking
+ FROM exam_record
+ LEFT JOIN user_info USING (UID)
+ WHERE LEVEL IN (6,7) ) t1 USING (UID)
+WHERE t1.ranking <= 3 AND t.ranking >= 0.5 -- 使用限制找到符合条件的记录
+
+GROUP BY t1.uid,
+ t1.month_d
+ORDER BY t1.uid,
+ t1.month_d
+```
+
+### 试卷完成数同比 2020 年的增长率及排名变化(困难)
+
+**描述**:
+
+现有试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ------ | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2021-01-01 10:00:00 |
+| 2 | 9002 | C++ | hard | 80 | 2021-01-01 10:00:00 |
+| 3 | 9003 | 算法 | hard | 80 | 2021-01-01 10:00:00 |
+| 4 | 9004 | PYTHON | medium | 70 | 2021-01-01 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ----- |
+| 1 | 1001 | 9001 | 2020-08-02 10:01:01 | 2020-08-02 10:31:01 | 89 |
+| 2 | 1002 | 9001 | 2020-04-01 18:01:01 | 2020-04-01 18:59:02 | 90 |
+| 3 | 1001 | 9001 | 2020-04-01 09:01:01 | 2020-04-01 09:21:59 | 80 |
+| 5 | 1002 | 9001 | 2021-03-02 19:01:01 | 2021-03-02 19:32:00 | 20 |
+| 8 | 1003 | 9001 | 2021-05-02 12:01:01 | 2021-05-02 12:31:01 | 98 |
+| 13 | 1003 | 9001 | 2020-01-02 10:01:01 | 2020-01-02 10:31:01 | 89 |
+| 9 | 1001 | 9002 | 2020-02-02 12:01:01 | 2020-02-02 12:20:01 | 99 |
+| 10 | 1002 | 9002 | 2021-02-02 12:01:01 | 2020-02-02 12:43:01 | 81 |
+| 11 | 1001 | 9002 | 2020-01-02 19:01:01 | 2020-01-02 19:59:01 | 69 |
+| 16 | 1002 | 9002 | 2020-02-02 12:01:01 | | |
+| 17 | 1002 | 9002 | 2020-03-02 12:11:01 | | |
+| 18 | 1001 | 9002 | 2021-05-05 18:01:01 | | |
+| 4 | 1002 | 9003 | 2021-01-20 10:01:01 | 2021-01-20 10:10:01 | 81 |
+| 6 | 1001 | 9003 | 2021-04-02 19:01:01 | 2021-04-02 19:40:01 | 89 |
+| 15 | 1002 | 9003 | 2021-01-01 18:01:01 | 2021-01-01 18:59:02 | 90 |
+| 7 | 1004 | 9004 | 2020-05-02 12:01:01 | 2020-05-02 12:20:01 | 99 |
+| 12 | 1001 | 9004 | 2021-09-02 12:11:01 | | |
+| 14 | 1002 | 9004 | 2020-01-01 12:11:01 | 2020-01-01 12:31:01 | 83 |
+
+请计算 2021 年上半年各类试卷的做完次数相比 2020 年上半年同期的增长率(百分比格式,保留 1 位小数),以及做完次数排名变化,按增长率和 21 年排名降序输出。
+
+由示例数据结果输出如下:
+
+| tag | exam_cnt_20 | exam_cnt_21 | growth_rate | exam_cnt_rank_20 | exam_cnt_rank_21 | rank_delta |
+| ---- | ----------- | ----------- | ----------- | ---------------- | ---------------- | ---------- |
+| SQL | 3 | 2 | -33.3% | 1 | 2 | 1 |
+
+解释:2020 年上半年有 3 个 tag 有作答完成的记录,分别是 C++、SQL、PYTHON,它们被做完的次数分别是 3、3、2,做完次数排名为 1、1(并列)、3;
+
+2021 年上半年有 2 个 tag 有作答完成的记录,分别是算法、SQL,它们被做完的次数分别是 3、2,做完次数排名为 1、2;具体如下:
+
+| tag | start_year | exam_cnt | exam_cnt_rank |
+| ------ | ---------- | -------- | ------------- |
+| C++ | 2020 | 3 | 1 |
+| SQL | 2020 | 3 | 1 |
+| PYTHON | 2020 | 2 | 3 |
+| 算法 | 2021 | 3 | 1 |
+| SQL | 2021 | 2 | 2 |
+
+因此能输出同比结果的 tag 只有 SQL,从 2020 到 2021 年,做完次数 3=>2,减少 33.3%(保留 1 位小数);排名 1=>2,后退 1 名。
+
+**思路:**
+
+本题难点在于长整型的数据类型要求不能有负号产生,用 cast 函数转换数据类型为 signed。
+
+以及用到的`增长率计算公式:(exam_cnt_21-exam_cnt_20)/exam_cnt_20`
+
+做完次数排名变化(2021 年和 2020 年比排名升了或者降了多少)
+
+计算公式:`exam_cnt_rank_21 - exam_cnt_rank_20`
+
+在 MySQL 中,`CAST()` 函数用于将一个表达式的数据类型转换为另一个数据类型。它的基本语法如下:
+
+```sql
+CAST(expression AS data_type)
+
+-- 将一个字符串转换成整数
+SELECT CAST('123' AS INT);
+```
+
+示例就不一一举例了,这个函数很简单
+
+**答案**:
+
+```sql
+SELECT
+ tag,
+ exam_cnt_20,
+ exam_cnt_21,
+ concat(
+ round(
+ 100 * (exam_cnt_21 - exam_cnt_20) / exam_cnt_20,
+ 1
+ ),
+ '%'
+ ) AS growth_rate,
+ exam_cnt_rank_20,
+ exam_cnt_rank_21,
+ cast(exam_cnt_rank_21 AS signed) - cast(exam_cnt_rank_20 AS signed) AS rank_delta
+FROM
+ (
+ #2020年、2021年上半年各类试卷的做完次数和做完次数排名
+ SELECT
+ tag,
+ count(
+ IF (
+ date_format(start_time, '%Y%m%d') BETWEEN '20200101'
+ AND '20200630',
+ start_time,
+ NULL
+ )
+ ) AS exam_cnt_20,
+ count(
+ IF (
+ substring(start_time, 1, 10) BETWEEN '2021-01-01'
+ AND '2021-06-30',
+ start_time,
+ NULL
+ )
+ ) AS exam_cnt_21,
+ rank() over (
+ ORDER BY
+ count(
+ IF (
+ date_format(start_time, '%Y%m%d') BETWEEN '20200101'
+ AND '20200630',
+ start_time,
+ NULL
+ )
+ ) DESC
+ ) AS exam_cnt_rank_20,
+ rank() over (
+ ORDER BY
+ count(
+ IF (
+ substring(start_time, 1, 10) BETWEEN '2021-01-01'
+ AND '2021-06-30',
+ start_time,
+ NULL
+ )
+ ) DESC
+ ) AS exam_cnt_rank_21
+ FROM
+ examination_info
+ JOIN exam_record USING (exam_id)
+ WHERE
+ submit_time IS NOT NULL
+ GROUP BY
+ tag
+ ) main
+WHERE
+ exam_cnt_21 * exam_cnt_20 <> 0
+ORDER BY
+ growth_rate DESC,
+ exam_cnt_rank_21 DESC
+```
+
+## 聚合窗口函数
+
+### 对试卷得分做 min-max 归一化
+
+**描述**:
+
+现有试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ------ | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2020-01-01 10:00:00 |
+| 2 | 9002 | C++ | hard | 80 | 2020-01-01 10:00:00 |
+| 3 | 9003 | 算法 | hard | 80 | 2020-01-01 10:00:00 |
+| 4 | 9004 | PYTHON | medium | 70 | 2020-01-01 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 6 | 1003 | 9001 | 2020-01-02 12:01:01 | 2020-01-02 12:31:01 | 68 |
+| 9 | 1001 | 9001 | 2020-01-02 10:01:01 | 2020-01-02 10:31:01 | 89 |
+| 1 | 1001 | 9001 | 2020-01-01 09:01:01 | 2020-01-01 09:21:59 | 90 |
+| 12 | 1002 | 9002 | 2021-05-05 18:01:01 | (NULL) | (NULL) |
+| 3 | 1004 | 9002 | 2020-01-01 12:01:01 | 2020-01-01 12:11:01 | 60 |
+| 2 | 1003 | 9002 | 2020-01-01 19:01:01 | 2020-01-01 19:30:01 | 75 |
+| 7 | 1001 | 9002 | 2020-01-02 12:01:01 | 2020-01-02 12:43:01 | 81 |
+| 10 | 1002 | 9002 | 2020-01-01 12:11:01 | 2020-01-01 12:31:01 | 83 |
+| 4 | 1003 | 9002 | 2020-01-01 12:01:01 | 2020-01-01 12:41:01 | 90 |
+| 5 | 1002 | 9002 | 2020-01-02 19:01:01 | 2020-01-02 19:32:00 | 90 |
+| 11 | 1002 | 9004 | 2021-09-06 12:01:01 | (NULL) | (NULL) |
+| 8 | 1001 | 9005 | 2020-01-02 12:11:01 | (NULL) | (NULL) |
+
+在物理学及统计学数据计算时,有个概念叫 min-max 标准化,也被称为离差标准化,是对原始数据的线性变换,使结果值映射到[0 - 1]之间。
+
+转换函数为:
+
+
+
+请你将用户作答高难度试卷的得分在每份试卷作答记录内执行 min-max 归一化后缩放到[0,100]区间,并输出用户 ID、试卷 ID、归一化后分数平均值;最后按照试卷 ID 升序、归一化分数降序输出。(注:得分区间默认为[0,100],如果某个试卷作答记录中只有一个得分,那么无需使用公式,归一化并缩放后分数仍为原分数)。
+
+由示例数据结果输出如下:
+
+| uid | exam_id | avg_new_score |
+| ---- | ------- | ------------- |
+| 1001 | 9001 | 98 |
+| 1003 | 9001 | 0 |
+| 1002 | 9002 | 88 |
+| 1003 | 9002 | 75 |
+| 1001 | 9002 | 70 |
+| 1004 | 9002 | 0 |
+
+解释:高难度试卷有 9001、9002、9003;
+
+作答了 9001 的记录有 3 条,分数分别为 68、89、90,按给定公式归一化后分数为:0、95、100,而后两个得分都是用户 1001 作答的,因此用户 1001 对试卷 9001 的新得分为(95+100)/2≈98(只保留整数部分),用户 1003 对于试卷 9001 的新得分为 0。最后结果按照试卷 ID 升序、归一化分数降序输出。
+
+**思路:**
+
+注意点:
+
+1. 将高难度的试卷,按每类试卷的得分,利用 max/min (col) over()窗口函数求得各组内最大最小值,然后进行归一化公式计算,缩放区间为[0,100],即 min_max\*100
+2. 若某类试卷只有一个得分,则无需使用归一化公式,因只有一个分 max_score=min_score,score,公式后结果可能会变成 0。
+3. 最后结果按 uid、exam_id 分组求归一化后均值,score 为 NULL 的要过滤掉。
+
+最后就是仔细看上面公式 (说实话,这题看起来就很绕)
+
+**答案**:
+
+```sql
+SELECT
+ uid,
+ exam_id,
+ round(sum(min_max) / count(score), 0) AS avg_new_score
+FROM
+ (
+ SELECT
+ *,
+ IF (
+ max_score = min_score,
+ score,
+ (score - min_score) / (max_score - min_score) * 100
+ ) AS min_max
+ FROM
+ (
+ SELECT
+ uid,
+ a.exam_id,
+ score,
+ max(score) over (PARTITION BY a.exam_id) AS max_score,
+ min(score) over (PARTITION BY a.exam_id) AS min_score
+ FROM
+ exam_record a
+ LEFT JOIN examination_info b USING (exam_id)
+ WHERE
+ difficulty = 'hard'
+ ) t
+ WHERE
+ score IS NOT NULL
+ ) t1
+GROUP BY
+ uid,
+ exam_id
+ORDER BY
+ exam_id ASC,
+ avg_new_score DESC;
+```
+
+### 每份试卷每月作答数和截止当月的作答总数
+
+**描述:**
+
+现有试卷作答记录表 exam_record(uid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2020-01-01 09:01:01 | 2020-01-01 09:21:59 | 90 |
+| 2 | 1002 | 9001 | 2020-01-20 10:01:01 | 2020-01-20 10:10:01 | 89 |
+| 3 | 1002 | 9001 | 2020-02-01 12:11:01 | 2020-02-01 12:31:01 | 83 |
+| 4 | 1003 | 9001 | 2020-03-01 19:01:01 | 2020-03-01 19:30:01 | 75 |
+| 5 | 1004 | 9001 | 2020-03-01 12:01:01 | 2020-03-01 12:11:01 | 60 |
+| 6 | 1003 | 9001 | 2020-03-01 12:01:01 | 2020-03-01 12:41:01 | 90 |
+| 7 | 1002 | 9001 | 2020-05-02 19:01:01 | 2020-05-02 19:32:00 | 90 |
+| 8 | 1001 | 9002 | 2020-01-02 19:01:01 | 2020-01-02 19:59:01 | 69 |
+| 9 | 1004 | 9002 | 2020-02-02 12:01:01 | 2020-02-02 12:20:01 | 99 |
+| 10 | 1003 | 9002 | 2020-02-02 12:01:01 | 2020-02-02 12:31:01 | 68 |
+| 11 | 1001 | 9002 | 2020-02-02 12:01:01 | 2020-02-02 12:43:01 | 81 |
+| 12 | 1001 | 9002 | 2020-03-02 12:11:01 | (NULL) | (NULL) |
+
+请输出每份试卷每月作答数和截止当月的作答总数。
+由示例数据结果输出如下:
+
+| exam_id | start_month | month_cnt | cum_exam_cnt |
+| ------- | ----------- | --------- | ------------ |
+| 9001 | 202001 | 2 | 2 |
+| 9001 | 202002 | 1 | 3 |
+| 9001 | 202003 | 3 | 6 |
+| 9001 | 202005 | 1 | 7 |
+| 9002 | 202001 | 1 | 1 |
+| 9002 | 202002 | 3 | 4 |
+| 9002 | 202003 | 1 | 5 |
+
+解释:试卷 9001 在 202001、202002、202003、202005 共 4 个月有被作答记录,每个月被作答数分别为 2、1、3、1,截止当月累积作答总数为 2、3、6、7。
+
+**思路:**
+
+这题就两个关键点:统计截止当月的作答总数、输出每份试卷每月作答数和截止当月的作答总数
+
+这个是关键`**sum(count(*)) over(partition by exam_id order by date_format(start_time,'%Y%m'))**`
+
+**答案**:
+
+```sql
+SELECT exam_id,
+ date_format(start_time, '%Y%m') AS start_month,
+ count(*) AS month_cnt,
+ sum(count(*)) OVER (PARTITION BY exam_id
+ ORDER BY date_format(start_time, '%Y%m')) AS cum_exam_cnt
+FROM exam_record
+GROUP BY exam_id,
+ start_month
+```
+
+### 每月及截止当月的答题情况(较难)
+
+**描述**:现有试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2020-01-01 09:01:01 | 2020-01-01 09:21:59 | 90 |
+| 2 | 1002 | 9001 | 2020-01-20 10:01:01 | 2020-01-20 10:10:01 | 89 |
+| 3 | 1002 | 9001 | 2020-02-01 12:11:01 | 2020-02-01 12:31:01 | 83 |
+| 4 | 1003 | 9001 | 2020-03-01 19:01:01 | 2020-03-01 19:30:01 | 75 |
+| 5 | 1004 | 9001 | 2020-03-01 12:01:01 | 2020-03-01 12:11:01 | 60 |
+| 6 | 1003 | 9001 | 2020-03-01 12:01:01 | 2020-03-01 12:41:01 | 90 |
+| 7 | 1002 | 9001 | 2020-05-02 19:01:01 | 2020-05-02 19:32:00 | 90 |
+| 8 | 1001 | 9002 | 2020-01-02 19:01:01 | 2020-01-02 19:59:01 | 69 |
+| 9 | 1004 | 9002 | 2020-02-02 12:01:01 | 2020-02-02 12:20:01 | 99 |
+| 10 | 1003 | 9002 | 2020-02-02 12:01:01 | 2020-02-02 12:31:01 | 68 |
+| 11 | 1001 | 9002 | 2020-01-02 19:01:01 | 2020-02-02 12:43:01 | 81 |
+| 12 | 1001 | 9002 | 2020-03-02 12:11:01 | (NULL) | (NULL) |
+
+请输出自从有用户作答记录以来,每月的试卷作答记录中月活用户数、新增用户数、截止当月的单月最大新增用户数、截止当月的累积用户数。结果按月份升序输出。
+
+由示例数据结果输出如下:
+
+| start_month | mau | month_add_uv | max_month_add_uv | cum_sum_uv |
+| ----------- | ---- | ------------ | ---------------- | ---------- |
+| 202001 | 2 | 2 | 2 | 2 |
+| 202002 | 4 | 2 | 2 | 4 |
+| 202003 | 3 | 0 | 2 | 4 |
+| 202005 | 1 | 0 | 2 | 4 |
+
+| month | 1001 | 1002 | 1003 | 1004 |
+| ------ | ---- | ---- | ---- | ---- |
+| 202001 | 1 | 1 | | |
+| 202002 | 1 | 1 | 1 | 1 |
+| 202003 | 1 | | 1 | 1 |
+| 202005 | | 1 | | |
+
+由上述矩阵可以看出,2020 年 1 月有 2 个用户活跃(mau=2),当月新增用户数为 2;
+
+2020 年 2 月有 4 个用户活跃,当月新增用户数为 2,最大单月新增用户数为 2,当前累积用户数为 4。
+
+**思路:**
+
+难点:
+
+1.如何求每月新增用户
+
+2.截至当月的答题情况
+
+大致流程:
+
+(1)统计每个人的首次登陆月份 `min()`
+
+(2)统计每月的月活和新增用户数:先得到每个人的首次登陆月份,再对首次登陆月份分组求和是该月份的新增人数
+
+(3)统计截止当月的单月最大新增用户数、截止当月的累积用户数 ,最终按照按月份升序输出
+
+**答案**:
+
+```sql
+-- 截止当月的单月最大新增用户数、截止当月的累积用户数,按月份升序输出
+SELECT
+ start_month,
+ mau,
+ month_add_uv,
+ max( month_add_uv ) over ( ORDER BY start_month ),
+ sum( month_add_uv ) over ( ORDER BY start_month )
+FROM
+ (
+ -- 统计每月的月活和新增用户数
+ SELECT
+ date_format( a.start_time, '%Y%m' ) AS start_month,
+ count( DISTINCT a.uid ) AS mau,
+ count( DISTINCT b.uid ) AS month_add_uv
+ FROM
+ exam_record a
+ LEFT JOIN (
+ -- 统计每个人的首次登陆月份
+ SELECT uid, min( date_format( start_time, '%Y%m' )) AS first_month FROM exam_record GROUP BY uid ) b ON date_format( a.start_time, '%Y%m' ) = b.first_month
+ GROUP BY
+ start_month
+ ) main
+ORDER BY
+ start_month
+```
+
diff --git a/docs/database/sql/sql-questions-05.md b/docs/database/sql/sql-questions-05.md
new file mode 100644
index 0000000000000000000000000000000000000000..7a527ef3e0e2fff4cc93ad06c4eab28c283428bf
--- /dev/null
+++ b/docs/database/sql/sql-questions-05.md
@@ -0,0 +1,1011 @@
+---
+title: SQL常见面试题总结(5)
+category: 数据库
+tag:
+ - 数据库基础
+ - SQL
+---
+
+> 题目来源于:[牛客题霸 - SQL 进阶挑战](https://www.nowcoder.com/exam/oj?page=1&tab=SQL%E7%AF%87&topicId=240)
+
+较难或者困难的题目可以根据自身实际情况和面试需要来决定是否要跳过。
+
+## 空值处理
+
+### 统计有未完成状态的试卷的未完成数和未完成率
+
+**描述**:
+
+现有试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分),数据如下:
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2020-01-02 09:01:01 | 2020-01-02 09:21:01 | 80 |
+| 2 | 1001 | 9001 | 2021-05-02 10:01:01 | 2021-05-02 10:30:01 | 81 |
+| 3 | 1001 | 9001 | 2021-09-02 12:01:01 | (NULL) | (NULL) |
+
+请统计有未完成状态的试卷的未完成数 incomplete_cnt 和未完成率 incomplete_rate。由示例数据结果输出如下:
+
+| exam_id | incomplete_cnt | complete_rate |
+| ------- | -------------- | ------------- |
+| 9001 | 1 | 0.333 |
+
+解释:试卷 9001 有 3 次被作答的记录,其中两次完成,1 次未完成,因此未完成数为 1,未完成率为 0.333(保留 3 位小数)
+
+**思路**:
+
+这题只需要注意一个是有条件限制,一个是没条件限制的;要么分别查询条件,然后合并;要么直接在 select 里面进行条件判断。
+
+**答案**:
+
+写法 1:
+
+```sql
+SELECT exam_id,
+ count(submit_time IS NULL OR NULL) incomplete_cnt,
+ ROUND(count(submit_time IS NULL OR NULL) / count(*), 3) complete_rate
+FROM exam_record
+GROUP BY exam_id
+HAVING incomplete_cnt <> 0
+```
+
+写法 2:
+
+```sql
+SELECT exam_id,
+ count(submit_time IS NULL OR NULL) incomplete_cnt,
+ ROUND(count(submit_time IS NULL OR NULL) / count(*), 3) complete_rate
+FROM exam_record
+GROUP BY exam_id
+HAVING incomplete_cnt <> 0
+```
+
+两种写法都可以,只有中间的写法不一样,一个是对符合条件的才`COUNT`,一个是直接上`IF`,后者更为直观,最后这个`having`解释一下, 无论是 `complete_rate` 还是 `incomplete_cnt`,只要不为 0 即可,不为 0 就意味着有未完成的。
+
+### 0 级用户高难度试卷的平均用时和平均得分
+
+**描述**:
+
+现有用户信息表 `user_info`(`uid` 用户 ID,`nick_name` 昵称, `achievement` 成就值, `level` 等级, `job` 职业方向, `register_time` 注册时间),数据如下:
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | --------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 号 | 10 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 2100 | 6 | 算法 | 2020-01-01 10:00:00 |
+
+试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间),数据如下:
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | SQL | hard | 60 | 2020-01-01 10:00:00 |
+| 2 | 9002 | SQL | easy | 60 | 2020-01-01 10:00:00 |
+| 3 | 9004 | 算法 | medium | 80 | 2020-01-01 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分),数据如下:
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2020-01-02 09:01:01 | 2020-01-02 09:21:59 | 80 |
+| 2 | 1001 | 9001 | 2021-05-02 10:01:01 | (NULL) | (NULL) |
+| 3 | 1001 | 9002 | 2021-02-02 19:01:01 | 2021-02-02 19:30:01 | 87 |
+| 4 | 1001 | 9001 | 2021-06-02 19:01:01 | 2021-06-02 19:32:00 | 20 |
+| 5 | 1001 | 9002 | 2021-09-05 19:01:01 | 2021-09-05 19:40:01 | 89 |
+| 6 | 1001 | 9002 | 2021-09-01 12:01:01 | (NULL) | (NULL) |
+| 7 | 1002 | 9002 | 2021-05-05 18:01:01 | 2021-05-05 18:59:02 | 90 |
+
+请输出每个 0 级用户所有的高难度试卷考试平均用时和平均得分,未完成的默认试卷最大考试时长和 0 分处理。由示例数据结果输出如下:
+
+| uid | avg_score | avg_time_took |
+| ---- | --------- | ------------- |
+| 1001 | 33 | 36.7 |
+
+解释:0 级用户有 1001,高难度试卷有 9001,1001 作答 9001 的记录有 3 条,分别用时 20 分钟、未完成(试卷时长 60 分钟)、30 分钟(未满 31 分钟),分别得分为 80 分、未完成(0 分处理)、20 分。因此他的平均用时为 110/3=36.7(保留一位小数),平均得分为 33 分(取整)
+
+**思路**:这题用`IF`是判断的最方便的,因为涉及到 NULL 值的判断。当然 `case when`也可以,大同小异。这题的难点就在于空值的处理,其他的这些查询条件什么的,我相信难不倒大家。
+
+**答案**:
+
+```sql
+SELECT UID,
+ round(avg(new_socre)) AS avg_score,
+ round(avg(time_diff), 1) AS avg_time_took
+FROM
+ (SELECT er.uid,
+ IF (er.submit_time IS NOT NULL, TIMESTAMPDIFF(MINUTE, start_time, submit_time), ef.duration) AS time_diff,
+ IF (er.submit_time IS NOT NULL,er.score,0) AS new_socre
+ FROM exam_record er
+ LEFT JOIN user_info uf ON er.uid = uf.uid
+ LEFT JOIN examination_info ef ON er.exam_id = ef.exam_id
+ WHERE uf.LEVEL = 0 AND ef.difficulty = 'hard' ) t
+GROUP BY UID
+ORDER BY UID
+```
+
+## 高级条件语句
+
+### 筛选限定昵称成就值活跃日期的用户(较难)
+
+**描述**:
+
+现有用户信息表 `user_info`(`uid` 用户 ID,`nick_name` 昵称, `achievement` 成就值, `level` 等级, `job` 职业方向, `register_time` 注册时间):
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | ----------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 号 | 1000 | 2 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 1200 | 3 | 算法 | 2020-01-01 10:00:00 |
+| 3 | 1003 | 进击的 3 号 | 2200 | 5 | 算法 | 2020-01-01 10:00:00 |
+| 4 | 1004 | 牛客 4 号 | 2500 | 6 | 算法 | 2020-01-01 10:00:00 |
+| 5 | 1005 | 牛客 5 号 | 3000 | 7 | C++ | 2020-01-01 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2020-01-02 09:01:01 | 2020-01-02 09:21:59 | 80 |
+| 3 | 1001 | 9002 | 2021-02-02 19:01:01 | 2021-02-02 19:30:01 | 87 |
+| 2 | 1001 | 9001 | 2021-05-02 10:01:01 | (NULL) | (NULL) |
+| 4 | 1001 | 9001 | 2021-06-02 19:01:01 | 2021-06-02 19:32:00 | 20 |
+| 6 | 1001 | 9002 | 2021-09-01 12:01:01 | (NULL) | (NULL) |
+| 5 | 1001 | 9002 | 2021-09-05 19:01:01 | 2021-09-05 19:40:01 | 89 |
+| 11 | 1002 | 9001 | 2020-01-01 12:01:01 | 2020-01-01 12:31:01 | 81 |
+| 12 | 1002 | 9002 | 2020-02-01 12:01:01 | 2020-02-01 12:31:01 | 82 |
+| 13 | 1002 | 9002 | 2020-02-02 12:11:01 | 2020-02-02 12:31:01 | 83 |
+| 7 | 1002 | 9002 | 2021-05-05 18:01:01 | 2021-05-05 18:59:02 | 90 |
+| 16 | 1002 | 9001 | 2021-09-06 12:01:01 | 2021-09-06 12:21:01 | 80 |
+| 17 | 1002 | 9001 | 2021-09-06 12:01:01 | (NULL) | (NULL) |
+| 18 | 1002 | 9001 | 2021-09-07 12:01:01 | (NULL) | (NULL) |
+| 8 | 1003 | 9003 | 2021-02-06 12:01:01 | (NULL) | (NULL) |
+| 9 | 1003 | 9001 | 2021-09-07 10:01:01 | 2021-09-07 10:31:01 | 89 |
+| 10 | 1004 | 9002 | 2021-08-06 12:01:01 | (NULL) | (NULL) |
+| 14 | 1005 | 9001 | 2021-02-01 11:01:01 | 2021-02-01 11:31:01 | 84 |
+| 15 | 1006 | 9001 | 2021-02-01 11:01:01 | 2021-02-01 11:31:01 | 84 |
+
+题目练习记录表 `practice_record`(`uid` 用户 ID, `question_id` 题目 ID, `submit_time` 提交时间, `score` 得分):
+
+| id | uid | question_id | submit_time | score |
+| ---- | ---- | ----------- | ------------------- | ----- |
+| 1 | 1001 | 8001 | 2021-08-02 11:41:01 | 60 |
+| 2 | 1002 | 8001 | 2021-09-02 19:30:01 | 50 |
+| 3 | 1002 | 8001 | 2021-09-02 19:20:01 | 70 |
+| 4 | 1002 | 8002 | 2021-09-02 19:38:01 | 70 |
+| 5 | 1003 | 8002 | 2021-09-01 19:38:01 | 80 |
+
+请找到昵称以『牛客』开头『号』结尾、成就值在 1200~2500 之间,且最近一次活跃(答题或作答试卷)在 2021 年 9 月的用户信息。
+
+由示例数据结果输出如下:
+
+| uid | nick_name | achievement |
+| ---- | --------- | ----------- |
+| 1002 | 牛客 2 号 | 1200 |
+
+**解释**:昵称以『牛客』开头『号』结尾且成就值在 1200~2500 之间的有 1002、1004;
+
+1002 最近一次试卷区活跃为 2021 年 9 月,最近一次题目区活跃为 2021 年 9 月;1004 最近一次试卷区活跃为 2021 年 8 月,题目区未活跃。
+
+因此最终满足条件的只有 1002。
+
+**思路**:
+
+先根据条件列出主要查询语句
+
+昵称以『牛客』开头『号』结尾: `nick_name LIKE "牛客%号"`
+
+成就值在 1200~2500 之间:`achievement BETWEEN 1200 AND 2500`
+
+第三个条件因为限定了为 9 月,所以直接写就行:`( date_format( record.submit_time, '%Y%m' )= 202109 OR date_format( pr.submit_time, '%Y%m' )= 202109 )`
+
+**答案**:
+
+```sql
+SELECT DISTINCT u_info.uid,
+ u_info.nick_name,
+ u_info.achievement
+FROM user_info u_info
+LEFT JOIN exam_record record ON record.uid = u_info.uid
+LEFT JOIN practice_record pr ON u_info.uid = pr.uid
+WHERE u_info.nick_name LIKE "牛客%号"
+ AND u_info.achievement BETWEEN 1200
+ AND 2500
+ AND (date_format(record.submit_time, '%Y%m')= 202109
+ OR date_format(pr.submit_time, '%Y%m')= 202109)
+GROUP BY u_info.uid
+```
+
+### 筛选昵称规则和试卷规则的作答记录(较难)
+
+**描述**:
+
+现有用户信息表 `user_info`(`uid` 用户 ID,`nick_name` 昵称, `achievement` 成就值, `level` 等级, `job` 职业方向, `register_time` 注册时间):
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | ----------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 号 | 1900 | 2 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 1200 | 3 | 算法 | 2020-01-01 10:00:00 |
+| 3 | 1003 | 牛客 3 号 ♂ | 2200 | 5 | 算法 | 2020-01-01 10:00:00 |
+| 4 | 1004 | 牛客 4 号 | 2500 | 6 | 算法 | 2020-01-01 10:00:00 |
+| 5 | 1005 | 牛客 555 号 | 2000 | 7 | C++ | 2020-01-01 10:00:00 |
+| 6 | 1006 | 666666 | 3000 | 6 | C++ | 2020-01-01 10:00:00 |
+
+试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | C++ | hard | 60 | 2020-01-01 10:00:00 |
+| 2 | 9002 | c# | hard | 80 | 2020-01-01 10:00:00 |
+| 3 | 9003 | SQL | medium | 70 | 2020-01-01 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2020-01-02 09:01:01 | 2020-01-02 09:21:59 | 80 |
+| 2 | 1001 | 9001 | 2021-05-02 10:01:01 | (NULL) | (NULL) |
+| 4 | 1001 | 9001 | 2021-06-02 19:01:01 | 2021-06-02 19:32:00 | 20 |
+| 3 | 1001 | 9002 | 2021-02-02 19:01:01 | 2021-02-02 19:30:01 | 87 |
+| 5 | 1001 | 9002 | 2021-09-05 19:01:01 | 2021-09-05 19:40:01 | 89 |
+| 6 | 1001 | 9002 | 2021-09-01 12:01:01 | (NULL) | (NULL) |
+| 11 | 1002 | 9001 | 2020-01-01 12:01:01 | 2020-01-01 12:31:01 | 81 |
+| 16 | 1002 | 9001 | 2021-09-06 12:01:01 | 2021-09-06 12:21:01 | 80 |
+| 17 | 1002 | 9001 | 2021-09-06 12:01:01 | (NULL) | (NULL) |
+| 18 | 1002 | 9001 | 2021-09-07 12:01:01 | (NULL) | (NULL) |
+| 7 | 1002 | 9002 | 2021-05-05 18:01:01 | 2021-05-05 18:59:02 | 90 |
+| 12 | 1002 | 9002 | 2020-02-01 12:01:01 | 2020-02-01 12:31:01 | 82 |
+| 13 | 1002 | 9002 | 2020-02-02 12:11:01 | 2020-02-02 12:31:01 | 83 |
+| 9 | 1003 | 9001 | 2021-09-07 10:01:01 | 2021-09-07 10:31:01 | 89 |
+| 8 | 1003 | 9003 | 2021-02-06 12:01:01 | (NULL) | (NULL) |
+| 10 | 1004 | 9002 | 2021-08-06 12:01:01 | (NULL) | (NULL) |
+| 14 | 1005 | 9001 | 2021-02-01 11:01:01 | 2021-02-01 11:31:01 | 84 |
+| 15 | 1006 | 9001 | 2021-02-01 11:01:01 | 2021-09-01 11:31:01 | 84 |
+
+找到昵称以"牛客"+纯数字+"号"或者纯数字组成的用户对于字母 c 开头的试卷类别(如 C,C++,c#等)的已完成的试卷 ID 和平均得分,按用户 ID、平均分升序排序。由示例数据结果输出如下:
+
+| uid | exam_id | avg_score |
+| ---- | ------- | --------- |
+| 1002 | 9001 | 81 |
+| 1002 | 9002 | 85 |
+| 1005 | 9001 | 84 |
+| 1006 | 9001 | 84 |
+
+解释:昵称满足条件的用户有 1002、1004、1005、1006;
+
+c 开头的试卷有 9001、9002;
+
+满足上述条件的作答记录中,1002 完成 9001 的得分有 81、80,平均分为 81(80.5 取整四舍五入得 81);
+
+1002 完成 9002 的得分有 90、82、83,平均分为 85;
+
+**思路**:
+
+还是老样子,既然给出了条件,就先把各个条件先写出来
+
+找到昵称以"牛客"+纯数字+"号"或者纯数字组成的用户: 我最开始是这么写的:`nick_name LIKE '牛客%号' OR nick_name REGEXP '^[0-9]+$'`,如果表中有个 “牛客 H 号” ,那也能通过。
+
+所以这里还得用正则: `nick_name LIKE '^牛客[0-9]+号'`
+
+对于字母 c 开头的试卷类别: `e_info.tag LIKE 'c%'` 或者 `tag regexp '^c|^C'` 第一个也能匹配到大写 C
+
+**答案**:
+
+```sql
+SELECT UID,
+ exam_id,
+ ROUND(AVG(score), 0) avg_score
+FROM exam_record
+WHERE UID IN
+ (SELECT UID
+ FROM user_info
+ WHERE nick_name RLIKE "^牛客[0-9]+号 $"
+ OR nick_name RLIKE "^[0-9]+$")
+ AND exam_id IN
+ (SELECT exam_id
+ FROM examination_info
+ WHERE tag RLIKE "^[cC]")
+ AND score IS NOT NULL
+GROUP BY UID,exam_id
+ORDER BY UID,avg_score;
+```
+
+### 根据指定记录是否存在输出不同情况(困难)
+
+**描述**:
+
+现有用户信息表 `user_info`(`uid` 用户 ID,`nick_name` 昵称, `achievement` 成就值, `level` 等级, `job` 职业方向, `register_time` 注册时间):
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | ----------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 号 | 19 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 1200 | 3 | 算法 | 2020-01-01 10:00:00 |
+| 3 | 1003 | 进击的 3 号 | 22 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 4 | 1004 | 牛客 4 号 | 25 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 5 | 1005 | 牛客 555 号 | 2000 | 7 | C++ | 2020-01-01 10:00:00 |
+| 6 | 1006 | 666666 | 3000 | 6 | C++ | 2020-01-01 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2020-01-02 09:01:01 | 2020-01-02 09:21:59 | 80 |
+| 2 | 1001 | 9001 | 2021-05-02 10:01:01 | (NULL) | (NULL) |
+| 3 | 1001 | 9002 | 2021-02-02 19:01:01 | 2021-02-02 19:30:01 | 87 |
+| 4 | 1001 | 9002 | 2021-09-01 12:01:01 | (NULL) | (NULL) |
+| 5 | 1001 | 9003 | 2021-09-02 12:01:01 | (NULL) | (NULL) |
+| 6 | 1001 | 9004 | 2021-09-03 12:01:01 | (NULL) | (NULL) |
+| 7 | 1002 | 9001 | 2020-01-01 12:01:01 | 2020-01-01 12:31:01 | 99 |
+| 8 | 1002 | 9003 | 2020-02-01 12:01:01 | 2020-02-01 12:31:01 | 82 |
+| 9 | 1002 | 9003 | 2020-02-02 12:11:01 | (NULL) | (NULL) |
+| 10 | 1002 | 9002 | 2021-05-05 18:01:01 | (NULL) | (NULL) |
+| 11 | 1002 | 9001 | 2021-09-06 12:01:01 | (NULL) | (NULL) |
+| 12 | 1003 | 9003 | 2021-02-06 12:01:01 | (NULL) | (NULL) |
+| 13 | 1003 | 9001 | 2021-09-07 10:01:01 | 2021-09-07 10:31:01 | 89 |
+
+请你筛选表中的数据,当有任意一个 0 级用户未完成试卷数大于 2 时,输出每个 0 级用户的试卷未完成数和未完成率(保留 3 位小数);若不存在这样的用户,则输出所有有作答记录的用户的这两个指标。结果按未完成率升序排序。
+
+由示例数据结果输出如下:
+
+| uid | incomplete_cnt | incomplete_rate |
+| ---- | -------------- | --------------- |
+| 1004 | 0 | 0.000 |
+| 1003 | 1 | 0.500 |
+| 1001 | 4 | 0.667 |
+
+**解释**:0 级用户有 1001、1003、1004;他们作答试卷数和未完成数分别为:6:4、2:1、0:0;
+
+存在 1001 这个 0 级用户未完成试卷数大于 2,因此输出这三个用户的未完成数和未完成率(1004 未作答过试卷,未完成率默认填 0,保留 3 位小数后是 0.000);
+
+结果按照未完成率升序排序。
+
+附:如果 1001 不满足『未完成试卷数大于 2』,则需要输出 1001、1002、1003 的这两个指标,因为试卷作答记录表里只有这三个用户的作答记录。
+
+**思路**:
+
+先把可能满足条件**“0 级用户未完成试卷数大于 2”**的 SQL 写出来
+
+```sql
+SELECT ui.uid UID
+FROM user_info ui
+LEFT JOIN exam_record er ON ui.uid = er.uid
+WHERE ui.uid IN
+ (SELECT ui.uid
+ FROM user_info ui
+ LEFT JOIN exam_record er ON ui.uid = er.uid
+ WHERE er.submit_time IS NULL
+ AND ui.LEVEL = 0 )
+GROUP BY ui.uid
+HAVING sum(IF(er.submit_time IS NULL, 1, 0)) > 2
+```
+
+然后再分别写出两种情况的 SQL 查询语句:
+
+情况 1. 查询存在条件要求的 0 级用户的试卷未完成率
+
+```sql
+SELECT
+ tmp1.uid uid,
+ sum(
+ IF
+ ( er.submit_time IS NULL AND er.start_time IS NOT NULL, 1, 0 )) incomplete_cnt,
+ round(
+ sum(
+ IF
+ ( er.submit_time IS NULL AND er.start_time IS NOT NULL, 1, 0 ))/ count( tmp1.uid ),
+ 3
+ ) incomplete_rate
+FROM
+ (
+ SELECT DISTINCT
+ ui.uid
+ FROM
+ user_info ui
+ LEFT JOIN exam_record er ON ui.uid = er.uid
+ WHERE
+ er.submit_time IS NULL
+ AND ui.LEVEL = 0
+ ) tmp1
+ LEFT JOIN exam_record er ON tmp1.uid = er.uid
+GROUP BY
+ tmp1.uid
+ORDER BY
+ incomplete_rate
+```
+
+情况 2. 查询不存在条件要求时所有有作答记录的 yong 用户的试卷未完成率
+
+```sql
+SELECT
+ ui.uid uid,
+ sum( CASE WHEN er.submit_time IS NULL AND er.start_time IS NOT NULL THEN 1 ELSE 0 END ) incomplete_cnt,
+ round(
+ sum(
+ IF
+ ( er.submit_time IS NULL AND er.start_time IS NOT NULL, 1, 0 ))/ count( ui.uid ),
+ 3
+ ) incomplete_rate
+FROM
+ user_info ui
+ JOIN exam_record er ON ui.uid = er.uid
+GROUP BY
+ ui.uid
+ORDER BY
+ incomplete_rate
+```
+
+拼在一起,就是答案
+
+```sql
+WITH host_user AS
+ (SELECT ui.uid UID
+ FROM user_info ui
+ LEFT JOIN exam_record er ON ui.uid = er.uid
+ WHERE ui.uid IN
+ (SELECT ui.uid
+ FROM user_info ui
+ LEFT JOIN exam_record er ON ui.uid = er.uid
+ WHERE er.submit_time IS NULL
+ AND ui.LEVEL = 0 )
+ GROUP BY ui.uid
+ HAVING sum(IF (er.submit_time IS NULL, 1, 0))> 2),
+ tt1 AS
+ (SELECT tmp1.uid UID,
+ sum(IF (er.submit_time IS NULL
+ AND er.start_time IS NOT NULL, 1, 0)) incomplete_cnt,
+ round(sum(IF (er.submit_time IS NULL
+ AND er.start_time IS NOT NULL, 1, 0))/ count(tmp1.uid), 3) incomplete_rate
+ FROM
+ (SELECT DISTINCT ui.uid
+ FROM user_info ui
+ LEFT JOIN exam_record er ON ui.uid = er.uid
+ WHERE er.submit_time IS NULL
+ AND ui.LEVEL = 0 ) tmp1
+ LEFT JOIN exam_record er ON tmp1.uid = er.uid
+ GROUP BY tmp1.uid
+ ORDER BY incomplete_rate),
+ tt2 AS
+ (SELECT ui.uid UID,
+ sum(CASE
+ WHEN er.submit_time IS NULL
+ AND er.start_time IS NOT NULL THEN 1
+ ELSE 0
+ END) incomplete_cnt,
+ round(sum(IF (er.submit_time IS NULL
+ AND er.start_time IS NOT NULL, 1, 0))/ count(ui.uid), 3) incomplete_rate
+ FROM user_info ui
+ JOIN exam_record er ON ui.uid = er.uid
+ GROUP BY ui.uid
+ ORDER BY incomplete_rate)
+ (SELECT tt1.*
+ FROM tt1
+ LEFT JOIN
+ (SELECT UID
+ FROM host_user) t1 ON 1 = 1
+ WHERE t1.uid IS NOT NULL )
+UNION ALL
+ (SELECT tt2.*
+ FROM tt2
+ LEFT JOIN
+ (SELECT UID
+ FROM host_user) t2 ON 1 = 1
+ WHERE t2.uid IS NULL)
+```
+
+V2 版本(根据上面做出的改进,答案缩短了,逻辑更强):
+
+```sql
+SELECT
+ ui.uid,
+ SUM(
+ IF
+ ( start_time IS NOT NULL AND score IS NULL, 1, 0 )) AS incomplete_cnt,#3.试卷未完成数
+ ROUND( AVG( IF ( start_time IS NOT NULL AND score IS NULL, 1, 0 )), 3 ) AS incomplete_rate #4.未完成率
+
+FROM
+ user_info ui
+ LEFT JOIN exam_record USING ( uid )
+WHERE
+CASE
+
+ WHEN (#1.当有任意一个0级用户未完成试卷数大于2时
+ SELECT
+ MAX( lv0_incom_cnt )
+ FROM
+ (
+ SELECT
+ SUM(
+ IF
+ ( score IS NULL, 1, 0 )) AS lv0_incom_cnt
+ FROM
+ user_info
+ JOIN exam_record USING ( uid )
+ WHERE
+ LEVEL = 0
+ GROUP BY
+ uid
+ ) table1
+ )> 2 THEN
+ uid IN ( #1.1找出每个0级用户
+ SELECT uid FROM user_info WHERE LEVEL = 0 ) ELSE uid IN ( #2.若不存在这样的用户,找出有作答记录的用户
+ SELECT DISTINCT uid FROM exam_record )
+ END
+ GROUP BY
+ ui.uid
+ ORDER BY
+ incomplete_rate #5.结果按未完成率升序排序
+```
+
+### 各用户等级的不同得分表现占比(较难)
+
+**描述**:
+
+现有用户信息表 `user_info`(`uid` 用户 ID,`nick_name` 昵称, `achievement` 成就值, `level` 等级, `job` 职业方向, `register_time` 注册时间):
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | ----------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 号 | 19 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 1200 | 3 | 算法 | 2020-01-01 10:00:00 |
+| 3 | 1003 | 牛客 3 号 ♂ | 22 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 4 | 1004 | 牛客 4 号 | 25 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 5 | 1005 | 牛客 555 号 | 2000 | 7 | C++ | 2020-01-01 10:00:00 |
+| 6 | 1006 | 666666 | 3000 | 6 | C++ | 2020-01-01 10:00:00 |
+
+试卷作答记录表 exam_record(uid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2020-01-02 09:01:01 | 2020-01-02 09:21:59 | 80 |
+| 2 | 1001 | 9001 | 2021-05-02 10:01:01 | (NULL) | (NULL) |
+| 3 | 1001 | 9002 | 2021-02-02 19:01:01 | 2021-02-02 19:30:01 | 75 |
+| 4 | 1001 | 9002 | 2021-09-01 12:01:01 | 2021-09-01 12:11:01 | 60 |
+| 5 | 1001 | 9003 | 2021-09-02 12:01:01 | 2021-09-02 12:41:01 | 90 |
+| 6 | 1001 | 9001 | 2021-06-02 19:01:01 | 2021-06-02 19:32:00 | 20 |
+| 7 | 1001 | 9002 | 2021-09-05 19:01:01 | 2021-09-05 19:40:01 | 89 |
+| 8 | 1001 | 9004 | 2021-09-03 12:01:01 | (NULL) | (NULL) |
+| 9 | 1002 | 9001 | 2020-01-01 12:01:01 | 2020-01-01 12:31:01 | 99 |
+| 10 | 1002 | 9003 | 2020-02-01 12:01:01 | 2020-02-01 12:31:01 | 82 |
+| 11 | 1002 | 9003 | 2020-02-02 12:11:01 | 2020-02-02 12:41:01 | 76 |
+
+为了得到用户试卷作答的定性表现,我们将试卷得分按分界点[90,75,60]分为优良中差四个得分等级(分界点划分到左区间),请统计不同用户等级的人在完成过的试卷中各得分等级占比(结果保留 3 位小数),未完成过试卷的用户无需输出,结果按用户等级降序、占比降序排序。
+
+由示例数据结果输出如下:
+
+| level | score_grade | ratio |
+| ----- | ----------- | ----- |
+| 3 | 良 | 0.667 |
+| 3 | 优 | 0.333 |
+| 0 | 良 | 0.500 |
+| 0 | 中 | 0.167 |
+| 0 | 优 | 0.167 |
+| 0 | 差 | 0.167 |
+
+解释:完成过试卷的用户有 1001、1002;完成了的试卷对应的用户等级和分数等级如下:
+
+| uid | exam_id | score | level | score_grade |
+| ---- | ------- | ----- | ----- | ----------- |
+| 1001 | 9001 | 80 | 0 | 良 |
+| 1001 | 9002 | 75 | 0 | 良 |
+| 1001 | 9002 | 60 | 0 | 中 |
+| 1001 | 9003 | 90 | 0 | 优 |
+| 1001 | 9001 | 20 | 0 | 差 |
+| 1001 | 9002 | 89 | 0 | 良 |
+| 1002 | 9001 | 99 | 3 | 优 |
+| 1002 | 9003 | 82 | 3 | 良 |
+| 1002 | 9003 | 76 | 3 | 良 |
+
+因此 0 级用户(只有 1001)的各分数等级比例为:优 1/6,良 1/6,中 1/6,差 3/6;3 级用户(只有 1002)各分数等级比例为:优 1/3,良 2/3。结果保留 3 位小数。
+
+**思路**:
+
+先把 **“将试卷得分按分界点[90,75,60]分为优良中差四个得分等级”**这个条件写出来,这里可以用到`case when`
+
+```sql
+CASE
+ WHEN a.score >= 90 THEN
+ '优'
+ WHEN a.score < 90 AND a.score >= 75 THEN
+ '良'
+ WHEN a.score < 75 AND a.score >= 60 THEN
+ '中' ELSE '差'
+END
+```
+
+这题的关键点就在于这,其他剩下的就是条件拼接了
+
+**答案**:
+
+```sql
+SELECT a.LEVEL,
+ a.score_grade,
+ ROUND(a.cur_count / b.total_num, 3) AS ratio
+FROM
+ (SELECT b.LEVEL AS LEVEL,
+ (CASE
+ WHEN a.score >= 90 THEN '优'
+ WHEN a.score < 90
+ AND a.score >= 75 THEN '良'
+ WHEN a.score < 75
+ AND a.score >= 60 THEN '中'
+ ELSE '差'
+ END) AS score_grade,
+ count(1) AS cur_count
+ FROM exam_record a
+ LEFT JOIN user_info b ON a.uid = b.uid
+ WHERE a.submit_time IS NOT NULL
+ GROUP BY b.LEVEL,
+ score_grade) a
+LEFT JOIN
+ (SELECT b.LEVEL AS LEVEL,
+ count(b.LEVEL) AS total_num
+ FROM exam_record a
+ LEFT JOIN user_info b ON a.uid = b.uid
+ WHERE a.submit_time IS NOT NULL
+ GROUP BY b.LEVEL) b ON a.LEVEL = b.LEVEL
+ORDER BY a.LEVEL DESC,
+ ratio DESC
+```
+
+## 限量查询
+
+### 注册时间最早的三个人
+
+**描述**:
+
+现有用户信息表 `user_info`(`uid` 用户 ID,`nick_name` 昵称, `achievement` 成就值, `level` 等级, `job` 职业方向, `register_time` 注册时间):
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | ----------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 号 | 19 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 1200 | 3 | 算法 | 2020-02-01 10:00:00 |
+| 3 | 1003 | 牛客 3 号 ♂ | 22 | 0 | 算法 | 2020-01-02 10:00:00 |
+| 4 | 1004 | 牛客 4 号 | 25 | 0 | 算法 | 2020-01-02 11:00:00 |
+| 5 | 1005 | 牛客 555 号 | 4000 | 7 | C++ | 2020-01-11 10:00:00 |
+| 6 | 1006 | 666666 | 3000 | 6 | C++ | 2020-11-01 10:00:00 |
+
+请从中找到注册时间最早的 3 个人。由示例数据结果输出如下:
+
+| uid | nick_name | register_time |
+| ---- | ----------- | ------------------- |
+| 1001 | 牛客 1 | 2020-01-01 10:00:00 |
+| 1003 | 牛客 3 号 ♂ | 2020-01-02 10:00:00 |
+| 1004 | 牛客 4 号 | 2020-01-02 11:00:00 |
+
+解释:按注册时间排序后选取前三名,输出其用户 ID、昵称、注册时间。
+
+**答案**:
+
+```sql
+SELECT uid, nick_name, register_time
+ FROM user_info
+ ORDER BY register_time
+ LIMIT 3
+```
+
+### 注册当天就完成了试卷的名单第三页(较难)
+
+**描述**:现有用户信息表 `user_info`(`uid` 用户 ID,`nick_name` 昵称, `achievement` 成就值, `level` 等级, `job` 职业方向, `register_time` 注册时间):
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | ----------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 | 19 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 1200 | 3 | 算法 | 2020-01-01 10:00:00 |
+| 3 | 1003 | 牛客 3 号 ♂ | 22 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 4 | 1004 | 牛客 4 号 | 25 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 5 | 1005 | 牛客 555 号 | 4000 | 7 | 算法 | 2020-01-11 10:00:00 |
+| 6 | 1006 | 牛客 6 号 | 25 | 0 | 算法 | 2020-01-02 11:00:00 |
+| 7 | 1007 | 牛客 7 号 | 25 | 0 | 算法 | 2020-01-02 11:00:00 |
+| 8 | 1008 | 牛客 8 号 | 25 | 0 | 算法 | 2020-01-02 11:00:00 |
+| 9 | 1009 | 牛客 9 号 | 25 | 0 | 算法 | 2020-01-02 11:00:00 |
+| 10 | 1010 | 牛客 10 号 | 25 | 0 | 算法 | 2020-01-02 11:00:00 |
+| 11 | 1011 | 666666 | 3000 | 6 | C++ | 2020-01-02 10:00:00 |
+
+试卷信息表 examination_info(exam_id 试卷 ID, tag 试卷类别, difficulty 试卷难度, duration 考试时长, release_time 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | 算法 | hard | 60 | 2020-01-01 10:00:00 |
+| 2 | 9002 | 算法 | hard | 80 | 2020-01-01 10:00:00 |
+| 3 | 9003 | SQL | medium | 70 | 2020-01-01 10:00:00 |
+
+试卷作答记录表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ----- |
+| 1 | 1001 | 9001 | 2020-01-02 09:01:01 | 2020-01-02 09:21:59 | 80 |
+| 2 | 1002 | 9003 | 2020-01-20 10:01:01 | 2020-01-20 10:10:01 | 81 |
+| 3 | 1002 | 9002 | 2020-01-01 12:11:01 | 2020-01-01 12:31:01 | 83 |
+| 4 | 1003 | 9002 | 2020-01-01 19:01:01 | 2020-01-01 19:30:01 | 75 |
+| 5 | 1004 | 9002 | 2020-01-01 12:01:01 | 2020-01-01 12:11:01 | 60 |
+| 6 | 1005 | 9002 | 2020-01-01 12:01:01 | 2020-01-01 12:41:01 | 90 |
+| 7 | 1006 | 9001 | 2020-01-02 19:01:01 | 2020-01-02 19:32:00 | 20 |
+| 8 | 1007 | 9002 | 2020-01-02 19:01:01 | 2020-01-02 19:40:01 | 89 |
+| 9 | 1008 | 9003 | 2020-01-02 12:01:01 | 2020-01-02 12:20:01 | 99 |
+| 10 | 1008 | 9001 | 2020-01-02 12:01:01 | 2020-01-02 12:31:01 | 98 |
+| 11 | 1009 | 9002 | 2020-01-02 12:01:01 | 2020-01-02 12:31:01 | 82 |
+| 12 | 1010 | 9002 | 2020-01-02 12:11:01 | 2020-01-02 12:41:01 | 76 |
+| 13 | 1011 | 9001 | 2020-01-02 10:01:01 | 2020-01-02 10:31:01 | 89 |
+
+
+
+找到求职方向为算法工程师,且注册当天就完成了算法类试卷的人,按参加过的所有考试最高得分排名。排名榜很长,我们将采用分页展示,每页 3 条,现在需要你取出第 3 页(页码从 1 开始)的人的信息。
+
+由示例数据结果输出如下:
+
+| uid | level | register_time | max_score |
+| ---- | ----- | ------------------- | --------- |
+| 1010 | 0 | 2020-01-02 11:00:00 | 76 |
+| 1003 | 0 | 2020-01-01 10:00:00 | 75 |
+| 1004 | 0 | 2020-01-01 11:00:00 | 60 |
+
+解释:除了 1011 其他用户的求职方向都为算法工程师;算法类试卷有 9001 和 9002,11 个用户注册当天都完成了算法类试卷;计算他们的所有考试最大分时,只有 1002 和 1008 完成了两次考试,其他人只完成了一场考试,1002 两场考试最高分为 81,1008 最高分为 99。
+
+按最高分排名如下:
+
+| uid | level | register_time | max_score |
+| ---- | ----- | ------------------- | --------- |
+| 1008 | 0 | 2020-01-02 11:00:00 | 99 |
+| 1005 | 7 | 2020-01-01 10:00:00 | 90 |
+| 1007 | 0 | 2020-01-02 11:00:00 | 89 |
+| 1002 | 3 | 2020-01-01 10:00:00 | 83 |
+| 1009 | 0 | 2020-01-02 11:00:00 | 82 |
+| 1001 | 0 | 2020-01-01 10:00:00 | 80 |
+| 1010 | 0 | 2020-01-02 11:00:00 | 76 |
+| 1003 | 0 | 2020-01-01 10:00:00 | 75 |
+| 1004 | 0 | 2020-01-01 11:00:00 | 60 |
+| 1006 | 0 | 2020-01-02 11:00:00 | 20 |
+
+每页 3 条,第三页也就是第 7~9 条,返回 1010、1003、1004 的行记录即可。
+
+**思路**:
+
+1. 每页三条,即需要取出第三页的人的信息,要用到`limit`
+
+2. 统计求职方向为算法工程师且注册当天就完成了算法类试卷的人的**信息和每次记录的得分**,先求满足条件的用户,后用 left join 做连接查找信息和每次记录的得分
+
+**答案**:
+
+```sql
+SELECT t1.uid,
+ LEVEL,
+ register_time,
+ max(score) AS max_score
+FROM exam_record t
+JOIN examination_info USING (exam_id)
+JOIN user_info t1 ON t.uid = t1.uid
+AND date(t.submit_time) = date(t1.register_time)
+WHERE job = '算法'
+ AND tag = '算法'
+GROUP BY t1.uid,
+ LEVEL,
+ register_time
+ORDER BY max_score DESC
+LIMIT 6,3
+```
+
+## 文本转换函数
+
+### 修复串列了的记录
+
+**描述**:现有试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | -------------- | ---------- | -------- | ------------------- |
+| 1 | 9001 | 算法 | hard | 60 | 2021-01-01 10:00:00 |
+| 2 | 9002 | 算法 | hard | 80 | 2021-01-01 10:00:00 |
+| 3 | 9003 | SQL | medium | 70 | 2021-01-01 10:00:00 |
+| 4 | 9004 | 算法,medium,80 | | 0 | 2021-01-01 10:00:00 |
+
+录题同学有一次手误将部分记录的试题类别 tag、难度、时长同时录入到了 tag 字段,请帮忙找出这些录错了的记录,并拆分后按正确的列类型输出。
+
+由示例数据结果输出如下:
+
+| exam_id | tag | difficulty | duration |
+| ------- | ---- | ---------- | -------- |
+| 9004 | 算法 | medium | 80 |
+
+**思路**:
+
+先来学习下本题要用到的函数
+
+`SUBSTRING_INDEX` 函数用于提取字符串中指定分隔符的部分。它接受三个参数:原始字符串、分隔符和指定要返回的部分的数量。
+
+以下是 `SUBSTRING_INDEX` 函数的语法:
+
+```sql
+SUBSTRING_INDEX(str, delimiter, count)
+```
+
+- `str`:要进行分割的原始字符串。
+- `delimiter`:用作分割的字符串或字符。
+- `count`:指定要返回的部分的数量。
+ - 如果 `count` 大于 0,则返回从左边开始的前 `count` 个部分(以分隔符为界)。
+ - 如果 `count` 小于 0,则返回从右边开始的前 `count` 个部分(以分隔符为界),即从右侧向左计数。
+
+下面是一些示例,演示了 `SUBSTRING_INDEX` 函数的使用:
+
+1. 提取字符串中的第一个部分:
+
+ ```sql
+ SELECT SUBSTRING_INDEX('apple,banana,cherry', ',', 1);
+ -- 输出结果:'apple'
+ ```
+
+2. 提取字符串中的最后一个部分:
+
+ ```sql
+ SELECT SUBSTRING_INDEX('apple,banana,cherry', ',', -1);
+ -- 输出结果:'cherry'
+ ```
+
+3. 提取字符串中的前两个部分:
+
+ ```sql
+ SELECT SUBSTRING_INDEX('apple,banana,cherry', ',', 2);
+ -- 输出结果:'apple,banana'
+ ```
+
+4. 提取字符串中的最后两个部分:
+
+ ```sql
+ SELECT SUBSTRING_INDEX('apple,banana,cherry', ',', -2);
+ -- 输出结果:'banana,cherry'
+ ```
+
+**答案**:
+
+```sql
+SELECT
+ exam_id,
+ substring_index( tag, ',', 1 ) tag,
+ substring_index( substring_index( tag, ',', 2 ), ',',- 1 ) difficulty,
+ substring_index( tag, ',',- 1 ) duration
+FROM
+ examination_info
+WHERE
+ difficulty = ''
+```
+
+### 对过长的昵称截取处理
+
+**描述**:现有用户信息表 `user_info`(`uid` 用户 ID,`nick_name` 昵称, `achievement` 成就值, `level` 等级, `job` 职业方向, `register_time` 注册时间):
+
+| id | uid | nick_name | achievement | level | job | register_time |
+| ---- | ---- | ---------------------- | ----------- | ----- | ---- | ------------------- |
+| 1 | 1001 | 牛客 1 | 19 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 2 | 1002 | 牛客 2 号 | 1200 | 3 | 算法 | 2020-01-01 10:00:00 |
+| 3 | 1003 | 牛客 3 号 ♂ | 22 | 0 | 算法 | 2020-01-01 10:00:00 |
+| 4 | 1004 | 牛客 4 号 | 25 | 0 | 算法 | 2020-01-01 11:00:00 |
+| 5 | 1005 | 牛客 5678901234 号 | 4000 | 7 | 算法 | 2020-01-11 10:00:00 |
+| 6 | 1006 | 牛客 67890123456789 号 | 25 | 0 | 算法 | 2020-01-02 11:00:00 |
+
+有的用户的昵称特别长,在一些展示场景会导致样式混乱,因此需要将特别长的昵称转换一下再输出,请输出字符数大于 10 的用户信息,对于字符数大于 13 的用户输出前 10 个字符然后加上三个点号:『...』。
+
+由示例数据结果输出如下:
+
+| uid | nick_name |
+| ---- | ------------------ |
+| 1005 | 牛客 5678901234 号 |
+| 1006 | 牛客 67890123... |
+
+解释:字符数大于 10 的用户有 1005 和 1006,长度分别为 13、17;因此需要对 1006 的昵称截断输出。
+
+**思路**:
+
+这题涉及到字符的计算,要计算字符串的字符数(即字符串的长度),可以使用 `LENGTH` 函数或 `CHAR_LENGTH` 函数。这两个函数的区别在于对待多字节字符的方式。
+
+1. `LENGTH` 函数:它返回给定字符串的字节数。对于包含多字节字符的字符串,每个字符都会被当作一个字节来计算。
+
+示例:
+
+```sql
+SELECT LENGTH('你好'); -- 输出结果:6,因为 '你好' 中的每个汉字每个占3个字节
+```
+
+1. `CHAR_LENGTH` 函数:它返回给定字符串的字符数。对于包含多字节字符的字符串,每个字符会被当作一个字符来计算。
+
+示例:
+
+```sql
+SELECT CHAR_LENGTH('你好'); -- 输出结果:2,因为 '你好' 中有两个字符,即两个汉字
+```
+
+**答案**:
+
+```sql
+SELECT
+ uid,
+CASE
+
+ WHEN CHAR_LENGTH( nick_name ) > 13 THEN
+ CONCAT( SUBSTR( nick_name, 1, 10 ), '...' ) ELSE nick_name
+ END AS nick_name
+FROM
+ user_info
+WHERE
+ CHAR_LENGTH( nick_name ) > 10
+GROUP BY
+ uid;
+```
+
+### 大小写混乱时的筛选统计(较难)
+
+**描述**:
+
+现有试卷信息表 `examination_info`(`exam_id` 试卷 ID, `tag` 试卷类别, `difficulty` 试卷难度, `duration` 考试时长, `release_time` 发布时间):
+
+| id | exam_id | tag | difficulty | duration | release_time |
+| ---- | ------- | ---- | ---------- | -------- | ------------------- |
+| 1 | 9001 | 算法 | hard | 60 | 2021-01-01 10:00:00 |
+| 2 | 9002 | C++ | hard | 80 | 2021-01-01 10:00:00 |
+| 3 | 9003 | C++ | hard | 80 | 2021-01-01 10:00:00 |
+| 4 | 9004 | sql | medium | 70 | 2021-01-01 10:00:00 |
+| 5 | 9005 | C++ | hard | 80 | 2021-01-01 10:00:00 |
+| 6 | 9006 | C++ | hard | 80 | 2021-01-01 10:00:00 |
+| 7 | 9007 | C++ | hard | 80 | 2021-01-01 10:00:00 |
+| 8 | 9008 | SQL | medium | 70 | 2021-01-01 10:00:00 |
+| 9 | 9009 | SQL | medium | 70 | 2021-01-01 10:00:00 |
+| 10 | 9010 | SQL | medium | 70 | 2021-01-01 10:00:00 |
+
+试卷作答信息表 `exam_record`(`uid` 用户 ID, `exam_id` 试卷 ID, `start_time` 开始作答时间, `submit_time` 交卷时间, `score` 得分):
+
+| id | uid | exam_id | start_time | submit_time | score |
+| ---- | ---- | ------- | ------------------- | ------------------- | ------ |
+| 1 | 1001 | 9001 | 2020-01-01 09:01:01 | 2020-01-01 09:21:59 | 80 |
+| 2 | 1002 | 9003 | 2020-01-20 10:01:01 | 2020-01-20 10:10:01 | 81 |
+| 3 | 1002 | 9002 | 2020-02-01 12:11:01 | 2020-02-01 12:31:01 | 83 |
+| 4 | 1003 | 9002 | 2020-03-01 19:01:01 | 2020-03-01 19:30:01 | 75 |
+| 5 | 1004 | 9002 | 2020-03-01 12:01:01 | 2020-03-01 12:11:01 | 60 |
+| 6 | 1005 | 9002 | 2020-03-01 12:01:01 | 2020-03-01 12:41:01 | 90 |
+| 7 | 1006 | 9001 | 2020-05-02 19:01:01 | 2020-05-02 19:32:00 | 20 |
+| 8 | 1007 | 9003 | 2020-01-02 19:01:01 | 2020-01-02 19:40:01 | 89 |
+| 9 | 1008 | 9004 | 2020-02-02 12:01:01 | 2020-02-02 12:20:01 | 99 |
+| 10 | 1008 | 9001 | 2020-02-02 12:01:01 | 2020-02-02 12:31:01 | 98 |
+| 11 | 1009 | 9002 | 2020-02-02 12:01:01 | 2020-01-02 12:43:01 | 81 |
+| 12 | 1010 | 9001 | 2020-01-02 12:11:01 | (NULL) | (NULL) |
+| 13 | 1010 | 9001 | 2020-02-02 12:01:01 | 2020-01-02 10:31:01 | 89 |
+
+试卷的类别 tag 可能出现大小写混乱的情况,请先筛选出试卷作答数小于 3 的类别 tag,统计将其转换为大写后对应的原本试卷作答数。
+
+如果转换后 tag 并没有发生变化,不输出该条结果。
+
+由示例数据结果输出如下:
+
+| tag | answer_cnt |
+| ---- | ---------- |
+| C++ | 6 |
+
+解释:被作答过的试卷有 9001、9002、9003、9004,他们的 tag 和被作答次数如下:
+
+| exam_id | tag | answer_cnt |
+| ------- | ---- | ---------- |
+| 9001 | 算法 | 4 |
+| 9002 | C++ | 6 |
+| 9003 | c++ | 2 |
+| 9004 | sql | 2 |
+
+作答次数小于 3 的 tag 有 c++和 sql,而转为大写后只有 C++本来就有作答数,于是输出 c++转化大写后的作答次数为 6。
+
+**思路**:
+
+首先,这题有点混乱,9004 根据示例数据查出来只有 1 次,这里显示有 2 次。
+
+先看一下大小写转换函数:
+
+1.`UPPER(s)`或`UCASE(s)`函数可以将字符串 s 中的字母字符全部转换成大写字母;
+
+2.`LOWER(s)`或者`LCASE(s)`函数可以将字符串 s 中的字母字符全部转换成小写字母。
+
+难点在于相同表做连接要查询不同的值
+
+**答案**:
+
+```sql
+WITH a AS
+ (SELECT tag,
+ COUNT(start_time) AS answer_cnt
+ FROM exam_record er
+ JOIN examination_info ei ON er.exam_id = ei.exam_id
+ GROUP BY tag)
+SELECT a.tag,
+ b.answer_cnt
+FROM a
+INNER JOIN a AS b ON UPPER(a.tag)= b.tag #a小写 b大写
+AND a.tag != b.tag
+WHERE a.answer_cnt < 3;
+```
diff --git a/docs/database/sql/sql-syntax-summary.md b/docs/database/sql/sql-syntax-summary.md
new file mode 100644
index 0000000000000000000000000000000000000000..12b4efde677bfc3a3e2962c7cad25683a97b32ed
--- /dev/null
+++ b/docs/database/sql/sql-syntax-summary.md
@@ -0,0 +1,1210 @@
+---
+title: SQL语法基础知识总结
+category: 数据库
+tag:
+ - 数据库基础
+ - SQL
+---
+
+> 本文整理完善自下面这两份资料:
+>
+> - [SQL 语法速成手册](https://juejin.cn/post/6844903790571700231)
+> - [MySQL 超全教程](https://www.begtut.com/mysql/mysql-tutorial.html)
+
+## 基本概念
+
+### 数据库术语
+
+- `数据库(database)` - 保存有组织的数据的容器(通常是一个文件或一组文件)。
+- `数据表(table)` - 某种特定类型数据的结构化清单。
+- `模式(schema)` - 关于数据库和表的布局及特性的信息。模式定义了数据在表中如何存储,包含存储什么样的数据,数据如何分解,各部分信息如何命名等信息。数据库和表都有模式。
+- `列(column)` - 表中的一个字段。所有表都是由一个或多个列组成的。
+- `行(row)` - 表中的一个记录。
+- `主键(primary key)` - 一列(或一组列),其值能够唯一标识表中每一行。
+
+### SQL 语法
+
+SQL(Structured Query Language),标准 SQL 由 ANSI 标准委员会管理,从而称为 ANSI SQL。各个 DBMS 都有自己的实现,如 PL/SQL、Transact-SQL 等。
+
+#### SQL 语法结构
+
+
+
+SQL 语法结构包括:
+
+- **`子句`** - 是语句和查询的组成成分。(在某些情况下,这些都是可选的。)
+- **`表达式`** - 可以产生任何标量值,或由列和行的数据库表
+- **`谓词`** - 给需要评估的 SQL 三值逻辑(3VL)(true/false/unknown)或布尔真值指定条件,并限制语句和查询的效果,或改变程序流程。
+- **`查询`** - 基于特定条件检索数据。这是 SQL 的一个重要组成部分。
+- **`语句`** - 可以持久地影响纲要和数据,也可以控制数据库事务、程序流程、连接、会话或诊断。
+
+#### SQL 语法要点
+
+- **SQL 语句不区分大小写**,但是数据库表名、列名和值是否区分,依赖于具体的 DBMS 以及配置。例如:`SELECT` 与 `select`、`Select` 是相同的。
+- **多条 SQL 语句必须以分号(`;`)分隔**。
+- 处理 SQL 语句时,**所有空格都被忽略**。
+
+SQL 语句可以写成一行,也可以分写为多行。
+
+```sql
+-- 一行 SQL 语句
+
+UPDATE user SET username='robot', password='robot' WHERE username = 'root';
+
+-- 多行 SQL 语句
+UPDATE user
+SET username='robot', password='robot'
+WHERE username = 'root';
+```
+
+SQL 支持三种注释:
+
+```sql
+## 注释1
+-- 注释2
+/* 注释3 */
+```
+
+### SQL 分类
+
+#### 数据定义语言(DDL)
+
+数据定义语言(Data Definition Language,DDL)是 SQL 语言集中负责数据结构定义与数据库对象定义的语言。
+
+DDL 的主要功能是**定义数据库对象**。
+
+DDL 的核心指令是 `CREATE`、`ALTER`、`DROP`。
+
+#### 数据操纵语言(DML)
+
+数据操纵语言(Data Manipulation Language, DML)是用于数据库操作,对数据库其中的对象和数据运行访问工作的编程语句。
+
+DML 的主要功能是 **访问数据**,因此其语法都是以**读写数据库**为主。
+
+DML 的核心指令是 `INSERT`、`UPDATE`、`DELETE`、`SELECT`。这四个指令合称 CRUD(Create, Read, Update, Delete),即增删改查。
+
+#### 事务控制语言(TCL)
+
+事务控制语言 (Transaction Control Language, TCL) 用于**管理数据库中的事务**。这些用于管理由 DML 语句所做的更改。它还允许将语句分组为逻辑事务。
+
+TCL 的核心指令是 `COMMIT`、`ROLLBACK`。
+
+#### 数据控制语言(DCL)
+
+数据控制语言 (Data Control Language, DCL) 是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。
+
+DCL 的核心指令是 `GRANT`、`REVOKE`。
+
+DCL 以**控制用户的访问权限**为主,因此其指令作法并不复杂,可利用 DCL 控制的权限有:`CONNECT`、`SELECT`、`INSERT`、`UPDATE`、`DELETE`、`EXECUTE`、`USAGE`、`REFERENCES`。
+
+根据不同的 DBMS 以及不同的安全性实体,其支持的权限控制也有所不同。
+
+**我们先来介绍 DML 语句用法。 DML 的主要功能是读写数据库实现增删改查。**
+
+## 增删改查
+
+增删改查,又称为 CRUD,数据库基本操作中的基本操作。
+
+### 插入数据
+
+`INSERT INTO` 语句用于向表中插入新记录。
+
+**插入完整的行**
+
+```sql
+# 插入一行
+INSERT INTO user
+VALUES (10, 'root', 'root', 'xxxx@163.com');
+# 插入多行
+INSERT INTO user
+VALUES (10, 'root', 'root', 'xxxx@163.com'), (12, 'user1', 'user1', 'xxxx@163.com'), (18, 'user2', 'user2', 'xxxx@163.com');
+```
+
+**插入行的一部分**
+
+```sql
+INSERT INTO user(username, password, email)
+VALUES ('admin', 'admin', 'xxxx@163.com');
+```
+
+**插入查询出来的数据**
+
+```sql
+INSERT INTO user(username)
+SELECT name
+FROM account;
+```
+
+### 更新数据
+
+`UPDATE` 语句用于更新表中的记录。
+
+```sql
+UPDATE user
+SET username='robot', password='robot'
+WHERE username = 'root';
+```
+
+### 删除数据
+
+- `DELETE` 语句用于删除表中的记录。
+- `TRUNCATE TABLE` 可以清空表,也就是删除所有行。
+
+**删除表中的指定数据**
+
+```sql
+DELETE FROM user
+WHERE username = 'robot';
+```
+
+**清空表中的数据**
+
+```sql
+TRUNCATE TABLE user;
+```
+
+### 查询数据
+
+`SELECT` 语句用于从数据库中查询数据。
+
+`DISTINCT` 用于返回唯一不同的值。它作用于所有列,也就是说所有列的值都相同才算相同。
+
+`LIMIT` 限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。
+
+- `ASC`:升序(默认)
+- `DESC`:降序
+
+**查询单列**
+
+```sql
+SELECT prod_name
+FROM products;
+```
+
+**查询多列**
+
+```sql
+SELECT prod_id, prod_name, prod_price
+FROM products;
+```
+
+**查询所有列**
+
+```sql
+SELECT *
+FROM products;
+```
+
+**查询不同的值**
+
+```sql
+SELECT DISTINCT
+vend_id FROM products;
+```
+
+**限制查询结果**
+
+```sql
+-- 返回前 5 行
+SELECT * FROM mytable LIMIT 5;
+SELECT * FROM mytable LIMIT 0, 5;
+-- 返回第 3 ~ 5 行
+SELECT * FROM mytable LIMIT 2, 3;
+```
+
+## 排序
+
+`order by` 用于对结果集按照一个列或者多个列进行排序。默认按照升序对记录进行排序,如果需要按照降序对记录进行排序,可以使用 `desc` 关键字。
+
+`order by` 对多列排序的时候,先排序的列放前面,后排序的列放后面。并且,不同的列可以有不同的排序规则。
+
+```sql
+SELECT * FROM products
+ORDER BY prod_price DESC, prod_name ASC;
+```
+
+## 分组
+
+**`group by`**:
+
+- `group by` 子句将记录分组到汇总行中。
+- `group by` 为每个组返回一个记录。
+- `group by` 通常还涉及聚合`count`,`max`,`sum`,`avg` 等。
+- `group by` 可以按一列或多列进行分组。
+- `group by` 按分组字段进行排序后,`order by` 可以以汇总字段来进行排序。
+
+**分组**
+
+```sql
+SELECT cust_name, COUNT(cust_address) AS addr_num
+FROM Customers GROUP BY cust_name;
+```
+
+**分组后排序**
+
+```sql
+SELECT cust_name, COUNT(cust_address) AS addr_num
+FROM Customers GROUP BY cust_name
+ORDER BY cust_name DESC;
+```
+
+**`having`**:
+
+- `having` 用于对汇总的 `group by` 结果进行过滤。
+- `having` 一般都是和 `group by` 连用。
+- `where` 和 `having` 可以在相同的查询中。
+
+**使用 WHERE 和 HAVING 过滤数据**
+
+```sql
+SELECT cust_name, COUNT(*) AS num
+FROM Customers
+WHERE cust_email IS NOT NULL
+GROUP BY cust_name
+HAVING COUNT(*) >= 1;
+```
+
+**`having` vs `where`**:
+
+- `where`:过滤过滤指定的行,后面不能加聚合函数(分组函数)。`where` 在`group by` 前。
+- `having`:过滤分组,一般都是和 `group by` 连用,不能单独使用。`having` 在 `group by` 之后。
+
+## 子查询
+
+子查询是嵌套在较大查询中的 SQL 查询,也称内部查询或内部选择,包含子查询的语句也称为外部查询或外部选择。简单来说,子查询就是指将一个 `select` 查询(子查询)的结果作为另一个 SQL 语句(主查询)的数据来源或者判断条件。
+
+子查询可以嵌入 `SELECT`、`INSERT`、`UPDATE` 和 `DELETE` 语句中,也可以和 `=`、`<`、`>`、`IN`、`BETWEEN`、`EXISTS` 等运算符一起使用。
+
+子查询常用在 `WHERE` 子句和 `FROM` 子句后边:
+
+- 当用于 `WHERE` 子句时,根据不同的运算符,子查询可以返回单行单列、多行单列、单行多列数据。子查询就是要返回能够作为 `WHERE` 子句查询条件的值。
+- 当用于 `FROM` 子句时,一般返回多行多列数据,相当于返回一张临时表,这样才符合 `FROM` 后面是表的规则。这种做法能够实现多表联合查询。
+
+> 注意:MYSQL 数据库从 4.1 版本才开始支持子查询,早期版本是不支持的。
+
+用于 `WHERE` 子句的子查询的基本语法如下:
+
+```sql
+select column_name [, column_name ]
+from table1 [, table2 ]
+where column_name operator
+ (select column_name [, column_name ]
+ from table1 [, table2 ]
+ [where])
+```
+
+- 子查询需要放在括号`( )`内。
+- `operator` 表示用于 where 子句的运算符。
+
+用于 `FROM` 子句的子查询的基本语法如下:
+
+```sql
+select column_name [, column_name ]
+from (select column_name [, column_name ]
+ from table1 [, table2 ]
+ [where]) as temp_table_name
+where condition
+```
+
+用于 `FROM` 的子查询返回的结果相当于一张临时表,所以需要使用 AS 关键字为该临时表起一个名字。
+
+**子查询的子查询**
+
+```sql
+SELECT cust_name, cust_contact
+FROM customers
+WHERE cust_id IN (SELECT cust_id
+ FROM orders
+ WHERE order_num IN (SELECT order_num
+ FROM orderitems
+ WHERE prod_id = 'RGAN01'));
+```
+
+内部查询首先在其父查询之前执行,以便可以将内部查询的结果传递给外部查询。执行过程可以参考下图:
+
+
+
+### WHERE
+
+- `WHERE` 子句用于过滤记录,即缩小访问数据的范围。
+- `WHERE` 后跟一个返回 `true` 或 `false` 的条件。
+- `WHERE` 可以与 `SELECT`,`UPDATE` 和 `DELETE` 一起使用。
+- 可以在 `WHERE` 子句中使用的操作符。
+
+| 运算符 | 描述 |
+| ------- | ------------------------------------------------------ |
+| = | 等于 |
+| <> | 不等于。注释:在 SQL 的一些版本中,该操作符可被写成 != |
+| > | 大于 |
+| < | 小于 |
+| >= | 大于等于 |
+| <= | 小于等于 |
+| BETWEEN | 在某个范围内 |
+| LIKE | 搜索某种模式 |
+| IN | 指定针对某个列的多个可能值 |
+
+**`SELECT` 语句中的 `WHERE` 子句**
+
+```ini
+SELECT * FROM Customers
+WHERE cust_name = 'Kids Place';
+```
+
+**`UPDATE` 语句中的 `WHERE` 子句**
+
+```ini
+UPDATE Customers
+SET cust_name = 'Jack Jones'
+WHERE cust_name = 'Kids Place';
+```
+
+**`DELETE` 语句中的 `WHERE` 子句**
+
+```ini
+DELETE FROM Customers
+WHERE cust_name = 'Kids Place';
+```
+
+### IN 和 BETWEEN
+
+- `IN` 操作符在 `WHERE` 子句中使用,作用是在指定的几个特定值中任选一个值。
+- `BETWEEN` 操作符在 `WHERE` 子句中使用,作用是选取介于某个范围内的值。
+
+**IN 示例**
+
+```sql
+SELECT *
+FROM products
+WHERE vend_id IN ('DLL01', 'BRS01');
+```
+
+**BETWEEN 示例**
+
+```sql
+SELECT *
+FROM products
+WHERE prod_price BETWEEN 3 AND 5;
+```
+
+### AND、OR、NOT
+
+- `AND`、`OR`、`NOT` 是用于对过滤条件的逻辑处理指令。
+- `AND` 优先级高于 `OR`,为了明确处理顺序,可以使用 `()`。
+- `AND` 操作符表示左右条件都要满足。
+- `OR` 操作符表示左右条件满足任意一个即可。
+- `NOT` 操作符用于否定一个条件。
+
+**AND 示例**
+
+```ini
+SELECT prod_id, prod_name, prod_price
+FROM products
+WHERE vend_id = 'DLL01' AND prod_price <= 4;
+```
+
+**OR 示例**
+
+```ini
+SELECT prod_id, prod_name, prod_price
+FROM products
+WHERE vend_id = 'DLL01' OR vend_id = 'BRS01';
+```
+
+**NOT 示例**
+
+```sql
+SELECT *
+FROM products
+WHERE prod_price NOT BETWEEN 3 AND 5;
+```
+
+### LIKE
+
+- `LIKE` 操作符在 `WHERE` 子句中使用,作用是确定字符串是否匹配模式。
+- 只有字段是文本值时才使用 `LIKE`。
+- `LIKE` 支持两个通配符匹配选项:`%` 和 `_`。
+- 不要滥用通配符,通配符位于开头处匹配会非常慢。
+- `%` 表示任何字符出现任意次数。
+- `_` 表示任何字符出现一次。
+
+**% 示例**
+
+```sql
+SELECT prod_id, prod_name, prod_price
+FROM products
+WHERE prod_name LIKE '%bean bag%';
+```
+
+**\_ 示例**
+
+```sql
+SELECT prod_id, prod_name, prod_price
+FROM products
+WHERE prod_name LIKE '__ inch teddy bear';
+```
+
+## 连接
+
+JOIN 是“连接”的意思,顾名思义,SQL JOIN 子句用于将两个或者多个表联合起来进行查询。
+
+连接表时需要在每个表中选择一个字段,并对这些字段的值进行比较,值相同的两条记录将合并为一条。**连接表的本质就是将不同表的记录合并起来,形成一张新表。当然,这张新表只是临时的,它仅存在于本次查询期间**。
+
+使用 `JOIN` 连接两个表的基本语法如下:
+
+```sql
+select table1.column1, table2.column2...
+from table1
+join table2
+on table1.common_column1 = table2.common_column2;
+```
+
+`table1.common_column1 = table2.common_column2` 是连接条件,只有满足此条件的记录才会合并为一行。您可以使用多个运算符来连接表,例如 =、>、<、<>、<=、>=、!=、`between`、`like` 或者 `not`,但是最常见的是使用 =。
+
+当两个表中有同名的字段时,为了帮助数据库引擎区分是哪个表的字段,在书写同名字段名时需要加上表名。当然,如果书写的字段名在两个表中是唯一的,也可以不使用以上格式,只写字段名即可。
+
+另外,如果两张表的关联字段名相同,也可以使用 `USING`子句来代替 `ON`,举个例子:
+
+```sql
+# join....on
+select c.cust_name, o.order_num
+from Customers c
+inner join Orders o
+on c.cust_id = o.cust_id
+order by c.cust_name;
+
+# 如果两张表的关联字段名相同,也可以使用USING子句:join....using()
+select c.cust_name, o.order_num
+from Customers c
+inner join Orders o
+using(cust_id)
+order by c.cust_name;
+```
+
+**`ON` 和 `WHERE` 的区别**:
+
+- 连接表时,SQL 会根据连接条件生成一张新的临时表。`ON` 就是连接条件,它决定临时表的生成。
+- `WHERE` 是在临时表生成以后,再对临时表中的数据进行过滤,生成最终的结果集,这个时候已经没有 JOIN-ON 了。
+
+所以总结来说就是:**SQL 先根据 ON 生成一张临时表,然后再根据 WHERE 对临时表进行筛选**。
+
+SQL 允许在 `JOIN` 左边加上一些修饰性的关键词,从而形成不同类型的连接,如下表所示:
+
+| 连接类型 | 说明 |
+| ---------------------------------------- | --------------------------------------------------------------------------------------------- |
+| INNER JOIN 内连接 | (默认连接方式)只有当两个表都存在满足条件的记录时才会返回行。 |
+| LEFT JOIN / LEFT OUTER JOIN 左(外)连接 | 返回左表中的所有行,即使右表中没有满足条件的行也是如此。 |
+| RIGHT JOIN / RIGHT OUTER JOIN 右(外)连接 | 返回右表中的所有行,即使左表中没有满足条件的行也是如此。 |
+| FULL JOIN / FULL OUTER JOIN 全(外)连接 | 只要其中有一个表存在满足条件的记录,就返回行。 |
+| SELF JOIN | 将一个表连接到自身,就像该表是两个表一样。为了区分两个表,在 SQL 语句中需要至少重命名一个表。 |
+| CROSS JOIN | 交叉连接,从两个或者多个连接表中返回记录集的笛卡尔积。 |
+
+下图展示了 LEFT JOIN、RIGHT JOIN、INNER JOIN、OUTER JOIN 相关的 7 种用法。
+
+
+
+如果不加任何修饰词,只写 `JOIN`,那么默认为 `INNER JOIIN`
+
+对于 `INNER JOIIN` 来说,还有一种隐式的写法,称为 “**隐式内连接**”,也就是没有 `INNER JOIIN` 关键字,使用 `WHERE` 语句实现内连接的功能
+
+```sql
+# 隐式内连接
+select c.cust_name, o.order_num
+from Customers c, Orders o
+where c.cust_id = o.cust_id
+order by c.cust_name;
+
+# 显式内连接
+select c.cust_name, o.order_num
+from Customers c inner join Orders o
+using(cust_id)
+order by c.cust_name;
+```
+
+## 组合
+
+`UNION` 运算符将两个或更多查询的结果组合起来,并生成一个结果集,其中包含来自 `UNION` 中参与查询的提取行。
+
+`UNION` 基本规则:
+
+- 所有查询的列数和列顺序必须相同。
+- 每个查询中涉及表的列的数据类型必须相同或兼容。
+- 通常返回的列名取自第一个查询。
+
+默认地,`UNION` 操作符选取不同的值。如果允许重复的值,请使用 `UNION ALL`。
+
+```sql
+SELECT column_name(s) FROM table1
+UNION ALL
+SELECT column_name(s) FROM table2;
+```
+
+`UNION` 结果集中的列名总是等于 `UNION` 中第一个 `SELECT` 语句中的列名。
+
+`JOIN` vs `UNION`:
+
+- `JOIN` 中连接表的列可能不同,但在 `UNION` 中,所有查询的列数和列顺序必须相同。
+- `UNION` 将查询之后的行放在一起(垂直放置),但 `JOIN` 将查询之后的列放在一起(水平放置),即它构成一个笛卡尔积。
+
+## 函数
+
+不同数据库的函数往往各不相同,因此不可移植。本节主要以 MySQL 的函数为例。
+
+### 文本处理
+
+| 函数 | 说明 |
+| -------------------- | ---------------------- |
+| `LEFT()`、`RIGHT()` | 左边或者右边的字符 |
+| `LOWER()`、`UPPER()` | 转换为小写或者大写 |
+| `LTRIM()`、`RTRIM()` | 去除左边或者右边的空格 |
+| `LENGTH()` | 长度,以字节为单位 |
+| `SOUNDEX()` | 转换为语音值 |
+
+其中, **`SOUNDEX()`** 可以将一个字符串转换为描述其语音表示的字母数字模式。
+
+```sql
+SELECT *
+FROM mytable
+WHERE SOUNDEX(col1) = SOUNDEX('apple')
+```
+
+### 日期和时间处理
+
+- 日期格式:`YYYY-MM-DD`
+- 时间格式:`HH:MM:SS`
+
+| 函 数 | 说 明 |
+| --------------- | ------------------------------ |
+| `AddDate()` | 增加一个日期(天、周等) |
+| `AddTime()` | 增加一个时间(时、分等) |
+| `CurDate()` | 返回当前日期 |
+| `CurTime()` | 返回当前时间 |
+| `Date()` | 返回日期时间的日期部分 |
+| `DateDiff()` | 计算两个日期之差 |
+| `Date_Add()` | 高度灵活的日期运算函数 |
+| `Date_Format()` | 返回一个格式化的日期或时间串 |
+| `Day()` | 返回一个日期的天数部分 |
+| `DayOfWeek()` | 对于一个日期,返回对应的星期几 |
+| `Hour()` | 返回一个时间的小时部分 |
+| `Minute()` | 返回一个时间的分钟部分 |
+| `Month()` | 返回一个日期的月份部分 |
+| `Now()` | 返回当前日期和时间 |
+| `Second()` | 返回一个时间的秒部分 |
+| `Time()` | 返回一个日期时间的时间部分 |
+| `Year()` | 返回一个日期的年份部分 |
+
+### 数值处理
+
+| 函数 | 说明 |
+| ------ | ------ |
+| SIN() | 正弦 |
+| COS() | 余弦 |
+| TAN() | 正切 |
+| ABS() | 绝对值 |
+| SQRT() | 平方根 |
+| MOD() | 余数 |
+| EXP() | 指数 |
+| PI() | 圆周率 |
+| RAND() | 随机数 |
+
+### 汇总
+
+| 函 数 | 说 明 |
+| --------- | ---------------- |
+| `AVG()` | 返回某列的平均值 |
+| `COUNT()` | 返回某列的行数 |
+| `MAX()` | 返回某列的最大值 |
+| `MIN()` | 返回某列的最小值 |
+| `SUM()` | 返回某列值之和 |
+
+`AVG()` 会忽略 NULL 行。
+
+使用 `DISTINCT` 可以让汇总函数值汇总不同的值。
+
+```sql
+SELECT AVG(DISTINCT col1) AS avg_col
+FROM mytable
+```
+
+**接下来,我们来介绍 DDL 语句用法。DDL 的主要功能是定义数据库对象(如:数据库、数据表、视图、索引等)**
+
+## 数据定义
+
+### 数据库(DATABASE)
+
+#### 创建数据库
+
+```sql
+CREATE DATABASE test;
+```
+
+#### 删除数据库
+
+```sql
+DROP DATABASE test;
+```
+
+#### 选择数据库
+
+```sql
+USE test;
+```
+
+### 数据表(TABLE)
+
+#### 创建数据表
+
+**普通创建**
+
+```sql
+CREATE TABLE user (
+ id int(10) unsigned NOT NULL COMMENT 'Id',
+ username varchar(64) NOT NULL DEFAULT 'default' COMMENT '用户名',
+ password varchar(64) NOT NULL DEFAULT 'default' COMMENT '密码',
+ email varchar(64) NOT NULL DEFAULT 'default' COMMENT '邮箱'
+) COMMENT='用户表';
+```
+
+**根据已有的表创建新表**
+
+```sql
+CREATE TABLE vip_user AS
+SELECT * FROM user;
+```
+
+#### 删除数据表
+
+```sql
+DROP TABLE user;
+```
+
+#### 修改数据表
+
+**添加列**
+
+```sql
+ALTER TABLE user
+ADD age int(3);
+```
+
+**删除列**
+
+```sql
+ALTER TABLE user
+DROP COLUMN age;
+```
+
+**修改列**
+
+```sql
+ALTER TABLE `user`
+MODIFY COLUMN age tinyint;
+```
+
+**添加主键**
+
+```sql
+ALTER TABLE user
+ADD PRIMARY KEY (id);
+```
+
+**删除主键**
+
+```sql
+ALTER TABLE user
+DROP PRIMARY KEY;
+```
+
+### 视图(VIEW)
+
+定义:
+
+- 视图是基于 SQL 语句的结果集的可视化的表。
+- 视图是虚拟的表,本身不包含数据,也就不能对其进行索引操作。对视图的操作和对普通表的操作一样。
+
+作用:
+
+- 简化复杂的 SQL 操作,比如复杂的联结;
+- 只使用实际表的一部分数据;
+- 通过只给用户访问视图的权限,保证数据的安全性;
+- 更改数据格式和表示。
+
+
+
+#### 创建视图
+
+```sql
+CREATE VIEW top_10_user_view AS
+SELECT id, username
+FROM user
+WHERE id < 10;
+```
+
+#### 删除视图
+
+```sql
+DROP VIEW top_10_user_view;
+```
+
+### 索引(INDEX)
+
+**索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。**
+
+索引的作用就相当于书的目录。打个比方: 我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
+
+**优点**:
+
+- 使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
+- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
+
+**缺点**:
+
+- 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
+- 索引需要使用物理文件存储,也会耗费一定空间。
+
+但是,**使用索引一定能提高查询性能吗?**
+
+大多数情况下,索引查询都是比全表扫描要快的。但是如果数据库的数据量不大,那么使用索引也不一定能够带来很大提升。
+
+关于索引的详细介绍,请看我写的 [MySQL 索引详解](https://javaguide.cn/database/mysql/mysql-index.html) 这篇文章。
+
+#### 创建索引
+
+```sql
+CREATE INDEX user_index
+ON user (id);
+```
+
+#### 添加索引
+
+```sql
+ALTER table user ADD INDEX user_index(id)
+```
+
+#### 创建唯一索引
+
+```sql
+CREATE UNIQUE INDEX user_index
+ON user (id);
+```
+
+#### 删除索引
+
+```sql
+ALTER TABLE user
+DROP INDEX user_index;
+```
+
+### 约束
+
+SQL 约束用于规定表中的数据规则。
+
+如果存在违反约束的数据行为,行为会被约束终止。
+
+约束可以在创建表时规定(通过 CREATE TABLE 语句),或者在表创建之后规定(通过 ALTER TABLE 语句)。
+
+约束类型:
+
+- `NOT NULL` - 指示某列不能存储 NULL 值。
+- `UNIQUE` - 保证某列的每行必须有唯一的值。
+- `PRIMARY KEY` - NOT NULL 和 UNIQUE 的结合。确保某列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。
+- `FOREIGN KEY` - 保证一个表中的数据匹配另一个表中的值的参照完整性。
+- `CHECK` - 保证列中的值符合指定的条件。
+- `DEFAULT` - 规定没有给列赋值时的默认值。
+
+创建表时使用约束条件:
+
+```sql
+CREATE TABLE Users (
+ Id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增Id',
+ Username VARCHAR(64) NOT NULL UNIQUE DEFAULT 'default' COMMENT '用户名',
+ Password VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '密码',
+ Email VARCHAR(64) NOT NULL DEFAULT 'default' COMMENT '邮箱地址',
+ Enabled TINYINT(4) DEFAULT NULL COMMENT '是否有效',
+ PRIMARY KEY (Id)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
+```
+
+**接下来,我们来介绍 TCL 语句用法。TCL 的主要功能是管理数据库中的事务。**
+
+## 事务处理
+
+不能回退 `SELECT` 语句,回退 `SELECT` 语句也没意义;也不能回退 `CREATE` 和 `DROP` 语句。
+
+**MySQL 默认是隐式提交**,每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 `START TRANSACTION` 语句时,会关闭隐式提交;当 `COMMIT` 或 `ROLLBACK` 语句执行后,事务会自动关闭,重新恢复隐式提交。
+
+通过 `set autocommit=0` 可以取消自动提交,直到 `set autocommit=1` 才会提交;`autocommit` 标记是针对每个连接而不是针对服务器的。
+
+指令:
+
+- `START TRANSACTION` - 指令用于标记事务的起始点。
+- `SAVEPOINT` - 指令用于创建保留点。
+- `ROLLBACK TO` - 指令用于回滚到指定的保留点;如果没有设置保留点,则回退到 `START TRANSACTION` 语句处。
+- `COMMIT` - 提交事务。
+
+```sql
+-- 开始事务
+START TRANSACTION;
+
+-- 插入操作 A
+INSERT INTO `user`
+VALUES (1, 'root1', 'root1', 'xxxx@163.com');
+
+-- 创建保留点 updateA
+SAVEPOINT updateA;
+
+-- 插入操作 B
+INSERT INTO `user`
+VALUES (2, 'root2', 'root2', 'xxxx@163.com');
+
+-- 回滚到保留点 updateA
+ROLLBACK TO updateA;
+
+-- 提交事务,只有操作 A 生效
+COMMIT;
+```
+
+**接下来,我们来介绍 DCL 语句用法。DCL 的主要功能是控制用户的访问权限。**
+
+## 权限控制
+
+要授予用户帐户权限,可以用`GRANT`命令。有撤销用户的权限,可以用`REVOKE`命令。这里以 MySQl 为例,介绍权限控制实际应用。
+
+`GRANT`授予权限语法:
+
+```sql
+GRANT privilege,[privilege],.. ON privilege_level
+TO user [IDENTIFIED BY password]
+[REQUIRE tsl_option]
+[WITH [GRANT_OPTION | resource_option]];
+```
+
+简单解释一下:
+
+1. 在`GRANT`关键字后指定一个或多个权限。如果授予用户多个权限,则每个权限由逗号分隔。
+2. `ON privilege_level` 确定权限应用级别。MySQL 支持 global(`*.*`),database(`database.*`),table(`database.table`)和列级别。如果使用列权限级别,则必须在每个权限之后指定一个或逗号分隔列的列表。
+3. `user` 是要授予权限的用户。如果用户已存在,则`GRANT`语句将修改其权限。否则,`GRANT`语句将创建一个新用户。可选子句`IDENTIFIED BY`允许您为用户设置新的密码。
+4. `REQUIRE tsl_option`指定用户是否必须通过 SSL,X059 等安全连接连接到数据库服务器。
+5. 可选 `WITH GRANT OPTION` 子句允许您授予其他用户或从其他用户中删除您拥有的权限。此外,您可以使用`WITH`子句分配 MySQL 数据库服务器的资源,例如,设置用户每小时可以使用的连接数或语句数。这在 MySQL 共享托管等共享环境中非常有用。
+
+`REVOKE` 撤销权限语法:
+
+```sql
+REVOKE privilege_type [(column_list)]
+ [, priv_type [(column_list)]]...
+ON [object_type] privilege_level
+FROM user [, user]...
+```
+
+简单解释一下:
+
+1. 在 `REVOKE` 关键字后面指定要从用户撤消的权限列表。您需要用逗号分隔权限。
+2. 指定在 `ON` 子句中撤销特权的特权级别。
+3. 指定要撤消 `FROM` 子句中的权限的用户帐户。
+
+`GRANT` 和 `REVOKE` 可在几个层次上控制访问权限:
+
+- 整个服务器,使用 `GRANT ALL` 和 `REVOKE ALL`;
+- 整个数据库,使用 `ON database.*`;
+- 特定的表,使用 `ON database.table`;
+- 特定的列;
+- 特定的存储过程。
+
+新创建的账户没有任何权限。账户用 `username@host` 的形式定义,`username@%` 使用的是默认主机名。MySQL 的账户信息保存在 mysql 这个数据库中。
+
+```sql
+USE mysql;
+SELECT user FROM user;
+```
+
+下表说明了可用于`GRANT`和`REVOKE`语句的所有允许权限:
+
+| **特权** | **说明** | **级别** | | | | | |
+| ----------------------- | ------------------------------------------------------------------------------------------------------- | -------- | ------ | -------- | -------- | --- | --- |
+| **全局** | 数据库 | **表** | **列** | **程序** | **代理** | | |
+| ALL [PRIVILEGES] | 授予除 GRANT OPTION 之外的指定访问级别的所有权限 | | | | | | |
+| ALTER | 允许用户使用 ALTER TABLE 语句 | X | X | X | | | |
+| ALTER ROUTINE | 允许用户更改或删除存储的例程 | X | X | | | X | |
+| CREATE | 允许用户创建数据库和表 | X | X | X | | | |
+| CREATE ROUTINE | 允许用户创建存储的例程 | X | X | | | | |
+| CREATE TABLESPACE | 允许用户创建,更改或删除表空间和日志文件组 | X | | | | | |
+| CREATE TEMPORARY TABLES | 允许用户使用 CREATE TEMPORARY TABLE 创建临时表 | X | X | | | | |
+| CREATE USER | 允许用户使用 CREATE USER,DROP USER,RENAME USER 和 REVOKE ALL PRIVILEGES 语句。 | X | | | | | |
+| CREATE VIEW | 允许用户创建或修改视图。 | X | X | X | | | |
+| DELETE | 允许用户使用 DELETE | X | X | X | | | |
+| DROP | 允许用户删除数据库,表和视图 | X | X | X | | | |
+| EVENT | 启用事件计划程序的事件使用。 | X | X | | | | |
+| EXECUTE | 允许用户执行存储的例程 | X | X | X | | | |
+| FILE | 允许用户读取数据库目录中的任何文件。 | X | | | | | |
+| GRANT OPTION | 允许用户拥有授予或撤消其他帐户权限的权限。 | X | X | X | | X | X |
+| INDEX | 允许用户创建或删除索引。 | X | X | X | | | |
+| INSERT | 允许用户使用 INSERT 语句 | X | X | X | X | | |
+| LOCK TABLES | 允许用户对具有 SELECT 权限的表使用 LOCK TABLES | X | X | | | | |
+| PROCESS | 允许用户使用 SHOW PROCESSLIST 语句查看所有进程。 | X | | | | | |
+| PROXY | 启用用户代理。 | | | | | | |
+| REFERENCES | 允许用户创建外键 | X | X | X | X | | |
+| RELOAD | 允许用户使用 FLUSH 操作 | X | | | | | |
+| REPLICATION CLIENT | 允许用户查询以查看主服务器或从属服务器的位置 | X | | | | | |
+| REPLICATION SLAVE | 允许用户使用复制从属从主服务器读取二进制日志事件。 | X | | | | | |
+| SELECT | 允许用户使用 SELECT 语句 | X | X | X | X | | |
+| SHOW DATABASES | 允许用户显示所有数据库 | X | | | | | |
+| SHOW VIEW | 允许用户使用 SHOW CREATE VIEW 语句 | X | X | X | | | |
+| SHUTDOWN | 允许用户使用 mysqladmin shutdown 命令 | X | | | | | |
+| SUPER | 允许用户使用其他管理操作,例如 CHANGE MASTER TO,KILL,PURGE BINARY LOGS,SET GLOBAL 和 mysqladmin 命令 | X | | | | | |
+| TRIGGER | 允许用户使用 TRIGGER 操作。 | X | X | X | | | |
+| UPDATE | 允许用户使用 UPDATE 语句 | X | X | X | X | | |
+| USAGE | 相当于“没有特权” | | | | | | |
+
+### 创建账户
+
+```sql
+CREATE USER myuser IDENTIFIED BY 'mypassword';
+```
+
+### 修改账户名
+
+```sql
+UPDATE user SET user='newuser' WHERE user='myuser';
+FLUSH PRIVILEGES;
+```
+
+### 删除账户
+
+```sql
+DROP USER myuser;
+```
+
+### 查看权限
+
+```sql
+SHOW GRANTS FOR myuser;
+```
+
+### 授予权限
+
+```sql
+GRANT SELECT, INSERT ON *.* TO myuser;
+```
+
+### 删除权限
+
+```sql
+REVOKE SELECT, INSERT ON *.* FROM myuser;
+```
+
+### 更改密码
+
+```sql
+SET PASSWORD FOR myuser = 'mypass';
+```
+
+## 存储过程
+
+存储过程可以看成是对一系列 SQL 操作的批处理。存储过程可以由触发器,其他存储过程以及 Java, Python,PHP 等应用程序调用。
+
+
+
+使用存储过程的好处:
+
+- 代码封装,保证了一定的安全性;
+- 代码复用;
+- 由于是预先编译,因此具有很高的性能。
+
+创建存储过程:
+
+- 命令行中创建存储过程需要自定义分隔符,因为命令行是以 `;` 为结束符,而存储过程中也包含了分号,因此会错误把这部分分号当成是结束符,造成语法错误。
+- 包含 `in`、`out` 和 `inout` 三种参数。
+- 给变量赋值都需要用 `select into` 语句。
+- 每次只能给一个变量赋值,不支持集合的操作。
+
+需要注意的是:**阿里巴巴《Java 开发手册》强制禁止使用存储过程。因为存储过程难以调试和扩展,更没有移植性。**
+
+
+
+至于到底要不要在项目中使用,还是要看项目实际需求,权衡好利弊即可!
+
+### 创建存储过程
+
+```sql
+DROP PROCEDURE IF EXISTS `proc_adder`;
+DELIMITER ;;
+CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_adder`(IN a int, IN b int, OUT sum int)
+BEGIN
+ DECLARE c int;
+ if a is null then set a = 0;
+ end if;
+
+ if b is null then set b = 0;
+ end if;
+
+ set sum = a + b;
+END
+;;
+DELIMITER ;
+```
+
+### 使用存储过程
+
+```less
+set @b=5;
+call proc_adder(2,@b,@s);
+select @s as sum;
+```
+
+## 游标
+
+游标(cursor)是一个存储在 DBMS 服务器上的数据库查询,它不是一条 `SELECT` 语句,而是被该语句检索出来的结果集。
+
+在存储过程中使用游标可以对一个结果集进行移动遍历。
+
+游标主要用于交互式应用,其中用户需要滚动屏幕上的数据,并对数据进行浏览或做出更改。
+
+使用游标的几个明确步骤:
+
+- 在使用游标前,必须声明(定义)它。这个过程实际上没有检索数据, 它只是定义要使用的 `SELECT` 语句和游标选项。
+
+- 一旦声明,就必须打开游标以供使用。这个过程用前面定义的 SELECT 语句把数据实际检索出来。
+
+- 对于填有数据的游标,根据需要取出(检索)各行。
+
+- 在结束游标使用时,必须关闭游标,可能的话,释放游标(有赖于具
+
+ 体的 DBMS)。
+
+```sql
+DELIMITER $
+CREATE PROCEDURE getTotal()
+BEGIN
+ DECLARE total INT;
+ -- 创建接收游标数据的变量
+ DECLARE sid INT;
+ DECLARE sname VARCHAR(10);
+ -- 创建总数变量
+ DECLARE sage INT;
+ -- 创建结束标志变量
+ DECLARE done INT DEFAULT false;
+ -- 创建游标
+ DECLARE cur CURSOR FOR SELECT id,name,age from cursor_table where age>30;
+ -- 指定游标循环结束时的返回值
+ DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true;
+ SET total = 0;
+ OPEN cur;
+ FETCH cur INTO sid, sname, sage;
+ WHILE(NOT done)
+ DO
+ SET total = total + 1;
+ FETCH cur INTO sid, sname, sage;
+ END WHILE;
+
+ CLOSE cur;
+ SELECT total;
+END $
+DELIMITER ;
+
+-- 调用存储过程
+call getTotal();
+```
+
+## 触发器
+
+触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。
+
+我们可以使用触发器来进行审计跟踪,把修改记录到另外一张表中。
+
+使用触发器的优点:
+
+- SQL 触发器提供了另一种检查数据完整性的方法。
+- SQL 触发器可以捕获数据库层中业务逻辑中的错误。
+- SQL 触发器提供了另一种运行计划任务的方法。通过使用 SQL 触发器,您不必等待运行计划任务,因为在对表中的数据进行更改之前或之后会自动调用触发器。
+- SQL 触发器对于审计表中数据的更改非常有用。
+
+使用触发器的缺点:
+
+- SQL 触发器只能提供扩展验证,并且不能替换所有验证。必须在应用程序层中完成一些简单的验证。例如,您可以使用 JavaScript 在客户端验证用户的输入,或者使用服务器端脚本语言(如 JSP,PHP,ASP.NET,Perl)在服务器端验证用户的输入。
+- 从客户端应用程序调用和执行 SQL 触发器是不可见的,因此很难弄清楚数据库层中发生了什么。
+- SQL 触发器可能会增加数据库服务器的开销。
+
+MySQL 不允许在触发器中使用 CALL 语句 ,也就是不能调用存储过程。
+
+> 注意:在 MySQL 中,分号 `;` 是语句结束的标识符,遇到分号表示该段语句已经结束,MySQL 可以开始执行了。因此,解释器遇到触发器执行动作中的分号后就开始执行,然后会报错,因为没有找到和 BEGIN 匹配的 END。
+>
+> 这时就会用到 `DELIMITER` 命令(DELIMITER 是定界符,分隔符的意思)。它是一条命令,不需要语句结束标识,语法为:`DELIMITER new_delemiter`。`new_delemiter` 可以设为 1 个或多个长度的符号,默认的是分号 `;`,我们可以把它修改为其他符号,如 `$` - `DELIMITER $` 。在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了 `$`,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。
+
+在 MySQL 5.7.2 版之前,可以为每个表定义最多六个触发器。
+
+- `BEFORE INSERT` - 在将数据插入表格之前激活。
+- `AFTER INSERT` - 将数据插入表格后激活。
+- `BEFORE UPDATE` - 在更新表中的数据之前激活。
+- `AFTER UPDATE` - 更新表中的数据后激活。
+- `BEFORE DELETE` - 在从表中删除数据之前激活。
+- `AFTER DELETE` - 从表中删除数据后激活。
+
+但是,从 MySQL 版本 5.7.2+开始,可以为同一触发事件和操作时间定义多个触发器。
+
+**`NEW` 和 `OLD`**:
+
+- MySQL 中定义了 `NEW` 和 `OLD` 关键字,用来表示触发器的所在表中,触发了触发器的那一行数据。
+- 在 `INSERT` 型触发器中,`NEW` 用来表示将要(`BEFORE`)或已经(`AFTER`)插入的新数据;
+- 在 `UPDATE` 型触发器中,`OLD` 用来表示将要或已经被修改的原数据,`NEW` 用来表示将要或已经修改为的新数据;
+- 在 `DELETE` 型触发器中,`OLD` 用来表示将要或已经被删除的原数据;
+- 使用方法:`NEW.columnName` (columnName 为相应数据表某一列名)
+
+### 创建触发器
+
+> 提示:为了理解触发器的要点,有必要先了解一下创建触发器的指令。
+
+`CREATE TRIGGER` 指令用于创建触发器。
+
+语法:
+
+```sql
+CREATE TRIGGER trigger_name
+trigger_time
+trigger_event
+ON table_name
+FOR EACH ROW
+BEGIN
+ trigger_statements
+END;
+```
+
+说明:
+
+- `trigger_name`:触发器名
+- `trigger_time` : 触发器的触发时机。取值为 `BEFORE` 或 `AFTER`。
+- `trigger_event` : 触发器的监听事件。取值为 `INSERT`、`UPDATE` 或 `DELETE`。
+- `table_name` : 触发器的监听目标。指定在哪张表上建立触发器。
+- `FOR EACH ROW`: 行级监视,Mysql 固定写法,其他 DBMS 不同。
+- `trigger_statements`: 触发器执行动作。是一条或多条 SQL 语句的列表,列表内的每条语句都必须用分号 `;` 来结尾。
+
+当触发器的触发条件满足时,将会执行 `BEGIN` 和 `END` 之间的触发器执行动作。
+
+示例:
+
+```sql
+DELIMITER $
+CREATE TRIGGER `trigger_insert_user`
+AFTER INSERT ON `user`
+FOR EACH ROW
+BEGIN
+ INSERT INTO `user_history`(user_id, operate_type, operate_time)
+ VALUES (NEW.id, 'add a user', now());
+END $
+DELIMITER ;
+```
+
+### 查看触发器
+
+```sql
+SHOW TRIGGERS;
+```
+
+### 删除触发器
+
+```sql
+DROP TRIGGER IF EXISTS trigger_insert_user;
+```
+
+## 文章推荐
+
+- [后端程序员必备:SQL 高性能优化指南!35+条优化建议立马 GET!](https://mp.weixin.qq.com/s/I-ZT3zGTNBZ6egS7T09jyQ)
+- [后端程序员必备:书写高质量 SQL 的 30 条建议](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486461&idx=1&sn=60a22279196d084cc398936fe3b37772&chksm=cea24436f9d5cd20a4fa0e907590f3e700d7378b3f608d7b33bb52cfb96f503b7ccb65a1deed&token=1987003517&lang=zh_CN#rd)
diff --git a/docs/distributed-system/api-gateway.md b/docs/distributed-system/api-gateway.md
index 11bab874ff1786fc584b6e456e817a36950cd662..9634421923b4c71bcd2a32c1a3042f3f56b2db4b 100644
--- a/docs/distributed-system/api-gateway.md
+++ b/docs/distributed-system/api-gateway.md
@@ -1,31 +1,66 @@
+---
+title: API网关基础知识总结
+category: 分布式
+---
-# 网关
+## 什么是网关?
-## 何为网关?为什么要网关?
+微服务背景下,一个系统被拆分为多个服务,但是像安全认证,流量控制,日志,监控等功能是每个服务都需要的,没有网关的话,我们就需要在每个服务中单独实现,这使得我们做了很多重复的事情并且没有一个全局的视图来统一管理这些功能。
-
+
-微服务背景下,一个系统被拆分为多个服务,但是像安全认证,流量控制,日志,监控等功能是每个服务都需要的,没有网关的话,我们就需要在每个服务中单独实现,这使得我们做了很多重复的事情并且没有一个全局的视图来统一管理这些功能。
+一般情况下,网关可以为我们提供请求转发、安全认证(身份/权限认证)、流量控制、负载均衡、降级熔断、日志、监控、参数校验、协议转换等功能。
+
+上面介绍了这么多功能,实际上,网关主要做了两件事情:**请求转发** + **请求过滤**。
+
+由于引入网关之后,会多一步网络转发,因此性能会有一点影响(几乎可以忽略不计,尤其是内网访问的情况下)。 另外,我们需要保障网关服务的高可用,避免单点风险。
+
+如下图所示,网关服务外层通过 Nginx(其他负载均衡设备/软件也行) 进⾏负载转发以达到⾼可⽤。Nginx 在部署的时候,尽量也要考虑高可用,避免单点风险。
+
+
-综上:**一般情况下,网关都会提供请求转发、安全认证(身份/权限认证)、流量控制、负载均衡、容灾、日志、监控这些功能。**
+## 网关能提供哪些功能?
-上面介绍了这么多功能,实际上,网关主要做了一件事情:**请求过滤** 。
+绝大部分网关可以提供下面这些功能:
+
+- **请求转发**:将请求转发到目标微服务。
+- **负载均衡**:根据各个微服务实例的负载情况或者具体的负载均衡策略配置对请求实现动态的负载均衡。
+- **安全认证**:对用户请求进行身份验证并仅允许可信客户端访问 API,并且还能够使用类似 RBAC 等方式来授权。
+- **参数校验**:支持参数映射与校验逻辑。
+- **日志记录**:记录所有请求的行为日志供后续使用。
+- **监控告警**:从业务指标、机器指标、JVM 指标等方面进行监控并提供配套的告警机制。
+- **流量控制**:对请求的流量进行控制,也就是限制某一时刻内的请求数。
+- **熔断降级**:实时监控请求的统计信息,达到配置的失败阈值后,自动熔断,返回默认值。
+- **响应缓存**:当用户请求获取的是一些静态的或更新不频繁的数据时,一段时间内多次请求获取到的数据很可能是一样的。对于这种情况可以将响应缓存起来。这样用户请求可以直接在网关层得到响应数据,无需再去访问业务服务,减轻业务服务的负担。
+- **响应聚合**:某些情况下用户请求要获取的响应内容可能会来自于多个业务服务。网关作为业务服务的调用方,可以把多个服务的响应整合起来,再一并返回给用户。
+- **灰度发布**:将请求动态分流到不同的服务版本(最基本的一种灰度发布)。
+- **异常处理**:对于业务服务返回的异常响应,可以在网关层在返回给用户之前做转换处理。这样可以把一些业务侧返回的异常细节隐藏,转换成用户友好的错误提示返回。
+- **API 文档:** 如果计划将 API 暴露给组织以外的开发人员,那么必须考虑使用 API 文档,例如 Swagger 或 OpenAPI。
+- **协议转换**:通过协议转换整合后台基于 REST、AMQP、Dubbo 等不同风格和实现技术的微服务,面向 Web Mobile、开放平台等特定客户端提供统一服务。
+
+下图来源于[百亿规模 API 网关服务 Shepherd 的设计与实现 - 美团技术团队 - 2021](https://mp.weixin.qq.com/s/iITqdIiHi3XGKq6u6FRVdg)这篇文章。
+
+
## 有哪些常见的网关系统?
### Netflix Zuul
-Zuul 是 Netflix 开发的一款提供动态路由、监控、弹性、安全的网关服务。
+Zuul 是 Netflix 开发的一款提供动态路由、监控、弹性、安全的网关服务,基于 Java 技术栈开发,可以和 Eureka、Ribbon、Hystrix 等组件配合使用。
+
+Zuul 核心架构如下:
+
+
Zuul 主要通过过滤器(类似于 AOP)来过滤请求,从而实现网关必备的各种功能。
-
+
我们可以自定义过滤器来处理请求,并且,Zuul 生态本身就有很多现成的过滤器供我们使用。就比如限流可以直接用国外朋友写的 [spring-cloud-zuul-ratelimit](https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit) (这里只是举例说明,一般是配合 hystrix 来做限流):
```xml
- org.springframework.cloud
+ org.springframework.cloud
spring-cloud-starter-netflix-zuul
@@ -35,31 +70,41 @@ Zuul 主要通过过滤器(类似于 AOP)来过滤请求,从而实现网
```
-Zuul 1.x 基于同步 IO,性能较差。Zuul 2.x 基于 Netty 实现了异步 IO,性能得到了大幅改进。
+[Zuul 1.x](https://netflixtechblog.com/announcing-zuul-edge-service-in-the-cloud-ab3af5be08ee) 基于同步 IO,性能较差。[Zuul 2.x](https://netflixtechblog.com/open-sourcing-zuul-2-82ea476cb2b3) 基于 Netty 实现了异步 IO,性能得到了大幅改进。
-- Github 地址 : https://github.com/Netflix/zuul
-- 官方 Wiki : https://github.com/Netflix/zuul/wiki
+
+
+- GitHub 地址:
+- 官方 Wiki:
### Spring Cloud Gateway
-SpringCloud Gateway 属于 Spring Cloud 生态系统中的网关,其诞生的目标是为了替代老牌网关 **Zuul **。准确点来说,应该是 Zuul 1.x。SpringCloud Gateway 起步要比 Zuul 2.x 更早。
+SpringCloud Gateway 属于 Spring Cloud 生态系统中的网关,其诞生的目标是为了替代老牌网关 **Zuul**。准确点来说,应该是 Zuul 1.x。SpringCloud Gateway 起步要比 Zuul 2.x 更早。
+
+为了提升网关的性能,SpringCloud Gateway 基于 Spring WebFlux 。Spring WebFlux 使用 Reactor 库来实现响应式编程模型,底层基于 Netty 实现同步非阻塞的 I/O。
-为了提升网关的性能,SpringCloud Gateway 基于 Spring WebFlux 。Spring WebFlux 使用 Reactor 库来实现响应式编程模型,底层基于 Netty 实现异步 IO。
+
-Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
+Spring Cloud Gateway 不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,限流。
Spring Cloud Gateway 和 Zuul 2.x 的差别不大,也是通过过滤器来处理请求。不过,目前更加推荐使用 Spring Cloud Gateway 而非 Zuul,Spring Cloud 生态对其支持更加友好。
-- Github 地址 : https://github.com/spring-cloud/spring-cloud-gateway
-- 官网 : https://spring.io/projects/spring-cloud-gateway
+- Github 地址:
+- 官网:
### Kong
-Kong 是一款基于 [OpenResty](https://github.com/openresty/) 的高性能、云原生、可扩展的网关系统。
+Kong 是一款基于 [OpenResty](https://github.com/openresty/) (Nginx + Lua)的高性能、云原生、可扩展的网关系统,主要由 3 个组件组成:
+
+- Kong Server:基于 Nginx 的服务器,用来接收 API 请求。
+- Apache Cassandra/PostgreSQL:用来存储操作数据。
+- Kong Dashboard:官方推荐 UI 管理工具,当然,也可以使用 RESTful 方式 管理 Admin api。
> OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
-Kong 提供了插件机制来扩展其功能。比如、在服务上启用 Zipkin 插件
+
+
+Kong 提供了插件机制来扩展其功能,插件在 API 请求响应循环的生命周期中被执行。比如在服务上启用 Zipkin 插件:
```shell
$ curl -X POST http://kong:8001/services/{service}/plugins \
@@ -68,28 +113,42 @@ $ curl -X POST http://kong:8001/services/{service}/plugins \
--data "config.sample_ratio=0.001"
```
-- Github 地址: https://github.com/Kong/kong
-- 官网地址 : https://konghq.com/kong
+> Kong 本身就是一个 Lua 应用程序,并且是在 Openresty 的基础之上做了一层封装的应用。归根结底就是利用 Lua 嵌入 Nginx 的方式,赋予了 Nginx 可编程的能力,这样以插件的形式在 Nginx 这一层能够做到无限想象的事情。例如限流、安全访问策略、路由、负载均衡等等。编写一个 Kong 插件,就是按照 Kong 插件编写规范,写一个自己自定义的 Lua 脚本,然后加载到 Kong 中,最后引用即可。
+
+
+
+- Github 地址:
+- 官网地址:
### APISIX
APISIX 是一款基于 Nginx 和 etcd 的高性能、云原生、可扩展的网关系统。
-> *etcd*是使用 Go 语言开发的一个开源的、高可用的分布式 key-value 存储系统,使用 Raft 协议做分布式共识。
+> etcd 是使用 Go 语言开发的一个开源的、高可用的分布式 key-value 存储系统,使用 Raft 协议做分布式共识。
与传统 API 网关相比,APISIX 具有动态路由和插件热加载,特别适合微服务系统下的 API 管理。并且,APISIX 与 SkyWalking(分布式链路追踪系统)、Zipkin(分布式链路追踪系统)、Prometheus(监控系统) 等 DevOps 生态工具对接都十分方便。
-
+
作为 NGINX 和 Kong 的替代项目,APISIX 目前已经是 Apache 顶级开源项目,并且是最快毕业的国产开源项目。国内目前已经有很多知名企业(比如金山、有赞、爱奇艺、腾讯、贝壳)使用 APISIX 处理核心的业务流量。
根据官网介绍:“APISIX 已经生产可用,功能、性能、架构全面优于 Kong”。
-- Github 地址 :https://github.com/apache/apisix
-- 官网地址: https://apisix.apache.org/zh/
+APISIX 同样支持定制化的插件开发。开发者除了能够使用 Lua 语言开发插件,还能通过下面两种方式开发来避开 Lua 语言的学习成本:
+
+- 通过 Plugin Runner 来支持更多的主流编程语言(比如 Java、Python、Go 等等)。通过这样的方式,可以让后端工程师通过本地 RPC 通信,使用熟悉的编程语言开发 APISIX 的插件。这样做的好处是减少了开发成本,提高了开发效率,但是在性能上会有一些损失。
+- 使用 Wasm(WebAssembly) 开发插件。Wasm 被嵌入到了 APISIX 中,用户可以使用 Wasm 去编译成 Wasm 的字节码在 APISIX 中运行。
+
+> Wasm 是基于堆栈的虚拟机的二进制指令格式,一种低级汇编语言,旨在非常接近已编译的机器代码,并且非常接近本机性能。Wasm 最初是为浏览器构建的,但是随着技术的成熟,在服务器端看到了越来越多的用例。
+
+
+
+- Github 地址:
+- 官网地址:
相关阅读:
+- [为什么说 Apache APISIX 是最好的 API 网关?](https://mp.weixin.qq.com/s/j8ggPGEHFu3x5ekJZyeZnA)
- [有了 NGINX 和 Kong,为什么还需要 Apache APISIX](https://www.apiseven.com/zh/blog/why-we-need-Apache-APISIX)
- [APISIX 技术博客](https://www.apiseven.com/zh/blog)
- [APISIX 用户案例](https://www.apiseven.com/zh/usercases)
@@ -98,10 +157,15 @@ APISIX 是一款基于 Nginx 和 etcd 的高性能、云原生、可扩展的网
Shenyu 是一款基于 WebFlux 的可扩展、高性能、响应式网关,Apache 顶级开源项目。
-
+
+
+Shenyu 通过插件扩展功能,插件是 ShenYu 的灵魂,并且插件也是可扩展和热插拔的。不同的插件实现不同的功能。Shenyu 自带了诸如限流、熔断、转发、重写、重定向、和路由监控等插件。
-Shenyu 通过插件扩展功能,插件是 ShenYu 的灵魂,并且插件也是可扩展和热插拔的。不同的插件实现不同的功能。Shenyu 自带了诸如限流、熔断、转发 、重写、重定向、和路由监控等插件。
+- Github 地址:
+- 官网地址:
-- Github 地址: https://github.com/apache/incubator-shenyu
-- 官网地址 : https://shenyu.apache.org/
+## 参考
+- Kong 插件开发教程[通俗易懂]:
+- API 网关 Kong 实战:
+- Spring Cloud Gateway 原理介绍和应用:
diff --git a/docs/distributed-system/distributed-configuration-center.md b/docs/distributed-system/distributed-configuration-center.md
new file mode 100644
index 0000000000000000000000000000000000000000..e10ba19d9ebfbf2d044f6b6ac03cae1b4e29b580
--- /dev/null
+++ b/docs/distributed-system/distributed-configuration-center.md
@@ -0,0 +1,10 @@
+---
+title: 分布式配置中心常见问题总结(付费)
+category: 分布式
+---
+
+**分布式配置中心** 相关的面试题为我的[知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)(点击链接即可查看详细介绍以及加入方法)专属内容,已经整理到了《Java 面试指北》中。
+
+
+
+
diff --git a/docs/distributed-system/distributed-id-design.md b/docs/distributed-system/distributed-id-design.md
new file mode 100644
index 0000000000000000000000000000000000000000..c3172737e66034bf8e5e97885b5f24e982f4e400
--- /dev/null
+++ b/docs/distributed-system/distributed-id-design.md
@@ -0,0 +1,173 @@
+---
+title: 分布式ID设计指南
+category: 分布式
+---
+
+::: tip
+
+看到百度 Geek 说的一篇结合具体场景聊分布式 ID 设计的文章,感觉挺不错的。于是,我将这篇文章的部分内容整理到了这里。原文传送门:[分布式 ID 生成服务的技术原理和项目实战](https://mp.weixin.qq.com/s/bFDLb6U6EgI-DvCdLTq_QA) 。
+
+:::
+
+网上绝大多数的分布式 ID 生成服务,一般着重于技术原理剖析,很少见到根据具体的业务场景去选型 ID 生成服务的文章。
+
+本文结合一些使用场景,进一步探讨业务场景中对 ID 有哪些具体的要求。
+
+## 场景一:订单系统
+
+我们在商场买东西一码付二维码,下单生成的订单号,使用到的优惠券码,联合商品兑换券码,这些是在网上购物经常使用到的单号,那么为什么有些单号那么长,有些只有几位数?有些单号一看就知道年月日的信息,有些却看不出任何意义?下面展开分析下订单系统中不同场景的 id 服务的具体实现。
+
+### 1、一码付
+
+我们常见的一码付,指的是一个二维码可以使用支付宝或者微信进行扫码支付。
+
+二维码的本质是一个字符串。聚合码的本质就是一个链接地址。用户使用支付宝微信直接扫一个码付钱,不用担心拿支付宝扫了微信的收款码或者用微信扫了支付宝的收款码,这极大减少了用户扫码支付的时间。
+
+实现原理是当客户用 APP 扫码后,网站后台就会判断客户的扫码环境。(微信、支付宝、QQ 钱包、京东支付、云闪付等)。
+
+判断扫码环境的原理就是根据打开链接浏览器的 HTTP header。任何浏览器打开 http 链接时,请求的 header 都会有 User-Agent(UA、用户代理)信息。
+
+UA 是一个特殊字符串头,服务器依次可以识别出客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等很多信息。
+
+各渠道对应支付产品的名称不一样,一定要仔细看各支付产品的 API 介绍。
+
+1. 微信支付:JSAPI 支付支付
+2. 支付宝:手机网站支付
+3. QQ 钱包:公众号支付
+
+其本质均为在 APP 内置浏览器中实现 HTML5 支付。
+
+
+
+文库的研发同学在这个思路上,做了优化迭代。动态生成一码付的二维码预先绑定用户所选的商品信息和价格,根据用户所选的商品动态更新。这样不仅支持一码多平台调起支付,而且不用用户选择商品输入金额,即可完成订单支付的功能,很丝滑。用户在真正扫码后,服务端才通过前端获取用户 UID,结合二维码绑定的商品信息,真正的生成订单,发送支付信息到第三方(qq、微信、支付宝),第三方生成支付订单推给用户设备,从而调起支付。
+
+区别于固定的一码付,在文库的应用中,使用到了动态二维码,二维码本质是一个短网址,ID 服务提供短网址的唯一标志参数。唯一的短网址映射的 ID 绑定了商品的订单信息,技术和业务的深度结合,缩短了支付流程,提升用户的支付体验。
+
+### 2、订单号
+
+订单号在实际的业务过程中作为一个订单的唯一标识码存在,一般实现以下业务场景:
+
+1. 用户订单遇到问题,需要找客服进行协助;
+2. 对订单进行操作,如线下收款,订单核销;
+3. 下单,改单,成单,退单,售后等系统内部的订单流程处理和跟进。
+
+很多时候搜索订单相关信息的时候都是以订单 ID 作为唯一标识符,这是由于订单号的生成规则的唯一性决定的。从技术角度看,除了 ID 服务必要的特性之外,在订单号的设计上需要体现几个特性:
+
+**(1)信息安全**
+
+编号不能透露公司的运营情况,比如日销、公司流水号等信息,以及商业信息和用户手机号,身份证等隐私信息。并且不能有明显的整体规律(可以有局部规律),任意修改一个字符就能查询到另一个订单信息,这也是不允许的。
+
+类比于我们高考时候的考生编号的生成规则,一定不能是连号的,否则只需要根据顺序往下查询就能搜索到别的考生的成绩,这是绝对不可允许。
+
+**(2)部分可读**
+
+位数要便于操作,因此要求订单号的位数适中,且局部有规律。这样可以方便在订单异常,或者退货时客服查询。
+
+过长的订单号或易读性差的订单号会导致客服输入困难且易错率较高,影响用户体验的售后体验。因此在实际的业务场景中,订单号的设计通常都会适当携带一些允许公开的对使用场景有帮助的信息,如时间,星期,类型等等,这个主要根据所涉及的编号对应的使用场景来。
+
+而且像时间、星期这些自增长的属于作为订单号的设计的一部分元素,有助于解决业务累积而导致的订单号重复的问题。
+
+**(3)查询效率**
+
+常见的电商平台订单号大多是纯数字组成,兼具可读性的同时,int 类型相对 varchar 类型的查询效率更高,对在线业务更加友好。
+
+### 3、优惠券和兑换券
+
+优惠券、兑换券是运营推广最常用的促销工具之一,合理使用它们,可以让买家得到实惠,商家提升商品销量。常见场景有:
+
+1. 在文库购买【文库 VIP+QQ 音乐年卡】联合商品,支付成功后会得到 QQ 音乐年卡的兑换码,可以去 QQ 音乐 App 兑换音乐会员年卡;
+2. 疫情期间,部分地方政府发放的消费券;
+3. 瓶装饮料经常会出现输入优惠编码兑换奖品。
+
+
+
+从技术角度看,有些场景适合 ID 即时生成,比如电商平台购物领取的优惠券,只需要在用户领取时分配优惠券信息即可。有些线上线下结合的场景,比如疫情优惠券,瓶盖开奖,京东卡,超市卡这种,则需要预先生成,预先生成的券码具备以下特性:
+
+1.预先生成,在活动正式开始前提供出来进行活动预热;
+
+2.优惠券体量大,以万为单位,通常在 10 万级别以上;
+
+3.不可破解、仿制券码;
+
+4.支持用后核销;
+
+5.优惠券、兑换券属于广撒网的策略,所以利用率低,也就不适合使用数据。
+
+**库进行存储(占空间,有效的数据有少)**
+
+设计思路上,需要设计一种有效的兑换码生成策略,支持预先生成,支持校验,内容简洁,生成的兑换码都具有唯一性,那么这种策略就是一种特殊的编解码策略,按照约定的编解码规则支撑上述需求。
+
+既然是一种编解码规则,那么需要约定编码空间(也就是用户看到的组成兑换码的字符),编码空间由字符 a-z,A-Z,数字 0-9 组成,为了增强兑换码的可识别度,剔除大写字母 O 以及 I,可用字符如下所示,共 60 个字符:
+
+abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXZY0123456789
+
+之前说过,兑换码要求近可能简洁,那么设计时就需要考虑兑换码的字符数,假设上限为 12 位,而字符空间有 60 位,那么可以表示的空间范围为 60^12=130606940160000000000000(也就是可以 12 位的兑换码可以生成天量,应该够运营同学挥霍了),转换成 2 进制:
+
+1001000100000000101110011001101101110011000000000000000000000(61 位)
+
+**兑换码组成成分分析**
+
+兑换码可以预先生成,并且不需要额外的存储空间保存这些信息,每一个优惠方案都有独立的一组兑换码(指运营同学组织的每一场运营活动都有不同的兑换码,不能混合使用, 例如双 11 兑换码不能使用在双 12 活动上),每个兑换码有自己的编号,防止重复,为了保证兑换码的有效性,对兑换码的数据需要进行校验,当前兑换码的数据组成如下所示:
+
+优惠方案 ID + 兑换码序列号 i + 校验码
+
+**编码方案**
+
+1. 兑换码序列号 i,代表当前兑换码是当前活动中第 i 个兑换码,兑换码序列号的空间范围决定了优惠活动可以发行的兑换码数目,当前采用 30 位 bit 位表示,可表示范围:1073741824(10 亿个券码)。
+2. 优惠方案 ID, 代表当前优惠方案的 ID 号,优惠方案的空间范围决定了可以组织的优惠活动次数,当前采用 15 位表示,可以表示范围:32768(考虑到运营活动的频率,以及 ID 的初始值 10000,15 位足够,365 天每天有运营活动,可以使用 54 年)。
+3. 校验码,校验兑换码是否有效,主要为了快捷的校验兑换码信息的是否正确,其次可以起到填充数据的目的,增强数据的散列性,使用 13 位表示校验位,其中分为两部分,前 6 位和后 7 位。
+
+深耕业务还会有区分通用券和单独券的情况,分别具备以下特点,技术实现需要因地制宜地思考。
+
+1. 通用券:多个玩家都可以输入兑换,然后有总量限制,期限限制。
+2. 单独券:运营同学可以在后台设置兑换码的奖励物品、期限、个数,然后由后台生成兑换码的列表,兑换之后核销。
+
+## 场景二:Tracing
+
+### 1、日志跟踪
+
+在分布式服务架构下,一个 Web 请求从网关流入,有可能会调用多个服务对请求进行处理,拿到最终结果。这个过程中每个服务之间的通信又是单独的网络请求,无论请求经过的哪个服务出了故障或者处理过慢都会对前端造成影响。
+
+处理一个 Web 请求要调用的多个服务,为了能更方便的查询哪个环节的服务出现了问题,现在常用的解决方案是为整个系统引入分布式链路跟踪。
+
+
+
+在分布式链路跟踪中有两个重要的概念:跟踪(trace)和 跨度( span)。trace 是请求在分布式系统中的整个链路视图,span 则代表整个链路中不同服务内部的视图,span 组合在一起就是整个 trace 的视图。
+
+在整个请求的调用链中,请求会一直携带 traceid 往下游服务传递,每个服务内部也会生成自己的 spanid 用于生成自己的内部调用视图,并和 traceid 一起传递给下游服务。
+
+### 2、TraceId 生成规则
+
+这种场景下,生成的 ID 除了要求唯一之外,还要求生成的效率高、吞吐量大。traceid 需要具备接入层的服务器实例自主生成的能力,如果每个 trace 中的 ID 都需要请求公共的 ID 服务生成,纯纯的浪费网络带宽资源。且会阻塞用户请求向下游传递,响应耗时上升,增加了没必要的风险。所以需要服务器实例最好可以自行计算 tracid,spanid,避免依赖外部服务。
+
+产生规则:服务器 IP + ID 产生的时间 + 自增序列 + 当前进程号 ,比如:
+
+0ad1348f1403169275002100356696
+
+前 8 位 0ad1348f 即产生 TraceId 的机器的 IP,这是一个十六进制的数字,每两位代表 IP 中的一段,我们把这个数字,按每两位转成 10 进制即可得到常见的 IP 地址表示方式 10.209.52.143,您也可以根据这个规律来查找到请求经过的第一个服务器。
+
+后面的 13 位 1403169275002 是产生 TraceId 的时间。之后的 4 位 1003 是一个自增的序列,从 1000 涨到 9000,到达 9000 后回到 1000 再开始往上涨。最后的 5 位 56696 是当前的进程 ID,为了防止单机多进程出现 TraceId 冲突的情况,所以在 TraceId 末尾添加了当前的进程 ID。
+
+### 3、SpanId 生成规则
+
+span 是层的意思,比如在第一个实例算是第一层, 请求代理或者分流到下一个实例处理,就是第二层,以此类推。通过层,SpanId 代表本次调用在整个调用链路树中的位置。
+
+假设一个 服务器实例 A 接收了一次用户请求,代表是整个调用的根节点,那么 A 层处理这次请求产生的非服务调用日志记录 spanid 的值都是 0,A 层需要通过 RPC 依次调用 B、C、D 三个服务器实例,那么在 A 的日志中,SpanId 分别是 0.1,0.2 和 0.3,在 B、C、D 中,SpanId 也分别是 0.1,0.2 和 0.3;如果 C 系统在处理请求的时候又调用了 E,F 两个服务器实例,那么 C 系统中对应的 spanid 是 0.2.1 和 0.2.2,E、F 两个系统对应的日志也是 0.2.1 和 0.2.2。
+
+根据上面的描述可以知道,如果把一次调用中所有的 SpanId 收集起来,可以组成一棵完整的链路树。
+
+**spanid 的生成本质:在跨层传递透传的同时,控制大小版本号的自增来实现的。**
+
+## 场景三:短网址
+
+短网址主要功能包括网址缩短与还原两大功能。相对于长网址,短网址可以更方便地在电子邮件,社交网络,微博和手机上传播,例如原来很长的网址通过短网址服务即可生成相应的短网址,避免折行或超出字符限制。
+
+
+
+常用的 ID 生成服务比如:MySQL ID 自增、 Redis 键自增、号段模式,生成的 ID 都是一串数字。短网址服务把客户的长网址转换成短网址,
+
+实际是在 dwz.cn 域名后面拼接新产生的数字类型 ID,直接用数字 ID,网址长度也有些长,服务可以通过数字 ID 转更高进制的方式压缩长度。这种算法在短网址的技术实现上越来越多了起来,它可以进一步压缩网址长度。转进制的压缩算法在生活中有广泛的应用场景,举例:
+
+- 客户的长网址:
+- ID 映射的短网址: (演示使用,可能无法正确打开)
+- 转进制后的短网址: (演示使用,可能无法正确打开)
diff --git a/docs/distributed-system/distributed-id.md b/docs/distributed-system/distributed-id.md
index 0ec45819e551a16b9b4aa1ce2adb7bb1659ce73b..2a85c53a382cc3c2069ae27aebd34546054a4cbc 100644
--- a/docs/distributed-system/distributed-id.md
+++ b/docs/distributed-system/distributed-id.md
@@ -1,52 +1,51 @@
-# 分布式 ID
+---
+title: 分布式ID介绍&实现方案总结
+category: 分布式
+---
## 分布式 ID 介绍
-### 何为 ID?
+### 什么是 ID?
日常开发中,我们需要对系统中的各种数据使用 ID 唯一表示,比如用户 ID 对应且仅对应一个人,商品 ID 对应且仅对应一件商品,订单 ID 对应且仅对应一个订单。
-
-
我们现实生活中也有各种 ID,比如身份证 ID 对应且仅对应一个人、地址 ID 对应且仅对应
简单来说,**ID 就是数据的唯一标识**。
-### 何为分布式 ID?
+### 什么是分布式 ID?
分布式 ID 是分布式系统下的 ID。分布式 ID 不存在与现实生活中,属于计算机系统中的一个概念。
我简单举一个分库分表的例子。
-我司的一个项目,使用的是单机 MySQL 。但是,没想到的是,项目上线一个月之后,随着使用人数越来越多,整个系统的数据量将越来越大。
-
-单机 MySQL 已经没办法支撑了,需要进行分库分表(推荐 Sharding-JDBC)。
+我司的一个项目,使用的是单机 MySQL 。但是,没想到的是,项目上线一个月之后,随着使用人数越来越多,整个系统的数据量将越来越大。单机 MySQL 已经没办法支撑了,需要进行分库分表(推荐 Sharding-JDBC)。
在分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。**我们如何为不同的数据节点生成全局唯一主键呢?**
-
-
这个时候就需要生成**分布式 ID**了。
+
+
### 分布式 ID 需要满足哪些要求?
-
+
分布式 ID 作为分布式系统中必不可少的一环,很多地方都要用到分布式 ID。
一个最基本的分布式 ID 需要满足下面这些要求:
-- **全局唯一** :ID 的全局唯一性肯定是首先要满足的!
-- **高性能** : 分布式 ID 的生成速度要快,对本地资源消耗要小。
-- **高可用** :生成分布式 ID 的服务要保证可用性无限接近于 100%。
-- **方便易用** :拿来即用,使用方便,快速接入!
+- **全局唯一**:ID 的全局唯一性肯定是首先要满足的!
+- **高性能**:分布式 ID 的生成速度要快,对本地资源消耗要小。
+- **高可用**:生成分布式 ID 的服务要保证可用性无限接近于 100%。
+- **方便易用**:拿来即用,使用方便,快速接入!
除了这些之外,一个比较好的分布式 ID 还应保证:
-- **安全** :ID 中不包含敏感信息。
-- **有序递增** :如果要把 ID 存放在数据库的话,ID 的有序性可以提升数据库写入速度。并且,很多时候 ,我们还很有可能会直接通过 ID 来进行排序。
-- **有具体的业务含义** :生成的 ID 如果能有具体的业务含义,可以让定位问题以及开发更透明化(通过 ID 就能确定是哪个业务)。
-- **独立部署** :也就是分布式系统单独有一个发号器服务,专门用来生成分布式 ID。这样就生成 ID 的服务可以和业务相关的服务解耦。不过,这样同样带来了网络调用消耗增加的问题。总的来说,如果需要用到分布式 ID 的场景比较多的话,独立部署的发号器服务还是很有必要的。
+- **安全**:ID 中不包含敏感信息。
+- **有序递增**:如果要把 ID 存放在数据库的话,ID 的有序性可以提升数据库写入速度。并且,很多时候 ,我们还很有可能会直接通过 ID 来进行排序。
+- **有具体的业务含义**:生成的 ID 如果能有具体的业务含义,可以让定位问题以及开发更透明化(通过 ID 就能确定是哪个业务)。
+- **独立部署**:也就是分布式系统单独有一个发号器服务,专门用来生成分布式 ID。这样就生成 ID 的服务可以和业务相关的服务解耦。不过,这样同样带来了网络调用消耗增加的问题。总的来说,如果需要用到分布式 ID 的场景比较多的话,独立部署的发号器服务还是很有必要的。
## 分布式 ID 常见解决方案
@@ -56,7 +55,7 @@
这种方式就比较简单直白了,就是通过关系型数据库的自增主键产生来唯一的 ID。
-
+
以 MySQL 举例,我们通过下面的方式即可。
@@ -84,14 +83,14 @@ COMMIT;
插入数据这里,我们没有使用 `insert into` 而是使用 `replace into` 来插入数据,具体步骤是这样的:
-1)第一步: 尝试把数据插入到表中。
+- 第一步:尝试把数据插入到表中。
-2)第二步: 如果主键或唯一索引字段出现重复数据错误而插入失败时,先从表中删除含有重复关键字值的冲突行,然后再次尝试把数据插入到表中。
+- 第二步:如果主键或唯一索引字段出现重复数据错误而插入失败时,先从表中删除含有重复关键字值的冲突行,然后再次尝试把数据插入到表中。
这种方式的优缺点也比较明显:
-- **优点** :实现起来比较简单、ID 有序递增、存储消耗空间小
-- **缺点** : 支持的并发量不大、存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊! )、每次获取 ID 都要访问一次数据库(增加了对数据库的压力,获取速度也慢)
+- **优点**:实现起来比较简单、ID 有序递增、存储消耗空间小
+- **缺点**:支持的并发量不大、存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊! )、每次获取 ID 都要访问一次数据库(增加了对数据库的压力,获取速度也慢)
#### 数据库号段模式
@@ -103,7 +102,7 @@ COMMIT;
以 MySQL 举例,我们通过下面的方式即可。
-**1.创建一个数据库表。**
+**1. 创建一个数据库表。**
```sql
CREATE TABLE `sequence_id_generator` (
@@ -116,21 +115,21 @@ CREATE TABLE `sequence_id_generator` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```
-`current_max_id` 字段和`step`字段主要用于获取批量 ID,获取的批量 id 为: `current_max_id ~ current_max_id+step`。
+`current_max_id` 字段和`step`字段主要用于获取批量 ID,获取的批量 id 为:`current_max_id ~ current_max_id+step`。
-
+
-`version` 字段主要用于解决并发问题(乐观锁),`biz_type` 主要用于表示业余类型。
+`version` 字段主要用于解决并发问题(乐观锁),`biz_type` 主要用于表示业务类型。
-**2.先插入一行数据。**
+**2. 先插入一行数据。**
```sql
INSERT INTO `sequence_id_generator` (`id`, `current_max_id`, `step`, `version`, `biz_type`)
VALUES
- (1, 0, 100, 0, 101);
+ (1, 0, 100, 0, 101);
```
-**3.通过 SELECT 获取指定业务下的批量唯一 ID**
+**3. 通过 SELECT 获取指定业务下的批量唯一 ID**
```sql
SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `biz_type` = 101
@@ -139,11 +138,11 @@ SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `bi
结果:
```
-id current_max_id step version biz_type
-1 0 100 1 101
+id current_max_id step version biz_type
+1 0 100 0 101
```
-**4.不够用的话,更新之后重新 SELECT 即可。**
+**4. 不够用的话,更新之后重新 SELECT 即可。**
```sql
UPDATE sequence_id_generator SET current_max_id = 0+100, version=version+1 WHERE version = 0 AND `biz_type` = 101
@@ -153,8 +152,8 @@ SELECT `current_max_id`, `step`,`version` FROM `sequence_id_generator` where `bi
结果:
```
-id current_max_id step version biz_type
-1 100 100 1 101
+id current_max_id step version biz_type
+1 100 100 1 101
```
相比于数据库主键自增的方式,**数据库的号段模式对于数据库的访问次数更少,数据库压力更小。**
@@ -163,12 +162,12 @@ id current_max_id step version biz_type
**数据库号段模式的优缺点:**
-- **优点** :ID 有序递增、存储消耗空间小
-- **缺点** :存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊! )
+- **优点**:ID 有序递增、存储消耗空间小
+- **缺点**:存在数据库单点问题(可以使用数据库集群解决,不过增加了复杂度)、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊! )
#### NoSQL
-
+
一般情况下,NoSQL 方案使用 Redis 多一些。我们通过 Redis 的 `incr` 命令即可实现对 id 原子顺序递增。
@@ -181,9 +180,9 @@ OK
"2"
```
-为了提高可用性和并发,我们可以使用 Redis Cluser。Redis Cluser 是 Redis 官方提供的 Redis 集群解决方案(3.0+版本)。
+为了提高可用性和并发,我们可以使用 Redis Cluster。Redis Cluster 是 Redis 官方提供的 Redis 集群解决方案(3.0+版本)。
-除了 Redis Cluser 之外,你也可以使用开源的 Redis 集群方案[Codis](https://github.com/CodisLabs/codis) (大规模集群比如上百个节点的时候比较推荐)。
+除了 Redis Cluster 之外,你也可以使用开源的 Redis 集群方案[Codis](https://github.com/CodisLabs/codis) (大规模集群比如上百个节点的时候比较推荐)。
除了高可用和并发之外,我们知道 Redis 基于内存,我们需要持久化数据,避免重启机器或者机器故障后数据丢失。Redis 支持两种不同的持久化方式:**快照(snapshotting,RDB)**、**只追加文件(append-only file, AOF)**。 并且,Redis 4.0 开始支持 **RDB 和 AOF 的混合持久化**(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。
@@ -191,24 +190,24 @@ OK
**Redis 方案的优缺点:**
-- **优点** : 性能不错并且生成的 ID 是有序递增的
-- **缺点** : 和数据库主键自增方案的缺点类似
+- **优点**:性能不错并且生成的 ID 是有序递增的
+- **缺点**:和数据库主键自增方案的缺点类似
除了 Redis 之外,MongoDB ObjectId 经常也会被拿来当做分布式 ID 的解决方案。
-
+
MongoDB ObjectId 一共需要 12 个字节存储:
- 0~3:时间戳
-- 3~6: 代表机器 ID
+- 3~6:代表机器 ID
- 7~8:机器进程 ID
-- 9~11 :自增值
+- 9~11:自增值
**MongoDB 方案的优缺点:**
-- **优点** : 性能不错并且生成的 ID 是有序递增的
-- **缺点** : 需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID) 、有安全性问题(ID 生成有规律性)
+- **优点**:性能不错并且生成的 ID 是有序递增的
+- **缺点**:需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)、有安全性问题(ID 生成有规律性)
### 算法
@@ -225,7 +224,7 @@ UUID.randomUUID()
[RFC 4122](https://tools.ietf.org/html/rfc4122) 中关于 UUID 的示例是这样的:
-
+
我们这里重点关注一下这个 Version(版本),不同的版本对应的 UUID 的生成规则是不同的。
@@ -238,7 +237,7 @@ UUID.randomUUID()
下面是 Version 1 版本下生成的 UUID 的示例:
-
+
JDK 中通过 `UUID` 的 `randomUUID()` 方法生成的 UUID 的版本默认为 4。
@@ -262,28 +261,28 @@ int version = uuid.version();// 4
最后,我们再简单分析一下 **UUID 的优缺点** (面试的时候可能会被问到的哦!) :
-- **优点** :生成速度比较快、简单易用
-- **缺点** : 存储消耗空间大(32 个字符串,128 位) 、 不安全(基于 MAC 地址生成 UUID 的算法会造成 MAC 地址泄露)、无序(非自增)、没有具体业务含义、需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)
+- **优点**:生成速度比较快、简单易用
+- **缺点**:存储消耗空间大(32 个字符串,128 位)、 不安全(基于 MAC 地址生成 UUID 的算法会造成 MAC 地址泄露)、无序(非自增)、没有具体业务含义、需要解决重复 ID 问题(当机器时间不对的情况下,可能导致会产生重复 ID)
#### Snowflake(雪花算法)
Snowflake 是 Twitter 开源的分布式 ID 生成算法。Snowflake 由 64 bit 的二进制数字组成,这 64bit 的二进制被分成了几部分,每一部分存储的数据都有特定的含义:
-- **第 0 位**: 符号位(标识正负),始终为 0,没有用,不用管。
-- **第 1~41 位** :一共 41 位,用来表示时间戳,单位是毫秒,可以支撑 2 ^41 毫秒(约 69 年)
-- **第 42~52 位** :一共 10 位,一般来说,前 5 位表示机房 ID,后 5 位表示机器 ID(实际项目中可以根据实际情况调整)。这样就可以区分不同集群/机房的节点。
-- **第 53~64 位** :一共 12 位,用来表示序列号。 序列号为自增值,代表单台机器每毫秒能够产生的最大 ID 数(2^12 = 4096),也就是说单台机器每毫秒最多可以生成 4096 个 唯一 ID。
+- **第 0 位**:符号位(标识正负),始终为 0,没有用,不用管。
+- **第 1~41 位**:一共 41 位,用来表示时间戳,单位是毫秒,可以支撑 2 ^41 毫秒(约 69 年)
+- **第 42~52 位**:一共 10 位,一般来说,前 5 位表示机房 ID,后 5 位表示机器 ID(实际项目中可以根据实际情况调整)。这样就可以区分不同集群/机房的节点。
+- **第 53~64 位**:一共 12 位,用来表示序列号。 序列号为自增值,代表单台机器每毫秒能够产生的最大 ID 数(2^12 = 4096),也就是说单台机器每毫秒最多可以生成 4096 个 唯一 ID。
-
+
如果你想要使用 Snowflake 算法的话,一般不需要你自己再造轮子。有很多基于 Snowflake 算法的开源实现比如美团 的 Leaf、百度的 UidGenerator,并且这些开源实现对原有的 Snowflake 算法进行了优化。
另外,在实际项目中,我们一般也会对 Snowflake 算法进行改造,最常见的就是在 Snowflake 算法生成的 ID 中加入业务类型信息。
-我们再来看看 Snowflake 算法的优缺点 :
+我们再来看看 Snowflake 算法的优缺点:
-- **优点** :生成速度比较快、生成的 ID 有序递增、比较灵活(可以对 Snowflake 算法进行简单的改造比如加入业务 ID)
-- **缺点** : 需要解决重复 ID 问题(依赖时间,当机器时间不对的情况下,可能导致会产生重复 ID)。
+- **优点**:生成速度比较快、生成的 ID 有序递增、比较灵活(可以对 Snowflake 算法进行简单的改造比如加入业务 ID)
+- **缺点**:需要解决重复 ID 问题(依赖时间,当机器时间不对的情况下,可能导致会产生重复 ID)。
### 开源框架
@@ -293,21 +292,19 @@ Snowflake 是 Twitter 开源的分布式 ID 生成算法。Snowflake 由 64 bit
不过,UidGenerator 对 Snowflake(雪花算法)进行了改进,生成的唯一 ID 组成如下。
-
+
可以看出,和原始 Snowflake(雪花算法)生成的唯一 ID 的组成不太一样。并且,上面这些参数我们都可以自定义。
UidGenerator 官方文档中的介绍如下:
-
+
自 18 年后,UidGenerator 就基本没有再维护了,我这里也不过多介绍。想要进一步了解的朋友,可以看看 [UidGenerator 的官方介绍](https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md)。
#### Leaf(美团)
-**[Leaf](https://github.com/Meituan-Dianping/Leaf)** 是美团开源的一个分布式 ID 解决方案 。这个项目的名字 Leaf(树叶) 起源于德国哲学家、数学家莱布尼茨的一句话: “There are no two identical leaves in the world”(世界上没有两片相同的树叶) 。这名字起得真心挺不错的,有点文艺青年那味了!
-
-
+**[Leaf](https://github.com/Meituan-Dianping/Leaf)** 是美团开源的一个分布式 ID 解决方案 。这个项目的名字 Leaf(树叶) 起源于德国哲学家、数学家莱布尼茨的一句话:“There are no two identical leaves in the world”(世界上没有两片相同的树叶) 。这名字起得真心挺不错的,有点文艺青年那味了!
Leaf 提供了 **号段模式** 和 **Snowflake(雪花算法)** 这两种模式来生成分布式 ID。并且,它支持双号段,还解决了雪花 ID 系统时钟回拨问题。不过,时钟问题的解决需要弱依赖于 Zookeeper 。
@@ -315,7 +312,7 @@ Leaf 的诞生主要是为了解决美团各个业务线生成分布式 ID 的
Leaf 对原有的号段模式进行改进,比如它这里增加了双号段避免获取 DB 在获取号段的时候阻塞请求获取 ID 的线程。简单来说,就是我一个号段还没用完之前,我自己就主动提前去获取下一个号段(图片来自于美团官方文章:[《Leaf——美团点评分布式 ID 生成系统》](https://tech.meituan.com/2017/04/21/mt-leaf.html))。
-
+
根据项目 README 介绍,在 4C8G VM 基础上,通过公司 RPC 方式调用,QPS 压测结果近 5w/s,TP999 1ms。
@@ -327,7 +324,7 @@ Leaf 对原有的号段模式进行改进,比如它这里增加了双号段避
为了搞清楚这个问题,我们先来看看基于数据库号段模式的简单架构方案。(图片来自于 Tinyid 的官方 wiki:[《Tinyid 原理介绍》](https://github.com/didi/tinyid/wiki/tinyid%E5%8E%9F%E7%90%86%E4%BB%8B%E7%BB%8D))
-
+
在这种架构模式下,我们通过 HTTP 请求向发号器服务申请唯一 ID。负载均衡 router 会把我们的请求送往其中的一台 tinyid-server。
@@ -340,18 +337,20 @@ Leaf 对原有的号段模式进行改进,比如它这里增加了双号段避
Tinyid 的原理比较简单,其架构如下图所示:
-
+
相比于基于数据库号段模式的简单架构方案,Tinyid 方案主要做了下面这些优化:
-- **双号段缓存** :为了避免在获取新号段的情况下,程序获取唯一 ID 的速度比较慢。 Tinyid 中的号段在用到一定程度的时候,就会去异步加载下一个号段,保证内存中始终有可用号段。
-- **增加多 db 支持** :支持多个 DB,并且,每个 DB 都能生成唯一 ID,提高了可用性。
-- **增加 tinyid-client** :纯本地操作,无 HTTP 请求消耗,性能和可用性都有很大提升。
+- **双号段缓存**:为了避免在获取新号段的情况下,程序获取唯一 ID 的速度比较慢。 Tinyid 中的号段在用到一定程度的时候,就会去异步加载下一个号段,保证内存中始终有可用号段。
+- **增加多 db 支持**:支持多个 DB,并且,每个 DB 都能生成唯一 ID,提高了可用性。
+- **增加 tinyid-client**:纯本地操作,无 HTTP 请求消耗,性能和可用性都有很大提升。
Tinyid 的优缺点这里就不分析了,结合数据库号段模式的优缺点和 Tinyid 的原理就能知道。
-## 分布式 ID 生成方案总结
+## 总结
+
+通过这篇文章,我基本上已经把最常见的分布式 ID 生成方案都总结了一波。
-这篇文章中,我基本上已经把最常见的分布式 ID 生成方案都总结了一波。
+除了上面介绍的方式之外,像 ZooKeeper 这类中间件也可以帮助我们生成唯一 ID。**没有银弹,一定要结合实际项目来选择最适合自己的方案。**
-除了上面介绍的方式之外,像 ZooKeeper 这类中间件也可以帮助我们生成唯一 ID。**没有银弹,一定要结合实际项目来选择最适合自己的方案。**
\ No newline at end of file
+不过,本文主要介绍的是分布式 ID 的理论知识。在实际的面试中,面试官可能会结合具体的业务场景来考察你对分布式 ID 的设计,你可以参考这篇文章:[分布式 ID 设计指南](./distributed-id-design)(对于实际工作中分布式 ID 的设计也非常有帮助)。
diff --git a/docs/distributed-system/distributed-lock-implementations.md b/docs/distributed-system/distributed-lock-implementations.md
new file mode 100644
index 0000000000000000000000000000000000000000..e230bc8b6449201c0a7a28753b0bc21a58a75476
--- /dev/null
+++ b/docs/distributed-system/distributed-lock-implementations.md
@@ -0,0 +1,366 @@
+---
+title: 分布式锁常见实现方案总结
+category: 分布式
+---
+
+通常情况下,我们一般会选择基于 Redis 或者 ZooKeeper 实现分布式锁,Redis 用的要更多一点,我这里也先以 Redis 为例介绍分布式锁的实现。
+
+## 基于 Redis 实现分布式锁
+
+### 如何基于 Redis 实现一个最简易的分布式锁?
+
+不论是本地锁还是分布式锁,核心都在于“互斥”。
+
+在 Redis 中, `SETNX` 命令是可以帮助我们实现互斥。`SETNX` 即 **SET** if **N**ot e**X**ists (对应 Java 中的 `setIfAbsent` 方法),如果 key 不存在的话,才会设置 key 的值。如果 key 已经存在, `SETNX` 啥也不做。
+
+```bash
+> SETNX lockKey uniqueValue
+(integer) 1
+> SETNX lockKey uniqueValue
+(integer) 0
+```
+
+释放锁的话,直接通过 `DEL` 命令删除对应的 key 即可。
+
+```bash
+> DEL lockKey
+(integer) 1
+```
+
+为了防止误删到其他的锁,这里我们建议使用 Lua 脚本通过 key 对应的 value(唯一值)来判断。
+
+选用 Lua 脚本是为了保证解锁操作的原子性。因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,从而保证了锁释放操作的原子性。
+
+```lua
+// 释放锁时,先比较锁对应的 value 值是否相等,避免锁的误释放
+if redis.call("get",KEYS[1]) == ARGV[1] then
+ return redis.call("del",KEYS[1])
+else
+ return 0
+end
+```
+
+
+
+这是一种最简易的 Redis 分布式锁实现,实现方式比较简单,性能也很高效。不过,这种方式实现分布式锁存在一些问题。就比如应用程序遇到一些问题比如释放锁的逻辑突然挂掉,可能会导致锁无法被释放,进而造成共享资源无法再被其他线程/进程访问。
+
+### 为什么要给锁设置一个过期时间?
+
+为了避免锁无法被释放,我们可以想到的一个解决办法就是:**给这个 key(也就是锁) 设置一个过期时间** 。
+
+```bash
+127.0.0.1:6379> SET lockKey uniqueValue EX 3 NX
+OK
+```
+
+- **lockKey**:加锁的锁名;
+- **uniqueValue**:能够唯一标示锁的随机字符串;
+- **NX**:只有当 lockKey 对应的 key 值不存在的时候才能 SET 成功;
+- **EX**:过期时间设置(秒为单位)EX 3 标示这个锁有一个 3 秒的自动过期时间。与 EX 对应的是 PX(毫秒为单位),这两个都是过期时间设置。
+
+**一定要保证设置指定 key 的值和过期时间是一个原子操作!!!** 不然的话,依然可能会出现锁无法被释放的问题。
+
+这样确实可以解决问题,不过,这种解决办法同样存在漏洞:**如果操作共享资源的时间大于过期时间,就会出现锁提前过期的问题,进而导致分布式锁直接失效。如果锁的超时时间设置过长,又会影响到性能。**
+
+你或许在想:**如果操作共享资源的操作还未完成,锁过期时间能够自己续期就好了!**
+
+### 如何实现锁的优雅续期?
+
+对于 Java 开发的小伙伴来说,已经有了现成的解决方案:**[Redisson](https://github.com/redisson/redisson)** 。其他语言的解决方案,可以在 Redis 官方文档中找到,地址: 。
+
+
+
+Redisson 是一个开源的 Java 语言 Redis 客户端,提供了很多开箱即用的功能,不仅仅包括多种分布式锁的实现。并且,Redisson 还支持 Redis 单机、Redis Sentinel、Redis Cluster 等多种部署架构。
+
+Redisson 中的分布式锁自带自动续期机制,使用起来非常简单,原理也比较简单,其提供了一个专门用来监控和续期锁的 **Watch Dog( 看门狗)**,如果操作共享资源的线程还未执行完成的话,Watch Dog 会不断地延长锁的过期时间,进而保证锁不会因为超时而被释放。
+
+
+
+看门狗名字的由来于 `getLockWatchdogTimeout()` 方法,这个方法返回的是看门狗给锁续期的过期时间,默认为 30 秒([redisson-3.17.6](https://github.com/redisson/redisson/releases/tag/redisson-3.17.6))。
+
+```java
+//默认 30秒,支持修改
+private long lockWatchdogTimeout = 30 * 1000;
+
+public Config setLockWatchdogTimeout(long lockWatchdogTimeout) {
+ this.lockWatchdogTimeout = lockWatchdogTimeout;
+ return this;
+}
+public long getLockWatchdogTimeout() {
+ return lockWatchdogTimeout;
+}
+```
+
+`renewExpiration()` 方法包含了看门狗的主要逻辑:
+
+```java
+private void renewExpiration() {
+ //......
+ Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
+ @Override
+ public void run(Timeout timeout) throws Exception {
+ //......
+ // 异步续期,基于 Lua 脚本
+ CompletionStage future = renewExpirationAsync(threadId);
+ future.whenComplete((res, e) -> {
+ if (e != null) {
+ // 无法续期
+ log.error("Can't update lock " + getRawName() + " expiration", e);
+ EXPIRATION_RENEWAL_MAP.remove(getEntryName());
+ return;
+ }
+
+ if (res) {
+ // 递归调用实现续期
+ renewExpiration();
+ } else {
+ // 取消续期
+ cancelExpirationRenewal(null);
+ }
+ });
+ }
+ // 延迟 internalLockLeaseTime/3(默认 10s,也就是 30/3) 再调用
+ }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
+
+ ee.setTimeout(task);
+ }
+```
+
+默认情况下,每过 10 秒,看门狗就会执行续期操作,将锁的超时时间设置为 30 秒。看门狗续期前也会先判断是否需要执行续期操作,需要才会执行续期,否则取消续期操作。
+
+Watch Dog 通过调用 `renewExpirationAsync()` 方法实现锁的异步续期:
+
+```java
+protected CompletionStage renewExpirationAsync(long threadId) {
+ return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
+ // 判断是否为持锁线程,如果是就执行续期操作,就锁的过期时间设置为 30s(默认)
+ "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
+ "redis.call('pexpire', KEYS[1], ARGV[1]); " +
+ "return 1; " +
+ "end; " +
+ "return 0;",
+ Collections.singletonList(getRawName()),
+ internalLockLeaseTime, getLockName(threadId));
+}
+```
+
+可以看出, `renewExpirationAsync` 方法其实是调用 Lua 脚本实现的续期,这样做主要是为了保证续期操作的原子性。
+
+我这里以 Redisson 的分布式可重入锁 `RLock` 为例来说明如何使用 Redisson 实现分布式锁:
+
+```java
+// 1.获取指定的分布式锁对象
+RLock lock = redisson.getLock("lock");
+// 2.拿锁且不设置锁超时时间,具备 Watch Dog 自动续期机制
+lock.lock();
+// 3.执行业务
+...
+// 4.释放锁
+lock.unlock();
+```
+
+只有未指定锁超时时间,才会使用到 Watch Dog 自动续期机制。
+
+```java
+// 手动给锁设置过期时间,不具备 Watch Dog 自动续期机制
+lock.lock(10, TimeUnit.SECONDS);
+```
+
+如果使用 Redis 来实现分布式锁的话,还是比较推荐直接基于 Redisson 来做的。
+
+### 如何实现可重入锁?
+
+所谓可重入锁指的是在一个线程中可以多次获取同一把锁,比如一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法即可重入 ,而无需重新获得锁。像 Java 中的 `synchronized` 和 `ReentrantLock` 都属于可重入锁。
+
+**不可重入的分布式锁基本可以满足绝大部分业务场景了,一些特殊的场景可能会需要使用可重入的分布式锁。**
+
+可重入分布式锁的实现核心思路是线程在获取锁的时候判断是否为自己的锁,如果是的话,就不用再重新获取了。为此,我们可以为每个锁关联一个可重入计数器和一个占有它的线程。当可重入计数器大于 0 时,则锁被占有,需要判断占有该锁的线程和请求获取锁的线程是否为同一个。
+
+实际项目中,我们不需要自己手动实现,推荐使用我们上面提到的 **Redisson** ,其内置了多种类型的锁比如可重入锁(Reentrant Lock)、自旋锁(Spin Lock)、公平锁(Fair Lock)、多重锁(MultiLock)、 红锁(RedLock)、 读写锁(ReadWriteLock)。
+
+
+
+### Redis 如何解决集群情况下分布式锁的可靠性?
+
+为了避免单点故障,生产环境下的 Redis 服务通常是集群化部署的。
+
+Redis 集群下,上面介绍到的分布式锁的实现会存在一些问题。由于 Redis 集群数据同步到各个节点时是异步的,如果在 Redis 主节点获取到锁后,在没有同步到其他节点时,Redis 主节点宕机了,此时新的 Redis 主节点依然可以获取锁,所以多个应用服务就可以同时获取到锁。
+
+
+
+针对这个问题,Redis 之父 antirez 设计了 [Redlock 算法](https://redis.io/topics/distlock) 来解决。
+
+
+
+Redlock 算法的思想是让客户端向 Redis 集群中的多个独立的 Redis 实例依次请求申请加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败。
+
+即使部分 Redis 节点出现问题,只要保证 Redis 集群中有半数以上的 Redis 节点可用,分布式锁服务就是正常的。
+
+Redlock 是直接操作 Redis 节点的,并不是通过 Redis 集群操作的,这样才可以避免 Redis 集群主从切换导致的锁丢失问题。
+
+Redlock 实现比较复杂,性能比较差,发生时钟变迁的情况下还存在安全性隐患。《数据密集型应用系统设计》一书的作者 Martin Kleppmann 曾经专门发文([How to do distributed locking - Martin Kleppmann - 2016](https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html))怼过 Redlock,他认为这是一个很差的分布式锁实现。感兴趣的朋友可以看看[Redis 锁从面试连环炮聊到神仙打架](https://mp.weixin.qq.com/s?__biz=Mzg3NjU3NTkwMQ==&mid=2247505097&idx=1&sn=5c03cb769c4458350f4d4a321ad51f5a&source=41#wechat_redirect)这篇文章,有详细介绍到 antirez 和 Martin Kleppmann 关于 Redlock 的激烈辩论。
+
+实际项目中不建议使用 Redlock 算法,成本和收益不成正比。
+
+如果不是非要实现绝对可靠的分布式锁的话,其实单机版 Redis 就完全够了,实现简单,性能也非常高。如果你必须要实现一个绝对可靠的分布式锁的话,可以基于 ZooKeeper 来做,只是性能会差一些。
+
+## 基于 ZooKeeper 实现分布式锁
+
+Redis 实现分布式锁性能较高,ZooKeeper 实现分布式锁可靠性更高。实际项目中,我们应该根据业务的具体需求来选择。
+
+### 如何基于 ZooKeeper 实现分布式锁?
+
+ZooKeeper 分布式锁是基于 **临时顺序节点** 和 **Watcher(事件监听器)** 实现的。
+
+获取锁:
+
+1. 首先我们要有一个持久节点`/locks`,客户端获取锁就是在`locks`下创建临时顺序节点。
+2. 假设客户端 1 创建了`/locks/lock1`节点,创建成功之后,会判断 `lock1`是否是 `/locks` 下最小的子节点。
+3. 如果 `lock1`是最小的子节点,则获取锁成功。否则,获取锁失败。
+4. 如果获取锁失败,则说明有其他的客户端已经成功获取锁。客户端 1 并不会不停地循环去尝试加锁,而是在前一个节点比如`/locks/lock0`上注册一个事件监听器。这个监听器的作用是当前一个节点释放锁之后通知客户端 1(避免无效自旋),这样客户端 1 就加锁成功了。
+
+释放锁:
+
+1. 成功获取锁的客户端在执行完业务流程之后,会将对应的子节点删除。
+2. 成功获取锁的客户端在出现故障之后,对应的子节点由于是临时顺序节点,也会被自动删除,避免了锁无法被释放。
+3. 我们前面说的事件监听器其实监听的就是这个子节点删除事件,子节点删除就意味着锁被释放。
+
+
+
+实际项目中,推荐使用 Curator 来实现 ZooKeeper 分布式锁。Curator 是 Netflix 公司开源的一套 ZooKeeper Java 客户端框架,相比于 ZooKeeper 自带的客户端 zookeeper 来说,Curator 的封装更加完善,各种 API 都可以比较方便地使用。
+
+`Curator`主要实现了下面四种锁:
+
+- `InterProcessMutex`:分布式可重入排它锁
+- `InterProcessSemaphoreMutex`:分布式不可重入排它锁
+- `InterProcessReadWriteLock`:分布式读写锁
+- `InterProcessMultiLock`:将多个锁作为单个实体管理的容器,获取锁的时候获取所有锁,释放锁也会释放所有锁资源(忽略释放失败的锁)。
+
+```java
+CuratorFramework client = ZKUtils.getClient();
+client.start();
+// 分布式可重入排它锁
+InterProcessLock lock1 = new InterProcessMutex(client, lockPath1);
+// 分布式不可重入排它锁
+InterProcessLock lock2 = new InterProcessSemaphoreMutex(client, lockPath2);
+// 将多个锁作为一个整体
+InterProcessMultiLock lock = new InterProcessMultiLock(Arrays.asList(lock1, lock2));
+
+if (!lock.acquire(10, TimeUnit.SECONDS)) {
+ throw new IllegalStateException("不能获取多锁");
+}
+System.out.println("已获取多锁");
+System.out.println("是否有第一个锁: " + lock1.isAcquiredInThisProcess());
+System.out.println("是否有第二个锁: " + lock2.isAcquiredInThisProcess());
+try {
+ // 资源操作
+ resource.use();
+} finally {
+ System.out.println("释放多个锁");
+ lock.release();
+}
+System.out.println("是否有第一个锁: " + lock1.isAcquiredInThisProcess());
+System.out.println("是否有第二个锁: " + lock2.isAcquiredInThisProcess());
+client.close();
+```
+
+### 为什么要用临时顺序节点?
+
+每个数据节点在 ZooKeeper 中被称为 **znode**,它是 ZooKeeper 中数据的最小单元。
+
+我们通常是将 znode 分为 4 大类:
+
+- **持久(PERSISTENT)节点**:一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
+- **临时(EPHEMERAL)节点**:临时节点的生命周期是与 **客户端会话(session)** 绑定的,**会话消失则节点消失** 。并且,**临时节点只能做叶子节点** ,不能创建子节点。
+- **持久顺序(PERSISTENT_SEQUENTIAL)节点**:除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 `/node1/app0000000001`、`/node1/app0000000002` 。
+- **临时顺序(EPHEMERAL_SEQUENTIAL)节点**:除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性。
+
+可以看出,临时节点相比持久节点,最主要的是对会话失效的情况处理不一样,临时节点会话消失则对应的节点消失。这样的话,如果客户端发生异常导致没来得及释放锁也没关系,会话失效节点自动被删除,不会发生死锁的问题。
+
+使用 Redis 实现分布式锁的时候,我们是通过过期时间来避免锁无法被释放导致死锁问题的,而 ZooKeeper 直接利用临时节点的特性即可。
+
+假设不使用顺序节点的话,所有尝试获取锁的客户端都会对持有锁的子节点加监听器。当该锁被释放之后,势必会造成所有尝试获取锁的客户端来争夺锁,这样对性能不友好。使用顺序节点之后,只需要监听前一个节点就好了,对性能更友好。
+
+### 为什么要设置对前一个节点的监听?
+
+> Watcher(事件监听器),是 ZooKeeper 中的一个很重要的特性。ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。
+
+同一时间段内,可能会有很多客户端同时获取锁,但只有一个可以获取成功。如果获取锁失败,则说明有其他的客户端已经成功获取锁。获取锁失败的客户端并不会不停地循环去尝试加锁,而是在前一个节点注册一个事件监听器。
+
+这个事件监听器的作用是:**当前一个节点对应的客户端释放锁之后(也就是前一个节点被删除之后,监听的是删除事件),通知获取锁失败的客户端(唤醒等待的线程,Java 中的 `wait/notifyAll` ),让它尝试去获取锁,然后就成功获取锁了。**
+
+### 如何实现可重入锁?
+
+这里以 Curator 的 `InterProcessMutex` 对可重入锁的实现来介绍(源码地址:[InterProcessMutex.java](https://github.com/apache/curator/blob/master/curator-recipes/src/main/java/org/apache/curator/framework/recipes/locks/InterProcessMutex.java))。
+
+当我们调用 `InterProcessMutex#acquire`方法获取锁的时候,会调用`InterProcessMutex#internalLock`方法。
+
+```java
+// 获取可重入互斥锁,直到获取成功为止
+@Override
+public void acquire() throws Exception {
+ if (!internalLock(-1, null)) {
+ throw new IOException("Lost connection while trying to acquire lock: " + basePath);
+ }
+}
+```
+
+`internalLock` 方法会先获取当前请求锁的线程,然后从 `threadData`( `ConcurrentMap` 类型)中获取当前线程对应的 `lockData` 。 `lockData` 包含锁的信息和加锁的次数,是实现可重入锁的关键。
+
+第一次获取锁的时候,`lockData`为 `null`。获取锁成功之后,会将当前线程和对应的 `lockData` 放到 `threadData` 中
+
+```java
+private boolean internalLock(long time, TimeUnit unit) throws Exception {
+ // 获取当前请求锁的线程
+ Thread currentThread = Thread.currentThread();
+ // 拿对应的 lockData
+ LockData lockData = threadData.get(currentThread);
+ // 第一次获取锁的话,lockData 为 null
+ if (lockData != null) {
+ // 当前线程获取过一次锁之后
+ // 因为当前线程的锁存在, lockCount 自增后返回,实现锁重入.
+ lockData.lockCount.incrementAndGet();
+ return true;
+ }
+ // 尝试获取锁
+ String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());
+ if (lockPath != null) {
+ LockData newLockData = new LockData(currentThread, lockPath);
+ // 获取锁成功之后,将当前线程和对应的 lockData 放到 threadData 中
+ threadData.put(currentThread, newLockData);
+ return true;
+ }
+
+ return false;
+}
+```
+
+`LockData`是 `InterProcessMutex`中的一个静态内部类。
+
+```java
+private final ConcurrentMap threadData = Maps.newConcurrentMap();
+
+private static class LockData
+{
+ // 当前持有锁的线程
+ final Thread owningThread;
+ // 锁对应的子节点
+ final String lockPath;
+ // 加锁的次数
+ final AtomicInteger lockCount = new AtomicInteger(1);
+
+ private LockData(Thread owningThread, String lockPath)
+ {
+ this.owningThread = owningThread;
+ this.lockPath = lockPath;
+ }
+}
+```
+
+如果已经获取过一次锁,后面再来获取锁的话,直接就会在 `if (lockData != null)` 这里被拦下了,然后就会执行`lockData.lockCount.incrementAndGet();` 将加锁次数加 1。
+
+整个可重入锁的实现逻辑非常简单,直接在客户端判断当前线程有没有获取锁,有的话直接将加锁次数加 1 就可以了。
+
+## 总结
+
+这篇文章我们介绍了实现分布式锁的两种常见方式。至于具体选择 Redis 还是 ZooKeeper 来实现分布式锁,还是要看业务的具体需求。如果对性能要求比较高的话,建议使用 Redis 实现分布式锁。如果对可靠性要求比较高的话,建议使用 ZooKeeper 实现分布式锁。
diff --git a/docs/distributed-system/distributed-lock.md b/docs/distributed-system/distributed-lock.md
new file mode 100644
index 0000000000000000000000000000000000000000..b1277415e3b9c2ca05eb7aad82b519d2db725060
--- /dev/null
+++ b/docs/distributed-system/distributed-lock.md
@@ -0,0 +1,80 @@
+---
+title: 分布式锁介绍
+category: 分布式
+---
+
+网上有很多分布式锁相关的文章,写了一个相对简洁易懂的版本,针对面试和工作应该够用了。
+
+这篇文章我们先介绍一下分布式锁的基本概念。
+
+## 为什么需要分布式锁?
+
+在多线程环境中,如果多个线程同时访问共享资源(例如商品库存、外卖订单),会发生数据竞争,可能会导致出现脏数据或者系统问题,威胁到程序的正常运行。
+
+举个例子,假设现在有 100 个用户参与某个限时秒杀活动,每位用户限购 1 件商品,且商品的数量只有 3 个。如果不对共享资源进行互斥访问,就可能出现以下情况:
+
+- 线程 1、2、3 等多个线程同时进入抢购方法,每一个线程对应一个用户。
+- 线程 1 查询用户已经抢购的数量,发现当前用户尚未抢购且商品库存还有 1 个,因此认为可以继续执行抢购流程。
+- 线程 2 也执行查询用户已经抢购的数量,发现当前用户尚未抢购且商品库存还有 1 个,因此认为可以继续执行抢购流程。
+- 线程 1 继续执行,将库存数量减少 1 个,然后返回成功。
+- 线程 2 继续执行,将库存数量减少 1 个,然后返回成功。
+- 此时就发生了超卖问题,导致商品被多卖了一份。
+
+
+
+为了保证共享资源被安全地访问,我们需要使用互斥操作对共享资源进行保护,即同一时刻只允许一个线程访问共享资源,其他线程需要等待当前线程释放后才能访问。这样可以避免数据竞争和脏数据问题,保证程序的正确性和稳定性。
+
+**如何才能实现共享资源的互斥访问呢?** 锁是一个比较通用的解决方案,更准确点来说是悲观锁。
+
+悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,**共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程**。
+
+对于单机多线程来说,在 Java 中,我们通常使用 `ReetrantLock` 类、`synchronized` 关键字这类 JDK 自带的 **本地锁** 来控制一个 JVM 进程内的多个线程对本地共享资源的访问。
+
+下面是我对本地锁画的一张示意图。
+
+
+
+从图中可以看出,这些线程访问共享资源是互斥的,同一时刻只有一个线程可以获取到本地锁访问共享资源。
+
+分布式系统下,不同的服务/客户端通常运行在独立的 JVM 进程上。如果多个 JVM 进程共享同一份资源的话,使用本地锁就没办法实现资源的互斥访问了。于是,**分布式锁** 就诞生了。
+
+举个例子:系统的订单服务一共部署了 3 份,都对外提供服务。用户下订单之前需要检查库存,为了防止超卖,这里需要加锁以实现对检查库存操作的同步访问。由于订单服务位于不同的 JVM 进程中,本地锁在这种情况下就没办法正常工作了。我们需要用到分布式锁,这样的话,即使多个线程不在同一个 JVM 进程中也能获取到同一把锁,进而实现共享资源的互斥访问。
+
+下面是我对分布式锁画的一张示意图。
+
+
+
+从图中可以看出,这些独立的进程中的线程访问共享资源是互斥的,同一时刻只有一个线程可以获取到分布式锁访问共享资源。
+
+## 分布式锁应该具备哪些条件?
+
+一个最基本的分布式锁需要满足:
+
+- **互斥**:任意一个时刻,锁只能被一个线程持有。
+- **高可用**:锁服务是高可用的,当一个锁服务出现问题,能够自动切换到另外一个锁服务。并且,即使客户端的释放锁的代码逻辑出现问题,锁最终一定还是会被释放,不会影响其他线程对共享资源的访问。这一般是通过超时机制实现的。
+- **可重入**:一个节点获取了锁之后,还可以再次获取锁。
+
+除了上面这三个基本条件之外,一个好的分布式锁还需要满足下面这些条件:
+
+- **高性能**:获取和释放锁的操作应该快速完成,并且不应该对整个系统的性能造成过大影响。
+- **非阻塞**:如果获取不到锁,不能无限期等待,避免对系统正常运行造成影响。
+
+## 分布式锁的常见实现方式有哪些?
+
+常见分布式锁实现方案如下:
+
+- 基于关系型数据库比如 MySQL 实现分布式锁。
+- 基于分布式协调服务 ZooKeeper 实现分布式锁。
+- 基于分布式键值存储系统比如 Redis 、Etcd 实现分布式锁。
+
+关系型数据库的方式一般是通过唯一索引或者排他锁实现。不过,一般不会使用这种方式,问题太多比如性能太差、不具备锁失效机制。
+
+基于 ZooKeeper 或者 Redis 实现分布式锁这两种实现方式要用的更多一些,我专门写了一篇文章来详细介绍这两种方案:[分布式锁常见实现方案总结](./distributed-lock-implementations.md)。
+
+## 总结
+
+这篇文章我们主要介绍了:
+
+- 分布式锁的用途:分布式系统下,不同的服务/客户端通常运行在独立的 JVM 进程上。如果多个 JVM 进程共享同一份资源的话,使用本地锁就没办法实现资源的互斥访问了。
+- 分布式锁的应该具备的条件:互斥、高可用、可重入、高性能、非阻塞。
+- 分布式锁的常见实现方式:关系型数据库比如 MySQL、分布式协调服务 ZooKeeper、分布式键值存储系统比如 Redis 、Etcd 。
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-in-action.md" b/docs/distributed-system/distributed-process-coordination/zookeeper/zookeeper-in-action.md
similarity index 66%
rename from "docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-in-action.md"
rename to docs/distributed-system/distributed-process-coordination/zookeeper/zookeeper-in-action.md
index 71dad09a4a241056fbefa72695a47ebd462906a9..04e49a7d21847ab52c67a745201d2bc28547afa2 100644
--- "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-in-action.md"
+++ b/docs/distributed-system/distributed-process-coordination/zookeeper/zookeeper-in-action.md
@@ -1,14 +1,17 @@
-# ZooKeeper 实战
+---
+title: ZooKeeper 实战
+category: 分布式
+tag:
+ - ZooKeeper
+---
-## 1. 前言
-
-这篇文章简单给演示一下 ZooKeeper 常见命令的使用以及 ZooKeeper Java客户端 Curator 的基本使用。介绍到的内容都是最基本的操作,能满足日常工作的基本需要。
+这篇文章简单给演示一下 ZooKeeper 常见命令的使用以及 ZooKeeper Java 客户端 Curator 的基本使用。介绍到的内容都是最基本的操作,能满足日常工作的基本需要。
如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!
-## 2. ZooKeeper 安装和使用
+## ZooKeeper 安装
-### 2.1. 使用Docker 安装 zookeeper
+### 使用 Docker 安装 zookeeper
**a.使用 Docker 下载 ZooKeeper**
@@ -22,13 +25,13 @@ docker pull zookeeper:3.5.8
docker run -d --name zookeeper -p 2181:2181 zookeeper:3.5.8
```
-### 2.2. 连接 ZooKeeper 服务
+### 连接 ZooKeeper 服务
-**a.进入ZooKeeper容器中**
+**a.进入 ZooKeeper 容器中**
先使用 `docker ps` 查看 ZooKeeper 的 ContainerID,然后使用 `docker exec -it ContainerID /bin/bash` 命令进入容器中。
-**b.先进入 bin 目录,然后通过 `./zkCli.sh -server 127.0.0.1:2181`命令连接ZooKeeper 服务**
+**b.先进入 bin 目录,然后通过 `./zkCli.sh -server 127.0.0.1:2181`命令连接 ZooKeeper 服务**
```bash
root@eaf70fc620cb:/apache-zookeeper-3.5.8-bin# cd bin
@@ -36,15 +39,15 @@ root@eaf70fc620cb:/apache-zookeeper-3.5.8-bin# cd bin
如果你看到控制台成功打印出如下信息的话,说明你已经成功连接 ZooKeeper 服务。
-
+
-### 2.3. 常用命令演示
+## ZooKeeper 常用命令演示
-#### 2.3.1. 查看常用命令(help 命令)
+### 查看常用命令(help 命令)
通过 `help` 命令查看 ZooKeeper 常用命令
-#### 2.3.2. 创建节点(create 命令)
+### 创建节点(create 命令)
通过 `create` 命令在根目录创建了 node1 节点,与它关联的字符串是"node1"
@@ -59,17 +62,18 @@ root@eaf70fc620cb:/apache-zookeeper-3.5.8-bin# cd bin
Created /node1/node1.1
```
-#### 2.3.3. 更新节点数据内容(set 命令)
+### 更新节点数据内容(set 命令)
```shell
[zk: 127.0.0.1:2181(CONNECTED) 11] set /node1 "set node1"
```
-#### 2.3.4. 获取节点的数据(get 命令)
+### 获取节点的数据(get 命令)
`get` 命令可以获取指定节点的数据内容和节点的状态,可以看出我们通过 `set` 命令已经将节点数据内容改为 "set node1"。
```shell
+[zk: zookeeper(CONNECTED) 12] get -s /node1
set node1
cZxid = 0x47
ctime = Sun Jan 20 10:22:59 CST 2019
@@ -85,7 +89,7 @@ numChildren = 1
```
-#### 2.3.5. 查看某个目录下的子节点(ls 命令)
+### 查看某个目录下的子节点(ls 命令)
通过 `ls` 命令查看根目录下的节点
@@ -103,7 +107,7 @@ numChildren = 1
ZooKeeper 中的 ls 命令和 linux 命令中的 ls 类似, 这个命令将列出绝对路径 path 下的所有子节点信息(列出 1 级,并不递归)
-#### 2.3.6. 查看节点状态(stat 命令)
+### 查看节点状态(stat 命令)
通过 `stat` 命令查看节点状态
@@ -122,13 +126,13 @@ dataLength = 11
numChildren = 1
```
-上面显示的一些信息比如 cversion、aclVersion、numChildren 等等,我在上面 “znode(数据节点)的结构” 这部分已经介绍到。
+上面显示的一些信息比如 cversion、aclVersion、numChildren 等等,我在上面 “[ZooKeeper 相关概念总结(入门)](https://javaguide.cn/distributed-system/distributed-process-coordination/zookeeper/zookeeper-intro.html)” 这篇文章中已经介绍到。
-#### 2.3.7. 查看节点信息和状态(ls2 命令)
+### 查看节点信息和状态(ls2 命令)
-`ls2` 命令更像是 `ls` 命令和 `stat` 命令的结合。 `ls2` 命令返回的信息包括 2 部分:
+`ls2` 命令更像是 `ls` 命令和 `stat` 命令的结合。 `ls2` 命令返回的信息包括 2 部分:
-1. 子节点列表
+1. 子节点列表
2. 当前节点的 stat 信息。
```shell
@@ -148,7 +152,7 @@ numChildren = 1
```
-#### 2.3.8. 删除节点(delete 命令)
+### 删除节点(delete 命令)
这个命令很简单,但是需要注意的一点是如果你要删除某一个节点,那么这个节点必须无子节点才行。
@@ -158,15 +162,15 @@ numChildren = 1
在后面我会介绍到 Java 客户端 API 的使用以及开源 ZooKeeper 客户端 ZkClient 和 Curator 的使用。
-## 3. ZooKeeper Java客户端 Curator简单使用
+## ZooKeeper Java 客户端 Curator 简单使用
-Curator 是Netflix公司开源的一套 ZooKeeper Java客户端框架,相比于 Zookeeper 自带的客户端 zookeeper 来说,Curator 的封装更加完善,各种 API 都可以比较方便地使用。
+Curator 是 Netflix 公司开源的一套 ZooKeeper Java 客户端框架,相比于 Zookeeper 自带的客户端 zookeeper 来说,Curator 的封装更加完善,各种 API 都可以比较方便地使用。
-
+
下面我们就来简单地演示一下 Curator 的使用吧!
-Curator4.0+版本对ZooKeeper 3.5.x支持比较好。开始之前,请先将下面的依赖添加进你的项目。
+Curator4.0+版本对 ZooKeeper 3.5.x 支持比较好。开始之前,请先将下面的依赖添加进你的项目。
```xml
@@ -181,9 +185,9 @@ Curator4.0+版本对ZooKeeper 3.5.x支持比较好。开始之前,请先将下
```
-### 3.1. 连接 ZooKeeper 客户端
+### 连接 ZooKeeper 客户端
-通过 `CuratorFrameworkFactory` 创建 `CuratorFramework` 对象,然后再调用 `CuratorFramework` 对象的 `start()` 方法即可!
+通过 `CuratorFrameworkFactory` 创建 `CuratorFramework` 对象,然后再调用 `CuratorFramework` 对象的 `start()` 方法即可!
```java
private static final int BASE_SLEEP_TIME = 1000;
@@ -202,22 +206,22 @@ zkClient.start();
对于一些基本参数的说明:
- `baseSleepTimeMs`:重试之间等待的初始时间
-- `maxRetries` :最大重试次数
-- `connectString` :要连接的服务器列表
-- `retryPolicy` :重试策略
+- `maxRetries`:最大重试次数
+- `connectString`:要连接的服务器列表
+- `retryPolicy`:重试策略
-### 3.2. 数据节点的增删改查
+### 数据节点的增删改查
-#### 3.2.1. 创建节点
+#### 创建节点
-我们在 [ZooKeeper常见概念解读](./zookeeper-intro.md) 中介绍到,我们通常是将 znode 分为 4 大类:
+我们在 [ZooKeeper 常见概念解读](./zookeeper-intro.md) 中介绍到,我们通常是将 znode 分为 4 大类:
-- **持久(PERSISTENT)节点** :一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
-- **临时(EPHEMERAL)节点** :临时节点的生命周期是与 **客户端会话(session)** 绑定的,**会话消失则节点消失** 。并且,临时节点 **只能做叶子节点** ,不能创建子节点。
-- **持久顺序(PERSISTENT_SEQUENTIAL)节点** :除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 `/node1/app0000000001` 、`/node1/app0000000002` 。
-- **临时顺序(EPHEMERAL_SEQUENTIAL)节点** :除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性。
+- **持久(PERSISTENT)节点**:一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
+- **临时(EPHEMERAL)节点**:临时节点的生命周期是与 **客户端会话(session)** 绑定的,**会话消失则节点消失** 。并且,临时节点 **只能做叶子节点** ,不能创建子节点。
+- **持久顺序(PERSISTENT_SEQUENTIAL)节点**:除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 `/node1/app0000000001`、`/node1/app0000000002` 。
+- **临时顺序(EPHEMERAL_SEQUENTIAL)节点**:除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性。
-你在使用的ZooKeeper 的时候,会发现 `CreateMode` 类中实际有 7种 znode 类型 ,但是用的最多的还是上面介绍的 4 种。
+你在使用的 ZooKeeper 的时候,会发现 `CreateMode` 类中实际有 7 种 znode 类型 ,但是用的最多的还是上面介绍的 4 种。
**a.创建持久化节点**
@@ -262,7 +266,7 @@ zkClient.getData().forPath("/node1/00001");//获取节点的数据内容,获
zkClient.checkExists().forPath("/node1/00001");//不为null的话,说明节点创建成功
```
-#### 3.2.2. 删除节点
+#### 删除节点
**a.删除一个子节点**
@@ -276,7 +280,7 @@ zkClient.delete().forPath("/node1/00001");
zkClient.delete().deletingChildrenIfNeeded().forPath("/node1");
```
-#### 3.2.3. 获取/更新节点数据内容
+#### 获取/更新节点数据内容
```java
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath("/node1/00001","java".getBytes());
@@ -284,13 +288,8 @@ zkClient.getData().forPath("/node1/00001");//获取节点的数据内容
zkClient.setData().forPath("/node1/00001","c++".getBytes());//更新节点数据内容
```
-#### 3.2.4. 获取某个节点的所有子节点路径
+#### 获取某个节点的所有子节点路径
```java
List childrenPaths = zkClient.getChildren().forPath("/node1");
```
-
-
-
-
-
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-intro.md" b/docs/distributed-system/distributed-process-coordination/zookeeper/zookeeper-intro.md
similarity index 65%
rename from "docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-intro.md"
rename to docs/distributed-system/distributed-process-coordination/zookeeper/zookeeper-intro.md
index 3ead663c4b3018aaa26d6e6eabca8e1d11ee6652..f4102f61e6d1a46aa68047b09cc76ff1d81a1929 100644
--- "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-intro.md"
+++ b/docs/distributed-system/distributed-process-coordination/zookeeper/zookeeper-intro.md
@@ -1,10 +1,13 @@
-# ZooKeeper 相关概念总结(入门)
-
-## 1. 前言
+---
+title: ZooKeeper相关概念总结(入门)
+category: 分布式
+tag:
+ - ZooKeeper
+---
相信大家对 ZooKeeper 应该不算陌生。但是你真的了解 ZooKeeper 到底有啥用不?如果别人/面试官让你给他讲讲对于 ZooKeeper 的认识,你能回答到什么地步呢?
-拿我自己来说吧!我本人曾经使用 Dubbo 来做分布式项目的时候,使用了 ZooKeeper 作为注册中心。为了保证分布式系统能够同步访问某个资源,我还使用 ZooKeeper 做过分布式锁。另外,我在学习 Kafka 的时候,知道 Kafka 很多功能的实现依赖了 ZooKeeper。
+拿我自己来说吧!我本人在大学曾经使用 Dubbo 来做分布式项目的时候,使用了 ZooKeeper 作为注册中心。为了保证分布式系统能够同步访问某个资源,我还使用 ZooKeeper 做过分布式锁。另外,我在学习 Kafka 的时候,知道 Kafka 很多功能的实现依赖了 ZooKeeper。
前几天,总结项目经验的时候,我突然问自己 ZooKeeper 到底是个什么东西?想了半天,脑海中只是简单的能浮现出几句话:
@@ -20,58 +23,58 @@
_如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!_
-## 2. ZooKeeper 介绍
+## ZooKeeper 介绍
-### 2.1. ZooKeeper 由来
+### ZooKeeper 由来
正式介绍 ZooKeeper 之前,我们先来看看 ZooKeeper 的由来,还挺有意思的。
-下面这段内容摘自《从 Paxos 到 ZooKeeper 》第四章第一节,推荐大家阅读一下:
+下面这段内容摘自《从 Paxos 到 ZooKeeper》第四章第一节,推荐大家阅读一下:
> ZooKeeper 最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。
>
> 关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的 Pig 项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家 RaghuRamakrishnan 开玩笑地说:“在这样下去,我们这儿就变成动物园了!”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了,而 ZooKeeper 正好要用来进行分布式环境的协调一一于是,ZooKeeper 的名字也就由此诞生了。
-### 2.2. ZooKeeper 概览
+### ZooKeeper 概览
ZooKeeper 是一个开源的**分布式协调服务**,它的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来,构成一个高效可靠的原语集,并以一系列简单易用的接口提供给用户使用。
> **原语:** 操作系统或计算机网络用语范畴。是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性·即原语的执行必须是连续的,在执行过程中不允许被中断。
-**ZooKeeper 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。**
+ZooKeeper 为我们提供了高可用、高性能、稳定的分布式数据一致性解决方案,通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。这些功能的实现主要依赖于 ZooKeeper 提供的 **数据存储+事件监听** 功能(后文会详细介绍到) 。
+
+ZooKeeper 将数据保存在内存中,性能是不错的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景)。
-另外,**ZooKeeper 将数据保存在内存中,性能是非常棒的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。(“读”多于“写”是协调服务的典型场景)。**
+另外,很多顶级的开源项目都用到了 ZooKeeper,比如:
-### 2.3. ZooKeeper 特点
+- **Kafka** : ZooKeeper 主要为 Kafka 提供 Broker 和 Topic 的注册以及多个 Partition 的负载均衡等功能。不过,在 Kafka 2.8 之后,引入了基于 Raft 协议的 KRaft 模式,不再依赖 Zookeeper,大大简化了 Kafka 的架构。
+- **Hbase** : ZooKeeper 为 Hbase 提供确保整个集群只有一个 Master 以及保存和提供 regionserver 状态信息(是否在线)等功能。
+- **Hadoop** : ZooKeeper 为 Namenode 提供高可用支持。
+
+### ZooKeeper 特点
- **顺序一致性:** 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。
- **原子性:** 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。
-- **单一系统映像 :** 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
+- **单一系统映像:** 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。
- **可靠性:** 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。
-### 2.4. ZooKeeper 典型应用场景
+### ZooKeeper 应用场景
ZooKeeper 概览中,我们介绍到使用其通常被用于实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。
下面选 3 个典型的应用场景来专门说说:
-1. **分布式锁** : 通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁。
-2. **命名服务** :可以通过 ZooKeeper 的顺序节点生成全局唯一 ID
-3. **数据发布/订阅** :通过 **Watcher 机制** 可以很方便地实现数据发布/订阅。当你将数据发布到 ZooKeeper 被监听的节点上,其他机器可通过监听 ZooKeeper 上节点的变化来实现配置的动态更新。
+1. **命名服务**:可以通过 ZooKeeper 的顺序节点生成全局唯一 ID。
+2. **数据发布/订阅**:通过 **Watcher 机制** 可以很方便地实现数据发布/订阅。当你将数据发布到 ZooKeeper 被监听的节点上,其他机器可通过监听 ZooKeeper 上节点的变化来实现配置的动态更新。
+3. **分布式锁**:通过创建唯一节点获得分布式锁,当获得锁的一方执行完相关代码或者是挂掉之后就释放锁。分布式锁的实现也需要用到 **Watcher 机制** ,我在 [分布式锁详解](https://javaguide.cn/distributed-system/distributed-lock.html) 这篇文章中有详细介绍到如何基于 ZooKeeper 实现分布式锁。
实际上,这些功能的实现基本都得益于 ZooKeeper 可以保存数据的功能,但是 ZooKeeper 不适合保存大量数据,这一点需要注意。
-### 2.5. 有哪些著名的开源项目用到了 ZooKeeper?
-
-1. **Kafka** : ZooKeeper 主要为 Kafka 提供 Broker 和 Topic 的注册以及多个 Partition 的负载均衡等功能。
-2. **Hbase** : ZooKeeper 为 Hbase 提供确保整个集群只有一个 Master 以及保存和提供 regionserver 状态信息(是否在线)等功能。
-3. **Hadoop** : ZooKeeper 为 Namenode 提供高可用支持。
-
-## 3. ZooKeeper 重要概念解读
+## ZooKeeper 重要概念
_破音:拿出小本本,下面的内容非常重要哦!_
-### 3.1. Data model(数据模型)
+### Data model(数据模型)
ZooKeeper 数据模型采用层次化的多叉树形结构,每个节点上都可以存储数据,这些数据可以是数字、字符串或者是二级制序列。并且。每个节点还可以拥有 N 个子节点,最上层是根节点以“/”来代表。每个数据节点在 ZooKeeper 中被称为 **znode**,它是 ZooKeeper 中数据的最小单元。并且,每个 znode 都一个唯一的路径标识。
@@ -79,27 +82,23 @@ ZooKeeper 数据模型采用层次化的多叉树形结构,每个节点上都
从下图可以更直观地看出:ZooKeeper 节点路径标识方式和 Unix 文件系统路径非常相似,都是由一系列使用斜杠"/"进行分割的路径表示,开发人员可以向这个节点中写入数据,也可以在节点下面创建子节点。这些操作我们后面都会介绍到。
-
+
-### 3.2. znode(数据节点)
+### znode(数据节点)
介绍了 ZooKeeper 树形数据模型之后,我们知道每个数据节点在 ZooKeeper 中被称为 **znode**,它是 ZooKeeper 中数据的最小单元。你要存放的数据就放在上面,是你使用 ZooKeeper 过程中经常需要接触到的一个概念。
-#### 3.2.1. znode 4 种类型
-
我们通常是将 znode 分为 4 大类:
-- **持久(PERSISTENT)节点** :一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
-- **临时(EPHEMERAL)节点** :临时节点的生命周期是与 **客户端会话(session)** 绑定的,**会话消失则节点消失** 。并且,**临时节点只能做叶子节点** ,不能创建子节点。
-- **持久顺序(PERSISTENT_SEQUENTIAL)节点** :除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 `/node1/app0000000001` 、`/node1/app0000000002` 。
-- **临时顺序(EPHEMERAL_SEQUENTIAL)节点** :除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性。
-
-#### 3.2.2. znode 数据结构
+- **持久(PERSISTENT)节点**:一旦创建就一直存在即使 ZooKeeper 集群宕机,直到将其删除。
+- **临时(EPHEMERAL)节点**:临时节点的生命周期是与 **客户端会话(session)** 绑定的,**会话消失则节点消失** 。并且,**临时节点只能做叶子节点** ,不能创建子节点。
+- **持久顺序(PERSISTENT_SEQUENTIAL)节点**:除了具有持久(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 `/node1/app0000000001`、`/node1/app0000000002` 。
+- **临时顺序(EPHEMERAL_SEQUENTIAL)节点**:除了具备临时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性
每个 znode 由 2 部分组成:
-- **stat** :状态信息
-- **data** : 节点存放的数据的具体内容
+- **stat**:状态信息
+- **data**:节点存放的数据的具体内容
如下所示,我通过 get 命令来获取 根目录下的 dubbo 节点的内容。(get 命令在下面会介绍到)。
@@ -121,40 +120,40 @@ dataLength = 0
numChildren = 1
```
-Stat 类中包含了一个数据节点的所有状态信息的字段,包括事务 ID-cZxid、节点创建时间-ctime 和子节点个数-numChildren 等等。
+Stat 类中包含了一个数据节点的所有状态信息的字段,包括事务 ID(cZxid)、节点创建时间(ctime) 和子节点个数(numChildren) 等等。
-下面我们来看一下每个 znode 状态信息究竟代表的是什么吧!(下面的内容来源于《从 Paxos 到 ZooKeeper 分布式一致性原理与实践》,因为 Guide 确实也不是特别清楚,要学会参考资料的嘛! ) :
+下面我们来看一下每个 znode 状态信息究竟代表的是什么吧!(下面的内容来源于《从 Paxos 到 ZooKeeper 分布式一致性原理与实践》,因为 Guide 确实也不是特别清楚,要学会参考资料的嘛! ):
-| znode 状态信息 | 解释 |
-| -------------- | ------------------------------------------------------------ |
-| cZxid | create ZXID,即该数据节点被创建时的事务 id |
-| ctime | create time,即该节点的创建时间 |
-| mZxid | modified ZXID,即该节点最终一次更新时的事务 id |
-| mtime | modified time,即该节点最后一次的更新时间 |
+| znode 状态信息 | 解释 |
+| -------------- | --------------------------------------------------------------------------------------------------- |
+| cZxid | create ZXID,即该数据节点被创建时的事务 id |
+| ctime | create time,即该节点的创建时间 |
+| mZxid | modified ZXID,即该节点最终一次更新时的事务 id |
+| mtime | modified time,即该节点最后一次的更新时间 |
| pZxid | 该节点的子节点列表最后一次修改时的事务 id,只有子节点列表变更才会更新 pZxid,子节点内容变更不会更新 |
-| cversion | 子节点版本号,当前节点的子节点每次变化时值增加 1 |
-| dataVersion | 数据节点内容版本号,节点创建时为 0,每更新一次节点内容(不管内容有无变化)该版本号的值增加 1 |
-| aclVersion | 节点的 ACL 版本号,表示该节点 ACL 信息变更次数 |
-| ephemeralOwner | 创建该临时节点的会话的 sessionId;如果当前节点为持久节点,则 ephemeralOwner=0 |
-| dataLength | 数据节点内容长度 |
-| numChildren | 当前节点的子节点个数 |
+| cversion | 子节点版本号,当前节点的子节点每次变化时值增加 1 |
+| dataVersion | 数据节点内容版本号,节点创建时为 0,每更新一次节点内容(不管内容有无变化)该版本号的值增加 1 |
+| aclVersion | 节点的 ACL 版本号,表示该节点 ACL 信息变更次数 |
+| ephemeralOwner | 创建该临时节点的会话的 sessionId;如果当前节点为持久节点,则 ephemeralOwner=0 |
+| dataLength | 数据节点内容长度 |
+| numChildren | 当前节点的子节点个数 |
-### 3.3. 版本(version)
+### 版本(version)
在前面我们已经提到,对应于每个 znode,ZooKeeper 都会为其维护一个叫作 **Stat** 的数据结构,Stat 中记录了这个 znode 的三个相关的版本:
-- **dataVersion** :当前 znode 节点的版本号
-- **cversion** : 当前 znode 子节点的版本
-- **aclVersion** : 当前 znode 的 ACL 的版本。
+- **dataVersion**:当前 znode 节点的版本号
+- **cversion**:当前 znode 子节点的版本
+- **aclVersion**:当前 znode 的 ACL 的版本。
-### 3.4. ACL(权限控制)
+### ACL(权限控制)
ZooKeeper 采用 ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。
对于 znode 操作的权限,ZooKeeper 提供了以下 5 种:
- **CREATE** : 能创建子节点
-- **READ** :能获取节点数据和列出其子节点
+- **READ**:能获取节点数据和列出其子节点
- **WRITE** : 能设置/更新节点数据
- **DELETE** : 能删除子节点
- **ADMIN** : 能设置节点 ACL 的权限
@@ -163,20 +162,20 @@ ZooKeeper 采用 ACL(AccessControlLists)策略来进行权限控制,类似
对于身份认证,提供了以下几种方式:
-- **world** : 默认方式,所有用户都可无条件访问。
+- **world**:默认方式,所有用户都可无条件访问。
- **auth** :不使用任何 id,代表任何已认证的用户。
-- **digest** :用户名:密码认证方式: _username:password_ 。
+- **digest** :用户名:密码认证方式:_username:password_ 。
- **ip** : 对指定 ip 进行限制。
-### 3.5. Watcher(事件监听器)
+### Watcher(事件监听器)
Watcher(事件监听器),是 ZooKeeper 中的一个很重要的特性。ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知到感兴趣的客户端上去,该机制是 ZooKeeper 实现分布式协调服务的重要特性。
-
+
-_破音:非常有用的一个特性,都能出小本本记好了,后面用到 ZooKeeper 基本离不开 Watcher(事件监听器)机制。_
+_破音:非常有用的一个特性,都拿出小本本记好了,后面用到 ZooKeeper 基本离不开 Watcher(事件监听器)机制。_
-### 3.6. 会话(Session)
+### 会话(Session)
Session 可以看作是 ZooKeeper 服务器与客户端的之间的一个 TCP 长连接,通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,也能够向 ZooKeeper 服务器发送请求并接受响应,同时还能够通过该连接接收来自服务器的 Watcher 事件通知。
@@ -184,48 +183,49 @@ Session 有一个属性叫做:`sessionTimeout` ,`sessionTimeout` 代表会
另外,在为客户端创建会话之前,服务端首先会为每个客户端都分配一个 `sessionID`。由于 `sessionID`是 ZooKeeper 会话的一个重要标识,许多与会话相关的运行机制都是基于这个 `sessionID` 的,因此,无论是哪台服务器为客户端分配的 `sessionID`,都务必保证全局唯一。
-## 4. ZooKeeper 集群
+## ZooKeeper 集群
为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。通常 3 台服务器就可以构成一个 ZooKeeper 集群了。ZooKeeper 官方提供的架构图就是一个 ZooKeeper 集群整体对外提供服务。
-
+
上图中每一个 Server 代表一个安装 ZooKeeper 服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 ZAB 协议(ZooKeeper Atomic Broadcast)来保持数据的一致性。
-**最典型集群模式: Master/Slave 模式(主备模式)**。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。
+**最典型集群模式:Master/Slave 模式(主备模式)**。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。
-### 4.1. ZooKeeper 集群角色
+### ZooKeeper 集群角色
但是,在 ZooKeeper 中没有选择传统的 Master/Slave 概念,而是引入了 Leader、Follower 和 Observer 三种角色。如下图所示
-
+
ZooKeeper 集群中的所有机器通过一个 **Leader 选举过程** 来选定一台称为 “**Leader**” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,**Follower** 和 **Observer** 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。
-| 角色 | 说明 |
-| -------- | ------------------------------------------------------------ |
-| Leader | 为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。 |
-| Follower | 为客户端提供读服务,如果是写服务则转发给 Leader。参与选举过程中的投票。 |
+| 角色 | 说明 |
+| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Leader | 为客户端提供读和写的服务,负责投票的发起和决议,更新系统状态。 |
+| Follower | 为客户端提供读服务,如果是写服务则转发给 Leader。参与选举过程中的投票。 |
| Observer | 为客户端提供读服务,如果是写服务则转发给 Leader。不参与选举过程中的投票,也不参与“过半写成功”策略。在不影响写性能的情况下提升集群的读性能。此角色于 ZooKeeper3.3 系列新增的角色。 |
+### ZooKeeper 集群 Leader 选举过程
+
当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,就会进入 Leader 选举过程,这个过程会选举产生新的 Leader 服务器。
这个过程大致是这样的:
1. **Leader election(选举阶段)**:节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。
-2. **Discovery(发现阶段)** :在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。
-3. **Synchronization(同步阶段)** :同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后
- 准 leader 才会成为真正的 leader。
+2. **Discovery(发现阶段)**:在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。
+3. **Synchronization(同步阶段)** :同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后准 leader 才会成为真正的 leader。
4. **Broadcast(广播阶段)** :到了这个阶段,ZooKeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。
-### 4.2. ZooKeeper 集群中的服务器状态
+ZooKeeper 集群中的服务器状态有下面几种:
-- **LOOKING** :寻找 Leader。
-- **LEADING** :Leader 状态,对应的节点为 Leader。
-- **FOLLOWING** :Follower 状态,对应的节点为 Follower。
-- **OBSERVING** :Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举。
+- **LOOKING**:寻找 Leader。
+- **LEADING**:Leader 状态,对应的节点为 Leader。
+- **FOLLOWING**:Follower 状态,对应的节点为 Follower。
+- **OBSERVING**:Observer 状态,对应节点为 Observer,该节点不参与 Leader 选举。
-### 4.3. ZooKeeper 集群为啥最好奇数台?
+### ZooKeeper 集群为啥最好奇数台?
ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooKeeper 服务器个数大于宕掉的个数的话整个 ZooKeeper 才依然可用。假如我们的集群中有 n 台 ZooKeeper 服务器,那么也就是剩下的服务数必须大于 n/2。先说一下结论,2n 和 2n-1 的容忍度是一样的,都是 n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学问题了。
@@ -234,7 +234,7 @@ ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooK
综上,何必增加那一个不必要的 ZooKeeper 呢?
-### 4.4. ZooKeeper 选举的过半机制防止脑裂
+### ZooKeeper 选举的过半机制防止脑裂
**何为集群脑裂?**
@@ -246,27 +246,28 @@ ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooK
ZooKeeper 的过半机制导致不可能产生 2 个 leader,因为少于等于一半是不可能产生 leader 的,这就使得不论机房的机器如何分配都不可能发生脑裂。
-## 5. ZAB 协议和 Paxos 算法
+## ZAB 协议和 Paxos 算法
Paxos 算法应该可以说是 ZooKeeper 的灵魂了。但是,ZooKeeper 并没有完全采用 Paxos 算法 ,而是使用 ZAB 协议作为其保证数据一致性的核心算法。另外,在 ZooKeeper 的官方文档中也指出,ZAB 协议并不像 Paxos 算法那样,是一种通用的分布式一致性算法,它是一种特别为 Zookeeper 设计的崩溃可恢复的原子消息广播算法。
-### 5.1. ZAB 协议介绍
+### ZAB 协议介绍
ZAB(ZooKeeper Atomic Broadcast 原子广播) 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。 在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
-### 5.2. ZAB 协议两种基本的模式:崩溃恢复和消息广播
+### ZAB 协议两种基本的模式:崩溃恢复和消息广播
ZAB 协议包括两种基本的模式,分别是
-- **崩溃恢复** :当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。其中,**所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致**。
-- **消息广播** :**当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。** 当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。
+- **崩溃恢复**:当整个服务框架在启动过程中,或是当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进入恢复模式并选举产生新的 Leader 服务器。当选举产生了新的 Leader 服务器,同时集群中已经有过半的机器与该 Leader 服务器完成了状态同步之后,ZAB 协议就会退出恢复模式。其中,**所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和 Leader 服务器的数据状态保持一致**。
+- **消息广播**:**当集群中已经有过半的 Follower 服务器完成了和 Leader 服务器的状态同步,那么整个服务框架就可以进入消息广播模式了。** 当一台同样遵守 ZAB 协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个 Leader 服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入数据恢复模式:找到 Leader 所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中去。
-关于 **ZAB 协议&Paxos 算法** 需要讲和理解的东西太多了,具体可以看下面这两篇文章:
+关于 **ZAB 协议&Paxos 算法** 需要讲和理解的东西太多了,具体可以看下面这几篇文章:
-- [图解 Paxos 一致性协议](http://codemacro.com/2014/10/15/explain-poxos/)
+- [Paxos 算法详解](https://javaguide.cn/distributed-system/protocol/paxos-algorithm.html)
- [Zookeeper ZAB 协议分析](https://dbaplus.cn/news-141-1875-1.html)
+- [Raft 算法详解](https://javaguide.cn/distributed-system/protocol/raft-algorithm.html)
-## 6. 总结
+## 总结
1. ZooKeeper 本身就是一个分布式程序(只要半数以上节点存活,ZooKeeper 就能正常服务)。
2. 为了保证高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。
@@ -275,6 +276,6 @@ ZAB 协议包括两种基本的模式,分别是
5. ZooKeeper 有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个 znode 被创建了,除非主动进行 znode 的移除操作,否则这个 znode 将一直保存在 ZooKeeper 上。
6. ZooKeeper 底层其实只提供了两个功能:① 管理(存储、读取)用户程序提交的数据;② 为用户程序提供数据节点监听服务。
-## 7. 参考
+## 参考
-1. 《从 Paxos 到 ZooKeeper 分布式一致性原理与实践》
+- 《从 Paxos 到 ZooKeeper 分布式一致性原理与实践》
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-plus.md" b/docs/distributed-system/distributed-process-coordination/zookeeper/zookeeper-plus.md
similarity index 62%
rename from "docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-plus.md"
rename to docs/distributed-system/distributed-process-coordination/zookeeper/zookeeper-plus.md
index 11f2ac5a1a895f3d950bec4228f993a9a79a70bc..a791b2dfc7bfcb1a15f6b107d52f04e880407603 100644
--- "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/zookeeper-plus.md"
+++ b/docs/distributed-system/distributed-process-coordination/zookeeper/zookeeper-plus.md
@@ -1,68 +1,63 @@
-# ZooKeeper 相关概念总结(进阶)
+---
+title: ZooKeeper相关概念总结(进阶)
+category: 分布式
+tag:
+ - ZooKeeper
+---
> [FrancisQ](https://juejin.im/user/5c33853851882525ea106810) 投稿。
-## 1. 好久不见
-
-离上一篇文章的发布也快一个月了,想想已经快一个月没写东西了,其中可能有期末考试、课程设计和驾照考试,但这都不是借口!
-
-一到冬天就懒的不行,望广大掘友督促我🙄🙄✍️✍️。
-
-> 文章很长,先赞后看,养成习惯。❤️ 🧡 💛 💚 💙 💜
-
-## 2. 什么是ZooKeeper
+## 什么是 ZooKeeper
`ZooKeeper` 由 `Yahoo` 开发,后来捐赠给了 `Apache` ,现已成为 `Apache` 顶级项目。`ZooKeeper` 是一个开源的分布式应用程序协调服务器,其为分布式系统提供一致性服务。其一致性是通过基于 `Paxos` 算法的 `ZAB` 协议完成的。其主要功能包括:配置维护、分布式同步、集群管理、分布式事务等。
-
-
简单来说, `ZooKeeper` 是一个 **分布式协调服务框架** 。分布式?协调服务?这啥玩意?🤔🤔
-其实解释到分布式这个概念的时候,我发现有些同学并不是能把 **分布式和集群 **这两个概念很好的理解透。前段时间有同学和我探讨起分布式的东西,他说分布式不就是加机器吗?一台机器不够用再加一台抗压呗。当然加机器这种说法也无可厚非,你一个分布式系统必定涉及到多个机器,但是你别忘了,计算机学科中还有一个相似的概念—— `Cluster` ,集群不也是加机器吗?但是 集群 和 分布式 其实就是两个完全不同的概念。
+其实解释到分布式这个概念的时候,我发现有些同学并不是能把 **分布式和集群** 这两个概念很好的理解透。前段时间有同学和我探讨起分布式的东西,他说分布式不就是加机器吗?一台机器不够用再加一台抗压呗。当然加机器这种说法也无可厚非,你一个分布式系统必定涉及到多个机器,但是你别忘了,计算机学科中还有一个相似的概念—— `Cluster` ,集群不也是加机器吗?但是 集群 和 分布式 其实就是两个完全不同的概念。
比如,我现在有一个秒杀服务,并发量太大单机系统承受不住,那我加几台服务器也 **一样** 提供秒杀服务,这个时候就是 **`Cluster` 集群** 。
-
+
-但是,我现在换一种方式,我将一个秒杀服务 **拆分成多个子服务** ,比如创建订单服务,增加积分服务,扣优惠券服务等等,**然后我将这些子服务都部署在不同的服务器上** ,这个时候就是 **`Distributed` 分布式** 。
+但是,我现在换一种方式,我将一个秒杀服务 **拆分成多个子服务** ,比如创建订单服务,增加积分服务,扣优惠券服务等等,**然后我将这些子服务都部署在不同的服务器上** ,这个时候就是 **`Distributed` 分布式** 。
-
+
而我为什么反驳同学所说的分布式就是加机器呢?因为我认为加机器更加适用于构建集群,因为它真是只有加机器。而对于分布式来说,你首先需要将业务进行拆分,然后再加机器(不仅仅是加机器那么简单),同时你还要去解决分布式带来的一系列问题。
-
+
比如各个分布式组件如何协调起来,如何减少各个系统之间的耦合度,分布式事务的处理,如何去配置整个分布式系统等等。`ZooKeeper` 主要就是解决这些问题的。
-## 3. 一致性问题
+## 一致性问题
设计一个分布式系统必定会遇到一个问题—— **因为分区容忍性(partition tolerance)的存在,就必定要求我们需要在系统可用性(availability)和数据一致性(consistency)中做出权衡** 。这就是著名的 `CAP` 定理。
理解起来其实很简单,比如说把一个班级作为整个系统,而学生是系统中的一个个独立的子系统。这个时候班里的小红小明偷偷谈恋爱被班里的大嘴巴小花发现了,小花欣喜若狂告诉了周围的人,然后小红小明谈恋爱的消息在班级里传播起来了。当在消息的传播(散布)过程中,你抓到一个同学问他们的情况,如果回答你不知道,那么说明整个班级系统出现了数据不一致的问题(因为小花已经知道这个消息了)。而如果他直接不回答你,因为整个班级有消息在进行传播(为了保证一致性,需要所有人都知道才可提供服务),这个时候就出现了系统的可用性问题。
-
+
-而上述前者就是 `Eureka` 的处理方式,它保证了AP(可用性),后者就是我们今天所要讲的 `ZooKeeper` 的处理方式,它保证了CP(数据一致性)。
+而上述前者就是 `Eureka` 的处理方式,它保证了 AP(可用性),后者就是我们今天所要讲的 `ZooKeeper` 的处理方式,它保证了 CP(数据一致性)。
-## 4. 一致性协议和算法
+## 一致性协议和算法
-而为了解决数据一致性问题,在科学家和程序员的不断探索中,就出现了很多的一致性协议和算法。比如 2PC(两阶段提交),3PC(三阶段提交),Paxos算法等等。
+而为了解决数据一致性问题,在科学家和程序员的不断探索中,就出现了很多的一致性协议和算法。比如 2PC(两阶段提交),3PC(三阶段提交),Paxos 算法等等。
这时候请你思考一个问题,同学之间如果采用传纸条的方式去传播消息,那么就会出现一个问题——我咋知道我的小纸条有没有传到我想要传递的那个人手中呢?万一被哪个小家伙给劫持篡改了呢,对吧?
-
+
这个时候就引申出一个概念—— **拜占庭将军问题** 。它意指 **在不可靠信道上试图通过消息传递的方式达到一致性是不可能的**, 所以所有的一致性算法的 **必要前提** 就是安全可靠的消息通道。
而为什么要去解决数据一致性的问题?你想想,如果一个秒杀系统将服务拆分成了下订单和加积分服务,这两个服务部署在不同的机器上了,万一在消息的传播过程中积分系统宕机了,总不能你这边下了订单却没加积分吧?你总得保证两边的数据需要一致吧?
-### 4.1. 2PC(两阶段提交)
+### 2PC(两阶段提交)
两阶段提交是一种保证分布式系统数据一致性的协议,现在很多数据库都是采用的两阶段提交协议来完成 **分布式事务** 的处理。
-在介绍2PC之前,我们先来想想分布式事务到底有什么问题呢?
+在介绍 2PC 之前,我们先来想想分布式事务到底有什么问题呢?
-还拿秒杀系统的下订单和加积分两个系统来举例吧(我想你们可能都吐了🤮🤮🤮),我们此时下完订单会发个消息给积分系统告诉它下面该增加积分了。如果我们仅仅是发送一个消息也不收回复,那么我们的订单系统怎么能知道积分系统的收到消息的情况呢?如果我们增加一个收回复的过程,那么当积分系统收到消息后返回给订单系统一个 `Response` ,但在中间出现了网络波动,那个回复消息没有发送成功,订单系统是不是以为积分系统消息接收失败了?它是不是会回滚事务?但此时积分系统是成功收到消息的,它就会去处理消息然后给用户增加积分,这个时候就会出现积分加了但是订单没下成功。
+还拿秒杀系统的下订单和加积分两个系统来举例吧(我想你们可能都吐了 🤮🤮🤮),我们此时下完订单会发个消息给积分系统告诉它下面该增加积分了。如果我们仅仅是发送一个消息也不收回复,那么我们的订单系统怎么能知道积分系统的收到消息的情况呢?如果我们增加一个收回复的过程,那么当积分系统收到消息后返回给订单系统一个 `Response` ,但在中间出现了网络波动,那个回复消息没有发送成功,订单系统是不是以为积分系统消息接收失败了?它是不是会回滚事务?但此时积分系统是成功收到消息的,它就会去处理消息然后给用户增加积分,这个时候就会出现积分加了但是订单没下成功。
所以我们所需要解决的是在分布式系统中,整个调用链中,我们所有服务的数据处理要么都成功要么都失败,即所有服务的 **原子性问题** 。
@@ -76,130 +71,128 @@
而如果在第一阶段并不是所有参与者都返回了准备好了的消息,那么此时协调者将会给所有参与者发送 **回滚事务的 `rollback` 请求**,参与者收到之后将会 **回滚它在第一阶段所做的事务处理** ,然后再将处理情况返回给协调者,最终协调者收到响应后便给事务发起者返回处理失败的结果。
-
+
个人觉得 2PC 实现得还是比较鸡肋的,因为事实上它只解决了各个事务的原子性问题,随之也带来了很多的问题。
-
+
-* **单点故障问题**,如果协调者挂了那么整个系统都处于不可用的状态了。
-* **阻塞问题**,即当协调者发送 `prepare` 请求,参与者收到之后如果能处理那么它将会进行事务的处理但并不提交,这个时候会一直占用着资源不释放,如果此时协调者挂了,那么这些资源都不会再释放了,这会极大影响性能。
-* **数据不一致问题**,比如当第二阶段,协调者只发送了一部分的 `commit` 请求就挂了,那么也就意味着,收到消息的参与者会进行事务的提交,而后面没收到的则不会进行事务提交,那么这时候就会产生数据不一致性问题。
+- **单点故障问题**,如果协调者挂了那么整个系统都处于不可用的状态了。
+- **阻塞问题**,即当协调者发送 `prepare` 请求,参与者收到之后如果能处理那么它将会进行事务的处理但并不提交,这个时候会一直占用着资源不释放,如果此时协调者挂了,那么这些资源都不会再释放了,这会极大影响性能。
+- **数据不一致问题**,比如当第二阶段,协调者只发送了一部分的 `commit` 请求就挂了,那么也就意味着,收到消息的参与者会进行事务的提交,而后面没收到的则不会进行事务提交,那么这时候就会产生数据不一致性问题。
-### 4.2. 3PC(三阶段提交)
+### 3PC(三阶段提交)
-因为2PC存在的一系列问题,比如单点,容错机制缺陷等等,从而产生了 **3PC(三阶段提交)** 。那么这三阶段又分别是什么呢?
+因为 2PC 存在的一系列问题,比如单点,容错机制缺陷等等,从而产生了 **3PC(三阶段提交)** 。那么这三阶段又分别是什么呢?
-> 千万不要吧PC理解成个人电脑了,其实他们是 phase-commit 的缩写,即阶段提交。
+> 千万不要吧 PC 理解成个人电脑了,其实他们是 phase-commit 的缩写,即阶段提交。
-1. **CanCommit阶段**:协调者向所有参与者发送 `CanCommit` 请求,参与者收到请求后会根据自身情况查看是否能执行事务,如果可以则返回 YES 响应并进入预备状态,否则返回 NO 。
-2. **PreCommit阶段**:协调者根据参与者返回的响应来决定是否可以进行下面的 `PreCommit` 操作。如果上面参与者返回的都是 YES,那么协调者将向所有参与者发送 `PreCommit` 预提交请求,**参与者收到预提交请求后,会进行事务的执行操作,并将 `Undo` 和 `Redo` 信息写入事务日志中** ,最后如果参与者顺利执行了事务则给协调者返回成功的响应。如果在第一阶段协调者收到了 **任何一个 NO** 的信息,或者 **在一定时间内** 并没有收到全部的参与者的响应,那么就会中断事务,它会向所有参与者发送中断请求(abort),参与者收到中断请求之后会立即中断事务,或者在一定时间内没有收到协调者的请求,它也会中断事务。
-3. **DoCommit阶段**:这个阶段其实和 `2PC` 的第二阶段差不多,如果协调者收到了所有参与者在 `PreCommit` 阶段的 YES 响应,那么协调者将会给所有参与者发送 `DoCommit` 请求,**参与者收到 `DoCommit` 请求后则会进行事务的提交工作**,完成后则会给协调者返回响应,协调者收到所有参与者返回的事务提交成功的响应之后则完成事务。若协调者在 `PreCommit` 阶段 **收到了任何一个 NO 或者在一定时间内没有收到所有参与者的响应** ,那么就会进行中断请求的发送,参与者收到中断请求后则会 **通过上面记录的回滚日志** 来进行事务的回滚操作,并向协调者反馈回滚状况,协调者收到参与者返回的消息后,中断事务。
+1. **CanCommit 阶段**:协调者向所有参与者发送 `CanCommit` 请求,参与者收到请求后会根据自身情况查看是否能执行事务,如果可以则返回 YES 响应并进入预备状态,否则返回 NO 。
+2. **PreCommit 阶段**:协调者根据参与者返回的响应来决定是否可以进行下面的 `PreCommit` 操作。如果上面参与者返回的都是 YES,那么协调者将向所有参与者发送 `PreCommit` 预提交请求,**参与者收到预提交请求后,会进行事务的执行操作,并将 `Undo` 和 `Redo` 信息写入事务日志中** ,最后如果参与者顺利执行了事务则给协调者返回成功的响应。如果在第一阶段协调者收到了 **任何一个 NO** 的信息,或者 **在一定时间内** 并没有收到全部的参与者的响应,那么就会中断事务,它会向所有参与者发送中断请求(abort),参与者收到中断请求之后会立即中断事务,或者在一定时间内没有收到协调者的请求,它也会中断事务。
+3. **DoCommit 阶段**:这个阶段其实和 `2PC` 的第二阶段差不多,如果协调者收到了所有参与者在 `PreCommit` 阶段的 YES 响应,那么协调者将会给所有参与者发送 `DoCommit` 请求,**参与者收到 `DoCommit` 请求后则会进行事务的提交工作**,完成后则会给协调者返回响应,协调者收到所有参与者返回的事务提交成功的响应之后则完成事务。若协调者在 `PreCommit` 阶段 **收到了任何一个 NO 或者在一定时间内没有收到所有参与者的响应** ,那么就会进行中断请求的发送,参与者收到中断请求后则会 **通过上面记录的回滚日志** 来进行事务的回滚操作,并向协调者反馈回滚状况,协调者收到参与者返回的消息后,中断事务。
-
+
-> 这里是 `3PC` 在成功的环境下的流程图,你可以看到 `3PC` 在很多地方进行了超时中断的处理,比如协调者在指定时间内为收到全部的确认消息则进行事务中断的处理,这样能 **减少同步阻塞的时间** 。还有需要注意的是,**`3PC` 在 `DoCommit` 阶段参与者如未收到协调者发送的提交事务的请求,它会在一定时间内进行事务的提交**。为什么这么做呢?是因为这个时候我们肯定**保证了在第一阶段所有的协调者全部返回了可以执行事务的响应**,这个时候我们有理由**相信其他系统都能进行事务的执行和提交**,所以**不管**协调者有没有发消息给参与者,进入第三阶段参与者都会进行事务的提交操作。
+> 这里是 `3PC` 在成功的环境下的流程图,你可以看到 `3PC` 在很多地方进行了超时中断的处理,比如协调者在指定时间内未收到全部的确认消息则进行事务中断的处理,这样能 **减少同步阻塞的时间** 。还有需要注意的是,**`3PC` 在 `DoCommit` 阶段参与者如未收到协调者发送的提交事务的请求,它会在一定时间内进行事务的提交**。为什么这么做呢?是因为这个时候我们肯定**保证了在第一阶段所有的协调者全部返回了可以执行事务的响应**,这个时候我们有理由**相信其他系统都能进行事务的执行和提交**,所以**不管**协调者有没有发消息给参与者,进入第三阶段参与者都会进行事务的提交操作。
-总之,`3PC` 通过一系列的超时机制很好的缓解了阻塞问题,但是最重要的一致性并没有得到根本的解决,比如在 `PreCommit` 阶段,当一个参与者收到了请求之后其他参与者和协调者挂了或者出现了网络分区,这个时候收到消息的参与者都会进行事务提交,这就会出现数据不一致性问题。
+总之,`3PC` 通过一系列的超时机制很好的缓解了阻塞问题,但是最重要的一致性并没有得到根本的解决,比如在 `DoCommit` 阶段,当一个参与者收到了请求之后其他参与者和协调者挂了或者出现了网络分区,这个时候收到消息的参与者都会进行事务提交,这就会出现数据不一致性问题。
-所以,要解决一致性问题还需要靠 `Paxos` 算法⭐️ ⭐️ ⭐️ 。
+所以,要解决一致性问题还需要靠 `Paxos` 算法 ⭐️ ⭐️ ⭐️ 。
-### 4.3. `Paxos` 算法
+### `Paxos` 算法
`Paxos` 算法是基于**消息传递且具有高度容错特性的一致性算法**,是目前公认的解决分布式一致性问题最有效的算法之一,**其解决的问题就是在分布式系统中如何就某个值(决议)达成一致** 。
在 `Paxos` 中主要有三个角色,分别为 `Proposer提案者`、`Acceptor表决者`、`Learner学习者`。`Paxos` 算法和 `2PC` 一样,也有两个阶段,分别为 `Prepare` 和 `accept` 阶段。
-#### 4.3.1. prepare 阶段
+#### prepare 阶段
-* `Proposer提案者`:负责提出 `proposal`,每个提案者在提出提案时都会首先获取到一个 **具有全局唯一性的、递增的提案编号N**,即在整个集群中是唯一的编号 N,然后将该编号赋予其要提出的提案,在**第一阶段是只将提案编号发送给所有的表决者**。
-* `Acceptor表决者`:每个表决者在 `accept` 某提案后,会将该提案编号N记录在本地,这样每个表决者中保存的已经被 accept 的提案中会存在一个**编号最大的提案**,其编号假设为 `maxN`。每个表决者仅会 `accept` 编号大于自己本地 `maxN` 的提案,在批准提案时表决者会将以前接受过的最大编号的提案作为响应反馈给 `Proposer` 。
+- `Proposer提案者`:负责提出 `proposal`,每个提案者在提出提案时都会首先获取到一个 **具有全局唯一性的、递增的提案编号 N**,即在整个集群中是唯一的编号 N,然后将该编号赋予其要提出的提案,在**第一阶段是只将提案编号发送给所有的表决者**。
+- `Acceptor表决者`:每个表决者在 `accept` 某提案后,会将该提案编号 N 记录在本地,这样每个表决者中保存的已经被 accept 的提案中会存在一个**编号最大的提案**,其编号假设为 `maxN`。每个表决者仅会 `accept` 编号大于自己本地 `maxN` 的提案,在批准提案时表决者会将以前接受过的最大编号的提案作为响应反馈给 `Proposer` 。
> 下面是 `prepare` 阶段的流程图,你可以对照着参考一下。
-
+
-#### 4.3.2. accept 阶段
+#### accept 阶段
当一个提案被 `Proposer` 提出后,如果 `Proposer` 收到了超过半数的 `Acceptor` 的批准(`Proposer` 本身同意),那么此时 `Proposer` 会给所有的 `Acceptor` 发送真正的提案(你可以理解为第一阶段为试探),这个时候 `Proposer` 就会发送提案的内容和提案编号。
表决者收到提案请求后会再次比较本身已经批准过的最大提案编号和该提案编号,如果该提案编号 **大于等于** 已经批准过的最大提案编号,那么就 `accept` 该提案(此时执行提案内容但不提交),随后将情况返回给 `Proposer` 。如果不满足则不回应或者返回 NO 。
-
+
当 `Proposer` 收到超过半数的 `accept` ,那么它这个时候会向所有的 `acceptor` 发送提案的提交请求。需要注意的是,因为上述仅仅是超过半数的 `acceptor` 批准执行了该提案内容,其他没有批准的并没有执行该提案内容,所以这个时候需要**向未批准的 `acceptor` 发送提案内容和提案编号并让它无条件执行和提交**,而对于前面已经批准过该提案的 `acceptor` 来说 **仅仅需要发送该提案的编号** ,让 `acceptor` 执行提交就行了。
-
+
-而如果 `Proposer` 如果没有收到超过半数的 `accept` 那么它将会将 **递增** 该 `Proposal` 的编号,然后 **重新进入 `Prepare` 阶段** 。
+而如果 `Proposer` 如果没有收到超过半数的 `accept` 那么它将会将 **递增** 该 `Proposal` 的编号,然后 **重新进入 `Prepare` 阶段** 。
> 对于 `Learner` 来说如何去学习 `Acceptor` 批准的提案内容,这有很多方式,读者可以自己去了解一下,这里不做过多解释。
-#### 4.3.3. `paxos` 算法的死循环问题
+#### paxos 算法的死循环问题
-其实就有点类似于两个人吵架,小明说我是对的,小红说我才是对的,两个人据理力争的谁也不让谁🤬🤬。
+其实就有点类似于两个人吵架,小明说我是对的,小红说我才是对的,两个人据理力争的谁也不让谁 🤬🤬。
比如说,此时提案者 P1 提出一个方案 M1,完成了 `Prepare` 阶段的工作,这个时候 `acceptor` 则批准了 M1,但是此时提案者 P2 同时也提出了一个方案 M2,它也完成了 `Prepare` 阶段的工作。然后 P1 的方案已经不能在第二阶段被批准了(因为 `acceptor` 已经批准了比 M1 更大的 M2),所以 P1 自增方案变为 M3 重新进入 `Prepare` 阶段,然后 `acceptor` ,又批准了新的 M3 方案,它又不能批准 M2 了,这个时候 M2 又自增进入 `Prepare` 阶段。。。
就这样无休无止的永远提案下去,这就是 `paxos` 算法的死循环问题。
-
+
那么如何解决呢?很简单,人多了容易吵架,我现在 **就允许一个能提案** 就行了。
-## 5. 引出 `ZAB`
+## 引出 ZAB
-### 5.1. `Zookeeper` 架构
+### Zookeeper 架构
作为一个优秀高效且可靠的分布式协调框架,`ZooKeeper` 在解决分布式数据一致性问题时并没有直接使用 `Paxos` ,而是专门定制了一致性协议叫做 `ZAB(ZooKeeper Atomic Broadcast)` 原子广播协议,该协议能够很好地支持 **崩溃恢复** 。
-
+
-### 5.2. `ZAB` 中的三个角色
+### ZAB 中的三个角色
和介绍 `Paxos` 一样,在介绍 `ZAB` 协议之前,我们首先来了解一下在 `ZAB` 中三个主要的角色,`Leader 领导者`、`Follower跟随者`、`Observer观察者` 。
-* `Leader` :集群中 **唯一的写请求处理者** ,能够发起投票(投票也是为了进行写请求)。
-* `Follower`:能够接收客户端的请求,如果是读请求则可以自己处理,**如果是写请求则要转发给 `Leader`** 。在选举过程中会参与投票,**有选举权和被选举权** 。
-* `Observer` :就是没有选举权和被选举权的 `Follower` 。
+- `Leader`:集群中 **唯一的写请求处理者** ,能够发起投票(投票也是为了进行写请求)。
+- `Follower`:能够接收客户端的请求,如果是读请求则可以自己处理,**如果是写请求则要转发给 `Leader`** 。在选举过程中会参与投票,**有选举权和被选举权** 。
+- `Observer`:就是没有选举权和被选举权的 `Follower` 。
在 `ZAB` 协议中对 `zkServer`(即上面我们说的三个角色的总称) 还有两种模式的定义,分别是 **消息广播** 和 **崩溃恢复** 。
-### 5.3. 消息广播模式
+### 消息广播模式
说白了就是 `ZAB` 协议是如何处理写请求的,上面我们不是说只有 `Leader` 能处理写请求嘛?那么我们的 `Follower` 和 `Observer` 是不是也需要 **同步更新数据** 呢?总不能数据只在 `Leader` 中更新了,其他角色都没有得到更新吧?
不就是 **在整个集群中保持数据的一致性** 嘛?如果是你,你会怎么做呢?
-
-
废话,第一步肯定需要 `Leader` 将写请求 **广播** 出去呀,让 `Leader` 问问 `Followers` 是否同意更新,如果超过半数以上的同意那么就进行 `Follower` 和 `Observer` 的更新(和 `Paxos` 一样)。当然这么说有点虚,画张图理解一下。
-
+
-嗯。。。看起来很简单,貌似懂了🤥🤥🤥。这两个 `Queue` 哪冒出来的?答案是 **`ZAB` 需要让 `Follower` 和 `Observer` 保证顺序性** 。何为顺序性,比如我现在有一个写请求A,此时 `Leader` 将请求A广播出去,因为只需要半数同意就行,所以可能这个时候有一个 `Follower` F1因为网络原因没有收到,而 `Leader` 又广播了一个请求B,因为网络原因,F1竟然先收到了请求B然后才收到了请求A,这个时候请求处理的顺序不同就会导致数据的不同,从而 **产生数据不一致问题** 。
+嗯。。。看起来很简单,貌似懂了 🤥🤥🤥。这两个 `Queue` 哪冒出来的?答案是 **`ZAB` 需要让 `Follower` 和 `Observer` 保证顺序性** 。何为顺序性,比如我现在有一个写请求 A,此时 `Leader` 将请求 A 广播出去,因为只需要半数同意就行,所以可能这个时候有一个 `Follower` F1 因为网络原因没有收到,而 `Leader` 又广播了一个请求 B,因为网络原因,F1 竟然先收到了请求 B 然后才收到了请求 A,这个时候请求处理的顺序不同就会导致数据的不同,从而 **产生数据不一致问题** 。
-所以在 `Leader` 这端,它为每个其他的 `zkServer` 准备了一个 **队列** ,采用先进先出的方式发送消息。由于协议是 **通过 `TCP` **来进行网络通信的,保证了消息的发送顺序性,接受顺序性也得到了保证。
+所以在 `Leader` 这端,它为每个其他的 `zkServer` 准备了一个 **队列** ,采用先进先出的方式发送消息。由于协议是 **通过 `TCP`** 来进行网络通信的,保证了消息的发送顺序性,接受顺序性也得到了保证。
-除此之外,在 `ZAB` 中还定义了一个 **全局单调递增的事务ID `ZXID`** ,它是一个64位long型,其中高32位表示 `epoch` 年代,低32位表示事务id。`epoch` 是会根据 `Leader` 的变化而变化的,当一个 `Leader` 挂了,新的 `Leader` 上位的时候,年代(`epoch`)就变了。而低32位可以简单理解为递增的事务id。
+除此之外,在 `ZAB` 中还定义了一个 **全局单调递增的事务 ID `ZXID`** ,它是一个 64 位 long 型,其中高 32 位表示 `epoch` 年代,低 32 位表示事务 id。`epoch` 是会根据 `Leader` 的变化而变化的,当一个 `Leader` 挂了,新的 `Leader` 上位的时候,年代(`epoch`)就变了。而低 32 位可以简单理解为递增的事务 id。
定义这个的原因也是为了顺序性,每个 `proposal` 在 `Leader` 中生成后需要 **通过其 `ZXID` 来进行排序** ,才能得到处理。
-### 5.4. 崩溃恢复模式
+### 崩溃恢复模式
说到崩溃恢复我们首先要提到 `ZAB` 中的 `Leader` 选举算法,当系统出现崩溃影响最大应该是 `Leader` 的崩溃,因为我们只有一个 `Leader` ,所以当 `Leader` 出现问题的时候我们势必需要重新选举 `Leader` 。
`Leader` 选举可以分为两个不同的阶段,第一个是我们提到的 `Leader` 宕机需要重新选举,第二则是当 `Zookeeper` 启动时需要进行系统的 `Leader` 初始化选举。下面我先来介绍一下 `ZAB` 是如何进行初始化选举的。
-假设我们集群中有3台机器,那也就意味着我们需要两台以上同意(超过半数)。比如这个时候我们启动了 `server1` ,它会首先 **投票给自己** ,投票内容为服务器的 `myid` 和 `ZXID` ,因为初始化所以 `ZXID` 都为0,此时 `server1` 发出的投票为 (1,0)。但此时 `server1` 的投票仅为1,所以不能作为 `Leader` ,此时还在选举阶段所以整个集群处于 **`Looking` 状态**。
+假设我们集群中有 3 台机器,那也就意味着我们需要两台以上同意(超过半数)。比如这个时候我们启动了 `server1` ,它会首先 **投票给自己** ,投票内容为服务器的 `myid` 和 `ZXID` ,因为初始化所以 `ZXID` 都为 0,此时 `server1` 发出的投票为 (1,0)。但此时 `server1` 的投票仅为 1,所以不能作为 `Leader` ,此时还在选举阶段所以整个集群处于 **`Looking` 状态**。
-接着 `server2` 启动了,它首先也会将投票选给自己(2,0),并将投票信息广播出去(`server1`也会,只是它那时没有其他的服务器了),`server1` 在收到 `server2` 的投票信息后会将投票信息与自己的作比较。**首先它会比较 `ZXID` ,`ZXID` 大的优先为 `Leader`,如果相同则比较 `myid`,`myid` 大的优先作为 `Leader`**。所以此时`server1` 发现 `server2` 更适合做 `Leader`,它就会将自己的投票信息更改为(2,0)然后再广播出去,之后`server2` 收到之后发现和自己的一样无需做更改,并且自己的 **投票已经超过半数** ,则 **确定 `server2` 为 `Leader`**,`server1` 也会将自己服务器设置为 `Following` 变为 `Follower`。整个服务器就从 `Looking` 变为了正常状态。
+接着 `server2` 启动了,它首先也会将投票选给自己(2,0),并将投票信息广播出去(`server1`也会,只是它那时没有其他的服务器了),`server1` 在收到 `server2` 的投票信息后会将投票信息与自己的作比较。**首先它会比较 `ZXID` ,`ZXID` 大的优先为 `Leader`,如果相同则比较 `myid`,`myid` 大的优先作为 `Leader`**。所以此时`server1` 发现 `server2` 更适合做 `Leader`,它就会将自己的投票信息更改为(2,0)然后再广播出去,之后`server2` 收到之后发现和自己的一样无需做更改,并且自己的 **投票已经超过半数** ,则 **确定 `server2` 为 `Leader`**,`server1` 也会将自己服务器设置为 `Following` 变为 `Follower`。整个服务器就从 `Looking` 变为了正常状态。
当 `server3` 启动发现集群没有处于 `Looking` 状态时,它会直接以 `Follower` 的身份加入集群。
还是前面三个 `server` 的例子,如果在整个集群运行的过程中 `server2` 挂了,那么整个集群会如何重新选举 `Leader` 呢?其实和初始化选举差不多。
-首先毫无疑问的是剩下的两个 `Follower` 会将自己的状态 **从 `Following` 变为 `Looking` 状态** ,然后每个 `server` 会向初始化投票一样首先给自己投票(这不过这里的 `zxid` 可能不是0了,这里为了方便随便取个数字)。
+首先毫无疑问的是剩下的两个 `Follower` 会将自己的状态 **从 `Following` 变为 `Looking` 状态** ,然后每个 `server` 会向初始化投票一样首先给自己投票(这不过这里的 `zxid` 可能不是 0 了,这里为了方便随便取个数字)。
假设 `server1` 给自己投票为(1,99),然后广播给其他 `server`,`server3` 首先也会给自己投票(3,95),然后也广播给其他 `server`。`server1` 和 `server3` 此时会收到彼此的投票信息,和一开始选举一样,他们也会比较自己的投票和收到的投票(`zxid` 大的优先,如果相同那么就 `myid` 大的优先)。这个时候 `server1` 收到了 `server3` 的投票发现没自己的合适故不变,`server3` 收到 `server1` 的投票结果后发现比自己的合适于是更改投票为(1,99)然后广播出去,最后 `server1` 收到了发现自己的投票已经超过半数就把自己设为 `Leader`,`server3` 也随之变为 `Follower`。
@@ -211,13 +204,13 @@
如果只是 `Follower` 挂了,而且挂的没超过半数的时候,因为我们一开始讲了在 `Leader` 中会维护队列,所以不用担心后面的数据没接收到导致数据不一致性。
-如果 `Leader` 挂了那就麻烦了,我们肯定需要先暂停服务变为 `Looking` 状态然后进行 `Leader` 的重新选举(上面我讲过了),但这个就要分为两种情况了,分别是 **确保已经被Leader提交的提案最终能够被所有的Follower提交** 和 **跳过那些已经被丢弃的提案** 。
+如果 `Leader` 挂了那就麻烦了,我们肯定需要先暂停服务变为 `Looking` 状态然后进行 `Leader` 的重新选举(上面我讲过了),但这个就要分为两种情况了,分别是 **确保已经被 Leader 提交的提案最终能够被所有的 Follower 提交** 和 **跳过那些已经被丢弃的提案** 。
-确保已经被Leader提交的提案最终能够被所有的Follower提交是什么意思呢?
+确保已经被 Leader 提交的提案最终能够被所有的 Follower 提交是什么意思呢?
假设 `Leader (server2)` 发送 `commit` 请求(忘了请看上面的消息广播模式),他发送给了 `server3`,然后要发给 `server1` 的时候突然挂了。这个时候重新选举的时候我们如果把 `server1` 作为 `Leader` 的话,那么肯定会产生数据不一致性,因为 `server3` 肯定会提交刚刚 `server2` 发送的 `commit` 请求的提案,而 `server1` 根本没收到所以会丢弃。
-
+
那怎么解决呢?
@@ -225,74 +218,74 @@
那么跳过那些已经被丢弃的提案又是什么意思呢?
-假设 `Leader (server2)` 此时同意了提案N1,自身提交了这个事务并且要发送给所有 `Follower` 要 `commit` 的请求,却在这个时候挂了,此时肯定要重新进行 `Leader` 的选举,比如说此时选 `server1` 为 `Leader` (这无所谓)。但是过了一会,这个 **挂掉的 `Leader` 又重新恢复了** ,此时它肯定会作为 `Follower` 的身份进入集群中,需要注意的是刚刚 `server2` 已经同意提交了提案N1,但其他 `server` 并没有收到它的 `commit` 信息,所以其他 `server` 不可能再提交这个提案N1了,这样就会出现数据不一致性问题了,所以 **该提案N1最终需要被抛弃掉** 。
+假设 `Leader (server2)` 此时同意了提案 N1,自身提交了这个事务并且要发送给所有 `Follower` 要 `commit` 的请求,却在这个时候挂了,此时肯定要重新进行 `Leader` 的选举,比如说此时选 `server1` 为 `Leader` (这无所谓)。但是过了一会,这个 **挂掉的 `Leader` 又重新恢复了** ,此时它肯定会作为 `Follower` 的身份进入集群中,需要注意的是刚刚 `server2` 已经同意提交了提案 N1,但其他 `server` 并没有收到它的 `commit` 信息,所以其他 `server` 不可能再提交这个提案 N1 了,这样就会出现数据不一致性问题了,所以 **该提案 N1 最终需要被抛弃掉** 。
-
+
-## 6. Zookeeper的几个理论知识
+## Zookeeper 的几个理论知识
了解了 `ZAB` 协议还不够,它仅仅是 `Zookeeper` 内部实现的一种方式,而我们如何通过 `Zookeeper` 去做一些典型的应用场景呢?比如说集群管理,分布式锁,`Master` 选举等等。
-这就涉及到如何使用 `Zookeeper` 了,但在使用之前我们还需要掌握几个概念。比如 `Zookeeper` 的 **数据模型** 、**会话机制**、**ACL**、**Watcher机制** 等等。
+这就涉及到如何使用 `Zookeeper` 了,但在使用之前我们还需要掌握几个概念。比如 `Zookeeper` 的 **数据模型**、**会话机制**、**ACL**、**Watcher 机制** 等等。
-### 6.1. 数据模型
+### 数据模型
`zookeeper` 数据存储结构与标准的 `Unix` 文件系统非常相似,都是在根节点下挂很多子节点(树型)。但是 `zookeeper` 中没有文件系统中目录与文件的概念,而是 **使用了 `znode` 作为数据节点** 。`znode` 是 `zookeeper` 中的最小数据单元,每个 `znode` 上都可以保存数据,同时还可以挂载子节点,形成一个树形化命名空间。
-
+
每个 `znode` 都有自己所属的 **节点类型** 和 **节点状态**。
其中节点类型可以分为 **持久节点**、**持久顺序节点**、**临时节点** 和 **临时顺序节点**。
-* 持久节点:一旦创建就一直存在,直到将其删除。
-* 持久顺序节点:一个父节点可以为其子节点 **维护一个创建的先后顺序** ,这个顺序体现在 **节点名称** 上,是节点名称后自动添加一个由 10 位数字组成的数字串,从 0 开始计数。
-* 临时节点:临时节点的生命周期是与 **客户端会话** 绑定的,**会话消失则节点消失** 。临时节点 **只能做叶子节点** ,不能创建子节点。
-* 临时顺序节点:父节点可以创建一个维持了顺序的临时节点(和前面的持久顺序性节点一样)。
+- 持久节点:一旦创建就一直存在,直到将其删除。
+- 持久顺序节点:一个父节点可以为其子节点 **维护一个创建的先后顺序** ,这个顺序体现在 **节点名称** 上,是节点名称后自动添加一个由 10 位数字组成的数字串,从 0 开始计数。
+- 临时节点:临时节点的生命周期是与 **客户端会话** 绑定的,**会话消失则节点消失** 。临时节点 **只能做叶子节点** ,不能创建子节点。
+- 临时顺序节点:父节点可以创建一个维持了顺序的临时节点(和前面的持久顺序性节点一样)。
-节点状态中包含了很多节点的属性比如 `czxid` 、`mzxid` 等等,在 `zookeeper` 中是使用 `Stat` 这个类来维护的。下面我列举一些属性解释。
+节点状态中包含了很多节点的属性比如 `czxid`、`mzxid` 等等,在 `zookeeper` 中是使用 `Stat` 这个类来维护的。下面我列举一些属性解释。
-* `czxid`:`Created ZXID`,该数据节点被 **创建** 时的事务ID。
-* `mzxid`:`Modified ZXID`,节点 **最后一次被更新时** 的事务ID。
-* `ctime`:`Created Time`,该节点被创建的时间。
-* `mtime`: `Modified Time`,该节点最后一次被修改的时间。
-* `version`:节点的版本号。
-* `cversion`:**子节点** 的版本号。
-* `aversion`:节点的 `ACL` 版本号。
-* `ephemeralOwner`:创建该节点的会话的 `sessionID` ,如果该节点为持久节点,该值为0。
-* `dataLength`:节点数据内容的长度。
-* `numChildre`:该节点的子节点个数,如果为临时节点为0。
-* `pzxid`:该节点子节点列表最后一次被修改时的事务ID,注意是子节点的 **列表** ,不是内容。
+- `czxid`:`Created ZXID`,该数据节点被 **创建** 时的事务 ID。
+- `mzxid`:`Modified ZXID`,节点 **最后一次被更新时** 的事务 ID。
+- `ctime`:`Created Time`,该节点被创建的时间。
+- `mtime`:`Modified Time`,该节点最后一次被修改的时间。
+- `version`:节点的版本号。
+- `cversion`:**子节点** 的版本号。
+- `aversion`:节点的 `ACL` 版本号。
+- `ephemeralOwner`:创建该节点的会话的 `sessionID` ,如果该节点为持久节点,该值为 0。
+- `dataLength`:节点数据内容的长度。
+- `numChildre`:该节点的子节点个数,如果为临时节点为 0。
+- `pzxid`:该节点子节点列表最后一次被修改时的事务 ID,注意是子节点的 **列表** ,不是内容。
-### 6.2. 会话
+### 会话
我想这个对于后端开发的朋友肯定不陌生,不就是 `session` 吗?只不过 `zk` 客户端和服务端是通过 **`TCP` 长连接** 维持的会话机制,其实对于会话来说你可以理解为 **保持连接状态** 。
-在 `zookeeper` 中,会话还有对应的事件,比如 `CONNECTION_LOSS 连接丢失事件` 、`SESSION_MOVED 会话转移事件` 、`SESSION_EXPIRED 会话超时失效事件` 。
+在 `zookeeper` 中,会话还有对应的事件,比如 `CONNECTION_LOSS 连接丢失事件`、`SESSION_MOVED 会话转移事件`、`SESSION_EXPIRED 会话超时失效事件` 。
-### 6.3. ACL
+### ACL
-`ACL` 为 `Access Control Lists` ,它是一种权限控制。在 `zookeeper` 中定义了5种权限,它们分别为:
+`ACL` 为 `Access Control Lists` ,它是一种权限控制。在 `zookeeper` 中定义了 5 种权限,它们分别为:
-* `CREATE` :创建子节点的权限。
-* `READ`:获取节点数据和子节点列表的权限。
-* `WRITE`:更新节点数据的权限。
-* `DELETE`:删除子节点的权限。
-* `ADMIN`:设置节点 ACL 的权限。
+- `CREATE`:创建子节点的权限。
+- `READ`:获取节点数据和子节点列表的权限。
+- `WRITE`:更新节点数据的权限。
+- `DELETE`:删除子节点的权限。
+- `ADMIN`:设置节点 ACL 的权限。
-### 6.4. Watcher机制
+### Watcher 机制
`Watcher` 为事件监听器,是 `zk` 非常重要的一个特性,很多功能都依赖于它,它有点类似于订阅的方式,即客户端向服务端 **注册** 指定的 `watcher` ,当服务端符合了 `watcher` 的某些事件或要求则会 **向客户端发送事件通知** ,客户端收到通知后找到自己定义的 `Watcher` 然后 **执行相应的回调方法** 。
-
+
-## 7. Zookeeper的几个典型应用场景
+## Zookeeper 的几个典型应用场景
前面说了这么多的理论知识,你可能听得一头雾水,这些玩意有啥用?能干啥事?别急,听我慢慢道来。
-
+
-### 7.1. 选主
+### 选主
还记得上面我们的所说的临时节点吗?因为 `Zookeeper` 的强一致性,能够很好地在保证 **在高并发的情况下保证节点创建的全局唯一性** (即无法重复创建同样的节点)。
@@ -302,21 +295,21 @@
你想想为什么我们要创建临时节点?还记得临时节点的生命周期吗?`master` 挂了是不是代表会话断了?会话断了是不是意味着这个节点没了?还记得 `watcher` 吗?我们是不是可以 **让其他不是 `master` 的节点监听节点的状态** ,比如说我们监听这个临时节点的父节点,如果子节点个数变了就代表 `master` 挂了,这个时候我们 **触发回调函数进行重新选举** ,或者我们直接监听节点的状态,我们可以通过节点是否已经失去连接来判断 `master` 是否挂了等等。
-
+
总的来说,我们可以完全 **利用 临时节点、节点状态 和 `watcher` 来实现选主的功能**,临时节点主要用来选举,节点状态和`watcher` 可以用来判断 `master` 的活性和进行重新选举。
-### 7.2. 分布式锁
+### 分布式锁
-分布式锁的实现方式有很多种,比如 `Redis` 、数据库 、`zookeeper` 等。个人认为 `zookeeper` 在实现分布式锁这方面是非常非常简单的。
+分布式锁的实现方式有很多种,比如 `Redis`、数据库、`zookeeper` 等。个人认为 `zookeeper` 在实现分布式锁这方面是非常非常简单的。
-上面我们已经提到过了 **zk在高并发的情况下保证节点创建的全局唯一性**,这玩意一看就知道能干啥了。实现互斥锁呗,又因为能在分布式的情况下,所以能实现分布式锁呗。
+上面我们已经提到过了 **zk 在高并发的情况下保证节点创建的全局唯一性**,这玩意一看就知道能干啥了。实现互斥锁呗,又因为能在分布式的情况下,所以能实现分布式锁呗。
如何实现呢?这玩意其实跟选主基本一样,我们也可以利用临时节点的创建来实现。
首先肯定是如何获取锁,因为创建节点的唯一性,我们可以让多个客户端同时创建一个临时节点,**创建成功的就说明获取到了锁** 。然后没有获取到锁的客户端也像上面选主的非主节点创建一个 `watcher` 进行节点状态的监听,如果这个互斥锁被释放了(可能获取锁的客户端宕机了,或者那个客户端主动释放了锁)可以调用回调函数重新获得锁。
-> `zk` 中不需要向 `redis` 那样考虑锁得不到释放的问题了,因为当客户端挂了,节点也挂了,锁也释放了。是不是很简答?
+> `zk` 中不需要向 `redis` 那样考虑锁得不到释放的问题了,因为当客户端挂了,节点也挂了,锁也释放了。是不是很简单?
那能不能使用 `zookeeper` 同时实现 **共享锁和独占锁** 呢?答案是可以的,不过稍微有点复杂而已。
@@ -328,15 +321,15 @@
这就很好地同时实现了共享锁和独占锁,当然还有优化的地方,比如当一个锁得到释放它会通知所有等待的客户端从而造成 **羊群效应** 。此时你可以通过让等待的节点只监听他们前面的节点。
-具体怎么做呢?其实也很简单,你可以让 **读请求监听比自己小的最后一个写请求节点,写请求只监听比自己小的最后一个节点** ,感兴趣的小伙伴可以自己去研究一下。
+具体怎么做呢?其实也很简单,你可以让 **读请求监听比自己小的最后一个写请求节点,写请求只监听比自己小的最后一个节点** ,感兴趣的小伙伴可以自己去研究一下。
-### 7.3. 命名服务
+### 命名服务
-如何给一个对象设置ID,大家可能都会想到 `UUID`,但是 `UUID` 最大的问题就在于它太长了。。。(太长不一定是好事,嘿嘿嘿)。那么在条件允许的情况下,我们能不能使用 `zookeeper` 来实现呢?
+如何给一个对象设置 ID,大家可能都会想到 `UUID`,但是 `UUID` 最大的问题就在于它太长了。。。(太长不一定是好事,嘿嘿嘿)。那么在条件允许的情况下,我们能不能使用 `zookeeper` 来实现呢?
-我们之前提到过 `zookeeper` 是通过 **树形结构** 来存储数据节点的,那也就是说,对于每个节点的 **全路径**,它必定是唯一的,我们可以使用节点的全路径作为命名方式了。而且更重要的是,路径是我们可以自己定义的,这对于我们对有些有语意的对象的ID设置可以更加便于理解。
+我们之前提到过 `zookeeper` 是通过 **树形结构** 来存储数据节点的,那也就是说,对于每个节点的 **全路径**,它必定是唯一的,我们可以使用节点的全路径作为命名方式了。而且更重要的是,路径是我们可以自己定义的,这对于我们对有些有语意的对象的 ID 设置可以更加便于理解。
-### 7.4. 集群管理和注册中心
+### 集群管理和注册中心
看到这里是不是觉得 `zookeeper` 实在是太强大了,它怎么能这么能干!
@@ -344,32 +337,30 @@
而 `zookeeper` 天然支持的 `watcher` 和 临时节点能很好的实现这些需求。我们可以为每条机器创建临时节点,并监控其父节点,如果子节点列表有变动(我们可能创建删除了临时节点),那么我们可以使用在其父节点绑定的 `watcher` 进行状态监控和回调。
-
+
-至于注册中心也很简单,我们同样也是让 **服务提供者** 在 `zookeeper` 中创建一个临时节点并且将自己的 `ip、port、调用方式` 写入节点,当 **服务消费者** 需要进行调用的时候会 **通过注册中心找到相应的服务的地址列表(IP端口什么的)** ,并缓存到本地(方便以后调用),当消费者调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从地址列表中取一个服务提供者的服务器调用服务。
+至于注册中心也很简单,我们同样也是让 **服务提供者** 在 `zookeeper` 中创建一个临时节点并且将自己的 `ip、port、调用方式` 写入节点,当 **服务消费者** 需要进行调用的时候会 **通过注册中心找到相应的服务的地址列表(IP 端口什么的)** ,并缓存到本地(方便以后调用),当消费者调用服务时,不会再去请求注册中心,而是直接通过负载均衡算法从地址列表中取一个服务提供者的服务器调用服务。
当服务提供者的某台服务器宕机或下线时,相应的地址会从服务提供者地址列表中移除。同时,注册中心会将新的服务地址列表发送给服务消费者的机器并缓存在消费者本机(当然你可以让消费者进行节点监听,我记得 `Eureka` 会先试错,然后再更新)。
-
+
-## 8. 总结
+## 总结
-看到这里的同学实在是太有耐心了👍👍👍,如果觉得我写得不错的话点个赞哈。
+看到这里的同学实在是太有耐心了 👍👍👍 不知道大家是否还记得我讲了什么 😒。
-不知道大家是否还记得我讲了什么😒。
-
-
+
这篇文章中我带大家入门了 `zookeeper` 这个强大的分布式协调框架。现在我们来简单梳理一下整篇文章的内容。
-* 分布式与集群的区别
+- 分布式与集群的区别
+
+- `2PC`、`3PC` 以及 `paxos` 算法这些一致性框架的原理和实现。
-* `2PC` 、`3PC` 以及 `paxos` 算法这些一致性框架的原理和实现。
+- `zookeeper` 专门的一致性算法 `ZAB` 原子广播协议的内容(`Leader` 选举、崩溃恢复、消息广播)。
-* `zookeeper` 专门的一致性算法 `ZAB` 原子广播协议的内容(`Leader` 选举、崩溃恢复、消息广播)。
+- `zookeeper` 中的一些基本概念,比如 `ACL`,数据节点,会话,`watcher`机制等等。
-* `zookeeper` 中的一些基本概念,比如 `ACL`,数据节点,会话,`watcher`机制等等。
+- `zookeeper` 的典型应用场景,比如选主,注册中心等等。
-* `zookeeper` 的典型应用场景,比如选主,注册中心等等。
-
- 如果忘了可以回去看看再次理解一下,如果有疑问和建议欢迎提出🤝🤝🤝。
+ 如果忘了可以回去看看再次理解一下,如果有疑问和建议欢迎提出 🤝🤝🤝。
diff --git a/docs/distributed-system/distributed-transaction.md b/docs/distributed-system/distributed-transaction.md
index 985fefbee098dadbda0ab2728e91a64fbeea22b8..2e3d3cf57a478d58c6509eb09e39637631fedf50 100644
--- a/docs/distributed-system/distributed-transaction.md
+++ b/docs/distributed-system/distributed-transaction.md
@@ -1,7 +1,10 @@
-# 分布式事务
+---
+title: 分布式事务常见问题总结(付费)
+category: 分布式
+---
-这部分内容为我的星球专属,已经整理到了[《Java面试进阶指北 打造个人的技术竞争力》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7?# )中。
+**分布式事务** 相关的面试题为我的[知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)(点击链接即可查看详细介绍以及加入方法)专属内容,已经整理到了《Java 面试指北》中。
-欢迎加入我的星球,[一个纯 Java 面试交流圈子 !Ready!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100015911&idx=1&sn=2e8a0f5acb749ecbcbb417aa8a4e18cc&chksm=4ea1b0ec79d639fae37df1b86f196e8ce397accfd1dd2004bcadb66b4df5f582d90ae0d62448#rd) (点击链接了解星球详细信息,还有专属优惠款可以领取)。
+
-
+
diff --git a/docs/distributed-system/images/api-gateway/api-gateway-overview.drawio b/docs/distributed-system/images/api-gateway/api-gateway-overview.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..6dd0690ca5074c426c136d36ab9bb8e01b0f6cb9
--- /dev/null
+++ b/docs/distributed-system/images/api-gateway/api-gateway-overview.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/distributed-system/images/api-gateway/api-gateway-overview.png b/docs/distributed-system/images/api-gateway/api-gateway-overview.png
new file mode 100644
index 0000000000000000000000000000000000000000..27d45a829cae89d1d08ebcf3147c9829afa50be7
Binary files /dev/null and b/docs/distributed-system/images/api-gateway/api-gateway-overview.png differ
diff --git a/docs/distributed-system/images/distributed-id/database-number-segment-mode.drawio b/docs/distributed-system/images/distributed-id/database-number-segment-mode.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..a9855fe95c5113ee893094ddf08f3f83beccac7c
--- /dev/null
+++ b/docs/distributed-system/images/distributed-id/database-number-segment-mode.drawio
@@ -0,0 +1 @@
+7VpZc+MoEP41qtp5sEuHJduPlo9sbe1uTa03O48pImGJBIEG4Wt+/YJAsi6PlcOTqUnKD4EGuqG/vkAxnHlyuGEgjf+iIcSGbYYHw1kYtm1Zpif+SMpRUSaurQgRQ6GedCKs0TeoiaamblEIs9pETinmKK0TA0oIDHiNBhij+/q0DcV1qSmIYIuwDgBuU7+gkMf6FPb4RP8doiguJFveVI0koJisT5LFIKT7CslZGs6cUcpVKznMIZbKK/Si1q3OjJYbY5DwPgu2t8d/zb//WU13iN1aq9uZffw20Fx2AG/1gfVm+bHQACThTCpS9AglgujHPMGiZ4lm9gh5IM9pyk5xQtlpb0/LgmFN43qzN5AmkLOjmLA/6dnVuosrKi5oDGLA0a6OE9BwRyW7UsJnisRObLMwTUfz0YbpFP2CRUa3LIB6VVWvDUb25AIjDlgEeYuRaFSOfSLlsD0BQvuFEIYgi2H4y+E5ei08W4yujKfzbl3youb7Qji6ZAtXhtBtIyY0vNZdynhMI0oAXp6oPqNbEuZ+KKE6zfmT0lQj+wA5P+oUCbacvq4fK+V+51SjnsbyYkd23zSejjr8z8Nc6jEFpAar93Urs7cfUEyZ4czk4aP73yy5RAg3q41Pue5FAUL4YAMShI9qQQIJpmpWIvAOdFswB4kA3i9FbBmCTIwQuG8Olsup2KLEsJSU5dYi5VjjoZseqkPqGHKMUJYAXBnba+eWg46pzMbEwvwgG0gRiETtlRwe+ABgFBE1huGGV0aQMG+ieZrFTvIRzgDJNoJTwZPoI+wpC+vyyoX3IHiMcp8ZNLRvu67SR7XxqSIuhAFlwkIpacoLUZZioIFBBKNiYIMp4I3ZXql8L8oR2jLpU3cJONwJA9M2I0xQmY2a1AoMckeCvo8Rh2sF3mIvSukXx/QdZIL3dx31UI/e2t9sT/crQd8ad0T9pl9WXbvmlE/1QO+sB0rjzIv9HPFFFQRnDOSvSjI3IKg76hlnazhX3ZlaQF8MB92eZ5u9LNesGK9Zsd/WNjIO07NWVpKVxn5e47Oa1fOkbX1dJYd9LeObXC6/Ms7oY3kdtBvaikEq5yWHSN6IhyJ07IMYMD4EhFCex507uUZHyoUOkxjcQ/yZZkgFpgVTZ/VTmecgW+6EjjMtg2k+ojuYmq+Q+5+Pl90TL8u9FmBW1x32I2F/JOxfNmHbzaeLN8/YVscd9ee/8Vz7JvPcl4TLT0zTfhfj17oSWT3eJN5zUnSaDvn2SdH9qKHfTQ3dNL/RW9fQVtcN7vwbZoBBlqHgBwf3H/SmaY6GjfesSb/g3YdVMw9c+WnMGn/gWvic2wRj9FxcO1hdD9f/HkY0+eLC2Zr6Myf2vtrOsfMrYC1ZXI7Pnq7zW4F+6RoT15jNjeXI8OfGdJxTVoYvGqucMvsD7MCN/M5bSL1nL5Q5NvyFMbVkY+Yb02kp6kGIyj8pDwPyxAhfjeZ5rTPXGVRfMDYI4wapqGYCKKsVQZAxHQUAz/RAgsIwr4W70ke9Ps5LIb/Me03h4gC6ThbBV/XnZYZ3/PxXL5LMHv7XykEdLtm/KrI6rimTrrLo6XlJdE9f0pVbnP4fwVn+Dw==
\ No newline at end of file
diff --git a/docs/distributed-system/images/distributed-id/distributed-id-requirements.drawio b/docs/distributed-system/images/distributed-id/distributed-id-requirements.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..39d63b3437ae2e8caee47dd8234a523df1b06bed
--- /dev/null
+++ b/docs/distributed-system/images/distributed-id/distributed-id-requirements.drawio
@@ -0,0 +1 @@
+1Zhbb6M4FMc/jaXZh1RcAphHSJPujDSzD9Wqs48GTsBTg1Mgl86n32PjJJCQVUebaNpESsw5vsA5P/9tQ9xZuXuo2ar4KjMQxLGyHXHviePYtuXjn7K8dhbqOZ0hr3lmKh0Nj/wnGKNlrGueQTOo2EopWr4aGlNZVZC2Axura7kdVltKMRx1xXI4MzymTJxbn3jWFuYpnOBo/xN4XuxHtv2w85RsX9k8SVOwTG57JndO3FktZduVyt0MhArePi5du8UF7+HGaqjatzRIoVj9Vb08ffv5MmXBy+Mz24gJ7XrZMLE2D2xutn3dR6CW6yoD1YlN3Hhb8BYeVyxV3i3mHG1FWwrjPjykhRdLLsRMClnrjtylp76qUlvLZ+h5fP1RLWTV9uzdR7V4hjYtTLe5YE1jyudBMHHZQN3CrmcyQXkAWUJbv2IV4/VdkyBDqBOY6+0x39N9Eotert2pMTLDWH7o+5gGLJhM/EJW7JGs+AKHjZNBbvyXtaJHh23S6LkTYQXbX+10bPZ+LOX6f+4R6pGIqkJsE2qpQjgl0YLMpySmymJGwhtPDu0uM2G9gYl+8gaA9DOpnsFMf3uPwoKVXKi0zOS65lDjTXyD7QhZy6WTpmNkZX7ie/51QAmsISiuew6KPQaKfytOnJtxEpIoJiFy4iskooDMkQ2XxPcfEw/wL+ARhIl1JR2hwRCP6W/Hw72hjERzQsOjnnxAKsDOPAjGqAj9wGVXEg16Ihqe4/1mKqa3Fw2kYqHXlEAvLh8SjyVNYVw0EupNvWttPsL3JhrezfDwSeiTONQ7jTmJF9pCSeh+aE4yBnQ5yomfUkiWV5IR+t72HuFFTgpXncdMeI8glNkEqmySCJk+q/NRNimAZbzKu4ulTNdNH5sR1hK5U6ipNhq2RNYZ1BM0dy1OWaSGRTyM1TmvjBVK/LXQhb93Hl7pKoJXMNlHTte7c3q9Lg0OyiNYK4mDYVObZVYq5MwNFiA20PKUoa8CDMyJv2vVq6UuG1Y1kwZq3rFirWTDWy7N7daAw/ENdL60w0s78uSTZ3ddHP//GJ98JjXNilWXkrMSjFddLmCH9bIbJWP0UIK7S18fSvQ2UxVmag1xrM/3Ksy4tIQzfVyhJMItqa20I45JZCtLjGXdCKUkirS+RCSMh5XxtIP5XPQkpgvGMEBoVvhekB6crO2JvgwmfSUrOFEIY2KC55jQ+xQlALXEjdXURwJEZBwlzzI1zKigDSXvoowNxE6wBETM0udctz69xZ7i0SstZf7JOdo5l6jDkbkvUc4VJOqfL/ETldXOY/M8/Pr97+r7D/ofx2j1+Fc5Sc80bTMSBmbXE2NhoS3RF7ZhD+r11WEBrf/nmIE6gIW2KqidVngY6gcOpd+U3aVVD+XuMd81zG/HdOT9UKy/us+WdZJ5PhEG0+UC1CPoX+bcOdnRT0eWYjrCuf3rnOPl8QWh9vVes7rzfwE=
\ No newline at end of file
diff --git a/docs/distributed-system/images/distributed-id/id-after-the-sub-table-not-conflict.drawio b/docs/distributed-system/images/distributed-id/id-after-the-sub-table-not-conflict.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..b90e8b9c3cdaa2bec97d0ff4d7431187ac1044dd
--- /dev/null
+++ b/docs/distributed-system/images/distributed-id/id-after-the-sub-table-not-conflict.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/distributed-system/images/distributed-id/nosql.drawio b/docs/distributed-system/images/distributed-id/nosql.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..5e720127319629ac42c277173241e5a5269ac9df
--- /dev/null
+++ b/docs/distributed-system/images/distributed-id/nosql.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/distributed-system/images/distributed-id/the-primary-key-of-the-database-increases-automatically.drawio b/docs/distributed-system/images/distributed-id/the-primary-key-of-the-database-increases-automatically.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..60c511c969479a75780a0594fe21679e4d59bcc5
--- /dev/null
+++ b/docs/distributed-system/images/distributed-id/the-primary-key-of-the-database-increases-automatically.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/distributed-system/images/zookeeper/znode-structure.drawio b/docs/distributed-system/images/zookeeper/znode-structure.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..7253bacb76918b13ddd8c9827d11be5762cf8fd4
--- /dev/null
+++ b/docs/distributed-system/images/zookeeper/znode-structure.drawio
@@ -0,0 +1 @@
+7Vrfc5s4EP5r9HgZQAiLR7DN3fSuc53Lw7VPHRVkzAUjV8iJ3b/+JBC/iYlTO3ZmmszE0mq1Et+utN8SAzjf7H/nZLv+yCKaAsuI9gAugGWZpuHIDyU5lJIZdktBzJNIKzWC++QH1UJDS3dJRPOOomAsFcm2KwxZltFQdGSEc/bUVVuxtLvqlsR0ILgPSTqU/ptEYl1KsTVr5H/QJF5XK5uOfr5vJHyIOdtlej1gwcAJggCXwxtS2dIPmq9JxJ5aIrgEcM4ZE2Vrs5/TVGFbwVbOC54ZrffNaSZeMmE/u//7wyHA8wP9fvhq/vmPTb7/pq3k4lDhQSMJj+4yLtYsZhlJl43UD3f8kSqjpuwUABQ9Q/aaCX8xttUq/1EhDtrxZCeYFK3FJtWjK5YJPWg6sp8Lzh5qV0iU/HKLal/PPnn1GGzHQ3rkcZEOMMJjKo7ozWr/yLinbEMFP8h5nKZEJI/dfRAdgHGt1zhBNrQfTvCJdXWfSFz54bOaf4eq7hdtrugs9p3eQfdu0pf4mr6Ev3x5Tl86P+nLYqrHOTm0FLYsyUTesvxJCaSCznEQ6htcZzi7f9H29E37qL5slDtooqp+lNcHmn31QLvJgHHfQ8Ag5L59wGgEH0m6oxWBCVIWPuSDSGpCQ/n5aZ0Ier8lhX+eJCk8HgarJE3nLGW8sAUjQvEqrMOjGslYRusIeaRc0P3xGBn6VE+wcBd8iPTReGrYnWlonXWL2dnOhXKAcwTp4tO4KOKGgSEOLoi424t2R2Wa60I+m4J8yHvPCfnctpGNLhnk/RRzA5jjKcyHvPZdYQ7x7WHuTmGevW/MkXV7mFcvLjqg97lXFnnq3UQDRkTydY16C2El/0SEoDwrJJYBR7lT2xP4BC41hLYFHBrBrZKdRpEGnMbuZwUMuyZKiqdnHSNH5oShkgMODJ2LJ5nm2BlLondOkxz71miSORseo8kS5mrl8fCEdqqplx7PF5Q6pr5eJmsdNO7wy9Y6yOjFETxe6/Rro57+ZWodE78itG7jNcxFq+gXh5b5zGXyxrE1UUc77lH9C8WWNZYfzpscRpNAL2OskPrVk1vy8uc8ScNGXYDrMuR6SQNeg4x1b4Nbp2IOdivSXHEoF1aSU+nYiLHZwNilKdnYG5VfDLznqVn/Nqzq11Ndjs0JQ5d291iV+7y7w5TkeRJ2ndw9sRPkje4T8bnV/tKkbdlrErXqVHm6E11nT9FXuzp69bflvDKGHNg39LZVnDVSxS0d4BnAd8ESA+wBLCUz9dd3B+ElU6boBtRkStYikiaxuldC6Xkq5b5KwElIUk8PbJIoKvjmGCfohm2fFpwho/fLQFleDzK6MxJf0Hg+lH7un98jdEo5KgA4AEsb+Bh43ojHli7AEPgLpexipV8qy7uqnmUZPzIWUWDNR0zWg3ICUiPahA88VBidA/XNFiMrNOqNIOAtgLxpR2LISVXMfOOyFavWxKLtByjWkrZdu/skGPizQoKAvwTYVhLPBB5Wy3pSzXvpsqxetzRePwouNmAr+9Km6xWrSIlc0VANvADespq1KDYghWaxN9lARUMuJ6t5w9zc8EkKyCZJVdh/oMLnJMlkJWJ8lLhUJ626mtdCbOUYghI4pKi9/KMU8ruYsTilZJvkdyHbFANhXqgGq9K6bLbtI8tvr3DesyzpeecsI2fIzuu3ae3DjNHJh1l2m28ulZd08/UwuPwf
\ No newline at end of file
diff --git a/docs/distributed-system/images/zookeeper/zookeeper-watcher.drawio b/docs/distributed-system/images/zookeeper/zookeeper-watcher.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..3da7244d7002a6ef0e7b4c0a934dda222c331a6e
--- /dev/null
+++ b/docs/distributed-system/images/zookeeper/zookeeper-watcher.drawio
@@ -0,0 +1 @@
+7Vjbbts4EP0aPiawRF2oR8m1tgg2wALZxW6fFqzESGxp0aXo2O7XdyhR0S321kjjGou+2JwzQ4qcOTwaG+Hlev+bopvyXuZMIHeR7xF+h1zXiTCGL4McWiQkUQsUiuc2qAce+FdmwYVFtzxn9ShQSyk034zBTFYVy/QIo0rJ3TjsUYrxUze0YDPgIaNijv7Nc122KHHDHn/PeFF2T3YCe76PNPtcKLmt7POQi9MgTVPSute0W8setC5pLncDCK8QXiopdTta75dMmNx2aWvnpUe8z/tWrNLfM4F5H/786wvZ4vv9v3dc8I85ubuxxav1ocsHyyE91pRKl7KQFRWrHk2aMzOz6gKsPuZ3KTcAOgB+YlofbK3pVkuASr0W1gsbVod/zPxbvzM/2OUa491+ZB2s9SgrbRd1ImundM2FCbhjOlGUVzUc515WsouXW5WZGaXWwCjXxzF8QJLMhwmobwspC8Hohte3mVw3jqxuQtPHdnUYDtf33cQ+oc2eSdnRolio7vZxrBId96kqmD4R5z5TB64kk2sGGYJ5igmq+dN4H9TejeI5zk6NlaKHQcBGcsjEYOU/DAAB3TUnlsT2krtkwrVJvB/5p+Jh0O6gswZH6aGGv2dw2R76iYot665kILQlwojlwZet7Bw3dUMpoMUCKrdvqtr5YVSY70xwU1C7HGqZ0zlnV6i/IIbtu5Jr9rChTfV3IKLjyzAkdWBsLsRSCqmatXBOGXnMAK+1kp9Z56lkxa7oBjwxpdn+9B2Yc7absBiTC/vW3vV67HRYOdDicHGc5gOqnc+k8JpU0TlDFYMr4sQPUUX3O1XxCMPeVhWnKucvTquiE56MfxtVdF+tik50RBVrpuDi/wxVXHqe7/n/a1X03WtTRW/GJAetMIJXO4FBgBKMYoJWPiIBIkuzU6qz0hBkwgRIih6X++UyDmpuISp4UYGZQRJhYZyYFHNo5mPrWPM8b+T4JX6NJfpadfNVrHGjSaP2EmuCF1iD34o1/ow17oA1HkpiRJJmkKAkaOiToghcIYo8FKWNK2pifASnIyu0isz0KG5iUhT7xhWvUOwaGhKCktC44gTF6S/yXZB8wZh8vjMn3zPRLkI+8jMauWltB42de3Zndw2kuOjv2x/Syc1bL0xG1PSmlGv3ZWf1rDv7hzKevLXx6ZYwfFW45/1Hwzk9tXeBhjOaCT4etQkkQjHoM0GxY9oEI92pUXgYwLsANN+oOrwUvEbwExStTHAC07GZHgVNcNNudP33L3m/hLzfOBN9x+Hb6TuY/V+jLTX7/5/x6hs=
\ No newline at end of file
diff --git "a/docs/distributed-system/\347\220\206\350\256\272&\347\256\227\346\263\225/cap&base\347\220\206\350\256\272.md" b/docs/distributed-system/protocol/cap-and-base-theorem.md
similarity index 68%
rename from "docs/distributed-system/\347\220\206\350\256\272&\347\256\227\346\263\225/cap&base\347\220\206\350\256\272.md"
rename to docs/distributed-system/protocol/cap-and-base-theorem.md
index 3340409a7997d5d8d8a68e5ecb6cefcdbbea97f9..781d47c00ad767ed610569f7975c7d12e5e03913 100644
--- "a/docs/distributed-system/\347\220\206\350\256\272&\347\256\227\346\263\225/cap&base\347\220\206\350\256\272.md"
+++ b/docs/distributed-system/protocol/cap-and-base-theorem.md
@@ -1,29 +1,27 @@
+---
+title: CAP & BASE理论详解
+category: 分布式
+tag:
+ - 分布式理论
+---
-# CAP & BASE理论
+经历过技术面试的小伙伴想必对 CAP & BASE 这个两个理论已经再熟悉不过了!
-经历过技术面试的小伙伴想必对这个两个概念已经再熟悉不过了!
+我当年参加面试的时候,不夸张地说,只要问到分布式相关的内容,面试官几乎是必定会问这两个分布式相关的理论。一是因为这两个分布式基础理论是学习分布式知识的必备前置基础,二是因为很多面试官自己比较熟悉这两个理论(方便提问)。
-Guide哥当年参加面试的时候,不夸张地说,只要问到分布式相关的内容,面试官几乎是必定会问这两个分布式相关的理论。
+我们非常有必要将这两个理论搞懂,并且能够用自己的理解给别人讲出来。
-并且,这两个理论也可以说是小伙伴们学习分布式相关内容的基础了!
+## CAP 理论
-因此,小伙伴们非常非常有必要将这理论搞懂,并且能够用自己的理解给别人讲出来。
+[CAP 理论/定理](https://zh.wikipedia.org/wiki/CAP%E5%AE%9A%E7%90%86)起源于 2000 年,由加州大学伯克利分校的 Eric Brewer 教授在分布式计算原理研讨会(PODC)上提出,因此 CAP 定理又被称作 **布鲁尔定理(Brewer’s theorem)**
-这篇文章我会站在自己的角度对这两个概念进行解读!
-
-*个人能力有限。如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!——爱你们的Guide哥*
-
-## CAP理论
-
-[CAP 理论/定理](https://zh.wikipedia.org/wiki/CAP%E5%AE%9A%E7%90%86)起源于 2000年,由加州大学伯克利分校的Eric Brewer教授在分布式计算原理研讨会(PODC)上提出,因此 CAP定理又被称作 **布鲁尔定理(Brewer’s theorem)**
-
-2年后,麻省理工学院的Seth Gilbert和Nancy Lynch 发表了布鲁尔猜想的证明,CAP理论正式成为分布式领域的定理。
+2 年后,麻省理工学院的 Seth Gilbert 和 Nancy Lynch 发表了布鲁尔猜想的证明,CAP 理论正式成为分布式领域的定理。
### 简介
**CAP** 也就是 **Consistency(一致性)**、**Availability(可用性)**、**Partition Tolerance(分区容错性)** 这三个单词首字母组合。
-
+
CAP 理论的提出者布鲁尔在提出 CAP 猜想的时候,并没有详细定义 **Consistency**、**Availability**、**Partition Tolerance** 三个单词的明确定义。
@@ -33,13 +31,13 @@ CAP 理论的提出者布鲁尔在提出 CAP 猜想的时候,并没有详细
- **一致性(Consistency)** : 所有节点访问同一份最新的数据副本
- **可用性(Availability)**: 非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应)。
-- **分区容错性(Partition tolerance)** : 分布式系统出现网络分区的时候,仍然能够对外提供服务。
+- **分区容错性(Partition Tolerance)** : 分布式系统出现网络分区的时候,仍然能够对外提供服务。
**什么是网络分区?**
-> 分布式系统中,多个节点之前的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题)某些节点之间不连通了,整个网络就分成了几块区域,这就叫网络分区。
+分布式系统中,多个节点之前的网络本来是连通的,但是因为某些故障(比如部分节点网络出了问题)某些节点之间不连通了,整个网络就分成了几块区域,这就叫 **网络分区**。
-
+
### 不是所谓的“3 选 2”
@@ -49,13 +47,13 @@ CAP 理论的提出者布鲁尔在提出 CAP 猜想的时候,并没有详细
>
> 简而言之就是:CAP 理论中分区容错性 P 是一定要满足的,在此基础上,只能满足可用性 A 或者一致性 C。
-因此,**分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。** 比如 ZooKeeper、HBase 就是 CP 架构,Cassandra、Eureka 就是 AP 架构,Nacos 不仅支持 CP 架构也支持 AP 架构。
+因此,**分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。** 比如 ZooKeeper、HBase 就是 CP 架构,Cassandra、Eureka 就是 AP 架构,Nacos 不仅支持 CP 架构也支持 AP 架构。
**为啥不可能选择 CA 架构呢?** 举个例子:若系统出现“分区”,系统中的某个节点在进行写操作。为了保证 C, 必须要禁止其他节点的读写操作,这就和 A 发生冲突了。如果为了保证 A,其他节点的读写操作正常的话,那就和 C 发生冲突了。
**选择 CP 还是 AP 的关键在于当前的业务场景,没有定论,比如对于需要确保强一致性的场景如银行一般会选择保证 CP 。**
-另外,需要补充说明的一点是: **如果网络分区正常的话(系统在绝大部分时候所处的状态),也就说不需要保证 P 的时候,C 和 A 能够同时保证。**
+另外,需要补充说明的一点是:**如果网络分区正常的话(系统在绝大部分时候所处的状态),也就说不需要保证 P 的时候,C 和 A 能够同时保证。**
### CAP 实际应用案例
@@ -65,7 +63,7 @@ CAP 理论的提出者布鲁尔在提出 CAP 猜想的时候,并没有详细
注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小。
-
+
常见的可以作为注册中心的组件有:ZooKeeper、Eureka、Nacos...。
@@ -73,6 +71,12 @@ CAP 理论的提出者布鲁尔在提出 CAP 猜想的时候,并没有详细
2. **Eureka 保证的则是 AP。** Eureka 在设计的时候就是优先保证 A (可用性)。在 Eureka 中不存在什么 Leader 节点,每个节点都是一样的、平等的。因此 Eureka 不会像 ZooKeeper 那样出现选举过程中或者半数以上的机器不可用的时候服务就是不可用的情况。 Eureka 保证即使大部分节点挂掉也不会影响正常提供服务,只要有一个节点是可用的就行了。只不过这个节点上的数据可能并不是最新的。
3. **Nacos 不仅支持 CP 也支持 AP。**
+**🐛 修正(参见:[issue#1906](https://github.com/Snailclimb/JavaGuide/issues/1906))**:
+
+ZooKeeper 通过可线性化(Linearizable)写入、全局 FIFO 顺序访问等机制来保障数据一致性。多节点部署的情况下, ZooKeeper 集群处于 Quorum 模式。Quorum 模式下的 ZooKeeper 集群, 是一组 ZooKeeper 服务器节点组成的集合,其中大多数节点必须同意任何变更才能被视为有效。
+
+由于 Quorum 模式下的读请求不会触发各个 ZooKeeper 节点之间的数据同步,因此在某些情况下还是可能会存在读取到旧数据的情况,导致不同的客户端视图上看到的结果不同,这可能是由于网络延迟、丢包、重传等原因造成的。ZooKeeper 为了解决这个问题,提供了 Watcher 机制和版本号机制来帮助客户端检测数据的变化和版本号的变更,以保证数据的一致性。
+
### 总结
在进行分布式系统设计和开发时,我们不应该仅仅局限在 CAP 问题上,还要关注系统的扩展性、可用性等等
@@ -91,11 +95,11 @@ CAP 理论的提出者布鲁尔在提出 CAP 猜想的时候,并没有详细
## BASE 理论
-[BASE 理论](https://dl.acm.org/doi/10.1145/1394127.1394128)起源于 2008 年, 由eBay的架构师Dan Pritchett在ACM上发表。
+[BASE 理论](https://dl.acm.org/doi/10.1145/1394127.1394128)起源于 2008 年, 由 eBay 的架构师 Dan Pritchett 在 ACM 上发表。
### 简介
-**BASE** 是 **Basically Available(基本可用)** 、**Soft-state(软状态)** 和 **Eventually Consistent(最终一致性)** 三个短语的缩写。BASE 理论是对 CAP 中一致性 C 和可用性 A 权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于 CAP 定理逐步演化而来的,它大大降低了我们对系统的要求。
+**BASE** 是 **Basically Available(基本可用)**、**Soft-state(软状态)** 和 **Eventually Consistent(最终一致性)** 三个短语的缩写。BASE 理论是对 CAP 中一致性 C 和可用性 A 权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于 CAP 定理逐步演化而来的,它大大降低了我们对系统的要求。
### BASE 理论的核心思想
@@ -117,7 +121,7 @@ CAP 理论这节我们也说过了:

-#### 1. 基本可用
+#### 基本可用
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。
@@ -126,29 +130,29 @@ CAP 理论这节我们也说过了:
- **响应时间上的损失**: 正常情况下,处理用户请求需要 0.5s 返回结果,但是由于系统出现故障,处理用户请求的时间变为 3 s。
- **系统功能上的损失**:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用。
-#### 2. 软状态
+#### 软状态
软状态指允许系统中的数据存在中间状态(**CAP 理论中的数据不一致**),并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
-#### 3. 最终一致性
+#### 最终一致性
最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
> 分布式一致性的 3 种级别:
>
-> 1. **强一致性** :系统写入了什么,读出来的就是什么。
+> 1. **强一致性**:系统写入了什么,读出来的就是什么。
>
-> 2. **弱一致性** :不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只是会尽量保证某个时刻达到数据一致的状态。
+> 2. **弱一致性**:不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只是会尽量保证某个时刻达到数据一致的状态。
>
-> 3. **最终一致性** :弱一致性的升级版,系统会保证在一定时间内达到数据一致的状态。
+> 3. **最终一致性**:弱一致性的升级版,系统会保证在一定时间内达到数据一致的状态。
>
> **业界比较推崇是最终一致性级别,但是某些对数据一致要求十分严格的场景比如银行转账还是要保证强一致性。**
那实现最终一致性的具体方式是什么呢? [《分布式协议与算法实战》](http://gk.link/a/10rZM) 中是这样介绍:
-> - **读时修复** : 在读取数据时,检测数据的不一致,进行修复。比如 Cassandra 的 Read Repair 实现,具体来说,在向 Cassandra 系统查询数据的时候,如果检测到不同节点 的副本数据不一致,系统就自动修复数据。
+> - **读时修复** : 在读取数据时,检测数据的不一致,进行修复。比如 Cassandra 的 Read Repair 实现,具体来说,在向 Cassandra 系统查询数据的时候,如果检测到不同节点的副本数据不一致,系统就自动修复数据。
> - **写时修复** : 在写入数据,检测数据的不一致时,进行修复。比如 Cassandra 的 Hinted Handoff 实现。具体来说,Cassandra 集群的节点之间远程写数据的时候,如果写失败 就将数据缓存下来,然后定时重传,修复数据的不一致性。
-> - **异步修复** : 这个是最常用的方式,通过定时对账检测副本数据的一致性,并修复。
+> - **异步修复** : 这个是最常用的方式,通过定时对账检测副本数据的一致性,并修复。
比较推荐 **写时修复**,这种方式对性能消耗比较低。
diff --git a/docs/distributed-system/protocol/gossip-protocl.md b/docs/distributed-system/protocol/gossip-protocl.md
new file mode 100644
index 0000000000000000000000000000000000000000..16a2239a815ec61f301381433ea94e0efe62fd90
--- /dev/null
+++ b/docs/distributed-system/protocol/gossip-protocl.md
@@ -0,0 +1,143 @@
+---
+title: Gossip 协议详解
+category: 分布式
+tag:
+ - 分布式协议&算法
+ - 共识算法
+---
+
+## 背景
+
+在分布式系统中,不同的节点进行数据/信息共享是一个基本的需求。
+
+一种比较简单粗暴的方法就是 **集中式发散消息**,简单来说就是一个主节点同时共享最新信息给其他所有节点,比较适合中心化系统。这种方法的缺陷也很明显,节点多的时候不光同步消息的效率低,还太依赖与中心节点,存在单点风险问题。
+
+于是,**分散式发散消息** 的 **Gossip 协议** 就诞生了。
+
+## Gossip 协议介绍
+
+Gossip 直译过来就是闲话、流言蜚语的意思。流言蜚语有什么特点呢?容易被传播且传播速度还快,你传我我传他,然后大家都知道了。
+
+
+
+**Gossip 协议** 也叫 Epidemic 协议(流行病协议)或者 Epidemic propagation 算法(疫情传播算法),别名很多。不过,这些名字的特点都具有 **随机传播特性** (联想一下病毒传播、癌细胞扩散等生活中常见的情景),这也正是 Gossip 协议最主要的特点。
+
+Gossip 协议最早是在 ACM 上的一篇 1987 年发表的论文 [《Epidemic Algorithms for Replicated Database Maintenance》](https://dl.acm.org/doi/10.1145/41840.41841)中被提出的。根据论文标题,我们大概就能知道 Gossip 协议当时提出的主要应用是在分布式数据库系统中各个副本节点同步数据。
+
+正如 Gossip 协议其名一样,这是一种随机且带有传染性的方式将信息传播到整个网络中,并在一定时间内,使得系统内的所有节点数据一致。
+
+在 Gossip 协议下,没有所谓的中心节点,每个节点周期性地随机找一个节点互相同步彼此的信息,理论上来说,各个节点的状态最终会保持一致。
+
+下面我们来对 Gossip 协议的定义做一个总结:**Gossip 协议是一种允许在分布式系统中共享状态的去中心化通信协议,通过这种通信协议,我们可以将信息传播给网络或集群中的所有成员。**
+
+## Gossip 协议应用
+
+NoSQL 数据库 Redis 和 Apache Cassandra、服务网格解决方案 Consul 等知名项目都用到了 Gossip 协议,学习 Gossip 协议有助于我们搞清很多技术的底层原理。
+
+我们这里以 Redis Cluster 为例说明 Gossip 协议的实际应用。
+
+我们经常使用的分布式缓存 Redis 的官方集群解决方案(3.0 版本引入) Redis Cluster 就是基于 Gossip 协议来实现集群中各个节点数据的最终一致性。
+
+
+
+Redis Cluster 是一个典型的分布式系统,分布式系统中的各个节点需要互相通信。既然要相互通信就要遵循一致的通信协议,Redis Cluster 中的各个节点基于 **Gossip 协议** 来进行通信共享信息,每个 Redis 节点都维护了一份集群的状态信息。
+
+Redis Cluster 的节点之间会相互发送多种 Gossip 消息:
+
+- **MEET**:在 Redis Cluster 中的某个 Redis 节点上执行 `CLUSTER MEET ip port` 命令,可以向指定的 Redis 节点发送一条 MEET 信息,用于将其添加进 Redis Cluster 成为新的 Redis 节点。
+- **PING/PONG**:Redis Cluster 中的节点都会定时地向其他节点发送 PING 消息,来交换各个节点状态信息,检查各个节点状态,包括在线状态、疑似下线状态 PFAIL 和已下线状态 FAIL。
+- **FAIL**:Redis Cluster 中的节点 A 发现 B 节点 PFAIL ,并且在下线报告的有效期限内集群中半数以上的节点将 B 节点标记为 PFAIL,节点 A 就会向集群广播一条 FAIL 消息,通知其他节点将故障节点 B 标记为 FAIL 。
+- ......
+
+下图就是主从架构的 Redis Cluster 的示意图,图中的虚线代表的就是各个节点之间使用 Gossip 进行通信 ,实线表示主从复制。
+
+
+
+有了 Redis Cluster 之后,不需要专门部署 Sentinel 集群服务了。Redis Cluster 相当于是内置了 Sentinel 机制,Redis Cluster 内部的各个 Redis 节点通过 Gossip 协议互相探测健康状态,在故障时可以自动切换。
+
+关于 Redis Cluster 的详细介绍,可以查看这篇文章 [Redis 集群详解(付费)](https://javaguide.cn/database/redis/redis-cluster.html) 。
+
+## Gossip 协议消息传播模式
+
+Gossip 设计了两种可能的消息传播模式:**反熵(Anti-Entropy)** 和 **传谣(Rumor-Mongering)**。
+
+### 反熵(Anti-entropy)
+
+根据维基百科:
+
+> 熵的概念最早起源于[物理学](https://zh.wikipedia.org/wiki/物理学),用于度量一个热力学系统的混乱程度。熵最好理解为不确定性的量度而不是确定性的量度,因为越随机的信源的熵越大。
+
+在这里,你可以把反熵中的熵了解为节点之间数据的混乱程度/差异性,反熵就是指消除不同节点中数据的差异,提升节点间数据的相似度,从而降低熵值。
+
+具体是如何反熵的呢?集群中的节点,每隔段时间就随机选择某个其他节点,然后通过互相交换自己的所有数据来消除两者之间的差异,实现数据的最终一致性。
+
+在实现反熵的时候,主要有推、拉和推拉三种方式:
+
+- 推方式,就是将自己的所有副本数据,推给对方,修复对方副本中的熵。
+- 拉方式,就是拉取对方的所有副本数据,修复自己副本中的熵。
+- 推拉就是同时修复自己副本和对方副本中的熵。
+
+伪代码如下:
+
+
+
+在我们实际应用场景中,一般不会采用随机的节点进行反熵,而是需要可以的设计一个闭环。这样的话,我们能够在一个确定的时间范围内实现各个节点数据的最终一致性,而不是基于随机的概率。像 InfluxDB 就是这样来实现反熵的。
+
+
+
+1. 节点 A 推送数据给节点 B,节点 B 获取到节点 A 中的最新数据。
+2. 节点 B 推送数据给 C,节点 C 获取到节点 A,B 中的最新数据。
+3. 节点 C 推送数据给 A,节点 A 获取到节点 B,C 中的最新数据。
+4. 节点 A 再推送数据给 B 形成闭环,这样节点 B 就获取到节点 C 中的最新数据。
+
+虽然反熵很简单实用,但是,节点过多或者节点动态变化的话,反熵就不太适用了。这个时候,我们想要实现最终一致性就要靠 **谣言传播(Rumor mongering)** 。
+
+### 谣言传播(Rumor mongering)
+
+谣言传播指的是分布式系统中的一个节点一旦有了新数据之后,就会变为活跃节点,活跃节点会周期性地联系其他节点向其发送新数据,直到所有的节点都存储了该新数据。
+
+如下图所示(下图来自于[INTRODUCTION TO GOSSIP](https://managementfromscratch.wordpress.com/2016/04/01/introduction-to-gossip/) 这篇文章):
+
+
+
+伪代码如下:
+
+
+
+谣言传播比较适合节点数量比较多的情况,不过,这种模式下要尽量避免传播的信息包不能太大,避免网络消耗太大。
+
+### 总结
+
+- 反熵(Anti-Entropy)会传播节点的所有数据,而谣言传播(Rumor-Mongering)只会传播节点新增的数据。
+- 我们一般会给反熵设计一个闭环。
+- 谣言传播(Rumor-Mongering)比较适合节点数量比较多或者节点动态变化的场景。
+
+## Gossip 协议优势和缺陷
+
+**优势:**
+
+1、相比于其他分布式协议/算法来说,Gossip 协议理解起来非常简单。
+
+2、能够容忍网络上节点的随意地增加或者减少,宕机或者重启,因为 Gossip 协议下这些节点都是平等的,去中心化的。新增加或者重启的节点在理想情况下最终是一定会和其他节点的状态达到一致。
+
+3、速度相对较快。节点数量比较多的情况下,扩散速度比一个主节点向其他节点传播信息要更快(多播)。
+
+**缺陷** :
+
+1、消息需要通过多个传播的轮次才能传播到整个网络中,因此,必然会出现各节点状态不一致的情况。毕竟,Gossip 协议强调的是最终一致,至于达到各个节点的状态一致需要多长时间,谁也无从得知。
+
+2、由于拜占庭将军问题,不允许存在恶意节点。
+
+3、可能会出现消息冗余的问题。由于消息传播的随机性,同一个节点可能会重复收到相同的消息。
+
+## 总结
+
+- Gossip 协议是一种允许在分布式系统中共享状态的通信协议,通过这种通信协议,我们可以将信息传播给网络或集群中的所有成员。
+- Gossip 协议被 Redis、Apache Cassandra、Consul 等项目应用。
+- 谣言传播(Rumor-Mongering)比较适合节点数量比较多或者节点动态变化的场景。
+
+## 参考
+
+- 一万字详解 Redis Cluster Gossip 协议:https://segmentfault.com/a/1190000038373546
+- 《分布式协议与算法实战》
+- 《Redis 设计与实现》
diff --git a/docs/distributed-system/protocol/images/gossip/gossip-rumor- mongering.gif b/docs/distributed-system/protocol/images/gossip/gossip-rumor- mongering.gif
new file mode 100644
index 0000000000000000000000000000000000000000..5dfa2ccb7f9100cb0e30040bc8631f9764ecf9d6
Binary files /dev/null and b/docs/distributed-system/protocol/images/gossip/gossip-rumor- mongering.gif differ
diff --git a/docs/distributed-system/protocol/images/gossip/gossip.png b/docs/distributed-system/protocol/images/gossip/gossip.png
new file mode 100644
index 0000000000000000000000000000000000000000..2d85b8d9ee3dd0ed4e729d981f3d3466f777008e
Binary files /dev/null and b/docs/distributed-system/protocol/images/gossip/gossip.png differ
diff --git a/docs/distributed-system/protocol/images/gossip/redis-cluster-gossip.png b/docs/distributed-system/protocol/images/gossip/redis-cluster-gossip.png
new file mode 100644
index 0000000000000000000000000000000000000000..0485ae3e1da19ff1c1011b3e07557d64855301d2
Binary files /dev/null and b/docs/distributed-system/protocol/images/gossip/redis-cluster-gossip.png differ
diff --git "a/docs/distributed-system/protocol/images/gossip/\345\217\215\347\206\265-\351\227\255\347\216\257.drawio" "b/docs/distributed-system/protocol/images/gossip/\345\217\215\347\206\265-\351\227\255\347\216\257.drawio"
new file mode 100644
index 0000000000000000000000000000000000000000..bc00005d2b3533b1b2f2380339c37486fbef149d
--- /dev/null
+++ "b/docs/distributed-system/protocol/images/gossip/\345\217\215\347\206\265-\351\227\255\347\216\257.drawio"
@@ -0,0 +1 @@
+5VhNc5swEP01HONBYDAcjeOm06bTTHNoc+ooIIwaQIyQY5NfXwkkg4w/iJukziQHR/skFmnf7mPBsGfZ+orCIvlGIpQalhmtDfvSsCwATJf/E0jVIL4/aYAFxZFc1AK3+AlJ0JToEkeo1BYyQlKGCx0MSZ6jkGkYpJSs9GUxSfW7FnCBesBtCNM++hNHLGlQz5q0+GeEF4m6M3D9ZiaDarE8SZnAiKw6kD037BklhDWjbD1DqQieiktz3ac9s5uNUZSzIRcEfoFuKoCmflF8L77+dn58yS6kl0eYLuWBjblneFPD44OJ+A38qdw/q1RQuGMef24EqwQzdFvAUMyseApwLGFZyi3Ah7AsGlJivEZ8H0GM03RGUkJrR3Ycx1YYcrxklDygzkzk3ruOK2ZU2ExhPCAWJtJ5THImEwZ43O4HRJ0OUYbWHUgG6AqRDDFa8SVy1lZpJ7PVGkt71eFeQkmHdoVBmW2LjeeWED6QnDyDn/EAfmYfhx9rix/b+8/8+P3YR1w/pEkoS8iC5DCdt2hAyTKPRLTrkLVrrgkpZOj+IMYqGTu4ZERnbXBgS7KkITqwfUcqKqQLxI6noTjbQZooSiHDj7p2vnjQQb8qrJ08XMN7/jjSMz7Fi5yPQx4rxHM5EMmHud5P5USGo6ihCZX4Cd7X/gRRBcE5q4/iBIZzOYiHQznTy/rNQ0zeVHtO7KoGc2R66vlaaY4G8yB934izdZaQOC55QmwTtdnC6dw5AwQt+DiCNla5fC6CNunz0yMjj6aisxJVlMKyxOFheUJrzH4JY2RajrTvRHxHrrQu1zLctVEpI+cH+lUvdJR5151rL6utquPkBlHMAyIqvMZOl0jVdB6TSGegRHaIdXYQq7BTK1g1Mt5Eb2R8X3fRnFte1e0atxyNna0E9bcyrwlMz9FL6QWwewmpmHsPWq/K6Z+1/oKLPXBsjYsL+9zV3nsFNdmogqYJrUQcUIWOmEgNAgcV6MOphuOP/O7fVu2D3dPP1hTLHfl63zI2zQ30Vsri9pJz/I6URZXWSygLFxZPV5az7yPBji8XryMt4Iiw7BWJo8UPzqr4LXfPu/Vzy3v7I4ptv3HL0H/HsN9RYYN9LxEn9QwTxz+bnoGb7efOZnn70die/wU=
\ No newline at end of file
diff --git "a/docs/distributed-system/protocol/images/gossip/\345\217\215\347\206\265-\351\227\255\347\216\257.png" "b/docs/distributed-system/protocol/images/gossip/\345\217\215\347\206\265-\351\227\255\347\216\257.png"
new file mode 100644
index 0000000000000000000000000000000000000000..0bf4e6050469e9f3f1ed03fae279be0252bdbf74
Binary files /dev/null and "b/docs/distributed-system/protocol/images/gossip/\345\217\215\347\206\265-\351\227\255\347\216\257.png" differ
diff --git a/docs/distributed-system/protocol/images/paxos/paxos-made-simple.png b/docs/distributed-system/protocol/images/paxos/paxos-made-simple.png
new file mode 100644
index 0000000000000000000000000000000000000000..4e51f58db4bc6c01d8d880c66f69287bd14a0647
Binary files /dev/null and b/docs/distributed-system/protocol/images/paxos/paxos-made-simple.png differ
diff --git a/docs/distributed-system/protocol/paxos-algorithm.md b/docs/distributed-system/protocol/paxos-algorithm.md
new file mode 100644
index 0000000000000000000000000000000000000000..f271047c649ebdc43dec30b3094ff035d83a3a01
--- /dev/null
+++ b/docs/distributed-system/protocol/paxos-algorithm.md
@@ -0,0 +1,81 @@
+---
+title: Paxos 算法详解
+category: 分布式
+tag:
+ - 分布式协议&算法
+ - 共识算法
+---
+
+## 背景
+
+Paxos 算法是 Leslie Lamport([莱斯利·兰伯特](https://zh.wikipedia.org/wiki/莱斯利·兰伯特))在 **1990** 年提出了一种分布式系统 **共识** 算法。这也是第一个被证明完备的共识算法(前提是不存在拜占庭将军问题,也就是没有恶意节点)。
+
+为了介绍 Paxos 算法,兰伯特专门写了一篇幽默风趣的论文。在这篇论文中,他虚拟了一个叫做 Paxos 的希腊城邦来更形象化地介绍 Paxos 算法。
+
+不过,审稿人并不认可这篇论文的幽默。于是,他们就给兰伯特说:“如果你想要成功发表这篇论文的话,必须删除所有 Paxos 相关的故事背景”。兰伯特一听就不开心了:“我凭什么修改啊,你们这些审稿人就是缺乏幽默细胞,发不了就不发了呗!”。
+
+于是乎,提出 Paxos 算法的那篇论文在当时并没有被成功发表。
+
+直到 1998 年,系统研究中心 (Systems Research Center,SRC)的两个技术研究员需要找一些合适的分布式算法来服务他们正在构建的分布式系统,Paxos 算法刚好可以解决他们的部分需求。因此,兰伯特就把论文发给了他们。在看了论文之后,这俩大佬觉得论文还是挺不错的。于是,兰伯特在 **1998** 年重新发表论文 [《The Part-Time Parliament》](http://lamport.azurewebsites.net/pubs/lamport-paxos.pdf)。
+
+论文发表之后,各路学者直呼看不懂,言语中还略显调侃之意。这谁忍得了,在 **2001** 年的时候,兰伯特专门又写了一篇 [《Paxos Made Simple》](http://lamport.azurewebsites.net/pubs/paxos-simple.pdf) 的论文来简化对 Paxos 的介绍,主要讲述两阶段共识协议部分,顺便还不忘嘲讽一下这群学者。
+
+《Paxos Made Simple》这篇论文就 14 页,相比于 《The Part-Time Parliament》的 33 页精简了不少。最关键的是这篇论文的摘要就一句话:
+
+
+
+> The Paxos algorithm, when presented in plain English, is very simple.
+
+翻译过来的意思大概就是:当我用无修饰的英文来描述时,Paxos 算法真心简单!
+
+有没有感觉到来自兰伯特大佬满满地嘲讽的味道?
+
+## 介绍
+
+Paxos 算法是第一个被证明完备的分布式系统共识算法。共识算法的作用是让分布式系统中的多个节点之间对某个提案(Proposal)达成一致的看法。提案的含义在分布式系统中十分宽泛,像哪一个节点是 Leader 节点、多个事件发生的顺序等等都可以是一个提案。
+
+兰伯特当时提出的 Paxos 算法主要包含 2 个部分:
+
+- **Basic Paxos 算法**:描述的是多节点之间如何就某个值(提案 Value)达成共识。
+- **Multi-Paxos 思想**:描述的是执行多个 Basic Paxos 实例,就一系列值达成共识。Multi-Paxos 说白了就是执行多次 Basic Paxos ,核心还是 Basic Paxos 。
+
+由于 Paxos 算法在国际上被公认的非常难以理解和实现,因此不断有人尝试简化这一算法。到了 2013 年才诞生了一个比 Paxos 算法更易理解和实现的共识算法—[Raft 算法](https://javaguide.cn/distributed-system/theorem&algorithm&protocol/raft-algorithm.html) 。更具体点来说,Raft 是 Multi-Paxos 的一个变种,其简化了 Multi-Paxos 的思想,变得更容易被理解以及工程实现。
+
+针对没有恶意节点的情况,除了 Raft 算法之外,当前最常用的一些共识算法比如 **ZAB 协议**、 **Fast Paxos** 算法都是基于 Paxos 算法改进的。
+
+针对存在恶意节点的情况,一般使用的是 **工作量证明(POW,Proof-of-Work)**、 **权益证明(PoS,Proof-of-Stake )** 等共识算法。这类共识算法最典型的应用就是区块链,就比如说前段时间以太坊官方宣布其共识机制正在从工作量证明(PoW)转变为权益证明(PoS)。
+
+区块链系统使用的共识算法需要解决的核心问题是 **拜占庭将军问题** ,这和我们日常接触到的 ZooKeeper、Etcd、Consul 等分布式中间件不太一样。
+
+下面我们来对 Paxos 算法的定义做一个总结:
+
+- Paxos 算法是兰伯特在 **1990** 年提出了一种分布式系统共识算法。
+- 兰伯特当时提出的 Paxos 算法主要包含 2 个部分: Basic Paxos 算法和 Multi-Paxos 思想。
+- Raft 算法、ZAB 协议、 Fast Paxos 算法都是基于 Paxos 算法改进而来。
+
+## Basic Paxos 算法
+
+Basic Paxos 中存在 3 个重要的角色:
+
+1. **提议者(Proposer)**:也可以叫做协调者(coordinator),提议者负责接受客户端的请求并发起提案。提案信息通常包括提案编号 (Proposal ID) 和提议的值 (Value)。
+2. **接受者(Acceptor)**:也可以叫做投票员(voter),负责对提议者的提案进行投票,同时需要记住自己的投票历史;
+3. **学习者(Learner)**:如果有超过半数接受者就某个提议达成了共识,那么学习者就需要接受这个提议,并就该提议作出运算,然后将运算结果返回给客户端。
+
+
+
+为了减少实现该算法所需的节点数,一个节点可以身兼多个角色。并且,一个提案被选定需要被半数以上的 Acceptor 接受。这样的话,Basic Paxos 算法还具备容错性,在少于一半的节点出现故障时,集群仍能正常工作。
+
+## Multi Paxos 思想
+
+Basic Paxos 算法的仅能就单个值达成共识,为了能够对一系列的值达成共识,我们需要用到 Basic Paxos 思想。
+
+⚠️**注意**:Multi-Paxos 只是一种思想,这种思想的核心就是通过多个 Basic Paxos 实例就一系列值达成共识。也就是说,Basic Paxos 是 Multi-Paxos 思想的核心,Multi-Paxos 就是多执行几次 Basic Paxos。
+
+由于兰伯特提到的 Multi-Paxos 思想缺少代码实现的必要细节(比如怎么选举领导者),所以在理解和实现上比较困难。
+
+不过,也不需要担心,我们并不需要自己实现基于 Multi-Paxos 思想的共识算法,业界已经有了比较出名的实现。像 Raft 算法就是 Multi-Paxos 的一个变种,其简化了 Multi-Paxos 的思想,变得更容易被理解以及工程实现,实际项目中可以优先考虑 Raft 算法。
+
+## 参考
+
+- https://zh.wikipedia.org/wiki/Paxos
+- 分布式系统中的一致性与共识算法:http://www.xuyasong.com/?p=1970
diff --git a/docs/distributed-system/protocol/raft-algorithm.md b/docs/distributed-system/protocol/raft-algorithm.md
new file mode 100644
index 0000000000000000000000000000000000000000..f09af1afe9a3ff65886e7ae0a5e3dc4bd646ba10
--- /dev/null
+++ b/docs/distributed-system/protocol/raft-algorithm.md
@@ -0,0 +1,169 @@
+---
+title: Raft 算法详解
+category: 分布式
+tag:
+ - 分布式协议&算法
+ - 共识算法
+---
+
+> 本文由 [SnailClimb](https://github.com/Snailclimb) 和 [Xieqijun](https://github.com/jun0315) 共同完成。
+
+## 1 背景
+
+当今的数据中心和应用程序在高度动态的环境中运行,为了应对高度动态的环境,它们通过额外的服务器进行横向扩展,并且根据需求进行扩展和收缩。同时,服务器和网络故障也很常见。
+
+因此,系统必须在正常操作期间处理服务器的上下线。它们必须对变故做出反应并在几秒钟内自动适应;对客户来说的话,明显的中断通常是不可接受的。
+
+幸运的是,分布式共识可以帮助应对这些挑战。
+
+### 1.1 拜占庭将军
+
+在介绍共识算法之前,先介绍一个简化版拜占庭将军的例子来帮助理解共识算法。
+
+> 假设多位拜占庭将军中没有叛军,信使的信息可靠但有可能被暗杀的情况下,将军们如何达成是否要进攻的一致性决定?
+
+解决方案大致可以理解成:先在所有的将军中选出一个大将军,用来做出所有的决定。
+
+举例如下:假如现在一共有 3 个将军 A,B 和 C,每个将军都有一个随机时间的倒计时器,倒计时一结束,这个将军就把自己当成大将军候选人,然后派信使传递选举投票的信息给将军 B 和 C,如果将军 B 和 C 还没有把自己当作候选人(自己的倒计时还没有结束),并且没有把选举票投给其他人,它们就会把票投给将军 A,信使回到将军 A 时,将军 A 知道自己收到了足够的票数,成为大将军。在有了大将军之后,是否需要进攻就由大将军 A 决定,然后再去派信使通知另外两个将军,自己已经成为了大将军。如果一段时间还没收到将军 B 和 C 的回复(信使可能会被暗杀),那就再重派一个信使,直到收到回复。
+
+### 1.2 共识算法
+
+共识是可容错系统中的一个基本问题:即使面对故障,服务器也可以在共享状态上达成一致。
+
+共识算法允许一组节点像一个整体一样一起工作,即使其中的一些节点出现故障也能够继续工作下去,其正确性主要是源于复制状态机的性质:一组`Server`的状态机计算相同状态的副本,即使有一部分的`Server`宕机了它们仍然能够继续运行。
+
+
+
+`图-1 复制状态机架构`
+
+一般通过使用复制日志来实现复制状态机。每个`Server`存储着一份包括命令序列的日志文件,状态机会按顺序执行这些命令。因为每个日志包含相同的命令,并且顺序也相同,所以每个状态机处理相同的命令序列。由于状态机是确定性的,所以处理相同的状态,得到相同的输出。
+
+因此共识算法的工作就是保持复制日志的一致性。服务器上的共识模块从客户端接收命令并将它们添加到日志中。它与其他服务器上的共识模块通信,以确保即使某些服务器发生故障。每个日志最终包含相同顺序的请求。一旦命令被正确地复制,它们就被称为已提交。每个服务器的状态机按照日志顺序处理已提交的命令,并将输出返回给客户端,因此,这些服务器形成了一个单一的、高度可靠的状态机。
+
+适用于实际系统的共识算法通常具有以下特性:
+
+- 安全。确保在非拜占庭条件(也就是上文中提到的简易版拜占庭)下的安全性,包括网络延迟、分区、包丢失、复制和重新排序。
+- 高可用。只要大多数服务器都是可操作的,并且可以相互通信,也可以与客户端进行通信,那么这些服务器就可以看作完全功能可用的。因此,一个典型的由五台服务器组成的集群可以容忍任何两台服务器端故障。假设服务器因停止而发生故障;它们稍后可能会从稳定存储上的状态中恢复并重新加入集群。
+- 一致性不依赖时序。错误的时钟和极端的消息延迟,在最坏的情况下也只会造成可用性问题,而不会产生一致性问题。
+
+- 在集群中大多数服务器响应,命令就可以完成,不会被少数运行缓慢的服务器来影响整体系统性能。
+
+## 2 基础
+
+### 2.1 节点类型
+
+一个 Raft 集群包括若干服务器,以典型的 5 服务器集群举例。在任意的时间,每个服务器一定会处于以下三个状态中的一个:
+
+- `Leader`:负责发起心跳,响应客户端,创建日志,同步日志。
+- `Candidate`:Leader 选举过程中的临时角色,由 Follower 转化而来,发起投票参与竞选。
+- `Follower`:接受 Leader 的心跳和日志同步数据,投票给 Candidate。
+
+在正常的情况下,只有一个服务器是 Leader,剩下的服务器是 Follower。Follower 是被动的,它们不会发送任何请求,只是响应来自 Leader 和 Candidate 的请求。
+
+
+
+`图-2:服务器的状态`
+
+### 2.2 任期
+
+
+
+`图-3:任期`
+
+如图 3 所示,raft 算法将时间划分为任意长度的任期(term),任期用连续的数字表示,看作当前 term 号。每一个任期的开始都是一次选举,在选举开始时,一个或多个 Candidate 会尝试成为 Leader。如果一个 Candidate 赢得了选举,它就会在该任期内担任 Leader。如果没有选出 Leader,将会开启另一个任期,并立刻开始下一次选举。raft 算法保证在给定的一个任期最少要有一个 Leader。
+
+每个节点都会存储当前的 term 号,当服务器之间进行通信时会交换当前的 term 号;如果有服务器发现自己的 term 号比其他人小,那么他会更新到较大的 term 值。如果一个 Candidate 或者 Leader 发现自己的 term 过期了,他会立即退回成 Follower。如果一台服务器收到的请求的 term 号是过期的,那么它会拒绝此次请求。
+
+### 2.3 日志
+
+- `entry`:每一个事件成为 entry,只有 Leader 可以创建 entry。entry 的内容为``其中 cmd 是可以应用到状态机的操作。
+- `log`:由 entry 构成的数组,每一个 entry 都有一个表明自己在 log 中的 index。只有 Leader 才可以改变其他节点的 log。entry 总是先被 Leader 添加到自己的 log 数组中,然后再发起共识请求,获得同意后才会被 Leader 提交给状态机。Follower 只能从 Leader 获取新日志和当前的 commitIndex,然后把对应的 entry 应用到自己的状态机中。
+
+## 3 领导人选举
+
+raft 使用心跳机制来触发 Leader 的选举。
+
+如果一台服务器能够收到来自 Leader 或者 Candidate 的有效信息,那么它会一直保持为 Follower 状态,并且刷新自己的 electionElapsed,重新计时。
+
+Leader 会向所有的 Follower 周期性发送心跳来保证自己的 Leader 地位。如果一个 Follower 在一个周期内没有收到心跳信息,就叫做选举超时,然后它就会认为此时没有可用的 Leader,并且开始进行一次选举以选出一个新的 Leader。
+
+为了开始新的选举,Follower 会自增自己的 term 号并且转换状态为 Candidate。然后他会向所有节点发起 RequestVoteRPC 请求, Candidate 的状态会持续到以下情况发生:
+
+- 赢得选举
+- 其他节点赢得选举
+- 一轮选举结束,无人胜出
+
+赢得选举的条件是:一个 Candidate 在一个任期内收到了来自集群内的多数选票`(N/2+1)`,就可以成为 Leader。
+
+在 Candidate 等待选票的时候,它可能收到其他节点声明自己是 Leader 的心跳,此时有两种情况:
+
+- 该 Leader 的 term 号大于等于自己的 term 号,说明对方已经成为 Leader,则自己回退为 Follower。
+- 该 Leader 的 term 号小于自己的 term 号,那么会拒绝该请求并让该节点更新 term。
+
+由于可能同一时刻出现多个 Candidate,导致没有 Candidate 获得大多数选票,如果没有其他手段来重新分配选票的话,那么可能会无限重复下去。
+
+raft 使用了随机的选举超时时间来避免上述情况。每一个 Candidate 在发起选举后,都会随机化一个新的选举超时时间,这种机制使得各个服务器能够分散开来,在大多数情况下只有一个服务器会率先超时;它会在其他服务器超时之前赢得选举。
+
+## 4 日志复制
+
+一旦选出了 Leader,它就开始接受客户端的请求。每一个客户端的请求都包含一条需要被复制状态机(`Replicated State Mechine`)执行的命令。
+
+Leader 收到客户端请求后,会生成一个 entry,包含``,再将这个 entry 添加到自己的日志末尾后,向所有的节点广播该 entry,要求其他服务器复制这条 entry。
+
+如果 Follower 接受该 entry,则会将 entry 添加到自己的日志后面,同时返回给 Leader 同意。
+
+如果 Leader 收到了多数的成功响应,Leader 会将这个 entry 应用到自己的状态机中,之后可以成为这个 entry 是 committed 的,并且向客户端返回执行结果。
+
+raft 保证以下两个性质:
+
+- 在两个日志里,有两个 entry 拥有相同的 index 和 term,那么它们一定有相同的 cmd
+- 在两个日志里,有两个 entry 拥有相同的 index 和 term,那么它们前面的 entry 也一定相同
+
+通过“仅有 Leader 可以生成 entry”来保证第一个性质,第二个性质需要一致性检查来进行保证。
+
+一般情况下,Leader 和 Follower 的日志保持一致,然后,Leader 的崩溃会导致日志不一样,这样一致性检查会产生失败。Leader 通过强制 Follower 复制自己的日志来处理日志的不一致。这就意味着,在 Follower 上的冲突日志会被领导者的日志覆盖。
+
+为了使得 Follower 的日志和自己的日志一致,Leader 需要找到 Follower 与它日志一致的地方,然后删除 Follower 在该位置之后的日志,接着把这之后的日志发送给 Follower。
+
+`Leader` 给每一个`Follower` 维护了一个 `nextIndex`,它表示 `Leader` 将要发送给该追随者的下一条日志条目的索引。当一个 `Leader` 开始掌权时,它会将 `nextIndex` 初始化为它的最新的日志条目索引数+1。如果一个 `Follower` 的日志和 `Leader` 的不一致,`AppendEntries` 一致性检查会在下一次 `AppendEntries RPC` 时返回失败。在失败之后,`Leader` 会将 `nextIndex` 递减然后重试 `AppendEntries RPC`。最终 `nextIndex` 会达到一个 `Leader` 和 `Follower` 日志一致的地方。这时,`AppendEntries` 会返回成功,`Follower` 中冲突的日志条目都被移除了,并且添加所缺少的上了 `Leader` 的日志条目。一旦 `AppendEntries` 返回成功,`Follower` 和 `Leader` 的日志就一致了,这样的状态会保持到该任期结束。
+
+## 5 安全性
+
+### 5.1 选举限制
+
+Leader 需要保证自己存储全部已经提交的日志条目。这样才可以使日志条目只有一个流向:从 Leader 流向 Follower,Leader 永远不会覆盖已经存在的日志条目。
+
+每个 Candidate 发送 RequestVoteRPC 时,都会带上最后一个 entry 的信息。所有节点收到投票信息时,会对该 entry 进行比较,如果发现自己的更新,则拒绝投票给该 Candidate。
+
+判断日志新旧的方式:如果两个日志的 term 不同,term 大的更新;如果 term 相同,更长的 index 更新。
+
+### 5.2 节点崩溃
+
+如果 Leader 崩溃,集群中的节点在 electionTimeout 时间内没有收到 Leader 的心跳信息就会触发新一轮的选主,在选主期间整个集群对外是不可用的。
+
+如果 Follower 和 Candidate 崩溃,处理方式会简单很多。之后发送给它的 RequestVoteRPC 和 AppendEntriesRPC 会失败。由于 raft 的所有请求都是幂等的,所以失败的话会无限的重试。如果崩溃恢复后,就可以收到新的请求,然后选择追加或者拒绝 entry。
+
+### 5.3 时间与可用性
+
+raft 的要求之一就是安全性不依赖于时间:系统不能仅仅因为一些事件发生的比预想的快一些或者慢一些就产生错误。为了保证上述要求,最好能满足以下的时间条件:
+
+`broadcastTime << electionTimeout << MTBF`
+
+- `broadcastTime`:向其他节点并发发送消息的平均响应时间;
+- `electionTimeout`:选举超时时间;
+- `MTBF(mean time between failures)`:单台机器的平均健康时间;
+
+`broadcastTime`应该比`electionTimeout`小一个数量级,为的是使`Leader`能够持续发送心跳信息(heartbeat)来阻止`Follower`开始选举;
+
+`electionTimeout`也要比`MTBF`小几个数量级,为的是使得系统稳定运行。当`Leader`崩溃时,大约会在整个`electionTimeout`的时间内不可用;我们希望这种情况仅占全部时间的很小一部分。
+
+由于`broadcastTime`和`MTBF`是由系统决定的属性,因此需要决定`electionTimeout`的时间。
+
+一般来说,broadcastTime 一般为 `0.5~20ms`,electionTimeout 可以设置为 `10~500ms`,MTBF 一般为一两个月。
+
+## 6 参考
+
+- https://tanxinyu.work/raft/
+- https://github.com/OneSizeFitsQuorum/raft-thesis-zh_cn/blob/master/raft-thesis-zh_cn.md
+- https://github.com/ongardie/dissertation/blob/master/stanford.pdf
+- https://knowledge-sharing.gitbooks.io/raft/content/chapter5.html
diff --git a/docs/distributed-system/rpc/dubbo.md b/docs/distributed-system/rpc/dubbo.md
index 870cc45dc5f4a67b11cb5e1419b036cc57655f7f..22a0618e76baadbc6cfa9f91288bac04f39c122f 100644
--- a/docs/distributed-system/rpc/dubbo.md
+++ b/docs/distributed-system/rpc/dubbo.md
@@ -1,98 +1,64 @@
-# Dubbo知识点&面试题总结
+---
+title: Dubbo常见问题总结
+category: 分布式
+tag:
+ - rpc
+---
-这篇文章是我根据官方文档以及自己平时的使用情况,对 Dubbo 所做的一个总结。欢迎补充!
-
-## RPC基础
-
-### 何为 RPC?
-
-**RPC(Remote Procedure Call)** 即远程过程调用,通过名字我们就能看出 RPC 关注的是远程调用而非本地调用。
-
-**为什么要 RPC ?** 因为,两个不同的服务器上的服务提供的方法不在一个内存空间,所以,需要通过网络编程才能传递方法调用所需要的参数。并且,方法调用的结果也需要通过网络编程来接收。但是,如果我们自己手动网络编程来实现这个调用过程的话工作量是非常大的,因为,我们需要考虑底层传输方式(TCP还是UDP)、序列化方式等等方面。
-
-
-**RPC 能帮助我们做什么呢?** 简单来说,通过 RPC 可以帮助我们调用远程计算机上某个服务的方法,这个过程就像调用本地方法一样简单。并且!我们不需要了解底层网络编程的具体细节。
-
-
-举个例子:两个不同的服务 A、B 部署在两台不同的机器上,服务 A 如果想要调用服务 B 中的某个方法的话就可以通过 RPC 来做。
-
-一言蔽之:**RPC 的出现就是为了让你调用远程方法像调用本地方法一样简单。**
-
-### RPC 的原理是什么?
-
-为了能够帮助小伙伴们理解 RPC 原理,我们可以将整个 RPC的 核心功能看作是下面👇 6 个部分实现的:
-
-
-1. **客户端(服务消费端)** :调用远程方法的一端。
-1. **客户端 Stub(桩)** : 这其实就是一代理类。代理类主要做的事情很简单,就是把你调用方法、类、方法参数等信息传递到服务端。
-1. **网络传输** : 网络传输就是你要把你调用的方法的信息比如说参数啊这些东西传输到服务端,然后服务端执行完之后再把返回结果通过网络传输给你传输回来。网络传输的实现方式有很多种比如最近基本的 Socket或者性能以及封装更加优秀的 Netty(推荐)。
-1. **服务端 Stub(桩)** :这个桩就不是代理类了。我觉得理解为桩实际不太好,大家注意一下就好。这里的服务端 Stub 实际指的就是接收到客户端执行方法的请求后,去指定对应的方法然后返回结果给客户端的类。
-1. **服务端(服务提供端)** :提供远程方法的一端。
-
-具体原理图如下,后面我会串起来将整个RPC的过程给大家说一下。
+::: tip
+- Dubbo3 已经发布,这篇文章是基于 Dubbo2 写的。Dubbo3 基于 Dubbo2 演进而来,在保持原有核心功能特性的同时, Dubbo3 在易用性、超大规模微服务实践、云原生基础设施适配、安全设计等几大方向上进行了全面升级。
+- 本文中的很多链接已经失效,主要原因是因为 Dubbo 官方文档进行了修改导致 URL 失效。
-
+:::
-1. 服务消费端(client)以本地调用的方式调用远程服务;
-1. 客户端 Stub(client stub) 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(序列化):`RpcRequest`;
-1. 客户端 Stub(client stub) 找到远程服务的地址,并将消息发送到服务提供端;
-1. 服务端 Stub(桩)收到消息将消息反序列化为Java对象: `RpcRequest`;
-1. 服务端 Stub(桩)根据`RpcRequest`中的类、方法、方法参数等信息调用本地的方法;
-1. 服务端 Stub(桩)得到方法执行结果并将组装成能够进行网络传输的消息体:`RpcResponse`(序列化)发送至消费方;
-1. 客户端 Stub(client stub)接收到消息并将消息反序列化为Java对象:`RpcResponse` ,这样也就得到了最终结果。over!
-
-相信小伙伴们看完上面的讲解之后,已经了解了 RPC 的原理。
-
-虽然篇幅不多,但是基本把 RPC 框架的核心原理讲清楚了!另外,对于上面的技术细节,我会在后面的章节介绍到。
-
-**最后,对于 RPC 的原理,希望小伙伴不单单要理解,还要能够自己画出来并且能够给别人讲出来。因为,在面试中这个问题在面试官问到 RPC 相关内容的时候基本都会碰到。**
+这篇文章是我根据官方文档以及自己平时的使用情况,对 Dubbo 所做的一个总结。欢迎补充!
-## Dubbo基础
+## Dubbo 基础
### 什么是 Dubbo?
-
+
-[Apache Dubbo](https://github.com/apache/dubbo) |ˈdʌbəʊ| 是一款高性能、轻量级的开源 Java RPC 框架。
+[Apache Dubbo](https://github.com/apache/dubbo) |ˈdʌbəʊ| 是一款高性能、轻量级的开源 WEB 和 RPC 框架。
根据 [Dubbo 官方文档](https://dubbo.apache.org/zh/)的介绍,Dubbo 提供了六大核心能力
-1. 面向接口代理的高性能RPC调用。
+1. 面向接口代理的高性能 RPC 调用。
2. 智能容错和负载均衡。
3. 服务自动注册和发现。
4. 高度可扩展能力。
5. 运行期流量调度。
6. 可视化的服务治理与运维。
-
+
-简单来说就是: **Dubbo 不光可以帮助我们调用远程服务,还提供了一些其他开箱即用的功能比如智能负载均衡。**
+简单来说就是:**Dubbo 不光可以帮助我们调用远程服务,还提供了一些其他开箱即用的功能比如智能负载均衡。**
-Dubbo 目前已经有接近 34.4 k 的 Star 。
+Dubbo 目前已经有接近 34.4 k 的 Star 。
-在 **2020 年度 OSC 中国开源项目** 评选活动中,Dubbo 位列开发框架和基础组件类项目的第7名。想比几年前来说,热度和排名有所下降。
+在 **2020 年度 OSC 中国开源项目** 评选活动中,Dubbo 位列开发框架和基础组件类项目的第 7 名。相比几年前来说,热度和排名有所下降。
-
+
-Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出现,才使得越来越多的公司开始使用以及接受分布式架构。
+Dubbo 是由阿里开源,后来加入了 Apache 。正是由于 Dubbo 的出现,才使得越来越多的公司开始使用以及接受分布式架构。
### 为什么要用 Dubbo?
-随着互联网的发展,网站的规模越来越大,用户数量越来越多。单一应用架构 、垂直应用架构无法满足我们的需求,这个时候分布式服务架构就诞生了。
+随着互联网的发展,网站的规模越来越大,用户数量越来越多。单一应用架构、垂直应用架构无法满足我们的需求,这个时候分布式服务架构就诞生了。
分布式服务架构下,系统被拆分成不同的服务比如短信服务、安全服务,每个服务独立提供系统的某个核心服务。
-我们可以使用 Java RMI(Java Remote Method Invocation)、Hessian这种支持远程调用的框架来简单地暴露和引用远程服务。但是!当服务越来越多之后,服务调用关系越来越复杂。当应用访问压力越来越大后,负载均衡以及服务监控的需求也迫在眉睫。我们可以用 F5 这类硬件来做负载均衡,但这样增加了成本,并且存在单点故障的风险。
+我们可以使用 Java RMI(Java Remote Method Invocation)、Hessian 这种支持远程调用的框架来简单地暴露和引用远程服务。但是!当服务越来越多之后,服务调用关系越来越复杂。当应用访问压力越来越大后,负载均衡以及服务监控的需求也迫在眉睫。我们可以用 F5 这类硬件来做负载均衡,但这样增加了成本,并且存在单点故障的风险。
不过,Dubbo 的出现让上述问题得到了解决。**Dubbo 帮助我们解决了什么问题呢?**
-1. **负载均衡** : 同一个服务部署在不同的机器时该调用哪一台机器上的服务。
-2. **服务调用链路生成** : 随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。
-3. **服务访问压力以及时长统计、资源调度和治理** :基于访问压力实时管理集群容量,提高集群利用率。
+1. **负载均衡**:同一个服务部署在不同的机器时该调用哪一台机器上的服务。
+2. **服务调用链路生成**:随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。
+3. **服务访问压力以及时长统计、资源调度和治理**:基于访问压力实时管理集群容量,提高集群利用率。
4. ......
-
+
另外,Dubbo 除了能够应用在分布式系统中,也可以应用在现在比较火的微服务系统中。不过,由于 Spring Cloud 在微服务中应用更加广泛,所以,我觉得一般我们提 Dubbo 的话,大部分是分布式系统的情况。
@@ -104,7 +70,7 @@ Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出
分布式或者说 SOA 分布式重要的就是面向服务,说简单的分布式就是我们把整个系统拆分成不同的服务然后将这些服务放在不同的服务器上减轻单体服务的压力提高并发量和性能。比如电商系统可以简单地拆分成订单系统、商品系统、登录系统等等,拆分之后的每个服务可以部署在不同的机器上,如果某一个服务的访问量比较大的话也可以将这个服务同时部署在多台机器上。
-
+
### 为什么要分布式?
@@ -118,7 +84,7 @@ Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出
[官方文档中的框架设计章节](https://dubbo.apache.org/zh/docs/v2.7/dev/design/) 已经介绍的非常详细了,我这里把一些比较重要的点再提一下。
-
+
上述节点简单介绍以及他们之间的关系:
@@ -134,14 +100,14 @@ Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出
简单来说,`Invoker` 就是 Dubbo 对远程调用的抽象。
-
+
-按照 Dubbo 官方的话来说,`Invoker` 分为
+按照 Dubbo 官方的话来说,`Invoker` 分为
-- 服务提供 `Invoker`
+- 服务提供 `Invoker`
- 服务消费 `Invoker`
-假如我们需要调用一个远程方法,我们需要动态代理来屏蔽远程调用的细节吧!我们屏蔽掉的这些细节就依赖对应的 `Invoker` 实现, `Invoker` 实现了真正的远程服务调用。
+假如我们需要调用一个远程方法,我们需要动态代理来屏蔽远程调用的细节吧!我们屏蔽掉的这些细节就依赖对应的 `Invoker` 实现, `Invoker` 实现了真正的远程服务调用。
### Dubbo 的工作原理了解么?
@@ -149,9 +115,9 @@ Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出
> 左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口。
-
+
-- **config 配置层**:Dubbo相关的配置。支持代码配置,同时也支持基于 Spring 来做配置,以 `ServiceConfig`, `ReferenceConfig` 为中心
+- **config 配置层**:Dubbo 相关的配置。支持代码配置,同时也支持基于 Spring 来做配置,以 `ServiceConfig`, `ReferenceConfig` 为中心
- **proxy 服务代理层**:调用远程方法像调用本地的方法一样简单的一个关键,真实调用过程依赖代理类,以 `ServiceProxy` 为中心。
- **registry 注册中心层**:封装服务地址的注册与发现。
- **cluster 路由层**:封装多个提供者的路由及负载均衡,并桥接注册中心,以 `Invoker` 为中心。
@@ -159,7 +125,7 @@ Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出
- **protocol 远程调用层**:封装 RPC 调用,以 `Invocation`, `Result` 为中心。
- **exchange 信息交换层**:封装请求响应模式,同步转异步,以 `Request`, `Response` 为中心。
- **transport 网络传输层**:抽象 mina 和 netty 为统一接口,以 `Message` 为中心。
-- **serialize 数据序列化层** :对需要在网络传输的数据进行序列化。
+- **serialize 数据序列化层**:对需要在网络传输的数据进行序列化。
### Dubbo 的 SPI 机制了解么? 如何扩展 Dubbo 中的默认实现?
@@ -167,7 +133,7 @@ SPI(Service Provider Interface) 机制被大量用在开源项目中,它
SPI 的具体原理是这样的:我们将接口的实现类放在配置文件中,我们在程序运行过程中读取配置文件,通过反射加载实现类。这样,我们可以在运行的时候,动态替换接口的实现类。和 IoC 的解耦思想是类似的。
-Java 本身就提供了 SPI 机制的实现。不过,Dubbo 没有直接用,而是对 Java原生的 SPI机制进行了增强,以便更好满足自己的需求。
+Java 本身就提供了 SPI 机制的实现。不过,Dubbo 没有直接用,而是对 Java 原生的 SPI 机制进行了增强,以便更好满足自己的需求。
**那我们如何扩展 Dubbo 中的默认实现呢?**
@@ -175,12 +141,12 @@ Java 本身就提供了 SPI 机制的实现。不过,Dubbo 没有直接用,
```java
package com.xxx;
-
+
import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Invocation;
-import org.apache.dubbo.rpc.RpcException;
-
+import org.apache.dubbo.rpc.RpcException;
+
public class XxxLoadBalance implements LoadBalance {
public Invoker select(List> invokers, Invocation invocation) throws RpcException {
// ...
@@ -203,15 +169,13 @@ src
|-org.apache.dubbo.rpc.cluster.LoadBalance (纯文本文件,内容为:xxx=com.xxx.XxxLoadBalance)
```
-`org.apache.dubbo.rpc.cluster.LoadBalance`
+`org.apache.dubbo.rpc.cluster.LoadBalance`
```
xxx=com.xxx.XxxLoadBalance
```
-其他还有很多可供扩展的选择,你可以在[官方文档@SPI扩展实现](https://dubbo.apache.org/zh/docs/v2.7/dev/impls/)这里找到。
-
-
+其他还有很多可供扩展的选择,你可以在[官方文档](https://cn.dubbo.apache.org/zh-cn/overview/home/)中找到。
### Dubbo 的微内核架构了解吗?
@@ -223,17 +187,17 @@ Dubbo 采用 微内核(Microkernel) + 插件(Plugin) 模式,简单来
微内核架构包含两类组件:**核心系统(core system)** 和 **插件模块(plug-in modules)**。
-
+
核心系统提供系统所需核心能力,插件模块可以扩展系统的功能。因此, 基于微内核架构的系统,非常易于扩展功能。
-我们常见的一些IDE,都可以看作是基于微内核架构设计的。绝大多数 IDE比如IDEA、VSCode都提供了插件来丰富自己的功能。
+我们常见的一些 IDE,都可以看作是基于微内核架构设计的。绝大多数 IDE 比如 IDEA、VSCode 都提供了插件来丰富自己的功能。
-正是因为Dubbo基于微内核架构,才使得我们可以随心所欲替换Dubbo的功能点。比如你觉得Dubbo 的序列化模块实现的不满足自己要求,没关系啊!你自己实现一个序列化模块就好了啊!
+正是因为 Dubbo 基于微内核架构,才使得我们可以随心所欲替换 Dubbo 的功能点。比如你觉得 Dubbo 的序列化模块实现的不满足自己要求,没关系啊!你自己实现一个序列化模块就好了啊!
-通常情况下,微核心都会采用 Factory、IoC、OSGi 等方式管理插件生命周期。Dubbo 不想依赖 Spring 等 IoC 容器,也不想自己造一个小的 IoC 容器(过度设计),因此采用了一种最简单的 Factory 方式管理插件 :**JDK 标准的 SPI 扩展机制** (`java.util.ServiceLoader`)。
+通常情况下,微核心都会采用 Factory、IoC、OSGi 等方式管理插件生命周期。Dubbo 不想依赖 Spring 等 IoC 容器,也不想自己造一个小的 IoC 容器(过度设计),因此采用了一种最简单的 Factory 方式管理插件:**JDK 标准的 SPI 扩展机制** (`java.util.ServiceLoader`)。
-### 关于Dubbo架构的一些自测小问题
+### 关于 Dubbo 架构的一些自测小问题
#### 注册中心的作用了解么?
@@ -251,7 +215,6 @@ Dubbo 采用 微内核(Microkernel) + 插件(Plugin) 模式,简单来
不会。两者都宕机也不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表。注册中心和监控中心都是可选的,服务消费者可以直连服务提供者。
-
## Dubbo 的负载均衡策略
### 什么是负载均衡?
@@ -266,7 +229,7 @@ Dubbo 采用 微内核(Microkernel) + 插件(Plugin) 模式,简单来
### Dubbo 提供的负载均衡策略有哪些?
-在集群负载均衡时,Dubbo 提供了多种均衡策略,默认为 `random` 随机调用。我们还可以自行扩展负载均衡策略(参考Dubbo SPI机制)。
+在集群负载均衡时,Dubbo 提供了多种均衡策略,默认为 `random` 随机调用。我们还可以自行扩展负载均衡策略(参考 Dubbo SPI 机制)。
在 Dubbo 中,所有负载均衡实现类均继承自 `AbstractLoadBalance`,该类实现了 `LoadBalance` 接口,并封装了一些公共的逻辑。
@@ -291,19 +254,19 @@ public abstract class AbstractLoadBalance implements LoadBalance {
`AbstractLoadBalance` 的实现类有下面这些:
-
+
-官方文档对负载均衡这部分的介绍非常详细,推荐小伙伴们看看,地址:[https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance](https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance ) 。
+官方文档对负载均衡这部分的介绍非常详细,推荐小伙伴们看看,地址:[https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance](https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#m-zhdocsv27devsourceloadbalance) 。
-#### RandomLoadBalance
+#### RandomLoadBalance
-根据权重随机选择(对加权随机算法的实现)。这是Dubbo默认采用的一种负载均衡策略。
+根据权重随机选择(对加权随机算法的实现)。这是 Dubbo 默认采用的一种负载均衡策略。
-` RandomLoadBalance` 具体的实现原理非常简单,假如有两个提供相同服务的服务器 S1,S2,S1的权重为7,S2的权重为3。
+` RandomLoadBalance` 具体的实现原理非常简单,假如有两个提供相同服务的服务器 S1,S2,S1 的权重为 7,S2 的权重为 3。
我们把这些权重值分布在坐标区间会得到:S1->[0, 7) ,S2->[7, 10)。我们生成[0, 10) 之间的随机数,随机数落到对应的区间,我们就选择对应的服务器来处理请求。
-
+
`RandomLoadBalance` 的源码非常简单,简单花几分钟时间看一下。
@@ -319,7 +282,7 @@ public class RandomLoadBalance extends AbstractLoadBalance {
int length = invokers.size();
boolean sameWeight = true;
- int[] weights = new int[length];
+ int[] weights = new int[length];
int totalWeight = 0;
// 下面这个for循环的主要作用就是计算所有该服务的提供者的权重之和 totalWeight(),
// 除此之外,还会检测每个服务提供者的权重是否相同
@@ -340,7 +303,7 @@ public class RandomLoadBalance extends AbstractLoadBalance {
return invokers.get(i);
}
}
-
+
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
@@ -348,7 +311,7 @@ public class RandomLoadBalance extends AbstractLoadBalance {
```
-#### LeastActiveLoadBalance
+#### LeastActiveLoadBalance
`LeastActiveLoadBalance` 直译过来就是**最小活跃数负载均衡**。
@@ -360,7 +323,7 @@ public class RandomLoadBalance extends AbstractLoadBalance {
**如果有多个服务提供者的活跃数相等怎么办?**
-很简单,那就再走一遍 `RandomLoadBalance` 。
+很简单,那就再走一遍 `RandomLoadBalance` 。
```java
public class LeastActiveLoadBalance extends AbstractLoadBalance {
@@ -426,7 +389,7 @@ public class LeastActiveLoadBalance extends AbstractLoadBalance {
```java
public class RpcStatus {
-
+
private static final ConcurrentMap> METHOD_STATISTICS =
new ConcurrentHashMap>();
@@ -442,60 +405,55 @@ public class RpcStatus {
}
```
-#### ConsistentHashLoadBalance
+#### ConsistentHashLoadBalance
-`ConsistentHashLoadBalance` 小伙伴们应该也不会陌生,在分库分表、各种集群中就经常使用这个负载均衡策略。
+`ConsistentHashLoadBalance` 小伙伴们应该也不会陌生,在分库分表、各种集群中就经常使用这个负载均衡策略。
-`ConsistentHashLoadBalance` 即**一致性Hash负载均衡策略**。 `ConsistentHashLoadBalance` 中没有权重的概念,具体是哪个服务提供者处理请求是由你的请求的参数决定的,也就是说相同参数的请求总是发到同一个服务提供者。
+`ConsistentHashLoadBalance` 即**一致性 Hash 负载均衡策略**。 `ConsistentHashLoadBalance` 中没有权重的概念,具体是哪个服务提供者处理请求是由你的请求的参数决定的,也就是说相同参数的请求总是发到同一个服务提供者。
-
+
另外,Dubbo 为了避免数据倾斜问题(节点不够分散,大量请求落到同一节点),还引入了虚拟节点的概念。通过虚拟节点可以让节点更加分散,有效均衡各个节点的请求量。
+
+官方有详细的源码分析:[https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#23-consistenthashloadbalance](https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#23-consistenthashloadbalance) 。这里还有一个相关的 [PR#5440](https://github.com/apache/dubbo/pull/5440) 来修复老版本中 ConsistentHashLoadBalance 存在的一些 Bug。感兴趣的小伙伴,可以多花点时间研究一下。我这里不多分析了,这个作业留给你们!
-
-
-官方有详细的源码分析:[https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#23-consistenthashloadbalance](https://dubbo.apache.org/zh/docs/v2.7/dev/source/loadbalance/#23-consistenthashloadbalance) 。这里还有一个相关的 [PR#5440](https://github.com/apache/dubbo/pull/5440) 来修复老版本中 ConsistentHashLoadBalance 存在的一些Bug。感兴趣的小伙伴,可以多花点时间研究一下。我这里不多分析了,这个作业留给你们!
-
-#### RoundRobinLoadBalance
+#### RoundRobinLoadBalance
加权轮询负载均衡。
-轮询就是把请求依次分配给每个服务提供者。加权轮询就是在轮询的基础上,让更多的请求落到权重更大的服务提供者上。比如假如有两个提供相同服务的服务器 S1,S2,S1的权重为7,S2的权重为3。
+轮询就是把请求依次分配给每个服务提供者。加权轮询就是在轮询的基础上,让更多的请求落到权重更大的服务提供者上。比如假如有两个提供相同服务的服务器 S1,S2,S1 的权重为 7,S2 的权重为 3。
-如果我们有 10 次请求,那么 7 次会被 S1处理,3次被 S2处理。
+如果我们有 10 次请求,那么 7 次会被 S1 处理,3 次被 S2 处理。
-但是,如果是 `RandomLoadBalance` 的话,很可能存在10次请求有9次都被 S1 处理的情况(概率性问题)。
+但是,如果是 `RandomLoadBalance` 的话,很可能存在 10 次请求有 9 次都被 S1 处理的情况(概率性问题)。
Dubbo 中的 `RoundRobinLoadBalance` 的代码实现被修改重建了好几次,Dubbo-2.6.5 版本的 `RoundRobinLoadBalance` 为平滑加权轮询算法。
-## Dubbo序列化协议
+## Dubbo 序列化协议
### Dubbo 支持哪些序列化方式呢?
-
+
-Dubbo 支持多种序列化方式:JDK自带的序列化、hessian2、JSON、Kryo、FST、Protostuff,ProtoBuf等等。
+Dubbo 支持多种序列化方式:JDK 自带的序列化、hessian2、JSON、Kryo、FST、Protostuff,ProtoBuf 等等。
-Dubbo 默认使用的序列化方式是 hession2。
+Dubbo 默认使用的序列化方式是 hessian2。
### 谈谈你对这些序列化协议了解?
一般我们不会直接使用 JDK 自带的序列化方式。主要原因有两个:
1. **不支持跨语言调用** : 如果调用的是其他语言开发的服务的时候就不支持了。
-2. **性能差** :相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
+2. **性能差**:相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
JSON 序列化由于性能问题,我们一般也不会考虑使用。
-像 Protostuff,ProtoBuf、hessian2这些都是跨语言的序列化方式,如果有跨语言需求的话可以考虑使用。
-
-Kryo和FST这两种序列化方式是 Dubbo 后来才引入的,性能非常好。不过,这两者都是专门针对 Java 语言的。Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。(文章地址:[https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/](https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/))
+像 Protostuff,ProtoBuf、hessian2 这些都是跨语言的序列化方式,如果有跨语言需求的话可以考虑使用。
-
+Kryo 和 FST 这两种序列化方式是 Dubbo 后来才引入的,性能非常好。不过,这两者都是专门针对 Java 语言的。Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。
Dubbo 官方文档中还有一个关于这些[序列化协议的性能对比图](https://dubbo.apache.org/zh/docs/v2.7/user/serialization/#m-zhdocsv27userserialization)可供参考。
-
-
+
diff --git a/docs/distributed-system/rpc/http&rpc.md b/docs/distributed-system/rpc/http&rpc.md
new file mode 100644
index 0000000000000000000000000000000000000000..88ac10b03fe032567288cfa179c3df3df067aba8
--- /dev/null
+++ b/docs/distributed-system/rpc/http&rpc.md
@@ -0,0 +1,194 @@
+---
+title: 有了 HTTP 协议,为什么还要有 RPC ?
+category: 分布式
+tag:
+ - rpc
+---
+
+> 本文来自[小白 debug](https://juejin.cn/user/4001878057422087)投稿,原文:https://juejin.cn/post/7121882245605883934 。
+
+我想起了我刚工作的时候,第一次接触 RPC 协议,当时就很懵,我 HTTP 协议用的好好的,为什么还要用 RPC 协议?
+
+于是就到网上去搜。
+
+不少解释显得非常官方,我相信大家在各种平台上也都看到过,解释了又好像没解释,都在**用一个我们不认识的概念去解释另外一个我们不认识的概念**,懂的人不需要看,不懂的人看了还是不懂。
+
+这种看了,又好像没看的感觉,云里雾里的很难受,**我懂**。
+
+为了避免大家有强烈的**审丑疲劳**,今天我们来尝试重新换个方式讲一讲。
+
+## 从 TCP 聊起
+
+作为一个程序员,假设我们需要在 A 电脑的进程发一段数据到 B 电脑的进程,我们一般会在代码里使用 socket 进行编程。
+
+这时候,我们可选项一般也就**TCP 和 UDP 二选一。TCP 可靠,UDP 不可靠。** 除非是马总这种神级程序员(早期 QQ 大量使用 UDP),否则,只要稍微对可靠性有些要求,普通人一般无脑选 TCP 就对了。
+
+类似下面这样。
+
+```ini
+fd = socket(AF_INET,SOCK_STREAM,0);
+```
+
+其中`SOCK_STREAM`,是指使用**字节流**传输数据,说白了就是**TCP 协议**。
+
+在定义了 socket 之后,我们就可以愉快的对这个 socket 进行操作,比如用`bind()`绑定 IP 端口,用`connect()`发起建连。
+
+
+
+在连接建立之后,我们就可以使用`send()`发送数据,`recv()`接收数据。
+
+光这样一个纯裸的 TCP 连接,就可以做到收发数据了,那是不是就够了?
+
+不行,这么用会有问题。
+
+## 使用纯裸 TCP 会有什么问题
+
+八股文常背,TCP 是有三个特点,**面向连接**、**可靠**、基于**字节流**。
+
+
+
+这三个特点真的概括的 **非常精辟** ,这个八股文我们没白背。
+
+每个特点展开都能聊一篇文章,而今天我们需要关注的是 **基于字节流** 这一点。
+
+字节流可以理解为一个双向的通道里流淌的二进制数据,也就是 **01 串** 。纯裸 TCP 收发的这些 01 串之间是 **没有任何边界** 的,你根本不知道到哪个地方才算一条完整消息。
+
+
+
+正因为这个没有任何边界的特点,所以当我们选择使用 TCP 发送 **"夏洛"和"特烦恼"** 的时候,接收端收到的就是 **"夏洛特烦恼"** ,这时候接收端没发区分你是想要表达 **"夏洛"+"特烦恼"** 还是 **"夏洛特"+"烦恼"** 。
+
+
+
+这就是所谓的 **粘包问题**,之前也写过一篇专门的[文章](https://mp.weixin.qq.com/s/0-YBxU1cSbDdzcZEZjmQYA)聊过这个问题。
+
+说这个的目的是为了告诉大家,纯裸 TCP 是不能直接拿来用的,你需要在这个基础上加入一些 **自定义的规则** ,用于区分 **消息边界** 。
+
+于是我们会把每条要发送的数据都包装一下,比如加入 **消息头** ,消息头里写清楚一个完整的包长度是多少,根据这个长度可以继续接收数据,截取出来后它们就是我们真正要传输的 **消息体** 。
+
+
+
+而这里头提到的 **消息头** ,还可以放各种东西,比如消息体是否被压缩过和消息体格式之类的,只要上下游都约定好了,互相都认就可以了,这就是所谓的 **协议。**
+
+每个使用 TCP 的项目都可能会定义一套类似这样的协议解析标准,他们可能 **有区别,但原理都类似**。
+
+**于是基于 TCP,就衍生了非常多的协议,比如 HTTP 和 RPC。**
+
+## HTTP 和 RPC
+
+### RPC 其实是一种调用方式
+
+我们回过头来看网络的分层图。
+
+
+
+**TCP 是传输层的协议** ,而基于 TCP 造出来的 HTTP 和各类 RPC 协议,它们都只是定义了不同消息格式的 **应用层协议** 而已。
+
+**HTTP**(**H**yper **T**ext **T**ransfer **P**rotocol)协议又叫做 **超文本传输协议** 。我们用的比较多,平时上网在浏览器上敲个网址就能访问网页,这里用到的就是 HTTP 协议。
+
+
+
+而 **RPC**(**R**emote **P**rocedure **C**all)又叫做 **远程过程调用**,它本身并不是一个具体的协议,而是一种 **调用方式** 。
+
+举个例子,我们平时调用一个 **本地方法** 就像下面这样。
+
+```ini
+ res = localFunc(req)
+```
+
+如果现在这不是个本地方法,而是个**远端服务器**暴露出来的一个方法`remoteFunc`,如果我们还能像调用本地方法那样去调用它,这样就可以**屏蔽掉一些网络细节**,用起来更方便,岂不美哉?
+
+```ini
+res = remoteFunc(req)
+```
+
+
+
+基于这个思路,大佬们造出了非常多款式的 RPC 协议,比如比较有名的`gRPC`,`thrift`。
+
+值得注意的是,虽然大部分 RPC 协议底层使用 TCP,但实际上 **它们不一定非得使用 TCP,改用 UDP 或者 HTTP,其实也可以做到类似的功能。**
+
+到这里,我们回到文章标题的问题。
+
+### 那既然有 RPC 了,为什么还要有 HTTP 呢?
+
+其实,TCP 是 **70 年** 代出来的协议,而 HTTP 是 **90 年代** 才开始流行的。而直接使用裸 TCP 会有问题,可想而知,这中间这么多年有多少自定义的协议,而这里面就有 **80 年代** 出来的`RPC`。
+
+所以我们该问的不是 **既然有 HTTP 协议为什么要有 RPC** ,而是 **为什么有 RPC 还要有 HTTP 协议?**
+
+现在电脑上装的各种联网软件,比如 xx 管家,xx 卫士,它们都作为客户端(Client) 需要跟服务端(Server) 建立连接收发消息,此时都会用到应用层协议,在这种 Client/Server (C/S) 架构下,它们可以使用自家造的 RPC 协议,因为它只管连自己公司的服务器就 ok 了。
+
+但有个软件不同,浏览器(Browser) ,不管是 Chrome 还是 IE,它们不仅要能访问自家公司的**服务器(Server)** ,还需要访问其他公司的网站服务器,因此它们需要有个统一的标准,不然大家没法交流。于是,HTTP 就是那个时代用于统一 **Browser/Server (B/S)** 的协议。
+
+也就是说在多年以前,**HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,B/S 和 C/S 在慢慢融合。** 很多软件同时支持多端,比如某度云盘,既要支持**网页版**,还要支持**手机端和 PC 端**,如果通信协议都用 HTTP 的话,那服务器只用同一套就够了。而 RPC 就开始退居幕后,一般用于公司内部集群里,各个微服务之间的通讯。
+
+那这么说的话,**都用 HTTP 得了,还用什么 RPC?**
+
+仿佛又回到了文章开头的样子,那这就要从它们之间的区别开始说起。
+
+### HTTP 和 RPC 有什么区别
+
+我们来看看 RPC 和 HTTP 区别比较明显的几个点。
+
+#### 服务发现
+
+首先要向某个服务器发起请求,你得先建立连接,而建立连接的前提是,你得知道 **IP 地址和端口** 。这个找到服务对应的 IP 端口的过程,其实就是 **服务发现**。
+
+在 **HTTP** 中,你知道服务的域名,就可以通过 **DNS 服务** 去解析得到它背后的 IP 地址,默认 **80 端口**。
+
+而 **RPC** 的话,就有些区别,一般会有专门的中间服务去保存服务名和 IP 信息,比如 **Consul、Etcd、Nacos、ZooKeeper,甚至是 Redis**。想要访问某个服务,就去这些中间服务去获得 IP 和端口信息。由于 DNS 也是服务发现的一种,所以也有基于 DNS 去做服务发现的组件,比如 **CoreDNS**。
+
+可以看出服务发现这一块,两者是有些区别,但不太能分高低。
+
+#### 底层连接形式
+
+以主流的 **HTTP1.1** 协议为例,其默认在建立底层 TCP 连接之后会一直保持这个连接(**keep alive**),之后的请求和响应都会复用这条连接。
+
+而 **RPC** 协议,也跟 HTTP 类似,也是通过建立 TCP 长链接进行数据交互,但不同的地方在于,RPC 协议一般还会再建个 **连接池**,在请求量大的时候,建立多条连接放在池内,要发数据的时候就从池里取一条连接出来,用完放回去,下次再复用,可以说非常环保。
+
+
+
+由于连接池有利于提升网络请求性能,所以不少编程语言的网络库里都会给 HTTP 加个连接池,比如 Go 就是这么干的。
+
+可以看出这一块两者也没太大区别,所以也不是关键。
+
+#### 传输的内容
+
+基于 TCP 传输的消息,说到底,无非都是 **消息头 Header 和消息体 Body。**
+
+**Header** 是用于标记一些特殊信息,其中最重要的是 **消息体长度**。
+
+**Body** 则是放我们真正需要传输的内容,而这些内容只能是二进制 01 串,毕竟计算机只认识这玩意。所以 TCP 传字符串和数字都问题不大,因为字符串可以转成编码再变成 01 串,而数字本身也能直接转为二进制。但结构体呢,我们得想个办法将它也转为二进制 01 串,这样的方案现在也有很多现成的,比如 **JSON,Protocol Buffers (Protobuf)** 。
+
+这个将结构体转为二进制数组的过程就叫 **序列化** ,反过来将二进制数组复原成结构体的过程叫 **反序列化**。
+
+
+
+对于主流的 HTTP1.1,虽然它现在叫超文本协议,支持音频视频,但 HTTP 设计 初是用于做网页文本展示的,所以它传的内容以字符串为主。Header 和 Body 都是如此。在 Body 这块,它使用 **JSON** 来 **序列化** 结构体数据。
+
+我们可以随便截个图直观看下。
+
+
+
+可以看到这里面的内容非常多的冗余,显得非常啰嗦。最明显的,像 Header 里的那些信息,其实如果我们约定好头部的第几位是 `Content-Type`,就不需要每次都真的把 `Content-Type` 这个字段都传过来,类似的情况其实在 Body 的 JSON 结构里也特别明显。
+
+而 RPC,因为它定制化程度更高,可以采用体积更小的 Protobuf 或其他序列化协议去保存结构体数据,同时也不需要像 HTTP 那样考虑各种浏览器行为,比如 302 重定向跳转啥的。**因此性能也会更好一些,这也是在公司内部微服务中抛弃 HTTP,选择使用 RPC 的最主要原因。**
+
+
+
+
+
+当然上面说的 HTTP,其实 **特指的是现在主流使用的 HTTP1.1**,`HTTP2`在前者的基础上做了很多改进,所以 **性能可能比很多 RPC 协议还要好**,甚至连`gRPC`底层都直接用的`HTTP2`。
+
+那么问题又来了。
+
+### 为什么既然有了 HTTP2,还要有 RPC 协议?
+
+这个是由于 HTTP2 是 2015 年出来的。那时候很多公司内部的 RPC 协议都已经跑了好些年了,基于历史原因,一般也没必要去换了。
+
+## 总结
+
+- 纯裸 TCP 是能收发数据,但它是个无边界的数据流,上层需要定义消息格式用于定义 **消息边界** 。于是就有了各种协议,HTTP 和各类 RPC 协议就是在 TCP 之上定义的应用层协议。
+- **RPC 本质上不算是协议,而是一种调用方式**,而像 gRPC 和 Thrift 这样的具体实现,才是协议,它们是实现了 RPC 调用的协议。目的是希望程序员能像调用本地方法那样去调用远端的服务方法。同时 RPC 有很多种实现方式,**不一定非得基于 TCP 协议**。
+- 从发展历史来说,**HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。但现在其实已经没分那么清了,B/S 和 C/S 在慢慢融合。** 很多软件同时支持多端,所以对外一般用 HTTP 协议,而内部集群的微服务之间则采用 RPC 协议进行通讯。
+- RPC 其实比 HTTP 出现的要早,且比目前主流的 HTTP1.1 性能要更好,所以大部分公司内部都还在使用 RPC。
+- **HTTP2.0** 在 **HTTP1.1** 的基础上做了优化,性能可能比很多 RPC 协议都要好,但由于是这几年才出来的,所以也不太可能取代掉 RPC。
diff --git a/docs/distributed-system/rpc/rpc-intro.md b/docs/distributed-system/rpc/rpc-intro.md
new file mode 100644
index 0000000000000000000000000000000000000000..b65ed37e4ab5dd1f81263af2988b056e0706affb
--- /dev/null
+++ b/docs/distributed-system/rpc/rpc-intro.md
@@ -0,0 +1,139 @@
+---
+title: RPC基础知识总结
+category: 分布式
+tag:
+ - rpc
+---
+
+这篇文章会简单介绍一下 RPC 相关的基础概念。
+
+## RPC 是什么?
+
+**RPC(Remote Procedure Call)** 即远程过程调用,通过名字我们就能看出 RPC 关注的是远程调用而非本地调用。
+
+**为什么要 RPC ?** 因为,两个不同的服务器上的服务提供的方法不在一个内存空间,所以,需要通过网络编程才能传递方法调用所需要的参数。并且,方法调用的结果也需要通过网络编程来接收。但是,如果我们自己手动网络编程来实现这个调用过程的话工作量是非常大的,因为,我们需要考虑底层传输方式(TCP 还是 UDP)、序列化方式等等方面。
+
+**RPC 能帮助我们做什么呢?** 简单来说,通过 RPC 可以帮助我们调用远程计算机上某个服务的方法,这个过程就像调用本地方法一样简单。并且!我们不需要了解底层网络编程的具体细节。
+
+举个例子:两个不同的服务 A、B 部署在两台不同的机器上,服务 A 如果想要调用服务 B 中的某个方法的话就可以通过 RPC 来做。
+
+一言蔽之:**RPC 的出现就是为了让你调用远程方法像调用本地方法一样简单。**
+
+## RPC 的原理是什么?
+
+为了能够帮助小伙伴们理解 RPC 原理,我们可以将整个 RPC 的 核心功能看作是下面 👇 5 个部分实现的:
+
+1. **客户端(服务消费端)**:调用远程方法的一端。
+1. **客户端 Stub(桩)**:这其实就是一代理类。代理类主要做的事情很简单,就是把你调用方法、类、方法参数等信息传递到服务端。
+1. **网络传输**:网络传输就是你要把你调用的方法的信息比如说参数啊这些东西传输到服务端,然后服务端执行完之后再把返回结果通过网络传输给你传输回来。网络传输的实现方式有很多种比如最近基本的 Socket 或者性能以及封装更加优秀的 Netty(推荐)。
+1. **服务端 Stub(桩)**:这个桩就不是代理类了。我觉得理解为桩实际不太好,大家注意一下就好。这里的服务端 Stub 实际指的就是接收到客户端执行方法的请求后,去执行对应的方法然后返回结果给客户端的类。
+1. **服务端(服务提供端)**:提供远程方法的一端。
+
+具体原理图如下,后面我会串起来将整个 RPC 的过程给大家说一下。
+
+
+
+1. 服务消费端(client)以本地调用的方式调用远程服务;
+1. 客户端 Stub(client stub) 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(序列化):`RpcRequest`;
+1. 客户端 Stub(client stub) 找到远程服务的地址,并将消息发送到服务提供端;
+1. 服务端 Stub(桩)收到消息将消息反序列化为 Java 对象: `RpcRequest`;
+1. 服务端 Stub(桩)根据`RpcRequest`中的类、方法、方法参数等信息调用本地的方法;
+1. 服务端 Stub(桩)得到方法执行结果并将组装成能够进行网络传输的消息体:`RpcResponse`(序列化)发送至消费方;
+1. 客户端 Stub(client stub)接收到消息并将消息反序列化为 Java 对象:`RpcResponse` ,这样也就得到了最终结果。over!
+
+相信小伙伴们看完上面的讲解之后,已经了解了 RPC 的原理。
+
+虽然篇幅不多,但是基本把 RPC 框架的核心原理讲清楚了!另外,对于上面的技术细节,我会在后面的章节介绍到。
+
+**最后,对于 RPC 的原理,希望小伙伴不单单要理解,还要能够自己画出来并且能够给别人讲出来。因为,在面试中这个问题在面试官问到 RPC 相关内容的时候基本都会碰到。**
+
+## 有哪些常见的 RPC 框架?
+
+我们这里说的 RPC 框架指的是可以让客户端直接调用服务端方法,就像调用本地方法一样简单的框架,比如我下面介绍的 Dubbo、Motan、gRPC 这些。 如果需要和 HTTP 协议打交道,解析和封装 HTTP 请求和响应。这类框架并不能算是“RPC 框架”,比如 Feign。
+
+### Dubbo
+
+
+
+Apache Dubbo 是一款微服务框架,为大规模微服务实践提供高性能 RPC 通信、流量治理、可观测性等解决方案,
+涵盖 Java、Golang 等多种语言 SDK 实现。
+
+Dubbo 提供了从服务定义、服务发现、服务通信到流量管控等几乎所有的服务治理能力,支持 Triple 协议(基于 HTTP/2 之上定义的下一代 RPC 通信协议)、应用级服务发现、Dubbo Mesh (Dubbo3 赋予了很多云原生友好的新特性)等特性。
+
+
+
+Dubbo 是由阿里开源,后来加入了 Apache 。正是由于 Dubbo 的出现,才使得越来越多的公司开始使用以及接受分布式架构。
+
+Dubbo 算的是比较优秀的国产开源项目了,它的源码也是非常值得学习和阅读的!
+
+- GitHub:[https://github.com/apache/incubator-dubbo](https://github.com/apache/incubator-dubbo "https://github.com/apache/incubator-dubbo")
+- 官网:https://dubbo.apache.org/zh/
+
+### Motan
+
+Motan 是新浪微博开源的一款 RPC 框架,据说在新浪微博正支撑着千亿次调用。不过笔者倒是很少看到有公司使用,而且网上的资料也比较少。
+
+很多人喜欢拿 Motan 和 Dubbo 作比较,毕竟都是国内大公司开源的。笔者在查阅了很多资料,以及简单查看了其源码之后发现:**Motan 更像是一个精简版的 Dubbo,可能是借鉴了 Dubbo 的思想,Motan 的设计更加精简,功能更加纯粹。**
+
+不过,我不推荐你在实际项目中使用 Motan。如果你要是公司实际使用的话,还是推荐 Dubbo ,其社区活跃度以及生态都要好很多。
+
+- 从 Motan 看 RPC 框架设计:[http://kriszhang.com/motan-rpc-impl/](http://kriszhang.com/motan-rpc-impl/ "http://kriszhang.com/motan-rpc-impl/")
+- Motan 中文文档:[https://github.com/weibocom/motan/wiki/zh_overview](https://github.com/weibocom/motan/wiki/zh_overview "https://github.com/weibocom/motan/wiki/zh_overview")
+
+### gRPC
+
+
+
+gRPC 是 Google 开源的一个高性能、通用的开源 RPC 框架。其由主要面向移动应用开发并基于 HTTP/2 协议标准而设计(支持双向流、消息头压缩等功能,更加节省带宽),基于 ProtoBuf 序列化协议开发,并且支持众多开发语言。
+
+**何谓 ProtoBuf?** [ProtoBuf( Protocol Buffer)](https://github.com/protocolbuffers/protobuf) 是一种更加灵活、高效的数据格式,可用于通讯协议、数据存储等领域,基本支持所有主流编程语言且与平台无关。不过,通过 ProtoBuf 定义接口和数据类型还挺繁琐的,这是一个小问题。
+
+
+
+不得不说,gRPC 的通信层的设计还是非常优秀的,[Dubbo-go 3.0](https://dubbogo.github.io/) 的通信层改进主要借鉴了 gRPC。
+
+不过,gRPC 的设计导致其几乎没有服务治理能力。如果你想要解决这个问题的话,就需要依赖其他组件比如腾讯的 PolarisMesh(北极星)了。
+
+- GitHub:[https://github.com/grpc/grpc](https://github.com/grpc/grpc "https://github.com/grpc/grpc")
+- 官网:[https://grpc.io/](https://grpc.io/ "https://grpc.io/")
+
+### Thrift
+
+Apache Thrift 是 Facebook 开源的跨语言的 RPC 通信框架,目前已经捐献给 Apache 基金会管理,由于其跨语言特性和出色的性能,在很多互联网公司得到应用,有能力的公司甚至会基于 thrift 研发一套分布式服务框架,增加诸如服务注册、服务发现等功能。
+
+`Thrift`支持多种不同的**编程语言**,包括`C++`、`Java`、`Python`、`PHP`、`Ruby`等(相比于 gRPC 支持的语言更多 )。
+
+- 官网:[https://thrift.apache.org/](https://thrift.apache.org/ "https://thrift.apache.org/")
+- Thrift 简单介绍:[https://www.jianshu.com/p/8f25d057a5a9](https://www.jianshu.com/p/8f25d057a5a9 "https://www.jianshu.com/p/8f25d057a5a9")
+
+### 总结
+
+gRPC 和 Thrift 虽然支持跨语言的 RPC 调用,但是它们只提供了最基本的 RPC 框架功能,缺乏一系列配套的服务化组件和服务治理功能的支撑。
+
+Dubbo 不论是从功能完善程度、生态系统还是社区活跃度来说都是最优秀的。而且,Dubbo 在国内有很多成功的案例比如当当网、滴滴等等,是一款经得起生产考验的成熟稳定的 RPC 框架。最重要的是你还能找到非常多的 Dubbo 参考资料,学习成本相对也较低。
+
+下图展示了 Dubbo 的生态系统。
+
+
+
+Dubbo 也是 Spring Cloud Alibaba 里面的一个组件。
+
+
+
+但是,Dubbo 和 Motan 主要是给 Java 语言使用。虽然,Dubbo 和 Motan 目前也能兼容部分语言,但是不太推荐。如果需要跨多种语言调用的话,可以考虑使用 gRPC。
+
+综上,如果是 Java 后端技术栈,并且你在纠结选择哪一种 RPC 框架的话,我推荐你考虑一下 Dubbo。
+
+## 如何设计并实现一个 RPC 框架?
+
+**《手写 RPC 框架》** 是我的[知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)的一个内部小册,我写了 12 篇文章来讲解如何从零开始基于 Netty+Kyro+Zookeeper 实现一个简易的 RPC 框架。
+
+麻雀虽小五脏俱全,项目代码注释详细,结构清晰,并且集成了 Check Style 规范代码结构,非常适合阅读和学习。
+
+**内容概览**:
+
+
+
+## 既然有了 HTTP 协议,为什么还要有 RPC ?
+
+关于这个问题的详细答案,请看这篇文章:[有了 HTTP 协议,为什么还要有 RPC ?](http&rpc.md) 。
diff --git a/docs/distributed-system/rpc/why-use-rpc.md b/docs/distributed-system/rpc/why-use-rpc.md
deleted file mode 100644
index a2fe5dbefa27faae2eed8519e460727403d8e7c1..0000000000000000000000000000000000000000
--- a/docs/distributed-system/rpc/why-use-rpc.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# 服务之间的调用为啥不直接用 HTTP 而用 RPC?
-
-## 什么是 RPC?RPC原理是什么?
-
-### **什么是 RPC?**
-
-RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务 A、B 部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。
-
-### **RPC原理是什么?**
-
-
-
-1. 服务消费端(client)以本地调用的方式调用远程服务;
-2. 客户端 Stub(client stub) 接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体(序列化):`RpcRequest`;
-3. 客户端 Stub(client stub) 找到远程服务的地址,并将消息发送到服务提供端;
-4. 服务端 Stub(桩)收到消息将消息反序列化为Java对象: `RpcRequest`;
-5. 服务端 Stub(桩)根据`RpcRequest`中的类、方法、方法参数等信息调用本地的方法;
-6. 服务端 Stub(桩)得到方法执行结果并将组装成能够进行网络传输的消息体:`RpcResponse`(序列化)发送至消费方;
-7. 客户端 Stub(client stub)接收到消息并将消息反序列化为Java对象:`RpcResponse` ,这样也就得到了最终结果。
-
-下面再贴一个网上的时序图,辅助理解:
-
-
-
-### RPC 解决了什么问题?
-
-从上面对 RPC 介绍的内容中,概括来讲RPC 主要解决了:**让分布式或者微服务系统中不同服务之间的调用像本地调用一样简单。**
-
-### 常见的 RPC 框架总结?
-
-- **RMI(JDK自带):** JDK自带的RPC,有很多局限性,不推荐使用。
-- **Dubbo:** Dubbo是 阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。目前 Dubbo 已经成为 Spring Cloud Alibaba 中的官方组件。
-- **gRPC** :gRPC是可以在任何环境中运行的现代开源高性能RPC框架。它可以通过可插拔的支持来有效地连接数据中心内和跨数据中心的服务,以实现负载平衡,跟踪,运行状况检查和身份验证。它也适用于分布式计算的最后一英里,以将设备,移动应用程序和浏览器连接到后端服务。
-- **Hessian:** Hessian是一个轻量级的remoting on http工具,使用简单的方法提供了RMI的功能。 相比WebService,Hessian更简单、快捷。采用的是二进制RPC协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。
-- **Thrift:** Apache Thrift是Facebook开源的跨语言的RPC通信框架,目前已经捐献给Apache基金会管理,由于其跨语言特性和出色的性能,在很多互联网公司得到应用,有能力的公司甚至会基于thrift研发一套分布式服务框架,增加诸如服务注册、服务发现等功能。
-
-### RPC学习材料
-
-- [跟着 Guide 哥造轮子](https://github.com/Snailclimb/guide-rpc-framework)
-
-## 既有 HTTP ,为啥用 RPC 进行服务调用?
-
-### RPC 只是一种设计而已
-
-RPC 只是一种概念、一种设计,就是为了解决 **不同服务之间的调用问题**, 它一般会包含有 **传输协议** 和 **序列化协议** 这两个。
-
-但是,HTTP 是一种协议,RPC框架可以使用 HTTP协议作为传输协议或者直接使用TCP作为传输协议,使用不同的协议一般也是为了适应不同的场景。
-
-### HTTP 和 TCP
-
-**可能现在很多对计算机网络不太熟悉的朋友已经被搞蒙了,要想真正搞懂,还需要来简单复习一下计算机网络基础知识:**
-
-> 我们通常谈计算机网络的五层协议的体系结构是指:应用层、传输层、网络层、数据链路层、物理层。
->
-> **应用层(application-layer)的任务是通过应用进程间的交互来完成特定网络应用。** HTTP 属于应用层协议,它会基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。HTTP协议工作于客户端-服务端架构上。浏览器作为HTTP客户端通过 URL 向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。HTTP协议建立在 TCP 协议之上。
->
-> **传输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务**。TCP是传输层协议,主要解决数据如何在网络中传输。相比于UDP,**TCP** 提供的是**面向连接**的,**可靠的**数据传输服务。
-
-### RPC框架功能更齐全
-
-成熟的 RPC框架还提供好了“服务自动注册与发现”、"智能负载均衡"、“可视化的服务治理和运维”、“运行期流量调度”等等功能,这些也算是选择
-RPC 进行服务注册和发现的一方面原因吧!
-
-**相关阅读:**
-
-- http://www.ruanyifeng.com/blog/2016/08/http.html (HTTP 协议入门- 阮一峰)
-
-### 一个常见的错误观点
-
-很多文章中还会提到说 HTTP 协议相较于自定义 TCP 报文协议,增加的开销在于连接的建立与断开,但是这个观点已经被否认,下面截取自知乎中一个回答,原回答地址:https://www.zhihu.com/question/41609070/answer/191965937 。
-
->首先要否认一点 HTTP 协议相较于自定义 TCP 报文协议,增加的开销在于连接的建立与断开。HTTP 协议是支持连接池复用的,也就是建立一定数量的连接不断开,并不会频繁的创建和销毁连接。二一要说的是 HTTP 也可以使用 Protobuf 这种二进制编码协议对内容进行编码,因此二者最大的区别还是在传输协议上。
-
-
-
diff --git a/docs/distributed-system/spring-cloud-gateway-questions.md b/docs/distributed-system/spring-cloud-gateway-questions.md
new file mode 100644
index 0000000000000000000000000000000000000000..a85c8ee189e48535bfbf2951fe934d0e8a25ed00
--- /dev/null
+++ b/docs/distributed-system/spring-cloud-gateway-questions.md
@@ -0,0 +1,155 @@
+---
+title: Spring Cloud Gateway常见问题总结
+category: 分布式
+---
+
+> 本文重构完善自[6000 字 | 16 图 | 深入理解 Spring Cloud Gateway 的原理 - 悟空聊架构](https://mp.weixin.qq.com/s/XjFYsP1IUqNzWqXZdJn-Aw)这篇文章。
+
+## 什么是 Spring Cloud Gateway?
+
+Spring Cloud Gateway 属于 Spring Cloud 生态系统中的网关,其诞生的目标是为了替代老牌网关 **Zuul**。准确点来说,应该是 Zuul 1.x。Spring Cloud Gateway 起步要比 Zuul 2.x 更早。
+
+为了提升网关的性能,Spring Cloud Gateway 基于 Spring WebFlux 。Spring WebFlux 使用 Reactor 库来实现响应式编程模型,底层基于 Netty 实现同步非阻塞的 I/O。
+
+
+
+Spring Cloud Gateway 不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,限流。
+
+Spring Cloud Gateway 和 Zuul 2.x 的差别不大,也是通过过滤器来处理请求。不过,目前更加推荐使用 Spring Cloud Gateway 而非 Zuul,Spring Cloud 生态对其支持更加友好。
+
+- GitHub 地址:
+- 官网:
+
+## Spring Cloud Gateway 的工作流程?
+
+Spring Cloud Gateway 的工作流程如下图所示:
+
+
+
+这是 Spring 官方博客中的一张图,原文地址:。
+
+具体的流程分析:
+
+1. **路由判断**:客户端的请求到达网关后,先经过 Gateway Handler Mapping 处理,这里面会做断言(Predicate)判断,看下符合哪个路由规则,这个路由映射后端的某个服务。
+2. **请求过滤**:然后请求到达 Gateway Web Handler,这里面有很多过滤器,组成过滤器链(Filter Chain),这些过滤器可以对请求进行拦截和修改,比如添加请求头、参数校验等等,有点像净化污水。然后将请求转发到实际的后端服务。这些过滤器逻辑上可以称作 Pre-Filters,Pre 可以理解为“在...之前”。
+3. **服务处理**:后端服务会对请求进行处理。
+4. **响应过滤**:后端处理完结果后,返回给 Gateway 的过滤器再次做处理,逻辑上可以称作 Post-Filters,Post 可以理解为“在...之后”。
+5. **响应返回**:响应经过过滤处理后,返回给客户端。
+
+总结:客户端的请求先通过匹配规则找到合适的路由,就能映射到具体的服务。然后请求经过过滤器处理后转发给具体的服务,服务处理后,再次经过过滤器处理,最后返回给客户端。
+
+## Spring Cloud Gateway 的断言是什么?
+
+断言(Predicate)这个词听起来极其深奥,它是一种编程术语,我们生活中根本就不会用它。说白了它就是对一个表达式进行 if 判断,结果为真或假,如果为真则做这件事,否则做那件事。
+
+在 Gateway 中,如果客户端发送的请求满足了断言的条件,则映射到指定的路由器,就能转发到指定的服务上进行处理。
+
+断言配置的示例如下,配置了两个路由规则,有一个 predicates 断言配置,当请求 url 中包含 `api/thirdparty`,就匹配到了第一个路由 `route_thirdparty`。
+
+
+
+常见的路由断言规则如下图所示:
+
+
+
+## Spring Cloud Gateway 的路由和断言是什么关系?
+
+Route 路由和 Predicate 断言的对应关系如下::
+
+
+
+- **一对多**:一个路由规则可以包含多个断言。如上图中路由 Route1 配置了三个断言 Predicate。
+- **同时满足**:如果一个路由规则中有多个断言,则需要同时满足才能匹配。如上图中路由 Route2 配置了两个断言,客户端发送的请求必须同时满足这两个断言,才能匹配路由 Route2。
+- **第一个匹配成功**:如果一个请求可以匹配多个路由,则映射第一个匹配成功的路由。如上图所示,客户端发送的请求满足 Route3 和 Route4 的断言,但是 Route3 的配置在配置文件中靠前,所以只会匹配 Route3。
+
+## Spring Cloud Gateway 如何实现动态路由?
+
+在使用 Spring Cloud Gateway 的时候,官方文档提供的方案总是基于配置文件或代码配置的方式。
+
+Spring Cloud Gateway 作为微服务的入口,需要尽量避免重启,而现在配置更改需要重启服务不能满足实际生产过程中的动态刷新、实时变更的业务需求,所以我们需要在 Spring Cloud Gateway 运行时动态配置网关。
+
+实现动态路由的方式有很多种,其中一种推荐的方式是基于 Nacos 配置中心来做。简单来说,我们将将路由配置放在 Nacos 中存储,然后写个监听器监听 Nacos 上配置的变化,将变化后的配置更新到 GateWay 应用的进程内。
+
+其实这些复杂的步骤并不需要我们手动实现,通过 Nacos Server 和 Spring Cloud Alibaba Nacos Config 即可实现配置的动态变更,官方文档地址: 。
+
+## Spring Cloud Gateway 的过滤器有哪些?
+
+过滤器 Filter 按照请求和响应可以分为两种:
+
+- **Pre 类型**:在请求被转发到微服务之前,对请求进行拦截和修改,例如参数校验、权限校验、流量监控、日志输出以及协议转换等操作。
+- **Post 类型**:微服务处理完请求后,返回响应给网关,网关可以再次进行处理,例如修改响应内容或响应头、日志输出、流量监控等。
+
+另外一种分类是按照过滤器 Filter 作用的范围进行划分:
+
+- **GatewayFilter**:局部过滤器,应用在单个路由或一组路由上的过滤器。标红色表示比较常用的过滤器。
+- **GlobalFilter**:全局过滤器,应用在所有路由上的过滤器。
+
+### 局部过滤器
+
+常见的局部过滤器如下图所示:
+
+
+
+具体怎么用呢?这里有个示例,如果 URL 匹配成功,则去掉 URL 中的 “api”。
+
+```yaml
+filters: #过滤器
+ - RewritePath=/api/(?.*),/$\{segment} # 将跳转路径中包含的 “api” 替换成空
+```
+
+当然我们也可以自定义过滤器,本篇不做展开。
+
+### 全局过滤器
+
+常见的全局过滤器如下图所示:
+
+
+
+全局过滤器最常见的用法是进行负载均衡。配置如下所示:
+
+```yaml
+spring:
+ cloud:
+ gateway:
+ routes:
+ - id: route_member # 第三方微服务路由规则
+ uri: lb://passjava-member # 负载均衡,将请求转发到注册中心注册的 passjava-member 服务
+ predicates: # 断言
+ - Path=/api/member/** # 如果前端请求路径包含 api/member,则应用这条路由规则
+ filters: #过滤器
+ - RewritePath=/api/(?.*),/$\{segment} # 将跳转路径中包含的api替换成空
+```
+
+这里有个关键字 `lb`,用到了全局过滤器 `LoadBalancerClientFilter`,当匹配到这个路由后,会将请求转发到 passjava-member 服务,且支持负载均衡转发,也就是先将 passjava-member 解析成实际的微服务的 host 和 port,然后再转发给实际的微服务。
+
+## Spring Cloud Gateway 支持限流吗?
+
+Spring Cloud Gateway 自带了限流过滤器,对应的接口是 `RateLimiter`,`RateLimiter` 接口只有一个实现类 `RedisRateLimiter` (基于 Redis + Lua 实现的限流),提供的限流功能比较简易且不易使用。
+
+从 Sentinel 1.6.0 版本开始,Sentinel 引入了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:route 维度和自定义 API 维度。也就是说,Spring Cloud Gateway 可以结合 Sentinel 实现更强大的网关流量控制。
+
+## Spring Cloud Gateway 如何自定义全局异常处理?
+
+在 SpringBoot 项目中,我们捕获全局异常只需要在项目中配置 `@RestControllerAdvice`和 `@ExceptionHandler`就可以了。不过,这种方式在 Spring Cloud Gateway 下不适用。
+
+Spring Cloud Gateway 提供了多种全局处理的方式,比较常用的一种是实现`ErrorWebExceptionHandler`并重写其中的`handle`方法。
+
+```java
+@Order(-1)
+@Component
+@RequiredArgsConstructor
+public class GlobalErrorWebExceptionHandler implements ErrorWebExceptionHandler {
+ private final ObjectMapper objectMapper;
+
+ @Override
+ public Mono handle(ServerWebExchange exchange, Throwable ex) {
+ // ...
+ }
+}
+```
+
+## 参考
+
+- Spring Cloud Gateway 官方文档:
+- Creating a custom Spring Cloud Gateway Filter:
+- 全局异常处理:
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/curator.png" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/curator.png"
deleted file mode 100644
index 28da0247ec3a4273f6117997b1045f3d41a0c583..0000000000000000000000000000000000000000
Binary files "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/curator.png" and /dev/null differ
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/watche\346\234\272\345\210\266.png" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/watche\346\234\272\345\210\266.png"
deleted file mode 100644
index 68144db1e54eaec125e11f1d7af8caaa0903dd06..0000000000000000000000000000000000000000
Binary files "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/watche\346\234\272\345\210\266.png" and /dev/null differ
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/znode-structure.png" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/znode-structure.png"
deleted file mode 100644
index 746c3f6e3a4c0d228071d4773d533ff7d3ac9f1d..0000000000000000000000000000000000000000
Binary files "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/znode-structure.png" and /dev/null differ
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/zookeeper\351\233\206\347\276\244.png" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/zookeeper\351\233\206\347\276\244.png"
deleted file mode 100644
index a3067cda18e6f9ca97590ea20dca344a6de508e8..0000000000000000000000000000000000000000
Binary files "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/zookeeper\351\233\206\347\276\244.png" and /dev/null differ
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/zookeeper\351\233\206\347\276\244\344\270\255\347\232\204\350\247\222\350\211\262.png" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/zookeeper\351\233\206\347\276\244\344\270\255\347\232\204\350\247\222\350\211\262.png"
deleted file mode 100644
index 6b118fe08e12b70c23c9b3286f42cd5bf56a1fb3..0000000000000000000000000000000000000000
Binary files "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/zookeeper\351\233\206\347\276\244\344\270\255\347\232\204\350\247\222\350\211\262.png" and /dev/null differ
diff --git "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/\350\277\236\346\216\245ZooKeeper\346\234\215\345\212\241.png" "b/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/\350\277\236\346\216\245ZooKeeper\346\234\215\345\212\241.png"
deleted file mode 100644
index d391329949d8b3f24c08b359fa0e02ee1fea75b0..0000000000000000000000000000000000000000
Binary files "a/docs/distributed-system/\345\210\206\345\270\203\345\274\217\345\215\217\350\260\203/zookeeper/images/\350\277\236\346\216\245ZooKeeper\346\234\215\345\212\241.png" and /dev/null differ
diff --git "a/docs/distributed-system/\347\220\206\350\256\272&\347\256\227\346\263\225/paxos&raft\347\256\227\346\263\225.md" "b/docs/distributed-system/\347\220\206\350\256\272&\347\256\227\346\263\225/paxos&raft\347\256\227\346\263\225.md"
deleted file mode 100644
index 36bd77241dd792df8e31f37dd72bbf1e4498a2ad..0000000000000000000000000000000000000000
--- "a/docs/distributed-system/\347\220\206\350\256\272&\347\256\227\346\263\225/paxos&raft\347\256\227\346\263\225.md"
+++ /dev/null
@@ -1,4 +0,0 @@
-# Paxos 算法和 Raft 算法
-
-Paxos 算法诞生于 1990 年,这是一种解决分布式系统一致性的经典算法 。但是,由于 Paxos 算法非常难以理解和实现,不断有人尝试简化这一算法。到了2013 年才诞生了一个比 Paxos 算法更易理解和实现的分布式一致性算法—Raft 算法。
-
diff --git a/docs/high-availability/fallback-and-circuit-breaker.md b/docs/high-availability/fallback-and-circuit-breaker.md
new file mode 100644
index 0000000000000000000000000000000000000000..bc2e477b753568dc53f7af11c60513f98a07ab4c
--- /dev/null
+++ b/docs/high-availability/fallback-and-circuit-breaker.md
@@ -0,0 +1,8 @@
+---
+title: 降级&熔断详解(付费)
+category: 高可用
+---
+
+**降级&熔断** 相关的面试题为我的[知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)(点击链接即可查看详细介绍以及加入方法)专属内容,已经整理到了[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)中。
+
+
diff --git "a/docs/high-availability/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241.md" b/docs/high-availability/high-availability-system-design.md
similarity index 62%
rename from "docs/high-availability/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241.md"
rename to docs/high-availability/high-availability-system-design.md
index e336f6762519d8927a9c35a037c885c285bc1f21..67547074c013db55facb6e224258007e47b4fd9e 100644
--- "a/docs/high-availability/\351\253\230\345\217\257\347\224\250\347\263\273\347\273\237\350\256\276\350\256\241.md"
+++ b/docs/high-availability/high-availability-system-design.md
@@ -1,16 +1,13 @@
-# 高可用系统设计
-
-一篇短小的文章,面试经常遇到的这个问题。本文主要包括下面这些内容:
-
-1. 高可用的定义
-2. 哪些情况可能会导致系统不可用?
-3. 有哪些提高系统可用性的方法?只是简单的提一嘴,更具体内容在后续的文章中介绍,就拿限流来说,你需要搞懂:何为限流?如何限流?为什么要限流?如何做呢?说一下原理?。
+---
+title: 高可用系统设计指南
+category: 高可用
+---
## 什么是高可用?可用性的判断标准是啥?
-**高可用描述的是一个系统在大部分时间都是可用的,可以为我们提供服务的。高可用代表系统即使在发生硬件故障或者系统升级的时候,服务仍然是可用的。**
+高可用描述的是一个系统在大部分时间都是可用的,可以为我们提供服务的。高可用代表系统即使在发生硬件故障或者系统升级的时候,服务仍然是可用的。
-**一般情况下,我们使用多少个 9 来评判一个系统的可用性,比如 99.9999% 就是代表该系统在所有的运行时间中只有 0.0001% 的时间是不可用的,这样的系统就是非常非常高可用的了!当然,也会有系统如果可用性不太好的话,可能连 9 都上不了。**
+一般情况下,我们使用多少个 9 来评判一个系统的可用性,比如 99.9999% 就是代表该系统在所有的运行时间中只有 0.0001% 的时间是不可用的,这样的系统就是非常非常高可用的了!当然,也会有系统如果可用性不太好的话,可能连 9 都上不了。
除此之外,系统的可用性还可以用某功能的失败次数与总的请求次数之比来衡量,比如对网站请求 1000 次,其中有 10 次请求失败,那么可用性就是 99%。
@@ -26,45 +23,46 @@
## 有哪些提高系统可用性的方法?
-### 1. 注重代码质量,测试严格把关
+### 注重代码质量,测试严格把关
我觉得这个是最最最重要的,代码质量有问题比如比较常见的内存泄漏、循环依赖都是对系统可用性极大的损害。大家都喜欢谈限流、降级、熔断,但是我觉得从代码质量这个源头把关是首先要做好的一件很重要的事情。如何提高代码质量?比较实际可用的就是 CodeReview,不要在乎每天多花的那 1 个小时左右的时间,作用可大着呢!
-另外,安利这个对提高代码质量有实际效果的宝贝:
+另外,安利几个对提高代码质量有实际效果的神器:
-1. sonarqube :保证你写出更安全更干净的代码!(ps: 目前所在的项目基本都会用到这个插件)。
-2. Alibaba 开源的 Java 诊断工具 Arthas 也是很不错的选择。
-3. IDEA 自带的代码分析等工具进行代码扫描也是非常非常棒的。
+- [Sonarqube](https://www.sonarqube.org/);
+- Alibaba 开源的 Java 诊断工具 [Arthas](https://arthas.aliyun.com/doc/);
+- [阿里巴巴 Java 代码规范](https://github.com/alibaba/p3c)(Alibaba Java Code Guidelines);
+- IDEA 自带的代码分析等工具。
-### 2.使用集群,减少单点故障
+### 使用集群,减少单点故障
先拿常用的 Redis 举个例子!我们如何保证我们的 Redis 缓存高可用呢?答案就是使用集群,避免单点故障。当我们使用一个 Redis 实例作为缓存的时候,这个 Redis 实例挂了之后,整个缓存服务可能就挂了。使用了集群之后,即使一台 Redis 实例挂了,不到一秒就会有另外一台 Redis 实例顶上。
-### 3.限流
+### 限流
-流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。——来自 alibaba-[Sentinel](https://github.com/alibaba/Sentinel "Sentinel") 的 wiki。
+流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。——来自 [alibaba-Sentinel](https://github.com/alibaba/Sentinel "Sentinel") 的 wiki。
-### 4.超时和重试机制设置
+### 超时和重试机制设置
一旦用户请求超过某个时间的得不到响应,就抛出异常。这个是非常重要的,很多线上系统故障都是因为没有进行超时设置或者超时设置的方式不对导致的。我们在读取第三方服务的时候,尤其适合设置超时和重试机制。一般我们使用一些 RPC 框架的时候,这些框架都自带的超时重试的配置。如果不进行超时设置可能会导致请求响应速度慢,甚至导致请求堆积进而让系统无法再处理请求。重试的次数一般设为 3 次,再多次的重试没有好处,反而会加重服务器压力(部分场景使用失败重试机制会不太适合)。
-### 5.熔断机制
+### 熔断机制
超时和重试机制设置之外,熔断机制也是很重要的。 熔断机制说的是系统自动收集所依赖服务的资源使用情况和性能指标,当所依赖的服务恶化或者调用失败次数达到某个阈值的时候就迅速失败,让当前系统立即切换依赖其他备用服务。 比较常用的流量控制和熔断降级框架是 Netflix 的 Hystrix 和 alibaba 的 Sentinel。
-### 6.异步调用
+### 异步调用
异步调用的话我们不需要关心最后的结果,这样我们就可以用户请求完成之后就立即返回结果,具体处理我们可以后续再做,秒杀场景用这个还是蛮多的。但是,使用异步之后我们可能需要 **适当修改业务流程进行配合**,比如**用户在提交订单之后,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**。除了可以在程序中实现异步之外,我们常常还使用消息队列,消息队列可以通过异步处理提高系统性能(削峰、减少响应所需时间)并且可以降低系统耦合性。
-### 7.使用缓存
+### 使用缓存
如果我们的系统属于并发量比较高的话,如果我们单纯使用数据库的话,当大量请求直接落到数据库可能数据库就会直接挂掉。使用缓存缓存热点数据,因为缓存存储在内存中,所以速度相当地快!
-### 8.其他
+### 其他
-1. **核心应用和服务优先使用更好的硬件**
-2. **监控系统资源使用情况增加报警设置。**
-3. **注意备份,必要时候回滚。**
-4. **灰度发布:** 将服务器集群分成若干部分,每天只发布一部分机器,观察运行稳定没有故障,第二天继续发布一部分机器,持续几天才把整个集群全部发布完毕,期间如果发现问题,只需要回滚已发布的一部分服务器即可
-5. **定期检查/更换硬件:** 如果不是购买的云服务的话,定期还是需要对硬件进行一波检查的,对于一些需要更换或者升级的硬件,要及时更换或者升级。
-6. .....(想起来再补充!也欢迎各位欢迎补充!)
+- **核心应用和服务优先使用更好的硬件**
+- **监控系统资源使用情况增加报警设置。**
+- **注意备份,必要时候回滚。**
+- **灰度发布:** 将服务器集群分成若干部分,每天只发布一部分机器,观察运行稳定没有故障,第二天继续发布一部分机器,持续几天才把整个集群全部发布完毕,期间如果发现问题,只需要回滚已发布的一部分服务器即可
+- **定期检查/更换硬件:** 如果不是购买的云服务的话,定期还是需要对硬件进行一波检查的,对于一些需要更换或者升级的硬件,要及时更换或者升级。
+- .....
diff --git a/docs/high-availability/limit-request.md b/docs/high-availability/limit-request.md
index 1c611e55a415e38ca4deab3beeb75ffb555a3215..72593d3c7136d9c91142fca2e4c5b388d599487d 100644
--- a/docs/high-availability/limit-request.md
+++ b/docs/high-availability/limit-request.md
@@ -1,6 +1,7 @@
-# 限流
-
-## 何为限流?为什么要限流?
+---
+title: 服务限流详解
+category: 高可用
+---
针对软件系统来说,限流就是对请求的速率进行限制,避免瞬时的大量请求击垮软件系统。毕竟,软件系统的处理能力是有限的。如果说超过了其处理能力的范围,软件系统可能直接就挂掉了。
@@ -8,9 +9,7 @@
现实生活中,处处都有限流的实际应用,就比如排队买票是为了避免大量用户涌入购票而导致售票员无法处理。
-
-
-## 常见限流算法
+## 常见限流算法有哪些?
简单介绍 4 种非常好理解并且容易实现的限流算法!
@@ -38,7 +37,7 @@
滑动窗口计数器算法相比于固定窗口计数器算法的优化在于:**它把时间以一定比例分片** 。
-例如我们的借口限流每分钟处理 60 个请求,我们可以把 1 分钟分为 60 个窗口。每隔 1 秒移动一次,每个窗口一秒只能处理 不大于 `60(请求数)/60(窗口数)` 的请求, 如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。
+例如我们的接口限流每分钟处理 60 个请求,我们可以把 1 分钟分为 60 个窗口。每隔 1 秒移动一次,每个窗口一秒只能处理 不大于 `60(请求数)/60(窗口数)` 的请求, 如果当前窗口的请求计数总和超过了限制的数量的话就不再处理其他请求。
很显然, **当滑动窗口的格子划分的越多,滑动窗口的滚动就越平滑,限流的统计就会越精确。**
@@ -58,11 +57,13 @@

-## 单机限流
+## 单机限流怎么做?
+
+单机限流针对的是单体架构应用。
单机限流可以直接使用 Google Guava 自带的限流工具类 `RateLimiter` 。 `RateLimiter` 基于令牌桶算法,可以应对突发流量。
-> Guava 地址:https://github.com/google/guava
+> Guava 地址:
除了最基本的令牌桶算法(平滑突发限流)实现之外,Guava 的`RateLimiter`还提供了 **平滑预热限流** 的算法实现。
@@ -163,7 +164,7 @@ get 1 tokens: 0.198359s
另外,**Bucket4j** 是一个非常不错的基于令牌/漏桶算法的限流库。
-> Bucket4j 地址:https://github.com/vladimir-bukhtoyarov/bucket4j
+> Bucket4j 地址:
相对于,Guava 的限流工具类来说,Bucket4j 提供的限流功能更加全面。不仅支持单机限流和分布式限流,还可以集成监控,搭配 Prometheus 和 Grafana 使用。
@@ -173,7 +174,7 @@ Spring Cloud Gateway 中自带的单机限流的早期版本就是基于 Bucket4
Resilience4j 是一个轻量级的容错组件,其灵感来自于 Hystrix。自[Netflix 宣布不再积极开发 Hystrix](https://github.com/Netflix/Hystrix/commit/a7df971cbaddd8c5e976b3cc5f14013fe6ad00e6) 之后,Spring 官方和 Netflix 都更推荐使用 Resilience4j 来做限流熔断。
-> Resilience4j 地址: https://github.com/resilience4j/resilience4j
+> Resilience4j 地址:
一般情况下,为了保证系统的高可用,项目的限流和熔断都是要一起做的。
@@ -181,23 +182,30 @@ Resilience4j 不仅提供限流,还提供了熔断、负载保护、自动重
因此,在绝大部分场景下 Resilience4j 或许会是更好的选择。如果是一些比较简单的限流场景的话,Guava 或者 Bucket4j 也是不错的选择。
-## 分布式限流
+## 分布式限流怎么做?
+
+分布式限流针对的分布式/微服务应用架构应用,在这种架构下,单机限流就不适用了,因为会存在多种服务,并且一种服务也可能会被部署多份。
分布式限流常见的方案:
-- **借助中间件架限流** :可以借助 Sentinel 或者使用 Redis 来自己实现对应的限流逻辑。
-- **网关层限流** :比较常用的一种方案,直接在网关层把限流给安排上了。不过,通常网关层限流通常也需要借助到中间件/框架。就比如 Spring Cloud Gateway 的分布式限流实现`RedisRateLimiter`就是基于 Redis+Lua 来实现的,再比如 Spring Cloud Gateway 还可以整合 Sentinel 来做限流。
+- **借助中间件架限流**:可以借助 Sentinel 或者使用 Redis 来自己实现对应的限流逻辑。
+- **网关层限流**:比较常用的一种方案,直接在网关层把限流给安排上了。不过,通常网关层限流通常也需要借助到中间件/框架。就比如 Spring Cloud Gateway 的分布式限流实现`RedisRateLimiter`就是基于 Redis+Lua 来实现的,再比如 Spring Cloud Gateway 还可以整合 Sentinel 来做限流。
如果你要基于 Redis 来手动实现限流逻辑的话,建议配合 Lua 脚本来做。
-网上也有很多现成的脚本供你参考,就比如 Apache 网关项目 ShenYu 的 RateLimiter 限流插件就基于 Redis + Lua 实现了令牌桶算法/并发令牌桶算法、漏桶算法、滑动窗口算法。
+**为什么建议 Redis+Lua 的方式?** 主要有两点原因:
+
+- **减少了网络开销**:我们可以利用 Lua 脚本来批量执行多条 Redis 命令,这些 Redis 命令会被提交到 Redis 服务器一次性执行完成,大幅减小了网络开销。
+- **原子性**:一段 Lua 脚本可以视作一条命令执行,一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰。
+
+我这里就不放具体的限流脚本代码了,网上也有很多现成的优秀的限流脚本供你参考,就比如 Apache 网关项目 ShenYu 的 RateLimiter 限流插件就基于 Redis + Lua 实现了令牌桶算法/并发令牌桶算法、漏桶算法、滑动窗口算法。
-> ShenYu 地址: https://github.com/apache/incubator-shenyu
+> ShenYu 地址:
-
+
## 相关阅读
-- 服务治理之轻量级熔断框架 Resilience4j :https://xie.infoq.cn/article/14786e571c1a4143ad1ef8f19
-- 超详细的 Guava RateLimiter 限流原理解析:https://cloud.tencent.com/developer/article/1408819
-- 实战 Spring Cloud Gateway 之限流篇 👍:https://www.aneasystone.com/archives/2020/08/spring-cloud-gateway-current-limiting.html
+- 服务治理之轻量级熔断框架 Resilience4j:
+- 超详细的 Guava RateLimiter 限流原理解析:
+- 实战 Spring Cloud Gateway 之限流篇 👍:
diff --git "a/docs/high-availability/\346\200\247\350\203\275\346\265\213\350\257\225.md" b/docs/high-availability/performance-test.md
similarity index 85%
rename from "docs/high-availability/\346\200\247\350\203\275\346\265\213\350\257\225.md"
rename to docs/high-availability/performance-test.md
index af54673f9661b96e12b7406692a20386e2df6f0c..e6e9f9d1f03d24b4a901d2f1d7c0bb2e75ca151c 100644
--- "a/docs/high-availability/\346\200\247\350\203\275\346\265\213\350\257\225.md"
+++ b/docs/high-availability/performance-test.md
@@ -1,13 +1,12 @@
-# 性能测试入门
+---
+title: 性能测试入门
+category: 高可用
+---
性能测试一般情况下都是由测试这个职位去做的,那还需要我们开发学这个干嘛呢?了解性能测试的指标、分类以及工具等知识有助于我们更好地去写出性能更好的程序,另外作为开发这个角色,如果你会性能测试的话,相信也会为你的履历加分不少。
这篇文章是我会结合自己的实际经历以及在测试这里取的经所得,除此之外,我还借鉴了一些优秀书籍,希望对你有帮助。
-本文思维导图:
-
-
-
## 一 不同角色看网站性能
### 1.1 用户
@@ -29,20 +28,20 @@
5. 系统用到的算法是否还需要优化?
6. 系统是否存在内存泄露的问题?
7. 项目使用的 Redis 缓存多大?服务器性能如何?用的是机械硬盘还是固态硬盘?
-8. ......
+8. ......
### 1.3 测试人员
测试人员一般会根据性能测试工具来测试,然后一般会做出一个表格。这个表格可能会涵盖下面这些重要的内容:
-1. 相应时间;
+1. 响应时间;
2. 请求成功率;
3. 吞吐量;
4. ......
### 1.4 运维人员
-运维人员会倾向于根据基础设施和资源的利用率来判断网站的性能,比如我们的服务器资源使用是否合理、数据库资源是否存在滥用的情况、当然,这是传统的运维人员,现在 Devpos 火起来后,单纯干运维的很少了。我们这里暂且还保留有这个角色。
+运维人员会倾向于根据基础设施和资源的利用率来判断网站的性能,比如我们的服务器资源使用是否合理、数据库资源是否存在滥用的情况、当然,这是传统的运维人员,现在 Devops 火起来后,单纯干运维的很少了。我们这里暂且还保留有这个角色。
## 二 性能测试需要注意的点
@@ -64,7 +63,7 @@
**响应时间就是用户发出请求到用户收到系统处理结果所需要的时间。** 重要吗?实在太重要!
-比较出名的 2-5-8 原则是这样描述的:通常来说,2到5秒,页面体验会比较好,5到8秒还可以接受,8秒以上基本就很难接受了。另外,据统计当网站慢一秒就会流失十分之一的客户。
+比较出名的 2-5-8 原则是这样描述的:通常来说,2 到 5 秒,页面体验会比较好,5 到 8 秒还可以接受,8 秒以上基本就很难接受了。另外,据统计当网站慢一秒就会流失十分之一的客户。
但是,在某些场景下我们也并不需要太看重 2-5-8 原则 ,比如我觉得系统导出导入大数据量这种就不需要,系统生成系统报告这种也不需要。
@@ -81,20 +80,20 @@
1. QPS(Query Per Second):服务器每秒可以执行的查询次数;
2. TPS(Transaction Per Second):服务器每秒处理的事务数(这里的一个事务可以理解为客户发出请求到收到服务器的过程);
3. 并发数;系统能同时处理请求的数目即同时提交请求的用户数目。
-4. 响应时间: 一般取多次请求的平均响应时间
+4. 响应时间:一般取多次请求的平均响应时间
理清他们的概念,就很容易搞清楚他们之间的关系了。
- **QPS(TPS)** = 并发数/平均响应时间
-- **并发数** = QPS\平均响应时间
+- **并发数** = QPS\*平均响应时间
书中是这样描述 QPS 和 TPS 的区别的。
-> QPS vs TPS:QPS 基本类似于 TPS,但是不同的是,对于一个页面的一次访问,形成一个TPS;但一次页面请求,可能产生多次对服务器的请求,服务器对这些请求,就可计入“QPS”之中。如,访问一个页面会请求服务器2次,一次访问,产生一个“T”,产生2个“Q”。
+> QPS vs TPS:QPS 基本类似于 TPS,但是不同的是,对于一个页面的一次访问,形成一个 TPS;但一次页面请求,可能产生多次对服务器的请求,服务器对这些请求,就可计入“QPS”之中。如,访问一个页面会请求服务器 2 次,一次访问,产生一个“T”,产生 2 个“Q”。
### 3.4 性能计数器
-**性能计数器是描述服务器或者操作系统的一些数据指标如内存使用、CPU使用、磁盘与网络I/O等情况。**
+**性能计数器是描述服务器或者操作系统的一些数据指标如内存使用、CPU 使用、磁盘与网络 I/O 等情况。**
### 四 几种常见的性能测试
@@ -108,7 +107,7 @@
对被测试的系统继续加大请求压力,直到服务器的某个资源已经达到饱和了,比如系统的缓存已经不够用了或者系统的响应时间已经不满足要求了。
-负载测试说白点就是测试系统的上线。
+负载测试说白点就是测试系统的上限。
### 压力测试
@@ -126,15 +125,15 @@
没记错的话,除了 LoadRunner 其他几款性能测试工具都是开源免费的。
-1. Jmeter :Apache JMeter 是 JAVA 开发的性能测试工具。
+1. Jmeter:Apache JMeter 是 JAVA 开发的性能测试工具。
2. LoadRunner:一款商业的性能测试工具。
-3. Galtling :一款基于Scala 开发的高性能服务器性能测试工具。
-4. ab :全称为 Apache Bench 。Apache 旗下的一款测试工具,非常实用。
+3. Galtling:一款基于 Scala 开发的高性能服务器性能测试工具。
+4. ab:全称为 Apache Bench 。Apache 旗下的一款测试工具,非常实用。
### 5.2 前端常用
-1. Fiddler:抓包工具,它可以修改请求的数据,甚至可以修改服务器返回的数据,功能非常强大,是Web 调试的利器。
-2. HttpWatch: 可用于录制HTTP请求信息的工具。
+1. Fiddler:抓包工具,它可以修改请求的数据,甚至可以修改服务器返回的数据,功能非常强大,是 Web 调试的利器。
+2. HttpWatch: 可用于录制 HTTP 请求信息的工具。
## 六 常见的性能优化策略
@@ -147,4 +146,4 @@
3. 系统是否存在死锁的地方?
4. 系统是否存在内存泄漏?(Java 的自动回收内存虽然很方便,但是,有时候代码写的不好真的会造成内存泄漏)
5. 数据库索引使用是否合理?
-6. ......
\ No newline at end of file
+6. ......
diff --git a/docs/high-availability/redundancy.md b/docs/high-availability/redundancy.md
new file mode 100644
index 0000000000000000000000000000000000000000..71b86b46f72f038856f3b3edf9e9f36c0dcfc6ff
--- /dev/null
+++ b/docs/high-availability/redundancy.md
@@ -0,0 +1,44 @@
+---
+title: 冗余设计详解
+category: 高可用
+---
+
+冗余设计是保证系统和数据高可用的最常的手段。
+
+对于服务来说,冗余的思想就是相同的服务部署多份,如果正在使用的服务突然挂掉的话,系统可以很快切换到备份服务上,大大减少系统的不可用时间,提高系统的可用性。
+
+对于数据来说,冗余的思想就是相同的数据备份多份,这样就可以很简单地提高数据的安全性。
+
+实际上,日常生活中就有非常多的冗余思想的应用。
+
+拿我自己来说,我对于重要文件的保存方法就是冗余思想的应用。我日常所使用的重要文件都会同步一份在 GitHub 以及个人云盘上,这样就可以保证即使电脑硬盘损坏,我也可以通过 GitHub 或者个人云盘找回自己的重要文件。
+
+高可用集群(High Availability Cluster,简称 HA Cluster)、同城灾备、异地灾备、同城多活和异地多活是冗余思想在高可用系统设计中最典型的应用。
+
+- **高可用集群** : 同一份服务部署两份或者多份,当正在使用的服务突然挂掉的话,可以切换到另外一台服务,从而保证服务的高可用。
+- **同城灾备**:一整个集群可以部署在同一个机房,而同城灾备中相同服务部署在同一个城市的不同机房中。并且,备用服务不处理请求。这样可以避免机房出现意外情况比如停电、火灾。
+- **异地灾备**:类似于同城灾备,不同的是,相同服务部署在异地(通常距离较远,甚至是在不同的城市或者国家)的不同机房中
+- **同城多活**:类似于同城灾备,但备用服务可以处理请求,这样可以充分利用系统资源,提高系统的并发。
+- **异地多活** : 将服务部署在异地的不同机房中,并且,它们可以同时对外提供服务。
+
+高可用集群单纯是服务的冗余,并没有强调地域。同城灾备、异地灾备、同城多活和异地多活实现了地域上的冗余。
+
+同城和异地的主要区别在于机房之间的距离。异地通常距离较远,甚至是在不同的城市或者国家。
+
+和传统的灾备设计相比,同城多活和异地多活最明显的改变在于“多活”,即所有站点都是同时在对外提供服务的。异地多活是为了应对突发状况比如火灾、地震等自然或者人为灾害。
+
+光做好冗余还不够,必须要配合上 **故障转移** 才可以! 所谓故障转移,简单来说就是实现不可用服务快速且自动地切换到可用服务,整个过程不需要人为干涉。
+
+举个例子:哨兵模式的 Redis 集群中,如果 Sentinel(哨兵) 检测到 master 节点出现故障的话, 它就会帮助我们实现故障转移,自动将某一台 slave 升级为 master,确保整个 Redis 系统的可用性。整个过程完全自动,不需要人工介入。我在[《Java 面试指北》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7)的「技术面试题篇」中的数据库部分详细介绍了 Redis 集群相关的知识点&面试题,感兴趣的小伙伴可以看看。
+
+再举个例子:Nginx 可以结合 Keepalived 来实现高可用。如果 Nginx 主服务器宕机的话,Keepalived 可以自动进行故障转移,备用 Nginx 主服务器升级为主服务。并且,这个切换对外是透明的,因为使用的虚拟 IP,虚拟 IP 不会改变。我在[《Java 面试指北》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7)的「技术面试题篇」中的「服务器」部分详细介绍了 Nginx 相关的知识点&面试题,感兴趣的小伙伴可以看看。
+
+异地多活架构实施起来非常难,需要考虑的因素非常多。本人不才,实际项目中并没有实践过异地多活架构,我对其了解还停留在书本知识。
+
+如果你想要深入学习异地多活相关的知识,我这里推荐几篇我觉得还不错的文章:
+
+- [搞懂异地多活,看这篇就够了- 水滴与银弹 - 2021](https://mp.weixin.qq.com/s/T6mMDdtTfBuIiEowCpqu6Q)
+- [四步构建异地多活](https://mp.weixin.qq.com/s/hMD-IS__4JE5_nQhYPYSTg)
+- [《从零开始学架构》— 28 | 业务高可用的保障:异地多活架构](http://gk.link/a/10pKZ)
+
+不过,这些文章大多也都是在介绍概念知识。目前,网上还缺少真正介绍具体要如何去实践落地异地多活架构的资料。
diff --git a/docs/high-availability/timeout-and-retry.md b/docs/high-availability/timeout-and-retry.md
new file mode 100644
index 0000000000000000000000000000000000000000..2dc78454ac1d38e6867329aea130df76f289327c
--- /dev/null
+++ b/docs/high-availability/timeout-and-retry.md
@@ -0,0 +1,70 @@
+---
+title: 超时&重试详解
+category: 高可用
+---
+
+由于网络问题、系统或者服务内部的 Bug、服务器宕机、操作系统崩溃等问题的不确定性,我们的系统或者服务永远不可能保证时刻都是可用的状态。
+
+为了最大限度的减小系统或者服务出现故障之后带来的影响,我们需要用到的 **超时(Timeout)** 和 **重试(Retry)** 机制。
+
+想要把超时和重试机制讲清楚其实很简单,因为它俩本身就不是什么高深的概念。
+
+虽然超时和重试机制的思想很简单,但是它俩是真的非常实用。你平时接触到的绝大部分涉及到远程调用的系统或者服务都会应用超时和重试机制。尤其是对于微服务系统来说,正确设置超时和重试非常重要。单体服务通常只涉及数据库、缓存、第三方 API、中间件等的网络调用,而微服务系统内部各个服务之间还存在着网络调用。
+
+## 超时机制
+
+### 什么是超时机制?
+
+超时机制说的是当一个请求超过指定的时间(比如 1s)还没有被处理的话,这个请求就会直接被取消并抛出指定的异常或者错误(比如 `504 Gateway Timeout`)。
+
+我们平时接触到的超时可以简单分为下面 2 种:
+
+- **连接超时(ConnectTimeout)**:客户端与服务端建立连接的最长等待时间。
+- **读取超时(ReadTimeout)**:客户端和服务端已经建立连接,客户端等待服务端处理完请求的最长时间。实际项目中,我们关注比较多的还是读取超时。
+
+一些连接池客户端框架中可能还会有获取连接超时和空闲连接清理超时。
+
+如果没有设置超时的话,就可能会导致服务端连接数爆炸和大量请求堆积的问题。
+
+这些堆积的连接和请求会消耗系统资源,影响新收到的请求的处理。严重的情况下,甚至会拖垮整个系统或者服务。
+
+我之前在实际项目就遇到过类似的问题,整个网站无法正常处理请求,服务器负载直接快被拉满。后面发现原因是项目超时设置错误加上客户端请求处理异常,导致服务端连接数直接接近 40w+,这么多堆积的连接直接把系统干趴了。
+
+### 超时时间应该如何设置?
+
+超时到底设置多长时间是一个难题!超时值设置太高或者太低都有风险。如果设置太高的话,会降低超时机制的有效性,比如你设置超时为 10s 的话,那设置超时就没啥意义了,系统依然可能会出现大量慢请求堆积的问题。如果设置太低的话,就可能会导致在系统或者服务在某些处理请求速度变慢的情况下(比如请求突然增多),大量请求重试(超时通常会结合重试)继续加重系统或者服务的压力,进而导致整个系统或者服务被拖垮的问题。
+
+通常情况下,我们建议读取超时设置为 **1500ms** ,这是一个比较普适的值。如果你的系统或者服务对于延迟比较敏感的话,那读取超时值可以适当在 **1500ms** 的基础上进行缩短。反之,读取超时值也可以在 **1500ms** 的基础上进行加长,不过,尽量还是不要超过 **1500ms** 。连接超时可以适当设置长一些,建议在 **1000ms ~ 5000ms** 之内。
+
+没有银弹!超时值具体该设置多大,还是要根据实际项目的需求和情况慢慢调整优化得到。
+
+更上一层,参考[美团的 Java 线程池参数动态配置](https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)思想,我们也可以将超时弄成可配置化的参数而不是固定的,比较简单的一种办法就是将超时的值放在配置中心中。这样的话,我们就可以根据系统或者服务的状态动态调整超时值了。
+
+## 重试机制
+
+### 什么是重试机制?
+
+重试机制一般配合超时机制一起使用,指的是多次发送相同的请求来避免瞬态故障和偶然性故障。
+
+瞬态故障可以简单理解为某一瞬间系统偶然出现的故障,并不会持久。偶然性故障可以理解为哪些在某些情况下偶尔出现的故障,频率通常较低。
+
+重试的核心思想是通过消耗服务器的资源来尽可能获得请求更大概率被成功处理。由于瞬态故障和偶然性故障是很少发生的,因此,重试对于服务器的资源消耗几乎是可以被忽略的。
+
+### 重试的次数如何设置?
+
+重试的次数不宜过多,否则依然会对系统负载造成比较大的压力。
+
+重试的次数通常建议设为 3 次。并且,我们通常还会设置重试的间隔,比如说我们要重试 3 次的话,第 1 次请求失败后,等待 1 秒再进行重试,第 2 次请求失败后,等待 2 秒再进行重试,第 3 次请求失败后,等待 3 秒再进行重试。
+
+### 重试幂等
+
+超时和重试机制在实际项目中使用的话,需要注意保证同一个请求没有被多次执行。
+
+什么情况下会出现一个请求被多次执行呢?客户端等待服务端完成请求完成超时但此时服务端已经执行了请求,只是由于短暂的网络波动导致响应在发送给客户端的过程中延迟了。
+
+举个例子:用户支付购买某个课程,结果用户支付的请求由于重试的问题导致用户购买同一门课程支付了两次。对于这种情况,我们在执行用户购买课程的请求的时候需要判断一下用户是否已经购买过。这样的话,就不会因为重试的问题导致重复购买了。
+
+## 参考
+
+- 微服务之间调用超时的设置治理:
+- 超时、重试和抖动回退:
diff --git "a/docs/high-availability/\347\201\276\345\244\207\350\256\276\350\256\241\345\222\214\345\274\202\345\234\260\345\244\232\346\264\273.md" "b/docs/high-availability/\347\201\276\345\244\207\350\256\276\350\256\241\345\222\214\345\274\202\345\234\260\345\244\232\346\264\273.md"
deleted file mode 100644
index 4f83dfa910b0b2d8a2a7be6bee90aca66b57d826..0000000000000000000000000000000000000000
--- "a/docs/high-availability/\347\201\276\345\244\207\350\256\276\350\256\241\345\222\214\345\274\202\345\234\260\345\244\232\346\264\273.md"
+++ /dev/null
@@ -1,14 +0,0 @@
-# 灾备设计&异地多活
-
-**灾备** = 容灾+备份。
-
-- **备份** : 将系统所产生的的所有重要数据多备份几份。
-- **容灾** : 在异地建立两个完全相同的系统。当某个地方的系统突然挂掉,整个应用系统可以切换到另一个,这样系统就可以正常提供服务了。
-
-**异地多活** 描述的是将服务部署在异地并且服务同时对外提供服务。和传统的灾备设计的最主要区别在于“多活”,即所有站点都是同时在对外提供服务的。异地多活是为了应对突发状况比如火灾、地震等自然或者认为灾害。
-
-相关阅读:
-
-- [搞懂异地多活,看这篇就够了](https://mp.weixin.qq.com/s/T6mMDdtTfBuIiEowCpqu6Q)
-- [四步构建异地多活](https://mp.weixin.qq.com/s/hMD-IS__4JE5_nQhYPYSTg)
-- [《从零开始学架构》— 28 | 业务高可用的保障:异地多活架构](http://gk.link/a/10pKZ)
\ No newline at end of file
diff --git "a/docs/high-availability/\350\266\205\346\227\266\345\222\214\351\207\215\350\257\225\346\234\272\345\210\266.md" "b/docs/high-availability/\350\266\205\346\227\266\345\222\214\351\207\215\350\257\225\346\234\272\345\210\266.md"
deleted file mode 100644
index 64f03f7506c4d7393343fa96734765027ae07ac2..0000000000000000000000000000000000000000
--- "a/docs/high-availability/\350\266\205\346\227\266\345\222\214\351\207\215\350\257\225\346\234\272\345\210\266.md"
+++ /dev/null
@@ -1,5 +0,0 @@
-# 超时&重试机制
-
-**一旦用户的请求超过某个时间得不到响应就结束此次请求并抛出异常。** 如果不进行超时设置可能会导致请求响应速度慢,甚至导致请求堆积进而让系统无法在处理请求。
-
-另外,重试的次数一般设为 3 次,再多次的重试没有好处,反而会加重服务器压力(部分场景使用失败重试机制会不太适合)。
\ No newline at end of file
diff --git "a/docs/high-availability/\351\231\215\347\272\247&\347\206\224\346\226\255.md" "b/docs/high-availability/\351\231\215\347\272\247&\347\206\224\346\226\255.md"
deleted file mode 100644
index 2ff7b92289353b2be905a21fd6660ee574ba98b0..0000000000000000000000000000000000000000
--- "a/docs/high-availability/\351\231\215\347\272\247&\347\206\224\346\226\255.md"
+++ /dev/null
@@ -1,9 +0,0 @@
-# 降级&熔断
-
-降级是从系统功能优先级的角度考虑如何应对系统故障。
-
-服务降级指的是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。
-
-熔断和降级是两个比较容易混淆的概念,两者的含义并不相同。
-
-降级的目的在于应对系统自身的故障,而熔断的目的在于应对当前系统依赖的外部系统或者第三方系统的故障。
\ No newline at end of file
diff --git "a/docs/high-availability/\351\233\206\347\276\244.md" "b/docs/high-availability/\351\233\206\347\276\244.md"
deleted file mode 100644
index 5da34020f32a4faabdb116ff82aeeaf3357cff2f..0000000000000000000000000000000000000000
--- "a/docs/high-availability/\351\233\206\347\276\244.md"
+++ /dev/null
@@ -1,3 +0,0 @@
-# 集群
-
-相同的服务部署多份,避免单点故障。
\ No newline at end of file
diff --git a/docs/high-performance/cdn.md b/docs/high-performance/cdn.md
new file mode 100644
index 0000000000000000000000000000000000000000..c5ffc53bd4196f5ca15ac91c9e249283de1e6faf
--- /dev/null
+++ b/docs/high-performance/cdn.md
@@ -0,0 +1,133 @@
+---
+title: CDN常见问题总结
+category: 高性能
+head:
+ - - meta
+ - name: keywords
+ content: CDN,内容分发网络
+ - - meta
+ - name: description
+ content: CDN 就是将静态资源分发到多个不同的地方以实现就近访问,进而加快静态资源的访问速度,减轻服务器以及带宽的负担。
+---
+
+## 什么是 CDN ?
+
+**CDN** 全称是 Content Delivery Network/Content Distribution Network,翻译过的意思是 **内容分发网络** 。
+
+我们可以将内容分发网络拆开来看:
+
+- 内容:指的是静态资源比如图片、视频、文档、JS、CSS、HTML。
+- 分发网络:指的是将这些静态资源分发到位于多个不同的地理位置机房中的服务器上,这样,就可以实现静态资源的就近访问比如北京的用户直接访问北京机房的数据。
+
+所以,简单来说,**CDN 就是将静态资源分发到多个不同的地方以实现就近访问,进而加快静态资源的访问速度,减轻服务器以及带宽的负担。**
+
+类似于京东建立的庞大的仓储运输体系,京东物流在全国拥有非常多的仓库,仓储网络几乎覆盖全国所有区县。这样的话,用户下单的第一时间,商品就从距离用户最近的仓库,直接发往对应的配送站,再由京东小哥送到你家。
+
+
+
+你可以将 CDN 看作是服务上一层的特殊缓存服务,分布在全国各地,主要用来处理静态资源的请求。
+
+
+
+我们经常拿全站加速和内容分发网络做对比,不要把两者搞混了!全站加速(不同云服务商叫法不同,腾讯云叫 ECDN、阿里云叫 DCDN)既可以加速静态资源又可以加速动态资源,内容分发网络(CDN)主要针对的是 **静态资源** 。
+
+
+
+绝大部分公司都会在项目开发中交使用 CDN 服务,但很少会有自建 CDN 服务的公司。基于成本、稳定性和易用性考虑,建议直接选择专业的云厂商(比如阿里云、腾讯云、华为云、青云)或者 CDN 厂商(比如网宿、蓝汛)提供的开箱即用的 CDN 服务。
+
+很多朋友可能要问了:**既然是就近访问,为什么不直接将服务部署在多个不同的地方呢?**
+
+- 成本太高,需要部署多份相同的服务。
+- 静态资源通常占用空间比较大且经常会被访问到,如果直接使用服务器或者缓存来处理静态资源请求的话,对系统资源消耗非常大,可能会影响到系统其他服务的正常运行。
+
+同一个服务在在多个不同的地方部署多份(比如同城灾备、异地灾备、同城多活、异地多活)是为了实现系统的高可用而不是就近访问。
+
+## CDN 工作原理是什么?
+
+搞懂下面 3 个问题也就搞懂了 CDN 的工作原理:
+
+1. 静态资源是如何被缓存到 CDN 节点中的?
+2. 如何找到最合适的 CDN 节点?
+3. 如何防止静态资源被盗用?
+
+### 静态资源是如何被缓存到 CDN 节点中的?
+
+你可以通过 **预热** 的方式将源站的资源同步到 CDN 的节点中。这样的话,用户首次请求资源可以直接从 CDN 节点中取,无需回源。这样可以降低源站压力,提升用户体验。
+
+如果不预热的话,你访问的资源可能不在 CDN 节点中,这个时候 CDN 节点将请求源站获取资源,这个过程是大家经常说的 **回源**。
+
+> - 回源:当 CDN 节点上没有用户请求的资源或该资源的缓存已经过期时,CDN 节点需要从原始服务器获取最新的资源内容,这个过程就是回源。当用户请求发生回源的话,会导致该请求的响应速度比未使用 CDN 还慢,因为相比于未使用 CDN 还多了一层 CDN 的调用流程。
+> - 预热:预热是指在 CDN 上提前将内容缓存到 CDN 节点上。这样当用户在请求这些资源时,能够快速地从最近的 CDN 节点获取到而不需要回源,进而减少了对源站的访问压力,提高了访问速度。
+
+
+
+如果资源有更新的话,你也可以对其 **刷新** ,删除 CDN 节点上缓存的旧资源,并强制 CDN 节点回源站获取最新资源。
+
+几乎所有云厂商提供的 CDN 服务都具备缓存的刷新和预热功能(下图是阿里云 CDN 服务提供的相应功能):
+
+
+
+**命中率** 和 **回源率** 是衡量 CDN 服务质量两个重要指标。命中率越高越好,回源率越低越好。
+
+### 如何找到最合适的 CDN 节点?
+
+GSLB (Global Server Load Balance,全局负载均衡)是 CDN 的大脑,负责多个 CDN 节点之间相互协作,最常用的是基于 DNS 的 GSLB。
+
+CDN 会通过 GSLB 找到最合适的 CDN 节点,更具体点来说是下面这样的:
+
+1. 浏览器向 DNS 服务器发送域名请求;
+2. DNS 服务器向根据 CNAME( Canonical Name ) 别名记录向 GSLB 发送请求;
+3. GSLB 返回性能最好(通常距离请求地址最近)的 CDN 节点(边缘服务器,真正缓存内容的地方)的地址给浏览器;
+4. 浏览器直接访问指定的 CDN 节点。
+
+
+
+为了方便理解,上图其实做了一点简化。GSLB 内部可以看作是 CDN 专用 DNS 服务器和负载均衡系统组合。CDN 专用 DNS 服务器会返回负载均衡系统 IP 地址给浏览器,浏览器使用 IP 地址请求负载均衡系统进而找到对应的 CDN 节点。
+
+**GSLB 是如何选择出最合适的 CDN 节点呢?** GSLB 会根据请求的 IP 地址、CDN 节点状态(比如负载情况、性能、响应时间、带宽)等指标来综合判断具体返回哪一个 CDN 节点的地址。
+
+### 如何防止资源被盗刷?
+
+如果我们的资源被其他用户或者网站非法盗刷的话,将会是一笔不小的开支。
+
+解决这个问题最常用最简单的办法设置 **Referer 防盗链**,具体来说就是根据 HTTP 请求的头信息里面的 Referer 字段对请求进行限制。我们可以通过 Referer 字段获取到当前请求页面的来源页面的网站地址,这样我们就能确定请求是否来自合法的网站。
+
+CDN 服务提供商几乎都提供了这种比较基础的防盗链机制。
+
+
+
+不过,如果站点的防盗链配置允许 Referer 为空的话,通过隐藏 Referer,可以直接绕开防盗链。
+
+通常情况下,我们会配合其他机制来确保静态资源被盗用,一种常用的机制是 **时间戳防盗链** 。相比之下,**时间戳防盗链** 的安全性更强一些。时间戳防盗链加密的 URL 具有时效性,过期之后就无法再被允许访问。
+
+时间戳防盗链的 URL 通常会有两个参数一个是签名字符串,一个是过期时间。签名字符串一般是通过对用户设定的加密字符串、请求路径、过期时间通过 MD5 哈希算法取哈希的方式获得。
+
+时间戳防盗链 URL 示例:
+
+```
+http://cdn.wangsu.com/4/123.mp3? wsSecret=79aead3bd7b5db4adeffb93a010298b5&wsTime=1601026312
+```
+
+- `wsSecret`:签名字符串。
+- `wsTime`: 过期时间。
+
+
+
+时间戳防盗链的实现也比较简单,并且可靠性较高,推荐使用。并且,绝大部分 CDN 服务提供商都提供了开箱即用的时间戳防盗链机制。
+
+
+
+除了 Referer 防盗链和时间戳防盗链之外,你还可以 IP 黑白名单配置、IP 访问限频配置等机制来防盗刷。
+
+## 总结
+
+- CDN 就是将静态资源分发到多个不同的地方以实现就近访问,进而加快静态资源的访问速度,减轻服务器以及带宽的负担。
+- 基于成本、稳定性和易用性考虑,建议直接选择专业的云厂商(比如阿里云、腾讯云、华为云、青云)或者 CDN 厂商(比如网宿、蓝汛)提供的开箱即用的 CDN 服务。
+- GSLB (Global Server Load Balance,全局负载均衡)是 CDN 的大脑,负责多个 CDN 节点之间相互协作,最常用的是基于 DNS 的 GSLB。CDN 会通过 GSLB 找到最合适的 CDN 节点。
+- 为了防止静态资源被盗用,我们可以利用 **Referer 防盗链** + **时间戳防盗链** 。
+
+## 参考
+
+- 时间戳防盗链 - 七牛云 CDN:
+- CDN 是个啥玩意?一文说个明白:
+- 《透视 HTTP 协议》- 37 | CDN:加速我们的网络服务:
diff --git a/docs/high-performance/images/message-queue/message-queue-pub-sub-model.drawio b/docs/high-performance/images/message-queue/message-queue-pub-sub-model.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..7a28c60d6dbdeb9de22402be645ef5f02da68393
--- /dev/null
+++ b/docs/high-performance/images/message-queue/message-queue-pub-sub-model.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/high-performance/images/message-queue/message-queue-pub-sub-model.png b/docs/high-performance/images/message-queue/message-queue-pub-sub-model.png
new file mode 100644
index 0000000000000000000000000000000000000000..a5a77736493e71ff3ffbeb7a3c064d58ecdf7520
Binary files /dev/null and b/docs/high-performance/images/message-queue/message-queue-pub-sub-model.png differ
diff --git a/docs/high-performance/images/message-queue/message-queue-queue-model.drawio b/docs/high-performance/images/message-queue/message-queue-queue-model.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..ea1c6caca4baa967538188f0d884d08b43503575
--- /dev/null
+++ b/docs/high-performance/images/message-queue/message-queue-queue-model.drawio
@@ -0,0 +1 @@
+7Zrbcts2EEC/hjPtQzS8iLdHSRGTdpLUidpp89SBSIjCGCRoELKkfH0XJCDxZstJ7Vp2Jc9YwAJcAtyDXWBFw5llu3ccFeuPLMHUsM1kZzhvDdu2TDuELynZ15LQdGpBykmiOh0FC/IN6yuVdEMSXLY6CsaoIEVbGLM8x7FoyRDnbNvutmK0fdcCpbgnWMSI9qV/kkSsa2lg+0f5e0zStb6z5akJL1F8nXK2ydX9DNuJvCiKgro5Q1qXmmi5RgnbNkTO3HBmnDFRl7LdDFP5bPVjq6+L7mg9jJvjXDzkgpvpl6Vd7vPZ+yj8vP32x++/5J/eKC23iG6wnoZHQd90xUAtjFrs1ZPybjZMN7wpKztOoIPlFrtjI5RS+X3FWbKJMdfaYFi1wrpZPZGDbhuGCgaHynS7JgIvChTLli0wB7K1yCjULCiisqgpWJEdTuRwCKUzRhmvFDnj2WQc+iAvBWfXuNHixQFermTLNRaxtI0pK9oqpppahDJCJcu/YjHliOQljPQjy5lqX7ANr8a2FgIQtV35FFx46vKf7FCOUsZSilFBylHMsqohLquu0arWDsWmfteeqjv0jaothLnAu4ZIGfkdZhkWHFSaqnWsgVML0nFUfdvAW4nWDbK1DKkFlR40H6GCguLqOxizBxjrmj9PJnItQy2mqCxJ3LZ69eBr12G5qt4wLSw605QGxDsi/lLXyPJXadiRq2pvd8rOVWWvCbgThxoh7RXs8wak1Lc9sdRx0vKHfYwamLgDmGgZxxQJctv2okPsqDtcMVI5FEWpa3Uo7eJXz0dd1XRrHUXeuKPI7ygSiKdY9BRVKB+m/eN0O6fpboAslzGB4PMBLTG9YiURhOXQtmRCAAng3yhJpSAG+4L7dKZU9pweYk2D+lX1aSidqGsFKwbp7blE0wzMqId9gsq1dK16QRRyGtkulfF/RFjpjwgE43KE81tMWYH//inDZQlx8ufvWKmtVXfGy+rhftd1OiCO+37XHlhQ9lP53fFpMrV14z0leVLxdiL+LiWEOPmwPAgOaP62EaAFKzlnAim4Q7Mfp1eu/BuM09Wnj079ef3x2zc7HJk2RLAuSY7bJ8nSwkdHyb04uYuTq5ycf2ZOzruQeSGz2geGHTK9ZybTv5B5IbMi0zszMoMBMus0TVmg/MeTPjMw/iY7Jn2W/JjvOaSB6ls8dRpoFcQ4jofQXgbu2L3/3P8qqAu7B+znTgOFp/1hIw2UM3mS+Dc5IF1+7BxQKRAX/WxVJY4I1cOFuajamSN1MnGk3EWdR7mnnzpvnkmCye/uB7p5oYcmmAL3eRNMeh1fNhL/941Ej8TnPnxZAz8fzV0jiIzQMuahAbEkgIJnTD0jCGQBqpOohy/MX7SdfRskFQyaMV6JejB3Sc1IktC7NhS8TmadDAJN5JzXQpPbTZwHfZq8/5SmgVR6nx3AKgyMMKpAg4L/omnSDmyhhm+9Frq8bqLogdvPp6NrIB3eZwkwmwTGxJKScG4E/SNKn66GPW1db4VA04yi13/g6P7ufNjINSzuD1jceQSLz/fO18+EF5vC/fTF5zdsnHiD7zZc9k0vc9/Uw3AA1rv3Tab/bPumQTLt+wJdYEzHxjS8bJteCExPt22C6vH1sPo4eXwHz5n/Aw==
\ No newline at end of file
diff --git a/docs/high-performance/images/message-queue/message-queue-queue-model.png b/docs/high-performance/images/message-queue/message-queue-queue-model.png
new file mode 100644
index 0000000000000000000000000000000000000000..8fb2cd6a7ba05795350d867e66a55d2dc3f4683a
Binary files /dev/null and b/docs/high-performance/images/message-queue/message-queue-queue-model.png differ
diff --git a/docs/high-performance/images/read-and-write-separation-and-library-subtable/horizontal-slicing-database.drawio b/docs/high-performance/images/read-and-write-separation-and-library-subtable/horizontal-slicing-database.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..1bc964d347fec64f00731150fb9780db1288312b
--- /dev/null
+++ b/docs/high-performance/images/read-and-write-separation-and-library-subtable/horizontal-slicing-database.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/high-performance/images/read-and-write-separation-and-library-subtable/horizontal-slicing-database.png b/docs/high-performance/images/read-and-write-separation-and-library-subtable/horizontal-slicing-database.png
new file mode 100644
index 0000000000000000000000000000000000000000..69569f469849929e2da0fcad68ab70117957143f
Binary files /dev/null and b/docs/high-performance/images/read-and-write-separation-and-library-subtable/horizontal-slicing-database.png differ
diff --git a/docs/high-performance/images/read-and-write-separation-and-library-subtable/read-and-write-separation-proxy.drawio b/docs/high-performance/images/read-and-write-separation-and-library-subtable/read-and-write-separation-proxy.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..0e1f4fc15a342fc75a9a58084e7715bc90d50161
--- /dev/null
+++ b/docs/high-performance/images/read-and-write-separation-and-library-subtable/read-and-write-separation-proxy.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/high-performance/images/read-and-write-separation-and-library-subtable/read-and-write-separation.drawio b/docs/high-performance/images/read-and-write-separation-and-library-subtable/read-and-write-separation.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..c1b3faa2b82b17404773004be8a88ca289409cf1
--- /dev/null
+++ b/docs/high-performance/images/read-and-write-separation-and-library-subtable/read-and-write-separation.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/high-performance/images/read-and-write-separation-and-library-subtable/two-forms-of-sub-table.drawio b/docs/high-performance/images/read-and-write-separation-and-library-subtable/two-forms-of-sub-table.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..c4c81fbb109ebd3e5c27bea2b4e83d328aee7a58
--- /dev/null
+++ b/docs/high-performance/images/read-and-write-separation-and-library-subtable/two-forms-of-sub-table.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/high-performance/images/read-and-write-separation-and-library-subtable/two-forms-of-sub-table.png b/docs/high-performance/images/read-and-write-separation-and-library-subtable/two-forms-of-sub-table.png
new file mode 100644
index 0000000000000000000000000000000000000000..8680ee4d501202496f61ca7739be772b4736976f
Binary files /dev/null and b/docs/high-performance/images/read-and-write-separation-and-library-subtable/two-forms-of-sub-table.png differ
diff --git a/docs/high-performance/images/read-and-write-separation-and-library-subtable/vertical-slicing-database.drawio b/docs/high-performance/images/read-and-write-separation-and-library-subtable/vertical-slicing-database.drawio
new file mode 100644
index 0000000000000000000000000000000000000000..d56fe197b5cf5357b795379ca771115eef39103f
--- /dev/null
+++ b/docs/high-performance/images/read-and-write-separation-and-library-subtable/vertical-slicing-database.drawio
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/high-performance/images/read-and-write-separation-and-library-subtable/vertical-slicing-database.png b/docs/high-performance/images/read-and-write-separation-and-library-subtable/vertical-slicing-database.png
new file mode 100644
index 0000000000000000000000000000000000000000..67c857a7c90fb1cd8a626fa75f3637ad0bb400b8
Binary files /dev/null and b/docs/high-performance/images/read-and-write-separation-and-library-subtable/vertical-slicing-database.png differ
diff --git a/docs/high-performance/load-balancing.md b/docs/high-performance/load-balancing.md
new file mode 100644
index 0000000000000000000000000000000000000000..a41af365247a79eda7daf82e68c447c04ed84610
--- /dev/null
+++ b/docs/high-performance/load-balancing.md
@@ -0,0 +1,235 @@
+---
+title: 负载均衡常见问题总结
+category: 高性能
+head:
+ - - meta
+ - name: keywords
+ content: 客户端负载均衡,服务负载均衡,Nginx,负载均衡算法,七层负载均衡,DNS解析
+ - - meta
+ - name: description
+ content: 负载均衡指的是将用户请求分摊到不同的服务器上处理,以提高系统整体的并发处理能力。负载均衡可以简单分为服务端负载均衡和客户端负载均衡 这两种。服务端负载均衡涉及到的知识点更多,工作中遇到的也比较多,因为,我会花更多时间来介绍。
+---
+
+## 什么是负载均衡?
+
+**负载均衡** 指的是将用户请求分摊到不同的服务器上处理,以提高系统整体的并发处理能力以及可靠性。负载均衡服务可以有由专门的软件或者硬件来完成,一般情况下,硬件的性能更好,软件的价格更便宜(后文会详细介绍到)。
+
+下图是[《Java 面试指北》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247519384&idx=1&sn=bc7e71af75350b755f04ca4178395b1a&chksm=cea1c353f9d64a458f797696d4144b4d6e58639371a4612b8e4d106d83a66d2289e7b2cd7431&token=660789642&lang=zh_CN&scene=21#wechat_redirect) 「高并发篇」中的一篇文章的配图,从图中可以看出,系统的商品服务部署了多份在不同的服务器上,为了实现访问商品服务请求的分流,我们用到了负载均衡。
+
+
+
+负载均衡是一种比较常用且实施起来较为简单的提高系统并发能力和可靠性的手段,不论是单体架构的系统还是微服务架构的系统几乎都会用到。
+
+## 负载均衡分为哪几种?
+
+负载均衡可以简单分为 **服务端负载均衡** 和 **客户端负载均衡** 这两种。
+
+服务端负载均衡涉及到的知识点更多,工作中遇到的也比较多,因为,我会花更多时间来介绍。
+
+### 服务端负载均衡
+
+**服务端负载均衡** 主要应用在 **系统外部请求** 和 **网关层** 之间,可以使用 **软件** 或者 **硬件** 实现。
+
+下图是我画的一个简单的基于 Nginx 的服务端负载均衡示意图:
+
+
+
+**硬件负载均衡** 通过专门的硬件设备(比如 **F5、A10、Array** )实现负载均衡功能。
+
+硬件负载均衡的优势是性能很强且稳定,缺点就是实在是太贵了。像基础款的 F5 最低也要 20 多万,绝大部分公司是根本负担不起的,业务量不大的话,真没必要非要去弄个硬件来做负载均衡,用软件负载均衡就足够了!
+
+在我们日常开发中,一般很难接触到硬件负载均衡,接触的比较多的还是 **软件负载均衡** 。软件负载均衡通过软件(比如 **LVS、Nginx、HAproxy** )实现负载均衡功能,性能虽然差一些,但价格便宜啊!像基础款的 Linux 服务器也就几千,性能好一点的 2~3 万的就很不错了。
+
+根据 OSI 模型,服务端负载均衡还可以分为:
+
+- 二层负载均衡
+- 三层负载均衡
+- 四层负载均衡
+- 七层负载均衡
+
+最常见的是四层和七层负载均衡,因此,本文也是重点介绍这两种负载均衡。
+
+> Nginx 官网对四层负载和七层负载均衡均衡做了详细介绍,感兴趣的可以看看。
+>
+> - [What Is Layer 4 Load Balancing?](https://www.nginx.com/resources/glossary/layer-4-load-balancing/)
+> - [What Is Layer 7 Load Balancing?](https://www.nginx.com/resources/glossary/layer-7-load-balancing/)
+
+
+
+- **四层负载均衡** 工作在 OSI 模型第四层,也就是传输层,这一层的主要协议是 TCP/UDP,负载均衡器在这一层能够看到数据包里的源端口地址以及目的端口地址,会基于这些信息通过一定的负载均衡算法将数据包转发到后端真实服务器。也就是说,四层负载均衡的核心就是 IP+端口层面的负载均衡,不涉及具体的报文内容。
+- **七层负载均衡** 工作在 OSI 模型第七层,也就是应用层,这一层的主要协议是 HTTP 。这一层的负载均衡比四层负载均衡路由网络请求的方式更加复杂,它会读取报文的数据部分(比如说我们的 HTTP 部分的报文),然后根据读取到的数据内容(如 URL、Cookie)做出负载均衡决策。也就是说,七层负载均衡器的核心是报文内容(如 URL、Cookie)层面的负载均衡,执行第七层负载均衡的设备通常被称为 **反向代理服务器** 。
+
+七层负载均衡比四层负载均衡会消耗更多的性能,不过,也相对更加灵活,能够更加智能地路由网络请求,比如说你可以根据请求的内容进行优化如缓存、压缩、加密。
+
+简单来说,**四层负载均衡性能更强,七层负载均衡功能更强!** 不过,对于绝大部分业务场景来说,四层负载均衡和七层负载均衡的性能差异基本可以忽略不计的。
+
+下面这段话摘自 Nginx 官网的 [What Is Layer 4 Load Balancing?](https://www.nginx.com/resources/glossary/layer-4-load-balancing/) 这篇文章。
+
+> Layer 4 load balancing was a popular architectural approach to traffic handling when commodity hardware was not as powerful as it is now, and the interaction between clients and application servers was much less complex. It requires less computation than more sophisticated load balancing methods (such as Layer 7), but CPU and memory are now sufficiently fast and cheap that the performance advantage for Layer 4 load balancing has become negligible or irrelevant in most situations.
+>
+> 第 4 层负载平衡是一种流行的流量处理体系结构方法,当时商用硬件没有现在这么强大,客户端和应用程序服务器之间的交互也不那么复杂。它比更复杂的负载平衡方法(如第 7 层)需要更少的计算量,但是 CPU 和内存现在足够快和便宜,在大多数情况下,第 4 层负载平衡的性能优势已经变得微不足道或无关紧要。
+
+在工作中,我们通常会使用 **Nginx** 来做七层负载均衡,LVS(Linux Virtual Server 虚拟服务器, Linux 内核的 4 层负载均衡)来做四层负载均衡。
+
+关于 Nginx 的常见知识点总结,[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html) 中「技术面试题篇」中已经有对应的内容了,感兴趣的小伙伴可以去看看。
+
+
+
+不过,LVS 这个绝大部分公司真用不上,像阿里、百度、腾讯、eBay 等大厂才会使用到,用的最多的还是 Nginx。
+
+### 客户端负载均衡
+
+**客户端负载均衡** 主要应用于系统内部的不同的服务之间,可以使用现成的负载均衡组件来实现。
+
+在客户端负载均衡中,客户端会自己维护一份服务器的地址列表,发送请求之前,客户端会根据对应的负载均衡算法来选择具体某一台服务器处理请求。
+
+客户端负载均衡器和服务运行在同一个进程或者说 Java 程序里,不存在额外的网络开销。不过,客户端负载均衡的实现会受到编程语言的限制,比如说 Spring Cloud Load Balancer 就只能用于 Java 语言。
+
+Java 领域主流的微服务框架 Dubbo、Spring Cloud 等都内置了开箱即用的客户端负载均衡实现。Dubbo 属于是默认自带了负载均衡功能,Spring Cloud 是通过组件的形式实现的负载均衡,属于可选项,比较常用的是 Spring Cloud Load Balancer(官方,推荐) 和 Ribbon(Netflix,已被启用)。
+
+下图是我画的一个简单的基于 Spring Cloud Load Balancer(Ribbon 也类似) 的客户端负载均衡示意图:
+
+
+
+## 负载均衡常见的算法有哪些?
+
+### 随机法
+
+**随机法** 是最简单粗暴的负载均衡算法。
+
+如果没有配置权重的话,所有的服务器被访问到的概率都是相同的。如果配置权重的话,权重越高的服务器被访问的概率就越大。
+
+未加权重的随机算法适合于服务器性能相近的集群,其中每个服务器承载相同的负载。加权随机算法适合于服务器性能不等的集群,权重的存在可以使请求分配更加合理化。
+
+不过,随机算法有一个比较明显的缺陷:部分机器在一段时间之内无法被随机到,毕竟是概率算法,就算是大家权重一样, 也可能会出现这种情况。
+
+于是,**轮询法** 来了!
+
+### 轮询法
+
+轮询法是挨个轮询服务器处理,也可以设置权重。
+
+如果没有配置权重的话,每个请求按时间顺序逐一分配到不同的服务器处理。如果配置权重的话,权重越高的服务器被访问的次数就越多。
+
+未加权重的轮询算法适合于服务器性能相近的集群,其中每个服务器承载相同的负载。加权轮询算法适合于服务器性能不等的集群,权重的存在可以使请求分配更加合理化。
+
+### 一致性 Hash 法
+
+相同参数的请求总是发到同一台服务器处理,比如同个 IP 的请求。
+
+### 最小连接法
+
+当有新的请求出现时,遍历服务器节点列表并选取其中活动连接数最小的一台服务器来响应当前请求。活动连接数可以理解为当前正在处理的请求数。
+
+最小连接法可以尽可能最大地使请求分配更加合理化,提高服务器的利用率。不过,这种方法实现起来也最复杂,需要监控每一台服务器处理的请求连接数。
+
+## 七层负载均衡可以怎么做?
+
+简单介绍两种项目中常用的七层负载均衡解决方案:DNS 解析和反向代理。
+
+除了我介绍的这两种解决方案之外,HTTP 重定向等手段也可以用来实现负载均衡,不过,相对来说,还是 DNS 解析和反向代理用的更多一些,也更推荐一些。
+
+### DNS 解析
+
+DNS 解析是比较早期的七层负载均衡实现方式,非常简单。
+
+DNS 解析实现负载均衡的原理是这样的:在 DNS 服务器中为同一个主机记录配置多个 IP 地址,这些 IP 地址对应不同的服务器。当用户请求域名的时候,DNS 服务器采用轮询算法返回 IP 地址,这样就实现了轮询版负载均衡。
+
+
+
+现在的 DNS 解析几乎都支持 IP 地址的权重配置,这样的话,在服务器性能不等的集群中请求分配会更加合理化。像我自己目前正在用的阿里云 DNS 就支持权重配置。
+
+
+
+### 反向代理
+
+客户端将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器,获取数据后再返回给客户端。对外暴露的是反向代理服务器地址,隐藏了真实服务器 IP 地址。反向代理“代理”的是目标服务器,这一个过程对于客户端而言是透明的。
+
+Nginx 就是最常用的反向代理服务器,它可以将接收到的客户端请求以一定的规则(负载均衡策略)均匀地分配到这个服务器集群中所有的服务器上。
+
+反向代理负载均衡同样属于七层负载均衡。
+
+
+
+## 客户端负载均衡通常是怎么做的?
+
+我们上面也说了,客户端负载均衡可以使用现成的负载均衡组件来实现。
+
+**Netflix Ribbon** 和 **Spring Cloud Load Balancer** 就是目前 Java 生态最流行的两个负载均衡组件。
+
+Ribbon 是老牌负载均衡组件,由 Netflix 开发,功能比较全面,支持的负载均衡策略也比较多。 Spring Cloud Load Balancer 是 Spring 官方为了取代 Ribbon 而推出的,功能相对更简单一些,支持的负载均衡也少一些。
+
+Ribbon 支持的 7 种负载均衡策略:
+
+- `RandomRule`:随机策略。
+- `RoundRobinRule`(默认):轮询策略
+- `WeightedResponseTimeRule`:权重(根据响应时间决定权重)策略
+- `BestAvailableRule`:最小连接数策略
+- `RetryRule`:重试策略(按照轮询策略来获取服务,如果获取的服务实例为 null 或已经失效,则在指定的时间之内不断地进行重试来获取服务,如果超过指定时间依然没获取到服务实例则返回 null)
+- `AvailabilityFilteringRule`:可用敏感性策略(先过滤掉非健康的服务实例,然后再选择连接数较小的服务实例)
+- `ZoneAvoidanceRule`:区域敏感性策略(根据服务所在区域的性能和服务的可用性来选择服务实例)
+
+Spring Cloud Load Balancer 支持的 2 种负载均衡策略:
+
+- `RandomLoadBalancer`:随机策略
+- `RoundRobinLoadBalancer`(默认):轮询策略
+
+```java
+public class CustomLoadBalancerConfiguration {
+
+ @Bean
+ ReactorLoadBalancer randomLoadBalancer(Environment environment,
+ LoadBalancerClientFactory loadBalancerClientFactory) {
+ String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
+ return new RandomLoadBalancer(loadBalancerClientFactory
+ .getLazyProvider(name, ServiceInstanceListSupplier.class),
+ name);
+ }
+}
+```
+
+不过,Spring Cloud Load Balancer 支持的负载均衡策略其实不止这两种,`ServiceInstanceListSupplier` 的实现类同样可以让其支持类似于 Ribbon 的负载均衡策略。这个应该是后续慢慢完善引入的,不看官方文档还真发现不了,所以说阅读官方文档真的很重要!
+
+这里举两个官方的例子:
+
+- `ZonePreferenceServiceInstanceListSupplier`:实现基于区域的负载平衡
+- `HintBasedServiceInstanceListSupplier`:实现基于 hint 提示的负载均衡
+
+```java
+public class CustomLoadBalancerConfiguration {
+ // 使用基于区域的负载平衡方法
+ @Bean
+ public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
+ ConfigurableApplicationContext context) {
+ return ServiceInstanceListSupplier.builder()
+ .withDiscoveryClient()
+ .withZonePreference()
+ .withCaching()
+ .build(context);
+ }
+}
+```
+
+关于 Spring Cloud Load Balancer 更详细更新的介绍,推荐大家看看官方文档: ,一切以官方文档为主。
+
+轮询策略基本可以满足绝大部分项目的需求,我们的实际项目中如果没有特殊需求的话,通常使用的就是默认的轮询策略。并且,Ribbon 和 Spring Cloud Load Balancer 都支持自定义负载均衡策略。
+
+个人建议如非必需 Ribbon 某个特有的功能或者负载均衡策略的话,就优先选择 Spring 官方提供的 Spring Cloud Load Balancer。
+
+最后再说说为什么我不太推荐使用 Ribbon 。
+
+Spring Cloud 2020.0.0 版本移除了 Netflix 除 Eureka 外的所有组件。Spring Cloud Hoxton.M2 是第一个支持 Spring Cloud Load Balancer 来替代 Netfix Ribbon 的版本。
+
+我们早期学习微服务,肯定接触过 Netflix 公司开源的 Feign、Ribbon、Zuul、Hystrix、Eureka 等知名的微服务系统构建所必须的组件,直到现在依然有非常非常多的公司在使用这些组件。不夸张地说,Netflix 公司引领了 Java 技术栈下的微服务发展。
+
+
+
+**那为什么 Spring Cloud 这么急着移除 Netflix 的组件呢?** 主要是因为在 2018 年的时候,Netflix 宣布其开源的核心组件 Hystrix、Ribbon、Zuul、Eureka 等进入维护状态,不再进行新特性开发,只修 BUG。于是,Spring 官方不得不考虑移除 Netflix 的组件。
+
+**Spring Cloud Alibaba** 是一个不错的选择,尤其是对于国内的公司和个人开发者来说。
+
+## 参考
+
+- 干货 | eBay 的 4 层软件负载均衡实现:
+- HTTP Load Balancing(Nginx 官方文档):
+- 深入浅出负载均衡 - vivo 互联网技术:
diff --git a/docs/high-performance/message-queue/disruptor-questions.md b/docs/high-performance/message-queue/disruptor-questions.md
new file mode 100644
index 0000000000000000000000000000000000000000..e8f011004912683487cbcae98bdae0b39b8ffa16
--- /dev/null
+++ b/docs/high-performance/message-queue/disruptor-questions.md
@@ -0,0 +1,138 @@
+---
+title: Disruptor常见问题总结
+category: 高性能
+tag:
+ - 消息队列
+---
+
+Disruptor 是一个相对冷门一些的知识点,不过,如果你的项目经历中用到了 Disruptor 的话,那面试中就很可能会被问到。
+
+一位球友之前投稿的面经(社招)中就涉及一些 Disruptor 的问题,文章传送门:[圆梦!顺利拿到字节、淘宝、拼多多等大厂 offer!](https://mp.weixin.qq.com/s/C5QMjwEb6pzXACqZsyqC4A) 。
+
+
+
+这篇文章可以看作是对 Disruptor 做的一个简单总结,每个问题都不会扯太深入,主要针对面试或者速览 Disruptor。
+
+## Disruptor 是什么?
+
+Disruptor 是一个开源的高性能内存队列,诞生初衷是为了解决内存队列的性能和内存安全问题,由英国外汇交易公司 LMAX 开发。
+
+根据 Disruptor 官方介绍,基于 Disruptor 开发的系统 LMAX(新的零售金融交易平台),单线程就能支撑每秒 600 万订单。Martin Fowler 在 2011 年写的一篇文章 [The LMAX Architecture](https://martinfowler.com/articles/lmax.html) 中专门介绍过这个 LMAX 系统的架构,感兴趣的可以看看这篇文章。。
+
+LMAX 公司 2010 年在 QCon 演讲后,Disruptor 获得了业界关注,并获得了 2011 年的 Oracle 官方的 Duke's Choice Awards(Duke 选择大奖)。
+
+
+
+> “Duke 选择大奖”旨在表彰过去一年里全球个人或公司开发的、最具影响力的 Java 技术应用,由甲骨文公司主办。含金量非常高!
+
+我专门找到了 Oracle 官方当年颁布获得 Duke's Choice Awards 项目的那篇文章(文章地址:https://blogs.oracle.com/java/post/and-the-winners-arethe-dukes-choice-award) 。从文中可以看出,同年获得此大奖荣誉的还有大名鼎鼎的 Netty、JRebel 等项目。
+
+
+
+Disruptor 提供的功能优点类似于 Kafka、RocketMQ 这类分布式队列,不过,其作为范围是 JVM(内存)。
+
+- Github 地址:
+- 官方教程:
+
+关于如何在 Spring Boot 项目中使用 Disruptor,可以看这篇文章:[Spring Boot + Disruptor 实战入门](https://mp.weixin.qq.com/s/0iG5brK3bYF0BgSjX4jRiA) 。
+
+## 为什么要用 Disruptor?
+
+Disruptor 主要解决了 JDK 内置线程安全队列的性能和内存安全问题。
+
+**JDK 中常见的线程安全的队列如下**:
+
+| 队列名字 | 锁 | 是否有界 |
+| ----------------------- | ----------------------- | -------- |
+| `ArrayBlockingQueue` | 加锁(`ReentrantLock`) | 有界 |
+| `LinkedBlockingQueue` | 加锁(`ReentrantLock`) | 有界 |
+| `LinkedTransferQueue` | 无锁(`CAS`) | 无界 |
+| `ConcurrentLinkedQueue` | 无锁(`CAS`) | 无界 |
+
+从上表中可以看出:这些队列要不就是加锁有界,要不就是无锁无界。而加锁的的队列势必会影响性能,无界的队列又存在内存溢出的风险。
+
+因此,一般情况下,我们都是不建议使用 JDK 内置线程安全队列。
+
+**Disruptor 就不一样了!它在无锁的情况下还能保证队列有界,并且还是线程安全的。**
+
+下面这张图是 Disruptor 官网提供的 Disruptor 和 ArrayBlockingQueue 的延迟直方图对比。
+
+
+
+Disruptor 真的很快,关于它为什么这么快这个问题,会在后文介绍到。
+
+此外,Disruptor 还提供了丰富的扩展功能比如支持批量操作、支持多种等待策略。
+
+## Kafka 和 Disruptor 什么区别?
+
+- **Kafka**:分布式消息队列,一般用在系统或者服务之间的消息传递,还可以被用作流式处理平台。
+- **Disruptor**:内存级别的消息队列,一般用在系统内部中线程间的消息传递。
+
+## 哪些组件用到了 Disruptor?
+
+用到 Disruptor 的开源项目还是挺多的,这里简单举几个例子:
+
+- **Log4j2**:Log4j2 是一款常用的日志框架,它基于 Disruptor 来实现异步日志。
+- **SOFATracer**:SOFATracer 是蚂蚁金服开源的分布式应用链路追踪工具,它基于 Disruptor 来实现异步日志。
+- **Storm** : Storm 是一个开源的分布式实时计算系统,它基于 Disruptor 来实现工作进程内发生的消息传递(同一 Storm 节点上的线程间,无需网络通信)。
+- **HBase**:HBase 是一个分布式列存储数据库系统,它基于 Disruptor 来提高写并发性能。
+- ......
+
+## Disruptor 核心概念有哪些?
+
+- **Event**:你可以把 Event 理解为存放在队列中等待消费的消息对象。
+- **EventFactory**:事件工厂用于生产事件,我们在初始化 `Disruptor` 类的时候需要用到。
+- **EventHandler**:Event 在对应的 Handler 中被处理,你可以将其理解为生产消费者模型中的消费者。
+- **EventProcessor**:EventProcessor 持有特定消费者(Consumer)的 Sequence,并提供用于调用事件处理实现的事件循环(Event Loop)。
+- **Disruptor**:事件的生产和消费需要用到 `Disruptor` 对象。
+- **RingBuffer**:RingBuffer(环形数组)用于保存事件。
+- **WaitStrategy**:等待策略。决定了没有事件可以消费的时候,事件消费者如何等待新事件的到来。
+- **Producer**:生产者,只是泛指调用 `Disruptor` 对象发布事件的用户代码,Disruptor 没有定义特定接口或类型。
+- **ProducerType**:指定是单个事件发布者模式还是多个事件发布者模式(发布者和生产者的意思类似,我个人比较喜欢用发布者)。
+- **Sequencer**:Sequencer 是 Disruptor 的真正核心。此接口有两个实现类 `SingleProducerSequencer`、`MultiProducerSequencer` ,它们定义在生产者和消费者之间快速、正确地传递数据的并发算法。
+
+下面这张图摘自 Disruptor 官网,展示了 LMAX 系统使用 Disruptor 的示例。
+
+
+
+## Disruptor 等待策略有哪些?
+
+**等待策略(WaitStrategy)** 决定了没有事件可以消费的时候,事件消费者如何等待新事件的到来。
+
+常见的等待策略有下面这些:
+
+
+
+- `BlockingWaitStrategy`:基于 `ReentrantLock`+`Condition` 来实现等待和唤醒操作,实现代码非常简单,是 Disruptor 默认的等待策略。虽然最慢,但也是 CPU 使用率最低和最稳定的选项生产环境推荐使用;
+- `BusySpinWaitStrategy`:性能很好,存在持续自旋的风险,使用不当会造成 CPU 负载 100%,慎用;
+- `LiteBlockingWaitStrategy`:基于 `BlockingWaitStrategy` 的轻量级等待策略,在没有锁竞争的时候会省去唤醒操作,但是作者说测试不充分,因此不建议使用;
+- `TimeoutBlockingWaitStrategy`:带超时的等待策略,超时后会执行业务指定的处理逻辑;
+- `LiteTimeoutBlockingWaitStrategy`:基于`TimeoutBlockingWaitStrategy`的策略,当没有锁竞争的时候会省去唤醒操作;
+- `SleepingWaitStrategy`:三段式策略,第一阶段自旋,第二阶段执行 Thread.yield 让出 CPU,第三阶段睡眠执行时间,反复的睡眠;
+- `YieldingWaitStrategy`:二段式策略,第一阶段自旋,第二阶段执行 Thread.yield 交出 CPU;
+- `PhasedBackoffWaitStrategy`:四段式策略,第一阶段自旋指定次数,第二阶段自旋指定时间,第三阶段执行 `Thread.yield` 交出 CPU,第四阶段调用成员变量的`waitFor`方法,该成员变量可以被设置为`BlockingWaitStrategy`、`LiteBlockingWaitStrategy`、`SleepingWaitStrategy`三个中的一个。
+
+## Disruptor 为什么这么快?
+
+- **RingBuffer(环形数组)** : Disruptor 内部的 RingBuffer 是通过数组实现的。由于这个数组中的所有元素在初始化时一次性全部创建,因此这些元素的内存地址一般来说是连续的。这样做的好处是,当生产者不断往 RingBuffer 中插入新的事件对象时,这些事件对象的内存地址就能够保持连续,从而利用 CPU 缓存的局部性原理,将相邻的事件对象一起加载到缓存中,提高程序的性能。这类似于 MySQL 的预读机制,将连续的几个页预读到内存里。除此之外,RingBuffer 基于数组还支持批量操作(一次处理多个元素)、还可以避免频繁的内存分配和垃圾回收(RingBuffer 是一个固定大小的数组,当向数组中添加新元素时,如果数组已满,则新元素将覆盖掉最旧的元素)。
+- **避免了伪共享问题**:CPU 缓存内部是按照 Cache Line(缓存行)管理的,一般的 Cache Line 大小在 64 字节左右。Disruptor 为了确保目标字段独占一个 Cache Line,会在目标字段前后增加了 64 个字节的填充(前 56 个字节和后 8 个字节),这样可以避免 Cache Line 的伪共享(False Sharing)问题。
+- **无锁设计**:Disruptor 采用无锁设计,避免了传统锁机制带来的竞争和延迟。Disruptor 的无锁实现起来比较复杂,主要是基于 CAS、内存屏障(Memory Barrier)、RingBuffer 等技术实现的。
+
+综上所述,Disruptor 之所以能够如此快,是基于一系列优化策略的综合作用,既充分利用了现代 CPU 缓存结构的特点,又避免了常见的并发问题和性能瓶颈。
+
+关于 Disruptor 高性能队列原理的详细介绍,可以查看这篇文章:[Disruptor 高性能队列原理浅析](https://qin.news/disruptor/) (参考了美团技术团队的[高性能队列——Disruptor](https://tech.meituan.com/2016/11/18/disruptor.html)这篇文章)。
+
+🌈 这里额外补充一点:**数组中对象元素地址连续为什么可以提高性能?**
+
+CPU 缓存是通过将最近使用的数据存储在高速缓存中来实现更快的读取速度,并使用预取机制提前加载相邻内存的数据以利用局部性原理。
+
+在计算机系统中,CPU 主要访问高速缓存和内存。高速缓存是一种速度非常快、容量相对较小的内存,通常被分为多级缓存,其中 L1、L2、L3 分别表示一级缓存、二级缓存、三级缓存。越靠近 CPU 的缓存,速度越快,容量也越小。相比之下,内存容量相对较大,但速度较慢。
+
+
+
+为了加速数据的读取过程,CPU 会先将数据从内存中加载到高速缓存中,如果下一次需要访问相同的数据,就可以直接从高速缓存中读取,而不需要再次访问内存。这就是所谓的 **缓存命中** 。另外,为了利用 **局部性原理** ,CPU 还会根据之前访问的内存地址预取相邻的内存数据,因为在程序中,连续的内存地址通常会被频繁访问到,这样做可以提高数据的缓存命中率,进而提高程序的性能。
+
+## 参考
+
+- Disruptor 高性能之道-等待策略:
+- 《Java 并发编程实战》- 40 | 案例分析(三):高性能队列 Disruptor:
diff --git "a/docs/high-performance/message-queue/kafka\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" b/docs/high-performance/message-queue/kafka-questions-01.md
similarity index 72%
rename from "docs/high-performance/message-queue/kafka\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md"
rename to docs/high-performance/message-queue/kafka-questions-01.md
index 8cea44f2225cb022225e2a86ccd69a5dcf178c8e..7f124abbca5994a12200ed79519c2fc0b160878f 100644
--- "a/docs/high-performance/message-queue/kafka\347\237\245\350\257\206\347\202\271&\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md"
+++ b/docs/high-performance/message-queue/kafka-questions-01.md
@@ -1,5 +1,9 @@
-
-# Kafka知识点&面试题总结
+---
+title: Kafka常见问题总结
+category: 高性能
+tag:
+ - 消息队列
+---
### Kafka 是什么?主要应用场景有哪些?
@@ -8,20 +12,20 @@ Kafka 是一个分布式流式处理平台。这到底是什么意思呢?
流平台具有三个关键功能:
1. **消息队列**:发布和订阅消息流,这个功能类似于消息队列,这也是 Kafka 也被归类为消息队列的原因。
-2. **容错的持久方式存储记录消息流**: Kafka 会把消息持久化到磁盘,有效避免了消息丢失的风险。
+2. **容错的持久方式存储记录消息流**:Kafka 会把消息持久化到磁盘,有效避免了消息丢失的风险。
3. **流式处理平台:** 在消息发布的时候进行处理,Kafka 提供了一个完整的流式处理类库。
Kafka 主要有两大应用场景:
-1. **消息队列** :建立实时流数据管道,以可靠地在系统或应用程序之间获取数据。
+1. **消息队列**:建立实时流数据管道,以可靠地在系统或应用程序之间获取数据。
2. **数据处理:** 构建实时的流数据处理程序来转换或处理数据流。
-### 和其他消息队列相比,Kafka的优势在哪里?
+### 和其他消息队列相比,Kafka 的优势在哪里?
我们现在经常提到 Kafka 的时候就已经默认它是一个非常优秀的消息队列了,我们也会经常拿它跟 RocketMQ、RabbitMQ 对比。我觉得 Kafka 相比其他消息队列主要的优势如下:
-1. **极致的性能** :基于 Scala 和 Java 语言开发,设计中大量使用了批量处理和异步的思想,最高可以每秒处理千万级别的消息。
-2. **生态系统兼容性无可匹敌** :Kafka 与周边生态系统的兼容性是最好的没有之一,尤其在大数据和流计算领域。
+1. **极致的性能**:基于 Scala 和 Java 语言开发,设计中大量使用了批量处理和异步的思想,最高可以每秒处理千万级别的消息。
+2. **生态系统兼容性无可匹敌**:Kafka 与周边生态系统的兼容性是最好的没有之一,尤其在大数据和流计算领域。
实际上在早期的时候 Kafka 并不是一个合格的消息队列,早期的 Kafka 在消息队列领域就像是一个衣衫褴褛的孩子一样,功能不完备并且有一些小问题比如丢失消息、不保证消息可靠性等等。当然,这也和 LinkedIn 最早开发 Kafka 用于处理海量的日志有很大关系,哈哈哈,人家本来最开始就不是为了作为消息队列滴,谁知道后面误打误撞在消息队列领域占据了一席之地。
@@ -47,21 +51,21 @@ Kafka 主要有两大应用场景:
发布-订阅模型主要是为了解决队列模型存在的问题。
-
+
发布订阅模型(Pub-Sub) 使用**主题(Topic)** 作为消息通信载体,类似于**广播模式**;发布者发布一条消息,该消息通过主题传递给所有的订阅者,**在一条消息广播之后才订阅的用户则是收不到该条消息的**。
**在发布 - 订阅模型中,如果只有一个订阅者,那它和队列模型就基本是一样的了。所以说,发布 - 订阅模型在功能层面上是可以兼容队列模型的。**
-**Kafka 采用的就是发布 - 订阅模型。**
+**Kafka 采用的就是发布 - 订阅模型。**
> **RocketMQ 的消息模型和 Kafka 基本是完全一样的。唯一的区别是 Kafka 中没有队列这个概念,与之对应的是 Partition(分区)。**
-### 什么是Producer、Consumer、Broker、Topic、Partition?
+### 什么是 Producer、Consumer、Broker、Topic、Partition?
Kafka 将生产者发布的消息发送到 **Topic(主题)** 中,需要这些消息的消费者可以订阅这些 **Topic(主题)**,如下图所示:
-
+
上面这张图也为我们引出了,Kafka 比较重要的几个概念:
@@ -91,8 +95,6 @@ Kafka 将生产者发布的消息发送到 **Topic(主题)** 中,需要这
> **要想搞懂 zookeeper 在 Kafka 中的作用 一定要自己搭建一个 Kafka 环境然后自己进 zookeeper 去看一下有哪些文件夹和 Kafka 有关,每个节点又保存了什么信息。** 一定不要光看不实践,这样学来的也终会忘记!这部分内容参考和借鉴了这篇文章:https://www.jianshu.com/p/a036405f989c 。
-
-
下图就是我的本地 Zookeeper ,它成功和我本地的 Kafka 关联上(以下文件夹结构借助 idea 插件 Zookeeper tool 实现)。
@@ -101,12 +103,12 @@ ZooKeeper 主要为 Kafka 提供元数据的管理的功能。
从图中我们可以看出,Zookeeper 主要为 Kafka 做了下面这些事情:
-1. **Broker 注册** :在 Zookeeper 上会有一个专门**用来进行 Broker 服务器列表记录**的节点。每个 Broker 在启动时,都会到 Zookeeper 上进行注册,即到 `/brokers/ids` 下创建属于自己的节点。每个 Broker 就会将自己的 IP 地址和端口等信息记录到该节点中去
-2. **Topic 注册** : 在 Kafka 中,同一个**Topic 的消息会被分成多个分区**并将其分布在多个 Broker 上,**这些分区信息及与 Broker 的对应关系**也都是由 Zookeeper 在维护。比如我创建了一个名字为 my-topic 的主题并且它有两个分区,对应到 zookeeper 中会创建这些文件夹:`/brokers/topics/my-topic/Partitions/0`、`/brokers/topics/my-topic/Partitions/1`
-3. **负载均衡** :上面也说过了 Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力。 对于同一个 Topic 的不同 Partition,Kafka 会尽力将这些 Partition 分布到不同的 Broker 服务器上。当生产者产生消息后也会尽量投递到不同 Broker 的 Partition 里面。当 Consumer 消费的时候,Zookeeper 可以根据当前的 Partition 数量以及 Consumer 数量来实现动态负载均衡。
+1. **Broker 注册**:在 Zookeeper 上会有一个专门**用来进行 Broker 服务器列表记录**的节点。每个 Broker 在启动时,都会到 Zookeeper 上进行注册,即到 `/brokers/ids` 下创建属于自己的节点。每个 Broker 就会将自己的 IP 地址和端口等信息记录到该节点中去
+2. **Topic 注册**:在 Kafka 中,同一个**Topic 的消息会被分成多个分区**并将其分布在多个 Broker 上,**这些分区信息及与 Broker 的对应关系**也都是由 Zookeeper 在维护。比如我创建了一个名字为 my-topic 的主题并且它有两个分区,对应到 zookeeper 中会创建这些文件夹:`/brokers/topics/my-topic/Partitions/0`、`/brokers/topics/my-topic/Partitions/1`
+3. **负载均衡**:上面也说过了 Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力。 对于同一个 Topic 的不同 Partition,Kafka 会尽力将这些 Partition 分布到不同的 Broker 服务器上。当生产者产生消息后也会尽量投递到不同 Broker 的 Partition 里面。当 Consumer 消费的时候,Zookeeper 可以根据当前的 Partition 数量以及 Consumer 数量来实现动态负载均衡。
4. ......
-### Kafka 如何保证消息的消费顺序?
+### Kafka 如何保证消息的消费顺序?
我们在使用消息队列的过程中经常有业务场景需要严格保证消息的消费顺序,比如我们同时发了 2 个消息,这 2 个消息对应的操作分别对应的数据库操作是:
@@ -138,11 +140,11 @@ Kafka 中发送 1 条消息的时候,可以指定 topic, partition, key,data
#### 生产者丢失消息的情况
-生产者(Producer) 调用`send`方法发送消息之后,消息可能因为网络问题并没有发送过去。
+生产者(Producer) 调用`send`方法发送消息之后,消息可能因为网络问题并没有发送过去。
-所以,我们不能默认在调用`send`方法发送消息之后消息发送成功了。为了确定消息是发送成功,我们要判断消息发送的结果。但是要注意的是 Kafka 生产者(Producer) 使用 `send` 方法发送消息实际上是异步的操作,我们可以通过 `get()`方法获取调用结果,但是这样也让它变为了同步操作,示例代码如下:
+所以,我们不能默认在调用`send`方法发送消息之后消息发送成功了。为了确定消息是发送成功,我们要判断消息发送的结果。但是要注意的是 Kafka 生产者(Producer) 使用 `send` 方法发送消息实际上是异步的操作,我们可以通过 `get()`方法获取调用结果,但是这样也让它变为了同步操作,示例代码如下:
-> **详细代码见我的这篇文章:[Kafka系列第三篇!10 分钟学会如何在 Spring Boot 程序中使用 Kafka 作为消息队列?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486269&idx=2&sn=ec00417ad641dd8c3d145d74cafa09ce&chksm=cea244f6f9d5cde0c8eb233fcc4cf82e11acd06446719a7af55230649863a3ddd95f78d111de&token=1633957262&lang=zh_CN#rd)**
+> **详细代码见我的这篇文章:[Kafka 系列第三篇!10 分钟学会如何在 Spring Boot 程序中使用 Kafka 作为消息队列?](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486269&idx=2&sn=ec00417ad641dd8c3d145d74cafa09ce&chksm=cea244f6f9d5cde0c8eb233fcc4cf82e11acd06446719a7af55230649863a3ddd95f78d111de&token=1633957262&lang=zh_CN#rd)**
```java
SendResult sendResult = kafkaTemplate.send(topic, o).get();
@@ -154,15 +156,15 @@ if (sendResult.getRecordMetadata() != null) {
但是一般不推荐这么做!可以采用为其添加回调函数的形式,示例代码如下:
-````java
+```java
ListenableFuture> future = kafkaTemplate.send(topic, o);
future.addCallback(result -> logger.info("生产者成功发送消息到topic:{} partition:{}的消息", result.getRecordMetadata().topic(), result.getRecordMetadata().partition()),
ex -> logger.error("生产者发送消失败,原因:{}", ex.getMessage()));
-````
+```
如果消息发送失败的话,我们检查失败的原因之后重新发送即可!
-**另外这里推荐为 Producer 的`retries `(重试次数)设置一个比较合理的值,一般是 3 ,但是为了保证消息不丢失的话一般会设置比较大一点。设置完成之后,当出现网络问题之后能够自动重试消息发送,避免消息丢失。另外,建议还要设置重试间隔,因为间隔太小的话重试的效果就不明显了,网络波动一次你3次一下子就重试完了**
+**另外这里推荐为 Producer 的`retries `(重试次数)设置一个比较合理的值,一般是 3 ,但是为了保证消息不丢失的话一般会设置比较大一点。设置完成之后,当出现网络问题之后能够自动重试消息发送,避免消息丢失。另外,建议还要设置重试间隔,因为间隔太小的话重试的效果就不明显了,网络波动一次你 3 次一下子就重试完了**
#### 消费者丢失消息的情况
@@ -176,15 +178,15 @@ if (sendResult.getRecordMetadata() != null) {
#### Kafka 弄丢了消息
- 我们知道 Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。生产者和消费者只与 leader 副本交互。你可以理解为其他副本只是 leader 副本的拷贝,它们的存在只是为了保证消息存储的安全性。
+我们知道 Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。生产者和消费者只与 leader 副本交互。你可以理解为其他副本只是 leader 副本的拷贝,它们的存在只是为了保证消息存储的安全性。
**试想一种情况:假如 leader 副本所在的 broker 突然挂掉,那么就要从 follower 副本重新选出一个 leader ,但是 leader 的数据还有一些没有被 follower 副本的同步的话,就会造成消息丢失。**
**设置 acks = all**
-解决办法就是我们设置 **acks = all**。acks 是 Kafka 生产者(Producer) 很重要的一个参数。
+解决办法就是我们设置 **acks = all**。acks 是 Kafka 生产者(Producer) 很重要的一个参数。
-acks 的默认值即为1,代表我们的消息被leader副本接收之后就算被成功发送。当我们配置 **acks = all** 代表则所有副本都要接收到该消息之后该消息才算真正成功被发送。
+acks 的默认值即为 1,代表我们的消息被 leader 副本接收之后就算被成功发送。当我们配置 **acks = all** 表示只有所有 ISR 列表的副本全部收到消息时,生产者才会接收到来自服务器的响应. 这种模式是最高级别的,也是最安全的,可以确保不止一个 Broker 接收到了消息. 该模式的延迟会很高.
**设置 replication.factor >= 3**
@@ -198,25 +200,25 @@ acks 的默认值即为1,代表我们的消息被leader副本接收之后就
**设置 unclean.leader.election.enable = false**
-> **Kafka 0.11.0.0版本开始 unclean.leader.election.enable 参数的默认值由原来的true 改为false**
+> **Kafka 0.11.0.0 版本开始 unclean.leader.election.enable 参数的默认值由原来的 true 改为 false**
-我们最开始也说了我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。多个 follower 副本之间的消息同步情况不一样,当我们配置了 **unclean.leader.election.enable = false** 的话,当 leader 副本发生故障时就不会从 follower 副本中和 leader 同步程度达不到要求的副本中选择出 leader ,这样降低了消息丢失的可能性。
+我们最开始也说了我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。多个 follower 副本之间的消息同步情况不一样,当我们配置了 **unclean.leader.election.enable = false** 的话,当 leader 副本发生故障时就不会从 follower 副本中和 leader 同步程度达不到要求的副本中选择出 leader ,这样降低了消息丢失的可能性。
### Kafka 如何保证消息不重复消费
-**kafka出现消息重复消费的原因:**
+**kafka 出现消息重复消费的原因:**
- 服务端侧已经消费的数据没有成功提交 offset(根本原因)。
- Kafka 侧 由于服务端处理业务时间长或者网络链接等等原因让 Kafka 认为服务假死,触发了分区 rebalance。
**解决方案:**
-- 消费消息服务做幂等校验,比如 Redis 的set、MySQL 的主键等天然的幂等功能。这种方法最有效。
-- 将 **`enable.auto.commit`** 参数设置为 false,关闭自动提交,开发者在代码中手动提交 offset。那么这里会有个问题:**什么时候提交offset合适?**
- * 处理完消息再提交:依旧有消息重复消费的风险,和自动提交一样
- * 拉取到消息即提交:会有消息丢失的风险。允许消息延时的场景,一般会采用这种方式。然后,通过定时任务在业务不繁忙(比如凌晨)的时候做数据兜底。
+- 消费消息服务做幂等校验,比如 Redis 的 set、MySQL 的主键等天然的幂等功能。这种方法最有效。
+- 将 **`enable.auto.commit`** 参数设置为 false,关闭自动提交,开发者在代码中手动提交 offset。那么这里会有个问题:**什么时候提交 offset 合适?**
+ - 处理完消息再提交:依旧有消息重复消费的风险,和自动提交一样
+ - 拉取到消息即提交:会有消息丢失的风险。允许消息延时的场景,一般会采用这种方式。然后,通过定时任务在业务不繁忙(比如凌晨)的时候做数据兜底。
### Reference
-- Kafka 官方文档: https://kafka.apache.org/documentation/
-- 极客时间—《Kafka核心技术与实战》第11节:无消息丢失配置怎么实现?
+- Kafka 官方文档:https://kafka.apache.org/documentation/
+- 极客时间—《Kafka 核心技术与实战》第 11 节:无消息丢失配置怎么实现?
diff --git a/docs/high-performance/message-queue/message-queue.md b/docs/high-performance/message-queue/message-queue.md
index 161bd5a021d9101e6b57ec26aeb42b1305b43006..4d7b6717ce9946d838127f56344e2c9b75018b63 100644
--- a/docs/high-performance/message-queue/message-queue.md
+++ b/docs/high-performance/message-queue/message-queue.md
@@ -1,46 +1,71 @@
-# 消息队列知识点&面试题总结
+---
+title: 消息队列基础知识总结
+category: 高性能
+tag:
+ - 消息队列
+---
-“RabbitMQ?”“Kafka?”“RocketMQ?”...在日常学习与开发过程中,我们常常听到消息队列这个关键词。我也在我的多篇文章中提到了这个概念。可能你是熟练使用消息队列的老手,又或者你是不懂消息队列的新手,不论你了不了解消息队列,本文都将带你搞懂消息队列的一些基本理论。如果你是老手,你可能从本文学到你之前不曾注意的一些关于消息队列的重要概念,如果你是新手,相信本文将是你打开消息队列大门的一板砖。
+::: tip
-## 一 什么是消息队列
+这篇文章中的消息队列主要指的是分布式消息队列。
-我们可以把消息队列看作是一个存放消息的容器,当我们需要使用消息的时候,直接从容器中取出消息供自己使用即可。
+:::
-
+“RabbitMQ?”“Kafka?”“RocketMQ?”...在日常学习与开发过程中,我们常常听到消息队列这个关键词。我也在我的多篇文章中提到了这个概念。可能你是熟练使用消息队列的老手,又或者你是不懂消息队列的新手,不论你了不了解消息队列,本文都将带你搞懂消息队列的一些基本理论。
-消息队列是分布式系统中重要的组件之一。使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。
+如果你是老手,你可能从本文学到你之前不曾注意的一些关于消息队列的重要概念,如果你是新手,相信本文将是你打开消息队列大门的一板砖。
-我们知道队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的。
+## 什么是消息队列?
-## 二 为什么要用消息队列
+我们可以把消息队列看作是一个存放消息的容器,当我们需要使用消息的时候,直接从容器中取出消息供自己使用即可。由于队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的。
+
+
+
+参与消息传递的双方称为 **生产者** 和 **消费者** ,生产者负责发送消息,消费者负责处理消息。
+
+
+
+我们知道操作系统中的进程通信的一种很重要的方式就是消息队列。我们这里提到的消息队列稍微有点区别,更多指的是各个服务以及系统内部各个组件/模块之前的通信,属于一种 **中间件** 。
+
+维基百科是这样介绍中间件的:
+
+> 中间件(英语:Middleware),又译中间件、中介层,是一类提供系统软件和应用软件之间连接、便于软件各部件之间的沟通的软件,应用软件可以借助中间件在不同的技术架构之间共享信息与资源。中间件位于客户机服务器的操作系统之上,管理着计算资源和网络通信。
+
+简单来说:**中间件就是一类为应用软件服务的软件,应用软件是为用户服务的,用户不会接触或者使用到中间件。**
+
+除了消息队列之外,常见的中间件还有 RPC 框架、分布式组件、HTTP 服务器、任务调度框架、配置中心、数据库层的分库分表工具和数据迁移工具等等。
+
+关于中间件比较详细的介绍可以参考阿里巴巴淘系技术的一篇回答:https://www.zhihu.com/question/19730582/answer/1663627873 。
+
+随着分布式和微服务系统的发展,消息队列在系统设计中有了更大的发挥空间,使用消息队列可以降低系统耦合性、实现任务异步、有效地进行流量削峰,是分布式和微服务系统中重要的组件之一。
+
+## 消息队列有什么用?
通常来说,使用消息队列能为我们的系统带来下面三点好处:
-1. **通过异步处理提高系统性能(减少响应所需时间)。**
+1. **通过异步处理提高系统性能(减少响应所需时间)**
2. **削峰/限流**
3. **降低系统耦合性。**
如果在面试的时候你被面试官问到这个问题的话,一般情况是你在你的简历上涉及到消息队列这方面的内容,这个时候推荐你结合你自己的项目来回答。
-《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。
+### 通过异步处理提高系统性能(减少响应所需时间)
-### 2.1 通过异步处理提高系统性能(减少响应所需时间)
-
-
+
将用户的请求数据存储到消息队列之后就立即返回结果。随后,系统再对消息进行消费。
因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此,**使用消息队列进行异步处理之后,需要适当修改业务流程进行配合**,比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。这就类似我们平时手机订火车票和电影票。
-### 2.2 削峰/限流
+### 削峰/限流
**先将短时间高并发产生的事务消息存储在消息队列中,然后后端服务再慢慢根据自己的能力去消费这些消息,这样就避免直接把后端服务打垮掉。**
举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:
-
+
-### 2.3 降低系统耦合性
+### 降低系统耦合性
使用消息队列还可以降低系统耦合性。我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。还是直接上图吧:
@@ -48,91 +73,216 @@
生产者(客户端)发送消息到消息队列中去,接受者(服务端)处理消息,需要消费的系统直接去消息队列取消息进行消费即可而不需要和其他系统有耦合,这显然也提高了系统的扩展性。
-**消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。
+**消息队列使用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。
消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。
另外,为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。
-**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的。除了发布-订阅模式,还有点对点订阅模式(一个消息只有一个消费者),我们比较常用的是发布-订阅模式。另外,这两种消息模型是 JMS 提供的,AMQP 协议还提供了 5 种消息模型。
+**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的。除了发布-订阅模式,还有点对点订阅模式(一个消息只有一个消费者),我们比较常用的是发布-订阅模式。另外,这两种消息模型是 JMS 提供的,AMQP 协议还提供了另外 5 种消息模型。
+
+### 实现分布式事务
+
+我们知道分布式事务的解决方案之一就是 MQ 事务。
+
+RocketMQ、 Kafka、Pulsar、QMQ 都提供了事务相关的功能。事务允许事件流应用将消费,处理,生产消息整个过程定义为一个原子操作。
+
+详细介绍可以查看 [分布式事务详解(付费)](https://javaguide.cn/distributed-system/distributed-transaction.html) 这篇文章。
+
+
-## 三 使用消息队列带来的一些问题
+## 使用消息队列会带来哪些问题?
- **系统可用性降低:** 系统可用性在某种程度上降低,为什么这样说呢?在加入 MQ 之前,你不用考虑消息丢失或者说 MQ 挂掉等等的情况,但是,引入 MQ 之后你就需要去考虑了!
- **系统复杂性提高:** 加入 MQ 之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
- **一致性问题:** 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!
-## 四 JMS VS AMQP
+## JMS 和 AMQP
-### 4.1 JMS
+### JMS 是什么?
-#### 4.1.1 JMS 简介
+JMS(JAVA Message Service,java 消息服务)是 Java 的消息服务,JMS 的客户端之间可以通过 JMS 服务进行异步的消息传输。**JMS(JAVA Message Service,Java 消息服务)API 是一个消息服务的标准或者说是规范**,允许应用程序组件基于 JavaEE 平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。
-JMS(JAVA Message Service,java 消息服务)是 java 的消息服务,JMS 的客户端之间可以通过 JMS 服务进行异步的消息传输。**JMS(JAVA Message Service,Java 消息服务)API 是一个消息服务的标准或者说是规范**,允许应用程序组件基于 JavaEE 平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。
+JMS 定义了五种不同的消息正文格式以及调用的消息类型,允许你发送并接收以一些不同形式的数据:
-**ActiveMQ 就是基于 JMS 规范实现的。**
+- `StreamMessage:Java` 原始值的数据流
+- `MapMessage`:一套名称-值对
+- `TextMessage`:一个字符串对象
+- `ObjectMessage`:一个序列化的 Java 对象
+- `BytesMessage`:一个字节的数据流
-#### 4.1.2 JMS 两种消息模型
+**ActiveMQ(已被淘汰) 就是基于 JMS 规范实现的。**
-**① 点到点(P2P)模型**
+### JMS 两种消息模型
-
+#### 点到点(P2P)模型
+
+
使用**队列(Queue)**作为消息通信载体;满足**生产者与消费者模式**,一条消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。比如:我们生产者发送 100 条消息的话,两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半(也就是你一个我一个的消费。)
-**② 发布/订阅(Pub/Sub)模型**
+#### 发布/订阅(Pub/Sub)模型
-
+
发布订阅模型(Pub/Sub) 使用**主题(Topic)**作为消息通信载体,类似于**广播模式**;发布者发布一条消息,该消息通过主题传递给所有的订阅者,**在一条消息广播之后才订阅的用户则是收不到该条消息的**。
-#### 4.1.3 JMS 五种不同的消息正文格式
-
-JMS 定义了五种不同的消息正文格式,以及调用的消息类型,允许你发送并接收以一些不同形式的数据,提供现有消息格式的一些级别的兼容性。
-
-- StreamMessage -- Java 原始值的数据流
-- MapMessage--一套名称-值对
-- TextMessage--一个字符串对象
-- ObjectMessage--一个序列化的 Java 对象
-- BytesMessage--一个字节的数据流
-
-### 4.2 AMQP
+### AMQP 是什么?
AMQP,即 Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 **高级消息队列协议**(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。
**RabbitMQ 就是基于 AMQP 协议实现的。**
-### 4.3 JMS vs AMQP
+### JMS vs AMQP
-| 对比方向 | JMS | AMQP |
-| :----------- | :-------------------------------------- | :----------------------------------------------------------- |
-| 定义 | Java API | 协议 |
-| 跨语言 | 否 | 是 |
-| 跨平台 | 否 | 是 |
+| 对比方向 | JMS | AMQP |
+| :----------: | :-------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 定义 | Java API | 协议 |
+| 跨语言 | 否 | 是 |
+| 跨平台 | 否 | 是 |
| 支持消息类型 | 提供两种消息模型:①Peer-2-Peer;②Pub/sub | 提供了五种消息模型:①direct exchange;②fanout exchange;③topic change;④headers exchange;⑤system exchange。本质来讲,后四种和 JMS 的 pub/sub 模型没有太大差别,仅是在路由机制上做了更详细的划分; |
-| 支持消息类型 | 支持多种消息类型 ,我们在上面提到过 | byte[](二进制) |
+| 支持消息类型 | 支持多种消息类型 ,我们在上面提到过 | byte[](二进制) |
**总结:**
- AMQP 为消息定义了线路层(wire-level protocol)的协议,而 JMS 所定义的是 API 规范。在 Java 体系中,多个 client 均可以通过 JMS 进行交互,不需要应用修改代码,但是其对跨平台的支持较差。而 AMQP 天然具有跨平台、跨语言特性。
-- JMS 支持 TextMessage、MapMessage 等复杂的消息类型;而 AMQP 仅支持 byte[] 消息类型(复杂的类型可序列化后发送)。
+- JMS 支持 `TextMessage`、`MapMessage` 等复杂的消息类型;而 AMQP 仅支持 `byte[]` 消息类型(复杂的类型可序列化后发送)。
- 由于 Exchange 提供的路由算法,AMQP 可以提供多样化的路由方式来传递消息到消息队列,而 JMS 仅支持 队列 和 主题/订阅 方式两种。
-## 五 常见的消息队列对比
+## RPC 和消息队列的区别
+
+RPC 和消息队列都是分布式微服务系统中重要的组件之一,下面我们来简单对比一下两者:
+
+- **从用途来看**:RPC 主要用来解决两个服务的远程通信问题,不需要了解底层网络的通信机制。通过 RPC 可以帮助我们调用远程计算机上某个服务的方法,这个过程就像调用本地方法一样简单。消息队列主要用来降低系统耦合性、实现任务异步、有效地进行流量削峰。
+- **从通信方式来看**:RPC 是双向直接网络通讯,消息队列是单向引入中间载体的网络通讯。
+- **从架构上来看**:消息队列需要把消息存储起来,RPC 则没有这个要求,因为前面也说了 RPC 是双向直接网络通讯。
+- **从请求处理的时效性来看**:通过 RPC 发出的调用一般会立即被处理,存放在消息队列中的消息并不一定会立即被处理。
+
+RPC 和消息队列本质上是网络通讯的两种不同的实现机制,两者的用途不同,万不可将两者混为一谈。
+
+## 分布式消息队列技术选型
+
+### 常见的消息队列有哪些?
+
+#### Kafka
+
+
+
+Kafka 是 LinkedIn 开源的一个分布式流式处理平台,已经成为 Apache 顶级项目,早期被用来用于处理海量的日志,后面才慢慢发展成了一款功能全面的高性能消息队列。
+
+流式处理平台具有三个关键功能:
+
+1. **消息队列**:发布和订阅消息流,这个功能类似于消息队列,这也是 Kafka 也被归类为消息队列的原因。
+2. **容错的持久方式存储记录消息流**:Kafka 会把消息持久化到磁盘,有效避免了消息丢失的风险。
+3. **流式处理平台:** 在消息发布的时候进行处理,Kafka 提供了一个完整的流式处理类库。
+
+Kafka 是一个分布式系统,由通过高性能 TCP 网络协议进行通信的服务器和客户端组成,可以部署在在本地和云环境中的裸机硬件、虚拟机和容器上。
+
+在 Kafka 2.8 之前,Kafka 最被大家诟病的就是其重度依赖于 Zookeeper 做元数据管理和集群的高可用。在 Kafka 2.8 之后,引入了基于 Raft 协议的 KRaft 模式,不再依赖 Zookeeper,大大简化了 Kafka 的架构,让你可以以一种轻量级的方式来使用 Kafka。
-| 对比方向 | 概要 |
-| -------- | ------------------------------------------------------------ |
-| 吞吐量 | 万级的 ActiveMQ 和 RabbitMQ 的吞吐量(ActiveMQ 的性能最差)要比 十万级甚至是百万级的 RocketMQ 和 Kafka 低一个数量级。 |
-| 可用性 | 都可以实现高可用。ActiveMQ 和 RabbitMQ 都是基于主从架构实现高可用性。RocketMQ 基于分布式架构。 kafka 也是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
-| 时效性 | RabbitMQ 基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。其他三个都是 ms 级。 |
-| 功能支持 | 除了 Kafka,其他三个功能都较为完备。 Kafka 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 |
-| 消息丢失 | ActiveMQ 和 RabbitMQ 丢失的可能性非常低, RocketMQ 和 Kafka 理论上不会丢失。 |
+不过,要提示一下:**如果要使用 KRaft 模式的话,建议选择较高版本的 Kafka,因为这个功能还在持续完善优化中。Kafka 3.3.1 版本是第一个将 KRaft(Kafka Raft)共识协议标记为生产就绪的版本。**
+
+
+
+Kafka 官网:http://kafka.apache.org/
+
+Kafka 更新记录(可以直观看到项目是否还在维护):https://kafka.apache.org/downloads
+
+#### RocketMQ
+
+
+
+RocketMQ 是阿里开源的一款云原生“消息、事件、流”实时数据处理平台,借鉴了 Kafka,已经成为 Apache 顶级项目。
+
+RocketMQ 的核心特性(摘自 RocketMQ 官网):
+
+- 云原生:生与云,长与云,无限弹性扩缩,K8s 友好
+- 高吞吐:万亿级吞吐保证,同时满足微服务与大数据场景。
+- 流处理:提供轻量、高扩展、高性能和丰富功能的流计算引擎。
+- 金融级:金融级的稳定性,广泛用于交易核心链路。
+- 架构极简:零外部依赖,Shared-nothing 架构。
+- 生态友好:无缝对接微服务、实时计算、数据湖等周边生态。
+
+根据官网介绍:
+
+> Apache RocketMQ 自诞生以来,因其架构简单、业务功能丰富、具备极强可扩展性等特点被众多企业开发者以及云厂商广泛采用。历经十余年的大规模场景打磨,RocketMQ 已经成为业内共识的金融级可靠业务消息首选方案,被广泛应用于互联网、大数据、移动互联网、物联网等领域的业务场景。
+
+RocketMQ 官网:https://rocketmq.apache.org/ (文档很详细,推荐阅读)
+
+RocketMQ 更新记录(可以直观看到项目是否还在维护):https://github.com/apache/rocketmq/releases
+
+#### RabbitMQ
+
+
+
+RabbitMQ 是采用 Erlang 语言实现 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的消息中间件,它最初起源于金融系统,用于在分布式系统中存储转发消息。
+
+RabbitMQ 发展到今天,被越来越多的人认可,这和它在易用性、扩展性、可靠性和高可用性等方面的卓著表现是分不开的。RabbitMQ 的具体特点可以概括为以下几点:
+
+- **可靠性:** RabbitMQ 使用一些机制来保证消息的可靠性,如持久化、传输确认及发布确认等。
+- **灵活的路由:** 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。这个后面会在我们讲 RabbitMQ 核心概念的时候详细介绍到。
+- **扩展性:** 多个 RabbitMQ 节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。
+- **高可用性:** 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。
+- **支持多种协议:** RabbitMQ 除了原生支持 AMQP 协议,还支持 STOMP、MQTT 等多种消息中间件协议。
+- **多语言客户端:** RabbitMQ 几乎支持所有常用语言,比如 Java、Python、Ruby、PHP、C#、JavaScript 等。
+- **易用的管理界面:** RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。在安装 RabbitMQ 的时候会介绍到,安装好 RabbitMQ 就自带管理界面。
+- **插件机制:** RabbitMQ 提供了许多插件,以实现从多方面进行扩展,当然也可以编写自己的插件。感觉这个有点类似 Dubbo 的 SPI 机制
+
+RabbitMQ 官网:https://www.rabbitmq.com/ 。
+
+RabbitMQ 更新记录(可以直观看到项目是否还在维护):https://www.rabbitmq.com/news.html
+
+#### Pulsar
+
+
+
+Pulsar 是下一代云原生分布式消息流平台,最初由 Yahoo 开发 ,已经成为 Apache 顶级项目。
+
+Pulsar 集消息、存储、轻量化函数式计算为一体,采用计算与存储分离架构设计,支持多租户、持久化存储、多机房跨区域数据复制,具有强一致性、高吞吐、低延时及高可扩展性等流数据存储特性,被看作是云原生时代实时消息流传输、存储和计算最佳解决方案。
+
+Pulsar 的关键特性如下(摘自官网):
+
+- 是下一代云原生分布式消息流平台。
+- Pulsar 的单个实例原生支持多个集群,可跨机房在集群间无缝地完成消息复制。
+- 极低的发布延迟和端到端延迟。
+- 可无缝扩展到超过一百万个 topic。
+- 简单的客户端 API,支持 Java、Go、Python 和 C++。
+- 主题的多种订阅模式(独占、共享和故障转移)。
+- 通过 Apache BookKeeper 提供的持久化消息存储机制保证消息传递 。
+- 由轻量级的 serverless 计算框架 Pulsar Functions 实现流原生的数据处理。
+- 基于 Pulsar Functions 的 serverless connector 框架 Pulsar IO 使得数据更易移入、移出 Apache Pulsar。
+- 分层式存储可在数据陈旧时,将数据从热存储卸载到冷/长期存储(如 S3、GCS)中。
+
+Pulsar 官网:https://pulsar.apache.org/
+
+Pulsar 更新记录(可以直观看到项目是否还在维护):https://github.com/apache/pulsar/releases
+
+#### ActiveMQ
+
+目前已经被淘汰,不推荐使用,不建议学习。
+
+### 如何选择?
+
+> 参考《Java 工程师面试突击第 1 季-中华石杉老师》
+
+| 对比方向 | 概要 |
+| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 吞吐量 | 万级的 ActiveMQ 和 RabbitMQ 的吞吐量(ActiveMQ 的性能最差)要比十万级甚至是百万级的 RocketMQ 和 Kafka 低一个数量级。 |
+| 可用性 | 都可以实现高可用。ActiveMQ 和 RabbitMQ 都是基于主从架构实现高可用性。RocketMQ 基于分布式架构。 Kafka 也是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
+| 时效性 | RabbitMQ 基于 Erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级,其他几个都是 ms 级。 |
+| 功能支持 | Pulsar 的功能更全面,支持多租户、多种消费模式和持久性模式等功能,是下一代云原生分布式消息流平台。 |
+| 消息丢失 | ActiveMQ 和 RabbitMQ 丢失的可能性非常低, Kafka、RocketMQ 和 Pulsar 理论上可以做到 0 丢失。 |
**总结:**
-- ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。
-- RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做 erlang 源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。
-- RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的 MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用 RocketMQ 挺好的
-- Kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。
+- ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用,已经被淘汰了。
+- RabbitMQ 在吞吐量方面虽然稍逊于 Kafka、RocketMQ 和 Pulsar,但是由于它基于 Erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 Erlang 开发,所以国内很少有公司有实力做 Erlang 源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这几种消息队列中,RabbitMQ 或许是你的首选。
+- RocketMQ 和 Pulsar 支持强一致性,对消息一致性要求比较高的场景可以使用。
+- RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的 MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。
+- Kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 Kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。Kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。
+
+## 参考
-参考:《Java 工程师面试突击第 1 季-中华石杉老师》
\ No newline at end of file
+- 《大型网站技术架构 》
+- KRaft: Apache Kafka Without ZooKeeper:https://developer.confluent.io/learn/kraft/
+- 消息队列的使用场景是什么样的?:https://mp.weixin.qq.com/s/4V1jI6RylJr7Jr9JsQe73A
diff --git a/docs/high-performance/message-queue/rabbitmq-intro.md b/docs/high-performance/message-queue/rabbitmq-intro.md
deleted file mode 100644
index d676114c8e69a3ccce00aa8a56033456cafa3b10..0000000000000000000000000000000000000000
--- a/docs/high-performance/message-queue/rabbitmq-intro.md
+++ /dev/null
@@ -1,304 +0,0 @@
-
-# RabbitMQ 入门总结
-
-## 一 RabbitMQ 介绍
-
-这部分参考了 《RabbitMQ实战指南》这本书的第 1 章和第 2 章。
-
-### 1.1 RabbitMQ 简介
-
-RabbitMQ 是采用 Erlang 语言实现 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的消息中间件,它最初起源于金融系统,用于在分布式系统中存储转发消息。
-
-RabbitMQ 发展到今天,被越来越多的人认可,这和它在易用性、扩展性、可靠性和高可用性等方面的卓著表现是分不开的。RabbitMQ 的具体特点可以概括为以下几点:
-
-- **可靠性:** RabbitMQ使用一些机制来保证消息的可靠性,如持久化、传输确认及发布确认等。
-- **灵活的路由:** 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。这个后面会在我们讲 RabbitMQ 核心概念的时候详细介绍到。
-- **扩展性:** 多个RabbitMQ节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。
-- **高可用性:** 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。
-- **支持多种协议:** RabbitMQ 除了原生支持 AMQP 协议,还支持 STOMP、MQTT 等多种消息中间件协议。
-- **多语言客户端:** RabbitMQ几乎支持所有常用语言,比如 Java、Python、Ruby、PHP、C#、JavaScript等。
-- **易用的管理界面:** RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。在安装 RabbitMQ 的时候会介绍到,安装好 RabbitMQ 就自带管理界面。
-- **插件机制:** RabbitMQ 提供了许多插件,以实现从多方面进行扩展,当然也可以编写自己的插件。感觉这个有点类似 Dubbo 的 SPI机制。
-
-### 1.2 RabbitMQ 核心概念
-
-RabbitMQ 整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。可以把消息传递的过程想象成:当你将一个包裹送到邮局,邮局会暂存并最终将邮件通过邮递员送到收件人的手上,RabbitMQ就好比由邮局、邮箱和邮递员组成的一个系统。从计算机术语层面来说,RabbitMQ 模型更像是一种交换机模型。
-
-下面再来看看图1—— RabbitMQ 的整体模型架构。
-
-
-
-下面我会一一介绍上图中的一些概念。
-
-#### 1.2.1 Producer(生产者) 和 Consumer(消费者)
-
-- **Producer(生产者)** :生产消息的一方(邮件投递者)
-- **Consumer(消费者)** :消费消息的一方(邮件收件人)
-
-消息一般由 2 部分组成:**消息头**(或者说是标签 Label)和 **消息体**。消息体也可以称为 payLoad ,消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。生产者把消息交由 RabbitMQ 后,RabbitMQ 会根据消息头把消息发送给感兴趣的 Consumer(消费者)。
-
-#### 1.2.2 Exchange(交换器)
-
-在 RabbitMQ 中,消息并不是直接被投递到 **Queue(消息队列)** 中的,中间还必须经过 **Exchange(交换器)** 这一层,**Exchange(交换器)** 会把我们的消息分配到对应的 **Queue(消息队列)** 中。
-
-**Exchange(交换器)** 用来接收生产者发送的消息并将这些消息路由给服务器中的队列中,如果路由不到,或许会返回给 **Producer(生产者)** ,或许会被直接丢弃掉 。这里可以将RabbitMQ中的交换器看作一个简单的实体。
-
-**RabbitMQ 的 Exchange(交换器) 有4种类型,不同的类型对应着不同的路由策略**:**direct(默认)**,**fanout**, **topic**, 和 **headers**,不同类型的Exchange转发消息的策略有所区别。这个会在介绍 **Exchange Types(交换器类型)** 的时候介绍到。
-
-Exchange(交换器) 示意图如下:
-
-
-
-生产者将消息发给交换器的时候,一般会指定一个 **RoutingKey(路由键)**,用来指定这个消息的路由规则,而这个 **RoutingKey 需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效**。
-
-RabbitMQ 中通过 **Binding(绑定)** 将 **Exchange(交换器)** 与 **Queue(消息队列)** 关联起来,在绑定的时候一般会指定一个 **BindingKey(绑定建)** ,这样 RabbitMQ 就知道如何正确将消息路由到队列了,如下图所示。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Exchange 和 Queue 的绑定可以是多对多的关系。
-
-Binding(绑定) 示意图:
-
-
-
-生产者将消息发送给交换器时,需要一个RoutingKey,当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许使用相同的 BindingKey。BindingKey 并不是在所有的情况下都生效,它依赖于交换器类型,比如fanout类型的交换器就会无视,而是将消息路由到所有绑定到该交换器的队列中。
-
-#### 1.2.3 Queue(消息队列)
-
-**Queue(消息队列)** 用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
-
-**RabbitMQ** 中消息只能存储在 **队列** 中,这一点和 **Kafka** 这种消息中间件相反。Kafka 将消息存储在 **topic(主题)** 这个逻辑层面,而相对应的队列逻辑只是topic实际存储文件中的位移标识。 RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。
-
-**多个消费者可以订阅同一个队列**,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免消息被重复消费。
-
-**RabbitMQ** 不支持队列层面的广播消费,如果有广播消费的需求,需要在其上进行二次开发,这样会很麻烦,不建议这样做。
-
-#### 1.2.4 Broker(消息中间件的服务节点)
-
-对于 RabbitMQ 来说,一个 RabbitMQ Broker 可以简单地看作一个 RabbitMQ 服务节点,或者RabbitMQ服务实例。大多数情况下也可以将一个 RabbitMQ Broker 看作一台 RabbitMQ 服务器。
-
-下图展示了生产者将消息存入 RabbitMQ Broker,以及消费者从Broker中消费数据的整个流程。
-
-
-
-这样图1中的一些关于 RabbitMQ 的基本概念我们就介绍完毕了,下面再来介绍一下 **Exchange Types(交换器类型)** 。
-
-#### 1.2.5 Exchange Types(交换器类型)
-
-RabbitMQ 常用的 Exchange Type 有 **fanout**、**direct**、**topic**、**headers** 这四种(AMQP规范里还提到两种 Exchange Type,分别为 system 与 自定义,这里不予以描述)。
-
-##### ① fanout
-
-fanout 类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,不需要做任何判断操作,所以 fanout 类型是所有的交换机类型里面速度最快的。fanout 类型常用来广播消息。
-
-##### ② direct
-
-direct 类型的Exchange路由规则也很简单,它会把消息路由到那些 Bindingkey 与 RoutingKey 完全匹配的 Queue 中。
-
-
-
-以上图为例,如果发送消息的时候设置路由键为“warning”,那么消息会路由到 Queue1 和 Queue2。如果在发送消息的时候设置路由键为"Info”或者"debug”,消息只会路由到Queue2。如果以其他的路由键发送消息,则消息不会路由到这两个队列中。
-
-direct 类型常用在处理有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以指派更多的资源去处理高优先级的队列。
-
-##### ③ topic
-
-前面讲到direct类型的交换器路由规则是完全匹配 BindingKey 和 RoutingKey ,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。topic类型的交换器在匹配规则上进行了扩展,它与 direct 类型的交换器相似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,但这里的匹配规则有些不同,它约定:
-
-- RoutingKey 为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如 “com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”;
-- BindingKey 和 RoutingKey 一样也是点号“.”分隔的字符串;
-- BindingKey 中可以存在两种特殊字符串“\*”和“#”,用于做模糊匹配,其中“\*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。
-
-
-
-以上图为例:
-
-- 路由键为 “com.rabbitmq.client” 的消息会同时路由到 Queue1 和 Queue2;
-- 路由键为 “com.hidden.client” 的消息只会路由到 Queue2 中;
-- 路由键为 “com.hidden.demo” 的消息只会路由到 Queue2 中;
-- 路由键为 “java.rabbitmq.demo” 的消息只会路由到 Queue1 中;
-- 路由键为 “java.util.concurrent” 的消息将会被丢弃或者返回给生产者(需要设置 mandatory 参数),因为它没有匹配任何路由键。
-
-##### ④ headers(不推荐)
-
-headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时指定一组键值对,当发送消息到交换器时,RabbitMQ会获取到该消息的 headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers 类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。
-
-## 二 安装 RabbitMQ
-
-通过 Docker 安装非常方便,只需要几条命令就好了,我这里是只说一下常规安装方法。
-
-前面提到了 RabbitMQ 是由 Erlang语言编写的,也正因如此,在安装RabbitMQ 之前需要安装 Erlang。
-
-注意:在安装 RabbitMQ 的时候需要注意 RabbitMQ 和 Erlang 的版本关系,如果不注意的话会导致出错,两者对应关系如下:
-
-
-
-### 2.1 安装 erlang
-
-**1 下载 erlang 安装包**
-
-在官网下载然后上传到 Linux 上或者直接使用下面的命令下载对应的版本。
-
-```shell
-[root@SnailClimb local]#wget https://erlang.org/download/otp_src_19.3.tar.gz
-```
-
-erlang 官网下载:[https://www.erlang.org/downloads](https://www.erlang.org/downloads)
-
- **2 解压 erlang 安装包**
-
-```shell
-[root@SnailClimb local]#tar -xvzf otp_src_19.3.tar.gz
-```
-
-**3 删除 erlang 安装包**
-
-```shell
-[root@SnailClimb local]#rm -rf otp_src_19.3.tar.gz
-```
-
-**4 安装 erlang 的依赖工具**
-
-```shell
-[root@SnailClimb local]#yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel unixODBC-devel
-```
-
-**5 进入erlang 安装包解压文件对 erlang 进行安装环境的配置**
-
-新建一个文件夹
-
-```shell
-[root@SnailClimb local]# mkdir erlang
-```
-
-对 erlang 进行安装环境的配置
-
-```shell
-[root@SnailClimb otp_src_19.3]#
-./configure --prefix=/usr/local/erlang --without-javac
-```
-
-**6 编译安装**
-
-```shell
-[root@SnailClimb otp_src_19.3]#
-make && make install
-```
-
-**7 验证一下 erlang 是否安装成功了**
-
-```shell
-[root@SnailClimb otp_src_19.3]# ./bin/erl
-```
-运行下面的语句输出“hello world”
-
-```erlang
- io:format("hello world~n", []).
-```
-
-
-大功告成,我们的 erlang 已经安装完成。
-
-**8 配置 erlang 环境变量**
-
-```shell
-[root@SnailClimb etc]# vim profile
-```
-
-追加下列环境变量到文件末尾
-
-```shell
-#erlang
-ERL_HOME=/usr/local/erlang
-PATH=$ERL_HOME/bin:$PATH
-export ERL_HOME PATH
-```
-
-运行下列命令使配置文件`profile`生效
-
-```shell
-[root@SnailClimb etc]# source /etc/profile
-```
-
-输入 erl 查看 erlang 环境变量是否配置正确
-
-```shell
-[root@SnailClimb etc]# erl
-```
-
-
-
-### 2.2 安装 RabbitMQ
-
-**1. 下载rpm**
-
-```shell
-wget https://www.rabbitmq.com/releases/rabbitmq-server/v3.6.8/rabbitmq-server-3.6.8-1.el7.noarch.rpm
-```
-或者直接在官网下载
-
-[https://www.rabbitmq.com/install-rpm.html](https://www.rabbitmq.com/install-rpm.html)
-
-**2. 安装rpm**
-
-```shell
-rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc
-```
-紧接着执行:
-
-```shell
-yum install rabbitmq-server-3.6.8-1.el7.noarch.rpm
-```
-中途需要你输入"y"才能继续安装。
-
-**3 开启 web 管理插件**
-
-```shell
-rabbitmq-plugins enable rabbitmq_management
-```
-
-**4 设置开机启动**
-
-```shell
-chkconfig rabbitmq-server on
-```
-
-**5. 启动服务**
-
-```shell
-service rabbitmq-server start
-```
-
-**6. 查看服务状态**
-
-```shell
-service rabbitmq-server status
-```
-
-**7. 访问 RabbitMQ 控制台**
-
-浏览器访问:http://你的ip地址:15672/
-
-默认用户名和密码:guest/guest; 但是需要注意的是:guest用户只是被容许从localhost访问。官网文档描述如下:
-
-```shell
-“guest” user can only connect via localhost
-```
-
-**解决远程访问 RabbitMQ 远程访问密码错误**
-
-新建用户并授权
-
-```shell
-[root@SnailClimb rabbitmq]# rabbitmqctl add_user root root
-Creating user "root" ...
-[root@SnailClimb rabbitmq]# rabbitmqctl set_user_tags root administrator
-
-Setting tags for user "root" to [administrator] ...
-[root@SnailClimb rabbitmq]#
-[root@SnailClimb rabbitmq]# rabbitmqctl set_permissions -p / root ".*" ".*" ".*"
-Setting permissions for user "root" in vhost "/" ...
-
-```
-
-再次访问:http://你的ip地址:15672/ ,输入用户名和密码:root root
-
-
-
-
diff --git a/docs/high-performance/message-queue/rabbitmq-questions.md b/docs/high-performance/message-queue/rabbitmq-questions.md
new file mode 100644
index 0000000000000000000000000000000000000000..0f0d9f8e519656ae025d3f451a96351ef99590c0
--- /dev/null
+++ b/docs/high-performance/message-queue/rabbitmq-questions.md
@@ -0,0 +1,245 @@
+---
+title: RabbitMQ常见问题总结
+category: 高性能
+tag:
+ - 消息队列
+head:
+ - - meta
+ - name: keywords
+ content: RabbitMQ,AMQP,Broker,Exchange,优先级队列,延迟队列
+ - - meta
+ - name: description
+ content: RabbitMQ 是一个在 AMQP(Advanced Message Queuing Protocol )基础上实现的,可复用的企业消息系统。它可以用于大型软件系统各个模块之间的高效通信,支持高并发,支持可扩展。它支持多种客户端如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX,持久化,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
+---
+
+> 本篇文章由 JavaGuide 收集自网络,原出处不明。
+
+## RabbitMQ 是什么?
+
+RabbitMQ 是一个在 AMQP(Advanced Message Queuing Protocol )基础上实现的,可复用的企业消息系统。它可以用于大型软件系统各个模块之间的高效通信,支持高并发,支持可扩展。它支持多种客户端如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP 等,支持 AJAX,持久化,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
+
+RabbitMQ 是使用 Erlang 编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP, STOMP,也正是如此,使的它变的非常重量级,更适合于企业级的开发。它同时实现了一个 Broker 构架,这意味着消息在发送给客户端时先在中心队列排队,对路由(Routing)、负载均衡(Load balance)或者数据持久化都有很好的支持。
+
+PS:也可能直接问什么是消息队列?消息队列就是一个使用队列来通信的组件。
+
+## RabbitMQ 特点?
+
+- **可靠性**: RabbitMQ 使用一些机制来保证可靠性, 如持久化、传输确认及发布确认等。
+- **灵活的路由** : 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能, RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个 交换器绑定在一起, 也可以通过插件机制来实现自己的交换器。
+- **扩展性**: 多个 RabbitMQ 节点可以组成一个集群,也可以根据实际业务情况动态地扩展 集群中节点。
+- **高可用性** : 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队 列仍然可用。
+- **多种协议**: RabbitMQ 除了原生支持 AMQP 协议,还支持 STOMP, MQTT 等多种消息 中间件协议。
+- **多语言客户端** :RabbitMQ 几乎支持所有常用语言,比如 Java、 Python、 Ruby、 PHP、 C#、 JavaScript 等。
+- **管理界面** : RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息、集 群中的节点等。
+- **插件机制** : RabbitMQ 提供了许多插件 , 以实现从多方面进行扩展,当然也可以编写自 己的插件。
+
+## RabbitMQ 核心概念?
+
+RabbitMQ 整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。可以把消息传递的过程想象成:当你将一个包裹送到邮局,邮局会暂存并最终将邮件通过邮递员送到收件人的手上,RabbitMQ 就好比由邮局、邮箱和邮递员组成的一个系统。从计算机术语层面来说,RabbitMQ 模型更像是一种交换机模型。
+
+RabbitMQ 的整体模型架构如下:
+
+
+
+下面我会一一介绍上图中的一些概念。
+
+### Producer(生产者) 和 Consumer(消费者)
+
+- **Producer(生产者)** :生产消息的一方(邮件投递者)
+- **Consumer(消费者)** :消费消息的一方(邮件收件人)
+
+消息一般由 2 部分组成:**消息头**(或者说是标签 Label)和 **消息体**。消息体也可以称为 payLoad ,消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。生产者把消息交由 RabbitMQ 后,RabbitMQ 会根据消息头把消息发送给感兴趣的 Consumer(消费者)。
+
+### Exchange(交换器)
+
+在 RabbitMQ 中,消息并不是直接被投递到 **Queue(消息队列)** 中的,中间还必须经过 **Exchange(交换器)** 这一层,**Exchange(交换器)** 会把我们的消息分配到对应的 **Queue(消息队列)** 中。
+
+**Exchange(交换器)** 用来接收生产者发送的消息并将这些消息路由给服务器中的队列中,如果路由不到,或许会返回给 **Producer(生产者)** ,或许会被直接丢弃掉 。这里可以将 RabbitMQ 中的交换器看作一个简单的实体。
+
+**RabbitMQ 的 Exchange(交换器) 有 4 种类型,不同的类型对应着不同的路由策略**:**direct(默认)**,**fanout**, **topic**, 和 **headers**,不同类型的 Exchange 转发消息的策略有所区别。这个会在介绍 **Exchange Types(交换器类型)** 的时候介绍到。
+
+Exchange(交换器) 示意图如下:
+
+
+
+生产者将消息发给交换器的时候,一般会指定一个 **RoutingKey(路由键)**,用来指定这个消息的路由规则,而这个 **RoutingKey 需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效**。
+
+RabbitMQ 中通过 **Binding(绑定)** 将 **Exchange(交换器)** 与 **Queue(消息队列)** 关联起来,在绑定的时候一般会指定一个 **BindingKey(绑定建)** ,这样 RabbitMQ 就知道如何正确将消息路由到队列了,如下图所示。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Exchange 和 Queue 的绑定可以是多对多的关系。
+
+Binding(绑定) 示意图:
+
+
+
+生产者将消息发送给交换器时,需要一个 RoutingKey,当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许使用相同的 BindingKey。BindingKey 并不是在所有的情况下都生效,它依赖于交换器类型,比如 fanout 类型的交换器就会无视,而是将消息路由到所有绑定到该交换器的队列中。
+
+### Queue(消息队列)
+
+**Queue(消息队列)** 用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
+
+**RabbitMQ** 中消息只能存储在 **队列** 中,这一点和 **Kafka** 这种消息中间件相反。Kafka 将消息存储在 **topic(主题)** 这个逻辑层面,而相对应的队列逻辑只是 topic 实际存储文件中的位移标识。 RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。
+
+**多个消费者可以订阅同一个队列**,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免消息被重复消费。
+
+**RabbitMQ** 不支持队列层面的广播消费,如果有广播消费的需求,需要在其上进行二次开发,这样会很麻烦,不建议这样做。
+
+### Broker(消息中间件的服务节点)
+
+对于 RabbitMQ 来说,一个 RabbitMQ Broker 可以简单地看作一个 RabbitMQ 服务节点,或者 RabbitMQ 服务实例。大多数情况下也可以将一个 RabbitMQ Broker 看作一台 RabbitMQ 服务器。
+
+下图展示了生产者将消息存入 RabbitMQ Broker,以及消费者从 Broker 中消费数据的整个流程。
+
+
+
+这样图 1 中的一些关于 RabbitMQ 的基本概念我们就介绍完毕了,下面再来介绍一下 **Exchange Types(交换器类型)** 。
+
+### Exchange Types(交换器类型)
+
+RabbitMQ 常用的 Exchange Type 有 **fanout**、**direct**、**topic**、**headers** 这四种(AMQP 规范里还提到两种 Exchange Type,分别为 system 与 自定义,这里不予以描述)。
+
+**1、fanout**
+
+fanout 类型的 Exchange 路由规则非常简单,它会把所有发送到该 Exchange 的消息路由到所有与它绑定的 Queue 中,不需要做任何判断操作,所以 fanout 类型是所有的交换机类型里面速度最快的。fanout 类型常用来广播消息。
+
+**2、direct**
+
+direct 类型的 Exchange 路由规则也很简单,它会把消息路由到那些 Bindingkey 与 RoutingKey 完全匹配的 Queue 中。
+
+
+
+以上图为例,如果发送消息的时候设置路由键为“warning”,那么消息会路由到 Queue1 和 Queue2。如果在发送消息的时候设置路由键为"Info”或者"debug”,消息只会路由到 Queue2。如果以其他的路由键发送消息,则消息不会路由到这两个队列中。
+
+direct 类型常用在处理有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以指派更多的资源去处理高优先级的队列。
+
+**3、topic**
+
+前面讲到 direct 类型的交换器路由规则是完全匹配 BindingKey 和 RoutingKey ,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。topic 类型的交换器在匹配规则上进行了扩展,它与 direct 类型的交换器相似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,但这里的匹配规则有些不同,它约定:
+
+- RoutingKey 为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如 “com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”;
+- BindingKey 和 RoutingKey 一样也是点号“.”分隔的字符串;
+- BindingKey 中可以存在两种特殊字符串“\*”和“#”,用于做模糊匹配,其中“\*”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。
+
+
+
+以上图为例:
+
+- 路由键为 “com.rabbitmq.client” 的消息会同时路由到 Queue1 和 Queue2;
+- 路由键为 “com.hidden.client” 的消息只会路由到 Queue2 中;
+- 路由键为 “com.hidden.demo” 的消息只会路由到 Queue2 中;
+- 路由键为 “java.rabbitmq.demo” 的消息只会路由到 Queue1 中;
+- 路由键为 “java.util.concurrent” 的消息将会被丢弃或者返回给生产者(需要设置 mandatory 参数),因为它没有匹配任何路由键。
+
+**4、headers(不推荐)**
+
+headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时指定一组键值对,当发送消息到交换器时,RabbitMQ 会获取到该消息的 headers(也是一个键值对的形式),对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers 类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。
+
+## AMQP 是什么?
+
+RabbitMQ 就是 AMQP 协议的 `Erlang` 的实现(当然 RabbitMQ 还支持 `STOMP2`、 `MQTT3` 等协议 ) AMQP 的模型架构 和 RabbitMQ 的模型架构是一样的,生产者将消息发送给交换器,交换器和队列绑定 。
+
+RabbitMQ 中的交换器、交换器类型、队列、绑定、路由键等都是遵循的 AMQP 协议中相 应的概念。目前 RabbitMQ 最新版本默认支持的是 AMQP 0-9-1。
+
+**AMQP 协议的三层**:
+
+- **Module Layer**:协议最高层,主要定义了一些客户端调用的命令,客户端可以用这些命令实现自己的业务逻辑。
+- **Session Layer**:中间层,主要负责客户端命令发送给服务器,再将服务端应答返回客户端,提供可靠性同步机制和错误处理。
+- **TransportLayer**:最底层,主要传输二进制数据流,提供帧的处理、信道服用、错误检测和数据表示等。
+
+**AMQP 模型的三大组件**:
+
+- **交换器 (Exchange)**:消息代理服务器中用于把消息路由到队列的组件。
+- **队列 (Queue)**:用来存储消息的数据结构,位于硬盘或内存中。
+- **绑定 (Binding)**:一套规则,告知交换器消息应该将消息投递给哪个队列。
+
+## **说说生产者 Producer 和消费者 Consumer?**
+
+**生产者** :
+
+- 消息生产者,就是投递消息的一方。
+- 消息一般包含两个部分:消息体(`payload`)和标签(`Label`)。
+
+**消费者**:
+
+- 消费消息,也就是接收消息的一方。
+- 消费者连接到 RabbitMQ 服务器,并订阅到队列上。消费消息时只消费消息体,丢弃标签。
+
+## 说说 Broker 服务节点、Queue 队列、Exchange 交换器?
+
+- **Broker**:可以看做 RabbitMQ 的服务节点。一般请下一个 Broker 可以看做一个 RabbitMQ 服务器。
+- **Queue** :RabbitMQ 的内部对象,用于存储消息。多个消费者可以订阅同一队列,这时队列中的消息会被平摊(轮询)给多个消费者进行处理。
+- **Exchange** : 生产者将消息发送到交换器,由交换器将消息路由到一个或者多个队列中。当路由不到时,或返回给生产者或直接丢弃。
+
+## 什么是死信队列?如何导致的?
+
+DLX,全称为 `Dead-Letter-Exchange`,死信交换器,死信邮箱。当消息在一个队列中变成死信 (`dead message`) 之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称之为死信队列。
+
+**导致的死信的几种原因**:
+
+- 消息被拒(`Basic.Reject /Basic.Nack`) 且 `requeue = false`。
+- 消息 TTL 过期。
+- 队列满了,无法再添加。
+
+## 什么是延迟队列?RabbitMQ 怎么实现延迟队列?
+
+延迟队列指的是存储对应的延迟消息,消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。
+
+RabbitMQ 本身是没有延迟队列的,要实现延迟消息,一般有两种方式:
+
+1. 通过 RabbitMQ 本身队列的特性来实现,需要使用 RabbitMQ 的死信交换机(Exchange)和消息的存活时间 TTL(Time To Live)。
+2. 在 RabbitMQ 3.5.7 及以上的版本提供了一个插件(rabbitmq-delayed-message-exchange)来实现延迟队列功能。同时,插件依赖 Erlang/OPT 18.0 及以上。
+
+也就是说,AMQP 协议以及 RabbitMQ 本身没有直接支持延迟队列的功能,但是可以通过 TTL 和 DLX 模拟出延迟队列的功能。
+
+## 什么是优先级队列?
+
+RabbitMQ 自 V3.5.0 有优先级队列实现,优先级高的队列会先被消费。
+
+可以通过`x-max-priority`参数来实现优先级队列。不过,当消费速度大于生产速度且 Broker 没有堆积的情况下,优先级显得没有意义。
+
+## RabbitMQ 有哪些工作模式?
+
+- 简单模式
+- work 工作模式
+- pub/sub 发布订阅模式
+- Routing 路由模式
+- Topic 主题模式
+
+## RabbitMQ 消息怎么传输?
+
+由于 TCP 链接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈,所以 RabbitMQ 使用信道的方式来传输数据。信道(Channel)是生产者、消费者与 RabbitMQ 通信的渠道,信道是建立在 TCP 链接上的虚拟链接,且每条 TCP 链接上的信道数量没有限制。就是说 RabbitMQ 在一条 TCP 链接上建立成百上千个信道来达到多个线程处理,这个 TCP 被多个线程共享,每个信道在 RabbitMQ 都有唯一的 ID,保证了信道私有性,每个信道对应一个线程使用。
+
+## **如何保证消息的可靠性?**
+
+消息到 MQ 的过程中搞丢,MQ 自己搞丢,MQ 到消费过程中搞丢。
+
+- 生产者到 RabbitMQ:事务机制和 Confirm 机制,注意:事务机制和 Confirm 机制是互斥的,两者不能共存,会导致 RabbitMQ 报错。
+- RabbitMQ 自身:持久化、集群、普通模式、镜像模式。
+- RabbitMQ 到消费者:basicAck 机制、死信队列、消息补偿机制。
+
+## 如何保证 RabbitMQ 消息的顺序性?
+
+- 拆分多个 queue(消息队列),每个 queue(消息队列) 一个 consumer(消费者),就是多一些 queue (消息队列)而已,确实是麻烦点;
+- 或者就一个 queue (消息队列)但是对应一个 consumer(消费者),然后这个 consumer(消费者)内部用内存队列做排队,然后分发给底层不同的 worker 来处理。
+
+## 如何保证 RabbitMQ 高可用的?
+
+RabbitMQ 是比较有代表性的,因为是基于主从(非分布式)做高可用性的,我们就以 RabbitMQ 为例子讲解第一种 MQ 的高可用性怎么实现。RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模式。
+
+**单机模式**
+
+Demo 级别的,一般就是你本地启动了玩玩儿的?,没人生产用单机模式。
+
+**普通集群模式**
+
+意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你创建的 queue,只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。
+
+你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个 queue 的读写操作。
+
+**镜像集群模式**
+
+这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,就是说,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。
+
+这样的好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据。
+
+## 如何解决消息队列的延时以及过期失效问题?
+
+RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是大量的数据会直接搞丢。我们可以采取一个方案,就是批量重导,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上 12 点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。
diff --git a/docs/high-performance/message-queue/rocketmq-intro.md b/docs/high-performance/message-queue/rocketmq-intro.md
deleted file mode 100644
index 2fbacfabec4896bb061612b1713f8d547e624e8d..0000000000000000000000000000000000000000
--- a/docs/high-performance/message-queue/rocketmq-intro.md
+++ /dev/null
@@ -1,456 +0,0 @@
-# RocketMQ入门总结
-
-> 文章很长,点赞再看,养成好习惯😋😋😋
->
-> [本文由 FrancisQ 老哥投稿!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485969&idx=1&sn=6bd53abde30d42a778d5a35ec104428c&chksm=cea245daf9d5cccce631f93115f0c2c4a7634e55f5bef9009fd03f5a0ffa55b745b5ef4f0530&token=294077121&lang=zh_CN#rd)
-
-## 消息队列扫盲
-
-消息队列顾名思义就是存放消息的队列,队列我就不解释了,别告诉我你连队列都不知道是啥吧?
-
-所以问题并不是消息队列是什么,而是 **消息队列为什么会出现?消息队列能用来干什么?用它来干这些事会带来什么好处?消息队列会带来副作用吗?**
-
-### 消息队列为什么会出现?
-
-消息队列算是作为后端程序员的一个必备技能吧,因为**分布式应用必定涉及到各个系统之间的通信问题**,这个时候消息队列也应运而生了。可以说分布式的产生是消息队列的基础,而分布式怕是一个很古老的概念了吧,所以消息队列也是一个很古老的中间件了。
-
-### 消息队列能用来干什么?
-
-#### 异步
-
-你可能会反驳我,应用之间的通信又不是只能由消息队列解决,好好的通信为什么中间非要插一个消息队列呢?我不能直接进行通信吗?
-
-很好👍,你又提出了一个概念,**同步通信**。就比如现在业界使用比较多的 `Dubbo` 就是一个适用于各个系统之间同步通信的 `RPC` 框架。
-
-我来举个🌰吧,比如我们有一个购票系统,需求是用户在购买完之后能接收到购买完成的短信。
-
-
-
-我们省略中间的网络通信时间消耗,假如购票系统处理需要 150ms ,短信系统处理需要 200ms ,那么整个处理流程的时间消耗就是 150ms + 200ms = 350ms。
-
-当然,乍看没什么问题。可是仔细一想你就感觉有点问题,我用户购票在购票系统的时候其实就已经完成了购买,而我现在通过同步调用非要让整个请求拉长时间,而短信系统这玩意又不是很有必要,它仅仅是一个辅助功能增强用户体验感而已。我现在整个调用流程就有点 **头重脚轻** 的感觉了,购票是一个不太耗时的流程,而我现在因为同步调用,非要等待发送短信这个比较耗时的操作才返回结果。那我如果再加一个发送邮件呢?
-
-
-
-这样整个系统的调用链又变长了,整个时间就变成了550ms。
-
-当我们在学生时代需要在食堂排队的时候,我们和食堂大妈就是一个同步的模型。
-
-我们需要告诉食堂大妈:“姐姐,给我加个鸡腿,再加个酸辣土豆丝,帮我浇点汁上去,多打点饭哦😋😋😋” 咦~~~ 为了多吃点,真恶心。
-
-然后大妈帮我们打饭配菜,我们看着大妈那颤抖的手和掉落的土豆丝不禁咽了咽口水。
-
-最终我们从大妈手中接过饭菜然后去寻找座位了...
-
-回想一下,我们在给大妈发送需要的信息之后我们是 **同步等待大妈给我配好饭菜** 的,上面我们只是加了鸡腿和土豆丝,万一我再加一个番茄牛腩,韭菜鸡蛋,这样是不是大妈打饭配菜的流程就会变长,我们等待的时间也会相应的变长。
-
-
-
-那后来,我们工作赚钱了有钱去饭店吃饭了,我们告诉服务员来一碗牛肉面加个荷包蛋 **(传达一个消息)** ,然后我们就可以在饭桌上安心的玩手机了 **(干自己其他事情)** ,等到我们的牛肉面上了我们就可以吃了。这其中我们也就传达了一个消息,然后我们又转过头干其他事情了。这其中虽然做面的时间没有变短,但是我们只需要传达一个消息就可以干其他事情了,这是一个 **异步** 的概念。
-
-所以,为了解决这一个问题,聪明的程序员在中间也加了个类似于服务员的中间件——消息队列。这个时候我们就可以把模型给改造了。
-
-
-
-这样,我们在将消息存入消息队列之后我们就可以直接返回了(我们告诉服务员我们要吃什么然后玩手机),所以整个耗时只是 150ms + 10ms = 160ms。
-
-> 但是你需要注意的是,整个流程的时长是没变的,就像你仅仅告诉服务员要吃什么是不会影响到做面的速度的。
-
-#### 解耦
-
-回到最初同步调用的过程,我们写个伪代码简单概括一下。
-
-
-
-那么第二步,我们又添加了一个发送邮件,我们就得重新去修改代码,如果我们又加一个需求:用户购买完还需要给他加积分,这个时候我们是不是又得改代码?
-
-
-
-如果你觉得还行,那么我这个时候不要发邮件这个服务了呢,我是不是又得改代码,又得重启应用?
-
-
-
-这样改来改去是不是很麻烦,那么 **此时我们就用一个消息队列在中间进行解耦** 。你需要注意的是,我们后面的发送短信、发送邮件、添加积分等一些操作都依赖于上面的 `result` ,这东西抽象出来就是购票的处理结果呀,比如订单号,用户账号等等,也就是说我们后面的一系列服务都是需要同样的消息来进行处理。既然这样,我们是不是可以通过 **“广播消息”** 来实现。
-
-我上面所讲的“广播”并不是真正的广播,而是接下来的系统作为消费者去 **订阅** 特定的主题。比如我们这里的主题就可以叫做 `订票` ,我们购买系统作为一个生产者去生产这条消息放入消息队列,然后消费者订阅了这个主题,会从消息队列中拉取消息并消费。就比如我们刚刚画的那张图,你会发现,在生产者这边我们只需要关注 **生产消息到指定主题中** ,而 **消费者只需要关注从指定主题中拉取消息** 就行了。
-
-
-
-> 如果没有消息队列,每当一个新的业务接入,我们都要在主系统调用新接口、或者当我们取消某些业务,我们也得在主系统删除某些接口调用。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,接下来收到消息如何处理,是下游的事情,无疑极大地减少了开发和联调的工作量。
-
-#### 削峰
-
-我们再次回到一开始我们使用同步调用系统的情况,并且思考一下,如果此时有大量用户请求购票整个系统会变成什么样?
-
-
-
-如果,此时有一万的请求进入购票系统,我们知道运行我们主业务的服务器配置一般会比较好,所以这里我们假设购票系统能承受这一万的用户请求,那么也就意味着我们同时也会出现一万调用发短信服务的请求。而对于短信系统来说并不是我们的主要业务,所以我们配备的硬件资源并不会太高,那么你觉得现在这个短信系统能承受这一万的峰值么,且不说能不能承受,系统会不会 **直接崩溃** 了?
-
-短信业务又不是我们的主业务,我们能不能 **折中处理** 呢?如果我们把购买完成的信息发送到消息队列中,而短信系统 **尽自己所能地去消息队列中取消息和消费消息** ,即使处理速度慢一点也无所谓,只要我们的系统没有崩溃就行了。
-
-留得江山在,还怕没柴烧?你敢说每次发送验证码的时候是一发你就收到了的么?
-
-#### 消息队列能带来什么好处?
-
-其实上面我已经说了。**异步、解耦、削峰。** 哪怕你上面的都没看懂也千万要记住这六个字,因为他不仅是消息队列的精华,更是编程和架构的精华。
-
-#### 消息队列会带来副作用吗?
-
-没有哪一门技术是“银弹”,消息队列也有它的副作用。
-
-比如,本来好好的两个系统之间的调用,我中间加了个消息队列,如果消息队列挂了怎么办呢?是不是 **降低了系统的可用性** ?
-
-那这样是不是要保证HA(高可用)?是不是要搞集群?那么我 **整个系统的复杂度是不是上升了** ?
-
-抛开上面的问题不讲,万一我发送方发送失败了,然后执行重试,这样就可能产生重复的消息。
-
-或者我消费端处理失败了,请求重发,这样也会产生重复的消息。
-
-对于一些微服务来说,消费重复消息会带来更大的麻烦,比如增加积分,这个时候我加了多次是不是对其他用户不公平?
-
-那么,又 **如何解决重复消费消息的问题** 呢?
-
-如果我们此时的消息需要保证严格的顺序性怎么办呢?比如生产者生产了一系列的有序消息(对一个id为1的记录进行删除增加修改),但是我们知道在发布订阅模型中,对于主题是无顺序的,那么这个时候就会导致对于消费者消费消息的时候没有按照生产者的发送顺序消费,比如这个时候我们消费的顺序为修改删除增加,如果该记录涉及到金额的话是不是会出大事情?
-
-那么,又 **如何解决消息的顺序消费问题** 呢?
-
-就拿我们上面所讲的分布式系统来说,用户购票完成之后是不是需要增加账户积分?在同一个系统中我们一般会使用事务来进行解决,如果用 `Spring` 的话我们在上面伪代码中加入 `@Transactional` 注解就好了。但是在不同系统中如何保证事务呢?总不能这个系统我扣钱成功了你那积分系统积分没加吧?或者说我这扣钱明明失败了,你那积分系统给我加了积分。
-
-那么,又如何 **解决分布式事务问题** 呢?
-
-我们刚刚说了,消息队列可以进行削峰操作,那如果我的消费者如果消费很慢或者生产者生产消息很快,这样是不是会将消息堆积在消息队列中?
-
-那么,又如何 **解决消息堆积的问题** 呢?
-
-可用性降低,复杂度上升,又带来一系列的重复消费,顺序消费,分布式事务,消息堆积的问题,这消息队列还怎么用啊😵?
-
-
-
-别急,办法总是有的。
-
-## RocketMQ是什么?
-
-
-
-哇,你个混蛋!上面给我抛出那么多问题,你现在又讲 `RocketMQ` ,还让不让人活了?!🤬
-
-别急别急,话说你现在清楚 `MQ` 的构造吗,我还没讲呢,我们先搞明白 `MQ` 的内部构造,再来看看如何解决上面的一系列问题吧,不过你最好带着问题去阅读和了解喔。
-
-`RocketMQ` 是一个 **队列模型** 的消息中间件,具有**高性能、高可靠、高实时、分布式** 的特点。它是一个采用 `Java` 语言开发的分布式的消息系统,由阿里巴巴团队开发,在2016年底贡献给 `Apache`,成为了 `Apache` 的一个顶级项目。 在阿里内部,`RocketMQ` 很好地服务了集团大大小小上千个应用,在每年的双十一当天,更有不可思议的万亿级消息通过 `RocketMQ` 流转。
-
-废话不多说,想要了解 `RocketMQ` 历史的同学可以自己去搜寻资料。听完上面的介绍,你只要知道 `RocketMQ` 很快、很牛、而且经历过双十一的实践就行了!
-
-## 队列模型和主题模型
-
-在谈 `RocketMQ` 的技术架构之前,我们先来了解一下两个名词概念——**队列模型** 和 **主题模型** 。
-
-首先我问一个问题,消息队列为什么要叫消息队列?
-
-你可能觉得很弱智,这玩意不就是存放消息的队列嘛?不叫消息队列叫什么?
-
-的确,早期的消息中间件是通过 **队列** 这一模型来实现的,可能是历史原因,我们都习惯把消息中间件成为消息队列。
-
-但是,如今例如 `RocketMQ` 、`Kafka` 这些优秀的消息中间件不仅仅是通过一个 **队列** 来实现消息存储的。
-
-### 队列模型
-
-就像我们理解队列一样,消息中间件的队列模型就真的只是一个队列。。。我画一张图给大家理解。
-
-
-
-在一开始我跟你提到了一个 **“广播”** 的概念,也就是说如果我们此时我们需要将一个消息发送给多个消费者(比如此时我需要将信息发送给短信系统和邮件系统),这个时候单个队列即不能满足需求了。
-
-当然你可以让 `Producer` 生产消息放入多个队列中,然后每个队列去对应每一个消费者。问题是可以解决,创建多个队列并且复制多份消息是会很影响资源和性能的。而且,这样子就会导致生产者需要知道具体消费者个数然后去复制对应数量的消息队列,这就违背我们消息中间件的 **解耦** 这一原则。
-
-### 主题模型
-
-那么有没有好的方法去解决这一个问题呢?有,那就是 **主题模型** 或者可以称为 **发布订阅模型** 。
-
-> 感兴趣的同学可以去了解一下设计模式里面的观察者模式并且手动实现一下,我相信你会有所收获的。
-
-在主题模型中,消息的生产者称为 **发布者(Publisher)** ,消息的消费者称为 **订阅者(Subscriber)** ,存放消息的容器称为 **主题(Topic)** 。
-
-其中,发布者将消息发送到指定主题中,订阅者需要 **提前订阅主题** 才能接受特定主题的消息。
-
-
-
-### RocketMQ中的消息模型
-
-`RocketMQ` 中的消息模型就是按照 **主题模型** 所实现的。你可能会好奇这个 **主题** 到底是怎么实现的呢?你上面也没有讲到呀!
-
-其实对于主题模型的实现来说每个消息中间件的底层设计都是不一样的,就比如 `Kafka` 中的 **分区** ,`RocketMQ` 中的 **队列** ,`RabbitMQ` 中的 `Exchange` 。我们可以理解为 **主题模型/发布订阅模型** 就是一个标准,那些中间件只不过照着这个标准去实现而已。
-
-所以,`RocketMQ` 中的 **主题模型** 到底是如何实现的呢?首先我画一张图,大家尝试着去理解一下。
-
-
-
-我们可以看到在整个图中有 `Producer Group` 、`Topic` 、`Consumer Group` 三个角色,我来分别介绍一下他们。
-
-- `Producer Group` 生产者组: 代表某一类的生产者,比如我们有多个秒杀系统作为生产者,这多个合在一起就是一个 `Producer Group` 生产者组,它们一般生产相同的消息。
-- `Consumer Group` 消费者组: 代表某一类的消费者,比如我们有多个短信系统作为消费者,这多个合在一起就是一个 `Consumer Group` 消费者组,它们一般消费相同的消息。
-- `Topic` 主题: 代表一类消息,比如订单消息,物流消息等等。
-
-你可以看到图中生产者组中的生产者会向主题发送消息,而 **主题中存在多个队列**,生产者每次生产消息之后是指定主题中的某个队列发送消息的。
-
-每个主题中都有多个队列(分布在不同的 `Broker`中,如果是集群的话,`Broker`又分布在不同的服务器中),集群消费模式下,一个消费者集群多台机器共同消费一个 `topic` 的多个队列,**一个队列只会被一个消费者消费**。如果某个消费者挂掉,分组内其它消费者会接替挂掉的消费者继续消费。就像上图中 `Consumer1` 和 `Consumer2` 分别对应着两个队列,而 `Consumer3` 是没有队列对应的,所以一般来讲要控制 **消费者组中的消费者个数和主题中队列个数相同** 。
-
-当然也可以消费者个数小于队列个数,只不过不太建议。如下图。
-
-
-
-**每个消费组在每个队列上维护一个消费位置** ,为什么呢?
-
-因为我们刚刚画的仅仅是一个消费者组,我们知道在发布订阅模式中一般会涉及到多个消费者组,而每个消费者组在每个队列中的消费位置都是不同的。如果此时有多个消费者组,那么消息被一个消费者组消费完之后是不会删除的(因为其它消费者组也需要呀),它仅仅是为每个消费者组维护一个 **消费位移(offset)** ,每次消费者组消费完会返回一个成功的响应,然后队列再把维护的消费位移加一,这样就不会出现刚刚消费过的消息再一次被消费了。
-
-
-
-可能你还有一个问题,**为什么一个主题中需要维护多个队列** ?
-
-答案是 **提高并发能力** 。的确,每个主题中只存在一个队列也是可行的。你想一下,如果每个主题中只存在一个队列,这个队列中也维护着每个消费者组的消费位置,这样也可以做到 **发布订阅模式** 。如下图。
-
-
-
-但是,这样我生产者是不是只能向一个队列发送消息?又因为需要维护消费位置所以一个队列只能对应一个消费者组中的消费者,这样是不是其他的 `Consumer` 就没有用武之地了?从这两个角度来讲,并发度一下子就小了很多。
-
-所以总结来说,`RocketMQ` 通过**使用在一个 `Topic` 中配置多个队列并且每个队列维护每个消费者组的消费位置** 实现了 **主题模式/发布订阅模式** 。
-
-## RocketMQ的架构图
-
-讲完了消息模型,我们理解起 `RocketMQ` 的技术架构起来就容易多了。
-
-`RocketMQ` 技术架构中有四大角色 `NameServer` 、`Broker` 、`Producer` 、`Consumer` 。我来向大家分别解释一下这四个角色是干啥的。
-
-- `Broker`: 主要负责消息的存储、投递和查询以及服务高可用保证。说白了就是消息队列服务器嘛,生产者生产消息到 `Broker` ,消费者从 `Broker` 拉取消息并消费。
-
- 这里,我还得普及一下关于 `Broker` 、`Topic` 和 队列的关系。上面我讲解了 `Topic` 和队列的关系——一个 `Topic` 中存在多个队列,那么这个 `Topic` 和队列存放在哪呢?
-
- **一个 `Topic` 分布在多个 `Broker`上,一个 `Broker` 可以配置多个 `Topic` ,它们是多对多的关系**。
-
- 如果某个 `Topic` 消息量很大,应该给它多配置几个队列(上文中提到了提高并发能力),并且 **尽量多分布在不同 `Broker` 上,以减轻某个 `Broker` 的压力** 。
-
- `Topic` 消息量都比较均匀的情况下,如果某个 `broker` 上的队列越多,则该 `broker` 压力越大。
-
- 
-
- > 所以说我们需要配置多个Broker。
-
-- `NameServer`: 不知道你们有没有接触过 `ZooKeeper` 和 `Spring Cloud` 中的 `Eureka` ,它其实也是一个 **注册中心** ,主要提供两个功能:**Broker管理** 和 **路由信息管理** 。说白了就是 `Broker` 会将自己的信息注册到 `NameServer` 中,此时 `NameServer` 就存放了很多 `Broker` 的信息(Broker的路由表),消费者和生产者就从 `NameServer` 中获取路由表然后照着路由表的信息和对应的 `Broker` 进行通信(生产者和消费者定期会向 `NameServer` 去查询相关的 `Broker` 的信息)。
-
-- `Producer`: 消息发布的角色,支持分布式集群方式部署。说白了就是生产者。
-
-- `Consumer`: 消息消费的角色,支持分布式集群方式部署。支持以push推,pull拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制。说白了就是消费者。
-
-听完了上面的解释你可能会觉得,这玩意好简单。不就是这样的么?
-
-
-
-嗯?你可能会发现一个问题,这老家伙 `NameServer` 干啥用的,这不多余吗?直接 `Producer` 、`Consumer` 和 `Broker` 直接进行生产消息,消费消息不就好了么?
-
-但是,我们上文提到过 `Broker` 是需要保证高可用的,如果整个系统仅仅靠着一个 `Broker` 来维持的话,那么这个 `Broker` 的压力会不会很大?所以我们需要使用多个 `Broker` 来保证 **负载均衡** 。
-
-如果说,我们的消费者和生产者直接和多个 `Broker` 相连,那么当 `Broker` 修改的时候必定会牵连着每个生产者和消费者,这样就会产生耦合问题,而 `NameServer` 注册中心就是用来解决这个问题的。
-
-> 如果还不是很理解的话,可以去看我介绍 `Spring Cloud` 的那篇文章,其中介绍了 `Eureka` 注册中心。
-
-当然,`RocketMQ` 中的技术架构肯定不止前面那么简单,因为上面图中的四个角色都是需要做集群的。我给出一张官网的架构图,大家尝试理解一下。
-
-
-
-其实和我们最开始画的那张乞丐版的架构图也没什么区别,主要是一些细节上的差别。听我细细道来🤨。
-
-第一、我们的 `Broker` **做了集群并且还进行了主从部署** ,由于消息分布在各个 `Broker` 上,一旦某个 `Broker` 宕机,则该`Broker` 上的消息读写都会受到影响。所以 `Rocketmq` 提供了 `master/slave` 的结构,` salve` 定时从 `master` 同步数据(同步刷盘或者异步刷盘),如果 `master` 宕机,**则 `slave` 提供消费服务,但是不能写入消息** (后面我还会提到哦)。
-
-第二、为了保证 `HA` ,我们的 `NameServer` 也做了集群部署,但是请注意它是 **去中心化** 的。也就意味着它没有主节点,你可以很明显地看出 `NameServer` 的所有节点是没有进行 `Info Replicate` 的,在 `RocketMQ` 中是通过 **单个Broker和所有NameServer保持长连接** ,并且在每隔30秒 `Broker` 会向所有 `Nameserver` 发送心跳,心跳包含了自身的 `Topic` 配置信息,这个步骤就对应这上面的 `Routing Info` 。
-
-第三、在生产者需要向 `Broker` 发送消息的时候,**需要先从 `NameServer` 获取关于 `Broker` 的路由信息**,然后通过 **轮询** 的方法去向每个队列中生产数据以达到 **负载均衡** 的效果。
-
-第四、消费者通过 `NameServer` 获取所有 `Broker` 的路由信息后,向 `Broker` 发送 `Pull` 请求来获取消息数据。`Consumer` 可以以两种模式启动—— **广播(Broadcast)和集群(Cluster)**。广播模式下,一条消息会发送给 **同一个消费组中的所有消费者** ,集群模式下消息只会发送给一个消费者。
-
-## 如何解决 顺序消费、重复消费
-
-其实,这些东西都是我在介绍消息队列带来的一些副作用的时候提到的,也就是说,这些问题不仅仅挂钩于 `RocketMQ` ,而是应该每个消息中间件都需要去解决的。
-
-在上面我介绍 `RocketMQ` 的技术架构的时候我已经向你展示了 **它是如何保证高可用的** ,这里不涉及运维方面的搭建,如果你感兴趣可以自己去官网上照着例子搭建属于你自己的 `RocketMQ` 集群。
-
-> 其实 `Kafka` 的架构基本和 `RocketMQ` 类似,只是它注册中心使用了 `Zookeeper` 、它的 **分区** 就相当于 `RocketMQ` 中的 **队列** 。还有一些小细节不同会在后面提到。
-
-### 顺序消费
-
-在上面的技术架构介绍中,我们已经知道了 **`RocketMQ` 在主题上是无序的、它只有在队列层面才是保证有序** 的。
-
-这又扯到两个概念——**普通顺序** 和 **严格顺序** 。
-
-所谓普通顺序是指 消费者通过 **同一个消费队列收到的消息是有顺序的** ,不同消息队列收到的消息则可能是无顺序的。普通顺序消息在 `Broker` **重启情况下不会保证消息顺序性** (短暂时间) 。
-
-所谓严格顺序是指 消费者收到的 **所有消息** 均是有顺序的。严格顺序消息 **即使在异常情况下也会保证消息的顺序性** 。
-
-但是,严格顺序看起来虽好,实现它可会付出巨大的代价。如果你使用严格顺序模式,`Broker` 集群中只要有一台机器不可用,则整个集群都不可用。你还用啥?现在主要场景也就在 `binlog` 同步。
-
-一般而言,我们的 `MQ` 都是能容忍短暂的乱序,所以推荐使用普通顺序模式。
-
-那么,我们现在使用了 **普通顺序模式** ,我们从上面学习知道了在 `Producer` 生产消息的时候会进行轮询(取决你的负载均衡策略)来向同一主题的不同消息队列发送消息。那么如果此时我有几个消息分别是同一个订单的创建、支付、发货,在轮询的策略下这 **三个消息会被发送到不同队列** ,因为在不同的队列此时就无法使用 `RocketMQ` 带来的队列有序特性来保证消息有序性了。
-
-
-
-那么,怎么解决呢?
-
-其实很简单,我们需要处理的仅仅是将同一语义下的消息放入同一个队列(比如这里是同一个订单),那我们就可以使用 **Hash取模法** 来保证同一个订单在同一个队列中就行了。
-
-### 重复消费
-
-emmm,就两个字—— **幂等** 。在编程中一个*幂等* 操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。比如说,这个时候我们有一个订单的处理积分的系统,每当来一个消息的时候它就负责为创建这个订单的用户的积分加上相应的数值。可是有一次,消息队列发送给订单系统 FrancisQ 的订单信息,其要求是给 FrancisQ 的积分加上 500。但是积分系统在收到 FrancisQ 的订单信息处理完成之后返回给消息队列处理成功的信息的时候出现了网络波动(当然还有很多种情况,比如Broker意外重启等等),这条回应没有发送成功。
-
-那么,消息队列没收到积分系统的回应会不会尝试重发这个消息?问题就来了,我再发这个消息,万一它又给 FrancisQ 的账户加上 500 积分怎么办呢?
-
-所以我们需要给我们的消费者实现 **幂等** ,也就是对同一个消息的处理结果,执行多少次都不变。
-
-那么如何给业务实现幂等呢?这个还是需要结合具体的业务的。你可以使用 **写入 `Redis`** 来保证,因为 `Redis` 的 `key` 和 `value` 就是天然支持幂等的。当然还有使用 **数据库插入法** ,基于数据库的唯一键来保证重复数据不会被插入多条。
-
-不过最主要的还是需要 **根据特定场景使用特定的解决方案** ,你要知道你的消息消费是否是完全不可重复消费还是可以忍受重复消费的,然后再选择强校验和弱校验的方式。毕竟在 CS 领域还是很少有技术银弹的说法。
-
-而在整个互联网领域,幂等不仅仅适用于消息队列的重复消费问题,这些实现幂等的方法,也同样适用于,**在其他场景中来解决重复请求或者重复调用的问题** 。比如将HTTP服务设计成幂等的,**解决前端或者APP重复提交表单数据的问题** ,也可以将一个微服务设计成幂等的,解决 `RPC` 框架自动重试导致的 **重复调用问题** 。
-
-## 分布式事务
-
-如何解释分布式事务呢?事务大家都知道吧?**要么都执行要么都不执行** 。在同一个系统中我们可以轻松地实现事务,但是在分布式架构中,我们有很多服务是部署在不同系统之间的,而不同服务之间又需要进行调用。比如此时我下订单然后增加积分,如果保证不了分布式事务的话,就会出现A系统下了订单,但是B系统增加积分失败或者A系统没有下订单,B系统却增加了积分。前者对用户不友好,后者对运营商不利,这是我们都不愿意见到的。
-
-那么,如何去解决这个问题呢?
-
-如今比较常见的分布式事务实现有 2PC、TCC 和事务消息(half 半消息机制)。每一种实现都有其特定的使用场景,但是也有各自的问题,**都不是完美的解决方案**。
-
-在 `RocketMQ` 中使用的是 **事务消息加上事务反查机制** 来解决分布式事务问题的。我画了张图,大家可以对照着图进行理解。
-
-
-
-在第一步发送的 half 消息 ,它的意思是 **在事务提交之前,对于消费者来说,这个消息是不可见的** 。
-
-> 那么,如何做到写入消息但是对用户不可见呢?RocketMQ事务消息的做法是:如果消息是half消息,将备份原消息的主题与消息消费队列,然后 **改变主题** 为RMQ_SYS_TRANS_HALF_TOPIC。由于消费组未订阅该主题,故消费端无法消费half类型的消息,**然后RocketMQ会开启一个定时任务,从Topic为RMQ_SYS_TRANS_HALF_TOPIC中拉取消息进行消费**,根据生产者组获取一个服务提供者发送回查事务状态请求,根据事务状态来决定是提交或回滚消息。
-
-你可以试想一下,如果没有从第5步开始的 **事务反查机制** ,如果出现网路波动第4步没有发送成功,这样就会产生 MQ 不知道是不是需要给消费者消费的问题,他就像一个无头苍蝇一样。在 `RocketMQ` 中就是使用的上述的事务反查来解决的,而在 `Kafka` 中通常是直接抛出一个异常让用户来自行解决。
-
-你还需要注意的是,在 `MQ Server` 指向系统B的操作已经和系统A不相关了,也就是说在消息队列中的分布式事务是——**本地事务和存储消息到消息队列才是同一个事务**。这样也就产生了事务的**最终一致性**,因为整个过程是异步的,**每个系统只要保证它自己那一部分的事务就行了**。
-
-## 消息堆积问题
-
-在上面我们提到了消息队列一个很重要的功能——**削峰** 。那么如果这个峰值太大了导致消息堆积在队列中怎么办呢?
-
-其实这个问题可以将它广义化,因为产生消息堆积的根源其实就只有两个——生产者生产太快或者消费者消费太慢。
-
-我们可以从多个角度去思考解决这个问题,当流量到峰值的时候是因为生产者生产太快,我们可以使用一些 **限流降级** 的方法,当然你也可以增加多个消费者实例去水平扩展增加消费能力来匹配生产的激增。如果消费者消费过慢的话,我们可以先检查 **是否是消费者出现了大量的消费错误** ,或者打印一下日志查看是否是哪一个线程卡死,出现了锁资源不释放等等的问题。
-
-> 当然,最快速解决消息堆积问题的方法还是增加消费者实例,不过 **同时你还需要增加每个主题的队列数量** 。
->
-> 别忘了在 `RocketMQ` 中,**一个队列只会被一个消费者消费** ,如果你仅仅是增加消费者实例就会出现我一开始给你画架构图的那种情况。
-
-
-
-## 回溯消费
-
-回溯消费是指 `Consumer` 已经消费成功的消息,由于业务上需求需要重新消费,在`RocketMQ` 中, `Broker` 在向`Consumer` 投递成功消息后,**消息仍然需要保留** 。并且重新消费一般是按照时间维度,例如由于 `Consumer` 系统故障,恢复后需要重新消费1小时前的数据,那么 `Broker` 要提供一种机制,可以按照时间维度来回退消费进度。`RocketMQ` 支持按照时间回溯消费,时间维度精确到毫秒。
-
-这是官方文档的解释,我直接照搬过来就当科普了😁😁😁。
-
-## RocketMQ 的刷盘机制
-
-上面我讲了那么多的 `RocketMQ` 的架构和设计原理,你有没有好奇
-
-在 `Topic` 中的 **队列是以什么样的形式存在的?**
-
-**队列中的消息又是如何进行存储持久化的呢?**
-
-我在上文中提到的 **同步刷盘** 和 **异步刷盘** 又是什么呢?它们会给持久化带来什么样的影响呢?
-
-下面我将给你们一一解释。
-
-### 同步刷盘和异步刷盘
-
-
-
-如上图所示,在同步刷盘中需要等待一个刷盘成功的 `ACK` ,同步刷盘对 `MQ` 消息可靠性来说是一种不错的保障,但是 **性能上会有较大影响** ,一般地适用于金融等特定业务场景。
-
-而异步刷盘往往是开启一个线程去异步地执行刷盘操作。消息刷盘采用后台异步线程提交的方式进行, **降低了读写延迟** ,提高了 `MQ` 的性能和吞吐量,一般适用于如发验证码等对于消息保证要求不太高的业务场景。
-
-一般地,**异步刷盘只有在 `Broker` 意外宕机的时候会丢失部分数据**,你可以设置 `Broker` 的参数 `FlushDiskType` 来调整你的刷盘策略(ASYNC_FLUSH 或者 SYNC_FLUSH)。
-
-### 同步复制和异步复制
-
-上面的同步刷盘和异步刷盘是在单个结点层面的,而同步复制和异步复制主要是指的 `Borker` 主从模式下,主节点返回消息给客户端的时候是否需要同步从节点。
-
-- 同步复制: 也叫 “同步双写”,也就是说,**只有消息同步双写到主从结点上时才返回写入成功** 。
-- 异步复制: **消息写入主节点之后就直接返回写入成功** 。
-
-然而,很多事情是没有完美的方案的,就比如我们进行消息写入的节点越多就更能保证消息的可靠性,但是随之的性能也会下降,所以需要程序员根据特定业务场景去选择适应的主从复制方案。
-
-那么,**异步复制会不会也像异步刷盘那样影响消息的可靠性呢?**
-
-答案是不会的,因为两者就是不同的概念,对于消息可靠性是通过不同的刷盘策略保证的,而像异步同步复制策略仅仅是影响到了 **可用性** 。为什么呢?其主要原因**是 `RocketMQ` 是不支持自动主从切换的,当主节点挂掉之后,生产者就不能再给这个主节点生产消息了**。
-
-比如这个时候采用异步复制的方式,在主节点还未发送完需要同步的消息的时候主节点挂掉了,这个时候从节点就少了一部分消息。但是此时生产者无法再给主节点生产消息了,**消费者可以自动切换到从节点进行消费**(仅仅是消费),所以在主节点挂掉的时间只会产生主从结点短暂的消息不一致的情况,降低了可用性,而当主节点重启之后,从节点那部分未来得及复制的消息还会继续复制。
-
-在单主从架构中,如果一个主节点挂掉了,那么也就意味着整个系统不能再生产了。那么这个可用性的问题能否解决呢?**一个主从不行那就多个主从的呗**,别忘了在我们最初的架构图中,每个 `Topic` 是分布在不同 `Broker` 中的。
-
-
-
-但是这种复制方式同样也会带来一个问题,那就是无法保证 **严格顺序** 。在上文中我们提到了如何保证的消息顺序性是通过将一个语义的消息发送在同一个队列中,使用 `Topic` 下的队列来保证顺序性的。如果此时我们主节点A负责的是订单A的一系列语义消息,然后它挂了,这样其他节点是无法代替主节点A的,如果我们任意节点都可以存入任何消息,那就没有顺序性可言了。
-
-而在 `RocketMQ` 中采用了 `Dledger` 解决这个问题。他要求在写入消息的时候,要求**至少消息复制到半数以上的节点之后**,才给客⼾端返回写⼊成功,并且它是⽀持通过选举来动态切换主节点的。这里我就不展开说明了,读者可以自己去了解。
-
-> 也不是说 `Dledger` 是个完美的方案,至少在 `Dledger` 选举过程中是无法提供服务的,而且他必须要使用三个节点或以上,如果多数节点同时挂掉他也是无法保证可用性的,而且要求消息复制半数以上节点的效率和直接异步复制还是有一定的差距的。
-
-### 存储机制
-
-还记得上面我们一开始的三个问题吗?到这里第三个问题已经解决了。
-
-但是,在 `Topic` 中的 **队列是以什么样的形式存在的?队列中的消息又是如何进行存储持久化的呢?** 还未解决,其实这里涉及到了 `RocketMQ` 是如何设计它的存储结构了。我首先想大家介绍 `RocketMQ` 消息存储架构中的三大角色——`CommitLog` 、`ConsumeQueue` 和 `IndexFile` 。
-
-- `CommitLog`: **消息主体以及元数据的存储主体**,存储 `Producer` 端写入的消息主体内容,消息内容不是定长的。单个文件大小默认1G ,文件名长度为20位,左边补零,剩余为起始偏移量,比如00000000000000000000代表了第一个文件,起始偏移量为0,文件大小为1G=1073741824;当第一个文件写满了,第二个文件为00000000001073741824,起始偏移量为1073741824,以此类推。消息主要是**顺序写入日志文件**,当文件满了,写入下一个文件。
-- `ConsumeQueue`: 消息消费队列,**引入的目的主要是提高消息消费的性能**(我们再前面也讲了),由于`RocketMQ` 是基于主题 `Topic` 的订阅模式,消息消费是针对主题进行的,如果要遍历 `commitlog` 文件中根据 `Topic` 检索消息是非常低效的。`Consumer` 即可根据 `ConsumeQueue` 来查找待消费的消息。其中,`ConsumeQueue`(逻辑消费队列)**作为消费消息的索引**,保存了指定 `Topic` 下的队列消息在 `CommitLog` 中的**起始物理偏移量 `offset` **,消息大小 `size` 和消息 `Tag` 的 `HashCode` 值。**`consumequeue` 文件可以看成是基于 `topic` 的 `commitlog` 索引文件**,故 `consumequeue` 文件夹的组织方式如下:topic/queue/file三层组织结构,具体存储路径为:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同样 `consumequeue` 文件采取定长设计,每一个条目共20个字节,分别为8字节的 `commitlog` 物理偏移量、4字节的消息长度、8字节tag `hashcode`,单个文件由30W个条目组成,可以像数组一样随机访问每一个条目,每个 `ConsumeQueue`文件大小约5.72M;
-- `IndexFile`: `IndexFile`(索引文件)提供了一种可以通过key或时间区间来查询消息的方法。这里只做科普不做详细介绍。
-
-总结来说,整个消息存储的结构,最主要的就是 `CommitLoq` 和 `ConsumeQueue` 。而 `ConsumeQueue` 你可以大概理解为 `Topic` 中的队列。
-
-
-
-`RocketMQ` 采用的是 **混合型的存储结构** ,即为 `Broker` 单个实例下所有的队列共用一个日志数据文件来存储消息。有意思的是在同样高并发的 `Kafka` 中会为每个 `Topic` 分配一个存储文件。这就有点类似于我们有一大堆书需要装上书架,`RockeMQ` 是不分书的种类直接成批的塞上去的,而 `Kafka` 是将书本放入指定的分类区域的。
-
-而 `RocketMQ` 为什么要这么做呢?原因是 **提高数据的写入效率** ,不分 `Topic` 意味着我们有更大的几率获取 **成批** 的消息进行数据写入,但也会带来一个麻烦就是读取消息的时候需要遍历整个大文件,这是非常耗时的。
-
-所以,在 `RocketMQ` 中又使用了 `ConsumeQueue` 作为每个队列的索引文件来 **提升读取消息的效率**。我们可以直接根据队列的消息序号,计算出索引的全局位置(索引序号*索引固定⻓度20),然后直接读取这条索引,再根据索引中记录的消息的全局位置,找到消息。
-
-讲到这里,你可能对 `RockeMQ` 的存储架构还有些模糊,没事,我们结合着图来理解一下。
-
-
-
-emmm,是不是有一点复杂🤣,看英文图片和英文文档的时候就不要怂,硬着头皮往下看就行。
-
-> 如果上面没看懂的读者一定要认真看下面的流程分析!
-
-首先,在最上面的那一块就是我刚刚讲的你现在可以直接 **把 `ConsumerQueue` 理解为 `Queue`**。
-
-在图中最左边说明了 红色方块 代表被写入的消息,虚线方块代表等待被写入的。左边的生产者发送消息会指定 `Topic` 、`QueueId` 和具体消息内容,而在 `Broker` 中管你是哪门子消息,他直接 **全部顺序存储到了 CommitLog**。而根据生产者指定的 `Topic` 和 `QueueId` 将这条消息本身在 `CommitLog` 的偏移(offset),消息本身大小,和tag的hash值存入对应的 `ConsumeQueue` 索引文件中。而在每个队列中都保存了 `ConsumeOffset` 即每个消费者组的消费位置(我在架构那里提到了,忘了的同学可以回去看一下),而消费者拉取消息进行消费的时候只需要根据 `ConsumeOffset` 获取下一个未被消费的消息就行了。
-
-上述就是我对于整个消息存储架构的大概理解(这里不涉及到一些细节讨论,比如稀疏索引等等问题),希望对你有帮助。
-
-因为有一个知识点因为写嗨了忘讲了,想想在哪里加也不好,所以我留给大家去思考🤔🤔一下吧。
-
-
-
-为什么 `CommitLog` 文件要设计成固定大小的长度呢?提醒:**内存映射机制**。
-
-## 总结
-
-总算把这篇博客写完了。我讲的你们还记得吗😅?
-
-这篇文章中我主要想大家介绍了
-
-1. 消息队列出现的原因
-2. 消息队列的作用(异步,解耦,削峰)
-3. 消息队列带来的一系列问题(消息堆积、重复消费、顺序消费、分布式事务等等)
-4. 消息队列的两种消息模型——队列和主题模式
-5. 分析了 `RocketMQ` 的技术架构(`NameServer` 、`Broker` 、`Producer` 、`Comsumer`)
-6. 结合 `RocketMQ` 回答了消息队列副作用的解决方案
-7. 介绍了 `RocketMQ` 的存储机制和刷盘策略。
-
-等等。。。
-
-> 如果喜欢可以点赞哟👍👍👍。
diff --git a/docs/high-performance/message-queue/rocketmq-questions.md b/docs/high-performance/message-queue/rocketmq-questions.md
index 68957689c11e2cfdce9af1c91ae48c3d98fc53bf..81c467201c576e5e9249944dc397b4c4063da0db 100644
--- a/docs/high-performance/message-queue/rocketmq-questions.md
+++ b/docs/high-performance/message-queue/rocketmq-questions.md
@@ -1,199 +1,458 @@
-# RocketMQ常见问题
-
-本文来自读者 [PR](https://github.com/Snailclimb/JavaGuide/pull/291)。
-
-## 1 单机版消息中心
-
-一个消息中心,最基本的需要支持多生产者、多消费者,例如下:
-
-```java
-class Scratch {
-
- public static void main(String[] args) {
- // 实际中会有 nameserver 服务来找到 broker 具体位置以及 broker 主从信息
- Broker broker = new Broker();
- Producer producer1 = new Producer();
- producer1.connectBroker(broker);
- Producer producer2 = new Producer();
- producer2.connectBroker(broker);
+---
+title: RocketMQ常见问题总结
+category: 高性能
+tag:
+ - RocketMQ
+ - 消息队列
+---
- Consumer consumer1 = new Consumer();
- consumer1.connectBroker(broker);
- Consumer consumer2 = new Consumer();
- consumer2.connectBroker(broker);
+> [本文由 FrancisQ 投稿!](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485969&idx=1&sn=6bd53abde30d42a778d5a35ec104428c&chksm=cea245daf9d5cccce631f93115f0c2c4a7634e55f5bef9009fd03f5a0ffa55b745b5ef4f0530&token=294077121&lang=zh_CN#rd)
- for (int i = 0; i < 2; i++) {
- producer1.asyncSendMsg("producer1 send msg" + i);
- producer2.asyncSendMsg("producer2 send msg" + i);
- }
- System.out.println("broker has msg:" + broker.getAllMagByDisk());
+## 消息队列扫盲
- for (int i = 0; i < 1; i++) {
- System.out.println("consumer1 consume msg:" + consumer1.syncPullMsg());
- }
- for (int i = 0; i < 3; i++) {
- System.out.println("consumer2 consume msg:" + consumer2.syncPullMsg());
- }
- }
+消息队列顾名思义就是存放消息的队列,队列我就不解释了,别告诉我你连队列都不知道是啥吧?
-}
+所以问题并不是消息队列是什么,而是 **消息队列为什么会出现?消息队列能用来干什么?用它来干这些事会带来什么好处?消息队列会带来副作用吗?**
-class Producer {
+### 消息队列为什么会出现?
- private Broker broker;
+消息队列算是作为后端程序员的一个必备技能吧,因为**分布式应用必定涉及到各个系统之间的通信问题**,这个时候消息队列也应运而生了。可以说分布式的产生是消息队列的基础,而分布式怕是一个很古老的概念了吧,所以消息队列也是一个很古老的中间件了。
- public void connectBroker(Broker broker) {
- this.broker = broker;
- }
+### 消息队列能用来干什么?
- public void asyncSendMsg(String msg) {
- if (broker == null) {
- throw new RuntimeException("please connect broker first");
- }
- new Thread(() -> {
- broker.sendMsg(msg);
- }).start();
- }
-}
+#### 异步
-class Consumer {
- private Broker broker;
+你可能会反驳我,应用之间的通信又不是只能由消息队列解决,好好的通信为什么中间非要插一个消息队列呢?我不能直接进行通信吗?
- public void connectBroker(Broker broker) {
- this.broker = broker;
- }
+很好 👍,你又提出了一个概念,**同步通信**。就比如现在业界使用比较多的 `Dubbo` 就是一个适用于各个系统之间同步通信的 `RPC` 框架。
- public String syncPullMsg() {
- return broker.getMsg();
- }
+我来举个 🌰 吧,比如我们有一个购票系统,需求是用户在购买完之后能接收到购买完成的短信。
-}
+
-class Broker {
+我们省略中间的网络通信时间消耗,假如购票系统处理需要 150ms ,短信系统处理需要 200ms ,那么整个处理流程的时间消耗就是 150ms + 200ms = 350ms。
- // 对应 RocketMQ 中 MessageQueue,默认情况下 1 个 Topic 包含 4 个 MessageQueue
- private LinkedBlockingQueue messageQueue = new LinkedBlockingQueue(Integer.MAX_VALUE);
+当然,乍看没什么问题。可是仔细一想你就感觉有点问题,我用户购票在购票系统的时候其实就已经完成了购买,而我现在通过同步调用非要让整个请求拉长时间,而短信系统这玩意又不是很有必要,它仅仅是一个辅助功能增强用户体验感而已。我现在整个调用流程就有点 **头重脚轻** 的感觉了,购票是一个不太耗时的流程,而我现在因为同步调用,非要等待发送短信这个比较耗时的操作才返回结果。那我如果再加一个发送邮件呢?
- // 实际发送消息到 broker 服务器使用 Netty 发送
- public void sendMsg(String msg) {
- try {
- messageQueue.put(msg);
- // 实际会同步或异步落盘,异步落盘使用的定时任务定时扫描落盘
- } catch (InterruptedException e) {
+
- }
- }
+这样整个系统的调用链又变长了,整个时间就变成了 550ms。
- public String getMsg() {
- try {
- return messageQueue.take();
- } catch (InterruptedException e) {
+当我们在学生时代需要在食堂排队的时候,我们和食堂大妈就是一个同步的模型。
- }
- return null;
- }
+我们需要告诉食堂大妈:“姐姐,给我加个鸡腿,再加个酸辣土豆丝,帮我浇点汁上去,多打点饭哦 😋😋😋” 咦~~~ 为了多吃点,真恶心。
- public String getAllMagByDisk() {
- StringBuilder sb = new StringBuilder("\n");
- messageQueue.iterator().forEachRemaining((msg) -> {
- sb.append(msg + "\n");
- });
- return sb.toString();
- }
-}
-```
+然后大妈帮我们打饭配菜,我们看着大妈那颤抖的手和掉落的土豆丝不禁咽了咽口水。
-问题:
-1. 没有实现真正执行消息存储落盘
-2. 没有实现 NameServer 去作为注册中心,定位服务
-3. 使用 LinkedBlockingQueue 作为消息队列,注意,参数是无限大,在真正 RocketMQ 也是如此是无限大,理论上不会出现对进来的数据进行抛弃,但是会有内存泄漏问题(阿里巴巴开发手册也因为这个问题,建议我们使用自制线程池)
-4. 没有使用多个队列(即多个 LinkedBlockingQueue),RocketMQ 的顺序消息是通过生产者和消费者同时使用同一个 MessageQueue 来实现,但是如果我们只有一个 MessageQueue,那我们天然就支持顺序消息
-5. 没有使用 MappedByteBuffer 来实现文件映射从而使消息数据落盘非常的快(实际 RocketMQ 使用的是 FileChannel+DirectBuffer)
+最终我们从大妈手中接过饭菜然后去寻找座位了...
-## 2 分布式消息中心
+回想一下,我们在给大妈发送需要的信息之后我们是 **同步等待大妈给我配好饭菜** 的,上面我们只是加了鸡腿和土豆丝,万一我再加一个番茄牛腩,韭菜鸡蛋,这样是不是大妈打饭配菜的流程就会变长,我们等待的时间也会相应的变长。
-### 2.1 问题与解决
+
-#### 2.1.1 消息丢失的问题
+那后来,我们工作赚钱了有钱去饭店吃饭了,我们告诉服务员来一碗牛肉面加个荷包蛋 **(传达一个消息)** ,然后我们就可以在饭桌上安心的玩手机了 **(干自己其他事情)** ,等到我们的牛肉面上了我们就可以吃了。这其中我们也就传达了一个消息,然后我们又转过头干其他事情了。这其中虽然做面的时间没有变短,但是我们只需要传达一个消息就可以干其他事情了,这是一个 **异步** 的概念。
-1. 当你系统需要保证百分百消息不丢失,你可以使用生产者每发送一个消息,Broker 同步返回一个消息发送成功的反馈消息
-2. 即每发送一个消息,同步落盘后才返回生产者消息发送成功,这样只要生产者得到了消息发送生成的返回,事后除了硬盘损坏,都可以保证不会消息丢失
-3. 但是这同时引入了一个问题,同步落盘怎么才能快?
+所以,为了解决这一个问题,聪明的程序员在中间也加了个类似于服务员的中间件——消息队列。这个时候我们就可以把模型给改造了。
-#### 2.1.2 同步落盘怎么才能快
+
-1. 使用 FileChannel + DirectBuffer 池,使用堆外内存,加快内存拷贝
-2. 使用数据和索引分离,当消息需要写入时,使用 commitlog 文件顺序写,当需要定位某个消息时,查询index 文件来定位,从而减少文件IO随机读写的性能损耗
+这样,我们在将消息存入消息队列之后我们就可以直接返回了(我们告诉服务员我们要吃什么然后玩手机),所以整个耗时只是 150ms + 10ms = 160ms。
-#### 2.1.3 消息堆积的问题
+> 但是你需要注意的是,整个流程的时长是没变的,就像你仅仅告诉服务员要吃什么是不会影响到做面的速度的。
-1. 后台定时任务每隔72小时,删除旧的没有使用过的消息信息
-2. 根据不同的业务实现不同的丢弃任务,具体参考线程池的 AbortPolicy,例如FIFO/LRU等(RocketMQ没有此策略)
-3. 消息定时转移,或者对某些重要的 TAG 型(支付型)消息真正落库
-
-#### 2.1.4 定时消息的实现
+#### 解耦
-1. 实际 RocketMQ 没有实现任意精度的定时消息,它只支持某些特定的时间精度的定时消息
-2. 实现定时消息的原理是:创建特定时间精度的 MessageQueue,例如生产者需要定时1s之后被消费者消费,你只需要将此消息发送到特定的 Topic,例如:MessageQueue-1 表示这个 MessageQueue 里面的消息都会延迟一秒被消费,然后 Broker 会在 1s 后发送到消费者消费此消息,使用 newSingleThreadScheduledExecutor 实现
+回到最初同步调用的过程,我们写个伪代码简单概括一下。
-#### 2.1.5 顺序消息的实现
+
-1. 与定时消息同原理,生产者生产消息时指定特定的 MessageQueue ,消费者消费消息时,消费特定的 MessageQueue,其实单机版的消息中心在一个 MessageQueue 就天然支持了顺序消息
-2. 注意:同一个 MessageQueue 保证里面的消息是顺序消费的前提是:消费者是串行的消费该 MessageQueue,因为就算 MessageQueue 是顺序的,但是当并行消费时,还是会有顺序问题,但是串行消费也同时引入了两个问题:
->1. 引入锁来实现串行
->2. 前一个消费阻塞时后面都会被阻塞
+那么第二步,我们又添加了一个发送邮件,我们就得重新去修改代码,如果我们又加一个需求:用户购买完还需要给他加积分,这个时候我们是不是又得改代码?
-#### 2.1.6 分布式消息的实现
+
-1. 需要前置知识:2PC
-2. RocketMQ4.3 起支持,原理为2PC,即两阶段提交,prepared->commit/rollback
-3. 生产者发送事务消息,假设该事务消息 Topic 为 Topic1-Trans,Broker 得到后首先更改该消息的 Topic 为 Topic1-Prepared,该 Topic1-Prepared 对消费者不可见。然后定时回调生产者的本地事务A执行状态,根据本地事务A执行状态,来是否将该消息修改为 Topic1-Commit 或 Topic1-Rollback,消费者就可以正常找到该事务消息或者不执行等
+如果你觉得还行,那么我这个时候不要发邮件这个服务了呢,我是不是又得改代码,又得重启应用?
->注意,就算是事务消息最后回滚了也不会物理删除,只会逻辑删除该消息
+
-#### 2.1.7 消息的 push 实现
+这样改来改去是不是很麻烦,那么 **此时我们就用一个消息队列在中间进行解耦** 。你需要注意的是,我们后面的发送短信、发送邮件、添加积分等一些操作都依赖于上面的 `result` ,这东西抽象出来就是购票的处理结果呀,比如订单号,用户账号等等,也就是说我们后面的一系列服务都是需要同样的消息来进行处理。既然这样,我们是不是可以通过 **“广播消息”** 来实现。
-1. 注意,RocketMQ 已经说了自己会有低延迟问题,其中就包括这个消息的 push 延迟问题
-2. 因为这并不是真正的将消息主动的推送到消费者,而是 Broker 定时任务每5s将消息推送到消费者
-3. pull模式需要我们手动调用consumer拉消息,而push模式则只需要我们提供一个listener即可实现对消息的监听,而实际上,RocketMQ的push模式是基于pull模式实现的,它没有实现真正的push。
-4. push方式里,consumer把轮询过程封装了,并注册MessageListener监听器,取到消息后,唤醒MessageListener的consumeMessage()来消费,对用户而言,感觉消息是被推送过来的。
+我上面所讲的“广播”并不是真正的广播,而是接下来的系统作为消费者去 **订阅** 特定的主题。比如我们这里的主题就可以叫做 `订票` ,我们购买系统作为一个生产者去生产这条消息放入消息队列,然后消费者订阅了这个主题,会从消息队列中拉取消息并消费。就比如我们刚刚画的那张图,你会发现,在生产者这边我们只需要关注 **生产消息到指定主题中** ,而 **消费者只需要关注从指定主题中拉取消息** 就行了。
-#### 2.1.8 消息重复发送的避免
+
-1. RocketMQ 会出现消息重复发送的问题,因为在网络延迟的情况下,这种问题不可避免的发生,如果非要实现消息不可重复发送,那基本太难,因为网络环境无法预知,还会使程序复杂度加大,因此默认允许消息重复发送
-2. RocketMQ 让使用者在消费者端去解决该问题,即需要消费者端在消费消息时支持幂等性的去消费消息
-3. 最简单的解决方案是每条消费记录有个消费状态字段,根据这个消费状态字段来判断是否消费或者使用一个集中式的表,来存储所有消息的消费状态,从而避免重复消费
-4. 具体实现可以查询关于消息幂等消费的解决方案
+> 如果没有消息队列,每当一个新的业务接入,我们都要在主系统调用新接口、或者当我们取消某些业务,我们也得在主系统删除某些接口调用。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,接下来收到消息如何处理,是下游的事情,无疑极大地减少了开发和联调的工作量。
-#### 2.1.9 广播消费与集群消费
+#### 削峰
-1. 消息消费区别:广播消费,订阅该 Topic 的消息者们都会消费**每个**消息。集群消费,订阅该 Topic 的消息者们只会有一个去消费**某个**消息
-2. 消息落盘区别:具体表现在消息消费进度的保存上。广播消费,由于每个消费者都独立的去消费每个消息,因此每个消费者各自保存自己的消息消费进度。而集群消费下,订阅了某个 Topic,而旗下又有多个 MessageQueue,每个消费者都可能会去消费不同的 MessageQueue,因此总体的消费进度保存在 Broker 上集中的管理
+我们再次回到一开始我们使用同步调用系统的情况,并且思考一下,如果此时有大量用户请求购票整个系统会变成什么样?
-#### 2.1.10 RocketMQ 不使用 ZooKeeper 作为注册中心的原因,以及自制的 NameServer 优缺点?
+
-1. ZooKeeper 作为支持顺序一致性的中间件,在某些情况下,它为了满足一致性,会丢失一定时间内的可用性,RocketMQ 需要注册中心只是为了发现组件地址,在某些情况下,RocketMQ 的注册中心可以出现数据不一致性,这同时也是 NameServer 的缺点,因为 NameServer 集群间互不通信,它们之间的注册信息可能会不一致
-2. 另外,当有新的服务器加入时,NameServer 并不会立马通知到 Producer,而是由 Producer 定时去请求 NameServer 获取最新的 Broker/Consumer 信息(这种情况是通过 Producer 发送消息时,负载均衡解决)
+如果,此时有一万的请求进入购票系统,我们知道运行我们主业务的服务器配置一般会比较好,所以这里我们假设购票系统能承受这一万的用户请求,那么也就意味着我们同时也会出现一万调用发短信服务的请求。而对于短信系统来说并不是我们的主要业务,所以我们配备的硬件资源并不会太高,那么你觉得现在这个短信系统能承受这一万的峰值么,且不说能不能承受,系统会不会 **直接崩溃** 了?
-#### 2.1.11 其它
+短信业务又不是我们的主业务,我们能不能 **折中处理** 呢?如果我们把购买完成的信息发送到消息队列中,而短信系统 **尽自己所能地去消息队列中取消息和消费消息** ,即使处理速度慢一点也无所谓,只要我们的系统没有崩溃就行了。
-![][1]
+留得江山在,还怕没柴烧?你敢说每次发送验证码的时候是一发你就收到了的么?
-加分项咯
-1. 包括组件通信间使用 Netty 的自定义协议
-2. 消息重试负载均衡策略(具体参考 Dubbo 负载均衡策略)
-3. 消息过滤器(Producer 发送消息到 Broker,Broker 存储消息信息,Consumer 消费时请求 Broker 端从磁盘文件查询消息文件时,在 Broker 端就使用过滤服务器进行过滤)
-4. Broker 同步双写和异步双写中 Master 和 Slave 的交互
-5. Broker 在 4.5.0 版本更新中引入了基于 Raft 协议的多副本选举,之前这是商业版才有的特性 [ISSUE-1046][2]
+#### 消息队列能带来什么好处?
-## 3 参考
+其实上面我已经说了。**异步、解耦、削峰。** 哪怕你上面的都没看懂也千万要记住这六个字,因为他不仅是消息队列的精华,更是编程和架构的精华。
-1. 《RocketMQ技术内幕》:https://blog.csdn.net/prestigeding/article/details/85233529
-2. 关于 RocketMQ 对 MappedByteBuffer 的一点优化:https://lishoubo.github.io/2017/09/27/MappedByteBuffer%E7%9A%84%E4%B8%80%E7%82%B9%E4%BC%98%E5%8C%96/
-3. 十分钟入门RocketMQ:https://developer.aliyun.com/article/66101
-4. 分布式事务的种类以及 RocketMQ 支持的分布式消息:https://www.infoq.cn/article/2018/08/rocketmq-4.3-release
-5. 滴滴出行基于RocketMQ构建企业级消息队列服务的实践:https://yq.aliyun.com/articles/664608
-6. 基于《RocketMQ技术内幕》源码注释:https://github.com/LiWenGu/awesome-rocketmq
+#### 消息队列会带来副作用吗?
-[1]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/RocketMQ%E6%B5%81%E7%A8%8B.png
-[2]: http://rocketmq.apache.org/release_notes/release-notes-4.5.0/
+没有哪一门技术是“银弹”,消息队列也有它的副作用。
+
+比如,本来好好的两个系统之间的调用,我中间加了个消息队列,如果消息队列挂了怎么办呢?是不是 **降低了系统的可用性** ?
+
+那这样是不是要保证 HA(高可用)?是不是要搞集群?那么我 **整个系统的复杂度是不是上升了** ?
+
+抛开上面的问题不讲,万一我发送方发送失败了,然后执行重试,这样就可能产生重复的消息。
+
+或者我消费端处理失败了,请求重发,这样也会产生重复的消息。
+
+对于一些微服务来说,消费重复消息会带来更大的麻烦,比如增加积分,这个时候我加了多次是不是对其他用户不公平?
+
+那么,又 **如何解决重复消费消息的问题** 呢?
+
+如果我们此时的消息需要保证严格的顺序性怎么办呢?比如生产者生产了一系列的有序消息(对一个 id 为 1 的记录进行删除增加修改),但是我们知道在发布订阅模型中,对于主题是无顺序的,那么这个时候就会导致对于消费者消费消息的时候没有按照生产者的发送顺序消费,比如这个时候我们消费的顺序为修改删除增加,如果该记录涉及到金额的话是不是会出大事情?
+
+那么,又 **如何解决消息的顺序消费问题** 呢?
+
+就拿我们上面所讲的分布式系统来说,用户购票完成之后是不是需要增加账户积分?在同一个系统中我们一般会使用事务来进行解决,如果用 `Spring` 的话我们在上面伪代码中加入 `@Transactional` 注解就好了。但是在不同系统中如何保证事务呢?总不能这个系统我扣钱成功了你那积分系统积分没加吧?或者说我这扣钱明明失败了,你那积分系统给我加了积分。
+
+那么,又如何 **解决分布式事务问题** 呢?
+
+我们刚刚说了,消息队列可以进行削峰操作,那如果我的消费者如果消费很慢或者生产者生产消息很快,这样是不是会将消息堆积在消息队列中?
+
+那么,又如何 **解决消息堆积的问题** 呢?
+
+可用性降低,复杂度上升,又带来一系列的重复消费,顺序消费,分布式事务,消息堆积的问题,这消息队列还怎么用啊 😵?
+
+
+
+别急,办法总是有的。
+
+## RocketMQ 是什么?
+
+
+
+哇,你个混蛋!上面给我抛出那么多问题,你现在又讲 `RocketMQ` ,还让不让人活了?!🤬
+
+别急别急,话说你现在清楚 `MQ` 的构造吗,我还没讲呢,我们先搞明白 `MQ` 的内部构造,再来看看如何解决上面的一系列问题吧,不过你最好带着问题去阅读和了解喔。
+
+`RocketMQ` 是一个 **队列模型** 的消息中间件,具有**高性能、高可靠、高实时、分布式** 的特点。它是一个采用 `Java` 语言开发的分布式的消息系统,由阿里巴巴团队开发,在 2016 年底贡献给 `Apache`,成为了 `Apache` 的一个顶级项目。 在阿里内部,`RocketMQ` 很好地服务了集团大大小小上千个应用,在每年的双十一当天,更有不可思议的万亿级消息通过 `RocketMQ` 流转。
+
+废话不多说,想要了解 `RocketMQ` 历史的同学可以自己去搜寻资料。听完上面的介绍,你只要知道 `RocketMQ` 很快、很牛、而且经历过双十一的实践就行了!
+
+## 队列模型和主题模型是什么?
+
+在谈 `RocketMQ` 的技术架构之前,我们先来了解一下两个名词概念——**队列模型** 和 **主题模型** 。
+
+首先我问一个问题,消息队列为什么要叫消息队列?
+
+你可能觉得很弱智,这玩意不就是存放消息的队列嘛?不叫消息队列叫什么?
+
+的确,早期的消息中间件是通过 **队列** 这一模型来实现的,可能是历史原因,我们都习惯把消息中间件成为消息队列。
+
+但是,如今例如 `RocketMQ`、`Kafka` 这些优秀的消息中间件不仅仅是通过一个 **队列** 来实现消息存储的。
+
+### 队列模型
+
+就像我们理解队列一样,消息中间件的队列模型就真的只是一个队列。。。我画一张图给大家理解。
+
+
+
+在一开始我跟你提到了一个 **“广播”** 的概念,也就是说如果我们此时我们需要将一个消息发送给多个消费者(比如此时我需要将信息发送给短信系统和邮件系统),这个时候单个队列即不能满足需求了。
+
+当然你可以让 `Producer` 生产消息放入多个队列中,然后每个队列去对应每一个消费者。问题是可以解决,创建多个队列并且复制多份消息是会很影响资源和性能的。而且,这样子就会导致生产者需要知道具体消费者个数然后去复制对应数量的消息队列,这就违背我们消息中间件的 **解耦** 这一原则。
+
+### 主题模型
+
+那么有没有好的方法去解决这一个问题呢?有,那就是 **主题模型** 或者可以称为 **发布订阅模型** 。
+
+> 感兴趣的同学可以去了解一下设计模式里面的观察者模式并且手动实现一下,我相信你会有所收获的。
+
+在主题模型中,消息的生产者称为 **发布者(Publisher)** ,消息的消费者称为 **订阅者(Subscriber)** ,存放消息的容器称为 **主题(Topic)** 。
+
+其中,发布者将消息发送到指定主题中,订阅者需要 **提前订阅主题** 才能接受特定主题的消息。
+
+
+
+### RocketMQ 中的消息模型
+
+`RocketMQ` 中的消息模型就是按照 **主题模型** 所实现的。你可能会好奇这个 **主题** 到底是怎么实现的呢?你上面也没有讲到呀!
+
+其实对于主题模型的实现来说每个消息中间件的底层设计都是不一样的,就比如 `Kafka` 中的 **分区** ,`RocketMQ` 中的 **队列** ,`RabbitMQ` 中的 `Exchange` 。我们可以理解为 **主题模型/发布订阅模型** 就是一个标准,那些中间件只不过照着这个标准去实现而已。
+
+所以,`RocketMQ` 中的 **主题模型** 到底是如何实现的呢?首先我画一张图,大家尝试着去理解一下。
+
+
+
+我们可以看到在整个图中有 `Producer Group`、`Topic`、`Consumer Group` 三个角色,我来分别介绍一下他们。
+
+- `Producer Group` 生产者组:代表某一类的生产者,比如我们有多个秒杀系统作为生产者,这多个合在一起就是一个 `Producer Group` 生产者组,它们一般生产相同的消息。
+- `Consumer Group` 消费者组:代表某一类的消费者,比如我们有多个短信系统作为消费者,这多个合在一起就是一个 `Consumer Group` 消费者组,它们一般消费相同的消息。
+- `Topic` 主题:代表一类消息,比如订单消息,物流消息等等。
+
+你可以看到图中生产者组中的生产者会向主题发送消息,而 **主题中存在多个队列**,生产者每次生产消息之后是指定主题中的某个队列发送消息的。
+
+每个主题中都有多个队列(分布在不同的 `Broker`中,如果是集群的话,`Broker`又分布在不同的服务器中),集群消费模式下,一个消费者集群多台机器共同消费一个 `topic` 的多个队列,**一个队列只会被一个消费者消费**。如果某个消费者挂掉,分组内其它消费者会接替挂掉的消费者继续消费。就像上图中 `Consumer1` 和 `Consumer2` 分别对应着两个队列,而 `Consumer3` 是没有队列对应的,所以一般来讲要控制 **消费者组中的消费者个数和主题中队列个数相同** 。
+
+当然也可以消费者个数小于队列个数,只不过不太建议。如下图。
+
+
+
+**每个消费组在每个队列上维护一个消费位置** ,为什么呢?
+
+因为我们刚刚画的仅仅是一个消费者组,我们知道在发布订阅模式中一般会涉及到多个消费者组,而每个消费者组在每个队列中的消费位置都是不同的。如果此时有多个消费者组,那么消息被一个消费者组消费完之后是不会删除的(因为其它消费者组也需要呀),它仅仅是为每个消费者组维护一个 **消费位移(offset)** ,每次消费者组消费完会返回一个成功的响应,然后队列再把维护的消费位移加一,这样就不会出现刚刚消费过的消息再一次被消费了。
+
+
+
+可能你还有一个问题,**为什么一个主题中需要维护多个队列** ?
+
+答案是 **提高并发能力** 。的确,每个主题中只存在一个队列也是可行的。你想一下,如果每个主题中只存在一个队列,这个队列中也维护着每个消费者组的消费位置,这样也可以做到 **发布订阅模式** 。如下图。
+
+
+
+但是,这样我生产者是不是只能向一个队列发送消息?又因为需要维护消费位置所以一个队列只能对应一个消费者组中的消费者,这样是不是其他的 `Consumer` 就没有用武之地了?从这两个角度来讲,并发度一下子就小了很多。
+
+所以总结来说,`RocketMQ` 通过**使用在一个 `Topic` 中配置多个队列并且每个队列维护每个消费者组的消费位置** 实现了 **主题模式/发布订阅模式** 。
+
+## RocketMQ 的架构图
+
+讲完了消息模型,我们理解起 `RocketMQ` 的技术架构起来就容易多了。
+
+`RocketMQ` 技术架构中有四大角色 `NameServer`、`Broker`、`Producer`、`Consumer` 。我来向大家分别解释一下这四个角色是干啥的。
+
+- `Broker`:主要负责消息的存储、投递和查询以及服务高可用保证。说白了就是消息队列服务器嘛,生产者生产消息到 `Broker` ,消费者从 `Broker` 拉取消息并消费。
+
+ 这里,我还得普及一下关于 `Broker`、`Topic` 和 队列的关系。上面我讲解了 `Topic` 和队列的关系——一个 `Topic` 中存在多个队列,那么这个 `Topic` 和队列存放在哪呢?
+
+ **一个 `Topic` 分布在多个 `Broker`上,一个 `Broker` 可以配置多个 `Topic` ,它们是多对多的关系**。
+
+ 如果某个 `Topic` 消息量很大,应该给它多配置几个队列(上文中提到了提高并发能力),并且 **尽量多分布在不同 `Broker` 上,以减轻某个 `Broker` 的压力** 。
+
+ `Topic` 消息量都比较均匀的情况下,如果某个 `broker` 上的队列越多,则该 `broker` 压力越大。
+
+ 
+
+ > 所以说我们需要配置多个 Broker。
+
+- `NameServer`:不知道你们有没有接触过 `ZooKeeper` 和 `Spring Cloud` 中的 `Eureka` ,它其实也是一个 **注册中心** ,主要提供两个功能:**Broker 管理** 和 **路由信息管理** 。说白了就是 `Broker` 会将自己的信息注册到 `NameServer` 中,此时 `NameServer` 就存放了很多 `Broker` 的信息(Broker 的路由表),消费者和生产者就从 `NameServer` 中获取路由表然后照着路由表的信息和对应的 `Broker` 进行通信(生产者和消费者定期会向 `NameServer` 去查询相关的 `Broker` 的信息)。
+
+- `Producer`:消息发布的角色,支持分布式集群方式部署。说白了就是生产者。
+
+- `Consumer`:消息消费的角色,支持分布式集群方式部署。支持以 push 推,pull 拉两种模式对消息进行消费。同时也支持集群方式和广播方式的消费,它提供实时消息订阅机制。说白了就是消费者。
+
+听完了上面的解释你可能会觉得,这玩意好简单。不就是这样的么?
+
+
+
+嗯?你可能会发现一个问题,这老家伙 `NameServer` 干啥用的,这不多余吗?直接 `Producer`、`Consumer` 和 `Broker` 直接进行生产消息,消费消息不就好了么?
+
+但是,我们上文提到过 `Broker` 是需要保证高可用的,如果整个系统仅仅靠着一个 `Broker` 来维持的话,那么这个 `Broker` 的压力会不会很大?所以我们需要使用多个 `Broker` 来保证 **负载均衡** 。
+
+如果说,我们的消费者和生产者直接和多个 `Broker` 相连,那么当 `Broker` 修改的时候必定会牵连着每个生产者和消费者,这样就会产生耦合问题,而 `NameServer` 注册中心就是用来解决这个问题的。
+
+> 如果还不是很理解的话,可以去看我介绍 `Spring Cloud` 的那篇文章,其中介绍了 `Eureka` 注册中心。
+
+当然,`RocketMQ` 中的技术架构肯定不止前面那么简单,因为上面图中的四个角色都是需要做集群的。我给出一张官网的架构图,大家尝试理解一下。
+
+
+
+其实和我们最开始画的那张乞丐版的架构图也没什么区别,主要是一些细节上的差别。听我细细道来 🤨。
+
+第一、我们的 `Broker` **做了集群并且还进行了主从部署** ,由于消息分布在各个 `Broker` 上,一旦某个 `Broker` 宕机,则该`Broker` 上的消息读写都会受到影响。所以 `Rocketmq` 提供了 `master/slave` 的结构,` salve` 定时从 `master` 同步数据(同步刷盘或者异步刷盘),如果 `master` 宕机,**则 `slave` 提供消费服务,但是不能写入消息** (后面我还会提到哦)。
+
+第二、为了保证 `HA` ,我们的 `NameServer` 也做了集群部署,但是请注意它是 **去中心化** 的。也就意味着它没有主节点,你可以很明显地看出 `NameServer` 的所有节点是没有进行 `Info Replicate` 的,在 `RocketMQ` 中是通过 **单个 Broker 和所有 NameServer 保持长连接** ,并且在每隔 30 秒 `Broker` 会向所有 `Nameserver` 发送心跳,心跳包含了自身的 `Topic` 配置信息,这个步骤就对应这上面的 `Routing Info` 。
+
+第三、在生产者需要向 `Broker` 发送消息的时候,**需要先从 `NameServer` 获取关于 `Broker` 的路由信息**,然后通过 **轮询** 的方法去向每个队列中生产数据以达到 **负载均衡** 的效果。
+
+第四、消费者通过 `NameServer` 获取所有 `Broker` 的路由信息后,向 `Broker` 发送 `Pull` 请求来获取消息数据。`Consumer` 可以以两种模式启动—— **广播(Broadcast)和集群(Cluster)**。广播模式下,一条消息会发送给 **同一个消费组中的所有消费者** ,集群模式下消息只会发送给一个消费者。
+
+## 如何解决顺序消费和重复消费?
+
+其实,这些东西都是我在介绍消息队列带来的一些副作用的时候提到的,也就是说,这些问题不仅仅挂钩于 `RocketMQ` ,而是应该每个消息中间件都需要去解决的。
+
+在上面我介绍 `RocketMQ` 的技术架构的时候我已经向你展示了 **它是如何保证高可用的** ,这里不涉及运维方面的搭建,如果你感兴趣可以自己去官网上照着例子搭建属于你自己的 `RocketMQ` 集群。
+
+> 其实 `Kafka` 的架构基本和 `RocketMQ` 类似,只是它注册中心使用了 `Zookeeper`、它的 **分区** 就相当于 `RocketMQ` 中的 **队列** 。还有一些小细节不同会在后面提到。
+
+### 顺序消费
+
+在上面的技术架构介绍中,我们已经知道了 **`RocketMQ` 在主题上是无序的、它只有在队列层面才是保证有序** 的。
+
+这又扯到两个概念——**普通顺序** 和 **严格顺序** 。
+
+所谓普通顺序是指 消费者通过 **同一个消费队列收到的消息是有顺序的** ,不同消息队列收到的消息则可能是无顺序的。普通顺序消息在 `Broker` **重启情况下不会保证消息顺序性** (短暂时间) 。
+
+所谓严格顺序是指 消费者收到的 **所有消息** 均是有顺序的。严格顺序消息 **即使在异常情况下也会保证消息的顺序性** 。
+
+但是,严格顺序看起来虽好,实现它可会付出巨大的代价。如果你使用严格顺序模式,`Broker` 集群中只要有一台机器不可用,则整个集群都不可用。你还用啥?现在主要场景也就在 `binlog` 同步。
+
+一般而言,我们的 `MQ` 都是能容忍短暂的乱序,所以推荐使用普通顺序模式。
+
+那么,我们现在使用了 **普通顺序模式** ,我们从上面学习知道了在 `Producer` 生产消息的时候会进行轮询(取决你的负载均衡策略)来向同一主题的不同消息队列发送消息。那么如果此时我有几个消息分别是同一个订单的创建、支付、发货,在轮询的策略下这 **三个消息会被发送到不同队列** ,因为在不同的队列此时就无法使用 `RocketMQ` 带来的队列有序特性来保证消息有序性了。
+
+
+
+那么,怎么解决呢?
+
+其实很简单,我们需要处理的仅仅是将同一语义下的消息放入同一个队列(比如这里是同一个订单),那我们就可以使用 **Hash 取模法** 来保证同一个订单在同一个队列中就行了。
+
+### 重复消费
+
+emmm,就两个字—— **幂等** 。在编程中一个*幂等* 操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。比如说,这个时候我们有一个订单的处理积分的系统,每当来一个消息的时候它就负责为创建这个订单的用户的积分加上相应的数值。可是有一次,消息队列发送给订单系统 FrancisQ 的订单信息,其要求是给 FrancisQ 的积分加上 500。但是积分系统在收到 FrancisQ 的订单信息处理完成之后返回给消息队列处理成功的信息的时候出现了网络波动(当然还有很多种情况,比如 Broker 意外重启等等),这条回应没有发送成功。
+
+那么,消息队列没收到积分系统的回应会不会尝试重发这个消息?问题就来了,我再发这个消息,万一它又给 FrancisQ 的账户加上 500 积分怎么办呢?
+
+所以我们需要给我们的消费者实现 **幂等** ,也就是对同一个消息的处理结果,执行多少次都不变。
+
+那么如何给业务实现幂等呢?这个还是需要结合具体的业务的。你可以使用 **写入 `Redis`** 来保证,因为 `Redis` 的 `key` 和 `value` 就是天然支持幂等的。当然还有使用 **数据库插入法** ,基于数据库的唯一键来保证重复数据不会被插入多条。
+
+不过最主要的还是需要 **根据特定场景使用特定的解决方案** ,你要知道你的消息消费是否是完全不可重复消费还是可以忍受重复消费的,然后再选择强校验和弱校验的方式。毕竟在 CS 领域还是很少有技术银弹的说法。
+
+而在整个互联网领域,幂等不仅仅适用于消息队列的重复消费问题,这些实现幂等的方法,也同样适用于,**在其他场景中来解决重复请求或者重复调用的问题** 。比如将 HTTP 服务设计成幂等的,**解决前端或者 APP 重复提交表单数据的问题** ,也可以将一个微服务设计成幂等的,解决 `RPC` 框架自动重试导致的 **重复调用问题** 。
+
+## RocketMQ 如何实现分布式事务?
+
+如何解释分布式事务呢?事务大家都知道吧?**要么都执行要么都不执行** 。在同一个系统中我们可以轻松地实现事务,但是在分布式架构中,我们有很多服务是部署在不同系统之间的,而不同服务之间又需要进行调用。比如此时我下订单然后增加积分,如果保证不了分布式事务的话,就会出现 A 系统下了订单,但是 B 系统增加积分失败或者 A 系统没有下订单,B 系统却增加了积分。前者对用户不友好,后者对运营商不利,这是我们都不愿意见到的。
+
+那么,如何去解决这个问题呢?
+
+如今比较常见的分布式事务实现有 2PC、TCC 和事务消息(half 半消息机制)。每一种实现都有其特定的使用场景,但是也有各自的问题,**都不是完美的解决方案**。
+
+在 `RocketMQ` 中使用的是 **事务消息加上事务反查机制** 来解决分布式事务问题的。我画了张图,大家可以对照着图进行理解。
+
+
+
+在第一步发送的 half 消息 ,它的意思是 **在事务提交之前,对于消费者来说,这个消息是不可见的** 。
+
+> 那么,如何做到写入消息但是对用户不可见呢?RocketMQ 事务消息的做法是:如果消息是 half 消息,将备份原消息的主题与消息消费队列,然后 **改变主题** 为 RMQ_SYS_TRANS_HALF_TOPIC。由于消费组未订阅该主题,故消费端无法消费 half 类型的消息,**然后 RocketMQ 会开启一个定时任务,从 Topic 为 RMQ_SYS_TRANS_HALF_TOPIC 中拉取消息进行消费**,根据生产者组获取一个服务提供者发送回查事务状态请求,根据事务状态来决定是提交或回滚消息。
+
+你可以试想一下,如果没有从第 5 步开始的 **事务反查机制** ,如果出现网路波动第 4 步没有发送成功,这样就会产生 MQ 不知道是不是需要给消费者消费的问题,他就像一个无头苍蝇一样。在 `RocketMQ` 中就是使用的上述的事务反查来解决的,而在 `Kafka` 中通常是直接抛出一个异常让用户来自行解决。
+
+你还需要注意的是,在 `MQ Server` 指向系统 B 的操作已经和系统 A 不相关了,也就是说在消息队列中的分布式事务是——**本地事务和存储消息到消息队列才是同一个事务**。这样也就产生了事务的**最终一致性**,因为整个过程是异步的,**每个系统只要保证它自己那一部分的事务就行了**。
+
+## 如何解决消息堆积问题?
+
+在上面我们提到了消息队列一个很重要的功能——**削峰** 。那么如果这个峰值太大了导致消息堆积在队列中怎么办呢?
+
+其实这个问题可以将它广义化,因为产生消息堆积的根源其实就只有两个——生产者生产太快或者消费者消费太慢。
+
+我们可以从多个角度去思考解决这个问题,当流量到峰值的时候是因为生产者生产太快,我们可以使用一些 **限流降级** 的方法,当然你也可以增加多个消费者实例去水平扩展增加消费能力来匹配生产的激增。如果消费者消费过慢的话,我们可以先检查 **是否是消费者出现了大量的消费错误** ,或者打印一下日志查看是否是哪一个线程卡死,出现了锁资源不释放等等的问题。
+
+> 当然,最快速解决消息堆积问题的方法还是增加消费者实例,不过 **同时你还需要增加每个主题的队列数量** 。
+>
+> 别忘了在 `RocketMQ` 中,**一个队列只会被一个消费者消费** ,如果你仅仅是增加消费者实例就会出现我一开始给你画架构图的那种情况。
+
+
+
+## 什么事回溯消费?
+
+回溯消费是指 `Consumer` 已经消费成功的消息,由于业务上需求需要重新消费,在`RocketMQ` 中, `Broker` 在向`Consumer` 投递成功消息后,**消息仍然需要保留** 。并且重新消费一般是按照时间维度,例如由于 `Consumer` 系统故障,恢复后需要重新消费 1 小时前的数据,那么 `Broker` 要提供一种机制,可以按照时间维度来回退消费进度。`RocketMQ` 支持按照时间回溯消费,时间维度精确到毫秒。
+
+这是官方文档的解释,我直接照搬过来就当科普了 😁😁😁。
+
+## RocketMQ 的刷盘机制
+
+上面我讲了那么多的 `RocketMQ` 的架构和设计原理,你有没有好奇
+
+在 `Topic` 中的 **队列是以什么样的形式存在的?**
+
+**队列中的消息又是如何进行存储持久化的呢?**
+
+我在上文中提到的 **同步刷盘** 和 **异步刷盘** 又是什么呢?它们会给持久化带来什么样的影响呢?
+
+下面我将给你们一一解释。
+
+### 同步刷盘和异步刷盘
+
+
+
+如上图所示,在同步刷盘中需要等待一个刷盘成功的 `ACK` ,同步刷盘对 `MQ` 消息可靠性来说是一种不错的保障,但是 **性能上会有较大影响** ,一般地适用于金融等特定业务场景。
+
+而异步刷盘往往是开启一个线程去异步地执行刷盘操作。消息刷盘采用后台异步线程提交的方式进行, **降低了读写延迟** ,提高了 `MQ` 的性能和吞吐量,一般适用于如发验证码等对于消息保证要求不太高的业务场景。
+
+一般地,**异步刷盘只有在 `Broker` 意外宕机的时候会丢失部分数据**,你可以设置 `Broker` 的参数 `FlushDiskType` 来调整你的刷盘策略(ASYNC_FLUSH 或者 SYNC_FLUSH)。
+
+### 同步复制和异步复制
+
+上面的同步刷盘和异步刷盘是在单个结点层面的,而同步复制和异步复制主要是指的 `Borker` 主从模式下,主节点返回消息给客户端的时候是否需要同步从节点。
+
+- 同步复制:也叫 “同步双写”,也就是说,**只有消息同步双写到主从节点上时才返回写入成功** 。
+- 异步复制:**消息写入主节点之后就直接返回写入成功** 。
+
+然而,很多事情是没有完美的方案的,就比如我们进行消息写入的节点越多就更能保证消息的可靠性,但是随之的性能也会下降,所以需要程序员根据特定业务场景去选择适应的主从复制方案。
+
+那么,**异步复制会不会也像异步刷盘那样影响消息的可靠性呢?**
+
+答案是不会的,因为两者就是不同的概念,对于消息可靠性是通过不同的刷盘策略保证的,而像异步同步复制策略仅仅是影响到了 **可用性** 。为什么呢?其主要原因**是 `RocketMQ` 是不支持自动主从切换的,当主节点挂掉之后,生产者就不能再给这个主节点生产消息了**。
+
+比如这个时候采用异步复制的方式,在主节点还未发送完需要同步的消息的时候主节点挂掉了,这个时候从节点就少了一部分消息。但是此时生产者无法再给主节点生产消息了,**消费者可以自动切换到从节点进行消费**(仅仅是消费),所以在主节点挂掉的时间只会产生主从结点短暂的消息不一致的情况,降低了可用性,而当主节点重启之后,从节点那部分未来得及复制的消息还会继续复制。
+
+在单主从架构中,如果一个主节点挂掉了,那么也就意味着整个系统不能再生产了。那么这个可用性的问题能否解决呢?**一个主从不行那就多个主从的呗**,别忘了在我们最初的架构图中,每个 `Topic` 是分布在不同 `Broker` 中的。
+
+
+
+但是这种复制方式同样也会带来一个问题,那就是无法保证 **严格顺序** 。在上文中我们提到了如何保证的消息顺序性是通过将一个语义的消息发送在同一个队列中,使用 `Topic` 下的队列来保证顺序性的。如果此时我们主节点 A 负责的是订单 A 的一系列语义消息,然后它挂了,这样其他节点是无法代替主节点 A 的,如果我们任意节点都可以存入任何消息,那就没有顺序性可言了。
+
+而在 `RocketMQ` 中采用了 `Dledger` 解决这个问题。他要求在写入消息的时候,要求**至少消息复制到半数以上的节点之后**,才给客⼾端返回写⼊成功,并且它是⽀持通过选举来动态切换主节点的。这里我就不展开说明了,读者可以自己去了解。
+
+> 也不是说 `Dledger` 是个完美的方案,至少在 `Dledger` 选举过程中是无法提供服务的,而且他必须要使用三个节点或以上,如果多数节点同时挂掉他也是无法保证可用性的,而且要求消息复制半数以上节点的效率和直接异步复制还是有一定的差距的。
+
+### 存储机制
+
+还记得上面我们一开始的三个问题吗?到这里第三个问题已经解决了。
+
+但是,在 `Topic` 中的 **队列是以什么样的形式存在的?队列中的消息又是如何进行存储持久化的呢?** 还未解决,其实这里涉及到了 `RocketMQ` 是如何设计它的存储结构了。我首先想大家介绍 `RocketMQ` 消息存储架构中的三大角色——`CommitLog`、`ConsumeQueue` 和 `IndexFile` 。
+
+- `CommitLog`:**消息主体以及元数据的存储主体**,存储 `Producer` 端写入的消息主体内容,消息内容不是定长的。单个文件大小默认 1G ,文件名长度为 20 位,左边补零,剩余为起始偏移量,比如 00000000000000000000 代表了第一个文件,起始偏移量为 0,文件大小为 1G=1073741824;当第一个文件写满了,第二个文件为 00000000001073741824,起始偏移量为 1073741824,以此类推。消息主要是**顺序写入日志文件**,当文件满了,写入下一个文件。
+- `ConsumeQueue`:消息消费队列,**引入的目的主要是提高消息消费的性能**(我们再前面也讲了),由于`RocketMQ` 是基于主题 `Topic` 的订阅模式,消息消费是针对主题进行的,如果要遍历 `commitlog` 文件中根据 `Topic` 检索消息是非常低效的。`Consumer` 即可根据 `ConsumeQueue` 来查找待消费的消息。其中,`ConsumeQueue`(逻辑消费队列)**作为消费消息的索引**,保存了指定 `Topic` 下的队列消息在 `CommitLog` 中的**起始物理偏移量 `offset` **,消息大小 `size` 和消息 `Tag` 的 `HashCode` 值。**`consumequeue` 文件可以看成是基于 `topic` 的 `commitlog` 索引文件**,故 `consumequeue` 文件夹的组织方式如下:topic/queue/file 三层组织结构,具体存储路径为:$HOME/store/consumequeue/{topic}/{queueId}/{fileName}。同样 `consumequeue` 文件采取定长设计,每一个条目共 20 个字节,分别为 8 字节的 `commitlog` 物理偏移量、4 字节的消息长度、8 字节 tag `hashcode`,单个文件由 30W 个条目组成,可以像数组一样随机访问每一个条目,每个 `ConsumeQueue`文件大小约 5.72M;
+- `IndexFile`:`IndexFile`(索引文件)提供了一种可以通过 key 或时间区间来查询消息的方法。这里只做科普不做详细介绍。
+
+总结来说,整个消息存储的结构,最主要的就是 `CommitLoq` 和 `ConsumeQueue` 。而 `ConsumeQueue` 你可以大概理解为 `Topic` 中的队列。
+
+
+
+`RocketMQ` 采用的是 **混合型的存储结构** ,即为 `Broker` 单个实例下所有的队列共用一个日志数据文件来存储消息。有意思的是在同样高并发的 `Kafka` 中会为每个 `Topic` 分配一个存储文件。这就有点类似于我们有一大堆书需要装上书架,`RockeMQ` 是不分书的种类直接成批的塞上去的,而 `Kafka` 是将书本放入指定的分类区域的。
+
+而 `RocketMQ` 为什么要这么做呢?原因是 **提高数据的写入效率** ,不分 `Topic` 意味着我们有更大的几率获取 **成批** 的消息进行数据写入,但也会带来一个麻烦就是读取消息的时候需要遍历整个大文件,这是非常耗时的。
+
+所以,在 `RocketMQ` 中又使用了 `ConsumeQueue` 作为每个队列的索引文件来 **提升读取消息的效率**。我们可以直接根据队列的消息序号,计算出索引的全局位置(索引序号\*索引固定⻓度 20),然后直接读取这条索引,再根据索引中记录的消息的全局位置,找到消息。
+
+讲到这里,你可能对 `RockeMQ` 的存储架构还有些模糊,没事,我们结合着图来理解一下。
+
+
+
+emmm,是不是有一点复杂 🤣,看英文图片和英文文档的时候就不要怂,硬着头皮往下看就行。
+
+> 如果上面没看懂的读者一定要认真看下面的流程分析!
+
+首先,在最上面的那一块就是我刚刚讲的你现在可以直接 **把 `ConsumerQueue` 理解为 `Queue`**。
+
+在图中最左边说明了红色方块代表被写入的消息,虚线方块代表等待被写入的。左边的生产者发送消息会指定 `Topic`、`QueueId` 和具体消息内容,而在 `Broker` 中管你是哪门子消息,他直接 **全部顺序存储到了 CommitLog**。而根据生产者指定的 `Topic` 和 `QueueId` 将这条消息本身在 `CommitLog` 的偏移(offset),消息本身大小,和 tag 的 hash 值存入对应的 `ConsumeQueue` 索引文件中。而在每个队列中都保存了 `ConsumeOffset` 即每个消费者组的消费位置(我在架构那里提到了,忘了的同学可以回去看一下),而消费者拉取消息进行消费的时候只需要根据 `ConsumeOffset` 获取下一个未被消费的消息就行了。
+
+上述就是我对于整个消息存储架构的大概理解(这里不涉及到一些细节讨论,比如稀疏索引等等问题),希望对你有帮助。
+
+因为有一个知识点因为写嗨了忘讲了,想想在哪里加也不好,所以我留给大家去思考 🤔🤔 一下吧。
+
+
+
+为什么 `CommitLog` 文件要设计成固定大小的长度呢?提醒:**内存映射机制**。
+
+## 总结
+
+总算把这篇博客写完了。我讲的你们还记得吗 😅?
+
+这篇文章中我主要想大家介绍了
+
+1. 消息队列出现的原因
+2. 消息队列的作用(异步,解耦,削峰)
+3. 消息队列带来的一系列问题(消息堆积、重复消费、顺序消费、分布式事务等等)
+4. 消息队列的两种消息模型——队列和主题模式
+5. 分析了 `RocketMQ` 的技术架构(`NameServer`、`Broker`、`Producer`、`Comsumer`)
+6. 结合 `RocketMQ` 回答了消息队列副作用的解决方案
+7. 介绍了 `RocketMQ` 的存储机制和刷盘策略。
+
+等等。。。
diff --git "a/docs/high-performance/\350\257\273\345\206\231\345\210\206\347\246\273&\345\210\206\345\272\223\345\210\206\350\241\250.md" b/docs/high-performance/read-and-write-separation-and-library-subtable.md
similarity index 56%
rename from "docs/high-performance/\350\257\273\345\206\231\345\210\206\347\246\273&\345\210\206\345\272\223\345\210\206\350\241\250.md"
rename to docs/high-performance/read-and-write-separation-and-library-subtable.md
index 4937d9fd7f3c961899c5b0a7e7aa9ec4cbf8d1c4..38c78977e082d198f84f8eda26bc51b32ad4b160 100644
--- "a/docs/high-performance/\350\257\273\345\206\231\345\210\206\347\246\273&\345\210\206\345\272\223\345\210\206\350\241\250.md"
+++ b/docs/high-performance/read-and-write-separation-and-library-subtable.md
@@ -1,24 +1,24 @@
-# 读写分离&分库分表
-
-大家好呀!今天和小伙伴们聊聊读写分离以及分库分表。
-
-相信很多小伙伴们对于这两个概念已经比较熟悉了,这篇文章全程都是大白话的形式,希望能够给你带来不一样的感受。
-
-如果你之前不太了解这两个概念,那我建议你搞懂之后,可以把自己对于读写分离以及分库分表的理解讲给你的同事/朋友听听。
-
-**原创不易,若有帮助,点赞/分享就是对我最大的鼓励!**
-
-_个人能力有限。如果文章有任何需要补充/完善/修改的地方,欢迎在评论区指出,共同进步!_
+---
+title: 读写分离和分库分表常见问题总结
+category: 高性能
+head:
+ - - meta
+ - name: keywords
+ content: 读写分离,分库分表,主从复制
+ - - meta
+ - name: description
+ content: 读写分离主要是为了将对数据库的读写操作分散到不同的数据库节点上。 这样的话,就能够小幅提升写性能,大幅提升读性能。 读写分离基于主从复制,MySQL 主从复制是依赖于 binlog 。分库就是将数据库中的数据分散到不同的数据库上。分表就是对单表的数据进行拆分,可以是垂直拆分,也可以是水平拆分。引入分库分表之后,需要系统解决事务、分布式 id、无法 join 操作问题。
+---
## 读写分离
-### 何为读写分离?
+### 什么是读写分离?
见名思意,根据读写分离的名字,我们就可以知道:**读写分离主要是为了将对数据库的读写操作分散到不同的数据库节点上。** 这样的话,就能够小幅提升写性能,大幅提升读性能。
我简单画了一张图来帮助不太清楚读写分离的小伙伴理解。
-
+
一般情况下,我们都会选择一主多从,也就是一台主数据库负责写,其他的从数据库负责读。主库和从库之间会进行数据同步,以保证从库中数据的准确性。这样的架构实现起来比较简单,并且也符合系统的写少读多的特点。
@@ -60,29 +60,29 @@ hintManager.setMasterRouteOnly();
落实到项目本身的话,常用的方式有两种:
-**1.代理方式**
+**1. 代理方式**
-
+
我们可以在应用和数据中间加了一个代理层。应用程序所有的数据请求都交给代理层处理,代理层负责分离读写请求,将它们路由到对应的数据库中。
-提供类似功能的中间件有 **MySQL Router**(官方)、**Atlas**(基于 MySQL Proxy)、**Maxscale**、**MyCat**。
+提供类似功能的中间件有 **MySQL Router**(官方)、**Atlas**(基于 MySQL Proxy)、**MaxScale**、**MyCat**。
-**2.组件方式**
+**2. 组件方式**
在这种方式中,我们可以通过引入第三方组件来帮助我们读写请求。
这也是我比较推荐的一种方式。这种方式目前在各种互联网公司中用的最多的,相关的实际的案例也非常多。如果你要采用这种方式的话,推荐使用 `sharding-jdbc` ,直接引入 jar 包即可使用,非常方便。同时,也节省了很多运维的成本。
-你可以在 shardingsphere 官方找到[sharding-jdbc 关于读写分离的操作](https://shardingsphere.apache.org/document/legacy/3.x/document/cn/manual/sharding-jdbc/usage/read-write-splitting/)。
+你可以在 shardingsphere 官方找到 [sharding-jdbc 关于读写分离的操作](https://shardingsphere.apache.org/document/legacy/3.x/document/cn/manual/sharding-jdbc/usage/read-write-splitting/)。
-### 主从复制原理了解么?
+### 主从复制原理是什么?
MySQL binlog(binary log 即二进制日志文件) 主要记录了 MySQL 数据库中数据的所有变化(数据库执行的所有 DDL 和 DML 语句)。因此,我们根据主库的 MySQL binlog 日志就能够将主库的数据同步到从库中。
更具体和详细的过程是这个样子的(图片来自于:[《MySQL Master-Slave Replication on the Same Machine》](https://www.toptal.com/mysql/mysql-master-slave-replication-tutorial)):
-
+
1. 主库将数据库中数据的变化写入到 binlog
2. 从库连接主库
@@ -113,42 +113,55 @@ MySQL binlog(binary log 即二进制日志文件) 主要记录了 MySQL 数据
答案之一就是 **分库分表**。
-### 何为分库?
+### 什么是分库?
-**分库** 就是将数据库中的数据分散到不同的数据库上。
+**分库** 就是将数据库中的数据分散到不同的数据库上,可以垂直分库,也可以水平分库。
-下面这些操作都涉及到了分库:
+**垂直分库** 就是把单一数据库按照业务进行划分,不同的业务使用不同的数据库,进而将一个数据库的压力分担到多个数据库。
-- 你将数据库中的用户表和用户订单表分别放在两个不同的数据库。
-- 由于用户表数据量太大,你对用户表进行了水平切分,然后将切分后的 2 张用户表分别放在两个不同的数据库。
+举个例子:说你将数据库中的用户表、订单表和商品表分别单独拆分为用户数据库、订单数据库和商品数据库。
-### 何为分表?
+
-**分表** 就是对单表的数据进行拆分,可以是垂直拆分,也可以是水平拆分。
+**水平分库** 是把同一个表按一定规则拆分到不同的数据库中,每个库可以位于不同的服务器上,这样就实现了水平扩展,解决了单表的存储和性能瓶颈的问题。
-**何为垂直拆分?**
+举个例子:订单表数据量太大,你对订单表进行了水平切分(水平分表),然后将切分后的 2 张订单表分别放在两个不同的数据库。
-简单来说,垂直拆分是对数据表列的拆分,把一张列比较多的表拆分为多张表。
+
-举个例子:我们可以将用户信息表中的一些列单独抽出来作为一个表。
+### 什么是分表?
+
+**分表** 就是对单表的数据进行拆分,可以是垂直拆分,也可以是水平拆分。
-**何为水平拆分?**
+**垂直分表** 是对数据表列的拆分,把一张列比较多的表拆分为多张表。
+
+举个例子:我们可以将用户信息表中的一些列单独抽出来作为一个表。
-简单来说,水平拆分是对数据表行的拆分,把一张行比较多的表拆分为多张表。
+**水平分表** 是对数据表行的拆分,把一张行比较多的表拆分为多张表,可以解决单一表数据量过大的问题。
举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。
-[《从零开始学架构》](https://time.geekbang.org/column/intro/100006601?code=i00Nq3pHUcUj04ZWy70NCRl%2FD2Lfj8GVzcGzZ3Wf5Ug%3D) 中的有一张图片对于垂直拆分和水平拆分的描述还挺直观的。
+水平拆分只能解决单表数据量大的问题,为了提升性能,我们通常会选择将拆分后的多张表放在不同的数据库中。也就是说,水平分表通常和水平分库同时出现。
-
+
### 什么情况下需要分库分表?
遇到下面几种场景可以考虑分库分表:
-- 单表的数据达到千万级别以上,数据库读写速度比较缓慢(分表)。
-- 数据库中的数据占用的空间越来越大,备份时间越来越长(分库)。
-- 应用的并发量太大(分库)。
+- 单表的数据达到千万级别以上,数据库读写速度比较缓慢。
+- 数据库中的数据占用的空间越来越大,备份时间越来越长。
+- 应用的并发量太大。
+
+### 常见的分片算法有哪些?
+
+分片算法主要解决了数据被水平分片之后,数据究竟该存放在哪个表的问题。
+
+- **哈希分片**:求指定 key(比如 id) 的哈希,然后根据哈希值确定数据应被放置在哪个表中。哈希分片比较适合随机读写的场景,不太适合经常需要范围查询的场景。
+- **范围分片**:按照特性的范围区间(比如时间区间、ID 区间)来分配数据,比如 将 `id` 为 `1~299999` 的记录分到第一个库, `300000~599999` 的分到第二个库。范围分片适合需要经常进行范围查找的场景,不太适合随机读写的场景(数据未被分散,容易出现热点数据的问题)。
+- **地理位置分片**:很多 NewSQL 数据库都支持地理位置分片算法,也就是根据地理位置(如城市、地域)来分配数据。
+- **融合算法**:灵活组合多种分片算法,比如将哈希分片和范围分片组合。
+- ......
### 分库分表会带来什么问题呢?
@@ -156,20 +169,34 @@ MySQL binlog(binary log 即二进制日志文件) 主要记录了 MySQL 数据
引入分库分表之后,会给系统带来什么挑战呢?
-- **join 操作** : 同一个数据库中的表分布在了不同的数据库中,导致无法使用 join 操作。这样就导致我们需要手动进行数据的封装,比如你在一个数据库中查询到一个数据之后,再根据这个数据去另外一个数据库中找对应的数据。
-- **事务问题** :同一个数据库中的表分布在了不同的数据库中,如果单个操作涉及到多个数据库,那么数据库自带的事务就无法满足我们的要求了。
-- **分布式 id** :分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。我们如何为不同的数据节点生成全局唯一主键呢?这个时候,我们就需要为我们的系统引入分布式 id 了。
+- **join 操作**:同一个数据库中的表分布在了不同的数据库中,导致无法使用 join 操作。这样就导致我们需要手动进行数据的封装,比如你在一个数据库中查询到一个数据之后,再根据这个数据去另外一个数据库中找对应的数据。
+- **事务问题**:同一个数据库中的表分布在了不同的数据库中,如果单个操作涉及到多个数据库,那么数据库自带的事务就无法满足我们的要求了。
+- **分布式 id**:分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键已经没办法满足生成的主键唯一了。我们如何为不同的数据节点生成全局唯一主键呢?这个时候,我们就需要为我们的系统引入分布式 id 了。
- ......
另外,引入分库分表之后,一般需要 DBA 的参与,同时还需要更多的数据库服务器,这些都属于成本。
### 分库分表有没有什么比较推荐的方案?
+Apache ShardingSphere 是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。
+
ShardingSphere 项目(包括 Sharding-JDBC、Sharding-Proxy 和 Sharding-Sidecar)是当当捐入 Apache 的,目前主要由京东数科的一些巨佬维护。
-
+ShardingSphere 绝对可以说是当前分库分表的首选!ShardingSphere 的功能完善,除了支持读写分离和分库分表,还提供分布式事务、数据库治理、影子库、数据加密和脱敏等功能。
-ShardingSphere 绝对可以说是当前分库分表的首选!ShardingSphere 的功能完善,除了支持读写分离和分库分表,还提供分布式事务、数据库治理等功能。
+ShardingSphere 提供的功能如下:
+
+
+
+ShardingSphere 的优势如下(摘自 ShardingSphere 官方文档: ):
+
+- 极致性能:驱动程序端历经长年打磨,效率接近原生 JDBC,性能极致。
+- 生态兼容:代理端支持任何通过 MySQL/PostgreSQL 协议的应用访问,驱动程序端可对接任意实现 JDBC 规范的数据库。
+- 业务零侵入:面对数据库替换场景,ShardingSphere 可满足业务无需改造,实现平滑业务迁移。
+- 运维低成本:在保留原技术栈不变前提下,对 DBA 学习、管理成本低,交互友好。
+- 安全稳定:基于成熟数据库底座之上提供增量能力,兼顾安全性及稳定性。
+- 弹性扩展:具备计算、存储平滑在线扩展能力,可满足业务多变的需求。
+- 开放生态:通过多层次(内核、功能、生态)插件化能力,为用户提供可定制满足自身特殊需求的独有系统。
另外,ShardingSphere 的生态体系完善,社区活跃,文档完善,更新和发布比较频繁。
@@ -188,3 +215,11 @@ ShardingSphere 绝对可以说是当前分库分表的首选!ShardingSphere
- 重复上一步的操作,直到老库和新库的数据一致为止。
想要在项目中实施双写还是比较麻烦的,很容易会出现问题。我们可以借助上面提到的数据库同步工具 Canal 做增量数据迁移(还是依赖 binlog,开发和维护成本较低)。
+
+## 总结
+
+- 读写分离主要是为了将对数据库的读写操作分散到不同的数据库节点上。 这样的话,就能够小幅提升写性能,大幅提升读性能。
+- 读写分离基于主从复制,MySQL 主从复制是依赖于 binlog 。
+- **分库** 就是将数据库中的数据分散到不同的数据库上。**分表** 就是对单表的数据进行拆分,可以是垂直拆分,也可以是水平拆分。
+- 引入分库分表之后,需要系统解决事务、分布式 id、无法 join 操作问题。
+- ShardingSphere 绝对可以说是当前分库分表的首选!ShardingSphere 的功能完善,除了支持读写分离和分库分表,还提供分布式事务、数据库治理等功能。另外,ShardingSphere 的生态体系完善,社区活跃,文档完善,更新和发布比较频繁。
diff --git a/docs/high-performance/sql-optimization.md b/docs/high-performance/sql-optimization.md
new file mode 100644
index 0000000000000000000000000000000000000000..ffd444fc3dcf7ef803b6cf0a1bee22bd35090d99
--- /dev/null
+++ b/docs/high-performance/sql-optimization.md
@@ -0,0 +1,17 @@
+---
+title: 常见SQL优化手段总结(付费)
+category: 高性能
+head:
+ - - meta
+ - name: keywords
+ content: 分页优化,索引,Show Profile,慢 SQL
+ - - meta
+ - name: description
+ content: SQL 优化是一个大家都比较关注的热门话题,无论你在面试,还是工作中,都很有可能会遇到。如果某天你负责的某个线上接口,出现了性能问题,需要做优化。那么你首先想到的很有可能是优化 SQL 优化,因为它的改造成本相对于代码来说也要小得多。
+---
+
+**常见 SQL 优化手段总结** 相关的内容为我的[知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)(点击链接即可查看详细介绍以及加入方法)专属内容,已经整理到了[《Java 面试指北》](https://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)中。
+
+
+
+
diff --git "a/docs/high-performance/\350\264\237\350\275\275\345\235\207\350\241\241.md" "b/docs/high-performance/\350\264\237\350\275\275\345\235\207\350\241\241.md"
deleted file mode 100644
index a9d98b2cea58821f040fdad69e0a2424b2769cc7..0000000000000000000000000000000000000000
--- "a/docs/high-performance/\350\264\237\350\275\275\345\235\207\350\241\241.md"
+++ /dev/null
@@ -1,13 +0,0 @@
-# 负载均衡
-
-负载均衡系统通常用于将任务比如用户请求处理分配到多个服务器处理以提高网站、应用或者数据库的性能和可靠性。
-
-常见的负载均衡系统包括 3 种:
-
-1. **DNS 负载均衡** :一般用来实现地理级别的均衡。
-2. **硬件负载均衡** : 通过单独的硬件设备比如 F5 来实现负载均衡功能(硬件的价格一般很贵)。
-3. **软件负载均衡** :通过负载均衡软件比如 Nginx 来实现负载均衡功能。
-
-## 推荐阅读
-
-- [《凤凰架构》-负载均衡](http://icyfenix.cn/architect-perspective/general-architecture/diversion-system/load-balancing.html)
diff --git a/docs/high-quality-technical-articles/advanced-programmer/20-bad-habits-of-bad-programmers.md b/docs/high-quality-technical-articles/advanced-programmer/20-bad-habits-of-bad-programmers.md
new file mode 100644
index 0000000000000000000000000000000000000000..bb7fb8f3b15722082d403f7e7b026d5a5757d7c1
--- /dev/null
+++ b/docs/high-quality-technical-articles/advanced-programmer/20-bad-habits-of-bad-programmers.md
@@ -0,0 +1,146 @@
+---
+title: 糟糕程序员的 20 个坏习惯
+category: 技术文章精选集
+author: Kaito
+tag:
+ - 练级攻略
+---
+
+> **推荐语**:Kaito 大佬的一篇文章,很实用的建议!
+>
+>
+>
+> **原文地址:**
+
+我想你肯定遇到过这样一类程序员:**他们无论是写代码,还是写文档,又或是和别人沟通,都显得特别专业**。每次遇到这类人,我都在想,他们到底是怎么做到的?
+
+随着工作时间的增长,渐渐地我也总结出一些经验,他们身上都保持着一些看似很微小的优秀习惯,但正是因为这些习惯,体现出了一个优秀程序员的基本素养。
+
+但今天我们来换个角度,来看看一个糟糕程序员有哪些坏习惯?只要我们都能避开这些问题,就可以逐渐向一个优秀程序员靠近。
+
+## 1、技术名词拼写不规范
+
+无论是个人简历,还是技术文档,我经常看到拼写不规范的技术名词,例如 JAVA、javascript、python、MySql、Hbase、restful。
+
+正确的拼写应该是 Java、JavaScript、Python、MySQL、HBase、RESTful,不要小看这个问题,很多面试官很有可能因为这一点刷掉你的简历。
+
+## 2、写文档,中英文混排不规范
+
+中文描述使用英文标点符号,英文和数字使用了全角字符,中文与英文、数字之间没有空格等等。
+
+其中很多人会忽视中文和英文、数字之间加一个「空格」,这样排版阅读起来会更舒服。之前我的文章排版,都是遵循了这些细节。
+
+## 3、重要逻辑不写注释,或写得很拖沓
+
+复杂且重要的逻辑代码,很多程序员不写注释,除了自己能看懂代码逻辑,其他人根本看不懂。或者是注释虽然写了,但写得很拖沓,没有逻辑可言。
+
+重要的逻辑不止要写注释,还要写得简洁、清晰。如果是一眼就能读懂的简单代码,可以不加注释。
+
+## 4、写复杂冗长的函数
+
+一个函数几百行,一个文件上千行代码,复杂函数不做拆分,导致代码变得越来越难维护,最后谁也不敢动。
+
+基本的设计模式还是要遵守的,例如单一职责,一个函数只做一件事,开闭原则,对扩展开放,对修改关闭。
+
+如果函数逻辑确实复杂,也至少要保证主干逻辑足够清晰。
+
+## 5、不看官方文档,只看垃圾博客
+
+很多人遇到问题不先去看官方文档,而是热衷于去看垃圾博客,这些博客的内容都是互相抄袭,错误百出。
+
+其实很多软件官方文档写得已经非常好了,常见问题都能找到答案,认真读一读官方文档,比看垃圾博客强一百倍,要养成看官方文档的好习惯。
+
+## 6、宣扬内功无用论
+
+有些人天天追求日新月异的开源项目和框架,却不肯花时间去啃一啃底层原理,常见问题虽然可以解决,但遇到稍微深一点的问题就束手无策。
+
+很多高大上的架构设计,思路其实都源于底层。想一想,像计算机体系结构、操作系统、网络协议这些东西,经过多少年演进才变为现在的样子,演进过程中遇到的复杂问题比比皆是,理解了解决这些问题的思路,再看上层技术会变得很简单。
+
+## 7、乐于炫技
+
+有些人天天把「高大上」的技术名词挂在嘴边,生怕别人不知道自己学了什么高深技术,嘴上乐于炫技,但别人一问他细节就会哑口无言。
+
+## 8、不接受质疑
+
+自己设计的方案,别人提出疑问时只会回怼,而不是理性分析利弊,抱着学习的心态交流。
+
+这些人学了点东西就觉得自己很有本事,殊不知只是自己见识太少。
+
+## 9、接口协议不规范
+
+和别人定 API 协议全靠口头沟通,不给规范的文档说明,甚至到了测试联调时会发现,竟然和协商的还不一样,或者改协议了却不通知对接方,合作体验极差。
+
+## 10、遇到问题自己死磕
+
+很初级程序员容易犯的问题,遇到问题只会自己死磕,拖到 deadline 也没有产出,领导来问才知道有问题解决不了。
+
+有问题及时反馈才是对自己负责,对团队负责。
+
+## 11、一说就会,一写就废
+
+平时技术方案吹得天花乱坠,一让他写代码就废,典型的眼高手低选手。
+
+## 12、表达没有逻辑,不站在对方角度看问题
+
+讨论问题不交代背景,上来就说自己的方案,别人听得云里雾里,让你从头描述你又讲不明白。
+
+学会沟通和表达,是合作的基础。
+
+## 13、不主动思考,伸手党
+
+遇到问题不去 google,不做思考就向别人提问,喜欢做伸手党。
+
+每个人的时间都很宝贵,大家都更喜欢你带着自己的思考来提问,一来可以规避很多低级问题,二来可以提高交流质量。
+
+## 14、经常犯重复的错误
+
+出问题后说下次会注意,但下次问题依旧,对自己不负责任,说到底是态度问题。
+
+## 15、加功能不考虑扩展性
+
+加新功能只关注某一小块业务,不考虑系统整体的扩展性,堆代码行为严重。
+
+要学会分析需求和未来可能发生的变化,设计更通用的解决方案,降低后期开发成本。
+
+## 16、接口不自测,出问题不打日志
+
+自己开发的接口不自测就和别人联调,出了问题又说没打日志,协作效率极低。
+
+## 17、提交代码不规范
+
+很多人提交代码不写描述,或者写的是无意义的描述,尤其是修改很少代码时,这种情况会导致回溯问题成本变高。
+
+制定代码提交规范,能让你在每一次提交代码时,不会做太随意的代码修改。
+
+## 18、手动修改生产环境数据库
+
+直连生产环境数据库修改数据,更有 UPDATE / DELETE SQL 忘写 WHERE 条件的情况,产生数据事故。
+
+修改生产环境数据库一定要谨慎再谨慎,建议操作前先找同事 review 代码再操作。
+
+## 19、没理清需求就直接写代码
+
+很多程序员接到需求后,不怎么思考就开始写代码,需求和自己理解的有偏差,造成无意义返工。
+
+多花些时间梳理需求,能规避很多不合理的问题。
+
+## 20、重要设计不写文档
+
+重要的设计没有文档输出,和别人交接系统时只做口头描述,丢失关键信息。
+
+有时候理解一个设计方案,一个好的文档要比看几百行代码更高效。
+
+## 总结
+
+以上这些不良习惯,你命中几个呢?或者你身边有没有碰到这样的人?
+
+我认为提早规避这些问题,是成为一个优秀程序员必须要做的。这些习惯总结起来大致分为这 4 个方面:
+
+- 良好的编程修养
+- 谦虚的学习心态
+- 良好的沟通和表达
+- 注重团队协作
+
+优秀程序员的专业技能,我们可能很难在短时间内学会,但这些基本的职业素养,是可以在短期内做到的。
+
+希望你我可以有则改之,无则加勉。
diff --git a/docs/high-quality-technical-articles/advanced-programmer/seven-tips-for-becoming-an-advanced-programmer.md b/docs/high-quality-technical-articles/advanced-programmer/seven-tips-for-becoming-an-advanced-programmer.md
new file mode 100644
index 0000000000000000000000000000000000000000..0284e68abca3286f4ce71346a1b709fa3e227c58
--- /dev/null
+++ b/docs/high-quality-technical-articles/advanced-programmer/seven-tips-for-becoming-an-advanced-programmer.md
@@ -0,0 +1,107 @@
+---
+title: 给想成长为高级别开发同学的七条建议
+category: 技术文章精选集
+author: Kaito
+tag:
+ - 练级攻略
+---
+
+> **推荐语**:普通程序员要想成长为高级程序员甚至是专家等更高级别,应该注意在哪些方面注意加强?开发内功修炼号主飞哥在这篇文章中就给出了七条实用的建议。
+>
+>
+>
+> **内容概览**:
+>
+> 1. 刻意加强需求评审能力
+> 2. 主动思考效率
+> 3. 加强内功能力
+> 4. 思考性能
+> 5. 重视线上
+> 6. 关注全局
+> 7. 归纳总结能力
+>
+> **原文地址**:
+
+### 建议 1:刻意加强需求评审能力
+
+先从需求评审开始说。在互联网公司,需求评审是开发工作的主要入口。
+
+对于普通程序员来说,一般就是根据产品经理提的需求细节,开始设想这个功能要怎么实现,开发成本大概需要多长时间。把自己当成了需求到代码之间的翻译官。很少去思考需求的合理性,对于自己做的事情有多大价值,不管也不问。
+
+而对于高级别的程序员来说,并不会一开始就陷入细节,而是会更多地会从产品本身出发,询问产品经理为啥要做这个细节,目的是啥。换个说法,就是会先考虑这个需求是不是合理。
+
+如果需求高级不合理就进行 PK ,要么对需求进行调整,要么就砍掉。不过要注意的是 PK 和调整需求不仅仅砍需求,还有另外一个方向,那就是对需求进行加强。
+
+产品同学由于缺乏技术背景,很可能想的并不够充分,这个时候如果你有更好的想法,也完全可以提出来,加到需求里,让这个需求变得更有价值。
+
+总之,高级程序员并不会一五一十地按产品经理的需求文档来进行后面的开发,而是**一切从有利于业务的角度出发思考,对产品经理的需求进行删、改、增。**
+
+这样的工作表面看似和开发无关,但是只有这样才能保证后续所有开发同学都是有价值的,而不是做一堆无用功。无用功做的多了会极大的挫伤开发的成就感。
+
+所以,**普通程序员要想成长为更高级别的开发,一定要加强需求评审能力的培养**。
+
+### 建议 2:主动思考效率
+
+普通的程序员,按部就班的去写代码,有活儿来我就干,没活儿的时候我就呆着。很少去深度思考现有的这些代码为什么要这么写,这么写的好处是啥,有哪些地方存在瓶颈,我是否可以把它优化一些。
+
+而高级一点程序员,并不会局限于把手头的活儿开发就算完事。他们会主动去琢磨,现在这种开发模式是不是不够的好。那么我是否能做一个什么东西能把这个效率给提升起来。
+
+举一个小例子,我 6 年前接手一个项目的时候,我发现运营一个月会找我四次,就是找我给她发送一个推送。她说以前的开发都是这么帮他弄的。虽然这个需求处理起来很简单,改两行发布一下就完事。但是烦啊,你想象一下你正专心写代码呢,她又双叒来找你了,思路全被她中断了。而且频繁地操作线上本来就会引入不确定的风险,万一那天手一抽抽搞错了,线上就完蛋了。
+
+我的做法就是,我专门抽了一周的时间,给她做了一套运营后台。这样以后所有的运营推送她就直接在后台上操作就完事了。我倒出精力去做其它更有价值的事情去了。
+
+所以,**第二个建议就是要主动思考一下现有工作中哪些地方效率有改进的空间,想到了就主动去改进它!**
+
+### 建议 3:加强内功能力
+
+哪些算是内功呢,我想内功修炼的读者们肯定也都很熟悉的了,指的就是大家学校里都学过的操作系统、网络等这些基础。
+
+普通的程序员会觉得,这些基础知识我都会好么,我大学可是足足学了四年的。工作了以后并不会刻意来回头再来加强自己在这些基础上的深层次的提升。
+
+高级的程序员,非常清楚自己当年学的那点知识太皮毛了。工作之余也会深入地去研究 Linux、研究网络等方向的底层实现。
+
+事实上,互联网业界的技术大牛们很大程度是因为对这些基础的理解相当是深厚,具备了深厚的内功以后才促使他们成长为了技术大牛。
+
+我很难相信一个不理解底层,只会 CURD,只会用别人框架的开发将来能在技术方向成长为大牛。
+
+所以,**还建议多多锻炼底层技术内功能力**。如果你不知道怎么练,那就坚持看「开发内功修炼」公众号。
+
+### 建议 4:思考性能
+
+普通程序员往往就是把需求开发完了就不管了,只要需求实现了,测试通过了就可以交付了。将来流量会有多大,没想过。自己的服务 QPS 能支撑多少,不清楚。
+
+而高级的程序员往往会关注自己写出来的代码的性能。
+
+在需求评审的时候,他们一般就会估算大概的请求流量有多大。进而设计阶段就会根据这个量设计符合性能要求的方案。
+
+在上线之前也会进行性能压测,检验一下在性能上是否符合预期。如果性能存在问题,瓶颈在哪儿,怎么样能进行优化一下。
+
+所以,**第四个建议就是一定要多多主动你所负责业务的性能,并多多进行优化和改进**。我想这个建议的重要程度非常之高。但这是需要你具备深厚的内功才可以办的到的,否则如果你连网络是怎么工作的都不清楚,谈何优化!
+
+### 建议 5:重视线上
+
+普通程序员往往对线上的事情很少去关注,手里记录的服务器就是自己的开发机和发布机,线上机器有几台,流量多大,最近有没有波动这些可能都不清楚。
+
+而高级的程序员深深的明白,有条件的话,会尽量多多观察自己的线上服务,观察一下代码跑的咋样,有没有啥 error log。请求峰值的时候 CPU、内存的消耗咋样。网络端口消耗的情况咋样,是否需要调节一些参数配置。
+
+当性能不尽如人意的时候,可能会回头再来思考出性能的改进方案,重新开发和上线。
+
+你会发现在线上出问题的时候,能紧急扑上前线救火的都是高级一点的程序员。
+
+所以,**飞哥给的第五个建议就是要多多观察线上运行情况**。只有多多关注线上,当线上出故障的时候,你才能承担的起快速排出线上问题的重任。
+
+### 建议 6:关注全局
+
+普通程序员是你分配给我哪个模块,我就干哪个模块,给自己的工作设定了非常小的一个边界,自己所有的眼光都聚集在这个小框框内。
+
+高级程序员是团队内所有项目模块,哪怕不是他负责的,他也会去熟悉,去了解。具备这种思维的同学无论在技术上,无论是在业务上,成长的也都是最快的。在职级上得到晋升,或者是职位上得到提拔的往往都是这类同学。
+
+甚至有更高级别的同学,还不止于把目光放在团队内,甚至还会关注公司内其它团队,甚至是业界的业务和技术栈。写到这里我想起了张一鸣说过的,不给自己的工作设边界。
+
+所以,**建议要有大局观,不仅仅是你负责的模块,整个项目其实你都应该去关注**。而不是连自己组内同学做的是啥都不知道。
+
+### 建议 7:归纳总结能力
+
+普通程序员往往是工作的事情做完就拉到,很少回头去对自己的技术,对业务进行归纳和总结。
+
+而高级的程序员往往都会在一件比较大的事情做完之后总结一下,做个 ppt,写个博客啥的记录下来。这样既对自己的工作是一个归纳,也可以分享给其它同学,促进团队的共同成长。
diff --git a/docs/high-quality-technical-articles/advanced-programmer/ten-years-of-dachang-growth-road.md b/docs/high-quality-technical-articles/advanced-programmer/ten-years-of-dachang-growth-road.md
new file mode 100644
index 0000000000000000000000000000000000000000..8a69e8f586260bf1a61b21219955299a80f4c35b
--- /dev/null
+++ b/docs/high-quality-technical-articles/advanced-programmer/ten-years-of-dachang-growth-road.md
@@ -0,0 +1,136 @@
+---
+title: 十年大厂成长之路
+category: 技术文章精选集
+author: CodingBetterLife
+tag:
+ - 练级攻略
+---
+
+> **推荐语**:这篇文章的作者有着丰富的工作经验,曾在大厂工作了 12 年。结合自己走过的弯路和接触过的优秀技术人,他总结出了一些对于个人成长具有普遍指导意义的经验和特质。
+>
+>
+>
+> **原文地址:**
+
+最近这段时间,有好几个年轻的同学和我聊到自己的迷茫。其中有关于技术成长的、有关于晋升的、有关于择业的。我很高兴他们愿意听我这个“过来人”分享自己的经验。
+
+我自己毕业后进入大厂,在大厂工作 12 年,我说的内容都来自于我自己或者身边人的真实情况。尤其,我会把 **【我自己走过的弯路】** 和 **【我看到过的优秀技术人的特质】** 相结合来给出建议。
+
+这些内容我觉得具有普遍的指导意义,所以决定做个整理分享出来。我相信,无论你在大厂还是小厂,如果你相信这些建议,或早或晚他们会帮助到你。
+
+我自己工作 12 年,走了些弯路,所以我就来讲讲,“在一个技术人 10 年的发展过程中,应该注意些什么”。我们把内容分为两块:
+
+1. **十年技术路怎么走**
+2. **一些重要选择**
+
+## 01 十年技术路怎么走
+
+### 【1-2 年】=> 从“菜鸟”到“职业”
+
+应届生刚进入到工作时,会有各种不适应。比如写好的代码会被反复打回、和团队老司机讨论技术问题会有一堆问号、不敢提问和质疑、碰到问题一个人使劲死磕等等。
+
+**简单来说就是,即使日以继夜地埋头苦干,最后也无法顺利的开展工作。**
+
+这个阶段最重要的几个点:
+
+**【多看多模仿】**:比如写代码的时候,不要就像在学校完成书本作业那样只关心功能是否正确,还要关心模块的设计、异常的处理、代码的可读性等等。在你还没有了解这些内容的精髓之前,也要照猫画虎地模仿起来,慢慢地你就会越来越明白真实世界的代码是怎么写的,以及为什么要这么写。
+
+做技术方案的时候也是同理,技术文档的要求你也许并不理解,但你可以先参考已有文档写起来。
+
+**【脸皮厚一点】**:不懂就问,你是新人大家都是理解的。你做的各种方案也可以多找老司机们 review,不要怕被看笑话。
+
+**【关注工作方式】**:比如发现需求在计划时间完不成就要尽快报风险、及时做好工作内容的汇报(例如周报)、开会后确定会议结论和 todo 项、承诺时间就要尽力完成、严格遵循公司的要求(例如发布规范、权限规范等)
+
+一般来说,工作 2 年后,你就应该成为一个职业人。老板可以相信任何工作交到你的手里,不会出现“意外”(例如一个重要需求明天要上线了,突然被告知上不了)。
+
+### 【3-4 年】=> 从“职业”到“尖兵”
+
+工作两年后,对业务以及现有系统的了解已经到达了一定的程度,技术同学会开始承担更有难度的技术挑战。
+
+例如需要将性能提升到某一个水位、例如需要对某一个重要模块进行重构、例如有个重要的项目需要协同 N 个团队一起完成。
+
+可见,上述的这些技术问题,难度都已经远远超过一个普通的需求。解决这些问题需要有一定的技术能力,同时也需要具备更高的协同能力。
+
+这个阶段最重要的几个点:
+
+**【技术能力提升】**:无论是公司内还是公司外的技术内容,都要多做主动的学习。基本上这个阶段的技术难题都集中在【性能】【稳定性】和【扩展性】上,而这些内容在业界都是有成型的方法论的。
+
+**【主人翁精神】**:技术难题除了技术方案设计及落地外,背后还有一系列的其他工作。例如上线后对效果的观测、重点项目对于上下游改造和风险的了解程度、对于整个技改后续的计划(二期、三期的优化思路)等。
+
+在工作四年后,基本上你成为了团队的一、二号技术位。很多技术难题即使不是你来落地,也是由你来决定方案。你会做调研、会做方案对比、会考虑整个技改的生命周期。
+
+### 【5-7 年】=> 从“尖兵”到“专家”
+
+技术尖兵重点在于解决某一个具体的技术难题或者重点项目。而下一步的发展方向,就是能够承担起来一整个“业务板块”,也就是“领域技术专家”。
+
+想要承担一整个“业务板块”需要 **【对业务领域有深刻的理解,同时基于这些理解来规划技术的发展方向】** 。
+
+拿支付做个例子。简单的支付功能其实很容易完成,只要处理好和双联(网联和银联)的接口调用(成功、失败、异常)即可。但在很多背景下,支付没有那么简单。
+
+例如,支付是一个用户敏感型操作,非常强调用户体验,如何能兼顾体验和接口的不稳定?支付接口还需要承担费用,同步和异步的接口费用不同,如何能够降本?支付接口往往还有限额等。这一系列问题的背后涉及到很多技术的设计,包括异步化、补偿设计、资金流设计、最终一致性设计等等。
+
+这个阶段最重要的几个点:
+
+**【深入理解行业及趋势】**:密切关注行业的各种变化(新鲜的玩法、政策的变动、竞对的策略、科技等外在因素的影响等等),和业务同学加强沟通。
+
+**【深入了解行业解决方案】**:充分对标已有的国内外技术方案,做深入学习和尝试,评估建设及运维成本,结合业务趋势制定计划。
+
+### 【8-10 年】=> 从“专家”到“TL”
+
+其实很多时候,如果能做到专家,基本也是一个 TL 的角色了,但这并不代表正在执行 TL 的职责。
+
+专家虽然已经可以做到“为业务发展而规划好技术发展”,但问题是要怎么落地呢?显然,靠一个人的力量是不可能完成建设的。所以,这里的 TL 更多强调的不是“领导”这个职位,而是 **【通过聚合一个团队的力量来实施技术规划】** 。
+
+所以,这里的 TL 需要具备【团队技术培养】【合理分配资源】【确认工作优先级】【激励与奖惩】等各种能力。
+
+这个阶段最重要的几个点:
+
+**【学习管理学】**:这里的管理学当然不是指 PUA,而是指如何在每个同学都有各自诉求的现实背景下,让个人目标和团队目标相结合,产生向前发展的动力。
+
+**【始终扎根技术】**:很多时候,工作重心偏向管理以后,就会荒废技术。但事实是,一个优秀的领导永远是一个优秀的技术人。参与一起讨论技术方案并给予指导、不断扩展自己的技术宽度、保持对技术的好奇心,这些是让一个技术领导持续拥有向心力的关键。
+
+## 02 一些重要选择
+
+下面来聊聊在十年间我们可能会碰到的一些重要选择。这些都是真实的血与泪的教训。
+
+### 我该不该转岗?
+
+大厂都有转岗的机制。转岗可以帮助员工寻找自己感兴趣的方向,也可以帮助新型团队招募有即战力的同学。
+
+转岗看似只是在公司内部变动,但你需要谨慎决定。
+
+本人转岗过多次。虽然还在同一家公司,但转岗等同于换工作。无论是领域沉淀、工作内容、信任关系、协作关系都是从零开始。
+
+针对转岗我的建议是:**如果你是想要拓宽自己的技术广度,也就是抱着提升技术能力的想法,我觉得可以转岗。但如果你想要晋升,不建议你转岗。**晋升需要在一个领域的持续积淀和在一个团队信任感的持续建立。
+
+当然,转岗可能还有其他原因,例如家庭原因、身体原因等,这个不展开讨论了。
+
+### 我该不该跳槽?
+
+跳槽和转岗一样,往往有很多因素造成,不能一概而论,我仅以几个场景来说:
+
+**【晋升失败】**:扪心自问,如果你觉得自己确实还不够格,那你就踏踏实实继续努力。如果你觉得评委有失偏颇,你可以尝试去外面面试一下,让市场来给你答案。
+
+**【成长局限】**:觉得自己做的事情没有挑战,无法成长。你可以和老板聊一下,有可能是因为你没有看到其中的挑战,也有可能老板没有意识到你的“野心”。
+
+**【氛围不适】**:一般来自于新入职或者领导更换,这种情况下不适是正常的。我的建议是,**如果一个环境是“对事不对人”的,那就可以留下来**,努力去适应,这种不适应只是做事方式不同导致的。但如果这个环境是“对人不对事”的话,走吧。
+
+### 跳槽该找怎样的工作?
+
+我们跳槽的时候往往会同时面试好几家公司。行情好的时候,往往可以收到多家 offer,那么我们要如何选择呢?
+
+考虑一个 offer 往往有这几点:【公司品牌】【薪资待遇】【职级职称】【技术背景】。每个同学其实都有自己的诉求,所以无论做什么选择都没有对错之分。
+
+我的一个建议是:**你要关注新岗位的空间,这个空间是有希望满足你的期待的**。
+
+比如,你想成为架构师,那新岗位是否有足够的技术挑战来帮助你提升技术能力,而不仅仅是疲于奔命地应付需求?
+
+比如,你想往技术管理发展,那新岗位是否有带人的机会?是否有足够的问题需要搭建团队来解决?
+
+比如,你想扎根在某个领域持续发展(例如电商、游戏),那新岗位是不是延续这个领域,并且可以碰到更多这个领域的问题?
+
+当然,如果薪资实在高到无法拒绝,以上参考可以忽略!
+
+## 结语
+
+以上就是我对互联网从业技术人员十年成长之路的心得,希望在你困惑和关键选择的时候可以帮助到你。如果我的只言片语能够在未来的某个时间帮助到你哪怕一点,那将是我莫大的荣幸。
diff --git a/docs/high-quality-technical-articles/advanced-programmer/the-growth-strategy-of-the-technological-giant.md b/docs/high-quality-technical-articles/advanced-programmer/the-growth-strategy-of-the-technological-giant.md
new file mode 100644
index 0000000000000000000000000000000000000000..2565f5f5a3999aa436ff892ddc6109674726f961
--- /dev/null
+++ b/docs/high-quality-technical-articles/advanced-programmer/the-growth-strategy-of-the-technological-giant.md
@@ -0,0 +1,205 @@
+---
+title: 程序员的技术成长战略
+category: 技术文章精选集
+author: 波波微课
+tag:
+ - 练级攻略
+---
+
+> **推荐语**:波波老师的一篇文章,写的非常好,不光是对技术成长有帮助,其他领域也是同样适用的!建议反复阅读,形成一套自己的技术成长策略。
+>
+>
+>
+> **原文地址:**
+
+## 1. 前言
+
+在波波的微信技术交流群里头,经常有学员问关于技术人该如何学习成长的问题,虽然是微信交流,但我依然可以感受到小伙伴们焦虑的心情。
+
+**技术人为啥焦虑?** 恕我直言,说白了是胆识不足格局太小。胆就是胆量,焦虑的人一般对未来的不确定性怀有恐惧。识就是见识,焦虑的人一般看不清楚周围世界,也看不清自己和适合自己的道路。格局也称志向,容易焦虑的人通常视野窄志向小。如果从战略和管理的视角来看,就是对自己和周围世界的认知不足,没有一个清晰和长期的学习成长战略,也没有可执行的阶段性目标计划+严格的执行。
+
+因为问此类问题的学员很多,让我感觉有点烦了,为了避免重复回答,所以我专门总结梳理了这篇长文,试图统一来回答这类问题。如果后面还有学员问类似问题,我会引导他们来读这篇文章,然后让他们用三个月、一年甚至更长的时间,去思考和回答这样一个问题:**你的技术成长战略究竟是什么?** 如果你想清楚了这个问题,有清晰和可落地的答案,那么恭喜你,你只需按部就班执行就好,根本无需焦虑,你实现自己的战略目标并做出成就只是一个时间问题;否则,你仍然需要通过不断磨炼+思考,务必去搞清楚这个人生的大问题!!!
+
+下面我们来看一些行业技术大牛是怎么做的。
+
+## 二. 跟技术大牛学成长战略
+
+我们知道软件设计是有设计模式(Design Pattern)的,其实技术人的成长也是有成长模式(Growth Pattern)的。波波经常在 Linkedin 上看一些技术大牛的成长履历,探究其中的成长模式,从而启发制定自己的技术成长战略。
+
+当然,很少有技术大牛会清晰地告诉你他们的技术成长战略,以及每一年的细分落地计划。但是,这并不妨碍我们通过他们的过往履历和产出成果,去溯源他们的技术成长战略。实际上, **越是牛逼的技术人,他们的技术成长战略和路径越是清晰,我们越容易从中探究出一些成功的模式。**
+
+### 2.1 系统性能专家案例
+
+国内的开发者大都热衷于系统性能优化,有些人甚至三句话离不开高性能/高并发,但真正能深入这个领域,做到专家级水平的却寥寥无几。
+
+我这边要特别介绍的这个技术大牛叫 **Brendan Gregg** ,他是系统性能领域经典书《System Performance: Enterprise and the Cloud》(中文版[《性能之巅:洞悉系统、企业和云计算》](https://www.amazon.cn/dp/B08GC261P9))的作者,也是著名的[性能分析利器火焰图(Flame Graph)](https://github.com/brendangregg/FlameGraph)的作者。
+
+Brendan Gregg 之前是 Netflix 公司的高级性能架构师,在 Netflix 工作近 7 年。2022 年 4 月,他离开了 Netflix 去了 Intel,担任院士职位。
+
+
+
+总体上,他已经在系统性能领域深耕超过 10 年,[Brendan Gregg 的过往履历](https://www.linkedin.com/in/brendangregg/)可以在 linkedin 上看到。在这 10 年间,除了书籍以外,Brendan Gregg 还产出了超过上百份和系统性能相关的技术文档,演讲视频/ppt,还有各种工具软件,相关内容都整整齐齐地分享在[他的技术博客](http://www.brendangregg.com/)上,可以说他是一个非常高产的技术大牛。
+
+
+
+上图来自 Brendan Gregg 的新书《BPF Performance Tools: Linux System and Application Observability》。从这个图可以看出,Brendan Gregg 对系统性能领域的掌握程度,已经深挖到了硬件、操作系统和应用的每一个角落,可以说是 360 度无死角,整个计算机系统对他来说几乎都是透明的。波波认为,Brendan Gregg 是名副其实的,世界级的,系统性能领域的大神级人物。
+
+### 2.2 从开源到企业案例
+
+我要分享的第二个技术大牛是 **Jay Kreps**,他是知名的开源消息中间件 Kafka 的创始人/架构师,也是 Confluent 公司的联合创始人和 CEO,Confluent 公司是围绕 Kafka 开发企业级产品和服务的技术公司。
+
+从[Jay Kreps 的 Linkedin 的履历](https://www.linkedin.com/in/jaykreps/)上我们可以看出,Jay Kreps 之前在 Linkedin 工作了 7 年多(2007.6 ~ 2014. 9),从高级工程师、工程主管,一直做到首席资深工程师。Kafka 大致是在 2010 年,Jay Kreps 在 Linkedin 发起的一个项目,解决 Linkedin 内部的大数据采集、存储和消费问题。之后,他和他的团队一直专注 Kafka 的打磨,开源(2011 年初)和社区生态的建设。
+
+到 2014 年底,Kafka 在社区已经非常成功,有了一个比较大的用户群,于是 Jay Kreps 就和几个早期作者一起离开了 Linkedin,成立了[Confluent 公司](https://tech.163.com/14/1107/18/AAFG92LD00094ODU.html),开始了 Kafka 和周边产品的企业化服务道路。今年(2020.4 月),Confluent 公司已经获得 E 轮 2.5 亿美金融资,公司估值达到 45 亿美金。从 Kafka 诞生到现在,Jay Kreps 差不多在这个产品和公司上投入了整整 10 年。
+
+
+
+上图是 Confluent 创始人三人组,一个非常有意思的组合,一个中国人(左),一个印度人(右),中间的 Jay Kreps 是美国人。
+
+我之所以对 Kafka 和 Jay Kreps 的印象特别深刻,是因为在 2012 年下半年,我在携程框架部也是专门搞大数据采集的,我还开发过一套功能类似 Kafka 的 Log Collector + Agent 产品。我记得同时期有不止 4 个同类型的开源产品:Facebook Scribe、Apache Chukwa、Apache Flume 和 Apache Kafka。现在回头看,只有 Kafka 走到现在发展得最好,这个和创始人的专注和持续投入是分不开的,当然背后和几个创始人的技术大格局也是分不开的。
+
+当年我对战略性思维几乎没有概念,还处在**什么技术都想学、认为各种项目做得越多越牛的阶段**。搞了半年的数据采集以后,我就掉头搞其它“更有趣的”项目去了(从这个事情的侧面,也可以看出我当年的技术格局是很小的)。中间我陆续关注过 Jay 的一些创业动向,但是没想到他能把 Confluent 公司发展到目前这个规模。现在回想,其实在十年前,Jay Kreps 对自己的技术成长就有比较明确的战略性思考,也具有大的技术格局和成事的一些必要特质。Jay Kreps 和 Kafka 给我上了一堂生动的技术战略和实践课。
+
+### 2.3 技术媒体大 V 案例
+
+介绍到这里,有些同学可能会反驳说:波波你讲的这些大牛都是学历背景好,功底扎实起点高,所以他们才更能成功。其实不然,这里我再要介绍一位技术媒体界的大 V 叫 Brad Traversy,大家可以看[他的 Linkedin 简历](https://www.linkedin.com/in/bradtraversy/),背景很一般,学历差不多是一个非正规的社区大学(相当于大专),没有正规大厂工作经历,有限几份工作一直是在做网站外包。
+
+
+
+但是!!!Brad Traversy 目前是技术媒体领域的一个大 V,当前[他在 Youtube 上的频道](https://www.youtube.com/c/TraversyMedia)有 138 万多的订阅量,10 年累计输出 Web 开发和编程相关教学视频超过 800 个。Brad Traversy 也是 [Udemy](https://www.udemy.com/user/brad-traversy/) 上的一个成功讲师,目前已经在 Udemy 上累计输出课程 19 门,购课学生数量近 42 万。
+
+
+
+Brad Traversy 目前是自由职业者,他的 Youtube 广告+Udemy 课程的收入相当不错。
+
+就是这样一位技术媒体大 V,你很难想象,在年轻的时候,贴在他身上的标签是:不良少年,酗酒,抽烟,吸毒,纹身,进监狱。。。直
+
+到结婚后的第一个孩子诞生,他才开始担起责任做出改变,然后凭借对技术的一腔热情,开始在 Youtube 平台上持续输出免费课程。从此他找到了适合自己的战略目标,然后人生开始发生各种积极的变化。。。如果大家对 Brad Traversy 的过往经历感兴趣,推荐观看他在 Youtube 上的自述视频[《My Struggles & Success》](https://www.youtube.com/watch?v=zA9krklwADI)。
+
+
+
+我粗略浏览了[Brad Traversy 在 Youtube 上的所有视频](https://www.youtube.com/c/TraversyMedia/videos),10 年总计输出 800+视频,平均每年 80+。第一个视频提交于 2010 年 8 月,刚开始几年几乎没有订阅量,2017 年 1 月订阅量才到 50k,这中间差不多隔了 6 年。2017.10 月订阅量猛增到 200k,2018 年 3 月订阅量到 300k。当前 2021.1 月,订阅量达到 138 万。可以认为从 2017 开始,也就是在积累了 6 ~ 7 年后,他的订阅量开始出现拐点。**如果把这些数据画出来,将会是一条非常漂亮的复利曲线**。
+
+### 2.4 案例小结
+
+Brendan Gregg,Jay Kreps 和 Brad Traversy 三个人走的技术路线各不相同,但是他们的成功具有共性或者说模式:
+
+**1、找到了适合自己的长期战略目标。**
+
+- Brendan Gregg: 成为系统性能领域顶级专家
+- Jay Kreps:开创基于 Kafka 开源消息队列的企业服务公司,并将公司做到上市
+- Brad Traversy: 成为技术媒体领域大 V 和课程讲师,并以此作为自己的职业
+
+**2、专注深耕一个(或有限几个相关的)细分领域(Niche),保持定力,不随便切换领域。**
+
+- Brendan Gregg:系统性能领域
+- Jay Kreps: 消息中间件/实时计算领域+创业
+- Brad Traversy: 技术媒体/教学领域,方向 Web 开发 + 编程语言
+
+**3、长期投入,三人都持续投入了 10 年。**
+
+**4、年度细分计划+持续可量化的价值产出(Persistent & Measurable Value Output)。**
+
+- Brendan Gregg:除公司日常工作产出以外,每年有超过 10 份以上的技术文档和演讲视频产出,平均每年有 2.5 个开源工具产出。十年共产出书籍 2 本,其中《System Performance》已经更新到第二版。
+- Jay Kreps:总体有开源产品+公司产出,1 本书产出,每年有 Kafka 和周边产品发版若干。
+- Brad Traversy: 每年有 Youtube 免费视频产出(平均每年 80+)+Udemy 收费视频课产出(平均每年 1.5 门)。
+
+**5、以终为始是牛人和普通人的一大区别。**
+
+普通人通常走一步算一步,很少长远规划。牛人通 常是先有远大目标,然后采用倒推法,将大目标细化到每年/月/周的详细落地计划。Brendan Gregg,Jay Kreps 和 Brad Traversy 三人都是以终为始的典型。
+
+
+
+上面总结了几位技术大牛的成长模式,其中一个重点就是:这些大牛的成长都是通过 **持续有价值产出(Persistent Valuable Output)** 来驱动的。持续产出为啥如此重要,这个还要从下面的学习金字塔说起。
+
+## 三、学习金字塔和刻意训练
+
+
+
+学习金字塔是美国缅因州国家训练实验室的研究成果,它认为:
+
+> 1. 我们平时上课听讲之后,学习内容平均留存率大致只有 5%左右;
+> 2. 书本阅读的平均留存率大致只有 10%左右;
+> 3. 学习配上视听效果的课程,平均留存率大致在 20%左右;
+> 4. 老师实际动手做实验演示后的平均留存率大致在 30%左右;
+> 5. 小组讨论(尤其是辩论后)的平均留存率可以达到 50%左右;
+> 6. 在实践中实际应用所学之后,平均留存率可以达到 75%左右;
+> 7. 在实践的基础上,再把所学梳理出来,转而再传授给他人后,平均留存率可以达到 90%左右。
+
+上面列出的 7 种学习方法,前四种称为 **被动学习** ,后三种称为 **主动学习**。
+
+拿学游泳做个类比,被动学习相当于你看别人游泳,而主动学习则是你自己要下水去游。我们知道游泳或者跑步之类的运动是要燃烧身体卡路里的,这样才能达到锻炼身体和长肌肉的效果(肌肉是卡路里燃烧的结果)。如果你只是看别人游泳,自己不实际去游,是不会长肌肉的。同样的,主动学习也是要燃烧脑部卡路里的,这样才能达到训练大脑和长脑部“肌肉”的效果。
+
+我们也知道,燃烧身体的卡路里,通常会让人感觉不舒适,如果燃烧身体卡路里会让人感觉舒适的话,估计这个世界上应该不会有胖子这类人。同样,燃烧脑部卡路里也会让人感觉不适、紧张、出汗或语无伦次,如果燃烧脑部卡路里会让人感觉舒适的话,估计这个世界上人人都很聪明,人人都能发挥最大潜能。当然,这些不舒适是短期的,长期会使你更健康和聪明。波波一直认为, **人与人之间的先天身体其实都差不多,但是后天身体素质和能力有差异,这些差异,很大程度是由后天对身体和大脑的训练质量、频度和强度所造成的。**
+
+明白这个道理之后,心智成熟和自律的人就会对自己进行持续地 **刻意训练** 。这个刻意训练包括对身体的训练,比如波波现在每天坚持跑步 3km,走 3km,每天做 60 个仰卧起坐,5 分钟平板撑等等,每天保持让身体燃烧一定量的卡路里。刻意训练也包括对大脑的训练,比如波波现在每天做项目写代码 coding(训练脑+手),平均每天在 B 站上输出十分钟免费视频(训练脑+口头表达),另外有定期总结输出公众号文章(训练脑+文字表达),还有每天打半小时左右的平衡球(下图)或古墓丽影游戏(训练小脑+手),每天保持让大脑燃烧一定量的卡路里,并保持一定强度(适度不适感)。
+
+
+
+关于刻意训练的专业原理和方法论,推荐看书籍《刻意练习》。
+
+
+
+注意,如果你平时从来不做举重锻炼的,那么某天突然做举重会很不适应甚至受伤。脑部训练也是一样的,如果你从来没有做过视频输出,那么刚开始做会很不适应,做出来的视频质量会很差。不过没有关系,任何训练都是一个循序渐进,不断强化的过程。等大脑相关区域的"肌肉"长出来以后,会逐步进入正循环,后面会越来越顺畅,相关"肌肉"会越来越发达。所以,和健身一样,健脑也不能遇到困难就放弃,需要循序渐进(Incremental)+持续地(Persistent)刻意训练。
+
+理解了学习金字塔和刻意训练以后,现在再来看 Brendan Gregg,Jay Kreps 和 Brad Traversy 这些大牛的做法,他们的学习成长都是建立在持续有价值产出的基础上的,这些产出都是刻意训练+燃烧脑部卡路里的成果。他们的产出要么是建立在实践基础上的产出,例如 Jay Kreps 的 Kafka 开源项目和 Confluent 公司;要么是在实践的基础上,再整理传授给其他人的产出,例如,Brendan Greeg 的技术演讲 ppt/视频,书籍,还有 Brad Traversy 的教学视频等等。换句话说,他们一直在学习金字塔的 5 ~ 7 层主动和高效地学习。并且,他们的学习产出还可以获得用户使用,有客户价值(Customer Value),有用户就有反馈和度量。记住,有反馈和度量的学习,也称闭环学习,它是能够不断改进提升的;反之,没有反馈和度量的学习,无法改进提升。
+
+现在,你也应该明白,晒个书单秀个技能图谱很简单,读个书上个课也不难。但是要你给出 5 ~ 10 年的总体技术成长战略,再基于这个战略给出每年的细分落地计划(尤其是产出计划),然后再严格按计划执行,这的确是很难的事情。这需要大量的实践训练+深度思考,要燃烧大量的脑部卡路里!但这是上天设置的进化法则,成长为真正的技术大牛如同成长为一流的运动员,是需要通过燃烧与之相匹配量的卡路里来交换的。成长为真正的技术大牛,也是需要通过产出与之匹配的社会价值来交换的,只有这样社会才能正常进化。你推进了社会进化,社会才会回馈你。如果不是这样,社会就无法正常进化。
+
+## 四、战略思维的诞生
+
+
+
+一般毕业生刚进入企业工作的时候,思考大都是以天/星期/月为单位的,基本上都是今天学个什么技术,明天学个什么语言,很少会去思考一年甚至更长的目标。这是个眼前漆黑看不到的懵懂时期,捕捉到机会点的能力和概率都非常小。
+
+工作了三年以后,悟性好的人通常会以一年为思考周期,制定和实施一些年度计划。这个时期是相信天赋和比拼能力的阶段,可以捕捉到一些小机会。
+
+工作了五年以后,一些悟性好的人会产生出一定的胆识和眼光,他们会以 3 ~ 5 年为周期来制定和实施计划,开始主动布局去捕捉一些中型机会点。
+
+工作了十年以后,悟性高的人会看到模式和规则变化,例如看出行业发展模式,还有人才的成长模式等,于是开始诞生出战略性思维。然后他们会以 5 ~ 10 年为周期来制定和实施自己的战略计划,开始主动布局去捕捉一些中大机会点。Brendan Gregg,Jay Kreps 和 Brad Traversy 都是属于这个阶段的人。
+
+当然还有很少一些更牛的时代精英,他们能够看透时代和人性,他们的思考是以一生甚至更长时间为单位的,这些超人不在本文讨论范围内。
+
+## 五、建议
+
+**1、以 5 ~ 10 年为周期去布局谋划你的战略。**
+
+现在大学生毕业的年龄一般在 22 ~ 23 岁,那么在工作了十年后,也就是在你 32 ~ 33 岁的时候,你也差不多看了十年了,应该对自己和周围的世界(你的行业和领域)有一个比较深刻的领悟了。**如果你到这个年纪还懵懵懂懂,今天抓东明天抓西,那么只能说你的胆识格局是相当的低**。在当前 IT 行业竞争这么激烈的情况下,到 35 岁被下岗可能就在眼前了。
+
+有了战略性思考,你应该以 5 ~ 10 年为周期去布局谋划你的战略。以 Brendan Gregg,Jay Kreps 和 Brad Traversy 这些大牛为例,**人生若真的要干点成就出来,投入周期一般都要十年的**。从 33 岁开始,你大致有 3 个十年,因为到 60 岁以后,一般人都老眼昏花干不了大事了。如果你悟性差一点,到 40 岁才开始规划,那么你大致还有 2 个十年。如果你规划好了,这 2 ~ 3 个十年可以成就不小的事业。否则,你很可能一生都成就不了什么事业,或者一直在帮助别人成就别人的事业。
+
+**2、专注自己的精力。**
+
+考虑到人生能干事业的时间也就是 2 ~ 3 个十年,你会发现人生其实很短暂,这时候你会把精力都投入到实现你的十年战略上去,没有时间再浪费在比如网上的闲聊和扯皮争论上去。
+
+**3、细分落地计划尤其是产出计划。**
+
+有了十年战略方向,下一步是每年的细分落地计划,尤其是产出计划。这些计划主要应该工作在学习金字塔的 5/6/7 层。**产出应该是刻意训练+燃烧卡路里的结果,每天让身体和大脑都保持燃烧一定量的卡路里**。
+
+**4、产出有价值的东西形成正反馈。**
+
+产出应该有客户价值,自己能学习(自己成长进化),对别人还有用(推动社会成长进化),这样可以得到**用户回馈和度量**,形成一个闭环,可以持续改进和提升你的学习。
+
+**5、少即是多。**
+
+深耕一个(或有限几个相关的)领域。所有细分计划应该紧密围绕你的战略展开。克制内心欲望,不要贪多和分心,不要被喧嚣的世界所迷惑。
+
+**6、战略方向+细分计划都要写下来,定期 review 优化。**
+
+**7、要有定力,持续努力。**
+
+曲则全、枉则直,战略实现是不可能直线的。战略方向和细分计划通常要按需调整,尤其在早期,但是最终要收敛。如果老是变不收敛,就是缺乏战略定力,是个必须思考和解决的大问题。
+
+别人的成长战略可以参考,但是不要刻意去模仿,你有你自己的颜色,**你应该成为独一无二的你**。
+
+战略方向和细分计划明确了,接下来就是按部就班执行,十年如一日铁打不动。
+
+**8、慢就是快。**
+
+战略目标的实现也和种树一样是生长出来的,需要时间耐心栽培,记住**慢就是快。**焦虑纠结的时候,像念经一样默念王阳明《传习录》中的教诲:
+
+> 立志用功,如种树然。方其根芽,犹未有干;及其有干,尚未有枝;枝而后叶,叶而后花实。初种根时,只管栽培灌溉。勿作枝想,勿作花想,勿作实想。悬想何益?但不忘栽培之功,怕没有枝叶花实?
+>
+> 译文:
+>
+> 实现战略目标,就像种树一样。刚开始只是一个小根芽,树干还没有长出来;树干长出来了,枝叶才能慢慢长出来;树枝长出来,然后才能开花和结果。刚开始种树的时候,只管栽培灌溉,别老是纠结枝什么时候长出来,花什么时候开,果实什么时候结出来。纠结有什么好处呢?只要你坚持投入栽培,还怕没有枝叶花实吗?
diff --git a/docs/high-quality-technical-articles/advanced-programmer/thinking-about-technology-and-business-after-five-years-of-work.md b/docs/high-quality-technical-articles/advanced-programmer/thinking-about-technology-and-business-after-five-years-of-work.md
new file mode 100644
index 0000000000000000000000000000000000000000..a35672c2f23ecc2fedfc19093efecdf42a4b6f22
--- /dev/null
+++ b/docs/high-quality-technical-articles/advanced-programmer/thinking-about-technology-and-business-after-five-years-of-work.md
@@ -0,0 +1,109 @@
+---
+title: 工作五年之后,对技术和业务的思考
+category: 技术文章精选集
+author: 知了一笑
+tag:
+ - 练级攻略
+---
+
+> **推荐语**:这是我在两年前看到的一篇对我触动比较深的文章。确实要学会适应变化,并积累能力。积累解决问题的能力,优化思考方式,拓宽自己的认知。
+>
+>
+>
+> **原文地址:**
+
+苦海无边,回头无岸。
+
+## 01 前言
+
+晃晃悠悠的,在互联网行业工作了五年,默然回首,你看哪里像灯火阑珊处?
+
+初入职场,大部分程序员会觉得苦学技术,以后会顺风顺水升职加薪,这样的想法没有错,但是不算全面,五年后你会不会继续做技术写代码这是核心问题。
+
+初入职场,会觉得努力加班可以不断提升能力,可以学到技术的公司就算薪水低点也可以接受,但是五年之后会认为加班都是在不断挤压自己的上升空间,薪水低是人生的天花板。
+
+这里想说的关键问题就是:初入职场的认知和想法大部分不会再适用于五年后的认知。
+
+工作五年之后面临的最大压力就是选择:职场天花板,技术能力天花板,薪水天花板,三十岁天花板。
+
+如何面对这些问题,是大部分程序员都在思考和纠结的。做选择的唯一参考点就是:利益最大化,这里可以理解为职场更好的升职加薪,顺风顺水。
+
+五年,变化最大不是工作经验,能力积累,而是心态,清楚的知道现实和理想之间是存在巨大的差距。
+
+## 02 学会适应变化,并积累能力
+
+回首自己的职场五年,最认可的一句话就是:学会适应变化,并积累能力。
+
+变化的就是,五年的时间技术框架更新迭代,开发工具的变迁,公司环境队友的更换,甚至是不同城市的流浪,想着能把肉体和灵魂安放在一处,有句很经典的话就是:唯一不变的就是变化本身。
+
+要积累的是:解决问题的能力,思考方式,拓宽认知。
+
+这种很难直白的描述,属于个人认知的范畴,不同的人有不一样的看法,所以只能站在大众化的角度去思考。
+
+首先聊聊技术,大部分小白级别的,都希望自己的技术能力不断提高,争取做到架构师级别,但是站在当前的互联网环境中,这种想法实现难度还是偏高,这里既不是打击也不是为了抬杠。
+
+可以观察一下现状,技术团队大的20-30人,小的10-15人,能有一个架构师去专门管理底层框架都是少有现象。
+
+这个问题的原因很多,首先架构师的成本过高,环境架构也不是需要经常升级,说的难听点可能框架比项目生命周期更高。
+
+所以大部分公司的大部分业务,基于现有大部分成熟的开源框架都可以解决,这也就导致架构师这个角色通常由项目主管代替或者级别较高的开发直接负责,这就是现实情况。
+
+这就导致技术框架的选择思路就是:只选对的。即这方面的人才多,开源解决方案多,以此降低技术方面对公司业务发展的影响。
+
+那为什么还要不断学习和积累技术能力?如果没有这个能力,程序员岗位可能根本走不了五年之久,需要用技术深度积累不断解决工作中的各种问题,用技术的广度提升自己实现业务需求的认知边界,这是安放肉体的根本保障。
+
+这就是导致很多五年以后的程序员压力陡然升高的原因,走向管理岗的另一个壁垒就是业务思维和认知。
+
+## 03 提高业务能力的积累
+
+程序员该不该用心研究业务,这个问题真的没有纠结的必要,只要不是纯技术型的公司,都需要面对业务。
+
+不管技术、运营、产品、管理层,都是在面向业务工作。
+
+从自己职场轨迹来看,五年变化最大就是解决业务问题的能力,职场之初面对很多业务场景都不知道如何下手,到几年之后设计业务的解决方案。
+
+这是大部分程序员在职场前五年跳槽就能涨薪的根本原因,面对业务场景,基于积累的经验和现有的开源工具,能快速给出合理的解决思路和实现过程。
+
+工作五年可能对技术底层的清晰程度都没有初入职场的小白清楚,但是写的程序却可以避开很多坑坑洼洼,对于业务的审视也是很细节全面。
+
+解决业务能力的积累,对于技术视野的宽度需求更甚,比如职场初期对于海量数据的处理束手无策,但是在工作几年之后见识数据行业的技术栈,真的就是技术选型的视野问题。
+
+什么是衡量技术能力的标准?站在一个共识的角度上看:系统的架构与代码设计能适应业务的不断变化和各种需求。
+
+相对比与技术,业务的变化更加快速频繁,高级工程师或者架构师之所以薪资高,这些角色一方面能适应业务的迭代,并且在工作中具有一定前瞻性,会考虑业务变化的情况下代码复用逻辑,这样的能力是需要一定的技术视野和业务思维的沉淀。
+
+所以职场中:业务能说的井井有条,代码能写的明明白白,得到机会的可能性更大。
+
+## 04 不同的阶段技术和业务的平衡和选择
+
+从理性的角度看技术和业务两个方面,能让大部分人职场走的平稳顺利,但是不同的阶段对两者的平衡和选择是不一样的。
+
+在思考如何选择的时候,可以参考二八原则的逻辑,即在任何一组东西中,最重要的只占其中一小部分,约20%,其余80%尽管是多数,却是次要的,因此又称二八定律。
+
+个人真的非常喜欢这个原则,大部分人都不是天才,所以很难三心二意同时做好几件事情,在同一时间段内应该集中精力做好一件事件。
+
+但是单纯的二八原则模式可能不适应大部分职场初期的人,因为初期要学习很多内容,如何在职场生存:专业能力,职场关系,为人处世,产品设计等等。
+
+当然这些东西不是都要用心刻意学习,但是合理安排二二六原则或其他组合是更明智的,首先是专业能力要重点练习,其次可以根据自己的兴趣合理选择一到两个方面去慢慢了解,例如产品,运营,运维,数据等,毕竟三五年以后会不会继续写代码很难说,多给自己留个机会总是有备无患。
+
+在职场初期,基本都是从技术角度去思考问题,如何快速提升自己的编码能力,在公司能稳定是首要目标,因此大部分时间都是在做基础编码和学习规范,这时可能90%的心思都是放在基础编码上,另外10%会学习环境架构。
+
+最多一到两年,就会开始独立负责模块需求开发,需要自己设计整个代码思路,这里业务就会进入视野,要懂得业务上下游关联关系,学会思考如何设计代码结构,才能在需求变动的情况下代码改动较少,这个时候可能就会放20%的心思在业务方面,30%学习架构方式。
+
+三到五年这个时间段,是解决问题能力提升最快的时候,因为这个阶段的程序员基本都是在开发核心业务链路,例如交易、支付、结算、智能商业等模块,需要对业务整体有较清晰的把握能力,不然就是给自己挖坑,这个阶段要对业务流付出大量心血思考。
+
+越是核心的业务线,越是容易爆发各种问题,如果在日常工作中不花心思处理各种细节问题,半夜异常自动的消息和邮件总是容易让人憔悴。
+
+所以努力学习技术是提升自己,培养自己的业务认知也同样重要,个人认为这二者的分量平分秋色,只是需要在合适的阶段做出合理的权重划分。
+
+## 05 学会在职场做选择和生存
+
+基于技术能力和业务思维,学会在职场做选择和生存,这些是职场前五年一路走来的最大体会。
+
+不管是技术还是业务,这两个概念依旧是个很大的命题,不容易把握,所以学会理清这两个方面能力中的公共模块是关键。
+
+不管技术还是业务,都不可能从一家公司完全复制到另一家公司,但是可以把一家公司的技术框架,业务解决方案学会,并且带到另一家公司,例如技术领域内的架构、设计、流程、数据管理,业务领域内的思考方式、产品逻辑、分析等,这些是核心能力并且是大部分公司人才招聘的要求,所以这些才是工作中需要重点积累的。
+
+人的精力是有限的,而且面对三十这个天花板,各种事件也会接连而至,在职场中学会合理安排时间并不断提升核心能力,这样才能保证自己的竞争力。
+
+职场就像苦海无边,回首望去可能也没有岸边停泊,但是要具有换船的能力或者有个小木筏也就大差不差了。
\ No newline at end of file
diff --git a/docs/high-quality-technical-articles/interview/how-to-examine-the-technical-ability-of-programmers-in-the-first-test-of-technology.md b/docs/high-quality-technical-articles/interview/how-to-examine-the-technical-ability-of-programmers-in-the-first-test-of-technology.md
new file mode 100644
index 0000000000000000000000000000000000000000..cef6e51c6e58e9de1df268bf189136398863b788
--- /dev/null
+++ b/docs/high-quality-technical-articles/interview/how-to-examine-the-technical-ability-of-programmers-in-the-first-test-of-technology.md
@@ -0,0 +1,344 @@
+---
+title: 如何在技术初试中考察程序员的技术能力
+category: 技术文章精选集
+author: 琴水玉
+tag:
+ - 面试
+---
+
+> **推荐语**:从面试官和面试者两个角度探讨了技术面试!非常不错!
+>
+>
+>
+> **内容概览:**
+>
+> - 实战与理论结合。比如,候选人叙述 JVM 内存模型布局之后,可以接着问:有哪些原因可能会导致 OOM , 有哪些预防措施? 你是否遇到过内存泄露的问题? 如何排查和解决这类问题?
+> - 项目经历考察不宜超过两个。因为要深入考察一个项目的详情,所占用的时间还是比较大的。一般来说,会让候选人挑选一个他或她觉得最有收获的/最有挑战的/印象最深刻的/自己觉得特有意思的项目。然后围绕这个项目进行发问。通常是从项目背景出发,考察项目的技术栈、项目模块及交互的整体理解、项目中遇到的有挑战性的技术问题及解决方案、排查和解决问题、代码可维护性问题、工程质量保障等。
+> - 多问少说,让候选者多表现。根据候选者的回答适当地引导或递进或横向移动。
+>
+>
+>
+> **原文地址**:https://www.cnblogs.com/lovesqcc/p/15169365.html
+
+## 灵魂三连问
+
+1. 你觉得人怎么样? 【表达能力、沟通能力、学习能力、总结能力、自省改进能力、抗压能力、情绪管理能力、影响力、团队管理能力】
+2. 如果让他独立完成项目的设计和实现,你觉得他能胜任吗? 【系统设计能力、项目管理能力】
+3. 他的分析和解决问题的能力,你的评价是啥?【原理理解能力、实战应用能力】
+
+## 考察目标和思路
+
+首先明确,技术初试的考察目标:
+
+- 候选人的技术基础;
+- 候选人解决问题的思路和能力。
+
+技术基础是基石(冰山之下的东西),占七分, 解决问题的思路和能力是落地(冰山之上露出的部分),占三分。 业务和技术基础考察,三七开。
+
+核心考察目标:分析和解决问题的能力。
+
+技术层面:深度 + 应用能力 + 广度。 对于校招或社招 P6 级别以下,要多注重 深度 + 应用能力,广度是加分项; 在 P6 之上,可增加 广度。
+
+- 校招:基础扎实,思维敏捷。 主要考察内容:基础数据结构与算法、进程与并发、内存管理、系统调用与 IO 机制、网络协议、数据库范式与设计、设计模式、设计原则、编程习惯;
+- 社招:经验丰富,里外兼修。 主要考察内容:有一定深度的基础技术机制,比如 Java 内存模型及内存泄露、 JVM 机制、类加载机制、数据库索引及查询优化、缓存、消息中间件、项目、架构设计、工程规范等。
+
+### 技术基础是什么?
+
+作为技术初试官,怎么去考察技术基础?究竟什么是技术基础?是知道什么,还是知道如何思考?知识作为现有的成熟原理体系,构成了基础的重要组成部分,而知道如何思考亦尤为重要。俗话说,知其然而知其所以然。知其然,是指熟悉现有知识体系,知其所以然,则是自底向上推导,真正理解知识的来龙去脉,理解为何是这样而不是那样。毕竟,对于本质是逻辑的程序世界而言,并无定法。知道如何思考,并能缜密地设计和开发,深入到细节,这就是技术基础吧。
+
+### 为什么要考察技术基础?
+
+程序员最重要的两种技术思维能力,是逻辑思维能力和抽象设计能力。逻辑思维能力是基础,抽象设计能力是高阶。 考察技术基础,正好可以同时考察这两种思维能力。能不能理解基础技术概念及关联,是考察逻辑思维能力;能不能把业务问题抽象成技术问题并合理的组织映射,是考察抽象设计能力。
+
+绝大部分业务问题,都可以抽象成技术问题。在某种意义上,业务问题只是技术问题的领域化表述。
+
+因此,通过技术基础考察候选者,才能考察到候选者的真实技术实力:技术深度和广度。
+
+### 为什么不能单考察业务维度?
+
+因为业务方面通常比较熟悉,可能就直接按照现有方案说出来了,很难考察到候选人的深入理解、横向拓展和归纳总结能力。
+
+这一点,建议有针对性地考察下候选人的归纳总结能力:比如, 微服务搭建或开发或维护/保证系统稳定性或性能方面的过程中,你收获了哪些可以分享的经验?
+
+### 为什么要考察业务维度?
+
+技术基础考察,容易错过的地方是,候选人的非技术能力特质,比如沟通组织能力、带项目能力、抗压能力、解决实际问题的能力、团队影响力、其它性格特质等。
+
+## 考察方法
+
+### 技术基础考察
+
+技术基础怎么考察?通过有效的多角度的发问模式来考察。
+
+**是什么-为什么**
+
+是什么考察对概念的基本理解,为什么考察对概念的实现原理。
+
+比如索引是什么? 索引是如何实现的?
+
+**引导-横向发问-深入发问**
+
+引导性,比如 “你对 java 同步工具熟悉吗?” 作个试探,得到肯定答复后,可以进一步问:“你熟悉哪些同步工具类?” 了解候选者的广度;
+
+获取候选者的回答后,可以进一步问:“ 谈谈 ConcurrentHashMap 或 AQS 的实现原理?”
+
+一个人在多大程度上把技术原理能讲得清晰,包括思路和细节,说明他对技术的掌握能力有多强。
+
+**深度有梯度和层次的发问**
+
+设置三个深度层次的发问。每个深度层次可以对应到某个技术深度。
+
+- 第一个发问是基本概念层次,考察候选人对概念的理解能力和深度;
+- 第二个发问是原理机制层次,考察候选人对概念的内涵和外延的理解深度;
+- 第三个发问是应用层次,考察候选人的应用能力和思维敏捷程度。
+
+**跳跃式/交叉式发问**
+
+比如,讲到哈希高效查找,可以谈谈哈希一致性算法 。 两者既有关联又有很多不同点。也是一种技术广度的考察方法。
+
+**总结性发问**
+
+比如,你在做 XXX 中,获得了哪些可以分享的经验? 考察候选人的归纳总结能力。
+
+**实战与理论结合**
+
+- 比如,候选人叙述 JVM 内存模型布局之后,可以接着问:有哪些原因可能会导致 OOM , 有哪些预防措施? 你是否遇到过内存泄露的问题? 如何排查和解决这类问题?
+- 比如,候选人有谈到 SQL 优化和索引优化,那就正好谈谈索引的实现原理,如何建立最佳索引?
+- 比如,候选人有谈到事务,那就正好谈谈事务实现原理,隔离级别,快照实现等;
+
+**熟悉与不熟悉结合**
+
+针对候选人简历上写的熟悉的部分,和没有写出的都问下。比如候选人简历上写着:熟悉 JVM 内存模型, 那我就考察下内存管理相关(熟悉部分),再考察下 Java 并发工具类(不确定是否熟悉部分)。
+
+**死知识与活知识结合**
+
+比如,查找算法有哪些?顺序查找、二分查找、哈希查找。这些大家通常能说出来,也是“死知识”。
+
+这些查找算法各适用于什么场景?在你工作中,有哪些场景用到了哪些查找算法?为什么? 这些是“活知识”。
+
+**学习或工作中遇到的**
+
+有时,在学习和工作中遇到的问题,也可以作为面试题。
+
+比如,最近在学习《操作系统导论》并发部分,有一章节是如何使数据结构成为线程安全的。这里就有一些可以提问的地方:如何实现一个锁?如何实现一个线程安全的计数器?如何实现一个线程安全的链表?如何实现一个线程安全的 Map ?如何提升并发的性能?
+
+工作中遇到的问题,也可以抽象提炼出来,作为技术基础面试题。
+
+**技术栈适配度发问**
+
+如果候选人(简历上所写的)使用的某些技术与本公司的技术栈比较契合,则可以针对这些技术点进行深入提问,考察候选人在这些技术点的掌握程度。如果掌握程度比较好,则技术适配度相对更高一些。
+
+当然,这一点并不能作为筛掉那些没有使用该技术栈的候选人的依据。比如本公司使用 MongoDB 和 MySQL, 而一个候选人没有用过 Mongodb, 但使用过 MySQL, Redis, ES, HBase 等多种存储系统,那么适配度并不比仅使用过 MySQL 和 Mongodb 的候选人逊色,因为他所涉及的技术广度更大,可以推断出他有足够能力掌握 Mongodb。
+
+**应对背题式面试**
+
+首先,背题式面试,说明候选人至少是有做准备的。当然,对于招聘的一方来说,更希望找到有能力而不是仅记忆了知识的候选人。
+
+应对背题式面试,可以通过 “引导-横向发问-深入发问” 的方式,先对候选人关于某个知识点的深度和广度做一个了解,然后出一道实际应用题来考察他是否能灵活使用知识。
+
+比如 Java 线程同步机制,可以出一道题:线程 A 执行了一段代码,然后创建了一个异步任务在线程 B 中执行,线程 A 需要等待线程 B 执行完成后才能继续执行,请问怎么实现?
+
+”理论 + 应用题“的模式。敌知我之变,而不知我变之形。变之形,不计其数。
+
+**实用不生僻**
+
+考察工作中频繁用到的知识、技能和能力,不考察冷僻的知识。
+
+比如我偏向考察数据结构与算法、并发、设计 这三类。因为这三类非常基础非常核心。
+
+**综合串联式发问**
+
+知识之间总是相互联系着的,不要单独考察一个知识点。
+
+设计一个初始问题,比如说查找算法,然后从这个初始问题出发,串联起各个知识点。比如:
+
+
+
+在每一个技术点上,都可以应用以上发问技巧,导向不同的问题分支。同时考察面试者的深度、广度和应用能力。
+
+**创造有个性的面试题库**
+
+每个技术面试官都会有一个面试题库。持续积累面试题库,日常中突然想到的问题,就随手记录下来。
+
+### 解决问题能力考察
+
+仅仅只是技术基础还不够,通常最好结合实际业务,针对他项目里的业务,抽象出技术问题进行考察。
+
+解决思路重在层层递进。这一点对于面试官的要求也比较高,兼具良好的倾听能力、技术深度和业务经验。首先要仔细倾听候选人的阐述,找到适当的技术切入点,然后进行发问。如果进不去,那就容易考察失败。
+常见问题:
+
+- 性能方面,qps, tps 多少?采用了什么优化措施,达成了什么效果?
+- 如果有大数据量,如何处理?如何保证稳定性?
+- 你觉得这个功能/模块/系统的关键点在哪里?有什么解决方案?
+- 为什么使用 XXX 而不是 YYY ?
+- 长字段如何做索引?
+- 还有哪些方案或思路?各自的利弊?
+- 第三方对接,如何应对外部接口的不稳定性?
+- 第三方对接,对接大量外部系统,代码可维护性?
+- 资损场景?严重故障场景?
+- 线上出现了 CPU 飙高,如何处理? OOM 如何处理? IO 读写尖刺,如何排查?
+- 线上运行过程中,出现过哪些问题?如何解决的?
+- 多个子系统之间的数据一致性问题?
+- 如果需要新增一个 XXX 需求,如何扩展?
+- 重来一遍,你觉得可以在哪些方面改进?
+
+系统可问的关联问题:
+
+- 绝大多数系统都有性能相关问题。如果没有性能问题,则说明是小系统,小系统就不值得考察了;
+- 中大型系统通常有技术选型问题;
+- 绝大多数系统都有改进空间;
+- 大多数业务系统都涉及可扩展性问题和可维护性问题;
+- 大多数重要业务系统都经历过比较惨重的线上教训;
+- 大数据量系统必定有稳定性问题;
+- 消费系统必定有时延和堆积问题;
+- 第三方系统对接必定涉及可靠性问题;
+- 分布式系统必定涉及可用性问题;
+- 多个子系统协同必定涉及数据一致性问题;
+- 交易系统有资损和故障场景;
+
+**设计问题**
+
+- 比如多个机器间共享大量业务对象,这些业务对象之间有些联合字段是重复的,如何去重? 如果字段比较长,怎么处理?
+- 如果瞬时有大量请求涌入,如何保证服务器的稳定性?
+- 组件级别:设计一个本地缓存? 设计一个分布式缓存?
+- 模块级别:设计一个任务调度模块?需要考虑什么因素?
+- 系统级别:设计一个内部系统,从各个部门获取销售数据然后统计出报表。复杂性体现在哪里?关键质量属性是哪些?模块划分,模块之间的关联关系?技术选型?
+
+**项目经历**
+
+项目经历考察不宜超过两个。因为要深入考察一个项目的详情,所占用的时间还是比较大的。
+
+一般来说,会让候选人挑选一个他或她觉得最有收获的/最有挑战的/印象最深刻的/自己觉得特有意思/感受到挫折的项目。然后围绕这个项目进行发问。通常是从项目背景出发,考察项目的技术栈、项目模块及交互的整体理解、项目中遇到的有挑战性的技术问题及解决方案、排查和解决问题、代码可维护性问题、工程质量保障、重来一遍可以改进哪些等。
+
+## 面试过程
+
+### 预先准备
+
+面试官也需要做一些准备。比如熟悉候选者的技能优势、工作经历等,做一个面试设计。
+
+在面试将要开始时,做好面试准备。此外,面试官也需要对公司的一些基本情况有所了解,尤其是公司所使用技术栈、业务全景及方向、工作内容、晋升制度等,这一点技术型候选人问得比较多。
+
+### 面试启动
+
+一般以候选人自我介绍启动,不过候选人往往会谈得比较散,因此,我会直接提问:谈谈你有哪些优势以及自己觉得可以改进的地方?
+
+然后以一个相对简单的基础题作为技术提问的开始:你熟悉哪些查找算法?大多数人是能答上顺序查找、二分查找、哈希查找的。
+
+### 问题设计
+
+提前阅读候选人简历,从简历中筛选出关键词,根据这些关键词进行有针对性地问题设计。
+
+比如候选人简历里提到 MVVM ,可以问 MVVM 与 MVC 的区别; 提到了观察者模式,可以谈谈观察者模式,顺便问问他还熟悉哪些设计模式。
+
+可遵循“优势-标准-随机”原则:
+
+- 首先,问他对哪方面技术感兴趣、投入较多(优势部分),根据其优势部分,阐述原理及实战应用;
+- 其次,问若干标准化的问题,看看他的原理理解、实战应用如何;
+- 最后,随机选一个问题,看看他的原理理解、实战应用如何;
+
+对于项目同样可以如此:
+
+- 首先,问他最有成就感的项目,技术栈、模块及关联、技术选型、设计关键问题、解决方案、实现细节、改进空间;
+- 其次,问他有挫折感的项目,问题在哪里、做过什么努力、如何改进;
+
+### 宽松氛围
+
+即使问的问题比较多比较难,也要注意保持宽松氛围。
+
+在面试前,根据候选人基本信息适当调侃一下,比如一位候选人叫汪奎,那我就说:之前我们团队有位叫袁奎,我们都喊他奎爷。
+
+在面试过程中,适当提示,或者给出少量自己的看法,也能缓解候选人的紧张情绪。
+
+### 学会倾听
+
+多问少说,让候选者多表现。根据候选者的回答适当地引导或递进或横向移动。
+
+引导候选人表现他最优势的一面,让他或她感觉好一些:毕竟一场面试双方都付出了时间和精力,不应该是面试官 Diss 候选人的场合,而应该让彼此有更好的交流。很大可能,你也能从候选人那里学到不少东西。
+
+面试这件事,只不过双方的角色和立场有所不同,但并不代表面试官的水平就一定高于候选人。
+
+### 记录重点
+
+认真客观地记录候选人的回答,尽可能避免任何主观评价,亦不作任何加工(比如自己给总结一下,总结能力也是候选人的一个特质)。
+
+### 多练习
+
+模拟面试。
+
+### 作出判断
+
+面试过程是一种铺垫,关键的是作出判断。
+
+作出判断最容易陷入误区的是:贪深求全。总希望候选人技术又深入又全面。实际上,这是一种奢望。如果候选人的技术能力又深入又全面,很可能也会面临两种情况:1. 候选人有更好的选择; 2. 候选人在其它方面可能存在不足,比如团队协作方面。
+
+一个比较合适的尺度是:1. 他或她的技术水平能否胜任当前工作; 2. 他或她的技术水平与同组团队成员水平如何; 3. 他或她的技术水平是否与年限相对匹配,是否有潜力胜任更复杂的任务。
+
+### 不同年龄看重的东西不一样
+
+对于三年以下的工程师,应当更看重其技术基础,因为这代表着他的未来潜能;同时也考察下他在实际开发中的体现,比如团队协作、业务经验、抗压能力、主动学习的热情和能力等。
+
+对于三年以上的工程师,应当更看重其业务经验、解决问题能力,看看他或她是如何分析具体问题,在业务范畴内考察其技术基础的深度和广度。
+
+如何判断一个候选人的真实技术水平及是否适合所需,这方面,我也在学习中。
+
+## 面试初上路
+
+- 提前准备好摄像头和音频,可以用耳机测试下。
+- 提前阅读候选人简历,从中筛选关键字,准备几个基本问题。
+- 多问技术基础题,培养下面试感觉。
+- 适当深入问下原理和实现。
+- 如果候选人简历有突出的地方,就先问那个部分;如果没有,就让候选人介绍项目背景,根据项目背景及经验来提问。
+- 小量练习“连问”技巧,直到能够熟悉使用。
+- 着重考察分析和解决问题的能力,必要的话,可以出个编程题。
+- 留出时间给对方问:你有什么想问的?并告知对方三个工作日内回复面试结果。
+
+## 高效考察
+
+当作为技术面试官有一定熟悉度时,就需要提升面试效率。即:在更少的时间内有效考察候选人的技术深度和技术广度。可以准备一些常见的问题,作为标准化测试。
+
+比如我喜欢考察内存管理及算法、数据库索引、缓存、并发、系统设计、问题分析和思考能力等子主题。
+
+- 熟悉哪些用于查找的数据结构和算法? 请任选一种阐述其思想以及你认为有意思的地方。
+- 如果运行到一个 Java 方法,里面创建了一个对象列表,内存是如何分配的?什么时候可能导致栈溢出?什么时候可能导致 OOM ? 导致 OOM 的原因有哪些?如何避免? 线上是否有遇到过 OOM ,怎么解决的?
+- Java 分代垃圾回收算法是怎样的? 项目里选用的垃圾回收器是怎样的?为什么选择这个回收器而不是那个?
+- Java 并发工具有哪些?不同工具适合于什么场景?
+- `Atomic` 原子类的实现原理 ? `ConcurrentHashMap` 的实现原理?
+- 如何实现一个可重入锁?
+- 举个项目中的例子,哪些字段使用了索引?为什么是这些字段?你觉得还有什么优化空间?如何建一个好的索引?
+- 缓存的可设置的参数有哪些?分别的影响是什么?
+- Redis 过期策略有哪些? 如何选择 redis 过期策略?
+- 如何实现病毒文件检测任务去重?
+- 熟悉哪些设计模式和设计原则?
+- 从 0 到 1 搭建一个模块/完整系统?你如何着手?
+
+如果候选人答不上,可以问:如果你来设计这样一个 XXX, 你会怎么做?
+
+时间占比大概为:技术基础(25-30 分钟) + 项目(20-25 分钟) + 候选人提问(5-10 分钟)
+
+## 给候选人的话
+
+**为什么候选人需要关注技术基础**
+
+一个常见的疑惑是:开发业务系统的大多数时候,基本不涉及数据结构与算法的设计与实现,为什么要考察 `HashMap` 的实现原理?为什么要学好数据结构与算法、操作系统、网络通信这些基础课程?
+
+现在我可以给出一个答案了:
+
+- 正如上面所述,绝大多数的业务问题,实际上最终都会映射到基础技术问题上:数据结构与算法的实现、内存管理、并发控制、网络通信等;这些是理解现代互联网大规模程序以及解决程序疑难问题的基石,—— 除非能祝福自己永远都不会遇到疑难问题,永远都只满足于编写 CRUD;
+- 这些技术基础正是程序世界里最有趣最激动人心的地方。如果对这些不感兴趣,就很难在这个领域里深入进去,不如及早转行从事其它职业,非技术的世界一直都很精彩广阔(有时我也想多出去走走,不想局限于技术世界);
+- 技术基础是程序员的内功,而具体技术则是招式。徒有招式而内功不深,遇到高手(优秀同行从业者的竞争及疑难杂症)容易不堪一击;
+- 具备扎实的专业技术基础,能达到的上限更高,未来更有可能胜任复杂的技术问题求解,或者在同样的问题上能够做到更好的方案;
+- 人们喜欢跟与自己相似的人合作,牛人倾向于与牛人合作能得到更好的效果;如果一个团队大部分人技术基础比较好,那么进来一个技术基础比较薄弱的人,协作成本会变高;如果你想和牛人一起合作拿到更好的结果,那就要让自己至少在技术基础上能够与牛人搭配的上;
+- 在 CRUD 的基础上拓展其它才能也不失为一种好的选择,但这不会是一个真正的程序员的姿态,顶多是有技术基础的产品经理、项目经理、HR、运营、客满等其它岗位人才。这是职业选择的问题,已经超出了考察程序员的范畴。
+
+**不要在意某个问题回答不上来**
+
+如果面试官问你很多问题,而有些没有回答上来,不要在意。面试官很可能只是在测试你的技术深度和广度,然后判断你是否达到某个水位线。
+
+重点是:有些问题你答得很有深度,也体现了你的深度思考能力。
+
+这一点是我当了技术面试官才领会到的。当然,并不是每位技术面试官都是这么想的,但我觉得这应该是个更合适的方式。
+
+## 参考资料
+
+- [技术面试官的 9 大误区](https://zhuanlan.zhihu.com/p/51404304)
+- [如何当一个好的面试官?](https://www.zhihu.com/question/26240321)
diff --git a/docs/high-quality-technical-articles/interview/my-personal-experience-in-2021.md b/docs/high-quality-technical-articles/interview/my-personal-experience-in-2021.md
new file mode 100644
index 0000000000000000000000000000000000000000..dba4a243ab6d0f562774918344d9fee945589b91
--- /dev/null
+++ b/docs/high-quality-technical-articles/interview/my-personal-experience-in-2021.md
@@ -0,0 +1,201 @@
+---
+title: 校招进入飞书的个人经验
+category: 技术文章精选集
+author: 月色真美
+tag:
+ - 面试
+---
+
+> **推荐语**:这篇文章的作者校招最终去了飞书做开发。在这篇文章中,他分享了自己的校招经历以及个人经验。
+>
+>
+>
+> **原文地址**:https://www.ihewro.com/archives/1217/
+
+## 基本情况
+
+我是 C++主要是后台开发的方向。
+
+2021 春招入职字节飞书客户端,入职字节之前拿到了百度 offer(音视频直播部分) 以及腾讯 PCG (微视、后台开发)的 HR 面试通过(还没有收到录用意向书)。
+
+## 不顺利的春招过程
+
+### 春招实习对我来说不太顺利
+
+实验室在 1 月份元旦的那天正式可以放假回家,但回家仍然继续“远程工作”,工作并没有减少,每天日复一日的测试,调试我们开发的“流媒体会议系统”。
+
+在 1 月的倒数第三天,我们开了“年终总结”线上会议。至此,作为研二基本上与实验室的工作开始告别。也正式开始了春招复习的阶段。
+
+2 月前已经间歇性的开始准备,无非就是在 LeetCode 上面刷刷题目,一天刷不了几道,后面甚至象征性的刷一下每日一题。对我的算法刷题帮助很少。
+
+2 月份开始,2 月初的时候,LeetCode 才刷了大概 40 多道题目,挤出了几周时间更新了 handsome 主题的 8.x 版本,这又是一个繁忙的几周。直到春节的当天正式发布,春节过后又开始陆陆续续用一些时间修复 bug,发布修复版本。2 月份这样悄悄溜走。
+
+### 找实习的过程
+
+**2021-3 月初**
+
+3 月 初的时候,投了阿里提前批,没想到阿里 3 月 4 号提前批就结束了,那一天约的一面的电话面也被取消了。紧接了开学实验室开会同步进度的时候,发现大家都一面/二面/三面的进度,而我还没有投递的进度。
+
+**2021-3-8**
+
+投递了字节飞书
+
+**2021-4 月初**
+
+字节第一次一面,腾讯第一次一面
+
+**2021-4 中旬**
+
+美团一、二面,腾讯第二次一面和二面,百度三轮面试,通过了。
+
+**2021-4 底**
+
+腾讯第三次一面和字节第二次一面
+
+**2021-5 月初**
+
+腾讯第三次二面和字节第二次二面,后面这两个都通过了
+
+#### 阿里
+
+第一次投了钉钉,没想到因为行测做的不好,在简历筛选给拒绝了。
+
+第二次阿里妈妈的后端面试,一面电话面试,我感觉面的还可以,最后题目也做出来了。最后反问阶段问对我的面试有什么建议,面试官说投阿里最好还是 Java 的… 然后电话结束后就给我拒了…
+
+当时真的心态有点崩,问了这个晚上 7 点半的面试,一直看书晚上都没吃…
+
+所以春招和阿里就无缘了。
+
+#### 美团
+
+美团一面的面试官真的人很好。也很轻松,因为他们是 Java 岗位,也没问 c++知识,聊了一些基础知识,后面半个小时就是聊非技术问题,比如最喜欢网络上的某位程序员是谁,如何写出优雅的代码,推荐的技术类的书籍之类的。当时回答王垠是比较喜欢的程序员,面试官笑了说他也很喜欢。面试的氛围感觉很好。
+
+二面的时候全程就问简历上的一个项目,问了大概 90 分钟,感觉他从一开始就有点不太想要我的感觉,很大原因我觉的是我是 c++,转 Java 可能成本还是有一些的。最后问 HR 说结果待定,几天后通知被拒了。
+
+#### 百度
+
+百度一共三轮面试,在一个下午一起进行,真的很刺激。一面就是很基础的一些 c++问题,写了一个题目说一下思路没让运行(真的要运行还不一定能运行起来:))
+
+二面也是基础,第一个题目合并两个有序数组,第二个题目写归并排序,写的结果不对,又给我换了一个题目,树的 BFS。二面面试官最后问我对今天面试觉得怎么样,我说虽然中间有一个道题目结果不对,但是思路是对的,可能某个小地方写的有问题,但总体的应该还是可以的。二面就给我通过了。
+
+三面问的技术问题比较少,30 多分钟,也没写题目,问了一些基本情况和基础知识。最后问部门做的什么内容。面试官说后面 hr 会联系我告诉我内容。
+
+#### 字节飞书
+
+第一次一面就凉了,原因应该是笔试题目结果不对…
+
+第二次一面在 4 月底了,很顺利。二面在五一劳动节后,面试官还让学姐告诉我让我多看看智能指针,面试的时候让我手写 shared_ptr,我之前看了一些实现,但是没有自己写过,导致代码考虑的不够完善,leader 就一直提醒我要怎么改怎么改。
+
+本来我以为凉了,在 5 月中旬的时候都准备去百度入职了,给我通知说过了,就这样决定去了字节。
+
+#### 感悟
+
+这么多次面试中,让我感悟最深的是面试中的考察题目真的很重要,因为我在基础知识上面也不突出,再加上如果算法题(一般 1 道或者 2 道)如果没做出来,基本就凉了。而面试之前的笔试考试反而没那么重要,也没那么难。基本 4 题写出来 1~2 道题目就有发起面试的机会了。难度也基本就是 LeetCode top 100 上面的那些算法。
+
+面试中做题,我很容易紧张,头脑就容易一片空白,稍不注意,写错个符号,或者链表赋值错了,很难看出来问题,导出最终结果不对。
+
+## 入职字节实习
+
+入职字节之前我本来觉得这个岗位可能是我面试的最适合我的了,因为我主 c++,而且飞书用 c++应该挺深的。来之后就觉得我可能不太喜欢做客户端相关,感觉好复杂…也许服务端好一些,现在我仍然不能确定。
+
+字节的实习福利在这些公司中应该算是比较好的,小问题是工位比较窄,还是工作强度比其他的互联网公司大一些。字节食堂免费而且挺不错的。字节办公大厦很多,我所在的办公地点比较小。
+
+目前,需要放轻松,仓库代码慢慢看呗,mentor 也让我不急,准备有问题就多问问,不能憋着,浪费时间。拿到转正 offer 后,秋招还是想多试试外企或者国企。强度太大的工作目前很难适应。
+
+希望过段时间可以分享一下我的感受,以及能够更加适应目前的工作内容。
+
+## 求职经验分享
+
+### 一些概念
+
+#### 日常实习与正式(暑期)实习有什么区别
+
+- **日常实习如果一个组比较缺人,就很可能一年四季都招实习生,就会有日常实习的机会**,只要是在校学生都可以去面试。而正式实习开始时间有一个范围比较固定,比如每年的 3-6 月,也就是暑期实习。
+- 日常实习相对要好进一些,但是有的日常实习没有转正名额,这个要先确认一下。
+- **字节的日常实习和正式实习在转正没什么区别,都是一起申请转正的。**
+
+#### 正式实习拿到 offer 之后什么时候可以去实习
+
+暑期实习拿到 offer 后就**可以立即实习**(一般需要走个流程 1 周左右的样子),**也可以选择晚一点去实习**,时间可以自己去把握,有的公司可以在系统上选择去实习的时间,有的是直接和 hr 沟通一下就可以。
+
+#### 提前批和正式批的区别
+
+以找实习为例:
+
+- 先提前批,再正式批,提前批一般是小组直接招人**不进系统**,**没有笔试**,**流程相对走的快**,一般一面过了,很快就是二面。
+- 正式批面试都会有面评,如果上一次失败的面试评价会影响下一次面试,所以还是谨慎一点好
+
+#### 实习 offer 和正式 offer 区别
+
+简单来说,实习 offer 只是给你一个实习的机会,如果在实习期间干的不错就可以转正,获得正式 offer。
+
+签署正式 offer 之后并不是意味着马上去上班,因为我们是校招生,拿到正式 offer 之后,可以继续实习(工资会是正式工资的百分比),也可以请假一段时间等真正毕业的时候再去正式工作。
+
+### 时间节点
+
+> 尽早把简历弄出来,最好就是最近一段时间,因为大家对实验室项目现在还很熟悉,现在写起来不是很难,再过几个月写简历就比较痛苦了。
+
+以去年为例:
+
+- 2 月份中旬的时候阿里提前批开始(基本上只有阿里这个时候开了提前批),3 月 8 号阿里提前批结束。腾讯提前批是 3 月多开始的,4 月 15 号结束
+- 3-5 月拿到实习 offer,最好在 4 月份可以拿到比较想去的实习 offer。
+- 4-8 月份实习,7 月初秋招提前批,7 月底或者 8 月初就是秋招正式批,9 月底秋招就少了挺多,但是只是相对来说,还是有机会,
+- 10 月底秋招基本结束,后面还会有秋招补录
+
+---
+
+- **怎么找实习机会**,个人觉得可以找认识的人内推比较好,内推好处除了可以帮看进度,一般可以直推到组,这样可以排除一些坑的组。提前知道这个组干嘛的。
+- **实习挺重要,最好是实习的时候就找到一个想去的公司,秋招会轻松很多**,因为实习转正基本没什么问题,其次实习转正的 offer 一般要比秋招的好(当然如果秋招表现好也是可以拿到很好的 offer)身边不少人正式 offer 都是实习转正的。
+- **控制好实习的时间**,因为边实习边准备秋招挺累的,一般实习的时候工作压力也挺大,没什么时间刷题。
+
+### 面试准备
+
+#### 项目经历
+
+我觉得我们实验室项目是没问题的,重要是要讲好。
+
+- **项目介绍**
+
+首先可能让你介绍一下这个项目是什么东西,以及**为什么要去做这个项目**。
+
+- **项目的结果**
+
+然后可能会问这个项目的一些数据上最终结果,比如会议系统能够同时多少人使用,或者量化的体验,比如流畅度,或者是一些其他的一些优势。
+
+- **项目中的困难**
+
+最后都会问过程中有没有遇到什么困难、挑战的,以及怎么解决的。这个过程中主要考察这个项目的技术点是什么。
+
+> 困难是指什么,个人觉得主要是花了好几天才解决的问题就是困难。
+
+举两个例子:
+
+**第一个例子是排查 bug 方面**,比如有一个内存泄露的问题花了一周才排查出来,那就算一个困难,那么解决这个困难的过程就是**如何去定位这个问题过程**,比如我们先根据错误搜索相关资料,肯定没那么容易就直接找到原因,而是我们会在这些资料中找到一些**关键词**,比如一些工具,那么我们对这个工具的使用就是解决问题的一个过程。
+
+**第二个例子是需求方案的设计**,比如某个需求完成,我们实现这个需求可能有多个可行的设计方案。解决这个困难的过程就是**我们对最终选择这个方法的原因,以及其他的设计方案的优缺点的思考**。
+
+[面试中被问到:你在工作中碰到的最困难的问题是什么?*发现问题,解决问题.-CSDN 博客*面试中问到工作中遇到困难是怎么解决的](https://blog.csdn.net/u012423865/article/details/79452713)
+
+有人说我解决方法就是通过百度搜索,但实际上细节也是先搜索某个错误或者问题,但是肯定不可能一下子就搜到了代码答案,而是找到一个答案中有某个关键词,接着我们继续找关键词获取其他的信息。
+
+#### 笔试
+
+找实习的笔试我觉得不会太难,一般如果是 4 道题目,做出来 1-2 道题目差不多就有面试的机会了。
+
+刷题老生常谈的问题,LeetCode Top100。一开始刷题很痛苦,等刷了 40 道题目的时候就有点感觉的,建议从链表、二叉树开始刷,数组类型题目有很多不能通用的技巧。
+
+- ::一定要用白版进行训练::,一定要用白板,不仅仅是为了面试记住 API,更重要的是用白板熟练后,写代码会更熟练而且思路更独立和没有依赖。
+- 算法题重中之重,终点不是困难题目,而是简单,中等,常见,高频的题目要熟能生巧,滚瓜烂熟。
+- 面试的笔试过程中,如果出现了问题,**一定要第一时间申请使用本地 IDE 进行调试**,否则可能很长时间找不到问题,浪费了机会。
+
+#### 面试
+
+面试一般 1 场 1 个小时候分为两个部分,前半部分会问一些基础知识或者项目经历,后半部分做题。
+
+**基础知识复习一开始没必要系统的去复习,首先是确保高频问题必会**,比如计算机网络、操作系统那几个必问的问题,可以多看看面经就能找到常问题的问题,对于比较偏问题就算没答上来也不是决定性的影响。
+
+- **多看面经!!!!!!** 不要一直埋头自己学,要看别人问过了哪些常问的问题。
+- 对于实习工作,**看的知识点常见的问题一定要全!!!!!**,不是那么精问题不大,一定要全,一定要全!!!!
+- **对于自己不会的,尽量多的说!!!!** 实在不行,就往别的地方说!!!总之是引导面试官往自己会的地方上说。
+- 面试中的笔试和前面的笔试风格不同,面试笔试题目不太难,但是考察是冷静思考,代码优雅,没有 bug,先思考清楚!!!在写!!!
+- 在描述项目的难点的时候,不要去聊文档调研是难点,回答这部分问题更应该是技术上的难点,最后通过了什么技术解决了这个问题,这部分技术可以让面试官来更多提问以便知道自己的技术能力。
diff --git a/docs/high-quality-technical-articles/interview/screen-candidates-for-packaging.md b/docs/high-quality-technical-articles/interview/screen-candidates-for-packaging.md
new file mode 100644
index 0000000000000000000000000000000000000000..b2ed4c772b319b0d6fe871aa95dfae47c716b3cc
--- /dev/null
+++ b/docs/high-quality-technical-articles/interview/screen-candidates-for-packaging.md
@@ -0,0 +1,118 @@
+---
+title: 如何甄别应聘者的包装程度
+category: 技术文章精选集
+author: Coody
+tag:
+ - 面试
+---
+
+> **推荐语**:经常听到培训班待过的朋友给我说他们的老师是怎么教他们“包装”自己的,不光是培训班,我认识的很多朋友也都会在面试之前“包装”一下自己,所以这个现象是普遍存在的。但是面试官也不都是傻子,通过下面这篇文章来看看面试官是如何甄别应聘者的包装程度。
+>
+>
+>
+> **原文地址**:https://my.oschina.net/hooker/blog/3014656
+
+## 前言
+
+上到职场干将下到职场萌新,都会接触到包装简历这个词语。当你简历投到心仪的公司,公司内负责求职的工作人员是如何甄别简历的包装程度的?我根据自己的经验写下了这篇文章,谁都不是天才,包装无可厚非,切勿对号入座!
+
+## 正文
+
+在互联网极速膨胀的社会背景下,各行各业涌入互联网的 IT 民工日益增大。
+
+早在 2016 年,我司发布了 Java、Ios 工程师的招聘信息,就 Java 工程师单个岗位而言,日收简历近 200 份,Ios 日收简历近一千份。
+
+没错,这就是当年培训机构对 Ios 工程师这个岗位发起的市场讨伐。而随着近几年的发展,市场供大于求现象日益严重。人员摸底成为用人单位对人才考核的重大难题。
+
+笔者初次与求职者以面试的形式进行沟通是 2015 年 6 月。由于当时笔者从业时间短,经验不够丰富,错过了一些优秀的求职者。
+
+三年后的,今天,笔者再次因公司规模扩大而深入与求职者进行沟通。
+
+### 1.初选如何鉴别劣质简历
+
+培训机构除了提供技术培训,往往还提供**简历编写指导**、**面试指导**。很多潜移默化的东西,我们很难甄别。但培训机构包装的简历,存在千遍一律的特征。
+
+**年龄较小却具备高级文凭**
+
+年龄较小却具备高级文凭,这个或许不能作为一项标准,但是大部分的应聘者,均符合传统文凭的市场情况。个别技术爱好者可能通过自考获得文凭,这种情况需提供独有的技术亮点。
+
+**年龄较大却几乎不具备技术经验**
+
+年龄较大却几乎不具备技术经验,相对前一点,这个问题就比较严重了。大家都知道,一个正常的人,对新事物的接受能力会随着年龄的增长而降低,互联网技术也包括其内。如果一个人年龄较大不具备技术经验,那么只有两种情况:
+
+1. 中途转行(通过培训、自学等方式强行入行)。
+2. 由于能力问题,已有的经验不敢写入简历中(能力与经验/薪资不符)。
+
+**项目经验多为管理系统**
+
+项目经验,这一项用来评估应聘者的水平太合适不过了。随着互联网的发展迭代,每一年都会出来很多创新型的互联网公司和新兴行业。笔者最近发布的招聘需求里面。CRM 系统、商城、XX 管理系统、问卷系统、课堂系统占了 90%的份额。试问现在 2019 年,内部管理系统这么火爆么。言归正传,我们对于简历的评估,应当多考虑“确有其事”的项目。比如说该人员当时就职于 XX 公司,该公司当时的背景下确实研发了该项目(外包除外)。
+
+**项目的背景不符合互联网发展背景**
+
+项目背景,每年的市场走向不同,从早些年的电商、彩票风波,到后来的 O2O、夺宝、直播、新零售。每个系列的产品的出现,都符合市场的定义。如果简历中出现 18 年、19 年才刚立项做彩票(15 年政府禁止互联网彩票)、O2O、商城、夺宝(17 年初禁止夺宝类产品)、直播等产品。显然是非常不符合市场需求的。这种情况下需考虑具体情况是否存在理解空间。
+
+**缺乏新意**
+
+不同工作经验下多个项目技术架构或项目结构一致,缺乏新意。一般情况而言,不同的公司技术栈不同,甚至产品的走向和模式完全不同。故此,当一个应聘者多家公司的多个项目中写到的技术千遍一律,业务流程异曲同工。看似整洁,实则更加缺乏说服力。
+
+**技术过于新颖,对旧技术却只字不提**
+
+技术过于新颖,根据互联网技术发展的走向来看,我们在不断向新型技术靠拢。但是任何企业作为资历深厚的 CTO、架构师来说。往往会选择更稳定、更成熟、学习成本更低的已有技术。对新技术的追求不会过于明显。而培训机构则是“哪项技术火我们就教哪项”。故此,出现了很多走入互联网行业的新人对旧技术一窍不通。甚至很多技术都没听过。
+
+**工作经验较丰富,但从事的工作较低级。**
+
+工作经验比较丰富,单从事的工作比较低级,这里存在很大的问题,要么就是原公司没法提供合理的舞台给该人员更好的发展空间,要么就是该人员能力不够,没法完成更高级的工作。当然,还有一种情况就是该人员包装过多的经验导致简历中不和谐。这种情况需要评估公司规模和背景。
+
+**公司背景跨省跨市**
+
+可能很多用人单位和鄙人一样,最近接受到的简历,90%为跨市跳槽的人员。其中武汉占了 60%以上。均为武汉 XX 网络科技有限公司。公司规模均小于 50 人。也有厦门、宁波、南京等等。这个问题笔者就不提了,大家都懂的。跨地区跳槽不好查证。
+
+**缺少业余热情于技术的证明**
+
+有些眼高手低的技术员,做了几个管理系统。用到的技术确是各种分布式、集群、高并发、大数据、消息队列、搜索引擎、镜像容器、多数据库、数据中心等等。期望的薪资也高于行业标准。一个对技术很热情的人,业余时间肯定在技术方面花费过不少时间。那么可以从该人员的博客、git 地址入手。甚至可以通过手机号、邮箱、昵称、马甲。去搜索引擎进行搜集,核实该人员是否在论坛、贴吧、开源组织有过技术背景。
+
+### 2. 进入面试阶段,如何甄别对方的水分
+
+在甄别对方水分这一块,并没有明确的标准,但是笔者可以提几个点。这也是笔者在实际面试中惯用的做法。
+
+**通过公司规模、团队规模、人员分配是否合理、人员合作方式来判断对方是否具备工作经验**
+
+当招聘初级、初中级 IT 人员的时候,可以询问一些问题,比如公司有多少人、产品团队多少人、产品、技术、后端、前端、客户端、UI、测试各多少人。工作中如何合作的、产品做了多少时间、何时上线的、上线后多长时间迭代一个版本、多长时间迭代一个活动、发展至今多少用户(后端)、多大并发等等(后端)。根据笔者的经验,如果一个人没有任何从业周期,面对这些问题的时候,或多或少答非所问或者给出的答案非常不合理。
+
+**背景公司入职时间、项目立项实现、完工时间、产品技术栈、迭代流程的核实**
+
+很多应聘者对于简历过于包装,只为了追求更高的薪资。当我们问起:你是 xx 年 xx 月入职的该公司?你们项目是 xx 年 xx 月上线的?你们项目使用到 xx 技术?你们每次上线前夕是如何评审的。面对这些问题,应聘者给出的答案经常与简历不符合。这样问题就来了。关于项目使用到的技术,很多项目我们可以通过搜索该项目的地址、APP。通过 HTTP 协议、技术特征、抛出异常特征来大致判别对方使用到的技术。如果应聘者给出的答案明显与之不匹配,嘿嘿。
+
+**通过技术深度,甄别对方的技术水平**
+
+1. 确定对方的技术栈,如:你做过最满意的项目是哪个,为什么?你最喜欢使用的技术是哪些,为什么?
+
+2. 确定对方项目的发展程度,如:你们产品做了多久,迭代了多久,发布了多少版本,发展到了多少用户,带来多大并发,多少流水?
+
+3. 确定对方的技术属性,如:平时你会通过什么渠道跟其他技术人形成技术沟通与交流,主要交流过哪些技术?
+
+笔者最近接待的面试者,很多面试者的简历上,写着层出不穷的各种技术,为了不跨越求职者的技术栈,笔者专门挑应聘者简历写到或用到的技术来进行询问。笔者举几个例子。
+
+**1)某求职者简历上写着熟练使用 Redis。**
+
+1. 介绍一下你使用过 Redis 的哪些数据结构,并描述一下使用的业务场景;
+2. 介绍一下你操作 Redis 用到的是什么插件;
+3. 介绍一下你们使用的序列化方式;
+4. 介绍一下你们使用 Redis 遇到过给你印象较深的问题;
+
+**2)某求职者声称熟练 HTTP 协议并编写过爬虫。**
+
+1. 介绍一下你所了解的几个 HTTP head 头并描述其用途;
+2. 如果前端提交成功,后端无法接受数据,这时候你将如何排查问题;
+3. 描述一下 HTTP 基本报文结构;
+4. 如果服务器返回 Cookie,存储在响应内容里面 head 头的字段叫做什么;
+5. 当服务端返回 Transfer-Encoding:chunked 代表什么含义
+6. 是否了解分段加载并描述下其技术流程。
+
+当然,面向不同的技术,对应的技术深度自然也不一样。
+
+大体上的套路便是如此:你说你杀过猪。那么你杀过几头猪,分别是啥时候,杀过多大的猪,有啥毛色。事实上对方可能给你的回答是:杀过、十几头、杀过五十斤的、杀过绿色、黄色、红色、蓝色的猪。那么问题就来了。
+
+然而笔者碰到的问题是:使用 Git 两年却不知道 GitHub、使用 Redis 一年却不知道数据结构也不知道序列化、专业做爬虫却不懂 `content-type` 含义、使用搜索引擎技术却说不出两个分词插件、使用数据库读写分离却不知道同步延时等等。
+
+写在最后,笔者认为在招聘途中,并不是不允许求职者包装,但是尽可能满足能筹平衡。虽然这篇文章没有完美的结尾,但是笔者提供了面试失败的各种经验。笔者最终招到了如意的小伙伴。也希望所有技术面试官早日找到符合自己产品发展的 IT 伙伴。
diff --git a/docs/high-quality-technical-articles/interview/some-secrets-about-alibaba-interview.md b/docs/high-quality-technical-articles/interview/some-secrets-about-alibaba-interview.md
new file mode 100644
index 0000000000000000000000000000000000000000..32a33086c4e7038ee4fdf7cea84a21ec221eb250
--- /dev/null
+++ b/docs/high-quality-technical-articles/interview/some-secrets-about-alibaba-interview.md
@@ -0,0 +1,119 @@
+---
+title: 阿里技术面试的一些秘密
+category: 技术文章精选集
+author: 龙叔
+tag:
+ - 面试
+---
+
+> **推荐语**:详细介绍了求职者在面试中应该具备哪些能力才会有更大概率脱颖而出。
+>
+>
+>
+> **原文地址:** https://mp.weixin.qq.com/s/M2M808PwQ2JcMqfLQfXQMw
+
+最近我的工作稍微轻松些,就被安排去校招面试了
+
+当时还是有些**激动**的,以前都是被面试的,现在我自己也成为一个面试别人的面试官
+
+接下来就谈谈我的面试心得(谈谈阿里面试的秘籍)
+
+## 我是怎么筛选简历的?
+
+面试之前都是要筛选简历,这个大家应该知道
+
+阿里对待招聘非常负责任,面试官必须对每位同学的简历进行查看和筛选,如果不合适还需要写清楚理由
+
+对于校招生来说,第一份工作非常重要,而且校招的面试机会也只有一次,一旦收到大家的简历意味着大家非常认可和喜爱阿里这家公司
+
+所以我们对每份简历都会认真看,大家可以非常放心,不会无缘无故挂掉大家的简历
+
+尽管我们报以非常负责任的态度,但有些同学们的简历实在是难以下看
+
+关于如何写简历,我之前写过类似的文章,这里就把之前的文章放这里让大家看看 [一份好的简历应该有哪些内容](https://mp.weixin.qq.com/s?__biz=MzI4MDYzNDc1Mg==&mid=2247484010&idx=1&sn=afbe90c8446f5f21631cae750431d3ee&scene=21#wechat_redirect)
+
+在筛选简历的时候会有以下信息非常重要,大家一定要认真写
+
+- **项目经历**,具体写法可以看上面提到的文章
+- **个人含金量比较高的奖项**,比如 ACM 奖牌、计算机竞赛等
+- **个人技能** 这块会看,但是大多数简历写法都差不多,尽量写得**言简意赅**
+- **重要期刊论文发表、开源项目** 加分项
+
+这些信息非常重要,我筛选简历的时候这些信息占整份简历的比重 4/5 左右
+
+## 面试的时候我会注重哪些方面?
+
+### **表达要清楚**
+
+这点是硬伤,在面试的时候有些同学半天说不清楚自己做的项目,我都在替你着急
+
+描述项目有个简单的方法论,我自己总结的 大家看看适不适合自己
+
+- 最好言简意赅的描述一下你的项目背景,让面试官很快知道项目干了啥(让面试官很快对项目感兴趣)
+- 说下项目用了哪些技术,做技术的用了哪些技术得说清楚,面试官会对你的技术比较感兴趣
+- 解决了什么问题,做项目肯定是为了解决问题,总不能为了做项目而做项目吧(解决问题的能力非常重要)
+- 遇到哪些难题,如何突破这些难题,项目遇到困难问题很正常,突破困难才是一次好的成长
+- 项目还有哪些完善的地方,不可能设计出完美的执行方案,有待改进说明你对项目认识深刻,思考深入
+
+一场面试时间一般 60—80 分钟,好的表达有助于彼此之间了解更多的问题
+
+### **基础知识要扎实**
+
+校招非常注重基础知识,所以这块问的问题比较多,我一般会结合你项目去问,看看同学对技术是停留在用的阶段还是有自己的深入思考
+
+每个方向对基础知识要求不同,但有些基础知识是通用的
+
+比如**数据结构与算法**、**操作系统**、**计算机网络** 等
+
+这些基础技术知识一定要掌握扎实,技术岗位都会或多或少去问这些基础
+
+### **动手能力很重要**
+
+action,action,action ,重要的事情说三遍,做技术的不可能光靠一张嘴,能落地才是最重要的
+
+面试官除了问你基础知识和项目还会去考考你的动手能力,面试时间一般不会太长,根据岗位的不同一般会让同学们写一些算法题目
+
+阿里面试,不会给你出非常变态的算法题目
+
+主要还是考察大家的动手能力、思考问题的能力、数据结构的应用能力
+
+在写代码的过程中,我也总结了自己的方法论:
+
+- 上来不要先写,审题、问清楚题目意图,不要自以为是的去理解思路,工作中 沟通需求、明确需求、提出质疑和建议是非常好的习惯
+- 接下来说思路 思路错了写到一半再去改会非常浪费时间
+- 描述清楚之后,先写代码思路的步骤注释,一边写注释,脑子里迭代一遍自己的思路是否正确,是否是最优解
+- 最后,代码规范
+
+## 除了上面这些常规的方面
+
+其实,现在面试已经非常**卷**了,上面说的这些很多都是 **八股文**
+
+有些学生会拿到很多面试题目和答案,反复的去记忆,面试官问问题他就开始在脑子里面检索答案
+
+我一般问几个问题就知道该学生是不是在背八股文了。
+
+对于背八股文的同学,我真的非常难过。
+
+尽管你背的很好,但不能给你过啊,得对得起自己职责,得对公司负责啊!
+
+背的在好,不如理解一个知识点,理解一个知识点会有助于你去理解很多其他的知识点,很多知识点连起来就是一个知识体系。
+
+当面试官问你体系中的任何一个问题,都可以把这个体系讲给他听,不是**背诵** 。
+
+深入理解问题,我会比较关注。
+
+我在面试过程中,会通过一个问题去问一串问题,慢慢就把整体体系串起来。
+
+你的**比赛**和**论文**是你的亮点,这些东西是非常重要的加分项。
+
+我也会在面试中穿插一些**开放性题目**,都是思考题 考验一个同学思考问题的方式。
+
+## 最后
+
+作为一个面试官,我很想对大家说,每个企业都非常渴望人才,都希望找到最适合企业发展的人
+
+面试的时候面试官会尽量去挖掘你的价值。
+
+但是,面试时间有限,同学们一定要在有限的时间里展现出自己的**能力**和**无限的潜力** 。
+
+最后,祝愿优秀的你能找到自己理想的工作!
diff --git a/docs/high-quality-technical-articles/interview/summary-of-spring-recruitment.md b/docs/high-quality-technical-articles/interview/summary-of-spring-recruitment.md
new file mode 100644
index 0000000000000000000000000000000000000000..1d4ca46390b5f770f46eaba300cf466094598b50
--- /dev/null
+++ b/docs/high-quality-technical-articles/interview/summary-of-spring-recruitment.md
@@ -0,0 +1,163 @@
+---
+title: 普通人的春招总结(阿里、腾讯offer)
+category: 技术文章精选集
+author: 钟期既遇
+tag:
+ - 面试
+---
+
+> **推荐语**:牛客网热帖,写的很全面!暑期实习,投了阿里、腾讯、字节,拿到了阿里和腾讯的 offer。
+>
+>
+>
+> **原文地址:** https://www.nowcoder.com/discuss/640519
+>
+>
+>
+> **下篇**:[十年饮冰,难凉热血——秋招总结](https://www.nowcoder.com/discuss/804679)
+
+## 背景
+
+写这篇文章的时候,腾讯 offer 已经下来了,春招也算结束了,这次找暑期实习没有像去年找日常实习一样海投,只投了 BAT 三家,阿里和腾讯收获了 offer,字节没有给面试机会,可能是笔试太拉垮了。
+
+楼主大三,双非本科,我的春招的起始时间应该是 2 月 20 日到 3 月 23 日收到阿里意向书为止,但是从 3 月 7 日蚂蚁技术终面面完之后就没有面过技术面了,只面过两个 HR 面,剩下的时间都在等 offer。最开始是找朋友内推了字节财经的日常实习,但是到现在还在简历评估,后面又投了财经的暑期实习,笔试之后就一直卡在流程里了。腾讯是一开始被天美捞了,一面挂了之后被 PCG 捞了,最后走完了流程。阿里提前批投了好多部门,蚂蚁最先走完了终面,就录入了系统,最后拿了 offer。这一路走过来真的是酸甜苦辣都经历过,因为学历自卑过,以至于想去考研。总而言之,一定要找一个搭档和你一起复习,比如说 @你怕是个憨批哦,这是我实验室的同学,也是我们实验室的队长,这个人是真的强,阿里核心部门都拿遍了,他在我复习的过程中给了我很多帮助。
+
+## 写这个帖子的目的
+
+1. 写给自己:总结反思一下大学前三年以及找工作的一些经历与感悟。
+2. 写给还在找实习的朋友:希望自己的经历以及面经]能给你们一些启发和帮助。
+3. 写给和我一样有着大厂梦的学弟学妹们:你们还有很长的准备时间,无论你之前在干什么,没有目标也好,碌碌无为也好,没找对方向也好,只要从现在开始,找对学习的方向,并且坚持不懈的学上一年两年,一定可以实现你的梦想的。
+
+## 我的大学经历
+
+先简单聊聊一下自己大学的经历。
+
+本人无论文、无比赛、无 ACM,要啥奖没啥奖,绩点还行,不是很拉垮,也不亮眼。保研肯定保不了,考研估计也考不上。
+
+大一时候加入了工作室,上学期自学了 C 语言和数据结构,从寒假开始学 Java,当时还不知道 Java 那么卷,我得到的消息是 Java 好找工作,这里就不由得感叹信息差的重要性了,我当时只知道前端、后端和安卓开发,而我确实对后端开发感兴趣,但是因为信息差,我只知道 Java 可以做后端开发,并不知道后端开发其实是一个很局限的概念,后面才慢慢了解到后台开发、服务端开发这些名词,也不知道 C++、Golang 等语言也可以做后台开发,所以就学了 Java。但其实 Java 更适合做业务,C++ 更适合做底层开发、服务端开发,我虽然对业务不反感,但是对 OS、Network 这些更感兴趣一些,当然这些会作为我的一些兴趣,业余时间会自己去研究下。
+
+### 学习路线
+
+大概学习的路线就是:Java SE 基础 -> MySQL -> Java Web(主要包括 JDBC、Servlet、JSP 等)-> SSM(其实当时 Spring Boot 已经兴起,但是我觉得没有 SSM 基础很难学会 Spring Boot,就先学了 SSM)-> Spring Boot -> Spring Cloud(当时虽然学了 Spring Cloud,但是缺少项目的锤炼,完全不会用,只是了解了分布式的一些概念)-> Redis -> Nginx -> 计算机网络(本来是计算机专业的必修课,可是我们专业要到大三下才学,所以就提前自学了)-> Dubbo -> Zookeeper -> JVM -> JUC -> Netty -> Rabbit MQ -> 操作系统(同计算机网络)-> 计算机组成原理(直接不开这门课)。
+
+这就是我的一个具体的学习路线,大概是在大二的下学期学完的这些东西,都是通过看视频学的,只会用,并不了解底层原理,达不到面试八股文的水准,把这些东西学完之后,搭建起了知识体系,就开始准备面试了,大概的开始时间是去年的六月份,开始在牛客网上看一些面经,然后会自己总结。准备面试的阶段我觉得最重要的是啃书 + 刷题,八股文只是辅助,我们只是自嘲说面试就背背八股文,但其实像阿里这样的公司,背八股文是完全不能蒙混过关的,除非你有非常亮眼的项目或者实习经历。
+
+### 书籍推荐
+
+- 《Thinking in Java》:不多说了,好书,但太厚了,买了没看。
+- 《深入理解 Java 虚拟机》:JVM 的圣经,看了两遍,每一遍都有不同的收获。
+- 《Java 并发编程的艺术》:阿里人写的,基本涵盖了面试会问的并发编程的问题。
+- 《MySQL 技术内幕》:写的很深入,但是对初学者可能不太友好,第一感觉写的比较深而杂,后面单独去看每一章节,觉得收获很大。
+- 《Redis 设计与实现》:书如其名,结合源码深入讲解了 Redis 的实现原理,必看。
+- 《深入理解计算机系统》:大名鼎鼎的 CSAPP,对你面 Java 可能帮助不是很大,但是不得不说这是一本经典,涵盖了计算机系统、体系结构、组成原理、操作系统等知识,我蚂蚁二面的时候就被问了遇到的最大的困难,我就和面试官交流了读这本书中遇到的一些问题,淘系二面的时候也和面试官交流了这本书,我们都觉得这本书还需要二刷。
+- 《TCP/IP 详解卷 1》:我只看了 TCP 相关的章节,但是是有必要通读一遍的,面天美时候和面试官交流了这本书。
+- 《操作系统导论》:颇具盛名的 OSTEP,南大操作系统的课本,看的时候可以结合在 B 站蒋炎岩老师的视频,我会在下面放链接。
+
+这几本书理解透彻了,我相信面试的时候可以面试官面试官聊的很深入了,面试官也会对你印象非常好。但是对于普通人来说,看一遍是肯定记不住的,遗忘是非常正常的现象,我很多也只看了一遍,很多细节也记不清了,最近准备二刷。
+
+更多书籍推荐建议大家看 [JavaGuide](https://javaguide.cn/books/) 这个网站上的书籍推荐,比较全面。
+
+
+
+### 教程推荐
+
+我上面谈到的学习路线,我建议是跟着视频学,尚硅谷和黑马的教程都可以,一定要手敲一遍。
+
+- [2021 南京大学 “操作系统:设计与实现” (蒋炎岩)](https://www.bilibili.com/video/BV1HN41197Ko):我不多说了,看评论就知道了。
+- [SpringSecurity-Social-OAuth2 社交登录接口授权鉴权系列课程](https://www.bilibili.com/video/BV16J41127jq):字母哥讲的 Spring Security 也很好,Spring Security 或者 Shiro 是做项目必备的,会一个就好,根据实际场景以及个人喜好(笑)来选型。
+- [清华大学邓俊辉数据结构与算法](https://www.bilibili.com/video/BV1jt4y117KR):清华不解释了。
+- [MySQL 实战 45 讲](https://time.geekbang.org/column/intro/100020801):前 27 讲多看几遍基本可以秒杀面试中遇到的 MySQL 问题了。
+- [Redis 核心技术与实战](https://time.geekbang.org/column/intro/100056701):讲解了大量的 Redis 在生产上的使用场景,和《Redis 设计与实现》配合着看,也可以秒杀面试中遇到的 Redis 问题了。
+- [JavaGuide](https://javaguide.cn/books/):「Java 学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。
+- [《Java 面试指北》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247519384&idx=1&sn=bc7e71af75350b755f04ca4178395b1a&chksm=cea1c353f9d64a458f797696d4144b4d6e58639371a4612b8e4d106d83a66d2289e7b2cd7431&token=660789642&lang=zh_CN&scene=21#wechat_redirect):这是一份教你如何更高效地准备面试的小册,涵盖常见八股文(系统设计、常见框架、分布式、高并发 ......)、优质面经等内容。
+
+## 找工作
+
+大概是去年 11 月的时候,牛客上日常实习的面经开始多了起来,我也有了找实习的意识,然后就开始一边复习一边海投,投了很多公司,给面试机会的就那几家,腾讯二面挂了两次,当时心态完全崩了,甚至有了看空春招的想法。很幸运最后收获了一个实习机会,在实习的时候,除了完成日常的工作以外,其余时间也没有松懈,晚上下班后、周末的时间都用来复习,心里也暗暗下定决心,春招一定要卷土重来!
+
+从二月下旬开始海投阿里提前批,基本都有了面试,开系统那天收到了 16 封内推邮件,具体的面经可以看我以前发的文章。
+
+从 3.1 到 3.7 那一个周平均每天三场面试,真的非常崩溃,一度想考研,也焦虑过、哭过、笑过,还好结果是好的,最后也去了一直想去的支付宝。
+
+我主要是想通过自己对面试过程的总结给大家提一些建议,大佬不喜勿喷。
+
+### 面试准备
+
+要去面试首先要准备一份简历,我个人认为一份好的简历应该有一下三个部分:
+
+1. 完整的个人信息,这个不多说了吧,个人信息不完整面试官或 HR 都联系不上你,就算学校不好也要写上去,因为听说有些公司没有学校无法进行简历评估,非科班或者说学校不太出名可以将教育信息写在最下面。
+2. 项目/实习经历,项目真的很重要,面试大部分时间会围绕着项目来,你项目准备好了可以把控面试的节奏,引导面试官问你擅长的方向,我就是在这方面吃了亏。如果没有项目怎么办,可以去 GitHub 上找一些开源的项目,自己跟着做一遍,加入一些自己的思考和理解。还有做项目不能简单实现功能,还要考虑性能和优化,面试官并不关注你这个功能是怎么实现的,他想知道的是你是如何一步步思考的,一开始的方案是什么,后面选了什么方案,对性能有哪些提升,还能再改进吗?
+3. 具备的专业技能,这个可以简单的写一下你学过的专业知识,这样可以让面试官有针对的问一些基础知识,切忌长篇罗列,最擅长的一定要写在上面,依次往下。
+
+简历写好了之后就进入了投递环节,最好找一个靠谱的内推人,因为内推人可以帮你跟进面试的进度,必要时候和 HR 沟通,哪怕挂了也可以告诉你原因,哪些方面表现的不好。现在内推已经不再是门槛,而是最低的入场券,没有认识的人内推也可以在牛客上找一些师兄内推,他们往往也很热情。
+
+在面试过程中一定不要紧张,因为一面面试官可能比我们大不了几岁,也工作没几年,所以 duck 不必紧张的不会说话,不会就说不会,然后笑一下,会就流利的表达出来,面试并不是一问一答,面试是沟通,是交流,你可以大胆的说出自己的思考,表达沟通能力也是面试的一个衡量指标。
+
+我个人认为面试和追妹子是差不多的,都是尽快的让对方了解自己,发现你身上的闪光点,只不过面试是让面试官了解你在技术上的造诣。所以,自我介绍环节就变得非常重要,你可以简单介绍完自己的个人信息之后,介绍一下你做过的项目,自我介绍最好长一些,因为在面试前,面试官可能没看过你的简历(逃),你最好留给面试官充足的时间去看你的简历。自我介绍包括项目的介绍可以写成一遍文档,多读几遍,在面试的时候能够背下来,实在不行也可以照着读。
+
+### 项目
+
+我还是要重点讲一下项目,我以前认为项目是一个不确定性非常大的地方,后来经过面试才知道项目是最容易带面试官节奏的地方。问项目的意义是通过项目来问基础知识,所以就要求你对自己的项目非常熟悉,考虑各种极端情况以及优化方案,熟悉用到的中间件原理,以及这些中间件是如何处理这些情况的,比如说,MQ 的宕机恢复,Redis 集群、哨兵,缓存雪崩、缓存击穿、缓存穿透等。
+
+优化主要可以从缓存、MQ 解耦、加索引、多线程、异步任务、用 ElasticSearch 做检索等方面考虑,我认为项目优化主要的着手点就是减少数据库的访问量,减少同步调用的次数,比如说加缓存、用 ElasticSearch 做检索就是通过减少数据库的访问来实现的优化,MQ 解耦、异步任务等就是通过减少同步调用的次数来实现的优化。
+
+项目中还可以学到很多东西,比如下面的这些就是通过项目来学习的:
+
+1. 权限控制(ABAC、RBAC)
+2. JWT
+3. 单点登录
+4. 分库分表
+5. 分片上传/导出
+6. 分布式锁
+7. 负载均衡
+
+当然还有很多东西,每个人的项目不一样,能学到的东西也天差地别,但是你要相信的是,你接触到的东西,面试官应该是都会的,所以一定要好好准备,不然容易被怼。
+
+本质上来讲,项目也可以拆解成八股文,可以用准备基础知识的方式来准备项目。
+
+### 算法
+
+项目的八股文化,会进一步导致无法准确的甄选候选人,所以就到了面试的第三个衡量标准,那就是算法,我曾经在反问阶段问过面试官刷算法对哪些方面有帮助,面试官直截了当的对我说,刷题对你以后找工作有帮助。我的观点是算法其实也是可以通过记忆来提高的,LeetCode 前 200 道题能刷上 3 遍,我不信面试时候还能手撕不了,所以在复习的过程中一定要保持算法的训练。
+
+### 面试建议
+
+1. 自我介绍尽量丰富一下,项目提前准备好如何介绍。
+2. 在面试的时候,遇到不会的问题最好不要直接说不会,然后愣着,等面试官问下一个问题,你可以说自己对这方面不太了解,但是对 XX 有一些了解,然后讲一下,如果面试官感兴趣,你就可以继续说,不感兴趣他就会问下一个问题,面试官一般是不会打断的,这也是让面试官快速了解你的一个小技巧。
+3. 尽量向面试官展示你的技术热情,比如说你可以和面试官聊 Java 每个版本的新特性,最近技术圈的一些新闻等等,因为就我所知,技术热情也是阿里面试考察的一方面。
+4. 面试是一个双向选择的过程,不要表现的太过去谄媚。
+5. 好好把握好反问阶段,问一些有价值的内容,比如说新人培养机制、转正机制等。
+
+## 经验
+
+1. 如果你现在大一,OK,我希望你能多了解一下互联网就业的方向,看看自己的兴趣在哪,先把基础打好,比如说数据结构、操作性、计算机网络、计算机组成原理,因为这四门课既是大部分学校考研的专业课,也是面试中常常会被问到的问题。
+2. 如果已经大二了,那就要明确自己的方向,要有自驱力,知道你学习的这个方向都要学哪些知识,学到什么程度能够就业,合理安排好时间,知道自己在什么阶段要达到什么样的水准。
+3. 如果你学历比较吃亏,亦或是非科班出身,那么我建议你一定要付出超过常人的努力,因为在我混迹牛客这么多年,我看到的面经一般是学校好一些的问的简单一些,相对差一些的问的难一些,其实也可以理解,毕竟普遍上来说名校出身的综合实力要强一些。
+4. 尽量早点实习,如果你现在大二,已经有了能够实习的水平,我建议你早点投简历,尽量找暑期实习,你相信我,如果你这个暑假去实习了,明年一定是乱杀。
+5. 接上条,如果找不到实习,尽量要做几个有挑战的项目,并且找到这个项目的抓手。
+6. 多刷刷牛客,我在牛客上就认识了很多志同道合的人,他们在我找工作过程中给了我很多帮助。
+
+## 建议
+
+1. 一定要抱团取暖,一起找工作的同学可以拉一个群,无论是自己学校的还是网上认识的,平常多交流复习心得,n 个 1 相加的和一定是大于 n 的。
+2. 知识的深度和广度都很重要,平常一定要多了解新技术,而且每学一门技术一定要争取了解它的原理,不然你学的不算是计算机,而是英语系,工作职位也不是研发工程师,而是 API 调用工程师。
+3. 运营好自己的 CSDN、掘金等博客平台,我有个学弟大二是 CSDN 博客专家,已经有猎头联系他了,平常写的代码尽量都提交到 GitHub 上,无论是项目也好,实验也好,如果有能力的话最好能录制一些视频发到哔哩哔哩上,因为这是面试官在面试你之前了解你表达能力的一个重要途径。
+4. 心态一定要好,面试不顺利,不一定是你的能力问题,也可能是因为他们招人很少,或者说某一些客观条件与他们不匹配,一定要多尝试不同的选择。
+5. 多和人沟通交流,不要自己埋头苦干,因为你以后进公司里也需要和别人合作,所以表达和沟通能力是一项基本的技能,要提前培养。
+
+## 闲聊
+
+### 谈谈信息差
+
+我觉得学校的差距并不只是体现在教学水平上,诚然名校的老师讲课水平、实验水平都是高于弱校的,但是信息差才是主要的差距。在 985 学校里面读书,不仅能接触到更多优质企业的校招宣讲、讲座,还能接触到更好的就业氛围,因为名校里面去大厂、去外企的人、甚至出国的人更多,学长学姐的内推只是一方面,另一方面是你可以从他们身上学到技术以外的东西,而双非学校去大厂的人少,他们能影响的只是很少一部分人,这就是信息差。信息差的劣势主要体现在哪些方面呢?比如人家大二已经开始找日常实习了,而你认为找工作是大四的事情,人家大三已经找到暑期实习了,你暑假还需要去参加学校组织的培训,一步步的就这样拉下了。
+
+好在,互联网的出现让信息更加透明,你可以在网上检索各种各样你想要的信息,比如我就在牛客]上认识了一些志同道合的朋友,他们在找工作的过程中给了我很多帮助。平常可以多刷刷牛客,能够有效的减小信息差。
+
+### 谈谈 Java 的内卷
+
+Java 卷吗?毫无疑问,很卷,我个人认为开发属于没有什么门槛的工作,本科生来干正合适,但是因为算法岗更是神仙打架,导致很多的研究生也转了开发,而且基本都转了 Java 开发。Java 的内卷只是这个原因造成的吗?当然不是,我认为还有一个原因就是培训机构的兴起,让这个行业的门槛进一步降低,你要学什么东西,怎么学,都有人给你安排好了,这是造成内卷的第二个原因。第三个原因就是非科班转码,其它行业的凋落和互联网行业的繁荣形成了鲜明对比,导致很多其它专业的人也自学计算机,找互联网的工作,导致这个行业的人越来越多,蛋糕就那么大,分蛋糕的人却越来越多。
+
+其实内卷也不一定是个坏现象,这说明阶级上升的通道还没有完全关闭,还是有不少人愿意通过努力来改变现状,这也一定程度上会加快行业的发展,社会的发展。选择权在你自己手上,你可以选择回老家躺平或者进互联网公司内卷,如果选择后者的话,我的建议还是尽早占下坑位,因为唯一不变的是变化,你永远不知道三年后是什么样子。
+
+## 祝福
+
+惟愿诸君,前程似锦!
diff --git a/docs/high-quality-technical-articles/interview/technical-preliminary-preparation.md b/docs/high-quality-technical-articles/interview/technical-preliminary-preparation.md
new file mode 100644
index 0000000000000000000000000000000000000000..4a7a3de9239e635162b51bfbcfcca9de990a721f
--- /dev/null
+++ b/docs/high-quality-technical-articles/interview/technical-preliminary-preparation.md
@@ -0,0 +1,214 @@
+---
+title: 从面试官和候选者的角度谈如何准备技术初试
+category: 技术文章精选集
+author: 琴水玉
+tag:
+ - 面试
+---
+
+> **推荐语**:从面试官和面试者两个角度探讨了技术面试!非常不错!
+>
+> **内容概览:**
+>
+> - 通过技术基础考察候选者,才能考察到候选者的真实技术实力:技术深度和广度。
+> - 实战与理论结合。比如,候选人叙述 JVM 内存模型布局之后,可以接着问:有哪些原因可能会导致 OOM , 有哪些预防措施? 你是否遇到过内存泄露的问题? 如何排查和解决这类问题?
+> - 项目经历考察不宜超过两个。因为要深入考察一个项目的详情,所占用的时间还是比较大的。一般来说,会让候选人挑选一个他或她觉得最有收获的/最有挑战的/印象最深刻的/自己觉得特有意思的项目。然后围绕这个项目进行发问。通常是从项目背景出发,考察项目的技术栈、项目模块及交互的整体理解、项目中遇到的有挑战性的技术问题及解决方案、排查和解决问题、代码可维护性问题、工程质量保障等。
+> - 多问少说,让候选者多表现。根据候选者的回答适当地引导或递进或横向移动。
+>
+> **原文地址:** https://www.cnblogs.com/lovesqcc/p/15169365.html
+
+## 考察目标和思路
+
+首先明确,技术初试的考察目标:
+
+- 候选人的技术基础;
+- 候选人解决问题的思路和能力。
+
+技术基础是基石(冰山之下的东西),占七分, 解决问题的思路和能力是落地(冰山之上露出的部分),占三分。 业务和技术基础考察,三七开。
+
+## 技术基础考察
+
+### 为什么要考察技术基础?
+
+程序员最重要的两种技术思维能力,是逻辑思维能力和抽象设计能力。逻辑思维能力是基础,抽象设计能力是高阶。 考察技术基础,正好可以同时考察这两种思维能力。能不能理解基础技术概念及关联,是考察逻辑思维能力;能不能把业务问题抽象成技术问题并合理的组织映射,是考察抽象设计能力。
+
+绝大部分业务问题,都可以抽象成技术问题。在某种意义上,业务问题只是技术问题的领域化表述。
+
+因此,**通过技术基础考察候选者,才能考察到候选者的真实技术实力:技术深度和广度。**
+
+### 技术基础怎么考察?
+
+技术基础怎么考察?通过有效的多角度的发问模式来考察。
+
+#### 是什么-为什么
+
+是什么考察对概念的基本理解,为什么考察对概念的实现原理。
+
+比如:索引是什么? 索引是如何实现的?
+
+#### 引导-横向发问-深入发问
+
+引导性,比如 “你对 Java 同步工具熟悉吗?” 作个试探,得到肯定答复后,可以进一步问:“你熟悉哪些同步工具类?” 了解候选者的广度;
+
+获取候选者的回答后,可以进一步问:“ 谈谈 `ConcurrentHashMap` 或 `AQS` 的实现原理?”
+
+一个人在多大程度上把技术原理能讲得清晰,包括思路和细节,说明他对技术的掌握能力有多强。
+
+#### 跳跃式/交叉式发问
+
+比如:讲到哈希高效查找,可以谈谈哈希一致性算法 。 两者既有关联又有很多不同点。也是一种技术广度的考察方法。
+
+#### 总结性发问
+
+比如:你在做 XXX 中,获得了哪些可以分享的经验? 考察候选人的归纳总结能力。
+
+#### 实战与理论结合
+
+比如,候选人叙述 JVM 内存模型布局之后,可以接着问:有哪些原因可能会导致 OOM , 有哪些预防措施? 你是否遇到过内存泄露的问题? 如何排查和解决这类问题?
+
+比如,候选人有谈到 SQL 优化和索引优化,那就正好谈谈索引的实现原理,如何建立最佳索引?
+
+再比如,候选人有谈到事务,那就正好谈谈事务实现原理,隔离级别,快照实现等;
+
+#### 熟悉与不熟悉结合
+
+针对候选人简历上写的熟悉的部分,和没有写出的都问下。比如候选人简历上写着:熟悉 JVM 内存模型, 那我就考察下内存管理相关(熟悉部分),再考察下 Java 并发工具类(不确定是否熟悉部分)。
+
+#### 死知识与活知识结合
+
+比如,查找算法有哪些?顺序查找、二分查找、哈希查找。这些大家通常能说出来,也是“死知识”。
+
+这些查找算法各适用于什么场景?在你工作中,有哪些场景用到了哪些查找算法?为什么? 这些是“活知识”。
+
+#### 学习或工作中遇到的
+
+有时,在学习和工作中遇到的问题,也可以作为面试题。
+
+比如,最近在学习《操作系统导论》并发部分,有一章节是如何使数据结构成为线程安全的。这里就有一些可以提问的地方:如何实现一个锁?如何实现一个线程安全的计数器?如何实现一个线程安全的链表?如何实现一个线程安全的 `Map` ?如何提升并发的性能?
+
+工作中遇到的问题,也可以抽象提炼出来,作为技术基础面试题。
+
+#### 技术栈适配度发问
+
+如果候选人(简历上所写的)使用的某些技术与本公司的技术栈比较契合,则可以针对这些技术点进行深入提问,考察候选人在这些技术点的掌握程度。如果掌握程度比较好,则技术适配度相对更高一些。
+
+当然,这一点并不能作为筛掉那些没有使用该技术栈的候选人的依据。比如本公司使用 `MongoDB` 和 `MySQL`, 而一个候选人没有用过 `Mongodb,` 但使用过 `MySQL`, `Redis`, `ES`, `HBase` 等多种存储系统,那么适配度并不比仅使用过 `MySQL` 和 `MongoDB` 的候选人逊色,因为他所涉及的技术广度更大,可以推断出他有足够能力掌握 `Mongodb`。
+
+#### 创造有个性的面试题库
+
+每个技术面试官都会有一个面试题库。持续积累面试题库,日常中突然想到的问题,就随手记录下来。
+
+## 业务维度考察
+
+### 为什么要考察业务维度?
+
+技术基础考察,容易错过的地方是,候选人的非技术能力特质,比如沟通组织能力、带项目能力、抗压能力、解决实际问题的能力、团队影响力、其它性格特质等。
+
+### 为什么不能单考察业务维度?
+
+因为业务方面通常比较熟悉,可能就直接按照现有方案说出来了,很难考察到候选人的深入理解、横向拓展和归纳总结能力。
+
+这一点,建议有针对性地考察下候选人的归纳总结能力:比如, 微服务搭建或开发或维护/保证系统稳定性或性能方面的过程中,你收获了哪些可以分享的经验?
+
+## 解决问题能力考察
+
+仅仅只是技术基础还不够,通常最好结合实际业务,针对他项目里的业务,抽象出技术问题进行考察。
+
+解决思路重在层层递进。这一点对于面试官的要求也比较高,兼具良好的倾听能力、技术深度和业务经验。首先要仔细倾听候选人的阐述,找到适当的技术切入点,然后进行发问。如果进不去,那就容易考察失败。
+
+### 设计问题
+
+- 比如多个机器间共享大量业务对象,这些业务对象之间有些联合字段是重复的,如何去重?
+- 如果瞬时有大量请求涌入,如何保证服务器的稳定性?
+
+### 项目经历
+
+项目经历考察不宜超过两个。因为要深入考察一个项目的详情,所占用的时间还是比较大的。
+
+一般来说,会让候选人挑选一个他或她觉得最有收获的/最有挑战的/印象最深刻的/自己觉得特有意思的项目。然后围绕这个项目进行发问。通常是从项目背景出发,考察项目的技术栈、项目模块及交互的整体理解、项目中遇到的有挑战性的技术问题及解决方案、排查和解决问题、代码可维护性问题、工程质量保障等。
+
+## 面试官如何做好一场面试?
+
+### 预先准备
+
+面试官也需要做一些准备。比如熟悉候选者的技能优势、工作经历等,做一个面试设计。
+
+在面试将要开始时,做好面试准备。此外,面试官也需要对公司的一些基本情况有所了解,尤其是公司所使用技术栈、业务全景及方向、工作内容、晋升制度等,这一点技术型候选人问得比较多。
+
+### 面试启动
+
+一般以候选人自我介绍启动,不过候选人往往会谈得比较散,因此,我会直接提问:谈谈你有哪些优势以及自己觉得可以改进的地方?
+
+然后以一个相对简单的基础题作为技术提问的开始:你熟悉哪些查找算法?大多数人是能答上顺序查找、二分查找、哈希查找的。
+
+### 问题设计
+
+提前阅读候选人简历,从简历中筛选出关键词,根据这些关键词进行有针对性地问题设计。
+
+比如候选人简历里提到 `MVVM` ,可以问 `MVVM` 与 `MVC` 的区别; 提到了观察者模式,可以谈谈观察者模式,顺便问问他还熟悉哪些设计模式。
+
+### 宽松氛围
+
+即使问的问题比较多比较难,也要注意保持宽松氛围。
+
+在面试前,根据候选人基本信息适当调侃一下,比如一位候选人叫汪奎,那我就说:之前我们团队有位叫袁奎,我们都喊他奎爷。
+
+在面试过程中,适当提示,或者给出少量自己的看法,也能缓解候选人的紧张情绪。
+
+### 学会倾听
+
+多问少说,让候选者多表现。根据候选者的回答适当地引导或递进或横向移动。
+
+引导候选人表现他最优势的一面,让他或她感觉好一些:毕竟一场面试双方都付出了时间和精力,不应该是面试官 Diss 候选人的场合,而应该让彼此有更好的交流。很大可能,你也能从候选人那里学到不少东西。
+
+面试这件事,只不过双方的角色和立场有所不同,但并不代表面试官的水平就一定高于候选人。
+
+### 记录重点
+
+认真客观地记录候选人的回答,尽可能避免任何主观评价,亦不作任何加工(比如自己给总结一下,总结能力也是候选人的一个特质)。
+
+## 作出判断
+
+面试过程是一种铺垫,关键的是作出判断。
+
+作出判断最容易陷入误区的是:贪深求全。总希望候选人技术又深入又全面。实际上,这是一种奢望。如果候选人的技术能力又深入又全面,很可能也会面临两种情况:
+
+1. 候选人有更好的选择;
+2. 候选人在其它方面可能存在不足,比如团队协作方面。
+
+一个比较合适的尺度是:
+
+1. 他或她的技术水平能否胜任当前工作;
+2. 他或她的技术水平与同组团队成员水平如何;
+3. 他或她的技术水平是否与年限相对匹配,是否有潜力胜任更复杂的任务。
+
+**不同年龄看重的东西不一样。**
+
+对于三年以下的工程师,应当更看重其技术基础,因为这代表着他的未来潜能;同时也考察下他在实际开发中的体现,比如团队协作、业务经验、抗压能力、主动学习的热情和能力等。
+
+对于三年以上的工程师,应当更看重其业务经验、解决问题能力,看看他或她是如何分析具体问题,在业务范畴内考察其技术基础的深度和广度。
+
+如何判断一个候选人的真实技术水平及是否适合所需,这方面,我也在学习中。
+
+## 给候选人的话
+
+### 关注技术基础
+
+一个常见的疑惑是:开发业务系统的大多数时候,基本不涉及数据结构与算法的设计与实现,为什么要考察 `HashMap` 的实现原理?为什么要学好数据结构与算法、操作系统、网络通信这些基础课程?
+
+现在我可以给出一个答案了:
+
+- 正如上面所述,绝大多数的业务问题,实际上最终都会映射到基础技术问题上:数据结构与算法的实现、内存管理、并发控制、网络通信等;这些是理解现代互联网大规模程序以及解决程序疑难问题的基石,—— 除非能祝福自己永远都不会遇到疑难问题,永远都只满足于编写 CRUD;
+- 这些技术基础正是程序世界里最有趣最激动人心的地方。如果对这些不感兴趣,就很难在这个领域里深入进去,不如及早转行从事其它职业,非技术的世界一直都很精彩广阔(有时我也想多出去走走,不想局限于技术世界);
+- 技术基础是程序员的内功,而具体技术则是招式。徒有招式而内功不深,遇到高手(优秀同行从业者的竞争及疑难杂症)容易不堪一击;
+- 具备扎实的专业技术基础,能达到的上限更高,未来更有可能胜任复杂的技术问题求解,或者在同样的问题上能够做到更好的方案;
+- 人们喜欢跟与自己相似的人合作,牛人倾向于与牛人合作能得到更好的效果;如果一个团队大部分人技术基础比较好,那么进来一个技术基础比较薄弱的人,协作成本会变高;如果你想和牛人一起合作拿到更好的结果,那就要让自己至少在技术基础上能够与牛人搭配的上;
+- 在 CRUD 的基础上拓展其它才能也不失为一种好的选择,但这不会是一个真正的程序员的姿态,顶多是有技术基础的产品经理、项目经理、HR、运营、客满等其它岗位人才。这是职业选择的问题,已经超出了考察程序员的范畴。
+
+### 不要在意某个问题回答不上来
+
+如果面试官问你很多问题,而有些没有回答上来,不要在意。面试官很可能只是在测试你的技术深度和广度,然后判断你是否达到某个水位线。
+
+重点是:有些问题你答得很有深度,也体现了你的深度思考能力。
+
+这一点是我当了技术面试官才领会到的。当然,并不是每位技术面试官都是这么想的,但我觉得这应该是个更合适的方式。
diff --git a/docs/high-quality-technical-articles/interview/the-experience-and-thinking-of-an-interview-experienced-by-an-older-programmer.md b/docs/high-quality-technical-articles/interview/the-experience-and-thinking-of-an-interview-experienced-by-an-older-programmer.md
new file mode 100644
index 0000000000000000000000000000000000000000..02ccd59a2666653210202722ecbda8d0f434c17e
--- /dev/null
+++ b/docs/high-quality-technical-articles/interview/the-experience-and-thinking-of-an-interview-experienced-by-an-older-programmer.md
@@ -0,0 +1,360 @@
+---
+title: 一位大龄程序员所经历的面试的历炼和思考
+category: 技术文章精选集
+author: 琴水玉
+tag:
+ - 面试
+---
+
+> **推荐语**:本文的作者,今年 36 岁,已有 8 年 JAVA 开发经验。在阿里云三年半,有赞四年半,已是标准的大龄程序员了。在这篇文章中,作者给出了一些关于面试和个人能力提升的一些小建议,非常实用!
+>
+> **内容概览**:
+>
+> 1. 个人介绍,是对自己的一个更为清晰、深入和全面的认识契机。
+> 2. 简历是充分展示自己的浓缩精华,也是重新审视自己和过往经历的契机。不仅仅是简要介绍技能和经验,更要最大程度凸显自己的优势领域(差异化)。
+> 3. 我个人是不赞成海投的,而倾向于定向投。找准方向投,虽然目标更少,但更有效率。
+> 4. 技术探索,一定要先理解原理。原理不懂,就会浮于表层,不能真正掌握它。技术原理探究要掌握到什么程度?数据结构与算法设计、考量因素、技术机制、优化思路。要在脑中回放,直到一切细节而清晰可见。如果能够清晰有条理地表述出来,就更好了。技术原理探究,一定要看源码。看了源码与没看源码是有区别的。没看源码,虽然说得出来,但终是隔了一层纸;看了源码,才捅破了那层纸,有了自己的理解,也就能说得更加有底气了。当然,也可能是我缺乏演戏的本领。
+> 5. 要善于从失败中学习。正是在杭州四个月空档期的持续学习、思考、积累和提炼,以及面试失败的反思、不断调整对策、完善准备、改善原有的短板,采取更为合理的方式,才在回武汉的短短两个周内拿到比较满意的 offer 。
+> 6. 面试是通过沟通来理解双方的过程。面试中的问题,千变万化,但有一些问题是需要提前准备好的。
+>
+> **原文地址**:https://www.cnblogs.com/lovesqcc/p/14354921.html
+
+从每一段经历中学习,在每一件事情中修行。善于从挫折中学习。
+
+## 引子
+
+我今年 36 岁,已有 8 年 JAVA 开发经验。在阿里云三年半,有赞四年半,已是标准的大龄程序员了。
+
+在多年的读书、学习和思考中,我的价值观、人生观和世界观也逐步塑造成型。我意识到自己的志趣在于做教育文化方面,因此在半冲动之下,8 月份下旬,裸辞去找工作了。有限理性难以阻挡冲动的个性。不建议裸辞,做事应该有规划、科学合理。
+
+尽管我最初认为自己“有理想有目标有意愿有能力”,找一份教育开发的工作应该不难,但事实上我还是过于乐观了。现实很快给我泼了一瓢瓢冷水。我屡战屡败,又屡败屡战。惊讶地发现自己还有这个韧性。面试是一项历炼,如果没有被失败击倒,那么从中会生长出一份韧性,这种韧性能让人走得更远。谁没有经历过失败的历练呢?失败是最伟大的导师了,如果你愿意跟他学一学的话。
+
+在面试的过程中,我很快发现自己的劣势:
+
+- 投入精力做业务,技术深度不够,对原理的理解局限于较浅的层次;
+- 视野不够开阔,局限于自己所做的订单业务线,对其它关联业务线(比如商品、营销、支付等)了解不够;
+- 思维不够开阔,大部分时间投入在开发和测试上,对运维、产品、业务、商业层面思考都思考不多;
+- 缺乏管理经验,年龄偏大;这两项劣势我一度低估,但逐渐凸显出来,甚至让我一度不自信,但最终我还是走出来了。
+
+但我也有自己的优势。职业竞争的基本法则是稀缺性和差异化。能够解决大型项目的架构设计和攻克技术难题,精通某个高端技术领域是稀缺性体现;而能够做事能做到缜密周全精细化,有高并发大流量系统开发经验,则是差异性体现。稀缺性是上策,差异化是中策,而降格以求就是下策了。
+
+我缺乏稀缺性优势,但还有一点差异化优势:
+
+- 对每一份工作都很踏实,时间均在 3 年 - 5 年之间,有一点大厂光环,能获得更多面试机会(虽然不一定能面上);
+- 坚持写博客,孜孜不倦地追求软件开发的“道”,时常思考记录开发中遇到的问题及解决方案;
+- 做事认真严谨,能够从整体分析和思考问题,也很注重基础提升;
+- 对工程质量、性能优化、稳定性建设、业务配置化设计有实践经验;
+- 大流量微服务系统的长期开发维护经验。
+
+我投出简历的公司并不多。在不多的面试中,我逐渐意识到网上的“斩获几十家大厂 offer”的说法并不可信。理由如下:
+
+- 如果能真斩获大量大厂 offer ,面试的级别很大概率是初级工程师。要知道面试 4 年以上的工程师,面试的深度和广度令人发指,从基础的算法、到各种中间件的原理机制到实际运维架构,无所不包,真个是沉浸在“技术的海洋”,除非一个人的背景和实力非常强大,平时也做了非常深且广的沉淀;
+- 一个背景和实力非常强大的人,是不会有兴趣去投入这么多精力去面各种公司,仅仅是为了吹嘘自己有多能耐;实力越强的人,他会有自己的选择逻辑,投的简历会更定向精准。话说,他为什么不花更多精力投入在那些能够让他有最大化收益的优秀企业呢?
+- 培训机构做的广告。因为他们最清楚新手需要的是信心,哪怕是伪装出来的信心。
+
+好了,闲话不多说了。我讲讲自己在面试中所经受的历练和思考吧。
+
+## 准备工作
+
+人生或许很长,但面试的时间很短,最长不过一小时或一个半小时。别人如何在短短一小时内能够更清晰地认识长达三十多年的你呢?这就需要你做大量细致的准备工作了。在某种程度上,面试与舞蹈有异曲同工之妙:台上五分钟,台下十年功。
+
+准备工作主要包括简历准备、个人介绍、公司了解、技术探索、表述能力、常见问题、中高端职位、好的心态。准备工作是对自身和对外部世界的一次全面深入的重新认知。
+
+初期,我以为自己准备很充分,简历改改就完事了。随着一次次受挫,才发现自己的准备很不充分。在现在的我看来,准备七分,应变三分。准备,就是要知己知彼,知道对方会问哪些问题(通常是系统/项目/技术的深度和广度)、自己应当如何作答;应变,就是当自己遇到不会、不懂、不知道的问题时,如何合理地展示自己的解决思路,以及根据面试中答不上来的问题查漏补缺,夯实基础。
+
+这个过程,实际上也是学习的过程。持续的反思和提炼、学习新的内容、重新认识自己和过往经历等。
+
+### 简历准备
+
+最开始,我做得比较简单。把以前的简历拿出来,添加上新的工作经历,略作修改,但整体上模板基本不变。
+
+在基本面上,我做的是较为细致的,诚实地写上了自己擅长和熟悉的技能和经验经历,排版也尽力做得整洁美观(学过一些 UI 设计)。不浮夸也不故作谦虚。
+
+在扩展面上,我做的还是不够的。有一天,一位猎头打电话给我,问:“你最大的优势是什么?”。我顿时说不上来。当时也未多加思考。在后续面试屡遭失败之后,一度有些不自信之后,我开始仔细思考自己的优势来。然后将“对工程质量、性能优化、稳定性建设、业务配置化设计有深入思考和实践经验”写在了“技能素养”栏的第一行,因为这确实是我所做过的、最实在且脚踏实地的且具备概括性的。
+
+有时,简历内容的编排顺序也很重要。之前,我把掌握的语言及技术写在前面,而“项目管理能力和团队影响力”之类的写在后面。但投年糕妈妈之后,未有面试直接被拉到不合适里面,受到了刺激,我意识到或许是对方觉得我管理经验不足。因此,刻意将“项目管理能力和团队影响力”提到了前面,表示自己是重视管理方面的,不过,投过新的简历之后,没有回应。我意识到,这样的编排顺序可能会让人误解我是管理能力偏重的(事实上有一位 HR 问我是不是还在写代码),但实际上管理方面我是欠缺的,最后,我还是调回了原来的顺序,凸出自己“工程师的本色”。后面,我又做了一些语句的编排上的修改。
+
+随着面试的进展,有时,也会发现自己的简历上写得不够或者以前做得不够的地方。比如,在订单导出这段经历里,我只是写了大幅提升性能和稳定性,显得定性描述化,因此,我添加了一些量化的东西(2w 阻塞 => 300w+,1w/1min)作为证实;比如,8 月份离职,到 12 月份面试的时候,有一段空档期,有些企业会问到这个。因此,我索性加了一句话,说明这段时间我在干些啥;比如,代表性系统和项目,每一个系统和项目的价值和意义(不一定写在上面,但是心里要有数)。功夫要下足。
+
+再比如,我很详细地写了有赞的工作经历及经验,但阿里云的那段基本没动。而有些企业对这段经历更感兴趣,我却觉得没太多可说的,留在脑海里的只有少量印象深刻的东西,以及一些博客文章的记录,相比这段工作经历来说显得太单薄。这里实质上不是简历的问题,而是过往经历复盘的问题。建议,在每个项目结束后,都要写个自我复盘。避免时间将这些可贵的经历冲淡。
+
+每个人其实都有很多可说的东西,但记录下来的又有多少呢?值得谈道的有多少呢?过往不努力,面试徒伤悲。
+
+**简历更新的心得**:
+
+- 简历是充分展示自己的浓缩精华,也是重新审视自己和过往经历的契机;
+- 不仅仅是简要介绍技能和经验,更要最大程度凸显自己的优势领域(差异化);
+- 增强工作经历的表述,凸显贡献,赢得别人的认可;
+- 复盘并记录每一个项目中的收获,为跳槽和面试打下好的铺垫。
+
+### 个人介绍
+
+面试前通常会要求做个简要的个人介绍。个人介绍通常作为进入面试的前奏曲和缓冲阶段,缓和下紧张气氛。
+
+我最开始的个人介绍,个性啊业余生活啊工作经历啊志趣啊等等,似乎不知道该说些什么。实际上,个人介绍是一个充分展示自己的主页。主页应当让自己最最核心的优势一目了然(需要挖掘自己的经历并仔细提炼)。我现在的个人介绍一般会包括:个性(比如偏安静)、做事风格(工作认真严谨、注重质量、善于整体思考)、最大优势(owner 意识、执行力、工程把控能力)、工作经历简述(在每个公司的工作负责什么、贡献了什么、收获了什么)。个人介绍简明扼要,无需赘言。
+
+个人介绍,是对自己的一个更为清晰、深入和全面的认识契机。
+
+### 公司了解
+
+很多人可能跟我一样,对公司业务了解甚少,就直接投出去了。这样其实是不合理的。首先,我个人是不赞成海投的,而倾向于定向投。找准方向投,虽然目标更少,但更有效率。这跟租房一样,我一般在豆瓣上租房,虽然目标源少,但逮着一个就是好运。
+
+投一家公司,是因为这家公司符合意向,值得争取,而不是因为这是一家公司。就像找对象,不是为了找一个女人。要确定这家公司是否符合意向,就应当多去了解这家公司:主营业务、未来发展及规划、所在行业及地位、财务状况、业界及网络评价等。
+
+在面试的过程中适当谈到公司的业务及思考,是可加分项。亦可用于“你有什么想问的?”的提问。
+
+### 技术探索
+
+技术能力是一个技术人的基本素养。因此,我觉得,无论未来做什么工作,技术能力过硬,总归是最不可或缺的不可忽视的。
+
+原理和设计思想是软件技术中最为精髓的东西。一般软件技术可以分为两个方面:
+
+- 原理:事物如何工作的基本规律和流程;
+- 架构:如何组织大规模逻辑的艺术。
+
+**技术探索,一定要先理解原理。原理不懂,就会浮于表层,不能真正掌握它。技术原理探究要掌握到什么程度?数据结构与算法设计、考量因素、技术机制、优化思路。要在脑中回放,直到一切细节而清晰可见。如果能够清晰有条理地表述出来,就更好了。**
+
+**技术原理探究,一定要看源码。看了源码与没看源码是有区别的。没看源码,虽然说得出来,但终是隔了一层纸;看了源码,才捅破了那层纸,有了自己的理解,也就能说得更加有底气了。当然,也可能是我缺乏演戏的本领。**
+
+我个人不太赞成刷题式面试。虽然刷题确实是进厂的捷径,但也有缺点:
+
+- 它依然是别人的知识体系,而不是自己总结的知识体系;
+- 技术探究是为了未来的工作准备,而不是为了应对一时之需,否则即使进去了还是会处于麻痹状态。
+
+经过系统的整理,我逐步形成了适合自己的技术体系结构:[“互联网应用服务端的常用技术思想与机制纲要”](https://www.cnblogs.com/lovesqcc/p/13633409.html) 。在这个基础上,再博采众长,看看面试题进行自测和查漏补缺,是更恰当的方式。我会在这个体系上深耕细作。
+
+### 表述能力
+
+目前,绝大多数企业的主要面试形式是通过口头沟通进行的,少部分企业可能有笔试或机试。口头沟通的形式是有其局限性的。对表述能力的要求比较高,而对专业能力的凸显并不明显。一个人掌握的专业和经验的深度和广度,很难通过几分钟的表述呈现出来。往往深度和广度越大,反而越难表述。而技术人员往往疏于表达。
+
+我平时写得多说得少,说起来不利索。有时没讲清楚背景,就直接展开,兼之啰嗦、跳跃和回旋往复(这种方式可能更适合写小说),让面试官有时摸不着头脑。表述的条理性和清晰性也是很重要的。不妨自己测试一下:Dubbo 的架构设计是怎样的? Redis 的持久化机制是怎样的?然后自己回答试试看。
+
+表述能力的基本法则:
+
+- 先总后分,先整体后局部;
+- 先说基本思路,然后说优化;
+- 体现互动。先综述,然后向面试官询问要听哪方面,再分述。避免自己一脑瓜子倾倒出来,让面试官猝不及防;系统设计的场景题,多问一些要求,比如时间要求、空间要求、要支持多大数据量或并发量、是否要考虑某些情况等。
+
+### 常见问题
+
+面试是通过沟通来理解双方的过程。面试中的问题,千变万化,但有一些问题是需要提前准备好的。
+
+比如“灵魂 N 问”:
+
+- 你为什么从 XXX 离职?
+- 你的期望薪资是多少?
+- 你有一段空档期,能解释下怎么回事么?
+- 你的职业规划是怎样的?
+
+高频技术问题:
+
+- 基础:数据结构与算法、网络;
+- 微服务:技术体系、组件、基础设施等;
+- Dubbo:Dubbo 整体架构、扩展机制、服务暴露、引用、调用、优雅停机等;
+- MySQL:索引与事务的实现原理、SQL 优化、分库分表;
+- Redis : 数据结构、缓存、分布式锁、持久化机制、复制机制;
+- 分布式:分布式事务、一致性问题;
+- 消息中间件:原理、对比;
+- 架构:架构设计方法、架构经验、设计模式;
+- 性能优化:JVM、GC、应用层面的性能优化;
+- 并发基础:ConcurrentHashMap, AQS, CAS,线程池等;
+- 高并发:IO 多路复用;缓存问题及方案;
+- 稳定性:稳定性的思想及经验;
+- 生产问题:工具及排查方法。
+
+### 中高端职位
+
+说起来,我这人可能有点不太自信。我是怀着“踏实做一个工程师”的思想投简历的。
+
+对于大龄程序员,企业的期望更高。我的每一份“高级工程师”投递,自动被转换为“技术专家”或“架构师”。无力反驳,倍感压力。面试中高端职位,需要更多准备:
+
+- 你有带团队经历吗?
+- 在你 X 年的工作经历中,有多少时间用于架构设计?
+- 架构过程是怎样的?你有哪些架构设计思想或方法论?
+
+如果不作准备,就被一下子问懵,乱了阵脚。实际上,我或许还是存着侥幸心理把“技术专家”和“架构师”岗位当做“高工”来面试的,也就无一不遭遇失败了。显然,我把次序弄反了:应当以“技术专家”和“架构师”的规格来面试高级工程师。
+
+好吧,那就迎难而上吧!我不是惧怕挑战的人。
+
+此外,“技术专家”和“架构师”职位应当至少留一天的时间来准备。已经有丰富经验的技术专家和架构师可以忽略。
+
+### 好的心态
+
+保持好的心态也尤为重要。我经历了“乐观-不自信-重拾信心”的心态变化过程。
+
+很长一段时间,由于“求成心切”,生怕某个技术问题回答不上来搞砸,因此小心谨慎,略显紧张,结果已经梳理好的往往说不清楚或者说得不够有条理。冲着“拿 offer ”的心态去面试,真的很难受,会觉得每场面试都很被动那么难过,甚至有点想要“降格以求”。
+
+有时,我在想:咋就混成这个样子了呢?按理来说,这个时候我应该有能力去追求自己喜爱的事业了啊!还是平时有点松懈了,视野狭窄,积累不够,导致今天的不利处境。
+
+我是一个守时的人,也希望对方尽可能守时。杭州的面试官中,基本是守时的,即使迟到也在心理接受范围内,回武汉面试后,节奏就有点被少量企业带偏了。有一两次,我甚至不确定面试官什么时候进入会议。我想,难道这是人才应该受到的“礼待”吗?我有点被轻微冒犯的感觉了。不过我还是“很有涵养地”表示没事。但我始终觉得:面试官迟到,是对人才的不尊重。进入不尊重人才的公司,我是怀有疑虑的。良禽择木而栖,良臣择主而事。难道我能因为此刻的不利处境,而放弃一些基本的原则底线,而屈从于一份不尊重人才的 offer 吗?
+
+我意识到:一个人应当用其实力去赢得对方的尊重和赏识,以后的合作才会更顺畅。不若,哪怕惜其无缘,亦不可强留。无论别人怎么存疑,心无旁骛地打磨实力,挖掘自己的才干和优势,终会发出自己的光芒。因此,我的心态顿时转变了:应当专注去沟通,与对方充分认识了解,赢得对方心服的认可,而不是拿到一张入门券,成为干活的工具。
+
+有一个“石头和玉”的小故事,把自己当做人才,并努力去提升自己,才能获得“人才的礼遇”;把自己当石头贱卖,放松努力,也就只能得到“石头的礼遇”。尽管一个人不一定马上就具备人才的能力,但在自己的内心里,就应当从人才的视角去观察待入职的企业,而不仅仅是为了找一份“赚更多钱”的工作。
+
+此外,焦虑也是不必要的。焦虑的实质是现实与目标的差距。一个人总可以评估目标的合理性及如何达成目标。如果目标过高,则适当调整目标级别;目标可行,则作出合理的决策,并通过持续的努力和恰当的出击来实现目标。决策、努力和出击能力都是可以持续修炼的。
+
+## 面试历炼
+
+技术人的面试还是更偏重于技术,因此,技术的深度和广度还是要好好准备的。面试官和候选人的处境是不一样的,一个面试官问的只是少量点,但是多个面试官合起来就是一个面。明白这一点,作为面试官的你就不要忘乎所以,以为自己就比候选人厉害。
+
+我面的企业不多,因为我已经打算从事教育事业,用“志趣和驱动力”这项就直接过滤了很多企业的面试邀请。在杭州面试的基本是教育企业,连阿里华为等抛来的橄榄枝都婉拒了(尽管我也不一定能面上)。虽然做法有点“直男”,但投入最多精力于自己期望从事的行业和事业,才是值得的。
+
+我所认为的教育事业,并不局限于现在常谈起的在线教育或 K12 教育,而是一个教育体系,任何可以更好滴起到教育效果的事业,包括而不限于教学、阅读、音乐、设计等。
+
+### 接力棒科技-高工
+
+面的第一家。畅谈一番后,没音讯了。但我也没有太在意。面试官问的比较偏交易业务性的东西,较深的就是如何保证应用的数据一致性了。
+
+此时的我,就像在路上扔了一颗探路的小石子,尚未意识到自己的处境。
+
+### 网易云音乐-高工
+
+接着是网易云音乐。大厂就是大厂。一面问的尽是缓存、分布式锁、Dubbo、ZK, MQ 中间件相关的机制。很遗憾,由于我平时关于技术原理的沉淀还是很少,基本是“一问两不知”,挂得很出彩。
+
+此时,我初步意识到自己的技术底子还很薄弱,也就开始了广阔的技术学习和夯实,自底向上地梳理原理和逻辑,系统地进行整理总结,最终初步形成了自己的互联网服务端技术知识体系结构。
+
+### 铭师堂-技术专家
+
+架构师面试的。问的相对多了一些,DB, Redis 等。反馈是技术还行,但缺乏管理经验。这是我第一次意识到大龄程序员缺乏管理经验的不利。中小企业的技术专家线招聘中,往往附加了管理经验的需求。应聘时要注意。
+
+缺乏管理经验,该怎么办呢?思考过一段时间后,我的想法是:
+
+- 改变能改变的,不能改变的,学习它。比如技术原理的学习是我能够改变的,但管理经验属于难以一时改变的,那就多了解点管理的基本理论吧。
+- 从经历中挖掘相关经验。虽然我没有正式带团队的实际经验,但是有带项目和带工程师,管控某个业务线的基本管理经验。多多挖掘自己的经历。
+
+### 字节教育-高工
+
+字节教育面试,我给自己挖了不少坑往里跳。
+
+比如面试官问,讲一个你比较成就感的项目经历。我选择的是近 4 年前的周期购项目。虽然这是我入职有赞的第一个有代表性的项目,但时间太久,又没有详细记录,很多技术细节遗忘不清晰了。我讲到当时印象比较深的“一体化”设计思想,却忘记了当时为什么会有这种思想(未做仔细记录)。
+
+再比如,一个上课的场景题,我问是用 CS 架构还是 BS 架构?面试官说用 CS 架构吧。这不是给自己挖坑吗?明明自己不熟悉 CS 架构,何必问这个选择呢,不如直接按照 BS 架构来讲解。哎!
+
+字节教育给我的反馈是:业务 Sense 不错,系统设计能力有待提高。我觉得还是比较中肯的。因此,也开始注重系统设计实战方面的文章阅读和思考训练。
+
+经验是:
+
+- 做项目时,要详细记录每个项目的技术栈、技术决策及原因、技术细节,为面试做好铺垫;
+- 提前准备好印象最深刻的最代表性的系统和项目,避免选择距离当前时间较久的缺乏详细记录的项目;
+- 选择熟悉的项目和架构,至少有好的第一印象,不然给面试官的印象就是你啥都不会。
+
+### 咪咕数媒-架构师
+
+好家伙,一下子 3 位面试官群面。可能我以前经历的太少了吧。似乎国企面试较高端职位,喜欢采取这种形式。兼听则明偏听则暗嘛。问的问题也很广泛,从 ES 的基本原理,到机房的数据迁移。有些技术机制虽然学习过,但不牢固,不清晰,答的也不好。比如 ES 的搜索原理优化,讲过倒排索引后,我对 Term Index 和 Trie 树 讲不清楚。这说明,知道并不代表真正理解了。只有能够清晰有条理地把思路和细节都讲清楚,才算是真正理解了。
+
+印象深刻的是,有一个问题:你有哪些架构思想?这是第一次被问到架构设计方面的东西,我顿时有点慌乱。虽然平时多有思考,也有写过文章,却没有形成系统精炼的方法论,结果就是答的比较凌乱。
+
+### 涂鸦智能-高工
+
+应聘涂鸦智能,是因为我觉得这家企业不错。优秀的企业至少应该多沟通一下,说不准以后有合作机会呢!看问题的思维要开阔一些,不能死守在自己想到的那一个事情上。
+
+涂鸦智能给我的整体观感还是不错的。面试官也很有礼貌有耐心,整体架构、技术和项目都问了很多,问到了我熟悉的地方,答得也还可以。也许我的经验正好是切中他们的需求吧。
+
+若不是当时想做教育的执念特别强,我很大概率会入职涂鸦智能。物联网在我看来应该是很有趣的领域。
+
+### 跟谁学-技术专家
+
+“跟谁学”基本能答上来。不过反馈是:对于提问抓重点的能力有所欠缺,对于技术的归纳整理也不够。我当时还有点不服气,认为自己写了那么多文章,也算是有不少思考,怎能算是总结不够呢?顶多是有技术盲点。技术犹如海洋,谁能没有盲点?
+
+不过现在反观,确实距离自己应该有的程度不够。对技术原理机制和生产问题排查的总结不够,不够清晰细致;对设计实践的经验总结也不够,不够系统扎实。这个事情还要持续深入地去做。
+
+此外,面得越多,越发现自己的表述能力确实有所欠缺。啰嗦、容易就一点展开说个没完、脱离背景直接说方案、跳跃、回旋往复,然后面试官很可能没耐心了。应该遵循“先总后分”、“基本思路-实现-优化”的一些基本逻辑来作答会更好一些。表述能力真的很重要,不可只顾着敲代码。还有每次面教育企业就不免紧张,生怕错过这个机会。
+
+这是第二家直接告诉我年龄与经验不匹配的企业,加深了我对年龄偏大的忧虑,以致于开始有点不自信了。
+
+那么我又是怎么重拾信心的呢?有一句老话:“留得青山在,不怕没柴烧”。就算我年龄比较大,如果我的技术能力打磨得足够硬朗,就不信找不到一家能够认可我的企业。大不了我去做开源项目好了。具备好的技术能力,并不一定就局限在企业的范围内去发挥作用,也没必要局限于那些被年龄偏见所蒙蔽的人的认知里。外界的认可固然重要,内在的可贵性却远胜于外在。
+
+### 亿童文教-架构师
+
+也是采用的 3 人同时面试。主要问的是项目经历,技术方面问得倒不是深入。个人觉得答得还行。面试官也问了架构设计相关的问题,我答得一般。此时,我仍然没有意识到自己在以面“高级工程师”的规格来面试“架构师”岗位。
+
+面试官比较温和,HR 也在积极联系和沟通,感觉还不错。只是,我没有主动去问反馈意见,也就没有下文了。
+
+### 新东方-高工
+
+面试新东方,主要是因为切中我做教育的期望,虽然职位需求是做信息管理系统,距离我理想中的业务还有一定距离。经过沟通了解,他们更需要的是对运维方面更熟悉的工程师,不过我正好对运维方面不太熟悉,平时关注不多,因此不太符合他们的真实招聘要求。面试官也是很温和的人,老家在宜昌,是我本科上大学的地方,面试体验不错。
+
+以后要花些时间学习一些运维相关的东西。作为一名优秀的工程师和合格的架构师,是要广泛学习和熟悉系统所采用的各种组件、中间件、运维部署等的。要有综观能力,不过我醒悟的可能有点迟。Better later than never.
+
+### ZOOM-高工
+
+ZOOM 的一位面试官或许是我见过的所有面试官中最差劲的。共有两位面试官,一位显得很有耐心,另一位则挺着胖胖的肚子,还打着哈欠,一副不怎么关心面试和候选人的样子。我心想,你要不想面,为啥还要来面呢?你以为候选人就低你一等么?换个位置我可以暴打你。不过我还是很有礼貌的,当做什么事也没发生。公司在挑人,候选人也在挑选公司。
+
+想想,ZOOM 还是疫情期间我们公司用过的远程通信会议软件。印象还不错,有这样的工程师和面试官藏于其中,我也是服了。难倒他是传说中的大大神?据我所知,国外对国内的互联网软件技术设施基本呈碾压态势,中国大部分企业所用的框架、中间件、基础设施等基本是拿国外的来用或者做定制化,真正有自研的很少,有什么好自满的呢?
+
+### 阿优文化-高工
+
+阿优文化有四轮技术面。其中第一个技术面给我印象比较深刻。看上去,面试官对操作系统的原理机制特别擅长和熟悉。很多问题我都没答上来。本以为挂了,不过又给了扳回一局的机会。第二位面试问的项目经历和技术问题是我很熟悉的。第三位面试官问的比较广泛,有答的上来的,有答不上来的。不过面试官很耐心。第四位是技术总监,也问得很广泛细致。
+
+整体来说,面试氛围还是很宽松的。不过,阿优当时的招聘需求并不强烈,估计是希望后续有机会时再联系我。可惜我那时准备回武汉了。主要是考虑父母年事已高,希望能多陪陪父母。
+
+想想,我想问题做决策还是过于简单的,不会做很复杂的计算和权衡。
+
+### 小米-专家/架构
+
+应聘小米,主要是因为职位与之前在有赞做的很相似,都是做交易中台相关。浏览小米官网之后,觉得他们做的事情很棒,可是与我想做教育文化事业的初衷不太贴合。
+
+加入小米的意愿不太强烈,面试也就失去了大半动力。我这个性子还是要改一改。
+
+### 视觉中国-高工
+
+围绕技术、项目和经历来问。总体来说,技术深度并不是太难,项目方面也涉及到了。人力面前辈很温和,我以为会针对自己的经历进行一番“轰炸”,结果是为前辈讲了讲有赞的产品服务和生意模式,然后略略带了下自己的一些经历。
+
+### 科大讯飞-架构师
+
+一二面,感觉面试官对安排的面试不太感兴趣。架构师,至少是一个对技术和设计能力非常高要求的职位。一面的技术和架构都问了些,二面总围绕我的背景和非技术相关的东西问,似乎对我的外在更关注,而对我自身的技术和设计能力不感兴趣。交流偏浅。
+
+能力固然有高下之分,但尊重人才的基本礼节却是不变的。尊重人才,是指聚焦人才的能力和才学,而不是一些与才学不甚相关的东西。
+
+### 青藤云-高工
+
+青藤云的技术面试风格是温和的。感受到坦率交流的味道,被认可的感觉。感受到 HR 求才若渴的心情。和我之前认为的“应当用其实力去赢得对方的尊重和赏识”不谋而合。
+
+### 腾讯会议-高工
+
+和腾讯面试官是用腾讯会议软件面试腾讯会议的职位。哈哈。由于网络不太稳定,面试过程充满了磕磕碰碰,一句话没说完整就听不清楚了。可想情况如何。但是我们都很有很有很有耐心,最终一起完成了一面。面试是双方智慧与力量的较量,更是双方一起去完成一件事情、发现彼此的合作。这样想来,传统的“单方考验筛选式”的面试观念需要革新。
+
+由于我已经拿到 offer , 且腾讯会议的事情并不太贴合自己的初衷,因此,我与腾讯方面沟通,停止了二面。
+
+### 最终选择
+
+当拿到多个 offer 时,如何选择呢?我个人主要看重:
+
+1. 志趣与驱动力;
+2. 薪资待遇;
+3. 公司发展前景和个人发展空间;
+4. 工作氛围;
+5. 小而有战斗力的企业。
+
+在视觉中国与青藤云之间如何选择?作个对比:
+
+- 薪资待遇:两者的薪资待遇不相上下,也都是认可我的;视觉中国给出的是 Leader 的职位,而青藤云给出的是核心业务的承诺;
+- 工作氛围:青藤云应该更偏工程师文化氛围,而视觉中国更偏业务化;
+- 挑战性:青藤云的技术挑战更强,而视觉中国的业务挑战性更强;
+- 志趣与驱动力:视觉中国更符合我想做文化的事情,而青藤云安全并不贴合我想做教育文化事业的初衷,而且比较偏技术和底层(我更希望做一些人文性的事情)。但青藤云做的是关于安全的事情,安全是一件很有价值很有意义的事情。而且,以后安全也可以服务于教育行业。有点曲线救国的味道。尤其是创始人张福的理想主义信念“让安全之光照亮互联网的每个角落”及自己的身体力行,让人更有一些触动。最终,我觉得做安全比做图片版权保护稍胜出一小筹。
+
+此外,我觉得做教育,更适合自己的是编程教育,或者是工程师教育。我还想成为一名系统设计师。还需要积累更多生产实践经验。可以多与初中级工程师打交道,在企业内部做培训指导。或者工作之余录制视频,上传到 B 站,服务广大吃瓜群众。将来,我或许还会写一本关于编程设计的书,汇聚毕生所学。
+
+因此,经过一天慎重的考虑,我决定,加入青藤云安全。当然,做这个选择的同时,也意味着我选择了一个更大的挑战:在安全方面我基本一穷二白,需要学习很多很多的知识和经验,对于我这个大龄程序员来说,是一项不小的挑战。
+
+## 小结
+
+很多事情都有解决的方法,即使“头疼的”大龄程序员找工作也不例外。确立明确清晰的目标、制定科学合理的决策、持续的努力、掌握基本面、恰当的出击,终能斩获胜利的果实。但要强调一下:功夫在平时。平时要是不累积好,面试的时候就要花更多时间去学习,会受挫、磕磕碰碰、过得也不太舒坦。还是平摊到平时比较好。此外,平时视野也要保持开阔,切忌在面试的时候才“幡然醒悟”。
+
+一个重要经验是,要善于从失败中学习。正是在杭州四个月空档期的持续学习、思考、积累和提炼,以及面试失败的反思、不断调整对策、完善准备、改善原有的短板,采取更为合理的方式,才在回武汉的短短两个周内拿到比较满意的 offer 。
+
+此外,值得提及的是,对于技术人员,写博客是一件很有价值的事情。面试通过沟通去了解对方,有其局限性所在。面试未能筛选出符合的人才其实是有比较大概率的:
+
+1. 面试的时间很短,即使是很有经验的面试官,也会看走眼(根本局限性);
+2. 面试官问到的正好是自己不会的(运气问题);
+3. 面试官情绪不好,没兴趣(运气问题);
+4. 面试官自身的水平。
+
+因此,具备真才实学而被 PASS 掉,并不值得伤心。写博客的意义在于,有更多展示自己思考和平时工作的维度。
+
+尊重人才的企业,一定是希望从多方面去认识候选人(在优点和缺点之间选择确认是否符合期望),包括博客;不尊重人才的企业,则会倾向于用偷懒的方法,对候选人真实的本领不在意,用一些外在的标准去快速过滤,固然高效,最终对人才的识别能力并不会有多大进步。
+
+经过这一段面试的历炼,我觉得现在相比离职时的自己,又有了不少进步的。不说脱胎换骨,至少也是蜕了一层皮吧。差距,差距还是有的。起码面试那些知名大厂企业的技术专家和架构师还有差距。这与我平时工作的挑战性、认知视野的局限性及总结不足有关。下一次,我希望积蓄足够实力做到更好,和内心热爱的有价值有意义的事情再近一些些。
+
+面试,其实也是一段工作经历。
diff --git a/docs/high-quality-technical-articles/interview/the-experience-of-get-offer-from-over-20-big-companies.md b/docs/high-quality-technical-articles/interview/the-experience-of-get-offer-from-over-20-big-companies.md
new file mode 100644
index 0000000000000000000000000000000000000000..16d8f9e48569adfb6436687a246b4f05d6f70532
--- /dev/null
+++ b/docs/high-quality-technical-articles/interview/the-experience-of-get-offer-from-over-20-big-companies.md
@@ -0,0 +1,197 @@
+---
+title: 斩获 20+ 大厂 offer 的面试经验分享
+category: 技术文章精选集
+author: 业余码农
+tag:
+ - 面试
+---
+
+> **推荐语**:很实用的面试经验分享!
+>
+>
+>
+> **原文地址**:https://mp.weixin.qq.com/s/HXKg6-H0kGUU2OA1DS43Bw
+
+突然回想起当年,我也在秋招时也斩获了 20+的互联网各大厂 offer。现在想起来也是有点唏嘘,毕竟拿得再多也只能选择一家。不过许多朋友想让我分享下互联网面试方法,今天就来给大家仔细讲讲打法!
+
+如今金九银十已经过去,满是硝烟的求职战场上也只留下一处处炮灰。在现在这段日子,又是重新锻炼,时刻准备着明年金三银四的时候。
+
+对于还没毕业的学生来说,明年三四月是春招补招或者实习招聘的机会;对于职场老油条来说,明年三四月也是拿完年终奖准备提桶跑路的时候。
+
+所以这段日子,就需要好好准备积累面试方法以及面试经验,明年的冲锋陷阵打下基础。这篇文章将为大家讲讲,程序员应该如何准备好技术面试。
+
+一般而言,互联网公司技术岗的招聘都会根据需要设置为 3 ~ 4 轮面试,一些 HC 较少的岗位可能还会经历 5 ~ 8 轮面试不等。除此之外,视公司情况,面试之前还可能也会设定相应的笔试环节。
+
+多轮的面试中包括技术面和 HR 面。相对来说,在整体的招聘流程中,技术面的决定性比较重要,HR 面更多的是确认候选人的基本情况和职业素养。
+
+不过在某些大厂,HR 也具有一票否决权,所以每一轮面试都该好好准备和应对。技术面试一般可分为五个部分:
+
+1. 双方自我介绍
+2. 项目经历
+3. 专业知识考查
+4. 编码能力考察
+5. 候选人 Q&A
+
+## 双方自我介绍
+
+面试往往是以自我介绍作为开场,很多时候一段条理清晰逻辑明确的开场会决定整场面试的氛围和节奏。
+
+**作为候选人,我们可以在自我介绍中适当的为本次面试提供指向性的信息,以辅助面试官去发掘自己身上的亮点和长处**。
+
+其实自我介绍并不是简单的个人基本情况的条条过目,而是对自己简历的有效性概括。
+
+什么是有效性概括呢,就是意味着需要对简历中的信息进行核心关键词的提取整合。一段话下来,就能够让面试官对你整体的情况有了了解,从而能够引导面试官的联系提问。
+
+## 项目经历
+
+项目经历是面试过程中非常重要的一环,特别是在社招的面试中。一般社招的职级越高,往往越看重项目经历。
+
+而对于一般的校招生而言,几份岗位度匹配度以及项目完整性高的项目经历可以成为面试的亮点,也是决定于拿`SP` or `SSP`的关键。
+
+但是准备好项目经历,并不是一件容易的事情。很多人并不清楚应该怎样去描述自己的项目,更不知道应该在经历中如何去体现自己的优势和亮点。
+
+这里针对项目经历给大家提几点建议:
+
+**1、高效有条理的描述**
+
+项目经历的一般是简历里篇幅最大的部分,所以在面试时这部分同样重要。在表述时,语言的逻辑和条理一定要清晰,以保证面试官能够在最快的时间抓到你的项目的整体思路。
+
+相信很多人都听说过写简历的各种原则,比如`STAR`、`SMART`等。但实际上这些原则都可以用来规范自己的表达逻辑。
+
+`STAR`原则相对简单,用来在面试过程中规范自己的条理非常有效。所谓`STAR`,即`Situation`、`Target`、`Action`、`Result`。这跟写论文写文档的逻辑划分大体一致。
+
+- `Situation`: 即项目背景,需要将项目提出的原因、现状以及出发点表述清楚。简单来说,就是要将项目提出的来龙去脉描述清晰。比如某某平台建设的原因,是切入用户怎样的痛点之类的。
+- `Target`: 即项目目标,这点描述的是项目预期达到或完成的程度。**最好是有可量化的指标和预期结果。**比如性能优化的指标、架构优化所带来的业务收益等等。
+- `Action`: 即方法方案,意味着完成项目具体实施的行为。这点在技术面试中最为重要,也是表现候选人能力的基础。**项目的方法或方案可以从技术栈出发,根据采用的不同技术点来具体写明解决了哪些问题。**比如用了什么框架/技术实现了什么架构/优化/设计,解决了项目中什么样的问题。
+- `Result`: 即项目获得结果,这点可以在面试中讲讲自己经历过项目后的思考和反思。这样会让面试官感受到你的成长和沉淀,会比直接的结果并动人。
+
+**2、充分准备项目亮点**
+
+说实话,大部分人其实都没有十分亮眼的项目,但是并不意味着没有项目经历的亮点。特别是在面试中。
+
+在面试中,你可以通过充分的准备以及深入的思考来突出你的项目亮点。比如可以从以下几个方向入手:
+
+- 充分了解项目的业务逻辑和技术架构
+- 熟悉项目的整体架构和关键设计
+- 明确的知道业务架构或技术方案选型以及决策逻辑
+- 深入掌握项目中涉及的组件以及框架
+- 熟悉项目中的疑难杂症或长期遗留 bug 的解决方案
+- ......
+
+## 专业知识考查
+
+有经验的面试官往往会在对项目经历刨根问底的同时,从中考察你的专业知识。
+
+所谓专业知识,对于程序员而言就是意向岗位的计算机知识图谱。对于校招生来说,大部分都是计算机基础;而对于社招而言,很大部分可能是对应岗位的技能树。
+
+计算机基础主要就是计算机网络、操作系统、编程语言之类的,也就是所谓的八股文。虽然这些东西在实际的工作中可能用处并不多,但是却是面试官评估候选人潜力的标准。
+
+而对应岗位的技能树就需要根据具体的岗位来划分,**比如说客户端岗位可能会问移动操作系统理解、端性能优化、客户端架构以及跨端框架之类的。跟直播视频相关的岗位,还会问音视频处理、通信等相关的知识。**
+
+而后端岗位可能就更偏向于**高可用架构、事务理论、分布式中间件以及一些服务化、异步、高可用可扩展的架构设计思想**。
+
+总而言之,工作经验越丰富,岗位技术能的问题也就越深入。
+
+怎么在面试前去准备这些技术点,在这里我就不过多说了, 因为很多学习路线以及说的很清楚了。
+
+这里我就讲讲在应对面试的时候,该怎样去更好的表达描述清楚。
+
+这里针对专业知识考察给大家提几点建议:
+
+**1、提前建立一份技术知识图谱**
+
+在面试之前,可以先将自己比较熟悉的知识点做一个简单的归纳总结,根据不同方向和领域画个简单的草图。这是为了辅助自己在面试时能够进行合理的扩展和延伸。
+
+面试官一问一答形式的面试总是会给人不太好的面试体验,所以在回答技术要点的过程中,要善于利用自己已有的知识图谱来进行技术广度的扩展和技术深度的钻研。这样一来能够引导面试官往你擅长的方向去提问,二来能够尽可能多的展现自己的亮点。
+
+**2、结合具体经验来总结理解**
+
+技术点本身都是非常死板和冰冷的,但是如果能够将生硬的技术点与具体的案例结合起来描述,会让人眼前一亮。同时也能够表明自己是的的确确理解了该知识点。
+
+现在网上各种面试素材应有尽有,可能你背背题就能够应付面试官的提问。但是面试官也同样知道这点,所以他能够很清楚的判别出你是否在背题。
+
+因此,结合具体的经验来解释表达问题是能够防止被误认为背题的有效方法。可能有人会问了,那具体的经验哪里去找呢。
+
+这就得靠平时的积累了,平时需要多积累沉淀,多看大厂的各类技术输出。经验不一定是自己的,也可以是从别的地方总结而来的。
+
+此外,也可以结合自己在做项目的过程中的一些技术选型经验以及技术方案更新迭代的过程进行融会贯通,相互结合的来进行表述。
+
+## 编码能力考察
+
+编码能力考察就是咱们俗称的手撕代码,也是许多同学最害怕的一关。很多人会觉得面试结果就是看手撕代码的表现,但其实并不一定。
+
+**首先得明确的一点是,编码能力不完全等于算法能力。**很多同学面试时候算法题明明写出来了,但是最终的面试评价却是编码能力一般。还有很多同学面试时算法题死活没通过,但是面试官却觉得他的编码能力还可以。
+
+所以一定要注意区分这点,编码能力不完全等于算法能力。从公司出发,如果纯粹为了出难度高的算法题来筛选候选人,是没有意义的。因为大家都知道,进了公司可能工作几年都写不了几个算法。
+
+要记住,做算法题只是一个用来验证编码能力和逻辑思维的手段和方式。
+
+当然说到底,在准备这一块的面试时,算法题肯定得刷,但是不该盲目追求难度,甚至是死记硬背。
+
+几点面试时的建议:
+
+**1、数据结构和算法思想是基础**
+
+算法本身实际上是逻辑思考的产物,所以掌握算法思想比会做某一道题要更有意义。数据结构是帮助实现算法的工具,这也很编程的基本能力。所以这二者的熟悉程度是手撕代码的基础。
+
+**2、不要忽视编码规范**
+
+这点就是提醒大家要记住,就算是一段很简单的算法题也能够从中看出你的编码能力。这往往就体现在一些基本的编码规范上。你说你编程经验有 3 年,但是发现连基本的函数封装类型保护都不会,让人怎么相信呢。
+
+**3、沟通很重要**
+
+手撕代码绝对不是一个闭卷考试的过程,而是一个相互沟通的过程。上面也说过,考察算法也是为了考察逻辑思维能力。所以让面试官知道你思考问题的思路以及逻辑比你直接写出答案更重要。
+
+不仅如此,提前沟通清楚思路,遇到题意不明确的地方及时询问,也是节省大家时间,给面试官留下好印象的机会。
+
+此外,自己写的代码一定要经得住推敲和质疑,自己能够讲的明白。这也是能够区分「背题」和「真正会做」的地方。
+
+最后,如果代码实在写不出来,但是也可以适当的表达自己的思路并与面试官交流探讨。毕竟面试也是一个学习的过程。
+
+## 候选人 Q&A
+
+一般正常的话,都会有候选人反问环节。倘若没有,可能是想让你回家等消息。
+
+反问环节其实也可以是面试中重要的环节,因为这个时候你能够从面试官口中获得关于公司关于岗位更具体真实的信息。
+
+这些信息可以帮助我们做出更全面更理性的决策,毕竟求职也是一个双向选择的过程。
+
+## 加分项
+
+最后,给能够坚持看到最后的同学一个福利。我们来谈谈面试中的加分项。
+
+很多同学会觉得明明面试时候的问题都答上来了,但是最终却没有通过面试,或者面试评价并不高。这很有可能就是面试过程中缺少了亮点,可能你并不差,但是没有打动面试官的地方。
+
+一般面试官会从下面几个方面去考察候选人的亮点:
+
+**1、沟通**
+
+面试毕竟是问答与表达的艺术,所以你流利的表达,清晰有条理的思路自然能够增加面试官对你的高感度。同时如果还具有举一反三的思维,那也能够从侧面证明你的潜力。
+
+**2、匹配度**
+
+这一点毋庸置疑,但是却很容易被忽视。因为往往大家都会认为,匹配度不高的都在简历筛选阶段被刷掉了。但其实在面试过程中,面试官同样也会评估面试人与岗位的匹配度。
+
+这个匹配度与工作经历强相关,与之前做过的业务和技术联系很大。特别是某些垂直领域的技术岗位,比如财经、资金、音视频等。
+
+所以在面试中,如若有跟目标岗位匹配度很高的经历和项目,可以着重详细介绍。
+
+**3、高业绩,有超出岗位的思考**
+
+这点就是可遇不可及,毕竟不是所有人都能够拿着好业绩然后跳槽。但是上一份工作所带来的好业绩,以及在重要项目中的骨干身份会为自己的经历加分。
+
+同时,如果能在面试中表现出超出岗位本身的能力,更能引起面试官注意。比如具备一定的技术视野,具备良好的规划能力,或者对业务方向有比较深入的见解。这些都能够成为亮点。
+
+**4、技术深度或广度**
+
+相信很多人都听过,职场中最受欢迎的是`T`型人才。也就是在拥有一定技术广度的基础上,在自己擅长的领域十分拔尖。这样的人才的确很难得,既要求能够胜任自己的在职工作,又能够不设边界的学习和输出其它领域的知识。
+
+除此之外,**比 T 型人才更为难得是所谓 π 型人才,相比于 T 型人才,有了不止一项拔尖的领域。这类人才更是公司会抢占的资源。**
+
+## 总结
+
+面试虽说是考察和筛选优秀人才的过程,但说到底还是人与人沟通并展现自我的方式。所以掌握有效面试的技巧也是帮助自己收获更多的工具。
+
+这篇文章其实算讲的是方法论,很多我们一看就明白的「道理」实施起来可能会很难。可能会遇到一个不按常理出牌的面试官,也可能也会遇到一个沟通困难的面试官,当然也可能会撞上一个不怎么匹配的岗位。
+
+总而言之,为了自己想要争取的东西,做好足够的准备总是没有坏处的。祝愿大家能成为`π`型人才,获得想要的`offer`!
diff --git a/docs/high-quality-technical-articles/personal-experience/8-years-programmer-work-summary.md b/docs/high-quality-technical-articles/personal-experience/8-years-programmer-work-summary.md
new file mode 100644
index 0000000000000000000000000000000000000000..f6362c7fa057c32a250ed3c59586d90f64f27c01
--- /dev/null
+++ b/docs/high-quality-technical-articles/personal-experience/8-years-programmer-work-summary.md
@@ -0,0 +1,234 @@
+---
+title: 一个中科大差生的 8 年程序员工作总结
+category: 技术文章精选集
+author: 陈小房
+tag:
+ - 个人经历
+---
+
+> **推荐语**:这篇文章讲述了一位中科大的朋友 8 年的经历:从 2013 年毕业之后加入上海航天 x 院某卫星研究所,再到入职华为,从华为离职。除了丰富的经历之外,作者在文章还给出了很多自己对于工作/生活的思考。我觉得非常受用!我在这里,向这位作者表达一下衷心的感谢。
+>
+>
+>
+> **原文地址**:https://www.cnblogs.com/scada/p/14259332.html
+
+---
+
+## 前言
+
+今年终于从大菊花厂离职了,离职前收入大概 60w 不到吧!在某乎属于比较差的,今天终于有空写一下自己的职场故事,也算是给自己近 8 年的程序员工作做个总结复盘。
+
+近 8 年有些事情做对了,也有更多事情做错了,在这里记录一下,希望能够给后人一些帮助吧,也欢迎私信交流。文笔不好,见谅,有些细节记不清了,如果有出入,就当是我编的这个故事吧。
+
+_PS:有几个问题先在这里解释一下,评论就不一一回复了_
+
+1. 关于差生,我本人在科大时确实成绩偏下,差生主要讲这一点,没其他意思。
+2. 因为买房是我人生中的大事,我认为需要记录和总结一下,本文中会有买房,房价之类的信息出现,您如果对房价,炒房等反感的话,请您停止阅读,并且我再这里为浪费您的时间先道个歉。
+
+## 2013 年
+
+### 加入上海航天 x 院某卫星研究所
+
+本人 86 年生人,13 年从中科大软件相关专业毕业,由于父母均是老师,从小接受的教育就是努力学习,找个稳定的“好工作”,报效国家。
+
+于是乎,毕业时候头脑一热加入了上海航天 x 院某卫星研究所,没有经过自己认真思考,仅仅听从父母意见,就草率的决定了自己的第一份工作,这也为我 5 年后离职埋下了隐患。这里总结第一条经验:
+
+**如果你的亲人是普通阶层,那对于人生中一些大事来说,他们给的建议往往就是普通阶层的思维,他们的阶层就是他们一生思维决策的结果,如果你的目标是跳出本阶层,那最好只把他们的建议当成参考。**
+
+13 年 4 月份,我坐上火车来到上海,在一路换乘地铁来到了大闵行,出了地铁走路到单位,一路上建筑都比较老旧,我心里想这跟老家也没什么区别嘛,还大上海呢。
+
+到达单位报道,负责报道的老师很亲切,填写完资料,分配了一间宿舍,还给了大概 3k 左右安家费,当时我心里那个激动啊(乡下孩子没有见过钱啊,见谅),拿了安家费,在附近小超市买好生活用品,这样我就开始了自己航天生涯。
+
+经过 1 个月集中培训后,我分配到部门,主要负责卫星上嵌入式软件开发。不过说是高大上的卫星软件开发,其实刚开始就是打杂,给实验室、厂房推箱子搬设备,呵呵,说航天是个体力活相信很多航天人都有同感吧。不过当时年轻,心思很单纯,每天搬完设备,晚上主动加班,看文档材料,画软件流程图,编程练习,日子过得很充实。
+
+记得第一个月到手大概 5k 左右(好少呀),当时很多一起入职的同事抱怨,我没有,我甚至不太愿意和他们比较工资,这里总结第二条经验:
+
+**不要和你的同事比工资,没有意义,比工资总会有人受伤,更多的是负面影响,并且很多时候受伤的会是你。**
+
+### 工作中暂露头角
+
+工作大概一个月的时候,我遇到了一件事情,让我从新员工里面开始暂露头角。事情是这样的当时国家要对军工单位进行 GJB5000A 软件开发等级认证(搞过这个认证的同学应该都知道,过这个认证那是要多酸爽有多酸爽),但是当时一个负责配置管理的同事却提出离职,原因是他考上了公务员,当时我们用的那个软件平台后台的版本控制是 SVN 实现的,恰好我在学校写程序时用过,呵呵,话说回来现在学生自己写软件很少有人会在本地搭版本控制器吧!我记得当时还被同学嘲笑过,这让我想起了乔布斯学习美术字的故事,这里总结一下:
+
+**不要说一项技能没有用,任何你掌握的技能都有价值,但是你要学会找到发挥它的场景。如果有一天你落水了,你可能会很庆幸,自己以前学会了游泳。**
+
+**工作中如果要上升,你要勇于承担麻烦的、有挑战的任务,当你推掉麻烦的时候,你也推掉了机遇。**
+
+好了,扯远了,回到前面,当时我主动跟单位认证负责人提出,我可以帮忙负责这方面的工作,我有一定经验。这里要提一下这个负责人,是位女士,她是我非常敬佩的一个前辈,认真,负责,无私,整个人为国家的航天事业奉献了几十年,其实航天领域有非常多这样的老前辈,他们默默奋斗,拿着不高的薪水,为祖国的国防建设做出了巨大的贡献。当时这位负责人,看我平时工作认真积极,思维反应也比较灵活(因为过认证需要和认证专家现场答辩的)就同意了我的请求,接受到这个任务之后,我迅速投入,学习认证流程、体系文件、迅速掌握认证工作要点,一点一点把相关的工作做好,同时周期性对业务进行复盘,总结复盘可能是我自己的一个优点:
+
+**很多人喜欢不停的做事,但不会停下来思考,缺乏总结复盘的能力,其实阶段性总结复盘,不仅能够固化前面的经验,也能梳理后面的方向;把事情做对很重要,但是更重要的是做对的事;另外不要贪快,方向正确慢就是快**(后半句是我后来才想明白的,如果早想明白,就不会混成目前这样了)
+
+1 个月后,当时有惊无险通过了当年的认证,当时负责人主动向单位申请了 2k 特别奖,当时我真的非常高兴,主要是自己的工作产生了价值,得到了认可。后来几个月的日子平淡无奇,有印象的好像只有两件事情。
+
+一件事情是当年端午,当时我们在单位的宿舍休息,突然楼道上一阵骚动,我打开宿舍门一看,原来是书记来慰问,还给每个人送了一箱消暑饮料,这件事印象比较深刻,是我觉得国企虽然有各种各样的问题,但是论人文关怀,还是国企要好得多。
+
+### 错失一次暴富的机会
+
+另一件事是当年室友刚买房,然后天天研究生财&之道,一会劝我买房,一会劝我买比&特&币,我当时没有鸟他,为什么呢,因为当时的室友生活习惯不太好,会躺在床上抽烟,还在宿舍内做饭(我们宿舍是那种很老的单位房,通风不好),我有鼻炎,所以不是很喜欢他(嗯,这里要向室友道歉,当年真是太幼稚了)。现在 B&T&C4 万美元了,我当时要是听了室友也能小发一笔了(其实我后来 18 年买了,但是没有拿住这是后话),这里要总结一下:
+
+**不要因为某人的外在,如外貌、习惯、学历等对人贴上标签,去盲目否定别人,对于别人的建议,应该从客观出发,综合分析,从善如流是一项非常难得的品质。**
+
+**人很难挣到他认知之外的财富,就算偶然拿到了,也可能很快失去。所以不要羡慕别人投机获得的财富,努力提升你的思维,财商才是正道。**
+
+### 航天生涯的第一个正式项目
+
+转眼到了 9 月份(我 4 月份入职的),我迎来了我航天生涯第一个正式的型号项目(型号,是军工的术语,就相当于某个产品系列比如华为的 mate),当时分配给我的型号正式启动,我终于可以开始写卫星上的代码了。
+
+当时真的是心无旁骛,一心投身军工码农事业,每天实验室,测试厂房,评审会,日子虽然忙碌,但是也算充实。并且由于我的努力工作,加上还算可以的技术水平,我很快就能独立胜任一些型号基础性的工作了,并且我的努力也受到了型号(产品)线的领导的认可,他们开始计划让我担任型号主管设计师,这是一般工作 1-2 年的员工的岗位,当时还是有的激动的。
+
+## 2014 年
+
+### 升任主管设计师后的一次波折
+
+转眼间到 2014 年了,大概上半年吧,我正式升任主管设计师,研发工作上也开时独挡一面了,但是没多久产品研发就给了我当头一棒。
+
+事情是这样的,当时有一个版本软件编写完毕,加载到整星上进行测试,有一天大领导来检查,当时非常巧,领导来时测试主岗按某个岗位的人员要求,发送了一串平时整星没有使用的命令(我在实验室是验证过的),结果我的软件立刻崩溃,无法运行。由于正好领导视察,这个问题立马被上报到质量处,于是我开始了苦逼的技术归零攻关(搞航天的都懂,我就不解释了)。
+
+期间每天都有 3 个以上领导,询问进度,当时作为新人的我压力可想而知,可是我无论如何都查不出来问题,在实验室我的软件完全正常!后来,某天中午我突然想到,整星上可能有不同的环境,具体过程就不说了。后人查出来是一个负责加载我软件的第三方软件没有受控,非法篡改了我程序的 4 个字节,而这 4 字节正好是那天发送命令才会执行的代码,结果导致我的软件崩溃。最后我花了进一个月完成了所有质量归零报告,技术分析报告,当然负责技术的领导没有责怪,相反的还更加看重我了,后来我每遇到一个质量问题,无论多忙最后定要写一份总结分析报告,这成了我一个技术习惯,也为后来我升任软件开发组长奠定了技术影响基础。
+
+**强烈建议技术团队定期开展质量回溯,需要文档化,还要当面讲解,深入的技术回溯有助于增加团队技术交流活跃度,同时提升团队技术积淀,是提升产品质量,打造优秀团队的有效方法。**
+
+**个人的话建议养成写技术总结文章的习惯,这不仅能提升个人技术,同时分享也可以增加你的影响力**
+
+### 职场软技能的重新认识
+
+上半年就在忙碌中度过了,到了年底,发生了一件对我们组影响很大的事情,年底单位开展优秀小组评比, 其实这个很多公司都有,那为什么说对我们组影响很大内,这里我先卖关子。这里先不得不提一个人,是个女孩子,南京大学的,比我晚来一年,她做事积极,反应灵敏,还做得一手不错的 PPT,非常优秀,就是黑了点(希望她看到了不要来找我,呵呵)。
+
+当时单位开展优秀小组评比,我们当时是新员工,什么都很新鲜,就想参加一下,当时领导说我们每年都参加的,我们问,我们每年做不少东西,怎么没有看到过评比的奖状,领导有点不好意思,说我们没有进过决赛。我们又问,多少名可以进入决赛圈,答曰前 27 名即可(总共好像 50+个组)我们当时心里真是一万个羊驼跑过。。。。
+
+其实当时我们组每年是做不少事情的,我们觉得我们不应该排名如此之低,于是我们几个年轻人开始策划,先是对我们的办公室彻底改造(因为要现场先打分,然后进决赛),然后好好梳理了我们当年取得的成绩,现场评比时我自告奋勇进行答辩(我沟通表达能力还不错,这也算我一个优势吧),后面在加上前文提到的女孩子做的漂亮 PPT,最后我们组拿到了铜牌班组的好成绩,我也因为这次答辩的优秀表现在领导那里又得到了认可,写了大半段了,再总结一下:
+
+**职场软技能如自我展示很重要,特别是程序员,往往在这方面是个弱项,如果可以的话,可以通过练习,培训强化一下这些软技能,对职场的中后期非常有帮助。**
+
+## 2015 年
+
+时间总是过得很快,一下就到 2015 年了,这一年发生了一件对我影响很大的事情。
+
+### 升任小组副组长
+
+当时我们小组有 18 个人了,有一天部门开会,主任要求大家匿名投票选副组长(当时部门领导还是很民主的),因为日常事务逐渐增多,老组长精力有限,想把一些事物分担出来,当天选举结果就出来了,我由于前面的技术积累和沟通表达能力的展现,居然升任副组长,当时即有些意外,因为总是有传言说国企没背景一辈子就是最最底层,后来我仔细思考过,下面是我不成熟的想法:
+
+**不要总觉得国企事业单位的人都是拼背景,拼关系,我承认存在关系户,但是不要把关系户和低能力挂钩,背景只是一个放大器,当关系户做出了成绩时它会正面放大影响,当关系户做了不光彩的事情是,它也会让影响更坏。没有背景,你可以作出更大的贡献来达到自己的目标,你奋斗的过程是更大的财富。另外,我遇到的关系户能力都很强,也可能是巧合,也可能是他们的父辈给给他们在经验层次上比我们更优秀的教育。**
+
+### 学习团队管理技巧
+
+升任副组长后,我的工作更加忙碌了,不仅要做自己项目的事情,还要横向管理和协调组内其他项目的事情,有人说要多体谅军工单位的基层班组长,这话真是没错啊。这个时候我开始学习一些管理技巧,如何凝聚团队,如何统一协调资源等等,这段时间我还是在不断成长。不过记得当年还是犯了一个很大的方向性错误,虽然更多的原因可能归结为体制吧,但是当时其实可以在力所能及的范围内做一些事情的。
+
+具体是这样的,当时管理上有项目线,有行政线,就是很常见的矩阵式管理体系,不过在这个特殊的体制下面出现了一些问题。当时部门一把手被上级强制要求不得挂名某个型号,因为他要负责部门资源调配,而下面的我们每个人都归属 1-2 个型号(项目),在更高层的管理上又有横向的行政线(不归属型号),又有纵向的型号管理线。
+
+而型号的任务往往是第一线的,因为产品还是第一位的,但是个人的绩效、升迁又归属行政线管理,这种形式在能够高效沟通的民企或者外企一般来说不是问题,但是在沟通效率缓慢,还有其他掣肘因素的国企最终导致组内每个人忙于自身的型号任务,各自单打独斗,无法聚焦,一年忙到头最终却得不到部门认可,我也因为要两面管理疲于应付,后来曾经反思过,其实可以聚焦精力打造通用平台(虽然这在我们行业很难)部分解决这个问题:
+
+**无论个人还是团队,做事情要聚焦,因为个人和团队资源永远都是有限的,如果集中一个事情都做不好,那分散就更难以成功,但是在聚焦之前要深入思考,往什么方向聚焦才是正确的,只有持续做正确的事情才是最重要的。**
+
+## 2016 年
+
+这一年是我人生的关键一年,发生了很多事情。
+
+### 升任小组副组长
+
+第一件事情是我正式升任组长,由于副组长的工作经验,在组长的岗位上也做得比较顺利,在保证研发工作的同时,继续带领团队连续获得铜牌以上班组奖励,另外各种认证检查都稳稳当通过,但是就在这个时候,因为年轻,我犯下了一个至今非常后悔的错误。
+
+大概是这样的,我们部门当时有两个大组,一个是我们的软件研发组,一个是负责系统设计的系统分析组。
+
+当时两个组的工作界面是系统组下发软件任务书给软件组,软件组依照任务书开发,当时由于历史原因,软件组有不少 10 年以上的老员工,而系统组由于新成立由很多员工工作时间不到 2 年,不知道从什么时候起,也不知道是从哪位人员开始,软件组的不少同事认为自己是给系统组打工的。并且,由于系统组同事工作年限较短,实际设计经验不足,任务书中难免出现遗漏,从而导致实际产品出错,两组同事矛盾不断加深。
+
+最后,出现了一个爆发:当时系统组主推一项新的平台,虽然这个平台得到了行政线的支持,但是由于军工产品迭代严谨,这个新平台当时没有型号愿意使用,同时平台的部分负责人,居然没有完整的型号经验!由于这个新平台的软件需要软件组实现,但是因为已经形成的偏见,软件同事认为这项工作中自己是为利益既得者打工。
+
+我当时也因为即负责实际软件开发,又负责部分行政事务,并且年轻思想不成熟,也持有类似的思想。过程中的摩擦、冲突就不说了,最后的结果是系统组、软件组多人辞职,系统组组长离职,部门主任离职创业(当然他们辞职不全是这个原因,包括我离职也不全是这个原因,但是我相信这件事情有一定的影响),这件事情我非常后悔,后来反思过其实当时自己应该站出来,协调两组矛盾,全力支持部门技术升级,可能最终就不会有那么多优秀的同事离开了。
+
+**公司战略的转型,技术的升级迭代,一定会伴随着阵痛,作为基层组织者,应该摒弃个人偏见,带领团队配合部门、公司主战略,主战略的成功才是团队成功的前提。**
+
+### 买房
+
+16 年我第二件大事情就是买房,关注过近几年房价的人都可能还记得,16 年一线城市猛涨的情景。其实当时 15 年底,上海市中心和学区房已经开始上涨,我 15 年底听同事开始讨论上涨的房价,我心里开始有了买房的打算,大约 16 春节(2 月份吧,具体记不得了),我回老家探望父母,同时跟他们提出了买房的打算。
+
+我的父亲是一个“央视新闻爱好者”,爱好看狼咸平,XX 刀,XX 檀的节目,大家懂了吧,父亲说上海房价太高了,都是泡沫,不要买。这个时候我已经不是菜鸟了,我想起我总结的第一条经验(见上文),我开始收集往年的房价数据,中央历年的房价政策,在复盘 15 年的经济政策时我发现,当年有 5 次降息降准,提升公积金贷款额度,放松贷款要求于是我判定房价一定会继续涨,涨到一个幅度各地才会出台各种限购政策,并且房价在城市中是按内环往外涨的于是我开始第一次在人生大事上反对父母,我坚决表态要买房。父亲还是不太同意,他说年底吧,先看看情况(实际是年底母亲的退休公积金可以拿出来大概十几万吧,另外未来丈母娘的公积金也能拿出来了大概比这多些)。我还是不同意,父亲最终拗不过我,终于松口,于是我们拿着双方家庭凑的 50w 现金开始买房,后来上海的房价大家都看到了。这件事也是我做的不多的正确的事情之一。
+
+但是最可笑的是,我研究房价的同时居然犯下了一个匪夷所思的错误,我居然没有研究买房子最重要的因素是什么,我们当时一心想买一手房(现在想想真是脑子进水),最后买了一套松江区交通不便的房子,这第一套房子的地理位置也为我后来第二次离职埋下了隐患,这个后面会说。
+
+**一线或者准一线城市能买尽量买,不要听信房产崩溃论,如果买不起,那可以在有潜力的城市群里用父母的名义先买一套,毕竟大多数人的财富其实是涨不过通货膨胀的。另外买房最重要的三个要素是,地段,地段,地段。**
+
+买房的那天上午和女朋友领的证,话说当时居然把身份证写错了三次 。。。
+
+这下我终于算是有个家了,交完首付那个时候身上真的是身无分文了。航天的基层员工的收入真的是不高,我记得我当时作为组长,每月到手大概也就 7k-8k 的样子,另外有少量的奖金,但是总数仍然不高,好在公积金比较多,我日常也没什么消费欲望,房贷到是压力不大。
+
+买完房子之后,我心里想,这下真的是把双方家庭都掏空了(我们双方家庭都比较普通,我的收入也在知乎垫底,没办法)万一有个意外怎么办,我思来想去,于是在我下一个月发工资之后,做了一个我至今也不知道是对是错的举动,我利用当月的工资,给全家人家人买了保险保险,各种重疾,意外都配好了。但是为什么我至今也不知道对错呢,因为后来老丈人,我母亲都遭遇病魔,但是两次保险公司都拒赔,找出的理由我真是哑口无言,谁叫我近视呢。另外真的是要感谢国家,亲人重病之后,最终还是走了医保,赔偿了部分,不然真的是一笔不小的负担。
+
+## 2017 年
+
+对我人生重大影响的 2016 年,在历史的长河中终究连浪花都激不起来。历史长河静静流淌到了 2017 年,这一年我参加了中国深空探测项目,当然后面我没有等到天问一号发射就离开了航天,但是有时候仰望星空的时候,想想我的代码正在遥远的星空发挥作用,心里也挺感慨的,我也算是重大历史的参与者了,呵呵。好了不说工作了,平淡无奇的 2017 年,对我来说也发生了两件大事。
+
+### 买了第二套房子
+
+第一件事是我买了第二套房子,说来可笑,当年第一套房子都是掏空家里,这第二年就买了第二套房子,生活真的是难以捉摸。到 2017 年时,前文说道,我母亲和丈母娘先后退休,公积金提取出来了,然后在双方家里各自办了酒席,酒席之后,双方父母都把所有礼金给了我们,父母对自己的孩子真的是无私之至。当时我们除了月光之外,其实没有什么外债,就是生活简单点。拿到这笔钱后,我们就在想如何使用,一天我在菜市场买菜,有人给我一张 xuanchuan 页,本来对于这样的 xuanchuan 页我一般是直接扔掉的,但是当天鬼死神差我看了一眼,只见上面写着“嘉善高铁房,紧邻上海 1.5w”我当时就石化了,我记得去年我研究上海房价的时候,曾经在网站上看到过嘉善的房价,我清楚的记得是 5-6k,我突然意识到我是不是错过了什么机会,反思一下:
+
+**工作生活中尽量保持好奇心,不要对什么的持怀疑态度,很多机会就隐藏在不起眼的细节中,比如二十年前有人告诉你未来可以在网上购物,有人告诉你未来可以用手机支付,你先别把他直接归为骗子,静下来想一想,凡事要有好奇心,但是要有自己的判断。**
+
+于是我立马飞奔回家,开始分析,大城市周边的房价。我分析了昆山,燕郊,东莞,我发现燕郊极其特殊,几乎没有产业,纯粹是承接大城市人口溢出,因此房价成高度波动。而昆山和东莞,由于自身有产业支撑,又紧邻大城市,因此房价稳定上涨。我和妻子一商量,开始了外地看房之旅,后来我们去了嘉善,觉得没有产业支撑,昆山限购,我们又到嘉兴看房,我发现嘉兴房价也涨了很多,但是这里购房的大多数新房,都是上海购房者,入住率比较低,很多都是打算买给父母住的,但是实际情况是父母几乎不在里面住,我觉得这里买房不妥,存在一个变现的问题。于是我开始继续寻找,一天我看着杭州湾的地图,突然想到,杭州湾北侧不行,那南侧呢?南侧绍兴,宁波经济不是更达吗。于是我们目光投向绍兴,看了一个月后,最后在绍兴紧贴杭州的一个区,购买了一套小房子,后来 17 年房价果然如我预料的那样完成中心城市的上涨之后开始带动三四线城市上涨。后来国家出台了大湾区政策,我对我的小房子更有信心了。这里稍微总结一下我个人不成熟的看法:
+
+**在稳定通胀的时代,负债其实是一种财富。长三角城市群会未来强于珠港澳,因为香港和澳门和深圳存在竞争关系,而长三角城市间更多的是互补,未来我们看澳门可能就跟看一个中等省会城市一样了。**
+
+### 准备要孩子
+
+2017 年的第二件事是,我们终于准备要孩子了,但是妻子怎么也备孕不成功,我们开始频繁的去医院,从 10 元挂号费的普通门诊,看到 200 元,300 元挂号费的专家门诊,看到 600 元的特需门诊,从综合医院看到妇幼医院,从西医看到中医,每个周末不是在医院排队,就是在去医院的路上。最后的诊疗结果是有一定的希望,但是有困难,得到消息时我真的感觉眼前一片黑暗,这种从来在新闻上才能看到了事情居然落到了我们头上,我们甚至开始接触地下 XX 市场。同时越来越高的医疗开销(专家门诊以上就不能报销了)也开始成为了我的负担,前文说了,我收入一直不高,又还贷款,又支付医疗开支渐渐的开始捉襟见肘,我甚至动了卖小房子的打算。
+
+## 2018 年
+
+前面说到,2017 年开始频繁出入医院,同时项目也越来越忙,我渐渐的开始喘不过气起来,最后医生也给了结论,需要做手术,手术有不小的失败的几率。我和妻子商量后一咬牙做吧,如果失败就走地下的路子,但是可能需要准备一笔钱(手术如果成功倒是花销不会太大),哎,古人说一分钱难倒英雄汉,真是诚不欺我啊,这个时候我已经开始萌生离职的想法了。怎么办呢,生活还是要继续,我想起了经常来单位办理贷款的银行人员,贷款吧,这种事情保险公司肯定不赔的嘛,于是我办理了一笔贷款,准备应急。
+
+### 项目结束,离职
+
+时间慢慢的时间走到了 8 月份,我的项目已经告一定段落,一颗卫星圆满发射成功,深空项目也通过了初样阶段我的第一份工作也算有始有终了。我开始在网上投递简历,我技术还算可以,沟通交流也不错,面试很顺利,一个月就拿到了 6 个 offer,其中就有大菊花厂的 offer,定级 16A,25k 月薪后来政策改革加了绩效工资 6k(其实我定级和总薪水还是有些偏低了和我是国企,本来总薪水就低有很大关系,话说菊花厂级别后面真的是注水严重,博士入职轻松 17 级)菊花厂的 offer 审批流程是我见过最长,我当时的接口人天天催于流程都走了近 2 个月。我向领导提出了离职,离职的过程很痛苦,有过经历的人估计都知道,这里就不说了。话说我为什么会选择华为呢,一是当时急需钱,二是总觉得搞嵌入式的不到华为看看真的是人生遗憾。现在想想没有认真去理解公司的企业文化就进入一家公司还是太草率了:
+
+**如果你不认同一个公司的企业文化,你大概率干不长,干不到中高层,IT 人你不及时突破到中高层很快你就会面临非常多问题;公司招人主要有两种人,一种是合格的人,一种是合适的人,合格的人是指技能合格,合适的人是指认同文化。企业招人就是先把合格的人找进来,然后通过日日宣讲,潜移默化把不合适的人淘汰掉。**
+
+### 入职华为
+
+经过一阵折腾终于离职成功,开始入职华为。离职我做了一件比较疯狂的事情,当时因为手上有一笔现金了,一直在支付利息,心里就像拿它干点啥。那时由于看病,接触了地下 XX 市场,听说了 B&TC,走之前我心一横买 B&T&C,后来不断波动,最终我还是卖了,挣了一些钱,但是最终没有拿到现在,果然是考验人性啊。
+
+## 2019 年
+
+### 成功转正
+
+华为的试用期真长,整整 6 个月,每个月还有流程跟踪,交流访谈,终于我转正了,转正答辩我不出意料拿到了 Excellent 评价,涨了点薪水,呵呵还不错。华为的事情我不太想说太多,总之我觉得自己没有资格评判这个公司,从公司看公司的角度华为真正是个伟大的公司,任老爷子也是一个值得敬佩的企业家。
+
+在华为干了半年后,我发现我终究还是入职的时候太草率了,我当时没有具体的了解这个岗位,这个部门。入职之后我发现,**我所在的是硬件部门,我在一个硬件部门的软件组,我真是脑子秀逗了**。
+
+**在一个部门,你需要尽力进入到部门主航道里,尽力不要在边缘的航道工作,特别是那些节奏快,考核严格的部门。**
+
+更严峻的是我所在的大组,居然是一个分布在全国 4 地的组,大组长(华为叫 LM)在上海,4 地各有一个本地业务负责人。我立刻意识到,到年终考评时,所有的成果一定会是 4 地分配,并且 4 地的负责人会占去一大部分,这是组织结构形成的优势。我所在的小组到时候会难以突破,资源分配会非常激烈。
+
+### 备孕成功
+
+先不说这些,在 18 年时妻子做完了手术,手术居然很成功。休息完之后我们 19 年初开始备孕了,这次真的是上天保佑,运气不错,很快就怀上了。这段时间,我虽然每天做地铁 1.5 小时到公司上班,经受高强度的工作,我心里每天还是乐滋滋的。但是,突然有一天,PL(华为小组长)根我说,LM 需要派人去杭研所支持工作,我是最合适人选,让我有个心里准备。当时我是不想去的,这个时候妻子是最需要关怀的时候,我想 LM 表达了我的意愿,并且我也知道如果去了杭州年底绩效考评肯定不高。过程不多说了,反正结果是我去了杭州。
+
+于是我开始了两头奔波的日子,每个月回上海一趟。这过程中还有个插曲,家里老家城中村改造,分了一点钱,父母执意卖掉了老家学校周边的房子,丈母娘也处理老家的一些房子,然后把钱都给了我们,然后我用这笔家里最后的资产,同时利用华为的现金流在绍、甬不限购地区购买一些房子,我没有炒房的想法,只是防止被通货膨胀侵蚀而已,不过后来结果证明我貌似又蒙对了啊,我自己的看法是:
+
+**杭绍甬在经济层面会连成紧密的一片,在行政区上杭州兼并绍 部分区域的概率其实不大,行政区的扩展应该是先兼并自身的下级代管城市。**
+
+### 宝宝出生
+
+不说房子了,继续工作吧。10 月份干了快一年时候,我华为的师傅(华为有师徒培养体系)偷偷告诉我被定为备选 PL 了,虽然不知道真假,但是我心里还是有点小高兴。不过我心里也慢慢意识到这个公司可能不是我真正想要的公司,这么多年了,愚钝如我慢慢也开始知道自己想干什么了。因为我的宝宝出生了,看着这只四脚吞金兽,我意识到自己已经是一个父亲了。
+
+2019 年随着美国不断升级的制裁消息,我在华为的日子也走到年底,马上将迎来神奇的 2020 年。
+
+## 2020
+
+> 2020 就少写一些了,有些东西真的可能忘却更好。
+
+### 在家办公
+
+年初就给大家来了一个重击,新冠疫情改变了太多的东西。这个时候真的是看出华为的执行力,居家办公也效率不减多少,并且迅速实现了复工。到了 3-4 月份,华为开始正式评议去年绩效等级,我心里开始有预感,以前的分析大概率会兑现,并且绩效和收入挂钩,华为是个风险意识极强的公司,去年的制裁会导致公司开始风险预备,虽然我日常工作还是受到多数人好评,但是我知道这其实在评议人员那里,没有任何意义。果然绩效评议结果出来了,呵呵,我很不满意。绩效沟通时 LM 破例跟我沟通了很长时间,我直接表达了我的想法。LM 承诺钱不会少,呵呵,我不评价吧。后来一天开始组织调整,成立一个新的小组,LM 给我电话让我当组长,我拒绝了,这件事情我不知道对错,我当时是这样考虑的
+
+1. **升任新的职位,未必是好事,更高的职位意味着更高的要求,因此对备选人员要么在原岗位已经能力有余,要么时间精力有余;我认为当时我这两个都不满足,呵呵离家有点远,LM 很可以只是因为绩效事情做些补偿。**
+2. **华为不会垮,这点大家有信心,但未来一定会出现战略收缩,最后这艘大船上还剩下哪些人不清楚,底层士兵有可能是牺牲品。**
+3. **我 34 岁了**
+
+### 提出离职
+
+另外我一直思考未来想做什么,已经有了一丝眉目,就这样,我拿了年终奖约 7 月就提出了离职,后来部门还让我做了最后一次贡献,把我硬留到 10 月份,这样就可以参加上半年考核了,让帮忙背了一个 C 呵呵,这是工作多年,最差绩效吧。
+
+这里还有一个小插曲,最后这三个月我负责什么工作呢,因为 20 年 3 月开始我就接手了部分部门招聘工作(在华为干过的都知道为什么非 HR 也要帮忙招聘,呵呵大坑啊,就不多解释了),结果最后三个月我这个待离职员工居然继续负责招聘,真的是很搞笑,不过由于我在上一份工作中其实一直也有招聘的工作,所以也算做的轻车熟路,每天看 50 份左右简历(我看得都非常仔细,我害怕自己的疏忽会导致一个优秀的人才错失机会,所以比较慢)其实也蛮有收货,最后好歹对程序员如何写简历有了一些心得。
+
+## 总结
+
+好了 7 年多,近 8 年的职场讲完了,不管过去如何,未来还是要继续努力,希望看到这篇文章觉得有帮助的朋友,可以帮忙点个推荐,这样可能更多的人看到,也许可以避免更多的人犯我犯的错误。另外欢迎私信或者其他方式交流(某 Xin 号,jingyewandeng),可以讨论职场经验,方向,我也可以帮忙改简历(免费啊),不用怕打扰,能帮助别人是一项很有成绩感的事,并且过程中也会有收获,程序员也不要太腼腆呵呵
diff --git a/docs/high-quality-technical-articles/personal-experience/four-year-work-in-tencent-summary.md b/docs/high-quality-technical-articles/personal-experience/four-year-work-in-tencent-summary.md
new file mode 100644
index 0000000000000000000000000000000000000000..c0d82be84178e8abdd313b5b4d50adbbf125826f
--- /dev/null
+++ b/docs/high-quality-technical-articles/personal-experience/four-year-work-in-tencent-summary.md
@@ -0,0 +1,107 @@
+---
+title: 从校招入职腾讯的四年工作总结
+category: 技术文章精选集
+author: pioneeryi
+tag:
+ - 个人经历
+---
+
+程序员是一个流动性很大的职业,经常会有新面孔的到来,也经常会有老面孔的离开,有主动离开的,也有被动离职的。
+
+再加上这几年卷得厉害,做的事更多了,拿到的却更少了,互联网好像也没有那么香了。
+
+人来人往,变动无常的状态,其实也早已习惯。
+
+打工人的唯一出路,无外乎精进自己的专业技能,提升自己的核心竞争力,这样无论有什么变动,走到哪里,都能有口饭吃。
+
+今天分享一位博主,校招入职腾讯,工作四年后,离开的故事。
+
+至于为什么离开,我也不清楚,可能是有其他更好的选择,或者是觉得当前的工作对自己的提升有限。
+
+**下文中的“我”,指这位作者本人。**
+
+> 原文地址:https://zhuanlan.zhihu.com/p/602517682
+
+研究生毕业后, 一直在腾讯工作,不知不觉就过了四年。个人本身没有刻意总结的习惯,以前只顾着往前奔跑了,忘了停下来思考总结。记得看过一个职业规划文档,说的三年一个阶段,五年一个阶段的说法,现在恰巧是四年,同时又从腾讯离开,该做一个总结了。
+
+先对自己这四年做一个简单的评价吧:个人认为,没有完全的浪费和辜负这四年的光阴。为何要这么说了?因为我发现和别人对比,好像意义不大,比我混的好的人很多;比我混的差的人也不少。说到底,我只是一个普普通通的人,才不惊人,技不压众,接受自己的平凡,然后看自己做的,是否让自己满意就好。
+
+下面具体谈几点吧,我主要想聊下工作,绩效,EPC,嫡系看法,最后再谈下收获。
+
+## 工作情况
+
+我在腾讯内部没有转过岗,但是做过的项目也还是比较丰富的,包括:BUGLY、分布式调用链(Huskie)、众包系统(SOHO),EPC 度量系统。其中一些是对外的,一些是内部系统,可能有些大家不知道。还是比较感谢这些项目经历,既有纯业务的系统,也有偏框架的系统,让我学到了不少知识。
+
+接下来,简单介绍一下每个项目吧,毕竟每一个项目都付出了很多心血的:
+
+BUGLY,这是一个终端 Crash 联网上报的系统,很多 APP 都接入了。Huskie,这是一个基于 zipkin 搭建的分布式调用链跟踪项目。SOHO,这是一个众包系统,主要是将数据标准和语音采集任务众包出去,让人家做。EPC 度量系统,这是研发效能度量系统,主要是度量研发效能情况的。这里我谈一下对于业务开发的理解和认识,很多人可能都跟我最开始一样,有一个疑惑,整天做业务开发如何成长?换句话说,就是说整天做 CRUD,如何成长?我开始也有这样的疑惑,后来我转变了观念。
+
+我觉得对于系统的复杂度,可以粗略的分为技术复杂度和业务复杂度,对于业务系统,就是业务复杂度高一些,对于框架系统就是技术复杂度偏高一些。解决这两种复杂度,都具有很大的挑战。
+
+此前做过的众包系统,就是各种业务逻辑,搞过去,搞过来,其实这就是业务复杂度高。为了解决这个问题,我们开始探索和实践领域驱动(DDD),确实带来了一些帮助,不至于系统那么混乱了。同时,我觉得这个过程中,自己对于 DDD 的感悟,对于我后来的项目系统划分和设计以及开发都带来了帮助。
+
+当然 DDD 不是银弹,我也不是吹嘘它有多好,只是了解了它后,有时候设计和开发时,能换一种思路。
+
+可以发现,其实平时咱们做业务,想做好,其实也没那么容易,如果可以多探索多实践,将一些好的方法或思想或架构引入进来,与个人和业务都会有有帮助。
+
+## 绩效情况
+
+我在腾讯工作四年,腾讯半年考核一次,一共考核八次,回想了下,四年来的绩效情况为:三星,三星,五星,三星,五星,四星,四星,三星。统计一下, 四五星占比刚好一半。
+
+
+
+PS:还好以前有奖杯,不然一点念想都没了。(现在腾讯似乎不发了)
+
+印象比较深的是两次五星获得经历。第一次五星是工作的第二年,那一年是在做众包项目,因为项目本身难度不大,因此我把一些精力投入到了团队的基础建设中,帮团队搭建了 java 以及 golang 的项目脚手架,又做了几次中心技术分享,最终 Leader 觉得我表现比较突出,因此给了我五星。看来,主动一些,与个人与团队都是有好处的,最终也能获得一些回报。
+
+第二次五星,就是与 EPC 有关了。说一个搞笑的事,我也是后来才知道的,项目初期,总监去汇报时,给老板演示系统,加载了很久指标才刷出来,总监很不好意思的说正在优化;过了一段时间,又去汇报演示,结果又很尴尬的刷了很久才出来,总监无赖表示还是在优化。没想到,自己曾经让总监这么丢脸,哈哈。好吧,说一下结果,最终,我自己写了一个查询引擎替换了 Mondrian,之后再也没有出现那种尴尬的情况了。随之而来,也给了好绩效鼓励。做 EPC 度量项目,我觉得自己成长很大,比如抗压能力,当你从零到一搭建一个系统时,会有一个先扛住再优化的过程,此外如果你的项目很重要,尤其是数据相关,那么任何一点问题,都可能让你神经紧绷,得想尽办法降低风险和故障。此外,另一个不同的感受就是,以前得项目,我大多是开发者,而这个系统,我是 Owner 负责人,当你 Owner 一个系统时,你得时刻负责,同时还需要思考系统的规划和方向,此外还需要分配好需求和把控进度,角色体验跟以前完全不一样。
+
+## 谈谈 EPC
+
+很多人都骂 EPC,或者笑 EPC,作为度量平台核心开发者之一,我来谈谈客观的看法。
+
+其实 EPC 初衷是好的,希望通过全方位多维度的研效指标,来度量研发效能各环节的质量,进而反推业务,提升研发效能。然而,最终在实践的过程中,才发现,客观条件并不支持(工具还没建设好);此外,一味的追求指标数据,使得下面的人想方设法让指标好看,最终违背了初衷。
+
+为什么,说 EPC 好了,其实如果你仔细了解下 EPC,你就会发现,他是一套相当完善且比较先进的指标度量体系。覆盖了需求,代码,缺陷,测试,持续集成,运营部署各个环节。
+
+此外,这个过程中,虽然一些人和一些业务做弊,但绝大多数业务还是做出了改变的,比如微视那边的人反馈是,以前的代码写的跟屎一样,当有了 EPC 后,代码质量好了很多。虽然最后微视还是亡了,但是大厦将倾,EPC 是救不了的,亡了也更不能怪 EPC。
+
+## 谈谈嫡系
+
+大家都说腾讯,嫡系文化盛行。但其实我觉得在那个公司都一样吧。这也符合事物的基本规律,人们只相信自己信任并熟悉的人。作为领导,你难道会把把重要的事情交给自己不熟悉的人吗?
+
+其实我也不知道我算不算嫡系,脉脉上有人问过”怎么知道自己算不算嫡系”,下面有一个回答,我觉得很妙:如果你不知道你是不是嫡系,那你就不是。哈哈,这么说来,我可能不是。
+
+但另一方面,后来我负责了团队内很重要的事情,应该是中心内都算很重要的事,我独自负责一个方向,直接向总监汇报,似乎又有点像。
+
+网上也有其他说法,一针见血,是不是嫡系,就看钱到不到位,这么说也有道理。我在 7 级时,就发了股票,自我感觉,还是不错的。我当时以为不出意外的话,我以后的钱途和发展是不是就会一帆风顺。不出意外就出了意外,第二年,EPC 不达预期,部门总经理和总监都被换了,中心来了一个新的的总监。
+
+好吧,又要重新建立信任了。再到后来,是不是嫡系已经不重要了,因为大环境不好,又加上裁员,大家主动的被动的差不多都走了。
+
+总结一下,嫡系的存在,其实情有可原。怎么样成为嫡系了?其实我也不知道。不过,我觉得,与其思考怎么成为嫡系,不如思考怎么展现自己的价值和能力,当别人发现你的价值和能力了,那自然更多的机会就会给予你,有了机会,只要把握住了,那就有更多的福利了。
+
+## 再谈收获
+
+收获,什么叫做收获了?个人觉得无论是外在的物质,技能,职级;还是内在的感悟,认识,都算收获。
+
+先说一些可量化的吧,我觉得有:
+
+- 级别上,升上了九级,高级工程师。虽然大家都在说腾讯职级缩水,但是有没有高工的能力自己其实是知道的,我个人感觉,通过我这几年的努力,我算是达到了我当时认为的我需要在高工时达到的状态;
+- 绩效上,自我评价,个人不是一个特别卷的人,或者说不会为了卷而卷。但是,如果我认定我应该把它做好得,我的 Owner 意识,以及负责态度,我觉得还是可以的。最终在腾讯四年的绩效也还算过的去。再谈一些其他软技能方面:
+
+**1、文档能力**
+
+作为程序员,文档能力其实是一项很重要的能力。其实我也没觉得自己文档能力有多好,但是前后两任总监,都说我的文档不错,那看来,我可能在平均水准之上。
+
+**2、明确方向**
+
+最后,说一个更虚的,但是我觉得最有价值的收获: 我逐渐明确了,或者确定了以后的方向和路,那就是走数据开发。
+
+其实,找到并确定一个目标很难,身边有清晰目标和方向的人很少,大多数是迷茫的。
+
+前一段时间,跟人聊天,谈到职业规划,说是可以从两个角度思考:
+
+- 选一个业务方向,比如电商,广告,不断地积累业务领域知识和业务相关技能,随着经验的不断积累,最终你就是这个领域的专家。
+- 深入一个技术方向,不断钻研底层技术知识,这样就有希望成为此技术专家。坦白来说,虽然我深入研究并实践过领域驱动设计,也用来建模和解决了一些复杂业务问题,但是发自内心的,我其实更喜欢钻研技术,同时,我又对大数据很感兴趣。因此,我决定了,以后的方向,就做数据相关的工作。
+
+腾讯的四年,是我的第一份工作经历,认识了很多厉害的人,学到了很多。最后自己主动离开,也算走的体面(即使损失了大礼包),还是感谢腾讯。
diff --git a/docs/high-quality-technical-articles/personal-experience/huawei-od-275-days.md b/docs/high-quality-technical-articles/personal-experience/huawei-od-275-days.md
new file mode 100644
index 0000000000000000000000000000000000000000..56b1c1299a18ba21f0d9aefefa3efacb01309271
--- /dev/null
+++ b/docs/high-quality-technical-articles/personal-experience/huawei-od-275-days.md
@@ -0,0 +1,337 @@
+---
+title: 华为 OD 275 天后,我进了腾讯!
+category: 技术文章精选集
+tag:
+ - 个人经历
+---
+
+> **推荐语**:一位朋友的华为 OD 工作经历以及腾讯面试经历分享,内容很不错。
+>
+>
+>
+> **原文地址**:https://www.cnblogs.com/shoufeng/p/14322931.html
+
+## 时间线
+
+- 18 年 7 月,毕业于某不知名 985 计科专业;
+- 毕业前,在某马的 JavaEE(后台开发)培训了 6 个月;
+- 第一份工作(18-07 ~ 19-12)接触了大数据,感觉大数据更有前景;
+- 19 年 12 月,入职中国平安产险(去到才发现是做后台开发 😢);
+- 20 年 3 月,从平安辞职,跳去华为 OD 做大数据基础平台;
+- 2021 年 1 月,入职鹅厂
+
+## 华为 OD 工作经历总结
+
+### 为什么会去华为 OD
+
+在平安产险(正式员工)只待了 3 个月,就跳去华为 OD,朋友们都是很不理解的 —— 好好的正编不做,去什么外包啊 😂
+
+但那个时候,我铁了心要去做大数据,不想和没完没了的 CRUD 打交道。刚好面试通过的岗位是华为 Cloud BU 的大数据部门,做的是国内政企中使用率绝对领先的大数据平台……
+平台和工作内容都不错,这么好的机会,说啥也要去啊 💪
+
+> 其实有想过在平安内部转岗到大数据的,但是不满足“入职一年以上”这个要求;
+> 「等待就是浪费生命」,在转正流程还没批下来的时候,赶紧溜了 😂
+
+### 华为 OD 的工作内容
+
+**带着无限的期待,火急火燎地去华为报到了。**
+
+和招聘的 HR 说的一样,和华为自有员工一起办公,工作内容和他们完全一样:
+
+> 主管根据你的能力水平分配工作,逐渐增加难度,能者多劳;
+> 试用期 6 个月,有导师带你,一般都是高你 2 个 Level 的华为自有员工,基本都是部门大牛。
+
+所以,**不存在外包做的都是基础的、流程性的、没有技术含量的工作** —— 顾虑这个的完全不用担心,你只需要打听清楚要去的部门/小组具体做什么,能接受就再考虑其他的。
+
+感触很深的一点是:华为是有着近 20 万员工的巨头,内部有很多流程和制度。好处是:能接触到大公司的产品从开发、测试,到发布、运维等一系列的流程,比如提交代码的时候,会由经验资深、经过内部认证的大牛给你 Review,在拉会检视的时候,可以学习他们考虑问题的角度,还有对整个产品全局的把控。
+
+但同时,个人觉得这也有不好的地方:流程繁琐会导致工作效率变低,比如改动几行代码,就需要跑完整个 CI(有些耗时比较久),还要提供自验和 VT 的报告。
+
+### OD 与华为自有员工的对比
+
+什么是 OD?Outstanding Dispatcher,人员派遣,官方强调说,OD 和常说的“外包”是不一样的。
+
+说说我了解的 OD:
+
+- 参考华为的薪酬框架,OD 人员的薪酬体系有一定的市场竞争力 —— 的确是这样,貌似会稍微倒挂同级别的自有员工;
+- 可以参与华为主力产品的研发 —— 是的,这也是和某软等“供应商”的兄弟们不一样的地方;
+- 外网权限也可以申请打开(对,就是梯子),部门内部的大多数文档都是可以看的;
+- 工号是单独的 300 号段,其他供应商员工的工号是 8 开头,或着 WX 开头;
+- 工卡带是红色的,和自有员工一样,但是工卡内容不同,OD 的明确标注:办公区通行证,并有德科公司的备注:
+
+
+
+还听到一些内部的说法:
+
+- 没股票,没 TUP,年终奖少,只有工资可能比我司高一点点而已;
+- 不能借针对 HW 的消费贷,也不能买公司提供的优惠保险…
+
+### 那,到底要不要去华为 OD?
+
+我想,搜到我这篇文字的你,心里其实是有偏向的,只是缺最后一片雪花 ❄️,让自己下决心。
+
+作为过来人之一,我再提供一些参考吧 😃
+
+1)除了华为 OD,**还有没有更好的选择?** 综合考虑加班(996、有些是 9106 甚至更多)、薪资、工作内容,以及这份工作经历对你整个职业的加成等等因素;
+
+2)有看到一些内部的说法,比如:“奇怪 OD 这么棒,为啥大家不自愿转去 OD 啊?”;再比如:“OD 等同华为?这话都说的出口,既然都等同,为啥还要 OD?就是降成本嘛……”
+
+3)内心够强大吗?虽然没有人会说你是 OD,但总有一些事情会提醒你:**你不是华为员工**。比如:
+
+a) 内部发文啥的,还有心声平台的大部分内容,都是无权限看的:
+
+
+
+b) 你的考勤是在租赁人员管理系统里考核,绩效管理也是;
+
+c) 自有员工的工卡具有消费功能(包括刷夜宵),OD 的工卡不能消费,需要办个消费卡,而且夜宵只能通过手机软件领取(自有员工是用工卡领的);
+
+d) 你的加班一定要提加班申请电子流换 Double 薪资,不然只能换调休,离职时没时间调休也换不来 Double —— 而华为员工即使自己主动离职,也是有 N+1,以及加班时间换成 Double 薪资的;
+
+### 网传的 OD 转华为正编,真的假的?
+
+这个放到单独的一节,是因为它很重要,有很多纠结的同学在关注这个问题。
+
+**答案是:真的。**
+
+据各类非官方渠道(比如知乎上的一些分享),转华为自有是有条件的(https://www.zhihu.com/question/356592219/answer/1562692667):
+
+1)入职时间:一年以上
+2)绩效要求:连续两次绩效 A
+3)认证要求:通过可信专业级认证
+4)其他条件:根据业务部门的人员需求及指标要求确定
+
+说说这些条件吧 😃
+
+**条件 2 连续两次绩效 A**
+
+上面链接里的说法:
+
+> 绩效 A 大约占整个部门的前 10%,连续两次 A 的意思就是一年里两次考评都排在部门前 10%,能做到这样的在华为属于火车头,这种难得的绩效会舍得分给一个租赁人员吗?
+
+OD 同学能拿到 A 吗?不知道,我入职晚,都没有经历一个完整的绩效考评。
+
+(20210605 更新下)一年多了,还留着的 OD 同学告知我:OD 是单独评绩效的,能拿到 A 的比例,大概是 1/5,对应的年终奖就是 4 个月;绩效是 B,年终奖就是 2 个月。
+
+在我看来,在试用期答辩时,能拿 A,接下来半年的绩效大概率也是拿 A 的。
+
+但总的来说,这种事既看实力,又看劳动态度(能不能拼命三郎疯狂加班),还要看运气(主管对你是不是认可)……
+
+**条件 3 通过可信专业级认证**
+
+可信专业级认证考试是啥?华为在推动技术人员的可信认证,算是一项安全合规的工作。
+专业级有哪些考试呢?共有四门:
+
+- 科目一:上级编程,对比力扣 2 道中等、1 道困难;
+- 科目二:编程知识与应用,考察基础的编程语言知识等;
+- 科目三:安全编程、质量、隐私,还有开发者测试等;
+- 科目四:重构知识,包括设计模式、代码重构等。
+
+上面这些,每一门单季度只能考一次(好像有些一年只能考 3 次),每个都要准备,少则 3 天,多则 1 星期,不准备,基本都过不了。
+我在 4 个月左右、还没转正的时候,就考过了专业级的科目二、三、四,只剩科目一大半年都没过(算法确实太菜了 😂
+但也有同事没准备,连着好几次都没通过。
+
+**条件 4 部门人员需求指标?**
+
+这个听起来都感觉很玄学。还是那句话,实力和运气到了,应该可以的!成功转正员工图镇楼:
+
+
+
+### 真的感谢 OD,也感谢华为
+
+运气很好,在我换工作还不到 3 个月的时候,华为还收我。
+
+我遇到了很好的主管,起码在工作时间,感觉跟兄长一样指导、帮助我;
+
+分配给我的导师,是我工作以来认识到技术实力最厉害的人,定位问题思路清晰,编码实力强悍,全局思考问题、制定方案……
+
+小组、部门的同学都很 nice,9 个多月里,我基本每天都跟打了鸡血一样,现在想想,也不知道当时为什么会那么积极有干劲 😂
+
+从个人能力上来讲,我是进不去华为的(心里还是有点数的 😂)。正是有了 OD 这个渠道,才有机会切身感受华为的工作氛围,也学到了很多软技能:
+
+- 积极主动,勇于承担尝试,好工作要抢过来自己做;
+- 及时同步工作进展,包括已完成、待完成,存在的风险困难等内容,要让领导知道你的工作情况;
+- 勤于总结提炼输出,形成个人 DNA,利人利己;
+- 有不懂的可以随时找人问,脸皮要厚,虚心求教;
+- 不管多忙,所有的会议,不论大小,都要有会议纪要,邮件发给相关人……
+
+再次感谢,大家都加油,向很牛掰很牛掰前进 💪
+
+## 投简历,找面试官求虐
+
+20 年 11 月初的一天,在同事们讨论“某某被其他公司高薪挖去了,钱景无限”的消息。
+
+我忽然惊觉,自己来到华为半年多,除了熟悉内部的系统和流程,好像没有什么成长和进步?
+
+不禁反思:只有厉害的人才会被挖,现在这个状态的我,在市场上值几个钱?
+
+刚好想起了之前的一个同事在离职聚会上分享的经验:
+
+> 技术人不能闭门造车,要多交流,多看看外面的动态。
+>
+> 如果感觉自己太安逸了,那就把简历挂出去,去了解其他公司用的是什么技术,他们更关注哪些痛点?面几次你就有方向了。
+
+这时候起了个念头:找面试官求虐,以此来鞭策自己,进而更好地制定学习方向。
+
+于是我重新下载了某聘软件,在首页推荐里投了几家公司。
+
+## 开始面试
+
+11 月 10 号投的简历,当天就有 2 家预约了 11 号下午的线上面试,其中就有鹅厂 🐧
+
+好巧不巧,10 号晚上要双十一业务保障,一直到第二天凌晨 2 点半才下班。
+
+熬夜太伤身,还好能申请调休一天,也省去了找借口请假 🙊
+
+这段时间集中面了 3 家:
+
+> 第 1 个是广州的公司,11 号当晚就完成了 2 轮线上面试,开得有点低,就婉拒了;
+> 第 2 个就是本文的重点——鹅厂;
+> 第 3 个是做跨境电商的公司,一面就跪(恭喜它荣升为“在我有限的工作经历中,面试体验最差的 2 家公司之一”🙂️)
+
+## 鹅厂,去还是不去?
+
+一直有一个大厂梦,奈何菜鸟一枚,之前试过好几次,都跪在技术面了。
+
+所以想了个曲线救国的方法:先在其他单位积累着,有机会了再争取大厂的机会 💪
+
+很幸运,也很猝不及防,这次竟然通过了鹅厂的所有面试。
+
+虽然已到年底,但是要是错过这么难得的机会,下次就不知道什么时候才能再通关了。
+
+所以,**年后拿到年终再跳槽 vs 已到手的鹅厂 Offer,我选择了后者 😄**
+
+## 我的鹅厂面试
+
+如本文标题所说,16 天通关五轮面试,第 17 天,我终于收到了期盼已久的鹅厂 Offer。
+
+做技术的同学,可能会对鹅厂的面试很好奇,他们都会问哪些问题呢?
+
+我应聘的是大数据开发(Java)岗位,接下来对我的面试做个梳理,也给想来鹅厂的同学们一个参考 😊
+
+> 几乎所有问题都能在网络上找到很详细的答案。
+> 篇幅有限,这里只写题目和一些引申的问题。
+
+### 技术一面
+
+#### Java 语言相关
+
+1、对 Java 的类加载器有没有了解?如何自定义类加载器?
+
+> 引申:一个类能被加载多次吗?`java/javax` 包下的类会被加载多次吗?
+
+2、Java 中要怎么创建一个对象 🐘?
+
+3、对多线程有了解吗?在什么场景下需要使用多线程?
+
+> 引申:对 **线程安全** 的认识;对线程池的了解,以及各个线程池的适用场景。
+
+4、对垃圾回收的了解?
+
+5、对 JVM 分代的了解?
+
+6、NIO 的了解?用过 RandomAccessFile 吗?
+
+> 引申:对 **同步、异步,阻塞、非阻塞** 的理解?
+>
+> 多路复用 IO 的优势?
+
+7、ArrayList 和 LinkedList 的区别?各自的适用场景?
+
+8、实现一个 Hash 集合,需要考虑哪些因素?
+
+> 引申:JDK 对 HashMap 的设计关键点,比如初识容量,扩所容,链表转红黑树,以及 JDK 7 和 JDK 8 的区别等等。
+
+#### 通用学科相关
+
+1、TCP 的三次握手;
+
+2、Linux 的常用命令,比如:
+
+> ```shell
+> ps aux / ps -ef、top C
+> df -h、du -sh *、free -g
+> vmstat、mpstat、iostat、netstat
+> ```
+
+#### 项目框架相关
+
+1、Kafka 和其他 MQ 的区别?它的吞吐量为什么高?
+
+> 消费者主动 pull 数据,目的是:控制消费节奏,还可以重复消费;
+>
+> 吞吐量高:各 partition 顺序写 IO,批量刷新到磁盘(OS 的 pageCache 负责刷盘,Kafka 不用管),比随机 IO 快;读取数据基于 sendfile 的 Zero Copy;批量数据压缩……
+
+2、Hive 和 SparkSQL 的区别?
+
+3、Ranger 的权限模型、权限对象,鉴权过程,策略如何刷新……
+
+#### 问题定位方法
+
+1、ssh 连接失败,如何定位?
+
+> 是否能 ping 通(DNS 是否正确)、对端端口是否开了防火墙、对端服务是否正常……
+
+2、运行 Java 程序的服务器,CPU 使用率达到 100%,如何定位?
+
+> `ps aux | grep xxx` 或 `jps` 命令找到 Java 的进程号 `pid`,
+>
+> 然后用 `top -Hp pid` 命令查看其阻塞的线程序号,**将其转换为 16 进制**;
+>
+> 再通过 `jstack pid` 命令跟踪此 Java 进程的堆栈,搜索上述转换来的 16 进制线程号,即可找到对应的线程名及其堆栈信息……
+
+3、Java 程序发生了内存溢出,如何定位?
+
+> `jmap` 工具查看堆栈信息,看 Eden、Old 区的变化……
+
+### 技术二面
+
+二面主要是过往项目相关的问题:
+
+1、Solr 和 Elasticsearch 的区别 / 优劣?
+
+2、对 Elasticsearch 的优化,它的索引过程,选主过程等问题……
+
+3、项目中遇到的难题,如何解决的?
+
+blabla 有少量的基础问题和一面有重复,还有几个和大数据相关的问题,记不太清了 😅
+
+### 技术三面
+
+这一面是总监面,更多是个人关于职业发展的一些想法,以及在之前公司的成长和收获、对下一份工作的期望等问题。
+
+但也问了几个技术问题。印象比较深的是这个:
+
+> 1 个 1TB 的大文件,每行都只是 1 个数字,无重复,8GB 内存,要怎么对这个文件进行排序?
+
+首先想到的是 MapReduce 的思路,拆分小文件,分批排序,最后合并。
+
+**此时连环追问来了:**
+
+> Q:如何尽可能多的利用内存呢?
+>
+> A:用位图法的思路,对数字按顺序映射。(对映射方法要有基本的了解)
+>
+> Q:如果在排好序之后,还需要快速查找呢?
+>
+> A:可以做索引,类似 Redis 的跳表,通过多级索引提高查找速度。
+>
+> Q:索引查找的还是文件。要如何才能更多地利用内存呢?
+>
+> A:那就要添加缓存了,把读取过的数字缓存到内存中。
+>
+> Q:缓存应该满足什么特点呢?
+>
+> A:应该使用 LRU 型的缓存。
+
+呼。。。总算是追问完了这道题 😂
+
+---
+
+还有 GM 面和 HR 面,问题都和个人经历相关,这里就略去不表。
+
+## 文末的絮叨
+
+**入职鹅厂已经 1 月有余。不同的岗位,不同的工作内容,也是不同的挑战。**
+
+感受比较深的是,作为程序员,还是要自我驱动,努力提升个人技术能力,横向纵向都要扩充,这样才能走得长远。
diff --git a/docs/high-quality-technical-articles/personal-experience/two-years-of-back-end-develop--experience-in-didi-and-toutiao.md b/docs/high-quality-technical-articles/personal-experience/two-years-of-back-end-develop--experience-in-didi-and-toutiao.md
new file mode 100644
index 0000000000000000000000000000000000000000..b3609658d43db35ea9c69b1dd65422efd931a6d1
--- /dev/null
+++ b/docs/high-quality-technical-articles/personal-experience/two-years-of-back-end-develop--experience-in-didi-and-toutiao.md
@@ -0,0 +1,150 @@
+---
+title: 滴滴和头条两年后端工作经验分享
+category: 技术文章精选集
+tag:
+ - 个人经历
+---
+
+> **推荐语**:很实用的工作经验分享,看完之后十分受用!
+>
+> **内容概览**:
+>
+> - 要学会深入思考,总结沉淀,这是我觉得最重要也是最有意义的一件事。
+> - 积极学习,保持技术热情。如果我们积极学习,保持技术能力、知识储备与工作年限成正比,这到了 35 岁哪还有什么焦虑呢,这样的大牛我觉得应该也是各大公司抢着要吧?
+> - 在能为公司办成事,创造价值这一点上,我觉得最重要的两个字就是主动,主动承担任务,主动沟通交流,主动推动项目进展,主动协调资源,主动向上反馈,主动创造影响力等等。
+> - 脸皮要厚一点,多找人聊,快速融入,最忌讳有问题也不说,自己把自己孤立起来。
+> - 想舔就舔,不想舔也没必要酸别人,Respect Greatness。
+> - 时刻准备着,技术在手就没什么可怕的,哪天干得不爽了直接跳槽。
+> - 平时积极总结沉淀,多跟别人交流,形成方法论。
+> - ......
+>
+> **原文地址**:https://www.nowcoder.com/discuss/351805
+
+先简单交代一下背景吧,某不知名 985 的本硕,17 年毕业加入滴滴,当时找工作时候也是在牛客这里跟大家一起奋战的。今年下半年跳槽到了头条,一直从事后端研发相关的工作。之前没有实习经历,算是两年半的工作经验吧。这两年半之间完成了一次晋升,换了一家公司,有过开心满足的时光,也有过迷茫挣扎的日子,不过还算顺利地从一只职场小菜鸟转变为了一名资深划水员。在这个过程中,总结出了一些还算实用的划水经验,有些是自己领悟到的,有些是跟别人交流学到的,在这里跟大家分享一下。
+
+## 学会深入思考,总结沉淀
+
+**我想说的第一条就是要学会深入思考,总结沉淀,这是我觉得最重要也是最有意义的一件事。**
+
+**先来说深入思考。** 在程序员这个圈子里,常能听到一些言论:_“我这个工作一点技术含量都没有,每天就 CRUD,再写写 if-else,这 TM 能让我学到什么东西?”_
+
+抛开一部分调侃和戏谑的论调不谈,这可能确实是一部分同学的真实想法,至少曾经的我,就这么认为过。后来随着工作经验的积累,加上和一些高 level 的同学交流探讨之后,我发现这个想法其实是非常错误的。之所以出现没什么可学的这样的看法,基本上是思维懒惰的结果。**任何一件看起来很不起眼的小事,只要进行深入思考,稍微纵向挖深或者横向拓宽一下,都是足以让人沉溺的知识海洋。**
+
+举一个例子。某次有个同学跟我说,这周有个服务 OOM 了,查了一周发现有个地方 defer 写的有问题,改了几行代码上线修复了,周报都没法写。可能大家也遇到过这样的场景,还算是有一定的代表性。其实就查 bug 这件事来说,是一个发现问题,排查问题,解决问题的过程,包含了触发、定位、复现、根因、修复、复盘等诸多步骤,花了一周来做这件事,一定有不断尝试与纠错的过程,这里面其实就有很多思考的空间。比如说定位,如何缩小范围的?走了哪些弯路?用了哪些分析工具?比如说根因,可以研究的点起码有 linux 的 OOM,k8s 的 OOM,go 的内存管理,defer 机制,函数闭包的原理等等。如果这些真的都不涉及,仍然花了一周时间做这件事,那复盘应该会有很多思考,提出来几十个 WHY 没问题吧...
+
+**再来说下总结沉淀。** 这个我觉得也是大多数程序员比较欠缺的地方,只顾埋头干活,可以把一件事做的很好。但是几乎从来不做抽象总结,以至于工作好几年了,所掌握的知识还是零星的几点,不成体系,不仅容易遗忘,而且造成自己视野比较窄,看问题比较局限。适时地做一些总结沉淀是很重要的,这是一个从术到道的过程,会让自己看问题的角度更广,层次更高。遇到同类型的问题,可以按照总结好的方法论,系统化、层次化地推进和解决。
+
+还是举一个例子。做后台服务,今天优化了 1G 内存,明天优化了 50%的读写耗时,是不是可以做一下性能优化的总结?比如说在应用层,可以管理服务对接的应用方,梳理他们访问的合理性;在架构层,可以做缓存、预处理、读写分离、异步、并行等等;在代码层,可以做的事情更多了,资源池化、对象复用、无锁化设计、大 key 拆分、延迟处理、编码压缩、gc 调优还有各种语言相关的高性能实践...等下次再遇到需要性能优化的场景,一整套思路立马就能套用过来了,剩下的就是工具和实操的事儿了。
+
+还有的同学说了,我就每天跟 PM 撕撕逼,做做需求,也不做性能优化啊。先不讨论是否可以搞性能优化,单就做业务需求来讲,也有可以总结的地方。比如说,如何做系统建设?系统核心能力,系统边界,系统瓶颈,服务分层拆分,服务治理这些问题有思考过吗?每天跟 PM 讨论需求,那作为技术同学该如何培养产品思维,引导产品走向,如何做到架构先行于业务,这些问题也是可以思考和总结的吧。就想一下,连接手维护别人烂代码这种蛋疼的事情,都能让 Martin Fowler 整出来一套重构理论,还显得那么高大上,我们确实也没啥必要对自己的工作妄自菲薄...
+
+所以说:**学习和成长是一个自驱的过程,如果觉得没什么可学的,大概率并不是真的没什么可学的,而是因为自己太懒了,不仅是行动上太懒了,思维上也太懒了。可以多写技术文章,多分享,强迫自己去思考和总结,毕竟如果文章深度不够,大家也不好意思公开分享。**
+
+## 积极学习,保持技术热情
+
+最近两年在互联网圈里广泛传播的一种焦虑论叫做 35 岁程序员现象,大意是说程序员这个行业干到 35 岁就基本等着被裁员了。不可否认,互联网行业在这一点上确实不如公务员等体制内职业。但是,这个问题里 35 岁程序员并不是绝对生理意义上的 35 岁,应该是指那些工作十几年和工作两三年没什么太大区别的程序员。后面的工作基本是在吃老本,没有主动学习与充电,35 岁和 25 岁差不多,而且没有了 25 岁时对学习成长的渴望,反而添了家庭生活的诸多琐事,薪资要求往往也较高,在企业看来这确实是没什么竞争力。
+
+**如果我们积极学习,保持技术能力、知识储备与工作年限成正比,这到了 35 岁哪还有什么焦虑呢,这样的大牛我觉得应该也是各大公司抢着要吧?** 但是,**学习这件事,其实是一个反人类的过程,这就需要我们强迫自己跳出自己的安逸区,主动学习,保持技术热情。** 在滴滴时有一句话大概是,**主动跳出自己的舒适区,感到挣扎与压力的时候,往往是黎明前的黑暗,那才是成长最快的时候。相反如果感觉自己每天都过得很安逸,工作只是在混时长,那可能真的是温水煮青蛙了。**
+
+刚毕业的这段时间,往往空闲时间还比较多,正是努力学习技术的好时候。借助这段时间夯实基础,培养出良好的学习习惯,保持积极的学习态度,应该是受益终身的。至于如何高效率学习,网上有很多大牛写这样的帖子,到了公司后内网也能找到很多这样的分享,我就不多谈了。
+
+**_可以加入学习小组和技术社区,公司内和公司外的都可以,关注前沿技术。_**
+
+## 主动承担,及时交流反馈
+
+前两条还是从个人的角度出发来说的,希望大家可以提升个人能力,保持核心竞争力,但从公司角度来讲,公司招聘员工入职,最重要的是让员工创造出业务价值,为公司服务。虽然对于校招生一般都会有一定的培养体系,但实际上公司确实没有帮助我们成长的义务。
+
+**在能为公司办成事,创造价值这一点上,我觉得最重要的两个字就是主动,主动承担任务,主动沟通交流,主动推动项目进展,主动协调资源,主动向上反馈,主动创造影响力等等。**
+
+我当初刚入职的时候,基本就是 leader 给分配什么任务就把本职工作做好,然后就干自己的事了,几乎从来不主动去跟别人交流或者主动去思考些能帮助项目发展的点子。自以为把本职工作保质保量完成就行了,后来发现这么做其实是非常不够的,这只是最基本的要求。而有些同学的做法则是 leader 只需要同步一下最近要做什么方向,下面的一系列事情基本不需要 leader 操心了 ,这样的同学我是 leader 我也喜欢啊。入职后经常会听到的一个词叫 owner 意识,大概就是这个意思吧。
+
+在这个过程中,另外很重要的一点就是及时向上沟通反馈。项目进展不顺利,遇到什么问题,及时跟 leader 同步,技术方案拿捏不准可以跟 leader 探讨,一些资源协调不了可以找 leader 帮忙,不要有太多顾忌,认为这些会太麻烦,leader 其实就是干这个事的。。如果项目进展比较顺利,确实也不需要 leader 介入,那也需要及时把项目的进度,取得的收益及时反馈,自己有什么想法也提出来探讨,问问 leader 对当前进展的建议,还有哪些地方需要改进,消除信息误差。做这些事一方面是合理利用 leader 的各种资源,另一方面也可以让 leader 了解到自己的工作量,对项目整体有所把控,毕竟 leader 也有 leader,也是要汇报的。可能算是大家比较反感的向上管理吧,有内味了,这个其实我也做得不好。但是最基本的一点,不要接了一个任务闷着头干活甚至与世隔绝了,一个月了也没跟 leader 同步过,想着憋个大招之类的,那基本凉凉。
+
+**一定要主动,可以先从强迫自己在各种公开场合发言开始,有问题或想法及时 one-one。**
+
+除了以上几点,还有一些小点我觉得也是比较重要的,列在下面:
+
+## 第一件事建立信任
+
+无论是校招还是社招,刚入职的第一件事是非常重要的,直接决定了 leader 和同事对自己的第一印象。入职后要做的第一件事一定要做好,最起码的要顺利完成而且不能出线上事故。这件事的目的就是为了建立信任,让团队觉得自己起码是靠谱的。如果这件事做得比较好,后面一路都会比较顺利。如果这件事就搞杂了,可能有的 leader 还会给第二次机会,再搞不好,后面就很难了,这一条对于社招来说更为重要。
+
+而刚入职,公司技术栈不熟练,业务繁杂很难理清什么头绪,压力确实比较大。这时候一方面需要自己投入更多的精力,另一方面要多跟组内的同学交流,不懂就问。**最有效率的学习方式,我觉得不是什么看书啊学习视频啊,而是直接去找对应的人聊,让别人讲一遍自己基本就全懂了,这效率比看文档看代码快多了,不仅省去了过滤无用信息的过程,还了解到了业务的演变历史。当然,这需要一定的沟通技巧,毕竟同事们也都很忙。**
+
+**脸皮要厚一点,多找人聊,快速融入,最忌讳有问题也不说,自己把自己孤立起来。**
+
+## 超出预期
+
+超出预期这个词的外延范围很广,比如 leader 让去做个值周,解答用户群里大家的问题,结果不仅解答了大家的问题,还收集了这些问题进行分类,进而做了一个智能问答机器人解放了值周的人力,这可以算超出预期。比如 leader 让给运营做一个小工具,结果建设了一系列的工具甚至发展成了一个平台,成为了一个完整的项目,这也算超出预期。超出预期要求我们有把事情做大的能力,也就是想到了 leader 没想到的地方,并且创造了实际价值,拿到了业务收益。这个能力其实也比较重要,在工作中发现,有的人能把一个小盘子越做越大,而有的人恰好反之,那么那些有创新能力,经常超出预期的同学发展空间显然就更大一点。
+
+**这块其实比较看个人能力,暂时没想到什么太好的捷径,多想一步吧。**
+
+## 体系化思考,系统化建设
+
+这句话是晋升时候总结出来的,大意就是做系统建设要有全局视野,不要局限于某一个小点,应该有良好的规划能力和清晰的演进蓝图。比如,今天加了一个监控,明天加一个报警,这些事不应该成为一个个孤岛,而是属于稳定性建设一期其中的一小步。这一期稳定性建设要做的工作是报警配置和监控梳理,包括机器监控、系统监控、业务监控、数据监控等,预期能拿到 XXX 的收益。这个工作还有后续的 roadmap,稳定性建设二期要做容量规划,接入压测,三期要做降级演练,多活容灾,四期要做...给人的感觉就是这个人思考非常全面,办事有体系有规划。
+
+**平时积极总结沉淀,多跟别人交流,形成方法论。**
+
+## 提升自己的软素质能力
+
+这里的软素质能力其实想说的就是 PPT、沟通、表达、时间管理、设计、文档等方面的能力。说实话,我觉得我当时能晋升就是因为 PPT 做的好了一点...可能大家平时对这些能力都不怎么关注,以前我也不重视,觉得比较简单,用时候直接上就行了,但事实可能并不像想象得那样简单。比如晋升时候 PPT+演讲+答辩这个工作,其实有很多细节的思考在里面,内容如何选取,排版怎么设计,怎样引导听众的情绪,如何回答评委的问题等等。晋升时候我见过很多同学 PPT 内容编排杂乱无章,演讲过程也不流畅自然,虽然确实做了很多实际工作,但在表达上欠缺了很多,属于会做不会说,如果再遇到不了解实际情况的外部门评委,吃亏是可以预见的。
+
+**_公司内网一般都会有一些软素质培训课程,可以找一些场合刻意训练。_**
+
+以上都是这些分享还都算比较伟光正,但是社会吧也不全是那么美好的。。下面这些内容有负能量倾向,三观特别正的同学以及观感不适者建议跳过。
+
+## 拍马屁是真的香
+
+拍马屁这东西入职前我是很反感的,我最初想加入互联网公司的原因就是觉得互联网公司的人情世故没那么多,事实证明,我错了...入职前几天,部门群里大 leader 发了一条消息,后面几十条带着大拇指的消息立马跟上,学习了,点赞,真不错,优秀,那场面,说是红旗招展锣鼓喧天鞭炮齐鸣一点也不过分。除了惊叹大家超强的信息接收能力和处理速度外,更进一步我还发现,连拍马屁都是有队形的,一级部门 leader 发消息,几个二级部门 leader 跟上,后面各组长跟上,最后是大家的狂欢,让我一度怀疑拍马屁的速度就决定了职业生涯的发展前景(没错,现在我已经不怀疑了)。
+
+坦诚地说,我到现在也没习惯在群里拍马屁,但也不反感了,可以说把这个事当成一乐了。倒不是说我没有那个口才和能力(事实上也不需要什么口才,大家都简单直接),在某些场合,为活跃气氛的需要,我也能小嘴儿抹了蜜,甚至能把古诗文彩虹屁给 leader 安排上。而是我发现我的直属 leader 也不怎么在群里拍马屁,所以我表面上不公开拍马屁其实属于暗地里事实上迎合了 leader 的喜好...
+
+但是拍马屁这个事只要掌握好度,整体来说还是香的,最多是没用,至少不会有什么坏处嘛。大家能力都差不多,每一次在群里拍马屁的机会就是一次露脸的机会,按某个同事的说法,这就叫打造个人技术影响力...
+
+**想舔就舔,不想舔也没必要酸别人,Respect Greatness。**
+
+## 永不缺席的撕逼甩锅实战
+
+有人的地方,就有江湖。虽然搞技术的大多城府也不深,但撕逼甩锅邀功抢活这些闹心的事儿基本也不会缺席,甚至我还见到过公开群发邮件撕逼的...这部分话题涉及到一些敏感信息就不多说了,而且我们低职级的遇到这些事儿的机会也不会太多。只是给大家提个醒,在工作的时候迟早都会吃到这方面的瓜,到时候留个心眼。
+
+**稍微注意一下,咱不会去欺负别人,但也不能轻易让别人给欺负了。**
+
+## 不要被画饼蒙蔽了双眼
+
+说实话,我个人是比较反感灌鸡汤、打鸡血、谈梦想、讲奋斗这一类行为的,9102 年都快过完了,这一套\*\*\*治还在大行其道,真不知道是该可笑还是可悲。当然,这些词本身并没有什么问题,但是这些东西应该是自驱的,而不应该成为外界的一种强 push。『我必须努力奋斗』这个句式我觉得是正常的,但是『你必须努力奋斗』这种话多少感觉有点诡异,努力奋斗所以让公司的股东们发家致富?尤其在钱没给够的情况下,这些行为无异于耍流氓。我们需要对 leader 的这些画饼操作保持清醒的认知,理性分析,作出决策。比如感觉钱没给够(或者职级太低,同理)的时候,可能有以下几种情况:
+
+1. leader 并没有注意到你薪资较低这一事实
+2. leader 知道这个事实,但是不知道你有多强烈的涨薪需求
+3. leader 知道你有涨薪的需求,但他觉得你能力还不够
+4. leader 知道你有涨薪的需求,能力也够,但是他不想给你涨
+5. leader 想给你涨,也向上反馈和争取了,但是没有资源
+
+这时候我们需要做的是向上反馈,跟 leader 沟通确认。如果是 1 和 2,那么通过沟通可以消除信息误差。如果是 3,需要分情况讨论。如果是 4 和 5,已经可以考虑撤退了。对于这些事儿,也没必要抱怨,抱怨解决不了任何问题。我们要做的就是努力提升好个人能力,保持个人竞争力,等一个合适的时机,跳槽就完事了。
+
+**时刻准备着,技术在手就没什么可怕的,哪天干得不爽了直接跳槽。**
+
+## 学会包装
+
+这一条说白了就是,要会吹。忘了从哪儿看到的了,能说、会写、善做是对职场人的三大要求。能说是很重要的,能说才能要来项目,拉来资源,招来人。同样一件事,不同的人能说出来完全不一样的效果。比如我做了个小工具上线了,我就只能说出来基本事实,而让 leader 描述一下,这就成了,打造了 XXX 的工具抓手,改进了 XXX 的完整生态,形成了 XXX 的业务闭环。老哥,我服了,硬币全给你还不行嘛。据我的观察,每个互联网公司都有这么几个词,抓手、生态、闭环、拉齐、梳理、迭代、owner 意识等等等等,我们需要做的就是熟读并背诵全文,啊不,是牢记并熟练使用。
+
+这是对事情的包装,对人的包装也是一样的,尤其是在晋升和面试这样的应试型场合,特点是流程短一锤子买卖,包装显得尤为重要。晋升和面试这里就不展开说了,这里面的道和术太多了。。下面的场景提炼自面试过程中和某公司面试官的谈话,大家可以感受一下:
+
+1. 我们背后是一个四五百亿美金的市场...
+2. 我负责过每天千亿级别访问量的系统...
+3. 工作两年能达到这个程度挺不错的...
+4. 贵司技术氛围挺好的,业务发展前景也很广阔...
+5. 啊,彼此彼此...
+6. 嗯,久仰久仰...
+
+人生如戏,全靠演技
+
+**可以多看 leader 的 PPT,多听老板的向上汇报和宣讲会。**
+
+## 选择和努力哪个更重要?
+
+这还用问么,当然是选择。在完美的选择面前,努力显得一文不值,我有个多年没联系的高中同学今年已经在时代广场敲钟了...但是这样的案例太少了,做出完美选择的随机成本太高,不确定性太大。对于大多数刚毕业的同学,对行业的判断力还不够成熟,对自身能力和创业难度把握得也不够精准,此时拉几个人去创业,显得风险太高。我觉得更为稳妥的一条路是,先加入规模稍大一点的公司,找一个好 leader,抱好大腿,提升自己的个人能力。好平台加上大腿,再加上个人努力,这个起飞速度已经可以了。等后面积累了一定人脉和资金,深刻理解了市场和需求,对自己有信心了,可以再去考虑创业的事。
+
+## 后记
+
+本来还想分享一些生活方面的故事,发现已经这么长了,那就先这样叭。上面写的一些总结和建议我自己做的也不是很好,还需要继续加油,和大家共勉。另外,其中某些观点,由于个人视角的局限性也不保证是普适和正确的,可能再工作几年这些观点也会发生改变,欢迎大家跟我交流~(甩锅成功)
+
+最后祝大家都能找到心仪的工作,快乐工作,幸福生活,广阔天地,大有作为。
diff --git a/docs/high-quality-technical-articles/programmer/efficient-book-publishing-and-practice-guide.md b/docs/high-quality-technical-articles/programmer/efficient-book-publishing-and-practice-guide.md
new file mode 100644
index 0000000000000000000000000000000000000000..e7077a592a5bb221178ce8ee588613f61cec93b2
--- /dev/null
+++ b/docs/high-quality-technical-articles/programmer/efficient-book-publishing-and-practice-guide.md
@@ -0,0 +1,140 @@
+---
+title: 程序员高效出书避坑和实践指南
+category: 技术文章精选集
+author: hsm_computer
+tag:
+ - 程序员
+---
+
+> **推荐语**:详细介绍了程序员出书的一些常见问题,强烈建议有出书想法的朋友看看这篇文章。
+>
+>
+>
+> **原文地址**:https://www.cnblogs.com/JavaArchitect/p/14128202.html
+
+古有三不朽, 所谓立德、立功、立言。程序员出一本属于自己的书,如果说是立言,可能过于高大上,但终究也算一件雅事。
+
+出书其实不挣钱,而且从写作到最终拿钱的周期也不短。但程序员如果有一本属于自己的技术书,那至少在面试中能很好地证明自己,也能渐渐地在业内积累自己的名气,面试和做其它事情时也能有不少底气。在本文里,本人就将结合自己的经验和自己踩过的坑,和大家聊聊程序员出书的那些事。
+
+## 1.出书的稿酬收益和所需要的时间
+
+先说下出书的收益和需要付出的代价,这里姑且先不谈“出书带来的无形资产”,先谈下真金白银的稿酬。
+
+如果直接和出版社联系,一般稿酬是版税,是书价格的 8%乘以印刷数(或者实际销售数),如果你是大牛的话,还可以往上加,不过一般版税估计也就 10%到 12%。请注意这里的价格是书的全价,不是打折后的价格。
+
+比如一本书全价是 70 块,在京东等地打 7 折销售,那么版税是 70 块的 8%,也就是说卖出一本作者能有 5.6 的收益,当然真实拿到手以后还再要扣税。
+
+同时也请注意合同的约定是支付稿酬的方式是印刷数还是实际销售数,我和出版社谈的,一般是印刷数量,这有什么差别呢?现在计算机类的图书一般是首印 2500 册,那么实际拿到手的钱数是 70*8%*2500,当然还要扣税。但如果是按实际销售数量算的话,如果首印才销了 1800 本的话,那么就得按这个数量算钱了。
+
+现在一本 300 页的书,定价一般在 70 左右,按版税 8%和 2500 册算的话,税前收益是 14000,税后估计是 12000 左右,对新手作者的话,300 的书至少要写 8 个月,由此大家可以算下平均每个月的收益,算下来其实每月也就 1500 的收益,真不多。
+
+别人的情况我不敢说,但我出书以后,除了稿酬,还有哪些其它的收益呢?
+
+- 在当下和之前的公司面试时,告诉面试官我在相关方面出过书以后,面试官就直接会认为我很资深,帮我省了不少事情。
+- 我还在做线下的培训,我就直接拿我最近出的 Python 书做教材了,省得我再备课了。
+- 和别人谈项目,能用我的书证明自己的技术实力,如果是第一次和别人打交道,那么这种证明能立杆见效。
+
+尤其是第一点,其实对一些小公司或者是一些外派开发岗而言,如果候选人在这个方面出过书,甚至都有可能免面试直接录取,本人之前面试过一个大公司的外派岗,就得到过这种待遇。
+
+## 2.支付稿酬的时间点和加印后的收益
+
+我是和出版社直接联系出书,支付稿酬的时间点一般是在首印后的 3 个月内拿到首印部分稿酬的一部分(具体是 50%到 90%),然后在图书出版后的一年后再拿到其它部分的稿酬。当下有不少书,能销掉首印的册数就不错了,不过也有不少书能加印,甚至出第二和第三版,一般加印册数的版税会在加印后的半年到一年内结清。
+
+从支付稿酬的时间点上来,对作者确实会有延迟,外加上稿酬也不算高,相对于作者的辛勤劳动,所以出书真不是挣钱的事,而且拿钱的周期还长。如果个别图书公司工作人员一方面在出书阶段对作者没什么帮助, 另一方面还要在中间再挣个差价,那么真有些作践作者的辛勤劳动了。
+
+## 3.同图书公司打交道的所见所闻
+
+在和出版社编辑沟通前,我也和图书公司的工作人员交流过,不少工作人员对我也是比较尊重,交流虽然不算深入,但也算客气。不过最终对比出版社给出的稿酬等条件,我还是没有通过图书公司出书,这也是比较可惜的事情。下面我给出些具体的经历。
+
+- 我经常在博客园等地收到一些图书公司工作人员的留言,问要不要出书,一般我不问,他们不会说自己是出版社编辑还是图书公司的工作人员。有个别图书公司的工作人员,会向作者,尤其是新手作者,说些“出版社编辑一般不会直接和作者联系”,以及“出书一般是通过图书公司”等的话。其实这些话不能算错,比如你不联系出版社编辑,那么对方自然不会直接联系你,但相反如果作者直接和出版社编辑联系,第一没难度,第二可能更直接。
+- 我和出版社编辑交流大纲时,即使大纲有不足,他们也能直接给出具体的修改意见,比如某个章节该写什么,某个小节的大纲该怎么写。而我和个别图书公司的工作人员交流过大纲时,得到的反馈大多是“要重写”,怎么个重写法?这些工作人员可能只能给出抽象的意见,什么都要我自己琢磨。在我之前的博文[程序员怎样出版一本技术书](./how-do-programmers-publish-a-technical-book)里,我就给出过具体的经历。
+- 由于交流不深,所以我没有和图书公司签订过出书协议,但我知道,只有出版社能出书。由于没有经历过,所以我也不知道图书公司在合同里是否有避规风险等条款,但我见过一位图书公司人员人员给出的一些退稿案例,并隐约流露出对作者的责备之意。细思感觉不妥,对接的工作人员第一不能在出问题的第一时间及时发现并向作者反馈,第二在出问题之后不能对应协调最终导致退稿,第三在退稿之后,作者在付出劳动的情况下图书公司不仅不用承担任何风险,并还能指摘作者。对此,退稿固然有作者的因素,但同是作者的我未免有兔死狐悲之谈。而我在出版社出书时,编辑有时候甚至会主动关心,主动给素材,哪怕有问题也会第一时间修改,所以甚至大范围修改稿件的情况都基本没有出现。
+- 再说下图书公司给作者的稿酬。我见过按页给钱,比如一页 30 到 50 块,并卖断版权,即书重印后作者也无法再得到稿酬,如果是按版税给钱,我也见过给 6%,至于图书公司能否给到 8 个点甚至更高,我没见到过,所以不知道,也不敢擅拟。
+
+我交流过的图书公司工作人员不多,交流也不深,因为我现在主要是和出版社的编辑交流。所以以上只是我对个别图书公司编辑的感受,我无意以偏概全,而和我交流的一些图书公司工作人员至少态度上对我很尊重。所以大家也可以对比尝试下和图书公司以及出版社合作的不同方式。不管怎样,你在写书甚至在签出书协议前,你需要问清楚如下的事项,并且对方有义务让你了解如下的事实。
+
+- 你得问清楚,对方的身份是出版社编辑还是图书公司工作人员,这其实应当是对方主动告之。
+- 你的书在哪个出版社出版?这点需要在出书协议里明确给出,不能是先完稿再定出版社。而且,最终能出版书的,一定是出版社,而不是图书公司。
+- 稿酬的支付方式,哪怕图书公司中间可能挣差价,但至少你得了解出版社能给到的稿酬。如果你是通过图书公司出的书,不管图书公司怎么和你谈的,但出版社给图书公司的钱一分不会少,中间部分应该就是图书公司的盈利。
+- 最终和你签订出书合同的,是图书公司还是出版社,这一定得在你签字前搞明白,哪怕你最终是和图书公司签协议,但至少得知道你还能直接和出版社签协议。
+- 你不能存有“在图书公司出书要求低”的想法,更不应该存有“我能力一般,所以只能在图书公司出书”的想法。图书公司自己是没有资格出书的,所以他们也是会把稿件交给出版社,所以该有的要求一点也不会低。你的大纲在出版社编辑那边通不过,那么在图书公司的工作人员那边同样通不过,哪怕你索要的稿酬少,图书公司方面对应的要求一定也不会降低。
+
+如果你明知“图书公司和出版社的差别”,并还是和图书公司合作,这个是两厢情愿的事情。但如果对方“不主动告知”,而你在不了解两者差异的基础上同图书公司合作,那么对方也无可指摘。不过兼听则明,大家如果要出书,不妨和出版社和图书公司都去打打交道对比下。
+
+## 4.如何直接同国内计算机图书的知名出版社编辑联系
+
+我在清华大学出版社、机械工业出版社、北京大学出版社和电子工业出版社出过书,出书流程也比较顺畅,和编辑打交道也比较愉快。我个人无意把国内出版社划分成三六九等,但计算机行业,比较知名的出版社有清华、机工、电子工业和人邮这四家,当然其它出版社在计算机方面也出版过精品书。
+
+如何同这些知名出版社的编辑直接打交道?
+
+- 直接到官网,一般官网上都直接有联系方式。
+- 你在博客园等地发表文章,会有人找你出书,其中除了图书公司的工作人员外,也有出版社编辑,一般出版社的编辑会直接说明身份,比如我是 xx 出版社的编辑 xx。
+- 本人也和些出版社的编辑联系过,大家如果要,我可以给。
+
+那怎么去找图书公司的工作人员?一般不用主动找,你发表若干博文后,他们会主动找你。如果你细问,“您是出版社编辑还是图书公司的编辑”,他们会表明身份,如果你再细问,那么他们可能会站在图书公司的立场上解释出版社和图书公司的差异。
+
+从中大家可以看到,不管你最终是否写成书,但去找知名出版社的编辑,并不难。并且,你找到后,他们还会进一步和你交流选题。
+
+## 5.定选题和出书的流程
+
+这里给出我和出版社编辑交流合作,最终出书的流程。
+
+第一,联系上出版社编辑后,先讨论选题,你可以选择一个你比较熟悉的方向,或者你愿意专攻的方向,这个方向可以是 java 分布式组件,Spring cloud 全家桶,微服务,或者是 Python 数据分析,机器学习或深度学习等。这方面你如果有扎实的项目经验那最好,如果你当下虽然不熟悉,但你有毅力经过短时间的系统学习确保你写的内容能成系统或者能帮到别人,那么你也可以在这方面出书。
+
+第二,定好选题方向后,你可以先列出大纲,比如以 Python 数据分析为例,你可以定 12 个章节,第一章讲语法,第二章讲 numpy 类等等,以此类推,你定大纲的时候,可以参考别人书的目录,从而制定你的写作内容。定好大纲以后,你可以和编辑交流,当编辑也认可这个大纲以后,就可以定出版协议。
+
+对一般作者而言,出版协议其实差不多,稿酬一般是 8 个点,写作周期是和出版社协商,支付周期可能也大同小异,然后出版社会买断这本书的电子以及各种文字的版权。但如果作者是大牛,那么这些细节都可以和出版社协商。
+
+然后是写书,这是很枯燥的,尤其是写最后几章的时候。我一般是工作日每天用半小时,两天周末周末用 4,5 个小时写,这样一般半年能写完一本 300 页的书,关于高效写书的技巧,后文会详细提及。
+
+在写书时,一般建议每写好一个章节就交给编辑审阅,这样就不会导致太大问题的出现,而且如果是新手作者,刚开始的措辞和写作技巧都需要积累,这样出版社的编辑在开始阶段也能及时帮到作者。
+
+当你写完把稿件交到编辑以后,可能会有三校三审的事情,在其中同我合作的编辑会帮助我修改语法和错别字等问题,然后会形成一个修改意见让我确认和修改。我了解下来,如果在图书公司出书,退稿的风险一般就发生在这个阶段,因为图书公司可能是会一次性地把稿件提交给出版社。但由于我会把每个章节都直接提交给出版社编辑审阅,所以即使有大问题,那么在写开始几个章节时都已经暴露并修改,所以最后的修改意见一般不会太长。也就是说,如果是直接和出版社沟通,在三校三审阶段,工作量可能未必大,我一般是在提交一本书以后,由编辑做这个事情,然后我就继续策划并开始写后一本书。
+
+最后就是拿稿酬,之前已经说了,作者其实不应该对稿酬有太大的期望,也就是聊胜于无。但如果一不小心写了本销量在 5000 乃至 10000 本左右的畅销书,那么可能在一年内也能有 5 万左右的额外收益,并能在业内积累些名气。
+
+## 6.出案例书比出经验书要快
+
+对一些作者而言,尤其是新手作者,出书不容易,往往是开始几个章节干劲十足,后面发现问题越积越多,外加工作一忙,就不了了之了,或者用 1 年以上的时间才能完成一本书。对此,我的感受是,一本 300 到 400 书的写作周期最长是 8 个月。为了能在这个时间段里完成一本书,我对应给出的建议是,新手作者可以写案例书,别先写介绍经验类的书。
+
+什么叫案例书?比如一本书里用一个大案例贯穿,系统介绍一个知识点,比如小程序开发,或者全栈开发等。或者一本书一个章节放一个案例,在一本书里给出 10 个左右 Python 深度学习方面的案例。什么叫经验类书呢?比如介绍面试经验的书就属于这这种,或者一些技术大牛写的介绍分布式高并发开发经验的书也算经验类书。
+
+请注意这里并没有区分两类书的差异,只是对新手作者而言,案例书好写。因为在其中,更多的是看图说话,先给出案例(比如 Python 深度学习里的图像识别案例),然后通过案例介绍 API 的用法(比如 Python 对应库的用法),以及技术的综合要点(比如如何用 Python 库综合实现图像识别功能)。并且案例书里需要作者主观发挥的点比较少,作者无需用自己的话整理相关的经验。对新手作者而言,在组织文字介绍经验时,可能会有自己明白但说不上来的感觉,这样一方面就无法达到预期的效果,另一方面还有可能因为无法有效表述而导致进度的延迟。
+
+但相反对于案例书,第一案例一般可以借鉴别人的,第二介绍现存的技术总比介绍自己的经验要容易,第三一般还有同类的书可以供作者参考,所以作者不大需要斟酌措辞,新手作者用半年到八个月的时间也有可能写完一本。当作者通过写几本书积累一定经验后,再去挑战经验类书,在这种情况下,写出来的经验类书就有可能畅销了。
+
+那么具体而言,怎么高效出一本案例书呢?
+
+- 对整本书而言,先用少量章节介绍搭建环境和通用基本语法的内容。
+- 在写每个章节案例时,用到总分总的结构,先总体介绍下你这个案例的需求功能,以及要用的技术点,再分开介绍每个功能点的代码实现,最后再总结下这些功能点的使用要点。
+- 在介绍案例中具体代码时,也可以用到总分总的结构,即先总体介绍下这段代码的结构,再分别给出关键代码的说明,最后再给出运行效果并综述其中技术的实现要点。
+
+这样的话,刚开始可以是 1 个月一个章节,写到后面熟练以后估计一个月能写两个章节,这样 8 个月完成一本书,也就不是不可能了。
+
+## 7.如何在参考现有内容的基础上避免版权问题
+
+写书时,一般多少都需要参考现有的代码和现有的书,但这绝不是重复劳动。比如某位作者整合了不同网站上多个案例,然后系统地讲述了 Python 数据分析,这样虽然现成资料都有,但对读者来说,就能一站式学习。同样地,比如在 Python 神经网络方面,现有 2,3 本书分别给出了若干人脸识别等若干案例,但如果你有效整合到一起,并加他人的基础上加上你的功能,那对读者来说也是有价值的。
+
+这里就涉及到版权问题,先要说明,作者不能抱有任何幻想,如果出了版权问题,书没出版还好,如果已经出版了,作者不仅要赔钱,而且在业内就会有不好的名声,可谓身败名裂。但其实要避免版权问题一点也不难。
+
+- 不能抄袭网上现有的内容,哪怕一句也不行。对此,作者可以在理解人家语句含义的基础上改写。不能抄袭人家书上现有的目录,更不能抄袭人家书上的话,同样一句也不行,对应的解决方法同样是在理解的基础上改写。
+- 不能抄袭 GitHub 上或者任何地方别人的代码,哪怕这个代码是开源的。对此,你可以在理解对方代码的基础上,先运行通,然后一定得自己新建一个项目,在你的项目里参考别人的代码实现你的功能,在这个过程中不能有大段的复制粘贴操作。也就是说,你的代码和别人的代码,在注释,变量命名,类名和方法名上不能有雷同的地方,当然你还可以额外加上你自己的功能。
+- 至于在写技术和案例介绍时,你就可以用你自己的话来说,这样也不会出现版权问题。
+
+用了上述办法以后,作者就可以在参考现有资料的基础上,充分加上属于你的功能,写上你独到的理解,从而高效地出版属于你自己的书。
+
+## 8.新手作者需要着着重避免的问题
+
+在上文里详细给出了出书的流程,并通过案例书,给出了具体的习作方法,这里就特别针对新手作者,给出些需要注意的实践要点。
+
+- 技术书不同于文艺书,在其中首先要确保把技能知识点讲清楚,然后再此基础上可以适当加上些风趣生动的措辞。所以对新手作者而言,甚至可以直接用朴素的文字介绍案例技术,而无需过多考虑文字上的生动性。
+- 内容需要针对初学者,在介绍技术时,从最基本的零基础讲起,别讲太深的。这里以 Python 机器学习为例,可以从什么是机器学习以及 Python 如何实现机器学习讲起,但如果首先就讲机器学习里的实践经验,就未必能确保初学者能学会。
+- 新手作者恨不得把自己知道的都写出来。这种态度非常好,但需要考虑读者的客观接受水平所以需要在写书前设置个预期效果,比如零基础的 Python 开发人员读了我的书以后至少能干活。这个预期效果别不可行,比如不能是“零基础的 Python 开发人员读了我书以后能达到 3 年开发的水准”。这样就可以根据预先制定的效果,制定写作内容,从在你的书就能更着重讲基础知识,这样读者就能有真正有收获。
+
+不过话说回来,如果新手作者直接和出版社编辑联系,找个热门点的方向,并根据案例仔细讲解技术,甚至都有可能写出销量过万的畅销书。
+
+## 9.总结:在国内知名出版社出书,其实是个体力活
+
+可能当下,写公众号和录视频等的方式,挣钱收益要高于出书,不过话可以这样说,经营公众号和录制视频也是个长期的事情,在短时间里可能未必有收益,如果不是系统地发表内容的话,可能甚至不会有收益。所以出书可能是个非常好的前期准备工作,你靠出书系统积累了素材,靠出书整合了你的知识体系,那么在此基础上,靠公众号或者录视频挣钱可能就会事半功倍。
+
+从上文里大家可以看到,在出书前期,联系出版社编辑和定选题并不难,如果要写案例书,那么在参考别人内容的基础上,要写完一般书可能也不是高不可攀的事情。甚至可以这样说,出书是个体力活,只要坚持,要出本书并不难,只是你愿不愿意坚持下去的问题。但一旦你有了属于自己的技术书,那么在找工作时,你就能自信地和面试官说你是这方面的专家,在你的视频、公众号和文字里,你也能正大光明地说,你是计算机图书的作者。更为重要的是,和名校、大厂经历一样,属于你的技术书同样是证明程序员能力的重要证据,当你通过出书有效整合了相关方面的知识体系后,那么在这方面,不管是找工作,或者是干私活,或者是接项目做,你都能理直气壮地和别人说:我能行!
diff --git a/docs/high-quality-technical-articles/programmer/how-do-programmers-publish-a-technical-book.md b/docs/high-quality-technical-articles/programmer/how-do-programmers-publish-a-technical-book.md
new file mode 100644
index 0000000000000000000000000000000000000000..1927c9271276af5b1b2915e07659f6adefa66997
--- /dev/null
+++ b/docs/high-quality-technical-articles/programmer/how-do-programmers-publish-a-technical-book.md
@@ -0,0 +1,93 @@
+---
+title: 程序员怎样出版一本技术书
+category: 技术文章精选集
+author: hsm_computer
+tag:
+ - 程序员
+---
+
+> **推荐语**:详细介绍了程序员应该如何从头开始出一本自己的书籍。
+>
+>
+>
+> **原文地址**:https://www.cnblogs.com/JavaArchitect/p/12195219.html
+
+在面试或联系副业的时候,如果能令人信服地证明自己的实力,那么很有可能事半功倍。如何证明自己的实力?最有信服力的是大公司职位背景背书,没有之一,比如在 BAT 担任资深架构,那么其它话甚至都不用讲了。
+
+不过,不是每个人入职后马上就是大公司架构师,在上进的路上,还可以通过公众号,专栏博文,GitHub 代码量和出书出视频等方式来证明自己。和其它方式相比,属于自己的技术图书由于经过了国家级出版社的加持,相对更能让别人认可自己的实力,而对于一些小公司而言,一本属于自己的书甚至可以说是免面试的通行证。所以在本文里,就将和广大程序员朋友聊聊出版技术书的那些事。
+
+## 1.不是有能力了再出书,而是在出书过程中升能力
+
+我知道的不少朋友,是在工作 3 年内出了第一本书,有些优秀的,甚至在校阶段就出书了。
+
+与之相比还有另外一种态度,不少同学可能想,要等到技术积累到一定程度再写。其实这或许就不怎么积极了,边写书,边升技术,而且写出的书对人还有帮助,这绝对可以做到的。
+
+比如有同学向深入了解最近比较热门的 Python 数据分析和机器学习,那么就可以在系统性的学习之后,整理之前学习到的爬虫,数据分析和机器学习的案例,根据自己的理解,用适合于初学者的方式整理一下,然后就能出书了。这种书,对资深的人帮助未必大,但由于包含案例,对入门级的读者绝对有帮助,因为这属于现身说法。而且话说回来,如果没有出书这个动力,或者学习过程也就是浅尝辄止,或者未必能全身心地投入,有了出书这个目标,更能保证学习的效果。
+
+## 2.适合初级开发,高级开发和架构师写的书
+
+之前也提到了,初级开发适合写案例书,就拿 Python 爬虫数据分析机器学习题材为例,可以先找几本这方面现成的书,这些书里,或者章节内容不同,但一起集成看的话,应该可以包含这方面的内容。然后就参考别人书的思路,比如一章写爬虫,一章写 pandas,一章写 matplotlib 等等,整合起来,就可以用 若干个章节构成一本书了。总之,别人书里包含什么内容,你别照抄,但可以参考别人写哪些技术点。
+
+定好章节后,再定下每个章节的小节,比如第三章讲爬虫案例,那么可以定 3.1 讲爬虫概念,3.2 讲如何搭建 Scrapy 库,3.3 讲如何开发 Scrapy 爬虫案例,通过先章再节的次序,就可以定好一本书的框架。由于是案例书,所以是先给运行通的代码,再用这些代码案例教别人入门,所以案例未必很深,但需要让初学者看了就能懂,而且按照你给出的知识体系逐步学习之后,能理解这个主题的内容。并且,能在看完你这本书以后,能通过调通你给出的爬虫,机器学习等的案例,掌握这一领域的知识,并能从事这方面的基本开发。这个目标,对初级开发而言,稍微用点心,费点时间,应该不难达到。
+
+而对于高级开发和架构师而言,除了写存粹案例书以外,还可以在书里给出你在大公司里总结出来的开发经验,也就是所谓踩过的坑,比如 Python 在用 matplotlib 会图例时,在设置坐标轴方面有哪些技巧,设置时会遇到哪些常见问题,如果在书里大量包含这种经验,你的书含金量更高。
+
+此外,高级开发和架构师还可以写一些技术含量更高的书,比如就讲高并发场景下的实践经验,或者 k8s+docker 应对高并发的经验,这种书里,可以给出代码,更可以给出实施方案和架构实施技巧,比如就讲高并发场景里,缓存该如何选型,如何避免击穿,雪崩等场景,如何排查线上 redis 问题,如何设计故障应对预案。除了这条路之外,还可以深入细节,比如通过讲 dubbo 底层代码,告诉大家如何高效配置 dubbo,出了问题该如何排查。如果架构师或高级开发有这类书作为背书,外带大厂工作经验,那么就更可以打出自己的知名度。
+
+## 3.可以直接找出版社,也可以找出版公司
+
+在我的这篇博文里,[程序员副业那些事:聊聊出书和录视频](https://www.cnblogs.com/JavaArchitect/p/11616906.html),给出了通过出版社出书和图书公司出书的差别,供大家参考,大家看了以后可以自行决定出书方式。
+
+不过不管怎么选,在出书前你得搞明白一些事,或许个别图书出版公司的工作人员不会主动说,这需要你自己问清楚。
+
+- 你的合作方是谁?图书出版公司还是出版社?
+- 你的书将在哪个出版社出版?国内比较有名的是清华,人邮,电子和机械,同时其它出版社不能说不好,但业内比较认这四个。
+- 和你沟通的人,是最终有决定权的图书编辑吗?还是图书公司里的工作人员?再啰嗦下,最后能决定书能否出版,以及确定修改意见的,是出版社的编辑。
+
+通过对比出版社和图书出版公司,在搞清楚诸多细节后,大家可以自己斟酌考虑合作的方式。而且,出版社和图书公司的联系方式,在官网上都有,大家可以自行通过邮件等方式联系。
+
+## 4.如果别人拿你做试错对象,或有不尊重,赶紧止损
+
+我之前看到有图书出版公司招募面向 Java 初学者图书的作者,并且也主动联系过相关人员,得到的反馈大多是:“要重写”。
+
+比如我列了大纲发过去,反馈是“要重写”,原因是对方没学过 Java,但作为零基础的人看了我的大纲,发现学不会。至于要重写成什么样子 ,对方也说不上来,总之让我再给个大纲,再给一版后,同样没过,这次好些,给了我几本其它类似书的大纲,让我自行看别人有什么好的点。总之不提(或者说提不出)具体的改进点,要我自行尝试各种改进点,试到对方感觉可以为止。
+
+相比我和几位出版社专业的编辑沟通时,哪怕大纲或稿件有问题,对方会指明到点,并给出具体的修改意见。我不知道图书出版公司里的组织结构,但出版社里,计算机图书有专门的部门,专门的编辑,对方提出的意见都是比较专业,且修改起来很有操作性。
+
+另外,我在各种渠道,时不时看到有图书出版公司的人员,晒出别人交付的稿件,在众目睽睽之下,说其中有什么问题,意思让大家引以为戒。姑且不论这样做的动机,并且这位工作人员也涂掉了能表面作者身份的信息。但作者出于信任把稿件交到你手上,在不征得作者同意就公开稿件,说“不把作者当回事”,这并不为过。不然,完全可以用私信的方式和作者交流,而不是把作者无心之过公示于众。
+
+我在和出版社合作时,这类事绝没发生过,而且我认识的出版社编辑,都对各位作者保持着足够的尊重。而且我和我的朋友和多位图书出版公司的朋友交流时,也能得到尊重和礼遇。所以,如果大家在写书时,尤其在写第一本书时,如果遇到被试错,或者从言辞等方面感觉对方不把你当会事,那么可以当即止损。其实也没有什么“损失”,你把当前的大纲和稿件再和出版社编辑交流时,或许你的收益还能提升。
+
+## 5.如何写好 30 页篇幅的章节?
+
+在和出版社定好写作合同后,就可以创作了。书是由章节构成,这里讲下如何构思并创作一个章节。
+
+比如写爬虫章节,大概 30 页,先定节和目,比如 3.1 搭建爬虫环境是小节,3.1.1 下载 Python Scrapy 包,则是目。先定要写的内容,具体到爬虫小节,可以写 3.1 搭建环境,3.2 Scrapy 的重要模块,3.3 如何开发 Scrapy 爬虫,3.4 开发好以后如何运行,3.5 如何把爬到的信息放入数据库,这些都是小节。
+
+再具体到目,比如 3.5 里,3.5.1 里写如何搭建数据库环境 3.5.2 里写如何在 Scrapy 里连接数据库 3.5.3 里给出实际案例 3.5.4 里给出运行步骤和示例效果。
+
+这样可以搭建好一个章的框架,在每个小节里,先给出可以运行通的,而且能说明问题的代码,再给出对代码的说明,再写下代码如何配置,开发时该注意哪些问题,必要时用表格和图来说明,用这样的条理,最多 3 个星期可以完成一个章节,快的话一周半就一个章节。
+
+以此类推,一本书大概有 12 个章节,第一章可以讲如何安装环境,以及基础语法,后面就可以由浅入深,一个章节一个主题,比如讲 Python 爬虫,第二章可以是讲基础语法,第三章讲 http 协议以及爬虫知识点,以此深入,讲全爬虫,数据分析,数据展示和机器学习等技能。
+
+按这样算,如果出第一本书,平均下来一个月 2 个章节,大概半年到八个月可以完成一本书,思路就是先搭建书的知识体系,写每个章节时再搭建某个知识点的框架,在小节和目里,用代码结合说明的方式,这样从简到难,大家就可以完成第一本属于自己的书了。
+
+## 6.如何写出一本销量过 5 千的书
+
+目前纸质书一般一次印刷在 2500 册,大多数书一般就一次印刷,买完为止。如果能销调 5000 本,就属于受欢迎了,如果销量过万,就可以说是大神级书的。这里先不论大神级书,就说下如何写一本过 5000 的畅销书。
+
+1 最好贴近热点,比如当前热点是全栈开发和机器学习等,如何找热点,就到京东等处去看热销书的关键字。具体操作起来,多和出版社编辑沟通,或许作者更多是从技术角度分析,但出版社的编辑是从市场角度来考虑问题。
+
+2 如果你的书能被培训机构用作教材,那想不热都不行。培训机构一般用哪些教材呢?第一面向初学者,第二代码全面,第三在这个领域里涵盖知识点全。如果要达成这点,大家可以和出版社的编辑直接沟通,问下相关细节。
+
+3 可以文字生动,但不能用过于花哨的文字来掩盖书的内涵不足,也就是说畅销书一定要有干货,能解决初学者实际问题,比如 Python 机器学习方向,就写一本用案例涵盖目前常用的机器学习算法,一个章节一种算法,并且案例中有可视化,数据分析,爬虫等要素,可视化的效果如果再吸引人,这本书畅销的可能性也很大。
+
+4 一定不能心存敷衍,代码调通不算,更力求简洁,说明文字多面向读者,内容上,确保读者一看就会,而且看了有收获,或许这点说起来很抽象,但我写了几本书以后切身体会,要做到这很难,同时做到了,书哪怕不畅想,但至少不误人子弟。
+
+## 7.总结,出书仅是一个里程碑,程序员在上进路上应永不停息
+
+出书不简单,因为不是每个人都愿意在半年到八个月里,每个晚上每个周末都费时费力写书。但出书也不难,毕竟时间用上去了,出书也只是调试代码加写文字的活,最多再外加些和人沟通的成本。
+
+其实出书收益并不高,算下来月入大概能在 3k 左右,如果是和图书出版公司合作,估计更少,但这好歹能证明自己的实力。不过在出书后不能止步于此,因为在大厂里有太多的牛人,甚至不用靠出书来证明自己的实力。
+
+那么如何让出书带来的利益最大化呢?第一可以靠这进大厂,面试时有自己的书绝对是加分项。第二可以用这个去各大网站开专栏,录视频,或者开公众号,毕竟有出版社的背书,能更让别人信服你的能力。第三更得用写书时积累的学习方法和上进的态势继续专研更高深技术,技术有了,不仅能到大厂挣更多的钱,还能通过企业培训等方式更高效地挣钱。
diff --git a/docs/high-quality-technical-articles/readme.md b/docs/high-quality-technical-articles/readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..62d8a285673dcbc60e14fedf182cbfec0854021f
--- /dev/null
+++ b/docs/high-quality-technical-articles/readme.md
@@ -0,0 +1,46 @@
+# 程序人生
+
+::: tip 这是一则或许对你有用的小广告
+👉 欢迎准备 Java 面试以及学习 Java 的同学加入我的[知识星球](./../about-the-author/zhishixingqiu-two-years.md),干货很多!收费虽然是白菜价,但星球里的内容或许比你参加上万的培训班质量还要高。
+
+👉 [《Java 面试指北》](./../zhuanlan/java-mian-shi-zhi-bei.md)持续更新完善中!这是一份教你如何更高效地准备面试的小册,涵盖常见八股文(系统设计、常见框架、分布式、高并发 ......)、优质面经等内容。
+:::
+
+这里主要会收录一些我看到的和程序员密切相关的非技术类的优质文章,每一篇都值得你阅读 3 遍以上!常看常新!
+
+## 练级攻略
+
+- [程序员的技术成长战略](./advanced-programmer/the-growth-strategy-of-the-technological-giant.md)
+- [十年大厂成长之路](./advanced-programmer/ten-years-of-dachang-growth-road.md)
+- [给想成长为高级别开发同学的七条建议](./advanced-programmer/seven-tips-for-becoming-an-advanced-programmer.md)
+- [糟糕程序员的 20 个坏习惯](./advanced-programmer/20-bad-habits-of-bad-programmers.md)
+- [工作五年之后,对技术和业务的思考](./advanced-programmer/thinking-about-technology-and-business-after-five-years-of-work.md)
+
+## 个人经历
+
+- [从校招入职腾讯的四年工作总结](./personal-experience/four-year-work-in-tencent-summary.md)
+- [我在滴滴和头条的两年后端研发工作经验分享](./personal-experience/two-years-of-back-end-develop--experience-in-didi-and-toutiao.md)
+- [一个中科大差生的 8 年程序员工作总结](./personal-experience/8-years-programmer-work-summary.md)
+- [华为 OD 275 天后,我进了腾讯!](./personal-experience/huawei-od-275-days.md)
+
+## 程序员
+
+- [程序员怎样出版一本技术书](./programmer/how-do-programmers-publish-a-technical-book.md)
+- [程序员高效出书避坑和实践指南](./programmer/efficient-book-publishing-and-practice-guide.md)
+
+## 面试
+
+- [斩获 20+ 大厂 offer 的面试经验分享](./interview/the-experience-of-get-offer-from-over-20-big-companies.md)
+- [一位大龄程序员所经历的面试的历炼和思考](./interview/the-experience-and-thinking-of-an-interview-experienced-by-an-older-programmer.md)
+- [从面试官和候选者的角度谈如何准备技术初试](./interview/technical-preliminary-preparation.md)
+- [包装严重的 IT 行业,作为面试官,我是如何甄别应聘者的包装程度](./interview/screen-candidates-for-packaging.md)
+- [普通人的春招总结(阿里、腾讯 offer)](./interview/summary-of-spring-recruitment.md)
+- [2021 校招我的个人经历和经验](./interview/my-personal-experience-in-2021.md)
+- [如何在技术初试中考察程序员的技术能力](./interview/how-to-examine-the-technical-ability-of-programmers-in-the-first-test-of-technology.md)
+- [阿里技术面试的一些秘密](./interview/some-secrets-about-alibaba-interview.md)
+
+## 工作
+
+- [新入职一家公司如何快速进入工作状态](./work/get-into-work-mode-quickly-when-you-join-a-company.md)
+- [32 条总结教你提升职场经验](./work/32-tips-improving-career.md)
+- [聊聊大厂的绩效考核](./work/employee-performance.md)
diff --git a/docs/high-quality-technical-articles/work/32-tips-improving-career.md b/docs/high-quality-technical-articles/work/32-tips-improving-career.md
new file mode 100644
index 0000000000000000000000000000000000000000..00da645167b80fd86318ba5b88a89618c355e985
--- /dev/null
+++ b/docs/high-quality-technical-articles/work/32-tips-improving-career.md
@@ -0,0 +1,70 @@
+---
+title: 32条总结教你提升职场经验
+category: 技术文章精选集
+tag:
+ - 工作
+---
+
+> **推荐语**:阿里开发者的一篇职场经验的分享。
+>
+> **原文地址:**
+
+## 成长的捷径
+
+- 入职伊始谦逊的态度是好的,但不要把“我是新人”作为心理安全线;
+- 写一篇技术博客大概需要两周左右,但可能是最快的成长方式;
+- 一定要读两本书:金字塔原理、高效能人士的七个习惯(这本书名字像成功学,实际讲的是如何塑造性格);
+- 多问是什么、为什么,追本溯源把问题解决掉,试图绕过的问题永远会在下个路口等着你;
+- 不要沉迷于忙碌带来的虚假安全感中,目标的确定和追逐才是最真实的安全;
+- 不用过于计较一时的得失,在公平的环境中,吃亏是福不是鸡汤;
+- 思维和技能不要受限于前端、后端、测试等角色,把自己定位成业务域问题的终结者;
+- 好奇和热爱是成长最大的捷径,长期主义者会认同自己的工作价值,甚至要高于组织当下给的认同(KPI)。
+
+## 功夫在日常
+
+- 每行代码要代表自己当下的最高水平,你觉得无所谓的小细节,有可能就是在晋升场上伤害你的暗箭;
+- 双周报不是工作日志流水账,不要被时间推着走,最起码要知道下次双周报里会有什么(小目标驱动);
+- 觉得日常都是琐碎工作、不技术、给师兄打杂等,可以尝试对手头事情做一下分类,想象成每个分类都是个小格子,这些格子连起来的终点就是自己的目标,这样每天不再是机械的做需求,而是有规划的填格子、为目标努力,甚至会给自己加需求,因为自己看清楚了要去哪里;
+- 日常的言行举止是能力的显微镜,大部分人可能意识不到,自己的强大和虚弱是那么的明显,不要无谓的试图掩盖,更不存在蒙混过关。
+
+> 最后一条大概意思就是有时候我们会在意自己在聚光灯下(述职、晋升、周报、汇报等)的表现,以为大家会根据这个评价自己。实际上日常是怎么完成业务需求、帮助身边同学、创造价值的,才是大家评价自己的依据,而且每个人是什么样的特质,合作过三次的伙伴就可以精准评价,在聚光灯下的表演只能骗自己。
+
+## 学会被管理
+
+> 上级、主管是泛指,开发对口的 PD 主管等也在范围内。
+
+- 不要传播负面情绪,不要总是抱怨;
+- 对上级不卑不亢更容易获得尊重,但不要当众反驳对方观点,分歧私下沟通;
+- 好好做向上管理,尤其是对齐预期,沟通绩效出现 Surprise 双方其实都有责任,但倒霉的是自己;
+- 尽量站在主管角度想问题:
+
+- - 这样能理解很多过去感觉匪夷所思的决策;
+ - 不要在意谁执行、功劳是谁的等,为团队分忧赢得主管信任的重要性远远高于这些;
+ - 不要把这个原则理解为唯上,这种最让人不齿。
+
+## 思维转换
+
+- 定义问题是个高阶能力,尽早形成 发现问题->定义问题->解决问题->消灭问题 的思维闭环;
+- 定事情价值导向,做事情结果导向,讲事情问题导向;
+- 讲不清楚,大概率不是因为自己是实干型,而是没想清楚,在晋升场更加明显;
+- 当一个人擅长解决某一场景的问题的时候,时间越久也许越离不开这个场景(被人贴上一个标签很难,撕掉一个标签更难)。
+
+## 要栓住情绪
+
+- 学会控制情绪,没人会认真听一个愤怒的人在说什么;
+- 再委屈、再愤怒也要保持理智,不要让自己成为需要被哄着的那种人;
+- 足够自信的人才会坦率的承认自己的问题,很多时候我们被激怒了,只是因为对方指出了自己藏在深处的自卑;
+- 伤害我们最深的既不是别人的所作所为,也不是自己犯的错误,而是我们对错误的回应。
+
+## 成为 Leader
+
+> Manager 有下属,Leader 有追随者,管理者不需要很多,但人人都可以是 Leader。
+
+- 让你信服、愿意追随的人不是职务上的 Manager,而是在帮助自己的那个人,自己想服众的话道理一样;
+- 不要轻易对人做负面评价,片面认知下的评价可能不准确,不经意的传播更是会给对方带来极大的困扰;
+- Leader 如果不认同公司的使命、愿景、价值观,会过的特别痛苦;
+- 困难时候不要否定自己的队友,多给及时、正向的反馈;
+- 船长最重要的事情不是造船,而是激发水手对大海的向往;
+- Leader 的天然职责是让团队活下去,唯一的途径是实现上级、老板、公司经营者的目标,越是艰难的时候越明显;
+- Leader 的重要职责是识别团队需要被做的事情,并坚定信念,使众人行,越是艰难的时候越要坚定;
+- Leader 应该让自己遇到的每个人都感觉自己很重要、被需要。
diff --git a/docs/high-quality-technical-articles/work/employee-performance.md b/docs/high-quality-technical-articles/work/employee-performance.md
new file mode 100644
index 0000000000000000000000000000000000000000..11e0d4b0bbcf1b4e898ac0773bcc02e4f64975e8
--- /dev/null
+++ b/docs/high-quality-technical-articles/work/employee-performance.md
@@ -0,0 +1,132 @@
+---
+title: 聊聊大厂的绩效考核
+category: 技术文章精选集
+tag:
+ - 工作
+---
+
+> **内容概览**:
+>
+> - 在大部分公司,绩效跟你的年终奖、职级晋升、薪水涨幅等等福利是直接相关的。
+> - 你的上级、上上级对你的绩效拥有绝对的话语权,这是潜规则,放到任何公司都是。成年人的世界,没有绝对的公平,绩效考核尤为明显。
+> - 提升绩效的打法:
+> - 短期打法:找出 1-2 件事,体现出你的独特价值(抓关键事件)。
+> - 长期打法:通过一步步信任的建立,成为团队的核心人员或者是老板的心腹,具备不可替代性。
+>
+>
+>
+> **原文地址**:https://mp.weixin.qq.com/s/D1s8p7z8Sp60c-ndGyh2yQ
+
+在新公司度过了一个完整的 Q3 季度,被打了绩效,也给下属打了绩效,感慨颇深。
+
+今天就好好聊聊**大厂打工人最最关心的「绩效考核」**,谈谈它背后的逻辑以及潜规则,摸清楚了它,你在大厂这片丛林里才能更好的生存下去。
+
+## 大厂的绩效到底有多重要?
+
+先从公司角度,谈谈为什么需要绩效考核?
+
+有一个著名的管理者言论,即:企业战略的上三路和下三路。
+
+> 上三路是使命、愿景、价值观,下三路是组织、人才、KPI。下三路需要确保上三路能执行下去,否则便是空谈。那怎么才能达成呢?
+
+马老板在湖畔大学的课堂上,对底下众多 CEO 学员说,“只能靠 KPI。没有 KPI,一切都是空话,组织和公司是不会进步的”。
+
+所以,KPI 一般是用来承接企业战略的。身处大厂的打工者们,也能深深感受到:每个季度的 KPI 是如何从大 Boss、到 Boss、再到基层,一层层拆解下来的,最终让所有人朝着一个方向行动,这便是 KPI 对于公司的意义。
+
+然鹅,并非每个员工都会站在 CEO 的高度去理解 KPI 的价值,大家更关注的是 KPI 对于我个人来说到底有什么意义?
+
+在互联网大厂,每家公司都会设定一套绩效考核体系,字节用的是 OKR,阿里用的是 KPI,通常都是「271」 制度,即:
+
+> 20% 的比例是 A+ 和 A,对应明星员工。
+>
+> 70% 的比例是 B,对应普通员工。
+>
+> 10% 的比例是 C 和 C-,对应需要绩效改进或者淘汰的员工。
+
+有了三六九等,然后才有了利益分配。
+
+**在大厂,绩效结果跟奖金、晋升、薪水涨幅、股票授予是直接相关的。在内卷的今天,甚至可以直接划上等号。**
+
+绩效好的员工,奖金必然多,一年可能调薪两次,晋升答辩时能 PK 掉绩效一般的人,职级低的人甚至可以晋升免试。
+
+而绩效差的人,有可能一年白干,甚至走人(大厂的末尾淘汰是不成文的规定)。
+
+总之,你能想到的直接利益都和「绩效」息息相关。所以,在大厂这片高手众多的丛林里,多琢磨下绩效背后的逻辑,既是生存之道,更是一技之长。
+
+## 你是怎么看待绩效的?
+
+凡是用来考核人的规则,大部分人在潜意识里都想去突破它,而不是被束缚。
+
+至少在我刚工作的前几年,看着身边有些同事因为背个 C 黯然离开的时候,觉得绩效考核就是一个冷血的管理工具。
+
+尤其遇到自己看不上的领导时,对于他给我打的绩效,其实也是很不屑的。
+
+到今天,实在见过太多的反面案例了,自己也踩过一些坑,逐渐认识到:当初的想法除了让自己心里爽一点,好像起不到任何作用,甚至会让我的工作方式变形。
+
+当思维方式变了,也就改变了我对绩效的态度,至少有两点我认为是打工人需要看清的。
+
+**第一,你的上级、上上级对你的绩效拥有绝对的话语权,这是潜规则,放到任何公司都是。**
+
+大家可以去看看身边发展特别好的人,除了有很强的个人能力以外,几乎都是善于利用规则,而不是去挑战规则的人。
+
+当然,我并不是说你要一味地去跪舔你的领导,而是表达:工作中不要站在领导的对立面去做对抗,如果领导做法很过分,要么直接沟通去影响他,要么选择离开。
+
+**第二,成年人的世界,没有绝对的公平,绩效考核尤为明显。**
+
+我所待过的团队,绩效考核还是相对公平的,虽然也存在受照顾的情况,但都是个例。
+
+另外就是,技术岗的绩效考核不同于销售或者运营岗,很容易指标化。
+
+需求吞吐量、BUG 数、线上事故... 的确有一大堆研发效能指标,但这些指标在绩效考核时是否会被参考?具体又该如何分配比重?本身就是一个扯不清楚的难题。
+
+最终决定你绩效结果的还是你领导的主观判断。你所见到的 360 环评,以及弄一些指标排序,这些都只是将绩效结果合理化的一种方式,并非关键所在。
+
+因此,多琢磨如何去影响你的领导?站在他的视角去审视他在绩效考核时到底关注哪些核心点?这才是至关重要的。
+
+上面讲了一堆潜规则,是不是意味着绩效考核是可以投机取巧,完全不看工作业绩呢,当然不是。
+
+“你的努力不一定会被看见”、“你的努力应该有的放矢”,大家先记住这两条。
+
+下面我再展开聊聊,大家最最关心的 A 和 C,它们背后的逻辑。
+
+## 绩效被打 A 和 C 的逻辑是什么?
+
+“铆足了劲拿不到 A,一不留神居然拿了个 C”,这是绝大多数打工人最真实的职场现状。
+
+A 和 C 属于绩效的两个极端,背后的逻辑类似,反着理解即可,下面我详细分析下 C。
+
+先从我身边人的情况说起,我所看到的案例绝大多数都属于:绩效被打了 C,完全没有任何预感,主管跟他沟通结果时,还是一脸懵逼,“为什么会给我打 C?一定是黑我呀!”。
+
+前阵子听公司一位大佬分享,用他的话说,这种人就是没有「角色认知」,他不知道他所处的角色和职级该做好哪些事?做成什么样才算「做好了」?被打 C 后自然觉得是在背锅。
+
+所以,务必确保你对于当前角色是认知到位的,这样才称得上进入了「工作状态」,否则你的一次松懈,一段不太好的表现,很可能导致 C 落在你的头上,岗位越高,摔得越重。
+
+有了角色认知,再说下对绩效的认知。
+
+第一,团队很优秀,是不是不用背 C?不是!大厂的 C 都是强制分配的,再优秀的团队也会有 C。所以团队越厉害,竞争越惨烈。
+
+第二,完成了 KPI,没有工作失误,是不是就万事大吉,不用背 C?不是,绩效是相对的,你必须清楚你在团队所处的位置,你在老板眼中的排序,慢慢练出这种嗅觉。
+
+懂了上面这些道理,很自然就能知道打 C 的逻辑,C 会集中在两类人上:
+
+> 1、工作表现称不上角色要求的人。
+>
+> 2、在老板眼里排序靠后,就算离开,对团队影响也很小的人。
+
+要规避 C,有两种打法。
+
+第 1 种是短期打法:抓关键事件,能不能找出 1-2 件事,体现出你的独特价值(比如本身影响力很大的项目,或者是领导最重视的事),相当于让你的排序有了最基本的保障。
+
+这种打法,你不能等到评价时再去改变,一定是在前期就抓住机会,承担起最有挑战的任务,然后全力以赴,做好了拿 A,不弄砸也不至于背 C,就怕静水潜流,躺平了去工作。
+
+第 2 种是长期打法:通过一步步信任的建立,成为团队的核心人员或者是老板的心腹,具备不可替代性。
+
+上面两种打法都是大的思路,还有很多锦上添花的技巧,比如:加强主动汇报(抹平领导的信息差)、让关键干系人给你点赞(能影响到你领导做出绩效决策的人)。
+
+## 写在最后
+
+有人的地方就有江湖,有江湖就一定有规则,大厂平面看似平静,其实在绩效考核、晋升等利益点面前,都是一场厮杀。
+
+当大家攻山头的能力都很强时,**到底做成什么样才算做好了?**当你弄清楚了这个玄机,职场也就看透了。
+
+如果这篇文章让你有一点启发,来个点赞和在看呀!我是武哥,我们下期见!
diff --git a/docs/high-quality-technical-articles/work/get-into-work-mode-quickly-when-you-join-a-company.md b/docs/high-quality-technical-articles/work/get-into-work-mode-quickly-when-you-join-a-company.md
new file mode 100644
index 0000000000000000000000000000000000000000..30b4e1cc0f37aaec5f9cdeaaab26df7cae068079
--- /dev/null
+++ b/docs/high-quality-technical-articles/work/get-into-work-mode-quickly-when-you-join-a-company.md
@@ -0,0 +1,94 @@
+---
+title: 新入职一家公司如何快速进入工作状态
+category: 技术文章精选集
+tag:
+ - 工作
+---
+
+> **推荐语**:强烈建议每一位即将入职/在职的小伙伴看看这篇文章,看完之后可以帮助你少踩很多坑。整篇文章逻辑清晰,内容全面!
+>
+>
+>
+> **原文地址**:https://www.cnblogs.com/hunternet/p/14675348.html
+
+
+
+一年一度的金三银四跳槽大戏即将落幕,相信很多跳槽的小伙伴们已经找到了心仪的工作,即将或已经有了新的开始。
+
+相信有过跳槽经验的小伙伴们都知道,每到一个新的公司面临的可能都是新的业务、新的技术、新的团队......这些可能会打破你原来工作思维、编码习惯、合作方式......
+
+而于公司而言,又不能给你几个月的时间去慢慢的熟悉。这个时候,如何快速进入工作状态,尽快发挥自己的价值是非常重要的。
+
+有些人可能会很幸运,入职的公司会有完善的流程与机制,通过一带一、各种培训等方式可以在短时间内快速的让新人进入工作状态。有些人可能就没有那么幸运了,就比如我在几年前跳槽进入某厂的时候,当时还没有像我们现在这么完善的带新人融入的机制,又赶上团队最忙的一段时间,刚一入职的当天下午就让给了我几个线上问题去排查,也没有任何的文档和培训。遇到情况,很多人可能会因为难以快速适应,最终承受不起压力而萌生退意。
+
+
+
+那么,**我们应该如何去快速的让自己进入工作状态,适应新的工作节奏呢?**
+
+新的工作面对着一堆的代码仓库,很多人常常感觉无从下手。但回顾一下自己过往的工作与项目的经验,我们可以发现它们有着异曲同工之处。当开始一个新的项目,一般会经历几个步骤:需求->设计->开发->测试->发布,就这么循环往复,我们完成了一个又一个的项目。
+
+
+
+而在这个过程中主要有四个方面的知识那就是业务、技术、项目与团队贯穿始终。新入职一家公司,我们第一阶段的目标就是要具备能够跟着团队做项目的能力,因此我们所应尽快掌握的知识点也要从这四个方面入手。
+
+## 业务
+
+很多人可能会认为作为一个技术人,最应该了解的不应该是技术吗?于是他们在进入一家公司后,就迫不及待的研究起来了一些技术文档,系统架构,甚至抱起来源代码就开始“啃”,如果你也是这么做的,那就大错特错了!在几乎所有的公司里,技术都是作为一个工具存在的,虽然它很重要,但是它也是为了承载业务所存在的,技术解决了如何做的问题,而业务却告诉我们,做什么,为什么做。一旦脱离了业务,那么技术的存在将毫无意义。
+
+想要了解业务,有两个非常重要的方式
+
+**一是靠问**
+
+如果你加入的团队,有着完善的业务培训机制,详尽的需求文档,也许你不需要过多的询问就可以了解业务,但这只是理想中的情况,大多数公司是没有这个条件的。因此我们只能靠问。
+
+这里不得不提的是,作为一个新人一定要有一定的脸皮厚度,不懂就要问。我见过很多新人会因为内向、腼腆,遇到疑问总是不好意思去问,这导致他们很长一段时间都难以融入团队、承担更重要的责任。不怕要怕挨训、怕被怼,而且我相信绝对多数的程序员还是很好沟通的!
+
+**二是靠测试**
+
+我认为测试绝对是一个人快速了解团队业务的方式。通过测试我们可以走一走自己团队所负责项目的整体流程,如果遇到自己走不下去或想不通的地方及时去问,在这个过程中我们自然而然的就可以快速的了解到核心的业务流程。
+
+在了解业务的过程中,我们应该注意的是不要让自己过多的去追求细节,我们的目的是先能够整体了解业务流程,我们面向哪些用户,提供了哪些服务......
+
+## 技术
+
+在我们初步了解完业务之后,就该到技术了,也许你已经按捺不住翻开源代码的准备了,但还是要先提醒你一句先不要着急。
+
+这个时候我们应该先按照自己了解到的业务,结合自己过往的工作经验去思考一下如果是自己去实现这个系统,应该如何去做?这一步很重要,它可以在后面我们具体去了解系统的技术实现的时候去对比一下与自己的实现思路有哪些差异,为什么会有这些差异,哪些更好,哪些不好,对于不好我们可以提出自己的意见,对于更好的我们可以吸收学习为己用!
+
+接下来,我们就是要了解技术了,但也不是一上来就去翻源代码。 **应该按照从宏观到细节,由外而内逐步地对系统进行分析。**
+
+首先,我们应该简单的了解一下 **自己团队/项目的所用到的技术栈** ,Java 还是.NET、亦或是多种语言并存,项目是前后端分离还是服务端全包,使用的数据库是 MySQL 还是 PostgreSQL......,这样我们可能会对所用到的技术和框架,以及自己所负责的内容有一定的预期,这一点有的人可能在面试的时候就会简单了解过。
+
+下一步,我们应该了解的是 **系统的宏观业务架构** 。自己的团队主要负责哪些系统,每个系统又主要包含哪些模块,又与哪些外部系统进行交互......对于这些,最好可以通过流程图或者思维导图等方式整理出来。
+
+然后,我们要做的是看一下 **自己的团队提供了哪些对外的接口或者服务** 。每个接口和服务所提供功能是什么。这一点我们可以继续去测试自己的系统,这个时候我们要看一看主要流程中主要包含了哪些页面,每个页面又调用了后端的哪些接口,每个后端接口又对应着哪个代码仓库。(如果是单纯做后端服务的,可以看一下我们提供了哪些服务,又有哪些上游服务,每个上游服务调用自己团队的哪些服务......),同样我们应该用画图的形式整理出来。
+
+接着,我们要了解一下 **自己的系统或服务又依赖了哪些外部服务** ,也就是说需要哪些外部系统的支持,这些服务也许是团队之外、公司之外,也可能是其他公司提供的。这个时候我们可以简单的进入代码看一下与外部系统的交互是怎么做的,包括通讯框架(REST、RPC)、通讯协议......
+
+到了代码层面,我们首先应该了解每个模块代码的层次结构,一个模块分了多少层,每个层次的职责是什么,了解了这个就对系统的整个设计有了初步的概念,紧接着就是代码的目录结构、配置文件的位置。
+
+最后,我们可以寻找一个示例,可以是一个接口,一个页面,让我们的思路跟随者代码的运行的路线,从入参到出参,完整的走一遍来验证一下我们之前的了解。
+
+到了这里我们对于技术层面的了解就可以先告一段落了,我们的目的知识对系统有一个初步的认知,更细节的东西,后面我们会有大把的时间去了解
+
+## 项目与团队
+
+上面我们提到,新入职一家公司,第一阶段的目标是有跟着团队做项目的能力,接下来我们要了解的就是项目是如何运作的。
+
+我们应该把握从需求设计到代码编写入库最终到发布上线的整个过程中的一些关键点。例如项目采用敏捷还是瀑布的模式,一个迭代周期是多长,需求的来源以及展现形式,有没有需求评审,代码的编写规范是什么,编写完成后如何构建,如何入库,有没有提交规范,如何交付测试,发布前的准备是什么,发布工具如何使用......
+
+关于项目我们只需要观察同事,或者自己亲身经历一个迭代的开发,就能够大概了解清楚。
+
+在了解项目运作的同时,我们还应该去了解团队,同样我们应该先从外部开始,我们对接了哪些外部团队,比如需求从哪里来,是否对接公司外部的团队,提供服务的上游团队有哪些,依赖的下游团队有哪些,团队之间如何沟通,常用的沟通方式是什么.......
+
+接下来则是团队内部,团队中有哪些角色,每个人的职责是什么,这样遇到问题我们也可以清楚的找到对应的同事寻求帮助。是否有一些定期的活动与会议,例如每日站会、周例会,是否有一些约定俗成的规矩,是否有一些内部评审,分享机制......
+
+## 总结
+
+新入职一家公司,面临新的工作挑战,能够尽快进入工作状态,实现自己的价值,将会给你带来一个好的开始。
+
+作为一个程序员,能够尽快进入工作状态,意味着我们首先应该具备跟着团队做项目的能力,这里我站在了一个后端开发的角度上从业务、技术、项目与团队四个方面总结了一些方法和经验。
+
+关于如何快速进入工作状态,如果你有好的方法与建议,欢迎在评论区留言。
+
+最后我们用一张思维导图来回顾一下这篇文章的内容。如果你觉得这篇文章对你有所帮助,可以关注文末公众号,我会经常分享一些自己成长过程中的经验与心得,与大家一起学习与进步。
diff --git a/docs/home.md b/docs/home.md
new file mode 100644
index 0000000000000000000000000000000000000000..b7e55b1984e8a63111d6a9958c0d95462fdfcd33
--- /dev/null
+++ b/docs/home.md
@@ -0,0 +1,435 @@
+---
+icon: creative
+title: JavaGuide(Java学习&面试指南)
+---
+
+::: tip 友情提示
+
+- **面试专版**:准备 Java 面试的小伙伴可以考虑面试专版:**[《Java 面试指北 》](./zhuanlan/java-mian-shi-zhi-bei.md)** (质量很高,专为面试打造,配合 JavaGuide 食用)。
+- **知识星球**:专属面试小册/一对一交流/简历修改/专属求职指南,欢迎加入 **[JavaGuide 知识星球](./about-the-author/zhishixingqiu-two-years.md)**(点击链接即可查看星球的详细介绍,一定确定自己真的需要再加入)。
+- **使用建议** :有水平的面试官都是顺着项目经历挖掘技术问题。一定不要死记硬背技术八股文!详细的学习建议请参考:[JavaGuide 使用建议](./javaguide/use-suggestion.md)。
+- **求个Star**:如果觉得 JavaGuide 的内容对你有帮助的话,还请点个免费的 Star,这是对我最大的鼓励,感谢各位一起同行,共勉!Github 地址:[https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide) 。
+- **转载须知**:以下所有文章如非文首说明为转载皆为 JavaGuide 原创,转载请在文首注明出处。如发现恶意抄袭/搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!
+
+:::
+
+
+
+[](https://github.com/Snailclimb/JavaGuide)
+
+[](https://javaguide.cn/)
+
+
+
+
+[GitHub](https://github.com/Snailclimb/JavaGuide) | [Gitee](https://gitee.com/SnailClimb/JavaGuide)
+
+
+
+[](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)
+
+## Java
+
+### 基础
+
+**知识点/面试题总结** : (必看:+1: ):
+
+- [Java 基础常见知识点&面试题总结(上)](./java/basis/java-basic-questions-01.md)
+- [Java 基础常见知识点&面试题总结(中)](./java/basis/java-basic-questions-02.md)
+- [Java 基础常见知识点&面试题总结(下)](./java/basis/java-basic-questions-03.md)
+
+**重要知识点详解**:
+
+- [为什么 Java 中只有值传递?](./java/basis/why-there-only-value-passing-in-java.md)
+- [Java 序列化详解](./java/basis/serialization.md)
+- [泛型&通配符详解](./java/basis/generics-and-wildcards.md)
+- [Java 反射机制详解](./java/basis/reflection.md)
+- [Java 代理模式详解](./java/basis/proxy.md)
+- [BigDecimal 详解](./java/basis/bigdecimal.md)
+- [Java 魔法类 Unsafe 详解](./java/basis/unsafe.md)
+- [Java SPI 机制详解](./java/basis/spi.md)
+- [Java 语法糖详解](./java/basis/syntactic-sugar.md)
+
+### 集合
+
+**知识点/面试题总结**:
+
+- [Java 集合常见知识点&面试题总结(上)](./java/collection/java-collection-questions-01.md) (必看 :+1:)
+- [Java 集合常见知识点&面试题总结(下)](./java/collection/java-collection-questions-02.md) (必看 :+1:)
+- [Java 容器使用注意事项总结](./java/collection/java-collection-precautions-for-use.md)
+
+**源码分析**:
+
+- [ArrayList 核心源码+扩容机制分析](./java/collection/arraylist-source-code.md)
+- [LinkedList 核心源码分析](./java/collection/linkedlist-source-code.md)
+- [HashMap 核心源码+底层数据结构分析](./java/collection/hashmap-source-code.md)
+- [ConcurrentHashMap 核心源码+底层数据结构分析](./java/collection/concurrent-hash-map-source-code.md)
+- [LinkedHashMap 核心源码分析](./java/collection/linkedhashmap-source-code.md)
+- [CopyOnWriteArrayList 核心源码分析](./java/collection/copyonwritearraylist-source-code.md)
+- [ArrayBlockingQueue 核心源码分析](./java/collection/arrayblockingqueue-source-code.md)
+
+### IO
+
+- [IO 基础知识总结](./java/io/io-basis.md)
+- [IO 设计模式总结](./java/io/io-design-patterns.md)
+- [IO 模型详解](./java/io/io-model.md)
+- [NIO 核心知识总结](./java/io/nio-basis.md)
+
+### 并发
+
+**知识点/面试题总结** : (必看 :+1:)
+
+- [Java 并发常见知识点&面试题总结(上)](./java/concurrent/java-concurrent-questions-01.md)
+- [Java 并发常见知识点&面试题总结(中)](./java/concurrent/java-concurrent-questions-02.md)
+- [Java 并发常见知识点&面试题总结(下)](./java/concurrent/java-concurrent-questions-03.md)
+
+**重要知识点详解**:
+
+- [JMM(Java 内存模型)详解](./java/concurrent/jmm.md)
+- **线程池**:[Java 线程池详解](./java/concurrent/java-thread-pool-summary.md)、[Java 线程池最佳实践](./java/concurrent/java-thread-pool-best-practices.md)
+- [ThreadLocal 详解](./java/concurrent/threadlocal.md)
+- [Java 并发容器总结](./java/concurrent/java-concurrent-collections.md)
+- [Atomic 原子类总结](./java/concurrent/atomic-classes.md)
+- [AQS 详解](./java/concurrent/aqs.md)
+- [CompletableFuture 详解](./java/concurrent/completablefuture-intro.md)
+
+### JVM (必看 :+1:)
+
+JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8](https://docs.oracle.com/javase/specs/jvms/se8/html/index.html) 和周志明老师的[《深入理解 Java 虚拟机(第 3 版)》](https://book.douban.com/subject/34907497/) (强烈建议阅读多遍!)。
+
+- **[Java 内存区域](./java/jvm/memory-area.md)**
+- **[JVM 垃圾回收](./java/jvm/jvm-garbage-collection.md)**
+- [类文件结构](./java/jvm/class-file-structure.md)
+- **[类加载过程](./java/jvm/class-loading-process.md)**
+- [类加载器](./java/jvm/classloader.md)
+- [【待完成】最重要的 JVM 参数总结(翻译完善了一半)](./java/jvm/jvm-parameters-intro.md)
+- [【加餐】大白话带你认识 JVM](./java/jvm/jvm-intro.md)
+- [JDK 监控和故障处理工具](./java/jvm/jdk-monitoring-and-troubleshooting-tools.md)
+
+### 新特性
+
+- **Java 8**:[Java 8 新特性总结(翻译)](./java/new-features/java8-tutorial-translate.md)、[Java8 常用新特性总结](./java/new-features/java8-common-new-features.md)
+- [Java 9 新特性概览](./java/new-features/java9.md)
+- [Java 10 新特性概览](./java/new-features/java10.md)
+- [Java 11 新特性概览](./java/new-features/java11.md)
+- [Java 12 & 13 新特性概览](./java/new-features/java12-13.md)
+- [Java 14 & 15 新特性概览](./java/new-features/java14-15.md)
+- [Java 16 新特性概览](./java/new-features/java16.md)
+- [Java 17 新特性概览](./java/new-features/java17.md)
+- [Java 18 新特性概览](./java/new-features/java18.md)
+- [Java 19 新特性概览](./java/new-features/java19.md)
+- [Java 20 新特性概览](./java/new-features/java20.md)
+
+## 计算机基础
+
+### 操作系统
+
+- [操作系统常见知识点&面试题总结(上)](./cs-basics/operating-system/operating-system-basic-questions-01.md)
+- [操作系统常见知识点&面试题总结(下)](./cs-basics/operating-system/operating-system-basic-questions-02.md)
+- **Linux**:
+ - [后端程序员必备的 Linux 基础知识总结](./cs-basics/operating-system/linux-intro.md)
+ - [Shell 编程基础知识总结](./cs-basics/operating-system/shell-intro.md)
+
+### 网络
+
+**知识点/面试题总结**:
+
+- [计算机网络常见知识点&面试题总结(上)](./cs-basics/network/other-network-questions.md)
+- [计算机网络常见知识点&面试题总结(下)](./cs-basics/network/other-network-questions2.md)
+- [谢希仁老师的《计算机网络》内容总结(补充)](./cs-basics/network/computer-network-xiexiren-summary.md)
+
+**重要知识点详解**:
+
+- [OSI 和 TCP/IP 网络分层模型详解(基础)](./cs-basics/network/osi-and-tcp-ip-model.md)
+- [应用层常见协议总结(应用层)](./cs-basics/network/application-layer-protocol.md)
+- [HTTP vs HTTPS(应用层)](./cs-basics/network/http-vs-https.md)
+- [HTTP 1.0 vs HTTP 1.1(应用层)](./cs-basics/network/http1.0-vs-http1.1.md)
+- [HTTP 常见状态码(应用层)](./cs-basics/network/http-status-codes.md)
+- [DNS 域名系统详解(应用层)](./cs-basics/network/dns.md)
+- [TCP 三次握手和四次挥手(传输层)](./cs-basics/network/tcp-connection-and-disconnection.md)
+- [TCP 传输可靠性保障(传输层)](./cs-basics/network/tcp-reliability-guarantee.md)
+- [ARP 协议详解(网络层)](./cs-basics/network/arp.md)
+- [NAT 协议详解(网络层)](./cs-basics/network/nat.md)
+- [网络攻击常见手段总结(安全)](./cs-basics/network/network-attack-means.md)
+
+### 数据结构
+
+**图解数据结构:**
+
+- [线性数据结构 :数组、链表、栈、队列](./cs-basics/data-structure/linear-data-structure.md)
+- [图](./cs-basics/data-structure/graph.md)
+- [堆](./cs-basics/data-structure/heap.md)
+- [树](./cs-basics/data-structure/tree.md):重点关注[红黑树](./cs-basics/data-structure/red-black-tree.md)、B-,B+,B\*树、LSM 树
+
+其他常用数据结构:
+
+- [布隆过滤器](./cs-basics/data-structure/bloom-filter.md)
+
+### 算法
+
+算法这部分内容非常重要,如果你不知道如何学习算法的话,可以看下我写的:
+
+- [算法学习书籍+资源推荐](https://www.zhihu.com/question/323359308/answer/1545320858) 。
+- [如何刷 Leetcode?](https://www.zhihu.com/question/31092580/answer/1534887374)
+
+**常见算法问题总结**:
+
+- [几道常见的字符串算法题总结](./cs-basics/algorithms/string-algorithm-problems.md)
+- [几道常见的链表算法题总结](./cs-basics/algorithms/linkedlist-algorithm-problems.md)
+- [剑指 offer 部分编程题](./cs-basics/algorithms/the-sword-refers-to-offer.md)
+- [十大经典排序算法](./cs-basics/algorithms/10-classical-sorting-algorithms.md)
+
+另外,[GeeksforGeeks](https://www.geeksforgeeks.org/fundamentals-of-algorithms/) 这个网站总结了常见的算法 ,比较全面系统。
+
+## 数据库
+
+### 基础
+
+- [数据库基础知识总结](./database/basis.md)
+- [NoSQL 基础知识总结](./database/nosql.md)
+- [字符集详解](./database/character-set.md)
+- SQL :
+ - [SQL 语法基础知识总结](./database/sql/sql-syntax-summary.md)
+ - [SQL 常见面试题总结](./database/sql/sql-questions-01.md)
+
+### MySQL
+
+**知识点/面试题总结:**
+
+- **[MySQL 常见知识点&面试题总结](./database/mysql/mysql-questions-01.md)** (必看 :+1:)
+- [MySQL 高性能优化规范建议总结](./database/mysql/mysql-high-performance-optimization-specification-recommendations.md)
+
+**重要知识点:**
+
+- [MySQL 索引详解](./database/mysql/mysql-index.md)
+- [MySQL 事务隔离级别图文详解)](./database/mysql/transaction-isolation-level.md)
+- [MySQL 三大日志(binlog、redo log 和 undo log)详解](./database/mysql/mysql-logs.md)
+- [InnoDB 存储引擎对 MVCC 的实现](./database/mysql/innodb-implementation-of-mvcc.md)
+- [SQL 语句在 MySQL 中的执行过程](./database/mysql/how-sql-executed-in-mysql.md)
+- [MySQL 查询缓存详解](./database/mysql/mysql-query-cache.md)
+- [MySQL 执行计划分析](./database/mysql/mysql-query-execution-plan.md)
+- [MySQL 自增主键一定是连续的吗](./database/mysql/mysql-auto-increment-primary-key-continuous.md)
+- [MySQL 时间类型数据存储建议](./database/mysql/some-thoughts-on-database-storage-time.md)
+- [MySQL 隐式转换造成索引失效](./database/mysql/index-invalidation-caused-by-implicit-conversion.md)
+
+### Redis
+
+**知识点/面试题总结** : (必看:+1: ):
+
+- [Redis 常见知识点&面试题总结(上)](./database/redis/redis-questions-01.md)
+- [Redis 常见知识点&面试题总结(下)](./database/redis/redis-questions-02.md)
+
+**重要知识点:**
+
+- [3 种常用的缓存读写策略详解](./database/redis/3-commonly-used-cache-read-and-write-strategies.md)
+- [Redis 5 种基本数据结构详解](./database/redis/redis-data-structures-01.md)
+- [Redis 3 种特殊数据结构详解](./database/redis/redis-data-structures-02.md)
+- [Redis 持久化机制详解](./database/redis/redis-persistence.md)
+- [Redis 内存碎片详解](./database/redis/redis-memory-fragmentation.md)
+- [Redis 常见阻塞原因总结](./database/redis/redis-common-blocking-problems-summary.md)
+- [Redis 集群详解](./database/redis/redis-cluster.md)
+
+### MongoDB
+
+- [MongoDB 常见知识点&面试题总结(上)](./database/mongodb/mongodb-questions-01.md)
+- [MongoDB 常见知识点&面试题总结(下)](./database/mongodb/mongodb-questions-02.md)
+
+## 搜索引擎
+
+[Elasticsearch 常见面试题总结(付费)](./database/elasticsearch/elasticsearch-questions-01.md)
+
+
+
+## 开发工具
+
+### Maven
+
+[Maven 核心概念总结](./tools/maven/maven-core-concepts.md)
+
+### Gradle
+
+[Gradle 核心概念总结](./tools/gradle/gradle-core-concepts.md)(可选,目前国内还是使用 Maven 普遍一些)
+
+### Docker
+
+- [Docker 核心概念总结](./tools/docker/docker-intro.md)
+- [Docker 实战](./tools/docker/docker-in-action.md)
+
+### Git
+
+- [Git 核心概念总结](./tools/git/git-intro.md)
+- [GitHub 实用小技巧总结](./tools/git/github-tips.md)
+
+## 系统设计
+
+- [系统设计常见面试题总结](./system-design/system-design-questions.md)
+- [设计模式常见面试题总结](./system-design/design-pattern.md)
+
+### 基础
+
+- [RestFul API 简明教程](./system-design/basis/RESTfulAPI.md)
+- [软件工程简明教程简明教程](./system-design/basis/software-engineering.md)
+- [代码命名指南](./system-design/basis/naming.md)
+- [代码重构指南](./system-design/basis/refactoring.md)
+- [单元测试指南](./system-design/basis/unit-test.md)
+
+### 常用框架
+
+#### Spring/SpringBoot (必看 :+1:)
+
+**知识点/面试题总结** :
+
+- [Spring 常见知识点&面试题总结](./system-design/framework/spring/spring-knowledge-and-questions-summary.md)
+- [SpringBoot 常见知识点&面试题总结](./system-design/framework/spring/springboot-knowledge-and-questions-summary.md)
+- [Spring/Spring Boot 常用注解总结](./system-design/framework/spring/spring-common-annotations.md)
+- [SpringBoot 入门指南](https://github.com/Snailclimb/springboot-guide)
+
+**重要知识点详解**:
+
+- [Spring 事务详解](./system-design/framework/spring/spring-transaction.md)
+- [Spring 中的设计模式详解](./system-design/framework/spring/spring-design-patterns-summary.md)
+- [SpringBoot 自动装配原理详解](./system-design/framework/spring/spring-boot-auto-assembly-principles.md)
+
+#### MyBatis
+
+[MyBatis 常见面试题总结](./system-design/framework/mybatis/mybatis-interview.md)
+
+### 安全
+
+#### 认证授权
+
+- [认证授权基础概念详解](./system-design/security/basis-of-authority-certification.md)
+- [JWT 基础概念详解](./system-design/security/jwt-intro.md)
+- [JWT 优缺点分析以及常见问题解决方案](./system-design/security/advantages-and-disadvantages-of-jwt.md)
+- [SSO 单点登录详解](./system-design/security/sso-intro.md)
+- [权限系统设计详解](./system-design/security/design-of-authority-system.md)
+- [常见加密算法总结](./system-design/security/encryption-algorithms.md)
+
+#### 数据脱敏
+
+数据脱敏说的就是我们根据特定的规则对敏感信息数据进行变形,比如我们把手机号、身份证号某些位数使用 \* 来代替。
+
+#### 敏感词过滤
+
+[敏感词过滤方案总结](./system-design/security/sentive-words-filter.md)
+
+### 定时任务
+
+[Java 定时任务详解](./system-design/schedule-task.md)
+
+### Web 实时消息推送
+
+[Web 实时消息推送详解](./system-design/web-real-time-message-push.md)
+
+## 分布式
+
+### 理论&算法&协议
+
+- [CAP 理论和 BASE 理论解读](./distributed-system/protocol/cap-and-base-theorem.md)
+- [Paxos 算法解读](./distributed-system/protocol/paxos-algorithm.md)
+- [Raft 算法解读](./distributed-system/protocol/raft-algorithm.md)
+- [Gossip 协议详解](./distributed-system/protocol/gossip-protocl.md)
+
+### RPC
+
+- [RPC 基础知识总结](./distributed-system/rpc/rpc-intro.md)
+- [Dubbo 常见知识点&面试题总结](./distributed-system/rpc/dubbo.md)
+
+### ZooKeeper
+
+> 这两篇文章可能有内容重合部分,推荐都看一遍。
+
+- [ZooKeeper 相关概念总结(入门)](./distributed-system/distributed-process-coordination/zookeeper/zookeeper-intro.md)
+- [ZooKeeper 相关概念总结(进阶)](./distributed-system/distributed-process-coordination/zookeeper/zookeeper-plus.md)
+
+### API 网关
+
+- [API 网关基础知识总结](./distributed-system/api-gateway.md)
+- [Spring Cloud Gateway 常见知识点&面试题总结](./distributed-system/spring-cloud-gateway-questions.md)
+
+### 分布式 ID
+
+- [分布式 ID 常见知识点&面试题总结](./distributed-system/distributed-id.md)
+- [分布式 ID 设计指南](./distributed-system/distributed-id-design.md)
+
+### 分布式锁
+
+- [分布式锁介绍](https://javaguide.cn/distributed-system/distributed-lock.html)
+- [分布式锁常见实现方案总结](https://javaguide.cn/distributed-system/distributed-lock-implementations.html)
+
+### 分布式事务
+
+[分布式事务常见知识点&面试题总结](./distributed-system/distributed-transaction.md)
+
+### 分布式配置中心
+
+[分布式配置中心常见知识点&面试题总结](./distributed-system/distributed-configuration-center.md)
+
+## 高性能
+
+### 数据库读写分离&分库分表
+
+[数据库读写分离和分库分表常见知识点&面试题总结](./high-performance/read-and-write-separation-and-library-subtable.md)
+
+### 负载均衡
+
+[负载均衡常见知识点&面试题总结](./high-performance/load-balancing.md)
+
+### SQL 优化
+
+[常见 SQL 优化手段总结](./high-performance/sql-optimization.md)
+
+### CDN
+
+[CDN(内容分发网络)常见知识点&面试题总结](./high-performance/cdn.md)
+
+### 消息队列
+
+- [消息队列基础知识总结](./high-performance/message-queue/message-queue.md)
+- [Disruptor 常见知识点&面试题总结](./high-performance/message-queue/disruptor-questions.md)
+- [RabbitMQ 常见知识点&面试题总结](./high-performance/message-queue/rabbitmq-questions.md)
+- [RocketMQ 常见知识点&面试题总结](./high-performance/message-queue/rocketmq-questions.md)
+- [Kafka 常常见知识点&面试题总结](./high-performance/message-queue/kafka-questions-01.md)
+
+## 高可用
+
+[高可用系统设计指南](./high-availability/high-availability-system-design.md)
+
+### 冗余设计
+
+[冗余设计详解](./high-availability/redundancy.md)
+
+### 限流
+
+[服务限流详解](./high-availability/limit-request.md)
+
+### 降级&熔断
+
+[降级&熔断详解](./high-availability/fallback-and-circuit-breaker.md)
+
+### 超时&重试
+
+[超时&重试详解](./high-availability/timeout-and-retry.md)
+
+### 集群
+
+相同的服务部署多份,避免单点故障。
+
+### 灾备设计和异地多活
+
+**灾备** = 容灾 + 备份。
+
+- **备份**:将系统所产生的的所有重要数据多备份几份。
+- **容灾**:在异地建立两个完全相同的系统。当某个地方的系统突然挂掉,整个应用系统可以切换到另一个,这样系统就可以正常提供服务了。
+
+**异地多活** 描述的是将服务部署在异地并且服务同时对外提供服务。和传统的灾备设计的最主要区别在于“多活”,即所有站点都是同时在对外提供服务的。异地多活是为了应对突发状况比如火灾、地震等自然或者人为灾害。
+
+## Star 趋势
+
+
+
+## 公众号
+
+如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号“**JavaGuide**”。
+
+
diff --git a/docs/idea-tutorial/idea-plugins/camel-case.md b/docs/idea-tutorial/idea-plugins/camel-case.md
deleted file mode 100644
index 5456378f4b230b6aa98c3e83f7bb84e7f887b99c..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/camel-case.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-title: Camel Case:命名之间快速切换
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-非常有用!这个插件可以实现包含6种常见命名格式之间的切换。并且,你还可以对转换格式进行相关配置(转换格式),如下图所示:
-
-
-
-有了这个插件之后,你只需要使用快捷键 `shift+option+u(mac)` / `shift+alt+u` 对准你要修改的变量或者方法名字,就能实现在多种格式之间切换了,如下图所示:
-
-
-
-如果你突然忘记快捷键的话,可以直接在IDEA的菜单栏的 Edit 部分找到。
-
-
-
-使用这个插件对开发效率提升高吗?拿我之前项目组的情况举个例子:
-
-我之前有一个项目组的测试名字是驼峰这种形式: `ShouldReturnTicketWhenRobotSaveBagGiven1LockersWith2FreeSpace` 。但是,使用驼峰形式命名测试方法的名字不太明显,一般建议用下划线_的形式: `should_return_ticket_when_robot_save_bag_given_1_lockers_with_2_free_space`
-
-如果我们不用这个插件,而是手动去一个一个改的话,工作量想必会很大,而且正确率也会因为手工的原因降低。
-
->
diff --git a/docs/idea-tutorial/idea-plugins/code-glance.md b/docs/idea-tutorial/idea-plugins/code-glance.md
deleted file mode 100644
index 9345ab427bb8fea5cb8b50bc60fdd65348c74172..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/code-glance.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-title: CodeGlance:代码微型地图
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-CodeGlance提供一个代码的微型地图,当你的类比较多的时候可以帮忙你快速定位到要去的位置。这个插件在我们日常做普通开发的时候用处不大,不过,在你阅读源码的时候还是很有用的,如下图所示:
-
-
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/code-statistic.md b/docs/idea-tutorial/idea-plugins/code-statistic.md
deleted file mode 100644
index 1d60c81bad67d51a414a4a38c090d811cf2a49ea..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/code-statistic.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: Statistic:项目代码统计
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-编程是一个很奇妙的事情,大部分的我们把大部分时间实际都花在了复制粘贴,而后修改代码上面。
-
-很多时候,我们并不关注代码质量,只要功能能实现,我才不管一个类的代码有多长、一个方法的代码有多长。
-
-因此,我们经常会碰到让自己想要骂街的项目,不过,说真的,你自己写的代码也有极大可能被后者 DISS。
-
-为了快速分析项目情况,判断这个项目是不是一个“垃圾”项目,有一个方法挺简单的。
-
-那就是**对代码的总行数、单个文件的代码行数、注释行数等信息进行统计。**
-
-**怎么统计呢?**
-
-首先想到的是 Excel 。不过,显然太麻烦了。
-
-**有没有专门用于代码统计的工具呢?**
-
-基于Perl语言开发的cloc(count lines of code)或许可以满足你的要求。
-
-**有没有什么更简单的办法呢?**
-
-如果你使用的是 IDEA 进行开发的话,推荐你可以使用一下 **Statistic** 这个插件。
-
-有了这个插件之后你可以非常直观地看到你的项目中所有类型的文件的信息比如数量、大小等等,可以帮助你更好地了解你们的项目。
-
-
-
-你还可以使用它看所有类的总行数、有效代码行数、注释行数、以及有效代码比重等等这些东西。
-
-
-
-如果,你担心插件过多影响IDEA速度的话,可以只在有代码统计需求的时候开启这个插件,其他时间禁用它就完事了!
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/git-commit-template.md b/docs/idea-tutorial/idea-plugins/git-commit-template.md
deleted file mode 100644
index c75dae11c795322e28fa3ca773d9ca48eddef7d2..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/git-commit-template.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-title: Git Commit Template:提交代码格式规范
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-没有安装这个插件之前,我们使用IDEA提供的Commit功能提交代码是下面这样的:
-
-
-
-使用了这个插件之后是下面这样的,提供了一个commit信息模板的输入框:
-
-
-
-完成之后的效果是这样的:
-
-
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/gson-format.md b/docs/idea-tutorial/idea-plugins/gson-format.md
deleted file mode 100644
index 56750e1cb050d7055495a3b28e039840df7ba5f5..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/gson-format.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-title: GsonFormat:JSON转对象
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-GsonFormat 这个插件可以根据Gson库使用的要求,将JSONObject格式的String 解析成实体类。
-
-> 说明:2021.x 版本以上的 IDEA 可以使用:GsonFormatPlus
-
-这个插件使用起来非常简单,我们新建一个类,然后在类中使用快捷键 `option + s`(Mac)或`alt + s` (win)调出操作窗口(**必须在类中使用快捷键才有效**),如下图所示。
-
-
-
-这个插件是一个国人几年前写的,不过已经很久没有更新了,可能会因为IDEA的版本问题有一些小Bug。而且,这个插件无法将JSON转换为Kotlin(这个其实无关痛痒,IDEA自带的就有Java转Kotlin的功能)。
-
-
-
-另外一个与之相似的插件是 **:RoboPOJOGenerator** ,这个插件的更新频率比较快。
-
-`File-> new -> Generate POJO from JSON`
-
-
-
-然后将JSON格式的数据粘贴进去之后,配置相关属性之后选择“*Generate*”
-
-
-
-
-
diff --git a/docs/idea-tutorial/idea-plugins/idea-features-trainer.md b/docs/idea-tutorial/idea-plugins/idea-features-trainer.md
deleted file mode 100644
index a5cb4960c4db2a747722afedce8c07eaea2215b2..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/idea-features-trainer.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-title: IDE Features Trainer:IDEA 交互式教程
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-**有了这个插件之后,你可以在 IDE 中以交互方式学习IDEA最常用的快捷方式和最基本功能。** 非常非常非常方便!强烈建议大家安装一个,尤其是刚开始使用IDEA的朋友。
-
-当我们安装了这个插件之后,你会发现我们的IDEA 编辑器的右边多了一个“**Learn**”的选项,我们点击这个选项就可以看到如下界面。
-
-
-
-我们选择“Editor Basics”进行,然后就可以看到如下界面,这样你就可以按照指示来练习了!非常不错!
-
-
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/idea-themes.md b/docs/idea-tutorial/idea-plugins/idea-themes.md
deleted file mode 100644
index ca38f19f51f9a7f3991fade8fb2bca4f940711a9..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/idea-themes.md
+++ /dev/null
@@ -1,99 +0,0 @@
----
-title: IDEA主题推荐
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-经常有小伙伴问我:“Guide哥,你的IDEA 主题怎么这么好看,能推荐一下不?”。就实在有点不耐烦了,才索性写了这篇文章。
-
-在这篇文章中,我精选了几个比较是和 Java 编码的 IDEA 主题供小伙伴们选择。另外,我自己用的是 One Dark theme 这款。
-
-**注意:以下主题按照使用人数降序排序。**
-
-## [Material Theme UI](https://plugins.jetbrains.com/plugin/8006-material-theme-ui)
-
-**推荐指数** :⭐⭐⭐⭐
-
-这是 IDEA 中使用人数最多的一款主题。
-
-当你安装完这个插件之后,你会发现这个主题本身又提供了多种相关的主题供你选择。
-
-
-
- **Material Deep Ocean** 这款的效果图如下。默认的字体是真的小,小伙伴们需要自行调整一下。
-
-
-
-## [One Dark theme](https://plugins.jetbrains.com/plugin/11938-one-dark-theme)
-
-**推荐指数** :⭐⭐⭐⭐⭐
-
-我比较喜欢的一款(*黄色比较多?*)。 没有花里花哨,简单大气,看起来比较舒服。颜色搭配也很棒,适合编码!
-
-这款主题的效果图如下。
-
-
-
-## [Gradianto](https://plugins.jetbrains.com/plugin/12334-gradianto)
-
-**推荐指数** :⭐⭐⭐⭐⭐
-
-Gradianto这个主题的目标是在保持页面色彩比较层次分明的情况下,让我们因为代码而疲惫的双眼更加轻松。
-
-Gradianto附带了自然界的渐变色,看着挺舒服的。另外,这个主题本身也提供了多种相关的主题供你选择。
-
-
-
-**Gradianto Nature Green** 的效果图如下。
-
-
-
-## [Dark Purple Theme](https://plugins.jetbrains.com/plugin/12100-dark-purple-theme)
-
-**推荐指数** :⭐⭐⭐⭐⭐
-
-这是一款紫色色调的深色主题,喜欢紫色的小伙伴不要错过。
-
-这个主题的效果图如下。个人觉得整体颜色搭配的是比较不错的,适合编码!
-
-
-
-## [Hiberbee Theme](https://plugins.jetbrains.com/plugin/12118-hiberbee-theme)
-
-**推荐指数** :⭐⭐⭐⭐⭐
-
-一款受到了 Monokai Pro 和 MacOS Mojave启发的主题,是一款色彩层次分明的深色主题。
-
-这个主题的效果图如下。看着也是非常赞!适合编码!
-
-
-
-上面推荐的都是偏暗色系的主题,这里我再推荐两款浅色系的主题。
-
-## [Gray Theme](https://plugins.jetbrains.com/plugin/12103-gray-theme)
-
-**推荐指数** :⭐⭐⭐
-
-这是一款对比度比较低的一款浅色主题,不太适合代码阅读,毕竟这款主题是专门为在IntelliJ IDE中使用Markdown而设计的。
-
-这个主题的效果图如下。
-
-
-
-## [Roboticket Light Theme](https://plugins.jetbrains.com/plugin/12191-roboticket-light-theme)
-
-**推荐指数** :⭐⭐⭐
-
-这是一款对比度比较低的浅色主题,不太适合代码阅读。
-
-这个主题的效果图如下。
-
-
-
-## 后记
-
-我个人还是比较偏爱深色系的主题。
-
-小伙伴们比较喜欢哪款主题呢?可以在评论区简单聊聊不?如果你还有其他比较喜欢的主题也可以在评论区说出来供大家参考哦!
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/improve-code.md b/docs/idea-tutorial/idea-plugins/improve-code.md
deleted file mode 100644
index 91b31b4e2327af92c3d113456c519d94abc3ee75..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/improve-code.md
+++ /dev/null
@@ -1,153 +0,0 @@
----
-title: IDEA 代码优化插件推荐
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
- - 代码优化
----
-
-## Lombok:帮你简化代码
-
-之前没有推荐这个插件的原因是觉得已经是人手必备的了。如果你要使用 Lombok 的话,不光是要安装这个插件,你的项目也要引入相关的依赖。
-
-```xml
-
- org.projectlombok
- lombok
- true
-
-```
-
-使用 Lombok 能够帮助我们少写很多代码比如 Getter/Setter、Constructor等等。
-
-关于Lombok的使用,可以查看这篇文章:[《十分钟搞懂Java效率工具Lombok使用与原理》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485385&idx=2&sn=a7c3fb4485ffd8c019e5541e9b1580cd&chksm=cea24802f9d5c1144eee0da52cfc0cc5e8ee3590990de3bb642df4d4b2a8cd07f12dd54947b9&token=1667678311&lang=zh_CN#rd)。
-
-
-## Codota:代码智能提示
-
-我一直在用的一个插件,可以说非常好用了(*我身边的很多大佬平时写代码也会用这个插件*)。
-
-Codota 这个插件用于智能代码补全,它基于数百万Java程序,能够根据程序上下文提示补全代码。相比于IDEA自带的智能提示来说,Codota 的提示更加全面一些。
-
-如果你觉得 IDEA 插件安装的太多比较卡顿的话,不用担心!Codota 插件还有一个对应的在线网站([https://www.codota.com/code](https://www.codota.com/code)),在这个网站上你可以根据代码关键字搜索相关代码示例,非常不错!
-
-我在工作中经常会用到,说实话确实给我带来了很大便利,比如我们搜索 `Files.readAllLines`相关的代码,搜索出来的结果如下图所示:
-
-
-
-另外,Codota 插件的基础功能都是免费的。你的代码也不会被泄露,这点你不用担心。
-
-简单来看看 Codota 插件的骚操作吧!
-
-### 代码智能补全
-
-我们使用`HttpUrlConnection ` 建立一个网络连接是真的样的:
-
-
-
-我们创建线程池现在变成下面这样:
-
-
-
-上面只是为了演示这个插件的强大,实际上创建线程池不推荐使用这种方式, 推荐使用 `ThreadPoolExecutor` 构造函数创建线程池。我下面要介绍的一个阿里巴巴的插件-**Alibaba Java Code Guidelines** 就检测出来了这个问题,所以,`Executors`下面用波浪线标记了出来。
-
-### 代码智能搜索
-
-除了,在写代码的时候智能提示之外。你还可以直接选中代码然后搜索相关代码示例。
-
-
-
-## Alibaba Java Code Guidelines:阿里巴巴 Java 代码规范
-
-阿里巴巴 Java 代码规范,对应的Github地址为:[https://github.com/alibaba/p3c](https://github.com/alibaba/p3c ) 。非常推荐安装!
-
-安装完成之后建议将与语言替换成中文,提示更加友好一点。
-
-
-
-根据官方描述:
-
-> 目前这个插件实现了开发手册中的的53条规则,大部分基于PMD实现,其中有4条规则基于IDEA实现,并且基于IDEA [Inspection](https://www.jetbrains.com/help/idea/code-inspection.html)实现了实时检测功能。部分规则实现了Quick Fix功能,对于可以提供Quick Fix但没有提供的,我们会尽快实现,也欢迎有兴趣的同学加入进来一起努力。 目前插件检测有两种模式:实时检测、手动触发。
-
-上述提到的开发手册也就是在Java开发领域赫赫有名的《阿里巴巴Java开发手册》。
-
-### 手动配置检测规则
-
-你还可以手动配置相关 inspection规则:
-
-
-
-### 使用效果
-
-这个插件会实时检测出我们的代码不匹配它的规则的地方,并且会给出修改建议。比如我们按照下面的方式去创建线程池的话,这个插件就会帮我们检测出来,如下图所示。
-
-
-
-这个可以对应上 《阿里巴巴Java开发手册》 这本书关于创建线程池的方式说明。
-
-
-
-## CheckStyle: Java代码格式规范
-
-### 为何需要CheckStyle插件?
-
-**CheckStyle 几乎是 Java 项目开发必备的一个插件了,它会帮助我们检查 Java 代码的格式是否有问题比如变量命名格式是否有问题、某一行代码的长度是否过长等等。**
-
-在项目上,**通过项目开发人员自我约束来规范代码格式必然是不靠谱的!** 因此,我们非常需要这样一款工具来帮助我们规范代码格式。
-
-如果你看过我写的轮子的话,可以发现我为每一个项目都集成了 CheckStyle,并且设置了 **Git Commit 钩子**,保证在提交代码之前代码格式没有问题。
-
-> **Guide哥造的轮子**(*代码简洁,结构清晰,欢迎学习,欢迎一起完善*):
->
-> 1. [guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) :A custom RPC framework implemented by Netty+Kyro+Zookeeper.(一款基于 Netty+Kyro+Zookeeper 实现的自定义 RPC 框架-附详细实现过程和相关教程)
-> 2. [jsoncat](https://github.com/Snailclimb/jsoncat) :仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架
->
-> **Git 钩子**: Git 能在特定的重要动作比如commit、push发生时触发自定义脚本。 钩子都被存储在 Git 目录下的 `hooks` 子目录中。 也即绝大部分项目中的 `.git/hooks` 。
-
-### 如何在Maven/Gradle项目中集成 Checksytle?
-
-一般情况下,我们都是将其集成在项目中,并设置相应的 Git 钩子。网上有相应的介绍文章,这里就不多提了。
-
-如果你觉得网上的文章不直观的话,可以参考我上面提到了两个轮子:
-
-1. [guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) :Maven项目集成 Checksytle。
-2. [jsoncat](https://github.com/Snailclimb/jsoncat) :Gradle项目集成 Checksytle。
-
-如果你在项目中集成了 Checksytle 的话,每次检测会生成一个 HTML格式的文件告诉你哪里的代码格式不对,这样看着非常不直观。通过 Checksytle插件的话可以非常直观的将项目中存在格式问题的地方显示出来。
-
-
-
-如果你只是自己在本地使用,并不想在项目中集成 Checksytle 的话也可以,只需要下载一个 Checksytle插件就足够了。
-
-### 如何安装?
-
-我们直接在 IDEA 的插件市场即可找到这个插件。我这里已经安装好了。
-
-
-
-安装插件之后重启 IDEA,你会发现就可以在底部菜单栏找到 CheckStyle 了。
-
-
-
-### 如何自定义检测规则?
-
-如果你需要自定义代码格式检测规则的话,可以按照如下方式进行配置(你可以导入用于自定义检测规则的`CheckStyle.xml`文件)。
-
-
-
-### 使用效果
-
-配置完成之后,按照如下方式使用这个插件!
-
-
-
-可以非常清晰的看到:CheckStyle 插件已经根据我们自定义的规则将项目中的代码存在格式问题的地方都检测了出来。
-
-## SonarLint:帮你优化代码
-
-SonarLint 帮助你发现代码的错误和漏洞,就像是代码拼写检查器一样,SonarLint 可以实时显示出代码的问题,并提供清晰的修复指导,以便你提交代码之前就可以解决它们。
-
-
-
-并且,很多项目都集成了 SonarQube,SonarLint 可以很方便地与 SonarQube 集成。
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/interface-beautification.md b/docs/idea-tutorial/idea-plugins/interface-beautification.md
deleted file mode 100644
index 3f3f62fb679a46ad3488934c8eb5a016707508b6..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/interface-beautification.md
+++ /dev/null
@@ -1,67 +0,0 @@
----
-title: IDEA 界面美化插件推荐
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
- - 代码优化
----
-
-
-## Background Image Plus:背景图片
-
-我这里推荐使用国人 Jack Chu 基于 Background Image Plus 开发的最新版本,适用于 2021.x 版本的 IDEA。
-
-前面几个下载量比较高的,目前都还未支持 2021.x 版本的 IDEA。
-
-
-
-通过这个插件,你可以将 IDEA 背景设置为指定的图片,支持随机背景。
-
-效果图如下:
-
-
-
-如果你想要设置随机背景的话,可以通过 IDEA 设置页 **Settings** -> **Appearance & Behaviour** -> **Background Image Plus** 自定义设置项,随机显示目录下的图片为背景图。
-
-## Power Mode II : 代码特效
-
-使用了这个插件之后,写代码会自带特效,适用于 2021.x 版本的 IDEA。 2021.x 版本之前,可以使用 **activate-power-mode** 。
-
-
-
-你可以通过 IDEA 设置页 **Settings** -> **Appearance & Behaviour** -> **Power Mode II** 自定义设置项。
-
-
-
-## Nyan Progress Bar : 进度条美化
-
-可以让你拥有更加漂亮的进度条。
-
-
-
-## Grep Console:控制台输出处理
-
-可以说是必备的一个 IDEA 插件,非常实用!
-
-这个插件主要的功能有两个:
-
-**1. 自定义设置控制台输出颜色**
-
-我们可以在设置中进行相关的配置:
-
-
-
-配置完成之后的 log warn 的效果对比图如下:
-
-
-
-**2. 过滤控制台输出**
-
-
-
-## Rainbow Brackets : 彩虹括号
-
-使用各种鲜明的颜色来展示你的括号,效果图如下。可以看出代码层级变得更加清晰了,可以说非常实用友好了!
-
-
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/jclasslib.md b/docs/idea-tutorial/idea-plugins/jclasslib.md
deleted file mode 100644
index c5f29d2b6578f3a4de12926f94eff97768846616..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/jclasslib.md
+++ /dev/null
@@ -1,93 +0,0 @@
----
-title: jclasslib :一款IDEA字节码查看神器
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
- - 字节码
----
-
-由于后面要分享的一篇文章中用到了这篇文章要推荐的一个插件,所以这里分享一下。非常实用!你会爱上它的!
-
-
-
-**开始推荐 IDEA 字节码查看神器之前,先来回顾一下 Java 字节码是啥。**
-
-## 何为 Java 字节码?
-
-Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。
-
-**什么是字节码?采用字节码的好处是什么?**
-
-> 在 Java 中,JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
-
-**Java 程序从源代码到运行一般有下面 3 步:**
-
-
-
-## 为什么要查看 Java 字节码?
-
-我们在平时学习的时候,经常需要查看某个 java 类的字节码文件。查看字节码文件更容易让我们搞懂 java 代码背后的原理比如搞懂 java 中的各种语法糖的本质。
-
-## 如何查看 Java 字节码?
-
-如果我们通过命令行来查看某个 class 的字节码文件的话,可以直接通过 `javap` 命令,不过这种方式太原始了,效率十分低,并且看起来不直观。
-
-下面介绍两种使用 IDEA 查看类对应字节码文件的方式(_`javap`这种方式就不提了_)。
-
-我们以这段代码作为案例:
-
-```java
-public class Main {
- public static void main(String[] args) {
- Integer i = null;
- Boolean flag = false;
- System.out.println(flag ? 0 : i);
- }
-}
-```
-
-上面这段代码由于使用三目运算符不当导致诡异了 NPE 异常。为了搞清楚事情的原因,我们来看其对应的字节码。
-
-### 使用 IDEA 自带功能
-
-我们点击 `View -> Show Bytecode` 即可通过 IDEA 查看某个类对应的字节码文件。
-
-> 需要注意的是:**查看某个类对应的字节码文件之前确保它已经被编译过。**
-
-
-
-稍等几秒钟之后,你就可以直观到看到对应的类的字节码内容了。
-
-
-
-从字节码中可以看出,我圈出来的位置发生了 **拆箱操作** 。
-
-> - **装箱**:将基本类型用它们对应的引用类型包装起来;
-> - **拆箱**:将包装类型转换为基本数据类型;
-
-详细解释下就是:`flag ? 0 : i` 这行代码中,0 是基本数据类型 int,返回数据的时候 i 会被强制拆箱成 int 类型,由于 i 的值是 null,因此就抛出了 NPE 异常。
-
-```java
-Integer i = null;
-Boolean flag = false;
-System.out.println(flag ? 0 : i);
-```
-
-如果,我们把代码中 `flag` 变量的值修改为 true 的话,就不会存在 NPE 问题了,因为会直接返回 0,不会进行拆箱操作。
-
-### 使用 IDEA 插件 jclasslib(推荐)
-
-相比于 IDEA 自带的查看类字节的功能,我更推荐 `jclasslib` 这个插件,非常棒!
-
-**使用 `jclasslib` 不光可以直观地查看某个类对应的字节码文件,还可以查看类的基本信息、常量池、接口、属性、函数等信息。**
-
-
-
-我们直接在 IDEA 的插件市场即可找到这个插件。我这里已经安装好了。
-
-
-
-安装完成之后,重启 IDEA。点击`View -> Show Bytecode With jclasslib` 即可通过`jclasslib` 查看某个类对应的字节码文件。
-
-
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/maven-helper.md b/docs/idea-tutorial/idea-plugins/maven-helper.md
deleted file mode 100644
index d2b064a9934b14eeb8ae322a008cbf4ef88bf9e0..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/maven-helper.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-title: Maven Helper:解决 Maven 依赖冲突问题
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
- - Maven
----
-
-
-**Maven Helper** 主要用来分析 Maven 项目的相关依赖,可以帮助我们解决 Maven 依赖冲突问题。
-
-
-
-**何为依赖冲突?**
-
-说白了就是你的项目使用的 2 个 jar 包引用了同一个依赖 h,并且 h 的版本还不一样,这个时候你的项目就存在两个不同版本的 h。这时 Maven 会依据依赖路径最短优先原则,来决定使用哪个版本的 Jar 包,而另一个无用的 Jar 包则未被使用,这就是所谓的依赖冲突。
-
-大部分情况下,依赖冲突可能并不会对系统造成什么异常,因为 Maven 始终选择了一个 Jar 包来使用。但是,不排除在某些特定条件下,会出现类似找不到类的异常,所以,只要存在依赖冲突,在我看来,最好还是解决掉,不要给系统留下隐患。
diff --git a/docs/idea-tutorial/idea-plugins/others.md b/docs/idea-tutorial/idea-plugins/others.md
deleted file mode 100644
index da505ff8dfe5d99da54b2c6fdb684fac1d624937..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/others.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-title: 其他
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-
-1. **leetcode editor** :提供在线 Leetcode 刷题功能,比较方便我们刷题,不过我试用之后发现有一些小 bug,个人感觉还是直接在网站找题目刷来的痛快一些。
-2. **A Search with Github** :直接通过 Github搜索相关代码。
-3. **stackoverflow** : 选中相关内容后单击右键即可快速跳转到 stackoverflow 。
-4. **CodeStream** :让code review变得更加容易。
-5. **Code screenshots** :代码片段保存为图片。
-6. **GitToolBox** :Git工具箱
-7. **OK, Gradle!** :搜索Java库用于Gradle项目
-8. **Java Stream Debugger** : Java8 Stream调试器
-9. **EasyCode** : Easycode 可以直接对数据的表生成entity、controller、service、dao、mapper无需任何编码,简单而强大。更多内容可以查看这篇文章:[《懒人 IDEA 插件插件:EasyCode 一键帮你生成所需代码~》](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247486205&idx=1&sn=0ff2f87f0d82a1bd9c0c44328ef69435&chksm=cea24536f9d5cc20c6cc7669f0d4167d747fe8b8c05a64546c0162d694aa96044a2862e24b57&token=1862674725&lang=zh_CN#rd)
-10. **JFormDesigner** :Swing GUI 在线编辑器。
-11. **VisualVM Launcher** : Java性能分析神器。
-12. ......
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Codota1.gif b/docs/idea-tutorial/idea-plugins/pictures/Codota1.gif
deleted file mode 100644
index 7b2947fe3a0b2dee5ff9a11a6099ad357bf93c9a..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/Codota1.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Codota2.png b/docs/idea-tutorial/idea-plugins/pictures/Codota2.png
deleted file mode 100644
index 0fe37e360472a7fcc826e02982822a82c196a3b6..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/Codota2.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Codota3.png b/docs/idea-tutorial/idea-plugins/pictures/Codota3.png
deleted file mode 100644
index 44d1093e492e9dd4db05f680cc987f807d46874a..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/Codota3.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Codota4.gif b/docs/idea-tutorial/idea-plugins/pictures/Codota4.gif
deleted file mode 100644
index 1322b60f5e0d8f9847eee908da763774b165e7cf..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/Codota4.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/GsonFormat1.png b/docs/idea-tutorial/idea-plugins/pictures/GsonFormat1.png
deleted file mode 100644
index c8e678acb686ff8136ed15f4e702ff3c2ad3cc65..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/GsonFormat1.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/GsonFormat2.gif b/docs/idea-tutorial/idea-plugins/pictures/GsonFormat2.gif
deleted file mode 100644
index 7c371162d9ef609b74c58b562f9710b579811d62..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/GsonFormat2.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/IDE-Features-Trainer1.png b/docs/idea-tutorial/idea-plugins/pictures/IDE-Features-Trainer1.png
deleted file mode 100644
index 27f888a94992a0384a752d5e1c5a16a45f1ed6a8..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/IDE-Features-Trainer1.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/IDE-Features-Trainer2.png b/docs/idea-tutorial/idea-plugins/pictures/IDE-Features-Trainer2.png
deleted file mode 100644
index 6d59082c281c7f1b95adc066e22c12ea3af6f961..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/IDE-Features-Trainer2.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/JavaStreamDebugger.gif b/docs/idea-tutorial/idea-plugins/pictures/JavaStreamDebugger.gif
deleted file mode 100644
index 6e910e72ed561196f1f57b2d9899606b0828a5b0..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/JavaStreamDebugger.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Presentation-Assistant.gif b/docs/idea-tutorial/idea-plugins/pictures/Presentation-Assistant.gif
deleted file mode 100644
index 335523ea5f9b7e99ad73ad29b9f36a66903690b0..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/Presentation-Assistant.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit1.png b/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit1.png
deleted file mode 100644
index 5a69bc0595a37a2e39f8a974c9996d9e9c0ff174..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit1.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit2.png b/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit2.png
deleted file mode 100644
index 6c8aefd76389738ce837e3371bef4f90d335127d..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit2.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit3.png b/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit3.png
deleted file mode 100644
index b6cf628e76a5376ab4dcfe5262ed7040b8d384bd..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit3.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit4.png b/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit4.png
deleted file mode 100644
index be15f46bdd2bab97dd44c97fd4fbd95ee7c0cc09..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/RestfulToolkit4.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/RoboPOJOGenerator1.png b/docs/idea-tutorial/idea-plugins/pictures/RoboPOJOGenerator1.png
deleted file mode 100644
index c2d7704766b119411bb2b16caba00139ac32d844..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/RoboPOJOGenerator1.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/RoboPOJOGenerator2.png b/docs/idea-tutorial/idea-plugins/pictures/RoboPOJOGenerator2.png
deleted file mode 100644
index 4334b3db39024fe4eae7c7b129c020f25de8d4cc..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/RoboPOJOGenerator2.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Statistic1.png b/docs/idea-tutorial/idea-plugins/pictures/Statistic1.png
deleted file mode 100644
index 47521ee25dd8591a950bb9e06b9cfb3289493aae..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/Statistic1.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/Statistic2.png b/docs/idea-tutorial/idea-plugins/pictures/Statistic2.png
deleted file mode 100644
index f815aa1c72235d44041cb533e5c4f236c501058f..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/Statistic2.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case1.png b/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case1.png
deleted file mode 100644
index 7fbbbba97e5b1b5ce8f18203a551e1b986eb91dd..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case1.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case2.gif b/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case2.gif
deleted file mode 100644
index 9565231e9d7819f8f41bf1db62875441bc48e0a2..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case2.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case3.png b/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case3.png
deleted file mode 100644
index d4b2fd27ab3b789088508abe38740f3033858a3d..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/camel-case/camel-case3.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/check-style.png b/docs/idea-tutorial/idea-plugins/pictures/check-style.png
deleted file mode 100644
index e0c17b640962dc54b28f3831fc3adfdedc72dafa..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/check-style.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/code-glance.png b/docs/idea-tutorial/idea-plugins/pictures/code-glance.png
deleted file mode 100644
index afdf1a1bca06a89a55826930aec4afdd600f8441..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/code-glance.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template1.png b/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template1.png
deleted file mode 100644
index 26da6cd1b0649d8baf3c6ea392c5eb1ca829dadf..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template1.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template2.png b/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template2.png
deleted file mode 100644
index c0e436432a51a826ee2cc2d974b389829c55438e..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template2.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template3.png b/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template3.png
deleted file mode 100644
index 17f81a23469d4a86ac0c6104447583686e5df3f1..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/git-commit-template/Git-Commit-Template3.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console.gif b/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console.gif
deleted file mode 100644
index 293c134207fe5ae0f5b452634bb761b5aaf0da4a..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console2.png b/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console2.png
deleted file mode 100644
index aa338d615ee8f62d451543f437f1a055c15e0455..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console2.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console3.png b/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console3.png
deleted file mode 100644
index 411128ed1217de3cc3f264f6563bb9196051f607..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/grep-console/grep-console3.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/maver-helper.png b/docs/idea-tutorial/idea-plugins/pictures/maver-helper.png
deleted file mode 100644
index 35a3f6e083f819f4ec45ce5beb5bcd1717fc4cce..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/maver-helper.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines1.png b/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines1.png
deleted file mode 100644
index 67c3571d8365e58b930940f3e81a7cc833d7e079..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines1.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines2.png b/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines2.png
deleted file mode 100644
index e4b1dc8c9a8610a310de8992e42e44e96278dd8e..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines2.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines3.png b/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines3.png
deleted file mode 100644
index 5213aff02cab2a19819251cf528448cf485ff8ad..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/p3c/Alibaba-Java-Code-Guidelines3.png and /dev/null differ
diff --git "a/docs/idea-tutorial/idea-plugins/pictures/p3c/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214-\347\272\277\347\250\213\346\261\240\345\210\233\345\273\272.png" "b/docs/idea-tutorial/idea-plugins/pictures/p3c/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214-\347\272\277\347\250\213\346\261\240\345\210\233\345\273\272.png"
deleted file mode 100644
index 4d18c60b05529bdd179eea2268e96cde6010cd46..0000000000000000000000000000000000000000
Binary files "a/docs/idea-tutorial/idea-plugins/pictures/p3c/\351\230\277\351\207\214\345\267\264\345\267\264\345\274\200\345\217\221\346\211\213\345\206\214-\347\272\277\347\250\213\346\261\240\345\210\233\345\273\272.png" and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/power-mode/Power-Mode-II.gif b/docs/idea-tutorial/idea-plugins/pictures/power-mode/Power-Mode-II.gif
deleted file mode 100644
index 026c32e947e9040a5dc233a9f7a30364358ed201..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/power-mode/Power-Mode-II.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/rainbow-brackets.png b/docs/idea-tutorial/idea-plugins/pictures/rainbow-brackets.png
deleted file mode 100644
index 6529899a3ed9dcc808d1706fad428634e81d508b..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/rainbow-brackets.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/save-actions/save-actions.png b/docs/idea-tutorial/idea-plugins/pictures/save-actions/save-actions.png
deleted file mode 100644
index cf765cb6d0e0d4c3cb65ddfaa1ec193b5eebef20..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/save-actions/save-actions.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/save-actions/save-actions2.gif b/docs/idea-tutorial/idea-plugins/pictures/save-actions/save-actions2.gif
deleted file mode 100644
index 93ae62cf6c25200165e8827ba83e72fe65310edd..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/save-actions/save-actions2.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/translation/translation1.jpg b/docs/idea-tutorial/idea-plugins/pictures/translation/translation1.jpg
deleted file mode 100644
index 7b512c115b1d284d21d060e8fdbbdb9650501649..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/translation/translation1.jpg and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/pictures/translation/translation2.png b/docs/idea-tutorial/idea-plugins/pictures/translation/translation2.png
deleted file mode 100644
index c92664718dddbd960882a6aca2f7900d11e74b0a..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-plugins/pictures/translation/translation2.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-plugins/rest-devlop.md b/docs/idea-tutorial/idea-plugins/rest-devlop.md
deleted file mode 100644
index 329552b33b2f949dfaf8c0cfb8d12ebe7a862eb7..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/rest-devlop.md
+++ /dev/null
@@ -1,96 +0,0 @@
----
-title: RestfulToolkit:RESTful Web 服务辅助开发工具
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-
-开始推荐这个 IDEA 插件之前,我觉得有必要花一小会时间简单聊聊 **REST** 这个我们经常打交道的概念。
-
-## REST 相关概念解读
-
-### 何为 REST?
-
-REST 即 **REpresentational State Transfer** 的缩写。这个词组的翻译过来就是"**表现层状态转化**"。
-
-这样理解起来甚是晦涩,实际上 REST 的全称是 **Resource Representational State Transfer** ,直白地翻译过来就是 **“资源”在网络传输中以某种“表现形式”进行“状态转移”** 。
-
-**有没有感觉很难理解?**
-
-没关系,看了我对 REST 涉及到的一些概念的解读之后你没准就能理解了!
-
-- **资源(Resource)** :我们可以把真实的对象数据称为资源。一个资源既可以是一个集合,也可以是单个个体。比如我们的班级 classes 是代表一个集合形式的资源,而特定的 class 代表单个个体资源。每一种资源都有特定的 URI(统一资源定位符)与之对应,如果我们需要获取这个资源,访问这个 URI 就可以了,比如获取特定的班级:`/class/12`。另外,资源也可以包含子资源,比如 `/classes/classId/teachers`:列出某个指定班级的所有老师的信息
-- **表现形式(Representational)**:"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式比如 json,xml,image,txt 等等叫做它的"表现层/表现形式"。
-- **状态转移(State Transfer)** :大家第一眼看到这个词语一定会很懵逼?内心 BB:这尼玛是啥啊? **大白话来说 REST 中的状态转移更多地描述的服务器端资源的状态,比如你通过增删改查(通过 HTTP 动词实现)引起资源状态的改变。** (HTTP 协议是一个无状态的,所有的资源状态都保存在服务器端)
-
-### 何为 RESTful 架构?
-
-满足 REST 风格的架构设计就可以称为 RESTful 架构:
-
-1. 每一个 URI 代表一种资源;
-2. 客户端和服务器之间,传递这种资源的某种表现形式比如 json,xml,image,txt 等等;
-3. 客户端通过特定的 HTTP 动词,对服务器端资源进行操作,实现"表现层状态转化"。
-
-### 何为 RESTful Web 服务?
-
-基于 REST 架构的 Web 服务就被称为 RESTful Web 服务。
-
-## RESTful Web 服务辅助开发工具
-
-### 安装
-
-这个插件的名字叫做 “**RestfulToolkit**” 。我们直接在 IDEA 的插件市场即可找到这个插件。如下图所示。
-
-> 如果你因为网络问题没办法使用 IDEA 自带的插件市场的话,也可以通过[IDEA 插件市场的官网](https://plugins.jetbrains.com/idea)手动下载安装。
-
-
-
-### 简单使用
-
-#### URL 跳转到对应方法
-
-根据 URL 直接跳转到对应的方法定义 (Windows: `ctrl+\` or `ctrl+alt+n` Mac:`command+\` or `command+alt+n` )并且提供了一个服务的树形可视化显示窗口。 如下图所示。
-
-
-
-#### 作为 HTTP 请求工具
-
-这个插件还可以作为一个简单的 http 请求工具来使用。如下图所示。
-
-
-
-#### 复制生成 URL、复制方法参数...
-
-这个插件还提供了生成 URL、查询参数、请求体(RequestBody)等功能。
-
-举个例子。我们选中 `Controller` 中的某个请求对应的方法右击,你会发现多了几个可选项。当你选择`Generate & Copy Full URL`的话,就可以把整个请求的路径直接复制下来。eg:`http://localhost:9333/api/users?pageNum=1&pageSize=1` 。
-
-
-
-#### 将 Java 类转换为对应的 JSON 格式
-
-这个插件还为 Java 类上添加了 **Convert to JSON** 功能 。
-
-我们选中的某个类对应的方法然后右击,你会发现多了几个可选项。
-
-
-
-当我们选择`Convert to JSON`的话,你会得到如下 json 类型的数据:
-
-```json
-{
- "username": "demoData",
- "password": "demoData",
- "rememberMe": true
-}
-```
-
-## 后记
-
-RESTFulToolkit 原作者不更新了,IDEA.201 及以上版本不再适配。
-
-因此,国内就有一个大佬参考 RESTFulToolkit 开发了一款类似的插件——RestfulTool(功能较少一些,不过够用了)。
-
-
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/save-actions.md b/docs/idea-tutorial/idea-plugins/save-actions.md
deleted file mode 100644
index fc149ad301bfbd97357c3c06ff0d14c7f4f0f166..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/save-actions.md
+++ /dev/null
@@ -1,23 +0,0 @@
----
-title: Save Actions:优化文件保存
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-
-真必备插件!可以帮助我们在保存文件的时候:
-
-1. 优化导入;
-2. 格式化代码;
-3. 执行一些quick fix
-4. ......
-
-这个插件是支持可配置的,我的配置如下:
-
-
-
-实际使用效果如下:
-
-
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/sequence-diagram.md b/docs/idea-tutorial/idea-plugins/sequence-diagram.md
deleted file mode 100644
index 050d6161163ea035d5ef85cd5a59548a96fd4238..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/sequence-diagram.md
+++ /dev/null
@@ -1,91 +0,0 @@
----
-title: SequenceDiagram:一键可以生成时序图
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-
-
-在平时的学习/工作中,我们会经常面临如下场景:
-
-1. 阅读别人的代码
-2. 阅读框架源码
-3. 阅读自己很久之前写的代码。
-
-千万不要觉得工作就是单纯写代码,实际工作中,你会发现你的大部分时间实际都花在了阅读和理解已有代码上。
-
-为了能够更快更清晰地搞清对象之间的调用关系,我经常需要用到序列图。手动画序列图还是很麻烦费时间的,不过 IDEA 提供了一个叫做**SequenceDiagram** 的插件帮助我们解决这个问题。通过 SequenceDiagram 这个插件,我们一键可以生成时序图。
-
-## 何为序列图?
-
-网上对于序列图的定义有很多,我觉得都不太好理解,太抽象了。最神奇的是,大部分文章对于序列图的定义竟然都是一模一样,看来大家是充分发挥了写代码的“精髓”啊!
-
-我还是简单说一说我的理解吧!不过,说实话,我自己对于 Sequence Diagram 也不是很明朗。下面的描述如有问题和需要完善的地方,还请指出。
-
-> **序列图**(Sequence Diagram),亦称为**循序图**,是一种[UML](https://zh.m.wikipedia.org/wiki/UML)行为图。表示系统执行某个方法/操作(如登录操作)时,对象之间的顺序调用关系。
->
-> 这个顺序调用关系可以这样理解:你需要执行系统中某个对象 a 提供的方法/操作 login(登录),但是这个对象又依赖了对象 b 提供的方法 getUser(获取用户)。因此,这里就有了 a -> b 调用关系之说。
-
-再举两个例子来说一下!
-
-下图是微信支付的业务流程时序图。这个图描述了微信支付相关角色(顾客,商家...)在微信支付场景下,基础支付和支付的的顺序调用关系。
-
-
-
-下图是我写的一个 HTTP 框架中的执行某个方法的序列图。这个图描述了我们在调用 `InterceptorFactory`类的 `loadInterceptors()` 方法的时候,所涉及到的类之间的调用关系。
-
-
-
-另外,国内一般更喜欢称呼序列图为"时序图"。
-
-- 如果你按照纯翻译的角度来说, sequence 这个单词并无"时间"的意思,只有序列,顺序等意思,因此也有人说“时序图”的说法是不准确的。
-- 如果从定义角度来说,时序图这个描述是没问题的。因为 Sequence Diagram 中每条消息的触发时机确实是按照时间顺序执行的。
-
-我觉得称呼 Sequence Diagram 为时序图或者序列图都是没问题的,不用太纠结。
-
-## 哪些场景下需要查看类的时序图?
-
-我们在很多场景下都需要时序图,比如说:
-
-1. **阅读源码** :阅读源码的时候,你可能需要查看调用目标方法涉及的相关类的调用关系。特别是在代码的调用层级比较多的时候,对于我们理解源码非常有用。(_题外话:实际工作中,大部分时间实际我们都花在了阅读理解已有代码上。_)
-2. **技术文档编写** :我们在写项目介绍文档的时候,为了让别人更容易理解你的代码,你需要根据核心方法为相关的类生成时序图来展示他们之间的调用关系。
-3. **梳理业务流程** :当我们的系统业务流程比较复杂的时候,我们可以通过序列图将系统中涉及的重要的角色和对象的之间关系可视化出来。
-4. ......
-
-## 如何使用 IDEA 根据类中方法生成时序图?
-
-**通过 SequenceDiagram 这个插件,我们一键可以生成时序图。**
-
-并且,你还可以:
-
-1. 点击时序图中的类/方法即可跳转到对应的地方。
-2. 从时序图中删除对应的类或者方法。
-3. 将生成的时序图导出为 PNG 图片格式。
-
-### 安装
-
-我们直接在 IDEA 的插件市场即可找到这个插件。我这里已经安装好了。
-
-> 如果你因为网络问题没办法使用 IDEA 自带的插件市场的话,也可以通过[IDEA 插件市场的官网](https://plugins.jetbrains.com/idea)手动下载安装。
-
-
-
-### 简单使用
-
-1. 选中方法名(注意不要选类名),然后点击鼠标右键,选择 **Sequence Diagram** 选项即可!
-
-
-
-2. 配置生成的序列图的一些基本的参数比如调用深度之后,我们点击 ok 即可!
-
-
-
-你还可以通过生成的时序图来定位到相关的代码,这对于我们阅读源码的时候尤其有帮助!
-
-
-
-时序图生成完成之后,你还可以选择将其导出为图片。
-
-
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/shortcut-key.md b/docs/idea-tutorial/idea-plugins/shortcut-key.md
deleted file mode 100644
index c7e585290e7cab2e1a4e64b871b9884137bb94b9..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/shortcut-key.md
+++ /dev/null
@@ -1,56 +0,0 @@
----
-title: IDEA 快捷键相关插件
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-
-相信我!下面这两个一定是IDEA必备的插件。
-
-## Key Promoter X:快捷键提示
-
-这个插件的功能主要是**在你本可以使用快捷键操作的地方提醒你用快捷键操作。**
-
-举个例子。我直接点击tab栏下的菜单打开 Version Control(版本控制) 的话,这个插件就会提示你可以用快捷键 `command+9`或者`shift+command+9`打开。如下图所示。
-
-
-
-除了这个很棒的功能之外,这个插件还有一个功能我觉得非常棒。
-
-它可以展示出哪些快捷键你忘记使用的次数最多!这样的话,你可以给予你忘记次数最多的那些快捷键更多的关注。
-
-我忘记最多的快捷键是debug的时候经常使用的 F8(Step Over)。如下图所示。
-
-
-
-关于快捷键,很多人不愿意去记,觉得单纯靠鼠标就完全够了。
-
-让我来说的话!我觉得如果你偶尔使用一两次 IDEA 的话,你完全没有必要纠结快捷键。
-
-但是,如果 IDEA 是你开发的主力,你经常需要使用的话,相信我,掌握常用的一些快捷键真的很重要!
-
-不说多的,**熟练掌握IDEA的一些最常见的快捷键,你的工作效率至少提升 30 %。**
-
-**除了工作效率的提升之外,使用快捷键会让我们显得更加专业。**
-
-你在使用快捷键进行操作的时候,是很帅,很酷啊!但是,当你用 IDEA 给别人演示一些操作的时候,你使用了快捷键的话,别人可能根本不知道你进行了什么快捷键操作。
-
-**怎么解决这个问题呢?**
-
-很简单!这个时候就轮到 **Presentation Assistant** 这个插件上场了!
-
-## Presentation Assistant:快捷键展示
-
-安装这个插件之后,你使用的快捷键操作都会被可视化地展示出来,非常适合自己在录制视频或者给别人展示代码的时候使用。
-
-举个例子。我使用快捷键 `command+9`打开 Version Control ,使用了这个插件之后的效果如下图所示。
-
-
-
-从上图可以很清晰地看到,IDEA 的底部中间的位置将我刚刚所使用的快捷键给展示了出来。
-
-并且,**这个插件会展示出 Mac 和 Win/Linux 两种不同的版本的快捷键。**
-
-因此,不论你的操作系统是 Mac 还是 Win/Linux ,这款插件都能满足你的需求。
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-plugins/translation.md b/docs/idea-tutorial/idea-plugins/translation.md
deleted file mode 100644
index 7de2619a3b530682d63785d37bcc62e4a0863660..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-plugins/translation.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-title: Translation:翻译
-category: IDEA指南
-tag:
- - IDEA
- - IDEA插件
----
-
-
-有了这个插件之后,你再也不用在编码的时候打开浏览器查找某个单词怎么拼写、某句英文注释什么意思了。
-
-并且,这个插件支持多种翻译源:
-
-1. Google 翻译
-2. Youdao 翻译
-3. Baidu 翻译
-
-除了翻译功能之外还提供了语音朗读、单词本等实用功能。这个插件的Github地址是:[https://github.com/YiiGuxing/TranslationPlugin](https://github.com/YiiGuxing/TranslationPlugin) (貌似是国人开发的,很赞)。
-
-**使用方法很简单!选中你要翻译的单词或者句子,使用快捷键 `command+ctrl+u(mac)` / `shift+ctrl+y(win/linux)`** (如果你忘记了快捷的话,鼠标右键操作即可!)
-
-
-
-**如果需要快速打开翻译框,使用快捷键`command+ctrl+i(mac)`/`ctrl + shift + o(win/linux)`**
-
-
-
-如果你需要将某个重要的单词添加到生词本的话,只需要点击单词旁边的收藏按钮即可!
\ No newline at end of file
diff --git a/docs/idea-tutorial/idea-tips/idea-plug-in-development-intro.md b/docs/idea-tutorial/idea-tips/idea-plug-in-development-intro.md
deleted file mode 100644
index 8f8822c5946c2a81f97ae65fac3617a8f61fdbc4..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-tips/idea-plug-in-development-intro.md
+++ /dev/null
@@ -1,213 +0,0 @@
-# IDEA 插件开发入门
-
-我这个人没事就喜欢推荐一些好用的 [IDEA 插件](https://mp.weixin.qq.com/mp/appmsgalbum?action=getalbum&album_id=1319419426898329600&__biz=Mzg2OTA0Njk0OA==#wechat_redirect)给大家。这些插件极大程度上提高了我们的生产效率以及编码舒适度。
-
-**不知道大家有没有想过自己开发一款 IDEA 插件呢?**
-
-我自己想过,但是没去尝试过。刚好有一位读者想让我写一篇入门 IDEA 开发的文章,所以,我在周末就花了一会时间简单了解一下。
-
-
-
-不过,**这篇文章只是简单带各位小伙伴入门一下 IDEA 插件开发**,个人精力有限,暂时不会深入探讨太多。如果你已经有 IDEA 插件开发的相关经验的话,这篇文章就可以不用看了,因为会浪费你 3 分钟的时间。
-
-好的废话不多说!咱们直接开始!
-
-## 01 新建一个基于 Gradle 的插件项目
-
-这里我们基于 Gradle 进行插件开发,这也是 IntelliJ 官方的推荐的插件开发解决方案。
-
-**第一步,选择 Gradle 项目类型并勾选上相应的依赖。**
-
-
-
-**第二步,填写项目相关的属性比如 GroupId、ArtifactId。**
-
-
-
-**第三步,静静等待项目下载相关依赖。**
-
-第一次创建 IDEA 插件项目的话,这一步会比较慢。因为要下载 IDEA 插件开发所需的 SDK 。
-
-## 02 插件项目结构概览
-
-新建完成的项目结构如下图所示。
-
-
-
-这里需要额外注意的是下面这两个配置文件。
-
-**`plugin.xml` :插件的核心配置文件。通过它可以配置插件名称、插件介绍、插件作者信息、Action 等信息。**
-
-```xml
-
- github.javaguide.my-first-idea-plugin
-
- Beauty
-
- JavaGuide
-
-
- 这尼玛是什么垃圾插件!!!
- ]]>
-
-
- com.intellij.modules.platform
-
-
-
-
-
-
-
-
-
-```
-
-**`build.gradle` :项目依赖配置文件。通过它可以配置项目第三方依赖、插件版本、插件版本更新记录等信息。**
-
-```groovy
-plugins {
- id 'java'
- id 'org.jetbrains.intellij' version '0.6.3'
-}
-
-group 'github.javaguide'
-// 当前插件版本
-version '1.0-SNAPSHOT'
-
-repositories {
- mavenCentral()
-}
-
-// 项目依赖
-dependencies {
- testCompile group: 'junit', name: 'junit', version: '4.12'
-}
-
-// See https://github.com/JetBrains/gradle-intellij-plugin/
-// 当前开发该插件的 IDEA 版本
-intellij {
- version '2020.1.2'
-}
-patchPluginXml {
- // 版本更新记录
- changeNotes """
- Add change notes here.
- most HTML tags may be used """
-}
-```
-
-没有开发过 IDEA 插件的小伙伴直接看这两个配置文件内容可能会有点蒙。所以,我专门找了一个 IDEA 插件市场提供的现成插件来说明一下。小伙伴们对照下面这张图来看下面的配置文件内容就非常非常清晰了。
-
-
-
-这就非常贴心了!如果这都不能让你点赞,我要这文章有何用!
-
-
-
-## 03 手动创建 Action
-
-我们可以把 Action 看作是 IDEA 提高的事件响应处理器,通过 Action 我们可以自定义一些事件处理逻辑/动作。比如说你点击某个菜单的时候,我们进行一个展示对话框的操作。
-
-**第一步,右键`java`目录并选择 new 一个 Action**
-
-![]()
-
-**第二步,配置 Action 相关信息比如展示名称。**
-
-![配置动作属性 (1)]()
-
-创建完成之后,我们的 `plugin.xml` 的 ``节点下会自动生成我们刚刚创建的 Action 信息:
-
-```xml
-
-
-
-
-
-
-```
-
-并且 `java` 目录下为生成一个叫做 `HelloAction` 的类。并且,这个类继承了 `AnAction` ,并覆盖了 `actionPerformed()` 方法。这个 `actionPerformed` 方法就好比 JS 中的 `onClick` 方法,会在你点击的时候被触发对应的动作。
-
-我简单对`actionPerformed` 方法进行了修改,添加了一行代码。这行代码很简单,就是显示 1 个对话框并展示一些信息。
-
-```java
-public class HelloAction extends AnAction {
-
- @Override
- public void actionPerformed(AnActionEvent e) {
- //显示对话框并展示对应的信息
- Messages.showInfoMessage("素材不够,插件来凑!", "Hello");
- }
-}
-
-```
-
-另外,我们上面也说了,每个动作都会归属到一个 Group 中,这个 Group 可以简单看作 IDEA 中已经存在的菜单。
-
-举个例子。我上面创建的 Action 的所属 Group 是 **ToolsMenu(Tools)** 。这样的话,我们创建的 Action 所在的位置就在 Tools 这个菜单下。
-
-
-
-再举个例子。加入我上面创建的 Action 所属的 Group 是**MainMenu** (IDEA 最上方的主菜单栏)下的 **FileMenu(File)** 的话。
-
-```xml
-
-
-
-
-
-
-```
-
-我们创建的 Action 所在的位置就在 File 这个菜单下。
-
-
-
-## 04 验收成果
-
-点击 `Gradle -> runIde` 就会启动一个默认了这个插件的 IDEA。然后,你可以在这个 IDEA 上实际使用这个插件了。
-
-
-
-效果如下:
-
-
-
-我们点击自定义的 Hello Action 的话就会弹出一个对话框并展示出我们自定义的信息。
-
-
-
-## 05 完善一下
-
-想要弄点界面花里胡哨一下, 我们还可以通过 Swing 来写一个界面。
-
-这里我们简单实现一个聊天机器人。代码的话,我是直接参考的我大二刚学 Java 那会写的一个小项目(_当时写的代码实在太烂了!就很菜!_)。
-
-
-
-首先,你需要在[图灵机器人官网](http://www.tuling123.com/ "图灵机器人官网")申请一个机器人。(_其他机器人也一样,感觉这个图灵机器人没有原来好用了,并且免费调用次数也不多_)
-
-
-
-然后,简单写一个方法来请求调用机器人。由于代码比较简单,我这里就不放出来了,大家简单看一下效果就好。
-
-
-
-## 06 深入学习
-
-如果你想要深入学习的 IDEA 插件的话,可以看一下官网文档:[https://jetbrains.org/intellij/sdk/docs/basics/basics.html ](https://jetbrains.org/intellij/sdk/docs/basics/basics.html "https://jetbrains.org/intellij/sdk/docs/basics/basics.html ") 。
-
-这方面的资料还是比较少的。除了官方文档的话,你还可以简单看看下面这几篇文章:
-
-- [8 条经验轻松上手 IDEA 插件开发](https://developer.aliyun.com/article/777850?spm=a2c6h.12873581.0.dArticle777850.118d6446r096V4&groupCode=alitech "8 条经验轻松上手 IDEA 插件开发")
-- [IDEA 插件开发入门教程](https://blog.xiaohansong.com/idea-plugin-development.html "IDEA 插件开发入门教程")
-
-## 07 后记
-
-我们开发 IDEA 插件主要是为了让 IDEA 更加好用,比如有些框架使用之后可以减少重复代码的编写、有些主题类型的插件可以让你的 IDEA 更好看。
-
-我这篇文章的这个案例说实话只是为了让大家简单入门一下 IDEA 开发,没有任何实际应用意义。**如果你想要开发一个不错的 IDEA 插件的话,还要充分发挥想象,利用 IDEA 插件平台的能力。**
diff --git a/docs/idea-tutorial/idea-tips/idea-refractor-intro.md b/docs/idea-tutorial/idea-tips/idea-refractor-intro.md
deleted file mode 100644
index 7c3d4a66590d9bc46efd5ce0d3c968fa1234b1dd..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-tips/idea-refractor-intro.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# IDEA 重构入门
-
-我们在使用 IDEA 进行重构之前,先介绍一个方便我们进行重构的快捷键:`ctrl+t(mac)/ctrl+shift+alt+t`(如果忘记快捷键的话,鼠标右键也能找到重构选项),使用这个快捷键可以快速调出常用重构的选项,如下图所示:
-
-
-
-### 重命名(rename)
-
-快捷键:**Shift + F6(mac) / Shift + F6(windows/Linux):** 对类、变量或者方法名重命名。
-
-
-
-### 提取相关重构手段
-
-这部分的快捷键实际很好记忆,我是这样记忆的:
-
-前面两个键位是 `command + option(mac) / ctrl + alt (Windows/Linux)` 是固定的,只有后面一个键位会变比如Extract constant (提取变量)就是 c(constant)、Extract variable (提取变量)就是 v(variable)。
-
-#### 提取常量(extract constant)
-
-1. **使用场景** :提取未经过定义就直接出现的常量。提取常量使得你的编码更易读,避免硬编码。
-2. **快捷键:** `command + option+ c(mac)/ ctrl + alt + c(Windows/Linux)`
-
-**示例:**
-
-
-
-#### 提取参数(exact parameter)
-
-1. **使用场景** :提取参数到方法中。
-2. **快捷键:** `command + option+ p(mac)/ ctrl + alt + p(Windows/Linux)`
-
-
-
-#### 提取变量(exact variable)
-
-1. **使用场景** :提取多次出现的表达式。
-2. **快捷键:** `command + option+ v(mac) / ctrl + alt + v(Windows/Linux) `
-
-**示例:**
-
-
-
-#### 提取属性(exact field)
-
-1. **使用场景** :把当前表达式提取成为类的一个属性。
-2. **快捷键:** `command + option+ f(mac) / ctrl + alt + f(Windows/Linux) `
-
-**示例:**
-
-
-
-
-**示例:**
-
-
-
-#### 提取方法(exact method)
-
-1. **使用场景** :1个或者多个表达式可以提取为一个方法。 提取方法也能使得你的编码更易读,更加语义化。
-2. **快捷键:** `command + option+ m(mac)/ ctrl + alt + m(Windows/Linux)`
-
-**示例:**
-
-
-
-#### 提取接口(exact interface)
-
-1. **使用场景** :想要把一个类中的1个或多个方法提取到一个接口中的时候。
-2. **快捷键:** `command + option+ m(mac)/ ctrl + alt + m(Windows/Linux)`
-
-**示例:**
-
-
-
diff --git a/docs/idea-tutorial/idea-tips/idea-source-code-reading-skills.md b/docs/idea-tutorial/idea-tips/idea-source-code-reading-skills.md
deleted file mode 100644
index 0064598c423ce3c1a88348f76bb18224e324bc6f..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/idea-tips/idea-source-code-reading-skills.md
+++ /dev/null
@@ -1,189 +0,0 @@
-# IDEA源码阅读技巧
-
-项目有个新来了一个小伙伴,他看我查看项目源代码的时候,各种骚操作“花里胡哨”的。于是他向我请教,想让我分享一下我平时使用 IDEA 看源码的小技巧。
-
-## 基本操作
-
-这一部分的内容主要是一些我平时看源码的时候常用的快捷键/小技巧!非常好用!
-
-掌握这些快捷键/小技巧,看源码的效率提升一个等级!
-
-### 查看当前类的层次结构
-
-| 使用频率 | 相关快捷键 |
-| -------- | ---------- |
-| ⭐⭐⭐⭐⭐ | `Ctrl + H` |
-
-平时,我们阅读源码的时候,经常需要查看类的层次结构。就比如我们遇到抽象类或者接口的时候,经常需要查看其被哪些类实现。
-
-拿 Spring 源码为例,`BeanDefinition` 是一个关于 Bean 属性/定义的接口。
-
-```java
-public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
- ......
-}
-```
-
-如果我们需要查看 `BeanDefinition` 被哪些类实现的话,只需要把鼠标移动到 `BeanDefinition` 类名上,然后使用快捷键 `Ctrl + H` 即可。
-
-
-
-同理,如果你想查看接口 `BeanDefinition` 继承的接口 `AttributeAccessor` 被哪些类实现的话,只需要把鼠标移动到 `AttributeAccessor` 类名上,然后使用快捷键 `Ctrl + H` 即可。
-
-### 查看类结构
-
-| 使用频率 | 相关快捷键 |
-| -------- | ------------------------------------- |
-| ⭐⭐⭐⭐ | `Alt + 7`(Win) / `Command +7` (Mac) |
-
-类结构可以让我们快速了解到当前类的方法、变量/常量,非常使用!
-
-我们在对应的类的任意位置使用快捷键 `Alt + 7`(Win) / `Command +7` (Mac)即可。
-
-
-
-### 快速检索类
-
-| 使用频率 | 相关快捷键 |
-| -------- | ---------------------------------------- |
-| ⭐⭐⭐⭐⭐ | `Ctrl + N` (Win) / `Command + O` (Mac) |
-
-使用快捷键 `Ctrl + N` (Win) / `Command + O` (Mac)可以快速检索类/文件。
-
-
-
-### 关键字检索
-
-| 使用频率 | 相关快捷键 |
-| -------- | ---------- |
-| ⭐⭐⭐⭐⭐ | 见下文 |
-
-- 当前文件下检索 : `Ctrl + F` (Win) / `Command + F` (Mac)
-- 全局的文本检索 : `Ctrl + Shift + F` (Win) / `Command + Shift + F` (Mac)
-
-### 查看方法/类的实现类
-
-| 使用频率 | 相关快捷键 |
-| -------- | -------------------------------------------------- |
-| ⭐⭐⭐⭐ | `Ctrl + Alt + B` (Win) / `Command + Alt + B` (Mac) |
-
-如果我们想直接跳转到某个方法/类的实现类,直接在方法名或者类名上使用快捷键 `Ctrl + Alt + B/鼠标左键` (Win) / `Command + Alt + B/鼠标左键` (Mac) 即可。
-
-如果对应的方法/类只有一个实现类的话,会直接跳转到对应的实现类。
-
-比如 `BeanDefinition` 接口的 `getBeanClassName()` 方法只被 `AbstractBeanDefinition` 抽象类实现,我们对这个方法使用快捷键就可以直接跳转到 `AbstractBeanDefinition` 抽象类中对应的实现方法。
-
-```java
-public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
- @Nullable
- String getBeanClassName();
- ......
-}
-```
-
-如果对应的方法/类有多个实现类的话,IDEA 会弹出一个选择框让你选择。
-
-比如 `BeanDefinition` 接口的 `getParentName()` 方法就有多个不同的实现。
-
-
-
-### 查看方法被使用的情况
-
-| 使用频率 | 相关快捷键 |
-| -------- | ---------- |
-| ⭐⭐⭐⭐ | `Alt + F7` |
-
-我们可以通过直接在方法名上使用快捷键 `Alt + F7` 来查看这个方法在哪些地方被调用过。
-
-
-
-### 查看最近使用的文件
-
-| 使用频率 | 相关快捷键 |
-| -------- | -------------------------------------- |
-| ⭐⭐⭐⭐⭐ | `Ctrl + E`(Win) / `Command +E` (Mac) |
-
-你可以通过快捷键 `Ctrl + E`(Win) / `Command +E` (Mac)来显示 IDEA 最近使用的一些文件。
-
-
-
-### 查看图表形式的类继承链
-
-| 使用频率 | 相关快捷键 |
-| -------- | ------------------------ |
-| ⭐⭐⭐⭐ | 相关快捷键较多,不建议记 |
-
-点击类名 **右键** ,选择 **Shw Diagrams** 即可查看图表形式的类继承链。
-
-
-
-你还可以对图表进行一些操作。比如,你可以点击图表中具体的类 **右键**,然后选择显示它的实现类或者父类。
-
-
-
-再比如你还可以选择是否显示类中的属性、方法、内部类等等信息。
-
-
-
-如果你想跳转到对应类的源码的话,直接点击图表中具体的类 **右键** ,然后选择 **Jump to Source** 。
-
-
-
-## 插件推荐
-
-### 一键生成方法的序列图
-
-**序列图**(Sequence Diagram),亦称为**循序图**,是一种 UML 行为图。表示系统执行某个方法/操作(如登录操作)时,对象之间的顺序调用关系。
-
-这个顺序调用关系可以这样理解:你需要执行系统中某个对象 a 提供的方法/操作 login(登录),但是这个对象又依赖了对象 b 提供的方法 getUser(获取用户)。因此,这里就有了 a -> b 调用关系之说。
-
-我们可以通过 **SequenceDiagram** 这个插件一键生成方法的序列图。
-
-> 如果你因为网络问题没办法使用 IDEA 自带的插件市场的话,也可以通过 IDEA 插件市场的官网手动下载安装。
-
-
-
-**如何使用呢?**
-
-1、选中方法名(注意不要选类名),然后点击鼠标右键,选择 **Sequence Diagram** 选项即可!
-
-
-
-2、配置生成的序列图的一些基本的参数比如调用深度之后,我们点击 ok 即可!
-
-
-
-3、你还可以通过生成的时序图来定位到相关的代码,这对于我们阅读源码的时候尤其有帮助!
-
-
-
-4、时序图生成完成之后,你还可以选择将其导出为图片。
-
-
-
-相关阅读:[《安利一个 IDEA 骚操作:一键生成方法的序列图》](https://mp.weixin.qq.com/s/SG1twZczqdup_EQAOmNERg) 。
-
-### 项目代码统计
-
-为了快速分析项目情况,我们可以对项目的 **代码的总行数、单个文件的代码行数、注释行数等信息进行统计。**
-
-**Statistic** 这个插件来帮助我们实现这一需求。
-
-
-
-有了这个插件之后你可以非常直观地看到你的项目中所有类型的文件的信息比如数量、大小等等,可以帮助你更好地了解你们的项目。
-
-
-
-你还可以使用它看所有类的总行数、有效代码行数、注释行数、以及有效代码比重等等这些东西。
-
-
-
-如果,你担心插件过多影响 IDEA 速度的话,可以只在有代码统计需求的时候开启这个插件,其他时间禁用它就完事了!
-
-相关阅读:[快速识别烂项目!试试这款项目代码统计 IDEA 插件](https://mp.weixin.qq.com/s/fVEeMW6elhu79I-rTZB40A)
-
-
-
-
-
diff --git a/docs/idea-tutorial/idea-tips/pictures/exact/exact-field.gif b/docs/idea-tutorial/idea-tips/pictures/exact/exact-field.gif
deleted file mode 100644
index 770df36522d828561acb4152d501979ffe4f096f..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-tips/pictures/exact/exact-field.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/exact/exact-interface.gif b/docs/idea-tutorial/idea-tips/pictures/exact/exact-interface.gif
deleted file mode 100644
index 678b93de0a83fd15bdf3060f9bdf8b9e158a28ed..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-tips/pictures/exact/exact-interface.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/exact/exact-method.gif b/docs/idea-tutorial/idea-tips/pictures/exact/exact-method.gif
deleted file mode 100644
index 3748903e21d996648705544e9f9578d25033c7ba..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-tips/pictures/exact/exact-method.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/exact/exact-parameter.gif b/docs/idea-tutorial/idea-tips/pictures/exact/exact-parameter.gif
deleted file mode 100644
index 578b5ccca831dd698ee73c2cf90309d54e31a700..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-tips/pictures/exact/exact-parameter.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/exact/exact-variable.gif b/docs/idea-tutorial/idea-tips/pictures/exact/exact-variable.gif
deleted file mode 100644
index 7326761ef55d66ac0b73d9e2b1dfa67004185e36..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-tips/pictures/exact/exact-variable.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/exact/extract-constant.gif b/docs/idea-tutorial/idea-tips/pictures/exact/extract-constant.gif
deleted file mode 100644
index 6752a385e74119fe915820d0d554d1fd1b702df1..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-tips/pictures/exact/extract-constant.gif and /dev/null differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/refractor-help.png b/docs/idea-tutorial/idea-tips/pictures/refractor-help.png
deleted file mode 100644
index 032319487aee0601c17f76c72a28390e4da1bee8..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-tips/pictures/refractor-help.png and /dev/null differ
diff --git a/docs/idea-tutorial/idea-tips/pictures/rename.gif b/docs/idea-tutorial/idea-tips/pictures/rename.gif
deleted file mode 100644
index c8a61b12863b05a11a5aeee4957e9be791df7d06..0000000000000000000000000000000000000000
Binary files a/docs/idea-tutorial/idea-tips/pictures/rename.gif and /dev/null differ
diff --git a/docs/idea-tutorial/readme.md b/docs/idea-tutorial/readme.md
deleted file mode 100644
index a5297b6bbed232a0e8d08c8339b41dbd67afdf92..0000000000000000000000000000000000000000
--- a/docs/idea-tutorial/readme.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-icon: creative
-category: IDEA指南
----
-
-# IntelliJ IDEA 使用指南 | 必备插件推荐 | 插件开发入门 | 重构小技巧 | 源码阅读技巧
-
-分享一下自己使用 IDEA 的一些经验,希望对大家有帮助!
-
-- Github 地址:https://github.com/CodingDocs/awesome-idea-tutorial
-- 码云地址:https://gitee.com/SnailClimb/awesome-idea-tutorial (Github 无法访问或者访问速度比较慢的小伙伴可以看码云上的对应内容)
diff --git a/docs/interview-preparation/interview-experience.md b/docs/interview-preparation/interview-experience.md
new file mode 100644
index 0000000000000000000000000000000000000000..623aa555e73a8d54e1dc9f513dc9d31779dc9ed7
--- /dev/null
+++ b/docs/interview-preparation/interview-experience.md
@@ -0,0 +1,30 @@
+---
+title: 优质面经汇总(付费)
+category: 知识星球
+icon: experience
+---
+
+古人云:“**他山之石,可以攻玉**” 。善于学习借鉴别人的面试的成功经验或者失败的教训,可以让自己少走许多弯路。
+
+在 **[《Java 面试指北》](../zhuanlan/java-mian-shi-zhi-bei.md)** 的 **「面经篇」** ,我分享了 15+ 篇高质量的 Java 后端面经,有校招的,也有社招的,有大厂的,也有中小厂的。
+
+如果你是非科班的同学,也能在这些文章中找到对应的非科班的同学写的面经。
+
+
+
+并且,[知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)还有专门分享面经和面试题的专题,里面会分享很多优质的面经和面试题。
+
+
+
+
+
+
+
+相比于牛客网或者其他网站的面经,《Java 面试指北》中整理的面经质量更高,并且,我会提供优质的参考资料。
+
+有很多同学要说了:“为什么不直接给出具体答案呢?”。主要原因有如下两点:
+
+1. 参考资料解释的要更详细一些,还可以顺便让你把相关的知识点复习一下。
+2. 给出的参考资料基本都是我的原创,假如后续我想对面试问题的答案进行完善,就不需要挨个把之前的面经写的答案给修改了(面试中的很多问题都是比较类似的)。当然了,我的原创文章也不太可能覆盖到面试的每个点,部面试问题的答案,我是精选的其他技术博主写的优质文章,文章质量都很高。
+
+
diff --git a/docs/interview-preparation/key-points-of-interview.md b/docs/interview-preparation/key-points-of-interview.md
new file mode 100644
index 0000000000000000000000000000000000000000..6375c8781008648eab5ca8d2bc1d6d3800437a41
--- /dev/null
+++ b/docs/interview-preparation/key-points-of-interview.md
@@ -0,0 +1,38 @@
+---
+title: Java面试重点总结(重要)
+category: 面试准备
+icon: star
+---
+
+::: tip 友情提示
+本文节选自 **[《Java 面试指北》](../zhuanlan/java-mian-shi-zhi-bei.md)**。这是一份教你如何更高效地准备面试的小册,涵盖常见八股文(系统设计、常见框架、分布式、高并发 ......)、优质面经等内容。
+:::
+
+## Java 后端面试哪些知识点是重点?
+
+**准备面试的时候,具体哪些知识点是重点呢?**
+
+给你几点靠谱的建议:
+
+1. Java 基础、集合、并发、MySQL、Redis、Spring、Spring Boot 这些 Java 后端开发必备的知识点。大厂以及中小厂的面试问的比较多的就是这些知识点(不信的话,你可以去多找一些面经看看)。我这里没有提到计算机基础相关的内容,这个会在下面提到。
+2. 你的项目经历涉及到的知识点,有水平的面试官都是会根据你的项目经历来问的。举个例子,你的项目经历使用了 Redis 来做限流,那 Redis 相关的八股文(比如 Redis 常见数据结构)以及限流相关的八股文(比如常见的限流算法)你就应该多花更多心思来搞懂!吃透!你把项目经历上的知识点吃透之后,再把你简历上哪些写熟练掌握的技术给吃透。最后,再去花时间准备其他知识点。
+3. 针对自身找工作的需求,你又可以适当地调整复习的重点。像中小厂一般问计算机基础比较少一些,有些大厂比如字节比较重视计算机基础尤其是算法。这样的话,如果你的目标是中小厂的话,计算机基础就准备面试来说不是那么重要了。如果复习时间不够的话,可以暂时先放放。
+4. 一般校招的面试不会强制要求你会分布式/微服务、高并发的知识(不排除个别岗位有这方面的硬性要求),所以到底要不要掌握还是要看你个人当前的实际情况。如果你会这方面的知识的话,对面试相对来说还是会更有利一些(想要让项目经历有亮点,还是得会一些性能优化的知识。性能优化的知识这也算是高并发知识的一个小分支了)。如果你的技能介绍或者项目经历涉及到分布式/微服务、高并发的知识,那建议你尽量也要抽时间去认真准备一下,面试中很可能会被问到,尤其是项目经历用到的时候。不过,也还是主要准备写在简历上的那些知识点就好。
+5. JVM 相关的知识点,一般是大厂才会问到,面试中小厂就没必要准备了。JVM 面试中比较常问的是 [Java 内存区域](https://javaguide.cn/java/jvm/memory-area.html)、[JVM 垃圾回收](https://javaguide.cn/java/jvm/jvm-garbage-collection.html)、[类加载器和双亲委派模型](https://javaguide.cn/java/jvm/classloader.html) 以及 JVM 调优和问题排查(我之前分享过一些[常见的线上问题案例](https://t.zsxq.com/0bsAac47U),里面就有 JVM 相关的)。
+6. 不同的大厂面试侧重点也会不同。比如说你要去阿里这种公司的话,项目和八股文就是重点,阿里笔试一般会有代码题,进入面试后就很少问代码题了,但是对原理性的问题问的比较深,经常会问一些你对技术的思考。再比如说你要面试字节这种公司,那计算机基础,尤其是算法是重点,字节的面试十分注重代码功底,有时候开始面试就会直接甩给你一道代码题,写出来再谈别的。也会问面试八股文,以及项目,不过,相对来说要少很多。建议你看一下这篇文章 [为了解开互联网大厂秋招内幕,我把他们全面了一遍](https://mp.weixin.qq.com/s/pBsGQNxvRupZeWt4qZReIA),了解一下常见大厂的面试题侧重点。
+
+看似 Java 后端八股文很多,实际把复习范围一缩小,重要的东西就是那些。考虑到时间问题,你不可能连一些比较冷门的知识点也给准备了。这没必要,主要精力先放在那些重要的知识点即可。
+
+## 如何更高效地准备八股文?
+
+对于技术八股文来说,尽量不要死记硬背,这种方式非常枯燥且对自身能力提升有限!但是!想要一点不背是不太现实的,只是说要结合实际应用场景和实战来理解记忆。
+
+我一直觉得面试八股文最好是和实际应用场景和实战相结合。很多同学现在的方向都错了,上来就是直接背八股文,硬生生学成了文科,那当然无趣了。
+
+举个例子:你的项目中需要用到 Redis 来做缓存,你对照着官网简单了解并实践了简单使用 Redis 之后,你去看了 Redis 对应的八股文。你发现 Redis 可以用来做限流、分布式锁,于是你去在项目中实践了一下并掌握了对应的八股文。紧接着,你又发现 Redis 内存不够用的情况下,还能使用 Redis Cluster 来解决,于是你就又去实践了一下并掌握了对应的八股文。
+
+**一定要记住你的主要目标是理解和记关键词,而不是像背课文一样一字一句地记下来!**
+
+另外,记录博客或者用自己的理解把对应的知识点讲给别人听也是一个不错的选择。
+
+最后,准备技术面试的同学一定要定期复习(自测的方式非常好),不然确实会遗忘的。
diff --git a/docs/interview-preparation/project-experience-guide.md b/docs/interview-preparation/project-experience-guide.md
new file mode 100644
index 0000000000000000000000000000000000000000..1c684e9a3db3b43145e43176fd656d9b8d144083
--- /dev/null
+++ b/docs/interview-preparation/project-experience-guide.md
@@ -0,0 +1,114 @@
+---
+title: 项目经验指南
+category: 面试准备
+icon: project
+---
+
+::: tip 友情提示
+本文节选自 **[《Java 面试指北》](../zhuanlan/java-mian-shi-zhi-bei.md)**。这是一份教你如何更高效地准备面试的小册,涵盖常见八股文(系统设计、常见框架、分布式、高并发 ......)、优质面经等内容。
+:::
+
+## 没有项目经验怎么办?
+
+没有项目经验是大部分应届生会碰到的一个问题。甚至说,有很多有工作经验的程序员,对自己在公司做的项目不满意,也想找一个比较有技术含量的项目来做。
+
+说几种我觉得比较靠谱的获取项目经验的方式,希望能够对你有启发。
+
+### 实战项目视频/专栏
+
+在网上找一个符合自己能力与找工作需求的实战项目视频或者专栏,跟着老师一起做。
+
+你可以通过慕课网、哔哩哔哩、拉勾、极客时间、培训机构(比如黑马、尚硅谷)等渠道获取到适合自己的实战项目视频/专栏。
+
+
+
+尽量选择一个适合自己的项目,没必要必须做分布式/微服务项目,对于绝大部分同学来说,能把一个单机项目做好就已经很不错了。
+
+我面试过很多求职者,简历上看着有微服务的项目经验,结果随便问两个问题就知道根本不是自己做的或者说做的时候压根没认真思考。这种情况会给我留下非常不好的印象。
+
+我在 **[《Java 面试指北》](../zhuanlan/java-mian-shi-zhi-bei.md)** 的「面试准备篇」中也说过:
+
+> 个人认为也没必要非要去做微服务或者分布式项目,不一定对你面试有利。微服务或者分布式项目涉及的知识点太多,一般人很难吃透。并且,这类项目其实对于校招生来说稍微有一点超标了。即使你做出来,很多面试官也会认为不是你独立完成的。
+>
+> 其实,你能把一个单体项目做到极致也很好,对于个人能力提升不比做微服务或者分布式项目差。如何做到极致?代码质量这里就不提了,更重要的是你要尽量让自己的项目有一些亮点(比如你是如何提升项目性能的、如何解决项目中存在的一个痛点的),项目经历取得的成果尽量要量化一下比如我使用 xxx 技术解决了 xxx 问题,系统 qps 从 xxx 提高到了 xxx。
+
+跟着老师做的过程中,你一定要有自己的思考,不要浅尝辄止。对于很多知识点,别人的讲解可能只是满足项目就够了,你自己想多点知识的话,对于重要的知识点就要自己学会去深入学习。
+
+### 实战类开源项目
+
+GitHub 或者码云上面有很多实战类别项目,你可以选择一个来研究,为了让自己对这个项目更加理解,在理解原有代码的基础上,你可以对原有项目进行改进或者增加功能。
+
+你可以参考 [Java 优质开源实战项目](https://javaguide.cn/open-source-project/practical-project.html "Java 优质开源实战项目") 上面推荐的实战类开源项目,质量都很高,项目类型也比较全面,涵盖博客/论坛系统、考试/刷题系统、商城系统、权限管理系统、快速开发脚手架以及各种轮子。
+
+
+
+一定要记住:**不光要做,还要改进,改善。不论是实战项目视频或者专栏还是实战类开源项目,都一定会有很多可以完善改进的地方。**
+
+### 从头开始做
+
+自己动手去做一个自己想完成的东西,遇到不会的东西就临时去学,现学现卖。
+
+这个要求比较高,我建议你已经有了一个项目经验之后,再采用这个方法。如果你没有做过项目的话,还是老老实实采用上面两个方法比较好。
+
+### 参加各种大公司组织的各种大赛
+
+如果参加这种赛事能获奖的话,项目含金量非常高。即使没获奖也没啥,也可以写简历上。
+
+
+
+### 参与实际项目
+
+通常情况下,你有如下途径接触到企业实际项目的开发:
+
+1. 老师接的项目;
+2. 自己接的私活;
+3. 实习/工作接触到的项目;
+
+老师接的项目和自己接的私活通常都是一些偏业务的项目,很少会涉及到性能优化。这种情况下,你可以考虑对项目进行改进,别怕花时间,某个时间用心做好一件事情就好比如你对项目的数据模型进行改进、引入缓存提高访问速度等等。
+
+实习/工作接触到的项目类似,如果遇到一些偏业务的项目,也是要自己私下对项目进行改进优化。
+
+尽量是真的对项目进行了优化,这本身也是对个人能力的提升。如果你实在是没时间去实践的话,也没关系,吃透这个项目优化手段就好,把一些面试可能会遇到的问题提前准备一下。
+
+## 有没有还不错的项目推荐?
+
+**[《Java 面试指北》](../zhuanlan/java-mian-shi-zhi-bei.md)** 的「面试准备篇」中有一篇文章专门整理了一些比较高质量的实战项目,非常适合用来学习或者作为项目经验。
+
+
+
+这篇文章一共推荐了 15+ 个实战项目,有业务类的,也有轮子类的,有开源项目、也有视频教程。对于参加校招的小伙伴,我更建议做一个业务类项目加上一个轮子类的项目。
+
+## 我跟着视频做的项目会被面试官嫌弃不?
+
+很多应届生都是跟着视频做的项目,这个大部分面试官都心知肚明。
+
+不排除确实有些面试官不吃这一套,这个也看人。不过我相信大多数面试官都是能理解的,毕竟你在学校的时候实际上是没有什么获得实际项目经验的途径的。
+
+大部分应届生的项目经验都是自己在网上找的或者像你一样买的付费课程跟着做的,极少部分是比较真实的项目。 从你能想着做一个实战项目来说,我觉得初衷是好的,确实也能真正学到东西。 但是,究竟有多少是自己掌握了很重要。看视频最忌讳的是被动接受,自己多改进一下,多思考一下!就算是你跟着视频做的项目,也是可以优化的!
+
+**如果你想真正学到东西的话,建议不光要把项目单纯完成跑起来,还要去自己尝试着优化!**
+
+简单说几个比较容易的优化点:
+
+1. **全局异常处理**:很多项目这方面都做的不是很好,可以参考我的这篇文章:[《使用枚举简单封装一个优雅的 Spring Boot 全局异常处理!》](https://mp.weixin.qq.com/s/Y4Q4yWRqKG_lw0GLUsY2qw) 来做优化。
+2. **项目的技术选型优化**:比如使用 Guava 做本地缓存的地方可以换成 **Caffeine** 。Caffeine 的各方面的表现要更加好!再比如 Controller 层是否放了太多的业务逻辑。
+3. **数据库方面**:数据库设计可否优化?索引是否使用使用正确?SQL 语句是否可以优化?是否需要进行读写分离?
+4. **缓存**:项目有没有哪些数据是经常被访问的?是否引入缓存来提高响应速度?
+5. **安全**:项目是否存在安全问题?
+6. ......
+
+另外,我在星球分享过常见的性能优化方向实践案例,涉及到多线程、异步、索引、缓存等方向,强烈推荐你看看: 。
+
+最后,**再给大家推荐一个 IDEA 优化代码的小技巧,超级实用!**
+
+分析你的代码:右键项目-> Analyze->Inspect Code
+
+
+
+扫描完成之后,IDEA 会给出一些可能存在的代码坏味道比如命名问题。
+
+
+
+并且,你还可以自定义检查规则。
+
+
diff --git a/docs/interview-preparation/resume-guide.md b/docs/interview-preparation/resume-guide.md
new file mode 100644
index 0000000000000000000000000000000000000000..8fd105f836af41d98b3fef30aa5d7ec4e0dcce7e
--- /dev/null
+++ b/docs/interview-preparation/resume-guide.md
@@ -0,0 +1,257 @@
+---
+title: 程序员简历编写指南(重要)
+category: 面试准备
+icon: jianli
+---
+
+::: tip 友情提示
+本文节选自 **[《Java 面试指北》](../zhuanlan/java-mian-shi-zhi-bei.md)**。这是一份教你如何更高效地准备面试的小册,涵盖常见八股文(系统设计、常见框架、分布式、高并发 ......)、优质面经等内容。
+:::
+
+## 前言
+
+一份好的简历可以在整个申请面试以及面试过程中起到非常重要的作用。
+
+**为什么说简历很重要呢?** 我们可以从下面几点来说:
+
+**1、简历就像是我们的一个门面一样,它在很大程度上决定了是否能够获得面试机会。**
+
+- 假如你是网申,你的简历必然会经过 HR 的筛选,一张简历 HR 可能也就花费 10 秒钟左右看一下,然后决定你能否进入面试。
+- 假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。
+
+另外,就算你通过了第一轮的筛选获得面试机会,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。
+
+**2、简历上的内容很大程度上决定了面试官提问的侧重点。**
+
+- 一般情况下你的简历上注明你会的东西才会被问到(Java 基础、集合、并发、MySQL、Redis 、Spring、Spring Boot 这些算是每个人必问的),比如写了你熟练使用 Redis,那面试官就很大概率会问你 Redis 的一些问题,再比如你写了你在项目中使用了消息队列,那面试官大概率问很多消息队列相关的问题。
+- 技能熟练度在很大程度上也决定了面试官提问的深度。
+
+在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。
+
+## 简历模板
+
+简历的样式真的非常非常重要!!!如果你的简历样式丑到没朋友的话,面试官真的没有看下去的欲望。一天处理上百份的简历的痛苦,你不懂!
+
+我这里的话,推荐大家使用 Markdown 语法写简历,然后再将 Markdown 格式转换为 PDF 格式后进行简历投递。如果你对 Markdown 语法不太了解的话,可以花半个小时简单看一下 Markdown 语法说明: 。
+
+下面是我收集的一些还不错的简历模板:
+
+- 适合中文的简历模板收集(推荐,免费):
+- 木及简历(部分收费) :
+- 简单简历(付费):
+- 站长简历:
+- typora+markdown+css 自定义简历模板 :
+- 极简简历 :
+- Markdown 简历排版工具:
+- 超级简历(部分收费) :
+
+上面这些简历模板大多是只有 1 页内容,很难展现足够的信息量。如果你不是顶级大牛(比如 ACM 大赛获奖)的话,我建议还是尽可能多写一点可以突出你自己能力的内容(校招生 2 页之内,社招生 3 页之内,记得精炼语言,不要过多废话)。
+
+再总结几点 **简历排版的注意事项**:
+
+- 尽量简洁,不要太花里胡哨。
+- 技术名词最好规范大小写比较好,比如 java->Java ,spring boot -> Spring Boot 。这个虽然有些面试官不会介意,但是很多面试官都会在意这个细节的。
+- 中文和数字英文之间加上空格的话看起来会舒服一点。
+
+## 简历内容
+
+### 个人信息
+
+- 最基本的 :姓名(身份证上的那个)、年龄、电话、籍贯、联系方式、邮箱地址
+- 潜在加分项 : Github 地址、博客地址(如果技术博客和 Github 上没有什么内容的话,就不要写了)
+
+示例:
+
+
+
+**简历要不要放照片呢?** 很多人写简历的时候都有这个问题。
+
+其实放不放都行,影响不大,完全不用在意这个问题。除非,你投递的岗位明确要求要放照片。 不过,如果要放的话,不要放生活照,还是应该放正规一些的照片比如证件照。
+
+### 求职意向
+
+你想要应聘什么岗位,希望在什么城市。另外,你也可以将求职意向放到个人信息这块写。
+
+示例:
+
+
+
+### 教育经历
+
+教育经历也不可或缺。通过教育经历的介绍,你要确保能让面试官就可以知道你的学历、专业、毕业学校以及毕业的日期。
+
+示例:
+
+> 北京理工大学 硕士,软件工程 2019.09 - 2022.01
+> 湖南大学 学士,应用化学 2015.09 ~ 2019.06
+
+### 专业技能
+
+先问一下你自己会什么,然后看看你意向的公司需要什么。一般 HR 可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。
+
+下面是一份最新的 Java 后端开发技能清单,你可以根据自身情况以及岗位招聘要求做动态调整,核心思想就是尽可能满足岗位招聘的所有技能要求。
+
+
+
+我这里再单独放一个我看过的某位同学的技能介绍,我们来找找问题。
+
+
+
+上图中的技能介绍存在的问题:
+
+- 技术名词最好规范大小写比较好,比如 java->Java ,spring boot -> Spring Boot 。这个虽然有些面试官不会介意,但是很多面试官都会在意这个细节的。
+- 技能介绍太杂,没有亮点。不需要全才,某个领域做得好就行了!
+- 对 Java 后台开发的部分技能比如 Spring Boot 的熟悉度仅仅为了解,无法满足企业的要求。
+
+### 实习经历/工作经历(重要)
+
+工作经历针对社招,实习经历针对校招。
+
+工作经历建议采用时间倒序的方式来介绍。实习经历和工作经历都需要简单突出介绍自己在职期间主要做了什么。
+
+示例:
+
+> **XXX 公司 (201X 年 X 月 ~ 201X 年 X 月 )**
+>
+> - **职位**:Java 后端开发工程师
+> - **工作内容**:主要负责 XXX
+
+### 项目经历(重要)
+
+简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。
+
+很多求职者的项目经历介绍都会面临过于啰嗦、过于简单、没突出亮点等问题。
+
+项目经历介绍模板如下:
+
+> 项目名称(字号要大一些)
+>
+> 2017-05~2018-06 淘宝 Java 后端开发工程师
+>
+> - **项目描述** : 简单描述项目是做什么的。
+> - **技术栈** :用了什么技术(如 Spring Boot + MySQL + Redis + Mybatis-plus + Spring Security + Oauth2)
+> - **工作内容/个人职责** : 简单描述自己做了什么,解决了什么问题,带来了什么实质性的改善。突出自己的能力,不要过于平淡的叙述。
+> - **个人收获(可选)** : 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用。通常是可以不用写个人收获的,因为你在个人职责介绍中写的东西已经表明了自己的主要收获。
+> - **项目成果(可选)** :简单描述这个项目取得了什么成绩。
+
+**1、项目经历应该突出自己做了什么,简单概括项目基本情况。**
+
+项目介绍尽量压缩在两行之内,不需要介绍太多,但也不要随便几个字就介绍完了。
+
+**2、技术架构直接写技术名词就行,不要再介绍技术是干嘛的了,没意义,属于无效介绍。**
+
+
+
+**3、尽量减少纯业务的个人职责介绍,对于面试不太友好。尽量再多挖掘一些亮点(6~8 条个人职责介绍差不多了,做好筛选),最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目优化了某个模块的性能。**
+
+即使不是你做的功能模块或者解决的问题,你只要搞懂吃透了就能拿来自己用,适当润色即可!
+
+像性能优化方向上的亮点面试之前也比较容易准备,但也不要都是性能优化相关的,这种也算是一个极端。
+
+另外,技术优化取得的成果尽量要量化一下:
+
+- 使用 xxx 技术解决了 xxx 问题,系统 QPS 从 xxx 提高到了 xxx。
+- 使用 xxx 技术了优化了 xxx 接口,系统 QPS 从 xxx 提高到了 xxx。
+- 使用 xxx 技术解决了 xxx 问题,查询速度优化了 xxx,系统 QPS 达到 10w+。
+- 使用 xxx 技术优化了 xxx 模块,响应时间从 2s 降低到 0.2s。
+- ......
+
+示例:
+
+- 使用 Sharding-JDBC 对 MySQL 数据库进行分库分表,优化千万级大表,单表数据量保持在 500w 以下。
+- 热门数据(如首页、热门博客)使用 Redis+Caffeine 两级缓存,解决了缓存击穿和穿透问题,查询速度毫秒级,QPS 30w+。
+- 使用 CompletableFuture 优化购物车查询模块,对获取用户信息、商品详情、优惠券信息等异步 RPC 调用进行编排,响应时间从 2s 降低 0.2s。
+
+**4、如果你觉得你的项目技术比较落后的话,可以自己私下进行改进。重要的是让项目比较有亮点,通过什么方式就无所谓了。**
+
+项目经历这部分对于简历来说非常重要,《Java 面试指北》的面试准备篇有好几篇关于优化项目经历的文章,建议你仔细阅读一下,应该会对你有帮助。
+
+
+
+**5、避免个人职责介绍都是围绕一个技术点来写,非常不可取。**
+
+
+
+### 荣誉奖项(可选)
+
+如果你有含金量比较高的竞赛(比如 ACM、阿里的天池大赛)的获奖经历的话,荣誉奖项这块内容一定要写一下!并且,你还可以将荣誉奖项这块内容适当往前放,放在一个更加显眼的位置。
+
+### 校园经历(可选)
+
+如果有比较亮眼的校园经历的话就简单写一下,没有就不写!
+
+### 个人评价
+
+**个人评价就是对自己的解读,一定要用简洁的语言突出自己的特点和优势,避免废话!** 像勤奋、吃苦这些比较虚的东西就不要扯了,面试官看着这种个人评价就烦。
+
+我们可以从下面几个角度来写个人评价:
+
+- 文档编写能力、学习能力、沟通能力、团队协作能力
+- 对待工作的态度以及个人的责任心
+- 能承受的工作压力以及对待困难的态度
+- 对技术的追求、对代码质量的追求
+- 分布式、高并发系统开发或维护经验
+
+列举 3 个实际的例子:
+
+- 学习能力较强,大三参加国家软件设计大赛的时候快速上手 Python 写了一个可配置化的爬虫系统。
+- 具有团队协作精神,大三参加国家软件设计大赛的时候协调项目组内 5 名开发同学,并对编码遇到困难的同学提供帮助,最终顺利在 1 个月的时间完成项目的核心功能。
+- 项目经验丰富,在校期间主导过多个企业级项目的开发。
+
+## STAR 法则和 FAB 法则
+
+### STAR 法则(Situation Task Action Result)
+
+相信大家一定听说过 STAR 法则。对于面试,你可以将这个法则用在自己的简历以及和面试官沟通交流的过程中。
+
+STAR 法则由下面 4 个单词组成(STAR 法则的名字就是由它们的首字母组成):
+
+- **Situation:** 情景。 事情是在什么情况下发生的?
+- **Task::** 任务。你的任务是什么?
+- **Action:** 行动。你做了什么?
+- **Result:** 结果。最终的结果怎样?
+
+### FAB 法则(Feature Advantage Benefit)
+
+除了 STAR 法则,你还需要了解在销售行业经常用到的一个叫做 FAB 的法则。
+
+FAB 法则由下面 3 个单词组成(FAB 法则的名字就是由它们的首字母组成):
+
+- **Feature:** 你的特征/优势是什么?
+- **Advantage:** 比别人好在哪些地方;
+- **Benefit:** 如果雇佣你,招聘方会得到什么好处。
+
+简单来说,**FAB 法则主要是让你的面试官知道你的优势和你能为公司带来的价值。**
+
+## 建议
+
+### 避免页数过多
+
+精简表述,突出亮点。校招简历建议不要超过 2 页,社招简历建议不要超过 3 页。如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。
+
+看了几千份简历,有少部分同学的简历页数都接近 10 页了,让我头皮发麻。
+
+
+
+### 避免语义模糊
+
+尽量避免主观表述,少一点语义模糊的形容词。表述要简洁明了,简历结构要清晰。
+
+举例:
+
+- 不好的表述:我在团队中扮演了很重要的角色。
+- 好的表述:我作为后端技术负责人,领导团队完成后端项目的设计与开发。
+
+### 注意简历样式
+
+简历样式同样很重要,一定要注意!不必追求花里胡哨,但要尽量保证结构清晰且易于阅读。
+
+### 其他
+
+- 一定要使用 PDF 格式投递,不要使用 Word 或者其他格式投递。这是最基本的!
+- 不会的东西就不要写在简历上了。注意简历真实性,适当润色没有问题。
+- 工作经历建议采用时间倒序的方式来介绍,实习经历建议将最有价值的放在最前面。
+- 将自己的项目经历完美的展示出来非常重要,重点是突出自己做了什么(挖掘亮点),而不是介绍项目是做什么的。
+- 项目经历建议以时间倒序排序,另外项目经历不在于多(精选 2~3 即可),而在于有亮点。
+- 准备面试的过程中应该将你写在简历上的东西作为重点,尤其是项目经历上和技能介绍上的。
+- 面试和工作是两回事,聪明的人会把面试官往自己擅长的领域领,其他人则被面试官牵着鼻子走。虽说面试和工作是两回事,但是你要想要获得自己满意的 offer ,你自身的实力必须要强。
diff --git a/docs/interview-preparation/self-test-of-common-interview-questions.md b/docs/interview-preparation/self-test-of-common-interview-questions.md
new file mode 100644
index 0000000000000000000000000000000000000000..85b0e236c0187f03d740433c114bf9c0cba1f087
--- /dev/null
+++ b/docs/interview-preparation/self-test-of-common-interview-questions.md
@@ -0,0 +1,19 @@
+---
+title: 常见面试题自测(付费)
+category: 知识星球
+icon: security-fill
+---
+
+面试之前,强烈建议大家多拿常见的面试题来进行自测,检查一下自己的掌握情况,这是一种非常实用的备战技术面试的小技巧。
+
+在 **[《Java 面试指北》](../zhuanlan/java-mian-shi-zhi-bei.md)** 的 **「技术面试题自测篇」** ,我总结了 Java 面试中最重要的知识点的最常见的面试题并按照面试提问的方式展现出来。
+
+
+
+每一道用于自测的面试题我都会给出重要程度,方便大家在时间比较紧张的时候根据自身情况来选择性自测。并且,我还会给出提示,方便你回忆起对应的知识点。
+
+在面试中如果你实在没有头绪的话,一个好的面试官也是会给你提示的。
+
+
+
+
diff --git a/docs/interview-preparation/teach-you-how-to-prepare-for-the-interview-hand-in-hand.md b/docs/interview-preparation/teach-you-how-to-prepare-for-the-interview-hand-in-hand.md
new file mode 100644
index 0000000000000000000000000000000000000000..0fb6e5be442e51d5055c06a89d8b7502ab25d101
--- /dev/null
+++ b/docs/interview-preparation/teach-you-how-to-prepare-for-the-interview-hand-in-hand.md
@@ -0,0 +1,234 @@
+---
+title: 手把手教你如何准备Java面试(重要)
+category: 知识星球
+icon: path
+---
+
+::: tip 友情提示
+本文节选自 **[《Java 面试指北》](../zhuanlan/java-mian-shi-zhi-bei.md)**。这是一份教你如何更高效地准备面试的小册,涵盖常见八股文(系统设计、常见框架、分布式、高并发 ......)、优质面经等内容。
+:::
+
+你的身边一定有很多编程比你厉害但是找的工作并没有你好的朋友!**技术面试不同于编程,编程厉害不代表技术面试就一定能过。**
+
+现在你去面个试,不认真准备一下,那简直就是往枪口上撞。我们大部分都只是普通人,没有发过顶级周刊或者获得过顶级大赛奖项。在这样一个技术面试氛围下,我们需要花费很多精力来准备面试,来提高自己的技术能力。“[面试造火箭,工作拧螺丝钉](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247491596&idx=1&sn=36fbf80922f71c200990de11514955f7&chksm=cea1afc7f9d626d1c70d5e54505495ac499ce6eb5e05ba4f4bb079a8563a84e27f17ceff38af&token=353590436&lang=zh_CN&scene=21#wechat_redirect)” 就是目前的一个常态,预计未来很长很长一段时间也还是会是这样。
+
+准备面试不等于耍小聪明或者死记硬背面试题。 **一定不要对面试抱有侥幸心理。打铁还需自身硬!** 千万不要觉得自己看几篇面经,看几篇面试题解析就能通过面试了。一定要静下心来深入学习!
+
+这篇我会从宏观面出发简单聊聊如何准备 Java 面试,让你少走弯路!
+
+## 尽早以求职为导向来学习
+
+我是比较建议还在学校的同学尽可能早一点以求职为导向来学习的。
+
+**这样更有针对性,并且可以大概率减少自己处在迷茫的时间,很大程度上还可以让自己少走很多弯路。**
+
+但是!不要把“以求职为导向学习”理解为“我就不用学课堂上那些计算机基础课程了”!
+
+我在之前的很多次分享中都强调过:**一定要用心学习计算机基础知识!操作系统、计算机组成原理、计算机网络真的不是没有实际用处的学科!!!**
+
+你会发现大厂面试你会用到,以后工作之后你也会用到。我分别列举 2 个例子吧!
+
+- **面试中**:像字节、腾讯这些大厂的技术面试以及几乎所有公司的笔试都会考操作系统相关的问题。
+- **工作中**:在实际使用缓存的时候,你会发现在操作系统中可以找到很多缓存思想的影子。比如 CPU Cache 缓存的是内存数据用于解决 CPU 处理速度和内存不匹配的问题,内存缓存的是硬盘数据用于解决硬盘访问速度过慢的问题。再比如操作系统在页表方案基础之上引入了快表来加速虚拟地址到物理地址的转换。我们可以把快表理解为一种特殊的高速缓冲存储器(Cache)。
+
+**如何求职为导向学习呢?** 简答来说就是:根据招聘要求整理一份目标岗位的技能清单,然后按照技能清单去学习和提升。
+
+1. 你首先搞清楚自己要找什么工作
+2. 然后根据招聘岗位的要求梳理一份技能清单
+3. 根据技能清单写好最终的简历
+4. 最后再按照建立的要求去学习和提升。
+
+这其实也是 **以终为始** 思想的运用。
+
+**何为以终为始?** 简单来说,以终为始就是我们可以站在结果来考虑问题,从结果出发,根据结果来确定自己要做的事情。
+
+你会发现,其实几乎任何领域都可以用到 **以终为始** 的思想。
+
+## 了解投递简历的黄金时间
+
+面试之前,你肯定是先要搞清楚春招和秋招的具体时间的。
+
+正所谓金三银四,金九银十,错过了这个时间,很多公司都没有 HC 了。
+
+**秋招一般 7 月份就开始了,大概一直持续到 9 月底。**
+
+**春招一般 3 月份就开始了,大概一直持续到 4 月底。**
+
+很多公司(尤其大厂)到了 9 月中旬(秋招)/3 月中旬(春招),很可能就会没有 HC 了。面试的话一般都是至少是 3 轮起步,一些大厂比如阿里、字节可能会有 5 轮面试。**面试失败话的不要紧,某一面表现差的话也不要紧,调整好心态。又不是单一选择对吧?你能投这么多企业呢! 调整心态。** 今年面试的话,因为疫情原因,有些公司还是可能会还是集中在线上进行面试。然后,还是因为疫情的影响,可能会比往年更难找工作(对大厂影响较小)。
+
+## 知道如何获取招聘信息
+
+下面是常见的获取招聘信息的渠道:
+
+- **目标企业的官网/公众号**:最及时最权威的获取招聘信息的途径。
+- **招聘网站**:[BOSS 直聘](https://www.zhipin.com/)、[智联招聘](https://www.zhaopin.com/)、[拉勾招聘](https://www.lagou.com/)......。
+- **牛客网**:每年秋招/春招,都会有大批量的公司会到牛客网发布招聘信息,并且还会有大量的公司员工来到这里发内推的帖子。地址: 。
+- **超级简历**:超级简历目前整合了各大企业的校园招聘入口,地址:
+- **认识的朋友**:如果你有认识的朋友在目标企业工作的话,你也可以找他们了解招聘信息,并且可以让他们帮你内推。
+- **宣讲会**:宣讲会也是一个不错的途径,不过,好的企业通常只会去比较好的学校,可以留意一下意向公司的宣讲会安排或者直接去到一所比较好的学校参加宣讲会。像我当时校招就去参加了几场宣讲会。不过,我是在荆州上学,那边没什么比较好的学校,一般没有公司去开宣讲会。所以,我当时是直接跑到武汉来了,参加了武汉理工大学以及华中科技大学的几场宣讲会。总体感觉还是很不错的!
+- **其他**:校园就业信息网、学校论坛、班级 or 年级 QQ 群。
+
+校招的话,建议以官网为准,有宣讲会、靠谱一点的内推的话更好。社招的话,可以多留意一下各大招聘网站比如 BOSS 直聘、拉勾上的职位信息,也可以找被熟人内推,获得面试机会的概率更大一些,进度一般也更快一些。
+
+一般是只能投递一个岗位,不过,也有极少数投递不同部门两个岗位的情况,这个应该不会有影响,但你的前一次面试情况可能会被记录,也就是说就算你投递成功两个岗位,第一个岗位面试失败的话,对第二个岗位也会有影响,很可能直接就被 pass。
+
+## 多花点时间完善简历
+
+一定一定一定要重视简历啊!朋友们!至少要花 2~3 天时间来专门完善自己的简历。
+
+最近看了很多份简历,满意的很少,我简单拿出一份来说分析一下(欢迎在评论区补充)。
+
+**1.个人介绍没太多实用的信息。**
+
+
+
+技术博客、GitHub 以及在校获奖经历的话,能写就尽量写在这里。 你可以参考下面 👇 的模板进行修改:
+
+
+
+**2.项目经历过于简单,完全没有质量可言**
+
+
+
+每一个项目经历真的就一两句话可以描述了么?还是自己不想写?还是说不是自己做的,不敢多写。
+
+如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:
+
+1. 你对项目整体设计的一个感受(面试官可能会让你画系统的架构图)
+2. 你在这个项目中你负责了什么、做了什么、担任了什么角色。
+3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用。
+4. 你在这个项目中是否解决过什么问题?怎么解决的?收获了什么?
+5. 你的项目用到了哪些技术?这些技术你吃透了没有?举个例子,你的项目经历使用了 Seata 来做分布式事务,那 Seata 相关的问题你要提前准备一下吧,比如说 Seata 支持哪些配置中心、Seata 的事务分组是怎么做的、Seata 支持哪些事务模式,怎么选择?
+6. 你在这个项目中犯过的错误,最后是怎么弥补的?
+
+**3.计算机二级这个证书对于计算机专业完全不用写了,没有含金量的。**
+
+
+
+**4.技能介绍问题太大。**
+
+
+
+- 技术名词最好规范大小写比较好,比如 java->Java ,spring boot -> Spring Boot 。这个虽然有些面试官不会介意,但是很多面试官都会在意这个细节的。
+- 技能介绍太杂,没有亮点。不需要全才,某个领域做得好就行了!
+- 对 Java 后台开发的部分技能比如 Spring Boot 的熟悉度仅仅为了解,无法满足企业的要求。
+
+## 岗位匹配度很重要
+
+校招通常会对你的项目经历的研究方向比较宽容,即使你的项目经历和对应公司的具体业务没有关系,影响其实也并不大。
+
+社招的话就不一样了,毕竟公司是要招聘可以直接来干活的人,你有相关的经验,公司会比较省事。社招通常会比较重视你的过往工作经历以及项目经历,HR 在筛选简历的时候会根据这两方面信息来判断你是否满足他们的招聘要求。就比如说你投递电商公司,而你之前的并没有和电商相关的工作经历以及项目经历,那 HR 在筛简历的时候很可能会直接把你 Pass 掉。
+
+不过,这个也并不绝对,也有一些公司在招聘的时候更看重的是你的过往经历,较少地关注岗位匹配度,优秀公司的工作经历以及有亮点的项目经验都是加分项。这类公司相信你既然在某个领域(比如电商、支付)已经做的不错了,那应该也可以在另外一个领域(比如流媒体平台、社交软件)很快成为专家。这个领域指的不是技术领域,更多的是业务方向。横跨技术领域(比如后端转算法、后端转大数据)找工作,你又没有相关的经验,几乎是没办法找到的。即使找到了,也大概率会面临 HR 压薪资的问题。
+
+## 提前准备技术面试和手撕算法
+
+面试之前一定要提前准备一下常见的面试题:
+
+- 自己面试中可能涉及哪些知识点、那些知识点是重点。
+- 面试中哪些问题会被经常问到、面试中自己改如何回答。(强烈不推荐死记硬背,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
+
+这块内容只会介绍面试大概会涉及到哪方面的知识点,具体这些知识点涵盖哪些问题,后面的文章有介绍到。
+
+**Java** :
+
+- Java 基础
+- Java 集合
+- Java 并发
+- JVM
+
+**计算机基础**:
+
+- 算法
+- 数据结构
+- 计算机网络
+- 操作系统
+
+**数据库**:
+
+- MySQL
+- Redis
+
+**常用框架**:
+
+- Spring
+- SpringBoot
+- MyBatis
+- Netty
+- Zookeeper
+- Dubbo
+
+**分布式** :
+
+- CAP 理论 和 BASE 理论、Paxos 算法和 Raft 算法
+- RPC
+- 分布式事务
+- 分布式 ID
+
+**高并发**:
+
+- 消息队列
+- 读写分离&分库分表
+- 负载均衡
+
+**高可用**:
+
+- 限流
+- 降级
+- 熔断
+
+
+
+不同类型的公司对于技能的要求侧重点是不同的比如腾讯、字节可能更重视计算机基础比如网络、操作系统这方面的内容。阿里、美团这种可能更重视你的项目经历、实战能力。
+
+关于如何准备算法面试请看《Java 面试指北》的「面试准备篇」中对应的文章。
+
+## 提前准备自我介绍
+
+自我介绍一般是你和面试官的第一次面对面正式交流,换位思考一下,假如你是面试官的话,你想听到被你面试的人如何介绍自己呢?一定不是客套地说说自己喜欢编程、平时花了很多时间来学习、自己的兴趣爱好是打球吧?
+
+我觉得一个好的自我介绍至少应该包含这几点要素:
+
+- 用简洁的话说清楚自己主要的技术栈于擅长的领域;
+- 把重点放在自己在行的地方以及自己的优势之处;
+- 重点突出自己的能力比如自己的定位的 bug 的能力特别厉害;
+
+简单来说就是用简洁的语言突出自己的亮点,也就是推销自己嘛!
+
+- 如果你去过大公司实习,那对应的实习经历就是你的亮点。
+- 如果你参加过技术竞赛,那竞赛经历就是你的亮点。
+- 如果你大学就接触过企业级项目的开发,实战经验比较多,那这些项目经历就是你的亮点。
+- ......
+
+从社招和校招两个角度来举例子吧!我下面的两个例子仅供参考,自我介绍并不需要死记硬背,记住要说的要点,面试的时候根据公司的情况临场发挥也是没问题的。另外,网上一般建议的是准备好两份自我介绍:一份对 hr 说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节和项目经验。
+
+**社招:**
+
+> 面试官,您好!我叫独秀儿。我目前有 1 年半的工作经验,熟练使用 Spring、MyBatis 等框架、了解 Java 底层原理比如 JVM 调优并且有着丰富的分布式开发经验。离开上一家公司是因为我想在技术上得到更多的锻炼。在上一个公司我参与了一个分布式电子交易系统的开发,负责搭建了整个项目的基础架构并且通过分库分表解决了原始数据库以及一些相关表过于庞大的问题,目前这个网站最高支持 10 万人同时访问。工作之余,我利用自己的业余时间写了一个简单的 RPC 框架,这个框架用到了 Netty 进行网络通信, 目前我已经将这个项目开源,在 GitHub 上收获了 2k 的 Star! 说到业余爱好的话,我比较喜欢通过博客整理分享自己所学知识,现在已经是多个博客平台的认证作者。 生活中我是一个比较积极乐观的人,一般会通过运动打球的方式来放松。我一直都非常想加入贵公司,我觉得贵公司的文化和技术氛围我都非常喜欢,期待能与你共事!
+
+**校招:**
+
+> 面试官,您好!我叫秀儿。大学时间我主要利用课外时间学习了 Java 以及 Spring、MyBatis 等框架 。在校期间参与过一个考试系统的开发,这个系统的主要用了 Spring、MyBatis 和 shiro 这三种框架。我在其中主要担任后端开发,主要负责了权限管理功能模块的搭建。另外,我在大学的时候参加过一次软件编程大赛,我和我的团队做的在线订餐系统成功获得了第二名的成绩。我还利用自己的业余时间写了一个简单的 RPC 框架,这个框架用到了 Netty 进行网络通信, 目前我已经将这个项目开源,在 GitHub 上收获了 2k 的 Star! 说到业余爱好的话,我比较喜欢通过博客整理分享自己所学知识,现在已经是多个博客平台的认证作者。 生活中我是一个比较积极乐观的人,一般会通过运动打球的方式来放松。我一直都非常想加入贵公司,我觉得贵公司的文化和技术氛围我都非常喜欢,期待能与你共事!
+
+## 减少抱怨
+
+就像现在的技术面试一样,大家都说内卷了,抱怨现在的面试真特么难。然而,单纯抱怨有用么?你对其他求职者说:“大家都不要刷 Leetcode 了啊!都不要再准备高并发、高可用的面试题了啊!现在都这么卷了!”
+
+会有人听你的么?**你不准备面试,但是其他人会准备面试啊!那你是不是傻啊?还是真的厉害到不需要准备面试呢?**
+
+因此,准备 Java 面试的第一步,我们一定要尽量减少抱怨。抱怨的声音多了之后,会十分影响自己,会让自己变得十分焦虑。
+
+## 面试之后及时复盘
+
+如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。
+
+面试就像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!
+
+## 总结
+
+这篇文章内容有点多,如果这篇文章只能让你记住 4 句话,那请记住下面这 4 句:
+
+1. 一定要提前准备面试!技术面试不同于编程,编程厉害不代表技术面试就一定能过。
+2. 一定不要对面试抱有侥幸心理。打铁还需自身硬!千万不要觉得自己看几篇面经,看几篇面试题解析就能通过面试了。一定要静下心来深入学习!
+3. 建议大学生尽可能早一点以求职为导向来学习的。这样更有针对性,并且可以大概率减少自己处在迷茫的时间,很大程度上还可以让自己少走很多弯路。 但是,不要把“以求职为导向学习”理解为“我就不用学课堂上那些计算机基础课程了”!
+4. 手撕算法是当下技术面试的标配,尽早准备!
diff --git "a/docs/java/basis/BigDecimal\350\247\243\345\206\263\346\265\256\347\202\271\346\225\260\350\277\220\347\256\227\347\262\276\345\272\246\344\270\242\345\244\261\351\227\256\351\242\230.md" "b/docs/java/basis/BigDecimal\350\247\243\345\206\263\346\265\256\347\202\271\346\225\260\350\277\220\347\256\227\347\262\276\345\272\246\344\270\242\345\244\261\351\227\256\351\242\230.md"
deleted file mode 100644
index 5f788f087e8b628153bcfa4cb647b78b6220c950..0000000000000000000000000000000000000000
--- "a/docs/java/basis/BigDecimal\350\247\243\345\206\263\346\265\256\347\202\271\346\225\260\350\277\220\347\256\227\347\262\276\345\272\246\344\270\242\345\244\261\351\227\256\351\242\230.md"
+++ /dev/null
@@ -1,69 +0,0 @@
-## BigDecimal 介绍
-
-`BigDecimal` 可以实现对浮点数的运算,不会造成精度丢失。
-
-那为什么浮点数 `float` 或 `double` 运算的时候会有精度丢失的风险呢?
-
-这是因为计算机是二进制的,浮点数没有办法用二进制精确表示。
-
-## BigDecimal 的用处
-
-《阿里巴巴Java开发手册》中提到:**浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。** 具体原理和浮点数的编码方式有关,这里就不多提了,我们下面直接上实例:
-
-```java
-float a = 1.0f - 0.9f;
-float b = 0.9f - 0.8f;
-System.out.println(a);// 0.100000024
-System.out.println(b);// 0.099999964
-System.out.println(a == b);// false
-```
-具有基本数学知识的我们很清楚的知道输出并不是我们想要的结果(**精度丢失**),我们如何解决这个问题呢?一种很常用的方法是:**使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作。**
-
-```java
-BigDecimal a = new BigDecimal("1.0");
-BigDecimal b = new BigDecimal("0.9");
-BigDecimal c = new BigDecimal("0.8");
-
-BigDecimal x = a.subtract(b);
-BigDecimal y = b.subtract(c);
-
-System.out.println(x); /* 0.1 */
-System.out.println(y); /* 0.1 */
-System.out.println(Objects.equals(x, y)); /* true */
-```
-
-## BigDecimal 常见方法
-
-## 大小比较
-
-`a.compareTo(b)` : 返回 -1 表示 `a` 小于 `b`,0 表示 `a` 等于 `b` , 1表示 `a` 大于 `b`。
-
-```java
-BigDecimal a = new BigDecimal("1.0");
-BigDecimal b = new BigDecimal("0.9");
-System.out.println(a.compareTo(b));// 1
-```
-### 保留几位小数
-
-通过 `setScale`方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA会提示。
-
-```java
-BigDecimal m = new BigDecimal("1.255433");
-BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
-System.out.println(n);// 1.255
-```
-
-## BigDecimal 的使用注意事项
-
-注意:我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 **BigDecimal(String)** 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。
-
-
-
-## 总结
-
-BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)。
-
-BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念
-
-
-
diff --git a/docs/java/basis/bigdecimal.md b/docs/java/basis/bigdecimal.md
new file mode 100644
index 0000000000000000000000000000000000000000..8363fd7890f169da2d4f0e80eed02853cce69326
--- /dev/null
+++ b/docs/java/basis/bigdecimal.md
@@ -0,0 +1,358 @@
+---
+title: BigDecimal 详解
+category: Java
+tag:
+ - Java基础
+---
+
+《阿里巴巴 Java 开发手册》中提到:“为了避免精度丢失,可以使用 `BigDecimal` 来进行浮点数的运算”。
+
+浮点数的运算竟然还会有精度丢失的风险吗?确实会!
+
+示例代码:
+
+```java
+float a = 2.0f - 1.9f;
+float b = 1.8f - 1.7f;
+System.out.println(a);// 0.100000024
+System.out.println(b);// 0.099999905
+System.out.println(a == b);// false
+```
+
+**为什么浮点数 `float` 或 `double` 运算的时候会有精度丢失的风险呢?**
+
+这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。
+
+就比如说十进制下的 0.2 就没办法精确转换成二进制小数:
+
+```java
+// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,
+// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。
+0.2 * 2 = 0.4 -> 0
+0.4 * 2 = 0.8 -> 0
+0.8 * 2 = 1.6 -> 1
+0.6 * 2 = 1.2 -> 1
+0.2 * 2 = 0.4 -> 0(发生循环)
+...
+```
+
+关于浮点数的更多内容,建议看一下[计算机系统基础(四)浮点数](http://kaito-kidd.com/2018/08/08/computer-system-float-point/)这篇文章。
+
+## BigDecimal 介绍
+
+`BigDecimal` 可以实现对浮点数的运算,不会造成精度丢失。
+
+通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 `BigDecimal` 来做的。
+
+《阿里巴巴 Java 开发手册》中提到:**浮点数之间的等值判断,基本数据类型不能用 == 来比较,包装数据类型不能用 equals 来判断。**
+
+
+
+具体原因我们在上面已经详细介绍了,这里就不多提了。
+
+想要解决浮点数运算精度丢失这个问题,可以直接使用 `BigDecimal` 来定义浮点数的值,然后再进行浮点数的运算操作即可。
+
+```java
+BigDecimal a = new BigDecimal("1.0");
+BigDecimal b = new BigDecimal("0.9");
+BigDecimal c = new BigDecimal("0.8");
+
+BigDecimal x = a.subtract(b);
+BigDecimal y = b.subtract(c);
+
+System.out.println(x.compareTo(y));// 0
+```
+
+## BigDecimal 常见方法
+
+### 创建
+
+我们在使用 `BigDecimal` 时,为了防止精度丢失,推荐使用它的`BigDecimal(String val)`构造方法或者 `BigDecimal.valueOf(double val)` 静态方法来创建对象。
+
+《阿里巴巴 Java 开发手册》对这部分内容也有提到,如下图所示。
+
+
+
+### 加减乘除
+
+`add` 方法用于将两个 `BigDecimal` 对象相加,`subtract` 方法用于将两个 `BigDecimal` 对象相减。`multiply` 方法用于将两个 `BigDecimal` 对象相乘,`divide` 方法用于将两个 `BigDecimal` 对象相除。
+
+```java
+BigDecimal a = new BigDecimal("1.0");
+BigDecimal b = new BigDecimal("0.9");
+System.out.println(a.add(b));// 1.9
+System.out.println(a.subtract(b));// 0.1
+System.out.println(a.multiply(b));// 0.90
+System.out.println(a.divide(b));// 无法除尽,抛出 ArithmeticException 异常
+System.out.println(a.divide(b, 2, RoundingMode.HALF_UP));// 1.11
+```
+
+这里需要注意的是,在我们使用 `divide` 方法的时候尽量使用 3 个参数版本,并且`RoundingMode` 不要选择 `UNNECESSARY`,否则很可能会遇到 `ArithmeticException`(无法除尽出现无限循环小数的时候),其中 `scale` 表示要保留几位小数,`roundingMode` 代表保留规则。
+
+```java
+public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode) {
+ return divide(divisor, scale, roundingMode.oldMode);
+}
+```
+
+保留规则非常多,这里列举几种:
+
+```java
+public enum RoundingMode {
+ // 2.5 -> 3 , 1.6 -> 2
+ // -1.6 -> -2 , -2.5 -> -3
+ UP(BigDecimal.ROUND_UP),
+ // 2.5 -> 2 , 1.6 -> 1
+ // -1.6 -> -1 , -2.5 -> -2
+ DOWN(BigDecimal.ROUND_DOWN),
+ // 2.5 -> 3 , 1.6 -> 2
+ // -1.6 -> -1 , -2.5 -> -2
+ CEILING(BigDecimal.ROUND_CEILING),
+ // 2.5 -> 2 , 1.6 -> 1
+ // -1.6 -> -2 , -2.5 -> -3
+ FLOOR(BigDecimal.ROUND_FLOOR),
+ // 2.5 -> 3 , 1.6 -> 2
+ // -1.6 -> -2 , -2.5 -> -3
+ HALF_UP(BigDecimal.ROUND_HALF_UP),
+ //......
+}
+```
+
+### 大小比较
+
+`a.compareTo(b)` : 返回 -1 表示 `a` 小于 `b`,0 表示 `a` 等于 `b` , 1 表示 `a` 大于 `b`。
+
+```java
+BigDecimal a = new BigDecimal("1.0");
+BigDecimal b = new BigDecimal("0.9");
+System.out.println(a.compareTo(b));// 1
+```
+
+### 保留几位小数
+
+通过 `setScale`方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA 会提示。
+
+```java
+BigDecimal m = new BigDecimal("1.255433");
+BigDecimal n = m.setScale(3,RoundingMode.HALF_DOWN);
+System.out.println(n);// 1.255
+```
+
+## BigDecimal 等值比较问题
+
+《阿里巴巴 Java 开发手册》中提到:
+
+
+
+`BigDecimal` 使用 `equals()` 方法进行等值比较出现问题的代码示例:
+
+```java
+BigDecimal a = new BigDecimal("1");
+BigDecimal b = new BigDecimal("1.0");
+System.out.println(a.equals(b));//false
+```
+
+这是因为 `equals()` 方法不仅仅会比较值的大小(value)还会比较精度(scale),而 `compareTo()` 方法比较的时候会忽略精度。
+
+1.0 的 scale 是 1,1 的 scale 是 0,因此 `a.equals(b)` 的结果是 false。
+
+
+
+`compareTo()` 方法可以比较两个 `BigDecimal` 的值,如果相等就返回 0,如果第 1 个数比第 2 个数大则返回 1,反之返回-1。
+
+```java
+BigDecimal a = new BigDecimal("1");
+BigDecimal b = new BigDecimal("1.0");
+System.out.println(a.compareTo(b));//0
+```
+
+## BigDecimal 工具类分享
+
+网上有一个使用人数比较多的 `BigDecimal` 工具类,提供了多个静态方法来简化 `BigDecimal` 的操作。
+
+我对其进行了简单改进,分享一下源码:
+
+```java
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+/**
+ * 简化BigDecimal计算的小工具类
+ */
+public class BigDecimalUtil {
+
+ /**
+ * 默认除法运算精度
+ */
+ private static final int DEF_DIV_SCALE = 10;
+
+ private BigDecimalUtil() {
+ }
+
+ /**
+ * 提供精确的加法运算。
+ *
+ * @param v1 被加数
+ * @param v2 加数
+ * @return 两个参数的和
+ */
+ public static double add(double v1, double v2) {
+ BigDecimal b1 = BigDecimal.valueOf(v1);
+ BigDecimal b2 = BigDecimal.valueOf(v2);
+ return b1.add(b2).doubleValue();
+ }
+
+ /**
+ * 提供精确的减法运算。
+ *
+ * @param v1 被减数
+ * @param v2 减数
+ * @return 两个参数的差
+ */
+ public static double subtract(double v1, double v2) {
+ BigDecimal b1 = BigDecimal.valueOf(v1);
+ BigDecimal b2 = BigDecimal.valueOf(v2);
+ return b1.subtract(b2).doubleValue();
+ }
+
+ /**
+ * 提供精确的乘法运算。
+ *
+ * @param v1 被乘数
+ * @param v2 乘数
+ * @return 两个参数的积
+ */
+ public static double multiply(double v1, double v2) {
+ BigDecimal b1 = BigDecimal.valueOf(v1);
+ BigDecimal b2 = BigDecimal.valueOf(v2);
+ return b1.multiply(b2).doubleValue();
+ }
+
+ /**
+ * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
+ * 小数点以后10位,以后的数字四舍五入。
+ *
+ * @param v1 被除数
+ * @param v2 除数
+ * @return 两个参数的商
+ */
+ public static double divide(double v1, double v2) {
+ return divide(v1, v2, DEF_DIV_SCALE);
+ }
+
+ /**
+ * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
+ * 定精度,以后的数字四舍五入。
+ *
+ * @param v1 被除数
+ * @param v2 除数
+ * @param scale 表示表示需要精确到小数点以后几位。
+ * @return 两个参数的商
+ */
+ public static double divide(double v1, double v2, int scale) {
+ if (scale < 0) {
+ throw new IllegalArgumentException(
+ "The scale must be a positive integer or zero");
+ }
+ BigDecimal b1 = BigDecimal.valueOf(v1);
+ BigDecimal b2 = BigDecimal.valueOf(v2);
+ return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
+ }
+
+ /**
+ * 提供精确的小数位四舍五入处理。
+ *
+ * @param v 需要四舍五入的数字
+ * @param scale 小数点后保留几位
+ * @return 四舍五入后的结果
+ */
+ public static double round(double v, int scale) {
+ if (scale < 0) {
+ throw new IllegalArgumentException(
+ "The scale must be a positive integer or zero");
+ }
+ BigDecimal b = BigDecimal.valueOf(v);
+ BigDecimal one = new BigDecimal("1");
+ return b.divide(one, scale, RoundingMode.HALF_UP).doubleValue();
+ }
+
+ /**
+ * 提供精确的类型转换(Float)
+ *
+ * @param v 需要被转换的数字
+ * @return 返回转换结果
+ */
+ public static float convertToFloat(double v) {
+ BigDecimal b = new BigDecimal(v);
+ return b.floatValue();
+ }
+
+ /**
+ * 提供精确的类型转换(Int)不进行四舍五入
+ *
+ * @param v 需要被转换的数字
+ * @return 返回转换结果
+ */
+ public static int convertsToInt(double v) {
+ BigDecimal b = new BigDecimal(v);
+ return b.intValue();
+ }
+
+ /**
+ * 提供精确的类型转换(Long)
+ *
+ * @param v 需要被转换的数字
+ * @return 返回转换结果
+ */
+ public static long convertsToLong(double v) {
+ BigDecimal b = new BigDecimal(v);
+ return b.longValue();
+ }
+
+ /**
+ * 返回两个数中大的一个值
+ *
+ * @param v1 需要被对比的第一个数
+ * @param v2 需要被对比的第二个数
+ * @return 返回两个数中大的一个值
+ */
+ public static double returnMax(double v1, double v2) {
+ BigDecimal b1 = new BigDecimal(v1);
+ BigDecimal b2 = new BigDecimal(v2);
+ return b1.max(b2).doubleValue();
+ }
+
+ /**
+ * 返回两个数中小的一个值
+ *
+ * @param v1 需要被对比的第一个数
+ * @param v2 需要被对比的第二个数
+ * @return 返回两个数中小的一个值
+ */
+ public static double returnMin(double v1, double v2) {
+ BigDecimal b1 = new BigDecimal(v1);
+ BigDecimal b2 = new BigDecimal(v2);
+ return b1.min(b2).doubleValue();
+ }
+
+ /**
+ * 精确对比两个数字
+ *
+ * @param v1 需要被对比的第一个数
+ * @param v2 需要被对比的第二个数
+ * @return 如果两个数一样则返回0,如果第一个数比第二个数大则返回1,反之返回-1
+ */
+ public static int compareTo(double v1, double v2) {
+ BigDecimal b1 = BigDecimal.valueOf(v1);
+ BigDecimal b2 = BigDecimal.valueOf(v2);
+ return b1.compareTo(b2);
+ }
+
+}
+```
+
+## 总结
+
+浮点数没有办法用二进制精确表示,因此存在精度丢失的风险。
+
+不过,Java 提供了`BigDecimal` 来操作浮点数。`BigDecimal` 的实现利用到了 `BigInteger` (用来操作大整数), 所不同的是 `BigDecimal` 加入了小数位的概念。
diff --git a/docs/java/basis/generics-and-wildcards.md b/docs/java/basis/generics-and-wildcards.md
new file mode 100644
index 0000000000000000000000000000000000000000..6f9be1fbc9abc6265ecc40191cbc5fbe177e37ea
--- /dev/null
+++ b/docs/java/basis/generics-and-wildcards.md
@@ -0,0 +1,18 @@
+---
+title: 泛型&通配符详解
+category: Java
+tag:
+ - Java基础
+---
+
+**泛型&通配符** 相关的面试题为我的[知识星球](https://javaguide.cn/about-the-author/zhishixingqiu-two-years.html)(点击链接即可查看详细介绍以及加入方法)专属内容,已经整理到了[《Java 面试指北》](hhttps://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)(点击链接即可查看详细介绍以及获取方法)中。
+
+[《Java 面试指北》](hhttps://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html) 的部分内容展示如下,你可以将其看作是 [JavaGuide](https://javaguide.cn/#/) 的补充完善,两者可以配合使用。
+
+
+
+[《Java 面试指北》](hhttps://javaguide.cn/zhuanlan/java-mian-shi-zhi-bei.html)只是星球内部众多资料中的一个,星球还有很多其他优质资料比如[专属专栏](https://javaguide.cn/zhuanlan/)、Java 编程视频、PDF 资料。
+
+
+
+
diff --git a/docs/java/basis/java-basic-questions-01.md b/docs/java/basis/java-basic-questions-01.md
new file mode 100644
index 0000000000000000000000000000000000000000..5c2ee3259908deb9ef17a2af87b7980e6ff846c6
--- /dev/null
+++ b/docs/java/basis/java-basic-questions-01.md
@@ -0,0 +1,1022 @@
+---
+title: Java基础常见面试题总结(上)
+category: Java
+tag:
+ - Java基础
+head:
+ - - meta
+ - name: keywords
+ content: JVM,JDK,JRE,字节码详解,Java 基本数据类型,装箱和拆箱
+ - - meta
+ - name: description
+ content: 全网质量最高的Java基础常见知识点和面试题总结,希望对你有帮助!
+---
+
+
+
+## 基础概念与常识
+
+### Java 语言有哪些特点?
+
+1. 简单易学;
+2. 面向对象(封装,继承,多态);
+3. 平台无关性( Java 虚拟机实现平台无关性);
+4. 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持);
+5. 可靠性(具备异常处理和自动内存管理机制);
+6. 安全性(Java 语言本身的设计就提供了多重安全防护机制如访问权限修饰符、限制程序直接访问操作系统资源);
+7. 高效性(通过 Just In Time 编译器等技术的优化,Java 语言的运行效率还是非常不错的);
+8. 支持网络编程并且很方便;
+9. 编译与解释并存;
+10. ......
+
+> **🐛 修正(参见:[issue#544](https://github.com/Snailclimb/JavaGuide/issues/544))**:C++11 开始(2011 年的时候),C++就引入了多线程库,在 windows、linux、macos 都可以使用`std::thread`和`std::async`来创建线程。参考链接:http://www.cplusplus.com/reference/thread/thread/?kw=thread
+
+🌈 拓展一下:
+
+“Write Once, Run Anywhere(一次编写,随处运行)”这句宣传口号,真心经典,流传了好多年!以至于,直到今天,依然有很多人觉得跨平台是 Java 语言最大的优势。实际上,跨平台已经不是 Java 最大的卖点了,各种 JDK 新特性也不是。目前市面上虚拟化技术已经非常成熟,比如你通过 Docker 就很容易实现跨平台了。在我看来,Java 强大的生态才是!
+
+### JVM vs JDK vs JRE
+
+#### JVM
+
+Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
+
+
+
+**JVM 并不是只有一种!只要满足 JVM 规范,每个公司、组织或者个人都可以开发自己的专属 JVM。** 也就是说我们平时接触到的 HotSpot VM 仅仅是是 JVM 规范的一种实现而已。
+
+除了我们平时最常用的 HotSpot VM 外,还有 J9 VM、Zing VM、JRockit VM 等 JVM 。维基百科上就有常见 JVM 的对比:[Comparison of Java virtual machines](https://en.wikipedia.org/wiki/Comparison_of_Java_virtual_machines) ,感兴趣的可以去看看。并且,你可以在 [Java SE Specifications](https://docs.oracle.com/javase/specs/index.html) 上找到各个版本的 JDK 对应的 JVM 规范。
+
+
+
+#### JDK 和 JRE
+
+JDK(Java Development Kit),它是功能齐全的 Java SDK,是提供给开发者使用的,能够创建和编译 Java 程序。他包含了 JRE,同时还包含了编译 java 源码的编译器 javac 以及一些其他工具比如 javadoc(文档注释工具)、jdb(调试器)、jconsole(基于 JMX 的可视化监控⼯具)、javap(反编译工具)等等。
+
+JRE(Java Runtime Environment) 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,主要包括 Java 虚拟机(JVM)、Java 基础类库(Class Library)。
+
+也就是说,JRE 是 Java 运行时环境,仅包含 Java 应用程序的运行时环境和必要的类库。而 JDK 则包含了 JRE,同时还包括了 javac、javadoc、jdb、jconsole、javap 等工具,可以用于 Java 应用程序的开发和调试。如果需要进行 Java 编程工作,比如编写和编译 Java 程序、使用 Java API 文档等,就需要安装 JDK。而对于某些需要使用 Java 特性的应用程序,如 JSP 转换为 Java Servlet、使用反射等,也需要 JDK 来编译和运行 Java 代码。因此,即使不打算进行 Java 应用程序的开发工作,也有可能需要安装 JDK。
+
+
+
+### 什么是字节码?采用字节码的好处是什么?
+
+在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以, Java 程序运行时相对来说还是高效的(不过,和 C++,Rust,Go 等语言还是有一定差距的),而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
+
+**Java 程序从源代码到运行的过程如下图所示**:
+
+
+
+我们需要格外注意的是 `.class->机器码` 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT(just-in-time compilation) 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 **Java 是编译与解释共存的语言** 。
+
+
+
+> HotSpot 采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是 JIT 所需要编译的部分。JVM 会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各方面的开销。JDK 支持分层编译和 AOT 协作使用。
+
+JDK、JRE、JVM、JIT 这四者的关系如下图所示。
+
+
+
+下面这张图是 JVM 的大致结构模型。
+
+
+
+### 为什么不全部使用 AOT 呢?
+
+AOT 可以提前编译节省启动时间,那为什么不全部使用这种编译方式呢?
+
+长话短说,这和 Java 语言的动态特性有千丝万缕的联系了。举个例子,CGLIB 动态代理使用的是 ASM 技术,而这种技术大致原理是运行时直接在内存中生成并加载修改后的字节码文件也就是 `.class` 文件,如果全部使用 AOT 提前编译,也就不能使用 ASM 技术了。为了支持类似的动态特性,所以选择使用 JIT 即时编译器。
+
+### 为什么说 Java 语言“编译与解释并存”?
+
+其实这个问题我们讲字节码的时候已经提到过,因为比较重要,所以我们这里再提一下。
+
+我们可以将高级编程语言按照程序的执行方式分为两种:
+
+- **编译型**:[编译型语言](https://zh.wikipedia.org/wiki/%E7%B7%A8%E8%AD%AF%E8%AA%9E%E8%A8%80) 会通过[编译器](https://zh.wikipedia.org/wiki/%E7%B7%A8%E8%AD%AF%E5%99%A8)将源代码一次性翻译成可被该平台执行的机器码。一般情况下,编译语言的执行速度比较快,开发效率比较低。常见的编译性语言有 C、C++、Go、Rust 等等。
+- **解释型**:[解释型语言](https://zh.wikipedia.org/wiki/%E7%9B%B4%E8%AD%AF%E8%AA%9E%E8%A8%80)会通过[解释器](https://zh.wikipedia.org/wiki/直譯器)一句一句的将代码解释(interpret)为机器代码后再执行。解释型语言开发效率比较快,执行速度比较慢。常见的解释性语言有 Python、JavaScript、PHP 等等。
+
+
+
+根据维基百科介绍:
+
+> 为了改善编译语言的效率而发展出的[即时编译](https://zh.wikipedia.org/wiki/即時編譯)技术,已经缩小了这两种语言间的差距。这种技术混合了编译语言与解释型语言的优点,它像编译语言一样,先把程序源代码编译成[字节码](https://zh.wikipedia.org/wiki/字节码)。到执行期时,再将字节码直译,之后执行。[Java](https://zh.wikipedia.org/wiki/Java)与[LLVM](https://zh.wikipedia.org/wiki/LLVM)是这种技术的代表产物。
+>
+> 相关阅读:[基本功 | Java 即时编译器原理解析及实践](https://tech.meituan.com/2020/10/22/java-jit-practice-in-meituan.html)
+
+**为什么说 Java 语言“编译与解释并存”?**
+
+这是因为 Java 语言既具有编译型语言的特征,也具有解释型语言的特征。因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(`.class` 文件),这种字节码必须由 Java 解释器来解释执行。
+
+### Oracle JDK vs OpenJDK
+
+可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么 Oracle JDK 和 OpenJDK 之间是否存在重大差异?下面我通过收集到的一些资料,为你解答这个被很多人忽视的问题。
+
+首先,2006 年 SUN 公司将 Java 开源,也就有了 OpenJDK。2009 年 Oracle 收购了 Sun 公司,于是自己在 OpenJDK 的基础上搞了一个 Oracle JDK。Oracle JDK 是不开源的,并且刚开始的几个版本(Java8 ~ Java11)还会相比于 OpenJDK 添加一些特有的功能和工具。
+
+其次,对于 Java 7 而言,OpenJDK 和 Oracle JDK 是十分接近的。 Oracle JDK 是基于 OpenJDK 7 构建的,只添加了一些小功能,由 Oracle 工程师参与维护。
+
+下面这段话摘自 Oracle 官方在 2012 年发表的一个博客:
+
+> 问:OpenJDK 存储库中的源代码与用于构建 Oracle JDK 的代码之间有什么区别?
+>
+> 答:非常接近 - 我们的 Oracle JDK 版本构建过程基于 OpenJDK 7 构建,只添加了几个部分,例如部署代码,其中包括 Oracle 的 Java 插件和 Java WebStart 的实现,以及一些闭源的第三方组件,如图形光栅化器,一些开源的第三方组件,如 Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源 Oracle JDK 的所有部分,除了我们考虑商业功能的部分。
+
+最后,简单总结一下 Oracle JDK 和 OpenJDK 的区别:
+
+1. **是否开源**:OpenJDK 是一个参考模型并且是完全开源的,而 Oracle JDK 是基于 OpenJDK 实现的,并不是完全开源的(个人观点:众所周知,JDK 原来是 SUN 公司开发的,后来 SUN 公司又卖给了 Oracle 公司,Oracle 公司以 Oracle 数据库而著名,而 Oracle 数据库又是闭源的,这个时候 Oracle 公司就不想完全开源了,但是原来的 SUN 公司又把 JDK 给开源了,如果这个时候 Oracle 收购回来之后就把他给闭源,必然会引起很多 Java 开发者的不满,导致大家对 Java 失去信心,那 Oracle 公司收购回来不就把 Java 烂在手里了吗!然后,Oracle 公司就想了个骚操作,这样吧,我把一部分核心代码开源出来给你们玩,并且我要和你们自己搞的 JDK 区分下,你们叫 OpenJDK,我叫 Oracle JDK,我发布我的,你们继续玩你们的,要是你们搞出来什么好玩的东西,我后续发布 Oracle JDK 也会拿来用一下,一举两得!)OpenJDK 开源项目:[https://github.com/openjdk/jdk](https://github.com/openjdk/jdk) 。
+2. **是否免费**:Oracle JDK 会提供免费版本,但一般有时间限制。JDK17 之后的版本可以免费分发和商用,但是仅有 3 年时间,3 年后无法免费商用。不过,JDK8u221 之前只要不升级可以无限期免费。OpenJDK 是完全免费的。
+3. **功能性**:Oracle JDK 在 OpenJDK 的基础上添加了一些特有的功能和工具,比如 Java Flight Recorder(JFR,一种监控工具)、Java Mission Control(JMC,一种监控工具)等工具。不过,在 Java 11 之后,OracleJDK 和 OpenJDK 的功能基本一致,之前 OracleJDK 中的私有组件大多数也已经被捐赠给开源组织。
+4. **稳定性**:OpenJDK 不提供 LTS 服务,而 OracleJDK 大概每三年都会推出一个 LTS 版进行长期支持。不过,很多公司都基于 OpenJDK 提供了对应的和 OracleJDK 周期相同的 LTS 版。因此,两者稳定性其实也是差不多的。
+5. **协议**:Oracle JDK 使用 BCL/OTN 协议获得许可,而 OpenJDK 根据 GPL v2 许可获得许可。
+
+> 既然 Oracle JDK 这么好,那为什么还要有 OpenJDK?
+>
+> 答:
+>
+> 1. OpenJDK 是开源的,开源意味着你可以对它根据你自己的需要进行修改、优化,比如 Alibaba 基于 OpenJDK 开发了 Dragonwell8:[https://github.com/alibaba/dragonwell8](https://github.com/alibaba/dragonwell8)
+>
+> 2. OpenJDK 是商业免费的(这也是为什么通过 yum 包管理器上默认安装的 JDK 是 OpenJDK 而不是 Oracle JDK)。虽然 Oracle JDK 也是商业免费(比如 JDK 8),但并不是所有版本都是免费的。
+>
+> 3. OpenJDK 更新频率更快。Oracle JDK 一般是每 6 个月发布一个新版本,而 OpenJDK 一般是每 3 个月发布一个新版本。(现在你知道为啥 Oracle JDK 更稳定了吧,先在 OpenJDK 试试水,把大部分问题都解决掉了才在 Oracle JDK 上发布)
+>
+> 基于以上这些原因,OpenJDK 还是有存在的必要的!
+
+
+
+**Oracle JDK 和 OpenJDK 如何选择?**
+
+建议选择 OpenJDK 或者基于 OpenJDK 的发行版,比如 AWS 的 Amazon Corretto,阿里巴巴的 Alibaba Dragonwell。
+
+🌈 拓展一下:
+
+- BCL 协议(Oracle Binary Code License Agreement):可以使用 JDK(支持商用),但是不能进行修改。
+- OTN 协议(Oracle Technology Network License Agreement):11 及之后新发布的 JDK 用的都是这个协议,可以自己私下用,但是商用需要付费。
+
+### Java 和 C++ 的区别?
+
+我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过 C++,也要记下来。
+
+虽然,Java 和 C++ 都是面向对象的语言,都支持封装、继承和多态,但是,它们还是有挺多不相同的地方:
+
+- Java 不提供指针来直接访问内存,程序内存更加安全
+- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
+- Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
+- C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)。
+- ......
+
+## 基本语法
+
+### 注释有哪几种形式?
+
+Java 中的注释有三种:
+
+
+
+1. **单行注释**:通常用于解释方法内某单行代码的作用。
+
+2. **多行注释**:通常用于解释一段代码的作用。
+
+3. **文档注释**:通常用于生成 Java 开发文档。
+
+用的比较多的还是单行注释和文档注释,多行注释在实际开发中使用的相对较少。
+
+
+
+在我们编写代码的时候,如果代码量比较少,我们自己或者团队其他成员还可以很轻易地看懂代码,但是当项目结构一旦复杂起来,我们就需要用到注释了。注释并不会执行(编译器在编译代码之前会把代码中的所有注释抹掉,字节码中不保留注释),是我们程序员写给自己看的,注释是你的代码说明书,能够帮助看代码的人快速地理清代码之间的逻辑关系。因此,在写程序的时候随手加上注释是一个非常好的习惯。
+
+《Clean Code》这本书明确指出:
+
+> **代码的注释不是越详细越好。实际上好的代码本身就是注释,我们要尽量规范和美化自己的代码来减少不必要的注释。**
+>
+> **若编程语言足够有表达力,就不需要注释,尽量通过代码来阐述。**
+>
+> 举个例子:
+>
+> 去掉下面复杂的注释,只需要创建一个与注释所言同一事物的函数即可
+>
+> ```java
+> // check to see if the employee is eligible for full benefits
+> if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))
+> ```
+>
+> 应替换为
+>
+> ```java
+> if (employee.isEligibleForFullBenefits())
+> ```
+
+### 标识符和关键字的区别是什么?
+
+在我们编写程序的时候,需要大量地为程序、类、变量、方法等取名字,于是就有了 **标识符** 。简单来说, **标识符就是一个名字** 。
+
+有一些标识符,Java 语言已经赋予了其特殊的含义,只能用于特定的地方,这些特殊的标识符就是 **关键字** 。简单来说,**关键字是被赋予特殊含义的标识**符 。比如,在我们的日常生活中,如果我们想要开一家店,则要给这个店起一个名字,起的这个“名字”就叫标识符。但是我们店的名字不能叫“警察局”,因为“警察局”这个名字已经被赋予了特殊的含义,而“警察局”就是我们日常生活中的关键字。
+
+### Java 语言关键字有哪些?
+
+| 分类 | 关键字 | | | | | | |
+| :------------------- | -------- | ---------- | -------- | ------------ | ---------- | --------- | ------ |
+| 访问控制 | private | protected | public | | | | |
+| 类,方法和变量修饰符 | abstract | class | extends | final | implements | interface | native |
+| | new | static | strictfp | synchronized | transient | volatile | enum |
+| 程序控制 | break | continue | return | do | while | if | else |
+| | for | instanceof | switch | case | default | assert | |
+| 错误处理 | try | catch | throw | throws | finally | | |
+| 包相关 | import | package | | | | | |
+| 基本类型 | boolean | byte | char | double | float | int | long |
+| | short | | | | | | |
+| 变量引用 | super | this | void | | | | |
+| 保留字 | goto | const | | | | | |
+
+> Tips:所有的关键字都是小写的,在 IDE 中会以特殊颜色显示。
+>
+> `default` 这个关键字很特殊,既属于程序控制,也属于类,方法和变量修饰符,还属于访问控制。
+>
+> - 在程序控制中,当在 `switch` 中匹配不到任何情况时,可以使用 `default` 来编写默认匹配的情况。
+> - 在类,方法和变量修饰符中,从 JDK8 开始引入了默认方法,可以使用 `default` 关键字来定义一个方法的默认实现。
+> - 在访问控制中,如果一个方法前没有任何修饰符,则默认会有一个修饰符 `default`,但是这个修饰符加上了就会报错。
+
+⚠️ 注意:虽然 `true`, `false`, 和 `null` 看起来像关键字但实际上他们是字面值,同时你也不可以作为标识符来使用。
+
+官方文档:[https://docs.oracle.com/javase/tutorial/java/nutsandbolts/\_keywords.html](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html)
+
+### 自增自减运算符
+
+在写代码的过程中,常见的一种情况是需要某个整数类型变量增加 1 或减少 1,Java 提供了一种特殊的运算符,用于这种表达式,叫做自增运算符(++)和自减运算符(--)。
+
+++ 和 -- 运算符可以放在变量之前,也可以放在变量之后,当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。例如,当 `b = ++a` 时,先自增(自己增加 1),再赋值(赋值给 b);当 `b = a++` 时,先赋值(赋值给 b),再自增(自己增加 1)。也就是,++a 输出的是 a+1 的值,a++输出的是 a 值。用一句口诀就是:“符号在前就先加/减,符号在后就后加/减”。
+
+### 移位运算符
+
+移位运算符是最基本的运算符之一,几乎每种编程语言都包含这一运算符。移位操作中,被操作的数据被视为二进制数,移位就是将其向左或向右移动若干位的运算。
+
+移位运算符在各种框架以及 JDK 自身的源码中使用还是挺广泛的,`HashMap`(JDK1.8) 中的 `hash` 方法的源码就用到了移位运算符:
+
+```java
+static final int hash(Object key) {
+ int h;
+ // key.hashCode():返回散列值也就是hashcode
+ // ^:按位异或
+ // >>>:无符号右移,忽略符号位,空位都以0补齐
+ return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
+ }
+
+```
+
+在 Java 代码里使用 `<<`、 `>>` 和`>>>`转换成的指令码运行起来会更高效些。
+
+掌握最基本的移位运算符知识还是很有必要的,这不光可以帮助我们在代码中使用,还可以帮助我们理解源码中涉及到移位运算符的代码。
+
+Java 中有三种移位运算符:
+
+
+
+- `<<` :左移运算符,向左移若干位,高位丢弃,低位补零。`x << 1`,相当于 x 乘以 2(不溢出的情况下)。
+- `>>` :带符号右移,向右移若干位,高位补符号位,低位丢弃。正数高位补 0,负数高位补 1。`x >> 1`,相当于 x 除以 2。
+- `>>>` :无符号右移,忽略符号位,空位都以 0 补齐。
+
+由于 `double`,`float` 在二进制中的表现比较特殊,因此不能来进行移位操作。
+
+移位操作符实际上支持的类型只有`int`和`long`,编译器在对`short`、`byte`、`char`类型进行移位前,都会将其转换为`int`类型再操作。
+
+**如果移位的位数超过数值所占有的位数会怎样?**
+
+当 int 类型左移/右移位数大于等于 32 位操作时,会先求余(%)后再进行左移/右移操作。也就是说左移/右移 32 位相当于不进行移位操作(32%32=0),左移/右移 42 位相当于左移/右移 10 位(42%32=10)。当 long 类型进行左移/右移操作时,由于 long 对应的二进制是 64 位,因此求余操作的基数也变成了 64。
+
+也就是说:`x<<42`等同于`x<<10`,`x>>42`等同于`x>>10`,`x >>>42`等同于`x >>> 10`。
+
+**左移运算符代码示例**:
+
+```java
+int i = -1;
+System.out.println("初始数据:" + i);
+System.out.println("初始数据对应的二进制字符串:" + Integer.toBinaryString(i));
+i <<= 10;
+System.out.println("左移 10 位后的数据 " + i);
+System.out.println("左移 10 位后的数据对应的二进制字符 " + Integer.toBinaryString(i));
+```
+
+输出:
+
+```
+初始数据:-1
+初始数据对应的二进制字符串:11111111111111111111111111111111
+左移 10 位后的数据 -1024
+左移 10 位后的数据对应的二进制字符 11111111111111111111110000000000
+```
+
+由于左移位数大于等于 32 位操作时,会先求余(%)后再进行左移操作,所以下面的代码左移 42 位相当于左移 10 位(42%32=10),输出结果和前面的代码一样。
+
+```java
+int i = -1;
+System.out.println("初始数据:" + i);
+System.out.println("初始数据对应的二进制字符串:" + Integer.toBinaryString(i));
+i <<= 42;
+System.out.println("左移 10 位后的数据 " + i);
+System.out.println("左移 10 位后的数据对应的二进制字符 " + Integer.toBinaryString(i));
+```
+
+右移运算符使用类似,篇幅问题,这里就不做演示了。
+
+### continue、break 和 return 的区别是什么?
+
+在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词:
+
+1. `continue`:指跳出当前的这一次循环,继续下一次循环。
+2. `break`:指跳出整个循环体,继续执行循环下面的语句。
+
+`return` 用于跳出所在方法,结束该方法的运行。return 一般有两种用法:
+
+1. `return;`:直接使用 return 结束方法执行,用于没有返回值函数的方法
+2. `return value;`:return 一个特定值,用于有返回值函数的方法
+
+思考一下:下列语句的运行结果是什么?
+
+```java
+ public static void main(String[] args) {
+ boolean flag = false;
+ for (int i = 0; i <= 3; i++) {
+ if (i == 0) {
+ System.out.println("0");
+ } else if (i == 1) {
+ System.out.println("1");
+ continue;
+ } else if (i == 2) {
+ System.out.println("2");
+ flag = true;
+ } else if (i == 3) {
+ System.out.println("3");
+ break;
+ } else if (i == 4) {
+ System.out.println("4");
+ }
+ System.out.println("xixi");
+ }
+ if (flag) {
+ System.out.println("haha");
+ return;
+ }
+ System.out.println("heihei");
+ }
+```
+
+运行结果:
+
+```
+0
+xixi
+1
+2
+xixi
+3
+haha
+```
+
+## 基本数据类型
+
+### Java 中的几种基本数据类型了解么?
+
+Java 中有 8 种基本数据类型,分别为:
+
+- 6 种数字类型:
+ - 4 种整数型:`byte`、`short`、`int`、`long`
+ - 2 种浮点型:`float`、`double`
+- 1 种字符类型:`char`
+- 1 种布尔型:`boolean`。
+
+这 8 种基本数据类型的默认值以及所占空间的大小如下:
+
+| 基本类型 | 位数 | 字节 | 默认值 | 取值范围 |
+| :-------- | :--- | :--- | :------ | ------------------------------------------ |
+| `byte` | 8 | 1 | 0 | -128 ~ 127 |
+| `short` | 16 | 2 | 0 | -32768 ~ 32767 |
+| `int` | 32 | 4 | 0 | -2147483648 ~ 2147483647 |
+| `long` | 64 | 8 | 0L | -9223372036854775808 ~ 9223372036854775807 |
+| `char` | 16 | 2 | 'u0000' | 0 ~ 65535 |
+| `float` | 32 | 4 | 0f | 1.4E-45 ~ 3.4028235E38 |
+| `double` | 64 | 8 | 0d | 4.9E-324 ~ 1.7976931348623157E308 |
+| `boolean` | 1 | | false | true、false |
+
+对于 `boolean`,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。
+
+另外,Java 的每种基本类型所占存储空间的大小不会像其他大多数语言那样随机器硬件架构的变化而变化。这种所占存储空间大小的不变性是 Java 程序比用其他大多数语言编写的程序更具可移植性的原因之一(《Java 编程思想》2.2 节有提到)。
+
+**注意:**
+
+1. Java 里使用 `long` 类型的数据一定要在数值后面加上 **L**,否则将作为整型解析。
+2. `char a = 'h'`char :单引号,`String a = "hello"` :双引号。
+
+这八种基本类型都有对应的包装类分别为:`Byte`、`Short`、`Integer`、`Long`、`Float`、`Double`、`Character`、`Boolean` 。
+
+### 基本类型和包装类型的区别?
+
+
+
+- **用途**:除了定义一些常量和局部变量之外,我们在其他地方比如方法参数、对象属性中很少会使用基本类型来定义变量。并且,包装类型可用于泛型,而基本类型不可以。
+- **存储方式**:基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 `static` 修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
+- **占用空间**:相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小。
+- **默认值**:成员变量包装类型不赋值就是 `null` ,而基本类型有默认值且不是 `null`。
+- **比较方式**:对于基本数据类型来说,`==` 比较的是值。对于包装数据类型来说,`==` 比较的是对象的内存地址。所有整型包装类对象之间值的比较,全部使用 `equals()` 方法。
+
+**为什么说是几乎所有对象实例都存在于堆中呢?** 这是因为 HotSpot 虚拟机引入了 JIT 优化之后,会对对象进行逃逸分析,如果发现某一个对象并没有逃逸到方法外部,那么就可能通过标量替换来实现栈上分配,而避免堆上分配内存
+
+⚠️ 注意:**基本数据类型存放在栈中是一个常见的误区!** 基本数据类型的成员变量如果没有被 `static` 修饰的话(不建议这么使用,应该要使用基本数据类型对应的包装类型),就存放在堆中。
+
+```java
+class BasicTypeVar{
+ private int x;
+}
+```
+
+### 包装类型的缓存机制了解么?
+
+Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。
+
+`Byte`,`Short`,`Integer`,`Long` 这 4 种包装类默认创建了数值 **[-128,127]** 的相应类型的缓存数据,`Character` 创建了数值在 **[0,127]** 范围的缓存数据,`Boolean` 直接返回 `True` or `False`。
+
+**Integer 缓存源码:**
+
+```java
+public static Integer valueOf(int i) {
+ if (i >= IntegerCache.low && i <= IntegerCache.high)
+ return IntegerCache.cache[i + (-IntegerCache.low)];
+ return new Integer(i);
+}
+private static class IntegerCache {
+ static final int low = -128;
+ static final int high;
+ static {
+ // high value may be configured by property
+ int h = 127;
+ }
+}
+```
+
+**`Character` 缓存源码:**
+
+```java
+public static Character valueOf(char c) {
+ if (c <= 127) { // must cache
+ return CharacterCache.cache[(int)c];
+ }
+ return new Character(c);
+}
+
+private static class CharacterCache {
+ private CharacterCache(){}
+ static final Character cache[] = new Character[127 + 1];
+ static {
+ for (int i = 0; i < cache.length; i++)
+ cache[i] = new Character((char)i);
+ }
+
+}
+```
+
+**`Boolean` 缓存源码:**
+
+```java
+public static Boolean valueOf(boolean b) {
+ return (b ? TRUE : FALSE);
+}
+```
+
+如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
+
+两种浮点数类型的包装类 `Float`,`Double` 并没有实现缓存机制。
+
+```java
+Integer i1 = 33;
+Integer i2 = 33;
+System.out.println(i1 == i2);// 输出 true
+
+Float i11 = 333f;
+Float i22 = 333f;
+System.out.println(i11 == i22);// 输出 false
+
+Double i3 = 1.2;
+Double i4 = 1.2;
+System.out.println(i3 == i4);// 输出 false
+```
+
+下面我们来看一个问题:下面的代码的输出结果是 `true` 还是 `false` 呢?
+
+```java
+Integer i1 = 40;
+Integer i2 = new Integer(40);
+System.out.println(i1==i2);
+```
+
+`Integer i1=40` 这一行代码会发生装箱,也就是说这行代码等价于 `Integer i1=Integer.valueOf(40)` 。因此,`i1` 直接使用的是缓存中的对象。而`Integer i2 = new Integer(40)` 会直接创建新的对象。
+
+因此,答案是 `false` 。你答对了吗?
+
+记住:**所有整型包装类对象之间值的比较,全部使用 equals 方法比较**。
+
+
+
+### 自动装箱与拆箱了解吗?原理是什么?
+
+**什么是自动拆装箱?**
+
+- **装箱**:将基本类型用它们对应的引用类型包装起来;
+- **拆箱**:将包装类型转换为基本数据类型;
+
+举例:
+
+```java
+Integer i = 10; //装箱
+int n = i; //拆箱
+```
+
+上面这两行代码对应的字节码为:
+
+```java
+ L1
+
+ LINENUMBER 8 L1
+
+ ALOAD 0
+
+ BIPUSH 10
+
+ INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
+
+ PUTFIELD AutoBoxTest.i : Ljava/lang/Integer;
+
+ L2
+
+ LINENUMBER 9 L2
+
+ ALOAD 0
+
+ ALOAD 0
+
+ GETFIELD AutoBoxTest.i : Ljava/lang/Integer;
+
+ INVOKEVIRTUAL java/lang/Integer.intValue ()I
+
+ PUTFIELD AutoBoxTest.n : I
+
+ RETURN
+```
+
+从字节码中,我们发现装箱其实就是调用了 包装类的`valueOf()`方法,拆箱其实就是调用了 `xxxValue()`方法。
+
+因此,
+
+- `Integer i = 10` 等价于 `Integer i = Integer.valueOf(10)`
+- `int n = i` 等价于 `int n = i.intValue()`;
+
+注意:**如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。**
+
+```java
+private static long sum() {
+ // 应该使用 long 而不是 Long
+ Long sum = 0L;
+ for (long i = 0; i <= Integer.MAX_VALUE; i++)
+ sum += i;
+ return sum;
+}
+```
+
+### 为什么浮点数运算的时候会有精度丢失的风险?
+
+浮点数运算精度丢失代码演示:
+
+```java
+float a = 2.0f - 1.9f;
+float b = 1.8f - 1.7f;
+System.out.println(a);// 0.100000024
+System.out.println(b);// 0.099999905
+System.out.println(a == b);// false
+```
+
+为什么会出现这个问题呢?
+
+这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。
+
+就比如说十进制下的 0.2 就没办法精确转换成二进制小数:
+
+```java
+// 0.2 转换为二进制数的过程为,不断乘以 2,直到不存在小数为止,
+// 在这个计算过程中,得到的整数部分从上到下排列就是二进制的结果。
+0.2 * 2 = 0.4 -> 0
+0.4 * 2 = 0.8 -> 0
+0.8 * 2 = 1.6 -> 1
+0.6 * 2 = 1.2 -> 1
+0.2 * 2 = 0.4 -> 0(发生循环)
+...
+```
+
+关于浮点数的更多内容,建议看一下[计算机系统基础(四)浮点数](http://kaito-kidd.com/2018/08/08/computer-system-float-point/)这篇文章。
+
+### 如何解决浮点数运算的精度丢失问题?
+
+`BigDecimal` 可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 `BigDecimal` 来做的。
+
+```java
+BigDecimal a = new BigDecimal("1.0");
+BigDecimal b = new BigDecimal("0.9");
+BigDecimal c = new BigDecimal("0.8");
+
+BigDecimal x = a.subtract(b);
+BigDecimal y = b.subtract(c);
+
+System.out.println(x); /* 0.1 */
+System.out.println(y); /* 0.1 */
+System.out.println(Objects.equals(x, y)); /* true */
+```
+
+关于 `BigDecimal` 的详细介绍,可以看看我写的这篇文章:[BigDecimal 详解](https://javaguide.cn/java/basis/bigdecimal.html)。
+
+### 超过 long 整型的数据应该如何表示?
+
+基本数值类型都有一个表达范围,如果超过这个范围就会有数值溢出的风险。
+
+在 Java 中,64 位 long 整型是最大的整数类型。
+
+```java
+long l = Long.MAX_VALUE;
+System.out.println(l + 1); // -9223372036854775808
+System.out.println(l + 1 == Long.MIN_VALUE); // true
+```
+
+`BigInteger` 内部使用 `int[]` 数组来存储任意大小的整形数据。
+
+相对于常规整数类型的运算来说,`BigInteger` 运算的效率会相对较低。
+
+## 变量
+
+### 成员变量与局部变量的区别?
+
+
+
+- **语法形式**:从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 `public`,`private`,`static` 等修饰符所修饰,而局部变量不能被访问控制修饰符及 `static` 所修饰;但是,成员变量和局部变量都能被 `final` 所修饰。
+- **存储方式**:从变量在内存中的存储方式来看,如果成员变量是使用 `static` 修饰的,那么这个成员变量是属于类的,如果没有使用 `static` 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
+- **生存时间**:从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
+- **默认值**:从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被 `final` 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
+
+成员变量与局部变量代码示例:
+
+```java
+public class VariableExample {
+
+ // 成员变量
+ private String name;
+ private int age;
+
+ // 方法中的局部变量
+ public void method() {
+ int num1 = 10; // 栈中分配的局部变量
+ String str = "Hello, world!"; // 栈中分配的局部变量
+ System.out.println(num1);
+ System.out.println(str);
+ }
+
+ // 带参数的方法中的局部变量
+ public void method2(int num2) {
+ int sum = num2 + 10; // 栈中分配的局部变量
+ System.out.println(sum);
+ }
+
+ // 构造方法中的局部变量
+ public VariableExample(String name, int age) {
+ this.name = name; // 对成员变量进行赋值
+ this.age = age; // 对成员变量进行赋值
+ int num3 = 20; // 栈中分配的局部变量
+ String str2 = "Hello, " + this.name + "!"; // 栈中分配的局部变量
+ System.out.println(num3);
+ System.out.println(str2);
+ }
+}
+
+```
+
+### 静态变量有什么作用?
+
+静态变量也就是被 `static` 关键字修饰的变量。它可以被类的所有实例共享,无论一个类创建了多少个对象,它们都共享同一份静态变量。也就是说,静态变量只会被分配一次内存,即使创建多个对象,这样可以节省内存。
+
+静态变量是通过类名来访问的,例如`StaticVariableExample.staticVar`(如果被 `private`关键字修饰就无法这样访问了)。
+
+```java
+public class StaticVariableExample {
+ // 静态变量
+ public static int staticVar = 0;
+}
+```
+
+通常情况下,静态变量会被 `final` 关键字修饰成为常量。
+
+```java
+public class ConstantVariableExample {
+ // 常量
+ public static final int constantVar = 0;
+}
+```
+
+### 字符型常量和字符串常量的区别?
+
+- **形式** : 字符常量是单引号引起的一个字符,字符串常量是双引号引起的 0 个或若干个字符。
+- **含义** : 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)。
+- **占内存大小**:字符常量只占 2 个字节; 字符串常量占若干个字节。
+
+⚠️ 注意 `char` 在 Java 中占两个字节。
+
+字符型常量和字符串常量代码示例:
+
+```java
+public class StringExample {
+ // 字符型常量
+ public static final char LETTER_A = 'A';
+
+ // 字符串常量
+ public static final String GREETING_MESSAGE = "Hello, world!";
+ public static void main(String[] args) {
+ System.out.println("字符型常量占用的字节数为:"+Character.BYTES);
+ System.out.println("字符串常量占用的字节数为:"+GREETING_MESSAGE.getBytes().length);
+ }
+}
+```
+
+输出:
+
+```
+字符型常量占用的字节数为:2
+字符串常量占用的字节数为:13
+```
+
+## 方法
+
+### 什么是方法的返回值?方法有哪几种类型?
+
+**方法的返回值** 是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用是接收出结果,使得它可以用于其他的操作!
+
+我们可以按照方法的返回值和参数类型将方法分为下面这几种:
+
+**1、无参数无返回值的方法**
+
+```java
+public void f1() {
+ //......
+}
+// 下面这个方法也没有返回值,虽然用到了 return
+public void f(int a) {
+ if (...) {
+ // 表示结束方法的执行,下方的输出语句不会执行
+ return;
+ }
+ System.out.println(a);
+}
+```
+
+**2、有参数无返回值的方法**
+
+```java
+public void f2(Parameter 1, ..., Parameter n) {
+ //......
+}
+```
+
+**3、有返回值无参数的方法**
+
+```java
+public int f3() {
+ //......
+ return x;
+}
+```
+
+**4、有返回值有参数的方法**
+
+```java
+public int f4(int a, int b) {
+ return a * b;
+}
+```
+
+### 静态方法为什么不能调用非静态成员?
+
+这个需要结合 JVM 的相关知识,主要原因如下:
+
+1. 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
+2. 在类的非静态成员不存在的时候静态方法就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。
+
+```java
+public class Example {
+ // 定义一个字符型常量
+ public static final char LETTER_A = 'A';
+
+ // 定义一个字符串常量
+ public static final String GREETING_MESSAGE = "Hello, world!";
+
+ public static void main(String[] args) {
+ // 输出字符型常量的值
+ System.out.println("字符型常量的值为:" + LETTER_A);
+
+ // 输出字符串常量的值
+ System.out.println("字符串常量的值为:" + GREETING_MESSAGE);
+ }
+}
+```
+
+### 静态方法和实例方法有何不同?
+
+**1、调用方式**
+
+在外部调用静态方法时,可以使用 `类名.方法名` 的方式,也可以使用 `对象.方法名` 的方式,而实例方法只有后面这种方式。也就是说,**调用静态方法可以无需创建对象** 。
+
+不过,需要注意的是一般不建议使用 `对象.方法名` 的方式来调用静态方法。这种方式非常容易造成混淆,静态方法不属于类的某个对象而是属于这个类。
+
+因此,一般建议使用 `类名.方法名` 的方式来调用静态方法。
+
+```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 核心技术》这本书是这样介绍重载的:
+
+> 如果多个方法(比如 `StringBuilder` 的构造方法)有相同的名字、不同的参数, 便产生了重载。
+>
+> ```java
+> StringBuilder sb = new StringBuilder();
+> StringBuilder sb2 = new StringBuilder("HelloWorld");
+> ```
+>
+> 编译器必须挑选出具体执行哪个方法,它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。 如果编译器找不到匹配的参数, 就会产生编译时错误, 因为根本不存在匹配, 或者没有一个比其他的更好(这个过程被称为重载解析(overloading resolution))。
+>
+> Java 允许重载任何方法, 而不只是构造器方法。
+
+综上:重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
+
+#### 重写
+
+重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。
+
+1. 方法名、参数列表必须相同,子类方法返回值类型应比父类方法返回值类型更小或相等,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
+2. 如果父类方法访问修饰符为 `private/final/static` 则子类就不能重写该方法,但是被 `static` 修饰的方法能够被再次声明。
+3. 构造方法无法被重写
+
+#### 总结
+
+综上:**重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。**
+
+| 区别点 | 重载方法 | 重写方法 |
+| :--------- | :------- | :--------------------------------------------------------------- |
+| 发生范围 | 同一个类 | 子类 |
+| 参数列表 | 必须修改 | 一定不能修改 |
+| 返回类型 | 可修改 | 子类方法返回值类型应比父类方法返回值类型更小或相等 |
+| 异常 | 可修改 | 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等; |
+| 访问修饰符 | 可修改 | 一定不能做更严格的限制(可以降低限制) |
+| 发生阶段 | 编译期 | 运行期 |
+
+**方法的重写要遵循“两同两小一大”**(以下内容摘录自《疯狂 Java 讲义》,[issue#892](https://github.com/Snailclimb/JavaGuide/issues/892) ):
+
+- “两同”即方法名相同、形参列表相同;
+- “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
+- “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
+
+⭐️ 关于 **重写的返回值类型** 这里需要额外多说明一下,上面的表述不太清晰准确:如果方法的返回类型是 void 和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。
+
+```java
+public class Hero {
+ public String name() {
+ return "超级英雄";
+ }
+}
+public class SuperMan extends Hero{
+ @Override
+ public String name() {
+ return "超人";
+ }
+ public Hero hero() {
+ return new Hero();
+ }
+}
+
+public class SuperSuperMan extends SuperMan {
+ public String name() {
+ return "超级超级英雄";
+ }
+
+ @Override
+ public SuperMan hero() {
+ return new SuperMan();
+ }
+}
+```
+
+### 什么是可变长参数?
+
+从 Java5 开始,Java 支持定义可变长参数,所谓可变长参数就是允许在调用方法时传入不定长度的参数。就比如下面的这个 `printVariable` 方法就可以接受 0 个或者多个参数。
+
+```java
+public static void method1(String... args) {
+ //......
+}
+```
+
+另外,可变参数只能作为函数的最后一个参数,但其前面可以有也可以没有任何其他参数。
+
+```java
+public static void method2(String arg1, String... args) {
+ //......
+}
+```
+
+**遇到方法重载的情况怎么办呢?会优先匹配固定参数还是可变参数的方法呢?**
+
+答案是会优先匹配固定参数的方法,因为固定参数的方法匹配度更高。
+
+我们通过下面这个例子来证明一下。
+
+```java
+/**
+ * 微信搜 JavaGuide 回复"面试突击"即可免费领取个人原创的 Java 面试手册
+ *
+ * @author Guide哥
+ * @date 2021/12/13 16:52
+ **/
+public class VariableLengthArgument {
+
+ public static void printVariable(String... args) {
+ for (String s : args) {
+ System.out.println(s);
+ }
+ }
+
+ public static void printVariable(String arg1, String arg2) {
+ System.out.println(arg1 + arg2);
+ }
+
+ public static void main(String[] args) {
+ printVariable("a", "b");
+ printVariable("a", "b", "c", "d");
+ }
+}
+```
+
+输出:
+
+```
+ab
+a
+b
+c
+d
+```
+
+另外,Java 的可变参数编译后实际会被转换成一个数组,我们看编译后生成的 `class`文件就可以看出来了。
+
+```java
+public class VariableLengthArgument {
+
+ public static void printVariable(String... args) {
+ String[] var1 = args;
+ int var2 = args.length;
+
+ for(int var3 = 0; var3 < var2; ++var3) {
+ String s = var1[var3];
+ System.out.println(s);
+ }
+
+ }
+ // ......
+}
+```
+
+## 参考
+
+- What is the difference between JDK and JRE?:https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre
+- Oracle vs OpenJDK:https://www.educba.com/oracle-vs-openjdk/
+- Differences between Oracle JDK and OpenJDK:https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk
+- 彻底弄懂 Java 的移位操作符:https://juejin.cn/post/6844904025880526861
diff --git a/docs/java/basis/java-basic-questions-02.md b/docs/java/basis/java-basic-questions-02.md
new file mode 100644
index 0000000000000000000000000000000000000000..c1facd578c0cfbe55d04d32e01dbca3f95014ef7
--- /dev/null
+++ b/docs/java/basis/java-basic-questions-02.md
@@ -0,0 +1,784 @@
+---
+title: Java基础常见面试题总结(中)
+category: Java
+tag:
+ - Java基础
+head:
+ - - meta
+ - name: keywords
+ content: 面向对象,构造方法,接口,抽象类,String,Object
+ - - meta
+ - name: description
+ content: 全网质量最高的Java基础常见知识点和面试题总结,希望对你有帮助!
+---
+
+## 面向对象基础
+
+### 面向对象和面向过程的区别
+
+两者的主要区别在于解决问题的方式不同:
+
+- 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
+- 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。
+
+另外,面向对象开发的程序一般更易维护、易复用、易扩展。
+
+相关 issue : [面向过程:面向过程性能比面向对象高??](https://github.com/Snailclimb/JavaGuide/issues/431) 。
+
+下面是一个求圆的面积和周长的示例,简单分别展示了面向对象和面向过程两种不同的解决方案。
+
+**面向对象**:
+
+```java
+public class Circle {
+ // 定义圆的半径
+ private double radius;
+
+ // 构造函数
+ public Circle(double radius) {
+ this.radius = radius;
+ }
+
+ // 计算圆的面积
+ public double getArea() {
+ return Math.PI * radius * radius;
+ }
+
+ // 计算圆的周长
+ public double getPerimeter() {
+ return 2 * Math.PI * radius;
+ }
+
+ public static void main(String[] args) {
+ // 创建一个半径为3的圆
+ Circle circle = new Circle(3.0);
+
+ // 输出圆的面积和周长
+ System.out.println("圆的面积为:" + circle.getArea());
+ System.out.println("圆的周长为:" + circle.getPerimeter());
+ }
+}
+```
+
+我们定义了一个 `Circle` 类来表示圆,该类包含了圆的半径属性和计算面积、周长的方法。
+
+**面向过程**:
+
+```java
+public class Main {
+ public static void main(String[] args) {
+ // 定义圆的半径
+ double radius = 3.0;
+
+ // 计算圆的面积和周长
+ double area = Math.PI * radius * radius;
+ double perimeter = 2 * Math.PI * radius;
+
+ // 输出圆的面积和周长
+ System.out.println("圆的面积为:" + area);
+ System.out.println("圆的周长为:" + perimeter);
+ }
+}
+```
+
+我们直接定义了圆的半径,并使用该半径直接计算出圆的面积和周长。
+
+### 创建一个对象用什么运算符?对象实体与对象引用有何不同?
+
+new 运算符,new 创建对象实例(对象实例在
+内存中),对象引用指向对象实例(对象引用存放在栈内存中)。
+
+- 一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);
+- 一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。
+
+### 对象的相等和引用相等的区别
+
+- 对象的相等一般比较的是内存中存放的内容是否相等。
+- 引用相等一般比较的是他们指向的内存地址是否相等。
+
+这里举一个例子:
+
+```java
+String str1 = "hello";
+String str2 = new String("hello");
+String str3 = "hello";
+// 使用 == 比较字符串的引用相等
+System.out.println(str1 == str2);
+System.out.println(str1 == str3);
+// 使用 equals 方法比较字符串的相等
+System.out.println(str1.equals(str2));
+System.out.println(str1.equals(str3));
+
+```
+
+输出结果:
+
+```
+false
+true
+true
+true
+```
+
+从上面的代码输出结果可以看出:
+
+- `str1` 和 `str2` 不相等,而 `str1` 和 `str3` 相等。这是因为 `==` 运算符比较的是字符串的引用是否相等。
+- `str1`、 `str2`、`str3` 三者的内容都相等。这是因为`equals` 方法比较的是字符串的内容,即使这些字符串的对象引用不同,只要它们的内容相等,就认为它们是相等的。
+
+### 如果一个类没有声明构造方法,该程序能正确执行吗?
+
+构造方法是一种特殊的方法,主要作用是完成对象的初始化工作。
+
+如果一个类没有声明构造方法,也可以执行!因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法(无论是否有参),Java 就不会添加默认的无参数的构造方法了。
+
+我们一直在不知不觉地使用构造方法,这也是为什么我们在创建对象的时候后面要加一个括号(因为要调用无参的构造方法)。如果我们重载了有参的构造方法,记得都要把无参的构造方法也写出来(无论是否用到),因为这可以帮助我们在创建对象的时候少踩坑。
+
+### 构造方法有哪些特点?是否可被 override?
+
+构造方法特点如下:
+
+- 名字与类名相同。
+- 没有返回值,但不能用 void 声明构造函数。
+- 生成类的对象时自动执行,无需调用。
+
+构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
+
+### 面向对象三大特征
+
+#### 封装
+
+封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。就好像如果没有空调遥控器,那么我们就无法操控空凋制冷,空调本身就没有意义了(当然现在还有很多其他方法 ,这里只是为了举例子)。
+
+```java
+public class Student {
+ private int id;//id属性私有化
+ private String name;//name属性私有化
+
+ //获取id的方法
+ public int getId() {
+ return id;
+ }
+
+ //设置id的方法
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ //获取name的方法
+ public String getName() {
+ return name;
+ }
+
+ //设置name的方法
+ public void setName(String name) {
+ this.name = name;
+ }
+}
+```
+
+#### 继承
+
+不同类型的对象,相互之间经常有一定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的特性(班级、学号等)。同时,每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好,小红的性格惹人喜爱;小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
+
+**关于继承如下 3 点请记住:**
+
+1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,**只是拥有**。
+2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
+3. 子类可以用自己的方式实现父类的方法。(以后介绍)。
+
+#### 多态
+
+多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。
+
+**多态的特点:**
+
+- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
+- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
+- 多态不能调用“只在子类存在但在父类不存在”的方法;
+- 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
+
+### 接口和抽象类有什么共同点和区别?
+
+**共同点**:
+
+- 都不能被实例化。
+- 都可以包含抽象方法。
+- 都可以有默认实现的方法(Java 8 可以用 `default` 关键字在接口中定义默认方法)。
+
+**区别**:
+
+- 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
+- 一个类只能继承一个类,但是可以实现多个接口。
+- 接口中的成员变量只能是 `public static final` 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。
+
+### 深拷贝和浅拷贝区别了解吗?什么是引用拷贝?
+
+关于深拷贝和浅拷贝区别,我这里先给结论:
+
+- **浅拷贝**:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。
+- **深拷贝**:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。
+
+上面的结论没有完全理解的话也没关系,我们来看一个具体的案例!
+
+#### 浅拷贝
+
+浅拷贝的示例代码如下,我们这里实现了 `Cloneable` 接口,并重写了 `clone()` 方法。
+
+`clone()` 方法的实现很简单,直接调用的是父类 `Object` 的 `clone()` 方法。
+
+```java
+public class Address implements Cloneable{
+ private String name;
+ // 省略构造函数、Getter&Setter方法
+ @Override
+ public Address clone() {
+ try {
+ return (Address) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+}
+
+public class Person implements Cloneable {
+ private Address address;
+ // 省略构造函数、Getter&Setter方法
+ @Override
+ public Person clone() {
+ try {
+ Person person = (Person) super.clone();
+ return person;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+}
+```
+
+测试:
+
+```java
+Person person1 = new Person(new Address("武汉"));
+Person person1Copy = person1.clone();
+// true
+System.out.println(person1.getAddress() == person1Copy.getAddress());
+```
+
+从输出结构就可以看出, `person1` 的克隆对象和 `person1` 使用的仍然是同一个 `Address` 对象。
+
+#### 深拷贝
+
+这里我们简单对 `Person` 类的 `clone()` 方法进行修改,连带着要把 `Person` 对象内部的 `Address` 对象一起复制。
+
+```java
+@Override
+public Person clone() {
+ try {
+ Person person = (Person) super.clone();
+ person.setAddress(person.getAddress().clone());
+ return person;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+}
+```
+
+测试:
+
+```java
+Person person1 = new Person(new Address("武汉"));
+Person person1Copy = person1.clone();
+// false
+System.out.println(person1.getAddress() == person1Copy.getAddress());
+```
+
+从输出结构就可以看出,显然 `person1` 的克隆对象和 `person1` 包含的 `Address` 对象已经是不同的了。
+
+**那什么是引用拷贝呢?** 简单来说,引用拷贝就是两个不同的引用指向同一个对象。
+
+我专门画了一张图来描述浅拷贝、深拷贝、引用拷贝:
+
+
+
+## Object
+
+### Object 类的常见方法有哪些?
+
+Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:
+
+```java
+/**
+ * native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
+ */
+public final native Class> getClass()
+/**
+ * native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
+ */
+public native int hashCode()
+/**
+ * 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
+ */
+public boolean equals(Object obj)
+/**
+ * native 方法,用于创建并返回当前对象的一份拷贝。
+ */
+protected native Object clone() throws CloneNotSupportedException
+/**
+ * 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
+ */
+public String toString()
+/**
+ * native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
+ */
+public final native void notify()
+/**
+ * native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
+ */
+public final native void notifyAll()
+/**
+ * native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
+ */
+public final native void wait(long timeout) throws InterruptedException
+/**
+ * 多了 nanos 参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 毫秒。。
+ */
+public final void wait(long timeout, int nanos) throws InterruptedException
+/**
+ * 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
+ */
+public final void wait() throws InterruptedException
+/**
+ * 实例被垃圾回收器回收的时候触发的操作
+ */
+protected void finalize() throws Throwable { }
+```
+
+### == 和 equals() 的区别
+
+**`==`** 对于基本类型和引用类型的作用效果是不同的:
+
+- 对于基本数据类型来说,`==` 比较的是值。
+- 对于引用数据类型来说,`==` 比较的是对象的内存地址。
+
+> 因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。
+
+**`equals()`** 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。`equals()`方法存在于`Object`类中,而`Object`类是所有类的直接或间接父类,因此所有的类都有`equals()`方法。
+
+`Object` 类 `equals()` 方法:
+
+```java
+public boolean equals(Object obj) {
+ return (this == obj);
+}
+```
+
+`equals()` 方法存在两种使用情况:
+
+- **类没有重写 `equals()`方法**:通过`equals()`比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 `Object`类`equals()`方法。
+- **类重写了 `equals()`方法**:一般我们都重写 `equals()`方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
+
+举个例子(这里只是为了举例。实际上,你按照下面这种写法的话,像 IDEA 这种比较智能的 IDE 都会提示你将 `==` 换成 `equals()` ):
+
+```java
+String a = new String("ab"); // a 为一个引用
+String b = new String("ab"); // b为另一个引用,对象的内容一样
+String aa = "ab"; // 放在常量池中
+String bb = "ab"; // 从常量池中查找
+System.out.println(aa == bb);// true
+System.out.println(a == b);// false
+System.out.println(a.equals(b));// true
+System.out.println(42 == 42.0);// true
+```
+
+`String` 中的 `equals` 方法是被重写过的,因为 `Object` 的 `equals` 方法是比较的对象的内存地址,而 `String` 的 `equals` 方法比较的是对象的值。
+
+当创建 `String` 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 `String` 对象。
+
+`String`类`equals()`方法:
+
+```java
+public boolean equals(Object anObject) {
+ if (this == anObject) {
+ return true;
+ }
+ if (anObject instanceof String) {
+ String anotherString = (String)anObject;
+ int n = value.length;
+ if (n == anotherString.value.length) {
+ char v1[] = value;
+ char v2[] = anotherString.value;
+ int i = 0;
+ while (n-- != 0) {
+ if (v1[i] != v2[i])
+ return false;
+ i++;
+ }
+ return true;
+ }
+ }
+ return false;
+}
+```
+
+### hashCode() 有什么用?
+
+`hashCode()` 的作用是获取哈希码(`int` 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。
+
+
+
+`hashCode()` 定义在 JDK 的 `Object` 类中,这就意味着 Java 中的任何类都包含有 `hashCode()` 函数。另外需要注意的是:`Object` 的 `hashCode()` 方法是本地方法,也就是用 C 语言或 C++ 实现的。
+
+> ⚠️ 注意:该方法在 **Oracle OpenJDK8** 中默认是 "使用线程局部状态来实现 Marsaglia's xor-shift 随机数生成", 并不是 "地址" 或者 "地址转换而来", 不同 JDK/VM 可能不同在 **Oracle OpenJDK8** 中有六种生成方式 (其中第五种是返回地址), 通过添加 VM 参数: -XX:hashCode=4 启用第五种。参考源码:
+>
+> - https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/runtime/globals.hpp(1127行)
+> - https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/87ee5ee27509/src/share/vm/runtime/synchronizer.cpp(537行开始)
+
+```java
+public native int hashCode();
+```
+
+散列表存储的是键值对(key-value),它的特点是:**能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)**
+
+### 为什么要有 hashCode?
+
+我们以“`HashSet` 如何检查重复”为例子来说明为什么要有 `hashCode`?
+
+下面这段内容摘自我的 Java 启蒙书《Head First Java》:
+
+> 当你把对象加入 `HashSet` 时,`HashSet` 会先计算对象的 `hashCode` 值来判断对象加入的位置,同时也会与其他已经加入的对象的 `hashCode` 值作比较,如果没有相符的 `hashCode`,`HashSet` 会假设对象没有重复出现。但是如果发现有相同 `hashCode` 值的对象,这时会调用 `equals()` 方法来检查 `hashCode` 相等的对象是否真的相同。如果两者相同,`HashSet` 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 `equals` 的次数,相应就大大提高了执行速度。
+
+其实, `hashCode()` 和 `equals()`都是用于比较两个对象是否相等。
+
+**那为什么 JDK 还要同时提供这两个方法呢?**
+
+这是因为在一些容器(比如 `HashMap`、`HashSet`)中,有了 `hashCode()` 之后,判断元素是否在对应容器中的效率会更高(参考添加元素进`HashSet`的过程)!
+
+我们在前面也提到了添加元素进`HashSet`的过程,如果 `HashSet` 在对比的时候,同样的 `hashCode` 有多个对象,它会继续使用 `equals()` 来判断是否真的相同。也就是说 `hashCode` 帮助我们大大缩小了查找成本。
+
+**那为什么不只提供 `hashCode()` 方法呢?**
+
+这是因为两个对象的`hashCode` 值相等并不代表两个对象就相等。
+
+**那为什么两个对象有相同的 `hashCode` 值,它们也不一定是相等的?**
+
+因为 `hashCode()` 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓哈希碰撞也就是指的是不同的对象得到相同的 `hashCode` )。
+
+总结下来就是:
+
+- 如果两个对象的`hashCode` 值相等,那这两个对象不一定相等(哈希碰撞)。
+- 如果两个对象的`hashCode` 值相等并且`equals()`方法也返回 `true`,我们才认为这两个对象相等。
+- 如果两个对象的`hashCode` 值不相等,我们就可以直接认为这两个对象不相等。
+
+相信大家看了我前面对 `hashCode()` 和 `equals()` 的介绍之后,下面这个问题已经难不倒你们了。
+
+### 为什么重写 equals() 时必须重写 hashCode() 方法?
+
+因为两个相等的对象的 `hashCode` 值必须是相等。也就是说如果 `equals` 方法判断两个对象是相等的,那这两个对象的 `hashCode` 值也要相等。
+
+如果重写 `equals()` 时没有重写 `hashCode()` 方法的话就可能会导致 `equals` 方法判断是相等的两个对象,`hashCode` 值却不相等。
+
+**思考**:重写 `equals()` 时没有重写 `hashCode()` 方法的话,使用 `HashMap` 可能会出现什么问题。
+
+**总结**:
+
+- `equals` 方法判断两个对象是相等的,那这两个对象的 `hashCode` 值也要相等。
+- 两个对象有相同的 `hashCode` 值,他们也不一定是相等的(哈希碰撞)。
+
+更多关于 `hashCode()` 和 `equals()` 的内容可以查看:[Java hashCode() 和 equals()的若干问题解答](https://www.cnblogs.com/skywang12345/p/3324958.html)
+
+## String
+
+### String、StringBuffer、StringBuilder 的区别?
+
+**可变性**
+
+`String` 是不可变的(后面会详细分析原因)。
+
+`StringBuilder` 与 `StringBuffer` 都继承自 `AbstractStringBuilder` 类,在 `AbstractStringBuilder` 中也是使用字符数组保存字符串,不过没有使用 `final` 和 `private` 关键字修饰,最关键的是这个 `AbstractStringBuilder` 类还提供了很多修改字符串的方法比如 `append` 方法。
+
+```java
+abstract class AbstractStringBuilder implements Appendable, CharSequence {
+ char[] value;
+ public AbstractStringBuilder append(String str) {
+ if (str == null)
+ return appendNull();
+ int len = str.length();
+ ensureCapacityInternal(count + len);
+ str.getChars(0, len, value, count);
+ count += len;
+ return this;
+ }
+ //...
+}
+```
+
+**线程安全性**
+
+`String` 中的对象是不可变的,也就可以理解为常量,线程安全。`AbstractStringBuilder` 是 `StringBuilder` 与 `StringBuffer` 的公共父类,定义了一些字符串的基本操作,如 `expandCapacity`、`append`、`insert`、`indexOf` 等公共方法。`StringBuffer` 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。`StringBuilder` 并没有对方法进行加同步锁,所以是非线程安全的。
+
+**性能**
+
+每次对 `String` 类型进行改变的时候,都会生成一个新的 `String` 对象,然后将指针指向新的 `String` 对象。`StringBuffer` 每次都会对 `StringBuffer` 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 `StringBuilder` 相比使用 `StringBuffer` 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
+
+**对于三者使用的总结:**
+
+1. 操作少量的数据: 适用 `String`
+2. 单线程操作字符串缓冲区下操作大量数据: 适用 `StringBuilder`
+3. 多线程操作字符串缓冲区下操作大量数据: 适用 `StringBuffer`
+
+### String 为什么是不可变的?
+
+`String` 类中使用 `final` 关键字修饰字符数组来保存字符串,~~所以`String` 对象是不可变的。~~
+
+```java
+public final class String implements java.io.Serializable, Comparable, CharSequence {
+ private final char value[];
+ //...
+}
+```
+
+> 🐛 修正:我们知道被 `final` 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。因此,`final` 关键字修饰的数组保存字符串并不是 `String` 不可变的根本原因,因为这个数组保存的字符串是可变的(`final` 修饰引用类型变量的情况)。
+>
+> `String` 真正不可变有下面几点原因:
+>
+> 1. 保存字符串的数组被 `final` 修饰且为私有的,并且`String` 类没有提供/暴露修改这个字符串的方法。
+> 2. `String` 类被 `final` 修饰导致其不能被继承,进而避免了子类破坏 `String` 不可变。
+>
+> 相关阅读:[如何理解 String 类型值的不可变? - 知乎提问](https://www.zhihu.com/question/20618891/answer/114125846)
+>
+> 补充(来自[issue 675](https://github.com/Snailclimb/JavaGuide/issues/675)):在 Java 9 之后,`String`、`StringBuilder` 与 `StringBuffer` 的实现改用 `byte` 数组存储字符串。
+>
+> ```java
+> public final class String implements java.io.Serializable,Comparable, CharSequence {
+> // @Stable 注解表示变量最多被修改一次,称为“稳定的”。
+> @Stable
+> private final byte[] value;
+> }
+>
+> abstract class AbstractStringBuilder implements Appendable, CharSequence {
+> byte[] value;
+>
+> }
+> ```
+>
+> **Java 9 为何要将 `String` 的底层实现由 `char[]` 改成了 `byte[]` ?**
+>
+> 新版的 String 其实支持两个编码方案:Latin-1 和 UTF-16。如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符,那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下,`byte` 占一个字节(8 位),`char` 占用 2 个字节(16),`byte` 相较 `char` 节省一半的内存空间。
+>
+> JDK 官方就说了绝大部分字符串对象只包含 Latin-1 可表示的字符。
+>
+> 
+>
+> 如果字符串中包含的汉字超过 Latin-1 可表示范围内的字符,`byte` 和 `char` 所占用的空间是一样的。
+>
+> 这是官方的介绍:https://openjdk.java.net/jeps/254 。
+
+### 字符串拼接用“+” 还是 StringBuilder?
+
+Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的运算符。
+
+```java
+String str1 = "he";
+String str2 = "llo";
+String str3 = "world";
+String str4 = str1 + str2 + str3;
+```
+
+上面的代码对应的字节码如下:
+
+
+
+可以看出,字符串对象通过“+”的字符串拼接方式,实际上是通过 `StringBuilder` 调用 `append()` 方法实现的,拼接完成之后调用 `toString()` 得到一个 `String` 对象 。
+
+不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:**编译器不会创建单个 `StringBuilder` 以复用,会导致创建过多的 `StringBuilder` 对象**。
+
+```java
+String[] arr = {"he", "llo", "world"};
+String s = "";
+for (int i = 0; i < arr.length; i++) {
+ s += arr[i];
+}
+System.out.println(s);
+```
+
+`StringBuilder` 对象是在循环内部被创建的,这意味着每循环一次就会创建一个 `StringBuilder` 对象。
+
+
+
+如果直接使用 `StringBuilder` 对象进行字符串拼接的话,就不会存在这个问题了。
+
+```java
+String[] arr = {"he", "llo", "world"};
+StringBuilder s = new StringBuilder();
+for (String value : arr) {
+ s.append(value);
+}
+System.out.println(s);
+```
+
+
+
+如果你使用 IDEA 的话,IDEA 自带的代码检查机制也会提示你修改代码。
+
+不过,使用 “+” 进行字符串拼接会产生大量的临时对象的问题在 JDK9 中得到了解决。在 JDK9 当中,字符串相加 “+” 改为了用动态方法 `makeConcatWithConstants()` 来实现,而不是大量的 `StringBuilder` 了。这个改进是 JDK9 的 [JEP 280](https://openjdk.org/jeps/280) 提出的,这也意味着 JDK 9 之后,你可以放心使用“+” 进行字符串拼接了。关于这部分改进的详细介绍,推荐阅读这篇文章:还在无脑用 [StringBuilder?来重温一下字符串拼接吧](https://juejin.cn/post/7182872058743750715) 。
+
+### String#equals() 和 Object#equals() 有何区别?
+
+`String` 中的 `equals` 方法是被重写过的,比较的是 String 字符串的值是否相等。 `Object` 的 `equals` 方法是比较的对象的内存地址。
+
+### 字符串常量池的作用了解吗?
+
+**字符串常量池** 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
+
+```java
+// 在堆中创建字符串对象”ab“
+// 将字符串对象”ab“的引用保存在字符串常量池中
+String aa = "ab";
+// 直接返回字符串常量池中字符串对象”ab“的引用
+String bb = "ab";
+System.out.println(aa==bb);// true
+```
+
+更多关于字符串常量池的介绍可以看一下 [Java 内存区域详解](https://javaguide.cn/java/jvm/memory-area.html) 这篇文章。
+
+### String s1 = new String("abc");这句话创建了几个字符串对象?
+
+会创建 1 或 2 个字符串对象。
+
+1、如果字符串常量池中不存在字符串对象“abc”的引用,那么它将首先在字符串常量池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。
+
+示例代码(JDK 1.8):
+
+```java
+String s1 = new String("abc");
+```
+
+对应的字节码:
+
+
+
+`ldc` 命令用于判断字符串常量池中是否保存了对应的字符串对象的引用,如果保存了的话直接返回,如果没有保存的话,会在堆中创建对应的字符串对象并将该字符串对象的引用保存到字符串常量池中。
+
+2、如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”。
+
+示例代码(JDK 1.8):
+
+```java
+// 字符串常量池中已存在字符串对象“abc”的引用
+String s1 = "abc";
+// 下面这段代码只会在堆中创建 1 个字符串对象“abc”
+String s2 = new String("abc");
+```
+
+对应的字节码:
+
+
+
+这里就不对上面的字节码进行详细注释了,7 这个位置的 `ldc` 命令不会在堆中创建新的字符串对象“abc”,这是因为 0 这个位置已经执行了一次 `ldc` 命令,已经在堆中创建过一次字符串对象“abc”了。7 这个位置执行 `ldc` 命令会直接返回字符串常量池中字符串对象“abc”对应的引用。
+
+### String#intern 方法有什么作用?
+
+`String.intern()` 是一个 native(本地)方法,其作用是将指定的字符串对象的引用保存在字符串常量池中,可以简单分为两种情况:
+
+- 如果字符串常量池中保存了对应的字符串对象的引用,就直接返回该引用。
+- 如果字符串常量池中没有保存了对应的字符串对象的引用,那就在常量池中创建一个指向该字符串对象的引用并返回。
+
+示例代码(JDK 1.8) :
+
+```java
+// 在堆中创建字符串对象”Java“
+// 将字符串对象”Java“的引用保存在字符串常量池中
+String s1 = "Java";
+// 直接返回字符串常量池中字符串对象”Java“对应的引用
+String s2 = s1.intern();
+// 会在堆中在单独创建一个字符串对象
+String s3 = new String("Java");
+// 直接返回字符串常量池中字符串对象”Java“对应的引用
+String s4 = s3.intern();
+// s1 和 s2 指向的是堆中的同一个对象
+System.out.println(s1 == s2); // true
+// s3 和 s4 指向的是堆中不同的对象
+System.out.println(s3 == s4); // false
+// s1 和 s4 指向的是堆中的同一个对象
+System.out.println(s1 == s4); //true
+```
+
+### String 类型的变量和常量做“+”运算时发生了什么?
+
+先来看字符串不加 `final` 关键字拼接的情况(JDK1.8):
+
+```java
+String str1 = "str";
+String str2 = "ing";
+String str3 = "str" + "ing";
+String str4 = str1 + str2;
+String str5 = "string";
+System.out.println(str3 == str4);//false
+System.out.println(str3 == str5);//true
+System.out.println(str4 == str5);//false
+```
+
+> **注意**:比较 String 字符串的值是否相等,可以使用 `equals()` 方法。 `String` 中的 `equals` 方法是被重写过的。 `Object` 的 `equals` 方法是比较的对象的内存地址,而 `String` 的 `equals` 方法比较的是字符串的值是否相等。如果你使用 `==` 比较两个字符串是否相等的话,IDEA 还是提示你使用 `equals()` 方法替换。
+
+
+
+**对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。**
+
+在编译过程中,Javac 编译器(下文中统称为编译器)会进行一个叫做 **常量折叠(Constant Folding)** 的代码优化。《深入理解 Java 虚拟机》中是也有介绍到:
+
+
+
+常量折叠会把常量表达式的值求出来作为常量嵌在最终生成的代码中,这是 Javac 编译器会对源代码做的极少量优化措施之一(代码优化几乎都在即时编译器中进行)。
+
+对于 `String str3 = "str" + "ing";` 编译器会给你优化成 `String str3 = "string";` 。
+
+并不是所有的常量都会进行折叠,只有编译器在程序编译期就可以确定值的常量才可以:
+
+- 基本数据类型( `byte`、`boolean`、`short`、`char`、`int`、`float`、`long`、`double`)以及字符串常量。
+- `final` 修饰的基本数据类型和字符串变量
+- 字符串通过 “+”拼接得到的字符串、基本数据类型之间算数运算(加减乘除)、基本数据类型的位运算(<<、\>>、\>>> )
+
+**引用的值在程序编译期是无法确定的,编译器无法对其进行优化。**
+
+对象引用和“+”的字符串拼接方式,实际上是通过 `StringBuilder` 调用 `append()` 方法实现的,拼接完成之后调用 `toString()` 得到一个 `String` 对象 。
+
+```java
+String str4 = new StringBuilder().append(str1).append(str2).toString();
+```
+
+我们在平时写代码的时候,尽量避免多个字符串对象拼接,因为这样会重新创建对象。如果需要改变字符串的话,可以使用 `StringBuilder` 或者 `StringBuffer`。
+
+不过,字符串使用 `final` 关键字声明之后,可以让编译器当做常量来处理。
+
+示例代码:
+
+```java
+final String str1 = "str";
+final String str2 = "ing";
+// 下面两个表达式其实是等价的
+String c = "str" + "ing";// 常量池中的对象
+String d = str1 + str2; // 常量池中的对象
+System.out.println(c == d);// true
+```
+
+被 `final` 关键字修改之后的 `String` 会被编译器当做常量来处理,编译器在程序编译期就可以确定它的值,其效果就相当于访问常量。
+
+如果 ,编译器在运行时才能知道其确切值的话,就无法对其优化。
+
+示例代码(`str2` 在运行时才能确定其值):
+
+```java
+final String str1 = "str";
+final String str2 = getStr();
+String c = "str" + "ing";// 常量池中的对象
+String d = str1 + str2; // 在堆上创建的新的对象
+System.out.println(c == d);// false
+public static String getStr() {
+ return "ing";
+}
+```
+
+## 参考
+
+- 深入解析 String#intern:
+- R 大(RednaxelaFX)关于常量折叠的回答:https://www.zhihu.com/question/55976094/answer/147302764
diff --git a/docs/java/basis/java-basic-questions-03.md b/docs/java/basis/java-basic-questions-03.md
new file mode 100644
index 0000000000000000000000000000000000000000..f21aa72d589dbffe88ad3d56ce29822fb413b555
--- /dev/null
+++ b/docs/java/basis/java-basic-questions-03.md
@@ -0,0 +1,571 @@
+---
+title: Java基础常见面试题总结(下)
+category: Java
+tag:
+ - Java基础
+head:
+ - - meta
+ - name: keywords
+ content: Java异常,泛型,反射,IO,注解
+ - - meta
+ - name: description
+ content: 全网质量最高的Java基础常见知识点和面试题总结,希望对你有帮助!
+---
+
+## 异常
+
+**Java 异常类层次结构图概览**:
+
+
+
+### Exception 和 Error 有什么区别?
+
+在 Java 中,所有的异常都有一个共同的祖先 `java.lang` 包中的 `Throwable` 类。`Throwable` 类有两个重要的子类:
+
+- **`Exception`** :程序本身可以处理的异常,可以通过 `catch` 来进行捕获。`Exception` 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
+- **`Error`**:`Error` 属于程序无法处理的错误 ,~~我们没办法通过 `catch` 来进行捕获~~不建议通过`catch`捕获 。例如 Java 虚拟机运行错误(`Virtual MachineError`)、虚拟机内存不够错误(`OutOfMemoryError`)、类定义错误(`NoClassDefFoundError`)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
+
+### Checked Exception 和 Unchecked Exception 有什么区别?
+
+**Checked Exception** 即 受检查异常 ,Java 代码在编译过程中,如果受检查异常没有被 `catch`或者`throws` 关键字处理的话,就没办法通过编译。
+
+比如下面这段 IO 操作的代码:
+
+
+
+除了`RuntimeException`及其子类以外,其他的`Exception`类及其子类都属于受检查异常 。常见的受检查异常有:IO 相关的异常、`ClassNotFoundException`、`SQLException`...。
+
+**Unchecked Exception** 即 **不受检查异常** ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
+
+`RuntimeException` 及其子类都统称为非受检查异常,常见的有(建议记下来,日常开发中会经常用到):
+
+- `NullPointerException`(空指针错误)
+- `IllegalArgumentException`(参数错误比如方法入参类型错误)
+- `NumberFormatException`(字符串转换为数字格式错误,`IllegalArgumentException`的子类)
+- `ArrayIndexOutOfBoundsException`(数组越界错误)
+- `ClassCastException`(类型转换错误)
+- `ArithmeticException`(算术错误)
+- `SecurityException` (安全错误比如权限不够)
+- `UnsupportedOperationException`(不支持的操作错误比如重复创建同一用户)
+- ......
+
+
+
+### Throwable 类常用方法有哪些?
+
+- `String getMessage()`: 返回异常发生时的简要描述
+- `String toString()`: 返回异常发生时的详细信息
+- `String getLocalizedMessage()`: 返回异常对象的本地化信息。使用 `Throwable` 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 `getMessage()`返回的结果相同
+- `void printStackTrace()`: 在控制台上打印 `Throwable` 对象封装的异常信息
+
+### try-catch-finally 如何使用?
+
+- `try`块:用于捕获异常。其后可接零个或多个 `catch` 块,如果没有 `catch` 块,则必须跟一个 `finally` 块。
+- `catch`块:用于处理 try 捕获到的异常。
+- `finally` 块:无论是否捕获或处理异常,`finally` 块里的语句都会被执行。当在 `try` 块或 `catch` 块中遇到 `return` 语句时,`finally` 语句块将在方法返回之前被执行。
+
+代码示例:
+
+```java
+try {
+ System.out.println("Try to do something");
+ throw new RuntimeException("RuntimeException");
+} catch (Exception e) {
+ System.out.println("Catch Exception -> " + e.getMessage());
+} finally {
+ System.out.println("Finally");
+}
+```
+
+输出:
+
+```
+Try to do something
+Catch Exception -> RuntimeException
+Finally
+```
+
+**注意:不要在 finally 语句块中使用 return!** 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。
+
+[jvm 官方文档](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.2.5)中有明确提到:
+
+> If the `try` clause executes a _return_, the compiled code does the following:
+>
+> 1. Saves the return value (if any) in a local variable.
+> 2. Executes a _jsr_ to the code for the `finally` clause.
+> 3. Upon return from the `finally` clause, returns the value saved in the local variable.
+
+代码示例:
+
+```java
+public static void main(String[] args) {
+ System.out.println(f(2));
+}
+
+public static int f(int value) {
+ try {
+ return value * value;
+ } finally {
+ if (value == 2) {
+ return 0;
+ }
+ }
+}
+```
+
+输出:
+
+```
+0
+```
+
+### finally 中的代码一定会执行吗?
+
+不一定的!在某些情况下,finally 中的代码不会被执行。
+
+就比如说 finally 之前虚拟机被终止运行的话,finally 中的代码就不会被执行。
+
+```java
+try {
+ System.out.println("Try to do something");
+ throw new RuntimeException("RuntimeException");
+} catch (Exception e) {
+ System.out.println("Catch Exception -> " + e.getMessage());
+ // 终止当前正在运行的Java虚拟机
+ System.exit(1);
+} finally {
+ System.out.println("Finally");
+}
+```
+
+输出:
+
+```
+Try to do something
+Catch Exception -> RuntimeException
+```
+
+另外,在以下 2 种特殊情况下,`finally` 块的代码也不会被执行:
+
+1. 程序所在的线程死亡。
+2. 关闭 CPU。
+
+相关 issue:。
+
+🧗🏻 进阶一下:从字节码角度分析`try catch finally`这个语法糖背后的实现原理。
+
+### 如何使用 `try-with-resources` 代替`try-catch-finally`?
+
+1. **适用范围(资源的定义):** 任何实现 `java.lang.AutoCloseable`或者 `java.io.Closeable` 的对象
+2. **关闭资源和 finally 块的执行顺序:** 在 `try-with-resources` 语句中,任何 catch 或 finally 块在声明的资源关闭后运行
+
+《Effective Java》中明确指出:
+
+> 面对必须要关闭的资源,我们总是应该优先使用 `try-with-resources` 而不是`try-finally`。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。`try-with-resources`语句让我们更容易编写必须要关闭的资源的代码,若采用`try-finally`则几乎做不到这点。
+
+Java 中类似于`InputStream`、`OutputStream`、`Scanner`、`PrintWriter`等的资源都需要我们调用`close()`方法来手动关闭,一般情况下我们都是通过`try-catch-finally`语句来实现这个需求,如下:
+
+```java
+//读取文本文件的内容
+Scanner scanner = null;
+try {
+ scanner = new Scanner(new File("D://read.txt"));
+ while (scanner.hasNext()) {
+ System.out.println(scanner.nextLine());
+ }
+} catch (FileNotFoundException e) {
+ e.printStackTrace();
+} finally {
+ if (scanner != null) {
+ scanner.close();
+ }
+}
+```
+
+使用 Java 7 之后的 `try-with-resources` 语句改造上面的代码:
+
+```java
+try (Scanner scanner = new Scanner(new File("test.txt"))) {
+ while (scanner.hasNext()) {
+ System.out.println(scanner.nextLine());
+ }
+} catch (FileNotFoundException fnfe) {
+ fnfe.printStackTrace();
+}
+```
+
+当然多个资源需要关闭的时候,使用 `try-with-resources` 实现起来也非常简单,如果你还是用`try-catch-finally`可能会带来很多问题。
+
+通过使用分号分隔,可以在`try-with-resources`块中声明多个资源。
+
+```java
+try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
+ BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
+ int b;
+ while ((b = bin.read()) != -1) {
+ bout.write(b);
+ }
+}
+catch (IOException e) {
+ e.printStackTrace();
+}
+```
+
+### 异常使用有哪些需要注意的地方?
+
+- 不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。
+- 抛出的异常信息一定要有意义。
+- 建议抛出更加具体的异常比如字符串转换为数字格式错误的时候应该抛出`NumberFormatException`而不是其父类`IllegalArgumentException`。
+- 使用日志打印异常之后就不要再抛出异常了(两者不要同时存在一段代码逻辑中)。
+- ......
+
+## 泛型
+
+### 什么是泛型?有什么作用?
+
+**Java 泛型(Generics)** 是 JDK 5 中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。
+
+编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如 `ArrayList persons = new ArrayList()` 这行代码就指明了该 `ArrayList` 对象只能传入 `Person` 对象,如果传入其他类型的对象就会报错。
+
+```java
+ArrayList extends AbstractList
+```
+
+并且,原生 `List` 返回类型是 `Object` ,需要手动转换类型才能使用,使用泛型后编译器自动转换。
+
+### 泛型的使用方式有哪几种?
+
+泛型一般有三种使用方式:**泛型类**、**泛型接口**、**泛型方法**。
+
+**1.泛型类**:
+
+```java
+//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
+//在实例化泛型类时,必须指定T的具体类型
+public class Generic{
+
+ private T key;
+
+ public Generic(T key) {
+ this.key = key;
+ }
+
+ public T getKey(){
+ return key;
+ }
+}
+```
+
+如何实例化泛型类:
+
+```java
+Generic genericInteger = new Generic(123456);
+```
+
+**2.泛型接口**:
+
+```java
+public interface Generator {
+ public T method();
+}
+```
+
+实现泛型接口,不指定类型:
+
+```java
+class GeneratorImpl implements Generator{
+ @Override
+ public T method() {
+ return null;
+ }
+}
+```
+
+实现泛型接口,指定类型:
+
+```java
+class GeneratorImpl implements Generator{
+ @Override
+ public String method() {
+ return "hello";
+ }
+}
+```
+
+**3.泛型方法**:
+
+```java
+ public static < E > void printArray( E[] inputArray )
+ {
+ for ( E element : inputArray ){
+ System.out.printf( "%s ", element );
+ }
+ System.out.println();
+ }
+```
+
+使用:
+
+```java
+// 创建不同类型数组:Integer, Double 和 Character
+Integer[] intArray = { 1, 2, 3 };
+String[] stringArray = { "Hello", "World" };
+printArray( intArray );
+printArray( stringArray );
+```
+
+> 注意: `public static < E > void printArray( E[] inputArray )` 一般被称为静态泛型方法;在 java 中泛型只是一个占位符,必须在传递类型后才能使用。类在实例化时才能真正的传递类型参数,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法的加载就已经完成了,所以静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的 ``
+
+### 项目中哪里用到了泛型?
+
+- 自定义接口通用返回结果 `CommonResult` 通过参数 `T` 可根据具体的返回类型动态指定结果的数据类型
+- 定义 `Excel` 处理类 `ExcelUtil` 用于动态指定 `Excel` 导出的数据类型
+- 构建集合工具类(参考 `Collections` 中的 `sort`, `binarySearch` 方法)。
+- ......
+
+## 反射
+
+关于反射的详细解读,请看这篇文章 [Java 反射机制详解](./reflection.md) 。
+
+### 何谓反射?
+
+如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
+
+### 反射的优缺点?
+
+反射可以让我们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利。
+
+不过,反射让我们在运行时有了分析操作类的能力的同时,也增加了安全问题,比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。
+
+相关阅读:[Java Reflection: Why is it so slow?](https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow) 。
+
+### 反射的应用场景?
+
+像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。但是!这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
+
+**这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。**
+
+比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 `Method` 来调用指定的方法。
+
+```java
+public class DebugInvocationHandler implements InvocationHandler {
+ /**
+ * 代理类中的真实对象
+ */
+ private final Object target;
+
+ public DebugInvocationHandler(Object target) {
+ this.target = target;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
+ System.out.println("before method " + method.getName());
+ Object result = method.invoke(target, args);
+ System.out.println("after method " + method.getName());
+ return result;
+ }
+}
+
+```
+
+另外,像 Java 中的一大利器 **注解** 的实现也用到了反射。
+
+为什么你使用 Spring 的时候 ,一个`@Component`注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 `@Value`注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
+
+这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
+
+## 注解
+
+### 何谓注解?
+
+`Annotation` (注解) 是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。
+
+注解本质是一个继承了`Annotation` 的特殊接口:
+
+```java
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Override {
+
+}
+
+public interface Override extends Annotation{
+
+}
+```
+
+JDK 提供了很多内置的注解(比如 `@Override`、`@Deprecated`),同时,我们还可以自定义注解。
+
+### 注解的解析方法有哪几种?
+
+注解只有被解析之后才会生效,常见的解析方法有两种:
+
+- **编译期直接扫描**:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用`@Override` 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。
+- **运行期通过反射处理**:像框架中自带的注解(比如 Spring 框架的 `@Value`、`@Component`)都是通过反射来进行处理的。
+
+## SPI
+
+关于 SPI 的详细解读,请看这篇文章 [Java SPI 机制详解](./spi.md) 。
+
+### 何谓 SPI?
+
+SPI 即 Service Provider Interface ,字面意思就是:“服务提供者的接口”,我的理解是:专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。
+
+SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。
+
+很多框架都使用了 Java 的 SPI 机制,比如:Spring 框架、数据库加载驱动、日志接口、以及 Dubbo 的扩展实现等等。
+
+
+
+### SPI 和 API 有什么区别?
+
+**那 SPI 和 API 有啥区别?**
+
+说到 SPI 就不得不说一下 API 了,从广义上来说它们都属于接口,而且很容易混淆。下面先用一张图说明一下:
+
+
+
+一般模块之间都是通过接口进行通讯,那我们在服务调用方和服务实现方(也称服务提供者)之间引入一个“接口”。
+
+当实现方提供了接口和实现,我们可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是 API ,这种接口和实现都是放在实现方的。
+
+当接口存在于调用方这边时,就是 SPI ,由接口调用方确定接口规则,然后由不同的厂商去根据这个规则对这个接口进行实现,从而提供服务。
+
+举个通俗易懂的例子:公司 H 是一家科技公司,新设计了一款芯片,然后现在需要量产了,而市面上有好几家芯片制造业公司,这个时候,只要 H 公司指定好了这芯片生产的标准(定义好了接口标准),那么这些合作的芯片公司(服务提供者)就按照标准交付自家特色的芯片(提供不同方案的实现,但是给出来的结果是一样的)。
+
+### SPI 的优缺点?
+
+通过 SPI 机制能够大大地提高接口设计的灵活性,但是 SPI 机制也存在一些缺点,比如:
+
+- 需要遍历加载所有的实现类,不能做到按需加载,这样效率还是相对较低的。
+- 当多个 `ServiceLoader` 同时 `load` 时,会有并发问题。
+
+## 序列化和反序列化
+
+关于序列化和反序列化的详细解读,请看这篇文章 [Java 序列化详解](./serialization.md) ,里面涉及到的知识点和面试题更全面。
+
+### 什么是序列化?什么是反序列化?
+
+如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
+
+简单来说:
+
+- **序列化**:将数据结构或对象转换成二进制字节流的过程
+- **反序列化**:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
+
+对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。
+
+下面是序列化和反序列化常见应用场景:
+
+- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
+- 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
+- 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
+- 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。
+
+维基百科是如是介绍序列化的:
+
+> **序列化**(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。
+
+综上:**序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。**
+
+
+
+https://www.corejavaguru.com/java/serialization/interview-questions-1
+
+**序列化协议对应于 TCP/IP 4 层模型的哪一层?**
+
+我们知道网络通信的双方必须要采用和遵守相同的协议。TCP/IP 四层模型是下面这样的,序列化协议属于哪一层呢?
+
+1. 应用层
+2. 传输层
+3. 网络层
+4. 网络接口层
+
+
+
+如上图所示,OSI 七层协议模型中,表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。反过来的话,就是将二进制流转换成应用层的用户数据。这不就对应的是序列化和反序列化么?
+
+因为,OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分。
+
+### 如果有些字段不想进行序列化怎么办?
+
+对于不想进行序列化的变量,使用 `transient` 关键字修饰。
+
+`transient` 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 `transient` 修饰的变量值不会被持久化和恢复。
+
+关于 `transient` 还有几点注意:
+
+- `transient` 只能修饰变量,不能修饰类和方法。
+- `transient` 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 `int` 类型,那么反序列后结果就是 `0`。
+- `static` 变量因为不属于任何对象(Object),所以无论有没有 `transient` 关键字修饰,均不会被序列化。
+
+### 常见序列化协议有哪些?
+
+JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。
+
+像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择。
+
+### 为什么不推荐使用 JDK 自带的序列化?
+
+我们很少或者说几乎不会直接使用 JDK 自带的序列化方式,主要原因有下面这些原因:
+
+- **不支持跨语言调用** : 如果调用的是其他语言开发的服务的时候就不支持了。
+- **性能差**:相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
+- **存在安全问题**:序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。相关阅读:[应用安全:JAVA 反序列化漏洞之殇](https://cryin.github.io/blog/secure-development-java-deserialization-vulnerability/) 。
+
+## I/O
+
+关于 I/O 的详细解读,请看下面这几篇文章,里面涉及到的知识点和面试题更全面。
+
+- [Java IO 基础知识总结](../io/io-basis.md)
+- [Java IO 设计模式总结](../io/io-design-patterns.md)
+- [Java IO 模型详解](../io/io-model.md)
+
+### Java IO 流了解吗?
+
+IO 即 `Input/Output`,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似于水流,因此称为 IO 流。IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。
+
+Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
+
+- `InputStream`/`Reader`: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
+- `OutputStream`/`Writer`: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
+
+### I/O 流为什么要分为字节流和字符流呢?
+
+问题本质想问:**不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?**
+
+个人认为主要有两点原因:
+
+- 字符流是由 Java 虚拟机将字节转换得到的,这个过程还算是比较耗时;
+- 如果我们不知道编码类型的话,使用字节流的过程中很容易出现乱码问题。
+
+### Java IO 中的设计模式有哪些?
+
+参考答案:[Java IO 设计模式总结](../io/io-design-patterns.md)
+
+### BIO、NIO 和 AIO 的区别?
+
+参考答案:[Java IO 模型详解](../io/io-model.md)
+
+## 语法糖
+
+### 什么是语法糖?
+
+**语法糖(Syntactic sugar)** 代指的是编程语言为了方便程序员开发程序而设计的一种特殊语法,这种语法对编程语言的功能并没有影响。实现相同的功能,基于语法糖写出来的代码往往更简单简洁且更易阅读。
+
+举个例子,Java 中的 `for-each` 就是一个常用的语法糖,其原理其实就是基于普通的 for 循环和迭代器。
+
+```java
+String[] strs = {"JavaGuide", "公众号:JavaGuide", "博客:https://javaguide.cn/"};
+for (String s : strs) {
+ System.out.println(s);
+}
+```
+
+不过,JVM 其实并不能识别语法糖,Java 语法糖要想被正确执行,需要先通过编译器进行解糖,也就是在程序编译阶段将其转换成 JVM 认识的基本语法。这也侧面说明,Java 中真正支持语法糖的是 Java 编译器而不是 JVM。如果你去看`com.sun.tools.javac.main.JavaCompiler`的源码,你会发现在`compile()`中有一个步骤就是调用`desugar()`,这个方法就是负责解语法糖的实现的。
+
+### Java 中有哪些常见的语法糖?
+
+Java 中最常用的语法糖主要有泛型、自动拆装箱、变长参数、枚举、内部类、增强 for 循环、try-with-resources 语法、lambda 表达式等。
+
+关于这些语法糖的详细解读,请看这篇文章 [Java 语法糖详解](./syntactic-sugar.md) 。
diff --git a/docs/java/basis/java-keyword-summary.md b/docs/java/basis/java-keyword-summary.md
new file mode 100644
index 0000000000000000000000000000000000000000..34a05f661978a7d3b1059862fdfe13f900049711
--- /dev/null
+++ b/docs/java/basis/java-keyword-summary.md
@@ -0,0 +1,307 @@
+# final,static,this,super 关键字总结
+
+## final 关键字
+
+**final 关键字,意思是最终的、不可修改的,最见不得变化 ,用来修饰类、方法和变量,具有以下特点:**
+
+1. final 修饰的类不能被继承,final 类中的所有成员方法都会被隐式的指定为 final 方法;
+
+2. final 修饰的方法不能被重写;
+
+3. final 修饰的变量是常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。
+
+说明:使用 final 方法的原因有两个:
+
+1. 把方法锁定,以防任何继承类修改它的含义;
+2. 效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。
+
+## static 关键字
+
+**static 关键字主要有以下四种使用场景:**
+
+1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被 static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()`
+2. **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
+3. **静态内部类(static 修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非 static 成员变量和方法。
+4. **静态导包(用来导入类中的静态资源,1.5 之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
+
+## this 关键字
+
+this 关键字用于引用类的当前实例。 例如:
+
+```java
+class Manager {
+ Employees[] employees;
+ void manageEmployees() {
+ int totalEmp = this.employees.length;
+ System.out.println("Total employees: " + totalEmp);
+ this.report();
+ }
+ void report() { }
+}
+```
+
+在上面的示例中,this 关键字用于两个地方:
+
+- this.employees.length:访问类 Manager 的当前实例的变量。
+- this.report():调用类 Manager 的当前实例的方法。
+
+此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。
+
+## super 关键字
+
+super 关键字用于从子类访问父类的变量和方法。 例如:
+
+```java
+public class Super {
+ protected int number;
+ protected showNumber() {
+ System.out.println("number = " + number);
+ }
+}
+public class Sub extends Super {
+ void bar() {
+ super.number = 10;
+ super.showNumber();
+ }
+}
+```
+
+在上面的例子中,Sub 类访问父类成员变量 number 并调用其父类 Super 的 `showNumber()` 方法。
+
+**使用 this 和 super 要注意的问题:**
+
+- 在构造器中使用 `super()` 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。
+- this、super 不能用在 static 方法中。
+
+**简单解释一下:**
+
+被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, **this 和 super 是属于对象范畴的东西,而静态方法是属于类范畴的东西**。
+
+## 参考
+
+- https://www.codejava.net/java-core/the-java-language/java-keywords
+- https://blog.csdn.net/u013393958/article/details/79881037
+
+# static 关键字详解
+
+## static 关键字主要有以下四种使用场景
+
+1. 修饰成员变量和成员方法
+2. 静态代码块
+3. 修饰类(只能修饰内部类)
+4. 静态导包(用来导入类中的静态资源,1.5 之后的新特性)
+
+### 修饰成员变量和成员方法(常用)
+
+被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被 static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。
+
+方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
+
+HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。
+
+调用格式:
+
+- `类名.静态变量名`
+- `类名.静态方法名()`
+
+如果变量或者方法被 private 则代表该属性或者该方法只能在类的内部被访问而不能在类的外部被访问。
+
+测试方法:
+
+```java
+public class StaticBean {
+ String name;
+ //静态变量
+ static int age;
+ public StaticBean(String name) {
+ this.name = name;
+ }
+ //静态方法
+ static void sayHello() {
+ System.out.println("Hello i am java");
+ }
+ @Override
+ public String toString() {
+ return "StaticBean{"+
+ "name=" + name + ",age=" + age +
+ "}";
+ }
+}
+```
+
+```java
+public class StaticDemo {
+ public static void main(String[] args) {
+ StaticBean staticBean = new StaticBean("1");
+ StaticBean staticBean2 = new StaticBean("2");
+ StaticBean staticBean3 = new StaticBean("3");
+ StaticBean staticBean4 = new StaticBean("4");
+ StaticBean.age = 33;
+ System.out.println(staticBean + " " + staticBean2 + " " + staticBean3 + " " + staticBean4);
+ //StaticBean{name=1,age=33} StaticBean{name=2,age=33} StaticBean{name=3,age=33} StaticBean{name=4,age=33}
+ StaticBean.sayHello();//Hello i am java
+ }
+}
+```
+
+### 静态代码块
+
+静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块 —> 非静态代码块 —> 构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
+
+静态代码块的格式是
+
+```
+static {
+语句体;
+}
+```
+
+一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM 加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM 将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
+
+
+
+静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问.
+
+### 静态内部类
+
+静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:
+
+1. 它的创建是不需要依赖外围类的创建。
+2. 它不能使用任何外围类的非 static 成员变量和方法。
+
+Example(静态内部类实现单例模式)
+
+```java
+public class Singleton {
+ //声明为 private 避免调用默认构造方法创建对象
+ private Singleton() {
+ }
+ // 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
+ private static class SingletonHolder {
+ private static final Singleton INSTANCE = new Singleton();
+ }
+ public static Singleton getUniqueInstance() {
+ return SingletonHolder.INSTANCE;
+ }
+}
+```
+
+当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance()`方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
+
+这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
+
+### 静态导包
+
+格式为:import static
+
+这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法
+
+```java
+ //将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用
+ //如果只想导入单一某个静态方法,只需要将*换成对应的方法名即可
+import static java.lang.Math.*;//换成import static java.lang.Math.max;具有一样的效果
+public class Demo {
+ public static void main(String[] args) {
+ int max = max(1,2);
+ System.out.println(max);
+ }
+}
+```
+
+## 补充内容
+
+### 静态方法与非静态方法
+
+静态方法属于类本身,非静态方法属于从该类生成的每个对象。 如果您的方法执行的操作不依赖于其类的各个变量和方法,请将其设置为静态(这将使程序的占用空间更小)。 否则,它应该是非静态的。
+
+Example
+
+```java
+class Foo {
+ int i;
+ public Foo(int i) {
+ this.i = i;
+ }
+ public static String method1() {
+ return "An example string that doesn't depend on i (an instance variable)";
+ }
+ public int method2() {
+ return this.i + 1; //Depends on i
+ }
+}
+```
+
+你可以像这样调用静态方法:`Foo.method1()`。 如果您尝试使用这种方法调用 method2 将失败。 但这样可行
+
+```java
+Foo bar = new Foo(1);
+bar.method2();
+```
+
+总结:
+
+- 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
+- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
+
+### `static{}`静态代码块与`{}`非静态代码块(构造代码块)
+
+相同点:都是在 JVM 加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些 static 变量进行赋值。
+
+不同点:静态代码块在非静态代码块之前执行(静态代码块 -> 非静态代码块 -> 构造方法)。静态代码块只在第一次 new 执行一次,之后不再执行,而非静态代码块在每 new 一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。
+
+> **🐛 修正(参见:[issue #677](https://github.com/Snailclimb/JavaGuide/issues/677))**:静态代码块可能在第一次 new 对象的时候执行,但不一定只在第一次 new 的时候执行。比如通过 `Class.forName("ClassDemo")`创建 Class 对象的时候也会执行,即 new 或者 `Class.forName("ClassDemo")` 都会执行静态代码块。
+> 一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:`Arrays` 类,`Character` 类,`String` 类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的.
+
+Example:
+
+```java
+public class Test {
+ public Test() {
+ System.out.print("默认构造方法!--");
+ }
+ //非静态代码块
+ {
+ System.out.print("非静态代码块!--");
+ }
+ //静态代码块
+ static {
+ System.out.print("静态代码块!--");
+ }
+ private static void test() {
+ System.out.print("静态方法中的内容! --");
+ {
+ System.out.print("静态方法中的代码块!--");
+ }
+ }
+ public static void main(String[] args) {
+ Test test = new Test();
+ Test.test();//静态代码块!--静态方法中的内容! --静态方法中的代码块!--
+ }
+}
+```
+
+上述代码输出:
+
+```
+静态代码块!--非静态代码块!--默认构造方法!--静态方法中的内容! --静态方法中的代码块!--
+```
+
+当只执行 `Test.test();` 时输出:
+
+```
+静态代码块!--静态方法中的内容! --静态方法中的代码块!--
+```
+
+当只执行 `Test test = new Test();` 时输出:
+
+```
+静态代码块!--非静态代码块!--默认构造方法!--
+```
+
+非静态代码块与构造函数的区别是:非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。
+
+### 参考
+
+- https://blog.csdn.net/chen13579867831/article/details/78995480
+- https://www.cnblogs.com/chenssy/p/3388487.html
+- https://www.cnblogs.com/Qian123/p/5713440.html
diff --git "a/docs/java/basis/java\345\237\272\347\241\200\347\237\245\350\257\206\346\200\273\347\273\223.md" "b/docs/java/basis/java\345\237\272\347\241\200\347\237\245\350\257\206\346\200\273\347\273\223.md"
deleted file mode 100644
index 867bfb6386711577017339b254dea6a6eb512bed..0000000000000000000000000000000000000000
--- "a/docs/java/basis/java\345\237\272\347\241\200\347\237\245\350\257\206\346\200\273\347\273\223.md"
+++ /dev/null
@@ -1,1374 +0,0 @@
----
-title: Java基础知识&面试题总结
-category: Java
-tag:
- - Java基础
----
-
-## 基础概念与常识
-
-### Java 语言有哪些特点?
-
-1. 简单易学;
-2. 面向对象(封装,继承,多态);
-3. 平台无关性( Java 虚拟机实现平台无关性);
-4. 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持);
-5. 可靠性;
-6. 安全性;
-7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便);
-8. 编译与解释并存;
-
-> **🐛 修正(参见: [issue#544](https://github.com/Snailclimb/JavaGuide/issues/544))** :C++11 开始(2011 年的时候),C++就引入了多线程库,在 windows、linux、macos 都可以使用`std::thread`和`std::async`来创建线程。参考链接:http://www.cplusplus.com/reference/thread/thread/?kw=thread
-
-### JVM vs JDK vs JRE
-
-#### JVM
-
-Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。
-
-**什么是字节码?采用字节码的好处是什么?**
-
-> 在 Java 中,JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
-
-**Java 程序从源代码到运行一般有下面 3 步:**
-
-
-
-我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
-
-> HotSpot 采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是 JIT 所需要编译的部分。JVM 会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各方面的开销。JDK 支持分层编译和 AOT 协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。
-
-**总结:**
-
-Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
-
-#### JDK 和 JRE
-
-JDK 是 Java Development Kit 缩写,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
-
-JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
-
-如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装 JDK 了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何 Java 开发,仍然需要安装 JDK。例如,如果要使用 JSP 部署 Web 应用程序,那么从技术上讲,您只是在应用程序服务器中运行 Java 程序。那你为什么需要 JDK 呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。
-
-### 为什么说 Java 语言“编译与解释并存”?
-
-高级编程语言按照程序的执行方式分为编译型和解释型两种。简单来说,编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立即执行。比如,你想阅读一本英文名著,你可以找一个英文翻译人员帮助你阅读,
-有两种选择方式,你可以先等翻译人员将全本的英文名著(也就是源码)都翻译成汉语,再去阅读,也可以让翻译人员翻译一段,你在旁边阅读一段,慢慢把书读完。
-
-Java 语言既具有编译型语言的特征,也具有解释型语言的特征,因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(`*.class` 文件),这种字节码必须由 Java 解释器来解释执行。因此,我们可以认为 Java 语言编译与解释并存。
-
-### Oracle JDK 和 OpenJDK 的对比
-
-可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么 Oracle JDK 和 OpenJDK 之间是否存在重大差异?下面我通过收集到的一些资料,为你解答这个被很多人忽视的问题。
-
-对于 Java 7,没什么关键的地方。OpenJDK 项目主要基于 Sun 捐赠的 HotSpot 源代码。此外,OpenJDK 被选为 Java 7 的参考实现,由 Oracle 工程师维护。关于 JVM,JDK,JRE 和 OpenJDK 之间的区别,Oracle 博客帖子在 2012 年有一个更详细的答案:
-
-> 问:OpenJDK 存储库中的源代码与用于构建 Oracle JDK 的代码之间有什么区别?
->
-> 答:非常接近 - 我们的 Oracle JDK 版本构建过程基于 OpenJDK 7 构建,只添加了几个部分,例如部署代码,其中包括 Oracle 的 Java 插件和 Java WebStart 的实现,以及一些闭源的第三方组件,如图形光栅化器,一些开源的第三方组件,如 Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源 Oracle JDK 的所有部分,除了我们考虑商业功能的部分。
-
-**总结:**
-
-1. Oracle JDK 大概每 6 个月发一次主要版本,而 OpenJDK 版本大概每三个月发布一次。但这不是固定的,我觉得了解这个没啥用处。详情参见:[https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence](https://blogs.oracle.com/java-platform-group/update-and-faq-on-the-java-se-release-cadence) 。
-2. OpenJDK 是一个参考模型并且是完全开源的,而 Oracle JDK 是 OpenJDK 的一个实现,并不是完全开源的;
-3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK 和 Oracle JDK 的代码几乎相同,但 Oracle JDK 有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择 Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用 OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到 Oracle JDK 就可以解决问题;
-4. 在响应性和 JVM 性能方面,Oracle JDK 与 OpenJDK 相比提供了更好的性能;
-5. Oracle JDK 不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本;
-6. Oracle JDK 使用 BCL/OTN 协议获得许可,而 OpenJDK 根据 GPL v2 许可获得许可。
-
-🌈 拓展一下:
-
-- BCL 协议(Oracle Binary Code License Agreement): 可以使用JDK(支持商用),但是不能进行修改。
-- OTN 协议(Oracle Technology Network License Agreement): 11 及之后新发布的JDK用的都是这个协议,可以自己私下用,但是商用需要付费。
-
-
-
-相关阅读👍:[《Differences Between Oracle JDK and OpenJDK》](https://www.baeldung.com/oracle-jdk-vs-openjdk)
-
-### Java 和 C++的区别?
-
-我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过 C++,也要记下来!
-
-- 都是面向对象的语言,都支持封装、继承和多态
-- Java 不提供指针来直接访问内存,程序内存更加安全
-- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
-- Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存。
-- C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载(操作符重载增加了复杂性,这与 Java 最初的设计思想不符)。
-- ......
-
-### import java 和 javax 有什么区别?
-
-刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来使用。然而随着时间的推移,javax 逐渐地扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包确实太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准 API 的一部分。
-
-所以,实际上 java 和 javax 没有区别。这都是一个名字。
-
-## 基本语法
-
-### 字符型常量和字符串常量的区别?
-
-1. **形式** : 字符常量是单引号引起的一个字符,字符串常量是双引号引起的 0 个或若干个字符
-2. **含义** : 字符常量相当于一个整型值( ASCII 值),可以参加表达式运算; 字符串常量代表一个地址值(该字符串在内存中存放位置)
-3. **占内存大小** : 字符常量只占 2 个字节; 字符串常量占若干个字节 (**注意: char 在 Java 中占两个字节**),
-
- > 字符封装类 `Character` 有一个成员常量 `Character.SIZE` 值为 16,单位是`bits`,该值除以 8(`1byte=8bits`)后就可以得到 2 个字节
-
-> java 编程思想第四版:2.2.2 节
-> 
-
-### 注释
-
-Java 中的注释有三种:
-
-1. 单行注释
-
-2. 多行注释
-
-3. 文档注释。
-
-在我们编写代码的时候,如果代码量比较少,我们自己或者团队其他成员还可以很轻易地看懂代码,但是当项目结构一旦复杂起来,我们就需要用到注释了。注释并不会执行(编译器在编译代码之前会把代码中的所有注释抹掉,字节码中不保留注释),是我们程序员写给自己看的,注释是你的代码说明书,能够帮助看代码的人快速地理清代码之间的逻辑关系。因此,在写程序的时候随手加上注释是一个非常好的习惯。
-
-《Clean Code》这本书明确指出:
-
-> **代码的注释不是越详细越好。实际上好的代码本身就是注释,我们要尽量规范和美化自己的代码来减少不必要的注释。**
->
-> **若编程语言足够有表达力,就不需要注释,尽量通过代码来阐述。**
->
-> 举个例子:
->
-> 去掉下面复杂的注释,只需要创建一个与注释所言同一事物的函数即可
->
-> ```java
-> // check to see if the employee is eligible for full benefits
-> if ((employee.flags & HOURLY_FLAG) && (employee.age > 65))
-> ```
->
-> 应替换为
->
-> ```java
-> if (employee.isEligibleForFullBenefits())
-> ```
-
-### 标识符和关键字的区别是什么?
-
-在我们编写程序的时候,需要大量地为程序、类、变量、方法等取名字,于是就有了标识符,简单来说,标识符就是一个名字。但是有一些标识符,Java 语言已经赋予了其特殊的含义,只能用于特定的地方,这种特殊的标识符就是关键字。因此,关键字是被赋予特殊含义的标识符。比如,在我们的日常生活中 ,“警察局”这个名字已经被赋予了特殊的含义,所以如果你开一家店,店的名字不能叫“警察局”,“警察局”就是我们日常生活中的关键字。
-
-### Java 中有哪些常见的关键字?
-
-| 分类 | 关键字 | | | | | | |
-| :-------------------- | -------- | ---------- | -------- | ------------ | ---------- | --------- | ------ |
-| 访问控制 | private | protected | public | | | | |
-| 类,方法和变量修饰符 | abstract | class | extends | final | implements | interface | native |
-| | new | static | strictfp | synchronized | transient | volatile | |
-| 程序控制 | break | continue | return | do | while | if | else |
-| | for | instanceof | switch | case | default | | |
-| 错误处理 | try | catch | throw | throws | finally | | |
-| 包相关 | import | package | | | | | |
-| 基本类型 | boolean | byte | char | double | float | int | long |
-| | short | null | true | false | | | |
-| 变量引用 | super | this | void | | | | |
-| 保留字 | goto | const | | | | | |
-
-### 自增自减运算符
-
-在写代码的过程中,常见的一种情况是需要某个整数类型变量增加 1 或减少 1,Java 提供了一种特殊的运算符,用于这种表达式,叫做自增运算符(++)和自减运算符(--)。
-
-++和--运算符可以放在变量之前,也可以放在变量之后,当运算符放在变量之前时(前缀),先自增/减,再赋值;当运算符放在变量之后时(后缀),先赋值,再自增/减。例如,当 `b = ++a` 时,先自增(自己增加 1),再赋值(赋值给 b);当 `b = a++` 时,先赋值(赋值给 b),再自增(自己增加 1)。也就是,++a 输出的是 a+1 的值,a++输出的是 a 值。用一句口诀就是:“符号在前就先加/减,符号在后就后加/减”。
-
-### continue、break、和 return 的区别是什么?
-
-在循环结构中,当循环条件不满足或者循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,当发生了某种条件之后 ,提前终止循环,这就需要用到下面几个关键词:
-
-1. continue :指跳出当前的这一次循环,继续下一次循环。
-2. break :指跳出整个循环体,继续执行循环下面的语句。
-
-return 用于跳出所在方法,结束该方法的运行。return 一般有两种用法:
-
-1. `return;` :直接使用 return 结束方法执行,用于没有返回值函数的方法
-2. `return value;` :return 一个特定值,用于有返回值函数的方法
-
-### Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符?
-
-Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
-
-Java 的泛型是伪泛型,这是因为 Java 在运行期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。
-
-```java
-List list = new ArrayList<>();
-
-list.add(12);
-//这里直接添加会报错
-list.add("a");
-Class extends List> clazz = list.getClass();
-Method add = clazz.getDeclaredMethod("add", Object.class);
-//但是通过反射添加,是可以的
-add.invoke(list, "kl");
-
-System.out.println(list);
-```
-
-泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。
-
-**1.泛型类**:
-
-```java
-//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
-//在实例化泛型类时,必须指定T的具体类型
-public class Generic {
-
- private T key;
-
- public Generic(T key) {
- this.key = key;
- }
-
- public T getKey() {
- return key;
- }
-}
-```
-
-如何实例化泛型类:
-
-```java
-Generic genericInteger = new Generic(123456);
-```
-
-**2.泛型接口** :
-
-```java
-public interface Generator {
- public T method();
-}
-```
-
-实现泛型接口,不指定类型:
-
-```java
-class GeneratorImpl implements Generator{
- @Override
- public T method() {
- return null;
- }
-}
-```
-
-实现泛型接口,指定类型:
-
-```java
-class GeneratorImpl implements Generator{
- @Override
- public String method() {
- return "hello";
- }
-}
-```
-
-**3.泛型方法** :
-
-```java
-public static void printArray(E[] inputArray) {
- for (E element : inputArray) {
- System.out.printf("%s ", element);
- }
- System.out.println();
-}
-```
-
-使用:
-
-```java
-// 创建不同类型数组: Integer, Double 和 Character
-Integer[] intArray = { 1, 2, 3 };
-String[] stringArray = { "Hello", "World" };
-printArray(intArray);
-printArray(stringArray);
-```
-
-**常用的通配符为: T,E,K,V,?**
-
-- ? 表示不确定的 java 类型
-- T (type) 表示具体的一个 java 类型
-- K V (key value) 分别代表 java 键值中的 Key Value
-- E (element) 代表 Element
-
-### ==和 equals 的区别
-
-对于基本数据类型来说,==比较的是值。对于引用数据类型来说,==比较的是对象的内存地址。
-
-> 因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。
-
-**`equals()`** 作用不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。`equals()`方法存在于`Object`类中,而`Object`类是所有类的直接或间接父类。
-
-`Object` 类 `equals()` 方法:
-
-```java
-public boolean equals(Object obj) {
- return (this == obj);
-}
-```
-
-`equals()` 方法存在两种使用情况:
-
-- **类没有覆盖 `equals()`方法** :通过`equals()`比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 `Object`类`equals()`方法。
-- **类覆盖了 `equals()`方法** :一般我们都覆盖 `equals()`方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
-
-**举个例子:**
-
-```java
-public class test1 {
- public static void main(String[] args) {
- String a = new String("ab"); // a 为一个引用
- String b = new String("ab"); // b为另一个引用,对象的内容一样
- String aa = "ab"; // 放在常量池中
- String bb = "ab"; // 从常量池中查找
- if (aa == bb) // true
- System.out.println("aa==bb");
- if (a == b) // false,非同一对象
- System.out.println("a==b");
- if (a.equals(b)) // true
- System.out.println("aEQb");
- if (42 == 42.0) { // true
- System.out.println("true");
- }
- }
-}
-```
-
-**说明:**
-
-- `String` 中的 `equals` 方法是被重写过的,因为 `Object` 的 `equals` 方法是比较的对象的内存地址,而 `String` 的 `equals` 方法比较的是对象的值。
-- 当创建 `String` 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 `String` 对象。
-
-`String`类`equals()`方法:
-
-```java
-public boolean equals(Object anObject) {
- if (this == anObject) {
- return true;
- }
- if (anObject instanceof String) {
- String anotherString = (String)anObject;
- int n = value.length;
- if (n == anotherString.value.length) {
- char v1[] = value;
- char v2[] = anotherString.value;
- int i = 0;
- while (n-- != 0) {
- if (v1[i] != v2[i])
- return false;
- i++;
- }
- return true;
- }
- }
- return false;
-}
-```
-
-### hashCode()与 equals()
-
-面试官可能会问你:“你重写过 `hashcode` 和 `equals`么,为什么重写 `equals` 时必须重写 `hashCode` 方法?”
-
-**1)hashCode()介绍:**
-
-`hashCode()` 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。`hashCode()`定义在 JDK 的 `Object` 类中,这就意味着 Java 中的任何类都包含有 `hashCode()` 函数。另外需要注意的是: `Object` 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
-
-```java
-public native int hashCode();
-```
-
-散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
-
-**2)为什么要有 hashCode?**
-
-我们以“`HashSet` 如何检查重复”为例子来说明为什么要有 hashCode?
-
-当你把对象加入 `HashSet` 时,`HashSet` 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,`HashSet` 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 `equals()` 方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,`HashSet` 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head First Java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
-
-**3)为什么重写 `equals` 时必须重写 `hashCode` 方法?**
-
-如果两个对象相等,则 hashcode 一定也是相同的。两个对象相等,对两个对象分别调用 equals 方法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不一定是相等的 。**因此,equals 方法被覆盖过,则 `hashCode` 方法也必须被覆盖。**
-
-> `hashCode()`的默认行为是对堆上的对象产生独特值。如果没有重写 `hashCode()`,则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
-
-**4)为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?**
-
-在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
-
-因为 `hashCode()` 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 `hashCode` )。
-
-我们刚刚也提到了 `HashSet`,如果 `HashSet` 在对比的时候,同样的 hashcode 有多个对象,它会使用 `equals()` 来判断是否真的相同。也就是说 `hashcode` 只是用来缩小查找成本。
-
-更多关于 `hashcode()` 和 `equals()` 的内容可以查看:[Java hashCode() 和 equals()的若干问题解答](https://www.cnblogs.com/skywang12345/p/3324958.html)
-
-## 基本数据类型
-
-### Java 中的几种基本数据类型是什么?对应的包装类型是什么?各自占用多少字节呢?
-
-Java 中有 8 种基本数据类型,分别为:
-
-1. 6 种数字类型 :`byte`、`short`、`int`、`long`、`float`、`double`
-2. 1 种字符类型:`char`
-3. 1 种布尔型:`boolean`。
-
-这 8 种基本数据类型的默认值以及所占空间的大小如下:
-
-| 基本类型 | 位数 | 字节 | 默认值 |
-| :-------- | :--- | :--- | :------ |
-| `int` | 32 | 4 | 0 |
-| `short` | 16 | 2 | 0 |
-| `long` | 64 | 8 | 0L |
-| `byte` | 8 | 1 | 0 |
-| `char` | 16 | 2 | 'u0000' |
-| `float` | 32 | 4 | 0f |
-| `double` | 64 | 8 | 0d |
-| `boolean` | 1 | | false |
-
-另外,对于 `boolean`,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。
-
-**注意:**
-
-1. Java 里使用 `long` 类型的数据一定要在数值后面加上 **L**,否则将作为整型解析。
-2. `char a = 'h'`char :单引号,`String a = "hello"` :双引号。
-
-这八种基本类型都有对应的包装类分别为:`Byte`、`Short`、`Integer`、`Long`、`Float`、`Double`、`Character`、`Boolean` 。
-
-包装类型不赋值就是 `Null` ,而基本类型有默认值且不是 `Null`。
-
-另外,这个问题建议还可以先从 JVM 层面来分析。
-
-基本数据类型直接存放在 Java 虚拟机栈中的局部变量表中,而包装类型属于对象类型,我们知道对象实例都存在于堆中。相比于对象类型, 基本数据类型占用的空间非常小。
-
-> 《深入理解 Java 虚拟机》 :局部变量表主要存放了编译期可知的基本数据类型 **(boolean、byte、char、short、int、float、long、double)**、**对象引用**(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
-
-### 自动装箱与拆箱
-
-- **装箱**:将基本类型用它们对应的引用类型包装起来;
-- **拆箱**:将包装类型转换为基本数据类型;
-
-举例:
-
-```java
-Integer i = 10; //装箱
-int n = i; //拆箱
-```
-
-上面这两行代码对应的字节码为:
-
-```java
- L1
-
- LINENUMBER 8 L1
-
- ALOAD 0
-
- BIPUSH 10
-
- INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
-
- PUTFIELD AutoBoxTest.i : Ljava/lang/Integer;
-
- L2
-
- LINENUMBER 9 L2
-
- ALOAD 0
-
- ALOAD 0
-
- GETFIELD AutoBoxTest.i : Ljava/lang/Integer;
-
- INVOKEVIRTUAL java/lang/Integer.intValue ()I
-
- PUTFIELD AutoBoxTest.n : I
-
- RETURN
-```
-
-从字节码中,我们发现装箱其实就是调用了 包装类的`valueOf()`方法,拆箱其实就是调用了 `xxxValue()`方法。
-
-因此,
-
-- `Integer i = 10` 等价于 `Integer i = Integer.valueOf(10)`
-- `int n = i` 等价于 `int n = i.intValue()`;
-
-### 8 种基本类型的包装类和常量池
-
-Java 基本类型的包装类的大部分都实现了常量池技术。`Byte`,`Short`,`Integer`,`Long` 这 4 种包装类默认创建了数值 **[-128,127]** 的相应类型的缓存数据,`Character` 创建了数值在[0,127]范围的缓存数据,`Boolean` 直接返回 `True` Or `False`。
-
-**Integer 缓存源码:**
-
-```java
-/**
-
-*此方法将始终缓存-128 到 127(包括端点)范围内的值,并可以缓存此范围之外的其他值。
-
-*/
-
-public static Integer valueOf(int i) {
-
- if (i >= IntegerCache.low && i <= IntegerCache.high)
-
- return IntegerCache.cache[i + (-IntegerCache.low)];
-
- return new Integer(i);
-
-}
-
-private static class IntegerCache {
-
- static final int low = -128;
-
- static final int high;
-
- static final Integer cache[];
-
-}
-```
-
-**`Character` 缓存源码:**
-
-```java
-public static Character valueOf(char c) {
-
- if (c <= 127) { // must cache
-
- return CharacterCache.cache[(int)c];
-
- }
-
- return new Character(c);
-
-}
-
-
-
-private static class CharacterCache {
-
- private CharacterCache(){}
-
-
-
- static final Character cache[] = new Character[127 + 1];
-
- static {
-
- for (int i = 0; i < cache.length; i++)
-
- cache[i] = new Character((char)i);
-
- }
-
-}
-```
-
-**`Boolean` 缓存源码:**
-
-```java
-public static Boolean valueOf(boolean b) {
-
- return (b ? TRUE : FALSE);
-
-}
-```
-
-如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
-
-两种浮点数类型的包装类 `Float`,`Double` 并没有实现常量池技术。
-
-```java
-Integer i1 = 33;
-
-Integer i2 = 33;
-
-System.out.println(i1 == i2);// 输出 true
-
-Float i11 = 333f;
-
-Float i22 = 333f;
-
-System.out.println(i11 == i22);// 输出 false
-
-Double i3 = 1.2;
-
-Double i4 = 1.2;
-
-System.out.println(i3 == i4);// 输出 false
-```
-
-下面我们来看一下问题。下面的代码的输出结果是 `true` 还是 `flase` 呢?
-
-```java
-Integer i1 = 40;
-
-Integer i2 = new Integer(40);
-
-System.out.println(i1==i2);
-```
-
-`Integer i1=40` 这一行代码会发生装箱,也就是说这行代码等价于 `Integer i1=Integer.valueOf(40)` 。因此,`i1` 直接使用的是常量池中的对象。而`Integer i1 = new Integer(40)` 会直接创建新的对象。
-
-因此,答案是 `false` 。你答对了吗?
-
-记住:**所有整型包装类对象之间值的比较,全部使用 equals 方法比较**。
-
-
-
-## 方法(函数)
-
-### 什么是方法的返回值?
-
-方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用是接收出结果,使得它可以用于其他的操作!
-
-### 方法有哪几种类型?
-
-**1.无参数无返回值的方法**
-
-```java
-// 无参数无返回值的方法(如果方法没有返回值,不能不写,必须写void,表示没有返回值)
-public void f1() {
- System.out.println("无参数无返回值的方法");
-}
-```
-
-**2.有参数无返回值的方法**
-
-```java
-/**
-* 有参数无返回值的方法
-* 参数列表由零组到多组“参数类型+形参名”组合而成,多组参数之间以英文逗号(,)隔开,形参类型和形参名之间以英文空格隔开
-*/
-public void f2(int a, String b, int c) {
- System.out.println(a + "-->" + b + "-->" + c);
-}
-```
-
-**3.有返回值无参数的方法**
-
-```java
-// 有返回值无参数的方法(返回值可以是任意的类型,在函数里面必须有return关键字返回对应的类型)
-public int f3() {
- System.out.println("有返回值无参数的方法");
- return 2;
-}
-```
-
-**4.有返回值有参数的方法**
-
-```java
-// 有返回值有参数的方法
-public int f4(int a, int b) {
- return a * b;
-}
-```
-
-**5.return 在无返回值方法的特殊使用**
-
-```java
-// return在无返回值方法的特殊使用
-public void f5(int a) {
- if (a > 10) {
- return;//表示结束所在方法 (f5方法)的执行,下方的输出语句不会执行
- }
- System.out.println(a);
-}
-```
-
-### 在一个静态方法内调用一个非静态成员为什么是非法的?
-
-这个需要结合 JVM 的相关知识,静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,然后通过类的实例对象去访问。在类的非静态成员不存在的时候静态成员就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。
-
-### 静态方法和实例方法有何不同?
-
-**1、调用方式**
-
-在外部调用静态方法时,可以使用 `类名.方法名` 的方式,也可以使用 `对象.方法名` 的方式,而实例方法只有后面这种方式。也就是说,**调用静态方法可以无需创建对象** 。
-
-不过,需要注意的是一般不建议使用 `对象.方法名` 的方式来调用静态方法。这种方式非常容易造成混淆,静态方法不属于类的某个对象而是属于这个类。
-
-因此,一般建议使用 `类名.方法名` 的方式来调用静态方法。
-
-```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 中只有值传递?
-
-首先,我们回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。
-
-**按值调用(call by value)** 表示方法接收的是调用者提供的值,**按引用调用(call by reference)** 表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。它用来描述各种程序设计语言(不只是 Java)中方法参数传递方式。
-
-**Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。**
-
-**下面通过 3 个例子来给大家说明**
-
-> **example 1**
-
-```java
-public static void main(String[] args) {
- int num1 = 10;
- int num2 = 20;
-
- swap(num1, num2);
-
- System.out.println("num1 = " + num1);
- System.out.println("num2 = " + num2);
-}
-
-public static void swap(int a, int b) {
- int temp = a;
- a = b;
- b = temp;
-
- System.out.println("a = " + a);
- System.out.println("b = " + b);
-}
-```
-
-**结果:**
-
-```
-a = 20
-b = 10
-num1 = 10
-num2 = 20
-```
-
-**解析:**
-
-
-
-在 swap 方法中,a、b 的值进行交换,并不会影响到 num1、num2。因为,a、b 中的值,只是从 num1、num2 的复制过来的。也就是说,a、b 相当于 num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
-
-**通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.**
-
-> **example 2**
-
-```java
- public static void main(String[] args) {
- int[] arr = { 1, 2, 3, 4, 5 };
- System.out.println(arr[0]);
- change(arr);
- System.out.println(arr[0]);
- }
-
- public static void change(int[] array) {
- // 将数组的第一个元素变为0
- array[0] = 0;
- }
-```
-
-**结果:**
-
-```
-1
-0
-```
-
-**解析:**
-
-
-
-array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的是同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
-
-**通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。**
-
-**很多程序设计语言(特别是,C++和 Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为 Java 程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。**
-
-> **example 3**
-
-```java
-public class Test {
-
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- Student s1 = new Student("小张");
- Student s2 = new Student("小李");
- Test.swap(s1, s2);
- System.out.println("s1:" + s1.getName());
- System.out.println("s2:" + s2.getName());
- }
-
- public static void swap(Student x, Student y) {
- Student temp = x;
- x = y;
- y = temp;
- System.out.println("x:" + x.getName());
- System.out.println("y:" + y.getName());
- }
-}
-```
-
-**结果:**
-
-```
-x:小李
-y:小张
-s1:小张
-s2:小李
-```
-
-**解析:**
-
-交换之前:
-
-
-
-交换之后:
-
-
-
-通过上面两张图可以很清晰的看出: **方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝**
-
-> **总结**
-
-Java 程序设计语言对对象采用的不是引用调用,实际上,对象引用是按
-值传递的。
-
-下面再总结一下 Java 中方法参数的使用情况:
-
-- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
-- 一个方法可以改变一个对象参数的状态。
-- 一个方法不能让对象参数引用一个新的对象。
-
-**参考:**
-
-《Java 核心技术卷 Ⅰ》基础知识第十版第四章 4.5 小节
-
-### 重载和重写的区别
-
-> 重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理
->
-> 重写就是当子类继承自父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法
-
-#### 重载
-
-发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
-
-下面是《Java 核心技术》对重载这个概念的介绍:
-
-
-
-综上:重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
-
-#### 重写
-
-重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。
-
-1. 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
-2. 如果父类方法访问修饰符为 `private/final/static` 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。
-3. 构造方法无法被重写
-
-综上:重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变
-
-暖心的 Guide 哥最后再来个图表总结一下!
-
-| 区别点 | 重载方法 | 重写方法 |
-| :--------- | :------- | :----------------------------------------------------------- |
-| 发生范围 | 同一个类 | 子类 |
-| 参数列表 | 必须修改 | 一定不能修改 |
-| 返回类型 | 可修改 | 子类方法返回值类型应比父类方法返回值类型更小或相等 |
-| 异常 | 可修改 | 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等; |
-| 访问修饰符 | 可修改 | 一定不能做更严格的限制(可以降低限制) |
-| 发生阶段 | 编译期 | 运行期 |
-
-**方法的重写要遵循“两同两小一大”**(以下内容摘录自《疯狂 Java 讲义》,[issue#892](https://github.com/Snailclimb/JavaGuide/issues/892) ):
-
-- “两同”即方法名相同、形参列表相同;
-- “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
-- “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
-
-⭐️ 关于 **重写的返回值类型** 这里需要额外多说明一下,上面的表述不太清晰准确:如果方法的返回类型是 void 和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。
-
-```java
-public class Hero {
- public String name() {
- return "超级英雄";
- }
-}
-public class SuperMan extends Hero{
- @Override
- public String name() {
- return "超人";
- }
- public Hero hero() {
- return new Hero();
- }
-}
-
-public class SuperSuperMan extends SuperMan {
- public String name() {
- return "超级超级英雄";
- }
-
- @Override
- public SuperMan hero() {
- return new SuperMan();
- }
-}
-```
-
-### 深拷贝 vs 浅拷贝
-
-1. **浅拷贝**:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
-2. **深拷贝**:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
-
-
-
-## Java 面向对象
-
-### 面向对象和面向过程的区别
-
-- **面向过程** :**面向过程性能比面向对象高。** 因为类调用时需要实例化,开销比较大,比较消耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发。但是,**面向过程没有面向对象易维护、易复用、易扩展。**
-- **面向对象** :**面向对象易维护、易复用、易扩展。** 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,**面向对象性能比面向过程低**。
-
-参见 issue : [面向过程 :面向过程性能比面向对象高??](https://github.com/Snailclimb/JavaGuide/issues/431)
-
-> 这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java 性能差的主要原因并不是因为它是面向对象语言,而是 Java 是半编译语言,最终的执行代码并不是可以直接被 CPU 执行的二进制机械码。
->
-> 而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比 Java 好。
-
-### 成员变量与局部变量的区别有哪些?
-
-1. 从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 `public`,`private`,`static` 等修饰符所修饰,而局部变量不能被访问控制修饰符及 `static` 所修饰;但是,成员变量和局部变量都能被 `final` 所修饰。
-2. 从变量在内存中的存储方式来看,如果成员变量是使用 `static` 修饰的,那么这个成员变量是属于类的,如果没有使用 `static` 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。
-3. 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
-4. 从变量是否有默认值来看,成员变量如果没有被赋初,则会自动以类型的默认值而赋值(一种情况例外:被 `final` 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
-
-### 创建一个对象用什么运算符?对象实体与对象引用有何不同?
-
-new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。
-
-一个对象引用可以指向 0 个或 1 个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有 n 个引用指向它(可以用 n 条绳子系住一个气球)。
-
-### 对象的相等与指向他们的引用相等,两者有什么不同?
-
-对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。
-
-### 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?
-
-构造方法主要作用是完成对类对象的初始化工作。
-
-如果一个类没有声明构造方法,也可以执行!因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法(无论是否有参),Java 就不会再添加默认的无参数的构造方法了,这时候,就不能直接 new 一个对象而不传递参数了,所以我们一直在不知不觉地使用构造方法,这也是为什么我们在创建对象的时候后面要加一个括号(因为要调用无参的构造方法)。如果我们重载了有参的构造方法,记得都要把无参的构造方法也写出来(无论是否用到),因为这可以帮助我们在创建对象的时候少踩坑。
-
-### 构造方法有哪些特点?是否可被 override?
-
-特点:
-
-1. 名字与类名相同。
-2. 没有返回值,但不能用 void 声明构造函数。
-3. 生成类的对象时自动执行,无需调用。
-
-构造方法不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。
-
-### 面向对象三大特征
-
-#### 封装
-
-封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好像我们看不到挂在墙上的空调的内部的零件信息(也就是属性),但是可以通过遥控器(方法)来控制空调。如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。就好像如果没有空调遥控器,那么我们就无法操控空凋制冷,空调本身就没有意义了(当然现在还有很多其他方法 ,这里只是为了举例子)。
-
-```java
-public class Student {
- private int id;//id属性私有化
- private String name;//name属性私有化
-
- //获取id的方法
- public int getId() {
- return id;
- }
-
- //设置id的方法
- public void setId(int id) {
- this.id = id;
- }
-
- //获取name的方法
- public String getName() {
- return name;
- }
-
- //设置name的方法
- public void setName(String name) {
- this.name = name;
- }
-}
-```
-
-#### 继承
-
-不同类型的对象,相互之间经常有一定数量的共同点。例如,小明同学、小红同学、小李同学,都共享学生的特性(班级、学号等)。同时,每一个对象还定义了额外的特性使得他们与众不同。例如小明的数学比较好,小红的性格惹人喜爱;小李的力气比较大。继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率。
-
-**关于继承如下 3 点请记住:**
-
-1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,**只是拥有**。
-2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
-3. 子类可以用自己的方式实现父类的方法。(以后介绍)。
-
-#### 多态
-
-多态,顾名思义,表示一个对象具有多种的状态。具体表现为父类的引用指向子类的实例。
-
-**多态的特点:**
-
-- 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
-- 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
-- 多态不能调用“只在子类存在但在父类不存在”的方法;
-- 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。
-
-### String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
-
-**可变性**
-
-简单的来说:`String` 类中使用 final 关键字修饰字符数组来保存字符串,`private final char value[]`,所以`String` 对象是不可变的。
-
-> 补充(来自[issue 675](https://github.com/Snailclimb/JavaGuide/issues/675)):在 Java 9 之后,String 、`StringBuilder` 与 `StringBuffer` 的实现改用 byte 数组存储字符串 `private final byte[] value`
-
-而 `StringBuilder` 与 `StringBuffer` 都继承自 `AbstractStringBuilder` 类,在 `AbstractStringBuilder` 中也是使用字符数组保存字符串`char[]value` 但是没有用 `final` 关键字修饰,所以这两种对象都是可变的。
-
-`StringBuilder` 与 `StringBuffer` 的构造方法都是调用父类构造方法也就是`AbstractStringBuilder` 实现的,大家可以自行查阅源码。
-
-`AbstractStringBuilder.java`
-
-```java
-abstract class AbstractStringBuilder implements Appendable, CharSequence {
- /**
- * The value is used for character storage.
- */
- char[] value;
-
- /**
- * The count is the number of characters used.
- */
- int count;
-
- AbstractStringBuilder(int capacity) {
- value = new char[capacity];
- }}
-```
-
-**线程安全性**
-
-`String` 中的对象是不可变的,也就可以理解为常量,线程安全。`AbstractStringBuilder` 是 `StringBuilder` 与 `StringBuffer` 的公共父类,定义了一些字符串的基本操作,如 `expandCapacity`、`append`、`insert`、`indexOf` 等公共方法。`StringBuffer` 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。`StringBuilder` 并没有对方法进行加同步锁,所以是非线程安全的。
-
-**性能**
-
-每次对 `String` 类型进行改变的时候,都会生成一个新的 `String` 对象,然后将指针指向新的 `String` 对象。`StringBuffer` 每次都会对 `StringBuffer` 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 `StringBuilder` 相比使用 `StringBuffer` 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
-
-**对于三者使用的总结:**
-
-1. 操作少量的数据: 适用 `String`
-2. 单线程操作字符串缓冲区下操作大量数据: 适用 `StringBuilder`
-3. 多线程操作字符串缓冲区下操作大量数据: 适用 `StringBuffer`
-
-### Object 类的常见方法总结
-
-Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:
-
-```java
-public final native Class> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。
-
-public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。
-public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。
-
-protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。
-
-public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。
-
-public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
-
-public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
-
-public final native void wait(long timeout) throws InterruptedException//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。
-
-public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。
-
-public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
-
-protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作
-```
-
-
-## 反射
-
-### 何为反射?
-
-如果说大家研究过框架的底层原理或者咱们自己写过框架的话,一定对反射这个概念不陌生。
-
-反射之所以被称为框架的灵魂,主要是因为它赋予了我们在运行时分析类以及执行类中方法的能力。
-
-通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
-
-### 反射机制优缺点
-
-- **优点** : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
-- **缺点** :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。[Java Reflection: Why is it so slow?](https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow)
-
-### 反射的应用场景
-
-像咱们平时大部分时候都是在写业务代码,很少会接触到直接使用反射机制的场景。
-
-但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
-
-**这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。**
-
-比如下面是通过 JDK 实现动态代理的示例代码,其中就使用了反射类 `Method` 来调用指定的方法。
-
-```java
-public class DebugInvocationHandler implements InvocationHandler {
- /**
- * 代理类中的真实对象
- */
- private final Object target;
-
- public DebugInvocationHandler(Object target) {
- this.target = target;
- }
-
-
- public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
- System.out.println("before method " + method.getName());
- Object result = method.invoke(target, args);
- System.out.println("after method " + method.getName());
- return result;
- }
-}
-
-```
-
-另外,像 Java 中的一大利器 **注解** 的实现也用到了反射。
-
-为什么你使用 Spring 的时候 ,一个`@Component`注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 `@Value`注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
-
-这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
-
-## 异常
-
-### Java 异常类层次结构图
-
-
-
-图片来自:https://simplesnippets.tech/exception-handling-in-java-part-1/
-
-
-
-图片来自:https://chercher.tech/java-programming/exceptions-java
-
-在 Java 中,所有的异常都有一个共同的祖先 `java.lang` 包中的 `Throwable` 类。`Throwable` 类有两个重要的子类 `Exception`(异常)和 `Error`(错误)。`Exception` 能被程序本身处理(`try-catch`), `Error` 是无法处理的(只能尽量避免)。
-
-`Exception` 和 `Error` 二者都是 Java 异常处理的重要子类,各自都包含大量子类。
-
-- **`Exception`** :程序本身可以处理的异常,可以通过 `catch` 来进行捕获。`Exception` 又可以分为 受检查异常(必须处理) 和 不受检查异常(可以不处理)。
-- **`Error`** :`Error` 属于程序无法处理的错误 ,我们没办法通过 `catch` 来进行捕获 。例如,Java 虚拟机运行错误(`Virtual MachineError`)、虚拟机内存不够错误(`OutOfMemoryError`)、类定义错误(`NoClassDefFoundError`)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
-
-**受检查异常**
-
-Java 代码在编译过程中,如果受检查异常没有被 `catch`/`throw` 处理的话,就没办法通过编译 。比如下面这段 IO 操作的代码。
-
-
-
-除了`RuntimeException`及其子类以外,其他的`Exception`类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、`ClassNotFoundException` 、`SQLException`...。
-
-**不受检查异常**
-
-Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
-
-`RuntimeException` 及其子类都统称为非受检查异常,例如:`NullPointerException`、`NumberFormatException`(字符串转换为数字)、`ArrayIndexOutOfBoundsException`(数组越界)、`ClassCastException`(类型转换错误)、`ArithmeticException`(算术错误)等。
-
-### Throwable 类常用方法
-
-- **`public String getMessage()`**:返回异常发生时的简要描述
-- **`public String toString()`**:返回异常发生时的详细信息
-- **`public String getLocalizedMessage()`**:返回异常对象的本地化信息。使用 `Throwable` 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 `getMessage()`返回的结果相同
-- **`public void printStackTrace()`**:在控制台上打印 `Throwable` 对象封装的异常信息
-
-### try-catch-finally
-
-- **`try`块:** 用于捕获异常。其后可接零个或多个 `catch` 块,如果没有 `catch` 块,则必须跟一个 `finally` 块。
-- **`catch`块:** 用于处理 try 捕获到的异常。
-- **`finally` 块:** 无论是否捕获或处理异常,`finally` 块里的语句都会被执行。当在 `try` 块或 `catch` 块中遇到 `return` 语句时,`finally` 语句块将在方法返回之前被执行。
-
-**在以下 3 种特殊情况下,`finally` 块不会被执行:**
-
-1. 在 `try` 或 `finally`块中用了 `System.exit(int)`退出程序。但是,如果 `System.exit(int)` 在异常语句之后,`finally` 还是会被执行
-2. 程序所在的线程死亡。
-3. 关闭 CPU。
-
-下面这部分内容来自 issue:。
-
-**注意:** 当 try 语句和 finally 语句中都有 return 语句时,在方法返回之前,finally 语句的内容将被执行,并且 finally 语句的返回值将会覆盖原始的返回值。如下:
-
-```java
-public class Test {
- public static int f(int value) {
- try {
- return value * value;
- } finally {
- if (value == 2) {
- return 0;
- }
- }
- }
-}
-```
-
-如果调用 `f(2)`,返回值将是 0,因为 finally 语句的返回值覆盖了 try 语句块的返回值。
-
-### 使用 `try-with-resources` 来代替`try-catch-finally`
-
-1. **适用范围(资源的定义):** 任何实现 `java.lang.AutoCloseable`或者 `java.io.Closeable` 的对象
-2. **关闭资源和 finally 块的执行顺序:** 在 `try-with-resources` 语句中,任何 catch 或 finally 块在声明的资源关闭后运行
-
-《Effecitve Java》中明确指出:
-
-> 面对必须要关闭的资源,我们总是应该优先使用 `try-with-resources` 而不是`try-finally`。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。`try-with-resources`语句让我们更容易编写必须要关闭的资源的代码,若采用`try-finally`则几乎做不到这点。
-
-Java 中类似于`InputStream`、`OutputStream` 、`Scanner` 、`PrintWriter`等的资源都需要我们调用`close()`方法来手动关闭,一般情况下我们都是通过`try-catch-finally`语句来实现这个需求,如下:
-
-```java
- //读取文本文件的内容
- Scanner scanner = null;
- try {
- scanner = new Scanner(new File("D://read.txt"));
- while (scanner.hasNext()) {
- System.out.println(scanner.nextLine());
- }
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } finally {
- if (scanner != null) {
- scanner.close();
- }
- }
-```
-
-使用 Java 7 之后的 `try-with-resources` 语句改造上面的代码:
-
-```java
-try (Scanner scanner = new Scanner(new File("test.txt"))) {
- while (scanner.hasNext()) {
- System.out.println(scanner.nextLine());
- }
-} catch (FileNotFoundException fnfe) {
- fnfe.printStackTrace();
-}
-```
-
-当然多个资源需要关闭的时候,使用 `try-with-resources` 实现起来也非常简单,如果你还是用`try-catch-finally`可能会带来很多问题。
-
-通过使用分号分隔,可以在`try-with-resources`块中声明多个资源。
-
-```java
-try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
- BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
- int b;
- while ((b = bin.read()) != -1) {
- bout.write(b);
- }
- }
- catch (IOException e) {
- e.printStackTrace();
- }
-```
-
-## I/O 流
-
-### 什么是序列化?什么是反序列化?
-
-如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
-
-简单来说:
-
-- **序列化**: 将数据结构或对象转换成二进制字节流的过程
-- **反序列化**:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
-
-对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。
-
-维基百科是如是介绍序列化的:
-
-> **序列化**(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。
-
-综上:**序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。**
-
-
-
-https://www.corejavaguru.com/java/serialization/interview-questions-1
-
-### Java 序列化中如果有些字段不想进行序列化,怎么办?
-
-对于不想进行序列化的变量,使用 `transient` 关键字修饰。
-
-`transient` 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 `transient` 修饰的变量值不会被持久化和恢复。
-
-关于 `transient` 还有几点注意:
-- `transient` 只能修饰变量,不能修饰类和方法。
-- `transient` 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 `int` 类型,那么反序列后结果就是 `0`。
-- `static` 变量因为不属于任何对象(Object),所以无论有没有 `transient` 关键字修饰,均不会被序列化。
-
-### 获取用键盘输入常用的两种方法
-
-方法 1:通过 `Scanner`
-
-```java
-Scanner input = new Scanner(System.in);
-String s = input.nextLine();
-input.close();
-```
-
-方法 2:通过 `BufferedReader`
-
-```java
-BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
-String s = input.readLine();
-```
-
-### Java 中 IO 流分为几种?
-
-- 按照流的流向分,可以分为输入流和输出流;
-- 按照操作单元划分,可以划分为字节流和字符流;
-- 按照流的角色划分为节点流和处理流。
-
-Java IO 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
-
-- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
-- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
-
-按操作方式分类结构图:
-
-
-
-按操作对象分类结构图:
-
-
-
-### 既然有了字节流,为什么还要有字符流?
-
-问题本质想问:**不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?**
-
-回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
-
-## 参考
-
-- https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre
-- https://www.educba.com/oracle-vs-openjdk/
-- https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk 基础概念与常识
-
diff --git "a/docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md" b/docs/java/basis/proxy.md
similarity index 90%
rename from "docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md"
rename to docs/java/basis/proxy.md
index 0006caed0aaff5853e7944fa7e18776af3cbace7..7ca450cfcdf8647ee1fb307743a4eda330dfde17 100644
--- "a/docs/java/basis/\344\273\243\347\220\206\346\250\241\345\274\217\350\257\246\350\247\243.md"
+++ b/docs/java/basis/proxy.md
@@ -1,5 +1,5 @@
---
-title: 代理详解!静态代理+JDK/CGLIB 动态代理实战
+title: Java 代理模式详解
category: Java
tag:
- Java基础
@@ -11,9 +11,9 @@ tag:
**代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。**
-举个例子:你找了小红来帮你问话,小红就可以看作是代理你的代理对象,代理的行为(方法)是问话。
+举个例子:新娘找来了自己的姨妈来代替自己处理新郎的提问,新娘收到的提问都是经过姨妈处理过滤之后的。姨妈在这里就可以看作是代理你的代理对象,代理的行为(方法)是接收和回复新郎的提问。
-
+
https://medium.com/@mithunsasidharan/understanding-the-proxy-design-pattern-5e63fe38052a
@@ -21,7 +21,7 @@ tag:
## 2. 静态代理
-**静态代理中,我们对目标对象的每个方法的增强都是手动完成的(_后面会具体演示代码_),非常不灵活(_比如接口一旦新增加方法,目标对象和代理对象都要进行修改_)且麻烦(_需要对每个目标类都单独写一个代理类_)。** 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
+**静态代理中,我们对目标对象的每个方法的增强都是手动完成的(_后面会具体演示代码_),非常不灵活(_比如接口一旦新增加方法,目标对象和代理对象都要进行修改_)且麻烦(_需要对每个目标类都单独写一个代理类_)。** 实际应用场景非常非常少,日常开发几乎看不到使用静态代理的场景。
上面我们是从实现和应用角度来说的静态代理,从 JVM 层面来说, **静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。**
@@ -237,7 +237,7 @@ public class JdkProxyFactory {
}
```
-`getProxy()` :主要通过`Proxy.newProxyInstance()`方法获取某个类的代理对象
+`getProxy()`:主要通过`Proxy.newProxyInstance()`方法获取某个类的代理对象
**5.实际使用**
@@ -272,16 +272,15 @@ after method send
public interface MethodInterceptor
extends Callback{
// 拦截被代理类中的方法
- public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
- MethodProxy proxy) throws Throwable;
+ public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
}
```
-1. **obj** :被代理的对象(需要增强的对象)
-2. **method** :被拦截的方法(需要增强的方法)
-3. **args** :方法入参
-4. **proxy** :用于调用原始方法
+1. **obj** : 被代理的对象(需要增强的对象)
+2. **method** : 被拦截的方法(需要增强的方法)
+3. **args** : 方法入参
+4. **proxy** : 用于调用原始方法
你可以通过 `Enhancer`类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 `MethodInterceptor` 中的 `intercept` 方法。
@@ -331,7 +330,7 @@ public class DebugMethodInterceptor implements MethodInterceptor {
/**
- * @param o 代理对象(增强的对象)
+ * @param o 被代理的对象(需要增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
@@ -393,12 +392,11 @@ after method send
## 4. 静态代理和动态代理的对比
-1. **灵活性** :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
-2. **JVM 层面** :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
+1. **灵活性**:动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
+2. **JVM 层面**:静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
## 5. 总结
这篇文章中主要介绍了代理模式的两种实现:静态代理以及动态代理。涵盖了静态代理和动态代理实战、静态代理和动态代理的区别、JDK 动态代理和 Cglib 动态代理区别等内容。
文中涉及到的所有源码,你可以在这里找到:[https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxy](https://github.com/Snailclimb/guide-rpc-framework-learning/tree/master/src/main/java/github/javaguide/proxy) 。
-
diff --git "a/docs/java/basis/\345\217\215\345\260\204\346\234\272\345\210\266\350\257\246\350\247\243.md" b/docs/java/basis/reflection.md
similarity index 72%
rename from "docs/java/basis/\345\217\215\345\260\204\346\234\272\345\210\266\350\257\246\350\247\243.md"
rename to docs/java/basis/reflection.md
index cac029638e4a8091f249d3ab6174a5ea1daf9ea1..61161c59e6b86bf83bbbbe4da089b24ad7f2e708 100644
--- "a/docs/java/basis/\345\217\215\345\260\204\346\234\272\345\210\266\350\257\246\350\247\243.md"
+++ b/docs/java/basis/reflection.md
@@ -1,5 +1,5 @@
---
-title: 反射机制详解!
+title: Java 反射机制详解
category: Java
tag:
- Java基础
@@ -53,9 +53,9 @@ public class DebugInvocationHandler implements InvocationHandler {
## 谈谈反射机制的优缺点
-**优点** : 可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
+**优点**:可以让咱们的代码更加灵活、为各种框架提供开箱即用的功能提供了便利
-**缺点** :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。相关阅读:[Java Reflection: Why is it so slow?](https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow)
+**缺点**:让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。相关阅读:[Java Reflection: Why is it so slow?](https://stackoverflow.com/questions/1392351/java-reflection-why-is-it-so-slow)
## 反射实战
@@ -63,7 +63,7 @@ public class DebugInvocationHandler implements InvocationHandler {
如果我们动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。Java 提供了四种方式获取 Class 对象:
-**1.知道具体类的情况下可以使用:**
+**1. 知道具体类的情况下可以使用:**
```java
Class alunbarClass = TargetObject.class;
@@ -71,30 +71,30 @@ Class alunbarClass = TargetObject.class;
但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象,通过此方式获取 Class 对象不会进行初始化
-**2.通过 `Class.forName()`传入类的路径获取:**
+**2. 通过 `Class.forName()`传入类的全路径获取:**
```java
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
```
-**3.通过对象实例`instance.getClass()`获取:**
+**3. 通过对象实例`instance.getClass()`获取:**
```java
TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();
```
-**4.通过类加载器`xxxClassLoader.loadClass()`传入类路径获取:**
+**4. 通过类加载器`xxxClassLoader.loadClass()`传入类路径获取:**
```java
-Class clazz = ClassLoader.loadClass("cn.javaguide.TargetObject");
+ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");
```
-通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行
+通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一系列步骤,静态代码块和静态对象不会得到执行
### 反射的一些基本操作
-1.创建一个我们要使用反射操作的类 `TargetObject`。
+1. 创建一个我们要使用反射操作的类 `TargetObject`。
```java
package cn.javaguide;
@@ -116,7 +116,7 @@ public class TargetObject {
}
```
-2.使用反射操作这个类的方法以及参数
+2. 使用反射操作这个类的方法以及参数
```java
package cn.javaguide;
@@ -128,35 +128,38 @@ import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
/**
- * 获取TargetObject类的Class对象并且创建TargetObject类实例
+ * 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例
*/
- Class> tagetClass = Class.forName("cn.javaguide.TargetObject");
- TargetObject targetObject = (TargetObject) tagetClass.newInstance();
+ Class> targetClass = Class.forName("cn.javaguide.TargetObject");
+ TargetObject targetObject = (TargetObject) targetClass.newInstance();
/**
- * 获取所有类中所有定义的方法
+ * 获取 TargetObject 类中定义的所有方法
*/
- Method[] methods = tagetClass.getDeclaredMethods();
+ Method[] methods = targetClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
+
/**
* 获取指定方法并调用
*/
- Method publicMethod = tagetClass.getDeclaredMethod("publicMethod",
+ Method publicMethod = targetClass.getDeclaredMethod("publicMethod",
String.class);
publicMethod.invoke(targetObject, "JavaGuide");
+
/**
* 获取指定参数并对参数进行修改
*/
- Field field = tagetClass.getDeclaredField("value");
+ Field field = targetClass.getDeclaredField("value");
//为了对类中的参数进行修改我们取消安全检查
field.setAccessible(true);
field.set(targetObject, "JavaGuide");
+
/**
* 调用 private 方法
*/
- Method privateMethod = tagetClass.getDeclaredMethod("privateMethod");
+ Method privateMethod = targetClass.getDeclaredMethod("privateMethod");
//为了调用private方法我们取消安全检查
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
@@ -177,6 +180,5 @@ value is JavaGuide
**注意** : 有读者提到上面代码运行会抛出 `ClassNotFoundException` 异常,具体原因是你没有下面把这段代码的包名替换成自己创建的 `TargetObject` 所在的包 。
```java
-Class> tagetClass = Class.forName("cn.javaguide.TargetObject");
+Class> targetClass = Class.forName("cn.javaguide.TargetObject");
```
-
diff --git a/docs/java/basis/serialization.md b/docs/java/basis/serialization.md
new file mode 100644
index 0000000000000000000000000000000000000000..f8a8a491ffcf5e98333b61d5761e38995ed752fd
--- /dev/null
+++ b/docs/java/basis/serialization.md
@@ -0,0 +1,221 @@
+---
+title: Java 序列化详解
+category: Java
+tag:
+ - Java基础
+---
+
+## 什么是序列化和反序列化?
+
+如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
+
+简单来说:
+
+- **序列化**:将数据结构或对象转换成二进制字节流的过程
+- **反序列化**:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
+
+对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。
+
+下面是序列化和反序列化常见应用场景:
+
+- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
+- 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
+- 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
+- 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。
+
+维基百科是如是介绍序列化的:
+
+> **序列化**(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中,能恢复原先状态的过程。依照序列化格式重新获取字节的结果时,可以利用它来产生与原始对象相同语义的副本。对于许多对象,像是使用大量引用的复杂对象,这种序列化重建的过程并不容易。面向对象中的对象序列化,并不概括之前原始对象所关系的函数。这种过程也称为对象编组(marshalling)。从一系列字节提取数据结构的反向操作,是反序列化(也称为解编组、deserialization、unmarshalling)。
+
+综上:**序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。**
+
+
+
+https://www.corejavaguru.com/java/serialization/interview-questions-1
+
+**序列化协议对应于 TCP/IP 4 层模型的哪一层?**
+
+我们知道网络通信的双方必须要采用和遵守相同的协议。TCP/IP 四层模型是下面这样的,序列化协议属于哪一层呢?
+
+1. 应用层
+2. 传输层
+3. 网络层
+4. 网络接口层
+
+
+
+如上图所示,OSI 七层协议模型中,表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。反过来的话,就是将二进制流转换成应用层的用户数据。这不就对应的是序列化和反序列化么?
+
+因为,OSI 七层协议模型中的应用层、表示层和会话层对应的都是 TCP/IP 四层模型中的应用层,所以序列化协议属于 TCP/IP 协议应用层的一部分。
+
+## 常见序列化协议有哪些?
+
+JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。
+
+像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择。
+
+### JDK 自带的序列化方式
+
+JDK 自带的序列化,只需实现 `java.io.Serializable`接口即可。
+
+```java
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Builder
+@ToString
+public class RpcRequest implements Serializable {
+ private static final long serialVersionUID = 1905122041950251207L;
+ private String requestId;
+ private String interfaceName;
+ private String methodName;
+ private Object[] parameters;
+ private Class>[] paramTypes;
+ private RpcMessageTypeEnum rpcMessageTypeEnum;
+}
+```
+
+**serialVersionUID 有什么作用?**
+
+序列化号 `serialVersionUID` 属于版本控制的作用。反序列化时,会检查 `serialVersionUID` 是否和当前类的 `serialVersionUID` 一致。如果 `serialVersionUID` 不一致则会抛出 `InvalidClassException` 异常。强烈推荐每个序列化类都手动指定其 `serialVersionUID`,如果不手动指定,那么编译器会动态生成默认的 `serialVersionUID`。
+
+**serialVersionUID 不是被 static 变量修饰了吗?为什么还会被“序列化”?**
+
+`static` 修饰的变量是静态变量,位于方法区,本身是不会被序列化的。 `static` 变量是属于类的而不是对象。你反序列之后,`static` 变量的值就像是默认赋予给了对象一样,看着就像是 `static` 变量被序列化,实际只是假象罢了。
+
+官方说明如下:
+
+> A serializable class can declare its own serialVersionUID explicitly by declaring a field named `"serialVersionUID"` that must be `static`, `final`, and of type `long`;
+>
+> 如果想显式指定 `serialVersionUID` ,则需要在类中使用 `static` 和 `final` 关键字来修饰一个 `long` 类型的变量,变量名字必须为 `"serialVersionUID"` 。
+
+也就是说,`serialVersionUID` 只是用来被 JVM 识别,实际并没有被序列化。
+
+**如果有些字段不想进行序列化怎么办?**
+
+对于不想进行序列化的变量,可以使用 `transient` 关键字修饰。
+
+`transient` 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 `transient` 修饰的变量值不会被持久化和恢复。
+
+关于 `transient` 还有几点注意:
+
+- `transient` 只能修饰变量,不能修饰类和方法。
+- `transient` 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 `int` 类型,那么反序列后结果就是 `0`。
+- `static` 变量因为不属于任何对象(Object),所以无论有没有 `transient` 关键字修饰,均不会被序列化。
+
+**为什么不推荐使用 JDK 自带的序列化?**
+
+我们很少或者说几乎不会直接使用 JDK 自带的序列化方式,主要原因有下面这些原因:
+
+- **不支持跨语言调用** : 如果调用的是其他语言开发的服务的时候就不支持了。
+- **性能差**:相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
+- **存在安全问题**:序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。相关阅读:[应用安全:JAVA 反序列化漏洞之殇 - Cryin](https://cryin.github.io/blog/secure-development-java-deserialization-vulnerability/)、[Java 反序列化安全漏洞怎么回事? - Monica](https://www.zhihu.com/question/37562657/answer/1916596031)。
+
+### Kryo
+
+Kryo 是一个高性能的序列化/反序列化工具,由于其变长存储特性并使用了字节码生成机制,拥有较高的运行速度和较小的字节码体积。
+
+另外,Kryo 已经是一种非常成熟的序列化实现了,已经在 Twitter、Groupon、Yahoo 以及多个著名开源项目(如 Hive、Storm)中广泛的使用。
+
+[guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 就是使用的 kryo 进行序列化,序列化和反序列化相关的代码如下:
+
+```java
+/**
+ * Kryo serialization class, Kryo serialization efficiency is very high, but only compatible with Java language
+ *
+ * @author shuang.kou
+ * @createTime 2020年05月13日 19:29:00
+ */
+@Slf4j
+public class KryoSerializer implements Serializer {
+
+ /**
+ * Because Kryo is not thread safe. So, use ThreadLocal to store Kryo objects
+ */
+ private final ThreadLocal kryoThreadLocal = ThreadLocal.withInitial(() -> {
+ Kryo kryo = new Kryo();
+ kryo.register(RpcResponse.class);
+ kryo.register(RpcRequest.class);
+ return kryo;
+ });
+
+ @Override
+ public byte[] serialize(Object obj) {
+ try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ Output output = new Output(byteArrayOutputStream)) {
+ Kryo kryo = kryoThreadLocal.get();
+ // Object->byte:将对象序列化为byte数组
+ kryo.writeObject(output, obj);
+ kryoThreadLocal.remove();
+ return output.toBytes();
+ } catch (Exception e) {
+ throw new SerializeException("Serialization failed");
+ }
+ }
+
+ @Override
+ public T deserialize(byte[] bytes, Class clazz) {
+ try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
+ Input input = new Input(byteArrayInputStream)) {
+ Kryo kryo = kryoThreadLocal.get();
+ // byte->Object:从byte数组中反序列化出对象
+ Object o = kryo.readObject(input, clazz);
+ kryoThreadLocal.remove();
+ return clazz.cast(o);
+ } catch (Exception e) {
+ throw new SerializeException("Deserialization failed");
+ }
+ }
+
+}
+```
+
+GitHub 地址:[https://github.com/EsotericSoftware/kryo](https://github.com/EsotericSoftware/kryo) 。
+
+### Protobuf
+
+Protobuf 出自于 Google,性能还比较优秀,也支持多种语言,同时还是跨平台的。就是在使用中过于繁琐,因为你需要自己定义 IDL 文件和生成对应的序列化代码。这样虽然不灵活,但是,另一方面导致 protobuf 没有序列化漏洞的风险。
+
+> Protobuf 包含序列化格式的定义、各种语言的库以及一个 IDL 编译器。正常情况下你需要定义 proto 文件,然后使用 IDL 编译器编译成你需要的语言
+
+一个简单的 proto 文件如下:
+
+```protobuf
+// protobuf的版本
+syntax = "proto3";
+// SearchRequest会被编译成不同的编程语言的相应对象,比如Java中的class、Go中的struct
+message Person {
+ //string类型字段
+ string name = 1;
+ // int 类型字段
+ int32 age = 2;
+}
+```
+
+GitHub 地址:[https://github.com/protocolbuffers/protobuf](https://github.com/protocolbuffers/protobuf)。
+
+### ProtoStuff
+
+由于 Protobuf 的易用性,它的哥哥 Protostuff 诞生了。
+
+protostuff 基于 Google protobuf,但是提供了更多的功能和更简易的用法。虽然更加易用,但是不代表 ProtoStuff 性能更差。
+
+GitHub 地址:[https://github.com/protostuff/protostuff](https://github.com/protostuff/protostuff)。
+
+### Hessian
+
+Hessian 是一个轻量级的,自定义描述的二进制 RPC 协议。Hessian 是一个比较老的序列化实现了,并且同样也是跨语言的。
+
+
+
+Dubbo2.x 默认启用的序列化方式是 Hessian2 ,但是,Dubbo 对 Hessian2 进行了修改,不过大体结构还是差不多。
+
+### 总结
+
+Kryo 是专门针对 Java 语言序列化方式并且性能非常好,如果你的应用是专门针对 Java 语言的话可以考虑使用,并且 Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。(文章地址:[https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/](https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/))
+
+
+
+像 Protobuf、 ProtoStuff、hessian 这类都是跨语言的序列化方式,如果有跨语言需求的话可以考虑使用。
+
+除了我上面介绍到的序列化方式的话,还有像 Thrift,Avro 这些。
diff --git a/docs/java/basis/spi.md b/docs/java/basis/spi.md
new file mode 100644
index 0000000000000000000000000000000000000000..338491a6293f2bd30588a91b8c21284c84b975e7
--- /dev/null
+++ b/docs/java/basis/spi.md
@@ -0,0 +1,560 @@
+---
+title: Java SPI 机制详解
+category: Java
+tag:
+ - Java基础
+head:
+ - - meta
+ - name: keywords
+ content: Java SPI机制
+ - - meta
+ - name: description
+ content: SPI 即 Service Provider Interface ,字面意思就是:“服务提供者的接口”,我的理解是:专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。
+---
+
+> 本文来自 [Kingshion](https://github.com/jjx0708) 投稿。欢迎更多朋友参与到 JavaGuide 的维护工作,这是一件非常有意义的事情。详细信息请看:[JavaGuide 贡献指南](https://javaguide.cn/javaguide/contribution-guideline.html) 。
+
+在面向对象的设计原则中,一般推荐模块之间基于接口编程,通常情况下调用方模块是不会感知到被调用方模块的内部具体实现。一旦代码里面涉及具体实现类,就违反了开闭原则。如果需要替换一种实现,就需要修改代码。
+
+为了实现在模块装配的时候不用在程序里面动态指明,这就需要一种服务发现机制。Java SPI 就是提供了这样一个机制:**为某个接口寻找服务实现的机制。这有点类似 IoC 的思想,将装配的控制权移交到了程序之外。**
+
+## SPI 介绍
+
+### 何谓 SPI?
+
+SPI 即 Service Provider Interface ,字面意思就是:“服务提供者的接口”,我的理解是:专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口。
+
+SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。
+
+很多框架都使用了 Java 的 SPI 机制,比如:Spring 框架、数据库加载驱动、日志接口、以及 Dubbo 的扩展实现等等。
+
+
+
+### SPI 和 API 有什么区别?
+
+**那 SPI 和 API 有啥区别?**
+
+说到 SPI 就不得不说一下 API 了,从广义上来说它们都属于接口,而且很容易混淆。下面先用一张图说明一下:
+
+
+
+一般模块之间都是通过通过接口进行通讯,那我们在服务调用方和服务实现方(也称服务提供者)之间引入一个“接口”。
+
+当实现方提供了接口和实现,我们可以通过调用实现方的接口从而拥有实现方给我们提供的能力,这就是 API ,这种接口和实现都是放在实现方的。
+
+当接口存在于调用方这边时,就是 SPI ,由接口调用方确定接口规则,然后由不同的厂商去根据这个规则对这个接口进行实现,从而提供服务。
+
+举个通俗易懂的例子:公司 H 是一家科技公司,新设计了一款芯片,然后现在需要量产了,而市面上有好几家芯片制造业公司,这个时候,只要 H 公司指定好了这芯片生产的标准(定义好了接口标准),那么这些合作的芯片公司(服务提供者)就按照标准交付自家特色的芯片(提供不同方案的实现,但是给出来的结果是一样的)。
+
+## 实战演示
+
+SLF4J (Simple Logging Facade for Java)是 Java 的一个日志门面(接口),其具体实现有几种,比如:Logback、Log4j、Log4j2 等等,而且还可以切换,在切换日志具体实现的时候我们是不需要更改项目代码的,只需要在 Maven 依赖里面修改一些 pom 依赖就好了。
+
+
+
+这就是依赖 SPI 机制实现的,那我们接下来就实现一个简易版本的日志框架。
+
+### Service Provider Interface
+
+新建一个 Java 项目 `service-provider-interface` 目录结构如下:(注意直接新建 Java 项目就好了,不用新建 Maven 项目,Maven 项目会涉及到一些编译配置,如果有私服的话,直接 deploy 会比较方便,但是没有的话,在过程中可能会遇到一些奇怪的问题。)
+
+```
+│ service-provider-interface.iml
+│
+├─.idea
+│ │ .gitignore
+│ │ misc.xml
+│ │ modules.xml
+│ └─ workspace.xml
+│
+└─src
+ └─edu
+ └─jiangxuan
+ └─up
+ └─spi
+ Logger.java
+ LoggerService.java
+ Main.class
+```
+
+新建 `Logger` 接口,这个就是 SPI , 服务提供者接口,后面的服务提供者就要针对这个接口进行实现。
+
+```java
+package edu.jiangxuan.up.spi;
+
+public interface Logger {
+ void info(String msg);
+ void debug(String msg);
+}
+```
+
+接下来就是 `LoggerService` 类,这个主要是为服务使用者(调用方)提供特定功能的。这个类也是实现 Java SPI 机制的关键所在,如果存在疑惑的话可以先往后面继续看。
+
+```java
+package edu.jiangxuan.up.spi;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ServiceLoader;
+
+public class LoggerService {
+ private static final LoggerService SERVICE = new LoggerService();
+
+ private final Logger logger;
+
+ private final List loggerList;
+
+ private LoggerService() {
+ ServiceLoader loader = ServiceLoader.load(Logger.class);
+ List list = new ArrayList<>();
+ for (Logger log : loader) {
+ list.add(log);
+ }
+ // LoggerList 是所有 ServiceProvider
+ loggerList = list;
+ if (!list.isEmpty()) {
+ // Logger 只取一个
+ logger = list.get(0);
+ } else {
+ logger = null;
+ }
+ }
+
+ public static LoggerService getService() {
+ return SERVICE;
+ }
+
+ public void info(String msg) {
+ if (logger == null) {
+ System.out.println("info 中没有发现 Logger 服务提供者");
+ } else {
+ logger.info(msg);
+ }
+ }
+
+ public void debug(String msg) {
+ if (loggerList.isEmpty()) {
+ System.out.println("debug 中没有发现 Logger 服务提供者");
+ }
+ loggerList.forEach(log -> log.debug(msg));
+ }
+}
+```
+
+新建 `Main` 类(服务使用者,调用方),启动程序查看结果。
+
+```java
+package org.spi.service;
+
+public class Main {
+ public static void main(String[] args) {
+ LoggerService service = LoggerService.getService();
+
+ service.info("Hello SPI");
+ service.debug("Hello SPI");
+ }
+}
+```
+
+程序结果:
+
+> info 中没有发现 Logger 服务提供者
+> debug 中没有发现 Logger 服务提供者
+
+此时我们只是空有接口,并没有为 `Logger` 接口提供任何的实现,所以输出结果中没有按照预期打印相应的结果。
+
+你可以使用命令或者直接使用 IDEA 将整个程序直接打包成 jar 包。
+
+### Service Provider
+
+接下来新建一个项目用来实现 `Logger` 接口
+
+新建项目 `service-provider` 目录结构如下:
+
+```
+│ service-provider.iml
+│
+├─.idea
+│ │ .gitignore
+│ │ misc.xml
+│ │ modules.xml
+│ └─ workspace.xml
+│
+├─lib
+│ service-provider-interface.jar
+|
+└─src
+ ├─edu
+ │ └─jiangxuan
+ │ └─up
+ │ └─spi
+ │ └─service
+ │ Logback.java
+ │
+ └─META-INF
+ └─services
+ edu.jiangxuan.up.spi.Logger
+
+```
+
+新建 `Logback` 类
+
+```java
+package edu.jiangxuan.up.spi.service;
+
+import edu.jiangxuan.up.spi.Logger;
+
+public class Logback implements Logger {
+ @Override
+ public void info(String s) {
+ System.out.println("Logback info 打印日志:" + s);
+ }
+
+ @Override
+ public void debug(String s) {
+ System.out.println("Logback debug 打印日志:" + s);
+ }
+}
+
+```
+
+将 `service-provider-interface` 的 jar 导入项目中。
+
+新建 lib 目录,然后将 jar 包拷贝过来,再添加到项目中。
+
+
+
+再点击 OK 。
+
+
+
+接下来就可以在项目中导入 jar 包里面的一些类和方法了,就像 JDK 工具类导包一样的。
+
+实现 `Logger` 接口,在 `src` 目录下新建 `META-INF/services` 文件夹,然后新建文件 `edu.jiangxuan.up.spi.Logger` (SPI 的全类名),文件里面的内容是:`edu.jiangxuan.up.spi.service.Logback` (Logback 的全类名,即 SPI 的实现类的包名 + 类名)。
+
+**这是 JDK SPI 机制 ServiceLoader 约定好的标准。**
+
+这里先大概解释一下:Java 中的 SPI 机制就是在每次类加载的时候会先去找到 class 相对目录下的 `META-INF` 文件夹下的 services 文件夹下的文件,将这个文件夹下面的所有文件先加载到内存中,然后根据这些文件的文件名和里面的文件内容找到相应接口的具体实现类,找到实现类后就可以通过反射去生成对应的对象,保存在一个 list 列表里面,所以可以通过迭代或者遍历的方式拿到对应的实例对象,生成不同的实现。
+
+所以会提出一些规范要求:文件名一定要是接口的全类名,然后里面的内容一定要是实现类的全类名,实现类可以有多个,直接换行就好了,多个实现类的时候,会一个一个的迭代加载。
+
+接下来同样将 `service-provider` 项目打包成 jar 包,这个 jar 包就是服务提供方的实现。通常我们导入 maven 的 pom 依赖就有点类似这种,只不过我们现在没有将这个 jar 包发布到 maven 公共仓库中,所以在需要使用的地方只能手动的添加到项目中。
+
+### 效果展示
+
+为了更直观的展示效果,我这里再新建一个专门用来测试的工程项目:`java-spi-test`
+
+然后先导入 `Logger` 的接口 jar 包,再导入具体的实现类的 jar 包。
+
+
+
+新建 Main 方法测试:
+
+```java
+package edu.jiangxuan.up.service;
+
+import edu.jiangxuan.up.spi.LoggerService;
+
+public class TestJavaSPI {
+ public static void main(String[] args) {
+ LoggerService loggerService = LoggerService.getService();
+ loggerService.info("你好");
+ loggerService.debug("测试Java SPI 机制");
+ }
+}
+```
+
+运行结果如下:
+
+> Logback info 打印日志:你好
+> Logback debug 打印日志:测试 Java SPI 机制
+
+说明导入 jar 包中的实现类生效了。
+
+如果我们不导入具体的实现类的 jar 包,那么此时程序运行的结果就会是:
+
+> info 中没有发现 Logger 服务提供者
+> debug 中没有发现 Logger 服务提供者
+
+通过使用 SPI 机制,可以看出服务(`LoggerService`)和 服务提供者两者之间的耦合度非常低,如果说我们想要换一种实现,那么其实只需要修改 `service-provider` 项目中针对 `Logger` 接口的具体实现就可以了,只需要换一个 jar 包即可,也可以有在一个项目里面有多个实现,这不就是 SLF4J 原理吗?
+
+如果某一天需求变更了,此时需要将日志输出到消息队列,或者做一些别的操作,这个时候完全不需要更改 Logback 的实现,只需要新增一个服务实现(service-provider)可以通过在本项目里面新增实现也可以从外部引入新的服务实现 jar 包。我们可以在服务(LoggerService)中选择一个具体的 服务实现(service-provider) 来完成我们需要的操作。
+
+那么接下来我们具体来说说 Java SPI 工作的重点原理—— **ServiceLoader** 。
+
+## ServiceLoader
+
+### ServiceLoader 具体实现
+
+想要使用 Java 的 SPI 机制是需要依赖 `ServiceLoader` 来实现的,那么我们接下来看看 `ServiceLoader` 具体是怎么做的:
+
+`ServiceLoader` 是 JDK 提供的一个工具类, 位于`package java.util;`包下。
+
+```
+A facility to load implementations of a service.
+```
+
+这是 JDK 官方给的注释:**一种加载服务实现的工具。**
+
+再往下看,我们发现这个类是一个 `final` 类型的,所以是不可被继承修改,同时它实现了 `Iterable` 接口。之所以实现了迭代器,是为了方便后续我们能够通过迭代的方式得到对应的服务实现。
+
+```java
+public final class ServiceLoader implements Iterable{ xxx...}
+```
+
+可以看到一个熟悉的常量定义:
+
+`private static final String PREFIX = "META-INF/services/";`
+
+下面是 `load` 方法:可以发现 `load` 方法支持两种重载后的入参;
+
+```java
+public static ServiceLoader load(Class service) {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ return ServiceLoader.load(service, cl);
+}
+
+public static ServiceLoader load(Class service,
+ ClassLoader loader) {
+ return new ServiceLoader<>(service, loader);
+}
+
+private ServiceLoader(Class svc, ClassLoader cl) {
+ service = Objects.requireNonNull(svc, "Service interface cannot be null");
+ loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
+ acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
+ reload();
+}
+
+public void reload() {
+ providers.clear();
+ lookupIterator = new LazyIterator(service, loader);
+}
+```
+
+根据代码的调用顺序,在 `reload()` 方法中是通过一个内部类 `LazyIterator` 实现的。先继续往下面看。
+
+`ServiceLoader` 实现了 `Iterable` 接口的方法后,具有了迭代的能力,在这个 `iterator` 方法被调用时,首先会在 `ServiceLoader` 的 `Provider` 缓存中进行查找,如果缓存中没有命中那么则在 `LazyIterator` 中进行查找。
+
+```java
+
+public Iterator iterator() {
+ return new Iterator() {
+
+ Iterator> knownProviders
+ = providers.entrySet().iterator();
+
+ public boolean hasNext() {
+ if (knownProviders.hasNext())
+ return true;
+ return lookupIterator.hasNext(); // 调用 LazyIterator
+ }
+
+ public S next() {
+ if (knownProviders.hasNext())
+ return knownProviders.next().getValue();
+ return lookupIterator.next(); // 调用 LazyIterator
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ };
+}
+```
+
+在调用 `LazyIterator` 时,具体实现如下:
+
+```java
+
+public boolean hasNext() {
+ if (acc == null) {
+ return hasNextService();
+ } else {
+ PrivilegedAction action = new PrivilegedAction() {
+ public Boolean run() {
+ return hasNextService();
+ }
+ };
+ return AccessController.doPrivileged(action, acc);
+ }
+}
+
+private boolean hasNextService() {
+ if (nextName != null) {
+ return true;
+ }
+ if (configs == null) {
+ try {
+ //通过PREFIX(META-INF/services/)和类名 获取对应的配置文件,得到具体的实现类
+ String fullName = PREFIX + service.getName();
+ if (loader == null)
+ configs = ClassLoader.getSystemResources(fullName);
+ else
+ configs = loader.getResources(fullName);
+ } catch (IOException x) {
+ fail(service, "Error locating configuration files", x);
+ }
+ }
+ while ((pending == null) || !pending.hasNext()) {
+ if (!configs.hasMoreElements()) {
+ return false;
+ }
+ pending = parse(service, configs.nextElement());
+ }
+ nextName = pending.next();
+ return true;
+}
+
+
+public S next() {
+ if (acc == null) {
+ return nextService();
+ } else {
+ PrivilegedAction action = new PrivilegedAction() {
+ public S run() {
+ return nextService();
+ }
+ };
+ return AccessController.doPrivileged(action, acc);
+ }
+}
+
+private S nextService() {
+ if (!hasNextService())
+ throw new NoSuchElementException();
+ String cn = nextName;
+ nextName = null;
+ Class> c = null;
+ try {
+ c = Class.forName(cn, false, loader);
+ } catch (ClassNotFoundException x) {
+ fail(service,
+ "Provider " + cn + " not found");
+ }
+ if (!service.isAssignableFrom(c)) {
+ fail(service,
+ "Provider " + cn + " not a subtype");
+ }
+ try {
+ S p = service.cast(c.newInstance());
+ providers.put(cn, p);
+ return p;
+ } catch (Throwable x) {
+ fail(service,
+ "Provider " + cn + " could not be instantiated",
+ x);
+ }
+ throw new Error(); // This cannot happen
+}
+```
+
+可能很多人看这个会觉得有点复杂,没关系,我这边实现了一个简单的 `ServiceLoader` 的小模型,流程和原理都是保持一致的,可以先从自己实现一个简易版本的开始学:
+
+### 自己实现一个 ServiceLoader
+
+我先把代码贴出来:
+
+```java
+package edu.jiangxuan.up.service;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Constructor;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+public class MyServiceLoader {
+
+ // 对应的接口 Class 模板
+ private final Class service;
+
+ // 对应实现类的 可以有多个,用 List 进行封装
+ private final List providers = new ArrayList<>();
+
+ // 类加载器
+ private final ClassLoader classLoader;
+
+ // 暴露给外部使用的方法,通过调用这个方法可以开始加载自己定制的实现流程。
+ public static MyServiceLoader load(Class service) {
+ return new MyServiceLoader<>(service);
+ }
+
+ // 构造方法私有化
+ private MyServiceLoader(Class service) {
+ this.service = service;
+ this.classLoader = Thread.currentThread().getContextClassLoader();
+ doLoad();
+ }
+
+ // 关键方法,加载具体实现类的逻辑
+ private void doLoad() {
+ try {
+ // 读取所有 jar 包里面 META-INF/services 包下面的文件,这个文件名就是接口名,然后文件里面的内容就是具体的实现类的路径加全类名
+ Enumeration urls = classLoader.getResources("META-INF/services/" + service.getName());
+ // 挨个遍历取到的文件
+ while (urls.hasMoreElements()) {
+ // 取出当前的文件
+ URL url = urls.nextElement();
+ System.out.println("File = " + url.getPath());
+ // 建立链接
+ URLConnection urlConnection = url.openConnection();
+ urlConnection.setUseCaches(false);
+ // 获取文件输入流
+ InputStream inputStream = urlConnection.getInputStream();
+ // 从文件输入流获取缓存
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
+ // 从文件内容里面得到实现类的全类名
+ String className = bufferedReader.readLine();
+
+ while (className != null) {
+ // 通过反射拿到实现类的实例
+ Class> clazz = Class.forName(className, false, classLoader);
+ // 如果声明的接口跟这个具体的实现类是属于同一类型,(可以理解为Java的一种多态,接口跟实现类、父类和子类等等这种关系。)则构造实例
+ if (service.isAssignableFrom(clazz)) {
+ Constructor extends S> constructor = (Constructor extends S>) clazz.getConstructor();
+ S instance = constructor.newInstance();
+ // 把当前构造的实例对象添加到 Provider的列表里面
+ providers.add(instance);
+ }
+ // 继续读取下一行的实现类,可以有多个实现类,只需要换行就可以了。
+ className = bufferedReader.readLine();
+ }
+ }
+ } catch (Exception e) {
+ System.out.println("读取文件异常。。。");
+ }
+ }
+
+ // 返回spi接口对应的具体实现类列表
+ public List getProviders() {
+ return providers;
+ }
+}
+```
+
+关键信息基本已经通过代码注释描述出来了,
+
+主要的流程就是:
+
+1. 通过 URL 工具类从 jar 包的 `/META-INF/services` 目录下面找到对应的文件,
+2. 读取这个文件的名称找到对应的 spi 接口,
+3. 通过 `InputStream` 流将文件里面的具体实现类的全类名读取出来,
+4. 根据获取到的全类名,先判断跟 spi 接口是否为同一类型,如果是的,那么就通过反射的机制构造对应的实例对象,
+5. 将构造出来的实例对象添加到 `Providers` 的列表中。
+
+## 总结
+
+其实不难发现,SPI 机制的具体实现本质上还是通过反射完成的。即:**我们按照规定将要暴露对外使用的具体实现类在 `META-INF/services/` 文件下声明。**
+
+另外,SPI 机制在很多框架中都有应用:Spring 框架的基本原理也是类似的方式。还有 Dubbo 框架提供同样的 SPI 扩展机制,只不过 Dubbo 和 spring 框架中的 SPI 机制具体实现方式跟咱们今天学得这个有些细微的区别,不过整体的原理都是一致的,相信大家通过对 JDK 中 SPI 机制的学习,能够一通百通,加深对其他高深框的理解。
+
+通过 SPI 机制能够大大地提高接口设计的灵活性,但是 SPI 机制也存在一些缺点,比如:
+
+1. 遍历加载所有的实现类,这样效率还是相对较低的;
+2. 当多个 `ServiceLoader` 同时 `load` 时,会有并发问题。
diff --git a/docs/java/basis/syntactic-sugar.md b/docs/java/basis/syntactic-sugar.md
new file mode 100644
index 0000000000000000000000000000000000000000..80919251fba05f85b5780ed71edc9ae9aaf7b30c
--- /dev/null
+++ b/docs/java/basis/syntactic-sugar.md
@@ -0,0 +1,810 @@
+---
+title: Java 语法糖详解
+category: Java
+tag:
+ - Java基础
+head:
+ - - meta
+ - name: keywords
+ content: Java 语法糖
+ - - meta
+ - name: description
+ content: 这篇文章介绍了 12 种 Java 中常用的语法糖。所谓语法糖就是提供给开发人员便于开发的一种语法而已。但是这种语法只有开发人员认识。要想被执行,需要进行解糖,即转成 JVM 认识的语法。当我们把语法糖解糖之后,你就会发现其实我们日常使用的这些方便的语法,其实都是一些其他更简单的语法构成的。有了这些语法糖,我们在日常开发的时候可以大大提升效率,但是同时也要避免过渡使用。使用之前最好了解下原理,避免掉坑。
+---
+
+> 作者:Hollis
+>
+> 原文:https://mp.weixin.qq.com/s/o4XdEMq1DL-nBS-f8Za5Aw
+
+语法糖是大厂 Java 面试常问的一个知识点。
+
+本文从 Java 编译原理角度,深入字节码及 class 文件,抽丝剥茧,了解 Java 中的语法糖原理及用法,帮助大家在学会如何使用 Java 语法糖的同时,了解这些语法糖背后的原理。
+
+## 什么是语法糖?
+
+**语法糖(Syntactic Sugar)** 也称糖衣语法,是英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。简而言之,语法糖让程序更加简洁,有更高的可读性。
+
+
+
+> 有意思的是,在编程领域,除了语法糖,还有语法盐和语法糖精的说法,篇幅有限这里不做扩展了。
+
+我们所熟知的编程语言中几乎都有语法糖。作者认为,语法糖的多少是评判一个语言够不够牛逼的标准之一。很多人说 Java 是一个“低糖语言”,其实从 Java 7 开始 Java 语言层面上一直在添加各种糖,主要是在“Project Coin”项目下研发。尽管现在 Java 有人还是认为现在的 Java 是低糖,未来还会持续向着“高糖”的方向发展。
+
+## Java 中有哪些常见的语法糖?
+
+前面提到过,语法糖的存在主要是方便开发人员使用。但其实, **Java 虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。**
+
+说到编译,大家肯定都知道,Java 语言中,`javac`命令可以将后缀名为`.java`的源文件编译为后缀名为`.class`的可以运行于 Java 虚拟机的字节码。如果你去看`com.sun.tools.javac.main.JavaCompiler`的源码,你会发现在`compile()`中有一个步骤就是调用`desugar()`,这个方法就是负责解语法糖的实现的。
+
+Java 中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等。本文主要来分析下这些语法糖背后的原理。一步一步剥去糖衣,看看其本质。
+
+我们这里会用到[反编译](https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650120609&idx=1&sn=5659f96310963ad57d55b48cee63c788&chksm=f36bbc80c41c3596a1e4bf9501c6280481f1b9e06d07af354474e6f3ed366fef016df673a7ba&scene=21#wechat_redirect),你可以通过 [Decompilers online](http://www.javadecompilers.com/) 对 Class 文件进行在线反编译。
+
+### switch 支持 String 与枚举
+
+前面提到过,从 Java 7 开始,Java 语言中的语法糖在逐渐丰富,其中一个比较重要的就是 Java 7 中`switch`开始支持`String`。
+
+在开始之前先科普下,Java 中的`switch`自身原本就支持基本类型。比如`int`、`char`等。对于`int`类型,直接进行数值的比较。对于`char`类型则是比较其 ascii 码。所以,对于编译器来说,`switch`中其实只能使用整型,任何类型的比较都要转换成整型。比如`byte`。`short`,`char`(ascii 码是整型)以及`int`。
+
+那么接下来看下`switch`对`String`的支持,有以下代码:
+
+```java
+public class switchDemoString {
+ public static void main(String[] args) {
+ String str = "world";
+ switch (str) {
+ case "hello":
+ System.out.println("hello");
+ break;
+ case "world":
+ System.out.println("world");
+ break;
+ default:
+ break;
+ }
+ }
+}
+```
+
+反编译后内容如下:
+
+```java
+public class switchDemoString
+{
+ public switchDemoString()
+ {
+ }
+ public static void main(String args[])
+ {
+ String str = "world";
+ String s;
+ switch((s = str).hashCode())
+ {
+ default:
+ break;
+ case 99162322:
+ if(s.equals("hello"))
+ System.out.println("hello");
+ break;
+ case 113318802:
+ if(s.equals("world"))
+ System.out.println("world");
+ break;
+ }
+ }
+}
+```
+
+看到这个代码,你知道原来 **字符串的 switch 是通过`equals()`和`hashCode()`方法来实现的。** 还好`hashCode()`方法返回的是`int`,而不是`long`。
+
+仔细看下可以发现,进行`switch`的实际是哈希值,然后通过使用`equals`方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行 `switch` 或者使用纯整数常量,但这也不是很差。
+
+### 泛型
+
+我们都知道,很多语言都是支持泛型的,但是很多人不知道的是,不同的编译器对于泛型的处理方式是不同的,通常情况下,一个编译器处理泛型有两种方式:`Code specialization`和`Code sharing`。C++和 C#是使用`Code specialization`的处理机制,而 Java 使用的是`Code sharing`的机制。
+
+> Code sharing 方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(`type erasue`)实现的。
+
+也就是说,**对于 Java 虚拟机来说,他根本不认识`Map map`这样的语法。需要在编译阶段通过类型擦除的方式进行解语法糖。**
+
+类型擦除的主要过程如下:1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。 2.移除所有的类型参数。
+
+以下代码:
+
+```java
+Map map = new HashMap();
+map.put("name", "hollis");
+map.put("wechat", "Hollis");
+map.put("blog", "www.hollischuang.com");
+```
+
+解语法糖之后会变成:
+
+```java
+Map map = new HashMap();
+map.put("name", "hollis");
+map.put("wechat", "Hollis");
+map.put("blog", "www.hollischuang.com");
+```
+
+以下代码:
+
+```java
+public static > A max(Collection xs) {
+ Iterator xi = xs.iterator();
+ A w = xi.next();
+ while (xi.hasNext()) {
+ A x = xi.next();
+ if (w.compareTo(x) < 0)
+ w = x;
+ }
+ return w;
+}
+```
+
+类型擦除后会变成:
+
+```java
+ public static Comparable max(Collection xs){
+ Iterator xi = xs.iterator();
+ Comparable w = (Comparable)xi.next();
+ while(xi.hasNext())
+ {
+ Comparable x = (Comparable)xi.next();
+ if(w.compareTo(x) < 0)
+ w = x;
+ }
+ return w;
+}
+```
+
+**虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的`Class`类对象。比如并不存在`List.class`或是`List.class`,而只有`List.class`。**
+
+### 自动装箱与拆箱
+
+自动装箱就是 Java 自动将原始类型值转换成对应的对象,比如将 int 的变量转换成 Integer 对象,这个过程叫做装箱,反之将 Integer 对象转换成 int 类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。原始类型 byte, short, char, int, long, float, double 和 boolean 对应的封装类为 Byte, Short, Character, Integer, Long, Float, Double, Boolean。
+
+先来看个自动装箱的代码:
+
+```java
+ public static void main(String[] args) {
+ int i = 10;
+ Integer n = i;
+}
+```
+
+反编译后代码如下:
+
+```java
+public static void main(String args[])
+{
+ int i = 10;
+ Integer n = Integer.valueOf(i);
+}
+```
+
+再来看个自动拆箱的代码:
+
+```java
+public static void main(String[] args) {
+
+ Integer i = 10;
+ int n = i;
+}
+```
+
+反编译后代码如下:
+
+```java
+public static void main(String args[])
+{
+ Integer i = Integer.valueOf(10);
+ int n = i.intValue();
+}
+```
+
+从反编译得到内容可以看出,在装箱的时候自动调用的是`Integer`的`valueOf(int)`方法。而在拆箱的时候自动调用的是`Integer`的`intValue`方法。
+
+所以,**装箱过程是通过调用包装器的 valueOf 方法实现的,而拆箱过程是通过调用包装器的 xxxValue 方法实现的。**
+
+### 可变长参数
+
+可变参数(`variable arguments`)是在 Java 1.5 中引入的一个特性。它允许一个方法把任意数量的值作为参数。
+
+看下以下可变参数代码,其中 `print` 方法接收可变参数:
+
+```java
+public static void main(String[] args)
+ {
+ print("Holis", "公众号:Hollis", "博客:www.hollischuang.com", "QQ:907607222");
+ }
+
+public static void print(String... strs)
+{
+ for (int i = 0; i < strs.length; i++)
+ {
+ System.out.println(strs[i]);
+ }
+}
+```
+
+反编译后代码:
+
+```java
+ public static void main(String args[])
+{
+ print(new String[] {
+ "Holis", "\u516C\u4F17\u53F7:Hollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com", "QQ\uFF1A907607222"
+ });
+}
+
+public static transient void print(String strs[])
+{
+ for(int i = 0; i < strs.length; i++)
+ System.out.println(strs[i]);
+
+}
+```
+
+从反编译后代码可以看出,可变参数在被使用的时候,他首先会创建一个数组,数组的长度就是调用该方法是传递的实参的个数,然后再把参数值全部放到这个数组当中,然后再把这个数组作为参数传递到被调用的方法中。
+
+### 枚举
+
+Java SE5 提供了一种新的类型-Java 的枚举类型,关键字`enum`可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。
+
+要想看源码,首先得有一个类吧,那么枚举类型到底是什么类呢?是`enum`吗?答案很明显不是,`enum`就和`class`一样,只是一个关键字,他并不是一个类,那么枚举是由什么类维护的呢,我们简单的写一个枚举:
+
+```java
+public enum t {
+ SPRING,SUMMER;
+}
+```
+
+然后我们使用反编译,看看这段代码到底是怎么实现的,反编译后代码内容如下:
+
+```java
+public final class T extends Enum
+{
+ private T(String s, int i)
+ {
+ super(s, i);
+ }
+ public static T[] values()
+ {
+ T at[];
+ int i;
+ T at1[];
+ System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
+ return at1;
+ }
+
+ public static T valueOf(String s)
+ {
+ return (T)Enum.valueOf(demo/T, s);
+ }
+
+ public static final T SPRING;
+ public static final T SUMMER;
+ private static final T ENUM$VALUES[];
+ static
+ {
+ SPRING = new T("SPRING", 0);
+ SUMMER = new T("SUMMER", 1);
+ ENUM$VALUES = (new T[] {
+ SPRING, SUMMER
+ });
+ }
+}
+```
+
+通过反编译后代码我们可以看到,`public final class T extends Enum`,说明,该类是继承了`Enum`类的,同时`final`关键字告诉我们,这个类也是不能被继承的。
+
+**当我们使用`enum`来定义一个枚举类型的时候,编译器会自动帮我们创建一个`final`类型的类继承`Enum`类,所以枚举类型不能被继承。**
+
+### 内部类
+
+内部类又称为嵌套类,可以把内部类理解为外部类的一个普通成员。
+
+**内部类之所以也是语法糖,是因为它仅仅是一个编译时的概念,`outer.java`里面定义了一个内部类`inner`,一旦编译成功,就会生成两个完全不同的`.class`文件了,分别是`outer.class`和`outer$inner.class`。所以内部类的名字完全可以和它的外部类名字相同。**
+
+```java
+public class OutterClass {
+ private String userName;
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public static void main(String[] args) {
+
+ }
+
+ class InnerClass{
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+}
+```
+
+以上代码编译后会生成两个 class 文件:`OutterClass$InnerClass.class`、`OutterClass.class` 。当我们尝试对`OutterClass.class`文件进行反编译的时候,命令行会打印以下内容:`Parsing OutterClass.class...Parsing inner class OutterClass$InnerClass.class... Generating OutterClass.jad` 。他会把两个文件全部进行反编译,然后一起生成一个`OutterClass.jad`文件。文件内容如下:
+
+```java
+public class OutterClass
+{
+ class InnerClass
+ {
+ public String getName()
+ {
+ return name;
+ }
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+ private String name;
+ final OutterClass this$0;
+
+ InnerClass()
+ {
+ this.this$0 = OutterClass.this;
+ super();
+ }
+ }
+
+ public OutterClass()
+ {
+ }
+ public String getUserName()
+ {
+ return userName;
+ }
+ public void setUserName(String userName){
+ this.userName = userName;
+ }
+ public static void main(String args1[])
+ {
+ }
+ private String userName;
+}
+```
+
+### 条件编译
+
+—般情况下,程序中的每一行代码都要参加编译。但有时候出于对程序代码优化的考虑,希望只对其中一部分内容进行编译,此时就需要在程序中加上条件,让编译器只对满足条件的代码进行编译,将不满足条件的代码舍弃,这就是条件编译。
+
+如在 C 或 CPP 中,可以通过预处理语句来实现条件编译。其实在 Java 中也可实现条件编译。我们先来看一段代码:
+
+```java
+public class ConditionalCompilation {
+ public static void main(String[] args) {
+ final boolean DEBUG = true;
+ if(DEBUG) {
+ System.out.println("Hello, DEBUG!");
+ }
+
+ final boolean ONLINE = false;
+
+ if(ONLINE){
+ System.out.println("Hello, ONLINE!");
+ }
+ }
+}
+```
+
+反编译后代码如下:
+
+```java
+public class ConditionalCompilation
+{
+
+ public ConditionalCompilation()
+ {
+ }
+
+ public static void main(String args[])
+ {
+ boolean DEBUG = true;
+ System.out.println("Hello, DEBUG!");
+ boolean ONLINE = false;
+ }
+}
+```
+
+首先,我们发现,在反编译后的代码中没有`System.out.println("Hello, ONLINE!");`,这其实就是条件编译。当`if(ONLINE)`为 false 的时候,编译器就没有对其内的代码进行编译。
+
+所以,**Java 语法的条件编译,是通过判断条件为常量的 if 语句实现的。其原理也是 Java 语言的语法糖。根据 if 判断条件的真假,编译器直接把分支为 false 的代码块消除。通过该方式实现的条件编译,必须在方法体内实现,而无法在正整个 Java 类的结构或者类的属性上进行条件编译,这与 C/C++的条件编译相比,确实更有局限性。在 Java 语言设计之初并没有引入条件编译的功能,虽有局限,但是总比没有更强。**
+
+### 断言
+
+在 Java 中,`assert`关键字是从 JAVA SE 1.4 引入的,为了避免和老版本的 Java 代码中使用了`assert`关键字导致错误,Java 在执行的时候默认是不启动断言检查的(这个时候,所有的断言语句都将忽略!),如果要开启断言检查,则需要用开关`-enableassertions`或`-ea`来开启。
+
+看一段包含断言的代码:
+
+```java
+public class AssertTest {
+ public static void main(String args[]) {
+ int a = 1;
+ int b = 1;
+ assert a == b;
+ System.out.println("公众号:Hollis");
+ assert a != b : "Hollis";
+ System.out.println("博客:www.hollischuang.com");
+ }
+}
+```
+
+反编译后代码如下:
+
+```java
+public class AssertTest {
+ public AssertTest()
+ {
+ }
+ public static void main(String args[])
+{
+ int a = 1;
+ int b = 1;
+ if(!$assertionsDisabled && a != b)
+ throw new AssertionError();
+ System.out.println("\u516C\u4F17\u53F7\uFF1AHollis");
+ if(!$assertionsDisabled && a == b)
+ {
+ throw new AssertionError("Hollis");
+ } else
+ {
+ System.out.println("\u535A\u5BA2\uFF1Awww.hollischuang.com");
+ return;
+ }
+}
+
+static final boolean $assertionsDisabled = !com/hollis/suguar/AssertTest.desiredAssertionStatus();
+
+}
+```
+
+很明显,反编译之后的代码要比我们自己的代码复杂的多。所以,使用了 assert 这个语法糖我们节省了很多代码。**其实断言的底层实现就是 if 语言,如果断言结果为 true,则什么都不做,程序继续执行,如果断言结果为 false,则程序抛出 AssertError 来打断程序的执行。**`-enableassertions`会设置\$assertionsDisabled 字段的值。
+
+### 数值字面量
+
+在 java 7 中,数值字面量,不管是整数还是浮点数,都允许在数字之间插入任意多个下划线。这些下划线不会对字面量的数值产生影响,目的就是方便阅读。
+
+比如:
+
+```java
+public class Test {
+ public static void main(String... args) {
+ int i = 10_000;
+ System.out.println(i);
+ }
+}
+```
+
+反编译后:
+
+```java
+public class Test
+{
+ public static void main(String[] args)
+ {
+ int i = 10000;
+ System.out.println(i);
+ }
+}
+```
+
+反编译后就是把`_`删除了。也就是说 **编译器并不认识在数字字面量中的`_`,需要在编译阶段把他去掉。**
+
+### for-each
+
+增强 for 循环(`for-each`)相信大家都不陌生,日常开发经常会用到的,他会比 for 循环要少写很多代码,那么这个语法糖背后是如何实现的呢?
+
+```java
+public static void main(String... args) {
+ String[] strs = {"Hollis", "公众号:Hollis", "博客:www.hollischuang.com"};
+ for (String s : strs) {
+ System.out.println(s);
+ }
+ List strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com");
+ for (String s : strList) {
+ System.out.println(s);
+ }
+}
+```
+
+反编译后代码如下:
+
+```java
+public static transient void main(String args[])
+{
+ String strs[] = {
+ "Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com"
+ };
+ String args1[] = strs;
+ int i = args1.length;
+ for(int j = 0; j < i; j++)
+ {
+ String s = args1[j];
+ System.out.println(s);
+ }
+
+ List strList = ImmutableList.of("Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com");
+ String s;
+ for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s))
+ s = (String)iterator.next();
+
+}
+```
+
+代码很简单,**for-each 的实现原理其实就是使用了普通的 for 循环和迭代器。**
+
+### try-with-resource
+
+Java 里,对于文件操作 IO 流、数据库连接等开销非常昂贵的资源,用完之后必须及时通过 close 方法将其关闭,否则资源会一直处于打开状态,可能会导致内存泄露等问题。
+
+关闭资源的常用方式就是在`finally`块里是释放,即调用`close`方法。比如,我们经常会写这样的代码:
+
+```java
+public static void main(String[] args) {
+ BufferedReader br = null;
+ try {
+ String line;
+ br = new BufferedReader(new FileReader("d:\\hollischuang.xml"));
+ while ((line = br.readLine()) != null) {
+ System.out.println(line);
+ }
+ } catch (IOException e) {
+ // handle exception
+ } finally {
+ try {
+ if (br != null) {
+ br.close();
+ }
+ } catch (IOException ex) {
+ // handle exception
+ }
+ }
+}
+```
+
+从 Java 7 开始,jdk 提供了一种更好的方式关闭资源,使用`try-with-resources`语句,改写一下上面的代码,效果如下:
+
+```java
+public static void main(String... args) {
+ try (BufferedReader br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ System.out.println(line);
+ }
+ } catch (IOException e) {
+ // handle exception
+ }
+}
+```
+
+看,这简直是一大福音啊,虽然我之前一般使用`IOUtils`去关闭流,并不会使用在`finally`中写很多代码的方式,但是这种新的语法糖看上去好像优雅很多呢。看下他的背后:
+
+```java
+public static transient void main(String args[])
+ {
+ BufferedReader br;
+ Throwable throwable;
+ br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"));
+ throwable = null;
+ String line;
+ try
+ {
+ while((line = br.readLine()) != null)
+ System.out.println(line);
+ }
+ catch(Throwable throwable2)
+ {
+ throwable = throwable2;
+ throw throwable2;
+ }
+ if(br != null)
+ if(throwable != null)
+ try
+ {
+ br.close();
+ }
+ catch(Throwable throwable1)
+ {
+ throwable.addSuppressed(throwable1);
+ }
+ else
+ br.close();
+ break MISSING_BLOCK_LABEL_113;
+ Exception exception;
+ exception;
+ if(br != null)
+ if(throwable != null)
+ try
+ {
+ br.close();
+ }
+ catch(Throwable throwable3)
+ {
+ throwable.addSuppressed(throwable3);
+ }
+ else
+ br.close();
+ throw exception;
+ IOException ioexception;
+ ioexception;
+ }
+}
+```
+
+**其实背后的原理也很简单,那些我们没有做的关闭资源的操作,编译器都帮我们做了。所以,再次印证了,语法糖的作用就是方便程序员的使用,但最终还是要转成编译器认识的语言。**
+
+### Lambda 表达式
+
+关于 lambda 表达式,有人可能会有质疑,因为网上有人说他并不是语法糖。其实我想纠正下这个说法。**Lambda 表达式不是匿名内部类的语法糖,但是他也是一个语法糖。实现方式其实是依赖了几个 JVM 底层提供的 lambda 相关 api。**
+
+先来看一个简单的 lambda 表达式。遍历一个 list:
+
+```java
+public static void main(String... args) {
+ List strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com");
+
+ strList.forEach( s -> { System.out.println(s); } );
+}
+```
+
+为啥说他并不是内部类的语法糖呢,前面讲内部类我们说过,内部类在编译之后会有两个 class 文件,但是,包含 lambda 表达式的类编译后只有一个文件。
+
+反编译后代码如下:
+
+```java
+public static /* varargs */ void main(String ... args) {
+ ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com");
+ strList.forEach((Consumer)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)());
+}
+
+private static /* synthetic */ void lambda$main$0(String s) {
+ System.out.println(s);
+}
+```
+
+可以看到,在`forEach`方法中,其实是调用了`java.lang.invoke.LambdaMetafactory#metafactory`方法,该方法的第四个参数 `implMethod` 指定了方法实现。可以看到这里其实是调用了一个`lambda$main$0`方法进行了输出。
+
+再来看一个稍微复杂一点的,先对 List 进行过滤,然后再输出:
+
+```java
+public static void main(String... args) {
+ List strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com");
+
+ List HollisList = strList.stream().filter(string -> string.contains("Hollis")).collect(Collectors.toList());
+
+ HollisList.forEach( s -> { System.out.println(s); } );
+}
+```
+
+反编译后代码如下:
+
+```java
+public static /* varargs */ void main(String ... args) {
+ ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com");
+ List HollisList = strList.stream().filter((Predicate)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Z, lambda$main$0(java.lang.String ), (Ljava/lang/String;)Z)()).collect(Collectors.toList());
+ HollisList.forEach((Consumer)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$1(java.lang.Object ), (Ljava/lang/Object;)V)());
+}
+
+private static /* synthetic */ void lambda$main$1(Object s) {
+ System.out.println(s);
+}
+
+private static /* synthetic */ boolean lambda$main$0(String string) {
+ return string.contains("Hollis");
+}
+```
+
+两个 lambda 表达式分别调用了`lambda$main$1`和`lambda$main$0`两个方法。
+
+**所以,lambda 表达式的实现其实是依赖了一些底层的 api,在编译阶段,编译器会把 lambda 表达式进行解糖,转换成调用内部 api 的方式。**
+
+## 可能遇到的坑
+
+### 泛型
+
+**一、当泛型遇到重载**
+
+```java
+public class GenericTypes {
+
+ public static void method(List list) {
+ System.out.println("invoke method(List list)");
+ }
+
+ public static void method(List list) {
+ System.out.println("invoke method(List list)");
+ }
+}
+```
+
+上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是`List`另一个是`List` ,但是,这段代码是编译通不过的。因为我们前面讲过,参数`List`和`List`编译之后都被擦除了,变成了一样的原生类型 List,擦除动作导致这两个方法的特征签名变得一模一样。
+
+**二、当泛型遇到 catch**
+
+泛型的类型参数不能用在 Java 异常处理的 catch 语句中。因为异常处理是由 JVM 在运行时刻来进行的。由于类型信息被擦除,JVM 是无法区分两个异常类型`MyException`和`MyException`的
+
+**三、当泛型内包含静态变量**
+
+```java
+public class StaticTest{
+ public static void main(String[] args){
+ GT gti = new GT();
+ gti.var=1;
+ GT gts = new GT