最近在倒腾 Eureka 源码,大环境太卷了,必须得卷点源码才行,另外呢,能够读懂开源项目的源码、解决项目中遇到的问题是实力的象征,是吧?如果只是会用些中间件,那是不够的,和 CRUD 区别不大。
话不多说,源码走起。本篇是 Eureka 源码分析的开篇,后续会持续分享源码解析的文章。
首先呢,Eureka 服务的启动入口在这里:EurekaBootStrap.java 的 contextInitialized 方法。
启动的整个流程图在文末,不想看源码剖析的,可以直接看文末流程图。
关于源码的获取直接到官网下载就好了。https://github.com/Netflix/eureka
本文已收录到我的 github:https://github.com/Jackson0714/PassJava-Learning
打开源码,找到这个启动类,EurekaBootStrap.java,在这个路径下:
\eureka\eureka-core\src\main\java\com\netflix\eureka\EurekaBootStrap.java
启动时序图给大家画好了:
初始化环境时序图启动代码:
@Override
public void contextInitialized(ServletContextEvent event) {
initEurekaEnvironment();
initEurekaServerContext();
// 省略非核心代码
}
分为两步,初始化环境和初始化上下文,先来看第一步。
初始化环境的方法是 initEurekaEnvironment(),点进去看下这个方法做了什么。
String dataCenter = ConfigurationManager.
getConfigInstance().getString(EUREKA_DATACENTER);
就是获取配置管理类的一个单例。单例的实现方法用的是 双重检测
+volatile
public static AbstractConfiguration getConfigInstance() {
if (instance == null) {
synchronized (ConfigurationManager.class) {
if (instance == null) {
instance = getConfigInstance(false));
}
}
}
return instance;
}
instance 变量定义成了 volatile,保证可见性。
static volatile AbstractConfiguration instance = null;
线程 A 修改后,会将变量的值刷到主内存中,线程 B 会将主内存中的值刷回到自己的线程内存中,也就是说线程 A 改了后,线程 B 可以看到改了后的值。
可以参考之前我写的文章:[反制面试官 - 14 张原理图 - 再也不怕被问 volatile]
初始化上下文的时序图如下:
初始化上下文的时序图
还是在 EurekaBootStrap.java 类中 contextInitialized 方法中,第二步调用了 initEurekaServerContext() 方法。
initEurekaServerContext 里面主要的操作分为六步:
第一步就是加载 配置文件。
基于接口的方式,获取配置项。
initEurekaServerContext 方法创建了一个 eurekaServerConfig 对象:
EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig();
EurekaServerConfig 是一个接口,里面定义了很多获取配置项的方法。和定义常量来获取配置项的方式不同。比如获取 AccessId 和 SecretKey。
String getAWSAccessId();
String getAWSSecretKey();
还有另外一种获取配置项的方式:Config.get(Constants.XX_XX),这种方式和上面的接口的方式相比:
new DefaultEurekaServerConfig (),会创建出一个默认的 server 配置,构造方法会调用 init 方法:
public DefaultEurekaServerConfig() {
init();
}
private void init() {
String env = ConfigurationManager.getConfigInstance().getString(
EUREKA_ENVIRONMENT, TEST);
ConfigurationManager.getConfigInstance().setProperty(
ARCHAIUS_DEPLOYMENT_ENVIRONMENT, env);
String eurekaPropsFile = EUREKA_PROPS_FILE.get();
try {
// ConfigurationManager
// .loadPropertiesFromResources(eurekaPropsFile);
ConfigurationManager
.loadCascadedPropertiesFromResources(eurekaPropsFile);
} catch (IOException e) {
logger.warn(
"Cannot find the properties specified : {}. This may be okay if there are other environment "
+ "specific properties or the configuration is installed with a different mechanism.",
eurekaPropsFile);
}
}
前两行是设置环境名称,后面几行是关键语句:获取配置文件,并放到 ConfigurationManager 单例中。
来看下 EUREKA_PROPS_FILE.get(); 做了什么。首先 EUREKA_PROPS_FILE 是这样定义的:
private static final DynamicStringProperty EUREKA_PROPS_FILE =
DynamicPropertyFactory
.getInstance().getStringProperty("eureka.server.props",
"eureka-server");
用单例工厂 DynamicPropertyFactory 设置了默认值 eureka-server,然后 EUREKA_PROPS_FILE.get() 就会从缓存里面这个默认值。
然后再调用 loadCascadedPropertiesFromResources 方法,来加载配置文件。
首先会拼接默认的配置文件:
String defaultConfigFileName = configName + ".properties";
然后获取默认配置文件的配置项:
Properties props = getPropertiesFromFile(url);
然后再拼接当前环境的配置文件
String envConfigFileName =
configName + "-" + environment + ".properties";
然后获取环境的配置文件的配置项并覆盖之前的默认配置项。
props.putAll(envProps);
putAll 方法就是将这些属性放到一个 map 中。
然后这些配置项统一都交给 ConfigurationManager 来管理:
config.loadProperties(props);
其实就是加载这个文件:
eureka-server.properties
打开这个文件后,发现里面有几个 demo 配置项,不过都被注释了。
上面可以看到 eureka-server.properties 都是空的,那配置项都配置在哪呢?
我们之前说过,DefaultEurekaServerConfig 是实现了 EurekaServerConfig 接口的,如下所示:
public class DefaultEurekaServerConfig implements EurekaServerConfig
在 EurekaServerConfig 接口里面定义很多 get 方法,而 DefaultEurekaServerConfig 实现了这些 get 方法,来看下怎么实现的:
@Override
public int getWaitTimeInMsWhenSyncEmpty() {
return configInstance.getIntProperty(
namespace + "waitTimeInMsWhenSyncEmpty", (1000 * 60 * 5)).get();
}
里面的类似这样的 getXX 的方法,都有一个 default value,比如上面的是 1000 * 60 * 5,所以我们可以知道,配置项是在 DefaultEurekaServerConfig 类中定义的。
configInstance 这个单例又是 DynamicPropertyFactory 类型的,而在创建 configInstance 单例的时候,ConfigurationManager 还做了一些事情:将配置文件中的配置项放到 DynamicPropertyFactory 单例中,这样的话,DefaultEurekaServerConfig 中的 get 方法就可以获取到配置文件中的配置项了。具体的代码在 DynamicPropertyFactory 类中的 initWithConfigurationSource 方法中。
结合上面的加载配置文件的分析,可以得出结论:如果配置文件中没有配置,则用 DefaultEurekaServerConfig 定义的默认值。
(1)创建一个 DefaultEurekaServerConfig 对象,实现了 EurekaServerConfig 接口,里面有很多获取配置项的方法。
(2)DefaultEurekaServerConfig 构造函数中调用了 init 方法。
(3)init 方法会加载 eureka-server.properties 配置文件,把里面的配置项都放到一个 map 中,然后交给 ConfigurationManager 来管理。
(4)DefaultEurekaServerConfig 对象里面有很多 get 方法,里面通过 hard code 定义了配置项的名称,当调用 get 方法时,调用的是 DynamicPropertyFactory 的获取配置项的方法,这些配置项如果在配置文件中有,则用配置项的。配置文件中的配置项是通过 ConfigurationManager 赋值给 DynamicPropertyFactory 的。
(5)当要获取配置项时,就调用对应的 get 方法,如果配置文件没有配置,则用默认值。
结构如下图所示:
创建了一个 ApplicationInfoManager 对象,服务配置管理器,Application 可以理解为一个 Eureka client,作为一个应用程序向 Eureka 服务注册的。
applicationInfoManager = new ApplicationInfoManager(
instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get());
创建这个对象时,传了 instanceConfig,这个就是 eureka 实例的配置。这个 instanceConfig 和之前讲过的 EurekaServerConfig 很像,都是实现了一个接口,通过接口的 getXX 方法来获取配置信息。
另外一个参数是 EurekaConfigBasedInstanceInfoProvider,这个 Provider 是用来构造 instanceInfo(服务实例)。
怎么构造出来的呢?用到了设计模式中的构造器模式
,而用到的配置信息就是从 EurekaInstanceConfig 里面获取到的。
InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder(vipAddressResolver);
builder.setXX
...
instanceInfo = builder.build();
setXX 的代码如下所示:
setXX 示例
(1)初始化服务实例的配置 instanceConfig 。
(2)用构造器模式初始化服务实例 instanceInfo。
(3)将 instanceConfig 和 instanceInfo 传给了 ApplicationInfoManager,交由它来管理。
eurekaClient 是包含在 eureka-server 服务中的,用来跟其他 eureka-server 进行通信的。为什么还会有其他 eureka-server,因为在集群环境中,是会有多个 eureka 服务的,而服务之间是需要相互通信的。
初始化 eureka-client 代码:
EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig();
eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
第一行又是初始化了一个配置,和之前初始化 server config,instance config 的地方很相似。也是通过接口方法里面的 DynamicPropertyFactory 来获取配置项的值。
eureka-client 也有一个加载配置文件的方法:
Archaius1Utils.initConfig(CommonConstants.CONFIG_FILE_NAME);
这个文件就是 eureka-client.properties。
初始化配置的时候还初始化了一个 DefaultEurekaTransportConfig(),可以理解为传输的配置。
再来看下第二行代码,创建了一个 DiscoveryClient 对象,赋值给了 eurekaClient。
创建 DiscoveryClient 对象的过程非常复杂,我们来细看下。
(1)拿到 eureka-client 的 config 、transport 的 config、instance 实例信息。
(2)判断是否要获取注册表信息,默认会获取。
if (config.shouldFetchRegistry())
如果在配置文件中定义了 fetch-registry: false,则不会获取,单机 eureka 情况下,配置为 false,因为自己就包含了注册表信息,而且也不需要从其他 eureka 实例上获取配置信息。当在集群环境下,才需要获取注册表信息。
(3)判断是否要把自己注册到其他 eureka 上,默认会注册。
if (config.shouldRegisterWithEureka())
单机情况下,配置 register-with-eureka: false。
(4)创建了一个支持任务调度的线程池。
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
(5)创建了一个支持心跳检测的线程池。
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
(6)创建了一个支持缓存刷新的线程池。
cacheRefreshExecutor = new ThreadPoolExecutor(
1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
(7)创建了一个支持 eureka client 和 eureka server 进行通信的对象
eurekaTransport = new EurekaTransport();
(8)初始化调度任务
initScheduledTasks();
这个里面就会根据 fetch-registry 来判断是否需要开始调度执行刷新注册表信息,默认 30 s 调度一次。这个刷新的操作是由一个 CacheRefreshThread 线程来执行的。
同样的,也会根据 register-with-eureka 来判断是否需要开始调度执行发送心跳,默认 30 s 调度一次。这个发送心跳的操作由一个 HeartbeatThread 线程来执行的。
然后还创建了一个实例信息的副本,用来将自己本地的 instanceInfo 实例信息传给其他服务。什么时候发送这些信息呢?
又创建了一个监听器 statusChangeListener,这个监听器监听到状态改变时,就调用副本的 onDemandUpdate() 方法,将 instanceInfo 传给其他服务。
创建了一个 PeerAwareInstanceRegistryImpl 对象,通过名字可以知道是可以感知集群实例注册表的实现类
。通过官方注释可以知道这个类的作用:
registry = new PeerAwareInstanceRegistryImpl(
eurekaServerConfig,
eurekaClient.getEurekaClientConfig(),
serverCodecs,
eurekaClient
);
PeerAwareInstanceRegistryImpl 继承 AbstractInstanceRegistry 抽象类,构造函数主要做了以下事情:
this.serverConfig = serverConfig;
this.clientConfig = clientConfig;
this.recentCanceledQueue = new CircularQueue<Pair<Long, String>>(1000);
this.recentRegisteredQueue = new CircularQueue<Pair<Long, String>>(1000);
创建了一个 PeerEurekaNodes,它是一个帮助类,来管理集群节点的生命周期。
this.recentRegisteredQueue = new CircularQueue<Pair<Long, String>>(1000);
创建了一个 DefaultEurekaServerContext 默认上下文。
serverContext = new DefaultEurekaServerContext(
eurekaServerConfig,
serverCodecs,
registry,
peerEurekaNodes,
applicationInfoManager
);
创建了一个 holder,用来持有上下文。其他地方想要获取上下文,就通过 holder 来获取。用到了单例模式。
EurekaServerContextHolder.initialize(serverContext);
holder 的 initialize() 初始化方法是一个线程安全的方法。
public static synchronized void initialize(EurekaServerContext serverContext) {
holder = new EurekaServerContextHolder(serverContext);
}
定义了一个静态的私有的 holder 变量
private static EurekaServerContextHolder holder;
其他地方想获取 holder 的话,就通过 getInstance() 方法来获取 holder。
private static EurekaServerContextHolder holder;
然后想要获取上下文的就调用 holder 的 getServerContext() 方法。
public EurekaServerContext getServerContext() {
return this.serverContext;
}
调用 serverContext 的 initialize() 方法来初始化。
public void initialize() throws Exception {
logger.info("Initializing ...");
peerEurekaNodes.start();
registry.init(peerEurekaNodes);
logger.info("Initialized");
}
peerEurekaNodes.start();
这个里面就是启动了一个定时任务,将集群节点的 URL 放到集合里面,这个集合不包含本地节点的 url。每隔一定时间,就更新 eureka server 集群的信息。
registry.init(peerEurekaNodes);
这个里面会初始化注册表,将集群中的 注册信息获取下,然后放到注册表里面。
int registryCount = registry.syncUp();
EurekaMonitors.registerAllStats();
An exception occurred applying plugin request [id: 'nebula.netflixoss', version: '3.6.0']
解决方案
plugins {
id 'nebula.netflixoss' version '5.1.1'
}
eureka-server-governator Plugin with id 'jetty' not found.
参考 https://blog.csdn.net/Sino_Crazy_Snail/article/details/79300058
来一份 Eureka 启动的整体流程图
Eureka 启动过程
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/frgG6oprX699X2irrtdU_g
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。