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中该服务信息,然后修改服务状态。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值