# Eureka学习(二)——服务端启动、服务注册、续约源码分析
Eureka源码分析整体分为两部分,一部分是服务端,一部分是客户端
**服务端源码分析**:
<a href="#EurekaServerStartUp">EurekaServer启动过程</a>
<a href="#EurekaServerInterface">EurekaServer服务接口暴露策略</a>
<a href="#EurekaServerRegistry">EurekaServer服务注册接口(接收客户端注册)</a>
<a href="#EurekaServerUp">EurekaServer续约接口(接受客户端续约)</a>
------
## <a id="EurekaServerStartUp">EurekaServer启动过程</a>
1、首先进入Eureka-server的关联jar包中,打开META-INF,可以看到这里是利用了SpringBoot的自动装配机制,在spring.factories中配置了服务端的

2、进入EurekaServerAutoConfiguration类,在EurekaServerAutoConfiguration类上有多个注解,注解含义分别如下
```java
@Configuration //声明为一个配置类
@Import(EurekaServerInitializerConfiguration.class)//需要引入一个EurekaServerInitializerConfiguration类
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)//自动装配此类的条件是需要Spring中含有EurekaServerMarkerConfiguration.Marker.class的Bean
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })//配置类不关注
@PropertySource("classpath:/eureka/server.properties")//配置文件不关注
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {}
```
3、首先先查看`@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)`,只有在Marker这个Class注入到Spring容器中才能启用自动装配,这里Marker.class的注入需要移到另外一个类中查看
进入Server端的@EnableEurekaServer注解,查看到这个注解里引入了EurekaServerMarkerConfiguration.class这个类

进入EurekaServerMarkerConfiguration类

看到这里将Marker.class类注入到了Spring容器中
4、查看具体的`EurekaServerAutoConfiguration`类,针对这个类,需要做逐步的分析
- EurekaController类

这里注入了一个`EurekaController`类,其实就是打开我们Eureka的仪表盘界面,如果我们没有配置eureka.dashboard.enabled = false的情况下,默认打开Eureka仪表盘
- PeerAwareInstanceRegistry类

对等节点感知实例注册器,在集群模式下注册服务需要使用的注册器,因为Eureka集群中各个节点是对等的,没有主从之分
- PeerEurekaNodes类

注入了PeerEurekaNodes类,辅助封装对等节点相关的信息与操作,更新集群当中对等节点的信息
进入PeerEurekaNodes类中,发现他在start方法中构建了一个线程池,并将更新对等节点的方法封装了一个线程类,这个线程何时启动,下面再介绍

- EurekaServerContext类

注入EurekaServer的上下文配置类
进入EurekaServerContext类,发现他在自身的初始化方法中,启动了注入的PeerEurekaNodes类的start方法

- EurekaServerBootstrap类

注入一个EurekaServerBootstrap类对象,后面启动引导的时候需要使用该对象
- FilterRegistrationBean类

注册一个Jersey过滤器,Jersey是一个Rest框架,帮助我们发布restful服务接口(类似于SpringMVC)
5、关注`@Import(EurekaServerInitializerConfiguration.class)//需要引入一个EurekaServerInitializerConfiguration类`
- 进入EurekaServerInitializerConfiguration类,首先看到start()方法,这里最重要的就是做了对EurekaServer初始化的工作

- 进入`org.springframework.cloud.netflix.eureka.server.EurekaServerBootstrap#contextInitialized`

主要分成两步,一步是作为初始化环境信息,一步是初始化上下文细节信息
- 这里关注第二步initEurekaServerContext()方法

- 主要关注的是this.registry.syncUp();方法

- 这里主要关注register()方法,进入`com.netflix.eureka.registry.AbstractInstanceRegistry#register`方法
首先查看registry详情,Lease是对实例信息的续约做了一个封装

将对应的实例信息注册到注册表中

- 下面进入`com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#openForTraffic`方法

将状态值改为在Eureka的仪表盘中的UP状态
进入`postInit()`方法,默认每隔60秒会执行一次失效实例剔除的动作

## **<a id="EurekaServerInterface">EurekaServer服务接口暴露策略</a>**
下面来看一下EurekaServer服务接口是如何暴露给客户端进行注册的,首先还是回到原来的`EurekaServerAutoConfiguration`类中
- 在Eureka Server启动过程中主配置类注册了Jersey框架(是⼀个发布restful风格接口的框架,类似于我们的springmvc)

- 初始化Application注入到Jersey中

将`{ "com.netflix.discovery","com.netflix.eureka" }`包及其子包进行扫描
- 查看对应的包路径下资源

这些就是使用Jersey发布的供Eureka Client调用的Restful风格服务接口(完成服务注册、心跳续约等接口)
## <a id="EurekaServerRegistry">EurekaServer服务注册接口(接收客户端注册)</a>
前面介绍了服务注册接口使用的是`com.netflix.eureka.resources.ApplicationResource#addInstance`方法对外提供服务,现在来看一下是如何接受客户端注册的
- 先判断请求参数是否合格

完成注册后,返回204状态给客户端

- 下面来看最重要的注册接口,进入`com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#register`方法

使用父类的注册方法尽心注册节点,注册成功后,将该节点的信息同步到其他EurekaServer集群的节点上去
- 查看`super.register(info, leaseDuration, isReplication);`方法,前面大致介绍过
```java
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock();//增加读锁
// registry是保存所有应⽤实例信息的Map:ConcurrentHashMap<String,Map<String, Lease<InstanceInfo>>>
// 从registry中获取当前appName的所有实例信息
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication);//注册统计+1
//如果当前实例没有获取到,则new一个基础信息放到registry中
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租约信息
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
// Retain the last dirty timestamp without overwriting it, if there is already a lease
// 如果已经有租约,则保留最后⼀个脏时间戳⽽不覆盖它
// (⽐较当前请求实例租约 和 已有租约 的LastDirtyTimestamp,选择靠后的)
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
// 如果之前不存在实例的租约,说明是新实例注册
// expectedNumberOfRenewsPerMin期待的每分钟续约数+1
// 并更新numberOfRenewsPerMinThreshold每分钟续约阀值(85%)
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// Since the client wants to register it, increase the number of clients sending renews
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
updateRenewsPerMinThreshold();
}
}
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());
}
//当前实例信息放到维护注册信息的Map
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
// 如果当前实例已经维护了OverriddenStatus,将其也放到此Eureka Server的overriddenInstanceStatusMap中
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
// 如果租约以UP状态注册,设置租赁服务时间戳
if (InstanceStatus.UP.equals(registrant.getStatus())) {
lease.serviceUp();
}
registrant.setActionType(ActionType.ADDED);
recentlyChangedQueue.add(new RecentlyChangedItem(lease));
registrant.setLastUpdatedTimestamp();
// 使当前应⽤的ResponseCache失效
invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());//更新最后更新时间
logger.info("Registered instance {}/{} with status {} (replication={})",
registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
} finally {
read.unlock();
}
}
```
- 注册完成后,将该节点信息同步到其他EurekaServer集群节点上
```java
private void replicateToPeers(Action action, String appName, String id,
InstanceInfo info /* optional */,
InstanceStatus newStatus /* optional */, boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
try {
// 如果是复制操作(针对当前节点,false),对已复制的次数加1
if (isReplication) {
numberOfReplicationsLastMin.increment();
}
// If it is a replication already, do not replicate again as this will create a poison replication
// 如果它已经是复制,直接return
if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
return;
}
// 遍历集群所有节点(除当前节点外)
for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
// If the url represents this host, do not replicate to yourself.
//判断是当前节点直接跳过
if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
continue;
}
// 复制Instance实例操作到某个node节点
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
} finally {
tracer.stop();
}
}
```

## <a id="EurekaServerUp">EurekaServer续约接口(接受客户端续约)</a>
因为Eureka默认客户端每30秒进行一次续约接口,服务端也提供了对应的续约接口

- 进入`com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl#renew`方法

- 续约最终是对lease对象中的时间戳进行了更新


- 续约成功后,调用上面介绍过的同步状态方法到其他EurekaServer集群节点上的方法进行更新

到这里为止,服务端从启动、到注册接口、续约接口就介绍完毕。
Eureka学习(二)——服务端启动、服务注册、续约源码分析