1、在使用eureka的时候,我们分别演示了服务端的使用以及客户端的使用,今天我们来分析一下eureka服务是如何实现的,我们先抛出几个问题:
1.1、eureka服务端是如何整合spring-cloud的?
1.2、eureka服务端是如何存储服务实例数据的?
1.3、eureka服务端是如何跟客户端进行交互的?
2、上面抛出两个问题接下来我们一 一解答:
2.1、解答eureka服务端是如何整合spring-cloud的?
答:我们先来看看构建eureka服务端的时候我们的maven依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
我们依赖了spring-boot-starter-web,那么我们肯定会启动一个web服务,我们又依赖了spring-cloud-starter-netflix-eureka-server,我们来看看spring-cloud-starter-netflix-eureka-server的核心maven依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-server</artifactId>
<version>2.2.5.RELEASE</version>
<scope>compile</scope>
</dependency>
由上面可以看出主要就是依赖了spring-cloud-netflix-eureka-server,我们找到spring-cloud-netflix-eureka-server这个jar包,如下图:果然发现了spring.factories文件
我们打开包下面的spring.factories文件,其内容如下:不了解spring.factories的请异步我的spring boot相关博客(手动狗头);
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
那么我们自然就打开 EurekaServerAutoConfiguration 这个类,这个类太长,不一 一 列举代码,我们找核心的地方讲解:
Ⅰ:先看这个自动装配类的激活条件:核心的激活条件就是@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class),也就是需要存在Marker的Bean定义,那么这个Bean定义是如何添加的呢?答案就在注解@EnableEurekaServer 上面;
@Configuration(proxyBeanMethods = false)
导入EurekaServer的初始者配置类
@Import(EurekaServerInitializerConfiguration.class)
当前的spring boot上下文中需要有EurekaServerMarkerConfiguration.Marker的B定义
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
@EnableEurekaServer 注解源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}
@Configuration(proxyBeanMethods = false)
public class EurekaServerMarkerConfiguration {
添加一个Marker的Bean定义
@Bean
public Marker eurekaServerMarkerBean() {
return new Marker();
}
class Marker {
}
}
Ⅱ:EurekaServerAutoConfiguration 自动装配的核心代码
@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
/**
* List of packages containing Jersey resources required by the Eureka server.
*/
private static final String[] EUREKA_PACKAGES = new String[] {
"com.netflix.discovery", "com.netflix.eureka" };
@Autowired
private ApplicationInfoManager applicationInfoManager;
@Autowired
private EurekaServerConfig eurekaServerConfig;
@Autowired
private EurekaClientConfig eurekaClientConfig;
@Autowired
private EurekaClient eurekaClient;
@Autowired
private InstanceRegistryProperties instanceRegistryProperties;
. . .
配置一个实例注册器,默认使用的是InstanceRegistry
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
ServerCodecs serverCodecs) {
this.eurekaClient.getApplications(); // force initialization
return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
serverCodecs, this.eurekaClient,
this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),
this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
}
配置一个eureka server 节点管理器
@Bean
@ConditionalOnMissingBean
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
ServerCodecs serverCodecs,
ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
this.eurekaClientConfig, serverCodecs, this.applicationInfoManager,
replicationClientAdditionalFilters);
}
配置一个eureka server 上下文类
@Bean
@ConditionalOnMissingBean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
registry, peerEurekaNodes, this.applicationInfoManager);
}
配置一个eureka serve 的引导类
@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
EurekaServerContext serverContext) {
return new EurekaServerBootstrap(this.applicationInfoManager,
this.eurekaClientConfig, this.eurekaServerConfig, registry,
serverContext);
}
. . .
}
我们先来看看这个自动配置类上的如下代码:含义就是导入 EurekaServerInitializerConfiguration 这个配置类, 我们来看看这个配置类到底做了什么事情:
@Import(EurekaServerInitializerConfiguration.class)
@Configuration(proxyBeanMethods = false)
实现了ServletContextAware 感知器,以及生命周期接口SmartLifecycle
public class EurekaServerInitializerConfiguration
implements ServletContextAware, SmartLifecycle, Ordered {
private static final Log log = LogFactory
.getLog(EurekaServerInitializerConfiguration.class);
@Autowired
private EurekaServerConfig eurekaServerConfig;
private ServletContext servletContext;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private EurekaServerBootstrap eurekaServerBootstrap;
private boolean running;
private int order = 1;
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
主要看生命周期接口的start方法。。。
@Override
public void start() {
new Thread(() -> {
try {
// TODO: is this class even needed now?
初始化eureka server,这里是核心。。。
eurekaServerBootstrap.contextInitialized(
EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");
发布eureka 注册器可用事件
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
EurekaServerInitializerConfiguration.this.running = true;
发布eureka server 已经启动事件
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
}
catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}).start();
}
。 。 。
}
核心方法初始化的实现:
public void contextInitialized(ServletContext context) {
try {
初始化eureka server基本配置信息
initEurekaEnvironment();
初始化eureka server 的上下文,注意不是spring boot上下文。这里会触发
PeerAwareInstanceRegistry.openForTraffic(ApplicationInfoManager applicationInfoManager,
int count);方法,这个方法的作用就是更改eureka server的状态为UP,同事也会调用其父类
AbstractInstanceRegistry的postInit()进行续约于剔除任务的初始化。
initEurekaServerContext();
context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
}
catch (Throwable e) {
log.error("Cannot bootstrap eureka server :", e);
throw new RuntimeException("Cannot bootstrap eureka server :", e);
}
}
postInit()的实现:重点方法:
protected void postInit() {
统计最后1min事件内的续约次数任务开始
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
开始剔除任务,默认60秒清除一次90没有续约的节点。
evictionTaskRef.set(new EvictionTask());
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}
我们是不是还发现 EurekaServerAutoConfiguration 源码里面有很多@Autowire依赖注入的bean : 那这些Bean是如何添加到应用上下文中的呢?
@Autowired
private ApplicationInfoManager applicationInfoManager;
@Autowired
private EurekaServerConfig eurekaServerConfig;
@Autowired
private EurekaClientConfig eurekaClientConfig;
@Autowired
private EurekaClient eurekaClient;
@Autowired
private InstanceRegistryProperties instanceRegistryProperties;
我们来看看spring-cloud-netflix-eureka-server依赖的jar:也就是说spring-cloud-netflix-eureka-server 也依赖了 spring-cloud-netflix-eureka-client,这就是eureka-server能自我注册的实现方式,上面自动注入的5个Bean也都是在 spring-cloud-netflix-eureka-client中装配的。
. . .
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
<version>2.2.5.RELEASE</version>
<scope>compile</scope>
</dependency>
. . .
接下来我们来看看自动装配的 EurekaServerContext:默认是 DefaultEurekaServerContext 类型:
@Bean
@ConditionalOnMissingBean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
registry, peerEurekaNodes, this.applicationInfoManager);
}
DefaultEurekaServerContext的源码如下:
@Singleton
public class DefaultEurekaServerContext implements EurekaServerContext {
private static final Logger logger = LoggerFactory.getLogger(DefaultEurekaServerContext.class);
private final EurekaServerConfig serverConfig;
private final ServerCodecs serverCodecs;
private final PeerAwareInstanceRegistry registry;
private final PeerEurekaNodes peerEurekaNodes;
private final ApplicationInfoManager applicationInfoManager;
@Inject
public DefaultEurekaServerContext(EurekaServerConfig serverConfig,
ServerCodecs serverCodecs,
PeerAwareInstanceRegistry registry,
PeerEurekaNodes peerEurekaNodes,
ApplicationInfoManager applicationInfoManager) {
this.serverConfig = serverConfig;
this.serverCodecs = serverCodecs;
this.registry = registry;
this.peerEurekaNodes = peerEurekaNodes;
this.applicationInfoManager = applicationInfoManager;
}
核心就在这里,初始化方法
@PostConstruct
@Override
public void initialize() {
启动一个任务去刷新 eureka其他节点的配置信息,例如eureka集群添加删除节点的时候需要更新集群的节点信息。
logger.info("Initializing ...");
peerEurekaNodes.start();
try {
初始化前面配置的实例注册器就是前面配置的InstanceRegistry实例。
registry.init(peerEurekaNodes);
} catch (Exception e) {
throw new RuntimeException(e);
}
logger.info("Initialized");
}
@PreDestroy
@Override
public void shutdown() {
logger.info("Shutting down ...");
registry.shutdown();
peerEurekaNodes.shutdown();
ServoControl.shutdown();
EurekaMonitors.shutdown();
logger.info("Shut down");
}
@Override
public EurekaServerConfig getServerConfig() {
return serverConfig;
}
@Override
public PeerEurekaNodes getPeerEurekaNodes() {
return peerEurekaNodes;
}
@Override
public ServerCodecs getServerCodecs() {
return serverCodecs;
}
@Override
public PeerAwareInstanceRegistry getRegistry() {
return registry;
}
@Override
public ApplicationInfoManager getApplicationInfoManager() {
return applicationInfoManager;
}
}
由于 InstanceRegistry 继承自 PeerAwareInstanceRegistryImpl 因此我们来到 PeerAwareInstanceRegistryImpl 的 init(...)方法:
@Override
public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
开启集群最后1分钟同步次数统计的任务,默认60s执行一次
this.numberOfReplicationsLastMin.start();
this.peerEurekaNodes = peerEurekaNodes;
初始化响应缓存,这个响应缓存里面放的就是eureka-client 需要获取的服务实例列表。
initializedResponseCache();
开启续约阈值的更新任务,默认是15分钟执行一次,这里在讲解自我保护机制的时候设涉及过。
scheduleRenewalThresholdUpdateTask();
初始化远程区域注册表,如果分区域注册服务的话,这里就有用了。
initRemoteRegionRegistry();
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register the JMX monitor for the InstanceRegistry :", e);
}
}
我们主要来分析一下初始化响应缓存的代码:
@Override
public synchronized void initializedResponseCache() {
if (responseCache == null) {
直接new一个响应缓存的实现类ResponseCacheImpl实例
responseCache = new ResponseCacheImpl(serverConfig, serverCodecs, this);
}
}
ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
this.serverConfig = serverConfig;
this.serverCodecs = serverCodecs;
默认使用只读缓存进行eureka-client请求的响应值,默认为true,可配置
this.shouldUseReadOnlyResponseCache = serverConfig.shouldUseReadOnlyResponseCache();
this.registry = registry;
响应缓存的跟行事件间隔,默认30s , 可配置。
long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();
实例化可读可写缓存,默认使用 CacheLoader 去注册的服务的数据中
(AbstractInstanceRegistry.registry 的ConcurrentHashMap)进行载入。使用的技术是guava cache。
this.readWriteCacheMap =
CacheBuilder.newBuilder().initialCapacity(serverConfig.getInitialCapacityOfResponseCache())
.expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)
.removalListener(new RemovalListener<Key, Value>() {
@Override
public void onRemoval(RemovalNotification<Key, Value> notification) {
Key removedKey = notification.getKey();
if (removedKey.hasRegions()) {
Key cloneWithNoRegions = removedKey.cloneWithoutRegions();
regionSpecificKeys.remove(cloneWithNoRegions, removedKey);
}
}
})
构建CacheLoader用于载入服务的注册数据到可读可写的缓存中。
.build(new CacheLoader<Key, Value>() {
@Override
public Value load(Key key) throws Exception {
if (key.hasRegions()) {
Key cloneWithNoRegions = key.cloneWithoutRegions();
regionSpecificKeys.put(cloneWithNoRegions, key);
}
Value value = generatePayload(key);
return value;
}
});
如果使用了只读缓存作为响应,那就启动一个任务按配置刷新间隔进行自动刷新只读缓存,默认每30s从readWriteCacheMap 中同步数据到readOnlyCacheMap中。
if (shouldUseReadOnlyResponseCache) {
timer.schedule(getCacheUpdateTask(),
new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
+ responseCacheUpdateIntervalMs),
responseCacheUpdateIntervalMs);
}
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register the JMX monitor for the InstanceRegistry", e);
}
}
只读缓存同步的任务实现如下:
private TimerTask getCacheUpdateTask() {
return new TimerTask() {
@Override
public void run() {
logger.debug("Updating the client cache from response cache");
这里是不是很奇怪,为啥只有当只读缓存中有数据才同步,但是又是因为同步才有数据,矛
盾,其实在服务只注册完成后只要有客户端进行了服务列表获取,在获取的时候,服务端会先手动做如下操作:
1、获取可读写缓存中的服务数据,如果readWriteCacheMap中没有就会使用CacheLoader进行载入。
2、将获取到的 或者是载入的数据写入到只读缓存readOnlyCacheMap中。
for (Key key : readOnlyCacheMap.keySet()) {
if (logger.isDebugEnabled()) {
logger.debug("Updating the client cache from response cache for key : {} {} {} {}",
key.getEntityType(), key.getName(), key.getVersion(), key.getType());
}
try {
CurrentRequestVersion.set(key.getVersion());
Value cacheValue = readWriteCacheMap.get(key);
Value currentCacheValue = readOnlyCacheMap.get(key);
if (cacheValue != currentCacheValue) {
readOnlyCacheMap.put(key, cacheValue);
}
} catch (Throwable th) {
logger.error("Error while updating the client cache from response cache for key {}", key.toStringCompact(), th);
} finally {
CurrentRequestVersion.remove();
}
}
}
};
}
手动初始化只读缓存 readOnlyCacheMap 的实现如下:ResponseCacheImpl 中
@VisibleForTesting
Value getValue(final Key key, boolean useReadOnlyCache) {
Value payload = null;
try {
if (useReadOnlyCache) {
final Value currentPayload = readOnlyCacheMap.get(key);
if (currentPayload != null) {
payload = currentPayload;
} else {
payload = readWriteCacheMap.get(key);
readOnlyCacheMap.put(key, payload);
}
} else {
payload = readWriteCacheMap.get(key);
}
} catch (Throwable t) {
logger.error("Cannot get value for key : {}", key, t);
}
return payload;
}
触发手动初始化只读缓存的点如下:
1、ApplicationResource.getApplication 的rest接口。
2、ApplicationResource.getContainers 的rest接口。
3、ApplicationResource.getContainerDifferential 的rest接口。
上述说得三个缓存就是eureka-server的三级缓存,模型图如下:
2.2、eureka server是如何存储服务提供者数据的呢???
通过上面我们大概知道了是使用 ConcurrentHashMap 来进行服务提供者数据存储的,那么存储的结构是啥呢?我们来到 AbstractInstanceRegistry 类,找到储存数据的Map,如下:
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
= new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
结构是key = appName
value = 节点信息Map( key = 节点名称, value = Lease实例)
案例数据截图:
Lease的hodler实例Instanceinfo实例截图:
2.3、eureka server 端是如何与客户端进行交互的呢?
eureka 是基于http通讯的,因此在eureka server端提供了一些rest接口,用于跟客户端进行交互,我们来看看都有那些rest接口:
1、注册接口,在 ApplicationResource 中提供了如下rest接口:
@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
if (isBlank(info.getId())) {
return Response.status(400).entity("Missing instanceId").build();
} else if (isBlank(info.getHostName())) {
return Response.status(400).entity("Missing hostname").build();
} else if (isBlank(info.getIPAddr())) {
return Response.status(400).entity("Missing ip address").build();
} else if (isBlank(info.getAppName())) {
return Response.status(400).entity("Missing appName").build();
} else if (!appName.equals(info.getAppName())) {
return Response.status(400).entity("Mismatched appName, expecting " + appName + " but was " + info.getAppName()).build();
} else if (info.getDataCenterInfo() == null) {
return Response.status(400).entity("Missing dataCenterInfo").build();
} else if (info.getDataCenterInfo().getName() == null) {
return Response.status(400).entity("Missing dataCenterInfo Name").build();
}
// handle cases where clients may be registering with bad DataCenterInfo with missing data
DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
if (dataCenterInfo instanceof UniqueIdentifier) {
String dataCenterInfoId = ((UniqueIdentifier) dataCenterInfo).getId();
if (isBlank(dataCenterInfoId)) {
boolean experimental = "true".equalsIgnoreCase(serverConfig.getExperimental("registration.validation.dataCenterInfoId"));
if (experimental) {
String entity = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id";
return Response.status(400).entity(entity).build();
} else if (dataCenterInfo instanceof AmazonInfo) {
AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo;
String effectiveId = amazonInfo.get(AmazonInfo.MetaDataKey.instanceId);
if (effectiveId == null) {
amazonInfo.getMetadata().put(AmazonInfo.MetaDataKey.instanceId.getName(), info.getId());
}
} else {
logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
}
}
}
registry.register(info, "true".equals(isReplication));
return Response.status(204).build(); // 204 to be backwards compatible
}
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
handleRegistration(info, resolveInstanceLeaseDuration(info), isReplication);
super.register(info, isReplication);
}
@Override
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);
同步给其他eureka server 集群的节点。
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
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.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());
}
gMap.put(registrant.getId(), lease);
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();
}
}
2、获取服务列表在 ApplicationsResource 的 getContainers,源码如下:
表示地址是/apps
@Path("/{version}/apps")
@Produces({"application/xml", "application/json"})
public class ApplicationsResource {
. . .
@GET
public Response getContainers(@PathParam("version") String version,
@HeaderParam(HEADER_ACCEPT) String acceptHeader,
@HeaderParam(HEADER_ACCEPT_ENCODING) String acceptEncoding,
@HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept,
@Context UriInfo uriInfo,
@Nullable @QueryParam("regions") String regionsStr) {
boolean isRemoteRegionRequested = null != regionsStr && !regionsStr.isEmpty();
String[] regions = null;
if (!isRemoteRegionRequested) {
EurekaMonitors.GET_ALL.increment();
} else {
regions = regionsStr.toLowerCase().split(",");
Arrays.sort(regions); // So we don't have different caches for same regions queried in different order.
EurekaMonitors.GET_ALL_WITH_REMOTE_REGIONS.increment();
}
// Check if the server allows the access to the registry. The server can
// restrict access if it is not
// ready to serve traffic depending on various reasons.
if (!registry.shouldAllowAccess(isRemoteRegionRequested)) {
return Response.status(Status.FORBIDDEN).build();
}
CurrentRequestVersion.set(Version.toEnum(version));
KeyType keyType = Key.KeyType.JSON;
String returnMediaType = MediaType.APPLICATION_JSON;
if (acceptHeader == null || !acceptHeader.contains(HEADER_JSON_VALUE)) {
keyType = Key.KeyType.XML;
returnMediaType = MediaType.APPLICATION_XML;
}
Key cacheKey = new Key(Key.EntityType.Application,
ResponseCacheImpl.ALL_APPS,
keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions
);
Response response;
if (acceptEncoding != null && acceptEncoding.contains(HEADER_GZIP_VALUE)) {
response = Response.ok(responseCache.getGZIP(cacheKey))
.header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)
.header(HEADER_CONTENT_TYPE, returnMediaType)
.build();
} else {
response = Response.ok(responseCache.get(cacheKey))
.build();
}
CurrentRequestVersion.remove();
return response;
}
. . .
}
核心的就是这两个接口,我们在分析eureka-client的时候会详细讲解。