Eureka源碼深度解析(3)

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的時候),也會調用來更新服務狀態。接口實現比較簡單,如下圖所示。

  1. ApplicationResource類接收Http服務請求,調用PeerAwareInstanceRegistryImplregister方法
  2. 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中該服務信息,然後修改服務狀態。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章