一.EurekaServer
1.入口分析:
切入点(一):
对于EurekaServer,我们只是在主配置类中添加了@EnableEurekaServer
这个注解,所以我们需要以此为入口分析EurekaServer端源码
@EnableEurekaServer
这个注解的主要作用是导入EurekaServerMarkerConfiguration配置类
主要作用向spring容器中注入一个标记类 Marker
通过判断spring容器中是否含有Marker这个bean,来判断是否是Euraka注册中心
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
//@EnableEurekaServer这个注解的主要作用是导入EurekaServerMarkerConfiguration配置类
public @interface EnableEurekaServer {
}
//主要作用向spring容器中注入一个标记类 Marker
//通过判断spring容器中是否含有marker这个bean,来判断是否是Euraka注册中心
@Configuration(proxyBeanMethods = false)
public class EurekaServerMarkerConfiguration {
@Bean
public Marker eurekaServerMarkerBean() {
return new Marker();
}
class Marker {
}
}
切入点(二):
利用springboot自动配置的方式,导入了EnableAutoConfiguration配置类
自动配置
首先分析下自动配置类上的注解信息:
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
判断spring容器中是否有Marker类,如果有则EurekaServerAutoConfiguration会被注入到spring容器中
结合切入点(一)得出结论,只有使用了@EnableEurekaServer
注解,才会激活EurekaServer的自动配置@Import(EurekaServerInitializerConfiguration.class)
导入了EurekaServerInitializerConfiguration这个Bean,这里先给出结论,后文在具体分析它的源码- 初始化 Eureka 配置
- 初始化 Eureka Context ,包括集群同步注册信息,启动一些定时器(服务剔除,自我保护机制监听…)
- 初始化自我保护机制的阈值
@PropertySource({"classpath:/eureka/server.properties"})
加载类路径下的:eureka/server.properties属性文件@EnableConfigurationProperties({ EurekaDashboardProperties.class, InstanceRegistryProperties.class })
把配置文件中eureka.dashboard下的信息绑定到EurekaDashboardProperties类,eureka.instance.registry下的信息绑定到InstanceRegistryProperties类
然后分析下注入的几个重要的Bean:
EurekaController
:初始化Eureka仪表盘页面访问的接口信息(通过指定地址访问EurekaServer控制页面)EurekaServerConfig
:封装了EurekaServer的配置信息(配置文件eureka.server)jerseyApplication
:构建一个Jersey过滤器,类似SpringMVC,用于拦截客户端发来的请求PeerAwareInstanceRegistry
:原生Eureka提供的接口,用于完成服务注册,服务同步功能EurekaServerBootstrap
:构建EurekaServer启动器
@Configuration(proxyBeanMethods = false)
/** 导入EurekaServerInitializerConfiguration bean,这个类作用:
* 1.初始化 Eureka 配置
* 2.初始化 Eureka Context ,包括集群同步注册信息,启动一些定时器(服务剔除,自我保护机制监听...)
* 3.初始化自我保护机制的阈值
*/
@Import(EurekaServerInitializerConfiguration.class)
//判断spring容器中是否有Marker类,如果有则EurekaServerAutoConfiguration会被注入到spring容器中
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
/**
* Euraka自动配置类
* 主要做两件事
* 1.初始化Eureka上下文
* 2.把jersy核心过滤器注入到spring web容器中
*/
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
//初始化 EurekaServer仪表盘控制页面
@Bean
@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled",
matchIfMissing = true)
public EurekaController eurekaController() {
return new EurekaController(this.applicationInfoManager);
}
// 加载EurekaServer端的相关配置(yml文件eureka.server下的信息)
@Configuration(proxyBeanMethods = false)
protected static class EurekaServerConfigBeanConfiguration {
@Bean
@ConditionalOnMissingBean
public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
EurekaServerConfigBean server = new EurekaServerConfigBean();
if (clientConfig.shouldRegisterWithEureka()) {
// Set a sensible default if we are supposed to replicate
server.setRegistrySyncRetries(5);
}
return server;
}
}
//PeerAwareInstanceRegistry为原生Eureka提供的接口,用于完成服务注册,服务同步
@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());
}
//初始化 EurekaServer 启动器,EurekaServerContext为原生Eureka的初始化类
@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
EurekaServerContext serverContext) {
return new EurekaServerBootstrap(this.applicationInfoManager,
this.eurekaClientConfig, this.eurekaServerConfig, registry,
serverContext);
}
//注册Jersey过滤器。 把Jersey过滤器工作起来,这样就可以拦截请求了
@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(
Collections.singletonList(EurekaConstants.DEFAULT_PREFIX + "/*"));
return bean;
}
//构建一个Jersey过滤器
@Bean
public javax.ws.rs.core.Application jerseyApplication(Environment environment,
ResourceLoader resourceLoader) {
ClassPathScanningCandidateComponentProvider provider =
new ClassPathScanningCandidateComponentProvider(
false, environment);
// Filter to include only classes that have a particular annotation.
// 过滤包含@Path @Provider标记的类,类似于springMVC中的注解
provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));
// Find classes in Eureka packages (or subpackages)
//
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);
}
}
// Construct the Jersey ResourceConfig
Map<String, Object> propsAndFeatures = new HashMap<>();
propsAndFeatures.put(
// Skip static content used by the webapp
ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,
EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*");
DefaultResourceConfig rc = new DefaultResourceConfig(classes);
rc.setPropertiesAndFeatures(propsAndFeatures);
return rc;
}
}
初始化
接下来我们看下EurekaServerInitializerConfiguration
这个初始化类,省略部分代码
发现它实现了SmartLifecycle接口,触发Spring生命周期回调机制,也就是说这个类注入到Spring容器后会调用start方法
在Start方法中完成了初始化EurekaServer,以及发布了特定事件,我们可以监听这种事件,然后做一些特定的业务需求
@Configuration(proxyBeanMethods = false)
public class EurekaServerInitializerConfiguration
implements ServletContextAware, SmartLifecycle, Ordered {
...
@Override
public void start() {
new Thread(() -> {
try {
//初始化EurekaServer,同时启动Eureka Server
eurekaServerBootstrap.contextInitialized(
EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");
//发布服务注册注册事件
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
EurekaServerInitializerConfiguration.this.running = true;
//发布EurekaServer启动完成事件
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
}
catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}).start();
}
...
}
主要做了两件事儿:初始化运行环境(eureka.environment配置信息)、初始化上下文
public void contextInitialized(ServletContext context) {
try {
//初始化 yml配置信息
this.initEurekaEnvironment();
//初始化Eureka上下文
this.initEurekaServerContext();
context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
} catch (Throwable var3) {
log.error("Cannot bootstrap eureka server :", var3);
throw new RuntimeException("Cannot bootstrap eureka server :", var3);
}
}
我们主要关注主要逻辑,在初始化上下文过程中首先进行了集群同步操作,注意这是在服务端启动时做的操作,在服务端运行过程中同样会进行集群同步操作,另外还设置了服务剔除的定时器,定时清除没有续约的实例,具体内容在后文主线部分再做分析
protected void initEurekaServerContext() throws Exception {
JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), 10000);
XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), 10000);
if (this.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");
//服务启动时,从集群中的其他节点同步信息
int registryCount = this.registry.syncUp();
//初始化自我保护机制阈值,设置服务剔除定时器
this.registry.openForTraffic(this.applicationInfoManager, registryCount);
EurekaMonitors.registerAllStats();
}
2.主线分析
主要从5方面分析Eureka Server源码:服务注册、心跳链接、服务下架、集群同步、自我保护机制
服务注册源码分析
Eureka服务注册实际是利用了Jersey框架核心过滤器,拦截服务注册请求实现的。
入口:ApplicationResource#addInstance
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
// 做一些信息校验
// ....
//核心方法 PeerAwareInstanceRegistry registry;
registry.register(info, "true".equals(isReplication));
return Response.status(204).build();
}
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
// Eureka服务器在接收到实例的最后一次发出的心跳后,需要等待多久才可以将此实例删除,默认90s
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
//服务注册
super.register(info, leaseDuration, isReplication);
//集群同步
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
/**
* Registers a new instance with a given duration.
* registrant 本次请求传入的注册信息
* leaseDuration 服务过期时间 默认是60秒
* isReplication 集群同步需要使用这个属性
*/
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
read.lock();
try {
//存储所有服务信息的集合
//ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
//数据结构,其中最外层String 对应服务称;里面Map<服务ID,租债器>
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) {
//expectedNumberOfClientsSendingRenews
//心跳连接预估值 会在服务注册和服务下架的时候更新
if (this.expectedNumberOfClientsSendingRenews > 0) {
this.expectedNumberOfClientsSendingRenews = this.
expectedNumberOfClientsSendingRenews + 1;
//自我保护阈值更新
updateRenewsPerMinThreshold();
}
}
}
Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
//完成服务注册
gMap.put(registrant.getId(), lease);
recentRegisteredQueue.add(new Pair<Long, String>(
System.currentTimeMillis(),
registrant.getAppName() + "(" + registrant.getId() + ")"));
// This is where the initial state transfer of overridden status happens
if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
overriddenInstanceStatusMap
.put(registrant.getId(), registrant.getOverriddenStatus());
}
}
InstanceStatus overriddenStatusFromMap=overriddenInstanceStatusMap.get(registrant.getId());
if (overriddenStatusFromMap != null) {
logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
registrant.setOverriddenStatus(overriddenStatusFromMap);
}
InstanceStatus overriddenInstanceStatus =
getOverriddenInstanceStatus(registrant, existingLease, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
if (InstanceStatus.UP.equals(registrant.getStatus())) {
lease.serviceUp();
}
registrant.setActionType(ActionType.ADDED);
recentlyChangedQueue.add(new RecentlyChangedItem(lease));
registrant.setLastUpdatedTimestamp();
invalidateCache(registrant.getAppName(),
registrant.getVIPAddress(),
registrant.getSecureVipAddress());
} finally {
read.unlock();
}
}
心跳续约源码分析
入口InstanceResource#renewLease
@PUT
public Response renewLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("overriddenstatus") String overriddenStatus,
@QueryParam("status") String status,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
boolean isFromReplicaNode = "true".equals(isReplication);
//心跳续约入口
boolean isSuccess = registry.renew(app.getName(), id, isFromReplicaNode);
if (!isSuccess) {
return Response.status(Status.NOT_FOUND).build();
}
// Check if we need to sync based on dirty time stamp, the client
// instance might have changed some value
Response response;
if (lastDirtyTimestamp != null && serverConfig.shouldSyncWhenTimestampDiffers()) {
response = this.validateDirtyTimestamp(
Long.valueOf(lastDirtyTimestamp),
isFromReplicaNode
);
if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()
&& (overriddenStatus != null)
&& !(InstanceStatus.UNKNOWN.name().equals(overriddenStatus))
&& isFromReplicaNode) {
registry.storeOverriddenStatusIfRequired(
app.getAppName(), id, InstanceStatus.valueOf(overriddenStatus));
}
} else {
response = Response.ok().build();
}
return response;
}
public boolean renew(final String appName, final String serverId, boolean isReplication) {
List<Application> applications = this.getSortedApplications();
Iterator var5 = applications.iterator();
while(var5.hasNext()) {
Application input = (Application)var5.next();
if (input.getName().equals(appName)) {
InstanceInfo instance = null;
Iterator var8 = input.getInstances().iterator();
while(var8.hasNext()) {
InstanceInfo info = (InstanceInfo)var8.next();
if (info.getId().equals(serverId)) {
instance = info;
break;
}
}
//事件监听机制
this.publishEvent(new EurekaInstanceRenewedEvent
(this, appName, serverId, instance, isReplication));
break;
}
}
return super.renew(appName, serverId, isReplication);
}
public boolean renew(final String appName, final String id, final boolean isReplication) {
if (super.renew(appName, id, isReplication)) {
//集群同步
replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
return true;
}
return false;
}
public boolean renew(String appName, String id, boolean isReplication) {
RENEW.increment(isReplication);
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToRenew = null;
if (gMap != null) {
leaseToRenew = gMap.get(id);
}
if (leaseToRenew == null) {
RENEW_NOT_FOUND.increment(isReplication);
return false;
} else {
InstanceInfo instanceInfo = leaseToRenew.getHolder();
if (instanceInfo != null) {
InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
instanceInfo, leaseToRenew, isReplication);
if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
RENEW_NOT_FOUND.increment(isReplication);
return false;
}
if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
instanceInfo.getOverriddenStatus().name(),
instanceInfo.getId());
instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);
}
}
renewsLastMin.increment();
leaseToRenew.renew();
return true;
}
}
集群同步
集群同步操作在PeerAwareInstanceRegistryImpl这个类中完成:
服务注册,服务下架,心跳续约,状态修改等操作均需要进行集群同步
public enum Action {
Heartbeat, Register, Cancel, StatusUpdate, DeleteStatusOverride;
private com.netflix.servo.monitor.Timer timer = Monitors.newTimer(this.name());
public com.netflix.servo.monitor.Timer getTimer() {
return this.timer;
}
}
原理:客户端client发送注册请求给服务端server1,之后服务端会发送同样的请求给集群中其他节点server2,server3,区别在于请求中是否携带isReplication属性
/**
* action 操作类型,appName 服务名,id服务ID,info 实例信息,isReplication 是否是集群同步操作
*/
private void replicateToPeers(Action action, String appName, String id,
InstanceInfo info ,
InstanceStatus newStatus , boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
try {
if (isReplication) {
numberOfReplicationsLastMin.increment();
}
if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
return;
}
for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
// 如果url代表的主机为当前节点点,没必要同步
if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
continue;
}
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
}
} finally {
tracer.stop();
}
}
//根据不同的操作,发送不同的请求
private void replicateInstanceActionsToPeers(Action action, String appName,
String id, InstanceInfo info,
InstanceStatus newStatus,
PeerEurekaNode node) {
try {
InstanceInfo infoFromRegistry;
CurrentRequestVersion.set(Version.V2);
switch (action) {
case Cancel:
node.cancel(appName, id);
break;
case Heartbeat:
InstanceStatus overriddenStatus = overriddenInstanceStatusMap.get(id);
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.heartbeat(appName, id, infoFromRegistry, overriddenStatus, false);
break;
case Register:
node.register(info);
break;
case StatusUpdate:
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.statusUpdate(appName, id, newStatus, infoFromRegistry);
break;
case DeleteStatusOverride:
infoFromRegistry = getInstanceByAppAndId(appName, id, false);
node.deleteStatusOverride(appName, id, infoFromRegistry);
break;
}
} catch (Throwable t) {
} finally {
CurrentRequestVersion.remove();
}
}
服务剔除、服务下架
入口:PeerAwareInstanceRegistryImpl#openForTraffic
通过上文分析,我们知道EurekaServer在初始化过程中会,设置一个服务剔除的定时器
@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// Renewals happen every 30 seconds and for a minute it should be a factor of 2.
this.expectedNumberOfClientsSendingRenews = count;
updateRenewsPerMinThreshold();
this.startupTime = System.currentTimeMillis();
if (count > 0) {
this.peerInstancesTransferEmptyOnStartup = false;
}
DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
//aws与亚马逊云有关,忽略
boolean isAws = Name.Amazon == selfName;
if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
logger.info("Priming AWS connections for all replicas..");
primeAwsReplicas(applicationInfoManager);
}
logger.info("Changing status to UP");
applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
//开启定时任务,默认60秒执行一次,用于清理60秒之内没有续约的实例
super.postInit();
}
protected void postInit() {
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
//设置服务剔除定时器,剔除逻辑在EvictionTask线程类中
evictionTaskRef.set(new EvictionTask());
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}
public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
//如果触发自我保护机制,则不进行剔除操作
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);
}
}
}
}
//判断如果此次剔除的数量如果过大的话,为保证可用性,不会全部剔除,会有个保护值默认85%
//比如100个服务要剔除20个,此次剔除操作超过85%阈值,此次剔除只会剔除15个服务
int registrySize = (int) getLocalRegistrySize();
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++) {
// Pick a random item (Knuth shuffle algorithm)
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);
}
}
}
自我保护机制
会在服务初始化,服务注册,服务下架,15分钟定时器自动触发
//15分钟定时器自动触发
private void scheduleRenewalThresholdUpdateTask() {
timer.schedule(new TimerTask() {
@Override
public void run() {
updateRenewalThreshold();
}
}, serverConfig.getRenewalThresholdUpdateIntervalMs(),
serverConfig.getRenewalThresholdUpdateIntervalMs());
}
阈值算法:所有注册的实例 * 60s / 心跳续约时间默认30s * 自我保护机制触发百分比默认85%
protected void updateRenewsPerMinThreshold() {
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfClientsSendingRenews
* (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
* serverConfig.getRenewalPercentThreshold());
}
服务发现
入口:ApplicationsResource#getContainers
@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) {
boolean isRemoteRegionRequested = null != regionsStr && !regionsStr.isEmpty();
String[] regions = null;
if (!isRemoteRegionRequested) {
EurekaMonitors.GET_ALL.increment();
} else {
regions = regionsStr.toLowerCase().split(",");
Arrays.sort(regions);
EurekaMonitors.GET_ALL_WITH_REMOTE_REGIONS.increment();
}
if (!registry.shouldAllowAccess(isRemoteRegionRequested)) {
return Response.status(Status.FORBIDDEN).build();
}
CurrentRequestVersion.set(Version.toEnum(version));
KeyType keyType = Key.KeyType.JSON;
String returnMediaType = MediaType.APPLICATION_JSON;
if (acceptHeader == null || !acceptHeader.contains(HEADER_JSON_VALUE)) {
keyType = Key.KeyType.XML;
returnMediaType = MediaType.APPLICATION_XML;
}
//封装缓存key
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();
}
CurrentRequestVersion.remove();
return response;
}
会向缓存中拿注册信息
public byte[] getGZIP(Key key) {
//入口
Value payload = getValue(key, shouldUseReadOnlyResponseCache);
if (payload == null) {
return null;
}
return payload.getGzipped();
}
Value getValue(final Key key, boolean useReadOnlyCache) {
Value payload = null;
try {
if (useReadOnlyCache) {
//只读缓存
final Value currentPayload = readOnlyCacheMap.get(key);
if (currentPayload != null) {
payload = currentPayload;
} else {
payload = readWriteCacheMap.get(key);
readOnlyCacheMap.put(key, payload);
}
} else {
//读写缓存 过期时间默认180s
payload = readWriteCacheMap.get(key);
}
} catch (Throwable t) {
logger.error("Cannot get value for key : {}", key, t);
}
return payload;
}
三级缓存
多级缓存设计思想 :
-
在拉取注册表的时候:
- 首先从ReadOnlyCacheMap里查缓存的注册表。
- 若没有,就找ReadWriteCacheMap里缓存的注册表。
- 如果还没有,就从内存中获取实际的注册表数据。
-
在注册表发生变更的时候:
- 会在内存中更新变更的注册表数据,同时过期掉ReadWriteCacheMap。
- 此过程不会影响ReadOnlyCacheMap提供人家查询注册表。
- 默认每30秒Eureka Server会将ReadWriteCacheMap更新到ReadOnlyCacheMap里
- 默认每180秒Eureka Server会将ReadWriteCacheMap里是数据失效
入口:ResponseCacheImpl
只读缓存:只读缓存的数据只会来源于读写缓存,而且没有主动更新缓存的API,只能通过定时更新,默认每30s执行一次
if (shouldUseReadOnlyResponseCache) {
timer.schedule(getCacheUpdateTask(),
new Date(
((System.currentTimeMillis() / esponseCacheUpdateIntervalMs)
* responseCacheUpdateIntervalMs)
+ responseCacheUpdateIntervalMs),
responseCacheUpdateIntervalMs);
}
private TimerTask getCacheUpdateTask() {
return new TimerTask() {
@Override
public void run() {
logger.debug("Updating the client cache from response cache");
for (Key key : readOnlyCacheMap.keySet()) {
if (logger.isDebugEnabled()) {
logger.debug("...");
}
try {
CurrentRequestVersion.set(key.getVersion());
Value cacheValue = readWriteCacheMap.get(key);
Value currentCacheValue = readOnlyCacheMap.get(key);
//判断只读缓存和读写缓存数据是否一致
if (cacheValue != currentCacheValue) {
readOnlyCacheMap.put(key, cacheValue);
}
} catch (Throwable th) {
logger.error("...");
} finally {
CurrentRequestVersion.remove();
}
}
}
};
}
读写缓存:guava
this.readWriteCacheMap =
CacheBuilder.newBuilder()
.initialCapacity(serverConfig.getInitialCapacityOfResponseCache())
//设置过期时间 默认180s
.expireAfterWrite(serverConfig.getResponseCacheAutoExpirationInSeconds(), TimeUnit.SECONDS)
.removalListener(new RemovalListener<Key, Value>() {
@Override
public void onRemoval(RemovalNotification<Key, Value> notification) {
Key removedKey = notification.getKey();
if (removedKey.hasRegions()) {
Key cloneWithNoRegions = removedKey.cloneWithoutRegions();
regionSpecificKeys.remove(cloneWithNoRegions, removedKey);
}
}
})
//设置监听器,如果读写缓存没有数据,则进入这个方法
.build(new CacheLoader<Key, Value>() {
@Override
public Value load(Key key) throws Exception {
if (key.hasRegions()) {
Key cloneWithNoRegions = key.cloneWithoutRegions();
regionSpecificKeys.put(cloneWithNoRegions, key);
}
//从真实缓存中获取数据,里面涉及全量拉取和增量拉取,
//与client端相对应,里面会有一个队列统计最近修改的数据
Value value = generatePayload(key);
return value;
}
});
真实缓存:
private Value generatePayload(Key key) {
Stopwatch tracer = null;
try {
String payload;
switch (key.getEntityType()) {
case Application:
boolean isRemoteRegionRequested = key.hasRegions();
//全量获取
if (ALL_APPS.equals(key.getName())) {
if (isRemoteRegionRequested) {
tracer = serializeAllAppsWithRemoteRegionTimer.start();
payload = getPayLoad(key,
registry.getApplicationsFromMultipleRegions(key.getRegions()));
} else {
tracer = serializeAllAppsTimer.start();
payload = getPayLoad(key, registry.getApplications());
}
//增量获取
} else if (ALL_APPS_DELTA.equals(key.getName())) {
if (isRemoteRegionRequested) {
tracer = serializeDeltaAppsWithRemoteRegionTimer.start();
versionDeltaWithRegions.incrementAndGet();
versionDeltaWithRegionsLegacy.incrementAndGet();
payload = getPayLoad(key,
registry.getApplicationDeltasFromMultipleRegions(key.getRegions()));
} else {
tracer = serializeDeltaAppsTimer.start();
versionDelta.incrementAndGet();
versionDeltaLegacy.incrementAndGet();
payload = getPayLoad(key, registry.getApplicationDeltas());
}
} else {
tracer = serializeOneApptimer.start();
payload = getPayLoad(key, registry.getApplication(key.getName()));
}
break;
case VIP:
case SVIP:
tracer = serializeViptimer.start();
payload = getPayLoad(key, getApplicationsForVip(key, registry));
break;
default:
payload = "";
break;
}
return new Value(payload);
} finally {
if (tracer != null) {
tracer.stop();
}
}
}
二.EurekaClient
1.入口分析
还是从spring.factories文件入手
利用springboot自动配置原理自动导入了EurekaClientAutoConfiguration配置类:
而EurekaClientAutoConfiguration自动配置类主要做了两件事情:
- 初始化Eureka客户端的相关配置
- 初始化Eureka客户端
//初始化Eureka客户端的相关配置,配置文件eureka.client下的信息
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class,
search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean(ConfigurableEnvironment env) {
EurekaClientConfigBean client = new EurekaClientConfigBean();
if ("bootstrap".equals(this.env.getProperty("spring.config.name"))) {
client.setRegisterWithEureka(false);
}
return client;
}
//初始化Eureka客户端,CloudEurekaClient是SpringCloud提供的客户端
@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);
}
并且在CloudEurekaClient实例化过程中,完成了服务注册,以及开启服务发现定时任务、心跳续约定时任务等核心功能
进入CloudEurekaClient实例化链,最终会调用到以下代码
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager,
EurekaClientConfig config,
AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider,
EndpointRandomizer endpointRandomizer) {
...
//设置定时调度任务
this.scheduler = Executors.newScheduledThreadPool( 2,
(new ThreadFactoryBuilder())
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
//心跳续约线程池
this.heartbeatExecutor = new ThreadPoolExecutor(
1, this.clientConfig.getHeartbeatExecutorThreadPoolSize(),
0L, TimeUnit.SECONDS,new SynchronousQueue(),
(new ThreadFactoryBuilder())
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build());
//缓存刷新线程池
this.cacheRefreshExecutor = new ThreadPoolExecutor(
1, this.clientConfig.getCacheRefreshExecutorThreadPoolSize(),
0L, TimeUnit.SECONDS, new SynchronousQueue(),
(new ThreadFactoryBuilder())
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true).build());
...
//完成服务发现(全量拉取)
if (this.clientConfig.shouldFetchRegistry() && !this.fetchRegistry(false)) {
//如果拉取失败,从备份服务器拉取
this.fetchRegistryFromBackup();
}
// 判断是否需要强制注册,默认false
if (this.clientConfig.shouldRegisterWithEureka()
&& this.clientConfig.shouldEnforceRegistrationAtInit()) {
try {
//2-1 注册接口
if (!this.register()) {
throw new IllegalStateException("...");
}
} catch (Throwable var9) {
logger.error("Registration error at startup: {}", var9.getMessage());
throw new IllegalStateException(var9);
}
}
//2-2 初始化调度任务
this.initScheduledTasks();,
}
private void initScheduledTasks() {
//判断是否开启服务发现功能,默认true
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
//定时拉取服务注册列表
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
new CacheRefreshThread() //该线程执行更新的具体逻辑
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
//判断是否开启服务注册功能
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
//心跳续约定时任务
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
new HeartbeatThread() //该线程执行续约的具体逻辑
),
renewalIntervalInSecs, TimeUnit.SECONDS);
// 服务注册线程
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
// 2-3 开始服务注册
instanceInfoReplicator
.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
2.主线分析
服务注册
入口代码中的2-1,以及2-3调用了相同的注册接口
底层实现其实就是组装URL,发送了一个http请求到Server端 ApplicationResource#addInstance方法
public EurekaHttpResponse<Void> register(InstanceInfo info) {
//请求服务端接口的url地址
String urlPath = "apps/" + info.getAppName();
ClientResponse response = null;
EurekaHttpResponse var5;
try {
Builder resourceBuilder = this.jerseyClient
.resource(this.serviceUrl).path(urlPath).getRequestBuilder();
this.addExtraHeaders(resourceBuilder);
//发送请求
response = (ClientResponse)
((Builder)((Builder)((Builder)resourceBuilder.header("Accept-Encoding", "gzip"))
.type(MediaType.APPLICATION_JSON_TYPE))
.accept(new String[]{"application/json"}))
.post(ClientResponse.class, info);
var5 = EurekaHttpResponse
.anEurekaHttpResponse(response.getStatus())
.headers(headersOf(response))
.build();
} finally {
if (response != null) {
response.close();
}
}
return var5;
}
服务发现
从服务发现定时任务的线程进入
/**
* forceFullRegistryFetch 是否全量拉取 初始化时全量拉取,定时更新增量拉取
*/
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
Stopwatch tracer = this.FETCH_REGISTRY_TIMER.start();
try {
boolean var4;
try {
//获取本地缓存
Applications applications = this.getApplications();
if (!this.clientConfig.shouldDisableDelta() && Strings.isNullOrEmpty(
this.clientConfig.getRegistryRefreshSingleVipAddress()) &&
!forceFullRegistryFetch &&
//本地缓存不为空
applications != null &&
applications.getRegisteredApplications().size() != 0 &&
applications.getVersion() != -1L) {
//增量拉取
this.getAndUpdateDelta(applications);
} else {
//全量拉取
this.getAndStoreFullRegistry();
}
applications.setAppsHashCode(applications.getReconcileHashCode());
this.logTotalInstances();
break label122;
} catch (Throwable var8) {
var4 = false;
} finally {
if (tracer != null) {
tracer.stop();
}
}
return var4;
}
this.onCacheRefreshed();
this.updateInstanceRemoteStatus();
return true;
}
全量拉取:发送了一个http请求到Server端 ApplicationsResource#getContainers方法
增量拉取:发送了一个http请求到Server端 ApplicationsResource#getContainerDifferential方法
private void getAndUpdateDelta(Applications applications) throws Throwable {
long currentUpdateGeneration = fetchRegistryGeneration.get();
//发送增量拉取的请求
Applications delta = null;
EurekaHttpResponse<Applications> httpResponse =
eurekaTransport.queryClient.getDelta(remoteRegionsRef.get());
if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) {
delta = httpResponse.getEntity();
}
if (delta == null) {
//如果没有拉取到服务信息,全量拉取
getAndStoreFullRegistry();
} else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration,
currentUpdateGeneration + 1)) {
String reconcileHashCode = "";
if (fetchRegistryUpdateLock.tryLock()) {
try {
//把拉取到的服务信息,合并到本地缓存
updateDelta(delta);
reconcileHashCode = getReconcileHashCode(applications);
} finally {
fetchRegistryUpdateLock.unlock();
}
} else {
logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta");
}
....
} else {
....
}
}
private void updateDelta(Applications delta) {
int deltaCount = 0;
Iterator var3 = delta.getRegisteredApplications().iterator();
while(var3.hasNext()) {
Application app = (Application)var3.next();
Iterator var5 = app.getInstances().iterator();
while(var5.hasNext()) {
InstanceInfo instance = (InstanceInfo)var5.next();
Applications applications = this.getApplications();
String instanceRegion = this.instanceRegionChecker.getInstanceRegion(instance);
if (!this.instanceRegionChecker.isLocalRegion(instanceRegion)) {
Applications remoteApps
= (Applications)this.remoteRegionVsApps.get(instanceRegion);
if (null == remoteApps) {
remoteApps = new Applications();
this.remoteRegionVsApps.put(instanceRegion, remoteApps);
}
applications = remoteApps;
}
++deltaCount;
Application existingApp;
//新增操作(服务注册)
if (ActionType.ADDED.equals(instance.getActionType())) {
existingApp = applications.getRegisteredApplications(instance.getAppName());
if (existingApp == null) {
applications.addApplication(app);
}
applications.getRegisteredApplications(instance.getAppName())
.addInstance(instance);
//修改操作(心跳续约)
} else if (ActionType.MODIFIED.equals(instance.getActionType())) {
existingApp = applications.getRegisteredApplications(instance.getAppName());
if (existingApp == null) {
applications.addApplication(app);
}
applications.getRegisteredApplications(instance.getAppName())
.addInstance(instance);
//删除操作(服务下架)
} else if (ActionType.DELETED.equals(instance.getActionType())) {
existingApp = applications.getRegisteredApplications(instance.getAppName());
if (existingApp != null) {
logger.debug("Deleted instance {} to the existing apps ", instance.getId());
existingApp.removeInstance(instance);
if (existingApp.getInstancesAsIsFromEureka().isEmpty()) {
applications.removeApplication(existingApp);
}
}
}
}
}
this.getApplications().setVersion(delta.getVersion());
this.getApplications().shuffleInstances(this.clientConfig.shouldFilterOnlyUpInstances());
var3 = this.remoteRegionVsApps.values().iterator();
while(var3.hasNext()) {
Applications applications = (Applications)var3.next();
applications.setVersion(delta.getVersion());
applications.shuffleInstances(this.clientConfig.shouldFilterOnlyUpInstances());
}
}
心跳续约
从心跳续约定时任务的线程HeartbeatThread进入
boolean renew() {
try {
//发送心跳续约请求
EurekaHttpResponse<InstanceInfo> httpResponse = this.eurekaTransport.registrationClient
.sendHeartBeat(
this.instanceInfo.getAppName(),
this.instanceInfo.getId(),
this.instanceInfo,
(InstanceStatus)null
);
//心跳续约失败
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
this.REREGISTER_COUNTER.increment();
long timestamp = this.instanceInfo.setIsDirtyWithTime();
//重新注册
boolean success = this.register();
if (success) {
this.instanceInfo.unsetIsDirty(timestamp);
}
return success;
} else {
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
}
} catch (Throwable var5) {
return false;
}
}
boolean renew() {
try {
//发送心跳续约请求
EurekaHttpResponse<InstanceInfo> httpResponse = this.eurekaTransport.registrationClient
.sendHeartBeat(
this.instanceInfo.getAppName(),
this.instanceInfo.getId(),
this.instanceInfo,
(InstanceStatus)null
);
//心跳续约失败
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
this.REREGISTER_COUNTER.increment();
long timestamp = this.instanceInfo.setIsDirtyWithTime();
//重新注册
boolean success = this.register();
if (success) {
this.instanceInfo.unsetIsDirty(timestamp);
}
return success;
} else {
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
}
} catch (Throwable var5) {
return false;
}
}