前言 本系列为面试专题,主要记录一些易混淆、易忘记的知识点; 目前共有四个部分:Java基础、计算机基础、数据库与框架; 4. 框架 4.1 Spring 1. Spring 中的 bean 的作用域:singleton(单例)、prototype(多例)、request(一次请求一个)、session(一次请求一个)、global-session(全局); 2. @Component 和 @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 解析消息,并更新本地服务地址列表; 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),则通过; 如果断路器状态为半开,则拒绝; 如果断路器状态为打开,则尝试转换状态为半开; 状态转为半开会重置(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 数组占用内存较大-使用惰性加载) 小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货。我有一些面试题、架构、设计类资料可以说是程序员面试必备!所有资料都整理到网盘了,需要的话欢迎下载!私信我回复【666】即可免费获取