eureka 源码
本文为eureka学习笔记,错误之处请指正。
-----------------------------------------------------------
@Configuration
@EurekaClientAutoConfiguration.ConditionalOnMissingRefreshScope
protected static class EurekaClientConfiguration {
//省略部分代码
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = {EurekaClient.class},search = SearchStrategy.CURRENT)
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config) {//用于跟服务器交互的客户端实例,实际是DiscoveryClient的一个子类(spring cloud对eureka自己进行了一些封装)
return new CloudEurekaClient(manager, config, this.optionalArgs, this.context);
}
@Bean
@ConditionalOnMissingBean( value = {ApplicationInfoManager.class}, search = SearchStrategy.CURRENT ) //在没有ApplicationInfoManager的实例的时候创建
public ApplicationInfoManager eurekaApplicationInfoManager(EurekaInstanceConfig config) {
InstanceInfo instanceInfo = (new InstanceInfoFactory()).create(config); //在这里创建了instanceInfo
return new ApplicationInfoManager(config, instanceInfo);
}
}
public InstanceInfo create(EurekaInstanceConfig config) {
Builder leaseInfoBuilder = Builder.newBuilder().setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds()).setDurationInSecs(config.getLeaseExpirationDurationInSeconds());
com.netflix.appinfo.InstanceInfo.Builder builder = com.netflix.appinfo.InstanceInfo.Builder.newBuilder();
builder.setNamespace(namespace).setAppName(config.getAppname()) // 这里用config的信息进行了大量的设置
.setInstanceId(config.getInstanceId())
.setAppGroupName(config.getAppGroupName())
.setDataCenterInfo(config.getDataCenterInfo())
.setIPAddr(config.getIpAddress()).setHostName(config.getHostName(false))
.setPort(config.getNonSecurePort())
.enablePort(InstanceInfo.PortType.UNSECURE,
config.isNonSecurePortEnabled())
.setSecurePort(config.getSecurePort())
.enablePort(InstanceInfo.PortType.SECURE, config.getSecurePortEnabled())
.setVIPAddress(config.getVirtualHostName())
.setSecureVIPAddress(config.getSecureVirtualHostName())
.setHomePageUrl(config.getHomePageUrlPath(), config.getHomePageUrl())
.setStatusPageUrl(config.getStatusPageUrlPath(),
config.getStatusPageUrl())
.setHealthCheckUrls(config.getHealthCheckUrlPath(),
config.getHealthCheckUrl(), config.getSecureHealthCheckUrl())
.setASGName(config.getASGName());
// 省略代码若干...
return instanceInfo;
}
其中,方法参数EurekaInstanceConfig的实际实现是EurekaInstanceConfigBean,类注解如下:
@ConfigurationProperties("eureka.instance")//读取配置文件中前缀为eureka.instance的内容
public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, EnvironmentAware {
private static final String UNKNOWN = "unknown";
private HostInfo hostInfo;
private InetUtils inetUtils;
......
}
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: " + renewalIntervalInSecs);
// Heartbeat timer
scheduler.schedule( //心跳检测的代码
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
//省略代码若干......
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
}
我们看到该方法启动了一个线程,new HeartbeatThread的run方法代码如下:
private class HeartbeatThread implements Runnable {
public void run() {
if (renew()) { //这个renew实际就是心跳检测的过程
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
renew方法代码:
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);//发送心跳请求
logger.debug("{} - Heartbeat status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
if (httpResponse.getStatusCode() == 404) {//返回状态码400,不存在,说明服务端没有信息,要进行注册操作
REREGISTER_COUNTER.increment();
logger.info("{} - Re-registering apps/{}", PREFIX + appPathIdentifier, instanceInfo.getAppName());
return register();//进行注册
}
return httpResponse.getStatusCode() == 200;//服务端正常返回,心跳检测成功
} catch (Throwable e) {
logger.error("{} - was unable to send heartbeat!", PREFIX + appPathIdentifier, e);
return false;
}
}
spring cloud中eureka的client跟server端都使用jersey进行交互,在server端,EurekaServerAutoConfiguration中的jerseyFilterRegistration方法动态注册了一个Jersey filter,用来拦截所有请求。具体对请求的处理是由ApplicationResource类的addInstance方法来完成的。
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info, @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
// validate that the instanceinfo contains all the necessary required fields
//参数校验代码 // handle cases where clients may be registering with bad DataCenterInfo with missing data
//高可用部分代码 registry.register(info, "true".equals(isReplication));
return Response.status(204).build(); // 204 to be backwards compatible
}
父类InstanceRegistry的register是具体的执行代码,这个register方法干了两件事儿:1、发布了一个事件,2、进行了一个注册操作。再父类PeerAwareInstanceRegistryImpl的主要功能是将注册操作分发到eureka server集群的其它节点,以保持数据的一致性。继续一步步点击进入,最终的注册由AbstractInstanceRegistry类的register完成,部分代码如下:
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>(); //这个是map里边套了个map,键为服务名称
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock();
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication);
if (gMap == null) {
final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();//这个是内部的map,键为示例名称,值为实例的具体信息
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
}
//其它代码
}
可以看到,最终服务端维护了一个ConcurrentHashMap,key为服务名值为该服务的实例组成的一个map,子map中key为实例名。
@PostConstruct
@Override
public void initialize() throws Exception {
logger.info("Initializing ...");
peerEurekaNodes.start(); //服务启动,刷新peer集群列表并启动更新任务
registry.init(peerEurekaNodes);
logger.info("Initialized");
}
其中start方法主要是启动了一个线程,每隔一段时间执行一次updatePeerEurekaNodes(List<String> newPeerUrls)方法,该方法主要作用是更新集群节点信息,添加新节点,删除关闭的节点。
public void register(final InstanceInfo info, final boolean isReplication) {
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
super.register(info, leaseDuration, isReplication);
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication); //复制注册信息到其它节点,isReplication用于防止复制自身,造成集群间节点相互复制
}
当然,信息的同步过程,不止在注册发生,在服务的续约(renew),下架(cancle)均会进行同步。
public void evict(long additionalLeaseMs) {
//其它代码
// We collect first all expired items, to evict them in random order. For large eviction sets,
// if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
// the impact should be evenly distributed across all applications.
List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
if (leaseMap != null) {
for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
Lease<InstanceInfo> lease = leaseEntry.getValue();
if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) { //判断服务失效
expiredLeases.add(lease);
}
}
}
}
//其它代码
}
public boolean isExpired(long additionalLeaseMs) {
return (evictionTimestamp > 0 || System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs)); //最后更新时间+lease的存活时间+补偿时间< 当前时间
}
5、自我保护机制
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
try {
// If the delta is disabled or if it is the first time, get all applications
Applications applications = getApplications();
if (clientConfig.shouldDisableDelta() || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress())) || forceFullRegistryFetch || (applications == null) || (applications.getRegisteredApplications().size() == 0) || (applications.getVersion() == -1)) {//Client application does not have latest library supporting delta
logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta());
logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress());
logger.info("Force full registry fetch : {}", forceFullRegistryFetch);
logger.info("Application is null : {}", (applications == null));
logger.info("Registered Applications size is zero : {}", (applications.getRegisteredApplications().size() == 0));
logger.info("Application version is -1: {}", (applications.getVersion() == -1));
getAndStoreFullRegistry();//全量数据
} else {
getAndUpdateDelta(applications);//增量数据
}
applications.setAppsHashCode(applications.getReconcileHashCode());
logTotalInstances();
} catch (Throwable e) {
logger.error(PREFIX + appPathIdentifier + " - was unable to refresh its cache! status = " + e.getMessage(), e);
return false;
} finally {
if (tracer != null) {
tracer.stop();
}
}
// Notify about cache refresh before updating the instance remote status
onCacheRefreshed();
// Update remote status based on refreshed data held in the cache
updateInstanceRemoteStatus();
// registry was fetched successfully, so return true
return true;
}
代码看出,根据disable-delta的值以及是否是初次获取数据来判断是否全量获取数据,如果disable-delta为true(禁用delta增量更新),则以后每次获取均为全量获取,默认为false,也就是除了第一次,以后都是增量获取数据。进入getAndStoreFullRegistry,再进入获取httpResponse的eurekaTransport.queryClient.getApplications(remoteRegionsRef.get(),跟踪可进入SessionedEurekaHttpClient的execute方法:
protected <R> EurekaHttpResponse<R> execute(RequestExecutor<R> requestExecutor) {
long now = System.currentTimeMillis();
long delay = now - lastReconnectTimeStamp;
if (delay >= currentSessionDurationMs) { //这里判断超时就创建新的session,实际是定期创建session,目的是防止一个客户端一直连接某个server,在server增加时不利于负载均衡的处理
logger.debug("Ending a session and starting anew");
lastReconnectTimeStamp = now;
currentSessionDurationMs = randomizeSessionDuration(sessionDurationMs);
TransportUtils.shutdown(eurekaHttpClientRef.getAndSet(null));
}
//省略代码
return requestExecutor.execute(eurekaHttpClient); //还得进入看实现
}
private Applications filterAndShuffle(Applications apps) {
if (apps != null) {
if (isFetchingRemoteRegionRegistries()) {
Map<String, Applications> remoteRegionVsApps = new ConcurrentHashMap<String, Applications>();
apps.shuffleAndIndexInstances(remoteRegionVsApps, clientConfig, instanceRegionChecker);
for (Applications applications : remoteRegionVsApps.values()) {
applications.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());
}
this.remoteRegionVsApps = remoteRegionVsApps;
} else {
apps.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances());
}
}
return apps;
}
private class HeartbeatThread implements Runnable {
public void run() {
if (renew()) {//可以看到,就是调用了renew方法
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
----------------------------------------------
eureka 源码的更多相关文章
- Eureka源码探索(一)-客户端服务端的启动和负载均衡
1. Eureka源码探索(一)-客户端服务端的启动和负载均衡 1.1. 服务端 1.1.1. 找起始点 目前唯一知道的,就是启动Eureka服务需要添加注解@EnableEurekaServer,但 ...
- Eureka 源码编译 部署
Netflix开源的Eureka 是使用Gradle 构建的,所以我们也使用Gradle来编译它 所需环境 Eclipse , Gradle , Tomcat ,git 这些插件如果己经安装可直接 ...
- 微服务之SpringCloud实战(四):SpringCloud Eureka源码分析
Eureka源码解析: 搭建Eureka服务的时候,我们会再SpringBoot启动类加上@EnableEurekaServer的注解,这个注解做了一些什么,我们一起来看. 点进@EnableEure ...
- 【一起学源码-微服务】Netflix Eureka 源码一:Netflix Eureka 源码初探,我们为什么要读源码?
前言 最近发现 网上好多自己的博客,很多朋友转载了文章却不加下 原载地址,本文欢迎转载一起学习,请在目录出加上原出处,感谢.转载来自:博客(一枝花算不算浪漫) 看了前面几篇文章的小伙伴知道,前几天在学 ...
- 【一起学源码-微服务】Nexflix Eureka 源码二:EurekaServer启动之配置文件加载以及面向接口的配置项读取
前言 上篇文章已经介绍了 为何要读netflix eureka源码了,这里就不再概述,下面开始正式源码解读的内容. 如若转载 请标明来源:一枝花算不算浪漫 代码总览 还记得上文中,我们通过web.xm ...
- Eureka 源码分析之 Eureka Server
文章首发于公众号<程序员果果> 地址 : https://mp.weixin.qq.com/s/FfJrAGQuHyVrsedtbr0Ihw 简介 上一篇文章<Eureka 源码分析 ...
- 【一起学源码-微服务】Nexflix Eureka 源码六:在眼花缭乱的代码中,EurekaClient是如何注册的?
前言 上一讲已经讲解了EurekaClient的启动流程,到了这里已经有6篇Eureka源码分析的文章了,看了下之前的文章,感觉代码成分太多,会影响阅读,后面会只截取主要的代码,加上注释讲解. 这一讲 ...
- 【一起学源码-微服务】Nexflix Eureka 源码十:服务下线及实例摘除,一个client下线到底多久才会被其他实例感知?
前言 前情回顾 上一讲我们讲了 client端向server端发送心跳检查,也是默认每30钟发送一次,server端接收后会更新注册表的一个时间戳属性,然后一次心跳(续约)也就完成了. 本讲目录 这一 ...
- 【一起学源码-微服务】Nexflix Eureka 源码十二:EurekaServer集群模式源码分析
前言 前情回顾 上一讲看了Eureka 注册中心的自我保护机制,以及里面提到的bug问题. 哈哈 转眼间都2020年了,这个系列的文章从12.17 一直写到现在,也是不容易哈,每天持续不断学习,输出博 ...
- 【一起学源码-微服务】Nexflix Eureka 源码十三:Eureka源码解读完结撒花篇~!
前言 想说的话 [一起学源码-微服务-Netflix Eureka]专栏到这里就已经全部结束了. 实话实说,从最开始Eureka Server和Eureka Client初始化的流程还是一脸闷逼,到现 ...
随机推荐
- register的功能
register:这个关键字请求编译器尽可能 的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率.这里注意是尽可能,不是绝对.你想想,一个CPU拥有 的寄存器也就那么几个或几十个,你要 ...
- Hibernate常见异常总结
系统配置 1.没有查找到src目录下的hibernate.cfg.xml Exception in thread "main" org.hibernate.HibernateExc ...
- day05.3-Linux进程管理
1. 通过top指令可查看系统当前进程信息. 2. 通过free指令可查看系统内核信息.其中 free -m:以M为单位查看内核: free -h:以G为单位查 ...
- 全新优化解决方案:Amplify Impostors
https://mp.weixin.qq.com/s/q6WPDHhMFk0jAMx937MLFg
- 【bzoj3172】: [Tjoi2013]单词 字符串-AC自动机
[bzoj3172]: [Tjoi2013]单词 先用所有单词构造一个AC自动机 题目要求的是每个单词在这个AC自动机里匹配到的次数 每次insert一个单词的时候把路径上的cnt++ 那么点p-&g ...
- vue_cli安装
一. 安装 node.js 首先需要安装node环境,可以直接到中文官网http://nodejs.cn/下载安装包. 只是这样安装的 node 是固定版本的,如果需要多版本的 node,可以使用 n ...
- php字符串截取中文出现乱码解决
在截取中文字符串时使用substr()容易出现乱码 可以使用mb_substr()用法与substr类似,但是比substr多了一个参数,第四个参数指定网页编码
- Tensorflow实践
确定文件的编码格式 # -*- coding : utf-8 -*- 引入tensorflow库 import tensorflow as tf 定义常量 hw=tf.contant("he ...
- Android----获取包名和sh1
第一种:使用java的一个工具---keytool工具(jdk目录下) SHA1叫安全哈希算法用于签名 keytool -list -v -keystore 签名证书所在位置path 第二种:通过An ...
- IO流图
1.InputStream类是字节输入流的抽象类,是所有字节输入流的父类,InputStream类具有层次结构如下图所示: 2.Reader类是字符输入流的抽象类,所有字符输入流的实现都是它的子类. ...