简介
eureka 作为注册中心而言如今也没有太多企业采用,因为eureka2.0之后闭源,且不再对现有版本维护,而且spring cloud也支持了zoomkeeper了作为注册中心。所以大多企业更愿倾向于zoomkeeper或者consul注册中心。对于初学者而言,eureka还是有一定的必要,因为它最先呗spring cloud集成,从职责单一性而言,比zoomkeeper要简单易学。而且与ribbon,Hystrix更容易结合,都是同一公司Netflix产品。
eureka的运用
eureka作为注册中心,需要三个部分组成,一个是服务端,一个是client端,一个是消费端。
从图上而言,eureka server端起来后,client端会向server端进行注册,消费端从server端拉取所有已注册的服务信息,获取所需服务的ip以及其他信息,进行消费。
server端
笔者目前使用的是spring boot为2.1.4.RELEASE 版本,spring cloud 为Greenwich.SR1版本。
引入对应的pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
application.properties的修改
`# 服务名称
spring.application.name=eureka-server
# 启动端口
server.port=1101
# 实例地址
eureka.instance.hostname=localhost
# 是否自i注册
eureka.client.register-with-eureka=false
# 是否拉取注册服务信息
eureka.client.fetch-registry=false
# 设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。多个地址可使用 , 分隔。
eureka.client.serviceUrl.defaultZone=http://localhost:7001/eureka/
启动类的修改
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
启动类上添加一个@EnableEurekaServer
注解,就可以启动了。启动后访问http://localhost:1101/就可以看到注册中心界面。
application下的值为空,说明目前没有服务注册上去。ds replicas值为空,说明目前也没有集群。
client端
pom文件依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
引入client的启动依赖。
application.properties的修改
spring.application.name=eureka-client
server.port=2101
# 指向eureka server端的IP地址
eureka.client.serviceUrl.defaultZone=http://localhost:1101/eureka/
相比于spring boot1.x版本,eureka client 端的启动不需要添加@EnableDiscoveryClient
这个注解,已经集成到spring boot auto configuration 里。
Controller层代码的编写
@GetMapping("/")
public String hello(@RequestParam String name) {
return "Hello, " + name + " " + new Date();
}
然后启动client端。
注册中心可以明显观察到一个子服务注册上去了。
访问http://localhost:2101/hello/?name=%22aaa%22
源码分析
关于eureka的启动源码分析,我推荐SpringCloud(第 049 篇)Netflix Eureka 源码深入剖析(上)这篇文章讲的很详细,需要耐心去读。在他的基础上,个人总结和简化一些。
eureka server端的启动类是EurekaServerBootstrap,启动的时候初始化了一些环境配置和内容。
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);
}
}
进入到initEurekaEnvironment()方法里,清楚的发现eureka启动时会去加载数据中心的实例,如果没有就使用默认。
protected void initEurekaEnvironment() throws Exception {
log.info("Setting the eureka configuration..");
String dataCenter = ConfigurationManager.getConfigInstance()
.getString(EUREKA_DATACENTER);
if (dataCenter == null) {
log.info(
"Eureka data center value eureka.datacenter is not set, defaulting to default");
ConfigurationManager.getConfigInstance()
.setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
}
else {
ConfigurationManager.getConfigInstance()
.setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
}
String environment = ConfigurationManager.getConfigInstance()
.getString(EUREKA_ENVIRONMENT);
if (environment == null) {
ConfigurationManager.getConfigInstance()
.setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
log.info(
"Eureka environment value eureka.environment is not set, defaulting to test");
}
else {
ConfigurationManager.getConfigInstance()
.setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, environment);
}
}
接下来我们查看另一个内容初始化的方法。
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();
}
从这里明白,内容初始化后的服务注册是在这里实现的。然后追踪这个register,定位到EurekaServerAutoConfiguration这个类
@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());
}
了解到从client端获取服务注册的信息。
接下来分析一下关于eureka client端的代码,首先进行@EnableDiscoveryClient注解类。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({EnableDiscoveryClientImportSelector.class})
public @interface EnableDiscoveryClient {
boolean autoRegister() default true;
}
这个类导入了EnableDiscoveryClientImportSelector类,然后进去看看这个类干了啥。
public String[] selectImports(AnnotationMetadata metadata) {
String[] imports = super.selectImports(metadata);
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(this.getAnnotationClass().getName(), true));
boolean autoRegister = attributes.getBoolean("autoRegister");
if (autoRegister) {
List<String> importsList = new ArrayList(Arrays.asList(imports));
importsList.add("org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration");
imports = (String[])importsList.toArray(new String[0]);
} else {
Environment env = this.getEnvironment();
if (ConfigurableEnvironment.class.isInstance(env)) {
ConfigurableEnvironment configEnv = (ConfigurableEnvironment)env;
LinkedHashMap<String, Object> map = new LinkedHashMap();
map.put("spring.cloud.service-registry.auto-registration.enabled", false);
MapPropertySource propertySource = new MapPropertySource("springCloudDiscoveryClient", map);
configEnv.getPropertySources().addLast(propertySource);
}
}
return imports;
}
protected boolean isEnabled() {
return (Boolean)this.getEnvironment().getProperty("spring.cloud.discovery.enabled", Boolean.class, Boolean.TRUE);
}
代码中主要两个方法,selectImports()方法调用父类中的selectImports()方法去读取jar包里的配置文件。isEnabled()方法就很易懂,判断是否可发现。也就是说client端起来的时候读取本地的配置文件,并发送请求到server端进行注册。
服务健康判断以及拉取所有服务的代码也简单明了。EurekaDiscoveryClient类里的getservice()方法拉取服务端所有注册的服务。
public List<String> getServices() {
Applications applications = this.eurekaClient.getApplications();
if (applications == null) {
return Collections.emptyList();
}
List<Application> registered = applications.getRegisteredApplications();
List<String> names = new ArrayList<>();
for (Application app : registered) {
if (app.getInstances().isEmpty()) {
continue;
}
names.add(app.getName().toLowerCase());
}
return names;
}
同理服务的健康监听也有专门的EurekaHealthIndicator去实现。
再讲一个重要的类EurekaClientConfigBean,这个类配置了很多注册以及缓存的相关信息。
// 标注了服务注册的延时时间
public int getInitialInstanceInfoReplicationIntervalSeconds() {
return initialInstanceInfoReplicationIntervalSeconds;
}
public int getRegistryFetchIntervalSeconds() {
return registryFetchIntervalSeconds;
}
// 标注了获的服务实例的延时时间
public int getInstanceInfoReplicationIntervalSeconds() {
return instanceInfoReplicationIntervalSeconds;
}
所以这就是eureka注册服务比较慢的原因,也是它不能保持cp的原因,因为注册服务以及更新缓存都有一定的延时性。