1 Eureka简介
Eureka是Spring Cloud体系中用于服务注册与发现的组件。主要解决子项目之间的通讯问题。
1.1 主要角色
Eureka体系调用关系图
注册中心(Eureka Server):主要用于服务注册和发现的注册中心。
- 失效剔除:将超出设置时间没有收到心跳续约的服务从注册服务列表中剔除。
- 自我保护:根据心跳续约失败率在规定时间内是否低于85%的统计信息,将注册服务实例信息保护起来。
服务提供者(Service Provider):向注册中心提供服务的客户端(如图Eureka Client A)。
- 服务注册:服务提供者启动时,通过API调用的方式向注册中心注册自己的服务信息,其中包括IP、端口、调用路由等信息。
- 服务续约:服务注册完毕过后,通过心跳机制向服务器更新状态,保持注册信息不被剔除。
- 服务取消(取消租约):服务提供者关闭时,通过API调用的方式向注册中心注销自己的服务。
- 获取服务:服务提供者在注册自己的同时也会从注册中心获取一份服务注册列表信息。
服务消费者(Service Consumer):从注册中心获取服务的客户端(如图Eureka Client B、C)。
- 获取服务:服务消费者启动过后也会向注册中心注册自己,同时从中获取最新的服务注册列表信息。
- 服务调用:获取到服务注册信息后,通过服务注册元数据(IP,PORT,URL等)采用HttpClient的方式调用原服务。
其中服务提供者可以有多个,Eureka Client自身通过负载均衡Ribbon和熔断机制Hystrix进行具体的调用。
1.2 Region和Zone
region是区域,属于地理层面上的分区。例如游戏服务分区的华北区、华东区、华中区、中国区等等。
zone是可用区,属于物理层面上的分区,比如一个机房属于一个可用区。一个region里面可以有一个或多个zone可用区。每个zone里面可以有一台或多台服务器。
spring cloud eureka中相同的服务优先调用同一个zone中的,当同一个zone中的服务调用失败时才调用同一个region中其他zone中的同一个服务。这样可以在网络延时方面节约调用时间。
Eureka配置文件示例:
spring:
application:
name: Server-1
server:
port: 12000
eureka:
instance:
prefer-ip-address: true
hostname: localhost
client:
#是否向服务器注册自己
register-with-eureka: true
#默认为true,通过registry-fetch-interval-seconds=30配置向服务器拉取服务注册列表
fetch-registry: true
prefer-same-zone-eureka: true
#地区
region: sichuan
#可用区
availability-zones:
sichuan: chengdu,leshan
service-url:
chengdu: http://localhost:12000/eureka/
leshan: http://localhost:12001/eureka/
Client配置文件示例:
spring:
application:
name: service
server:
port: 12003
eureka:
instance:
prefer-ip-address: true
#指定zone
metadata-map:
zone: chengdu
client:
register-with-eureka: true
fetch-registry: true
prefer-same-zone-eureka: true
#地区
region: sichuan
availability-zones:
sichuan: chengdu,leshan
service-url:
chengdu: http://localhost:12000/eureka/
leshan: http://localhost:12001/eureka/
1.3 启动分析
打开Eureka客户端启动类注解@EnableDiscoveryClient类,通过注释“Annotation to enable a DiscoveryClient implementation.”可知道此注解启动了DiscoveryClient的实现类,直接全局搜索得到如下类图。
2 服务注册
2.1 客户端
通过上图,全局搜索找到实现类DiscoveryClient.class。通过浏览此类的构造函数,找到如下代码:
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider) {
//......省略
initScheduledTasks();
//......省略
}
private void initScheduledTasks() {
//......省略
if (clientConfig.shouldRegisterWithEureka()) {
//......省略
// InstanceInfo replicator
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
//......省略
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
由代码可知, initScheduledTasks();方法中启动了三个任务,分别是服务获取,服务续约和服务注册,如上所示,instanceInfoReplicator就是负责服务注册的线程,其run方法如下:
public void run() {
try {
discoveryClient.refreshInstanceInfo();
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
discoveryClient.register();
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
logger.warn("There was a problem with the instance info replicator", t);
} finally {
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
其中调用注册方法register()。源码如下:
boolean register() throws Throwable {
logger.info(PREFIX + appPathIdentifier + ": registering service...");
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn("{} - registration failed {}", PREFIX + appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == 204;
}
由源码可知客户端注册是采用的http rest请求的方式注册自己的。通过debug源码,是通过com.netflix.discovery.shared.transport.jersey.AbstractJerseyEurekaHttpClient类的register()方法发起的rest调用,调用地址为:http://localhost:8001/eureka/apps/API-BASE,8001端口部署的是eureka服务器项目,com.netflix.appinfo.InstanceInfo是客户端注册时发送给服务端的元数据,截图如下: