Eureka源码剖析

@EnableEurekaServer

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

启动EurekaServer只需要@EnableEurekaServer注解就可以了,所以配置和初始化都在这个注解之中操作。

@EnableDiscoveryClient
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EurekaServerMarkerConfiguration.class})
public @interface EnableEurekaServer {
}
@Configuration
public class EurekaServerMarkerConfiguration {
    public EurekaServerMarkerConfiguration() {
    }
    // 只是把一个空的Marker类变成了spring中的Bean
    @Bean
    public EurekaServerMarkerConfiguration.Marker eurekaServerMarkerBean() {
        return new EurekaServerMarkerConfiguration.Marker();
    }
    
    class Marker {
        Marker() {
        }
    }
}

这里用到了Springboot自动配置的功能,那就来看下Springboot的自动配置原理,参考这篇blog:https://blog.csdn.net/u014745069/article/details/83820511

EurekaServerAutoConfiguration

这里可以看到在spring-cloud-netfix-eureka-server-{version}.jar中, META-INF/spring.factories之中配置了自动装配类:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
@Configuration
@Import({EurekaServerInitializerConfiguration.class})
@ConditionalOnBean({Marker.class})
@EnableConfigurationProperties({EurekaDashboardProperties.class, InstanceRegistryProperties.class})
@PropertySource({"classpath:/eureka/server.properties"})
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
  1. @ConditionalOnBean判断EurekaServerMarkerConfiguration.Marker这个Bean是否存在。如果存在才会解析这个自动配置类,从而呼应了@EnableEurekaServer这个注解的功能。

这种都是Springboot的基本操作了,再Saga Omega的代码中也出现过@ConditionalOnProperty基于配置文件来选择是否启用配置:

@Configuration
@Import({OmegaSpringConfig.class,TransactionAspectConfig.class})
@ConditionalOnProperty(value = {"omega.enabled"}, matchIfMissing = true)
public class OmegaSpringAutoConfiguration {
}
  1. @EnableConfigurationProperties注解和@PropertySource注解都加载了一些键值对的属性。

  2. @Import导入了一个初始化类EurekaServerInitializerConfiguration

EurekaServerAutoConfiguration作为自动配置类,我们看看它主要配置了哪些东西(有所忽略)

看板

	@Bean
	@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
	public EurekaController eurekaController() {
		return new EurekaController(this.applicationInfoManager);
	}

发现注册

	@Bean
	public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
			ServerCodecs serverCodecs) {
		this.eurekaClient.getApplications(); // force initialization
		return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
				serverCodecs, this.eurekaClient,
				this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(),
				this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
	}

启动引导

	@Bean
	public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
			EurekaServerContext serverContext) {
		return new EurekaServerBootstrap(this.applicationInfoManager,
				this.eurekaClientConfig, this.eurekaServerConfig, registry,
				serverContext);
	}

Jersey提供rpc调用

/**
 * Construct a Jersey {@link javax.ws.rs.core.Application} with all the resources
 * required by the Eureka server.
 */
@Bean
public javax.ws.rs.core.Application jerseyApplication(Environment environment,
      ResourceLoader resourceLoader) {

   ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
         false, environment);

   // Filter to include only classes that have a particular annotation.
   //
   provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
   provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));

   // Find classes in Eureka packages (or subpackages)
   //
   Set<Class<?>> classes = new HashSet<Class<?>>();
   for (String basePackage : EUREKA_PACKAGES) {
      Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
      for (BeanDefinition bd : beans) {
         Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
               resourceLoader.getClassLoader());
         classes.add(cls);
      }
   }

   // Construct the Jersey ResourceConfig
   //
   Map<String, Object> propsAndFeatures = new HashMap<String, Object>();
   propsAndFeatures.put(
         // Skip static content used by the webapp
         ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,
         EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*");

   DefaultResourceConfig rc = new DefaultResourceConfig(classes);
   rc.setPropertiesAndFeatures(propsAndFeatures);

   return rc;
}

Jersey是一个RESTFUL请求服务JAVA框架,与常规的JAVA编程使用的struts框架类似,它主要用于处理业务逻辑层。和SpringMVC有区别,反正是个Controller层的框架,不用详细学习,只要知道就可以了。

现在看下之前@EnableEurekaServer注解导入的EurekaServerInitializerConfiguration类:

@Configuration
public class EurekaServerInitializerConfiguration implements ServletContextAware, SmartLifecycle, Ordered {
    // ...

    @Override
    public void start() {
        new Thread(() -> {
            try {
                eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);

                publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
                EurekaServerInitializerConfiguration.this.running = true;
                publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
            } catch (Exception ex) {
                //
            }
        }).start();
    }

}

初始化过程调用了EurekaServerBootstrap的contextInitialized方法,我们跟进看看

public void contextInitialized(ServletContext context) {
    try {
        initEurekaEnvironment();
        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);
    }
}

这里初始化了EurekaEnvironment和EurekaServerContext,EurekaEnvironment无非就是设置了各种配置之类的东西。我们打开initEurekaServerContext看看

protected void initEurekaServerContext() throws Exception {
    // ......

    // Copy registry from neighboring eureka node
    int registryCount = this.registry.syncUp();
    this.registry.openForTraffic(this.applicationInfoManager, registryCount);

    // Register all monitoring statistics.
    EurekaMonitors.registerAllStats();
}

这里主要做了两件事:

1)从相邻的集群节点当中同步注册信息(EurekaServer是集群)

2)注册一个统计器

总结

@EnableEurekaServer注解开启了EurekaServerAutoConfiguration这个配置类的解析,EurekaServerAutoConfiguration这个配置了主要准备了看板、注册发现、启动引导、Jersey等,EurekaServerInitializerConfigration将会触发启动引导,引导过程会从其它Eureka集群节点当中同步注册信息。

eureka服务端注册服务

eureka是使用jersey来对外提供restful风格的rpc调用的。我们得找到注册服务的Resource(jersey中称为resource,对应springmvc的controller)

ApplicationResource类中的addInstance方法将作为服务端注册服务的入口

private final PeerAwareInstanceRegistry registry;

@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info, @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
    //...

    registry.register(info, "true".equals(isReplication));
    return Response.status(204).build(); 
}
public void register(InstanceInfo info, boolean isReplication) {
    int leaseDuration = 90;
    if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
        leaseDuration = info.getLeaseInfo().getDurationInSecs();
    }
    // 注册信息
    super.register(info, leaseDuration, isReplication);
    // 复制到其他节点
    this.replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Register, info.getAppName(), info.getId(), info, (InstanceStatus)null, isReplication);
}

register方法主要做了两件事

1)注册实例信息

2)复制到其它节点

我们关注注册实例,跟进super.register方法

register方法很长,看看核心的流程

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    try {
        this.read.lock();
        Map<String, Lease<InstanceInfo>> gMap = (Map)this.registry.get(registrant.getAppName());
        EurekaMonitors.REGISTER.increment(isReplication);
        if (gMap == null) {
            ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap();
            // 只是将InstanceInfo添加到registry集合之中
            gMap = (Map)this.registry.putIfAbsent(registrant.getAppName(), gNewMap);
            if (gMap == null) {
                gMap = gNewMap;
            }
        } Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
        
        gMap.put(registrant.getId(), lease);
        
           // 省略
    } finally {
        // 
    }
ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry

很容易理解服务名称是唯一的,但是服务是集群式的服务,所以服务中是有很多实例的。

这个是registry集合的数据结构

{
    "商品服务": { // 服务名
        "实例的唯一ID": { // 实例标识符
            "lease": { // 持有实例信息
                "registrationTimestamp":yyy, // 注册时间
                "lastUpdateTimestamp":xxx, 	 // 最后修改时间
                .....
                "instanceInfo": { // 实例信息
                    "appName": "商品服务",
                    "instanceId": "实例的唯一ID",
                    "ipAddr": "IP地址",
                    "port": "调用端口"
              			.......
                }
            }
        }
    }
}
public class Lease<T> {

    enum Action {
        Register, Cancel, Renew
    };

    public static final int DEFAULT_DURATION_IN_SECS = 90;

    private T holder;
    private long evictionTimestamp;
    private long registrationTimestamp;
    private long serviceUpTimestamp;
    // Make it volatile so that the expiration task would see this quicker
    private volatile long lastUpdateTimestamp;
    private long duration;
  ......
public class InstanceInfo {

    /**
     * {@link InstanceInfo} JSON and XML format for port information does not follow the usual conventions, which
     * makes its mapping complicated. This class represents the wire format for port information.
     */
   private volatile String instanceId;

    private volatile String appName;
    @Auto
    private volatile String appGroupName;

    private volatile String ipAddr;

    private static final String SID_DEFAULT = "na";
    @Deprecated
    private volatile String sid = SID_DEFAULT;

    private volatile int port = DEFAULT_PORT;
    private volatile int securePort = DEFAULT_SECURE_PORT;

    @Auto
    private volatile String homePageUrl;
    @Auto
    private volatile String statusPageUrl;
    @Auto
    private volatile String healthCheckUrl;
    @Auto
    private volatile String secureHealthCheckUrl;
    @Auto
    private volatile String vipAddress;
    @Auto
    private volatile String secureVipAddress;
  ......
    

eureka采用jersey来提供rpc服务,注册服务实际上就是向registry添加了一份实例信息。不过我们可以看到Eureka并没有对实例数据进行持久化,所以实例数据都是瞬时态的,这与zookeeper的做法存在区别。

所以每一个EurekaServer都是存储了服务的信息,并且EurekaServer也相互之间进行注册,并同步。

所以看下复制到其他节点的逻辑,看下replicateToPeers方法

private void replicateToPeers(Action action, String appName, String id,
                              InstanceInfo info /* optional */,
                              InstanceStatus newStatus /* optional */, boolean isReplication) {
    Stopwatch tracer = action.getTimer().start();
    try {
        if (isReplication) {
            numberOfReplicationsLastMin.increment();
        }
        // 如果本次register操作本身就是复制,就不再复制到其它节点了
        if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
            return;
        }

        // 遍历左右节点
        for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
            // 如果是当前节点,直接跳过
            if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
                continue;
            }
            // 复制操作触发, 其实就是向其他节点发送注册请求
            replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
        }
    } finally {
        tracer.stop();
    }
}
private void replicateInstanceActionsToPeers(Action action, String appName,
                                             String id, InstanceInfo info, InstanceStatus newStatus,
                                             PeerEurekaNode node) {
    try {
        InstanceInfo infoFromRegistry = null;
        CurrentRequestVersion.set(Version.V2);
        switch (action) {
            case Cancel:
                node.cancel(appName, id);
                break;
            case Heartbeat:
                InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
                infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
                break;
            // 注册
            case Register:
                node.register(info);
                break;
            case StatusUpdate:
                infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                node.statusUpdate(appName, id, newStatus, infoFromRegistry);
                break;
            case DeleteStatusOverride:
                infoFromRegistry = getInstanceByAppAndId(appName, id, false);
                node.deleteStatusOverride(appName, id, infoFromRegistry);
                break;
        }
    } catch (Throwable t) {
        // ... 
    }
}
// 复制实例信息被构造成了一个任务丢给了batchingDispatcher去异步执行,如果失败将会重试。
public void register(final InstanceInfo info) throws Exception {
    long expiryTime = System.currentTimeMillis() + getLeaseRenewalOf(info);
    // 异步执行任务
    batchingDispatcher.process(
            taskId("register", info),
            // 构造了一个复制实例信息的任务
            new InstanceReplicationTask(targetHost, Action.Register, info, null, true) {
                public EurekaHttpResponse<Void> execute() {
                    return replicationClient.register(info);
                }
            },
            expiryTime
    );
}
// replicationClient.register()
// Jersey实现,就是发送http请求到其他Eureka Server
public EurekaHttpResponse<Void> register(InstanceInfo info) {
    String urlPath = "apps/" + info.getAppName();
    ClientResponse response = null;
    try {
        Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
        addExtraHeaders(resourceBuilder);
        response = resourceBuilder
                .header("Accept-Encoding", "gzip")
                .type(MediaType.APPLICATION_JSON_TYPE)
                .accept(MediaType.APPLICATION_JSON)
                .post(ClientResponse.class, info);
        return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
    } finally {
        // ...
    }
}

eureka Server会将register、cancel、heartbeat等操作从一个节点同步发送到其它节点,从而实现了复制的功能。eureka和zookeeper不一样,它是遵循AP的,所以采用了最终一致性,并没有像zookeeper一样选择强一致(CP)。eureka Server之间的维持最终一致性的细节点还是很多的,比如失败重试、超时、心跳、实例的版本号、同一个节点的锁控制等等。有兴趣的话可以详细了解它。

eureka获取服务列表

eureka服务端维护了一个服务信息的列表,服务端节点之间相互复制服务信息。而作为eureka的客户端将会从eureka服务端请求这个服务信息列表,选择对应的实例。

eureka服务端基于jersey来提供http服务调用,所以我们先找到它的Resource。

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

    // ...

    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 {
        // 从responseCache当中获取了Applications的序列号结果直接返回了
        response = Response.ok(responseCache.get(cacheKey))
                .build();
    }
    return response;
}

ResponseCache是在ApplicationsResource构造的时候从Registry中获取的

@Inject
ApplicationsResource(EurekaServerContext eurekaServer) {
    this.serverConfig = eurekaServer.getServerConfig();
    this.registry = eurekaServer.getRegistry();
    this.responseCache = registry.getResponseCache();
}

看看ResponseCache的get方法做了什么

String get(final Key key, boolean useReadOnlyCache) {
    Value payload = getValue(key, useReadOnlyCache);
    if (payload == null || payload.getPayload().equals(EMPTY_PAYLOAD)) {
        return null;
    } else {
        return payload.getPayload();
    }
}

继续跟进getValue

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;
}

可以看到,其实只是从ReadWriteCacheMap当中获取对应的值,那么我们再看看ReadWriteCacheMap是怎么被构造的

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);
                                    }
                                }
                            })
                            .build(new CacheLoader<Key, Value>() {
                                @Override
                                public Value load(Key key) throws Exception {
                                    if (key.hasRegions()) {
                                        Key cloneWithNoRegions = key.cloneWithoutRegions();
                                        regionSpecificKeys.put(cloneWithNoRegions, key);
                                    }
                                    // 调用ReadWriteCacheMap的get方法,将会generatePayload方法
                                    Value value = generatePayload(key);
                                    return value;
                                }
                            });
private Value generatePayload(Key key) {
    Stopwatch tracer = null;
    try {
        String payload;
        switch (key.getEntityType()) {
            case Application:
                boolean isRemoteRegionRequested = key.hasRegions();
                // 获取所有Application
                if (ALL_APPS.equals(key.getName())) {
                    if (isRemoteRegionRequested) {
                        tracer = serializeAllAppsWithRemoteRegionTimer.start();
                        payload = getPayLoad(key, registry.getApplicationsFromMultipleRegions(key.getRegions()));
                    } else {
                        tracer = serializeAllAppsTimer.start();
                        payload = getPayLoad(key, registry.getApplications());
                    }
                } else if (ALL_APPS_DELTA.equals(key.getName())) {
                    // ...
                } else {
                    tracer = serializeOneApptimer.start();
                    // 获取某个Application
                    payload = getPayLoad(key, registry.getApplication(key.getName()));
                }
                break;
            // ...
        }
        return new Value(payload);
    } finally {
        
    }
}

我们看到这里payload主要构成元素是Application,也就是我们需要的服务列表信息。

最后,我们跟进getPayLoad方法,看看这些服务列表信息是怎么被序列号成payload的

private String getPayLoad(Key key, Applications apps) {
    // 编码器
    EncoderWrapper encoderWrapper = serverCodecs.getEncoder(key.getType(), key.getEurekaAccept());
    String result;
    try {
        result = encoderWrapper.encode(apps);
    } catch (Exception e) {
        return "";
    }
    return result;
}

总结:

获取服务信息列表其实就是从registry当中获取Applications,然后做一次序列化,最后通过http响应回去。总体来说还是比较简单的。

eureka客户端自动配置

@EnableDiscoveryClient

@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {

   /**
    * If true, the ServiceRegistry will automatically register the local server.
    */
   boolean autoRegister() default true;
}

给了个参数autoRegister,默认为true,如果设置为false就不会去注册了。

@EnableDiscoveryClient(autoRegister = false)

不同于Server端的是,client端不是直接引入一个Configuration而是通过一个ImportSelector来导入类。跟进EnableDiscoveryClientImportSelector类

@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector
		extends SpringFactoryImportSelector<EnableDiscoveryClient> {

	@Override
	public String[] selectImports(AnnotationMetadata metadata) {
		String[] imports = super.selectImports(metadata);

		AnnotationAttributes attributes = AnnotationAttributes.fromMap(
				metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));

		boolean autoRegister = attributes.getBoolean("autoRegister");

		if (autoRegister) {
			List<String> importsList = new ArrayList<>(Arrays.asList(imports));
      // 返回AutoServiceRegistrationConfiguration,之后被导入
      importsList.add("org.springframework.cloud.client.serviceregistry."+
                      "AutoServiceRegistrationConfiguration");
			imports = importsList.toArray(new String[0]);
		}

		return imports;
	}

	@Override
	protected boolean isEnabled() {
		return new RelaxedPropertyResolver(getEnvironment()).getProperty(
				"spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
	}

	@Override
	protected boolean hasDefaultFactory() {
		return true;
	}

}

selectImports方法主要就是导入了一个类AutoServiceRegistrationCofiguration,我们看看导入的这个类干了啥

@Configuration
@EnableConfigurationProperties(AutoServiceRegistrationProperties.class)
public class AutoServiceRegistrationConfiguration {
}
@ConfigurationProperties("spring.cloud.service-registry.auto-registration")
public class AutoServiceRegistrationProperties {

   /** If Auto-Service Registration is enabled, default to true. */
   private boolean enabled = true;

   /** Should startup fail if there is no AutoServiceRegistration, default to false. */
   private boolean failFast = false;

   public boolean isFailFast() {
      return failFast;
   }

   public void setFailFast(boolean failFast) {
      this.failFast = failFast;
   }
}

只是简单的加载了一些属性,如果没有配置该属性那么将会是默认值(eureka启动的大部分默认值都是true,所以你会发现,即使没有添加@EnableDiscoveryClient注解,使用默认值也是可以的)。

EurekaClientAutoConfiguration自动配置客户端

@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@Import(DiscoveryClientOptionalArgsConfiguration.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({ NoopDiscoveryClientAutoConfiguration.class,
        CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = {
        "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
        "org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
        "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
public class EurekaClientAutoConfiguration {
    // ...
}

注解比较多,但是没那么复杂。就是加载一些附加的配置,以及判断一下是否开始自动配置。下面,我们再看看内部添加了哪些东西(只关注几个核心的)

配置当前实例信息

配置实例信息包含很多,不过核心的无非就是名称、唯一标识、IP地址、端口等等

@Bean
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class,
        search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
        ManagementMetadataProvider managementMetadataProvider) {
    // ...
    EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);

    instance.setNonSecurePort(serverPort);
    instance.setInstanceId(getDefaultInstanceId(env));
    instance.setPreferIpAddress(preferIpAddress);
    instance.setSecurePortEnabled(isSecurePortEnabled);
    if (StringUtils.hasText(ipAddress)) {
        instance.setIpAddress(ipAddress);
    }

    // ...

    setupJmxPort(instance, jmxPort);
    return instance;
}

负责注册的Bean

@Bean
public EurekaServiceRegistry eurekaServiceRegistry() {
    return new EurekaServiceRegistry();
}

自动注册调用的Bean

@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(
        value = "spring.cloud.service-registry.auto-registration.enabled",
        matchIfMissing = true)
public EurekaAutoServiceRegistration eurekaAutoServiceRegistration(
        ApplicationContext context, EurekaServiceRegistry registry,
        EurekaRegistration registration) {
    return new EurekaAutoServiceRegistration(context, registry, registration);
}

Eureka待注册的对象

这个对象会包含上面的eurekaInstanceIConfigBean

@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(
        value = "spring.cloud.service-registry.auto-registration.enabled",
        matchIfMissing = true)
public EurekaRegistration eurekaRegistration(EurekaClient eurekaClient,
        CloudEurekaInstanceConfig instanceConfig,
        ApplicationInfoManager applicationInfoManager, @Autowired(
                required = false) ObjectProvider<HealthCheckHandler> healthCheckHandler) {
    return EurekaRegistration.builder(instanceConfig).with(applicationInfoManager)
            .with(eurekaClient).with(healthCheckHandler).build();
}

总结

eureka客户端的自动配置,其实就是做了一些注册等操作之前的准备。准备实例信息,配置eureka框架的东西。代码很多,但是逻辑并不复杂。下一篇文章,我们将看看如何自动注册。

eureka客户端自动注册服务

在自动配置的时候会产生一个负责自动注册的Bean,也就是EurekaAutoServiceRegistration,所以,我们打开EurekaAutoServiceRegistration看看

public class EurekaAutoServiceRegistration implements AutoServiceRegistration, SmartLifecycle, Ordered, SmartApplicationListener {

    private ApplicationContext context;

    private EurekaServiceRegistry serviceRegistry;

    private EurekaRegistration registration;

    public EurekaAutoServiceRegistration(ApplicationContext context, EurekaServiceRegistry serviceRegistry, EurekaRegistration registration) {
        this.context = context;
        this.serviceRegistry = serviceRegistry;
        this.registration = registration;
    }

    @Override
    public void start() {
        // ...

        if (!this.running.get() && this.registration.getNonSecurePort() > 0) {

            // 调用注册
            this.serviceRegistry.register(this.registration);

            this.context.publishEvent(new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));

            this.running.set(true);
        }
    }

}

注册任务被委托给了serviceRegistry来做,跟进register方法

@Override
public void register(EurekaRegistration reg) {
    maybeInitializeClient(reg);

    // ...

    reg.getApplicationInfoManager().setInstanceStatus(reg.getInstanceConfig().getInitialStatus());

    reg.getHealthCheckHandler().ifAvailable(healthCheckHandler -> reg.getEurekaClient().registerHealthCheck(healthCheckHandler));
}

调用了eurekaClient原始的registerhealthCheck方法,跟进它

@Override
public void registerHealthCheck(HealthCheckHandler healthCheckHandler) {
    if (instanceInfo == null) {
        logger.error("Cannot register a healthcheck handler when instance info is null!");
    }
    if (healthCheckHandler != null) {
        // 注册心跳检查处理器
        this.healthCheckHandlerRef.set(healthCheckHandler);
        
        // schedule an onDemand update of the instanceInfo when a new healthcheck handler is registered
        if (instanceInfoReplicator != null) {
            instanceInfoReplicator.onDemandUpdate();
        }
    }
}

心跳检查处理器被设置为了成员变量,执行的核心逻辑被托付给了onDemandUpdate方法,跟进它

public boolean onDemandUpdate() {
    if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {
        if (!scheduler.isShutdown()) {
            scheduler.submit(new Runnable() {
                @Override
                public void run() {
                    // ...

                    InstanceInfoReplicator.this.run();
                }
            });
            return true;
        } else {
            // ... 
        }
    } else {
        // ...
    }
}

单线程异步执行了当前类的run方法,进入run方法

public void run() {
    try {
        // 刷新实例信息
        discoveryClient.refreshInstanceInfo();

        Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
        if (dirtyTimestamp != null) {
            // 注册
            discoveryClient.register();
            instanceInfo.unsetIsDirty(dirtyTimestamp);
        }
    } catch (Throwable t) {
        // ...
    } finally {
        // 下一次延迟执行
        Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
        scheduledPeriodicRef.set(next);
    }
}

run方法每次执行都会刷新实例信息,然后调用register注册新的实例信息,最后发出下一次执行的延迟任务

跟进register方法

boolean register() throws Throwable {
    logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
    EurekaHttpResponse<Void> httpResponse;
    try {
        // 发出远程请求
        httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
    } catch (Exception e) {
        
    }
    
    return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode();
}

继续跟进register方法,发出了http请求,这里以jersey为例

@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
    String urlPath = "apps/" + info.getAppName();
    ClientResponse response = null;
    try {
        Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
        addExtraHeaders(resourceBuilder);
        response = resourceBuilder
                .header("Accept-Encoding", "gzip")
                .type(MediaType.APPLICATION_JSON_TYPE)
                .accept(MediaType.APPLICATION_JSON)
                // 发出http请求
                .post(ClientResponse.class, info);
        return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
    } finally {
        if (logger.isDebugEnabled()) {
            logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
                    response == null ? "N/A" : response.getStatus());
        }
        if (response != null) {
            response.close();
        }
    }
}

http请求将进入eureka服务端,

总结

eureka客户端自动注册服务主要是将自动配置的时候拿到的实例信息通过http请求发送给eureka服务端,默认30秒会执行一次。

posted @ 2020-06-02 20:31  江舟  阅读(357)  评论(0)    收藏  举报