java学习整理
2023-05-25 13:43:39 14 举报
AI智能生成
java面试涉及知识点
作者其他创作
大纲/内容
mybatis
动态代理Mapper
插件
mybatis执行流程 https://mp.weixin.qq.com/s/Su6OdULXBwEtN5QF4_qNvA
71.如何避免 sql 注入?
使用preparestatement预编译
使用正则表达式将包含有 单引号('),分号(;) 和 注释符号(--)的语句给替换掉来防止SQL注入
mybatis框架尽量使用#{}的预编译写法,少用${}的写法。
使用preparestatement预编译
使用正则表达式将包含有 单引号('),分号(;) 和 注释符号(--)的语句给替换掉来防止SQL注入
mybatis框架尽量使用#{}的预编译写法,少用${}的写法。
通常一个mapper.xml文件,都会对应一个Dao接口,这个Dao接口的工作原理是什么?
Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象 MappedProxy,代理对象会拦截接口方法,根据类的全限定名+方法名,唯一定位到一个MapperStatement并调用执行器执行所代表的sql,然后将sql执行结果返回。
经典面试题
网关的作用
https://juejin.im/post/59a94a61518825243779b157
一条慢SQL 如何排查
https://mp.weixin.qq.com/s/O42Y37PYm_pJ_H6vcbQ_Lg
分布式事务
2PC
XA,事务管理器,资源管理器
3PC
2pc 3pc归根到底是选择系统可用性还是选择系统一致性(CAP理论中的抉择问题)
2pc 一致性好、可用性较低,3pc 一致性较低、可用性高
2pc 一致性好、可用性较低,3pc 一致性较低、可用性高
相比较2PC而言,3PC对于协调者(Coordinator)和参与者(Partcipant)都设置了超时时间,而2PC只有协调者才拥有超时机制
TCC
BASE
BA 基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。
S 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是 CAP 中的不一致。
E 最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。
BASE 解决了 CAP 中理论没有网络延迟,在 BASE 中用软状态和最终一致,保证了延迟后的一致性。
S 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是 CAP 中的不一致。
E 最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。
BASE 解决了 CAP 中理论没有网络延迟,在 BASE 中用软状态和最终一致,保证了延迟后的一致性。
CAP
三选二?
“三选二”是一个伪命题。不是为了 P(分区容忍性),要在 A 和 C 之间选择一个。分区很少出现,CAP 在大多数时候允许完美的 C 和 A 。但当分区存在或可感知其影响的情况下,就要预备一种策略去探知分区并显式处理其影响
MQ事务
最大努力通知方案
可靠消息最终一致性方案
springboot
自动装配
链接:https://zhuanlan.zhihu.com/p/61028032
Spring Boot 最重要的功能是:自动配置。为什么说是自动配置?Spring Boot 的开启注解是:@SpringBootApplication,其实它就是由下面三个注解组成的:@Configuration@ComponentScan@EnableAutoConfiguration上面三个注解,前面两个都是 Spring 自带的,和 Spring Boot 无关,所以说上面的回答的不是在点上。具体请看这篇文章:Spring Boot 最核心的 3 个注解详解。所以说 Spring Boot 最最核心的就是这个 @EnableAutoConfiguration 注解了,它能根据类路径下的 jar 包和配置动态加载配置和注入bean。举个例子,比如我在 lib 下放一个 druid 连接池的 jar 包,然后在 application.yml 文件配置 druid 相关的参数,Spring Boot 就能够自动配置所有我们需要的东西,如果我把 jar 包拿掉或者把参数去掉,那 Spring Boot 就不会自动配置。这样我们就能把许多功能做成公共的自动配置的启动器(starters),其实 druid 连接池就是这么做的,它提供了针对 Spring Boot 的启动器:druid-spring-boot-starter。有了这个自动配置的启动器,我们就能非常简单的使用它,先添加 jar 包依赖:<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>再添加相关参数:spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
……如果是传统的项目,我们要自己手动写一大堆的配置,而且还不灵活,有了这个启动器,我们就可以做到简单集成。具体大家可以看 druid-spring-boot-starter 是怎么实现的,也可以参考之前写的文章:Spring Boot自动配置原理、实战。所以,这才是 Spring Boot 的核心,这才是我们为什么使用 Spring Boot 的原因。如果答不到这个关键点,那真没有掌握到 Spring Boot 的核心所在。
链接:https://zhuanlan.zhihu.com/p/61028032
Spring Boot 最重要的功能是:自动配置。为什么说是自动配置?Spring Boot 的开启注解是:@SpringBootApplication,其实它就是由下面三个注解组成的:@Configuration@ComponentScan@EnableAutoConfiguration上面三个注解,前面两个都是 Spring 自带的,和 Spring Boot 无关,所以说上面的回答的不是在点上。具体请看这篇文章:Spring Boot 最核心的 3 个注解详解。所以说 Spring Boot 最最核心的就是这个 @EnableAutoConfiguration 注解了,它能根据类路径下的 jar 包和配置动态加载配置和注入bean。举个例子,比如我在 lib 下放一个 druid 连接池的 jar 包,然后在 application.yml 文件配置 druid 相关的参数,Spring Boot 就能够自动配置所有我们需要的东西,如果我把 jar 包拿掉或者把参数去掉,那 Spring Boot 就不会自动配置。这样我们就能把许多功能做成公共的自动配置的启动器(starters),其实 druid 连接池就是这么做的,它提供了针对 Spring Boot 的启动器:druid-spring-boot-starter。有了这个自动配置的启动器,我们就能非常简单的使用它,先添加 jar 包依赖:<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>再添加相关参数:spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
……如果是传统的项目,我们要自己手动写一大堆的配置,而且还不灵活,有了这个启动器,我们就可以做到简单集成。具体大家可以看 druid-spring-boot-starter 是怎么实现的,也可以参考之前写的文章:Spring Boot自动配置原理、实战。所以,这才是 Spring Boot 的核心,这才是我们为什么使用 Spring Boot 的原因。如果答不到这个关键点,那真没有掌握到 Spring Boot 的核心所在。
启动过程
https://www.cnblogs.com/theRhyme/p/how-does-springboot-start.html
https://segmentfault.com/a/1190000022119546
1.从spring.factories配置文件中加载EventPublishingRunListener对象,该对象拥有SimpleApplicationEventMulticaster属性,即在SpringBoot启动过程的不同阶段用来发射内置的生命周期事件;
准备环境变量,包括系统变量,环境变量,命令行参数,默认变量,servlet相关配置变量,随机值以及配置文件(比如application.properties)等;
控制台打印SpringBoot的bannner标志;
根据不同类型环境创建不同类型的applicationcontext容器,因为这里是servlet环境,所以创建的是AnnotationConfigServletWebServerApplicationContext容器对象;
从spring.factories配置文件中加载FailureAnalyzers对象,用来报告SpringBoot启动过程中的异常;
为刚创建的容器对象做一些初始化工作,准备一些容器属性值等,对ApplicationContext应用一些相关的后置处理和调用各个ApplicationContextInitializer的初始化方法来执行一些初始化逻辑等;
刷新容器,这一步至关重要。比如调用bean factory的后置处理器,注册BeanPostProcessor后置处理器,初始化事件广播器且广播事件,初始化剩下的单例bean和SpringBoot创建内嵌的Tomcat服务器等等重要且复杂的逻辑都在这里实现,主要步骤可见代码的注释,关于这里的逻辑会在以后的spring源码分析专题详细分析;
执行刷新容器后的后置处理逻辑,注意这里为空方法;
调用ApplicationRunner和CommandLineRunner的run方法,我们实现这两个接口可以在spring容器启动后需要的一些东西比如加载一些业务数据等;
报告启动异常,即若启动过程中抛出异常,此时用FailureAnalyzers来报告异常;
最终返回容器对象,这里调用方法没有声明对象来接收。
准备环境变量,包括系统变量,环境变量,命令行参数,默认变量,servlet相关配置变量,随机值以及配置文件(比如application.properties)等;
控制台打印SpringBoot的bannner标志;
根据不同类型环境创建不同类型的applicationcontext容器,因为这里是servlet环境,所以创建的是AnnotationConfigServletWebServerApplicationContext容器对象;
从spring.factories配置文件中加载FailureAnalyzers对象,用来报告SpringBoot启动过程中的异常;
为刚创建的容器对象做一些初始化工作,准备一些容器属性值等,对ApplicationContext应用一些相关的后置处理和调用各个ApplicationContextInitializer的初始化方法来执行一些初始化逻辑等;
刷新容器,这一步至关重要。比如调用bean factory的后置处理器,注册BeanPostProcessor后置处理器,初始化事件广播器且广播事件,初始化剩下的单例bean和SpringBoot创建内嵌的Tomcat服务器等等重要且复杂的逻辑都在这里实现,主要步骤可见代码的注释,关于这里的逻辑会在以后的spring源码分析专题详细分析;
执行刷新容器后的后置处理逻辑,注意这里为空方法;
调用ApplicationRunner和CommandLineRunner的run方法,我们实现这两个接口可以在spring容器启动后需要的一些东西比如加载一些业务数据等;
报告启动异常,即若启动过程中抛出异常,此时用FailureAnalyzers来报告异常;
最终返回容器对象,这里调用方法没有声明对象来接收。
初始化过程
dubbo
dubbo线程模型
Dubbo默认的底层网络通讯使用的是Netty,服务提供方NettyServer使用两级线程池,其中 EventLoopGroup(boss) 主要用来接受客户端的链接请求,并把接受的请求分发给 EventLoopGroup(worker) 来处理,boss和worker线程组我们称之为IO线程。
如果服务提供方的逻辑能迅速完成,并且不会发起新的IO请求,那么直接在IO线程上处理会更快,因为这减少了线程池调度。
但如果处理逻辑很慢,或者需要发起新的IO请求,比如需要查询数据库,则IO线程必须派发请求到新的线程池进行处理,否则IO线程会阻塞,将导致不能接收其它请求。
IO线程将请求交给Dispatcher,Dispatcher其实是ChannelHandler,可以通过ProtocolConfig的dispatcher指定不同的Dispatcher实现,
每个Dispatcher里面包含了一个ThreadPool线程池,同样的线程池实现可以通过ProtocolConfig的threadpool指定。这个线程池就是上文提到的新线程池。Dispatcher收到消息后,如果需要交给业务线程处理,那么便创建一个Runable对象,将Runable提交给线程池,如果是IO线程处理,那么便在当前线程中执行
如果服务提供方的逻辑能迅速完成,并且不会发起新的IO请求,那么直接在IO线程上处理会更快,因为这减少了线程池调度。
但如果处理逻辑很慢,或者需要发起新的IO请求,比如需要查询数据库,则IO线程必须派发请求到新的线程池进行处理,否则IO线程会阻塞,将导致不能接收其它请求。
IO线程将请求交给Dispatcher,Dispatcher其实是ChannelHandler,可以通过ProtocolConfig的dispatcher指定不同的Dispatcher实现,
每个Dispatcher里面包含了一个ThreadPool线程池,同样的线程池实现可以通过ProtocolConfig的threadpool指定。这个线程池就是上文提到的新线程池。Dispatcher收到消息后,如果需要交给业务线程处理,那么便创建一个Runable对象,将Runable提交给线程池,如果是IO线程处理,那么便在当前线程中执行
dubbo提供的Dispatcher配置及其功能:
- (默认)all :所有消息都派发到业务线程池,包括请求,响应,连接事件,断开事件,心跳等。
- direct: 所有消息都不派发到业务线程池,全部在 IO 线程上直接执行。
- message :只有请求响应消息派发到业务线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。
- execution :只有请求消息派发到业务线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在IO线程上执行。
- connection :在IO线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到业务线程池。
dubbo提供的线程池策略
- fixed :固定大小线程池,启动时建立线程,不关闭,一直持有。(缺省)
- cached :缓存线程池,空闲一分钟自动删除,需要时重建。
- limited :可伸缩线程池,但池中的线程数只会增长不会收缩。只增长不收缩的目的是为了避免收缩时突然来了大流量引起的性能问题。
- eager :优先创建Worker线程池。在任务数量大于corePoolSize但是小于maximumPoolSize时,优先创建Worker来处理任务。当任务数量大于maximumPoolSize时,将任务放入阻塞队列中。阻塞队列充满时抛出RejectedExecutionException。可以通过“connect.queue.capacity”设置队列的大小,默认队列长度是Integer.MAX_VALUE。
https://zhuanlan.zhihu.com/p/157354148
泛化调用
用来做网关
https://blog.csdn.net/qq_39513430/article/details/106874883?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-1.control&spm=1001.2101.3001.4242
远程调用
dubbo通信模型
select,poll,epoll区别
https://blog.csdn.net/xd_rbt_/article/details/80287959
https://blog.csdn.net/xd_rbt_/article/details/80287959
服务暴露过程
1、在容器启动过程,通过ServiceConfig解析标签,创建dubbo标签解析器来解析dubbo的标签,容器创建完成后,触发ContextRefreshEvent事件回调开始暴露服务
2、通过ProxyFactory获取invoker,invoker包含了需要执行方法的对象信息和具体的URL地址
3、再通过DubboProtocol的实现把包装后的invoker转换成exporter,然后启动服务器server,监听端口
4、通过RegistryProtocol保存URL地址和invoker的映射关系,同时注册到服务注册中心
2、通过ProxyFactory获取invoker,invoker包含了需要执行方法的对象信息和具体的URL地址
3、再通过DubboProtocol的实现把包装后的invoker转换成exporter,然后启动服务器server,监听端口
4、通过RegistryProtocol保存URL地址和invoker的映射关系,同时注册到服务注册中心
服务引用过程
1、消费者启动时会通过 ReferenceConfig#get 获取服务引用Ref,首先通过ReferenceConfig#checkAndUpdateSubConfigs 检查配置合法性,随后从缓存中获取Ref,如果缓存没有命中,则准备通过 ReferenceConfig#createProxy 创建 Ref 代理 RefProxy。
2、ReferenceConfig#createProxy 创建过程中首先会检查是否是本地服务调用,如果是则按照本地服务调用,返回 InjvmInvoker。
3、否则判断是否是单一注册中心或者单一直连URL,多URL场景相较于 单URL场景 多了一步通过 Cluster 合并多个 Invoker。不过前置过程大致类似: RegistryDirectory 订阅zk注册中心的providers、configurators、routers 节点,如果节点发生变化,则会通过回调方式通知 RegistryDirectory 进行相应的服务列表、配置、路由信息的更新。同时获取到当前的节点信息,对节点信息进行解析,对于 providers 子节点URL,开始准备 URL 转换为 Invoker。
4、URL 转Invoker 时,会通过 DubboProtocol#refer 将 URL转化为 DubboProtocol#refer ,在 转换过程中会创建Netty客户端并与服务提供者的Netty服务端建立连接。此时Invoker 中包含对应提供者的Netty客户端连接。
5、RegistryDirectory 获取到URL 转换的Invoker 后, 会更新本地 Invokers 列表以供后续服务调用。随机 RegistryDirectory 被包装成 MockClusterInvoker -》 FailoverClusterInvoker -》RegistryDirectory 形式。如果是多URL场景,则在这里通过 Cluster 合并多个 Invoker。
6、随后通过 proxyFactory.getProxy(invoker) 将 MockClusterInvoker 转化为 Ref 代理对象 供消费者调用。
7、当消费者发起调用时会调用 Ref 代理对象,而 Ref 代理对象会将请求委托给内部 Invoker, 即 MockClusterInvoker 。
8、MockClusterInvoker 经过一定调用后最终交由 DubboInvoker#invoke 来发起远程调用。
2、ReferenceConfig#createProxy 创建过程中首先会检查是否是本地服务调用,如果是则按照本地服务调用,返回 InjvmInvoker。
3、否则判断是否是单一注册中心或者单一直连URL,多URL场景相较于 单URL场景 多了一步通过 Cluster 合并多个 Invoker。不过前置过程大致类似: RegistryDirectory 订阅zk注册中心的providers、configurators、routers 节点,如果节点发生变化,则会通过回调方式通知 RegistryDirectory 进行相应的服务列表、配置、路由信息的更新。同时获取到当前的节点信息,对节点信息进行解析,对于 providers 子节点URL,开始准备 URL 转换为 Invoker。
4、URL 转Invoker 时,会通过 DubboProtocol#refer 将 URL转化为 DubboProtocol#refer ,在 转换过程中会创建Netty客户端并与服务提供者的Netty服务端建立连接。此时Invoker 中包含对应提供者的Netty客户端连接。
5、RegistryDirectory 获取到URL 转换的Invoker 后, 会更新本地 Invokers 列表以供后续服务调用。随机 RegistryDirectory 被包装成 MockClusterInvoker -》 FailoverClusterInvoker -》RegistryDirectory 形式。如果是多URL场景,则在这里通过 Cluster 合并多个 Invoker。
6、随后通过 proxyFactory.getProxy(invoker) 将 MockClusterInvoker 转化为 Ref 代理对象 供消费者调用。
7、当消费者发起调用时会调用 Ref 代理对象,而 Ref 代理对象会将请求委托给内部 Invoker, 即 MockClusterInvoker 。
8、MockClusterInvoker 经过一定调用后最终交由 DubboInvoker#invoke 来发起远程调用。
dubbo常见面试题
https://blog.51cto.com/u_13294304/2958641
dubbo服务invoker
springcloud
Netfix 全家桶
注册中心
eureka
通过注册中心,调用方(Consumer)获得服务方(Provider)的地址,从而能够调用。
当然,实际情况下,会分成两种注册中心的发现模式:
客户端发现模式
服务端发现模式
在 Spring Cloud 中,我们使用前者,即客户端发现模式。
客户端发现模式
服务端发现模式
在 Spring Cloud 中,我们使用前者,即客户端发现模式。
在应用启动时,Eureka 客户端向服务端注册自己的服务信息,同时将服务端的服务信息缓存到本地。客户端会和服务端周期性的进行心跳交互,以更新服务租约和服务信息。
负载均衡
ribbon
目前负载均衡有两种模式:
客户端模式
服务端模式
在 Spring Cloud 中,我们使用前者,即客户端模式。 服务器端负载均衡:例如Nginx,通过Nginx进行负载均衡,先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。
客户端负载均衡:例如spring cloud中的ribbon,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配。
客户端模式
服务端模式
在 Spring Cloud 中,我们使用前者,即客户端模式。 服务器端负载均衡:例如Nginx,通过Nginx进行负载均衡,先发送请求,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配。
客户端负载均衡:例如spring cloud中的ribbon,客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配。
首先,Ribbon 会从 Eureka Client 里获取到对应的服务列表。
然后,Ribbon 使用负载均衡算法获得使用的服务。
最后,Ribbon 调用对应的服务。
然后,Ribbon 使用负载均衡算法获得使用的服务。
最后,Ribbon 调用对应的服务。
feign
Feign的一个关键机制就是使用了动态代理。咱们一起来看看下面的图,结合图来分析:
首先,如果你对某个接口定义了 @FeignClient 注解,Feign 就会针对这个接口创建一个动态代理。
接着你要是调用那个接口,本质就是会调用 Feign 创建的动态代理,这是核心中的核心。
Feig n的动态代理会根据你在接口上的 @RequestMapping 等注解,来动态构造出你要请求的服务的地址。
最后针对这个地址,发起请求、解析响应。
首先,如果你对某个接口定义了 @FeignClient 注解,Feign 就会针对这个接口创建一个动态代理。
接着你要是调用那个接口,本质就是会调用 Feign 创建的动态代理,这是核心中的核心。
Feig n的动态代理会根据你在接口上的 @RequestMapping 等注解,来动态构造出你要请求的服务的地址。
最后针对这个地址,发起请求、解析响应。
feign 内部用ribbon做负载均衡
首先,用户调用 Feign 创建的动态代理。
然后,Feign 调用 Ribbon 发起调用流程。
首先,Ribbon 会从 Eureka Client 里获取到对应的服务列表。
然后,Ribbon 使用负载均衡算法获得使用的服务。Ribbon 调用 Feign ,而 Feign 调用 HTTP 库最终调用使用的服务。
然后,Feign 调用 Ribbon 发起调用流程。
首先,Ribbon 会从 Eureka Client 里获取到对应的服务列表。
然后,Ribbon 使用负载均衡算法获得使用的服务。Ribbon 调用 Feign ,而 Feign 调用 HTTP 库最终调用使用的服务。
rpc=socket + 动态代理 说到 rpc 与http接口,不要太复杂了。rpc 协议更简单内容更小,那么来说效率是要高一点
服务保障
Hystrix
作用:断路器,保护系统,控制故障范围。
简介:Hystrix 是一个延迟和容错库,旨在隔离远程系统,服务和第三方库的访问点,当出现故障是不可避免的故障时,停止级联故障并在复杂的分布式系统中实现弹性。
简介:Hystrix 是一个延迟和容错库,旨在隔离远程系统,服务和第三方库的访问点,当出现故障是不可避免的故障时,停止级联故障并在复杂的分布式系统中实现弹性。
Hystrix 有两种隔离策略:
线程池隔离
信号量隔离
实际场景下,使用线程池隔离居多,因为支持超时功能。
信号量隔离
实际场景下,使用线程池隔离居多,因为支持超时功能。
Hystrix缓存策略
Hystrix 提供缓存功能,作用是:
减少重复的请求数。
在同一个用户请求的上下文中,相同依赖服务的返回数据始终保持一致。
减少重复的请求数。
在同一个用户请求的上下文中,相同依赖服务的返回数据始终保持一致。
Hystrix 断路器
Hystrix 服务降级
网关服务
Zuul
对比NGINX
这是两个概念,nginx是做负载均衡请求转发,更多被用作负载均衡器使用的;zuul是请求转发,一般用来做网关的,zuul配合eureka来使用,zuul功能也很强大,nginx要做这些功能也是可以,但是需要各种脚本语言来支持,比如lua脚本等,但是zuul来说的话开发成本就低很多,懂spring就够了。
功能
什么是服务网关
服务网关 = 路由转发 + 过滤器
1、路由转发:接收一切外界请求,转发到后端的微服务上去;
2、过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成(其实路由转发也是通过过滤器实现的)。
服务网关 = 路由转发 + 过滤器
1、路由转发:接收一切外界请求,转发到后端的微服务上去;
2、过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成(其实路由转发也是通过过滤器实现的)。
https://blog.csdn.net/singgel/article/details/84250577
作用
作用:API 网关,路由,负载均衡等多种作用。
简介:类似 Nginx ,反向代理的功能,不过 Netflix 自己增加了一些配合其他组件的特性。
在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个 API网关根据请求的 url ,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。
简介:类似 Nginx ,反向代理的功能,不过 Netflix 自己增加了一些配合其他组件的特性。
在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个 API网关根据请求的 url ,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。
配置中心
Spring Cloud Config
链路追踪
Spring Cloud Sleuth
微服务
优点
每一个服务足够内聚
微服务是松耦合的,是有功能意义的服务
可以用不同的语言开发,面向接口编程
开发效率提高,一个服务只做一件事
缺点
服务间通信成本
分布式系统的负责性
数据一致性
MQ
RabbitMQ
消息堆积
消息暴增
有可能将MQSERVER的内存打爆,所以要让provider暂时不发送消息;并且考虑增加消费者,但是会将下游的压力增大,要慎重
消费者消费能力不足
修复消费者,考虑增添消费者
kafka
redis
string
动态字符串
动态字符串,是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。
list
ziplist 或quicklist
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成quicklist。因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。所以Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。
hash
类似hashMaP
扩容
。所以Redis采用了渐进式rehash的方案。它会同时保留两个新旧hash结构,在后续的定时任务以及hash结构的读写指令中将旧结构的元素逐渐迁移到新的结构中。这样就可以避免因扩容导致的线程卡顿现象。(一次完整rehash的过程就会耗时很长。这对于单线程的Redis里来说有点压力山大)
缩容
缩容的原理和扩容是一致的,只不过新的数组大小要比旧数组小一倍
set
Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。
zset
链表+跳表
持久化
RDB
原理
全量模式的持久化
保存的是数据的快照
Redis的全量写入包含2种方式:save 和 bgsave
save 可以由客户端显示触发的,也可以在redis shutdown 时触发。Save本身是单线程串行化的方式执行的,因此当数据量大时,有可能会发生Redis Server的长时间卡顿。
bgsave 也可以由客户端显示触发、可以通过配置定时任务触发也可以在master-slave的分布式结构下由slave节点触发。bgsave命令在执行的时候,会fork一个子进程。子进程提交完成之后,会立即给客户端返回响应,备份操作在后台异步执行,在此期间不会影响Redis的正常响应
bgsave相对于Save来说,其优势是异步执行,不影响后续的命令执行。但是Fork子进程时,涉及父进程的内存复制,此时会增加服务器的内存开销。当内存开销高到使用虚拟内存时,bgsave的Fork子进程会阻塞运行,可能会造成秒级的不可用。
优点
崩溃回复快
缺点
存在数据丢失的情况
AOF
原理
增量模式的持久化(AOF)
而AOF(append-only-file)记录的则是每条写命令的记录
always:每一次的刷新缓冲区,都会同步触发同步操作。因为每次的写操作都会触发同步,所以该策略会降低Redis的吞吐量,但是这种模式会拥有最高的容错能力。
every second:每秒异步的触发同步操作,这种是Redis的默认配置。
no:由操作系统决定何时同步,这种方式Redis无法决定何时落地,因此不可控。
every second:每秒异步的触发同步操作,这种是Redis的默认配置。
no:由操作系统决定何时同步,这种方式Redis无法决定何时落地,因此不可控。
优点
更可靠,不丢数据
AOF 日志是一个追加文件,所以不需要定位;即使由于某种原因文件末尾是一个写到一半的命令(磁盘满或者其他原因),redis-check-aof 工具也可以很轻易的修复。
缺点
aof做崩溃回复出现过bug
做崩溃回复没有rdb快
虚拟内存
不推荐使用
适用场景为value占用空间较大,导致内存不足时,那么VM无疑是一个很好的选择。
将非热点的key对应的value放到磁盘中;当客户端请求获取这些value时,他们被将从swap 文件中读回,并载入到内存中。
与memch区别
拥有更多的数据结构和并支持更丰富的数据操作
memcached没有原生的集群模式,redis目前是原生支持cluster模式的
线程模型
文件事件处理器(redis基于reactor模式)
point
文件事件处理器是单线程模式运行的,但是通过IO多路复用机制监听多个socket,可以实现高性能的网络通信模型
IO多路复用程序可以同时监听AE_REABLE和AE_WRITABLE两种事件
如果是客户端要连接redis,那么会为socket关联连接应答处理器
如果是客户端要写数据到redis,那么会为socket关联命令请求处理器
如果是客户端要从redis读数据,那么会为socket关联命令回复处理器
如果是客户端要写数据到redis,那么会为socket关联命令请求处理器
如果是客户端要从redis读数据,那么会为socket关联命令回复处理器
文件事件处理器的结构
多个socket
IO多路复用程序
文件事件分派器
事件处理器(命令请求处理器、命令回复处理器、连接应答处理器,等等)
过期策略
定期删除+惰性删除
定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。
采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。
于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。
采用定期删除+惰性删除就没其他问题了么?
不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制。
内存淘汰机制
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最近访问频率最少的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
- allkeys-lfu:从数据集(server.db[i].dict)中挑选最近访问频率最少的数据淘汰
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-enviction(驱逐):禁止驱逐数据,内存超过了就报错
redis高可用
哨兵
cluster
TCP UDP 传输层
81.简述 tcp 和 udp的区别?
1:TCP基于连接,UDP基于无连接。(TCP三次握手,UDP是尽最大努力交付型)
2:TCP对系统资源要求高,UDP少。
3:TCP可靠(TCP会对数据包进行追踪,失败重发)
4:TCP保证序列性(TCP对数据包添加序列号,确保有序到达)
tcp用于HTTP,HTTPS,FTP UDP 用于DNS,IP电话
1:TCP基于连接,UDP基于无连接。(TCP三次握手,UDP是尽最大努力交付型)
2:TCP对系统资源要求高,UDP少。
3:TCP可靠(TCP会对数据包进行追踪,失败重发)
4:TCP保证序列性(TCP对数据包添加序列号,确保有序到达)
tcp用于HTTP,HTTPS,FTP UDP 用于DNS,IP电话
为什么要三次握手
“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误
问题的本质是,信道是不可靠的,但是我们要建立可靠的连接发送可靠的数据,也就是数据传输是需要可靠的。在这个时候三次握手是一个理论上的最小值,并不是说是tcp协议要求的,而是为了满足在不可靠的信道上传输可靠的数据所要求的
为什么要四次挥手
而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了,但是还能接收数据,而自己也未必全部数据都发送给对方了。
所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方是否现在关闭发送数据通道,需要上层应用来决定,因此,己方ACK和FIN一般都会分开发送。
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方是否现在关闭发送数据通道,需要上层应用来决定,因此,己方ACK和FIN一般都会分开发送。
三次握手四次挥手动画版
https://juejin.im/post/5b29d2c4e51d4558b80b1d8c
TCP粘包问题
https://blog.csdn.net/tiandijun/article/details/41961785
滑动窗口
TCP滑动窗口技术通过动态改变窗口大小来调节两台主机间数据传输。
https://yq.aliyun.com/articles/63923?spm=5176.10695662.1996646101.searchclickresult.48d0784fLVhB3r
TCP的特点之一是提供体积可变的滑动窗口机制,支持端到端的流量控制。TCP的窗口以字节为单位进行调整,以适应接收方的处理能力。处理过程如下:
(1)TCP连接阶段,双方协商窗口尺寸,同时接收方预留数据缓存区;
(2)发送方根据协商的结果,发送符合窗口尺寸的数据字节流,并等待对方的确认;
(3)发送方根据确认信息,改变窗口的尺寸,增加或者减少发送未得到确认的字节流中的字节数。调整过程包括:如果出现发送拥塞,发送窗口缩小为原来的一半,同时将超时重传的时间间隔扩大一倍。
TCP的窗口机制和确认保证了数据传输的可靠性和流量控制。
(1)TCP连接阶段,双方协商窗口尺寸,同时接收方预留数据缓存区;
(2)发送方根据协商的结果,发送符合窗口尺寸的数据字节流,并等待对方的确认;
(3)发送方根据确认信息,改变窗口的尺寸,增加或者减少发送未得到确认的字节流中的字节数。调整过程包括:如果出现发送拥塞,发送窗口缩小为原来的一半,同时将超时重传的时间间隔扩大一倍。
TCP的窗口机制和确认保证了数据传输的可靠性和流量控制。
拥塞控制
慢开始
TCP从一个MSS(最大报文长度)报文开始,每次收到接收端的一个ACK后,就在原来MSS的基础上增加一个MSS(拥塞窗口cwnd变为2),这样当返回一个ACK的时候变为2个MSS长度的报文了,这两个报文发送出去后如果再收到一个ACK,每个MSS报文又变为2个MSS的报文(拥塞窗口cwnd变为4)。所以经过两次确认后,发送端可发送的报文为4个。以此类推,可以发现,可发送报文的长度是按照指数增长的。
拥塞避免
拥塞避免是一种按照加法增大的算法,如果一直采用慢开始算法,那么很快就会到达阈值,为了在网络发生拥塞之前避免它,可以采用加法增大的算法,即每次cwnd只在原来的基础增加1,直到网络发生拥塞为止
拥塞检测
拥塞避免是一种按照加法增大的算法,如果一直采用慢开始算法,那么很快就会到达阈值,为了在网络发生拥塞之前避免它,可以采用加法增大的算法,即每次cwnd只在原来的基础增加1,直到网络发生拥塞为止
总结
由于刚开始不清楚网络的拥塞情况,所以会首先采用慢开始算法,开始阶段,窗口大小由1指数增大,直到窗口大小到达门限值。窗口大小到达门限值后,就开始执行拥塞避免算法,之后窗口值按照线性规律增大,直到出现超时或者到达最大的窗口大小值。这个时候,会开始执行拥塞拥塞算法,也就是把门限值变为窗口大小的一半,之后继续执行拥塞避免算法,窗口大小按照线性规律增大。
mysql
索引
顺序IO 和 随机IO
由于索引idx_name_birthday_phone_number对应的B+树中的记录首先会按照name列的值进行排序,所以值在Asa~Barlow之间的记录在磁盘中的存储是相连的,集中分布在一个或几个数据页中,我们可以很快的把这些连着的记录从磁盘中读出来,这种读取方式我们也可以称为顺序I/O。根据第1步中获取到的记录的id字段的值可能并不相连,而在聚簇索引中记录是根据id(也就是主键)的顺序排列的,所以根据这些并不连续的id值到聚簇索引中访问完整的用户记录可能分布在不同的数据页中,这样读取完整的用户记录可能要访问更多的数据页,这种读取方式我们也可以称为随机I/O。一般情况下,顺序I/O比随机I/O的性能高很多,所以步骤1的执行可能很快,而步骤2就慢一些。所以这个使用索引idx_name_birthday_phone_number的查询有这么两个特点:
会使用到两个B+树索引,一个二级索引,一个聚簇索引。
访问二级索引使用顺序I/O,访问聚簇索引使用随机I/O。
会使用到两个B+树索引,一个二级索引,一个聚簇索引。
访问二级索引使用顺序I/O,访问聚簇索引使用随机I/O。
作用
加快索引速度
排序
唯一约束
顺序IO
集中分布在一个或几个数据页中,我们可以很快的把这些连着的记录从磁盘中读出来,这种读取方式我们也可以称为顺序I/O
所以根据这些并不连续的id值到聚簇索引中访问完整的用户记录可能分布在不同的数据页中,这样读取完整的用户记录可能要访问更多的数据页,这种读取方式我们也可以称为随机I/O
SQL执行过程
连接器: 身份认证和权限相关(登录 MySQL 的时候)。
•查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
•分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
•优化器: 按照 MySQL 认为最优的方案去执行。
•执行器: 执行语句,然后从存储引擎返回数据。
•查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
•分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
•优化器: 按照 MySQL 认为最优的方案去执行。
•执行器: 执行语句,然后从存储引擎返回数据。
SQL 等执行过程分为两类,一类对于查询等过程如下:权限校验---》查询缓存---》分析器---》优化器---》权限校验---》执行器---》引擎
•对于更新等语句执行流程如下:分析器----》权限校验----》执行器---》引擎---redo log prepare---》binlog---》redo log commit
•对于更新等语句执行流程如下:分析器----》权限校验----》执行器---》引擎---redo log prepare---》binlog---》redo log commit
MVCC 多版本并发控制
https://www.jianshu.com/p/a3d49f7507ff
聚簇索引的记录除了会保存完整的用户数据以外,而且还会自动添加名为trx_id、roll_pointer的隐藏列,如果用户没有在表中定义主键以及UNIQUE键,还会自动添加一个名为row_id的隐藏列
其中的trx_id列其实还蛮好理解的,就是某个对这个聚簇索引记录做改动的语句所在的事务对应的事务id而已(此处的改动可以是INSERT、DELETE、UPDATE操作)
是时候揭开roll_pointer的真实面纱了,这个占用7个字节的字段其实一点都不神秘,本质上就是一个指向记录对应的undo日志的一个指针。比方说我们上边向undo_demo表里插入了2条记录,每条记录都有与其对应的一条undo日志
ReadView
对于使用READ UNCOMMITTED隔离级别的事务来说,直接读取记录的最新版本就好了,对于使用SERIALIZABLE隔离级别的事务来说,使用加锁的方式来访问记录。对于使用READ COMMITTED和REPEATABLE READ隔离级别的事务来说,就需要用到我们上边所说的版本链了,核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。所以设计InnoDB的大叔提出了一个ReadView的概念,这个ReadView中主要包含当前系统中还有哪些活跃的读写事务,把它们的事务id放到一个列表中,我们把这个列表命名为为m_ids。这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
如果被访问版本的trx_id属性值小于m_ids列表中最小的事务id,表明生成该版本的事务在生成ReadView前已经提交,所以该版本可以被当前事务访问。
如果被访问版本的trx_id属性值大于m_ids列表中最大的事务id,表明生成该版本的事务在生成ReadView后才生成,所以该版本不可以被当前事务访问。
如果被访问版本的trx_id属性值在m_ids列表中最大的事务id和最小事务id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
对于使用READ UNCOMMITTED隔离级别的事务来说,直接读取记录的最新版本就好了,对于使用SERIALIZABLE隔离级别的事务来说,使用加锁的方式来访问记录。对于使用READ COMMITTED和REPEATABLE READ隔离级别的事务来说,就需要用到我们上边所说的版本链了,核心问题就是:需要判断一下版本链中的哪个版本是当前事务可见的。所以设计InnoDB的大叔提出了一个ReadView的概念,这个ReadView中主要包含当前系统中还有哪些活跃的读写事务,把它们的事务id放到一个列表中,我们把这个列表命名为为m_ids。这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
如果被访问版本的trx_id属性值小于m_ids列表中最小的事务id,表明生成该版本的事务在生成ReadView前已经提交,所以该版本可以被当前事务访问。
如果被访问版本的trx_id属性值大于m_ids列表中最大的事务id,表明生成该版本的事务在生成ReadView后才生成,所以该版本不可以被当前事务访问。
如果被访问版本的trx_id属性值在m_ids列表中最大的事务id和最小事务id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
在MySQL中,READ COMMITTED和REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同,我们来看一下。
READ COMMITTED --- 每次读取数据前都生成一个ReadView
REPEATABLE READ ---在第一次读取数据时生成一个ReadView
undo redo
事务的 ACID 是通过 InnoDB 日志和锁来保证。事务的隔离性是通过数据库锁的机制实现的(还有MVCC),持久性通过 Redo Log(重做日志)来实现,原子性和一致性通过 Undo Log 来实现。Undo Log 的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为 Undo Log)。然后进行数据的修改。如果出现了错误或者用户执行了 Rollback 语句,系统可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。 和 Undo Log 相反,Redo Log 记录的是新数据的备份。在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到最新的状态。
redo
redo日志会把事务在执行过程中对数据库所做的所有修改都记录下来,在之后系统奔溃重启后可以把事务所做的任何修改都恢复出来。
redo日志写入log buffer
向log buffer中写入redo日志的过程是顺序的,也就是先往前边的block中写,当该block的空闲空间用完之后再往下一个block中写。当我们想往log buffer中写入redo日志时,第一个遇到的问题就是应该写在哪个block的哪个偏移量处,所以设计InnoDB的大叔特意提供了一个称之为buf_free的全局变量,该变量指明后续写入的redo日志应该写入到log buffer中的哪个位置
向log buffer中写入redo日志的过程是顺序的,也就是先往前边的block中写,当该block的空闲空间用完之后再往下一个block中写。当我们想往log buffer中写入redo日志时,第一个遇到的问题就是应该写在哪个block的哪个偏移量处,所以设计InnoDB的大叔特意提供了一个称之为buf_free的全局变量,该变量指明后续写入的redo日志应该写入到log buffer中的哪个位置
redo日志刷盘时机
log buffer空间不足时
事务提交时
后台线程不停的刷刷刷
后台有一个线程,大约每秒都会刷新一次log buffer中的redo日志到磁盘。
后台有一个线程,大约每秒都会刷新一次log buffer中的redo日志到磁盘。
正常关闭服务器时
ACID
原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
一致性: 执行事务前后,数据保持一致;
隔离性: 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的;
持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
一致性: 执行事务前后,数据保持一致;
隔离性: 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的;
持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
数据库调优策略
数据库调优策略
表结构优化
1、字段避免null值出现,null值很难查询优化且占用额外的索引空间,推荐默认数字0代替null。
原则:尽可能使用 not null,MySQL中每条记录都需要额外的存储空间,表示每个字段是否为null。因此通常使用特殊的数据进行占位
单表不要有太多字段,建议在20以内
用整形存储IP,用inet_aton(),数字转IP函数 inet_ntoa()
尽量使用TIMESTAMP而非DATETIME
b)DATETIME使用8字节的存储空间,TIMESTAMP的存储空间为4字节。因此,TIMESTAMP比DATETIME的空间利用率更高。
这个区别解释了为啥timestamp类型用的多
c)两者的存储方式不一样 ,对于TIMESTAMP,它把客户端插入的时间从当前时区转化为UTC(世界标准时间)进行存储。查询时,将其又转化为客户端当前时区进行返回。而对于DATETIME,不做任何改变,基本上是原样输入和输出。
这个区别解释了为啥timestamp类型用的多
c)两者的存储方式不一样 ,对于TIMESTAMP,它把客户端插入的时间从当前时区转化为UTC(世界标准时间)进行存储。查询时,将其又转化为客户端当前时区进行返回。而对于DATETIME,不做任何改变,基本上是原样输入和输出。
分库分表
sql语句优化
用limit对查询结果的记录进行限定
避免select *,将需要查找的字段列出来
使用连接(join)来代替子查询
避免select *,将需要查找的字段列出来
使用连接(join)来代替子查询
避免%xxx式查询
索引优化
3、值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段
4、字符字段只建前缀索引
5、字符字段最好不要做主键
4、字符字段只建前缀索引
5、字符字段最好不要做主键
1、索引并不是越多越好
分离活跃数据
读写分离
mysql调优工具
.mycheckpoint工具:配置之后,可以设置为定时采集,并保存到数据库中,以web形式绘图展现
什么是分区
分区后,表面上还是一张表,但数据散列到多个位置了。app读写的时候操作的还是大表名字,db自动去组织分区的数据。
啥情况不使用索引
以%开头的LIKE查询不会使用B-TREE索引
数据类型出现隐式转换时不会使用索引
不符合最左前缀原则
如果MySql认为使用索引会比全表查询更慢,则不会使用索引
explain
type
const
这个我们前边唠叨过,就是当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const
这个我们前边唠叨过,就是当我们根据主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const
system
当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问方法就是system。
当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,比如MyISAM、Memory,那么对该表的访问方法就是system。
eq_ref
在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是eq_ref
在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较),则对该被驱动表的访问方法就是eq_ref
ref
当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref
当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是ref
ref_or_null
当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值时,那么对该表的访问方法就可能是ref_or_null
当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值时,那么对该表的访问方法就可能是ref_or_null
index_merge
一般情况下对于某个表的查询只能使用到一个索引,但我们唠叨单表访问方法时特意强调了在某些场景下可以使用Intersection、Union、Sort-Union这三种索引合并的方式来执行查询,从执行计划的type列的值是index_merge就可以看出,MySQL打算使用索引合并的方式来执行对s1表的查询。
一般情况下对于某个表的查询只能使用到一个索引,但我们唠叨单表访问方法时特意强调了在某些场景下可以使用Intersection、Union、Sort-Union这三种索引合并的方式来执行查询,从执行计划的type列的值是index_merge就可以看出,MySQL打算使用索引合并的方式来执行对s1表的查询。
range
如果使用索引获取某些范围区间的记录,那么就可能使用到range访问方法,比如下边的这个查询:
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c');
如果使用索引获取某些范围区间的记录,那么就可能使用到range访问方法,比如下边的这个查询:
mysql> EXPLAIN SELECT * FROM s1 WHERE key1 IN ('a', 'b', 'c');
index
当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是index 小贴士: 再一次强调,对于使用InnoDB存储引擎的表来说,二级索引的记录只包含索引列和主键列的值,而聚簇索引中包含用户定义的全部列以及一些隐藏列,所以扫描二级索引的代价比直接全表扫描,也就是扫描聚簇索引的代价更低一些。
当我们可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方法就是index 小贴士: 再一次强调,对于使用InnoDB存储引擎的表来说,二级索引的记录只包含索引列和主键列的值,而聚簇索引中包含用户定义的全部列以及一些隐藏列,所以扫描二级索引的代价比直接全表扫描,也就是扫描聚簇索引的代价更低一些。
ALL
最熟悉的全表扫描
最熟悉的全表扫描
possible_keys和key
在EXPLAIN语句输出的执行计划中,possible_keys列表示在某个查询语句中,对某个表执行单表查询时可能用到的索引有哪些,key列表示实际用到的索引有哪些
key_len
key_len列表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度,它是由这三个部分构成的:
对于使用固定长度类型的索引列来说,它实际占用的存储空间的最大长度就是该固定值,对于指定字符集的变长类型的索引列来说,比如某个索引列的类型是VARCHAR(100),使用的字符集是utf8,那么该列实际占用的最大存储空间就是100 × 3 = 300个字节。
如果该索引列可以存储NULL值,则key_len比不可以存储NULL值时多1个字节。
对于变长字段来说,都会有2个字节的空间来存储该变长列的实际长度。
对于使用固定长度类型的索引列来说,它实际占用的存储空间的最大长度就是该固定值,对于指定字符集的变长类型的索引列来说,比如某个索引列的类型是VARCHAR(100),使用的字符集是utf8,那么该列实际占用的最大存储空间就是100 × 3 = 300个字节。
如果该索引列可以存储NULL值,则key_len比不可以存储NULL值时多1个字节。
对于变长字段来说,都会有2个字节的空间来存储该变长列的实际长度。
乐观锁和悲观锁的区别?
分库分表
分库
将每个服务相关的表拆出来单独建立一个数据库,这其实就是“分库”了
分表
水平拆分
照 id 来拆分,还可以按照时间维度拆分,比如订单表,可以按照每日、每月等进行拆分
垂直拆分
基于表或者字段划分,表结构不同
将多个字段拆分到不同表
将多个字段拆分到不同表
zookeeper
持久化
https://blog.csdn.net/varyall/article/details/79564418 直接看总结
ACL
https://www.cnblogs.com/xsht/p/5258907.html
https://blog.csdn.net/xiaoliuliu2050/article/details/82497051
项目
扣减库存关键技术
https://zhuanlan.zhihu.com/p/473990908
高可用方案
mysql
主从或主主半同步复制
一些优化,比如说写一份binlog服务器日志
MHA+多节点集群
MHA Manager(管理节点)和MHA Node(数据节点)
MHA Node运行在每台MySQL服务器上,MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将最新数据的slave提升为新的master,然后将所有其他的slave重新指向新的master
要搭建MHA,要求一个复制集群中必须最少有三台数据库服务器,一主二从,即一台充当master,一台充当备用master,另外一台充当从库
MHA 故障切换过程
(1)从宕机崩溃的master保存二进制日志事件(binlog events);
(2)识别含有最新更新的slave;
(3)应用差异的中继日志(relay log)到其他的slave;
(4)应用从master保存的二进制日志事件(binlog events);
(5)提升一个slave为新的master;
(6)使其他的slave连接新的master进行复制;
(2)识别含有最新更新的slave;
(3)应用差异的中继日志(relay log)到其他的slave;
(4)应用从master保存的二进制日志事件(binlog events);
(5)提升一个slave为新的master;
(6)使其他的slave连接新的master进行复制;
半同步复制
半同步复制核心点就是 master写入binlog之后,需要等待slave写入replayLog 并且发出ACK,master才会提交
redis
哨兵
redis cluster 一致性hash
mq
镜像集群
消息堆积
消息暴增
有可能将MQSERVER的内存打爆,所以要让provider暂时不发送消息;并且考虑增加消费者,但是会将下游的压力增大,要慎重
消费者消费能力不足
修复消费者,考虑增添消费者
设计模式
单例
枚举
DATASOURCE 被声明为 static 的,根据类加载过程,可以知道虚拟机会保证一个类的初始化 方法在多线程环境中被正确的加锁、同步。所以,枚举实现是在实例化时是线程安全
枚举类进行反射时连类装载的入口方法<init>()都没有了
模板方法
代理模式
装饰器模式
原型模式
java基础
异常
ThrowAble
error
Exception
ConcurrentModificationException(速度失败)
ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界异常)
NullPointerException(空指针异常)
IndexOutOfBoundsException(数组越界异常)
NullPointerException(空指针异常)
)java.lang.NumberFormatException 字符串转换为数字异常
网络
IO
NIO
https://www.jianshu.com/p/486b0965c296
https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/Java%20IO%E4%B8%8ENIO.md#%E5%85%AB-%E9%AB%98%E5%B9%B6%E5%8F%91java%EF%BC%888%EF%BC%89%EF%BC%9Anio%E5%92%8Caio
AIO
netty
best
阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,当数据没有准备的时候阻塞:往往需要等待缞冲区中的数据准备好过后才处理其他的事情,否則一直等待在那里。
非阻塞:当我们的进程访问我们的数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回
非阻塞:当我们的进程访问我们的数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回
同步和异步都是基于应用程序私操作系统处理IO事件所采用的方式,比如同步:是应用程序要直接参与IO读写的操作。异步:所有的IO读写交给搡作系统去处理,应用程序只需要等待通知。
同步方式在处理IO事件的时候,必须阻塞在某个方法上靣等待我们的IO事件完成(阻塞IO事件或者通过轮询IO事件的方式).对于异步来说,所有的IO读写都交给了搡作系统。这个时候,我们可以去做其他的事情,并不拓要去完成真正的IO搡作,当搡作完成IO后.会给我们的应用程序一个通知
同步:阻塞到IO事件,阻塞到read成则write。这个时候我们就完全不能做自己的事情,让读写方法加入到线程里面,然后阻塞线程来实现,对线程的性能开销比较大,
同步方式在处理IO事件的时候,必须阻塞在某个方法上靣等待我们的IO事件完成(阻塞IO事件或者通过轮询IO事件的方式).对于异步来说,所有的IO读写都交给了搡作系统。这个时候,我们可以去做其他的事情,并不拓要去完成真正的IO搡作,当搡作完成IO后.会给我们的应用程序一个通知
同步:阻塞到IO事件,阻塞到read成则write。这个时候我们就完全不能做自己的事情,让读写方法加入到线程里面,然后阻塞线程来实现,对线程的性能开销比较大,
NIO 的非阻塞是指 那个线程如果发现channel数据还没准备好,就去轮训别的数据
https://blog.csdn.net/charjay_lin/article/details/81810922
https://uule.iteye.com/blog/2429099
线程模型
基础知识
序列化
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题
并发
synchronized
lock
线程池 Executor
ThreadPoolExecutor 7个参数
核心线程数
最大线程数
线程存活时间
线程存活时间单位
拒绝策略
阻塞队列
threadFactory 线程工厂
ArrayBlockingQueue以及LinkedBlockingQueue
https://www.cnblogs.com/moonandstar08/p/4893337.html
各种线程池适用场景
https://www.jianshu.com/p/6eb9217a2af9
线程池中的线程如何回收?
https://mp.weixin.qq.com/s/-tGmJD7YHGwYACfBg3nDoQ
ThreadLocal
ThreadLocal的实现原理是,在每个线程中维护一个Map,键是ThreadLocal类型,值是Object类型。当想获取ThreadLocal的值时,就从当前线程中拿出Map,然后在把ThreadLocal本身作为键从Map中拿出值返回。
内存泄漏问题。可以看到ThreadLocalMap中的Entry是继承WeakReference的,其中ThreadLocal是以弱引用形式存在Entry中,如果ThreadLocal在外部没有被强引用,那么垃圾回收的时候就会被回收掉,又因为Entry中的value是强引用,就会出现内存泄漏。虽然ThreadLocal源码中的会对这种情况进行了处理,但还是建议不需要用TreadLocal的时候,手动调remove方法
线程
终止线程
协作终止(使用volatile变量)
无法终止调用了wait、sleep、join的线程
暴力终止 stop函数
使用中断(无法中断阻塞在OS层面的线程)
interrupt()
处理不可中断的阻塞
我们上边说的那些阻塞方法都是有InterruptedException的异常说明的,但是并不是所有的阻塞操作都会响应中断,比如因为获取不到锁而发生的阻塞或者因为数据源或者接收端没准备好而造成很多I/O操作发生阻塞。
对于因为获取不到锁而发生阻塞的情况,可以使用Lock接口提供的lockInterruptibly方法,该获取锁的方法是支持响应中断的。
我们上边说的那些阻塞方法都是有InterruptedException的异常说明的,但是并不是所有的阻塞操作都会响应中断,比如因为获取不到锁而发生的阻塞或者因为数据源或者接收端没准备好而造成很多I/O操作发生阻塞。
对于因为获取不到锁而发生阻塞的情况,可以使用Lock接口提供的lockInterruptibly方法,该获取锁的方法是支持响应中断的。
取消任务
使用Future来取消任务
我们知道,每当任务通过调用线程池的submit方法提交之后都会得到一个Future对象,通过这个对象我们可以看到这个任务实时的执行状态,并且调用get方法还可以获得任务的执行结果。这个Future对象里还提供了两个关于取消任务的方法:上面的那张图片
我们知道,每当任务通过调用线程池的submit方法提交之后都会得到一个Future对象,通过这个对象我们可以看到这个任务实时的执行状态,并且调用get方法还可以获得任务的执行结果。这个Future对象里还提供了两个关于取消任务的方法:上面的那张图片
JU
Map
HashMap
LinkedHashMap
Collection
Set
List
ArrayList和LinkedList的区别
ArrayList快速失败是如何实现的?
queue
https://blog.csdn.net/tonywu1992/article/details/83419448
JUC
AQS
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
核心点
private volatile int state; //共享变量,表征同步状态的值,用volatile修饰保证线程间可见性
互斥锁,共享锁,可重入锁都是靠 state变量
https://mp.weixin.qq.com/s/3eUiYIxCU4XVw3p3wy4bKA
双向队列 FIFO
ConcurrentHashMap
1.8 在数组层面采用CAS,链表采用sync
1.7 采用segment分段锁,每个segment下面就是一个hashmap
keys不能为空
简单来说,这与数据结构是否支持并发息息相关。当ConcurrentMaps使用map.get(key)时返回为null,无法判断key是不存在还是值为空,non-concurrent还可以再调用map.contains(key)检查,但ConcurrentMaps可能再两次调用间已经发生改变。
value不能为空
另外,由于指令重排序的原因,在插入一个新节点的时候,有可能对该新节点已经插入到链表中了,可是该节点的value字段的赋值还没完成,所以如果调用get方法获取到了节点,还需要判断一下节点的value值是不是为null,如果为null的话,需要加锁重新获取。这也是为什么在添加键值对的时候,值不许为null的原因。
CopyOnWrite
LONG
64位的线程安全问题
Intenger
缓冲池问题
为什么要区分内核态和用户态
(基于安全的角度考虑,用户程序是不可信的)内核态能有效保护硬件资源的安全。
JVM
ClassLoader
双亲委派
如何破坏双亲委派
重写loadclass方法
OSGI热部署
每次收到类加载请求时,先将请求委派给父类加载器完成(所有加载请求最终会委派到顶层的Bootstrap ClassLoader加载器中),如果父类加载器无法完成这个加载(该加载器的搜索范围中没有找到对应的类),子类尝试自己加载
全盘负责委托机制
当一个ClassLoader加载一个类时,除非显示的使用另一个ClassLoader,该类所依赖和引用的类也由这个ClassLoader载入
流程
加载、验证、准备、解析、初始化、使用和卸载
加载
由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部;然后将其转换为一个与目标类型对应的java.lang.Class对象实例
验证
验证类是否是一个有效的字节码文件,验证内容涵盖了类数据信息的格式验证、语义分析、操作验证等
文件格式的验证,元数据的验证,字节码的验证,符号应用的验证
准备
为类中的所有静态变量分配内存空间,并为其设置一个初始值;被final修饰的静态变量,会直接赋予原值;
解析(非必须步骤)
将常量池中的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法)
初始化
将一个类中所有被static关键字标识的代码统一执行一遍,如果执行的是静态变量,那么就会使用用户指定的值覆盖之前在准备阶段设置的初始值;如果执行的是static代码块,那么在初始化阶段,JVM就会执行static代码块中定义的所有操作。
JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待
tomcat的双亲委派是怎么实现的
内存模型
主存和线程本地缓存
四种引用类型
强引用
大部分引用实际上都是强引用,这是使用最普遍的引用(当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误;也不会靠随意回收具有强引用的对象来解决内存不足问题。)
软引用
如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存
弱引用
。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
虚引用
虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
为什么要有不同的引用类型?
利用软引用和弱引用解决 OOM 问题。用一个 HashMap 来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM 会自动回收这些缓存图片对象所占用的空间,从而有效地避免了 OOM 的问题.
通过软引用实现 Java 对象的高速缓存。比如我们创建了一 Person 的类,如果每次需要查询一个人的信息,哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这将引起大量 Person 对象的消耗,并且由于这些对象的生命周期相对较短,会引起多次 GC 影响性能。此时,通过软引用和 HashMap 的结合可以构建高速缓存,提供性能。
Java中提供这四种引用类型主要有两个目的:
第一是可以让程序员通过代码的方式决定某些对象的生命周期;
第二是有利于JVM进行垃圾回收。
第一是可以让程序员通过代码的方式决定某些对象的生命周期;
第二是有利于JVM进行垃圾回收。
GC 算法
引用计数法
无法处理循环引用的问题
可达性分析
复制
步骤
复制算法将内存划分为两个区间
GC线程会将活动区间内的存活对象,全部复制到空闲区间,且严格按照内存地址依次排列
缺点
浪费内存
假设是100%存活,那么我们需要将所有对象都复制一遍
标记整理
步骤
(1)标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记。
(2)整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
(2)整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。
缺点
效率比较低(递归与全堆对象遍历),并且还要移动对象
标记清除
步骤
(1)标记:标记的过程其实就是,遍历所有的GC Roots,然后将所有GC Roots可达的对象标记为存活的对象。
(2)清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉
(2)清除:清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉
缺点
效率比较低(递归与全堆对象遍历)(导致STW时间长)
空闲内存是不连续的;JVM就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找。
可以做ROOT的节点
1、虚拟机栈中的引用的对象。
2、方法区中的类静态属性引用的对象。
3、方法区中的常量引用的对象。
4、本地方法栈中JNI的引用的对象。
2、方法区中的类静态属性引用的对象。
3、方法区中的常量引用的对象。
4、本地方法栈中JNI的引用的对象。
GC收集器
新生代(都采用复制算法)
Serial收集器:单线程,垃圾回收时需要停下所有的线程工作。
缺:stop the world;
优:简单高效,没有线程交互开销,专注于GC;
优:简单高效,没有线程交互开销,专注于GC;
ParNew收集器:Serial的多线程版本
缺:stop the world
优:并行并发GC
优:并行并发GC
Parallel Scavenge收集器:年轻代,多线程并行收集。设计目标是实现一个可控的吞吐量
主要关注吞吐量,通过吞吐量的设置控制停顿时间,适应不同的场景
老年代
Serial Old:Serial老年代版本(标记整理算法)
缺:stop the world
Parallel Old (标记整理)(Parallel Scavenge老年代版本)
主要关注吞吐量,通过吞吐量的设置控制停顿时间,适应不同的场景
CMS(标记清除算法)
步骤
整个过程四个步骤:初始标记(标记GCRoot直接关联对象,速度很快)、并发标记(从GCRoot向下标记)、重新标记(并发标记过程中发生变化的对象)、并发清除(清除老年代垃圾)。
初始标记和重新标记需要停顿所有用户线程。
缺点
无法处理浮动垃圾、有空间碎片的产生、对CPU敏感
cms 因为浮动垃圾的关系可能会失败,失败就会采用serialOld
G1(整体基于标记整理算法)(适用于新生代和老年代)
优点
优:并行与并发,
分代收集,空间整合(标记整理算法),可预测停顿
分代收集,空间整合(标记整理算法),可预测停顿
GC收集器对比
对象分配
大部分对象放在堆上
方式
指针碰撞
两块规整的内存,指针指向最后被占用的内存,给新对象分配空间时,只需使指针偏移即可
空闲表
JVM维护了一个空闲内存列表
栈上分配
如果对象不会逃逸出方法,可以采用栈上分配对象空间
TLAB
每个线程会在堆上拥有一块默认大小的内存,这样就无需向JVM要内存
对象分配规则
对象优先分配在 Eden 区。
大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。
长期存活的对象进入老年代。
动态判断对象的年龄。
空间分配担保。
每次进行 Minor GC 时,JVM 会计算 Survivor 区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次 Full GC ,如果小于检查 HandlePromotionFailure 设置,如果 true 则只进行 Monitor GC ,如果 false 则进行 Full GC 。
内存结构
常见的GC优化和命令 -XMS -XMX -XSS
XMS(启动时堆大小)256M
XMX(堆最大空间,超过就OutOfMemory)512m
XSS(每个线程的堆栈大小 1024K)
JVM 线上排查问题
A a = new A() 经历过什么过程
1.检测类是否被加载
2.为对象分配内存
3.为分配的内存空间初始化零值
4.执行 init 方法
2.为对象分配内存
3.为分配的内存空间初始化零值
4.执行 init 方法
对象的内存布局
对象头
第一部分,是存储对象自身的运行时数据,如哈希码,GC 分代年龄,锁状态标志,线程持有的锁等等。
第二部分,是类型指针,即对象指向类元数据的指针。
第二部分,是类型指针,即对象指向类元数据的指针。
实例数据
就是数据。
对齐填充
不是必然的存在,就是为了对齐。
当出现了内存溢出,你怎么排错?
1、首先,控制台查看错误日志。
2、然后,使用 JDK 自带的 jvisualvm 工具查看系统的堆栈日志。
3、定位出内存溢出的空间:堆,栈还是永久代(JDK8 以后不会出现永久代的内存溢出)。
如果是堆内存溢出,看是否创建了超大的对象。
如果是栈内存溢出,看是否创建了超大的对象,或者产生了死循环。
1、首先,控制台查看错误日志。
2、然后,使用 JDK 自带的 jvisualvm 工具查看系统的堆栈日志。
3、定位出内存溢出的空间:堆,栈还是永久代(JDK8 以后不会出现永久代的内存溢出)。
如果是堆内存溢出,看是否创建了超大的对象。
如果是栈内存溢出,看是否创建了超大的对象,或者产生了死循环。
方法区是否能被回收?
方法区可以被回收,但是价值很低,主要回收废弃的常量和无用的类。
如何判断无用的类,需要完全满足如下三个条件:
该类所有实例都被回收(Java 堆中没有该类的对象)。
加载该类的 ClassLoader 已经被回收。
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方利用反射访问该类。
方法区可以被回收,但是价值很低,主要回收废弃的常量和无用的类。
如何判断无用的类,需要完全满足如下三个条件:
该类所有实例都被回收(Java 堆中没有该类的对象)。
加载该类的 ClassLoader 已经被回收。
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方利用反射访问该类。
spring
IOC
所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系
有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合
对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。
Singleton - 每个 Spring IoC 容器仅有一个单 Bean 实例。默认
Prototype - 每次请求都会产生一个新的实例。
Request - 每一次 HTTP 请求都会产生一个新的 Bean 实例,并且该 Bean 仅在当前 HTTP 请求内有效。
Session - 每一个的 Session 都会产生一个新的 Bean 实例,同时该 Bean 仅在当前 HTTP Session 内有效。
Application - 每一个 Web Application 都会产生一个新的 Bean ,同时该 Bean 仅在当前 Web Application 内有效。
Prototype - 每次请求都会产生一个新的实例。
Request - 每一次 HTTP 请求都会产生一个新的 Bean 实例,并且该 Bean 仅在当前 HTTP 请求内有效。
Session - 每一个的 Session 都会产生一个新的 Bean 实例,同时该 Bean 仅在当前 HTTP Session 内有效。
Application - 每一个 Web Application 都会产生一个新的 Bean ,同时该 Bean 仅在当前 Web Application 内有效。
DI
IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。
IOC DI 区别
ioc是目的,di是手段。ioc是指让生成类的方式由传统方式(new)反过来,既程序员不调用new,需要类的时候由框架注入(di),是同一件不同层面的解读。
2.依赖注入和控制反转是对同一件事情的不同描述,从某个方面讲,就是它们描述的角度不同。
依赖注入是从应用程序的角度在描述,可以把依赖注入描述完整点:应用程序依赖容器创建并注入它所需要的外部资源;
而控制反转是从容器的角度在描述,描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。
依赖注入是从应用程序的角度在描述,可以把依赖注入描述完整点:应用程序依赖容器创建并注入它所需要的外部资源;
而控制反转是从容器的角度在描述,描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。
AOP
反射
反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法
通过Class类和reflect类 动态获得 field,method,constuor
Java 反射机制主要提供了以下功能:
在运行时构造一个类的对象。
判断一个类所具有的成员变量和方法。
调用一个对象的方法。
生成动态代理。
在运行时构造一个类的对象。
判断一个类所具有的成员变量和方法。
调用一个对象的方法。
生成动态代理。
动态代理
JDK
JDK动态代理技术。通过需要代理的目标类的getClass().getInterfaces()方法获取到接口信息(这里实际上是使用了Java反射技术。getClass()和getInterfaces()函数都在Class类中,Class对象描述的是一个正在运行期间的Java对象的类和接口信息),通过读取这些代理接口信息生成一个实现了代理接口的动态代理Class(动态生成代理类的字节码),然后通过反射机制获得动态代理类的构造函数,并利用该构造函数生成该Class的实例对象(InvokeHandler作为构造函数的入参传递进去),在调用具体方法前调用InvokeHandler来处理。
CGLIB
CGLib代理其生成的动态代理对象是目标类的子类
字节码技术。利用asm开源包,把代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。采用非常底层的字节码技术,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑
代理能干嘛?代理可以帮我们增强对象的行为!使用动态代理实质上就是调用时拦截对象方法,对方法进行改造、增强!
JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。
如果是单例的代理,推荐使用CGLib
如果是单例的代理,推荐使用CGLib
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
静态代理
AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象
AOP实现原理
https://mp.weixin.qq.com/s/f16eDgjV064w8iPZWsNuyg
动态代理和装饰器模式区别
术语
连接点(Join point):
能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点~
切点(Poincut):
具体定位的连接点:上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点。
增强/通知(Advice):
表示添加到切点的一段逻辑代码,并定位连接点的方位信息。
简单来说就定义了是干什么的,具体是在哪干
Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用!
织入(Weaving):
将增强/通知添加到目标类的具体连接点上的过程。
引入/引介(Introduction):
引入/引介允许我们向现有的类添加新方法或属性。是一种特殊的增强!
切面(Aspect):
切面由切点和增强/通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义。
在《Spring 实战 (第4版)》给出的总结是这样子的:
通知/增强包含了需要用于多个应用对象的横切行为;连接点是程序执行过程中能够应用通知的所有点;切点定义了通知/增强被应用的具体位置。其中关键的是切点定义了哪些连接点会得到通知/增强。
能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点~
切点(Poincut):
具体定位的连接点:上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点。
增强/通知(Advice):
表示添加到切点的一段逻辑代码,并定位连接点的方位信息。
简单来说就定义了是干什么的,具体是在哪干
Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用!
织入(Weaving):
将增强/通知添加到目标类的具体连接点上的过程。
引入/引介(Introduction):
引入/引介允许我们向现有的类添加新方法或属性。是一种特殊的增强!
切面(Aspect):
切面由切点和增强/通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义。
在《Spring 实战 (第4版)》给出的总结是这样子的:
通知/增强包含了需要用于多个应用对象的横切行为;连接点是程序执行过程中能够应用通知的所有点;切点定义了通知/增强被应用的具体位置。其中关键的是切点定义了哪些连接点会得到通知/增强。
事务(所谓事务管理,其实就是“按照给定的事务规则来执行提交或者回滚操作”。)
PlatformTransactionManager: (平台)事务管理器
Spring并不直接管理事务,而是提供了多种事务管理器。pring事务管理器的接口是: org.springframework.transaction.PlatformTransactionManager ,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了
TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
定义了一些基本的事务属性
TransactionDefinition接口中定义了5个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等的常量
隔离级别
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务传播行为
支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
不支持当前事务的情况:
TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
事务超时属性(一个事务允许执行的最长时间)
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
事务只读属性(对事物资源是否执行只读操作)
事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。
回滚规则(定义事务回滚规则)
这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)。 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
TransactionStatus: 事务运行状态
TransactionStatus接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息
事务类型
编程式事务(代码级别)
ransactionTemplate 的execute()方法有一个TransactionCallback类型的参数,该接口中定义了一个doInTransaction()方法,可通过匿名内部累的方式实现TransactionCallBack接口,将业务代码写在doInTransaction()方法中,业务代码中不需要显示调用任何事物管理API,除了异常回滚外,也可以在业务代码的任意位置通过transactionStatus.setRollbackOnly();执行回滚操作。
声明式事务(方法级别)
Spring的声明式事务管理建立在AOP基础上,其本质是在目标方法执行前进行拦截,在方法开始前创建一个事务,在执行完方法后根据执行情况提交或回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不用侵入业务代码,只需要在配置文件中做相关的事物声明就可将业务规则应用到业务逻辑中。和编程式事务相比,声明式事务唯一的不足是智能作用到方法级别,无法做到像编程式事务那样到代码块级别
https://mp.weixin.qq.com/s?__biz=MzUzMTA2NTU2Ng==&mid=2247484702&idx=1&sn=c04261d63929db09ff6df7cadc7cca21&chksm=fa497aafcd3ef3b94082da7bca841b5b7b528eb2a52dbc4eb647b97be63a9a1cf38a9e71bf90&token=165108535&lang=zh_CN#rd
spring事务的原理
https://mp.weixin.qq.com/s/qF8yGXrxw12He-YB9lPEZw
常见面试题
BeanFactory FactoryBean ApplicationContext 区别
BeanFacotry是spring中比较原始的Factory。如XMLBeanFactory就是一种典型的BeanFactory。原始的BeanFactory无法支持spring的许多插件,如AOP功能、Web应用等。
ApplicationContext接口,它由BeanFactory接口派生而来,ApplicationContext包含BeanFactory的所有功能,通常建议比BeanFactory优先
ApplicationContext接口,它由BeanFactory接口派生而来,ApplicationContext包含BeanFactory的所有功能,通常建议比BeanFactory优先
https://www.cnblogs.com/aspirant/p/9082858.html
2、FactoryBean
一般情况下,Spring通过反射机制利用<bean>的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在<bean>中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。
一般情况下,Spring通过反射机制利用<bean>的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在<bean>中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。
BeanFactory和FactoryBean的区别
BeanFactory是接口,提供了OC容器最基本的形式,给具体的IOC容器的实现提供了规范,
FactoryBean也是接口,为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式
BeanFactory是接口,提供了OC容器最基本的形式,给具体的IOC容器的实现提供了规范,
FactoryBean也是接口,为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式
1、 BeanFactory
BeanFactory,以Factory结尾,表示它是一个工厂类(接口), 它负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。XmlBeanFactory类将持有此XML配置元数据,并用它来构建一个完全可配置的系统或应用。
BeanFactory,以Factory结尾,表示它是一个工厂类(接口), 它负责生产和管理bean的一个工厂。在Spring中,BeanFactory是IOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。XmlBeanFactory类将持有此XML配置元数据,并用它来构建一个完全可配置的系统或应用。
BeanFactory和ApplicationContext就是spring框架的两个IOC容器,现在一般使用ApplicationnContext,其不但包含了BeanFactory的作用,同时还进行更多的扩展。
BeanFacotry是spring中比较原始的Factory。如XMLBeanFactory就是一种典型的BeanFactory。
原始的BeanFactory无法支持spring的许多插件,如AOP功能、Web应用等。ApplicationContext接口,它由BeanFactory接口派生而来,
ApplicationContext包含BeanFactory的所有功能,通常建议比BeanFactory优先
ApplicationContext以一种更向面向框架的方式工作以及对上下文进行分层和实现继承,ApplicationContext包还提供了以下的功能:
• MessageSource, 提供国际化的消息访问
• 资源访问,如URL和文件
• 事件传播
• 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层;
BeanFacotry是spring中比较原始的Factory。如XMLBeanFactory就是一种典型的BeanFactory。
原始的BeanFactory无法支持spring的许多插件,如AOP功能、Web应用等。ApplicationContext接口,它由BeanFactory接口派生而来,
ApplicationContext包含BeanFactory的所有功能,通常建议比BeanFactory优先
ApplicationContext以一种更向面向框架的方式工作以及对上下文进行分层和实现继承,ApplicationContext包还提供了以下的功能:
• MessageSource, 提供国际化的消息访问
• 资源访问,如URL和文件
• 事件传播
• 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层;
循环依赖
种类
构造器的循环依赖。
无法解决
field 属性的循环依赖。(Spring是先将Bean对象实例化之后再设置对象属性的)
让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。
知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
spring只能解决单例的循环依赖,因为非单例不会有缓存
动态代理和装饰器区别
1.两者都是对类的方法进行扩展,但装饰器模式强调的是增强自身,在被装饰之后你能够在被增强的类上使用增强后的功能。增强后你还是你,只不过能力更强了而已;而代理模式则强调要让别人帮你去做一些本身与你业务没有太多关系的职责(记录日志、设置缓存)。代理模式是为了实现对象的控制,因为被代理的对象往往难以直接获得或者是其内部不想暴露出来。
spring bean 生命周期
1. 实例化Bean
容器通过获取BeanDefinition对象中的信息进行实例化。并且这一步仅仅是简单的实例化,并未进行依赖注入。
设置对象属性(依赖注入)
Spring根据BeanDefinition中的信息进行依赖注入。
并且通过BeanWrapper提供的设置属性的接口完成依赖注入。
并且通过BeanWrapper提供的设置属性的接口完成依赖注入。
1、通过实现InitializeBean接口设置对象属性
2、通过@PostConstruct注解设置对象属性
2、通过@PostConstruct注解设置对象属性
注入Aware接口
紧接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean。
(与生命周期无关,知识解释aware接口作用)Aware接口也是为了能够感知到自身的一些属性,BeanNameAware接口是为了让自身Bean能够感知到,获取到自身在Spring容器中的id属性。
BeanPostProcessor
如果你想要对象被使用前再进行一些自定义的处理,就可以通过BeanPostProcessor接口实现。
postProcessBeforeInitialzation( Object bean, String beanName ) 当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。 这个函数会先于InitialzationBean执行,因此称为前置处理。 所有Aware接口的注入就是在这一步完成的。
postProcessAfterInitialzation( Object bean, String beanName ) 当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。 这个函数会在InitialzationBean完成后执行,因此称为后置处理。
InitializingBean与init-method
当BeanPostProcessor的前置处理完成后就会进入本阶段。InitializingBean接口只有一个函数:
afterPropertiesSet()
afterPropertiesSet()
DisposableBean和destroy-method
和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑
AOP遇上循环依赖
https://mp.weixin.qq.com/s/NUlIzgsJwOSPh8ExSDS1Kw
@Autowired的实现原理
https://mp.weixin.qq.com/s/y0fOv8naBnqTCpalJ4f3Cw
如何解析xml
springMVC
从dispatchServlet 到 viewResolver
Spring MVC 拦截器
工作原理
https://mp.weixin.qq.com/s/NFo8orL-PFDZlDYCA6ovbQ
HandlerAdapter主要处理方法参数、相关注解、数据绑定、消息转换、返回值、调用视图解析器等等。
HandlerAdapter 做的事情很多。
HandlerAdapter 做的事情很多。
什么是rest
说白了就是资源的定位方式,从以前的动词指向到现在的名词指向:
例如以前的写法:/getOrder?id=1
REST的写法就是: /orders/1
这样有什么好处,可能搞web的会比较清楚。REST不是一种强制的协议,而是一种规范,而RESTful就是采用了这种规范的编程了。
例如以前的写法:/getOrder?id=1
REST的写法就是: /orders/1
这样有什么好处,可能搞web的会比较清楚。REST不是一种强制的协议,而是一种规范,而RESTful就是采用了这种规范的编程了。
算法
排序
堆排
快排
树的遍历
前序
中序
后序
二分查找
链表,数组基本操作
定时任务XXL-JOB
xxl-job框架
https://mp.weixin.qq.com/s/c05rF_IGsqjGsJuCEWFO6Q
xxl-job的设计
https://mp.weixin.qq.com/s/yHGundWy9Aw2lyxdVw8DwA
Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象 MappedProxy,代理对象会拦截接口方法,根据类的全限定名+方法名,唯一定位到一个MapperStatement并调用执行器执行所代表的sql,然后将sql执行结果返回。
————————————————
版权声明:本文为CSDN博主「张维鹏」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/a745233700/article/details/80977133
————————————————
版权声明:本文为CSDN博主「张维鹏」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/a745233700/article/details/80977133
0 条评论
下一页