Eureka Server啓動過程
同Eureka Client啓動一樣,需要添加@EnableEurekaServer註解。在該類中用@Import(EurekaServerMarkerConfiguration.class)表明了程序在啓動時會先加載EurekaServerMarkerConfiguration配置類中的配置,而在該配置類中,發佈了一個標記類 EurekaServerMarkerConfiguration$Marker,該標記類會用於開啓後續EurekaServer相關配置的加載工作。
org.springframework.cloud.netflix.eureka.server.EnableEurekaServer
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EurekaServerMarkerConfiguration.class})
public @interface EnableEurekaServer {
}
org.springframework.cloud.netflix.eureka.server.EurekaServerMarkerConfiguration
@Configuration
public class EurekaServerMarkerConfiguration {
public EurekaServerMarkerConfiguration() {
}
@Bean
public EurekaServerMarkerConfiguration.Marker eurekaServerMarkerBean() {
return new EurekaServerMarkerConfiguration.Marker();
}
class Marker {
Marker() {
}
}
}
我們看到只是實例化了一個空類,沒有任何實現,從註釋中可以看到 EurekaServerMarkerConfiguration 是一個激活 EurekaServerAutoConfiguration 的開關。通過之前的分析,我們實際可以發現SpringBoot相關項目的一些設計模式了,很多的類並不是被顯示的加載到容器中,而是通過配置的方式,最經典的方式就是放到META-INF/spring.factories文件中去加載,那麼我們也來看下spring-cloud-netflix-eureka-server-{version}.jar!META-INF/spring.factories,具體內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
真正的配置信息在 EurekaServerAutoConfiguration 中,我們看到 @ConditionalOnBean(Marker.class) 只有存在 Marker 實例的時候,纔會繼續加載配置項,這也就要求必須有 @EnableEurekaServer 註解,才能正常的啓動。
源碼如下:
@Configuration //表明這是一個配置類
@Import({EurekaServerInitializerConfiguration.class}) //導入啓動EurekaServer的bean
@ConditionalOnBean({Marker.class}) //這個是表示只有在spring容器裏面含有Market這個實例的時候,纔會加載當前這個Bean(EurekaServerAutoConfiguration ),這個就是控制是否開啓EurekaServer的關鍵
@EnableConfigurationProperties({EurekaDashboardProperties.class, InstanceRegistryProperties.class})
@PropertySource({"classpath:/eureka/server.properties"}) //加載配置文件
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
...
//Eureka-server的可視化界面就是通過EurekaController提供的
public EurekaController eurekaController() {
return new EurekaController(this.applicationInfoManager);
}
...
//接收客戶端的註冊等請求就是通過InstanceRegistry來處理的,是真正處理業務的類
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(ServerCodecs serverCodecs) {
this.eurekaClient.getApplications();
return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.eurekaClient, this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(), this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
}
...
//初始化Eureka-server,會同步其他註冊中心的數據到當前註冊中心
@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry, EurekaServerContext serverContext) {
return new EurekaServerBootstrap(this.applicationInfoManager, this.eurekaClientConfig, this.eurekaServerConfig, registry, serverContext);
}
@Bean
public FilterRegistrationBean jerseyFilterRegistration(Application eurekaJerseyApp) {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new ServletContainer(eurekaJerseyApp));
bean.setOrder(2147483647);
//創建Filter,並匹配路徑/eureka/*
bean.setUrlPatterns(Collections.singletonList("/eureka/*"));
return bean;
}
@Bean
public Application jerseyApplication(Environment environment, ResourceLoader resourceLoader) {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false, environment);
//創建相關的web節點, 比如註冊接口/eureka/apps/{appId}
provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
Set<Class<?>> classes = new HashSet();
String[] var5 = EUREKA_PACKAGES;
int var6 = var5.length;
//掃描restful接口資源的類
for(int var7 = 0; var7 < var6; ++var7) {
String basePackage = var5[var7];
Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
Iterator var10 = beans.iterator();
while(var10.hasNext()) {
BeanDefinition bd = (BeanDefinition)var10.next();
Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(), resourceLoader.getClassLoader());
classes.add(cls);
}
}
Map<String, Object> propsAndFeatures = new HashMap();
propsAndFeatures.put("com.sun.jersey.config.property.WebPageContentRegex", "/eureka/(fonts|images|css|js)/.*");
DefaultResourceConfig rc = new DefaultResourceConfig(classes);
rc.setPropertiesAndFeatures(propsAndFeatures);
return rc;
}
...
//創建並加載EurekaServerConfig的實現類,主要是Eureka-server的配置信息
@Configuration
protected static class EurekaServerConfigBeanConfiguration {
protected EurekaServerConfigBeanConfiguration() {
}
@Bean
@ConditionalOnMissingBean
public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
EurekaServerConfigBean server = new EurekaServerConfigBean();
if (clientConfig.shouldRegisterWithEureka()) {
//當eureka服務器啓動時嘗試去獲取集羣裏其他服務器上的註冊信息的次數,默認爲5
server.setRegistrySyncRetries(5);
}
return server;
}
}
}
通過以上分析可知,EurekaServer在啓動的時候,會加載很多bean到Spring容器中,每個bean都實現了各自的功能,其中真正處理客戶端請求的類是 InstanceRegistry
package org.springframework.cloud.netflix.eureka.server;
public class InstanceRegistry extends PeerAwareInstanceRegistryImpl implements ApplicationContextAware {
private static final Log log = LogFactory.getLog(InstanceRegistry.class);
private ApplicationContext ctxt;
private int defaultOpenForTrafficCount;
...
// 1.接收客戶端註冊請求
public void register(InstanceInfo info, int leaseDuration, boolean isReplication) {
this.handleRegistration(info, leaseDuration, isReplication);
super.register(info, leaseDuration, isReplication);
}
// 2.接收客戶端下線請求
public boolean cancel(String appName, String serverId, boolean isReplication) {
this.handleCancelation(appName, serverId, isReplication);
return super.cancel(appName, serverId, isReplication);
}
// 3.接收客戶端續約請求
public boolean renew(final String appName, final String serverId, boolean isReplication) {
this.log("renew " + appName + " serverId " + serverId + ", isReplication {}" + isReplication);
List<Application> applications = this.getSortedApplications();
Iterator var5 = applications.iterator();
while(var5.hasNext()) {
Application input = (Application)var5.next();
if (input.getName().equals(appName)) {
InstanceInfo instance = null;
Iterator var8 = input.getInstances().iterator();
while(var8.hasNext()) {
InstanceInfo info = (InstanceInfo)var8.next();
if (info.getId().equals(serverId)) {
instance = info;
break;
}
}
this.publishEvent(new EurekaInstanceRenewedEvent(this, appName, serverId, instance, isReplication));
break;
}
}
return super.renew(appName, serverId, isReplication);
}
...
}
以上是在處理客戶端的不同請求,但是,客戶端發送的是HTTP請求,這只是一個類,服務端應該也有一個接收HTTP請求的類,然後將接收到的請求封裝後委託給InstanceRegistry來處理具體業務。這個類就是com.netflix.eureka.resources包下的ApplicationResource、InstanceResource。
Register原理(服務註冊)
這個接口會在Service Provider啓動時被調用來實現服務註冊。同時,當Service Provider的服務狀態發生變化時(如自身檢測認爲Down的時候),也會調用來更新服務狀態。接口實現比較簡單,如下圖所示。
ApplicationResource
類接收Http服務請求,調用PeerAwareInstanceRegistryImpl
的register
方法PeerAwareInstanceRegistryImpl
完成服務註冊後,調用replicateToPeers
向其它Eureka Server節點(Peer)做狀態同步
源碼:AbstractInstanceRegistry.register(InstanceInfo registrant, int leaseDuration, boolean isReplication)註冊
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
this.read.lock();
// 所有的服務信息都添加到registry這個map中,
// 格式爲:ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>()
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication);
// 如果沒有該服務的信息,則新建,並添加到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;
}
}
//existingLease信息即服務的一些註冊時間等信息,主要是爲了校驗該服務是否過期,如果已過期,則剔除
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
if (existingLease != null && (existingLease.getHolder() != null)) {
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
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 {
synchronized (lock) {
if (this.expectedNumberOfRenewsPerMin > 0) {
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);
...
} finally {
read.unlock();
}
}
服務註冊信息最終存放到 ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>,外層map的key即爲應用的服務名,內層map的key爲我們設置的eureka.instance.instance-id,設置成這種格式,當多個應用提供相同服務時,那麼外層map的key都相同,內層map的key不同。
Renew(服務續約)
由Service Provider定期調用,類似於heartbeat。主要是用來告訴Eureka Server Service Provider還活着,避免服務被剔除掉。接口實現如下圖所示。
可以看到,接口實現方式和register
基本一致:首先更新自身狀態,再同步到其它Peer。
源碼:AbstractInstanceRegistry.renew(String appName, String id, boolean isReplication)續約
public boolean renew(String appName, String id, boolean isReplication) {
EurekaMonitors.RENEW.increment(isReplication);
// 1.獲取對應map
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToRenew = null;
if (gMap != null) {
// 2.主要是爲了獲取當前服務的一些過期信息
leaseToRenew = gMap.get(id);
}
...
renewsLastMin.increment();
// 主要操作在這裏,將最新更新時間重置,剔除任務檢查的也就是這個最新更新時間
// lastUpdateTimestamp = System.currentTimeMillis() + duration;
leaseToRenew.renew();
return true;
}
}
Cancel(服務下線)
一般在Service Provider shut down的時候調用,用來把自身的服務從Eureka Server中刪除,以防客戶端調用不存在的服務。接口實現如下圖所示。
源碼:AbstractInstanceRegistry.cancel(String appName, String id, boolean isReplication)下線
protected boolean internalCancel(String appName, String id, boolean isReplication) {
boolean var7;
try {
this.read.lock();
EurekaMonitors.CANCEL.increment(isReplication);
// 1.獲取gmap
Map<String, Lease<InstanceInfo>> gMap = (Map)this.registry.get(appName);
Lease<InstanceInfo> leaseToCancel = null;
if (gMap != null) {
// 2.刪除gmap中該服務id
leaseToCancel = (Lease)gMap.remove(id);
}
AbstractInstanceRegistry.CircularQueue var6 = this.recentCanceledQueue;
synchronized(this.recentCanceledQueue) {
this.recentCanceledQueue.add(new Pair(System.currentTimeMillis(), appName + "(" + id + ")"));
}
InstanceStatus instanceStatus = (InstanceStatus)this.overriddenInstanceStatusMap.remove(id);
if (instanceStatus != null) {
logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
}
if (leaseToCancel != null) {
//3.將當前服務的剔除時間置爲當前時間 evictionTimestamp = System.currentTimeMillis();
leaseToCancel.cancel();
// 4.獲取服務信息
InstanceInfo instanceInfo = (InstanceInfo)leaseToCancel.getHolder();
String vip = null;
String svip = null;
if (instanceInfo != null) {
// 5.將服務信息置爲已刪除
instanceInfo.setActionType(ActionType.DELETED);
this.recentlyChangedQueue.add(new AbstractInstanceRegistry.RecentlyChangedItem(leaseToCancel));
instanceInfo.setLastUpdatedTimestamp();
vip = instanceInfo.getVIPAddress();
svip = instanceInfo.getSecureVipAddress();
}
this.invalidateCache(appName, vip, svip);
logger.info("Cancelled instance {}/{} (replication={})", new Object[]{appName, id, isReplication});
boolean var10 = true;
return var10;
}
EurekaMonitors.CANCEL_NOT_FOUND.increment(isReplication);
logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
var7 = false;
} finally {
this.read.unlock();
}
return var7;
}
綜上所述,簡單來講,服務的註冊實際上是將服務信息添加到一個map中,map的key是服務名稱,value也是一個map,是提供該服務的所有客戶端信息; 服務的續約實際上是獲取map中該服務的客戶端信息,然後修改其最新更新時間;服務的下線實際上是刪除該map中該服務信息,然後修改服務狀態。