-
-配置中心将配置从应用中剥离出来,统一管理,优雅的解决了配置的动态变更、持久化、运维成本等问题。
-
-应用自身既不需要去添加管理配置接口,也不需要自己去实现配置的持久化,更不需要引入“定时任务”以便降低运维成本。
-
- **总得来说,配置中心就是一种统一管理各种应用配置的基础服务组件。**
-
- 在系统架构中,配置中心是整个微服务基础架构体系中的一个组件,如下图,它的功能看上去并不起眼,无非就是配置的管理和存取,但它是整个微服务架构中不可或缺的一环。
-
-
-
-
-
- 集中管理配置,那么就要将应用的配置作为一个单独的服务抽离出来了,同理也需要解决新的问题,比如:版本管理(为了支持回滚),权限管理等。
-
- 总结一下,在传统巨型单体应用纷纷转向细粒度微服务架构的历史进程中,配置中心是微服务化不可缺少的一个系统组件,在这种背景下中心化的配置服务即配置中心应运而生,一个合格的配置中心需要满足:
-
-* 配置项容易读取和修改
-
-* 添加新配置简单直接
-
-* 支持对配置的修改的检视以把控风险
-
-* 可以查看配置修改的历史记录
-
-* 不同部署环境支持隔离
-
-
-Apollo简介
-----------
-
-### 主流配置中心
-
-目前市面上用的比较多的配置中心有:(按开源时间排序)
-
-1. Disconf
-
- 2014年7月百度开源的配置管理中心,专注于各种「分布式系统配置管理」的「通用组件」和「通用平台」, 提供统一的「配置管理服务」。目前已经不再维护更新。
-
- [https://github.com/knightliao/disconf](https://github.com/knightliao/disconf)
-
-2. Spring Cloud Config
-
- 2014年9月开源,Spring Cloud 生态组件,可以和Spring Cloud体系无缝整合。
-
- [https://github.com/spring-cloud/spring-cloud-config](https://github.com/spring-cloud/spring-cloud-config)
-
-3. Apollo
-
- 2016年5月,携程开源的配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
-
- [https://github.com/ctripcorp/apollo](https://github.com/ctripcorp/apollo)
-
-4. Nacos
-
- 2018年6月,阿里开源的配置中心,也可以做DNS和RPC的服务发现。
-
- [https://github.com/alibaba/nacos](https://github.com/alibaba/nacos)
-
-
-
-#### 功能特性对比
-
-由于Disconf不再维护,下面主要对比一下Spring Cloud Config、Apollo和Nacos。
-
-
-
-| 功能点 | Spring Cloud Config | Apollo | Nacos |
-| ------------ | ---------------------- | ------------------------ | ------------------------ |
-| 配置实时推送 | 支持(Spring Cloud Bus) | 支持(HTTP长轮询1s内) | 支持(HTTP长轮询1s内) |
-| 版本管理 | 支持(Git) | 支持 | 支持 |
-| 配置回滚 | 支持(Git) | 支持 | 支持 |
-| 灰度发布 | 支持 | 支持 | 不支持 |
-| 权限管理 | 支持(依赖Git) | 支持 | 不支持 |
-| 多集群 | 支持 | 支持 | 支持 |
-| 多环境 | 支持 | 支持 | 支持 |
-| 监听查询 | 支持 | 支持 | 支持 |
-| 多语言 | 只支持Java | 主流语言,提供了Open API | 主流语言,提供了Open API |
-| 配置格式校验 | 不支持 | 支持 | 支持 |
-| 单机读(QPS) | 7(限流所致) | 9000 | 15000 |
-| 单击写(QPS) | 5(限流所致) | 1100 | 1800 |
-| 3节点读(QPS) | 21(限流所致) | 27000 | 45000 |
-| 3节点写(QPS) | 5限流所致() | 3300 | 5600 |
-
-
-
-#### 总结
-
-总的来看,Apollo和Nacos相对于Spring Cloud Config的生态支持更广,在配置管理流程上做的更好。Apollo相对于Nacos在配置管理做的更加全面,Nacos则使用起来相对比较简洁,在对性能要求比较高的大规模场景更适合。但对于一个开源项目的选型,项目上的人力投入(迭代进度、文档的完整性)、社区的活跃度(issue的数量和解决速度、Contributor数量、社群的交流频次等),这些因素也比较关键,考虑到Nacos开源时间不长和社区活跃度,所以从目前来看Apollo应该是最合适的配置中心选型。
-
-
-
-### Apollo简介
-
-
-
-**Apollo - A reliable configuration management system**
-
-[https://github.com/ctripcorp/apollo](https://github.com/ctripcorp/apollo)
-
-Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用的不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
-
-Apollo包括服务端和客户端两部分:
-
-服务端基于Spring Boot和Spring Cloud开发,打包后可以直接运行,不需要额外安装Tomcat等应用容器。
-
-Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时对Spring/Spring Boot环境也有较好的支持。
-
-
-
-### Apollo特性
-
-基于配置的特殊性,所以Apollo从设计之初就立志于成为一个有治理能力的配置发布平台,目前提供了以下的特性:
-
-* **统一管理不同环境、不同集群的配置**
- * Apollo提供了一个统一界面集中式管理不同环境(environment)、不同集群(cluster)、不同命名空间(namespace)的配置。
- * 同一份代码部署在不同的集群,可以有不同的配置,比如zookeeper的地址等
- * 通过命名空间(namespace)可以很方便地支持多个不同应用共享同一份配置,同时还允许应用对共享的配置进行覆盖
-* **配置修改实时生效(热发布)**
- * 用户在Apollo修改完配置并发布后,客户端能实时(1秒)接收到最新的配置,并通知到应用程序
-* **版本发布管理**
- * 所有的配置发布都有版本概念,从而可以方便地支持配置的回滚
-* **灰度发布**
- * 支持配置的灰度发布,比如点了发布后,只对部分应用实例生效,等观察一段时间没问题后再推给所有应用实例
-* **权限管理、发布审核、操作审计**
- * 应用和配置的管理都有完善的权限管理机制,对配置的管理还分为了编辑和发布两个环节,从而减少人为的错误。
- * 所有的操作都有审计日志,可以方便地追踪问题
-* **客户端配置信息监控**
- * 可以在界面上方便地看到配置在被哪些实例使用
-* **提供Java和.Net原生客户端**
- * 提供了Java和.Net的原生客户端,方便应用集成
- * 支持Spring Placeholder, Annotation和Spring Boot的ConfigurationProperties,方便应用使用(需要Spring 3.1.1+)
- * 同时提供了Http接口,非Java和.Net应用也可以方便地使用
-* **提供开放平台API**
- * Apollo自身提供了比较完善的统一配置管理界面,支持多环境、多数据中心配置管理、权限、流程治理等特性。不过Apollo出于通用性考虑,不会对配置的修改做过多限制,只要符合基本的格式就能保存,不会针对不同的配置值进行针对性的校验,如数据库用户名、密码,Redis服务地址等
- * 对于这类应用配置,Apollo支持应用方通过开放平台API在Apollo进行配置的修改和发布,并且具备完善的授权和权限控制
-
-
-
-Apollo快速入门
-------------
-
-### 执行流程
-
-
-
-操作流程如下:
-
-1、在Apollo配置中心修改配置
-
-2、应用程序通过Apollo客户端从配置中心拉取配置信息
-
- 用户通过Apollo配置中心修改或发布配置后,会有两种机制来保证应用程序来获取最新配置:一种是Apollo配置中心会向客户端推送最新的配置;另外一种是Apollo客户端会定时从Apollo配置中心拉取最新的配置,通过以上两种机制共同来保证应用程序能及时获取到配置。
-
-
-
-### 安装Apollo
-
-#### 运行时环境
-
-Java
-
-* Apollo服务端:1.8+
-* Apollo客户端:1.7+
-
-由于需要同时运行服务端和客户端,所以建议安装Java 1.8+。
-
-MySQL
-
-* 版本要求:5.6.5+
-
-Apollo的表结构对`timestamp`使用了多个default声明,所以需要5.6.5以上版本。
-
-
-
-#### 下载配置
-
-1. 访问Apollo的官方主页获取安装包(我自己学习使用的是1.3版本):
-
- [https://github.com/ctripcorp/apollo/tags](https://github.com/ctripcorp/apollo/tags)
-
-
-
-2. 打开1.3发布链接,下载必须的安装包:[https://github.com/ctripcorp/apollo/releases/tag/v1.3.0](https://github.com/ctripcorp/apollo/releases/tag/v1.3.0)。三个都要下
-
-
-
-解压安装包后将apollo-configservice-1.3.0.jar, apollo-adminservice-1.3.0.jar, apollo-portal-1.3.0.jar放置于apollo目录下
-
-
-
-#### 创建数据库
-
-Apollo服务端共需要两个数据库:`ApolloPortalDB`和`ApolloConfigDB`,ApolloPortalDB只需要在生产环境部署一个即可,**而ApolloConfigDB需要在每个环境部署一套。**
-
-1. 创建ApolloPortalDB,sql脚本下载地址:[https://github.com/ctripcorp/apollo/blob/v1.3.0/scripts/db/migration/configdb/V1.0.0__initialization.sql](https://github.com/ctripcorp/apollo/blob/v1.3.0/scripts/db/migration/configdb/V1.0.0__initialization.sql)
-
- 以MySQL原生客户端为例:推荐用Navicat直接导入此sql即可
-
- source apollo/ApolloPortalDB__initialization.sql
-
-2. 验证ApolloPortalDB
-
- 导入成功后,可以通过执行以下sql语句来验证:
-
-```sql
- select `Id`, `Key`, `Value`, `Comment` from `ApolloPortalDB`.`ServerConfig` limit 1;
-```
-
-
-> 注:ApolloPortalDB只需要在生产环境部署一个即可
-
-3. 创建ApolloConfigDB,sql脚本下载地址:[https://github.com/ctripcorp/apollo/blob/v1.3.0/scripts/db/migration/configdb/V1.0.0__initialization.sql](https://github.com/ctripcorp/apollo/blob/v1.3.0/scripts/db/migration/configdb/V1.0.0__initialization.sql)
-
- 以MySQL原生客户端为例:
-
- source apollo/ApolloConfigDB__initialization.sql
-
-4. 验证ApolloConfigDB
-
- 导入成功后,可以通过执行以下sql语句来验证:
-
-```sql
- select `Id`, `Key`, `Value`, `Comment` from `ApolloConfigDB`.`ServerConfig` limit 1;
-```
-
-
-
-#### 启动Apollo
-
-**方法一**
-
-1. 确保端口未被占用
-
- **Apollo默认会启动3个服务,分别使用8070, 8080, 8090端口,请确保这3个端口当前没有被使用**
-
-2. 启动apollo-configservice,在apollo目录下执行如下命令
-
- 可通过-Dserver.port=8080修改默认端口
-
-```shell
- java -Xms256m -Xmx256m -Dspring.datasource.url=jdbc:mysql://localhost:3306/ApolloConfigDB?characterEncoding=utf8 -Dspring.datasource.username=root -Dspring.datasource.password=pbteach0430 -jar apollo-configservice-1.3.0.jar
-```
-
-
-
-
-3. 启动apollo-adminservice
-
- 可通过-Dserver.port=8090修改默认端口
-
-```shell
- java -Xms256m -Xmx256m -Dspring.datasource.url=jdbc:mysql://localhost:3306/ApolloConfigDB?characterEncoding=utf8 -Dspring.datasource.username=root -Dspring.datasource.password=pbteach0430 -jar apollo-adminservice-1.3.0.jar
-```
-
-
-
-
-4. 启动apollo-portal
-
- 可通过-Dserver.port=8070修改默认端口
-
-```shell
- java -Xms256m -Xmx256m -Ddev_meta=http://localhost:8080/ -Dserver.port=8070 -Dspring.datasource.url=jdbc:mysql://localhost:3306/ApolloPortalDB?characterEncoding=utf8 -Dspring.datasource.username=root -Dspring.datasource.password=pbteach0430 -jar apollo-portal-1.3.0.jar
-```
-
-
-
-**方法2**
-
-1. 也可以使用提供的runApollo.bat快速启动三个服务(修改数据库连接地址,数据库以及密码)
-
-
-
- 这里面是一个很简单的脚本
-
-
-
-
-
-具体代码:
-
-```bash
-echo
-
-set url="localhost:3306"
-set username="root"
-set password="123456"
-
-start "configService" java -Xms256m -Xmx256m -Dapollo_profile=github -Dspring.datasource.url=jdbc:mysql://%url%/ApolloConfigDB?characterEncoding=utf8 -Dspring.datasource.username=%username% -Dspring.datasource.password=%password% -Dlogging.file=.\logs\apollo-configservice.log -jar .\apollo-configservice-1.3.0.jar
-start "adminService" java -Xms256m -Xmx256m -Dapollo_profile=github -Dspring.datasource.url=jdbc:mysql://%url%/ApolloConfigDB?characterEncoding=utf8 -Dspring.datasource.username=%username% -Dspring.datasource.password=%password% -Dlogging.file=.\logs\apollo-adminservice.log -jar .\apollo-adminservice-1.3.0.jar
-start "ApolloPortal" java -Xms256m -Xmx256m -Dapollo_profile=github,auth -Ddev_meta=http://localhost:8080/ -Dserver.port=8070 -Dspring.datasource.url=jdbc:mysql://%url%/ApolloPortalDB?characterEncoding=utf8 -Dspring.datasource.username=%username% -Dspring.datasource.password=%password% -Dlogging.file=.\logs\apollo-portal.log -jar .\apollo-portal-1.3.0.jar
-```
-
-
-
-1. 运行runApollo.bat即可启动Apollo
-2. 待启动成功后,访问[管理页面](http://localhost:8070/),初始用户名: apollo,初始密码:admin
-
-
-
-
-### 代码实现
-
-#### 发布配置
-
-1. 打开[apollo](http://localhost:8070/) :新建项目apollo-quickstart
-
-
-
-2. 新建配置项sms.enable
-
-
-
-
-
-
-
-确认提交配置项
-
-
-
-
-
-
-
-
-
-3. 发布配置项
-
-
-
-#### 应用读取配置
-
-1、新建Maven工程
-
-
-
-```java
-
-
-
-运行GetConfigTest,打开控制台,观察输出结果
-
-```
-sma.enable: true
-```
-
-
-
-#### 修改配置
-
-1. 到管理页面修改sms.enable的值为false,再运行GetConfigTest,可以看到输出结果已为false
-
-
-
-#### 热发布
-
-1. 修改代码为每3秒获取一次
-
-```java
- public class GetConfigTest {
- public static void main(String[] args) throws InterruptedException {
- Config config = ConfigService.getAppConfig();
- String someKey = "sms.enable";
- while (true) {
- String value = config.getProperty(someKey, null);
- System.out.printf("now: %s, sms.enable: %s%n", LocalDateTime.now().toString(), value);
- Thread.sleep(3000L);
- }
- }
- }
-```
-
-
-
-2. 运行GetConfigTest观察输出结果
-
-
-
-3. 在Apollo管理界面修改配置项
-
-
-
-4. 发布配置
-
-
-
-5. 在控制台查看详细情况:可以看到程序获取的sms.enable的值已由false变成了修改后的true
-
-
-
-
-
-Apollo应用
-----------
-
-### Apollo工作原理
-
-下图是Apollo架构模块的概览
-
-
-
-#### 各模块职责
-
-上图简要描述了Apollo的总体设计,我们可以从下往上看:
-
-* Config Service提供配置的读取、推送等功能,服务对象是Apollo客户端
-* Admin Service提供配置的修改、发布等功能,服务对象是Apollo Portal(管理界面)
-* Eureka提供服务注册和发现,为了简单起见,目前Eureka在部署时和Config Service是在一个JVM进程中的
-* Config Service和Admin Service都是多实例、无状态部署,所以需要将自己注册到Eureka中并保持心跳
-* 在Eureka之上架了一层Meta Server用于封装Eureka的服务发现接口
-* Client通过域名访问Meta Server获取Config Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Client侧会做load balance、错误重试
-* Portal通过域名访问Meta Server获取Admin Service服务列表(IP+Port),而后直接通过IP+Port访问服务,同时在Portal侧会做load balance、错误重试
-* 为了简化部署,我们实际上会把Config Service、Eureka和Meta Server三个逻辑角色部署在同一个JVM进程中
-
-
-
-#### 分步执行流程
-
-1. Apollo启动后,Config/Admin Service会自动注册到Eureka服务注册中心,并定期发送保活心跳。
-2. Apollo Client和Portal管理端通过配置的Meta Server的域名地址经由Software Load Balancer(软件负载均衡器)进行负载均衡后分配到某一个Meta Server
-3. Meta Server从Eureka获取Config Service和Admin Service的服务信息,相当于是一个Eureka Client
-4. Meta Server获取Config Service和Admin Service(IP+Port)失败后会进行重试
-5. 获取到正确的Config Service和Admin Service的服务信息后,Apollo Client通过Config Service为应用提供配置获取、实时更新等功能;Apollo Portal管理端通过Admin Service提供配置新增、修改、发布等功能
-
-
-
-### 核心概念
-
-1. **application (应用)**
-
-这个很好理解,就是实际使用配置的应用,Apollo客户端在运行时需要知道当前应用是谁,从而可以去获取对应的配置
-
-**关键字:appId**
-
-2. **environment (环境)**
-
-配置对应的环境,Apollo客户端在运行时需要知道当前应用处于哪个环境,从而可以去获取应用的配置
-
-**关键字:env**
-
-3. **cluster (集群)**
-
-一个应用下不同实例的分组,比如典型的可以**按照数据中心分**,把上海机房的应用实例分为一个集群,把北京机房的应用实例分为另一个集群。
-
-**关键字:cluster**
-
-4. **namespace (命名空间)**
-
-一个应用下不同配置的分组,可以简单地把namespace类比为文件,不同类型的配置存放在不同的文件中,如数据库配置文件,RPC配置文件,应用自身的配置文件等
-
-**关键字:namespaces**
-
-它们的关系如下图所示:
-
-
-
-
-
-### 项目管理
-
-#### 基础设置
-
-1. 部门管理
-
-apollo 默认部门有两个。要增加自己的部门,可在系统参数中修改:
-
-* 进入系统参数设置
-
-
-
-
-
-
-
-* 输入key查询已存在的部门设置:organizations
-
-
-
-* 修改value值来添加新部门,下面添加一个微服务部门:
-
- ```json
- [{"orgId":"TEST1","orgName":"样例部门1"},{"orgId":"TEST2","orgName":"样例部门2"},{"orgId":"micro_service","orgName":"微服务部门"}]
- ```
-
-
-
-2. 添加用户
-
-apollo默认提供一个超级管理员: apollo,可以自行添加用户
-
-* 新建用户张三
-
-
-
-
-
-
-
-#### 创建项目
-
-1. 打开apollo-portal主页:[http://localhost:8070/](http://localhost:8070/)
-
-2. 点击“创建项目”:account-service
-
-
-
-
-3. 输入项目信息
-
- * 部门:选择应用所在的部门
- * 应用AppId:用来标识应用身份的唯一id,格式为string,需要和项目配置文件applications.properties中配置的app.id对应
- * 应用名称:应用名,仅用于界面展示
- * 应用负责人:选择的人默认会成为该项目的管理员,具备项目权限管理、集群创建、Namespace创建等权限
-
-
-
-4. 点击提交,创建成功后,会自动跳转到项目首页
-
-
-
-1. 赋予之前添加的用户张三管理account-service服务的权限
-
- * 使用管理员apollo将指定项目授权给用户张三
-
-
-
- * 将修改和发布权限都授权给张三
-
- * 使用zhangsan登录,查看项目配置
-
- * 点击account-service即可管理配置
-
-
-
-#### 删除项目
-
-如果要删除整个项目,点击右上角的“管理员工具–》删除应用、集群…”
-
-首先查询出要删除的项目,点击“删除应用”
-
-
-
-
-
-### 配置管理
-
-下边在account-service项目中进行配置。
-
-#### 添加发布配置项
-
-1. 通过表格模式添加配置,点击新增配置,输入配置项:sms.enable,点击提交即可。
-
-2. 通过文本模式编辑,Apollo除了支持表格模式,逐个添加、修改配置外,还提供文本模式批量添加、修改。 这个对于从已有的properties文件迁移尤其有用
-
- - 切换到文本编辑模式
- - 输入配置项,并点击提交修改
-
-3. 发布配置
-
-
-
-#### 修改配置
-
-1. 找到对应的配置项,点击修改
-2. 修改为需要的值,点击提交
-3. 发布配置
-
-#### 删除配置
-
-1. 找到需要删除的配置项,点击删除
-2. 确认删除后,点击发布
-
-
-
-#### 添加Namespace
-
-Namespace作为配置的分类,可当成一个配置文件。
-
-以添加rocketmq配置为例,添加“spring-rocketmq” Namespace配置rocketmq相关信息。
-
-1. 添加项目私有Namespace:spring-rocketmq
-
-进入项目首页,点击左下脚的“添加Namespace”,共包括两项:关联公共Namespace和创建Namespace,这里选择“创建Namespace”
-
-
-
-2. 添加配置项
-
-```properties
- rocketmq.name-server = 127.0.0.1:9876
- rocketmq.producer.group = PID_ACCOUNT
-```
-
-3. 发布配置
-
-
-
-#### 公共配置
-
-##### 添加公共Namespace
-
-在项目开发中,有一些配置可能是通用的,我们可以通过把这些通用的配置放到公共的Namespace中,这样其他项目要使用时可以直接添加需要的Namespace
-
-1. 新建common-template项目
-
-
-
-2. 添加公共Namespace:spring-boot-http
-
-进入common-template项目管理页面:[http://localhost:8070/config.html?#/appid=common-template](http://localhost:8070/config.html?#/appid=common-template)
-
-
-
-
-
-
-
-1. 添加配置项并发布
-
- ```properties
- spring.http.encoding.enabled = true
- spring.http.encoding.charset = UTF-8
- spring.http.encoding.force = true
- server.tomcat.remote_ip_header = x-forwarded-for
- server.tomcat.protocol_header = x-forwarded-proto
- server.use-forward-headers = true
- server.servlet.context-path = /
- ```
-
-
-
-
-
-##### 关联公共Namespace
-
-1. 打开之前创建的account-service项目
-2. 点击左侧的添加Namespace
-3. 添加Namespace
-
-
-
-4. 根据需求可以覆盖引入公共Namespace中的配置,下面以覆盖server.servlet.context-path为例
-
-
-
-5. 修改server.servlet.context-path为:/account-service
-6. 发布修改的配置项
-
-
-
-### 集群管理
-
-在有些情况下,应用有需求对不同的集群做不同的配置,比如部署在A机房的应用连接的RocketMQ服务器地址和部署在B机房的应用连接的RocketMQ服务器地址不一样。另外在项目开发过程中,也可为不同的开发人员创建不同的集群来满足开发人员的自定义配置。
-
-#### 创建集群
-
-1. 点击页面左侧的“添加集群”按钮
-2. 输入集群名称SHAJQ,选择环境并提交:添加上海金桥数据中心为例
-
-
-
-
-
-
-
-3. 切换到对应的集群,修改配置并发布即可
-
-
-
-#### 同步集群配置
-
-同步集群的配置是指在同一个应用中拷贝某个环境下的集群的配置到目标环境下的目标集群。
-
-1. 从其他集群同步已有配置到新集群
-
- * 切换到原有集群
-
- * 展开要同步的Namespace,点击同步配置
-
-
-
-
-
-
- 
-
- * 选择同步到的新集群,再选择要同步的配置
-
-
-
- * 同步完成后,切换到SHAJQ集群,发布配置
-
-
-
-#### 读取配置
-
-读取某个集群的配置,需要启动应用时指定具体的应用、环境和集群。
-
--Dapp.id=应用名称
-
--Denv=环境名称
-
--Dapollo.cluster=集群名称
-
--D环境_meta=meta地址
-
-```bash
--Dapp.id=account-service -Denv=DEV -Dapollo.cluster=SHAJQ -Ddev_meta=http://localhost:8080
-```
-
-
-
-### 配置发布原理
-
-在配置中心中,一个重要的功能就是配置发布后实时推送到客户端。下面我们简要看一下这块是怎么设计实现的。
-
-
-
-上图简要描述了配置发布的主要过程:
-
-1. 用户在Portal操作配置发布
-2. Portal调用Admin Service的接口操作发布
-3. Admin Service发布配置后,发送ReleaseMessage给各个Config Service
-4. Config Service收到ReleaseMessage后,通知对应的客户端
-
-
-
-#### 发送ReleaseMessage
-
-Admin Service在配置发布后,需要通知所有的Config Service有配置发布,从而Config Service可以通知对应的客户端来拉取最新的配置。
-
-从概念上来看,这是一个典型的消息使用场景,Admin Service作为producer(生产者)发出消息,各个Config Service作为consumer(消费者)消费消息。通过一个消息队列组件(Message Queue)就能很好的实现Admin Service和Config Service的解耦。
-
-在实现上,考虑到Apollo的实际使用场景,以及为了尽可能减少外部依赖,我们没有采用外部的消息中间件,而是通过数据库实现了一个简单的消息队列。
-
-具体实现方式如下:
-
-1. Admin Service在配置发布后会往ReleaseMessage表插入一条消息记录,消息内容就是配置发布的AppId+Cluster+Namespace
-
- ```sql
- SELECT * FROM ApolloConfigDB.ReleaseMessage
- ```
-
-
-
-
-
-消息发送类:[DatabaseMessageSende](https://github.com/ctripcorp/apollo/blob/master/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/message/DatabaseMessageSender.java)
-
-```java
-@Override
- @Transactional
- public void sendMessage(String message, String channel) {
- logger.info("Sending message {} to channel {}", message, channel);
- if (!Objects.equals(channel, Topics.APOLLO_RELEASE_TOPIC)) {
- logger.warn("Channel {} not supported by DatabaseMessageSender!", channel);
- return;
- }
-
- Tracer.logEvent("Apollo.AdminService.ReleaseMessage", message);
- Transaction transaction = Tracer.newTransaction("Apollo.AdminService", "sendMessage");
- try {
- //这里发送消息
- ReleaseMessage newMessage = releaseMessageRepository.save(new ReleaseMessage(message));
- toClean.offer(newMessage.getId());
- transaction.setStatus(Transaction.SUCCESS);
- } catch (Throwable ex) {
- logger.error("Sending message to database failed", ex);
- transaction.setStatus(ex);
- throw ex;
- } finally {
- transaction.complete();
- }
- }
-```
-
-
-
-2. Config Service有一个线程会每秒扫描一次ReleaseMessage表,看看是否有新的消息记录
-
-消息扫描类:[ReleaseMessageScanner](https://github.com/ctripcorp/apollo/blob/master/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/message/ReleaseMessageScanner.java)
-
-```java
- /**
- * scan messages and send
- *
- * @return whether there are more messages
- */
- private boolean scanAndSendMessages() {
- //current batch is 500
- List
-
-#### Config Service通知客户端
-
-上一节中简要描述了NotificationControllerV2是如何得知有配置发布的,那NotificationControllerV2在得知有配置发布后是如何通知到客户端的呢?
-
-实现方式如下:
-
-1. 客户端会发起一个Http请求到Config Service的`notifications/v2`接口[NotificationControllerV2](https://github.com/ctripcorp/apollo/blob/master/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/controller/NotificationControllerV2.java)
-
-```java
-/**
- * @author Jason Song(song_s@ctrip.com)
- */
-@RestController
-@RequestMapping("/notifications/v2")
-public class NotificationControllerV2 implements ReleaseMessageListener {
- private static final Logger logger = LoggerFactory.getLogger(NotificationControllerV2.class);
- private final Multimap
-
-
-
-**完整的主配置文件为:**
-
-application.properties
-
-```properties
-# 指定读哪个应用的配置(必须写在配置文件里,无法写在apollo里,因为配置文件里如果没有写,都不知道读apollo里的哪个应用)
-app.id=account-service
-# 下面这个配置是开启Apollo的客户端,使其生效
-apollo.bootstrap.enabled = true
-
-# namespace以逗号分隔
-apollo.bootstrap.namespaces = application,micro_service.spring-boot-http,spring-rocketmq,micro_service.spring-boot-druid
-
-server.port=63000
-```
-
-
-
-#### 启用配置
-
-在咱们应用的启动类添加`@EnableApolloConfig`注解即可:
-
-```java
-@SpringBootApplication(scanBasePackages = "com.itcast.account")
-@EnableApolloConfig
-public class AccountApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(AccountApplication.class, args);
- }
-}
-```
-
-
-#### 应用配置
-
-1. 将local-config/account.properties中的配置添加到apollo中
-
- ```properties
- swagger.enable=true
- sms.enable=true
-
- spring.http.encoding.charset=UTF-8
- spring.http.encoding.force=true
- spring.http.encoding.enabled=true
- server.use-forward-headers=true
- server.tomcat.protocol_header=x-forwarded-proto
- server.servlet.context-path=/account-service
- server.tomcat.remote_ip_header=x-forwarded-for
-
- spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- spring.datasource.druid.stat-view-servlet.allow=127.0.0.1,192.168.163.1
- spring.datasource.druid.web-stat-filter.session-stat-enable=false
- spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
- spring.datasource.druid.max-active=20
- spring.datasource.druid.stat-view-servlet.reset-enable=false
- spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
- spring.datasource.druid.stat-view-servlet.enabled=true
- spring.datasource.druid.web-stat-filter.enabled=true
- spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
- spring.datasource.druid.stat-view-servlet.deny=192.168.1.73
- spring.datasource.url=jdbc\:mysql\://127.0.0.1\:3306/p2p_account?useUnicode\=true
- spring.datasource.druid.filters=config,stat,wall,log4j2
- spring.datasource.druid.test-on-return=false
- spring.datasource.druid.web-stat-filter.profile-enable=true
- spring.datasource.druid.initial-size=5
- spring.datasource.druid.min-idle=5
- spring.datasource.druid.max-wait=60000
- spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
- spring.datasource.druid.pool-prepared-statements=true
- spring.datasource.druid.test-while-idle=true
- spring.datasource.password=pbteach0430
- spring.datasource.username=root
- spring.datasource.druid.stat-view-servlet.login-password=admin
- spring.datasource.druid.stat-view-servlet.login-username=admin
- spring.datasource.druid.web-stat-filter.url-pattern=/*
- spring.datasource.druid.time-between-eviction-runs-millis=60000
- spring.datasource.druid.min-evictable-idle-time-millis=300000
- spring.datasource.druid.test-on-borrow=false
- spring.datasource.druid.web-stat-filter.principal-session-name=admin
- spring.datasource.druid.filter.stat.log-slow-sql=true
- spring.datasource.druid.web-stat-filter.principal-cookie-name=admin
- spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
- spring.datasource.druid.aop-patterns=com.pbteach.wanxinp2p.*.service.*
- spring.datasource.druid.filter.stat.slow-sql-millis=1
- spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
- ```
-
-
-
-
-
-2. spring-http命名空间在之前已通过关联公共命名空间添加好了,现在来添加spring-boot-druid命名空间
-
-
-
-3. 添加本地文件中的配置到对应的命名空间,然后发布配置
-
-
-
-4. 在account-service/src/main/resources/application.properties中配置apollo.bootstrap.namespaces需要引入的命名空间(上面写过)
-
- ```properties
- # 指定读哪个应用的配置(必须写在配置文件里,无法写在apollo里,因为配置文件里如果没有写,都不知道读apollo里的哪个应用)
- app.id=account-service
- # 下面这个配置是开启Apollo的客户端,使其生效
- apollo.bootstrap.enabled = true
-
- # namespace以逗号分隔
- apollo.bootstrap.namespaces = application,micro_service.spring-boot-http,spring-rocketmq,micro_service.spring-boot-druid
-
- server.port=63000
- ```
-
-5.写一个测试Controller
-
- ```java
-
-package cn.itcast.account;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-public class AccountController {
-
- @Value("${sms.enable}")
- private Boolean smsEnable;
-
- @GetMapping("/hi")
- public String hi() {
- return "hi";
- }
-
- @GetMapping("/sms")
- public String getSmsConfig() {
- return "smsEnable: " + smsEnable;
- }
-
- @Value("${rocketmq.name-server}")
- private String mqNameServer;
-
- @Value("${rocketmq.producer.group}")
- private String mqProducerGroup;
-
- @GetMapping("/mq")
- public String getRocketMQConf() {
- return mqNameServer + ": " + mqProducerGroup;
- }
-
- @Value("${timeout}")
- private Long timeout;
-
- @GetMapping("/timeout")
- public Long getTimeout() {
- return timeout;
- }
-
- @GetMapping("/db-url")
- public String getDBConfig(@Value("${spring.datasource.url}") String url) {
- return url;
- }
-
-}
-
- ```
-
-
-
-#### 读取配置
-
-1. 启动应用
-
-2. 访问:[http://127.0.0.1:63000/account-service/hi](http://127.0.0.1:63000/account-service/hi),确认Spring Boot中配置的context-path是否生效
-
-通过/account-service能正常访问,说明apollo的配置已生效
-
-
-
-3. 确认spring-boot-druid配置
-
-* 为了快速确认可以在AccountController中通过@Value获取来验证
-
-* 访问[http://127.0.0.1:63000/account-service/db-url](http://127.0.0.1:63000/account-service/db-url),显示结果
-
-
-
-#### 创建其它项目
-
-参考account-service将其它项目也创建完成。
-
-### 生产环境部署
-
-当一个项目要上线部署到生产环境时,项目的配置比如数据库连接、RocketMQ地址等都会发生变化,这时候就需要通过Apollo为生产环境添加自己的配置。
-
-#### 企业部署方案
-
-在企业中常用的部署方案为:Apollo-adminservice和Apollo-configservice两个服务分别在线上环境(pro),仿真环境(uat)和开发环境(dev)各部署一套,Apollo-portal做为管理端只部署一套,统一管理上述三套环境。
-
-具体如下图所示:
-
-
-
-下面以添加生产环境部署为例
-
-#### 创建数据库
-
-创建生产环境的ApolloConfigDB:**每添加一套环境就需要部署一套ApolloConfgService和ApolloAdminService**
-
-source apollo/ApolloConfigDB\_PRO\_\_initialization.sql
-
-#### 配置启动参数
-
-1. 设置生产环境数据库连接
-
-2. 设置ApolloConfigService端口为:8081,ApolloAdminService端口为8091
-
- ```bash
- echo
-
- set url="localhost:3306"
- set username="root"
- set password="123456"
-
- start "configService-PRO" java -Dserver.port=8081 -Xms256m -Xmx256m -Dapollo_profile=github -Dspring.datasource.url=jdbc:mysql://%url%/ApolloConfigDBPRO?characterEncoding=utf8 -Dspring.datasource.username=%username% -Dspring.datasource.password=%password% -Dlogging.file=.\logs\apollo-configservice.log -jar .\apollo-configservice-1.3.0.jar
- start "adminService-PRO" java -Dserver.port=8091 -Xms256m -Xmx256m -Dapollo_profile=github -Dspring.datasource.url=jdbc:mysql://%url%/ApolloConfigDBPRO?characterEncoding=utf8 -Dspring.datasource.username=%username% -Dspring.datasource.password=%password% -Dlogging.file=.\logs\apollo-adminservice.log -jar .\apollo-adminservice-1.3.0.jar
- ```
-
-
-
-1. 运行runApollo-PRO.bat【关于这个bat的说明在上面已经说过】
-
-#### 修改Eureka地址
-
-因为上一套**ApolloConfgService和ApolloAdminService**里的Eureka占用了8080端口,所以这里第二套需要改一下端口
-
-更新生产环境Apollo的Eureka地址:
-
-```sql
-USE ApolloConfigDBPRO;
-
-UPDATE ServerConfig SET `Value` = "http://localhost:8081/eureka/" WHERE `key` = "eureka.service.url";
-```
-
-
-
-#### 调整ApolloPortal服务配置
-
-服务配置项统一存储在ApolloPortalDB.ServerConfig表中,可以通过`管理员工具 - 系统参数`页面进行配置:apollo.portal.envs - 可支持的环境列表
-
-
-
-默认值是dev,如果portal需要管理多个环境的话,以逗号分隔即可(大小写不敏感),如:
-
- dev,pro
-
-
-
-#### 启动ApolloPortal
-
-Apollo Portal需要在不同的环境访问不同的meta service(apollo-configservice)地址,所以我们需要在配置中提供这些信息。
-
-```bash
--Ddev_meta=http://localhost:8080/ -Dpro_meta=http://localhost:8081/
-```
-
-
-1. 关闭之前启动的ApolloPortal服务,使用runApolloPortal.bat启动多环境配置
-
- ```bash
- echo
-
- set url="localhost:3306"
- set username="root"
- set password="mysql"
-
- start "ApolloPortal" java -Xms256m -Xmx256m -Dapollo_profile=github,auth -Ddev_meta=http://localhost:8080/ -Dpro_meta=http://localhost:8081/ -Dserver.port=8070 -Dspring.datasource.url=jdbc:mysql://%url%/ApolloPortalDB?characterEncoding=utf8 -Dspring.datasource.username=%username% -Dspring.datasource.password=%password% -Dlogging.file=.\logs\apollo-portal.log -jar .\apollo-portal-1.3.0.jar
- ```
-
-
-
-1. 启动之后,点击account-service服务配置后会提示环境缺失,此时需要补全上边新增生产环境的配置
-
-
-
-3. 点击左下方的补缺环境
-
-
-
-4. 补缺过生产环境后,切换到PRO环境后会提示有Namespace缺失,点击补缺
-
-
-
-5. 从dev环境同步配置到pro
-
-
-
-#### 验证配置
-
-1. 同步完成后,切换到pro环境,修改生产环境rocketmq地址后发布配置
-
-
-
-2. 配置项目使用pro环境,测试配置是否生效
-
-* 在apollo-env.properties中增加pro.meta=[http://localhost:8081](http://localhost:8081)
-
-* 修改account-service启动参数为:-Denv=pro
-
-```bash
- -Denv=pro -Dapollo.cacheDir=/opt/data/apollo-config -Dapollo.cluster=DEFAULT
-```
-
-* 访问[http://127.0.0.1:63000/account-service/mq](http://127.0.0.1:63000/account-service/mq) 验证RocketMQ地址是否为上边设置的PRO环境的值
-
-
-
-### 灰度发布
-
-#### 定义
-
- 灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。
-
-
-
-#### Apollo实现的功能
-
-1. 对于一些对程序有比较大影响的配置,可以先在一个或者多个实例生效,观察一段时间没问题后再全量发布配置。
-2. 对于一些需要调优的配置参数,可以通过灰度发布功能来实现A/B测试。可以在不同的机器上应用不同的配置,不断调整、测评一段时间后找出较优的配置再全量发布配置。
-
-#### 场景介绍
-
-apollo-quickstart项目有两个客户端:
-
-1. 172.16.0.160
-2. 172.16.0.170
-
-
-
-**灰度目标**
-
-当前有一个配置timeout=2000,我们希望对172.16.0.160灰度发布timeout=3000,对172.16.0.170仍然是timeout=2000。
-
-
-
-#### 创建灰度
-
-1. 点击application namespace右上角的`创建灰度`按钮
-
-
-
-2. 点击确定后,灰度版本就创建成功了,页面会自动切换到`灰度版本`Tab
-
-
-
-#### 灰度配置
-
-1. 点击`主版本的配置`中,timeout配置最右侧的`对此配置灰度`按钮
-
-2. 在弹出框中填入要灰度的值:3000,点击提交
-
-
-
-#### 配置灰度规则
-
-1. 切换到`灰度规则`Tab,点击`新增规则`按钮
-
-
-
-2. 在弹出框中`灰度的IP`下拉框会默认展示当前使用配置的机器列表,选择我们要灰度的IP,点击完成
-
-
-
-如果下拉框中没找到需要的IP,说明机器还没从Apollo取过配置,可以点击手动输入IP来输入,输入完后点击添加按钮
-
-
-
-#### 灰度发布
-
-1. 启动apollo-quickstart项目的GrayTest类输出timeout的值
-
-vm options: `-Dapp.id=apollo-quickstart -Denv=DEV -Ddev_meta=http://localhost:8080`
-
-```java
- public class GrayTest {
-
- // VM options:
- // -Dapp.id=apollo-quickstart -Denv=DEV -Ddev_meta=http://localhost:8080
- public static void main(String[] args) throws InterruptedException {
- Config config = ConfigService.getAppConfig();
- String someKey = "timeout";
-
- while (true) {
- String value = config.getProperty(someKey, null);
- System.out.printf("now: %s, timeout: %s%n", LocalDateTime.now().toString(), value);
- Thread.sleep(3000L);
- }
- }
- }
-```
-
-
-
-2. 切换到`配置`Tab,再次检查灰度的配置部分,如果没有问题,点击`灰度发布`
-
-
-
-3. 在弹出框中可以看到主版本的值是2000,灰度版本即将发布的值是3000。填入其它信息后,点击发布
-
-
-
-4. 发布后,切换到`灰度实例列表`Tab,就能看到172.16.0.160已经使用了灰度发布的值
-
-
-
-
-
-
-
-#### 全量发布
-
-如果灰度的配置测试下来比较理想,符合预期,那么就可以操作`全量发布`。
-
-全量发布的效果是:
-
-1. 灰度版本的配置会合并回主版本,在这个例子中,就是主版本的timeout会被更新成3000
-2. 主版本的配置会自动进行一次发布
-3. 在全量发布页面,可以选择是否保留当前灰度版本,默认为不保留。
-
-
-
-#### 放弃灰度
-
-如果灰度版本不理想或者不需要了,可以点击`放弃灰度`
-
-
-
-#### 发布历史
-
-点击主版本的`发布历史`按钮,可以看到当前namespace的主版本以及灰度版本的发布历史
-
-
-
diff --git a/docs/Computer_NetWork/计算机网络-总结.md b/docs/Computer_NetWork/计算机网络-总结.md
deleted file mode 100644
index 8de79f6..0000000
--- a/docs/Computer_NetWork/计算机网络-总结.md
+++ /dev/null
@@ -1,829 +0,0 @@
----
-title: 计算机网络-总结篇
-tags:
- - 计算机网络
- - 面试
-categories:
- - 计算机网络
-keywords: 计算机网络,计网,面试
-description: 计算机网络-总结篇,可以用来期末复习,校招面试等。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/network.jpg'
-abbrlink: 3905e6f8
-date: 2020-04-16 17:21:58
----
-
-
-
-# 备注
-
-1、打【】的是有印象,能大致说出来即可,但是没打【】的也不要背,理解性记忆。
-
-2、如果有知识点,有的博客讲的很清楚,我就直接贴链接了。
-
-3、《计算机网络7》是一本很好的书,讲的很详细,而且不难懂,读者有时间的话,建议看一下。
-
-4、如果有错误,欢迎在评论区指正
-
-> 1、本人正在准备秋招,秋招完之后会持续更新博客。这些总结的部分,也是准备面试期间看了很多很多博客写下来的。
-> 2、本人博客:https://youthlql.gitee.io/
-> 3、等忙完秋招,会陆续更新一些内容。喜欢的朋友可以收藏一下博客
->
-
-# 补充
-
-这里面的就是讲的比较详细的博客
-
-## 关于计网比较好的博客
-
-https://blog.csdn.net/ThinkWon/article/details/104903925
-
-https://www.cnblogs.com/xjtu-lyh/p/12439036.html
-
-
-
-## 三次挥手,四次握手
-
-https://blog.csdn.net/qzcsu/article/details/72861891
-
-
-
-## DNS解析
-
-https://blog.csdn.net/weixin_40470303/article/details/80642190
-
-
-
-## http1.0,http1.1,http2.0介绍
-
-https://segmentfault.com/a/1190000016656529
-
-
-
-
-
-## https建立链接过程
-
-https://blog.csdn.net/iispring/article/details/51615631
-
-
-
-
-
-
-
-
-
-
-
-# OSI七层模型与TCP/IP 五层模型
-
-## 物理层
-
- 物理层考虑的是怎样才能在连接各种计算机的传输介质上传输数据比特流。现有的计算机网络中的硬件设备和传输媒体(介质)的种类非常多,而通信手段也有许多不同方式。物理层的作用正是要尽可能地屏蔽掉这些传输媒体和硬件设备的差异,使物理层上面的数据链路层感觉不到这些差异,这样就可使数据链路层只考虑如何完成本层的协议和服务,而不必考虑网络具体的传输媒体和通信手段是什么。
-
-> 参考《计算机网络7》P51
-
-
-
-## 数据链路层
-
-
-
-### 概念
-
- 数据链路层研究的是分组怎样从一台主机传送到另一台主机,但并不经过路由器转发。从整个互联网来看,局域网仍属于数据链路层的范围。数据传送单位是帧。
-
-**简单的过程:**
-
-1、在两个相邻节点之间传送数据时,数据链路层将网络层交下来的**IP 数据报**添加首部和尾部组装成帧**,**在两个相邻节点间的链路上传送帧**。**每一帧包括数据和必要的控制信息【如同步信息,地址信息,差错控制等】。
-
-2、数据链路层在收到一个帧后,通过控制信息检测收到的帧中是否有差错,如果没有就可从中提出**IP数据报**部分,上交给网络层**。**如果发现差错,数据链路层就简单地丢弃这个出了差错的帧,以避免继续在网络中传送下去白白浪费网络资源 【在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。控制信息还使接收端能够检测到所收到的帧中有误差错。如果发现差错,数据链路层就简单地丢弃这个出了差错的帧,以避免继续在网络中传送下去白白浪费网络资源。如果需要改正数据在链路层传输时出现差错(这就是说,数据链路层不仅要检错,而且还要纠错),那么就要采用可靠性传输协议来纠正出现的差错。这种方法会使链路层的协议复杂些】
-
-
-
-### 一些小细节
-
-
-
-**数据链路层使用的信道主要有两种:**
-
-①点对点信道
-
-②广播信道
-
- **三个基本问题:**
-
-封装成帧、透明传输和差错检测。
-
-> 更细致的看《计算机网络7》 P82
-
-
-
-
-
-## 网络层
-
-1、在计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点,确保数据及时传送。网络层向上只提供简单灵活的、无连接的、尽最大努力交付的IP数据报服务。【其实数据报或IP数据报就是我们经常使用的“分组”】
-
-2、网络层在发送分组时不需要先建立连接,没有给分组进行上编号,所传送的分组可能出错、丢失、重复和失序。如果主机(即端系统)中的进程之间的通信需要是可靠的,那么就由网络的主机中的运输层负责(包括差错处理、流量控制等)
-
-
-
-> 更多细节-->《计算机网络7》 P124
-
-
-
-
-
-## 运输层
-
-
-
-1、运输层的主要任务就是负责向两台主机的进程之间提供通用的数据传输服务。
-
-2、运输层有一个很重要的功能 复用和分用。这里的“复用"是指在发送方不同的应用进程都可以使用同一个运输层协议传送数据(当然需要加上适当的首部),而“分用”是指接收方的运输层在剥去报文的首部后能够把这些数据正确交付目的应用进程
-
-3、运输层向高层用户屏蔽了下面网络核心的细节【如网络拓扑、所采用的路由选择协议等】,**它使应用进程看见的就是好像在两个运输层实体之间有一条端到端的逻辑通信信道**。但这条逻辑通信信道对上层的表现却因运输层使用的不同协议而有很大的差别。当运输层釆用面向连接的TCP协议时,尽管下面的网络层是不可靠的(只提供尽最大努力服务),但这种逻辑通信信道就相当于一条全双工的可靠信道。但当运输层采用无连接的udp协议时,这种逻辑通信信道仍然是一条不可靠信道。
-
-
-
-
-
-## 应用层
-
-(在上一章,我们巳学习了运输层为应用进程提供了端到端的通信服务)。
-
-**1.** **不同的网络应用的应用进程之间,还需要有不同的通信规则。因此在运输层协议之上,还需要有应用层协议****。**
-
-**2.** **每个应用层协议都是为了解决某一类应用问题,**(而问题的解决又必须通过位于不同主机中的多个应用进程之间的通信和协同工作来完成)**。应用进程之间的这种通信必须遵循严格的规则。应用层的具体内容就是精确定义这些通信规则。**
-
-3.**运输层是两台主机间进程的交互。应用层是为了更加细化不同网络应用的交互规则。**
-
-
-
-
-
-# 常见应用层协议和运输层、网络层协议
-
-## 各层协议
-
-**应用层:**HTTP(超文本传输协议) ,DNS(域名系统) ,FTP(文件传输协议) ,SMTP(简单邮件传送协议)
-
-**运输层:**TCP ,UDP
-
-**网络层:** IP, ARP(地址解析协议)--> 见《计网7》P134
-
-
-
-## 硬件如路由器之类在哪一层
-
-- 路由器在网络层,用来进行路由选择
-
-
-
-# TCP与UDP区别和应用场景,基于TCP的协议有哪些,基于UDP的有哪些
-
-## 区别+应用场景
-
-
-
-**总结:**
-
-1、UDP的主要特点是
-
-①无连接②尽最大努力交付③面向报文④无拥塞控制⑤支持一对一,一对多,多对一和多对多的交互通信⑥首部开销小(只有四个字段:源端口,目的端口,长度和检验和)
-
-2、TCP的主要特点是
-
-①面向连接②每一条TCP连接只能是一对一的③提供可靠交付④提供全双工通信⑤面向字节流
-
-
-
-## 基于TCP的协议有哪些,基于UDP的有哪些
-
-**TCP:**
-
-HTTP, 超文本传输协议
-
-FTP, 文件传输协议
-
-SMTP,简单邮件传输协议,用来发送电子邮件
-
-SSH 安全外壳协议,用于加密安全登陆
-
-**UDP:**
-
-DHCP协议:动态主机配置协议,动态配置IP地址
-
-NTP协议:网络时间协议,用于网络时间同步
-
-RIP(路由选择协议)
-
-DNS
-
-
-
-# TCP可靠传输的保证,拥塞控制目的和过程
-
-## 如何保证可靠传输
-
-TCP通过三次握手建立可靠连接
-
-①数据被分割成 TCP 认为最适合发送的数据包。TCP 给发送的每一个包进行编号,接收方对数据包进行排序,将有序数据传送给应用层。**TCP通过序列号和确认应答提高可靠性**
-
-②校验和: TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到端的校验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
-
-③流量控制: TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。
-
-④拥塞控制: 当网络拥塞时,减少数据的发送。
-
-⑤ARQ协议:分为停止等待ARQ协议和连续ARQ协议
-
-5.1 它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
-
-5.2 超时重传::当 TCP 发出一个报文段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
-
-5.3 TCP 的接收端会丢弃重复的数据
-
-
-
-## ARQ
-
-- 停止等待ARQ:它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组。停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重转时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为自动重传请求ARQ。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。
-
-- 连续ARQ协议:(流水线的传输方式)可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。
-
-
-
-
-
-## 拥塞控制
-
-**目的:**
-
-拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
-
-
-
-**过程:**
-
-为了进行拥塞控制,TCP 发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。**慢开始和拥塞避免都是基于窗口的拥塞控制**。
-
-
-
-**区别:**
-
-> https://blog.csdn.net/ligupeng7929/article/details/79597423
-
-* **慢开始:** 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd初始值为1,每经过一个传播轮次,cwnd加倍。
-* **拥塞避免:** 拥塞避免算法的思路是让拥塞窗口cwnd缓慢增大,即每经过一个往返时间RTT就把发送放的cwnd加1.
-
-- 为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量(如何设置ssthresh)。慢开始门限ssthresh的用法如下:
-
- 当 cwnd < ssthresh 时,使用上述的慢开始算法。
-
- 当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
-
- 当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞控制避免算法。
-
-- 拥塞避免算法:让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。
-
-**快重传和快恢复:**
-
-1、**快重传**算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时才进行捎带确认。
-
-2、发送方知道现在只是丢失了个别的报文段。于是不启动慢开始,而是执行**快恢复算法**。这时,发送方调整门限值ssthresh= (cwnd/2=8,,同时设置拥塞窗口cwnd = ssthresh=8 ,并开始执行拥塞避免算法。
-
-3、请注意,也有的快恢复实现是把快恢复开始时的拥塞窗口cwnd值再增大一一些(增大3个报文段的长度),即等于新的ssthresh + 3 x MSS。这样做的理由是:既然发送方收到3个重复的确认,就表明有3个分组已经离开了网络。这3个分组不再消耗网络的资源而是停留在接收方的缓存中(接收方发送出3个重复的确认就证明了这个事实)。可见现在网络中并不是堆积了分组而是减少了3个分组。因此可以适当把拥塞窗口扩大些。
-
-
-
-在采用快恢复算法时,慢开始算法只是在TCP连接建立时和网络出现超时时才使用。
-
-
-
-
-
-
-
-
-
-## 为什么要进行流量控制
-
-一般来说,我们总是希望数据传输更快一些。但如果发送方把数据发送的过快,接收方就可能来不及接收,这就会造成数据的丢失(丢包)。
-
-
-
-
-
-# TCP粘包现象原因和解决方法
-
-
-
-## 原因
-
-1.UDP协议的保护消息边界使得每一个消息都是独立的
-
-2.而tcp是基于流的传输,流传输却把数据当作一串数据流,他不认为数据是一个一个的消息
-
-3.发送端需要等缓冲区满才发送出去,造成粘包
-
-4.接收方不及时接收缓冲区的包,造成多个包粘包
-
-具体点:
-
-(1)发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
-
-(2)接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。
-
-
-
-## 解决方法
-
-(1)对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP程序收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;
-
-(2)对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象;
-
-(3)由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。
-
-
-
-## 为什么粘包需要处理?
-
-不是所有的粘包现象都需要处理,若传输的数据为不带结构的连续流数据(如文件传输),则不必把粘连的包分开(简称分包)。但在实际工程应用中,传输的数据一般为带结构的数据,这时就需要做分包处理。分包一般难度较大,所以尽量避免粘包
-
-
-
-
-
-# 三次握手相关问题
-
-
-
-## 过程+状态改变
-
-把**补充**里面的**第二个博客**的过程背下来。(有的地方需要参考第一个博客)
-
-
-
-## 为什么三次,两次为什么不行?
-
-### 第一种答案
-
-两次握手只能保证单向连接是畅通的。只有经过第三次握手,才能确保双向都可以接收到对方的发送的 数据。两次握手接收方这里不能确定自己的的发送是正常的,发送方的接收是正常的。
-
-**具体点:**
-
-**三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。**
-
-第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
-
-第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
-
-第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
-
-所以三次握手就能确认双发收发功能都正常,缺一不可。
-
-
-
-### 第二种答案
-
- 一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
-
-
-
-
-
-
-
-## 如果已经建立了连接,但是客户端突然出现故障了怎么办?
-
-
-
-互联网(或互连网):多个网络通过路由器互连起来,这样就构成了一个覆盖范围更大的网络,即互连网(互联网)。因此,互联网又称为“网络的网络(Network of Networks)”。
-
-
-
-因特网:因特网(Internet)是世界上最大的互连网络(用户数以亿计,互连的网络数以百万计)。
-
-
-
-
-
-> **internet与Internet的区别**
->
-> * internet(互联网或互连网)是一个通用名词,它泛指多个计算机网络互连而成的网络。在这些网络之间的通信协议可以是任意的。
-> * Internet(因特网)则是一个专用名词,它指当前全球最大的、开放的、由众多网络互连而成的特定计算机网络,它采用TCP/IP协议族作为通信的规则,其前身是美国的ARPANET。
->
-
-
-
-### 因特网发展的三个阶段
-
-
-
-
-
-**因特网服务提供者`ISP`(`I`nternet `S`ervice `P`rovider)**
-
-
-
-> Q:普通用户是如何接入到因特网的呢?
->
-> A:通过ISP接入因特网
->
-> ISP可以从因特网管理机构申请到成块的IP地址,同时拥有通信线路以及路由器等联网设备。任何机构和个人只需缴纳费用,就可从ISP的得到所需要的IP地址。这一点很重要。因为因特网上的主机都必须有IP地址才能进行通信,这样就可以通过该ISP接入到因特网
->
-
-
-
-**中国的三大`ISP`:中国电信,中国联通,中国移动**
-
-
-
-
-
-**基于ISP的三层结构的因特网**
-
-
-
-1、图中红线部分是两个私人用户进行通信的大致链路,可以看到需要经过多个ISP层级
-
-2、一旦某个用户能够接入到因特网,那么他也可以成为一个ISP,所需要做的就是购买一些如调制解调器或路由器这样的设备,让其他用户可以和他相连。
-
-3、第一层ISP可以直接互联,第二层ISP和一些大公司都是第一层ISP的用户。
-
-
-
-
-
-### 因特网的标准化工作
-
-* 因特网的标准化工作对因特网的发展起到了非常重要的作用。
-* 因特网在指定其标准上的一个很大的特点是**面向公众。**
- * 因特网所有的**RFC**(Request For Comments)技术文档都可从因特网上免费下载;
- * 任何人都可以随时用电子邮件发表对某个文档的意见或建议。
-* **因特网协会ISOC**是一个国际性组织,它负责对因特网进行全面管理,以及在世界范围内促进其发展和使用。
- * 因特网体系结构委员会IAB,负责管理因特网有关协议的开发;
- * 因特网工程部IETF,负责研究中短期工程问题,主要针对协议的开发和标准化;
- * 因特网研究部IRTF,从事理论方面的研究和开发一些需要长期考虑的问题。
-
-
-
-* 制订因特网的正式标准要经过一下**4个阶段**:
-
- 1、因特网草案(在这个阶段还不是RFC文档)
-
- 2、建议标准(从这个阶段开始就成为RFC文档)
-
- 3、草案标准
-
- 4、因特网标准
-
-
-
-### 因特网的组成
-
-* 边缘部分
-
- 由所有连接在因特网上的**主机**组成(台式电脑,大型服务器,笔记本电脑,平板,智能手机,智能手表,以及物联网智能硬件等)。这部分是**用户直接使用**的,用来进行**通信**(传送数据、音频或视频)和**资源共享**。
-
-* 核心部分
-
- 由**大量网络**和连接这些网络的**路由器**组成。这部分是**为边缘部分提供服务**的(提供连通性和交换)。
-
-
-
-1. 路由器是一种专用计算机,但我们不称它为主机,路由器是实现分组交换的关键构建,其任务是转发收到的分组,这是网络核心最重要的部分。
-
-
-
-## 三种交换方式
-
-网络核心部分是互联网中最复杂的部分。
-
-网络中的核心部分要向网络边缘中的大量主机提供连通性,使边缘部分中的任何一个主机都能够向其他主机通信(即传送或接收各种形式的数据)。
-
-在网络核心部分起特殊作用的是**路由器**(router)。
-
-**路由器**是实现**分组交换** (packet switching) 的关键构件,其任务是转发收到的分组,这是网络核心部分最重要的功能。
-
-### 电路交换
-
-
-
-1. 传统两两相连的方式,当电话数量很多时,电话线也很多,就很不方便
-2. 所以要使得每一部电话能够很方便地和另一部电话进行通信,就应该使用一个**中间设备**将这些电话连接起来,这个中间设备就是**电话交换机**
-
-
-
-* 电话交换机接通电话线的方式称为电路交换;
-
-* 从通信资源的分配角度来看,交换(Switching)就是按照某种方式动态地分配传输线路的资源;
-
-* 电路交换的三个步骤:
-
- 1、建立连接(分配通信资源)
-
- 2、通话(一直占用通信资源)
-
- 3、释放连接(归还通信资源)
-
-
-
-1. 当使用电路交换来传送计算机数据时,其线路的传输效率往往很低。这是因为计算机数据是突发式地出现在传输线路上的,而不是像打电话一样一直占用着通信资源。
-2. 试想一下这种情况,当用户正在输入和编辑一份待传输的文件时,用户所占用的通信资源暂时未被利用,该通信资源也不能被其它用户利用,宝贵的通信线路资源白白被浪费了
-3. 因此计算机通常采用的是**分组交换**,而不是线路交换
-
-### 分组交换
-
-
-
-1. 通常我们把表示该消息的整块数据成为一个**报文**。
-2. 在发送报文之前,先把较长的报文划分成一个个更小的等长数据段,在每一个数据段前面。加上一些由必要的控制信息组成的首部后,就构成一个分组,也可简称为“包”,相应地,首部也可称为“包头”。首部起到了很大的作用,首先首部中肯定包含了分组的目的地址,否则分组传输路径中的各分组交换机(也就是个路由器)就不知道如何转发分组了
-3. 分组从源主机到目的主机,可走不同的路径(也就是不同的路由)。
-4. 分组乱序:也就是分组到达目的站的顺序不一定与分组在源站的发送顺序相同。
-5. 分组也可能出现丢失,误码,重复等问题(后面介绍)
-
-
-
-**分组交换过程中各角色的功能**
-
-1、发送方
-
-* 构造分组
-* 发送分组
-
-2、路由器
-
-* 缓存分组
-* 转发分组
-
-
-
-路由器处理分组的过程是:分组交换机收到一个分组后,先将分组暂存下来,按照首部中的目的地址进行查表转发,找到合适的转发接口,通过该接口将分组转发给下一个分组交换机
-
-3、接收方
-
-* 接收分组
-* 还原报文
-
-
-
-### 报文交换
-
-报文交换中的交换结点也采用存储转发方式,但报文交换对报文的大小没有限制,这就要求交换结点需要较大的缓存空间。报文交换主要用于早期的电报通信网,现在较少使用,通常被较先进的分组交换方式所取代。因此,不再详细介绍报文交换。
-
-
-
-### 三种交换方式的对比
-
-假设A,B,C,D是分组传输路径所要经过的4个结点交换机,纵坐标为时间
-
-
-
-
-
-电路交换:
-
-* 通信之前首先要建立连接;连接建立好之后,就可以使用已建立好的连接进行数据传送;数据传送后,需释放连接,以归还之前建立连接所占用的通信线路资源。
-
-* 一旦建立连接,中间的各结点交换机就是直通形式的,比特流可以直达终点;
-
-报文交换:
-
-* 可以随时发送报文,而不需要事先建立连接;整个报文先传送到相邻结点交换机,**全部存储**下来后进行查表转发,转发到下一个结点交换机。
-* **整个报文**需要在各结点交换机上进行存储转发,由于不限制报文大小,因此需要各结点交换机都具有较大的缓存空间。
-
-分组交换:
-
-* 可以随时发送分组,而不需要事先建立连接。构成原始报文的**一个个分组**,**依次**在各结点交换机上存储转发。各结点交换机在发送分组的同时,还缓存接收到的分组。
-* **构成原始报文的一个个分组**,在各结点交换机上进行存储转发,相比报文交换,减少了转发时延,还可以避免过长的报文长时间占用链路,同时也有利于进行差错控制。
-
-
-
-
-
-## 计算机网络的定义和分类
-
-### 定义
-
-* 计算机网络的精确定义并未统一,随着网络的发展,给出了不同的定义,这些定义反映了当时网络技术发展的水平
-* 计算机网络的最简单的定义是:一些互相连接的、自治的计算机的集合。
- * 互连:是指计算机之间可以通过有线或无线的方式进行数据通信;
- * 自治:是指独立的计算机,他有自己的硬件和软件,可以单独运行使用;
- * 集合:是指至少需要两台计算机;
-* 计算机网络的较好的定义是:计算机网络主要是由一些通用的,可编程的硬件(一定包含有中央处理机CPU)互连而成的,而这些硬件并非专门用来实现某一特定目的(例如,传送数据或视频信号)。这些可编程的硬件能够用来传送多种不同类型的数据,并能支持广泛的和日益增长的应用。
- * 计算机网络所连接的硬件,并不限于一般的计算机,而是包括了智能手机等智能硬件。
- * 计算机网络并非专门用来传送数据,而是能够支持很多种的应用(包括今后可能出现的各种应用)。
-
-
-
-上图所示的各终端机只是具有显示和输入设备的终端,而并不是自治的计算机,所以上图并不是计算机网络,只是一个运行分时系统的大型机系统。
-
-### 分类
-
-**按交换技术分类:**
-
-* 电路交换网络
-* 报文交换网络
-* 分组交换网络
-
-**按使用者分类:**
-
-* 公用网,公用是指电信公司出资建造的大型网络,公用的意思是所有愿意按电信公司规定交纳费用的人都可以使用这种网络
-* 专用网,是指某个单位为单位内部建立的网络,不对本单位意外提供服务,例如军队,铁路,电力等系统均有本系统的专用网
-
-**按传输介质分类:**
-
-* 有线网络,包括双绞线网络,光纤网络等
-* 无线网络,比如wifi
-
-**按覆盖范围分类:**
-
-* 广域网WAN(Wide Area Network)
-
-作用范围通常为几十到几千公里,因而有时也称为远程网。广域网是互联网的核心部分,其任务是通过长距离(例如,跨越不同的国家)运送主机所发送的数据。
-
-* 城域网MAN
-
-作用范围一般是一个城市,可跨越几个街区甚至整个城市,作用距离通常为5-50公里
-
-* 局域网LAN
-
-一般用微型计算机或工作站通过高速通信线路相连(速率通常在 10 Mbit/s 以上),但地理上范围较小(1 km 左右),比如一栋实验楼,一个校园等
-
-* 个域网PAN
-
-就是在个人工作的地方把个人使用的电子设备用无线技术连接起来的网络。(比如笔记本,耳机,键盘等),覆盖范围大约为10米
-
-**按拓扑结构分类:**
-
-* 总线型网络
-
-
-
-优点:建网容易,增减节点方便,节省线路
-
-缺点:重负载是通信效率不高,总线任意一处出现故障,则全网瘫痪。
-
-* 星型网络
-
-
-
-星型网络图中的中央设备现在一般是交换机或路由器,
-
-优点:这种网络拓扑便于网络的集中控制和管理,因为端用户都要通过中央设备
-
-缺点:成本高,中央设备对故障敏感
-
-
-
-* 环形网络
-
-
-
-环中信号是单向传输的
-
-* 网状型网络
-
-
-
-一般情况下,每个节点至少由两条路径与其他节点相连,多用在广域网中
-
-优点:可靠性高
-
-缺点:控制复杂,线路成本高
-
-
-
-## 计算机网络的性能指标
-
-### 速率
-
-
-
-### 带宽
-
-
-
-### 吞吐量
-
-
-
-带宽1 Gb/s的以太网,代表其额定速率是1 Gb/s,这个数值也是该以太网的吞吐量的上限值。实际上,对于带宽1 Gb/s的以太网,可能实际吞吐量只有 700 Mb/s,甚至更低。
-
-### 时延
-
-时延时指数据(一个报文或分组,甚至比特)从网络(或链路)的一端传送到另一端所需的时间。
-
-我们来看看分组从源主机传送给目的主机的过程中,都会在哪些地方产生时延
-
-* 发送时延:源主机将分组发往传输线路所需的时间。
-
-* 传播时延:代表分组的电信号在链路上传输所需的时间。
-
-* 处理时延:路由器在收到分组后,对其进行存储转发所花费的时间
-
-> 有的教材中还有一个排队时延,本课程将排队时延与处理时延合并称为处理时延
-
-网卡的发送速率,信道带宽,交换机的接口速率,它们共同决定着主机的发送速率。
-
-
-
-当处理时延忽略不计时,发送时延和传播时延谁占主导,要具体情况具体分析
-
-
-
-### 时延带宽积
-
-
-
-
-
-### 往返时间
-
-
-
-
-
-### 利用率
-
-
-
-
-
-### 丢包率
-
-
-
-
-
-
-
-## 计算机网络体系结构
-
-### 常见的计算机网络体系结构
-
-
-
-1、如今用的最多的是TCP/IP体系结构,现今规模最大的、覆盖全球的、基于TCP/IP的互联网并未使用OSI标准。TCP/IP体系结构相当于将OSI体系结构的物理层和数据链路层合并为了网络接口层,并去掉了会话层和表示层。
-
-2、TCP/IP在网络层使用的协议是IP协议,IP协议的意思是网际协议,因此TCP/IP体系结构的网络层称为网际层
-
-
-
-
-
-1、在用户主机的操作系统中,通常都带有符合TCP/IP体系结构标准的TCP/IP协议族。而用于网络互连的路由器中,也带有符合TCP/IP体系结构标准的TCP/IP协议族。只不过路由器一般只包含网络接口层和网际层。
-
-2、
-
-**网络接口层**:并没有规定具体内容,这样做的目的是可以互连全世界各种不同的网络接口,例如:有线的以太网接口,无线局域网的WIFI接口等。因此本质上TCP/IP协议体系结构只有上面的三层。
-
-**网际层**:核心协议是IP协议。
-
-**运输层**:TCP和UDP是这层的两个重要协议。
-
-**应用层**: 包含了大量的应用层协议,如 HTTP , DNS 等。
-
-
-
-3、IP协议可以将不同的网络接口(网络接口层)进行互连,并向其上的TCP协议和UDP协议提供网络互连服务。
-
-- 而TCP协议在享受IP协议提供的网络互连服务的基础上,可向应用层的相应协议提供可靠的传输服务。
-
-- UDP协议在享受IP协议提供的网络互连服务的基础上,可向应用层的相应协议提供不可靠的传输服务。
-
-
-
-4、TCP/IP体系结构中最重要的是**IP协议**和**TCP协议**,因此用TCP和IP来表示整个协议大家族。
-
-
-
-
-
-
-
-
-
-由于TCP/IP体系结构为了将不同的网络接口进行互连,因此它的网络接口层并没有规定什么具体的内容。然而,这对于我们学习计算机网络的完整体系而言就会缺少一部分内容,因此学习计算机体系结构时往往采取折中的办法。也就是综合OSI和TCP/IP的优点,采用一种5层体系的原理结构。
-
-### 分层的必要性
-
-
-
-**物理层问题**
-
-
-
-
-
-严格来说,传输媒体并不属于物理层。计算机传输的信号,并不是图示的方波信号,这样举例只是让初学者容易理解
-
-**数据链路层问题**
-
-
-
-1、如图所示,主机A要给主机C发送数据。
-
-2、但是表示数据的信号会通过总线传播到总线上的每一个主机
-
-3、那么问题来了,主机C是如何知道该数据是发送给自己的,自己要接受?而主机B,D,E又如何知道该数据不删发送给自己的,自己应该拒绝呢?
-
-4、这就很自然的引出了下面几个问题
-
-Q:如何标识网络中的各主机(也就是主机编址问题,例如MAC地址)?
-
-A:主机在发送数据时,应该给数据附加上目的地址,当其他主机收到后,根据目的地址和自身地址来决定是否接受数据。
-
-
-
-Q:目的主机如何从信号所表示的一连串比特流中区分出地址和数据?
-
-A:也就是需要解决分组的封装格式问题
-
-
-
-Q:另外对于总线型的网络,还会出现下面这种典型的问题
-
-
-
-
-
-某个时刻,总线是空闲的。 片刻之后,主机B和D同时向总线发送数据,这必然造成信号碰撞。不过这种总线型的网络早已淘汰,现在常用的是使用以太网交换机将多台主机互连形成的交换式以太网。那么,以太网交换机又是如何实现的呢?
-
-
-
-**网络层问题**
-
-> 到这里可能会发现,只要解决了物理层和数据链路层各自所面临的问题,我们就可以实现分组在**一个网络**上传输了。但是,我们每天都会使用的因特网是由**非常多的网络**和路由器互连起来的,仅解决物理层和数据链路层的问题还是不能正常工作。
-
-
-
-
-
-
-
-**运输层问题**
-
-1、假设这台主机中运行着两个与网络通信有关的应用进程(QQ和谷歌浏览器),这台服务器中运行着与网络通信相关的服务器进程。某个时刻,主机收到了来自服务器的分组,那么这些分组应该交给浏览器进程处理呢,还是应该交给QQ进程处理?
-
-- 引出了我们如何标识与网络通信相关的引用进程,进而解决进程之间基于网络通信的问题。
-
-
-
-
-
-**应用层问题**
-
-
-
-
-
-**总结**
-
-
-
-
-
-### 分层思想举例
-
-> 此例子只是简单的例子,后续可能会写一篇详细的。
-
-假设网络拓扑如下所示
-
-
-
-
-
-**解析:**
-
-主机和Web服务器之间基于网络的通信,实际上是主机中的浏览器应用进程与Web服务器中的Web服务器应用进程之间基于网络的通信
-
-
-
-
-
-**各层在整个过程中起到怎样的作用?**
-
-**1、应用层**
-
-- 应用层按照HTTP协议的规定构建一个HTTP请求报文
-
-- 应用层将HTTP请求报文交付给运输层处理
-
-
-
-
-
-
-
-**2、运输层**
-
-- 运输层给HTTP请求报文添加一个TCP首部,使之成为TCP报文段
-
-- TCP报文段的首部格式作用是区分应用进程以及实现可靠传输
-
-- 运输层将TCP报文段交付给网络层处理
-
-
-
-
-
-**3、网络层**
-
-- 网络层给TCP报文段添加一个IP首部,使之成为IP数据报
-
-- IP数据报的首部格式作用是使IP数据报可以在互联网传输,也就是被路由器转发
-
-- 网络层将IP数据报交付给数据链路层处理
-
-
-
-
-
-**4、数据链路层**
-
-
-
-- 数据链路层给IP数据报添加一个首部和一个尾部,使之成为帧 (图示右边为首部,左边为尾部)
-
-- 该首部的作用主要是为了让帧能够在一段链路上或一个网络上传输,能够被相应的目的主机接收
-
-- 该尾部的作用是让目的主机检查所接收到的帧是否有误码
-
-- 数据链路层将帧交付给物理层
-
-
-
-
-
-
-
-**5、物理层**
-
-* 物理层先将帧看做是比特流,这里的网络N1假设是以太网,所以物理层还会给该比特流前面添加前导码
-* 前导码的作用是为了让目的主机做好接收帧的准备
-* 物理层将装有前导码的比特流变换成相应的信号发送给传输媒体
-
-
-
-
-
-**6、路由器**
-
-- 信号通过传输媒体到达路由器
-
-
-
-
-
-7、**路由器转发**
-
-
-
-在路由器中
-
-* 物理层将信号变为比特流,然后去掉前导码后,将其交付给数据链路层
-* 数据链路层将帧的首部和尾部去掉后,将其交付给网络层,这实际交付的是IP数据报
-* 网络层解析IP数据报的首部,从中提取目的网络地址
-
-* 提取目的网络地址后查找自身路由表。确定转发端口, 以便进行转发
-* 网络层将IP数据报交付给数据链路层
-* 数据链路层给IP数据报添加一个首部和一个尾部,使之成为帧
-* 数据链路层将帧交付给物理层
-* 物理层先将帧看成比特流,这里的网络N2假设是以太网,所以物理层还会给该比特流前面添加前导码
-* 物理层将装有前导码的比特流变换成相应的信号发送给传输媒体,信号通过传输媒体到达Web服务器
-
-
-
-
-
-
-
-**8、接收方接收**
-
-在Web 服务器上
-
-* 物理层将信号变换为比特流,然后去掉前导码后成为帧,交付给数据链路层
-* 数据链路层将帧的首部和尾部去掉后成为IP数据报,将其交付给网络层
-* 网络层将IP数据报的首部去掉后成为TCP报文段,将其交付给运输层
-* 运输层将TCP报文段的首部去掉后成为HTTP请求报文,将其交付给应用层
-* 应用层对HTTP请求报文进行解析,然后给主机发回响应报文
-
-**发回响应报文的步骤和之前过程类似**
-
-
-
-### 专用术语
-
-以下介绍的专用术语来源于OSI的七层协议体系结构,但也适用于TCP/IP的四层体系结构和五层协议体系结构
-
-**实体**
-
-
-
-
-
-**协议**
-
-
-
-1、协议:控制两个对等实体进行逻辑通信的规则的集合
-
-2、之所以称为逻辑通信,是因为这种通信其实并不存在,它只是我们假设出来的一种通信,目的在于方便我们单独研究体系结构某一层时而不用考虑其它层
-
-3、协议三要素:
-
-* 语法:定义所交换信息的格式
-* 语义:定义收发双方所要完成的操作
-* 同步:定义收发双发的时序关系
-
-
-
-**服务**
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/ElasticSearch/ElasticSearch-入门.md b/docs/ElasticSearch/ElasticSearch-入门.md
deleted file mode 100644
index f7e2553..0000000
--- a/docs/ElasticSearch/ElasticSearch-入门.md
+++ /dev/null
@@ -1,651 +0,0 @@
----
-title: ElasticSearch-入门篇
-tags:
- - ElasticSearch
- - ELK
- - 全文检索
-categories:
- - ElasticSearch
- - 用法
-keywords: ElasticSearch,全文检索
-description: ElasticSearch-入门篇,适合做入门,或者知识回顾。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/es.jpg'
-abbrlink: 7f60dde9
-date: 2020-02-03 13:11:45
----
-
-
-
-> 文章很长,喜欢的话,可以关注下博客。这段时间秋招忙完之后,会持续更新新内容
-
-# ElasticSearch介绍
-
-## 介绍
-
-1、elasticsearch是一个基于Lucene的高扩展的分布式搜索服务器,支持开箱即用。
-
-2、elasticsearch隐藏了Lucene的复杂性,对外提供Restful 接口来操作索引、搜索。
-
-
-
-**突出优点:**
-
-1. 扩展性好,可部署上百台服务器集群,处理PB级数据。
-
-2. 近实时的去索引数据、搜索数据。
-
-**es和solr选择哪个?**
-
-1. 如果你公司现在用的solr可以满足需求就不要换了。
-
-2. 如果你公司准备进行全文检索项目的开发,建议优先考虑elasticsearch,因为像Github这样大规模的搜索都在用它。
-
-
-
-## 倒排索引
-
-下图是ElasticSearch的索引结构,下边黑色部分是物理结构,上边黄色部分是逻辑结构,逻辑结构也是为了更好的 去描述ElasticSearch的工作原理及去使用物理结构中的索引文件。
-
-
-
-逻辑结构部分是一个倒排索引表:
-
-1、将要搜索的文档内容分词,所有不重复的词组成分词列表。
-
-2、将搜索的文档最终以Document方式存储起来。
-
-3、每个词和docment都有关联。
-
-如下:
-
-
-
-现在,如果我们想搜到`quick brown`我们只需要查找包含每个词条的文档:
-
-
-
-两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配词条数量的简单 相似性算法 , 那么,我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳
-
-# 基本概念
-
-
-
-1.创建索引库 --------------------->类似于:数据库的建表
-
-2.创建映射 --------------------->类似于:数据库的添加表中字段
-
-3.创建(添加)文档 --------------------->类似于:数据库的往表中添加数据。术语称这个过程为:创建索引
-
-5.搜索文档 --------------------->类似于:从数据库里查数据
-
-6.文档 --------------------->类似于:数据库中的一行记录(数据)
-
-7.Field(域) --------------------->类似于:数据库中的字段
-
-
-
-
-
-
-
-## 创建索引库
-
-### 概念:
-
-ES的索引库是一个逻辑概念,它包括了分词列表及文档列表,同一个索引库中存储了相同类型的文档。它就相当于MySQL中的表,或相当于Mongodb中的集合。
-
-索引(index)
-
-```shell
-# 索引是 ES 对逻辑数据的逻辑存储,所以可以被分为更小的部分
-
-# 可以将索引看成 MySQL 的 Table,索引的结构是为快速有效的全文索引准备的,特别是它不存储原始值
-
-# 可以将索引存放在一台机器,或分散在多台机器上
-
-# 每个索引有一或多个分片(shard),每个分片可以有多个副本(replica)
-```
-
-### 操作:
-
-使用postman这样的工具创建: put http://localhost:9200/索引库名称
-
-
-```shell
-# ES 中提供非结构化索引,实际上在底层 ES 会进行结构化操作,对用户透明
-
-PUT http://localhost:9200/索引库名称
-{
- "settings":{
- "index":{
- "number_of_shards":"1", # 分片数
- "number_of_replicas":"0" # 副本数
- }
- }
-}
-```
-
-- number_of_shards:设置分片的数量,在集群中通常设置多个分片,表示一个索引库将拆分成多片分别存储不同 的结点,提高了ES的处理能力和高可用性,入门程序使用单机环境,这里设置为1。
-
-- number_of_replicas:设置副本的数量,设置副本是为了提高ES的高可靠性,单机环境设置为0.
-
-
-
-## 创建映射
-
-### 概念
-
-在索引中每个文档都包括了一个或多个field,创建映射就是向索引库中创建field的过程,下边是document和field 与关系数据库的概念的类比:
-
-文档(Document)----- Row记录
-
-字段(Field)----- Columns 列
-
-注意:6.0之前的版本有type(类型)概念,type相当于关系数据库的表,ES官方将在ES9.0版本中彻底删除type。 上边讲的创建索引库相当于关系数据库中的数据库还是表?
-
-1、如果相当于数据库就表示一个索引库可以创建很多不同类型的文档,这在ES中也是允许的。
-
-2、如果相当于表就表示一个索引库只能存储相同类型的文档,ES官方建议在一个索引库中只存储相同类型的文档。
-
-3、所以索引库相当于数句酷的一个表
-
-
-
-### 操作
-
-1、我们要把课程信息存储到ES中,这里我们创建课程信息的映射,先来一个简单的映射,如下:
-
-发送:post http://localhost:9200/索引库名称/类型名称/_mapping
-
-2、创建类型为xc_course的映射,共包括三个字段:name、description、studymondel 由于ES6.0版本还没有将type彻底删除,所以暂时把type起一个没有特殊意义的名字doc。post 请求:http://localhost:9200/xc_course/doc/_mapping
-
-表示:在xc_course索引库下的doc类型下创建映射。doc是类型名,可以自定义,在ES6.0中要弱化类型的概念, 给它起一个没有具体业务意义的名称。
-
-```json
- {
- "properties": {
- "name": {
- "type": "text"
- },
-
- "description":{
- "type": "text"
- },
-
- "studymodel":{
- "type":"keyword"
- }
- }
-}
-```
-
-
-## 创建文档
-
-### 概念
-
-ES中的文档相当于MySQL数据库表中的记录。
-
-```shell
-# 存储在 ES 中的主要实体叫文档,可以看成 MySQL 的一条记录
-
-# ES 与 Mongo 的 document 类似,都可以有不同的结构,但 ES 相同字段必须有相同类型
-
-# document 由多个字段组成,每个字段可能多次出现在一个文档里,这样的字段叫多值字段(multivalued)
-
-# 每个字段的类型,可以使文本、数值、日期等。
-
-# 字段类型也可以是复杂类型,一个字段包含其他子文档或者数组
-
-# 在 ES 中,一个索引对象可以存储很多不同用途的 document,例如一个博客App中,可以保存文章和评论
-
-# 每个 document 可以有不同的结构
-
-# 不同的 document 不能为相同的属性设置不同的类型,例 : title 在同一索引中所有 Document 都应该相同数据类型
-```
-
-
-
-### 操作
-
-发送:put 或Post http://localhost:9200/xc_course/doc/id值
-
-(如果不指定id值ES会自动生成ID)
-
-http://localhost:9200/xc_course/doc/4028e58161bcf7f40161bcf8b77c0000
-
-```json
-
-{
- "name":”Bootstrap开发框架",
-
- "description" : "Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较为广泛。此开发框架包含 了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现个不受浏览器限制的精美界面 效果。”,
-
- "studymodel": "201001"
-
-}
-```
-
-
-
-## 搜索文档
-
-1、根据课程id查询文档
-
-发送:get http://localhost:9200/xc_course/doc/4028e58161bcf7f40161bcf8b77c0000
-
-使用postman测试:
-
-
-
-
-
-2、查询所有记录
-
-发送 get http://localhost:9200/xc_course/doc/_search
-
-
-
-
-
-3、查询名称中包括spring 关键字的的记录
-
-发送:get http://localhost:9200/xc_course/doc/_search?q=name:bootstrap
-
-
-
-
-
-4、查询学习模式为201001的记录
-
-发送 get http://localhost:9200/xc_course/doc/_search?q=studymodel:201001
-
-
-
-**查询结果分析:**
-
-```json
-{
- "took": 1,
- "timed_out": false,
- "_shards": {
- "total": 1,
- "successful": 1,
- "skipped": 0,
- "failed": 0
- },
- "hits": {
- "total": 1,
- "max_score": 0.2876821,
- "hits": [
- {
- "_index": "xc_course",
- "_type": "doc",
- "_id": "4028e58161bcf7f40161bcf8b77c0000",
- "_score": 0.2876821,
- "_source": {
- "name": "Bootstrap开发框架",
- "description": "Bootstrap是由Twitter推出的一个前台页面开发框架,在行业之中使用较 为广泛。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长页面开发的程序人员)轻松的实现 一个不受浏览器限制的精美界面效果。",
- "studymodel": "201001"
- }
- }
- ]
- }
-}
-```
-
-**结果说明:**
-
-took:本次操作花费的时间,单位为毫秒。timed_out:请求是否超时
-
-_shards:说明本次操作共搜索了哪些分片hits:搜索命中的记录
-
-hits.total : 符合条件的文档总数 hits.hits :匹配度较高的前N个文档
-
-hits.max_score:文档匹配得分,这里为最高分
-
-_score:每个文档都有一个匹配度得分,按照降序排列。
-
-_source:显示了文档的原始内容。
-
-
-
-
-
-# 分词
-
-## 内置分词
-
-### 分词API
-
-分词是将一个文本转换成一系列单词的过程,也叫文本分析,在 ES 中称之为 Analysis
-
-例如 : 我是中国人 -> 我 | 是 | 中国人
-
-```json
-# 指定分词器进行分词
-POST http://['自己的ip 加 port']/_analyze
-{
- "analyzer":"standard",
- "text":"hello world"
-}
-
-# 结果中不仅可以看出分词的结果,还返回了该词在文本中的位置
-
-# 指定索引分词
-POST http://['自己的ip 加 port']/beluga/_analyze
-{
- "analyzer":"standard",
- "field":"hobby",
- "text":"听音乐"
-}
-```
-
-
-
-### Standard
-
-```shell
-# Standard 标准分词,按单词切分,并且会转换成小写
-POST http://['自己的ip 加 port']/_analyze
-{
- "analyzer":"standard",
- "text": "A man becomes learned by asking questions."
-}
-```
-
-### Simple
-
-```shell
-# Simple 分词器,按照非单词切分,并且做小写处理
-POST http://['自己的ip 加 port']/_analyze
-{
- "analyzer":"simple",
- "text":"If the document does't already exist"
-}
-```
-
-### Whitespace
-
-```shell
-# Whitespace 是按照空格切分
-POST http://['自己的ip 加 port']/_analyze
-{
- "analyzer":"whitespace",
- "text":"If the document does't already exist"
-}
-```
-
-### Stop
-
-```shell
-# Stop 去除 Stop Word 语气助词,如 the、an 等
-POST http://['自己的ip 加 port']/_analyze
-{
- "analyzer":"stop",
- "text":"If the document does't already exist"
-}
-```
-
-### Keyword
-
-```shell
-# keyword 分词器,意思是传入就是关键词,不做分词处理
-POST http://['自己的ip 加 port']/_analyze
-{
- "analyzer":"keyword",
- "text":"If the document does't already exist"
-}
-```
-
-### 中文分词
-
-```shell
-# 中文分词的难点在于,汉语中没有明显的词汇分界点
-
-# 常用中文分词器,IK jieba THULAC 等,推荐 IK
-
-# IK Github 站点<自定义词典扩展,禁用词典扩展等>
-https://github.com/medcl/elasticsearch-analysis-ik
-```
-
-
-
-## IK分词器
-
-安装过程这里不介绍,主要是解决常见中文分词的问题
-
-Github地址:https://github.com/medcl/elasticsearch-analysis-ik
-
-### 两种分词模式
-
-ik分词器有两种分词模式:ik_max_word和ik_smart模式。
-
- 1、ik_max_word
-
-会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、 华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。
-
-2、ik_smart
-
-会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。 测试两种分词模式:
-
-
-
-
-
-# 映射
-
-上边章节安装了ik分词器,如果在索引和搜索时去使用ik分词器呢?如何指定其它类型的field,比如日期类型、数 值类型等。本章节学习各种映射类型及映射维护方法。
-
-## 映射维护方法
-
-1、查询所有索引的映射:
-
-GET: http://localhost:9200/_mapping
-
-2、创建映射
-
-post 请求:http://localhost:9200/xc_course/doc/_mapping
-
-在上面提到过
-
-```
- {
- "properties": {
- "name": {
- "type": "text"
- },
-
- "description":{
- "type": "text"
- },
-
- "studymodel":{
- "type":"keyword"
- }
- }
-}
-```
-
-
-
-3、更新映射
-
-映射创建成功可以添加新字段,已有字段不允许更新。
-
-4、删除映射
-
-通过删除索引来删除映射。
-
-
-
-## 常用映射类型
-
-### text文本字段
-
-**1)text**
-
-字符串包括text和keyword两种类型: 通过analyzer属性指定分词器。
-
-下边指定name的字段类型为text,使用ik分词器的ik_max_word分词模式。
-
-```json
-{
- "name": {
- "type": "text",
- "analyzer": "ik_max_word"
- }
-}
-```
-
-上边指定了analyzer是指在索引和搜索都使用ik_max_word,如果单独想定义搜索时使用的分词器则可以通过search_analyzer属性。
-
-对于ik分词器建议是索引时使用ik_max_word将搜索内容进行细粒度分词,搜索时使用ik_smart提高搜索精确性。
-
-```json
-{
- "name": {
- "type": "text",
- "analyzer": "ik_max_word",
- "search_analyzer": "ik_smart"
- }
-}
-```
-
-**2) index**
-
-通过index属性指定是否索引。
-
-默认为index=true,即要进行索引,只有进行索引才可以从索引库搜索到。
-
-但是也有一些内容不需要索引,比如:商品图片地址只被用来展示图片,不进行搜索图片,此时可以将index设置 为false。
-
-删除索引,重新创建映射,将pic的index设置为false,尝试根据pic去搜索,结果搜索不到数据
-
-```json
-{
- "pic": {
- "type": "text",
- "index": false
- }
-}
-```
-
-
-
-**3)store**
-
-是否在source之外存储,每个文档索引后会在 ES中保存一份原始文档,存放在"_source"中,一般情况下不需要设置 store为true,因为在_source中已经有一份原始文档了。
-
-
-
-### keyword关键字字段
-
-上边介绍的text文本字段在映射时要设置分词器,keyword字段为关键字字段,通常搜索keyword是按照整体搜 索,所以创建keyword字段的索引时是不进行分词的,比如:邮政编码、手机号码、身份证等。keyword字段通常 用于过虑、排序、聚合等。
-
-**测试:**
-
-更改映射:
-
-```json
-{
- "properties": {
- "studymodel": {
- "type": "keyword"
- },
- "name": {
- "type": "keyword"
- }
- }
-}
-```
-
-添加文档:
-
-```json
-{
- "name": "java编程基础",
- "description": "java语言是世界第一编程语言,在软件开发领域使用人数最多。",
- "pic": "group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
- "studymodel": "201001"
-}
-```
-
-根据name查询文档。搜索:http://localhost:9200/xc_course/_search?q=name:java name是keyword类型,所以查询方式是精确查询。
-
-
-
-### 日期类型
-
-日期类型不用设置分词器。
-
-通常日期类型的字段用于排序。
-
-1)format
-
-通过format设置日期格式例子:
-
-下边的设置允许date字段存储年月日时分秒、年月日及毫秒三种格式
-
-```json
-{
- "properties": {
- "timestamp": {
- "type": "date",
- "format": "yyyy‐MM‐dd HH:mm:ss||yyyy‐MM‐dd"
- }
- }
-}
-```
-
-插入文档:
-
-Post :http://localhost:9200/xc_course/doc/3
-
-```json
-{
- "name": "spring开发基础",
- "description": "spring 在java领域非常流行,java程序员都在用。",
- "studymodel": "201001",
- "pic": "group1/M00/00/01/wKhlQFqO4MmAOP53AAAcwDwm6SU490.jpg",
- "timestamp": "2018‐07‐04 18:28:58"
-}
-```
-
-
-
-### 综合例子
-
-post:http://localhost:9200/xc_course/doc/_mapping
-
-```json
-{
- "properties": {
- "description": {
- "type": "text",
- "analyzer": "ik_max_word",
- "search_analyzer": "ik_smart"
- },
- "name": {
- "type": "text",
- "analyzer": "ik_max_word",
- "search_analyzer": "ik_smart"
- },
- "pic": {
- "type": "text",
- "index": false
- },
- "price": {
- "type": "float"
- },
- "studymodel": {
- "type": "keyword"
- },
- "timestamp": {
- "type": "date",
- "format": "yyyy‐MM‐dd HH:mm:ss||yyyy‐MM‐dd||epoch_millis"
- }
- }
-}
-```
-
diff --git a/docs/ElasticSearch/ElasticSearch-进阶.md b/docs/ElasticSearch/ElasticSearch-进阶.md
deleted file mode 100644
index 4cf7978..0000000
--- a/docs/ElasticSearch/ElasticSearch-进阶.md
+++ /dev/null
@@ -1,1732 +0,0 @@
----
-title: ElasticSearch-进阶篇
-tags:
- - ElasticSearch
- - ELK
- - 全文检索
-categories:
- - ElasticSearch
- - 用法
-keywords: ElasticSearch,全文检索
-description: ElasticSearch-进阶篇,ElasticSearch的一些实战用法,集成SpringBoot。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/es.jpg'
-abbrlink: 50e81c79
-date: 2020-02-08 18:06:23
----
-
-
-
-# 搭建工程
-
-ES提供多种不同的客户端:
-
-1、TransportClient
-
-ES提供的传统客户端,官方计划8.0版本删除此客户端。
-
-2、RestClient
-
-RestClient是官方推荐使用的,它包括两种:Java Low Level REST Client和 Java High Level REST Client。
-
-ES在6.0之后提供 Java High Level REST Client, 两种客户端官方更推荐使用 Java High Level REST Client,不过当
-
-前它还处于完善中,有些功能还没有。
-
-
-
-我们采用SpringBoot2.x与ElasticSearch集成
-
-## Maven依赖
-
-部分依赖
-
-```maven
-
+
1. Java 和 C++语言的区别,就在于垃圾收集技术和内存动态分配上,C++语言没有垃圾收集技术,需要程序员手动的收集。
@@ -102,7 +102,7 @@ date: 2020-11-16 18:14:02
**十几年前磁盘碎片整理的日子**
-
+
@@ -174,7 +174,7 @@ date: 2020-11-16 18:14:02
### 应该关心哪些区域的回收?
-
+
1. 垃圾收集器可以对年轻代回收,也可以对老年代回收,甚至是全栈和方法区的回收,
1. 其中,**Java堆是垃圾收集器的工作重点**
@@ -216,7 +216,7 @@ date: 2020-11-16 18:14:02
### 循环引用
-
+
当p的指针断开的时候,内部的引用形成一个循环,计数器都还算1,无法被回收,这就是循环引用,从而造成内存泄漏
@@ -255,7 +255,7 @@ public class RefCountGC {
-
+
* 如果不小心直接把`obj1.reference`和`obj2.reference`置为null。则在Java堆中的两块内存依然保持着互相引用,无法被回收
@@ -353,7 +353,7 @@ Process finished with exit code 0
3. 如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象。
4. 在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。
-
+
@@ -371,7 +371,7 @@ Process finished with exit code 0
- 基本数据类型对应的Class对象,一些常驻的异常对象(如:NullPointerException、OutofMemoryError),系统类加载器。
7. 反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
-
+
@@ -453,7 +453,7 @@ Object 类中 finalize() 源码
**通过 JVisual VM 查看 Finalizer 线程**
-
+
@@ -579,7 +579,7 @@ MAT与JProfiler的GC Roots溯源
**方式一:命令行使用 jmap**
-
+
@@ -631,23 +631,23 @@ public class GCRootsTest {
1、先执行第一步,然后停下来,去生成此步骤dump文件
-
+
2、 点击【堆 Dump】
-
+
3、右键 --\> 另存为即可
-
+
4、输入命令,继续执行程序
-
+
5、我们接着捕获第二张堆内存快照
-
+
@@ -657,19 +657,19 @@ public class GCRootsTest {
> 点击Open Heap Dump也行
-
+
2、选择Java Basics --> GC Roots
-
+
3、第一次捕捉堆内存快照时,GC Roots 中包含我们定义的两个局部变量,类型分别为 ArrayList 和 Date,Total:21
-
+
4、打开第二个dump文件,第二次捕获内存快照时,由于两个局部变量引用的对象被释放,所以这两个局部变量不再作为 GC Roots ,从 Total Entries = 19 也可以看出(少了两个 GC Roots)
-
+
@@ -716,13 +716,13 @@ public class GCRootsTest {
1、
-
+
2、
-
+
-
+
可以发现颜色变绿了,可以动态的看变化
@@ -730,11 +730,11 @@ public class GCRootsTest {
3、右击对象,选择 Show Selection In Heap Walker,单独的查看某个对象
-
+
-
+
@@ -742,13 +742,13 @@ public class GCRootsTest {
点击Show Paths To GC Roots,在弹出界面中选择默认设置即可
-
+
-
+
-
+
### JProfiler 分析 OOM
@@ -798,11 +798,11 @@ count = 6
1、看这个超大对象
-
+
2、揪出 main() 线程中出问题的代码
-
+
@@ -837,7 +837,7 @@ count = 6
* 注意:标记的是被引用的对象,也就是可达对象,并非标记的是即将被清除的垃圾对象
2. 清除:Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收
-
+
@@ -876,7 +876,7 @@ count = 6
将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收
-
+
新生代里面就用到了复制算法,Eden区和S0区存活对象整体复制到S1区
@@ -906,7 +906,7 @@ count = 6
2. 老年代大量的对象存活,那么复制的对象将会有很多,效率会很低
3. 在新生代,对常规应用的垃圾回收,一次通常可以回收70% - 99% 的内存空间。回收性价比很高。所以现在的商业虚拟机都是用这种收集算法回收新生代。
-
+
@@ -935,7 +935,7 @@ count = 6
2. 第二阶段将所有的存活对象压缩到内存的一端,按顺序排放。之后,清理边界外所有的空间。
-
+
@@ -1066,7 +1066,7 @@ A:无,没有最好的算法,只有最合适的算法
1. 一般来说,在相同条件下,堆空间越大,一次GC时所需要的时间就越长,有关GC产生的停顿也越长。为了更好地控制GC产生的停顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。
3. 分代算法将按照对象的生命周期长短划分成两个部分,分区算法将整个堆空间划分成连续的不同小区间。每一个小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间。
-
+
diff --git a/docs/JVM/JVM系列-第11章-垃圾回收相关概念.md b/docs/JVM/JVM系列-第11章-垃圾回收相关概念.md
index 5ba6c4f..33daa7a 100644
--- a/docs/JVM/JVM系列-第11章-垃圾回收相关概念.md
+++ b/docs/JVM/JVM系列-第11章-垃圾回收相关概念.md
@@ -8,7 +8,7 @@ categories:
- 1.内存与垃圾回收篇
keywords: JVM,虚拟机。
description: JVM系列-第11章-垃圾回收相关概念。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/jvm.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/jvm.png'
abbrlink: 4d401a8b
date: 2020-11-17 12:33:24
---
@@ -117,7 +117,7 @@ JVM参数:
2、我也查过了大对象阈值的默认值
-
+
我不太懂这个默认值为啥是0,我猜测可能是代表什么比例,目前也没有搜到相关的东西。这个不太重要,暂时就没有太深究,希望读者有知道的可以告知我一声。
@@ -185,11 +185,11 @@ Heap
1、来看看字节码:实例方法局部变量表第一个变量肯定是 this
-
+
2、你有没有看到,局部变量表的大小是 2。但是局部变量表里只有一个索引为0的啊?那索引为1的是哪个局部变量呢?实际上索引为1的位置是buffer在占用着,执行 System.gc() 时,栈中还有 buffer 变量指向堆中的字节数组,所以没有进行GC
-
+
3、那么这种代码块的情况,什么时候会被GC呢?我们来看第四个方法
@@ -217,11 +217,11 @@ A:局部变量表长度为 2 ,这说明了出了代码块时,buffer 就出
> 这点看不懂的可以看我前面的文章:虚拟机栈 --> Slot的重复利用
-
+
-
+
@@ -302,7 +302,7 @@ Heap
右边的图:后期有一些对象不用了,按道理应该断开引用,但是存在一些链没有断开(图示中的Forgotten Reference Memory Leak),从而导致没有办法被回收。
-
+
@@ -446,7 +446,7 @@ Process finished with exit code -1
2. 并发不是真正意义上的“同时进行”,只是CPU把一个时间段划分成几个时间片段(时间区间),然后在这几个时间区间之间来回切换。由于CPU处理的速度非常快,只要时间间隔处理得当,即可让用户感觉是多个应用程序同时在进行
-
+
@@ -461,7 +461,7 @@ Process finished with exit code -1
3. 适合科学计算,后台处理等弱交互场景
-
+
> **并发与并行的对比**
@@ -482,7 +482,7 @@ Process finished with exit code -1
* 相较于并行的概念,单线程执行。
* 如果内存不够,则程序暂停,启动JVM垃圾回收器进行垃圾回收(单线程)
-
+
@@ -492,7 +492,7 @@ Process finished with exit code -1
- 比如用户程序在继续运行,而垃圾收集程序线程运行于另一个CPU上;
2. 典型垃圾回收器:CMS、G1
-
+
@@ -551,7 +551,7 @@ Process finished with exit code -1
1、一般的垃圾回收算法至少会划分出两个年代,年轻代和老年代。但是单纯的分代理论在垃圾回收的时候存在一个巨大的缺陷:为了找到年轻代中的存活对象,却不得不遍历整个老年代,反过来也是一样的。
-
+
2、如果我们从年轻代开始遍历,那么可以断定N, S, P, Q都是存活对象。但是,V却不会被认为是存活对象,其占据的内存会被回收了。这就是一个惊天的大漏洞!因为U本身是老年代对象,而且有外部引用指向它,也就是说U是存活对象,而U指向了V,也就是说V也应该是存活对象才是!而这都是因为我们只遍历年轻代对象!
@@ -599,7 +599,7 @@ Process finished with exit code -1
4. 这4种引用强度依次逐渐减弱。除强引用外,其他3种引用均可以在java.lang.ref包中找到它们的身影。如下图,显示了这3种引用类型对应的类,开发人员可以在应用程序中直接使用它们。
-
+
@@ -661,7 +661,7 @@ Hello,尚硅谷
`StringBuffer str = new StringBuffer("hello,尚硅谷");`
-
+
diff --git a/docs/JVM/JVM系列-第12章-垃圾回收器.md b/docs/JVM/JVM系列-第12章-垃圾回收器.md
index 8f94f44..06fae9f 100644
--- a/docs/JVM/JVM系列-第12章-垃圾回收器.md
+++ b/docs/JVM/JVM系列-第12章-垃圾回收器.md
@@ -8,7 +8,7 @@ categories:
- 1.内存与垃圾回收篇
keywords: JVM,虚拟机。
description: JVM系列-第12章-垃圾回收器。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/jvm.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/jvm.png'
abbrlink: 7706d61d
date: 2020-11-19 18:33:24
---
@@ -44,7 +44,7 @@ GC 分类与性能指标
**按线程数分(垃圾回收线程数),可以分为串行垃圾回收器和并行垃圾回收器。**
-
+
1. 串行回收指的是在同一时间段内只允许有一个CPU用于执行垃圾回收操作,此时工作线程被暂停,直至垃圾收集工作结束。
1. 在诸如单CPU处理器或者较小的应用内存等硬件平台不是特别优越的场合,串行回收器的性能表现可以超过并行回收器和并发回收器。所以,串行回收默认被应用在客户端的Client模式下的JVM中
@@ -60,7 +60,7 @@ GC 分类与性能指标
1. 并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的停顿时间。
2. 独占式垃圾回收器(Stop the World)一旦运行,就停止应用程序中的所有用户线程,直到垃圾回收过程完全结束。
-
+
@@ -105,7 +105,7 @@ GC 分类与性能指标
2. 这种情况下,应用程序能容忍较高的暂停时间,因此,高吞吐量的应用程序有更长的时间基准,快速响应是不必考虑的
3. 吞吐量优先,意味着在单位时间内,STW的时间最短:0.2+0.2=0.4
-
+
@@ -115,7 +115,7 @@ GC 分类与性能指标
- 例如,GC期间100毫秒的暂停时间意味着在这100毫秒期间内没有应用程序线程是活动的
2. 暂停时间优先,意味着尽可能让单次STW的时间最短:0.1+0.1 + 0.1+ 0.1+ 0.1=0.5,但是总的GC时间可能会长
-
+
@@ -169,17 +169,17 @@ GC 分类与性能指标
2. 并行回收器:ParNew、Parallel Scavenge、Parallel old
3. 并发回收器:CMS、G1
-
+
**官方文档**
-
+
**7款经典回收器与垃圾分代之间的关系**
-
+
1. 新生代收集器:Serial、ParNew、Parallel Scavenge;
@@ -192,7 +192,7 @@ GC 分类与性能指标
### 垃圾收集器的组合关系
-
+
@@ -251,11 +251,11 @@ jinfo -flag UseParallelOldGC 进程id
JDK 8 中默认使用 ParallelGC 和 ParallelOldGC 的组合
-
+
#### JDK9
-
+
@@ -281,7 +281,7 @@ Serial 回收器:串行回收
这个收集器是一个单线程的收集器,“单线程”的意义:它只会使用一个CPU(串行)或一条收集线程去完成垃圾收集工作。更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(Stop The World)
-
+
@@ -313,7 +313,7 @@ ParNew 回收器:并行回收
2. ParNew 收集器除了采用**并行回收**的方式执行内存回收外,两款垃圾收集器之间几乎没有任何区别。ParNew收集器在年轻代中同样也是采用复制算法、"Stop-the-World"机制。
3. ParNew 是很多JVM运行在Server模式下新生代的默认垃圾收集器。
-
+
1. 对于新生代,回收次数频繁,使用并行方式高效。
2. 对于老年代,回收次数少,使用串行方式节省资源。(CPU并行需要切换线程,串行可以省去切换线程的资源)
@@ -361,7 +361,7 @@ Parallel 回收器:吞吐量优先
5. Parallel Old收集器采用了标记-压缩算法,但同样也是基于并行回收和"Stop-the-World"机制。
-
+
1. 在程序吞吐量优先的应用场景中,Parallel收集器和Parallel Old收集器的组合,在server模式下的内存回收性能很不错。
2. **在Java8中,默认是此垃圾收集器。**
@@ -418,7 +418,7 @@ CMS 回收器:低延迟
### CMS 工作原理(过程)
-
+
CMS整个过程比之前的收集器要复杂,整个过程分为4个主要阶段,即初始标记阶段、并发标记阶段、重新标记阶段和并发清除阶段。(涉及STW的阶段主要是:初始标记 和 重新标记)
@@ -438,7 +438,7 @@ CMS整个过程比之前的收集器要复杂,整个过程分为4个主要阶
3. 另外,由于在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此,CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,**而是当堆内存使用率达到某一阈值时,便开始进行回收**,以确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次**“Concurrent Mode Failure”** 失败,这时虚拟机将启动后备预案:临时启用Serial old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。
4. CMS收集器的垃圾收集算法采用的是**标记清除算法**,这意味着每次执行完内存回收后,由于被执行内存回收的无用对象所占用的内存空间极有可能是不连续的一些内存块,**不可避免地将会产生一些内存碎片**。那么CMS在为新对象分配内存空间时,将无法使用指针碰撞(Bump the Pointer)技术,而只能够选择空闲列表(Free List)执行内存分配。
-
+
@@ -558,11 +558,11 @@ G1 回收器:区域化分代式
G1的分代,已经不是下面这样的了
-
+
G1的分区是这样的一个区域
-
+
**空间整合**
@@ -655,7 +655,7 @@ G1中提供了三种垃圾回收模式:YoungGC、Mixed GC和Full GC,在不
>
> 如图所示,可以将区域分配到Eden,幸存者和旧时代区域。 此外,还有第四种类型的物体被称为巨大区域。 这些区域旨在容纳标准区域大小的50%或更大的对象。 它们存储为一组连续区域。 最后,最后一种区域类型是堆的未使用区域。
-
+
@@ -667,7 +667,7 @@ G1中提供了三种垃圾回收模式:YoungGC、Mixed GC和Full GC,在不
**Regio的细节**
-
+
1. 每个Region都是通过指针碰撞来分配空间
2. G1为每一个Region设 计了两个名为TAMS(Top at Mark Start)的指针,把Region中的一部分空间划分出来用于并发回收过程中的新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置以上。
@@ -686,7 +686,7 @@ G1 GC的垃圾回收过程主要包括如下三个环节:
* 混合回收(Mixed GC)
* (如果需要,单线程、独占式、高强度的Full GC还是继续存在的。它针对GC的评估失败提供了一种失败保护机制,即强力回收。)
-
+
顺时针,Young GC --> Young GC+Concurrent Marking --> Mixed GC顺序,进行垃圾回收
@@ -731,7 +731,7 @@ G1 GC的垃圾回收过程主要包括如下三个环节:
-
+
1. 在回收 Region 时,为了不进行全堆的扫描,引入了 Remembered Set
2. Remembered Set 记录了当前 Region 中的对象被哪个对象引用了
@@ -748,7 +748,7 @@ G1 GC的垃圾回收过程主要包括如下三个环节:
2. 年轻代回收只回收Eden区和Survivor区
3. YGC时,首先G1停止应用程序的执行(Stop-The-World),G1创建回收集(Collection Set),回收集是指需要被回收的内存分段的集合,年轻代回收过程的回收集包含年轻代Eden区和Survivor区所有的内存分段。
-
+
图的大致意思就是:
@@ -804,7 +804,7 @@ G1 GC的垃圾回收过程主要包括如下三个环节:
当越来越多的对象晋升到老年代Old Region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分的Old Region。这里需要注意:是一部分老年代,而不是全部老年代。可以选择哪些Old Region进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC并不是Full GC。
-
+
@@ -854,11 +854,11 @@ G1 GC的垃圾回收过程主要包括如下三个环节:
截止JDK1.8,一共有7款不同的垃圾收集器。每一款的垃圾收集器都有不同的特点,在具体使用的时候,需要根据具体的情况选用不同的垃圾收集器。
-
+
-
+
@@ -922,11 +922,11 @@ GC 日志分析
2、这个只会显示总的GC堆的变化,如下:
-
+
3、参数解析
-
+
@@ -938,11 +938,11 @@ GC 日志分析
2、输入信息如下
-
+
3、参数解析
-
+
@@ -954,7 +954,7 @@ GC 日志分析
2、输出信息如下
-
+
3、说明:日志带上了日期和时间
@@ -989,13 +989,13 @@ GC 日志分析
#### Young GC
-
+
#### Full GC
-
+
@@ -1029,11 +1029,11 @@ public class GCLogTest1 {
1、首先我们会将3个2M的数组存放到Eden区,然后后面4M的数组来了后,将无法存储,因为Eden区只剩下2M的剩余空间了,那么将会进行一次Young GC操作,将原来Eden区的内容,存放到Survivor区,但是Survivor区也存放不下,那么就会直接晋级存入Old 区
-
+
2、然后我们将4M对象存入到Eden区中
-
+
老年代图画的有问题,free应该是4M
@@ -1056,7 +1056,7 @@ Process finished with exit code 0
```
-
+
与 JDK7 不同的是,JDK8 直接判定 4M 的数组为大对象,直接怼到老年区去了
@@ -1082,15 +1082,15 @@ GCViewer、GCEasy、GCHisto、GCLogViewer、Hpjmeter、garbagecat等
在线分析网址:gceasy.io
-
+
-
+
-
+
@@ -1126,7 +1126,7 @@ GCViewer、GCEasy、GCHisto、GCLogViewer、Hpjmeter、garbagecat等
1. 停顿时间比其他几款收集器确实有了质的飞跃,但也未实现最大停顿时间控制在十毫秒以内的目标。
2. 而吞吐量方面出现了明显的下降,总运行时间是所有测试收集器里最长的。
-
+
@@ -1156,7 +1156,7 @@ GCViewer、GCEasy、GCHisto、GCLogViewer、Hpjmeter、garbagecat等
**吞吐量**
-
+
max-JOPS:以低延迟为首要前提下的数据
@@ -1166,13 +1166,13 @@ critical-JOPS:不考虑低延迟下的数据
**低延迟**
-
+
在ZGC的强项停顿时间测试上,它毫不留情的将Parallel、G1拉开了两个数量级的差距。无论平均停顿、95%停顿、998停顿、99. 98停顿,还是最大停顿时间,ZGC都能毫不费劲控制在10毫秒以内。
虽然ZGC还在试验状态,没有完成所有特性,但此时性能已经相当亮眼,用“令人震惊、革命性”来形容,不为过。未来将在服务端、大内存、低延迟应用的首选垃圾收集器。
-
+
@@ -1192,4 +1192,4 @@ critical-JOPS:不考虑低延迟下的数据
AliGC是阿里巴巴JVM团队基于G1算法,面向大堆(LargeHeap)应用场景。指定场景下的对比:
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/JVM/JVM系列-第1章-JVM与Java体系结构.md b/docs/JVM/JVM系列-第1章-JVM与Java体系结构.md
index 5fef9cd..e7129f3 100644
--- a/docs/JVM/JVM系列-第1章-JVM与Java体系结构.md
+++ b/docs/JVM/JVM系列-第1章-JVM与Java体系结构.md
@@ -8,7 +8,7 @@ categories:
- 1.内存与垃圾回收篇
keywords: JVM,虚拟机。
description: JVM系列-第1章-JVM与Java体系结构。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/jvm.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/jvm.png'
abbrlink: 8c954c6
date: 2020-11-02 11:51:56
---
@@ -38,7 +38,7 @@ date: 2020-11-02 11:51:56
3. 新项目上线,对各种JVM参数设置一脸茫然,直接默认吧然后就JJ了。
4. 每次面试之前都要重新背一遍JVM的一些原理概念性的东西,然而面试官却经常问你在实际项目中如何调优VM参数,如何解决GC、OOM等问题,一脸懵逼。
-
+
大部分Java开发人员,除了会在项目中使用到与Java平台相关的各种高精尖技术,对于Java技术的核心Java虚拟机了解甚少。
@@ -50,7 +50,7 @@ date: 2020-11-02 11:51:56
1. 一些有一定工作经验的开发人员,打心眼儿里觉得SSM、微服务等上层技术才是重点,基础技术并不重要,这其实是一种本末倒置的“病态”。
2. 如果我们把核心类库的API比做数学公式的话,那么Java虚拟机的知识就好比公式的推导过程。
-
+
- 计算机系统体系对我们来说越来越远,在不了解底层实现方式的前提下,通过高级语言很容易编写程序代码。但事实上计算机并不认识高级语言。
@@ -87,7 +87,7 @@ Java VS C++
1. 垃圾收集机制为我们打理了很多繁琐的工作,大大提高了开发的效率,但是,垃圾收集也不是万能的,懂得JVM内部的内存结构、工作机制,是设计高扩展性应用和诊断运行时问题的基础,也是Java工程师进阶的必备能力。
2. C++语言需要程序员自己来分配内存和回收内存,对于高手来说可能更加舒服,但是对于普通开发者,如果技术实力不够,很容易造成内存泄漏。而Java全部交给JVM进行内存分配和回收,这也是一种趋势,减少程序员的工作量。
-
+
## 什么人需要学JVM?
@@ -103,26 +103,26 @@ Java VS C++
**英文文档规范**:https://docs.oracle.com/javase/specs/index.html
-
+
**中文书籍:**
-
+
> 周志明老师的这本书**非常推荐看**,不过只推荐看第三版,第三版较第二版更新了很多,个人觉得没必要再看第二版。
-
+
-
+
TIOBE排行榜
-----------
**TIOBE 排行榜**:https://www.tiobe.com/tiobe-index/
-
+
- 世界上没有最好的编程语言,只有最适用于具体应用场景的编程语言。
- 目前网上一直流传Java被python,go撼动Java第一的地位。学习者不需要太担心,Java强大的生态圈,也不是说是朝夕之间可以被撼动的。
@@ -148,14 +148,14 @@ Java-跨平台的语言
-
+
JVM-跨语言的平台
------
-
+
@@ -187,7 +187,7 @@ JVM-跨语言的平台
2. 自己动手写一个Java虚拟机,难吗?
3. 天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣
-
+
Java发展重大事件
------------
@@ -215,7 +215,7 @@ Java发展重大事件
## Open JDK和Oracle JDK
-
+
- 在JDK11之前,Oracle JDK中还会存在一些Open JDK中没有的,闭源的功能。但在JDK11中,我们可以认为Open JDK和Oracle JDK代码实质上已经达到完全一致的程度了。
- 主要的区别就是两者更新周期不一样
@@ -258,11 +258,11 @@ JVM的位置
JVM是运行在操作系统之上的,它与硬件没有直接的交互
-
+
-
+
@@ -275,7 +275,7 @@ JVM的整体结构
-
+
@@ -284,7 +284,7 @@ Java代码执行流程
凡是能生成被Java虚拟机所能解释、运行的字节码文件,那么理论上我们就可以自己设计一套语言了
-
+
diff --git a/docs/JVM/JVM系列-第2章-类加载子系统.md b/docs/JVM/JVM系列-第2章-类加载子系统.md
index 2cc7087..7df2aa3 100644
--- a/docs/JVM/JVM系列-第2章-类加载子系统.md
+++ b/docs/JVM/JVM系列-第2章-类加载子系统.md
@@ -8,7 +8,7 @@ categories:
- 1.内存与垃圾回收篇
keywords: JVM,虚拟机。
description: JVM系列-第2章-类加载子系统。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/jvm.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/jvm.png'
abbrlink: 2e0079af
date: 2020-11-02 21:31:58
---
@@ -25,19 +25,19 @@ date: 2020-11-02 21:31:58
### 简图
-
+
### 详细图
英文版
-
+
中文版
-
+
注意:方法区只有HotSpot虚拟机有,J9,JRockit都没有
@@ -59,7 +59,7 @@ date: 2020-11-02 21:31:58
3. **加载的类信息存放于一块称为方法区的内存空间**。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
-
+
@@ -71,7 +71,7 @@ date: 2020-11-02 21:31:58
-
+
类加载过程
-------
@@ -96,11 +96,11 @@ public class HelloLoader {
* 加载成功,则进行链接、初始化等操作。完成后调用 HelloLoader 类中的静态方法 main
* 加载失败则抛出异常
-
+
完整的流程图如下所示:
-
+
@@ -142,7 +142,7 @@ public class HelloLoader {
使用 BinaryViewer软件查看字节码文件,其开头均为 CAFE BABE ,如果出现不合法的字节码文件,那么将会验证不通过。
-
+
#### 准备(Prepare)
@@ -185,7 +185,7 @@ public class HelloApp {
* 反编译 class 文件后可以查看符号引用,下面带# 的就是符号引用
-
+
### 初始化阶段
@@ -225,7 +225,7 @@ public class HelloApp {
查看下面这个代码的字节码,可以发现有一个`
+
```java
public class ClassInitTest {
@@ -277,15 +277,15 @@ public class ClassInitTest {
**举例2:无 static 变量**
-
+
加上之后就有了
-
+
#### 4说明
-
+
在构造器中:
@@ -296,7 +296,7 @@ public class ClassInitTest {
若该类具有父类,JVM会保证子类的`
+
如上代码,加载流程如下:
@@ -374,17 +374,17 @@ class DeadThread{
-
+
**ExtClassLoader**
-
+
**AppClassLoader**
-
+
@@ -579,15 +579,15 @@ public class CustomClassLoader extends ClassLoader {
ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器)
-
+
sun.misc.Launcher 它是一个java虚拟机的入口应用
-
+
#### 获取ClassLoader途径
-
+
@@ -640,7 +640,7 @@ Java虚拟机对class文件采用的是**按需加载**的方式,也就是说
3. 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
4. 父类加载器一层一层往下分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常
-
+
### 双亲委派机制代码演示
@@ -707,7 +707,7 @@ public class String {
}
```
-
+
由于双亲委派机制一直找父类,所以最后找到了Bootstrap ClassLoader,Bootstrap ClassLoader找到的是 JDK 自带的 String 类,在那个String类中并没有 main() 方法,所以就报了上面的错误。
@@ -765,7 +765,7 @@ Process finished with exit code 1
-
+
diff --git a/docs/JVM/JVM系列-第3章-运行时数据区.md b/docs/JVM/JVM系列-第3章-运行时数据区.md
index f23a9ba..420e87c 100644
--- a/docs/JVM/JVM系列-第3章-运行时数据区.md
+++ b/docs/JVM/JVM系列-第3章-运行时数据区.md
@@ -8,7 +8,7 @@ categories:
- 1.内存与垃圾回收篇
keywords: JVM,虚拟机。
description: JVM系列-第3章-运行时数据区。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/jvm.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/jvm.png'
abbrlink: a7ad3cab
date: 2020-11-09 15:38:42
---
@@ -27,15 +27,15 @@ date: 2020-11-09 15:38:42
本节主要讲的是运行时数据区,也就是下图这部分,它是在类加载完成后的阶段
-
+
当我们通过前面的:类的加载 --> 验证 --> 准备 --> 解析 --\> 初始化,这几个阶段完成后,就会用到执行引擎对我们的类进行使用,同时执行引擎将会使用到我们运行时数据区
-
+
类比一下也就是大厨做饭,我们把大厨后面的东西(切好的菜,刀,调料),比作是运行时数据区。而厨师可以类比于执行引擎,将通过准备的东西进行制作成精美的菜品。
-
+
@@ -50,7 +50,7 @@ date: 2020-11-09 15:38:42
> 下图来自阿里巴巴手册JDK8
-
+
@@ -62,7 +62,7 @@ date: 2020-11-09 15:38:42
- 线程独有:独立包括程序计数器、栈、本地方法栈
- 线程间共享:堆、堆外内存(永久代或元空间、代码缓存)
-
+
@@ -70,7 +70,7 @@ date: 2020-11-09 15:38:42
**每个JVM只有一个Runtime实例**。即为运行时环境,相当于内存结构的中间的那个框框:运行时环境。
-
+
@@ -114,7 +114,7 @@ PC寄存器介绍
> 官方文档网址:https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
-
+
1. JVM中的程序计数寄存器(Program Counter Register)中,Register的命名源于CPU的寄存器,**寄存器存储指令相关的现场信息**。CPU只有把数据装载到寄存器才能够运行。
2. 这里,并非是广义上所指的物理寄存器,或许将其翻译为PC计数器(或指令计数器)会更加贴切(也称为程序钩子),并且也不容易引起一些不必要的误会。**JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟**。
@@ -132,7 +132,7 @@ PC寄存器介绍
PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令,并执行该指令。
-
+
@@ -268,7 +268,7 @@ SourceFile: "PCRegisterTest.java"
* 左边的数字代表**指令地址(指令偏移)**,即 PC 寄存器中可能存储的值,然后执行引擎读取 PC 寄存器中的值,并执行该指令
-
+
@@ -282,7 +282,7 @@ SourceFile: "PCRegisterTest.java"
2. JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令
-
+
@@ -306,7 +306,7 @@ CPU 时间片
3. 但在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,**每个程序轮流执行**。
-
+
@@ -314,7 +314,7 @@ CPU 时间片
## 本地方法
-
+
@@ -393,7 +393,7 @@ Java使用起来非常方便,然而有些层次的任务用Java实现起来不
4. 本地方法一般是使用C语言或C++语言实现的。
5. 它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。
-
+
diff --git a/docs/JVM/JVM系列-第4章-虚拟机栈.md b/docs/JVM/JVM系列-第4章-虚拟机栈.md
index 2a9e4cc..ec3d516 100644
--- a/docs/JVM/JVM系列-第4章-虚拟机栈.md
+++ b/docs/JVM/JVM系列-第4章-虚拟机栈.md
@@ -8,7 +8,7 @@ categories:
- 1.内存与垃圾回收篇
keywords: JVM,虚拟机。
description: JVM系列-第4章-虚拟机栈。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/jvm.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/jvm.png'
abbrlink: 5b1b6560
date: 2020-11-10 10:38:42
---
@@ -31,7 +31,7 @@ date: 2020-11-10 10:38:42
1. 首先栈是运行时的单位,而堆是存储的单位。
2. 即:栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放,放哪里
-
+
@@ -66,7 +66,7 @@ date: 2020-11-10 10:38:42
-
+
- 虚拟机栈的生命周期
- 生命周期和线程一致,也就是线程结束了,该虚拟机栈也销毁了
@@ -90,7 +90,7 @@ date: 2020-11-10 10:38:42
- 对于栈来说不存在垃圾回收问题
- 栈不需要GC,但是可能存在OOM
-
+
### 虚拟机栈的异常
@@ -164,7 +164,7 @@ Exception in thread "main" java.lang.StackOverflowError
**设置栈参数之后**
-
+
部分输出结果
@@ -202,7 +202,7 @@ Exception in thread "main" java.lang.StackOverflowError
4. 如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前帧。
-
+
1. **不同线程中所包含的栈帧是不允许存在相互引用的**,即不可能在一个栈帧之中引用另外一个线程的栈帧。
2. 如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧。
@@ -230,11 +230,11 @@ Exception in thread "main" java.lang.StackOverflowError
- 一些附加信息
-
+
并行每个线程下的栈都是私有的,因此每个线程都有自己各自的栈,并且每个栈里面都有很多栈帧,栈帧的大小主要由局部变量表 和 操作数栈决定的
-
+
局部变量表
-------
@@ -312,7 +312,7 @@ public class LocalVariablesTest {
}
```
-
+
看完字节码后,可得结论:所以局部变量表所需的容量大小是在编译期确定下来的。
@@ -324,29 +324,29 @@ public class LocalVariablesTest {
1、0-15 也就是有16行字节码
-
+
2、方法异常信息表
-
+
3、Misc
-
+
4、行号表
Java代码的行号和字节码指令行号的对应关系
-
+
5、注意:生效行数和剩余有效行数都是针对于字节码文件的行数
-
+
1、图中圈的东西表示该局部变量的作用域
@@ -370,7 +370,7 @@ Java代码的行号和字节码指令行号的对应关系
6. 如果需要访问局部变量表中一个64bit的局部变量值时,只需要使用前一个索引即可。(比如:访问long或double类型变量)
7. 如果当前帧是由构造方法或者实例方法创建的,那么**该对象引用this将会存放在index为0的slot处**,其余的参数按照参数表顺序继续排列。(this也相当于一个变量)
-
+
### Slot代码示例
@@ -388,7 +388,7 @@ Java代码的行号和字节码指令行号的对应关系
局部变量表:this 存放在 index = 0 的位置
-
+
@@ -408,7 +408,7 @@ Java代码的行号和字节码指令行号的对应关系
weight 为 double 类型,index 直接从 3 蹦到了 5
-
+
@@ -451,7 +451,7 @@ this 不存在与 static 方法的局部变量表中,所以无法调用
局部变量 c 重用了局部变量 b 的 slot 位置
-
+
@@ -499,13 +499,13 @@ this 不存在与 static 方法的局部变量表中,所以无法调用
- 比如:执行复制、交换、求和等操作
-
+
-
+
@@ -536,7 +536,7 @@ this 不存在与 static 方法的局部变量表中,所以无法调用
-
+
局部变量表就相当于食材
@@ -571,23 +571,23 @@ this 不存在与 static 方法的局部变量表中,所以无法调用
10 return
```
-
+
### 一步一步看流程
1、首先执行第一条语句,PC寄存器指向的是0,也就是指令地址为0,然后使用bipush让操作数15入操作数栈。
-
+
2、执行完后,PC寄存器往下移,指向下一行代码,下一行代码就是将操作数栈的元素存储到局部变量表1的位置(istore_1),我们可以看到局部变量表的已经增加了一个元素。并且操作数栈为空了
* 解释为什么局部变量表索引从 1 开始,因为该方法为实例方法,局部变量表索引为 0 的位置存放的是 this
-
+
3、然后PC下移,指向的是下一行。让操作数8也入栈,同时执行store操作,存入局部变量表中
-
+
@@ -595,11 +595,11 @@ this 不存在与 static 方法的局部变量表中,所以无法调用
iload_1:取出局部变量表中索引为1的数据入操作数栈
-
+
5、然后将操作数栈中的两个元素执行相加操作,并存储在局部变量表3的位置
-
+
@@ -607,7 +607,7 @@ iload_1:取出局部变量表中索引为1的数据入操作数栈
**关于类型转换的说明**
-
+
@@ -616,7 +616,7 @@ iload_1:取出局部变量表中索引为1的数据入操作数栈
-
+
- m改成800之后,byte存储不了,就成了short型,sipush 800
@@ -645,11 +645,11 @@ iload_1:取出局部变量表中索引为1的数据入操作数栈
getSum() 方法字节码指令:最后带着个 ireturn
-
+
testGetSum() 方法字节码指令:一上来就加载 getSum() 方法的返回值()
-
+
@@ -841,7 +841,7 @@ SourceFile: "DynamicLinkingTest.java"
-
+
@@ -1136,7 +1136,7 @@ interface MethodInterface {
Son 类中 show() 方法的字节码指令如下
-
+
@@ -1176,7 +1176,7 @@ public class Lambda {
}
```
-
+
@@ -1233,7 +1233,7 @@ Java:String info = "mogu blog"; (Java是静态类型语言的,会先
如图所示:如果类中重写了方法,那么调用的时候,就会直接在该类的虚方法表中查找
-
+
1、比如说son在调用toString的时候,Son没有重写过,Son的父类Father也没有重写过,那就直接调用Object类的toString。那么就直接在虚方法表里指明toString直接指向Object类。
@@ -1243,24 +1243,24 @@ Java:String info = "mogu blog"; (Java是静态类型语言的,会先
**例子2**
-
+
-
+
-
+
-
+
方法返回地址
--------
-
+
> 在一些帖子里,方法返回地址、动态链接、一些附加信息 也叫做帧数据区
@@ -1309,7 +1309,7 @@ Java:String info = "mogu blog"; (Java是静态类型语言的,会先
2. 方法执行过程中,抛出异常时的异常处理,存储在一个异常处理表,方便在发生异常的时候找到处理异常的代码
-
+
@@ -1321,7 +1321,7 @@ Java:String info = "mogu blog"; (Java是静态类型语言的,会先
* target :出现异常跳转至地址为 11 的指令执行
* type :捕获异常的类型
-
+
diff --git a/docs/JVM/JVM系列-第5章-堆.md b/docs/JVM/JVM系列-第5章-堆.md
index 59aa5bc..c0438a1 100644
--- a/docs/JVM/JVM系列-第5章-堆.md
+++ b/docs/JVM/JVM系列-第5章-堆.md
@@ -8,7 +8,7 @@ categories:
- 1.内存与垃圾回收篇
keywords: JVM,虚拟机。
description: JVM系列-第5章-堆。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/jvm.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/jvm.png'
abbrlink: 50ac3a1c
date: 2020-11-11 20:38:42
---
@@ -27,7 +27,7 @@ date: 2020-11-11 20:38:42
1. 堆针对一个JVM进程来说是唯一的。也就是**一个进程只有一个JVM实例**,一个JVM实例中就有一个运行时数据区,一个运行时数据区只有一个堆和一个方法区。
2. 但是**进程包含多个线程,他们是共享同一堆空间的**。
-
+
@@ -68,7 +68,7 @@ public class SimpleHeap {
}
```
-
+
@@ -88,13 +88,13 @@ public class SimpleHeap {
约定:新生区 <–> 新生代 <–> 年轻代 、 养老区 <–> 老年区 <–> 老年代、 永久区 <–\> 永久代
-
+
2. 堆空间内部结构,JDK1.8之前从永久代 替换成 元空间
-
+
@@ -122,17 +122,17 @@ public class HeapDemo {
1、双击jdk目录下的这个文件
-
+
2、工具 -> 插件 -> 安装Visual GC插件
-
+
3、运行上面的代码
-
+
@@ -215,7 +215,7 @@ public class HeapSpaceInitial {
设置下参数再看
-
+
```java
public class HeapSpaceInitial {
@@ -250,7 +250,7 @@ public class HeapSpaceInitial {
**方式一: jps / jstat -gc 进程id**
-
+
> jps:查看java进程
>
@@ -285,7 +285,7 @@ OU: 老年代使用的量
**方式二:-XX:+PrintGCDetails**
-
+
@@ -333,11 +333,11 @@ Process finished with exit code 1
2、堆内存变化图
-
+
3、原因:大对象导致堆内存溢出
-
+
@@ -357,9 +357,9 @@ Process finished with exit code 1
3、其中年轻代又可以划分为Eden空间、Survivor0空间和Survivor1空间(有时也叫做from区、to区)
-
+
-
+
- 配置新生代与老年代在堆结构的占比
@@ -383,7 +383,7 @@ Process finished with exit code 1
-
+
@@ -439,7 +439,7 @@ public class EdenSurvivorTest {
1、我们创建的对象,一般都是存放在Eden区的,**当我们Eden区满了后,就会触发GC操作**,一般被称为 YGC / Minor GC操作
-
+
2、当我们进行一次垃圾收集后,红色的对象将会被回收,而绿色的独享还被占用着,存放在S0(Survivor From)区。同时我们给每个对象设置了一个年龄计数器,经过一次回收后还存在的对象,将其年龄加 1。
@@ -453,11 +453,11 @@ public class EdenSurvivorTest {
>
> 3、也就是说s0区和s1区在互相转换。
-
+
4、我们继续不断的进行对象生成和垃圾回收,当Survivor中的对象的年龄达到15的时候,将会触发一次 Promotion 晋升的操作,也就是将年轻代中的对象晋升到老年代中
-
+
关于垃圾回收:频繁在新生区收集,很少在养老区收集,几乎不在永久区/元空间收集。
@@ -475,7 +475,7 @@ public class EdenSurvivorTest {
* 那万一老年代都放不下,则先触发FullGC ,再看看能不能放下,放得下最好,但如果还是放不下,那只能报 OOM
3. 如果 Eden 区满了,将对象往幸存区拷贝时,发现幸存区放不下啦,那只能便宜了某些新对象,让他们直接晋升至老年区
-
+
### 常用调优工具
@@ -528,7 +528,7 @@ GC分类
3. Minor GC会引发STW(Stop The World),暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
-
+
@@ -649,7 +649,7 @@ Heap
* 新生代:有Eden、两块大小相同的survivor(又称为from/to或s0/s1)构成,to总为空。
* 老年代:存放新生代中经历多次GC仍然存活的对象。
-
+
@@ -661,7 +661,7 @@ Heap
-
+
@@ -711,7 +711,7 @@ TLAB(Thread Local Allocation Buffer)
2. 多线程同时分配内存时,使用TLAB可以避免一系列的非线程安全问题,同时还能够提升内存分配的吞吐量,因此我们可以将这种内存分配方式称之为**快速分配策略**。
3. 据我所知所有OpenJDK衍生出来的JVM都提供了TLAB的设计。
-
+
1、每个线程都有一个TLAB空间
@@ -741,7 +741,7 @@ TLAB(Thread Local Allocation Buffer)
**TLAB 分配过程**
-
+
diff --git a/docs/JVM/JVM系列-第6章-方法区.md b/docs/JVM/JVM系列-第6章-方法区.md
index 2d6c3cd..e5d83ce 100644
--- a/docs/JVM/JVM系列-第6章-方法区.md
+++ b/docs/JVM/JVM系列-第6章-方法区.md
@@ -8,7 +8,7 @@ categories:
- 1.内存与垃圾回收篇
keywords: JVM,虚拟机。
description: JVM系列-第6章-方法区。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/jvm.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/jvm.png'
abbrlink: 136cd965
date: 2020-11-13 19:38:42
---
@@ -24,7 +24,7 @@ date: 2020-11-13 19:38:42
ThreadLocal:如何保证多个线程在并发环境下的安全性?典型场景就是数据库连接管理,以及会话管理。
-
+
**栈、堆、方法区的交互关系**
@@ -35,7 +35,7 @@ ThreadLocal:如何保证多个线程在并发环境下的安全性?典型场
3. 真正的 person 对象存放在 Java 堆中
4. 在 person 对象中,有个指针指向方法区中的 person 类型数据,表明这个 person 对象是用方法区中的 Person 类 new 出来的
-
+
方法区的理解
--------
@@ -47,7 +47,7 @@ ThreadLocal:如何保证多个线程在并发环境下的安全性?典型场
1. 《Java虚拟机规范》中明确说明:尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。但对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
3. 所以,**方法区可以看作是一块独立于Java堆的内存空间**。
-
+
@@ -87,7 +87,7 @@ public class MethodAreaDemo {
简单的程序,加载了1600多个类
-
+
@@ -103,7 +103,7 @@ public class MethodAreaDemo {
5. 永久代、元空间二者并不只是名字变了,内部结构也调整了
6. 根据《Java虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出OOM异常
-
+
@@ -120,7 +120,7 @@ public class MethodAreaDemo {
2. -XX:MaxPermsize来设定永久代最大可分配空间。32位机器默认是64M,64位机器模式是82M
3. 当JVM加载的类信息容量超过了这个值,会报异常OutofMemoryError:PermGen space。
-
+
### JDK8及以后(元空间)
@@ -228,11 +228,11 @@ Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
#### 概念
-
+
《深入理解Java虚拟机》书中对方法区(Method Area)存储内容描述如下:它用于存储已被虚拟机加载的**类型信息、常量、静态变量、即时编译器编译后的代码缓存**等。
-
+
@@ -714,7 +714,7 @@ public static int count;
-
+
1. 方法区,内部包含了运行时常量池
2. 字节码文件,内部包含了常量池。(之前的字节码文件中已经看到了很多Constant pool的东西,这个就是常量池)
@@ -728,7 +728,7 @@ public static int count;
1. 一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述符信息外。还包含一项信息就是**常量池表**(**Constant Pool Table**),包括各种字面量和对类型、域和方法的符号引用。
2. 字面量: 10 , “我是某某”这种数字和字符串都是字面量
-
+
**为什么需要常量池?**
@@ -749,7 +749,7 @@ public static int count;
2. 比如说我们这个文件中有6个地方用到了"hello"这个字符串,如果不用常量池,就需要在6个地方全写一遍,造成臃肿。我们可以将"hello"等所需用到的结构信息记录在常量池中,并通过**引用的方式**,来加载、调用所需的结构
4. 这里的代码量其实很少了,如果代码多的话,引用的结构将会更多,这里就需要用到常量池了。
-
+
**常量池中有啥?**
@@ -924,69 +924,69 @@ SourceFile: "MethodAreaDemo.java"
1、初始状态
-
+
2、首先将操作数500压入操作数栈中
-
+
3、然后操作数 500 从操作数栈中取出,存储到局部变量表中索引为 1 的位置
-
+
4、
-
+
5、
-
+
6、
-
+
7、
-
+
8、
-
+
9、
-
+
10、
-
+
11、图片写错了是#25和#26(获得System类)
-
+
12、
-
+
13、
-
+
15、执行加法运算后,将计算结果放在操作数栈顶
-
+
16、就是真正的打印
-
+
17、
-
+
@@ -1021,7 +1021,7 @@ SourceFile: "MethodAreaDemo.java"
方法区由永久代实现,使用 JVM 虚拟机内存(虚拟的内存)
-
+
@@ -1029,7 +1029,7 @@ SourceFile: "MethodAreaDemo.java"
方法区由永久代实现,使用 JVM 虚拟机内存
-
+
@@ -1037,7 +1037,7 @@ SourceFile: "MethodAreaDemo.java"
方法区由元空间实现,使用物理机本地内存
-
+
@@ -1095,15 +1095,15 @@ public class StaticFieldTest {
JDK6环境下
-
+
JDK7环境下
-
+
JDK8环境
-
+
@@ -1150,7 +1150,7 @@ public class StaticObjTest {
4、测试发现:三个对象的数据在内存中的地址都落在Eden区范围内,所以结论:**只要是对象实例必然会在Java堆中分配**。
-
+
> 1、0x00007f32c7800000(Eden区的起始地址) ---- 0x00007f32c7b50000(Eden区的终止地址)
>
@@ -1162,7 +1162,7 @@ public class StaticObjTest {
5、接着,找到了一个引用该staticObj对象的地方,是在一个java.lang.Class的实例里,并且给出了这个实例的地址,通过Inspector查看该对象实例,可以清楚看到这确实是一个java.lang.Class类型的对象实例,里面有一个名为staticobj的实例字段:
-
+
从《Java虚拟机规范》所定义的概念模型来看,所有Class相关的信息都应该存放在方法区之中,但方法区该如何实现,《Java虚拟机规范》并未做出规定,这就成了一件允许不同虚拟机自己灵活把握的事情。JDK7及其以后版本的HotSpot虚拟机选择把静态变量与类型在Java语言一端的映射Class对象存放在一起,**存储于Java堆之中**,从我们的实验中也明确验证了这一点
@@ -1217,7 +1217,7 @@ public class StaticObjTest {
-
+
@@ -1271,7 +1271,7 @@ public class BufferTest {
直接占用了 1G 的本地内存
-
+
@@ -1281,13 +1281,13 @@ public class BufferTest {
原来采用BIO的架构,在读写本地文件时,我们需要从用户态切换成内核态
-
+
**直接缓冲区(NIO)**
NIO 直接操作物理磁盘,省去了中间过程
-
+
### 直接内存与 OOM
@@ -1351,7 +1351,7 @@ Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
-
+
diff --git a/docs/JVM/JVM系列-第7章-对象的实例化内存布局与访问定位.md b/docs/JVM/JVM系列-第7章-对象的实例化内存布局与访问定位.md
index 110e55a..7a9d2d9 100644
--- a/docs/JVM/JVM系列-第7章-对象的实例化内存布局与访问定位.md
+++ b/docs/JVM/JVM系列-第7章-对象的实例化内存布局与访问定位.md
@@ -8,7 +8,7 @@ categories:
- 1.内存与垃圾回收篇
keywords: JVM,虚拟机。
description: JVM系列-第7章-对象的实例化内存布局与访问定位。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/jvm.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/jvm.png'
abbrlink: debff71a
date: 2020-11-14 19:38:42
---
@@ -36,7 +36,7 @@ date: 2020-11-14 19:38:42
-
+
### 对象创建的方式
@@ -211,7 +211,7 @@ class Account{
对象的内存布局
---------
-
+
@@ -242,7 +242,7 @@ class Account{
图解内存布局
-
+
@@ -251,7 +251,7 @@ class Account{
**JVM是如何通过栈帧中的对象引用访问到其内部的对象实例呢?**
-
+
定位,通过栈上reference访问
@@ -262,7 +262,7 @@ class Account{
1. 缺点:在堆空间中开辟了一块空间作为句柄池,句柄池本身也会占用空间;通过两次指针访问才能访问到堆中的对象,效率低
2. 优点:reference中存储稳定句柄地址,对象被移动(垃圾收集时移动对象很普遍)时只会改变句柄中实例数据指针即可,reference本身不需要被修改
-
+
@@ -271,4 +271,4 @@ class Account{
1. 优点:直接指针是局部变量表中的引用,直接指向堆中的实例,在对象实例中有类型指针,指向的是方法区中的对象类型数据
2. 缺点:对象被移动(垃圾收集时移动对象很普遍)时需要修改 reference 的值
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/JVM/JVM系列-第8章-执行引擎.md b/docs/JVM/JVM系列-第8章-执行引擎.md
index e6fbdef..33224ec 100644
--- a/docs/JVM/JVM系列-第8章-执行引擎.md
+++ b/docs/JVM/JVM系列-第8章-执行引擎.md
@@ -8,7 +8,7 @@ categories:
- 1.内存与垃圾回收篇
keywords: JVM,虚拟机。
description: JVM系列-第8章-执行引擎。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/jvm.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/jvm.png'
abbrlink: 408712f4
date: 2020-11-15 19:48:42
---
@@ -23,7 +23,7 @@ date: 2020-11-15 19:48:42
-
+
### 执行引擎概述
@@ -34,7 +34,7 @@ date: 2020-11-15 19:48:42
3. JVM的主要任务是负责**装载字节码到其内部**,但字节码并不能够直接运行在操作系统之上,因为字节码指令并非等价于本地机器指令,它内部包含的仅仅只是一些能够被JVM所识别的字节码指令、符号表,以及其他辅助信息。
4. 那么,如果想要让一个Java程序运行起来,执行引擎(Execution Engine)的任务就是**将字节码指令解释/编译为对应平台上的本地机器指令才可以**。简单来说,JVM中的执行引擎充当了将高级语言翻译为机器语言的译者。
-
+
1、前端编译:从Java程序员-字节码文件的这个过程叫前端编译
@@ -51,7 +51,7 @@ date: 2020-11-15 19:48:42
3. 当然方法在执行的过程中,执行引擎有可能会通过存储在局部变量表中的对象引用准确定位到存储在Java堆区中的对象实例信息,以及通过对象头中的元数据指针定位到目标对象的类型信息。
4. 从外观上来看,所有的Java虚拟机的执行引擎输入、处理、输出都是一致的:输入的是字节码二进制流,处理过程是字节码解析执行、即时编译的等效过程,输出的是执行过程。
-
+
@@ -71,18 +71,18 @@ Java代码编译和执行过程
-
+
3. javac编译器(前端编译器)流程图如下所示:
-
+
4. Java字节码的执行是由JVM执行引擎来完成,流程图如下所示
-
+
@@ -105,7 +105,7 @@ Java代码编译和执行过程
**用图总结一下**
-
+
机器码 指令 汇编语言
-------------
@@ -164,7 +164,7 @@ Java代码编译和执行过程
-
+
@@ -193,7 +193,7 @@ Java代码编译和执行过程
2. 汇编过程:实际上指把汇编语言代码翻译成目标机器指令的过程。
-
+
@@ -211,7 +211,7 @@ Java代码编译和执行过程
3. 当一条字节码指令被解释执行完成后,接着再根据PC寄存器中记录的下一条需要被执行的字节码指令执行解释操作。
-
+
@@ -288,7 +288,7 @@ Java代码编译和执行过程
2. 在生产环境发布过程中,以分批的方式进行发布,根据机器数量划分成多个批次,每个批次的机器数至多占到整个集群的1/8。曾经有这样的故障案例:某程序员在发布平台进行分批发布,在输入发布总批数时,误填写成分为两批发布。如果是热机状态,在正常情况下一半的机器可以勉强承载流量,但由于刚启动的JVM均是解释执行,还没有进行热点代码统计和JIT动态编译,导致机器启动之后,当前1/2发布成功的服务器马上全部宕机,此故障说明了JIT的存在。—**阿里团队**
-
+
@@ -317,7 +317,7 @@ public class JITTest {
通过 JVisualVM 查看 JIT 编译器执行的编译次数
-
+
@@ -369,7 +369,7 @@ public class JITTest {
* 如果已超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。
* 如果未超过阈值,则使用解释器对字节码文件解释执行
-
+
@@ -387,7 +387,7 @@ public class JITTest {
它的作用是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge)。显然,建立回边计数器统计的目的就是为了触发OSR编译。
-
+
@@ -401,7 +401,7 @@ public class JITTest {
-
+
diff --git a/docs/JVM/JVM系列-第9章-StringTable(字符串常量池).md b/docs/JVM/JVM系列-第9章-StringTable(字符串常量池).md
index b00b39a..d1bef75 100644
--- a/docs/JVM/JVM系列-第9章-StringTable(字符串常量池).md
+++ b/docs/JVM/JVM系列-第9章-StringTable(字符串常量池).md
@@ -8,7 +8,7 @@ categories:
- 1.内存与垃圾回收篇
keywords: JVM,虚拟机。
description: JVM系列-第9章-StringTable(字符串常量池)。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/jvm.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/jvm.png'
abbrlink: ee2ba71e
date: 2020-11-16 12:38:02
---
@@ -166,11 +166,11 @@ str 的内容并没有变:“test ok” 位于字符串常量池中的另一
4. 在JDK7中,StringTable的长度默认值是60013,StringTablesize设置没有要求
5. 在JDK8中,StringTable的长度默认值是60013,StringTable可以设置的最小值为1009
-
+
-
+
@@ -276,11 +276,11 @@ String 的内存分配
-
+
-
+
@@ -382,23 +382,23 @@ public class StringTest4 {
1、程序启动时已经加载了 2293 个字符串常量
-
+
2、加载了一个换行符(println),所以多了一个
-
+
3、加载了字符串常量 “1”~“9”
-
+
4、加载字符串常量 “10”
-
+
5、之后的字符串"1" 到 "10"不会再次加载
-
+
@@ -425,7 +425,7 @@ class Memory {
分析运行时内存(foo() 方法是实例方法,其实图中少了一个 this 局部变量)
-
+
@@ -493,7 +493,7 @@ class Memory {
IDEA 反编译 class 文件后,来看这个问题
-
+
@@ -929,7 +929,7 @@ public class StringNewTest {
5. `23 ldc #8 ` :在字符串常量池中放入 “b”(如果之前字符串常量池中没有 “b” 的话)
6. `31 invokevirtual #9
+
@@ -988,13 +988,13 @@ JDK6 :正常眼光判断即可
* new String() 即在堆中
* str.intern() 则把字符串放入常量池中
-
+
JDK7及后续版本,**注意大坑**
-
+
@@ -1053,11 +1053,11 @@ public class StringExer1 {
**JDK6**
-
+
**JDK7/8**
-
+
@@ -1080,7 +1080,7 @@ public class StringExer1 {
}
```
-
+
**练习3**
@@ -1169,11 +1169,11 @@ public class StringIntern2 {
arr[i] = new String(String.valueOf(data[i % data.length]));
```
-
+
-
+
2、使用 intern() 方法:由于数组中字符串的引用都指向字符串常量池中的字符串,所以程序需要维护的 String 对象更少,内存占用也更低
@@ -1182,11 +1182,11 @@ arr[i] = new String(String.valueOf(data[i % data.length]));
arr[i] = new String(String.valueOf(data[i % data.length])).intern();
```
-
+
-
+
@@ -1218,11 +1218,11 @@ public class StringGCTest {
* Number of entries 和 Number of literals 明显没有 100000
* 以上两点均说明 StringTable 区发生了垃圾回收
-
+
-
+
diff --git a/docs/Java/Basis/Java8新特性.md b/docs/Java/Basis/Java8新特性.md
index fdfb545..eb9eb86 100644
--- a/docs/Java/Basis/Java8新特性.md
+++ b/docs/Java/Basis/Java8新特性.md
@@ -9,7 +9,7 @@ categories:
- 新特性
keywords: Java8,新特性,JDK8
description: 详解JDK8出现的新特性。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/java.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/java.png'
abbrlink: de3879ae
date: 2020-10-19 22:15:58
---
@@ -20,7 +20,7 @@ date: 2020-10-19 22:15:58
> 本篇文章只讲解比较重要的
-
+
@@ -308,13 +308,13 @@ public class LambdaTest1 {
**核心函数式接口**
-
+
**其它函数式接口**
-
+
@@ -902,7 +902,7 @@ Stream到底是什么呢?
-
+
@@ -1219,7 +1219,7 @@ public class StreamAPITest2 {
## 常用API
-
+
diff --git a/docs/Java/Basis/泛型.md b/docs/Java/Basis/泛型.md
index 9156e82..26d5f7f 100644
--- a/docs/Java/Basis/泛型.md
+++ b/docs/Java/Basis/泛型.md
@@ -9,7 +9,7 @@ categories:
- 重难点
keywords: Java基础,泛型
description: 万字长文详解Java泛型。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/java.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/java.png'
abbrlink: adb2faf0
date: 2020-10-19 22:21:58
---
@@ -1038,7 +1038,7 @@ class Dog extends Animal {
`test1()`在编译时就会飘红
-
+
@@ -1290,7 +1290,7 @@ public class Test_difference {
}
```
-
+
### 区别3:?通配符可以使用超类限定而T不行
diff --git a/docs/Java/collection/HashMap-JDK7源码讲解.md b/docs/Java/collection/HashMap-JDK7源码讲解.md
index 516b55d..451966c 100644
--- a/docs/Java/collection/HashMap-JDK7源码讲解.md
+++ b/docs/Java/collection/HashMap-JDK7源码讲解.md
@@ -8,7 +8,7 @@ categories:
- HashMap
keywords: Java集合,HashMap。
description: HashMap-JDK7源码讲解。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/java.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/java.png'
abbrlink: f1f58db2
date: 2020-11-01 10:21:58
---
@@ -202,7 +202,7 @@ hadoop2
大致是这样的一个结构
-
+
- 每个链表就算哈希表的桶(bucket)
- 链表的节点值就算一个键值对
@@ -398,7 +398,7 @@ static class Entry
+
1、一个正方形代表一个Entry对象,同时也代表一个键值对。
@@ -769,15 +769,15 @@ void transfer(Entry[] newTable, boolean rehash) {
大概画了一下图:
-
+
-
+
-
+
-
+
@@ -871,7 +871,7 @@ void transfer(Entry[] newTable, boolean rehash) {
**hashmap初始状态**
-
+
@@ -897,7 +897,7 @@ void transfer(Entry[] newTable, boolean rehash) {
**两个线程调用完毕之后,hashmap目前是这样的。**
-
+
@@ -918,9 +918,9 @@ void transfer(Entry[] newTable, boolean rehash) {
3、来看下此时内存里的状态
-
+
-
+
## 步骤4
@@ -957,7 +957,7 @@ void transfer(Entry[] newTable, boolean rehash) {
2、线程2直接**扩容完毕**,那么完成后的状态是这样【假设e2和e3还是hash到同一个位置】
-
+
3、线程1还是原来的状态
@@ -967,11 +967,11 @@ void transfer(Entry[] newTable, boolean rehash) {
目前两个线程里的新数组是这样的
-
+
为了方便后面观看,我画成这样。
-
+
@@ -1015,7 +1015,7 @@ void transfer(Entry[] newTable, boolean rehash) {
也就变成了下面这个样子。
-
+
@@ -1061,7 +1061,7 @@ void transfer(Entry[] newTable, boolean rehash) {
执行完,变成这样。
-
+
@@ -1077,7 +1077,7 @@ void transfer(Entry[] newTable, boolean rehash) {
3、执行pos_3: newTable[i] = e得到 newTable1[3] == e2
-
+
这样就形成了循环链表,再get()数据就会陷入死循环。
diff --git a/docs/Java/collection/HashMap-JDK8源码讲解及常见面试题.md b/docs/Java/collection/HashMap-JDK8源码讲解及常见面试题.md
index fdebb79..91d8ea2 100644
--- a/docs/Java/collection/HashMap-JDK8源码讲解及常见面试题.md
+++ b/docs/Java/collection/HashMap-JDK8源码讲解及常见面试题.md
@@ -8,7 +8,7 @@ categories:
- HashMap
keywords: Java集合,HashMap。
description: HashMap-JDK8源码讲解及常见面试题。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/java.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/java.png'
abbrlink: cbc5672a
date: 2020-11-01 10:22:05
---
@@ -27,7 +27,7 @@ date: 2020-11-01 10:22:05
在JDK8中,优化了HashMap的数据结构,引入了红黑树。即HashMap的数据结构:数组+链表+红黑树。HashMap变成了这样。
-
+
### 为什么要引入红黑树
@@ -456,7 +456,7 @@ Process finished with exit code 0
JDK8 hash的运算原理:高位参与低位运算,使得hash更加均匀。
-
+
@@ -569,7 +569,7 @@ JDK8 hash的运算原理:高位参与低位运算,使得hash更加均匀。
JDK8扩容时,数据在数组下标的计算方式
-
+
* `JDK8`根据此结论作出的新元素存储位置计算规则非常简单,提高了扩容效率。
diff --git a/docs/design_patterns/设计模式-01.设计思想.md b/docs/design_patterns/设计模式-01.设计思想.md
index 995d542..0ed9f7e 100644
--- a/docs/design_patterns/设计模式-01.设计思想.md
+++ b/docs/design_patterns/设计模式-01.设计思想.md
@@ -8,7 +8,7 @@ categories:
- 01.设计思想
keywords: 设计模式,设计思想
description: 设计模式第一部分-常用设计思想。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/design_patterns.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/design_patterns.jpg'
abbrlink: c3dcce5d
date: 2021-06-07 17:21:58
---
@@ -198,7 +198,7 @@ public class Ostrich extends AbstractBird { //鸵鸟
1. 这种设计思路虽然可以解决问题,但不够优美。因为除了鸵鸟之外,不会飞的鸟还有很多,比如企鹅。对于这些不会飞的鸟来说,我们都需要重写 fly() 方法,抛出异常。这样的设计,一方面,徒增了编码的工作量;另一方面,也违背了我们之后要讲的最小知识原则(Least Knowledge Principle,也叫最少知识原则或者迪米特法则),暴露不该暴露的接口给外部,增加了类使用过程中被误用的概率。
2. 你可能又会说,那我们再通过 AbstractBird 类派生出两个更加细分的抽象类:会飞的鸟类 AbstractFlyableBird 和不会飞的鸟类 AbstractUnFlyableBird,让麻雀、乌鸦这些会飞的鸟都继承 AbstractFlyableBird,让鸵鸟、企鹅这些不会飞的鸟,都继承 AbstractUnFlyableBird 类,不就可以了吗?具体的继承关系如下图所示:
-
+
@@ -206,7 +206,7 @@ public class Ostrich extends AbstractBird { //鸵鸟
2. 是否会飞?是否会叫?两个行为搭配起来会产生四种情况:会飞会叫、不会飞会叫、会飞不会叫、不会飞不会叫。如果我们继续沿用刚才的设计思路,那就需要再定义四个抽象类(AbstractFlyableTweetableBird、AbstractFlyableUnTweetableBird、AbstractUnFlyableTweetableBird、AbstractUnFlyableUnTweetableBird)。
-
+
@@ -376,7 +376,7 @@ demofunction(client);
-
+
diff --git a/docs/design_patterns/设计模式-02.经典设计原则-第一节[必读].md b/docs/design_patterns/设计模式-02.经典设计原则-第一节[必读].md
index c8d9629..215f33c 100644
--- a/docs/design_patterns/设计模式-02.经典设计原则-第一节[必读].md
+++ b/docs/design_patterns/设计模式-02.经典设计原则-第一节[必读].md
@@ -8,7 +8,7 @@ categories:
- 02.经典设计原则
keywords: 设计模式,经典设计原则
description: 设计模式-经典设计原则,例如:单一职责原则,开闭原则,接口隔离原则等等。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/design_patterns.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/design_patterns.jpg'
abbrlink: fc1c7619
date: 2021-06-13 19:21:58
---
diff --git a/docs/design_patterns/设计模式-02.经典设计原则-第二节[必读].md b/docs/design_patterns/设计模式-02.经典设计原则-第二节[必读].md
index af94ca4..1dad219 100644
--- a/docs/design_patterns/设计模式-02.经典设计原则-第二节[必读].md
+++ b/docs/design_patterns/设计模式-02.经典设计原则-第二节[必读].md
@@ -8,7 +8,7 @@ categories:
- 02.经典设计原则
keywords: 设计模式,经典设计原则
description: 设计模式-经典设计原则,例如:迪米特法则,依赖反转原则,KISS等等。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/design_patterns.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/design_patterns.jpg'
abbrlink: 994a8ed3
date: 2021-06-20 19:21:58
---
@@ -724,7 +724,7 @@ public class UserRepo {
前面也提到,“高内聚”有助于“松耦合”,同理,“低内聚”也会导致“紧耦合”。关于这一点,我画了一张对比图来解释。图中左边部分的代码结构是“高内聚、松耦合”;右边部分正好相反,是“低内聚、紧耦合”。
-
+
diff --git a/docs/design_patterns/设计模式-03.01-创建型-单例.md b/docs/design_patterns/设计模式-03.01-创建型-单例.md
index 53ce080..d131268 100644
--- a/docs/design_patterns/设计模式-03.01-创建型-单例.md
+++ b/docs/design_patterns/设计模式-03.01-创建型-单例.md
@@ -8,7 +8,7 @@ categories:
- 03.创建型
keywords: 设计模式,单例
description: 详解了单例设计模式。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/design_patterns.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/design_patterns.jpg'
abbrlink: b5a1ed4a
date: 2021-06-26 21:51:58
---
diff --git a/docs/design_patterns/设计模式-03.02-创建型-工厂&建造者&原型.md b/docs/design_patterns/设计模式-03.02-创建型-工厂&建造者&原型.md
index 938a825..ff04ec4 100644
--- a/docs/design_patterns/设计模式-03.02-创建型-工厂&建造者&原型.md
+++ b/docs/design_patterns/设计模式-03.02-创建型-工厂&建造者&原型.md
@@ -10,7 +10,7 @@ categories:
- 03.创建型
keywords: 设计模式,工厂,建造者,原型
description: 详解常用的工厂模式和建造者模式,以及不常用的原型模式
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/design_patterns.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/design_patterns.jpg'
abbrlink: ba432704
date: 2021-06-27 00:51:58
---
@@ -727,7 +727,7 @@ public class BeansFactory {
1. 在平时的开发中,创建一个对象最常用的方式是,使用 new 关键字调用类的构造函数来完成。我的问题是,什么情况下这种方式就不适用了,就需要采用建造者模式来创建对象呢?你可以先思考一下,下面我通过一个例子来带你看一下。
2. 假设有这样一道设计面试题:我们需要定义一个资源池配置类 ResourcePoolConfig。这里的资源池,你可以简单理解为线程池、连接池、对象池等。在这个资源池配置类中,有以下几个成员变量,也就是可配置项。现在,请你编写代码实现这个 ResourcePoolConfig 类。
-
+
@@ -1003,7 +1003,7 @@ r.setHeight(3); // r is valid
2. 如果你熟悉的是 Java 语言,可以直接使用语言中提供的 HashMap 容器来实现。其中,HashMap 的 key 为搜索关键词,value 为关键词详细信息(比如搜索次数)。我们只需要将数据从数据库中读取出来,放入 HashMap 就可以了。
3. 不过,我们还有另外一个系统 B,专门用来分析搜索日志,定期(比如间隔 10 分钟)批量地更新数据库中的数据,并且标记为新的数据版本。比如,在下面的示例图中,我们对 v2 版本的数据进行更新,得到 v3 版本的数据。这里我们假设只有更新和新添关键词,没有删除关键词的行为。
-
+
@@ -1150,15 +1150,15 @@ public class Demo {
我们来看,在内存中,用散列表组织的搜索关键词信息是如何存储的。我画了一张示意图,大致结构如下所示。从图中我们可以发现,散列表索引中,每个结点存储的 key 是搜索关键词,value 是 SearchWord 对象的内存地址。SearchWord 对象本身存储在散列表之外的内存空间中。
-
+
浅拷贝和深拷贝的区别在于,浅拷贝只会复制图中的索引(散列表),不会复制数据(SearchWord 对象)本身。相反,深拷贝不仅仅会复制索引,还会复制数据本身。浅拷贝得到的对象(newKeywords)跟原始对象(currentKeywords)共享数据(SearchWord 对象),而深拷贝得到的是一份完完全全独立的对象。具体的对比如下图所示:
-
+
-
+
diff --git a/docs/design_patterns/设计模式-04.01-结构型-代理&桥接&装饰器&适配器.md b/docs/design_patterns/设计模式-04.01-结构型-代理&桥接&装饰器&适配器.md
index aaf201d..e49f269 100644
--- a/docs/design_patterns/设计模式-04.01-结构型-代理&桥接&装饰器&适配器.md
+++ b/docs/design_patterns/设计模式-04.01-结构型-代理&桥接&装饰器&适配器.md
@@ -11,7 +11,7 @@ categories:
- 04.结构型
keywords: 设计模式,代理模式,桥接模式,装饰器模式,适配器模式
description: 对代理模式,桥接模式,装饰器模式,适配器模式这4个模式进行了比较详细的讲述。其实学习设计模式主要是为了后序看源码
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/design_patterns.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/design_patterns.jpg'
abbrlink: 926a065c
date: 2021-07-04 00:51:58
---
@@ -608,7 +608,7 @@ IUserController userController = (IUserController) proxy.createProxy(new UserCon
现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:
-
+
@@ -616,7 +616,7 @@ IUserController userController = (IUserController) proxy.createProxy(new UserCon
传统方法对应的类图
-
+
1. 扩展性问题(**类爆炸**),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。
2. 违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本.
@@ -933,7 +933,7 @@ public class DriverManager {
-
+
@@ -1112,7 +1112,7 @@ public class TrivialNotification extends Notification {
### 方案一
-
+
@@ -1127,7 +1127,7 @@ public class TrivialNotification extends Notification {
前面分析到方案 1 因为咖啡单品+调料组合会造成类的倍增,因此可以做改进,将调料内置到 Drink 类,这样就不会造成类数量过多。从而提高项目的维护性(如图)
-
+
@@ -1142,7 +1142,7 @@ public class TrivialNotification extends Notification {
### 装饰器模式代码
-
+
#### Drink【抽象类-主体Component】
@@ -1390,7 +1390,7 @@ Java IO 类库非常庞大和复杂,有几十个类,负责 IO 数据的读
针对不同的读取和写入场景,Java IO 又在这四个父类基础之上,扩展出了很多子类。具体如下所示:
-
+
diff --git a/docs/design_patterns/设计模式-04.02-结构型-门面&组合&享元.md b/docs/design_patterns/设计模式-04.02-结构型-门面&组合&享元.md
index 111e448..9d89373 100644
--- a/docs/design_patterns/设计模式-04.02-结构型-门面&组合&享元.md
+++ b/docs/design_patterns/设计模式-04.02-结构型-门面&组合&享元.md
@@ -10,7 +10,7 @@ categories:
- 04.结构型
keywords: 设计模式,门面模式,组合模式,享元模式
description: 对代理模式,门面模式,组合模式,享元模式这3个设计模式进行了比较详细的讲述。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/design_patterns.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/design_patterns.jpg'
abbrlink: 5ea604e0
date: 2021-07-05 16:51:58
---
@@ -75,7 +75,7 @@ date: 2021-07-05 16:51:58
### 传统方案
-
+
1. 在 ClientTest 的 main 方法中,创建各个子系统的对象,并直接去调用子系统(对象)相关方法,会造成调用过程 混乱,没有清晰的过程 不利于在 ClientTest 中,去维护对子系统的操作
@@ -623,7 +623,7 @@ public class Demo {
-
+
diff --git a/docs/design_patterns/设计模式-05.01-行为型-观察者&模板.md b/docs/design_patterns/设计模式-05.01-行为型-观察者&模板.md
index a5d9c48..e314ad5 100644
--- a/docs/design_patterns/设计模式-05.01-行为型-观察者&模板.md
+++ b/docs/design_patterns/设计模式-05.01-行为型-观察者&模板.md
@@ -8,7 +8,7 @@ categories:
- 05.行为型
keywords: 观察者模式,模板模式
description: 观察者模式,模板模式。很常用的两个模式
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/design_patterns.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/design_patterns.jpg'
abbrlink: dd09051e
date: 2021-07-14 16:51:58
---
@@ -34,7 +34,7 @@ date: 2021-07-14 16:51:58
### 方案一
-
+
@@ -721,11 +721,11 @@ public DObserver{
1. Guava EventBus 的功能我们已经讲清楚了,总体上来说,还是比较简单的。接下来,我们就重复造轮子,“山寨”一个 EventBus 出来。
2. 我们重点来看,EventBus 中两个核心函数 register() 和 post() 的实现原理。弄懂了它们,基本上就弄懂了整个 EventBus 框架。下面两张图是这两个函数的实现原理图。
-
+
-
+
diff --git a/docs/design_patterns/设计模式-05.02-行为型-策略&职责链.md b/docs/design_patterns/设计模式-05.02-行为型-策略&职责链.md
index a8b7e10..39ec29b 100644
--- a/docs/design_patterns/设计模式-05.02-行为型-策略&职责链.md
+++ b/docs/design_patterns/设计模式-05.02-行为型-策略&职责链.md
@@ -8,7 +8,7 @@ categories:
- 05.行为型
keywords: 策略模式,职责链模式
description: 不多说,看文章
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/design_patterns.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/design_patterns.jpg'
abbrlink: 2c3cc5fd
date: 2021-08-01 15:51:58
---
@@ -1171,7 +1171,7 @@ public class SensitiveWordFilter {
Servlet Filter 是 Java Servlet 规范中定义的组件,翻译成中文就是过滤器,它可以实现对 HTTP 请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。因为它是 Servlet 规范的一部分,所以,只要是支持 Servlet 的 Web 容器(比如,Tomcat、Jetty 等),都支持过滤器功能。为了帮助你理解,我画了一张示意图阐述它的工作原理,如下所示。
-
+
在实际项目中,我们该如何使用 Servlet Filter 呢?我写了一个简单的示例代码,如下所示。添加一个过滤器,我们只需要定义一个实现 javax.servlet.Filter 接口的过滤器类,并且将它配置在 web.xml 配置文件中。Web 容器启动的时候,会读取 web.xml 中的配置,创建过滤器对象。当有请求到来的时候,会先经过过滤器,然后才由 Servlet 来处理。
@@ -1279,7 +1279,7 @@ ApplicationFilterChain 中的 doFilter() 函数的代码实现比较有技巧,
1. 刚刚讲了 Servlet Filter,现在我们来讲一个功能上跟它非常类似的东西,Spring Interceptor,翻译成中文就是拦截器。尽管英文单词和中文翻译都不同,但这两者基本上可以看作一个概念,都用来实现对 HTTP 请求进行拦截处理。
2. 它们不同之处在于,Servlet Filter 是 Servlet 规范的一部分,实现依赖于 Web 容器。Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架来提供实现。客户端发送的请求,会先经过 Servlet Filter,然后再经过 Spring Interceptor,最后到达具体的业务代码中。我画了一张图来阐述一个请求的处理流程,具体如下所示。
-
+
diff --git a/docs/design_patterns/设计模式-05.03-行为型-状态&迭代器.md b/docs/design_patterns/设计模式-05.03-行为型-状态&迭代器.md
index 0d9480b..4d1baf8 100644
--- a/docs/design_patterns/设计模式-05.03-行为型-状态&迭代器.md
+++ b/docs/design_patterns/设计模式-05.03-行为型-状态&迭代器.md
@@ -8,7 +8,7 @@ categories:
- 05.行为型
keywords: 状态模式,迭代器模式
description: 看文章
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/design_patterns.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/design_patterns.jpg'
abbrlink: 877f4ef2
date: 2021-08-02 15:51:58
---
@@ -30,7 +30,7 @@ date: 2021-08-02 15:51:58
4. 实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的“状态”,游戏情节(比如吃了蘑菇)就是状态机中的“事件”,加减积分就是状态机中的“动作”。比如,吃蘑菇这个事件,会触发状态的转移:从小马里奥转移到超级马里奥,以及触发动作的执行(增加 100 积分)。
5. 为了方便接下来的讲解,我对游戏背景做了简化,只保留了部分状态和事件。简化之后的状态转移如下图所示:
-
+
@@ -180,7 +180,7 @@ public class MarioStateMachine {
1. 实际上,上面这种实现方法有点类似 hard code,对于复杂的状态机来说不适用,而状态机的第二种实现方式查表法,就更加合适了。接下来,我们就一块儿来看下,如何利用查表法来补全骨架代码。
2. 实际上,除了用状态转移图来表示之外,状态机还可以用二维表来表示,如下所示。在这个二维表中,第一维表示当前状态,第二维表示事件,值表示当前状态经过事件之后,转移到的新状态及其执行的动作。
-
+
3. 相对于分支逻辑的实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,我们只需要修改 transitionTable 和 actionTable 两个二维数组即可。实际上,如果我们把这两个二维数组存储在配置文件中,当需要修改状态机时,我们甚至可以不修改任何代码,只需要修改配置文件就可以了。具体的代码如下所示:
@@ -511,7 +511,7 @@ public class MarioStateMachine {
2. 在开篇中我们讲到,它用来遍历集合对象。这里说的“集合对象”也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如数组、链表、树、图、跳表。迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。
3. 迭代器是用来遍历容器的,所以,一个完整的迭代器模式一般会涉及**容器和容器迭代器**部分两部分内容。为了达到基于接口而非实现编程的目的,容器又包含容器接口、容器实现类,迭代器又包含迭代器接口、迭代器实现类。对于迭代器模式,我画了一张简单的类图,你可以看一看,先有个大致的印象。
-
+
@@ -629,7 +629,7 @@ public class Demo {
2. 结合刚刚的例子,我们来总结一下迭代器的设计思路。总结下来就三句话:迭代器中需要定义 hasNext()、currentItem()、next() 三个最基本的方法。待遍历的容器对象通过依赖注入传递到迭代器类中。容器通过 iterator() 方法来创建迭代器。
3. 这里我画了一张类图,如下所示。实际上就是对上面那张类图的细化,你可以结合着一块看。
-
+
@@ -752,7 +752,7 @@ public class Demo {
4. 为了保持数组存储数据的连续性,数组的删除操作会涉及元素的搬移。当执行到第 57 行代码的时候,我们从数组中将元素 a 删除掉,b、c、d 三个元素会依次往前搬移一位,这就会导致游标本来指向元素 b,现在变成了指向元素 c。原本在执行完第 56 行代码之后,我们还可以遍历到 b、c、d 三个元素,但在执行完第 57 行代码之后,我们只能遍历到 c、d 两个元素,b 遍历不到了。
5. 对于上面的描述,我画了一张图,你可以对照着理解。
-
+
6. 不过,如果第 57 行代码删除的不是游标前面的元素(元素 a)以及游标所在位置的元素(元素 b),而是游标后面的元素(元素 c 和 d),这样就不会存在任何问题了,不会存在某个元素遍历不到的情况了。
7. 所以,我们前面说,在遍历的过程中删除集合元素,结果是不可预期的,有时候没问题(删除元素 c 或 d),有时候就有问题(删除元素 a 或 b),这个要视情况而定(到底删除的是哪个位置的元素),就是这个意思。
@@ -778,7 +778,7 @@ public class Demo {
10. 跟删除情况类似,如果我们在游标的后面添加元素,就不会存在任何问题。所以,在遍历的同时添加集合元素也是一种不可预期行为。
11. 同样,对于上面的添加元素的情况,我们也画了一张图,如下所示,你可以对照着理解。
-
+
diff --git a/docs/dubbo-sourcecode-v1/01&02.Dubbo源码系列V1-Dubbo第一二节-基本应用与高级应用.md b/docs/dubbo-sourcecode-v1/01&02.Dubbo源码系列V1-Dubbo第一二节-基本应用与高级应用.md
index 1e0bcb7..16a8d20 100644
--- a/docs/dubbo-sourcecode-v1/01&02.Dubbo源码系列V1-Dubbo第一二节-基本应用与高级应用.md
+++ b/docs/dubbo-sourcecode-v1/01&02.Dubbo源码系列V1-Dubbo第一二节-基本应用与高级应用.md
@@ -8,7 +8,7 @@ categories:
- Dubbo源码系列v1
keywords: Dubbo,rpc
description: 前两节合成一节
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/dubbo.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/dubbo.png'
abbrlink: d3c530c4
date: 2021-09-11 15:21:58
---
@@ -97,7 +97,7 @@ Dubbo网关参考:[https://github.com/apache/dubbo-proxy](https://github.com/a
### 基本原理
-
+
@@ -914,11 +914,11 @@ dubbo.config-center.address=
2. 管理台的**配置管理**作用就是可以实时更改dubbo相关的配置,在这里面写了和在appliaction.properties里面写是一样的效果,这个还不用重启服务。如果appliaction.properties里和管理台写了相同的配置,以管理台的为主。
-
+
3. **动态配置**这里,也可以很方便的替代服务提供者@service注解上标注的那些配置。管理台是实时生效的,如果改代码里的@service还需要重启服务。
-
+
很多配置都可以在管理台上配。管理台上写的配置会持久化在**你配置的配置中心**里。只有注册中心里的服务提供者信息不持久化,如果注册中心是zookeeper,那么服务提供者在zk上就是临时节点。
diff --git a/docs/dubbo-sourcecode-v1/03.Dubbo源码系列V1-Dubbo第三节-可扩展机制SPI源码解析.md b/docs/dubbo-sourcecode-v1/03.Dubbo源码系列V1-Dubbo第三节-可扩展机制SPI源码解析.md
index 8a91313..289f90d 100644
--- a/docs/dubbo-sourcecode-v1/03.Dubbo源码系列V1-Dubbo第三节-可扩展机制SPI源码解析.md
+++ b/docs/dubbo-sourcecode-v1/03.Dubbo源码系列V1-Dubbo第三节-可扩展机制SPI源码解析.md
@@ -8,7 +8,7 @@ categories:
- Dubbo源码系列v1
keywords: Dubbo,rpc
description: Dubbo里面SPI是基础,大量用到了SPI
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/dubbo.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/dubbo.png'
abbrlink: dbcfef47
date: 2021-09-12 15:21:58
---
diff --git a/docs/dubbo-sourcecode-v1/04.Dubbo源码系列V1-Dubbo第四节-Spring与Dubbo整合原理与源码分析.md b/docs/dubbo-sourcecode-v1/04.Dubbo源码系列V1-Dubbo第四节-Spring与Dubbo整合原理与源码分析.md
index c34d278..9787476 100644
--- a/docs/dubbo-sourcecode-v1/04.Dubbo源码系列V1-Dubbo第四节-Spring与Dubbo整合原理与源码分析.md
+++ b/docs/dubbo-sourcecode-v1/04.Dubbo源码系列V1-Dubbo第四节-Spring与Dubbo整合原理与源码分析.md
@@ -8,7 +8,7 @@ categories:
- Dubbo源码系列v1
keywords: Dubbo,rpc
description: Spring与Dubbo整合原理与源码分析
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/dubbo.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/dubbo.png'
abbrlink: 796f395d
date: 2021-10-06 13:21:58
---
diff --git a/docs/dubbo-sourcecode-v1/05.Dubbo源码系列V1-Dubbo第五节-服务导出源码解析.md b/docs/dubbo-sourcecode-v1/05.Dubbo源码系列V1-Dubbo第五节-服务导出源码解析.md
index ad652f9..710520c 100644
--- a/docs/dubbo-sourcecode-v1/05.Dubbo源码系列V1-Dubbo第五节-服务导出源码解析.md
+++ b/docs/dubbo-sourcecode-v1/05.Dubbo源码系列V1-Dubbo第五节-服务导出源码解析.md
@@ -8,7 +8,7 @@ categories:
- Dubbo源码系列v1
keywords: Dubbo,rpc
description: Dubbo服务导出源码解析
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/dubbo.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/dubbo.png'
abbrlink: '48141866'
date: 2021-10-06 14:11:58
---
diff --git a/docs/dubbo-sourcecode-v1/06.Dubbo源码系列V1-Dubbo第六节-服务引入源码解析.md b/docs/dubbo-sourcecode-v1/06.Dubbo源码系列V1-Dubbo第六节-服务引入源码解析.md
index 02e2073..6feaae4 100644
--- a/docs/dubbo-sourcecode-v1/06.Dubbo源码系列V1-Dubbo第六节-服务引入源码解析.md
+++ b/docs/dubbo-sourcecode-v1/06.Dubbo源码系列V1-Dubbo第六节-服务引入源码解析.md
@@ -8,7 +8,7 @@ categories:
- Dubbo源码系列v1
keywords: Dubbo,rpc
description: 服务引入源码解析
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/dubbo.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/dubbo.png'
abbrlink: bda15919
date: 2021-11-08 14:11:58
---
@@ -243,7 +243,7 @@ public class Application {
-
+
### 源码分析-解析@Reference注解上的配置
@@ -438,19 +438,19 @@ public class Application {
```
-
+
Dubbo官方给的Demo没有配置URL,所以这里就是NULL
-
+
-
+
-
+
@@ -622,7 +622,7 @@ Dubbo官方给的Demo没有配置URL,所以这里就是NULL
}
```
-
+
#### RegistryDirectory
@@ -641,7 +641,7 @@ Dubbo官方给的Demo没有配置URL,所以这里就是NULL
3. 看下面的截图,registry属性是zookeeper的URL,所以应该是要调用ZookeeperRegistry的subscribe()方法,但是ZookeeperRegistry没有这个方法,所以我们就要找它的父类了,也就是FailbackRegistry,
4. 然后再调用doSubscribe(),ZookeeperRegistry重写了此方法,很明显这是个模板模式。
-
+
#### FailbackRegistry
@@ -843,7 +843,7 @@ Dubbo官方给的Demo没有配置URL,所以这里就是NULL
最终走到了这一步
-
+
#### RegistryDirectory
@@ -1097,7 +1097,7 @@ consumer://192.168.0.100/org.apache.dubbo.demo.DemoService?application=dubbo-dem
到此,路由链构造完毕。
-
+
diff --git a/docs/dubbo-sourcecode-v1/07.Dubbo源码系列V1-Dubbo第七节-服务调用源码解析.md b/docs/dubbo-sourcecode-v1/07.Dubbo源码系列V1-Dubbo第七节-服务调用源码解析.md
index f5215f1..cbda226 100644
--- a/docs/dubbo-sourcecode-v1/07.Dubbo源码系列V1-Dubbo第七节-服务调用源码解析.md
+++ b/docs/dubbo-sourcecode-v1/07.Dubbo源码系列V1-Dubbo第七节-服务调用源码解析.md
@@ -8,7 +8,7 @@ categories:
- Dubbo源码系列v1
keywords: Dubbo,rpc
description: 服务调用源码解析
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/dubbo.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/dubbo.png'
abbrlink: 84653c9d
date: 2021-11-09 14:11:58
---
diff --git a/docs/java_concurrency/Java并发体系-第一阶段-多线程基础知识.md b/docs/java_concurrency/Java并发体系-第一阶段-多线程基础知识.md
index 9fd98ca..4d977d1 100644
--- a/docs/java_concurrency/Java并发体系-第一阶段-多线程基础知识.md
+++ b/docs/java_concurrency/Java并发体系-第一阶段-多线程基础知识.md
@@ -9,7 +9,7 @@ categories:
- 原理
keywords: Java并发,原理,源码
description: 万字系列长文讲解Java并发-第一阶段-多线程基础知识。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/Java_concurrency.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/Java_concurrency.png'
abbrlink: efc79183
date: 2020-10-05 22:40:58
---
@@ -31,13 +31,13 @@ date: 2020-10-05 22:40:58
概念:进程可进一步细化为线程,是一个程序内部的一条执行路径。
说明:线程作为CPU调度和执行的单位,每个线程拥独立的运行栈和程序计数器(pc),线程切换的开销小。
-
+
补充:
-
+
进程可以细化为多个线程。
每个线程,拥有自己独立的:栈、程序计数器
@@ -281,7 +281,7 @@ private void init(ThreadGroup g, Runnable target, String name,
1、多线程的设计之中,使用了代理设计模式的结构,用户自定义的线程主体只是负责项目核心功能的实现,而所有的辅助实现全部交由Thread类来处理。
2、在进行Thread启动多线程的时候调用的是start()方法,而后找到的是run()方法,但通过Thread类的构造方法传递了一个Runnable接口对象的时候,那么该接口对象将被Thread类中的target属性所保存,在start()方法执行的时候会调用Thread类中的run()方法。而这个run()方法去调用实现了Runnable接口的那个类所重写过run()方法,进而执行相应的逻辑。多线程开发的本质实质上是在于多个线程可以进行同一资源的抢占,那么Thread主要描述的是线程,而资源的描述是通过Runnable完成的。如下图所示:
-
+
@@ -644,7 +644,7 @@ public void run() {
1、如果直接调用run()方法,相当于就是简单的调用一个普通方法。
-
+
2、run()的调用是在start0()这个Native C++方法里调用的
@@ -654,11 +654,11 @@ public void run() {
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态,这几个状态在Java源码中用枚举来表示。
-
+
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示。
-
+
> 图中 wait到 runnable状态的转换中,`join`实际上是`Thread`类的方法,但这里写成了`Object`。
@@ -699,7 +699,7 @@ public static void main(String[] args) {
2、当JVM启动后,实际有多个线程,但是至少有一个非守护线程(比如main线程)。
-
+
- Finalizer:GC守护线程
@@ -1574,7 +1574,7 @@ volatile自己虽然不能保证原子性,但是和CAS结合起来就可以保
-
+
@@ -1782,7 +1782,7 @@ public Unsafe getUnsafe() throws IllegalAccessException {
Unsafe的功能如下图:
-
+
## CAS相关
diff --git a/docs/java_concurrency/Java并发体系-第三阶段-JUC并发包-[1].md b/docs/java_concurrency/Java并发体系-第三阶段-JUC并发包-[1].md
index cab8af9..5a8c137 100644
--- a/docs/java_concurrency/Java并发体系-第三阶段-JUC并发包-[1].md
+++ b/docs/java_concurrency/Java并发体系-第三阶段-JUC并发包-[1].md
@@ -9,7 +9,7 @@ categories:
- 原理
keywords: Java并发,原理,源码
description: 万字系列长文讲解-Java并发体系-第三阶段-JUC并发包。JUC在高并发编程中使用频率非常高,这里会详细介绍其用法。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/Java_concurrency.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/Java_concurrency.png'
abbrlink: 5be45d9e
date: 2020-10-09 22:13:58
---
@@ -1869,7 +1869,7 @@ public class ForkJoinRecursiveAction {
ForkJoinTask就是ForkJoinPool里面的每一个任务。他主要有两个子类:`RecursiveAction`和`RecursiveTask`。然后通过fork()方法去分配任务执行任务,通过join()方法汇总任务结果。
-
+
## 小总结
diff --git a/docs/java_concurrency/Java并发体系-第三阶段-JUC并发包-[2].md b/docs/java_concurrency/Java并发体系-第三阶段-JUC并发包-[2].md
index 68943bf..0a5884c 100644
--- a/docs/java_concurrency/Java并发体系-第三阶段-JUC并发包-[2].md
+++ b/docs/java_concurrency/Java并发体系-第三阶段-JUC并发包-[2].md
@@ -9,7 +9,7 @@ categories:
- 原理
keywords: Java并发,原理,源码
description: 万字系列长文讲解-Java并发体系-第三阶段-JUC并发包。JUC在高并发编程中使用频率非常高,这里会详细介绍其用法。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/Java_concurrency.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/Java_concurrency.png'
abbrlink: 70c90e5d
date: 2020-10-10 22:13:58
---
@@ -563,7 +563,7 @@ public int getUnarrivedParties()
根据上面的代码,我们可以画出下面这个很简单的图:
-
+
这棵树上有 7 个 phaser 实例,每个 phaser 实例在构造的时候,都指定了 parties 为 5,但是,对于每个拥有子节点的节点来说,每个子节点都是它的一个 party,我们可以通过 phaser.getRegisteredParties() 得到每个节点的 parties 数量:
@@ -952,7 +952,7 @@ public class ThreadPoolDemo {
## 线程池的底层工作流程
-
+
1、创建线程池后,等待请求任务
@@ -1646,7 +1646,7 @@ public class ExecutorCompletionService
+
diff --git a/docs/java_concurrency/Java并发体系-第二阶段-锁与同步-[1].md b/docs/java_concurrency/Java并发体系-第二阶段-锁与同步-[1].md
index 6c57573..ce0eaae 100644
--- a/docs/java_concurrency/Java并发体系-第二阶段-锁与同步-[1].md
+++ b/docs/java_concurrency/Java并发体系-第二阶段-锁与同步-[1].md
@@ -9,7 +9,7 @@ categories:
- 原理
keywords: Java并发,原理,源码
description: '万字系列长文讲解-Java并发体系-第二阶段,从C++和硬件方面讲解。'
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/Java_concurrency.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/Java_concurrency.png'
abbrlink: 230c5bb3
date: 2020-10-06 22:09:58
---
@@ -291,7 +291,7 @@ d = e - f ;
由于处理器使用缓存和读写缓存冲区,这使得加载(load)和存储(store)操作看上去可能是在乱序执行,因为三级缓存的存在,导致内存与缓存的数据同步存在时间差。
-
+
@@ -343,7 +343,7 @@ int c = a + b;
冯诺依曼,提出计算机由五大组成部分,输入设备,输出设备存储器,控制器,运算器。
-
+
输入设备:鼠标,键盘等等
@@ -367,7 +367,7 @@ int c = a + b;
CPU的运算速度和内存的访问速度相差比较大。这就导致CPU每次操作内存都要耗费很多等待时间。内 存的读写速度成为了计算机运行的瓶颈。于是就有了在CPU和主内存之间增加缓存的设计。靠近CPU 的缓存称为L1,然后依次是 L2,L3和主内存,CPU缓存模型如图下图所示。
-
+
CPU Cache分成了三个级别: L1, L2, L3。级别越小越接近CPU,速度也更快,同时也代表着容量越小。速度越快的价格越贵。
@@ -377,7 +377,7 @@ CPU Cache分成了三个级别: L1, L2, L3。级别越小越接近CPU,速
3、L3 Cache是三级缓存中大的一级,例如12MB,同时也是缓存中慢的一级,在同一个CPU插槽 之间的核共享一个L3 Cache。
-
+
上面的图中有一个Latency指标。比如Memory这个指标为59.4ns,表示CPU在操作内存的时候有59.4ns的延迟,一级缓存最快只有1.2ns。
@@ -409,7 +409,7 @@ Cache的出现是为了解决CPU直接访问内存效率低下问题的。
每一个线程有自己的工作内存,工作内存只存储该线程对共享变量的副本。线程对变量的所有的操 作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接 访问对方工作内存中的变量。
-
+
Java的线程不能直接在主内存中操作共享变量。而是首先将主内存中的共享变量赋值到自己的工作内存中,再进行操作,操作完成之后,刷回主内存。
@@ -424,7 +424,7 @@ Java内存模型是一套在多线程读写共享数据时,对共享数据的
JMM内存模型与CPU硬件内存架构的关系:
-
+
工作内存:可能对应CPU寄存器,也可能对应CPU缓存,也可能对应内存。
@@ -434,9 +434,9 @@ JMM内存模型与CPU硬件内存架构的关系:
## 再谈可见性
-
+
-
+
1、图中所示是 个双核 CPU 系统架构 ,每个核有自己的控制器和运算器,其中控制器包含一组寄存器和操作控制器,运算器执行算术逻辅运算。每个核都有自己的1级缓存,在有些架构里面还有1个所有 CPU 共享的2级缓存。 那么 Java 内存模型里面的工作内存,就对应这里的 Ll 或者 L2 存或者 CPU 寄存器。
@@ -452,7 +452,7 @@ JMM内存模型与CPU硬件内存架构的关系:
为了保证数据交互时数据的正确性,Java内存模型中定义了8种操作来完成这个交互过程,这8种操作本身都是原子性的。虚拟机实现时必须保证下面 提及的每一种操作都是原子的、不可再分的。
-
+
> (1)lock:作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
>
@@ -478,7 +478,7 @@ JMM内存模型与CPU硬件内存架构的关系:
如果没有synchronized,那就是下面这样的
-
+
@@ -589,7 +589,7 @@ volatile不保证原子性,只保证可见性和禁止指令重排
## CPU术语介绍
-
+
@@ -717,7 +717,7 @@ public class VolatileExample {
**1、下面是保守策略下,volatile写插入内存屏障后生成的指令序列示意图**
-
+
> 图中的StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作已经对任 意处理器可见了。这是因为StoreStore屏障将保障上面所有的普通写在volatile写之前刷新到主内存。这里比较有意思的是,volatile写后面的StoreLoad屏障。此屏障的作用是避免volatile写与 后面可能有的volatile读/写操作重排序。因为编译器常常无法准确判断在一个volatile写的后面 是否需要插入一个StoreLoad屏障(比如,一个volatile写之后方法立即return)。为了保证能正确 实现volatile的内存语义,JMM在采取了保守策略:在每个volatile写的后面,或者在每个volatile 读的前面插入一个StoreLoad屏障。从整体执行效率的角度考虑,JMM最终选择了在每个volatile写的后面插入一个StoreLoad屏障。因为volatile写-读内存语义的常见使用模式是:一个 写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时, 选择在volatile写之后插入StoreLoad屏障将带来可观的执行效率的提升。从这里可以看到JMM在实现上的一个特点:首先确保正确性,然后再去追求执行效率
@@ -725,7 +725,7 @@ public class VolatileExample {
**2、下面是在保守策略下,volatile读插入内存屏障后生成的指令序列示意图**
-
+
> 图中的LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。 LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。 上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。
@@ -752,7 +752,7 @@ class VolatileBarrierExample {
针对readAndWrite()方法,编译器在生成字节码时可以做如下的优化
-
+
注意,最后的StoreLoad屏障不能省略。因为第二个volatile写之后,方法立即return。此时编译器可能无法准确断定后面是否会有volatile读或写,为了安全起见,编译器通常会在这里插入一个StoreLoad屏障。
@@ -766,7 +766,7 @@ class VolatileBarrierExample {
X86处理器仅会对写-读操作做重排序。X86不会对读-读、读-写和写-写操作 做重排序,因此在X86处理器中会省略掉这3种操作类型对应的内存屏障。在X86中,JMM仅需在volatile写后面插入一个StoreLoad屏障即可正确实现volatile写-读的内存语义。这意味着在X86处理器中,volatile写的开销比volatile读的开销会大很多(因为执行StoreLoad屏障开销会比较大)。
-
+
diff --git a/docs/java_concurrency/Java并发体系-第二阶段-锁与同步-[2].md b/docs/java_concurrency/Java并发体系-第二阶段-锁与同步-[2].md
index 8a81ccc..e5921e9 100644
--- a/docs/java_concurrency/Java并发体系-第二阶段-锁与同步-[2].md
+++ b/docs/java_concurrency/Java并发体系-第二阶段-锁与同步-[2].md
@@ -9,7 +9,7 @@ categories:
- 原理
keywords: Java并发,原理,源码
description: '万字系列长文讲解-Java并发体系-第二阶段,从C++和硬件方面讲解。'
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/Java_concurrency.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/Java_concurrency.png'
abbrlink: '8210870'
date: 2020-10-07 22:10:58
---
@@ -18,7 +18,7 @@ date: 2020-10-07 22:10:58
# 可见性设计的硬件
-
+
从硬件的级别来考虑一下可见性的问题
@@ -305,13 +305,13 @@ MESI协议规定了一组消息,就说各个处理器在操作内存数据的
-
+
## MESI-优化
-
+
MESI协议如果每次写数据的时候都要发送invalidate消息等待所有处理器返回ack,然后获取独占锁后才能写数据,那可能就会导致性能很差了,因为这个对共享变量的写操作,实际上在硬件级别变成串行的了。所以为了解决这个问题,硬件层面引入了写缓冲器和无效队列
@@ -449,7 +449,7 @@ int b = c; //load
## 相关术语
-
+
@@ -461,7 +461,7 @@ int b = c; //load
**第一个机制是通过总线锁保证原子性。**如果多个处理器同时对共享变量进行读改写操作 (i++就是经典的读改写操作),那么共享变量就会被多个处理器同时进行操作,这样读改写操 作就不是原子的,操作完之后共享变量的值会和期望的不一致。举个例子,如果i=1,我们进行 两次i++操作,我们期望的结果是3,但是有可能结果是2,如图所示。
-
+
原因可能是多个处理器同时从各自的缓存中读取变量i,分别进行加1操作,然后分别写入 系统内存中。那么,想要保证读改写共享变量的操作是原子的,就必须保证CPU1读改写共享 变量的时候,CPU2不能操作缓存了该共享变量内存地址的缓存。
diff --git a/docs/java_concurrency/Java并发体系-第二阶段-锁与同步-[3].md b/docs/java_concurrency/Java并发体系-第二阶段-锁与同步-[3].md
index 83ab3fc..f829930 100644
--- a/docs/java_concurrency/Java并发体系-第二阶段-锁与同步-[3].md
+++ b/docs/java_concurrency/Java并发体系-第二阶段-锁与同步-[3].md
@@ -9,7 +9,7 @@ categories:
- 原理
keywords: Java并发,原理,源码
description: '万字系列长文讲解-Java并发体系-第二阶段,从C++和硬件方面讲解。'
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/Java_concurrency.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/Java_concurrency.png'
abbrlink: 113a3931
date: 2020-10-08 22:10:58
---
@@ -357,17 +357,17 @@ monitorexit释放锁。 monitorexit插入在方法结束处和异常处,JVM保
术语参考: http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html 在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。如下图所示:
-
+
## 对象头
当一个线程尝试访问synchronized修饰的代码块时,它首先要获得锁,那么这个锁到底存在哪里呢?是 存在锁对象的对象头中的。 HotSpot采用instanceOopDesc和arrayOopDesc来描述对象头,arrayOopDesc对象用来描述数组类型。instanceOopDesc的定义的在Hotspot源码的 instanceOop.hpp 文件中,另外,arrayOopDesc 的定义对应 arrayOop.hpp
-
+
从instanceOopDesc代码中可以看到 instanceOopDesc继承自oopDesc,oopDesc的定义载Hotspot 源码中的 oop.hpp 文件中。
-
+
- 在普通实例对象中,oopDesc的定义包含两个成员,分别是 _mark 和 _metadata
@@ -383,21 +383,21 @@ monitorexit释放锁。 monitorexit插入在方法结束处和异常处,JVM保
Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、 线程持有的锁、偏向线程ID、偏向时间戳等等,占用内存大小与虚拟机位长一致。Mark Word对应的类 型是 markOop 。源码位于 markOop.hpp 中。
-
+
-
+
在64位虚拟机下,Mark Word是64bit大小的,其存储结构如下:
-
+
在32位虚拟机下,Mark Word是32bit大小的,其存储结构如下:
-
+
再加一个图对比一下,有一丁点的补充
-
+
@@ -447,7 +447,7 @@ Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)
线程在执行同步块之前,JVM会先在当前的线程的栈帧中创建一个`Lock Record`,其包括一个用于存储对象头中的 `mark word`(官方称之为`Displaced Mark Word`)以及一个指向对象的指针。下图右边的部分就是一个`Lock Record`。
-
+
@@ -571,7 +571,7 @@ synchronized(obj){
}
```
-
+
## 轻量级锁什么时候升级为重量级锁?
@@ -608,7 +608,7 @@ synchronized(obj){
- 重量级锁的状态下,对象的`mark word`为指向一个堆中monitor对象的指针。一个monitor对象包括这么几个关键字段:cxq(下图中的ContentionList),EntryList ,WaitSet,owner。其中cxq ,EntryList ,WaitSet都是由ObjectWaiter的链表结构,owner指向持有锁的线程。
-
+
在HotSpot虚拟机中,monitor是由ObjectMonitor实现的。其源码是用c++来实现的,位于HotSpot虚 拟机源码ObjectMonitor.hpp文件中(src/share/vm/runtime/objectMonitor.hpp)。ObjectMonitor主 要数据结构如下:
@@ -661,7 +661,7 @@ ObjectMonitor() {
1、执行monitorenter时,会调用InterpreterRuntime.cpp (位于:src/share/vm/interpreter/interpreterRuntime.cpp) 的 InterpreterRuntime::monitorenter函 数。具体代码可参见HotSpot源码。
-
+
@@ -1147,7 +1147,7 @@ if (TryLock(Self) > 0) break ;
可以看到ObjectMonitor的函数调用中会涉及到Atomic::cmpxchg_ptr,Atomic::inc_ptr等内核函数, 执行同步代码块,没有竞争到锁的对象会park()被挂起,竞争到锁的线程会unpark()唤醒。这个时候就 会存在操作系统用户态和内核态的转换,这种切换会消耗大量的系统资源。所以synchronized是Java语 言中是一个重量级(Heavyweight)的操作。 用户态和和内核态是什么东西呢?要想了解用户态和内核态还需要先了解一下Linux系统的体系架构:
-
+
从上图可以看出,Linux操作系统的体系架构分为:用户空间(应用程序的活动空间)和内核。 内核:本质上可以理解为一种软件,控制计算机的硬件资源,并提供上层应用程序运行的环境。 用户空间:上层应用程序活动的空间。应用程序的执行必须依托于内核提供的资源,包括CPU资源、存 储资源、I/O资源等。 系统调用:为了使上层应用能够访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用。
diff --git a/docs/java_concurrency/Java并发体系-第四阶段-AQS源码解读-[1].md b/docs/java_concurrency/Java并发体系-第四阶段-AQS源码解读-[1].md
index 50b6211..7aaaa85 100644
--- a/docs/java_concurrency/Java并发体系-第四阶段-AQS源码解读-[1].md
+++ b/docs/java_concurrency/Java并发体系-第四阶段-AQS源码解读-[1].md
@@ -8,7 +8,7 @@ categories:
- 原理
keywords: Java并发,AQS源码
description: '万字系列长文讲解-Java并发体系-第四阶段-AQS源码解读-[1]。'
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/Java_concurrency.png'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/Java_concurrency.png'
abbrlink: 92c4503d
date: 2020-10-26 17:59:42
---
@@ -410,11 +410,11 @@ Process finished with exit code 0
**技术翻译:**是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石, 通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量`state`表示持有锁的状态。
-
+
-
+
AbstractOwnableSynchronizer
AbstractQueuedLongSynchronizer
@@ -426,7 +426,7 @@ AbstractQueuedSynchronizer
AQS是一个抽象的父类,可以将其理解为一个框架。基于AQS这个框架,我们可以实现多种同步器,比如下方图中的几个Java内置的同步器。同时我们也可以基于AQS框架实现我们自己的同步器以满足不同的业务场景需求。
-
+
@@ -434,7 +434,7 @@ AQS是一个抽象的父类,可以将其理解为一个框架。基于AQS这
加锁会导致阻塞:有阻塞就需要排队,实现排队必然需要有某种形式的队列来进行管理
-
+
1、抢到资源的线程直接使用办理业务,抢占不到资源的线程的必然涉及一种**排队等候机制**,抢占资源失败的线程继续去等待(类似办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。
@@ -515,7 +515,7 @@ Node 的数据结构其实也挺简单的,就是 thread + waitStatus + pre + n
## AQS队列基本结构
-
+
注意排队队列,不包括head(也就是后文要说的哨兵节点)。
@@ -583,7 +583,7 @@ public class AQSDemo {
以这样的一个实际例子说明。
-
+
@@ -765,7 +765,7 @@ public class AQSDemo {
2、C在if逻辑里准备入队,进行相应设置后,变成下面这样。
-
+
@@ -831,7 +831,7 @@ public class AQSDemo {
此时队列变成了下面的样子:
-
+
3、然后if结束之后,继续空的for循环,B线程开始了第二轮循环。
@@ -843,11 +843,11 @@ public class AQSDemo {
2、`node.prev = t`,进入if之后,让B节点的prev指针指向t,然后`compareAndSetTail(t, node)`设置尾节点
-
+
3、CAS设置尾节点成功之后,执行if里的逻辑
-
+
@@ -1197,7 +1197,7 @@ protected final void setExclusiveOwnerThread(Thread thread) {
-
+
diff --git a/docs/netty/Netty入门-第一话.md b/docs/netty/Netty入门-第一话.md
index ff64bb3..2db351a 100644
--- a/docs/netty/Netty入门-第一话.md
+++ b/docs/netty/Netty入门-第一话.md
@@ -7,7 +7,7 @@ categories:
- 入门
keywords: Netty
description: 第一话对BIO和NIO进行了讲解,为后续做准备。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/netty_logo.jpg'
abbrlink: 3f9283e7
date: 2021-04-08 14:21:58
---
@@ -33,7 +33,7 @@ date: 2021-04-08 14:21:58
相对简单的一个体系图
-
+
## Netty 的应用场景
@@ -67,7 +67,7 @@ date: 2021-04-08 14:21:58
## Netty 的学习资料参考
-
+
@@ -97,11 +97,11 @@ date: 2021-04-08 14:21:58
2. `Java` 共支持 `3` 种网络编程模型 `I/O` 模式:`BIO`、`NIO`、`AIO`。
3. `Java BIO`:同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。【简单示意图】
-
+
4. `Java NIO`:同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 `I/O` 请求就进行处理。【简单示意图】
-
+
5. `Java AIO(NIO.2)`:异步非阻塞,`AIO` 引入异步通道的概念,采用了 `Proactor` 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。
6. 我们依次展开讲解。
@@ -120,7 +120,7 @@ date: 2021-04-08 14:21:58
## Java BIO 工作机制
-
+
对 `BIO` 编程流程的梳理
@@ -207,7 +207,7 @@ public class BIOServer {
}
```
-
+
@@ -277,7 +277,7 @@ public class BasicBuffer {
3. `BIO` 基于字节流和字符流进行操作,而 `NIO` 基于 `Channel`(通道)和 `Buffer`(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。`Selector`(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。
4. Buffer和Channel之间的数据流向是双向的
-
+
## NIO 三大核心原理示意图
@@ -287,7 +287,7 @@ public class BasicBuffer {
关系图的说明:
-
+
1. 每个 `Channel` 都会对应一个 `Buffer`。
2. `Selector` 对应一个线程,一个线程对应多个 `Channel`(连接)。
@@ -303,17 +303,17 @@ public class BasicBuffer {
缓冲区(`Buffer`):缓冲区本质上是一个**可以读写数据的内存块**,可以理解成是一个**容器对象(含数组)**,该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。`Channel` 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 `Buffer`,如图:【后面举例说明】
-
+
### Buffer 类及其子类
1. 在 `NIO` 中,`Buffer` 是一个顶层父类,它是一个抽象类,类的层级关系图:
-
+
2. `Buffer` 类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息:
-
+
@@ -321,13 +321,13 @@ public class BasicBuffer {
3. `Buffer` 类相关方法一览
-
+
### ByteBuffer
从前面可以看出对于 `Java` 中的基本数据类型(`boolean` 除外),都有一个 `Buffer` 类型与之相对应,最常用的自然是 `ByteBuffer` 类(二进制数据),该类的主要方法如下:
-
+
## 通道(Channel)
@@ -343,7 +343,7 @@ public class BasicBuffer {
5. `FileChannel` 用于文件的数据读写,`DatagramChannel` 用于 `UDP` 的数据读写,`ServerSocketChannel` 和 `SocketChannel` 用于 `TCP` 的数据读写。
6. 图示
-
+
### FileChannel 类
@@ -444,7 +444,7 @@ public class NIOFileChannel02 {
2. 拷贝一个文本文件 `1.txt`,放在项目下即可
3. 代码演示
-
+
```java
package com.atguigu.nio;
@@ -721,7 +721,7 @@ public class ScatteringAndGatheringTest {
### Selector 示意图和特点说明
-
+
说明如下:
@@ -733,7 +733,7 @@ public class ScatteringAndGatheringTest {
### Selector 类相关方法
-
+
### 注意事项
@@ -748,7 +748,7 @@ public class ScatteringAndGatheringTest {
`NIO` 非阻塞网络编程相关的(`Selector`、`SelectionKey`、`ServerScoketChannel` 和 `SocketChannel`)关系梳理图
-
+
对上图的说明:
@@ -938,21 +938,21 @@ public static final int OP_ACCEPT = 1 << 4;
2. `SelectionKey` 相关方法
-
+
### ServerSocketChannel
1. `ServerSocketChannel` 在服务器端监听新的客户端 `Socket` 连接,负责监听,不负责实际的读写操作
2. 相关方法如下
-
+
### SocketChannel
1. `SocketChannel`,网络 `IO` 通道,**具体负责进行读写操作**。`NIO` 把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区。
2. 相关方法如下
-
+
## NIO网络编程应用实例 - 群聊系统
@@ -965,7 +965,7 @@ public static final int OP_ACCEPT = 1 << 4;
5. 目的:进一步理解 `NIO` 非阻塞网络编程机制
6. 示意图分析和代码
-
+
代码:
@@ -1243,7 +1243,7 @@ socket.getOutputStream().write(arr);
### 传统 IO 模型
-
+
**DMA**:`direct memory access` 直接内存拷贝(不使用 `CPU`)
@@ -1252,19 +1252,19 @@ socket.getOutputStream().write(arr);
1. `mmap` 通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户空间的拷贝次数。如下图
2. `mmap` 示意图
-
+
### sendFile 优化
1. `Linux2.1` 版本提供了 `sendFile` 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 `SocketBuffer`,同时,由于和用户态完全无关,就减少了一次上下文切换
2. 示意图和小结
-
+
3. 提示:零拷贝从操作系统角度,是没有 `cpu` 拷贝
4. `Linux在2.4` 版本中,做了一些修改,避免了从内核缓冲区拷贝到 `Socketbuffer` 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝。具体如下图和小结:
-
+
5. 这里其实有一次 `cpu` 拷贝 `kernel buffer` -> `socket buffer` 但是,拷贝的信息很少,比如 `lenght`、`offset` 消耗低,可以忽略
diff --git a/docs/netty/Netty入门-第三话.md b/docs/netty/Netty入门-第三话.md
index 0bfca27..6aa80f8 100644
--- a/docs/netty/Netty入门-第三话.md
+++ b/docs/netty/Netty入门-第三话.md
@@ -7,7 +7,7 @@ categories:
- 入门
keywords: Netty
description: 对前面两话一些迷惑的点进行细说,讲解handler调用机制,TCP粘包,以及用netty写一个十分简单的RPC。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/netty_logo.jpg'
abbrlink: 429acc6d
date: 2021-04-21 17:38:58
---
@@ -25,7 +25,7 @@ date: 2021-04-21 17:38:58
1. 编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码[示意图]
2. `codec`(编解码器)的组成部分有两个:`decoder`(解码器)和 `encoder`(编码器)。`encoder` 负责把业务数据转换成字节码数据,`decoder` 负责把字节码数据转换成业务数据
-
+
## Netty 本身的编码解码的机制和问题分析
@@ -54,7 +54,7 @@ date: 2021-04-21 17:38:58
8. 然后通过 `protoc.exe` 编译器根据 `.proto` 自动生成 `.java` 文件
9. `protobuf` 使用示意图
-
+
## Protobuf 快速入门实例
@@ -91,7 +91,7 @@ message Student { //会在 StudentPOJO 外部类生成一个内部类 Student,
protoc.exe --java_out=.Student.proto
将生成的 StudentPOJO 放入到项目使用
-
+
生成的StudentPOJO代码太长就不贴在这里了
@@ -676,7 +676,7 @@ public class NettyClientHandler extends ChannelInboundHandlerAdapter {
2. `ChannelHandler` 充当了处理入站和出站数据的应用程序逻辑的容器。例如,实现 `ChannelInboundHandler` 接口(或 `ChannelInboundHandlerAdapter`),你就可以接收入站事件和数据,这些数据会被业务逻辑处理。当要给客户端发送响应时,也可以从 `ChannelInboundHandler` 冲刷数据。业务逻辑通常写在一个或者多个 `ChannelInboundHandler` 中。`ChannelOutboundHandler` 原理一样,只不过它是用来处理出站数据的
3. `ChannelPipeline` 提供了 `ChannelHandler` 链的容器。以客户端应用程序为例,如果事件的运动方向是从客户端到服务端的,那么我们称这些事件为出站的,即客户端发送给服务端的数据会通过 `pipeline` 中的一系列 `ChannelOutboundHandler`,并被这些 `Handler` 处理,反之则称为入站的
-
+
> 出站,入站如果搞不清楚,看下面的**Netty的handler链的调用机制**,通过一个例子和图讲清楚
@@ -689,12 +689,12 @@ public class NettyClientHandler extends ChannelInboundHandlerAdapter {
1. 关系继承图
-
+
2. 由于不可能知道远程节点是否会一次性发送一个完整的信息,`tcp` 有可能出现粘包拆包的问题,这个类会对入站数据进行缓冲,直到它准备好被处理.【后面有说TCP的粘包和拆包问题】
3. 一个关于 `ByteToMessageDecoder` 实例分析
-
+
@@ -710,7 +710,7 @@ public class NettyClientHandler extends ChannelInboundHandlerAdapter {
> 读者可以看下这个图,带着这个图去看下面的例子。
-
+
@@ -978,11 +978,11 @@ public class MyLongToByteEncoder extends MessageToByteEncoder
+
-
+
@@ -1002,14 +1002,10 @@ public class MyLongToByteEncoder extends MessageToByteEncoder
+
-> 下面是Netty官方源码给的图,我个人觉的不是太好理解,上面的图好理解一些
-
-
-
## ByteToMessageDecoder的小细节
@@ -1090,13 +1086,13 @@ public class MyByteToLongDecoder extends ByteToMessageDecoder {
如下图验证结果:
-
+
2. 同时又引出了一个小问题
-
+
当我们`MyClientHandler`传一个Long时,会调用我们的`MyLongToByteEncoder`的编码器。那么控制台就会打印这样一句话:**MyLongToByteEncoder encode 被调用**。但是这里并没有调用编码器,这是为什么呢?
@@ -1149,7 +1145,7 @@ public class MyByteToLongDecoder extends ByteToMessageDecoder {
ctx.writeAndFlush(Unpooled.copiedBuffer("abcdabcdabcdabcd",CharsetUtil.UTF_8));
```
-
+
@@ -1198,7 +1194,7 @@ public class MyByteToLongDecoder2 extends ReplayingDecoder
+
@@ -1249,7 +1245,7 @@ log4j.appender.stdout.layout.ConversionPattern=[%p]%C{1}-%m%n
3. 演示整合
-
+
@@ -1261,7 +1257,7 @@ log4j.appender.stdout.layout.ConversionPattern=[%p]%C{1}-%m%n
2. 由于 `TCP` 无消息保护边界,需要在接收端处理消息边界问题,也就是我们所说的粘包、拆包问题,看一张图
3. `TCP` 粘包、拆包图解
-
+
假设客户端分别发送了两个数据包 `D1` 和 `D2` 给服务端,由于服务端一次读取到字节数是不确定的,故可能存在以下四种情况:
@@ -1502,11 +1498,11 @@ public class MyClientHandler extends SimpleChannelInboundHandler
+
**Server**
-
+
@@ -1514,13 +1510,13 @@ public class MyClientHandler extends SimpleChannelInboundHandler
+
**Server**
-
+
@@ -1538,7 +1534,7 @@ public class MyClientHandler extends SimpleChannelInboundHandler
+
@@ -1996,7 +1992,7 @@ MyMessageEncoder encode 方法被调用
1. `RPC(Remote Procedure Call)`—远程过程调用,是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程
2. 两个或多个应用程序都分布在不同的服务器上,它们之间的调用都像是本地方法调用一样(如图)
-
+
过程:
@@ -2018,11 +2014,11 @@ MyMessageEncoder encode 方法被调用
3. 常见的 `RPC` 框架有:比较知名的如阿里的 `Dubbo`、`Google` 的 `gRPC`、`Go` 语言的 `rpcx`、`Apache` 的 `thrift`,`Spring` 旗下的 `SpringCloud`。
-
+
## 我们的RPC 调用流程图
-
+
**RPC 调用流程说明**
@@ -2052,7 +2048,7 @@ MyMessageEncoder encode 方法被调用
3. 创建一个消费者,该类需要透明的调用自己不存在的方法,内部需要使用 `Netty` 请求提供者返回数据
4. 开发的分析图
-
+
diff --git a/docs/netty/Netty入门-第二话.md b/docs/netty/Netty入门-第二话.md
index b2add4f..d5a88db 100644
--- a/docs/netty/Netty入门-第二话.md
+++ b/docs/netty/Netty入门-第二话.md
@@ -7,7 +7,7 @@ categories:
- 入门
keywords: Netty
description: 对Netty的架构进行了解析,主要是Reactor设计模式的多种解决方案。同时讲解了Netty的核心模块组件。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/netty_logo.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/netty_logo.jpg'
abbrlink: f846f3f
date: 2021-04-15 14:21:58
---
@@ -29,7 +29,7 @@ date: 2021-04-15 14:21:58
Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.
-
+
## Netty 的优点
@@ -80,7 +80,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
1. 当并发数很大,就会创建大量的线程,占用很大系统资源
2. 连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在 Handler对象中的`read` 操作,导致上面的处理线程资源浪费
-
+
## Reactor 模式
@@ -97,7 +97,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
1. 基于线程池复用线程资源:不必再为每个连接创建线程,将连接完成后的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的业务。(解决了当并发数很大时,会创建大量线程,占用很大系统资源)
2. 基于 `I/O` 复用模型:多个客户端进行连接,先把连接请求给`ServiceHandler`。多个连接共用一个阻塞对象`ServiceHandler`。假设,当C1连接没有数据要处理时,C1客户端只需要阻塞于`ServiceHandler`,C1之前的处理线程便可以处理其他有数据的连接,不会造成线程资源的浪费。当C1连接再次有数据时,`ServiceHandler`根据线程池的空闲状态,将请求分发给空闲的线程来处理C1连接的任务。(解决了线程资源浪费的那个问题)
-
+
@@ -105,7 +105,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
### I/O 复用结合线程池,就是 Reactor 模式基本设计思想,如图
-
+
对上图说明:
@@ -132,7 +132,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
原理图,并使用 `NIO` 群聊系统验证
-
+
### 方案说明
@@ -155,7 +155,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
### 方案说明
-
+
@@ -178,11 +178,11 @@ Netty is an asynchronous event-driven network application framework for rapid de
针对单 `Reactor` 多线程模型中,`Reactor` 在单线程中运行,高并发场景下容易成为性能瓶颈,可以让 `Reactor` 在多线程中运行
-
+
-
+
> SubReactor是可以有多个的,如果只有一个SubReactor的话那和`单 Reactor 多线程`就没什么区别了。
@@ -197,7 +197,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
### Scalable IO in Java 对 Multiple Reactors 的原理图解
-
+
### 方案优缺点说明
@@ -230,7 +230,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
`Netty` 主要基于主从 `Reactors` 多线程模型(如图)做了一定的改进,其中主从 `Reactor` 多线程模型有多个 `Reactor`
-
+
**对上图说明**
@@ -240,7 +240,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
### 工作原理示意图2 - 进阶版
-
+
@@ -248,7 +248,7 @@ Netty is an asynchronous event-driven network application framework for rapid de
### 工作原理示意图3 - 详细版
-
+
@@ -669,9 +669,9 @@ public class NettyServerHandler extends ChannelInboundHandlerAdapter {
下面第一张图就是管道,中间会经过多个handler
-
+
-
+
说明:
@@ -917,11 +917,11 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
4. 我们经常需要自定义一个 `Handler` 类去继承 `ChannelInboundHandlerAdapter`,然后通过重写相应方法实现业务逻辑,我们接下来看看一般都需要重写哪些方法
-
+
## Pipeline 和 ChannelPipeline
@@ -931,7 +931,7 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
4. 常用方法
`ChannelPipeline addFirst(ChannelHandler... handlers)`,把一个业务处理类(`handler`)添加到链中的第一个位置`ChannelPipeline addLast(ChannelHandler... handlers)`,把一个业务处理类(`handler`)添加到链中的最后一个位置
@@ -940,7 +940,7 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
- `TestServerInitializer`和`HttpServerCodec`这些东西本身也是`handler`
- 一般来说事件从客户端往服务器走我们称为出站,反之则是入站。
@@ -950,7 +950,7 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
3. 常用方法
@@ -959,14 +959,14 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
## ChannelOption
1. `Netty` 在创建 `Channel` 实例后,一般都需要设置 `ChannelOption` 参数。
2. `ChannelOption` 参数如下:
-
+
## EventLoopGroup 和其实现类 NioEventLoopGroup
@@ -974,7 +974,7 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
4. 常用方法
`public NioEventLoopGroup()`,构造方法
@@ -985,11 +985,11 @@ public class TestHttpServerHandler extends SimpleChannelInboundHandler
3. 举例说明 `Unpooled` 获取 `Netty` 的数据容器 `ByteBuf` 的基本使用
-
+
案例 1
@@ -1096,7 +1096,7 @@ public class NettyByteBuf02 {
-
+
代码如下:
@@ -1523,7 +1523,7 @@ public class MyServerHandler extends ChannelInboundHandlerAdapter {
4. 客户端浏览器和服务器端会相互感知,比如服务器关闭了,浏览器会感知,同样浏览器关闭了,服务器会感知
5. 运行界面
-
+
@@ -1724,7 +1724,7 @@ public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler
diff --git a/docs/os/操作系统-IO与零拷贝.md b/docs/os/操作系统-IO与零拷贝.md
index a64bcda..a5b4d27 100644
--- a/docs/os/操作系统-IO与零拷贝.md
+++ b/docs/os/操作系统-IO与零拷贝.md
@@ -9,7 +9,7 @@ categories:
- 操作系统
keywords: 操作系统,IO,零拷贝
description: 基本面试会问到的IO进行了详解,同时本篇文章也对面试以及平时工作中会看到的零拷贝进行了充分的解析。万字长文系列,读到就是赚到。
-cover: 'https://npm.elemecdn.com/lql_static@latest/logo/os_logo.jpg'
+cover: 'https://upyunimg.imlql.cn/lql_static@latest/logo/os_logo.jpg'
abbrlink: e959db2e
date: 2021-04-08 15:21:58
---
@@ -47,7 +47,7 @@ date: 2021-04-08 15:21:58
3. 而在用户进程这边,整 个进程会被阻塞。当**内核**一直等到数据准备好了,它就会将数据从**内核**中拷贝到用户内存,然后**内核**返回果,用户进程才解除 block的状态,重新运行起来。
4. **所以,blocking IO的特点就是在IO执行的两个阶段都被block了。**
-
+
@@ -58,7 +58,7 @@ date: 2021-04-08 15:21:58
3. 虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的CPU的资源。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。
4. **所以,用户进程第一个阶段不是阻塞的,需要不断的主动询问内核数据好了没有;第二个阶段依然总是阻塞的。**
-
+
@@ -76,7 +76,7 @@ date: 2021-04-08 15:21:58
2. 它的基本原理就是select /epoll这个函数会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程,正式发起read请求。
3. 从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket(也就是数据准备好了的socket),即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
-
+
**select函数**
@@ -90,7 +90,7 @@ date: 2021-04-08 15:21:58
4. 通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时(就是数据准备好的时候),则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。
5. 由于select函数是阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。注意,这里的所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。(一般在使用IO多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起IO请求时,数据已经到达了,用户线程一定不会被阻塞。)
-
+
@@ -106,7 +106,7 @@ date: 2021-04-08 15:21:58
3. 而另一方面,从**内核**的角度,当它受到一个异步读之后,首先它会立刻返回,所以不会对用户进程产生任何阻塞。然后,内核会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都 完成之后,**内核**会给用户进程发送一个信号,告诉它read操作完成了,用户线程直接使用即可。 在这整个过程中,进程完全没有被阻塞。
4. 异步IO模型使用了Proactor设计模式实现了这一机制。**(具体怎么搞得,看上面的文章链接)**
-
+
@@ -196,7 +196,7 @@ date: 2021-04-08 15:21:58
1. 来看一个标准设备(不是真实存在的,相当于一个逻辑上抽象的东西),通过它来帮助我们更好地理解设备交互的机制。可以看到一个包含两部分重要组件的设备。第一部分是向系统其他部分展现的硬件接口(interface)。同软件一样,硬件也需要一些接口,让系统软件来控制它的操作。因此,所有设备都有自己的特定接口以及典型交互的协议。
-
+
2. 第2部分是它的内部结构(internal structure)。这部分包含设备相关的特定实现,负责具体实现设备展示给系统的抽象接口。
@@ -231,11 +231,11 @@ While (STATUS == BUSY);//wait until device is done with your request
1. 没有中断时:进程1在CPU上运行一段时间(对应CPU那一行上重复的1),然后发出一个读取数据的I/O请求给磁盘。如果没有中断,那么操作系统就会简单自旋,不断轮询设备状态,直到设备完成I/O操作(对应其中的p)。当设备完成请求的操作后,进程1又可以继续运行。
-
+
2. 有了中断后:中断允许计算与I/O重叠(overlap),这是提高CPU利用率的关键。我们利用中断并允许重叠,操作系统就可以在等待磁盘操作时做其他事情。
-
+
- 在这个例子中,在磁盘处理进程1的请求时,操作系统在CPU上运行进程2。磁盘处理完成后,触发一个中断,然后操作系统唤醒进程1继续运行。这样,在这段时间,无论CPU还是磁盘都可以有效地利用。
@@ -243,7 +243,7 @@ While (STATUS == BUSY);//wait until device is done with your request
中断仍旧存在的缺点:
-
+
IO过程简述:
@@ -268,7 +268,7 @@ IO过程简述:
>
> 标准协议还有一点需要我们注意。具体来说,如果使用编程的I/O将一大块数据传给设备,CPU又会因为琐碎的任务而变得负载很重,浪费了时间和算力,本来更好是用于运行其他进程。下面的时间线展示了这个问题:
>
->
+>
>
> 进程1在运行过程中需要向磁盘写一些数据,所以它开始进行I/O操作,将数据从内存拷贝到磁盘(其中标示c的过程)。**拷贝结束后,磁盘上的I/O操作开始执行,此时CPU才可以处理其他请求。**
@@ -278,13 +278,13 @@ IO过程简述:
>
> DMA工作过程如下。为了能够将数据传送给设备,操作系统会通过编程告诉DMA引擎数据在内存的位置,要拷贝的大小以及要拷贝到哪个设备。在此之后,操作系统就可以处理其他请求了。当DMA的任务完成后,DMA控制器会抛出一个中断来告诉操作系统自己已经完成数据传输。修改后的时间线如下:
>
->
+>
>
> 从时间线中可以看到,数据的拷贝工作都是由DMA控制器来完成的。因为CPU在此时是空闲的,所以操作系统可以让它做一些其他事情,比如此处调度进程2到CPU来运行。因此进程2在进程1再次运行之前可以使用更多的CPU。
为了更好理解,看图:
-
+
过程:
@@ -302,7 +302,7 @@ IO过程简述:
场景:将磁盘上的文件读取出来,然后通过网络协议发送给客户端。
-
+
1. 很明显发生了4次拷贝
@@ -326,7 +326,7 @@ IO过程简述:
`read()` 系统调用的过程中会把内核缓冲区的数据拷贝到用户的缓冲区里,为了减少这一步开销,我们可以用 `mmap()` 替换 `read()` 系统调用函数。`mmap()` 系统调用函数会直接把内核缓冲区里的数据映射到用户空间,这样,操作系统内核与用户空间共享缓冲区,就不需要再进行任何的数据拷贝操作。
-
+
总的来说mmap减少了一次数据拷贝,总共4次上下文切换,3次数据拷贝
@@ -336,7 +336,7 @@ IO过程简述:
`Linux2.1` 版本提供了 `sendFile` 函数,其基本原理如下:数据根本不经过用户态,直接从内核缓冲区进入到 `SocketBuffer`
-
+
总的来说有2次上下文切换,3次数据拷贝。
@@ -344,7 +344,7 @@ IO过程简述:
`Linux在2.4` 版本中,做了一些修改,避免了从内核缓冲区拷贝到 `Socketbuffer` 的操作,直接拷贝到协议栈,从而再一次减少了数据拷贝
-
+
diff --git a/docs/spring-sourcecode-v1/01.第1章-Spring源码纵览.md b/docs/spring-sourcecode-v1/01.第1章-Spring源码纵览.md
index f5f30f0..f732dd4 100644
--- a/docs/spring-sourcecode-v1/01.第1章-Spring源码纵览.md
+++ b/docs/spring-sourcecode-v1/01.第1章-Spring源码纵览.md
@@ -38,7 +38,7 @@ Spring源码纵览这一节,主要是先了解下Spring的一些核心东西
-
+
- **蓝色实线箭头**是指继承关系
- **绿色虚线箭头**是指接口实现关系
@@ -91,13 +91,13 @@ BeanPostProcessor
快捷键:ctrl+F12 看类的方法
-
+
#### 实现类
快捷键:ctrl+h 查看接口实现类
-
+
@@ -189,7 +189,7 @@ public interface ResourceLoader {
#### 实现类
-
+
@@ -232,7 +232,7 @@ public interface BeanFactory {
> 源码分析小技巧:看源码时,我们可以先看一个类的接口继承关系,因为接口就是规范,大部分开源框架源码都是遵守这一规范的。
-
+
@@ -248,7 +248,7 @@ public interface BeanFactory {
#### AbstractApplicationContext
-
+
环境类的意思就是谁持有这个策略;这里就解释了上文说ResourceLoader是环境类接口
@@ -265,7 +265,7 @@ public interface BeanFactory {
#### GenericApplicationContext
-
+
这里组合了DefaultListableBeanFactory
@@ -310,7 +310,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
-
+
1. BeanDefinitionRegistry:Bean定义信息注册中心
2. SimpleAliasRegistry:别名注册中心
@@ -336,7 +336,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
#### 流程图-BeanDefinition注册流程
-
+
- 我们要看BeanDefinition是何时被放入到beanDefinitionMap,只需要在DefaultListableBeanFactory用到`beanDefinitionMap.put()`的地方打个断点。
- 我们在DefaultListableBeanFactory里搜索,发现了registerBeanDefinition(注册Bean定义信息)这个方法名很像我们要找的东西,再看里面的代码,果然有`beanDefinitionMap.put()`这串代码,我们试着在这里打个断点
@@ -361,7 +361,7 @@ public class MainTest {
#### Debug调用栈
-
+
> 调用栈的调用顺序已经非常清楚了,可以把图放大一点看,下面只说一些必要的信息。
@@ -780,7 +780,7 @@ public class MainTest {
- 还有一个debug的猜测方向,想要注册BeanDefinition肯定要new,我们可以直接在AbstractBeanDefinition这个抽象父类的构造函数打断点,我们不知道会走哪个构造函数,所以给三个构造函数都打断点。
- 下面就是打完断点之后,运行MainTest测试类后的调用栈
-
+
@@ -936,7 +936,7 @@ public class MainTest {
### ApplicationContext接口功能
-
+
1. ioc事件派发器
2. 国际化解析
@@ -969,7 +969,7 @@ public interface Aware {
2. 注释的大致意思是:Aware是一个标记性的超接口(顶级接口),指示了一个Bean有资格通过回调方法的形式获取Spring容器底层组件。实际回调方法被定义在每一个子接口中,而且通常一个子接口只包含一个接口一个参数并且返回值为void的方法。
3. 说白了:只要实现了Aware子接口的Bean都能获取到一个Spring底层组件。
-
+
比如实现了ApplicationContextAware接口,实现它的方法,就能通过回调机制拿到ApplicationContext
@@ -979,7 +979,7 @@ public interface Aware {
##### 流程图-Bean对象创建流程
-
+
##### Debug调用栈
@@ -1055,7 +1055,7 @@ public class MainConfig {
-
+
1. 有一些一样的东西不再赘述
2. 在`AbstractApplicationContext#refresh()`里会调用
@@ -1267,7 +1267,7 @@ public class MainConfig {
##### Debug调用栈
-
+
@@ -1434,7 +1434,7 @@ public class MainTest {
-
+
@@ -1475,7 +1475,7 @@ public class MainTest {
我们看到此时,Person的name属性还是null
-
+
@@ -1497,7 +1497,7 @@ public class MainTest {
这里拿到属性值
-
+
@@ -1512,13 +1512,13 @@ public class MainTest {
}
```
-
+
bw就是上面说的 => 里面封装好了真正的Person对象
-
+
@@ -1528,7 +1528,7 @@ bw就是上面说的 => 里面封装好了真正的Person对象
这里就是一层一层调,不重要跳过。
-
+
@@ -1549,7 +1549,7 @@ private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv)
-
+
@@ -1588,7 +1588,7 @@ private class BeanPropertyHandler extends PropertyHandler {
-
+
最后就是反射走到了我们的`Person#setName(String name)`
@@ -1598,7 +1598,7 @@ private class BeanPropertyHandler extends PropertyHandler {
### 再来看看messageSource何时赋值
-
+
@@ -1742,7 +1742,7 @@ public class Person implements ApplicationContextAware, MessageSourceAware {
老样子,只看不一样的调用栈
-
+
### AbstractAutowireCapableBeanFactory#populateBean()
@@ -1785,7 +1785,7 @@ public class Person implements ApplicationContextAware, MessageSourceAware {
这里有一个非常著名的后置处理器,`AutowiredAnnotationBeanPostProcessor`自动装配注解后置处理器,顾名思义就是用来处理@Autowired注解自动装配的。
-
+
diff --git a/docs/spring-sourcecode-v1/02.第2章-后置工厂处理器和Bean生命周期.md b/docs/spring-sourcecode-v1/02.第2章-后置工厂处理器和Bean生命周期.md
index d7b9194..3775b8b 100644
--- a/docs/spring-sourcecode-v1/02.第2章-后置工厂处理器和Bean生命周期.md
+++ b/docs/spring-sourcecode-v1/02.第2章-后置工厂处理器和Bean生命周期.md
@@ -97,7 +97,7 @@ public interface BeanFactoryPostProcessor {
}
```
-
+
核心就是我们对于传进来的参数,可以**修改,覆盖,添加**它的东西。对于BeanPostProcessor来说,传进来的参数是`(Object bean, String beanName)` ,它都已经把bean传给你了,这意味着我们可以修改传进来的Bean的任何东西。不管你是事务也好,AOP也好,都是通过这些个后置处理器来添加这些额外功能的。
@@ -106,11 +106,11 @@ BeanFactoryPostProcessor:后置增强BeanFactory,也就是增强Bean工厂
### BeanFactoryPostProcessor的接口关系
-
+
### BeanPostProcessor接口关系
-
+
DestructionAwareBeanPostProcessor接口是跟销毁有关的,我们这里不分析
@@ -391,7 +391,7 @@ public class MainTest {
### 流程图-Bean生命周期与后置工厂处理器
-
+
### BeanDefinitionRegistryPostProcessor
@@ -399,7 +399,7 @@ public class MainTest {
##### Debug调用栈
-
+
##### AbstractApplicationContext#refresh()
@@ -615,9 +615,9 @@ public class MainTest {
> Spring中所有组件的获取都是通过getBean(),容器中有就拿,没有就创建。
-
+
-
+
下面那个是Spring默认提供的后置处理器,我们后面再讲。
@@ -677,11 +677,11 @@ public interface Ordered {
##### Debug调用栈
-
+
从PostProcessorRegistrationDelegate 142行开始走不同的调用,代码在上面有注释
-
+
##### PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors()
@@ -706,7 +706,7 @@ private static void invokeBeanDefinitionRegistryPostProcessors(
##### Debug调用栈
-
+
@@ -733,7 +733,7 @@ private static void invokeBeanFactoryPostProcessors(
##### Debug调用栈
-
+
@@ -741,7 +741,7 @@ private static void invokeBeanFactoryPostProcessors(
##### Debug调用栈
-
+
代码注释也是上面那个,BeanDefinitionRegistryPostProcessor和BeanFactoryPostProcessor执行逻辑基本一样
@@ -779,11 +779,11 @@ public class MainConfig {
-
+
从这一步进来
-
+
@@ -791,7 +791,7 @@ public class MainConfig {
#### PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors()
-
+
F7进入
@@ -799,7 +799,7 @@ F7进入
ConfigurationClassPostProcessor配置类的后置处理
-
+
@@ -914,7 +914,7 @@ ConfigurationClassPostProcessor配置类的后置处理
这几个怎么来的我们后面说
-
+
diff --git a/docs/spring-sourcecode-v1/03.第3章-后置处理器和Bean生命周期.md b/docs/spring-sourcecode-v1/03.第3章-后置处理器和Bean生命周期.md
index c58dc9f..19c41fa 100644
--- a/docs/spring-sourcecode-v1/03.第3章-后置处理器和Bean生命周期.md
+++ b/docs/spring-sourcecode-v1/03.第3章-后置处理器和Bean生命周期.md
@@ -250,7 +250,7 @@ public class MainTest {
### 流程图-Bean生命周期与后置处理器
-
+
### BeanPostProcessor-执行无参构造
@@ -258,7 +258,7 @@ public class MainTest {
#### Debug调用栈
-
+
#### AbstractApplicationContext#registerBeanPostProcessors()注册Bean后置处理器
@@ -345,7 +345,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
- Bean工厂后置处理器调用的是`invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory , List
+
@@ -355,7 +355,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
> 2. BeanFactoryPostProcessor是先执行完每一个的无参构造和实现的几个方法,再去执行下一个BeanFactoryPostProcessor
> 3. BeanPostProcessor是先执行所有BeanPostProcessor的无参构造,再执行所有BeanPostProcessor实现的方法。
-
+
### MergedBeanDefinitionPostProcessor-执行无参构造
@@ -365,7 +365,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
同上
-
+
@@ -375,7 +375,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
#### Debug调用栈
-
+
@@ -542,7 +542,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
3. 判断是不是想要的类型。
3. 这里有没有优化空间,再存一个BeanType=>BeanName的对应关系?但是这样的关系是一对多的,同一个BeanType下可能有多个beanName,Spring可能是考虑到空间成本,没有这样弄。
-
+
@@ -554,7 +554,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
2. 我们来看看此时单例池里有哪些对象
-
+
```java
protected boolean isTypeMatch(String name, ResolvableType typeToMatch, boolean allowFactoryBeanInit)
@@ -687,13 +687,13 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
在此方法里为什么Cat会进来两次呢?往后面看
-
+
### InstantiationAwareBeanPostProcessor-执行postProcessBeforeInstantiation方法
#### Debug调用栈
-
+
#### AbstractApplicationContext#finishBeanFactoryInitialization()完成BeanFactory初始化
@@ -746,7 +746,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
2. 上面我们也看了只有Cat的对象还没创建,还没初始化,所以下面就开始创建对象了。
-
+
@@ -860,7 +860,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
#### Debug调用栈
-
+
> 前面还是一样的执行逻辑,直接来到下面
@@ -1003,7 +1003,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
1. 实例提供者:
-
+
@@ -1025,7 +1025,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
### Cat-执行无参构造方法
-
+
@@ -1039,7 +1039,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
#### Debug调用栈
-
+
@@ -1061,7 +1061,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
#### Debug调用栈
-
+
#### AbstractAutowireCapableBeanFactory#populateBean()属性赋值
@@ -1141,7 +1141,7 @@ protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFa
}
```
-
+
@@ -1193,7 +1193,7 @@ public void setValue(@Nullable Object value) throws Exception { //name setName
#### Debug调用栈
-
+
@@ -1201,7 +1201,7 @@ public void setValue(@Nullable Object value) throws Exception { //name setName
#### AutowiredAnnotationBeanPostProcessor#postProcessProperties()
-
+
```java
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
@@ -1322,7 +1322,7 @@ public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, Str
#### Debug调用栈
-
+
这也说明了,@Autowire,@Value赋值的时候会去找setXXX,这也是@Autowire的原理
@@ -1350,7 +1350,7 @@ public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, Str
#### Debug调用栈
-
+
@@ -1461,7 +1461,7 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro
#### Debug调用栈
-
+
@@ -1513,7 +1513,7 @@ public Object postProcessBeforeInitialization(Object bean, String beanName) thro
#### Debug调用栈
-
+
diff --git a/docs/spring-sourcecode-v1/04.第4章-Bean初始化流程.md b/docs/spring-sourcecode-v1/04.第4章-Bean初始化流程.md
index aee5f6c..2a2bb4a 100644
--- a/docs/spring-sourcecode-v1/04.第4章-Bean初始化流程.md
+++ b/docs/spring-sourcecode-v1/04.第4章-Bean初始化流程.md
@@ -16,7 +16,7 @@ date: 2022-01-27 21:01:02
## 流程图-bean初始化流程
-
+
## AbstractApplicationContext#refresh()
@@ -206,7 +206,7 @@ date: 2022-01-27 21:01:02
## 工厂Bean的初始化方式
-
+
@@ -313,13 +313,13 @@ public class Hello {
从这里开始看调用链
-
+
-
+
@@ -475,13 +475,13 @@ String FACTORY_BEAN_PREFIX = "&";
最后返回
-
+
-
+
@@ -532,7 +532,7 @@ public class Cat implements InitializingBean, SmartInitializingSingleton {
### Debug调用栈
-
+
diff --git a/docs/spring-sourcecode-v1/05.第5章-容器刷新流程.md b/docs/spring-sourcecode-v1/05.第5章-容器刷新流程.md
index 85abcf7..ed20860 100644
--- a/docs/spring-sourcecode-v1/05.第5章-容器刷新流程.md
+++ b/docs/spring-sourcecode-v1/05.第5章-容器刷新流程.md
@@ -18,7 +18,7 @@ date: 2022-02-13 18:01:02
## 流程图-容器刷新
-
+
## 容器创建
@@ -209,7 +209,7 @@ public class AnnotationMainTest {
上面的方法走完,我们可以看看到主要是下面4个后置处理器
-
+
##### RootBeanDefinition
@@ -360,7 +360,7 @@ public class AnnotationMainTest {
走完之后,注册中心肯定多了咱们的配置类
-
+
@@ -584,7 +584,7 @@ protected void initPropertySources() {
}
```
-
+
@@ -607,11 +607,11 @@ protected void initPropertySources() {
这一步有个很关键的后置处理器
-
+
-
+
@@ -746,7 +746,7 @@ protected void initPropertySources() {
-
+
@@ -1043,7 +1043,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
拿到所有类(资源),不管你有没有标@Component注解。然后挨个遍历每一个资源是不是候选的组件(根据前面准备的一些条件,在这里进行判断)
-
+
@@ -1051,7 +1051,7 @@ public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateCo
最后我们看一下执行完之后的BeanDefinition信息
-
+
@@ -1309,7 +1309,7 @@ public class B {
这就是循环引用的场景,这种写法由于Spring内部获取Bean都是通过getBean方法来获取,就造成了下面的死循环。我们来看看Spring是怎么解决的。
-
+
@@ -1568,16 +1568,16 @@ public class B {
1. pos_1位置先进入pos_3位置的`getSingleton(beanName, true)`,查看缓存中有没有A组件
-
+
2. 然后走到pos_2调用pos_5的`getSingleton()`开始创建A的流程
3. 在pos_5的`getSingleton()`中走到pos_6的`beforeSingletonCreation()`,就变成下面这样
-
+
4. 接着pos_7的会调用pos_2的lamda表达式里的`createbean()`,里面再调用`doCreateBean()`。前面讲过不多说,最终调用A的无参构造(pos_8),创建完之后发现A的B属性是null。
-
+
5.在pos_9处的`addSingletonFactory()`来准备解决循环引用
@@ -1594,7 +1594,7 @@ public class B {
}
```
-
+
@@ -1662,11 +1662,11 @@ public class B {
通过前面讲过的AutowiredAnnotationBeanPostProcessor来注入B,最后发现要调用setB方法给B赋值
-
+
7. 继续走,发现要想获得B还是要调用getBean
-
+
```java
public Object resolveCandidate(String beanName, Class> requiredType, BeanFactory beanFactory)
@@ -1680,13 +1680,13 @@ public class B {
然后图就是这样子
-
+
8. B也是走这一套
-
+
9. B为了获取A,还要再走一次getBean()流程,最终还是走到
@@ -1720,13 +1720,13 @@ public class B {
}
```
-
+
10.
-
+
@@ -1739,7 +1739,7 @@ public class B {
12. B初始化完之后,回到getSingleton,把自己放到单例池里
-
+
```java
protected void addSingleton(String beanName, Object singletonObject) {
@@ -1752,15 +1752,15 @@ protected void addSingleton(String beanName, Object singletonObject) {
}
```
-
+
-
+
13. B全部结束之后回到A的流程,A赋值工作结束了,然后就开始A的初始化。初始化的过程中
-
+
```java
protected void addSingleton(String beanName, Object singletonObject) {
@@ -1775,7 +1775,7 @@ protected void addSingleton(String beanName, Object singletonObject) {
-
+
@@ -1787,7 +1787,7 @@ protected void addSingleton(String beanName, Object singletonObject) {
-
+
diff --git a/docs/spring-sourcecode-v1/06.第6章-AOP的后置处理器和代理对象的创建.md b/docs/spring-sourcecode-v1/06.第6章-AOP的后置处理器和代理对象的创建.md
index 354d990..704e0ff 100644
--- a/docs/spring-sourcecode-v1/06.第6章-AOP的后置处理器和代理对象的创建.md
+++ b/docs/spring-sourcecode-v1/06.第6章-AOP的后置处理器和代理对象的创建.md
@@ -222,7 +222,7 @@ class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
### Debug调用栈
-
+
### ImportBeanDefinitionRegistrar
@@ -290,7 +290,7 @@ pos_1 Debug进去是下面的方法
### AnnotationAwareAspectJAutoProxyCreator后置处理器
-
+
我们发现这就是一个后置处理器,印证了我们之前说过的几乎所有功能增强或功能附加都是由后置处理器来完成
@@ -318,13 +318,13 @@ pos_1 Debug进去是下面的方法
##### 作用
-
+
1. 由于它是后置处理器所以肯定是在refresh方法里的registerBeanPostProcessors这一步开始干活的
2. 果然是从getBean调用到了我们的重写方法断点处,也就验证了我们上面说怎么分析。绿色包含的前面已经讲过,不再赘述。
3. 也确实是AnnotationAwareAspectJAutoProxyCreator,走到这里说明前面已经创建出了AnnotationAwareAspectJAutoProxyCreator对象(前面怎么创建的,getbean的流程之前已经讲的很清楚了),后面只是对AnnotationAwareAspectJAutoProxyCreator进行初始化
-
+
##### AbstractAutowireCapableBeanFactory#initializeBean()进行初始化
@@ -410,7 +410,7 @@ pos_1 Debug进去是下面的方法
}
```
-
+
```java
public ReflectiveAspectJAdvisorFactory(@Nullable BeanFactory beanFactory) {
@@ -483,7 +483,7 @@ public ReflectiveAspectJAdvisorFactory(@Nullable BeanFactory beanFactory) {
}
```
-
+
1. 我们来看一下是什么时候触发的,看图中的beanName==myBeanPostProcessor时触发的,也就是创建完AnnotationAwareAspectJAutoProxyCreator,第一次创建别的后置处理器时触发的。
2. 这里虽然开始参与其它Bean的创建过程,但也可能是什么都没做。
@@ -610,7 +610,7 @@ protected boolean isInfrastructureClass(Class> beanClass) {
#### Debug调用栈-调用aspectJAdvisorsBuilder的findCandidateAdvisors()方法
-
+
AbstractAutoProxyCreator#postProcessBeforeInstantiation()这个方法,上面刚分析过,这次来分析shouldskip()
@@ -633,7 +633,7 @@ AbstractAutoProxyCreator#postProcessBeforeInstantiation()这个方法,上面
-
+
它进去的时候就是beanName==LogAspect,我们看看它是怎么判断的
@@ -653,7 +653,7 @@ AbstractAutoProxyCreator#postProcessBeforeInstantiation()这个方法,上面
我们这里先放行,看构造了哪些增强器
-
+
##### BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors()找到切面,并创建增强器advisors
@@ -738,11 +738,11 @@ AbstractAutoProxyCreator#postProcessBeforeInstantiation()这个方法,上面
-
+
for循环所有组件之后,有切面的话,就赋值,然后aspectNames
-
+
#### 结论
@@ -939,7 +939,7 @@ AbstractAutoProxyCreator
-
+
@@ -981,21 +981,21 @@ AbstractAutoProxyCreator
-
+
获取父类的方法
-
+
然后回到这里
-
+
最后回到这个for循环,循环所有的方法
-
+
#### ReflectiveAspectJAdvisorFactory#getAdvisor()
@@ -1126,7 +1126,7 @@ AbstractAutoProxyCreator
到了初始化这里了,说明前面无参构造创建myBeanPostProcessor对象已经完成了
-
+
#### AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors()
@@ -1148,7 +1148,7 @@ protected List
+
#### BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors()
@@ -1246,7 +1246,7 @@ protected List
+
### 创建HelloService代理对象之前的工作
@@ -1254,25 +1254,25 @@ finishBeanFactoryInitialization(beanFactory);
我们在诸如下面的地方打上条件断点`beanName.equals("helloService") || beanName.equals("logAspect")`
-
+
debug放行
-
+
往下走进入getBean()
-
+
-
+
走到熟悉的createbean
-
+
我们以前讲过resolveBeforeInstantiation是返回代理对象的机会,我们现在来看一下AOP有没有在这里给helloService返回代理对象。F7进入此方法
@@ -1302,25 +1302,25 @@ debug放行
}
```
-
+
这是有一个负责AOP功能的后置处理器,就是我们前面说的那个
当循环到这个后置处理器的时候,我们进入方法
-
+
-
+
我debug完之后发现前置过程啥也没干,返回了个NULL。什么都没干的原因就是**负责AOP功能的后置处理器第一次运行准备好数据**和**构建增强器**这两步已经干过了
-
+
@@ -1329,11 +1329,11 @@ debug放行
1. 最后我们惊奇的发现,AOP的后置处理器在resolveBeforeInstantiation这一步竟然没有返回代理对象,这可能跟大部分人想的有出入。
2. 想一下为什么没有返回代理对象?走到这一步的时候,咱们的HelloService对象都还没有创建,更没有赋值,初始化。你如果在这里直接返回代理对象,那假设HelloService还注入了其它组件,那你返回的代理对象不就没有这些组件了嘛?直接跳过了。
-
+
往下走,创建一个最原始的对象
-
+
#### 赋值
@@ -1341,15 +1341,15 @@ debug放行
往下走进入赋值环节
-
+
我们来看下AOP的后置处理器在这一步有没有做事
-
+
啥事没干,直接返回
-
+
@@ -1359,15 +1359,15 @@ debug放行
#### 初始化
-
+
进入之后发现,AOP的后置处理器在此介入了,我们再进去看下到底做了啥
-
+
啥也没做,直接返回原生Bean
-
+
@@ -1376,11 +1376,11 @@ debug放行
##### applyBeanPostProcessorsAfterInitialization后置初始化方法
-
+
-
+
F7进入方法
@@ -1471,7 +1471,7 @@ F7进入方法
我发现他调用了咱们之前讲过的方法
-
+
因为之前调用过,缓存中有,直接返回
@@ -1520,7 +1520,7 @@ F7进入方法
}
```
-
+
@@ -1568,7 +1568,7 @@ F7进入方法
接着我们返回到这一步,看到了这几个增强器
-
+
这里又有一个重要方法
@@ -1605,11 +1605,11 @@ F7进入方法
}
```
-
+
回到AbstractAutoProxyCreator#wrapIfNecessary,调用下面的方法真正开始创建代理对象
-
+
##### AbstractAutoProxyCreator#createProxy()开始创建代理对象
@@ -1761,27 +1761,27 @@ F7进入方法
最后返回
-
+
继续返回
-
+
-
+
最终单例池里就有代理对象了
-
+
##### logAspect创建原生对象,而不是代理对象
logAspect切面对象最后创建的是原生对象,如下图,因为他不需要代理
-
+
diff --git a/docs/spring-sourcecode-v1/07.第7章-AOP的执行流程原理和监听器原理.md b/docs/spring-sourcecode-v1/07.第7章-AOP的执行流程原理和监听器原理.md
index 39a9dbf..efb735e 100644
--- a/docs/spring-sourcecode-v1/07.第7章-AOP的执行流程原理和监听器原理.md
+++ b/docs/spring-sourcecode-v1/07.第7章-AOP的执行流程原理和监听器原理.md
@@ -20,7 +20,7 @@ date: 2022-04-17 12:01:02
## 流程图-AOP运行流程原理
-
+
## 由Aop的执行流程引出方法拦截器
@@ -28,11 +28,11 @@ date: 2022-04-17 12:01:02
断点打到这里,F7进入方法
-
+
自然而然的跳到了cglib这里
-
+
#### CglibAopProxy#intercept()
@@ -80,7 +80,7 @@ date: 2022-04-17 12:01:02
#### CglibAopProxy#getInterceptorsAndDynamicInterceptionAdvice()
-
+
1. 把5个增强器变成了方法拦截器,增强器只是保存信息的,真正执行还得靠方法拦截器。
2. 我们再给上面的470行打上断点,看下之前是如何生成方法拦截器的。因为第一次生成的时候没有缓存,肯定能进去470行。
@@ -91,9 +91,9 @@ date: 2022-04-17 12:01:02
#### Debug调用栈
-
+
-
+
可以看到就是在之前创建代理对象的时候增强器转成的拦截器
@@ -240,7 +240,7 @@ public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeA
然后debug回到accept()方法
-
+
@@ -248,12 +248,12 @@ public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeA
## 正式开始分析AOP运行流程-链式执行
-
+
1. F7进入方法,上面讲过会调用CglibAopProxy内部类的DynamicAdvisedInterceptor#intercept()。这次我们来说下为什么会跳到DynamicAdvisedInterceptor#intercept()方法
2. HelloService是一个代理对象,它的AOP代理是一个DynamicAdvisedInterceptor对象
-
+
3. 而DynamicAdvisedInterceptor实现了MethodInterceptor接口
@@ -347,27 +347,27 @@ public interface MethodInterceptor extends Interceptor {
}
```
-
+
在前面创建HelloService代理对象时创建好的方法拦截器,然后调用proceed()
-
+
### CglibMethodInvocation#proceed()
-
+
顾名思义,就是用来执行Cglib生成的代理对象的方法
-
+
#### CglibAopProxy#proceed()
-
+
```java
public Object proceed() throws Throwable {
@@ -429,7 +429,7 @@ public interface MethodInterceptor extends Interceptor {
}
```
-
+
上面5个拦截器都继承了Spring的org.aopalliance.intercept.MethodInterceptor,和Cglib的MethodInterceptor没关系了
@@ -437,19 +437,19 @@ public interface MethodInterceptor extends Interceptor {
获取第一个拦截器ExposeInvocationInterceptor
-
+
往下走,直接走到了这里,准备调用ExposeInvocationInterceptor的invoke()
-
+
-
+
##### CglibAopProxy#proceed()
-
+
```java
public Object proceed() throws Throwable {
@@ -481,35 +481,35 @@ public Object proceed() throws Throwable {
结果又调回了,很明显这是个递归调用
-
+
#### MethodBeforeAdviceInterceptor-前置通知
然后后面的调用逻辑和前面就一样了,如下
-
+
-
+
-
+
这个时候就先执行切面的before方法,前置通知就执行了
-
+
继续调父类的方法
-
+
#### AspectJAfterAdvice-后置通知
-
+
后置通知这里先不执行,先继续执行下面的方法拦截器链路,最后finally再执行后置通知
@@ -517,7 +517,7 @@ public Object proceed() throws Throwable {
#### AfterReturningAdviceInterceptor-返回通知
-
+
1. 先往下走,我们就继续
@@ -527,7 +527,7 @@ public Object proceed() throws Throwable {
#### AspectJAfterThrowingAdvice-异常通知
-
+
继续往下发现索引要超了
@@ -535,17 +535,17 @@ public Object proceed() throws Throwable {
#### 真正执行sayhello()
-
+
-
+
放行之后看控制台,sayhello方法就打印了。并且到目前为止,只有前面说的前置通知执行了。
-
+
然后咱们就往后返,
@@ -553,7 +553,7 @@ public Object proceed() throws Throwable {
#### AspectJAfterThrowingAdvice-异常通知
-
+
咱们这里没异常,就继续返回了
@@ -561,13 +561,13 @@ public Object proceed() throws Throwable {
返回到这里,准备执行返回通知
-
+
放行之后的控制台
-
+
继续返回
@@ -575,11 +575,11 @@ public Object proceed() throws Throwable {
准备执行后置通知
-
+
放行finally之后的控制台
-
+
@@ -591,13 +591,13 @@ public Object proceed() throws Throwable {
继续返回
-
+
#### CglibAopProxy$DynamicAdvisedInterceptor
-
+
@@ -760,7 +760,7 @@ public class AnnotationMainTest {
在还未进行refresh()十二大步刷新时,容器就已经有了这两事件相关的Bean定义信息了。
-
+
@@ -768,7 +768,7 @@ public class AnnotationMainTest {
#### 继承树
-
+
1. 我们看到实现了SmartInitializingSingleton和BeanFactoryPostProcessor
2. BeanFactoryPostProcessor我们反复在说,就是工厂后置处理环节。EventListenerMethodProcessor实现了BeanFactoryPostProcessor,说明他在工厂后置处理环节会做事
@@ -776,7 +776,7 @@ public class AnnotationMainTest {
> AbstractApplicationContext#refresh() ==> AbstractApplicationContext#finishBeanFactoryInitialization() ==> DefaultListableBeanFactory#preInstantiateSingletons()
-
+
@@ -786,7 +786,7 @@ public class AnnotationMainTest {
Debug启动
-
+
果然是从工厂后置处理那里过来的
@@ -794,7 +794,7 @@ Debug启动
放行,继续往下走。果然SmartInitializingSingleton这里开始做事了
-
+
@@ -850,7 +850,7 @@ Debug启动
我们在这里打上条件断点
-
+
@@ -858,15 +858,15 @@ Debug启动
-
+
F7进入下面的方法
-
+
咱们发现是创建了一个适配器
-
+
```java
public ApplicationListenerMethodAdapter(String beanName, Class> targetClass, Method method) {
@@ -885,17 +885,17 @@ public ApplicationListenerMethodAdapter(String beanName, Class> targetClass, M
为啥要创建这样一个适配器呢?虽然我们的AppEventListener不是监听器,它只是在方法里标注了监听注解,我们自己没有写监听器。但是咱们解析@EventListener注解之后,在这里生成的适配器却实现了EventListener,也就说明这个适配器就是个监听器。
-
+
继续往下放行
-
+
-
+
把适配器放到了事件多播器里
@@ -907,7 +907,7 @@ public ApplicationListenerMethodAdapter(String beanName, Class> targetClass, M
最后就是三个方法,三个适配器
-
+
@@ -915,11 +915,11 @@ public ApplicationListenerMethodAdapter(String beanName, Class> targetClass, M
给下面的位置打上断点
-
+
-
+
##### AbstractApplicationContext#publishEvent()
diff --git a/docs/spring-sourcecode-v1/08.第8章-SpringMVC子容器和Spring父容器的启动原理.md b/docs/spring-sourcecode-v1/08.第8章-SpringMVC子容器和Spring父容器的启动原理.md
index c19b434..8181a88 100644
--- a/docs/spring-sourcecode-v1/08.第8章-SpringMVC子容器和Spring父容器的启动原理.md
+++ b/docs/spring-sourcecode-v1/08.第8章-SpringMVC子容器和Spring父容器的启动原理.md
@@ -92,11 +92,11 @@ public class AppConfig {
-
+
根路径是在这里配的,tomcat的配置自己百度下,很简单
-
+
## Java的SPI机制
@@ -321,7 +321,7 @@ cn.imlql.mysql.MySQLSaveService
### WebApplicationInitializer
-
+
@@ -342,13 +342,13 @@ public interface ServletContainerInitializer {
-
+
-
+
@@ -469,15 +469,15 @@ ServiceLoader
+
接着在最底下的for循环执行所有实现了WebApplicationInitializer的类的onStartup(),然后就走到了我们的AppStarter
-
+
到这一步,ioc容器都没有创建,我们给refresh()打个断点,看什么时候启动的ioc
-
+
@@ -485,7 +485,7 @@ ServiceLoader
+
不过我凭经验猜测Springmvc里最重要的是DispatcherServlet,会不会是DispatcherServlet的那一步启动了IOC,我们开始进行下面的尝试
@@ -502,7 +502,7 @@ ServiceLoader
+
4. Serlvet是被谁调用开始初始化的属于tomcat的源码,我们这里不研究,我们这里只需要知道,每一个Servlet都会被初始化就可以了。
@@ -512,7 +512,7 @@ spring-web中有一个叫DispatcherServlet的类,很明显他是一个Servlet
-
+
1. 想要启动IOC容器,只可能是创建DispatcherServlet对象或者调用init()的时候来搞。上面我们也看到了,创建DispatcherServlet对象的时候debug调用栈并没有显示跳到了refresh方法,所以显然不是创建对象的时候
2. 那就只有可能是调用init()的时候开始启动的IOC容器
@@ -652,13 +652,13 @@ DispatcherServlet没有重写initFrameworkServlet()
webloc.setParent(springloc)。类似于双亲委派,容器隔离。先看当前容器有没有这个组件,当前容器没有再去父容器找有没有这个组件
-
+
### AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC
-
AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC
+
AbstractAnnotationConfigDispatcherServletInitializer能更快的整合Spring和SpringMVC
> 后面的讲解都用这个测试类
@@ -811,7 +811,7 @@ public interface ServletContextListener extends EventListener {
#### SpringServletContainerInitializer#onStartup()
-
+
@@ -858,7 +858,7 @@ public interface ServletContextListener extends EventListener {
#### AbstractDispatcherServletInitializer#onStartup()
-
+
因为咱们的QuickAppStarter没有onStarup()所以就调用了父类AbstractDispatcherServletInitializer的,没想到AbstractDispatcherServletInitializer也是继续调用父类的
@@ -875,7 +875,7 @@ public interface ServletContextListener extends EventListener {
#### AbstractContextLoaderInitializer#onStartup()
-
+
@@ -907,7 +907,7 @@ public abstract class AbstractContextLoaderInitializer implements WebApplication
}
```
-
+
@@ -930,7 +930,7 @@ public abstract class AbstractContextLoaderInitializer implements WebApplication
}
```
-
+
getRootConfigClasses()正好是咱们QuickAppStarter这个子类重写的,debug F7进入
@@ -938,19 +938,19 @@ getRootConfigClasses()正好是咱们QuickAppStarter这个子类重写的,debu
果不其然,调用了QuickAppStarter#getRootConfigClasses()
-
+
继续往下走创建Web容器,这是Spring父容器,因为你看它getRootConfigClasses()获取的是父容器配置
-
+
然后返回
-
+
@@ -983,7 +983,7 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte
接着就继续返回
-
+
@@ -1022,7 +1022,7 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte
}
```
-
+
@@ -1040,25 +1040,25 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte
}
```
-
+
这里又new了一个容器,和上面那个容器一样都没有初始化。这里也是调用咱们QuickAppStarter重写的方法,因为这里调用的是getServletConfigClasses(),所以很明显这里的容器是Web子容器
-
+
然后就一路往回返,走到这里
-
+
继续F7进入
-
+
这里就是保存咱们上面刚创建的Web子容器,然后再返回
#### 返回到SpringServletContainerInitializer#onStartup()
-
+
1. 这里应用就加载完了,接下来干嘛呢?
2. 你往前看看,咱们的Spring容器和Web子容器都是只是创建完了,都还没有初始化,甚至都没有webloc.setParent(springloc)这样产生父子容器的关系
@@ -1072,7 +1072,7 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte
#### ContextLoaderListener#contextInitialized()
-
+
@@ -1080,23 +1080,23 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte
然后真的走到了这里。tomcat里的代码位置是乱的,乱的意思就是比如说上面写的是4766行的调用,但实际上那里是个`}`大括号。也不知道是什么问题,所以我们就大致看下tomcat的代码,不细究。
-
+
这里的调用还是对的
-
+
应该就是类似这样的调用
-
+
走的应该是第一个if
-
+
@@ -1104,21 +1104,21 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte
#### ContextLoader#initWebApplicationContext()
-
+
F7进入,这里因为我重新启动了一次,所以你看到根容器是@3661
-
+
终于要调用refresh了
-
+
这里直接放行到容器refresh完毕看下父容器
-
+
父容器只扫描了,springconfig和helloService,我们继续放行看下Web子容器.
@@ -1130,7 +1130,7 @@ F7进入,这里因为我重新启动了一次,所以你看到根容器是@36
跳到了这里,为什么会跳到这里呢?记不记得之前我们用DispatcherServlet保存了Web子容器,这里就要调用DispatcherServlet的相关初始化方法
-
+
@@ -1140,7 +1140,7 @@ F7进入,这里因为我重新启动了一次,所以你看到根容器是@36
-
+
上面父子容器关系形成了,并且父容器已经refresh完毕
@@ -1202,11 +1202,11 @@ F7进入,这里因为我重新启动了一次,所以你看到根容器是@36
#### FrameworkServlet#createWebApplicationContext()
-
+
再次来到Web子容器的刷新
-
+
1. 然后我们看到子容器只有它自己的东西
2. 虽然子容器只有controller,但是因为它保存了父容器。所以它是可以拿到HelloService的,也就是我们可以在HelloController里装配HelloService
diff --git a/docs/spring-sourcecode-v1/09.第9章-SpringMVC请求处理源码和HandlerMapping原理.md b/docs/spring-sourcecode-v1/09.第9章-SpringMVC请求处理源码和HandlerMapping原理.md
index d9cd564..878f614 100644
--- a/docs/spring-sourcecode-v1/09.第9章-SpringMVC请求处理源码和HandlerMapping原理.md
+++ b/docs/spring-sourcecode-v1/09.第9章-SpringMVC请求处理源码和HandlerMapping原理.md
@@ -16,7 +16,7 @@ date: 2022-06-21 12:01:02
## 请求的处理链路
-
+
1. tomcat里面可以部署多个项目应用。/abc_test和mvc_test这种就是项目路径,用于区分多个项目
2. 在以前的Servlet开发中,每一个路径都需要有一个Servlet来处理。比如上图所画
@@ -26,7 +26,7 @@ date: 2022-06-21 12:01:02
### Servlet继承树
-
+
@@ -41,7 +41,7 @@ date: 2022-06-21 12:01:02
### Debug调用栈
-
+
### DispatcherServlet#doService()
@@ -191,7 +191,7 @@ date: 2022-06-21 12:01:02
### doDispatch处理大流程图
-
+
@@ -210,7 +210,7 @@ protected HttpServletRequest checkMultipart(HttpServletRequest request) throws M
}
```
-
+
咱们这里目前连解析器都没有,所以就直接返回了
@@ -268,7 +268,7 @@ public boolean isMultipart(HttpServletRequest request) {
[官网介绍](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet-special-bean-types)
-
+
@@ -328,7 +328,7 @@ public boolean isMultipart(HttpServletRequest request) {
### Debug调用栈
-
+
@@ -381,7 +381,7 @@ public boolean isMultipart(HttpServletRequest request) {
-
+
@@ -491,7 +491,7 @@ org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.
### DispatcherServlet#getHandler()根据请求拿Controller
-
+
@@ -506,13 +506,13 @@ org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.
-
+
BeanNameUrlHandlerMapping里找不到映射关系,就直接下一个循环了。咱们主要看RequestMappingHandlerMapping怎么处理的
## RequestMappingHandlerMapping处理流程
-
+
@@ -569,7 +569,7 @@ F7进入`mapping.getHandler(request)`
}
```
-
+
F7进入`getHandlerInternal(request)`
@@ -609,7 +609,7 @@ F7进入`getHandlerInternal(request)`
-
+
F7进入`lookupHandlerMethod(lookupPath, request)`
@@ -617,7 +617,7 @@ F7进入`lookupHandlerMethod(lookupPath, request)`
### AbstractHandlerMethodMapping#lookupHandlerMethod()真正根据URL查Controller
-
+
@@ -627,7 +627,7 @@ F7进入`lookupHandlerMethod(lookupPath, request)`
### 返回到AbstractHandlerMethodMapping#getHandlerInternal()
-
+
这个时候是已经找到了由哪个处理器处理,接着返回
@@ -635,7 +635,7 @@ F7进入`lookupHandlerMethod(lookupPath, request)`
返回到这一步,准备执行`getHandlerExecutionChain(handler, request)`
-
+
@@ -661,20 +661,20 @@ F7进入`lookupHandlerMethod(lookupPath, request)`
}
```
-
+
- 咱们没写拦截器,就没有。
- 继续往回返
### 返回到DispatcherServlet#getHandler()
-
+
这里就是责任链模式,有能处理的handler就直接返回
### 返回到DispatcherServlet#doDispatch()
-
+
自此RequestMappingHandlerMapping处理结束
@@ -754,9 +754,9 @@ class MappingRegistry {
### Debug调用栈
-
+
-
+
意料之中,启动应用的时候从init初始化那里调用过来了
@@ -808,7 +808,7 @@ public void afterPropertiesSet() {
### AbstractHandlerMethodMapping#initHandlerMethods()初始化HandlerMethods
-
+
@@ -890,7 +890,7 @@ public void afterPropertiesSet() {
-
+
diff --git a/docs/suibi/我的校招-不完全知识点整理.md b/docs/suibi/我的校招-不完全知识点整理.md
deleted file mode 100644
index d983bdb..0000000
--- a/docs/suibi/我的校招-不完全知识点整理.md
+++ /dev/null
@@ -1,4374 +0,0 @@
----
-title: 我的校招-不完全知识点整理
-tags:
- - 校招
- - 面试
-categories:
- - 随笔
-keywords: 校招,面试
-description: 如题,一部分的整理【三万字】,具体的看文章
-cover: 'https://npm.elemecdn.com/lql_static@latest/bg/00009.webp'
-abbrlink: 5df2d017
-date: 2021-04-22 14:21:58
----
-
-
-
-
-
-
-
-# 必看说明
-
-
-> 1. 这篇文章是笔者校招时整理的一部分,有的是直接给出了url链接,也有很多是自己看书的总结(所以直接看答案可能会看不懂),都是相当简洁的版本(反正就是很水很乱,哈哈哈)。这篇文章所包含的知识点,我后面大部分都会重新写详细版,目前放出这篇文章的意义只是给需要看的人一个参考。
-> 2. 比如说
-> - Java集合的源码,我会写详细版【TODO,这些东西大部分已经看过了,在计划中】
-> - Java并发我已经写了详细版。不过还有一些的东西没写比如:阻塞队列,ConcurrentHashMap,CopyOnWriteArrayList等这些并发容器,也是常考点,还有一些常非面试的内容比如手写线程池等等,手写阻塞队列等等我觉得很有意思的点。【TODO,看了一部分内容,在计划中】
-> - jvm也已经写了详细版。还有jvm实战还没写【暂时没计划,因为我还不会,嘿嘿】
-> - Mysql部分很重要,下面给出的是极简版【TODO,看了一部分内容,在计划中】
-> - Redis【暂时没计划】
-> - Dubbo源码【TODO,正在学习,在计划中】
-> - 等等
-> 3. 希望大家多看些书和视频,背答案只是为了应试(无奈),多看书和视频以及一些讲的比较好的博客,才能理解的更深刻。
-> 4. 此篇文章发表于2021年4月,不再更新,后续可能删除
-
-
-
-# Java基础
-
-## 为什么重写 equals 方法就必须重写 hashcode 方法?
-
-### hashCode()介绍
-
-- hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
-
-- hashCode() 在散列表中才有用,在其它情况下没用。散列表存储的是键值对(key-value),在散列表中hashCode() 的作用是获取对象的散列码,进而快速确定该对象在散列表中的位置。(可以快速找到所需要的对象)
-
-### 为什么要有 hashCode
-
-**问题:**假设,HashSet中已经有1000个元素。当插入第1001个元素时,需要怎么处理?
-
-- 因为HashSet是Set集合,它不允许有重复元素。“将第1001个元素逐个的和前面1000个元素进行比较”?显然,这个效率是相等低下的。
-
-- 散列表很好的解决了这个问题,它根据元素的散列码计算出元素在散列表中的位置,然后将元素插入该位置即可。对于相同的元素,自然是只保存了一个。
-
-
-
-### 为什么重写equals方法就必须重写hashcode方法?
-
-- 在散列表中,
- 1、如果两个对象相等,那么它们的hashCode()值一定要相同; 这里的相等是指,通过equals()比较两个对象 时返回true
-
- 2、如果两个对象hashCode()相等,它们并不一定相等。(不相等时就是哈希冲突)
-
- 注意:这是在散列表中的情况。在非散列表中一定如此!
-
-
-
-- 考虑只重写equals而不重写 hashcode 时,虽然两个属性值完全相同的对象通过equals方法判断为true,但是当把这两个对象加入到 HashSet 时。会发现HashSet中有重复元素,这就是因为HashSet 使用 hashcode 判断对象是否已存在时造成了歧义,结果会导 致HashSet 的不正常运行。所以重写 equals 方法必须重写 hashcode 方法。
-
-
-
-## 深拷贝和浅拷贝
-
-> https://blog.csdn.net/baiye_xing/article/details/71788741
-
-1. **浅拷贝**:对一个对象进行拷贝时,这个对象对应的类里的成员变量。
- * 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值拷贝,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据
- * 对于数据类型是引用数据类型的成员变量(也就是子对象,或者数组啥的),也就是只是将该成员变量的引用值(引用拷贝【并发引用传递,Java本质还是值传递】)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
-2. **深拷贝**:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
-3. 也就是说浅拷贝对于子对象只是拷贝了引用值,并没有真正的拷贝整个对象。
-
-深拷贝实现思路:
-
-1、对于每个子对象都实现Cloneable 接口,并重写clone方法。最后在最顶层的类的重写的 clone 方法中调用所有子对象的 clone 方法即可实现深拷贝。【简单的说就是:每一层的每个子对象都进行浅拷贝=深拷贝】
-
-2、利用序列化。【先对对象进行序列化,紧接着马上反序列化出 】
-
-
-
-
-
-## 八中数据类型及其范围
-
-> 为什么是-128-127 :https://zhidao.baidu.com/question/588564780479617005.html
-
-* byte:1字节,范围为-128-127 -2^7——2^7-1
-* short:2字节,范围为-32768-32767 -2^15——2^15-1
-* int:4字节, -2^31——2^31-1
-* long:8字节
-* float:4字节
-* double:8字节
-* booolean:比较特殊
- - 官方文档:boolean 值只有 true 和 false 两种,这个数据类型只代表 1 bit 的信息,但是它的“大小”没有严格的定义。也就是说,不管它占多大的空间,只有一个bit的信息是有意义的。
- - 单个boolean 类型变量被编译成 int 类型来使用,占 4 个 byte 。
- - boolean 数组被编译成 byte 数组类型,每个 boolean 数组成员占 1 个 byte。
- - 在 Java 虚拟机里,1 表示 true ,0 表示 false 。
- - 这只是 Java 虚拟机的建议。
- - 可以肯定的是,不会是 1 个 bit 。
-* char:2字节
-
-从高精度转到低精度有可能损失精度,所以不会自动强转。比如int(高精度)就不会被自动强转为short,如果需要强转就只能强制转换。
-
-
-
-### Java中int类型的最小值是怎么表示的?
-
-首先计算机中保存的都是补码,都是二进制的补码
-
-**int型能表示的最大正数**
-
-int型的32bit位中,**第一位是符号为,正数位0**。因此,int型能表示的最大的正数的二进制码是0111 1111 1111 1111 1111 1111 1111 1111,也就是2^31-1。
-
-**int型能表示的最小负数**
-
-最小的负数的二进制码是1000 0000 0000 0000 0000 0000 0000 0000,其补码还是1000 0000 0000 0000 0000 0000 0000 0000,值是2^31。用2^31来代替-0
-
-
-
-
-
-## 反射的作用及机制
-
-**名词解释**
-
-Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键
-
-**用途**
-
-- 常用在通用框架里,比如Spring。为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,**运行时动态加载需要加载的对象**。
-
-原理
-
-```java
-public class NewInstanceTest {
-
- @Test
- public void test1() throws IllegalAccessException, InstantiationException {
-
- Class
-
-由上面代码可以看出来,this()方法可以调用本类的无参构造函数。如果本类继承的有父类,那么无参构造函数会有一个隐式的super()的存在。所以在同一个构造函数里面如果this()之后再调用super(),那么就相当于调用了两次super(),也就是调用了两次父类的无参构造,就失去了语句的意义,编译器也不会通过。
-
-
-
-## 面向对象三大特征
-
-> https://www.cnblogs.com/wujing-hubei/p/6012105.html
-
-1、**多态**是建立在继承的基础上的,是指子类类型的对象可以赋值给父类类型的引用变量,但运行时仍表现子类的行为特征。也就是说,同一种类型的对象执行同一个方法时可以表现出不同的行为特征。
-
-
-
-## 泛型
-
-> 笔者有篇泛型文章讲的很详细,包括泛型擦除这些都讲了
-
-
-
-## 父子类相关的问题
-
-https://www.nowcoder.com/questionTerminal/d31ea6176417421b9152d19e8bd1b689
-
-https://www.nowcoder.com/profile/2164604/test/35226827/365890
-
-http://www.mamicode.com/info-detail-2252140.html
-
-https://developer.aliyun.com/article/653204
-
-
-
-
-
-## new出来的对象都是在堆上分配的吗?
-
-https://imlql.cn/post/50ac3a1c.html:我在此篇文章的 `堆是分配对象的唯一选择么?`有讲解。
-
-
-
-# Java集合
-
-> 这部分我会重新写新文章
-
-## ArrayList和LinkedList时间复杂度
-
-**头部插入**:由于ArrayList头部插入需要移动后面所有元素,所以必然导致效率低。LinkedList不用移动后面元素,自然会快一些。
-**中间插入**:查看源码会注意到LinkedList的中间插入其实是先判断插入位置距离头尾哪边更接近,然后从近的一端遍历找到对应位置,而ArrayList是需要将后半部分的数据复制重排,所以两种方式其实都逃不过遍历的操作,相对效率都很低,但是从实验结果还是ArrayList更胜一筹,我猜测这与数组在内存中是连续存储有关。
-**尾部插入**:ArrayList并不需要复制重排数据,所以效率很高,这也应该是我们日常写代码时的首选操作,而LinkedList由于还需要new对象和变换指针,所以效率反而低于ArrayList。
-
-删除操作和添加操作没有什么区别。所以笼统的说LinkedList插入删除效率比ArrayList高是不对的,有时候反而还低。之所以笼统的说LinkedList插入删除效率比ArrayList高,我猜测是ArrayList复制数组需要时间,也占一定的时间复杂度。而因为数据量太少,这种效果就体现不出来。
-
-https://blog.csdn.net/hollis_chuang/article/details/102480657
-
-## fail-fast和fail-safe
-
-https://blog.csdn.net/zwwhnly/article/details/104987143
-
-https://blog.csdn.net/Kato_op/article/details/80356618
-
-https://blog.csdn.net/striner/article/details/86375684
-
-
-
-## LinkedHashMap
-
-> https://www.cnblogs.com/xiaowangbangzhu/p/10445574.html
->
-> https://blog.csdn.net/qq_28051453/article/details/71169801
-
-### 如何保证插入数据的有序性?
-
-1、在实现上,LinkedHashMap 很多方法直接继承自 HashMap(比如put remove方法就是直接用的父类的),仅为维护双向链表覆写了部分方法(get()方法是重写的)。
-
-2、重新定义了数组中保存的元素Entry(继承于HashMap.node),该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。仍然保留hash拉链法的next属性,所以既可像HashMap一样快速查找,用next获取该链表下一个Entry。也可以通过双向链接,通过after完成所有数据的有序迭代.
-
-3、在这个构造方法中,有个`accessOrder`,它不同的值有不同的意义: 默认为false,即按插入时候的顺序进行迭代。设置为true后,按访问时候的顺序进行迭代输出,即链表的最后一个元素总是最近才访问的。
-
-**访问的顺序:**如果有1 2 3这3个Entry,那么访问了1,就把1移到尾部去,即2 3 1。每次访问都把访问的那个数据移到双向队列的尾部去,那么每次要淘汰数据的时候,双向队列最头的那个数据不就是最不常访问的那个数据了吗?换句话说,双向链表最头的那个数据就是要淘汰的数据。
-
-4、链表节点的删除过程
-
-与插入操作一样,LinkedHashMap 删除操作相关的代码也是直接用父类的实现,但是LinkHashMap 重写了removeNode()方法 afterNodeRemoval()方法,该removeNode方法在hashMap 删除的基础上有调用了afterNodeRemoval 回调方法。完成删除。
-
-删除的过程并不复杂,上面这么多代码其实就做了三件事:
-
-1. 根据 hash 定位到桶位置
-2. 遍历链表或调用红黑树相关的删除方法
-3. 从 LinkedHashMap 维护的双链表中移除要删除的节点
-
-
-
-
-
-
-
-## 红黑树
-
-https://blog.csdn.net/qq_36610462/article/details/83277524
-
-## 集合继承结构
-
-
-
-
-
-# Java并发
-
-> 下面只补充遗漏的,剩余的所有在我的Java并发系列文章都有
-
-## 手写DCL,并解释为什么是这样写
-
-外层的if是为了让线程尽量少的进入synchronized块
-
-内层的if则看下面的解释
-
-```java
-public static Object getInstance() {
- if(instance == null) {//线程1,2到达这里
- synchronized(b) {//线程1到这里开始继续往下执行,线程2等待
- if(instance == null) {//线程1到这里发现instance为空,继续执行if代码块,
- //执行完成后退出同步区域,然后线程2进入同步代码块,如果在这里不再加一次判断,
- //就会造成instance再次实例化,由于增加了判断,
- //线程2到这里发现instance已被实例化于是就跳过了if代码块
- instance = new Object();
- }
- }
- }
- }
-```
-
-
-
-## 我们知道ArrayList是线程不安全,请编写一个不安全的案例并给出解决方案
-
-```java
-/* 笔记
- * 1.只有一边写一边读的时候才会报java.util.ConcurrentModificationException
- * 单独测写的时候都没有报这个异常
- * */
-public class Video20_01 {
-
- public static void main(String[] args) {
- List
- * 功能描述: 集合不安全问题的解决(写时复制)
- */
-
-public class Video20_02 {
-
- public static void main(String[] args) {
-// List