@[TOC](Java 后端框架部分)
前言
本系列为面试专题,主要记录一些易混淆、易忘记的知识点;
目前共有四个部分:Java基础、计算机基础、数据库与框架;
4. 框架
4.1 Spring
1. Spring 中的 bean 的作用域:singleton(单例)、prototype(多例)、request(一次请求一个)、session(一次请求一个)、global-session(全局);
2. @Component 和 @Bean:
比较项 | @Component | @Bean |
---|---|---|
作用对象 | 类 | 方法 |
加载类型 | 通过类路径扫描 | 在标有该注解的方法中定义 bean |
自定义 | 自定义性更强 |
3. Spring MVC:DispatcherServlet、HandlerMapping、HandlerAdapter(Handler,ModelAndView)、ViewResolver、View;
4. 设计模式:工厂(通过 BeanFactory/ApplicationContex 创建对象)、代理(Spring AOP)、单例(Bean 默认单例)、包装器(动态切换数据源)、观察者(事件驱动模型)、适配器(spring MVC);
5. AOP 代理方式:JDK动态代理(创建接口实现类)、CGLIB动态代理(创建子类的代理对象);
6. AOP 术语:连接点(增强哪些方法)、切入点(实际被真正增强的方法)、通知(增强的类型)、切面(动词,把通知应用到切入点的过程);
7. 事务传播行为:@Transactional。支持当前事务、不支持当前事务、其他;
4.2 Spring Boot
1. 三个注解:@Configuration、@EnableAutoConfiguration、@ComponentScan;
2. 启动过程:1.始化准备 ApplicationContext
、告知服务启动、2.准备 Environment
、告知 Environment 环境准备好、(打印banner)、3.Environment设置进ApplicationContext
、4.初始化 ApplicationContext
、告知上下文环境准备好、5.加载 Config 配置到 ApplicationContext
、告知上下文环境加载完毕、6.refresh() 方法刷新应用上下文
、告知应用程序启动完毕;
4.3 Zookeeper
1. 数据:结构化存储、由 Znode(key/value形式)组成、维护 stat 状态信息;
2. 节点类型:持久化节点、临时节点、有序节点、容器节点、TTL 节点;
4.4 Oauth2
1. 工作原理:第三方客户端向资源所有者(用户)申请认证请求、资源所有者同意请求返回许可、客户端根据许可向认证服务器申请认证令牌 Token、客户端根据认证令牌向资源服务器申请相关资源;
4.5 Nacos
1. SpringCloud 客户端集成 Nacos:
- spring-cloud-commons 的
META-INF/spring.factories
包下包含 Spring Cloud 自动配置类的全类名; - 其中有个
AutoServiceRegistrationAutoConfiguration
服务注册相关的配置类; - 这个配置类注入了一个
AutoServiceRegistration
实例; - 我们的
NacosAutoServiceRegistration
间接实现了这个接口(中间隔了个 AbstractAutoServiceRegistration); - 同时,AutoServiceRegistration 类实现了
EventListener
接口,说明 Nacos 通过事件监听机制注册进 Spring Cloud; - 当 Webserver 初始化完成之后,调用
this.bind ( event )
方法启用事件监听; - 最终会调用
NacosServiceRegistry.register()
方法进行服务注册; - register() 方法中调用
namingService.registerInstance()
完成服务的注册。具体来说会做这几件事:- 通过
BeatReactor.addBeatInfo()
创建心跳信息实现健康检测; - 通过
NamingProxy.registerService()
,最终使用open API 或 SDK 方式发生HTTP请求给Nacos服务器;
- 通过
2. 服务的注册服务实例:
- 在 nacos-naming 模块下的
InstanceController
中使用接口 nacos/v1/ns/instance 接受请求; - 请求参数获得
serviceName
(服务名)和namespaceId
(命名空间Id); - 调用
registerInstance
注册实例; - 根据服务名和命名空间id从缓存获取,service对象,没有则构建一个保存到
ConcurrentHashMap
集合中(也就是 Nacos控制台的服务信息),并加到缓存; - 使用定时任务对当前服务下的所有实例建立心跳检测机制;
- 基于数据一致性协议服务数据进行同步;
3. 服务端查询服务实例:
- 客户端通过sdk或api发生请求;
- 解析请求参数;
- 根据
namespaceId
、serviceName
获得Service实例; - 从 Service 实例中基于 srvIPs 得到所有服务提供者实例;
- 遍历组装 JSON 字符串并返回;
4. Nacos 服务地址动态感知原理:
- 客户端通过
subscribe()
方法来实现监听; - Nacos 客户端中有一个
HostReactor
类,实现服务的动态更新,基本原理是:- 客户端发起时间订阅后,在 HostReactor 中有一个
UpdateTask
线程,每 10s 发送一次Pull
请求,获得服务端最新的地址列表; - 对于服务端,它和服务提供者的实例之间维持了心跳检测,一旦服务提供者出现异常,则会发送一个
Push
消息给 Nacos 客户端,也就是服务端消费者; - 服务消费者收到请求之后,使用
HostReactor
中提供的processServiceJSON
解析消息,并更新本地服务地址列表;
- 客户端发起时间订阅后,在 HostReactor 中有一个
5. Spring Cloud 加载配置原理:
- Nacos 的配置初始化依赖于 Spring Cloud 的配置自动加载;
- Spring Cloud 的配置自动加载在 Spring Cloud
主程序类加载
时加载进来; - 而主程序类加载有这么几个关键的步骤:始化准备 ApplicationContext、准备 Environment、Environment 设置进 ApplicationContext、加载 Config 配置到 ApplicationContext、refresh() 方法刷新应用上下文;
- 其中在准备完
Environment
环境后会使用事件监听机制通知BootstrapApplicationListener
加载 classpath 路径下查找 META-INF/spring.factories 预定义的配置类。这些配置类是 Spring Cloud 提供的; - 在最后一步
刷新应用上下文
时会执行一些Spring Cloud非官方的操作,比如从 Nacos 服务器里加载配置文件等;(最终调用的是 NacosPropertySourceLocator.locate() 方法)- 该方法的主要作用是:初始化
ConfigService
对象,按照顺序分别加载共享配置、扩展配置、应用名称对应的配置;
- 该方法的主要作用是:初始化
6. 客户端的长轮询定时机制
- 在创建 ConfigService 对象时使用反射机制创建
NacosConfigService
对象; - 在 NacosConfigService 的构造方法里有规定长轮询定时机制的一些基本属性:
HttpAgent
、ClientWorker
; - ClientWorker 创建了
两个定时调度的线程池
,其中一个每隔 10s 检查一次配置是否发生变化。另一个用于实现客户端的定时长轮询功能。 - (配置用一个 cacheMap 来存储,key 是根据 datalD/group/tenant 拼接的值,值是配置文件)
- (超过 3000 个配置集会启动多个 LongPollingRunnable 去执行)
- 检查配置这里先会
检查本地配置
,再检查服务端的配置
是否发生改变,发生改变就使用 HttpAgent 调用接口请求变更配置的 id 等信息; - (等待30s)
- 然后在 30s 后收到一个
HttpResult
,里面有存在数据变更的 Data ID、Group、Tenant; - 然后通过
getServerConfig()
调用 HttpAgent 的接口请求去 Nacos 服务器上读取具体的配置内容;
7. 服务端的长轮询定时机制:
- 服务器使用
ConfigController
类里的 /listener 接口接受请求,然后执行主要两个逻辑: 获取客户端请求的变化的配置
,使用 MD5 值校验。和执行长轮询定时机制
;- 长轮询定时机制首先会
将客户端的长轮询请求封装成 ClientPolling
; - 然后使用一个
ClientLongPolling 线程池
执行长轮询定时机制; - 具体来说就是把 ClientLongPolling 实例放进一个
allSubs 队列
中。在 29.5s 后拿出来执行任务。校验配置是否发生改变,发生改变则通过 response 返回给客户端; - 返回的是存在数据变更的 Data ID、Group、Tenant;
- (提前 0.5s 是避免客户端超时);
- 缺点是:服务器连接会消耗资源,服务器开销;
- 好处是:使得客户端和服务端之间在 30s 之内数据没有发生变化的情况下一直处于连接状态。解决了轮询机制请求频繁对资源的消耗;
8. 服务器修改配置:
- 服务器谁配置的修改是通过
监听机制
实现的; - 我们在 Nacos 服务器或通过 API 方式变更配置后,会发布一个
LocalDataChangeEvent 事件
,该事件会被LongPollingService 监听
; - 然后会使用线程池执行
DataChangeTask 任务
,修改服务器上的配置;
4.6 Sentinel
1. 如何拦截请求:
SentinelWebAutoConfiguration
配置类里有个FilterRegistrationBean
;- 这个 Bean 注册了个
CommonFilter
; - 默认情况下通过
/*
规则拦截所有的请求,并且将这些请求设置为Sentinel 的资源
;
2. Sentinel 的工作原理
- Sentinel 依赖
ProcessorSlot 调用链
进行工作,使用的是责任链模式
,链表元素是一个Slot 槽
; - Sentinel 里给我们实现了很多 Slot 槽,其中有
FlowSlot(流控槽)
、StatisticSlot(统计槽)
、DegradeSlot(熔断槽)
; - 先调用
lookProcessChain()
方法从缓存中获取 slot 调用链,没有就创建一个; - 然后以遍历链表的方式完成流控、熔断和统计等功能;
- 进入每个槽的方法是:
xxxSlot.entry()
方法,里面都是调用两个方法(除了统计槽外),checkXxx() 检查规则
和fireEntry() 调用下一个Slot槽
;
3. 流控槽 lowSlot:
- 先到 Sentinel 控制台获取流控规则;
- 会根据获取到的信息做一些判断和匹配,如:
请求路径
、是否集群
、阈值类型
(QPS,并发线程数)、流控类型
(直接,关联,链路)、流控效果
(快速失败,排队等待等); - 在代码层面根据:
流控类型(Strategy)
和针对来源(limitApp)
用 if-else 的方式给我们实现了三种场景:- 1. 优先保证针对来源(QPS - 每秒的响应请求数);
- 2. 优先保证规则,所有来源共用一个规则;
- 3. 其他情况(针对场景1中没有配置规则的应用来源);
- 选定场景后会根据流控效果进行相应处理,有四种处理策略:
- 1. 直接拒绝:抛异常;
- 2. 匀速排队:请求以匀速的速度通过;
- 3. 冷启动:QPS阈值除以冷加载因子得预热时长,预热时长后达到QPS阈值(秒杀场景);
- 4. 匀速+冷启动;
4. 熔断槽 DegradeSlot:
- 熔断功能在 Sentinel-1.8.0 版本前后有较大变化;
- 先根据资源名称 resourceName (请求路径)获取断路器;
- 循环判断每个断路器;
- 如果断路器状态为关闭(State.CLOSED),则
通过
; - 如果断路器状态为半开,则
拒绝
; - 如果断路器状态为打开,则
尝试转换状态为半开
;
- 如果断路器状态为关闭(State.CLOSED),则
- 状态转为半开会重置(retry)一个超时时间,如果在这个时间内请求成功,则断路器关闭。反之打开;
- 断路器打开的条件根据
熔断策略
去配置,有:慢比例调用、异常比例、异常数; - 断路器打开的条件如下:
- 慢比例调用:统计时间内请求数大于最小请求数 + 慢调用(响应时间大于RT)比例大于阈值;
- 异常比例:统计时间内请求数大于最小请求数 + 请求异常的比例大于阈值;
- 异常数:统计时间内的异常数目超过阈值;
5. 统计槽 StatisticSlot:
- 统计槽的实现与其他槽不一样,它先调用
fireEntry()
方法执行后续槽,再进行统计; - 它主要统计的是两个核心指标:
增加线程数
和请求通过数
; - Sentinel 使用的是滑动窗口算法来统计单位时间内的请求数;
- 简单来说就是用一个环形数组,数组元素表示单位时间内的请求统计量,随单位时间循环遍历数组;
- 具体实现来看是使用一个
ArrayMetric 指标数组
,里面有个LeapArray 环形数组
,ArrayMetric 指标数组
里的所有方法都是操作LeapArray 环形数组
的; LeapArray 环形数组
里的元素是WindowWrap 窗口包装类
,也就是我们说的窗口,包装类包装的是MetricBucket 指标桶
;- 每个
MetricBucket 指标桶
统计的是单位时间内的请求成功、失败、异常、响应时长等指标数据,指标数据存储在LongAdder[]
数组里; LongAdder
对象是 JDK1.8 新增的类,用于在高并发场景下代替 AtomicLong,以用空间换时间的方式降低了 CAS 失败的概率,从而提高性能;- (AtomicLong 的高并发性能问题:使用 CAS 算法,过多线程同时竞争同一个变量,大量线程会竞争失败,处于自旋状态消耗性能。LongAdder 内部维护一个 Cells 数组,把一个变量分解为多个变量,避免大量线程同时失效);
- (总结来说 LongAdder 比 AtomicLong 的有几处优化:使用Cells数组分解变量、获取资源失败时尝试获取其他原子变量的锁而不是自旋 CAS 重试、Cells 数组占用内存较大-使用惰性加载)
最后
::: hljs-center
新人制作,如有错误,欢迎指出,感激不尽!
:::
::: hljs-center
欢迎关注公众号,会分享一些更日常的东西!
:::
::: hljs-center
如需转载,请标注出处!
:::
::: hljs-center
:::