【SpringCloud 技术专题】「Eureka 源码分析」从源码层面让你认识 Eureka 工作流程和运作机制(上)
前言介绍
了解到了 SpringCloud,大家都应该知道注册中心,而对于我们从过去到现在,SpringCloud 中用的最多的注册中心就是 Eureka 了,所以深入 Eureka 的原理和源码,接下来我们要进行讲解下 eureka 的源码分析,由此应运而产生的本章节的内容。
基本原理
Eureka Server 提供服务注册服务,各个节点启动后,会在 Eureka Server 中进行注册,这样 Eureka Server 中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Client 是一个 Java 客户端,用于简化与 Eureka Server 的交互,客户端同时也具备一个内置的、使用轮询负载算法的负载均衡器。
在应用启动后,将会向 Eureka Server 发送心跳(默认周期为 30 秒),如果 Eureka Server 在多个心跳周期(默认 3 个周期)没有收到某个节点的心跳,Eureka Server 将会从服务注册表中把这个服务节点移除(默认 90 秒)。
Eureka Server 之间将会通过复制的方式完成数据的同步;
Eureka Client 具有缓存的机制,即使所有的 Eureka Server 都挂掉的话,客户端依然可以利用缓存中的信息消费其它服务的 API;
启动流程分析
从 EurekaServer 启动的流程日志入手分析:
Eureka 微服务已启动.
日志打印“Setting the eureka configuration..”,eureka 开始进行配置,说不定也许就是 Eureka Server 流程启动的开始呢?我们抱着怀疑的心态进入这行日志打印的 EurekaServerBootstrap 类去看看。
EurekaServerBootstrap 类
看看,看这个类的名字,见名知意,应该就是 EurekaServer 的启动类了:
initEurekaEnvironment 方法
我们看到日志在 initEurekaEnvironment 方法中被打印出来,然后我顺着这个方法寻找该方法被调用的地方;
contextInitialized
接着发现 contextInitialized 这个方法里面调用了 initEurekaEnvironment 方法,接着我们再往上层寻找被调用的地方;
EurekaServerInitializerConfiguration
接着我们看到 EurekaServerInitializerConfiguration 类中有个 start 方法,该方法创建了一个线程来后台执行 EurekaServer 的初始化流程;
进入 EurekaServerInitializerConfiguration 方法,看看这个所谓的 EurekaServer 初始化配置做了哪些事情?
看到 log.info("Started Eureka Server"); 这行代码,相信大家已经释然了,这里就是所谓的启动了 EurekaServer 了,其实也就是 eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext) 初始化了一些我们未知的东西;
当打印完启动 Eureka Server 日志后,调用了两次 publish 方法,该方法最终调用的是 this.applicationContext.publishEvent(event) 方法,目的是利用 Spring 中 ApplicationContext 对事件传递性质,事件发布者(applicationContext)来发布事件(event),但是缺少的是监听者,其实你仔细搜索下代码,发现好像没有地方对 EurekaServerStartedEvent、EurekaRegistryAvailableEvent 进行监听。
然后找到 EurekaServerStartedEvent 所在的目录下,EurekaInstanceCanceledEvent、EurekaInstanceRegisteredEvent、EurekaInstanceRenewedEvent、EurekaRegistryAvailableEvent、EurekaServerStartedEvent 有这么几个事件的类,服务下线事件、服务注册事件、服务续约事件、注册中心启动事件、Eureka Server 启动事件,这么几个事件都没有被监听。
像这样 @EventListener public void listen(EurekaInstanceCanceledEvent event) {下线逻辑 },添加 EventListener 监听注解,就可以在我们自己的代码逻辑中收到这个事件的回调了,所以想想 SpringCloud 还是挺机制的,提供回调接口让我们自己实现自己的业务逻辑,真心不错;
那么反过来想想,为啥会无缘无故 start 方法就被调用了呢?那么反向继续向上找调用 start 方法的地方,结果找到了 DefaultLifecycleProcessor 类的 doStart 方法调用了 bean.start();
DefaultLifecycleProcessor
EurekaServerInitializerConfiguration.start 方法是如何被触发的?
看到在 bean.isRunning 等一系列状态的判断下才去调用 bean.start() 方法的,我们再往上寻找被调用地方;
该类是 DefaultLifecycleProcessor 中内部类 LifecycleGroup 的一个方法,再往上寻找调用方;
startBeans 属于 DefaultLifecycleProcessor 类的一个私有方法,startBeans 方法第一行就是获取 getLifecycleBeans() 生命周期 Bean 对象,由此可见似乎 Eureka Server 之所以会被启动,是不是实现了某个接口或者重写了某个方法,才会导致由于容易在初始化的过程中因调用某些特殊方法或者某些类才启动的,因此我们回头去看看 EurekaServerInitializerConfiguration 这个类;
结果发现 EurekaServerInitializerConfiguration 这个类实现了 SmartLifecycle 这么样的一个接口,而 SmartLifecycle 接口又继承了 Lifecycle 生命周期接口类,所以真想已经重见天日了,原来是实现了 Lifecycle 这样的一个接口,然后实现了 start 方法,因此 Eureka Server 就这么稀里糊涂的就被莫名其妙的启动起来了?
我们之前仅仅只是通过了日志来逆向分析,但是我们是不是忘了我们本应该标志是 Eureka Server 的这个注解了呢?没错,我们在分析的过程中已经将 @EnableEurekaServer 这个注解遗忘了,那么我们现在先回到这个注解类来看看;
EnableEurekaServer
我们不难发现 EnableEurekaServer 类上有个 @Import 注解,引用了一个 class 文件,由此我们进入观察;
EurekaServerConfiguration 类看看,看名称的话,理解的意思大概就是 Eureka Server 配置类;
果不其然,这个类有很多 @Bean、@Configuration 注解过的方法,那是不是我们可以认为刚才 3.1~3.4 的推论是不是就是由于被实例化了这么一个 Bean,然后就慢慢的调用到了 start 方法了呢?
搜索 “Bootstrap” 字样,还真发现了有这么一个方法;
既然有这么一个 Bean,那么是不是和刚开始顺着日志逆向分析也是有一定道理的,没有这么一个 Bean 的存在,那么 DefaultLifecycleProcessor.startBeans 方法中 getLifecycleBeans 的这个也就没那么顺畅被找到了呢?不过我的猜想是这样的,本人没有将源码下载下来,将 eurekaServerBootstrap 方法中的 @Bean 注解注释掉试试,不过推理起来也八九不离十,这个疑问悬念就留给大家尝试尝试吧;
既然找到了一个 @Bean 注解过的方法,那我们再找找其他的一些被注解过的方法,比如一些通用全局用的类似词眼,比如 Context,Bean,Init、Server 之类的;
DefaultEurekaServerContext.initialize 初始化了一些东西,现在还不知道干啥用的,先放这里,打上断点;
PeerEurekaNodes.start 方法,又是一个 start 方法,但是该类没有实现任何类,姑且先放这里,打上断点;
InstanceRegistry.register 方法,而且还有几个呢,可能是客户端注册用的,也先放这里,都打上断点,或者将 这个类的所有方法都断点上,断点打完后发现有注册的,有续约的,有注销的;
打完这些断点后,感觉没有思路了,索性就断点跑一把,看看有什么新的发现点;
start 方法中会看到一个定时调度的任务,updatePeerEurekaNodes(resolvePeerUrls()); 间隔 600000 毫秒,即 10 分钟 间隔执行一次服务集群数据同步;
然后断点放走放下走,进入 initialize 方法中 registry.init(peerEurekaNodes);
缓存也配置好了,定时任务也配置好了,似乎应该没啥了,那么我们把断点放开,看看下一步会走到哪里?
EurekaServerInitializerConfiguration.start。
先是 DefaultLifecycleProcessor.doStart 方法进断点,然后才是 EurekaServerInitializerConfiguration.start 方法进断点;
再一次证明刚刚的逆向分析仅仅只是缺了个从头 EnableEurekaServer 分析罢了,但是最终方法论分析思路还是对的,由于开始分析过这里,然而我们就跳过,继续放开断点向后继续看看;
InstanceRegistry.openForTraffic
【这不就是我们刚才在 “步骤 3.7 之分析七” 打的断点么?看下堆栈信息,正是 “步骤 3.2 之分析一” 中 initEurekaServerContext 方法中有这么一句 this.registry.openForTraffic(this.applicationInfoManager, registryCount); 调用到了,因果轮回,代码千变万化,打上断点还有有好处的,结果还是回到了开始日志逆向分析的地方。
c 进入 super.openForTraffic 方法;
这里主要设置了服务状态,以及开启了定时清理失效节点的定时任务,每分钟扫描一次;
继续放开断点,来到了日志打印 “main] c.n.e.EurekaDiscoveryClientConfiguration : Updating port to 8761” 的 EurekaDiscoveryClientConfiguration 类中 onApplicationEvent 方法。
设置端口,当看到 Updating port to 8761 这样的日志打印出来的话,说明 Eureka Server 整个启动也就差不多 Over 了。现在回头看看,发现分析了不少的方法和流程,有种感觉被掏空的感觉了。
总结 EurekaServer 启动时候大概干了哪些事情?
1、初始化 Eureka 环境,Eureka 上下文;2、初始化 EurekaServer 的缓存 3、启动了一些定时任务,比如充值心跳阈值定时任务,清理失效节点定时任务;4、更新 EurekaServer 上电状态,更新 EurekaServer 端口;
版权声明: 本文为 InfoQ 作者【李浩宇/Alex】的原创文章。
原文链接:【http://xie.infoq.cn/article/fa7d62e2cc54bbe750ce1da8e】。文章转载请联系作者。
评论