@[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设置进ApplicationContext4.初始化 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发生请求;
  • 解析请求参数;
  • 根据 namespaceIdserviceName 获得Service实例;
  • 从 Service 实例中基于 srvIPs 得到所有服务提供者实例;
  • 遍历组装 JSON 字符串并返回;

4. Nacos 服务地址动态感知原理

  • 客户端通过 subscribe() 方法来实现监听;
  • Nacos 客户端中有一个 HostReactor 类,实现服务的动态更新,基本原理是:
    • 客户端发起时间订阅后,在 HostReactor 中有一个 UpdateTask 线程,每 10s 发送一次 Pull 请求,获得服务端最新的地址列表;
    • 对于服务端,它和服务提供者的实例之间维持了心跳检测,一旦服务提供者出现异常,则会发送一个 Push 消息给 Nacos 客户端,也就是服务端消费者;
    • 服务消费者收到请求之后,使用 HostReactor 中提供的 processServiceJSON 解析消息,并更新本地服务地址列表;

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 的构造方法里有规定长轮询定时机制的一些基本属性:HttpAgentClientWorker
  • 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),则通过
    • 如果断路器状态为半开,则拒绝
    • 如果断路器状态为打开,则尝试转换状态为半开
  • 状态转为半开会重置(retry)一个超时时间,如果在这个时间内请求成功,则断路器关闭。反之打开;
  • 断路器打开的条件根据 熔断策略 去配置,有:慢比例调用、异常比例、异常数;
  • 断路器打开的条件如下:
    • 慢比例调用:统计时间内请求数大于最小请求数 + 慢调用(响应时间大于RT)比例大于阈值;
    • 异常比例:统计时间内请求数大于最小请求数 + 请求异常的比例大于阈值;
    • 异常数:统计时间内的异常数目超过阈值;

5. 统计槽 StatisticSlot

  • 统计槽的实现与其他槽不一样,它先调用 fireEntry() 方法执行后续槽,再进行统计;
  • 它主要统计的是两个核心指标:增加线程数请求通过数
  • Sentinel 使用的是滑动窗口算法来统计单位时间内的请求数;
  • 简单来说就是用一个环形数组,数组元素表示单位时间内的请求统计量,随单位时间循环遍历数组;
  • 具体实现来看是使用一个 ArrayMetric 指标数组,里面有个 LeapArray 环形数组ArrayMetric 指标数组 里的所有方法都是操作 LeapArray 环形数组 的;
  • LeapArray 环形数组 里的元素是 WindowWrap 窗口包装类,也就是我们说的窗口,包装类包装的是 MetricBucket 指标桶
  • 每个 MetricBucket 指标桶 统计的是单位时间内的请求成功、失败、异常、响应时长等指标数据,指标数据存储在 LongAdder[] 数组里;
  • LongAdder 对象是 JDK1.8 新增的类,用于在高并发场景下代替 AtomicLong,以用空间换时间的方式降低了 CAS 失败的概率,从而提高性能;
  • (AtomicLong 的高并发性能问题:使用 CAS 算法,过多线程同时竞争同一个变量,大量线程会竞争失败,处于自旋状态消耗性能。LongAdder 内部维护一个 Cells 数组,把一个变量分解为多个变量,避免大量线程同时失效);
  • (总结来说 LongAdderAtomicLong 的有几处优化:使用Cells数组分解变量、获取资源失败时尝试获取其他原子变量的锁而不是自旋 CAS 重试、Cells 数组占用内存较大-使用惰性加载)

最后

::: hljs-center

新人制作,如有错误,欢迎指出,感激不尽!

:::

::: hljs-center

欢迎关注公众号,会分享一些更日常的东西!

:::

::: hljs-center

如需转载,请标注出处!

:::

::: hljs-center

公众号

:::