1. 源码定位
方法:com.netflix.discovery.InstanceInfoReplicator
源码版本: v1.7x
源码地址:https://github.com/Netflix/eureka/tree/v1.7.x
2.一切的开始InstanceInfoReplicator
2.1 线程任务InstanceInfoReplicator初始化与启动
eureka server想要接受请求,首先得有客户端发送。而在DiscoveryClient
初始化过程中创建了InstanceInfoReplicator
线程任务对象,并且在DiscoveryClient
初始化的最后阶段开启了一个定时任务,在延迟40秒后开始注册服务到eureka server 上去。
2.2 开始向服务器注册
如果自身状态没有发生改变的话,其实是可以不用去注册的
public void run() {
try {
// 刷新自身状态信息和配置信息等,如果有发生变化,则InstanceInfo就会成为dirty状态
discoveryClient.refreshInstanceInfo();
// 如果自身信息发生变化,则存在dirtyTimestamp就是不为null
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
//
discoveryClient.register();
// 设置InstanceInfo的脏数据状态为false
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
logger.warn("There was a problem with the instance info replicator", t);
} finally {
// 30秒后自动执行下一次复制到对等节点的任务
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
// 把这个任务交给scheduledPeriodicRef,如果在下次执行onDemandUpdate时,会先判断上次的是否还在执行,如果还在执行,那就取消上次的执行
scheduledPeriodicRef.set(next);
}
}
2.3 注册的具体内容
booleanregister()throwsThrowable {
logger.info(PREFIX+ appPathIdentifier + ": registering service...");
EurekaHttpResponse<Void> httpResponse;
try{
//将自身信息注册到eureka server节点上
//这个eurekaTransport.registrationClient跟 eurekaTransport.queryClient的获取方式基本一
//致。只不过是两个独立的对象而已
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
}catch(Exception e) {
logger.warn("{} - registration failed {}",PREFIX+ appPathIdentifier, e.getMessage(), e);
throwe;
}
if(logger.isInfoEnabled()) {
logger.info("{} - registration status: {}",PREFIX+ appPathIdentifier, httpResponse.getStatusCode());
}
returnhttpResponse.getStatusCode() == 204;
}
关于httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
这块的调用链路比较复杂,如果想了解的可以参考我写的 《eureka 巧妙的EurekaHttpClient工厂设计与请求发送过程》 这篇文章。
最终会发现调用时机上是JerseyApplicationClient内的register方法,这个方法实际上是在其父类之中com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient#register
3.基于/apps/{InstanceInfo.appName} 去找web处理类
这里是基于web框架jersey框架来实现的,这个框架国内比较小众,所以也不细看了。我们直接通过/apps 找到com.netflix.eureka.resources.ApplicationsResource
从上图可以看出,实际处理请求的应该是ApplicationResource
类
4.PeerAwareInstanceRegistryImpl 注册请求实际处理类
这里其实就是做了两件事:
- 将注册任务交给父类
super.register
- 将注册行为同步到eureka server的其他节点上去
replicateToPeers
5.注册过程源码解析
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>>();
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
}
//获取本地已经存在的续约对象
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
// Retain the last dirty timestamp without overwriting it, if there is already a lease
if (existingLease != null && (existingLease.getHolder() != null)) {
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
// this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
// InstanceInfo instead of the server local copy.
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
" than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
registrant = existingLease.getHolder();
}
} else {
// 续约消息不存在,作为一个新的服务实例进行注册
// The lease does not exist and hence it is a new registration
synchronized (lock) {
if (this.expectedNumberOfRenewsPerMin > 0) {
// Since the client wants to cancel it, reduce the threshold
// (1
// for 30 seconds, 2 for a minute)
// 这里的计算也是按照心跳发送时间为30秒进行计算的(所以,不要瞎修改心跳发送周期,容易凉)
this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
}
}
logger.debug("No previous lease information found; it is new registration");
}
Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
gMap.put(registrant.getId(), lease);
synchronized (recentRegisteredQueue) {
// 放入最近增量注册表队列
recentRegisteredQueue.add(new Pair<Long, String>(
System.currentTimeMillis(),
registrant.getAppName() + "(" + registrant.getId() + ")"));
}
// This is where the initial state transfer of overridden status happens
// 实例状态变更记录表
if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
+ "overrides", registrant.getOverriddenStatus(), registrant.getId());
if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
logger.info("Not found overridden id {} and hence adding it", registrant.getId());
overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
}
}
InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(registrant.getId());
if (overriddenStatusFromMap != null) {
logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
registrant.setOverriddenStatus(overriddenStatusFromMap);
}
// Set the status based on the overridden status rules 这种服务实例状态基于状态覆写规则
InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
// If the lease is registered with UP status, set lease service up timestamp
if (InstanceStatus.UP.equals(registrant.getStatus())) {
// 设置在线时间
lease.serviceUp();
}
// 新增到增量队列里去
registrant.setActionType(ActionType.ADDED);
recentlyChangedQueue.add(new RecentlyChangedItem(lease));
registrant.setLastUpdatedTimestamp();
// 读写缓存失效
invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
logger.info("Registered instance {}/{} with status {} (replication={})",
registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
} finally {
read.unlock();
}
}
源码分析总结:
- 判断本地是否已经存在续约对象Lease,存在则将本地与传递过来的进行对比,用最近的那个。
- 如果是一个新的服务实例,则需要更新每分钟期待心跳数和每分钟最低应该有的心跳数(低于这个就会进入自我保护机制)。
- 加入服务实例ID和续约映射表中
- 加入最近注册队列
recentRegisteredQueue.add(newPair<Long, String>(
System.currentTimeMillis(),
registrant.getAppName() + "(" + registrant.getId() + ")"));
-
写入实例状态变更记录表中
overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
-
服务上线标识更新
lease.serviceUp();
-
加入最近修改队列
recentlyChangedQueue.add(new RecentlyChangedItem(lease));
-
读写缓存失效
invalidateCache
这里可以看到注册就是在更新四个缓存:
registry
是一个ConcurrentHashMap,键为appName,值也是一个map,是instanceId和Lease续约对象的映射表。 这个是保存服务实例和续约对象的容器- recentRegisteredQueue 最近注册的InstanceInfo放入队列(主要用于debug和统计使用的)
- recentlyChangedQueue 最近修改的InstanceInfo放入队列 (主要用于增量注册表使用)
overriddenInstanceStatusMap
服务实例id与服务状态的映射表
其实我们需要考虑一个问题,更新这四个缓存的目的是啥?有何意义?
registry
在这里相当于eureka server的注册表,而我们在DiscoveryClient中的localRegionApps相当于Eureka Client的拉取的注册表缓存recentlyChangedQueue
主要是用于返回增量注册表给客户端overriddenInstanceStatusMap
这是在发送请求时,会将服务实例状态进行传递,直接从这里获取的缓存
缓存越多,维护起来越麻烦,但是相应的获取数据的方式会更加简单。
6.小结
eureka server接受服务注册其实就是更新本地缓存的注册表,然后加入最近更新队列来作为增量下发内容。