Spring Cloud Eureka(三):Server源码
@EnableEurekaServer:入口
在第一篇搭建的Spring Cloud Eureka服务端项目中,需要在启动类上添加@EnableEurekaServer
注解来启动 Eureka 服务端,那我们就以这个注解为入口,到 Eureka Server 内部去看一看。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {}
@Configuration(proxyBeanMethods = false)
public class EurekaServerMarkerConfiguration {
@Bean
public Marker eurekaServerMarkerBean() {
return new Marker();
}
class Marker {}
}
可以看到@EnableEurekaServer
注解引入了一个类EurekaServerMarkerConfiguration
,而这个类又只是把一个内部类Marker
注册为Bean,似乎什么都没做就结束了,那 Eureka 是什么时候注册到 Spring 中的呢?
和上一篇一样,找到 spring-cloud-netflix-eureka-server.jar 中 META-INF/spring.factories 文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration
该类EurekaServerAutoConfiguration
的@ConditionalOnBean
条件就是必须注册了Marker
才能够生效,由于@EnableEurekaServer
注册了Marker
,所以该类开始生效。而该类又引入了EurekaServerInitializerConfiguration
,这个类执行了 EurekaServer 初始化的工作,所以 Eureka Server 得以启动。
@Configuration(proxyBeanMethods = false)
// 初始化EurekaServer
@Import(EurekaServerInitializerConfiguration.class)
// EurekaServerMarkerConfiguration.Marker 注册为 bean,该配置类才生效
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {...}
EurekaServerInitializerConfiguration:初始化
@Configuration(proxyBeanMethods = false)
public class EurekaServerInitializerConfiguration
implements ServletContextAware, SmartLifecycle, Ordered {
private boolean running;
@Autowired
private EurekaServerBootstrap eurekaServerBootstrap;
@Override
public void start() {
new Thread(() -> {
try {
// 启动 Eureka Server
eurekaServerBootstrap.contextInitialized(
EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");
// 发布事件:Eureka Server 可以接受注册了
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
// 表示状态为正在运行
EurekaServerInitializerConfiguration.this.running = true;
// 发布事件:Eureka Server 已经启动完成
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
}
catch (Exception ex) {
log.error("Could not initialize Eureka servlet context", ex);
}
}).start();
}
@Override
public boolean isRunning() {
return this.running;
}
@Override
public void stop() {
this.running = false;
eurekaServerBootstrap.contextDestroyed(this.servletContext);
}
}
实现SmartLifeCycle
接口可以在容器创建后,关闭之前做一些自定义的操作,以及判断应用是否在运行。主要看start
方法,新起了一个线程来做Eureka Server的初始化,主要通过eurekaServerBootstrap.contextInitialized
方法执行初始化。
EurekaServerBootstrap#contextInitialized
public class EurekaServerBootstrap {
public void contextInitialized(ServletContext context) {
try {
// 初始化 Eureka 环境配置
initEurekaEnvironment();
// 初始化 EurekaServer 上下文
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);
}
}
protected PeerAwareInstanceRegistry registry;
protected void initEurekaServerContext() throws Exception {
// 省略一些初始化操作
log.info("Initialized server context");
//从相邻的 Eureka 节点复制注册表
int registryCount = this.registry.syncUp();
//
this.registry.openForTraffic(this.applicationInfoManager, registryCount);
// Register all monitoring statistics.
EurekaMonitors.registerAllStats();
}
}
PeerAwareInstanceRegistryImpl#syncUp:同步注册表
// DiscoveryClient
protected final EurekaClient eurekaClient;
@Override
public int syncUp() {
int count = 0;
// 从相邻的 Eureka 节点复制注册表
// 最多重试 eureka.server.registry-sync-retries 次,默认 0 次
for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
if (i > 0) {
try {
// 重试时间间隔 eureka.server.registry-sync-retry-wait-ms, 默认 30 秒
Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
} catch (InterruptedException e) {
logger.warn("Interrupted during registry transfer..");
break;
}
}
// 获取注册表
Applications apps = eurekaClient.getApplications();
for (Application app : apps.getRegisteredApplications()) {
for (InstanceInfo instance : app.getInstances()) {
try {
// 判断是否需要注册,region校验
if (isRegisterable(instance)) {
// 注册该实例
// instance.getLeaseInfo().getDurationInSecs() 续约周期默认90s
register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
count++;
}
} catch (Throwable t) {
logger.error("During DS init copy", t);
}
}
}
}
return count;
}
这里的EurekaClient
就是DiscoveryClient
,高可用的注册中心会把自己也当做客户端向其他服务端进行注册,所以这里的DiscoveryClient
初始化的时候也会拉取注册表,具体可以看上一篇。
AbstractInstanceRegistry#register:实例信息存到本地注册表
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock read = readWriteLock.readLock();
// 将实例信息存到本地注册表中
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
// 读锁
read.lock();
try {
// 根据注册表中该appName的实例租约
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication);
// 注册表中没有则创建
if (gMap == null) {
final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
}
// 获取该实例租约
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
// 已有该实例的租约信息
if (existingLease != null && (existingLease.getHolder() != null)) {
// 获取两个实例的最近一次的租约时间
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
// 已有的比要注册的时间近则使用已有的
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
registrant = existingLease.getHolder();
}
} else {
// 不存在该实例的租约信息,表示有一个新的客户要进行租约
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// 需要续约的客户数 + 1
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
// 计算每分钟续约的阈值
updateRenewsPerMinThreshold();
}
}
}
// 创建新的租约对象,包含一些时间戳信息以及InstanceInfo本身
Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
// 如果已有租约信息,那么设置一下服务上线的时间
if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
// 放到appName的租约表里面
gMap.put(registrant.getId(), lease);
// 设置一些该实例的状态以及时间戳,省略...
} finally {
read.unlock();
}
}
// 计算每分钟应该收到的续约阈值
protected void updateRenewsPerMinThreshold() {
// 发送续约的客户端数 * 客户端每分钟发送续约的次数 * 阈值比例
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
* (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
* serverConfig.getRenewalPercentThreshold());
}
PeerAwareInstanceRegistryImpl#openForTraffic:服务端上线&&开启剔除任务
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// 省略...
// 修改server 状态为 UP,接受客户端注册
logger.info("Changing status to UP");
applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
// 调用父类 AbstarctInstanceRegistry.postInit()
super.postInit();
}
AbstarctInstanceRegistry#postInit:开启剔除任务
protected void postInit() {
// 计数任务
renewsLastMin.start();
// 取消之前的剔除任务
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
// 开启新的剔除任务
evictionTaskRef.set(new EvictionTask());
evictionTimer.schedule(evictionTaskRef.get(),
// 剔除任务的时间周期:eureka.server.eviction-interval-timer-in-ms
// 默认是60s,延时60s执行,然后每60s执行一次
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}
class EvictionTask extends TimerTask {
@Override
public void run() {
try {
// 计算一个补偿时间
long compensationTimeMs = getCompensationTimeMs();
evict(compensationTimeMs);
} catch (Throwable e) {
logger.error("Could not run the evict task", e);
}
}
public void evict(long additionalLeaseMs) {
// 不允许实例过期则直接返回
if (!isLeaseExpirationEnabled()) {
logger.debug("DS: lease expiration is currently disabled.");
return;
}
// 遍历注册表中所有实例,过滤出全部过期的实例
List<Lease<InstanceInfo>> expiredLeases = new ArrayList<>();
for (Entry<String, Map<String, Lease<InstanceInfo>>> groupEntry : registry.entrySet()) {
Map<String, Lease<InstanceInfo>> leaseMap = groupEntry.getValue();
if (leaseMap != null) {
for (Entry<String, Lease<InstanceInfo>> leaseEntry : leaseMap.entrySet()) {
Lease<InstanceInfo> lease = leaseEntry.getValue();
// 计算该租约是否过期
if (lease.isExpired(additionalLeaseMs) && lease.getHolder() != null) {
expiredLeases.add(lease);
}
}
}
}
// 本地注册表大小
int registrySize = (int) getLocalRegistrySize();
// 注册表的最小阈值
// eureka.server.renewal-percent-threshold 默认 0.85
int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
// 最多可剔除的实例数量
int evictionLimit = registrySize - registrySizeThreshold;
// 已过期的实例数 和 最多可剔除的实例数,取较小者
int toEvict = Math.min(expiredLeases.size(), evictionLimit);
// 有可剔除的名额则进行剔除
if (toEvict > 0) {
Random random = new Random(System.currentTimeMillis());
for (int i = 0; i < toEvict; i++) {
// 随机挑选已过期的实例去剔除
int next = i + random.nextInt(expiredLeases.size() - i);
Collections.swap(expiredLeases, i, next);
Lease<InstanceInfo> lease = expiredLeases.get(i);
// 过期实例属性
String appName = lease.getHolder().getAppName();
String id = lease.getHolder().getId();
// 过期计数
EXPIRED.increment();
logger.warn("DS: Registry: expired lease for {}/{}", appName, id);
// 下线服务
internalCancel(appName, id, false);
}
}
}
}
PeerAwareInstanceRegistryImpl#isLeaseExpirationEnabled
public boolean isLeaseExpirationEnabled() {
// 自我保护机制 eureka.server.enable-self-preservation 默认true
if (!isSelfPreservationModeEnabled()) {
// 关闭自我保护,返回 true,代表允许剔除
return true;
}
// 开启自我保护,则需要计算是否应该剔除
// 每分钟应收到的续约阈值 > 0 && 上一分钟收到的续约数 > 每分钟应收到的续约阈值
return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}
Lease#isExpired
public boolean isExpired(long additionalLeaseMs) {
// 过期时间 > 0 || 当前时间 > 上次续约时间 + 续约周期 + 补偿时间
return (evictionTimestamp > 0 ||
System.currentTimeMillis() > (lastUpdateTimestamp + duration + additionalLeaseMs));
}
AbstarctInstanceRegistry#internalCancel:服务下线
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock read = readWriteLock.readLock();
protected boolean internalCancel(String appName, String id, boolean isReplication) {
// 读锁
read.lock();
try {
CANCEL.increment(isReplication);
// 获取该appName的全部实例
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToCancel = null;
if (gMap != null) {
// 从注册表移除
leaseToCancel = gMap.remove(id);
}
// 最近下线的实例队列
recentCanceledQueue.add(new Pair<Long, String>(System.currentTimeMillis(), appName + "(" + id + ")"));
InstanceStatus instanceStatus = overriddenInstanceStatusMap.remove(id);
if (instanceStatus != null) {
logger.debug("Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name());
}
// 不存在则记录并返回
if (leaseToCancel == null) {
CANCEL_NOT_FOUND.increment(isReplication);
logger.warn("DS: Registry: cancel failed because Lease is not registered for: {}/{}", appName, id);
return false;
} else {
// 记录下线时间
leaseToCancel.cancel();
InstanceInfo instanceInfo = leaseToCancel.getHolder();
String vip = null;
String svip = null;
if (instanceInfo != null) {
instanceInfo.setActionType(ActionType.DELETED);
recentlyChangedQueue.add(new RecentlyChangedItem(leaseToCancel));
instanceInfo.setLastUpdatedTimestamp();
vip = instanceInfo.getVIPAddress();
svip = instanceInfo.getSecureVipAddress();
}
// 清除缓存
invalidateCache(appName, vip, svip);
logger.info("Cancelled instance {}/{} (replication={})", appName, id, isReplication);
}
} finally {
read.unlock();
}
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// 需要续约的客户数 - 1
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews - 1;
// 计算每分钟需要续约的阈值
updateRenewsPerMinThreshold();
}
}
return true;
}
JerseyFilter: 配置EurekaServer的Rest接口
以上都是Server主动去获得注册表,去剔除服务。同样的,Server也可以被动的接收客户端发来的注册以及下线信息。
还是回到最初的配置类EurekaServerAutoConfiguration
中,这个配置类注册了两个Bean
// 包路径,分别在 eureka-client.jar 和 eureka-core.jar中
private static final String[] EUREKA_PACKAGES = new String[] {
"com.netflix.discovery", "com.netflix.eureka" };
@Bean
public FilterRegistrationBean<?> jerseyFilterRegistration(
javax.ws.rs.core.Application eurekaJerseyApp) {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<Filter>();
bean.setFilter(new ServletContainer(eurekaJerseyApp));
bean.setOrder(Ordered.LOWEST_PRECEDENCE);
bean.setUrlPatterns(
// eureka/*
Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));
return bean;
}
@Bean
public javax.ws.rs.core.Application jerseyApplication(Environment environment,ResourceLoader resourceLoader) {
// 扫描包路径
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
false, environment);
// 过滤出带有 @Path 和 @Provider 注解的类
// @Path 相当于 @RequestMapping
// @Provider 相当于 @Controller
provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
// 开始扫描
Set<Class<?>> classes = new HashSet<>();
for (String basePackage : EUREKA_PACKAGES) {
Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
for (BeanDefinition bd : beans) {
Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
resourceLoader.getClassLoader());
classes.add(cls);
}
}
DefaultResourceConfig rc = new DefaultResourceConfig(classes);
// 省略部分代码
return rc;
}
从扫描出的类中,取出用于获取注册表的类ApplicationsResource
来看一看。
ApplicationsResource:获取注册表
@Path("/{version}/apps")
@Produces({"application/xml", "application/json"})
public class ApplicationsResource {
// 省略部分代码
private final EurekaServerConfig serverConfig;
private final PeerAwareInstanceRegistry registry;
private final ResponseCache responseCache;
@Inject
ApplicationsResource(EurekaServerContext eurekaServer) {
this.serverConfig = eurekaServer.getServerConfig();
this.registry = eurekaServer.getRegistry();
this.responseCache = registry.getResponseCache();
}
// 具体单类服务的操作,交给 ApplicationResource 处理
@Path("{appId}")
public ApplicationResource getApplicationResource(@PathParam("version") String version,
@PathParam("appId") String appId) {
CurrentRequestVersion.set(Version.toEnum(version));
try {
return new ApplicationResource(appId, serverConfig, registry);
} finally {
CurrentRequestVersion.remove();
}
}
// 获取全量注册表
@GET
public Response getContainers(@PathParam("version") String version,
@HeaderParam(HEADER_ACCEPT) String acceptHeader,
@HeaderParam(HEADER_ACCEPT_ENCODING) String acceptEncoding,
@HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept,
@Context UriInfo uriInfo,
@Nullable @QueryParam("regions") String regionsStr) {
// 省略部分代码
Key cacheKey = new Key(Key.EntityType.Application,ResponseCacheImpl.ALL_APPS,
keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions);
Response response;
if (acceptEncoding != null && acceptEncoding.contains(HEADER_GZIP_VALUE)) {
response = Response.ok(responseCache.getGZIP(cacheKey))
.header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)
.header(HEADER_CONTENT_TYPE, returnMediaType)
.build();
} else {
response = Response.ok(responseCache.get(cacheKey)).build();
}
return response;
}
// 获取增量注册表
@Path("delta")
@GET
public Response getContainerDifferential(
@PathParam("version") String version,
@HeaderParam(HEADER_ACCEPT) String acceptHeader,
@HeaderParam(HEADER_ACCEPT_ENCODING) String acceptEncoding,
@HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept,
@Context UriInfo uriInfo, @Nullable @QueryParam("regions") String regionsStr) {
// 省略部分代码
Key cacheKey = new Key(Key.EntityType.Application,
ResponseCacheImpl.ALL_APPS_DELTA,
keyType, CurrentRequestVersion.get(), EurekaAccept.fromString(eurekaAccept), regions
);
final Response response;
if (acceptEncoding != null && acceptEncoding.contains(HEADER_GZIP_VALUE)) {
response = Response.ok(responseCache.getGZIP(cacheKey))
.header(HEADER_CONTENT_ENCODING, HEADER_GZIP_VALUE)
.header(HEADER_CONTENT_TYPE, returnMediaType)
.build();
} else {
response = Response.ok(responseCache.get(cacheKey)).build();
}
return response;
}
}
ApplicationResource:单类服务信息
接收请求路径apps/{appId}
,这里的 appId 实际是应用名。
// apps/{appName}
@Produces({"application/xml", "application/json"})
public class ApplicationResource {
// 省略部分代码
private final String appName;
private final EurekaServerConfig serverConfig;
private final PeerAwareInstanceRegistry registry;
private final ResponseCache responseCache;
ApplicationResource(String appName,EurekaServerConfig serverConfig,PeerAwareInstanceRegistry registry) {
this.appName = appName.toUpperCase();
this.serverConfig = serverConfig;
this.registry = registry;
this.responseCache = registry.getResponseCache();
}
// GET请求获取单类服务信息
@GET
public Response getApplication(@PathParam("version") String version,
@HeaderParam("Accept") final String acceptHeader,
@HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept) {
// 省略部分代码
Key cacheKey = new Key(Key.EntityType.Application,
appName,keyType,CurrentRequestVersion.get(),EurekaAccept.fromString(eurekaAccept));
String payLoad = responseCache.get(cacheKey);
if (payLoad != null) {
logger.debug("Found: {}", appName);
return Response.ok(payLoad).build();
} else {
logger.debug("Not Found: {}", appName);
return Response.status(Status.NOT_FOUND).build();
}
}
// POST请求,这类服务的实例注册
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
// 省略部分代码
registry.register(info, "true".equals(isReplication));
return Response.status(204).build();
}
// 这类服务的某个具体实例信息,交给 InstanceResource 处理
@Path("{id}")
public InstanceResource getInstanceInfo(@PathParam("id") String id) {
return new InstanceResource(this, id, serverConfig, registry);
}
}
InstanceResource:单个实例信息
// apps/{appName}/{appId}
@Produces({"application/xml", "application/json"})
public class InstanceResource {
private final PeerAwareInstanceRegistry registry;
private final EurekaServerConfig serverConfig;
private final String id;
private final ApplicationResource app;
InstanceResource(ApplicationResource app, String id, EurekaServerConfig serverConfig, PeerAwareInstanceRegistry registry) {
this.app = app;
this.id = id;
this.serverConfig = serverConfig;
this.registry = registry;
}
// GET请求,获取实例信息
@GET
public Response getInstanceInfo() {
InstanceInfo appInfo = registry.getInstanceByAppAndId(app.getName(), id);
if (appInfo != null) {
return Response.ok(appInfo).build();
} else {
return Response.status(Status.NOT_FOUND).build();
}
}
// PUT请求,心跳续约
@PUT
public Response renewLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("overriddenstatus") String overriddenStatus,
@QueryParam("status") String status,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
// 简化代码
// 更新 LeaseInfo 的一些时间戳
return this.validateDirtyTimestamp(Long.valueOf(lastDirtyTimestamp), isFromReplicaNode);
}
// DELETE请求,实例下线
@DELETE
public Response cancelLease(@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
try {
boolean isSuccess = registry.cancel(app.getName(), id,"true".equals(isReplication));
if (isSuccess) {
return Response.ok().build();
} else {
return Response.status(Status.NOT_FOUND).build();
}
} catch (Throwable e) {
logger.error("Error (cancel): {} - {}", app.getName(), id, e);
return Response.serverError().build();
}
}
}