系列文章目录
springCloud实践
springCloud实践之浅谈Feign原理
springCloud-Eureka(一)
springCloud-Eureka—服务注册与服务续约(二)
springCloud-Eureka—服务同步与剔除(三)
文章目录
- 系列文章目录
- 前言
- 1、服务注册机制
- 1.1、@ConditionalOnClass(EurekaClientConfig.class)
- 1.2、@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
- 1.3、EurekaClientAutoConfiguration
- 1.4、EurekaAutoServiceRegistration
- 1.5、EurekaServiceRegistry
- 1.6、ApplicationInfoManager
- 1.7、DiscoveryClient -> initScheduledTasks()
- 1.8、InstanceInfoReplicator -> onDemandUpdate()
- 1.9、DiscoveryClient -> register
- 2、服务心跳续约
- 总结
前言
前一篇文章我们介绍了eureka的特点、适用场景以及数据的存储结构,本文继上文结合源码讲解eureka的服务注册、续约机制
1、服务注册机制
服务提供者、服务消费者以及注册中心自己,启动后都会向注册中心注册服务(配置了注册)
注册中心服务接收到register请求后:
- 保存服务信息,将服务信息保存到registry中;
- 更新队列,将此事件添加到更新队列中,供eureka Client增量同步服务信息使用;
- 清空二级缓存,即readWriteCacheMap,用于保证数据一致性;
- 更新阈值,供剔除服务使用;
- 同步服务信息,将此事件同步至其他的Eureka Server节点。
我应用的是spring-cloud-starter-netflix-eureka-server,它整合了spring-cloud-netflix-eureka-client
入口:看一下Eureka client启动时做了什么,通过META-INF/spring.factories里配置的
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\Eureka Client自动配置类,负责client中关键beans配置和初始化
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\负载均衡相关配置
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration配置自动注册和应用的健康检查器
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
EurekaClientAutoConfiguration完成自动注入+注册
下面我们就从EurekaClientAutoConfiguration着手,看看都有哪些操作
@Configuration
//加载配置文件解析
@EnableConfigurationProperties
//加载client配置项
@ConditionalOnClass(EurekaClientConfig.class)
//加载DiscoveryClientOptionalArgsConfiguration到容器
@Import(DiscoveryClientOptionalArgsConfiguration.class)
//客户端启用引导标记
@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
//启用eureka client,默认启用
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
//当前配置类EurekaClientAutoConfiguration加载完毕后的后续加载
@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{
}
1.1、@ConditionalOnClass(EurekaClientConfig.class)
@ImplementedBy(DefaultEurekaClientConfig.class)
public interface EurekaClientConfig {
}
看一下默认实现DefaultEurekaClientConfig
@Singleton
@ProvidedBy(DefaultEurekaClientConfigProvider.class)
public class DefaultEurekaClientConfig implements EurekaClientConfig {
@Deprecated
public static final String DEFAULT_NAMESPACE = CommonConstants.DEFAULT_CONFIG_NAMESPACE + ".";
public static final String DEFAULT_ZONE = "defaultZone";
private final String namespace;
private final DynamicPropertyFactory configInstance;
private final EurekaTransportConfig transportConfig;
public DefaultEurekaClientConfig() {
this(CommonConstants.DEFAULT_CONFIG_NAMESPACE);
}
public DefaultEurekaClientConfig(String namespace) {
this.namespace = namespace.endsWith(".")
? namespace
: namespace + ".";
this.configInstance = Archaius1Utils.initConfig(CommonConstants.CONFIG_FILE_NAME);
this.transportConfig = new DefaultEurekaTransportConfig(namespace, configInstance);
}
@Override
public int getRegistryFetchIntervalSeconds() {
return configInstance.getIntProperty(
namespace + REGISTRY_REFRESH_INTERVAL_KEY, 30).get();
}
@Override
public int getInstanceInfoReplicationIntervalSeconds() {
return configInstance.getIntProperty(
namespace + REGISTRATION_REPLICATION_INTERVAL_KEY, 30).get();
}
@Override
public int getInitialInstanceInfoReplicationIntervalSeconds() {
return configInstance.getIntProperty(
namespace + INITIAL_REGISTRATION_REPLICATION_DELAY_KEY, 40).get();
}
@Override
public int getEurekaServiceUrlPollIntervalSeconds() {
return configInstance.getIntProperty(
namespace + EUREKA_SERVER_URL_POLL_INTERVAL_KEY, 5 * 60 * 1000).get() / 1000;
}
@Override
public String getProxyHost() {
return configInstance.getStringProperty(
namespace + EUREKA_SERVER_PROXY_HOST_KEY, null).get();
}
********************加载 eureka client 默认值*********************
}
1.2、@ConditionalOnBean(EurekaDiscoveryClientConfiguration.Marker.class)
该配置主要做了两件事情,一个是监听RefreshScopeRefreshedEvent事件,配置文件动态刷新时触发。另一个是配置健康检查处理程序。
@Configuration
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
public class EurekaDiscoveryClientConfiguration {
class Marker {}
@Bean
public Marker eurekaDiscoverClientMarker() {
return new Marker();
}
//配置刷新事件的监听器
@Configuration
@ConditionalOnClass(RefreshScopeRefreshedEvent.class)
protected static class EurekaClientConfigurationRefresher {
@Autowired(required = false)
private EurekaClient eurekaClient;
@Autowired(required = false)
private EurekaAutoServiceRegistration autoRegistration;
@EventListener(RefreshScopeRefreshedEvent.class)
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
//This will force the creation of the EurkaClient bean if not already created
//to make sure the client will be reregistered after a refresh event
if(eurekaClient != null) {
eurekaClient.getApplications();
}
if (autoRegistration != null) {
// register in case meta data changed
this.autoRegistration.stop();
this.autoRegistration.start();
}
}
}
//健康检查配置
@Configuration
@ConditionalOnProperty(value = "eureka.client.healthcheck.enabled", matchIfMissing = false)
protected static class EurekaHealthCheckHandlerConfiguration {
@Autowired(required = false)
private HealthAggregator healthAggregator = new OrderedHealthAggregator();
@Bean
@ConditionalOnMissingBean(HealthCheckHandler.class)
public EurekaHealthCheckHandler eurekaHealthCheckHandler() {
return new EurekaHealthCheckHandler(this.healthAggregator);
}
}
}
1.3、EurekaClientAutoConfiguration
源码可以看到这里创建了EurekaClientConfigBean、EurekaInstanceConfigBean两个基本配置,以及EurekaServiceRegistry
@Bean
@ConditionalOnMissingBean(value = ApplicationInfoManager.class, search = SearchStrategy.CURRENT)
public ApplicationInfoManager eurekaApplicationInfoManager(
EurekaInstanceConfig config) {
InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
return new ApplicationInfoManager(config, instanceInfo);
}
-
InstanceInfo
使用EurekaInstanceConfig,通过new InstanceInfoFactory().create(config)创建 -
ApplicationInfoManager
使用InstanceInfo以及EurekaInstanceConfig创建,new ApplacationInfoManager(config, instancInfo) -
EurekaClient
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingBean(value = EurekaClient.class, search = SearchStrategy.CURRENT)
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config) {
return new CloudEurekaClient(manager, config, this.optionalArgs,
this.context);
}
使用ApplicationInfoManager、EurekaClientConfig创建,new CloudEurekaClient(manager,config,this.optionalArgs,this.context)
- EurekaRegistration
@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();
}
通过EurekaServiceRegistry、EurekaRegistration创建
- EurekaAutoServiceRegistration
@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);
}
通过ApplicationContext、EurekaServiceRegistry、EurekaRegistration
1.4、EurekaAutoServiceRegistration
该类实现了接口SmartLifecyle的方法。SmartLifecycle是一个接口。当Spring容器加载所有bean并完成初始化后,会接着回调该接口的类中对应的方法。
public class EurekaAutoServiceRegistration implements AutoServiceRegistration, SmartLifecycle, Ordered {
private static final Log log = LogFactory.getLog(EurekaAutoServiceRegistration.class);
private AtomicBoolean running = new AtomicBoolean(false);
private int order = 0;
private AtomicInteger port = new AtomicInteger(0);
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;
}
/**
*把自身应用实例的信息注册到eureka server中(start)
*/
@Override
public void start() {
//设置端口号
if (this.port.get() != 0) {
if (this.registration.getNonSecurePort() == 0) {
this.registration.setNonSecurePort(this.port.get());
}
if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
this.registration.setSecurePort(this.port.get());
}
}
// 判断端口号是否被占用
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);
}
}
/**
* 服务主动下线stop()
*/
@Override
public void stop() {
//通过调用this.serviceRegistry.deregister(this.registration)方法,告知eureka server自身服务下线
this.serviceRegistry.deregister(this.registration);
this.running.set(false);
}
@Override
public boolean isRunning() {
return this.running.get();
}
@Override
public int getPhase() {
return 0;
}
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable callback) {
stop();
callback.run();
}
@Override
public int getOrder() {
return this.order;
}
@EventListener(WebServerInitializedEvent.class)
public void onApplicationEvent(WebServerInitializedEvent event) {
// TODO: take SSL into account
int localPort = event.getWebServer().getPort();
if (this.port.get() == 0) {
log.info("Updating port to " + localPort);
this.port.compareAndSet(0, localPort);
start();
}
}
@EventListener(ContextClosedEvent.class)
public void onApplicationEvent(ContextClosedEvent event) {
if( event.getApplicationContext() == context ) {
stop();
}
}
}
1.5、EurekaServiceRegistry
根据EurekaAutoServiceRegistration源码可知,其中start()、stop()方法分别调用了EurekaServiceRegistry的register()、deregister()方法实现了服务的注册和服务主动下线,下面我们分别看一下这两个方法;
1.5.1、服务注册register
调用ApplicationInfoManager的setInstanceStatus方法来变更状态
@Override
public void register(EurekaRegistration reg) {
maybeInitializeClient(reg);
if (log.isInfoEnabled()) {
log.info("Registering application " + reg.getApplicationInfoManager().getInfo().getAppName()
+ " with eureka with status "
+ reg.getInstanceConfig().getInitialStatus());
}
reg.getApplicationInfoManager()
.setInstanceStatus(reg.getInstanceConfig().getInitialStatus());
reg.getHealthCheckHandler().ifAvailable(healthCheckHandler ->
reg.getEurekaClient().registerHealthCheck(healthCheckHandler));
}
private void maybeInitializeClient(EurekaRegistration reg) {
// force initialization of possibly scoped proxies
reg.getApplicationInfoManager().getInfo();
reg.getEurekaClient().getApplications();
}
1.5.2、服务自动下线deregister
@Override
public void deregister(EurekaRegistration reg) {
if (reg.getApplicationInfoManager().getInfo() != null) {
if (log.isInfoEnabled()) {
log.info("Unregistering application " + reg.getApplicationInfoManager().getInfo().getAppName()
+ " with eureka with status DOWN");
}
reg.getApplicationInfoManager().setInstanceStatus(InstanceInfo.InstanceStatus.DOWN);
//shutdown of eureka client should happen with EurekaRegistration.close()
//auto registration will create a bean which will be properly disposed
//manual registrations will need to call close()
}
}
1.6、ApplicationInfoManager
设置服务实例的状态并发布StatusChangeEvent事件,通知该事件所有监听者
/**
* 设置实例状态,应用程序可以使用它来表示它是否已经准备好接收流量。
* 此处设置状态还会通知所有已注册的监听器状态改变事件
*/
public synchronized void setInstanceStatus(InstanceStatus status) {
//当前服务实例状态
InstanceStatus next = instanceStatusMapper.map(status);
if (next == null) {
return;
}
//当前服务实例之前的状态
InstanceStatus prev = instanceInfo.setStatus(next);
if (prev != null) {
for (StatusChangeListener listener : listeners.values()) {
try {
//状态变化事件通知所有监听者
listener.notify(new StatusChangeEvent(prev, next));
} catch (Exception e) {
logger.warn("failed to notify listener: {}", listener.getId(), e);
}
}
}
}
1.7、DiscoveryClient -> initScheduledTasks()
这里注册了StatusChangeListener监听,之后触发instanceInfoReplicator.onDemandUpdate()方法
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider) {
if (args != null) {
this.healthCheckHandlerProvider = args.healthCheckHandlerProvider;
this.healthCheckCallbackProvider = args.healthCheckCallbackProvider;
this.eventListeners.addAll(args.getEventListeners());
this.preRegistrationHandler = args.preRegistrationHandler;
} else {
this.healthCheckCallbackProvider = null;
this.healthCheckHandlerProvider = null;
this.preRegistrationHandler = null;
}
this.applicationInfoManager = applicationInfoManager;
InstanceInfo myInfo = applicationInfoManager.getInfo();
clientConfig = config;
staticClientConfig = clientConfig;
transportConfig = config.getTransportConfig();
instanceInfo = myInfo;
//客户端路径唯一标识符:xx-service/120.0.0.1:8080
if (myInfo != null) {
appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId();
} else {
logger.warn("Setting instanceInfo to a passed in null value");
}
this.backupRegistryProvider = backupRegistryProvider;
this.urlRandomizer = new EndpointUtils.InstanceInfoBasedUrlRandomizer(instanceInfo);
localRegionApps.set(new Applications());
//获取远程eureka服务注册列表的次数
fetchRegistryGeneration = new AtomicLong(0);
remoteRegionsToFetch = new AtomicReference<String>(clientConfig.fetchRegistryForRemoteRegions());
remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(","));
//判断配置是否需要获取远程eureka服务注册列表
if (config.shouldFetchRegistry()) {
this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
} else {
this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
}
//判断是否需要注册到远程eureka服务
if (config.shouldRegisterWithEureka()) {
this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
} else {
this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
}
logger.info("Initializing Eureka in region {}", clientConfig.getRegion());
//客户端配置为既不注册也不查询数据
if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
logger.info("Client configured to neither register nor query for data.");
scheduler = null;
heartbeatExecutor = null;
cacheRefreshExecutor = null;
eurekaTransport = null;
instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());
// This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
// to work with DI'd DiscoveryClient
DiscoveryManager.getInstance().setDiscoveryClient(this);
DiscoveryManager.getInstance().setEurekaClientConfig(config);
initTimestampMs = System.currentTimeMillis();
logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
initTimestampMs, this.getApplications().size());
return; // no need to setup up an network tasks and we are done
}
//# 开始定义定时任务调度器 scheduler
try {
// default size of 2 - 1 each for heartbeat and cacheRefresh
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
//通过定时调度每隔30s,处理心跳
//线程池的核心线程数为1,最大线程数默认为2(HeartbeatExecutorThreadPoolSize)
//使用同步队列:SynchronousQueue,每次提交都要阻塞等待处理
heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
//通过定时调度每隔30s,处理eureka提供的服务列表缓存
//线程池的核心线程数为1,最大线程数默认为2(HeartbeatExecutorThreadPoolSize)
//使用同步队列:SynchronousQueue,每次提交都要阻塞等待处理
cacheRefreshExecutor = new ThreadPoolExecutor(
1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
//初始化一个eureka请求传输器
eurekaTransport = new EurekaTransport();
scheduleServerEndpointTask(eurekaTransport, args);
AzToRegionMapper azToRegionMapper;
if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);
} else {
azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);
}
if (null != remoteRegionsToFetch.get()) {
azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(","));
}
instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
}
if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
fetchRegistryFromBackup();
}
if (this.preRegistrationHandler != null) {
this.preRegistrationHandler.beforeRegistration();
}
//如果配置需要注册为eureka的服务,并且初始化强制注册,则进行注册
if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
try {
if (!register() ) {
throw new IllegalStateException("Registration error at startup. Invalid server response.");
}
} catch (Throwable th) {
logger.error("Registration error at startup: {}", th.getMessage());
throw new IllegalStateException(th);
}
}
//最后,初始化调度任务(例如,集群解析器、心跳、instanceInfo复制器、fetch)
initScheduledTasks();
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register timers", e);
}
// This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
// to work with DI'd DiscoveryClient
DiscoveryManager.getInstance().setDiscoveryClient(this);
DiscoveryManager.getInstance().setEurekaClientConfig(config);
initTimestampMs = System.currentTimeMillis();
logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
initTimestampMs, this.getApplications().size());
}
/**
* Initializes all scheduled tasks.
*/
private void initScheduledTasks() {
//根据客户端配置是否需要获取eureka的服务列表,需要的话就开启注册表缓存刷新
if (clientConfig.shouldFetchRegistry()) {
// 缓存刷新定时器
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
//注册表缓存刷新任务,超时时间和执行时间30s
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread()
),
//注册表缓存定时调度,30s一次
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
//根据客户端配置是否需要注册到eureka,需要的话就开启心跳续约
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
// 心跳定时器
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
//心跳定时调度,30s一次
renewalIntervalInSecs, TimeUnit.SECONDS);
// 状态变更监听器
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
};
//这里是开启了在需要的时候更新状态变化的开关才会添加监听器,此处当开关开启时,状态发生变化,会立即收到通知,调用onDemandUpdate方法
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
//启动周期性实例信息复制到远程定时器,默认延迟40s执行
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
1.8、InstanceInfoReplicator -> onDemandUpdate()
这里的onDemandUpdate()方法主要是执行InstanceInfoReplicator.this.run()而这个run方法主要是判断是否dirty,如果是则调用discoveryClient.register()方法
public boolean onDemandUpdate() {
if (rateLimiter.acquire(burstSize, allowedRatePerMinute)) {
if (!scheduler.isShutdown()) {
scheduler.submit(new Runnable() {
@Override
public void run() {
logger.debug("Executing on-demand update of local InstanceInfo");
Future latestPeriodic = scheduledPeriodicRef.get();
if (latestPeriodic != null && !latestPeriodic.isDone()) {
logger.debug("Canceling the latest scheduled update, it will be rescheduled at the end of on demand update");
latestPeriodic.cancel(false);
}
InstanceInfoReplicator.this.run();
}
});
return true;
} else {
logger.warn("Ignoring onDemand update due to stopped scheduler");
return false;
}
} else {
logger.warn("Ignoring onDemand update due to rate limiter");
return false;
}
}
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);
}
}
1.9、DiscoveryClient -> register
最后终于找到了服务注册的具体实现,register()才是真正去远程的Eureka Server交互,注册服务的操作
/**
* Register with the eureka service by making the appropriate REST call.
* 通过Rest调用来注册eureka服务
*/
boolean register() throws Throwable {
logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e);
throw e;
}
if (logger.isInfoEnabled()) {
logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode());
}
return httpResponse.getStatusCode() == 204;
}
2、服务心跳续约
服务注册后,要定时(默认30s,可自己配置)向注册中心发送续约请求,告诉注册中心“我还活着”。
注册中心收到续约请求后:
- 更新服务对象最近续约时间,即Lease对象的lastUpdateTimestamp
- 同步服务信息,将此事件同步至其他的Eureka Server节点
剔除服务之前会先判断服务是否已经过期,判断服务是否过期的条件之一是续约时间和当前时间的差值是不是大于阈值
/**
* The heartbeat task that renews the lease in the given intervals.
*/
private class HeartbeatThread implements Runnable {
public void run() {
//心跳续约租期
if (renew()) {
//如果心跳正常,更新发起上次心跳的时间戳为当前时间
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
/**通过发送rest调用更新eureka服务
* Renew with the eureka service by making the appropriate REST call
*/
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
//客户端请求eureka服务器,维持客户端与eureka服务器的心跳
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
//如果eureka服务器未发现当前客户端,则进行注册
if (httpResponse.getStatusCode() == 404) {
REREGISTER_COUNTER.increment();
logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
long timestamp = instanceInfo.setIsDirtyWithTime();
//客户端发起注册eureka
boolean success = register();
if (success) {
instanceInfo.unsetIsDirty(timestamp);
}
return success;
}
return httpResponse.getStatusCode() == 200;
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
return false;
}
}
总结
服务注册:
EurekaClientAutoConfiguration构造了EurekaClientConfigBean、EurekaInstanceConfigBean以及EurekaServiceRegistry,接着在这几个对象的基础上进一步构建了ApplicationInfoManager、CloudEurekaClient等。 其中ApplicationInfoManager负责变更实例状态并发布StatusChangeEvent事件,而CloudEurekaClient继承了com.netflix.discoveryClient包含了statusChangeListener用于响应StatusChangeEvent,最后触发的是DiscoveryClient.register方法,与远程的Eureka Server通信,同步实例状态。服务续约:
客户端发送续约请求到Eureka Server,正常续约后更新当前实例最后续约时间,当服务不存在则注册该服务实例