Eureka(3)--->eureka服务端实现原理分析

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的时候会详细讲解。

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值