Eureka Server源码分析一——走进Eureka 注册中心

1、简介

Eureka Server,也称为服务注册中心。通过接收客户端发送的注册、续约、服务下线等请求,维护服务注册表。消费者,从注册中心获取注册表信息,于客户端处进行负载均衡后进行请求操作。
另外,多台Eureka Server,通过相互注册,通过同步各自注册表,以达到高可用效果。

2、源码分析

要想开启服务注册中心,需要在启动类或配置类上,添加EnableEurekaServer注解。本节将分析EnableEurekaServer如何开启服务注册中心。

2.1 开启自动配置

查看EnableEurekaServer定义,其通过Import注解,引入EurekaServerMarkerConfiguration配置类。该配置类源码如下,非常简洁。

@Configuration
public class EurekaServerMarkerConfiguration {

	@Bean
	public Marker eurekaServerMarkerBean() {
		return new Marker();
	}

	class Marker {

	}
}

看到这里,可能各位要失望了。其实,这个配置类,需要结合EurekaServerAutoConfiguration一起使用,才能真正发挥其作用。该配置类的部分定义如下所示

@Configuration
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
		InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {

Marker Bean只不过是用于开启EurekaServerAutoConfiguration配置类的钥匙,真正的服务注册中心的配置与开启,是通过该配置类完成的。

2.2 注册操作初步分析

在上一篇博客介绍到,Eureka Client向Eureka Server发送注册等请求,用于注册中心,维护服务提供方提供的服务信息。其中服务注册请求接收方,对应于ApplicationResource.addInstance方法。

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

上述代码,通过registry.register完成服务注册,registry对应于PeerAwareInstanceRegistry实例。该接口拥有Eureka Server所提供诸如注册、下线、续约等功能。继承体系如下所示
在这里插入图片描述
关于PeerAwareInstanceRegistry中的注册、续约、下线等操作,将在后续博文进行详细分析。

2.3 与Eureka对接

针对于PeerAwareInstanceRegistry,Spring提供了一个InstanceRegistry实现类,在原实现类(PeerAwareInstanceRegistryImpl)基础上,发布对应操作事件。那么这个类如何工作呢?

2.3.1 Eureka侧PeerAwareInstanceRegistry注入

通过上面介绍,ApplicationResource基于PeerAwareInstanceRegistry完成注册操作。该对象通过构造方法,进行传递。

ApplicationResource(String appName,
                        EurekaServerConfig serverConfig,
                        PeerAwareInstanceRegistry registry) {
        this.appName = appName.toUpperCase();
        this.serverConfig = serverConfig;
        this.registry = registry;
        this.responseCache = registry.getResponseCache();
    }

通过查看ApplicationResource构造顺序,发现该属性传入顺序如下:

  • PeerReplicationResource类的构造方法处,通过EurekaServerContextHolder.getInstance().getServerContext(),获取EurekaServerContext对象,通过该对象getRegistry方法,创建registry属性。
  • 通过PeerReplicationResource.createApplicationResource方法,将registry属性传递给ApplicationResource。

也就是说,PeerAwareInstanceRegistry接口实例,通过EurekaServerContext.getRegistry创建,而EurekaServerContext实例,保存于EurekaServerContextHolder对象中。

2.3.2 Spring侧PeerAwareInstanceRegistry注入

Spring正是通过修改EurekaServerContextHolder中存储的EurekaServerContext对象,达到让InstanceRegistry工作的效果。而这一步操作,是通过EurekaServerBootstrap.initEurekaServerContext完成。

protected void initEurekaServerContext() throws Exception {
		// For backward compatibility
		JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
				XStream.PRIORITY_VERY_HIGH);
		XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(),
				XStream.PRIORITY_VERY_HIGH);

		if (isAws(this.applicationInfoManager.getInfo())) {
			this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig,
					this.eurekaClientConfig, this.registry, this.applicationInfoManager);
			this.awsBinder.start();
		}

		EurekaServerContextHolder.initialize(this.serverContext);

		log.info("Initialized server context");

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

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

那么EurekaServerBootstrap实例,又是怎么诞生的呢?这个问题又要回到EurekaServerAutoConfiguration配置类了,在该配置类中,创建如下3个Bean。

	@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());
	}

	@Bean
	public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
			PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
		return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
				registry, peerEurekaNodes, this.applicationInfoManager);
	}

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

于是EurekaServerBootstrap实例所需的全部对象,完成创建(包括InstanceRegistry)

2.3.3 initEurekaServerContext方法调用

EurekaServerAutoConfiguration配置类,通过Import注解,引入EurekaServerInitializerConfiguration配置类。

在该配置类的start方法处,以线程方式,执行contextInitialized方法,从而调用initEurekaServerContext方法,将EurekaServerContext对象,初始化进EurekaServerContextHolder。

public void start() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					// TODO: is this class even needed now?
					eurekaServerBootstrap.contextInitialized(
							EurekaServerInitializerConfiguration.this.servletContext);
					log.info("Started Eureka Server");

					publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
					EurekaServerInitializerConfiguration.this.running = true;
					publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
				}
				catch (Exception ex) {
					// Help!
					log.error("Could not initialize Eureka servlet context", ex);
				}
			}
		}).start();
	}

到这里,Spring巧妙完成将InstanceRegistry实现类,提供给Eureka Server,进行注册等操作。

3、运行注册中心

到目前为止,介绍完Spring如何开启Eureka Server自动配置,以及将自己提供的注册等操作处理类,提供给Eureka Server进行注册操作(关于注册等操作分析,详见后续博文)。
下面,按照如下步骤,创建一个Eureka Server。

  • 通过start.spring.io,创建一个Spring Boot工程,然后在pom.xml,添加如下依赖
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
  • 启动类上,添加EnableEurekaServer注解。
  • 在application.properties配置文件处,添加如下配置
spring.application.name=microserver-eureka
server.port=8010
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka

启动该项目,通过http://localhost:8010,可以看到如下管理界面
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我要做个有钱人2020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值