走进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,可以看到如下管理界面