Spring Cloud系列(三):Eureka源碼解析之服務端


一、自動裝配

  1、根據自動裝配原理(詳見:Spring Boot系列(二):Spring Boot自動裝配原理解析),找到spring-cloud-starter-netflix-eureka-server.jar的spring.factories,查看spring.factories如下:

  2、進入EurekaServer的自動裝配類EurekaServerAutoConfiguration:

   3、@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)也就是說當容器中有EurekaServerMarkerConfig uration.Marker.class時,該配置類才起作用。接下來看下啟動類EurekaApplication,該啟動類上不光有@SpringBootApplication自動裝配,還有@EnableEurekaServer,開啟EurekaServer。

二、EurekaServer

  1、@EnableEurekaServer注解開啟EurekaServer 

  1.1 點進去@EnableEurekaServer點進去,發現其@Import(EurekaServerMarkerConfiguration.class),導入了EurekaServer MarkerConfiguration配置類

  1.2 EurekaServerMarkerConfiguration配置類,該配置類導入了EurekaServerMarkerConfiguration.Marker.class。如下:

   2、EurekaServerAutoConfiguration配置類

  當容器中有EurekaServerMarkerConfiguration.Marker.class,就可以激活該配置類,接下來詳細看下該配置類為我們做了什么。

  2.1 @Import(EurekaServerInitializerConfiguration.class)

  

   EurekaServerInitializerConfiguration配置類實現了SmartLifecycle,我們知道實現了SmartLifecycle接口的,會在Ioc容器中所有Bean初始化完成后,根據isAutoStartup()方法返回true來執行該配置類的start()

  ① 進入EurekaServerInitializerConfiguration.start()方法:

public void start() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //EurekaServerAutoConfiguration->@Bean EurekaServerBootstrap
                    eurekaServerBootstrap.contextInitialized(
                            EurekaServerInitializerConfiguration.this.servletContext);
                    log.info("Started Eureka Server");
                    //發布EurekaRegistryAvailableEvent事件
                    publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
                    //設置運行狀態為true
                    EurekaServerInitializerConfiguration.this.running = true;
                    //發布EurekaServerStartedEvent事件
                    publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
                }
                catch (Exception ex) {
                    // Help!
                    log.error("Could not initialize Eureka servlet context", ex);
                }
            }
        }).start();
    }

  ② 進入EurekaServerBootstrap.contextInitialized(ServletContext context)方法:

public void contextInitialized(ServletContext context) {
        try {
            //初始化EurekaServer的運行環境
            initEurekaEnvironment();
            //初始化EurekaServer的上下文
            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);
        }
    }

  ③ 進入EurekaServerBootstrap.initEurekaServerContext()方法:

protected void initEurekaServerContext() throws Exception {
        //省略非核心代碼
        //從集群其他節點復制注冊
        int registryCount = this.registry.syncUp();
        /**
         * 1、修改狀態為UP
         * 2、調用父類的postInit 開啟一個剔除定時任務,每隔60執行一次,從當前服務清單中把超時(默認90秒)沒有續約剔
         */
        this.registry.openForTraffic(this.applicationInfoManager, registryCount);
        // Register all monitoring statistics.
        EurekaMonitors.registerAllStats();
    }

  ④ PeerAwareInstanceRegistryImpl.syncUp()方法:

  該方法中的eurekaClient.getApplications()是通過http調用,獲取集群中的其他節點的所有服務實例。然后遍歷獲取到的apps,根據isRegisterable(instance)判斷是否可注冊,如果可以注冊就調用register(instance, instance.getLeaseInfo().getDurationInSecs(), true)進行注冊,注冊實質就是往AbstractInstanceRegistry的屬性private final ConcurrentHashMap<String, Map<String, Lease <InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();中加入服務實例信息。

  ⑤ PeerAwareInstanceRegistryImpl.openForTraffic()方法:

  該方法核心第一步:applicationInfoManager.setInstanceStatus(InstanceStatus.UP)修改狀態為UP,第二步:調用super.postInit();開啟一個剔除定時任務,每隔60執行一次,從當前服務清單中把超時(默認90秒)沒有續約剔除。

  ⑥ 進入AbstractInstanceRegistry.postInit()方法:

protected void postInit() {
        renewsLastMin.start();
        if (evictionTaskRef.get() != null) {
            evictionTaskRef.get().cancel();
        }
        //設置剔除任務EvictionTask
        evictionTaskRef.set(new AbstractInstanceRegistry.EvictionTask());
        //每隔60s執行一次EvictionTask的run方法
        evictionTimer.schedule(evictionTaskRef.get(),
                serverConfig.getEvictionIntervalTimerInMs(),
                serverConfig.getEvictionIntervalTimerInMs());
    }

  ⑦ EvictionTask的run方法:

  執行AbstractInstanceRegistry.evict(),剔除邏輯:主要的功能是將注冊表registry,其實就是一個ConcurrentHashMap的所有注冊實例遍歷下,看哪些是過期的,過期了就加入到expiredLeases中,然后遍歷expiredLeases,執行internalCancel方法把實例狀態修改成DELETED狀態,這樣客戶端就拿不到。

  2.2 導入的核心Bean

  ① EurekaServerConfig:初始化eurekaServer配置;

  ② EurekaController:初始化dashboard的相關接口,用戶獲取eurekaServer的相關信息;

  ③ PeerAwareInstanceRegistry:初始化集群注冊表;

  ④ PeerEurekaNodes:初始化集群節點;

  ⑤ EurekaServerContext:基於eurekaServer配置,注冊表,集群節點,以及服務實例初始化eurekaServer上下文;

  ⑥ EurekaServerBootstrap:初始化eureka啟動類;

  ⑦ FilterRegistrationBean:往Filter注冊表里面注冊一個Jsrsey過濾器;

  其中EurekaServerContext的默認實現DefaultEurekaServerContext在初始化的時候會調用initialize()方法,流程圖如下:

   3、Eureka的Jersey服務

  3.1 服務注冊接口

  ApplicationResource.addInstance()方法,核心邏輯就是調用PeerAwareInstanceRegistryImpl.register(final InstanceInfo info, final boolean isReplication)方法如下:

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();
        }
        //往注冊表中注冊實例信息,然后執行invalidateCache(),把讀寫緩存readWriteCacheMap失效掉
        super.register(info, leaseDuration, isReplication);
        //復制到集群中的其他節 發起http調用,調用集群中的其他節點的注冊服務實例接口
        replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
    }

  3.2 獲取全量實例信息接口

  ApplicationsResource.getContainers()方法如下:

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) {

        //省略......
        //1、構建緩存key
        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)) {
            //省略......
        } else {
            //2、根據緩存key從緩存中獲取,首先從只讀緩存中取為null,再去讀寫緩存中取,然后設置到只讀緩存中
            response = Response.ok(responseCache.get(cacheKey))
                    .build();
        }
        return response;
    }

  3.3 獲取增量實例信息接口

  ApplicationsResource.getContainerDifferential()方法,邏輯同獲取全量實例信息接口一樣,不同在於構建緩存key的時候傳入的ALL_APPS_DELTA,而獲取全量實例信息接口傳入的是ALL_APPS。

  3.4 心跳接口

  InstanceResource.renewLease()方法,核心邏輯就是調用PeerAwareInstanceRegistryImpl.renew(final String appName, final String id, final boolean isReplication)方法進行續約,其實就是設置實例的lastUpdateTimestamp為當前時間+duration

public void renew() {
        //設置lastUpdateTimestamp為當前時間+duration
        lastUpdateTimestamp = System.currentTimeMillis() + duration;
    }

  3.5 服務下線接口

  InstanceResource.cancelLease()方法,核心就是調用PeerAwareInstanceRegistryImpl.cancel(final String appName, final String id,final boolean isReplication)方法進行服務下線,其實就是把實例的狀態設置成DELETE,然后執行invalidateCache(),把讀寫緩存readWriteCacheMap失效掉

三、Eureka服務端流程圖

  自此Eureka服務端源碼解析完成,Eureka客戶端源碼詳見:Spring Cloud系列(四):Eureka源碼解析之客戶端。Eureka應用詳見:Spring Cloud系列(二):Eureka應用詳解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM