【Q-01】你曾阅读过 Spring Cloud 的源码吗?我们知道,Spring Cloud 是通过 Spring Boot 集成了很多第三方框架构成的。现在准备解析 Spring Cloud 中某子框架的源码,若还没有找到合适的入手位置,那么从哪里开始解析可能是一个不错的选择?
【RA】我自己曾阅读过 Spring Cloud 中的 Eureka、OpenFeign、Ribbon 等的源码。对于一个未曾阅读过的子框架源码,我认为从自动配置类开始解析可能是一个不错的选择。 我们知道 Spring Cloud 是通过 Spring Boot 将其它第三方框架集成进来的。Spring Boot 最大的特点就是自动配置,我们可以通过导入相关 Starter 来实现需求功能的自动配置、相关核心业务类实例的创建等。也就是说,核心业务类都是集中在自动配置类中的。所以从这里下手分析应该是个不错的选择。 那么从哪里可以找到这个自动配置类呢?从导入的 starter 依赖工程的 META-INF 目录中的 spring.factory 文件中可以找到。该文件的内容为 key-value 对,查找EnableAutoConfiguration的全限定性类名作为 key 的 value,这个 value 就是我们要找到的自动配置类。
【Q-02】@EnableConfigurationProperties 注解对于 Starter 的定义很重要,请谈一上你对这个注解的认识。
【RA】@EnableConfigurationProperties 注解在 Starter 定义时主要用于读取 application.yml 配置文件中相关的属性,并封装到指定类型的实例中,以备 Starter 中的核心业务实例使用。 具体来说,它就是开启了@ConfigurationProperties 注解的 Bean 的自动注册,注解到Spring 容器中。这种 Bean 有两种注册方式:在配置类使用@Bean 方法注册,或直接使用该注解的 value 属性进行注册。若在配置类中使用@Bean 注册,则需要在配置类中定义一个@Bean 方法,该方法的返回值为“使用@ConfigurationProperties 注解标注”的类。若直接使用该注解的 value 属性进行注册,则需要将这个“使用@ConfigurationProperties 注解标注”的类作为 value 属性值出现即可。
【Q-03】Spring Boot 中定义了很多条件注解,这些注解一般用于对配置类的控制。在这些条件注解中有一个@ConditionalOnMissingBean 注解,你了解过嘛?请谈一下你对它的认识。
【RA】@ConditionalOnMissingBean 注解是 Spring Boot 提供的众多条件注册中的一个。其表示的意义是,当容器中没有指定名称或指定类型的 Bean 时,该条件为 true。不过,这里需要强调一点的是,这里要查找的“容器”是可以指定的。通过 search 属性指定。其 search的范围有三种:仅搜索当前配置类容器;搜索所有层次的父类容器,但不包含当前配置类容器;搜索当前配置类容器及其所有层次的父类容器,这个是默认搜索范围。
【Q-04】Spring Cloud 中默认情况下对于 Eureka Client 实例的创建中,@RefreshScope 注解是比较重要的,请谈一下你对这个注解的认识。
【RA】@RefreshScope 注解是 Spring Cloud 中定义的一个注解。该注解用于配置类,可以添加在配置类上,也可以添加在@Bean 方法上。其表示的意思是,以这种方式注解的 bean 可以在运行时刷新,使用它们的任何组件将在下一次方法调用时获得一个新实例,该实例已完全初始化并使用所有依赖项注入。这种方式就等价于在 Spring的 xml 配置文件中指定<bean/>标签的 scope 属性值为 refresh。当然,若一个配置类上添加了该注解,则表示该配置类中的所有@Bean 方法创建的实例都是@RefreshScope 的。
【Q-05】Spring Cloud 中默认情况下对于 Eureka Client 实例的创建是在 EurekaClient 的自动配置类中通过@Bean方法完成的。但在源码中,这个@Bean方法上同时出现了@RefreshScope、@ConditionalOnMissionBean,与@Lazy 注解,从这些注解的意义来分析,是否存在矛盾呢?它们联合使用又是什么意思呢?请谈一下你的看法。
【RA】首先来说,这三个注解的意义都是比较复杂的。 @RefreshScope 注解是 Spring Cloud 中定义的一个注解。其表示的意思是,该@Bean 方法会以多例的形式生成会自动刷新的 Bean 实例。 @ConditionalOnMissionBean 注解表示的意思是,只有当容器中没有@Bean 要创建的实例时才会创建新的实例,即这里创建的@Bean 实例是单例的。 @Lazy 注解表示延迟实例化。即在当前配置类被实例化时并不会调用这里的@Bean 方法去创建实例,而是在代码执行过程中,真正需要这个@Bean 方法的实例时才会创建。 这三个注解的联用不存在矛盾,其要表达的意思是,这个@Bean 会以延迟实例化的形式创建一个单例的对象,而该对象具有自动刷新功能。
【Q-06】Spring Cloud 中大量地使用了条件注解,其中@ConditionalOnRefreshScope 注解对于Eureka Client 的创建非常重要。请谈一下你对这个注解的认识。
【RA】首先,关于条件注解,实际是 Spring Boot 中出现的内容,其一般应用于配置类中。表示只有当该条件满足时才会创建该实例。而您提到的@ConditionalOnRefreshScope 注解,其实际是 Eureka Client 的自动配置类中的一个内部注解。该注解不同于 Spring Boot 中的一般性注解的是,其是一个复合条件注解,其复合的条件有三个:
在当前类路径下具有 RefreshScope 类在容器中要具有 RefreshAutoConfiguration 类的实例指定的 eureka.client.refresh.enable 属性值为 true。不过,其缺省值就是 true。这也就是为什么我们的配置文件默认支持自动更新的原因。只有当这个复合注解中的三个条件均成立时,@ConditionalOnRefreshScope 注解才满足条件。此时才有可能会调用创建 Eureka Client 的@Bean 方法。所以,该注解对于 Eureka Client的创建非常重要。
【Q-07】你刚才已经谈过了对@ConditionalOnRefreshScope 注解的认识,非常不错。不过,与这个注解相对应的另一个注解@ConditionalOnMissingRefreshScope,你是否了解?若关注过,谈一下你的认识。
【RA】@ConditionalOnMissingRefreshScope 我也曾了解过。这个注解就像@ConditionalOnRefreshScope 注解一样,也是一个复合条件注解,其也包含了三个条件。不同的是,这个注解中的条件是或的关系,只要满足其中一条这个注解就匹配上了。而@ConditionalOnRefreshScope 注解中的三个条件是与的关系,必须所有条件均满足其才能匹配上。 这个或的关系是通过让一个复合条件类继承自一个能够表示或关系的复合条件父类AnyNestedCondition 实现的。这样的话,这个复合条件类中定义的多个内部条件类中,只要有一个匹配上,那么这个复合条件类就算匹配上了。
【Q-08】Spring Cloud 中 Eureka Client 的源码中有一个非常重要的类 Applications,其被称为客户端注册表。请谈一下你对它的认识。
【RA】Applications 类实例中封装了来自于 Eureka Server 的所有注册信息,通常称其为“客户端注册表”。只所以要强调“客户端”是因为,服务端的注册表不是这样表示的,是一个Map。 该类中封装着一个非常重要的 Map 集合,key 为微服务名称,而 Value 则为 Application实例。Application 类中封装了一个 Set 集合,集合元素为“可以提供该微服务的所有主机的InstanceInfo”。也就是说,Applications 中封装着所有微服务的所有提供者信息。
【Q-09】Eureka 源码中 InstanceInfo 类中具有两个最终修改时间戳,这两个时间戳对于 Eureka 的 Server 端与 Client 端源码的理解都比较重要。这两个时间戳你了解过吗?若了解过,请谈一下你对它们的认识。
【RA】InstanceInfo 实例中封装着一个 Eureka Client 的所有信息,其就可以代表了一个 Eureka Client。其封装的两个最终修改时间戳分别为 lastDirtyTimestamp 与 lastUpdatedTimestamp。这两个时间戳的区别是:
lastDirtyTimestamp:记录 intance 在 Client 被修改的时间。该修改会被传递到 Server 端。lastUpdatedTimestamp:记录 intance 状态在 Server 端被修改的时间。【Q-10】Eureka 源码中 InstanceInfo 类中具有两个状态属性,是哪两个,你了解过它们吗?
【RA】Eureka 源码中 InstanceInfo 类具有两个状态属性,分别是 status 与 overriddenStatus。下面我依次谈一下我对它们的了解。 status 就是当前 Instance 的工作状态,其初值为 UP,表示可以正常提供服务。 overriddenStatus 用于记录外部对当前 instance 修改的状态,初值为 UNKNOWN。这个状态仅在 Server 端是有意义的。其意义就是通过修改 Server 端 instanceInfo 的 overriddenStatus的值来达到修改 Server 端对应 instanceInfo 的 status 的值的目的。 若要深入了解 overriddenStatus 的意义,就需要了解其被修改的过程。当用户通过 Actuator 的 service-registry 监控终端向某个 Client 提交 POST 状态修改请求后,该请求会被 Client 接收并直接再以 PUT 请求的方式提交给 Server。Server 在接收到这个请求后,会从注册表中找到该 Client 对应的 InstanceInfo,修改其 overriddenStatus、status 为指定状态。注意,用户请求提交到 Client 后,Client 并未修改当前 Client 的 InstanceInfo 的任何状态。即 Client端的该 InstanceInfo 的状态一直为 UP 状态。 那么这个 overriddenStatus 有什么用呢?当 Server 端注册表中某 instanceInfo 的 status为 UP 时,其是可以被其它 instance 服务发现的。相反,若其 status 为非 UP,其它 instance 是无法发现和使用该 instance 提供的服务的。Server 端 InstanceInfo 的 status 是可以通过用户提交状态修改请求修改 overriddenStatus 而达到修改 status 的目的的。 例如,一旦将某 InstanceInfo 的 overriddenStatus 修改为了 OUT_OF_SERVICE,则其 Server端的 status 也就成了 OUT_OF_SERVICE,此时 Consumer 是无法调用到该 InstanceInfo 对应的 Provider 的服务的。不过需要注意,若直接从浏览器访问该 Provider,其是可以正常给出响应结果的,并且,此时查看 Provider 中的 InstanceInfo 的 status,仍为 UP。
【Q-11】Spring Cloud 中 Eureka Client 与 Eureka Server 的通信,及 Eureka Server 间的通信是如何实现的?请简单介绍一下。
【RA】Spring Cloud 中 Eureka Client 与 Eureka Server 的通信,及 Eureka Server 间的通信,均采用的是 Jersey 框架。 Jersey 框架是一个开源的 RESTful 框架,实现了 JAX-RS 规范。该框架的作用与 SpringMVC 是相同的,其也是用户提交 URI 后,在处理器中进行路由匹配,路由到指定的后台业务。这个路由功能同样也是通过处理器完成的,只不过这里的处理器不叫Controller,而叫Resource。
【Q-01】Spring Cloud 中 Eureka Client 在启动时需要从 Eureka Server 中下载注册表到本地进行缓存,以备进行负载均衡调用。请谈一下你对这个启动时下载注册表过程的认识。
【RA】Spring Cloud 中 Eureka Client 在启动时需要从 Eureka Server 中下载注册表到本地进行缓存。这次下载属于全量下载,即要将 Server 端所有注册信息 Applicaitons 全部下载到本地并缓存。当然,若指定可以从远程 Region 获取,其也会通过其所连接的这个 Server,将远程 Region 中的注册信息也全部获取到。这个过程称为获取客户端注册表。 获取“客户端注册表”最终执行的操作是,通过 Jersey 框架提交了一个 GET 请求,然后获取到的 Applications 实例结果。 若获取失败,其会从本地备用注册表中获取并缓存。
【Q-02】Spring Cloud 中 Eureka Client 需要定时从 Eureka Server 中获取注册表信息,这个过程称为服务发现。请谈一下你对这个获取过程的认识。
【RA】Eureka Client 从 Eureka Server 中获取注册表分为两种情况,一种是将 Server 中所有注册信息全部下载到当前客户端本地并进行缓存,这种称为全量获取;一种是仅获取在 Server 中发生变更的注册信息到本地,然后根据变更修改本地缓存中的注册信息,这种称为增量获取。当 Client 在启动时第一次下载就属于全量获取,而后期每 30 秒从 Server 下载一次的定时下载属于增量下载。无论是哪种情况,Client 都是通过 Jersey 框架向 Server 发送了一个GET 请求。只不过是,不同的获取方式,提交请求时携带的参数是不同的。
【Q-03】Spring Cloud 中 Eureka Client 需要注册到 Eureka Server 中,请谈一下你对这个注册过程的认识。
【RA】Eureka Client向Eureka Server提交的注册请求,实际是通过Jersey框架完成的一次POST提交,将当前 Client 的封装对象 intanceInfo 提交到 Server 端,写入到 Server 端的注册表中。 但这个注册请求在默认情况下并不是在Client启动时直接提交的,而是在Client向Server发送续约信息时,由于其未在 Server 中注册,所以 Server 会中其返回 404,在这种情况下,而引发的 Client 注册。当然,若 Client 的续约信息发生了变更 Client 也会提交注册请求。
【Q-04】Spring Cloud 中 Eureka Client 需要定时从 Eureka Server 中更新注册信息。对于这个定时器及其执行过程,请谈一下你的看法。
【RA】Spring Cloud 中 Eureka Client 需要定时从 Eureka Server 中更新注册信息。但这里是使用了 one-shot action 的一次性定时器实现的 repeated 定时执行。这个 repeated 过程是通过其一次性的定时任务实现的:当这个一次性定时任务执行完毕后,会调用启动下一次的定时任务。
【Q-05】Spring Cloud 中 Eureka Client 需要定时从 Eureka Server 中更新注册信息。这个定时任务是通过一个 one-shot action 的定时器完成的。其为什么不直接使用一个 repeated 的定时器呢?请谈一下你的看法。
【RA】Spring Cloud 中 Eureka Client 需要定时从 Eureka Server 中更新注册信息。这个定时任务是通过一个 one-shot action 的定时器完成的。只所以选择 one-shot action 的定时器来完成一个 repeated 的事情,其主要目的就是方便控制 delay,保证任务顺利完成。 定时器要执行的任务是通过网络从 Server 下载注册信息。而我们知道,网络传输存在不稳定性,不同的传输数据可能走的网络链路是不同的,而不同的链路的传输时间可能也是不同的。本次传输超时,下次重试可能走的链路不同就不超时了。 repeated 定时器的执行原理是,本次任务的开始,必须在上一次的任务完成后,不存在超时。即只要没完成就不会通过执行下次任务进行重试。 使用 one-shot action 定时器完成 repeated 定时任务时,若本次定时任务出现了超时,则可以在下次任务执行之前增大定时器的 delay。当然,若下载速率都很快,也可将已经增大的 delay 再进行减小。方便控制 delay,保证任务的顺利完成。
【Q-06】Future 是 JDK5 中提供的一个接口。其方法 cancel()是一个经常被用到的方法,请谈一下你对这个方法的认识。
【RA】Future 是 JDK5 中提供的一个 JUC 的接口,其用于执行一些异步任务。对于其执行的异步任务,可以通过 cancel()方法尝试着进行取消。其用法为:
若任务已经完成或已经取消,则本次取消失败。若任务在启动之前就调用了 cancel(),则这个任务将不会再执行。若任务已经启动,此时再执行 cancel(),那么这个执行的任务是否立即被中断,取决于cancel()方法的参数。若参数为 true,则会立即中断任务的执行;若参数为 false,则会让正在执行中的任务执行完毕,然后再中断。若cancel()方法执行完毕后,顺序执行isDone()或isCancelled()方法,这些方法均返回true。【Q-07】Future 是 JDK5 中提供的一个接口。其方法 get()是一个经常被用到的方法,请谈一下你对这个方法的认识。
【RA】Future 是 JDK5 中提供的一个 JUC 的接口,其用于执行一些异步任务,并通过 get()方法可以获得该异步任务的结果。get()方法是一个阻塞的方法,可以为其指定阻塞的最长时长。
若在指定时长内异步操作完成,则阻塞会被立即唤醒;若在指定时长内该异步操作被 cancel(),则阻塞也会被立即唤醒,并抛出一个CancellationException;若在指定时长内未发生任务事情,则阻塞也会被唤醒,并抛出一个TimeoutException;若 get()方法没有指定阻塞时长,则其会一直阻塞下去,直到任务完成或取消。【Q-08】Spring Cloud 中 Eureka Client 向 Eureka Server 发送增量获取注册表请求,Server 会返回给 Client 一个 delta,关于这个返回值 delta,请谈一下你的看法。
【RA】Spring Cloud 中 Eureka Client 向 Eureka Server 发送增量获取注册表请求,Server 会返回给 Client 一个 delta,这个 delta 是一个 Applications 类型的变量。
Server 返回的这个 delta 值若为 null,则表示 Server 端基于安全考虑,禁止增量下载,其会自动进行全量下载。Server 返回的这个 delta 值不为 null,但其包含的 application 数量为 0,则表示没有更新内容。若数量大于 0,则表示有更新内容。有更新内容,则需要将更新添加到本地缓存的注册表 applications 中。【Q-09】Spring Cloud 中 Eureka Client 向 Eureka Server 发送增量获取注册表请求,Server 端是如何知道哪些 intance 对这个 Client 来说是变更过的?请谈一下你的看法。
【RA】当 Eureka Server 接收到 Eureka Client 发送的增量获取注册表请求后,其并不知道哪些 instance 对于这个 Client 来说是更新过的。但是在 Server 中维护着一个“最近变更队列”,无论对于哪个 Client 的增量请求,Server 端都是将该队列中的 instance 变更信息发送给了Client。当然,Server 中有一个定时任务,当这个 instance 的变更信息不再属于“最近”时,会将该 instance 变更信息从队列中清除。 当 Client 接收到 Server 发送的增量变更信息后,Client 端有一种判断机制,可以判断出其接收到这个增量信息对它自己来说是否出现了更新丢失。即出现了队列中清除掉的变更信息,并没有更新到当前 Client 本地。若发生更新丢失,Client 会再发起全量获取,以保证 Client获取到的注册表是最完整的注册表。
【Q-10】Spring Cloud中Eureka Client从Eureka Server中增量获取到的注册信息,都是在Server端发生了变更的 instanceInfo 信息。这些变更信息中包含变更的类型。其中对于 ADDED 与 MODIFIED 这两种类型的变更,我们发现处理方式是相同的,为什么?请谈一下你的看法。
【RA】Eureka Client 接收到的来自 Server 的增量注册信息后,对于添加变更与修改变更的处理方式的确是相同的,都是采用了添加变更的处理方式。其实,无论是添加的 InstanceInfo 还是修改的 InstanceInfo,Client 首先都是根据该 InstanceInfo 的 id,从本地 InstanceInfo 的 Set集合中将其删除,然后再将新来的变更的 InstanceInfo 加入 Set 集合。只不过,对于添加变更,其在原来的 Set 集合中找不到其要删除的 InstanceInfo 而已。 只所以能够被根据 InstanceId 在 Set 集合中进行删除,是因为 Set 集合的元素 InstanceInfo重写了 equals()方法——根据 instanceId 进行元素相等判断。
【Q-11】Spring Cloud 中 Eureka Client 会向 Eureka Server 进行定时续约,请谈一下你对这个定时续约的认识。
【RA】Eureka Client 会向 Eureka Server 进行定时续约,即会进行定时心跳。其最终就是通过 Jersey 框架向 Eureka Server 提交一个 PUT 请求。该 PUT 请求没有携带任何请求体,而 Eureka Server 仅仅就是接收到一个 PUT 请求。但通过请求,Server 能够知道这个请求的发送者 instanceId。而 Eureka Server 就是通过这个发送者 instanceId 从注册表中查找其 instance 信息的。当然,Server 也给 Client 发送来了响应信息:若从 Server 的注册表中找到了该续约的 instance,则返回该 instanceInfo 实例;若没有找到,则返回 404。
【Q-01】Spring Cloud 中 Eureka Client 会定时检测 Client 的 instanceInfo 是否发生了变更。请谈一下你对这个定时任务的认识。
【RA】Eureka Client 会定时检测 Client 的 instanceInfo 是否发生了变更,其主要是检测了两样内容:一个是检测数据中心中的关于当前 instanceInfo 的信息是否变更,一个是检测配置文件中当前 instanceInfo 中的续约信息是否变更。只要发生了变更,则将变化后的信息发送给Server,这个发送执行的是 register()注册。
【Q-02】Spring Cloud 中 Eureka Client 向 Eureka Server 提交 registrer()注册请求的时机较多,请简单总结一下。
【RA】Eureka Client 向 Eureka Server 提交的 register()注册请求的情况有三种:
在 Client 实例初始化时直接提交 register()注册请求定时发送心跳时,服务端返回 404,此时 Client 会发出 registrer()注册请求定时更新 Client 续约信息给 Server 时,只要 Client 续约信息发生变更,其提交的就是register()注册请求【Q-03】Spring Cloud 中 Eureka Client 在做定时更新续约信息给 Server 时有一个定时任务,定时查看本地配置文件中的 instanceInfo 是否发生了变更。这个定时任务在 Eureka Client 启动时通过 start()启动了定时任务,该定时器是一个 one-shot action 定时器,其会调用 InstanceInfoReplicator 的 run()方法。而该方法会再次启动一个 one-shot action 的定时任务,实现了 repeated 定时执行。然而,当 instanceInfo 的状态发生变更后会调用一个按需更新方法 onDemandUpdate(),该方法同样会调用 InstanceInfoReplicator 的 run()方法,再次启动一个 one-shot action 的定时任务,实现了 repeated 定时执行。这样的话,只要发生一次状态变更,就会启动一个 repeated 的定时任务持续执行下去。那么若 InstanceInfo 的状态多次发生变更,是否就会启动很多的一直持续执行的该定时任务了?请谈一下你的看法。
【RA】答案当然是否定的。关键就在于两点:一个是,前面题目中无论是 start()方法还是 InstanceInfoReplicator 的 run()方法,在启动了定时任务后,都会将定时任务实例 future 写入到一个原子引用类型的缓存中,且后放入的会将先放入的覆盖,即这个缓存中存放的始终为最后一个定时任务。第二个关键点是 这 个 onDemandUpdate() 方法。其在调用 InstanceInfoReplicator 的 run()方法之前首先将这个缓存中的异步操作 cancel(),即将最后一个定时任务结束,然后才会再启动一个新的定时任务。所以,只会同时存在一个该定时任务。
【Q-04】Spring Cloud 中 Eureka Client 的续约配置信息默认情况下是允许动态变更的。为了限制变更的频率,Eureka Client 使用了一种限流策略,是什么策略?请谈一下你对这种策略的认识。
【RA】Spring Cloud 中 Eureka Client 的续约配置信息默认情况下是允许动态变更的。为了限制变更的频率,Eureka Client 使用了令牌桶算法。 该算法实现中维护着一个队列,首先所有元素需要进入到队列中,当队列满时,未进入到队列的元素将丢弃。进入到队列中的元素是否可以被处理,需要看其是否能够从令牌桶中拿到令牌。一个元素从令牌桶中拿到一个令牌,那么令牌桶中的令牌数量就会减一。若元素生成速率较快,则其从令牌桶中获取到令牌的速率就会较大。一旦令牌桶中没有了令牌,则队列很快就会变满,那么再来的元素将被丢弃。
【Q-05】Spring Cloud 中 Eureka Client 的哪些操作会引发客户端 InstanceInfo 的最新修改时间戳 lastDirtyTimestamp 的变化?请谈一下你的认识。
【RA】Spring Cloud 中 Eureka Client 的操作中有两处操作可以引发客户端 InstanceInfo 的最新修改时间戳 lastDirtyTimestamp 的变化。
在进行第一次心跳发送时,由于 Server 中没有发现该 InstanceInfo 而向其返回了 404。此时的 Client 会修改 lastDirtyTimestamp 的值。在续约信息发生更新时修改 lastDirtyTimestamp 的值。【Q-06】Spring Cloud 中 Eureka Client 通过 Actuator 提交的 POST 请求的 shutdown 进行服务下架时,其调用的下架处理方法我们从哪里找到它?请谈一下你的思路。
【RA】Spring Cloud 中 Eureka Client 通过 Actuator 提交的 POST 请求的 shutdown 进行服务下架,就是要销毁 Eureka Client 实例。而该 Eureka Client 实例是在 EurekaClient 的自动配置类中通过@Bean 方法创建的。所以,这个下架处理方法应该是@Bean 注解的 destroyMethod属性指定的方法。
【Q-07】Spring Cloud 中 Eureka Client 通过 Actuator 提交的 POST 请求的 shutdown 进行服务下架时,其 Client 内部都做了些什么重要工作?请谈一下你对这个服务下架的认识。
【RA】Eureka Client 通过 Actuator 提交的 POST 请求的 shutdown 进行服务下架时,其内部主要完成了四样工作:
将状态变更监听器删除停止了一堆定时任务的执行,并关闭了定时器通过 Jersey 框架向 Eureka Server 提交了一个 DELETE 请求关闭了一些其它相关工作【Q-08】对于 Spring Cloud 中的 Eureka,用户通过平滑上下线方式进行 Client 状态的修改。这个状态修改请求是被客户端的哪个类处理的,这个类实例是在何时创建的?请谈一下你的认识。
【RA】对于 Spring Cloud 中的 Eureka,用户通过平滑上下线方式进行 Client 状态的修改。这个状态修改请求是被客户端的 ServiceRegistryEndpoint 类实例的 setStatus()方法处理的。这个类实例是在 Spring Cloud 应用启动时被加载创建的。具体来说,spring-cloud-starter 依赖于spring-cloud-common,而该 common 依赖加载并实例化了 ServiceRegistryAutoConfigration 配置类。在该配置类中实例化了 ServiceRegistryEndpoint 类。
【Q-09】对于 Spring Cloud 中的 Eureka,用户通过 Actuator 的 service-registry 监控终端提交状态修改请求,请谈一下你对这个请求处理过程的认识。
【RA】对于 Spring Cloud 中的 Eureka,用户通过 Actuator 的 service-registry 监控终端提交状态修改请求,服务平滑上下线就属于这种情况,但这种情况不仅限于服务平滑上下线。 当 Client 提交的状态为 UP 或 OUT_OF_SERVICE 时,属于平滑上下线场景。该请求会被Client 接收并直接再以 PUT 请求的方式提交给 Server,在 Client 端并未修改 Client 的任何状态。Server 在接收到这个请求后,会从注册表中找到该 Client 对应的 InstanceInfo,修改其overriddenStatus、status 为指定状态。 当 Client 提交的状态为 CANCEL_OVERRIDE 时,是要将 Server 端当前 Client 对应 InstanceInfo 的 overriddenStatus 从一个缓存 map 中删除,并将其 overriddenStatus 与 status 修改为 UNKNOWN 状态。这个缓存 map 中记录着注册到当前 Server 中的每一个 instanceInfo 对应的 overriddenStatus,这个 map 中的状态值对于其它 Client 发现一个 InstanceInfo 的对外表现状态 status 非常重要。当服务端的某 InstanceInfo 的 overriddenStatus 变为 UNKNOWN 时,该 Client 发送的心跳 Server 是不接收的。Server 会向该 Client 返回 404。
【Q-10】对于 Spring Cloud 中的 Eureka,用户通过 Actuator 的 service-registry 监控终端提交状态修改请求,如果用户提交的状态是一个非法状态会怎么样?请谈一下你的认识。
【RA】对于 Spring Cloud 中的 Eureka,用户通过 Actuator 的 service-registry 监控终端提交状态修改请求,这个状态值一般为五个标准状态 UP、DOWN、OUT_OF_SERVICE、STARTING、UNKNOWN 中的 UP 或 OUT_OF_SERVICE,也可以是 CANCEL_OVERRIDE。但若用户提交的不是这六种情况之一,系统会将其最终归结为 UNKNOWN 状态。
【Q-11】Spring Cloud 中 EurekaServerAutoConfiguration 自动配置类被实例化的一个前提条件是,容器中要有一个 Marker 实例,这个 Marker 实例是在哪被添加到了容器?
【RA】Spring Cloud 中关于 Eureka Server 的这个 Marker 实例,就是 Eureka Server 的一个标识,一个开关。该实例被添加到了容器,Eureka Server 就开启了。其是在 Eureke Server 启动类上的@EnableEurekaServer 注解中被实例化并添加到的容器。所以,若没有添加该注解,Eureka Server 启动类的启动是不会创建启动 Eureka Server 的。
【Q-12】Spring Cloud 中 Eureka Client 会注册到 Eureka Server 的注册表中,在 Server 中这个注册表是以怎样的形式存在的?当Eureka Client将Eureke Server中的注册信息下载到本地后,这个注册表又是以怎样的形式存在的?请谈一下你的认识。
【RA】Spring Cloud 的 Eureka Server 中的这个注册表是一个双层 Map,外层 map 的 key 为微服务名称,value 为内层 map。内层 map 的 key 为 instanceId,value 为 Lease 对象。Lease 对象中包含一个执有者 Holder 属性,表示该 Lease 对象所属的 InstanceInfo。 当 Eureka Client 将 Server 端的注册表下载到了本地,该注册表是以 Applications 形式出现的。Applications 中维护着一上 Map 集合,key 为微服务名称,value 为 Application 实例。该 Application 实例中包含了所有提供该微服务名称的 InstanceInfo 信息。因为 Application 中也维护着一个 Map,key 为 instanceId,value 为 InstanceInfo。
【Q-13】Eureka Client 提交的状态修改请求,Eureka Server 是如何处理的,Server 端都做了哪些变更?请谈一下你的认识。
【RA】Eureka Client 提交的状态修改请求,Eureka Server 在接收到后,首先根据该 Client 的微服务名称及 instanceId 在 Server 端注册表中进行了查找。若没有找到,则直接返回 404;若找到了,其会执行两大任务:
任务一:将客户端修改请求中的新状态写入到注册表中。任务二:将写入到当前 server 注册表中新的状态同步到其它 server。而在第一项任务中完成的重要工作有如下几项:
使用新状态替换缓存 overriddenInstanceStatusMap 中的老状态修改 instanceInfo 的 overriddenStatus 状态为新状态修改 instanceInfo 的 status 状态为新状态更新来自于请求参数的客户端修改时间戳 lastDirtyTimestamp将本次修改形为记录到 instanceInfo 的行为类型中修改服务端修改时间戳 lastUpdatedTimestamp将本次修改记录到最近更新队列 recentlyChanngeQueue 中【Q-14】Eureka Server 中的注册表发生了变更,其是怎样将变更同步到其它 Server 的?请谈一下你的看法。
【RA】Eureka Server 中的注册表发生了变更,并不是一定要同步给其它 Server 的,需要分情况处理。 若这个变更是由 Client 端直接引发,则当前 Server 会遍历所有其它 Server,通过 Jersey框架向每一个其它 Server 发送变更请求。这样就实现了同步。 若这个变更是由其它 Server 端发送的变更,则其仅仅会在本地变更一下即可,不会再向其它 Server 发送变更请求,以防止出现无限递归。