1. 服务注册
分析要素:
1)eureka 发起的服务注册的位置;
2)eureka 发起注册携带的核心信息;
开始分析:
我们从使用角度为切入点分析:
EnableEurekaClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(EnableDiscoveryClientImportSelector.class)
public @interface EnableDiscoveryClient {
/**
* If true, the ServiceRegistry will automatically register the local server.
*/
boolean autoRegister() default true;
}
EnableDiscoveryClientImportSelector
跟进查看:EnableDiscoveryClientImportSelector
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class EnableDiscoveryClientImportSelector extends SpringFactoryImportSelector<EnableDiscoveryClient> {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
String[] imports = super.selectImports(metadata);
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(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 = importsList.toArray(new String[0]);
} else {
Environment env = 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;
}
}
上面代码实质目的:将 org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration
实例化到容器,为 EurekaClientAutoConfiguration 配置加载提供条件支持;
在这里,AutoServiceRegistrationConfiguration 这种自动化的配置类,一般都会在spring.factories 中配置;如下:
spring-cloud-netflix-eureka-client-2.1.0.RELEASE.jar
|- 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,\
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 代码太多,这里就不粘贴了,这个类是自动化配置的核心类;核心就是将eureka 相关的bean 添加到 spring的IOC map中;
🍌这里我们重点关注:EurekaAutoServiceRegistration
public class EurekaAutoServiceRegistration implements AutoServiceRegistration, SmartLifecycle, Ordered, SmartApplicationListener {
}
如上,我们看到此类实现了SmartLifecycle 接口,这个接口会在spring 加载完所有的bean后调用 start() 方法;跟踪代码执行,得到如下时序图:
注:SmartLifecycle 的解释可以参见:spring-boot 常用类
2. 服务续租
续租:
eureka-client 向 eureka-server 发起注册应用实例后获得 租约 (Lease);
eureka-client 定期向 eureka-server 发送租约(renew),避免租约过期;
默认情况下,租约有效期为 90s, 续租频率为 30s; 即:保证在网络异常的情况下,有三次重试的机会;
宏观续租
Eureka 在初始化时,会创建心跳线程,固定间隔向eureka-server 发起续租。实现代码如下;
DiscoveryClient
@Singleton
public class DiscoveryClient implements EurekaClient {
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider) {
// xxx ...
// default size of 2 - 1 each for heartbeat and cacheRefresh
// 初始化定时线程服务
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
// 心跳检测-线程池
heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
// 初始化线程池
// finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
initScheduledTasks();
// xxx ...
}
/**
* Initializes all scheduled tasks.
*/
private void initScheduledTasks() {
// xxx ...
//
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
// Heartbeat timer
// 启动定时心跳检测
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
}
// xxx ...
}
}
TimedSupervisorTask
public class TimedSupervisorTask extends TimerTask {
public TimedSupervisorTask(String name, ScheduledExecutorService scheduler, ThreadPoolExecutor executor,
int timeout, TimeUnit timeUnit, int expBackOffBound, Runnable task) {
this.scheduler = scheduler;
this.executor = executor;
this.timeoutMillis = timeUnit.toMillis(timeout);
this.task = task;
this.delay = new AtomicLong(timeoutMillis);
this.maxDelay = timeoutMillis * expBackOffBound;
// Initialize the counters and register.
successCounter = Monitors.newCounter("success");
timeoutCounter = Monitors.newCounter("timeouts");
rejectedCounter = Monitors.newCounter("rejectedExecutions");
throwableCounter = Monitors.newCounter("throwables");
threadPoolLevelGauge = new LongGauge(MonitorConfig.builder("threadPoolUsed").build());
Monitors.registerObject(name, this);
}
@Override
public void run() {
Future<?> future = null;
try {
// 执行任务
future = executor.submit(task);
threadPoolLevelGauge.set((long) executor.getActiveCount());
future.get(timeoutMillis, TimeUnit.MILLISECONDS); // block until done or timeout
delay.set(timeoutMillis);
threadPoolLevelGauge.set((long) executor.getActiveCount());
successCounter.increment();
}
// xxx ...
// 超时后取消任务
finally {
if (future != null) {
future.cancel(true);
}
// 如果scheduler 没有关闭,再次延迟执行续租
if (!scheduler.isShutdown()) {
scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);
}
}
}
}
如上述代码,续租的宏观逻辑如下图:
续租细节
DiscoveryClient.HeartbeatThread
private class HeartbeatThread implements Runnable {
public void run() {
if (renew()) {
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
com.netflix.discovery.DiscoveryClient#renew
/**
* Renew with the eureka service by making the appropriate REST call
*/
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
// 向eureka-server 发送心跳 续租
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
REREGISTER_COUNTER.increment();
logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
long timestamp = instanceInfo.setIsDirtyWithTime();
// 续租失败,再次发起注册
boolean success = register();
if (success) {
instanceInfo.unsetIsDirty(timestamp);
}
return success;
}
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
return false;
}
}
3. 服务下线
4. 面试怎么问?
4.1 客户端如何向服务端注册?
在客户端启动的时候,会创建一个保持心跳的定时任务,定时去和服务器发送心跳;
在第一次启动的手,如果心跳是 404; 标识:eureka 上没有这个服务;
客户端会发起注册,将自己的 ip、端口、实例id 注册上;
每个30秒一次,如果90s 内还没有,就认为这个服务挂了;
4.2 服务端如何保存客户端信息?
服务器上,是将客户端是数据保存在 ConconcrentHashMap 中;
4.3 客户端如何拉取服务端已保存的服务数据(是需要的时候去拉取,还 是先拉取保存到本地,使用的时候直接从本地获取)?
客户端拉取服务信息是通过定时任务拉取的,每次拉取的时候刷新本地的副本;
使用的时候直接从本地获取;
4.4 如何搭建高可用的Eureka 集群?
仅需要在eureka 上配置 其他注册中心地址就行;
这些注册中心会 进行通信; 同步所有的示例服务;
4.5 什么事服务续约?
心跳每隔30秒一次,如果90s 内还没有,就认为这个服务挂了;
4.6 神马事失效剔除?
心跳没有续上,90s 内,没发送心跳; eureka 会把这个服务放到剔除列表里面;
开启一个定时任务;对失效的服务进行剔除;
4.7 啥是cap ,并说明Eureka 包含CAP 中的哪些?
C: 一致性
A:可用性
P: 分区容错
Eureka 保证 一个注册中心挂了后,服务还能用,还可以向其他服务中注册;
4.8 Eureaka 的负载均衡策略有哪些?
随机
加权随机
轮询
加权轮询
最低并发策略
区域负载均衡
- 在多个区进行部署时,可以根据各个区域的服务处理能力来选择示例;
重试策略