接口调用者通过nginx或ipvs等中间件等调用接口服务,并对接口服务集群做负载均衡策略,若针对服务内部之间的调用,若接口实例下线,nginx等并不能感知到,故针对于这样的调用方式,在服务之间并不合理。
引入SpringCloud中Eureka相关依赖
增加SpringCloud相关配置
通过@EnableDiscoveryClient注解,开启服务注册与发现功能
查看zone的概念
查看Eureka的自我保护机制
eureka.instance.instanted | 实例唯一ID | |
---|---|---|
eureka.client.serviceUrl | Eureka客户端的地址 | |
eureka.client.registerWithEureka | 是否注册到eureka上 | |
eureka.client.fetchRegistry | 是否拉去服务 |
@org.springframework.cloud.context.config.annotation.RefreshScope
@Lazy /// 返回的是一个EurekaClient 说明就是在此处构建了一个EurekaClient 且是以懒加载的形式构建的
public EurekaClient eurekaClient(ApplicationInfoManager manager, EurekaClientConfig config, EurekaInstanceConfig instance, @Autowired(required = false) HealthCheckHandler healthCheckHandler) {
// eurekaApplicationInfoManager方法 返回的ApplicationInfoManager 作为参数传到改方法中 还将EurekaInstanceConfig 作为参数加载了进来
ApplicationInfoManager appManager;
if (AopUtils.isAopProxy(manager)) {
appManager = (ApplicationInfoManager)ProxyUtils.getTargetObject(manager);
} else {
appManager = manager;
}
// 定位2 创建 CloudEurekaClient 方法
CloudEurekaClient cloudEurekaClient = new CloudEurekaClient(appManager, config, this.optionalArgs, this.context);
cloudEurekaClient.registerHealthCheck(healthCheckHandler);
return cloudEurekaClient;
}
// 下面这个方法加载的结果是为上个方法提供的
@Bean
@ConditionalOnMissingBean(
value = {ApplicationInfoManager.class},
search = SearchStrategy.CURRENT
)
@org.springframework.cloud.context.config.annotation.RefreshScope
@Lazy
public ApplicationInfoManager eurekaApplicationInfoManager(EurekaInstanceConfig config) {
InstanceInfo instanceInfo = (new InstanceInfoFactory()).create(config); // 创建了一个Instance对象 在这边加载和读取所有的Instance配置 如果我们没有配置,他将会默认的创建一个Instance配置
return new ApplicationInfoManager(config, instanceInfo); // 将对象放入ApplicationInfoManager中
}
public InstanceInfo create(EurekaInstanceConfig config) {
Builder leaseInfoBuilder = Builder.newBuilder().setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds()).setDurationInSecs(config.getLeaseExpirationDurationInSeconds());
com.netflix.appinfo.InstanceInfo.Builder builder = com.netflix.appinfo.InstanceInfo.Builder.newBuilder();
String namespace = config.getNamespace();
if (!namespace.endsWith(".")) {
namespace = namespace + ".";
}
// 在这边做了一堆的builder 将config这个配置放入其中
builder.setNamespace(namespace).setAppName(config.getAppname()).setInstanceId(config.getInstanceId()).setAppGroupName(config.getAppGroupName()).setDataCenterInfo(config.getDataCenterInfo()).setIPAddr(config.getIpAddress()).setHostName(config.getHostName(false)).setPort(config.getNonSecurePort()).enablePort(PortType.UNSECURE, config.isNonSecurePortEnabled()).setSecurePort(config.getSecurePort()).enablePort(PortType.SECURE, config.getSecurePortEnabled()).setVIPAddress(config.getVirtualHostName()).setSecureVIPAddress(config.getSecureVirtualHostName()).setHomePageUrl(config.getHomePageUrlPath(), config.getHomePageUrl()).setStatusPageUrl(config.getStatusPageUrlPath(), config.getStatusPageUrl()).setHealthCheckUrls(config.getHealthCheckUrlPath(), config.getHealthCheckUrl(), config.getSecureHealthCheckUrl()).setASGName(config.getASGName());
if (!config.isInstanceEnabledOnit()) {
InstanceStatus initialStatus = InstanceStatus.STARTING;
if (log.isInfoEnabled()) {
log.info("Setting initial instance status as: " + initialStatus);
}
builder.setStatus(initialStatus);
} else if (log.isInfoEnabled()) {
log.info("Setting initial instance status as: " + InstanceStatus.UP + ". This may be too early for the instance to advertise itself as available. You would instead want to control this via a healthcheck handler.");
}
Iterator var9 = config.getMetadataMap().entrySet().iterator();
while(var9.hasNext()) {
Entry<String, String> mapEntry = (Entry)var9.next();
String key = (String)mapEntry.getKey();
String value = (String)mapEntry.getValue();
if (value != null && !value.isEmpty()) {
builder.add(key, value);
}
}
InstanceInfo instanceInfo = builder.build();
instanceInfo.setLeaseInfo(leaseInfoBuilder.build());
return instanceInfo;
}
public interface EurekaInstanceConfig { //EurekaInstanceConfig 是一个接口,找到其实现类:
}
.....// 省略代码
@ConfigurationProperties("eureka.instance") // 装载以eureka.instance 开头的配置
public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, EnvironmentAware {
private static final String UNKNOWN = "unknown";
private HostInfo hostInfo;
......// 省略代码
public String getHostname() {
return this.getHostName(false);
}
......// 省略代码
}
public CloudEurekaClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs<?> args, ApplicationEventPublisher publisher) {
// 定位3 追中父类信息
super(applicationInfoManager, config, args); // 调用了一个父类的构造方法 传入的参数是我们的配置applicationInfoManager 同时将我们的实例信息也传了过来
this.cacheRefreshedCount = new AtomicLong(0L);
this.eurekaHttpClient = new AtomicReference();
this.applicationInfoManager = applicationInfoManager;
this.publisher = publisher;
this.eurekaTransportField = ReflectionUtils.findField(DiscoveryClient.class, "eurekaTransport");
ReflectionUtils.makeAccessible(this.eurekaTransportField);
}
public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args) {
this(applicationInfoManager, config, args, new Provider<BackupRegistry>() {
private volatile BackupRegistry backupRegistryInstance;
// 代码都在装载配置
// 定位4 何时将值set进配置中
public synchronized BackupRegistry get() {
if (this.backupRegistryInstance == null) {
String backupRegistryClassName = config.getBackupRegistryImpl();
if (null != backupRegistryClassName) {
try {
this.backupRegistryInstance = (BackupRegistry)Class.forName(backupRegistryClassName).newInstance();
DiscoveryClient.logger.info("Enabled backup registry of type {}", this.backupRegistryInstance.getClass());
} catch (InstantiationException var3) {
DiscoveryClient.logger.error("Error instantiating BackupRegistry.", var3);
} catch (IllegalAccessException var4) {
DiscoveryClient.logger.error("Error instantiating BackupRegistry.", var4);
} catch (ClassNotFoundException var5) {
DiscoveryClient.logger.error("Error instantiating BackupRegistry.", var5);
}
}
if (this.backupRegistryInstance == null) {
DiscoveryClient.logger.warn("Using default backup registry implementation which does not do anything.");
this.backupRegistryInstance = new NotImplementedRegistryImpl();
}
}
return this.backupRegistryInstance;
}
});
}
@Inject // 查询改注解的作用
// 构造方法中各种各样的参数都是由配置好的IOC容器提供的 Client的一些配置参数和必要的东西都是在这个里面获取到的
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider) {
this.RECONCILE_HASH_CODES_MISMATCH = Monitors.newCounter("DiscoveryClient_ReconcileHashCodeMismatch");
......// todo 省略代码
this.remoteRegionsRef = new AtomicReference(this.remoteRegionsToFetch.get() == null ? null : ((String)this.remoteRegionsToFetch.get()).split(","));
// 定位5 在这里开始做相应的判断
if (config.shouldFetchRegistry()) {
this.registryStalenessMonitor = new ThresholdLevelsMetric(this, "eurekaClient.registry.lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
} else {
this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
}
if (config.shouldRegisterWithEureka()) {
this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, "eurekaClient.registration.lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
} else {
this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
}
......// todo 省略代码
logger.info("Initializing Eureka in region {}", this.clientConfig.getRegion());
if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
logger.info("Client configured to neither register nor query for data.");
logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}", this.initTimestampMs, this.getApplications().size());
} else {
try {
// 定位6 出现了线程池 newScheduledThreadPool 在固定的时间执行一次 定时任务线程池
this.scheduler = Executors.newScheduledThreadPool(2, (new ThreadFactoryBuilder()).setNameFormat("DiscoveryClient-%d").setDaemon(true).build());
// heartbeatExecutor heartbeat心跳 心跳线程池
this.heartbeatExecutor = new ThreadPoolExecutor(1, this.clientConfig.getHeartbeatExecutorThreadPoolSize(), 0L, TimeUnit.SECONDS, new SynchronousQueue(), (new ThreadFactoryBuilder()).setNameFormat("DiscoveryClient-HeartbeatExecutor-%d").setDaemon(true).build());
//cacheRefreshExecutor 刷新缓存的定时任务
this.cacheRefreshExecutor = new ThreadPoolExecutor(1, this.clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0L, TimeUnit.SECONDS, new SynchronousQueue(), (new ThreadFactoryBuilder()).setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d").setDaemon(true).build());
......// todo 省略代码
// 定位7 在此处初始化所有的定时任务
this.initScheduledTasks();
try {
Monitors.registerObject(this);
} catch (Throwable var7) {
logger.warn("Cannot register timers", var7);
}
DiscoveryManager.getInstance().setDiscoveryClient(this);
DiscoveryManager.getInstance().setEurekaClientConfig(config);
this.initTimestampMs = System.currentTimeMillis();
logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}", this.initTimestampMs, this.getApplications().size());
}
}
private void initScheduledTasks() { // 在这里将线程一一启动,但是并未发现服务的注册流程 思考:什么时候开始注册服务,服务在什么情境下可以被注册进去 ps:伴随着心跳检测机制 定位8 心跳检测线程
int renewalIntervalInSecs;
int expBackOffBound;
if (this.clientConfig.shouldFetchRegistry()) {
renewalIntervalInSecs = this.clientConfig.getRegistryFetchIntervalSeconds();
expBackOffBound = this.clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
this.scheduler.schedule(new TimedSupervisorTask("cacheRefresh", this.scheduler, this.cacheRefreshExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.CacheRefreshThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
}
if (this.clientConfig.shouldRegisterWithEureka()) {
renewalIntervalInSecs = this.instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
expBackOffBound = this.clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: renew interval is: {}", renewalIntervalInSecs);
// 定位8 心跳检测线程 new DiscoveryClient.HeartbeatThread()) 查看线程创建过程
this.scheduler.schedule(new TimedSupervisorTask("heartbeat", this.scheduler, this.heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new DiscoveryClient.HeartbeatThread()), (long)renewalIntervalInSecs, TimeUnit.SECONDS);
this.instanceInfoReplicator = new InstanceInfoReplicator(this, this.instanceInfo, this.clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2);
this.statusChangeListener = new StatusChangeListener() {
public String getId() {
return "statusChangeListener";
}
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN != statusChangeEvent.getStatus() && InstanceStatus.DOWN != statusChangeEvent.getPreviousStatus()) {
DiscoveryClient.logger.info("Saw local status change event {}", statusChangeEvent);
} else {
DiscoveryClient.logger.warn("Saw local status change event {}", statusChangeEvent);
}
DiscoveryClient.this.instanceInfoReplicator.onDemandUpdate();
}
};
if (this.clientConfig.shouldOnDemandUpdateStatusChange()) {
this.applicationInfoManager.registerStatusChangeListener(this.statusChangeListener);
}
this.instanceInfoReplicator.start(this.clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
private class HeartbeatThread implements Runnable {
private HeartbeatThread() {
}
public void run() {
if (DiscoveryClient.this.renew()) { // 定位9 追中renew方法
DiscoveryClient.this.lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
boolean renew() {
try {
// 定位10 InstanceInfo(之前我们看到的InstanceInfo) EurekaHttpResponse 定义了statusCode
EurekaHttpResponse<InstanceInfo> httpResponse = this.eurekaTransport.registrationClient.sendHeartBeat(this.instanceInfo.getAppName(), this.instanceInfo.getId(), this.instanceInfo, (InstanceStatus)null);
logger.debug("DiscoveryClient_{} - Heartbeat status: {}", this.appPathIdentifier, httpResponse.getStatusCode());
// 定位11 NOT_FOUND 404 未找打 执行注册
if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
this.REREGISTER_COUNTER.increment(); // 定位12 在此处对数字进行加加减减(暂时未弄清楚其作用)
logger.info("DiscoveryClient_{} - Re-registering apps/{}", this.appPathIdentifier, this.instanceInfo.getAppName());
long timestamp = this.instanceInfo.setIsDirtyWithTime();
boolean success = this.register(); // 定位13 在此处进行注册
if (success) {
this.instanceInfo.unsetIsDirty(timestamp);
}
return success;
} else {
return httpResponse.getStatusCode() == Status.OK.getStatusCode();
}
} catch (Throwable var5) {
logger.error("DiscoveryClient_{} - was unable to send heartbeat!", this.appPathIdentifier, var5);
return false;
}
}
说明
在Client初始化时,或初始化一系列定时任务,这些定时任务中包含了心跳的定时任务,心跳定时任务启动后会向服务中心发起一次请求,判断当前的服务实例有没有被注册到Eureka上面,如果没有注册,将返回一个404回来,返回404之后,就会进入到我的注册中来;如果返回的不是404,则会判断当前的服务实例是否正常
以上就是我们所关心的,, Eureka是如何将服务注册上的(此处解决了第一个问题:启动时服务如何注册到Eureka的)
ps:暂时不看server端代码
@Configuration
@Import({EurekaServerInitializerConfiguration.class})
@ConditionalOnBean({Marker.class})
@EnableConfigurationProperties({EurekaDashboardProperties.class, InstanceRegistryProperties.class})
@PropertySource({"classpath:/eureka/server.properties"})
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
private static final String[] EUREKA_PACKAGES = new String[]{"com.netflix.discovery", "com.netflix.eureka"};
@Autowired
private ApplicationInfoManager applicationInfoManager;
@Autowired
private EurekaServerConfig eurekaServerConfig; // 在此处读取配置信息 在其下面都有详细加载读取config
@Autowired
private EurekaClientConfig eurekaClientConfig;
@Autowired
private EurekaClient eurekaClient;
@Autowired
private InstanceRegistryProperties instanceRegistryProperties;
public static final CloudJacksonJson JACKSON_JSON = new CloudJacksonJson();
...//省略代码
private static CodecWrapper getFullJson(EurekaServerConfig serverConfig) {
CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getJsonCodecName());
return codec == null ? CodecWrappers.getCodec(JACKSON_JSON.codecName()) : codec;
}
...// 省略代码
}
@ConfigurationProperties("eureka.server")
public class EurekaServerConfigBean implements EurekaServerConfig {
public static final String PREFIX = "eureka.server"; // 读取以eureka.server开头的属性 如果没有配置,则使用默认值
private static final int MINUTES = 60000;
@Autowired(
required = false
)
PropertyResolver propertyResolver;
private String aWSAccessId;
private String aWSSecretKey;
private int eIPBindRebindRetries = 3;
private int eIPBindingRetryIntervalMs = 300000;
private int eIPBindingRetryIntervalMsWhenUnbound = 60000;
private boolean enableSelfPreservation = true;
private double renewalPercentThreshold = 0.85D;
private int renewalThresholdUpdateIntervalMs = 900000;
private int peerEurekaNodesUpdateIntervalMs = 600000;
private int numberOfReplicationRetries = 5;
private int peerEurekaStatusRefreshTimeIntervalMs = 30000;
private int waitTimeInMsWhenSyncEmpty = 300000;
private int peerNodeConnectTimeoutMs = 200;
private int peerNodeReadTimeoutMs = 200;
..... // 省略代码
}
/**
* Register with the eureka service by making the appropriate REST call.
*/
boolean register() throws Throwable {
logger.info(PREFIX + "{}: registering service...", appPathIdentifier);
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo); // 在此发送http请求 对register方法的实现类上面打断点进行debug调试 定位14 进行调试
} 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() == Status.NO_CONTENT.getStatusCode();
}
// 重写 register 方法
@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
String urlPath = "apps/" + info.getAppName(); // 在这边拼装一个 urlPath 地址
ClientResponse response = null;
try {
Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
addExtraHeaders(resourceBuilder); // 拼装的 urlPath 通过JerseyClient 进行发送
response = resourceBuilder // 在此处拿到 response
.header("Accept-Encoding", "gzip")
.type(MediaType.APPLICATION_JSON_TYPE)
.accept(MediaType.APPLICATION_JSON)
.post(ClientResponse.class, info);
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
@Bean
public FilterRegistrationBean jerseyFilterRegistration(Application eurekaJerseyApp) {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new ServletContainer(eurekaJerseyApp));
bean.setOrder(2147483647);
bean.setUrlPatterns(Collections.singletonList("/eureka/*"));
return bean;
}
// debug级别
logging.level.org.springframework.cloud.netflix = debug
logging.level.com.netflix = debug
截取部分日志信息
2019-05-31 11:43:10.044 DEBUG 11832 --- [io-10000-exec-3] o.s.c.n.eureka.server.InstanceRegistry : renew HELLOSERVER serverId localhost:helloserver:8002, isReplication {}false // 注册时信息
2019-05-31 11:43:10.044 DEBUG 11832 --- [io-10000-exec-3] c.n.e.registry.AbstractInstanceRegistry : Fetching applications registry with remote regions: false, Regions argument []
2019-05-31 11:43:10.044 DEBUG 11832 --- [io-10000-exec-3] c.n.e.registry.AbstractInstanceRegistry : Processing override status using rule: [com.netflix.eureka.registry.rule.DownOrStartingRule, com.netflix.eureka.registry.rule.OverrideExistsRule, com.netflix.eureka.registry.rule.LeaseExistsRule, com.netflix.eureka.registry.rule.AlwaysMatchInstanceStatusRule]
2019-05-31 11:43:10.044 DEBUG 11832 --- [io-10000-exec-3] c.n.e.registry.rule.LeaseExistsRule : There is already an existing lease with status UP for instance localhost:helloserver:8002
2019-05-31 11:43:10.044 DEBUG 11832 --- [io-10000-exec-3] c.n.eureka.resources.InstanceResource : Found (Renew): HELLOSERVER - localhost:helloserver:8002; reply status=200
2019-05-31 11:43:10.547 DEBUG 11832 --- [et_localhost-16] c.n.d.shared.MonitoredConnectionManager : Get connection: {}->http://localhost:10000, timeout = 200 // 返回的状态吗
2019-05-31 11:43:10.547 DEBUG 11832 --- [et_localhost-16] c.n.d.shared.NamedConnectionPool : [{}->http://localhost:10000] total kept alive: 1, total issued: 0, total allocated: 1 out of 1000
2019-05-31 11:43:10.547 DEBUG 11832 --- [et_localhost-16] c.n.d.shared.NamedConnectionPool : Getting free connection [{}->http://localhost:10000][null]
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] c.n.d.util.DeserializerStringCache : clearing global-level cache with size 0
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] c.n.d.util.DeserializerStringCache : clearing app-level serialization cache with size 0
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] o.s.c.n.eureka.server.InstanceRegistry : renew HELLOSERVER serverId localhost:helloserver:8002, isReplication {}true
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] c.n.e.registry.AbstractInstanceRegistry : Fetching applications registry with remote regions: false, Regions argument []
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] c.n.e.registry.AbstractInstanceRegistry : Processing override status using rule: [com.netflix.eureka.registry.rule.DownOrStartingRule, com.netflix.eureka.registry.rule.OverrideExistsRule, com.netflix.eureka.registry.rule.LeaseExistsRule, com.netflix.eureka.registry.rule.AlwaysMatchInstanceStatusRule]
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] c.n.e.r.r.AlwaysMatchInstanceStatusRule : Returning the default instance status UP for instance localhost:helloserver:8002
2019-05-31 11:43:10.549 DEBUG 11832 --- [io-10000-exec-9] c.n.eureka.resources.InstanceResource : Found (Renew): HELLOSERVER - localhost:helloserver:8002; reply status=200
@Produces({"application/xml", "application/json"})
public class InstanceResource {
@GET
public Response getInstanceInfo() {
InstanceInfo appInfo = registry
.getInstanceByAppAndId(app.getName(), id); // 定位15 找 app 的 getName app 指的是 ApplicationResource
if (appInfo != null) {
logger.debug("Found: {} - {}", app.getName(), id);
return Response.ok(appInfo).build();
} else {
logger.debug("Not Found: {} - {}", app.getName(), id);
return Response.status(Status.NOT_FOUND).build();
}
}
}
@GET
public Response getApplication(@PathParam("version") String version,
@HeaderParam("Accept") final String acceptHeader,
@HeaderParam(EurekaAccept.HTTP_X_EUREKA_ACCEPT) String eurekaAccept) {
if (!registry.shouldAllowAccess(false)) {
return Response.status(Status.FORBIDDEN).build();
}
EurekaMonitors.GET_APPLICATION.increment();
CurrentRequestVersion.set(Version.toEnum(version));
KeyType keyType = Key.KeyType.JSON;
if (acceptHeader == null || !acceptHeader.contains("json")) {
keyType = Key.KeyType.XML;
}
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();
}
}
@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); // So we don't have different caches for same regions queried in different order.
EurekaMonitors.GET_ALL_WITH_REMOTE_REGIONS.increment();
}
// Check if the server allows the access to the registry. The server can
// restrict access if it is not
// ready to serve traffic depending on various reasons.
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 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("{appId}")
public ApplicationResource getApplicationResource(
@PathParam("version") String version,
@PathParam("appId") String appId) {
CurrentRequestVersion.set(Version.toEnum(version));
return new ApplicationResource(appId, serverConfig, registry);
}
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info,
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) {
logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
// validate that the instanceinfo contains all the necessary required fields
// 省略代码
registry.register(info, "true".equals(isReplication)); // 定位14 追踪 register 方法
return Response.status(204).build(); // 204 to be backwards compatible
}
public void register(final InstanceInfo info, final boolean isReplication) {
this.handleRegistration(info, this.resolveInstanceLeaseDuration(info), isReplication);
super.register(info, isReplication); // 定位16
}
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;
if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
leaseDuration = info.getLeaseInfo().getDurationInSecs();
}
super.register(info, leaseDuration, isReplication); // 定位17 在此又一次调用了父类的 register 方法
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication); // 高可用的同步机制方法
}
/**
* Registers a new instance with a given duration.
*
* @see com.netflix.eureka.lease.LeaseManager#register(java.lang.Object, int, boolean)
*/
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock(); // 在此将 读 锁住
// AbstractInstanceRegistry 类中定义表的 registry , registry 是一个 ConcurrentHashMap
// private final ConcurrentHashMap>> registry = new ConcurrentHashMap>>();
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName()); // 在此 通过一个registry 去获取一个Map
REGISTER.increment(isReplication);
if (gMap == null) {
final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>(); // 到了这里我们就应该明白,这个服务实例是通过一个 ConcurrentHashMap 来维护的
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
}
Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
// Retain the last dirty timestamp without overwriting it, if there is already a lease
if (existingLease != null && (existingLease.getHolder() != null)) {
Long existingLastDirtyTimestamp = existingLease.getHolder().getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
// this is a > instead of a >= because if the timestamps are equal, we still take the remote transmitted
// InstanceInfo instead of the server local copy.
if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater" +
" than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
registrant = existingLease.getHolder();
}
} else {
// The lease does not exist and hence it is a new registration
synchronized (lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
// Since the client wants to register it, increase the number of clients sending renews
this.expectedNumberOfClientsSendingRenews = this.expectedNumberOfClientsSendingRenews + 1;
updateRenewsPerMinThreshold();
}
}
logger.debug("No previous lease information found; it is new registration");
}
Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);
if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
gMap.put(registrant.getId(), lease);
synchronized (recentRegisteredQueue) {
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())) {
logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the "
+ "overrides", registrant.getOverriddenStatus(), registrant.getId());
if (!overriddenInstanceStatusMap.containsKey(registrant.getId())) {
logger.info("Not found overridden id {} and hence adding it", 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);
}
// Set the status based on the overridden status rules
InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus(registrant, existingLease, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
// If the lease is registered with UP status, set lease service up timestamp
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());
logger.info("Registered instance {}/{} with status {} (replication={})",
registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication);
} finally {
read.unlock();
}
}
/**
* The task that fetches the registry information at specified intervals.
*
*/
class CacheRefreshThread implements Runnable {
public void run() {
refreshRegistry();
}
}
spring:
profiles:
active: dev
## 通过更改不停的 spring.profiles.active 属性 启动不通的客户端
---
spring:
application:
name: eureka-peer
profiles: dev
server:
port: 10000
eureka:
instance:
hostname: dev
instance-id: dev
client:
fetch-registry: false
register-with-eureka: false
service-url:
# defaultZone: http://localhost:10000/eureka/,http://localhost:10001/eureka/,http://localhost:10002/eureka/
defaultZone: http://localhost:10000/eureka/
server:
wait-time-in-ms-when-sync-empty: 0
enable-self-preservation: true
peer-eureka-nodes-update-interval-ms: 10000
---
spring:
profiles: dev1
application:
name: eureka-peer2
server:
port: 10001
eureka:
instance:
hostname: dev1
instance-id: dev1
client:
fetch-registry: false
register-with-eureka: false
service-url:
defaultZone: http://localhost:10000/eureka/,http://localhost:10001/eureka/,http://localhost:10002/eureka/
server:
wait-time-in-ms-when-sync-empty: 0
enable-self-preservation: true
peer-eureka-nodes-update-interval-ms: 10000
---
spring:
profiles: dev2
application:
name: eureka-peer3
server:
port: 10002
eureka:
instance:
hostname: dev2
instance-id: dev2
client:
fetch-registry: false
register-with-eureka: false
service-url:
defaultZone: http://localhost:10000/eureka/,http://localhost:10001/eureka/,http://localhost:10002/eureka/
server:
wait-time-in-ms-when-sync-empty: 0
enable-self-preservation: true
peer-eureka-nodes-update-interval-ms: 10000
---
@Bean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) { // PeerAwareInstanceRegistry P2P形式的注册
// DefaultEurekaServerContext 是 EurekaServerContext 上线文的初始化
return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, registry, peerEurekaNodes, this.applicationInfoManager); // 进入 DefaultEurekaServerContext 里面去瞧一瞧
}
@Inject
public DefaultEurekaServerContext(EurekaServerConfig serverConfig,
ServerCodecs serverCodecs,
PeerAwareInstanceRegistry registry,
PeerEurekaNodes peerEurekaNodes,
ApplicationInfoManager applicationInfoManager) {
this.serverConfig = serverConfig;
this.serverCodecs = serverCodecs;
this.registry = registry;
this.peerEurekaNodes = peerEurekaNodes;
this.applicationInfoManager = applicationInfoManager;
}
// 初始化
@PostConstruct
@Override
public void initialize() {
logger.info("Initializing ...");
peerEurekaNodes.start(); // 定位1
try {
registry.init(peerEurekaNodes);
} catch (Exception e) {
throw new RuntimeException(e);
}
logger.info("Initialized");
}
public void start() {
taskExecutor = Executors.newSingleThreadScheduledExecutor(
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
thread.setDaemon(true);
return thread;
}
}
);
try {
updatePeerEurekaNodes(resolvePeerUrls());
Runnable peersUpdateTask = new Runnable() {
@Override
public void run() {
try {
updatePeerEurekaNodes(resolvePeerUrls()); // 定义了一个 updatePeerEurekaNodes 传入 resolvePeerUrls() 定位2 定位3
} catch (Throwable e) {
logger.error("Cannot update the replica Nodes", e);
}
}
};
taskExecutor.scheduleWithFixedDelay(
peersUpdateTask,
serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
serverConfig.getPeerEurekaNodesUpdateIntervalMs(),
TimeUnit.MILLISECONDS
);
} catch (Exception e) {
throw new IllegalStateException(e);
}
for (PeerEurekaNode node : peerEurekaNodes) {
logger.info("Replica node URL: {}", node.getServiceUrl());
}
}
/**
* Resolve peer URLs.
*
* @return peer URLs with node's own URL filtered out
*/
protected List<String> resolvePeerUrls() {
InstanceInfo myInfo = applicationInfoManager.getInfo(); // 获取InstanceInfo
String zone = InstanceInfo.getZone(clientConfig.getAvailabilityZones(clientConfig.getRegion()), myInfo); // 拿到我们定义的zone(配置文件配置的)
// 同时将拿到的地址放入 list 集合中
List<String> replicaUrls = EndpointUtils
.getDiscoveryServiceUrls(clientConfig, zone, new EndpointUtils.InstanceInfoBasedUrlRandomizer(myInfo));
int idx = 0;
// 在此处进行了一波迭代
while (idx < replicaUrls.size()) {
if (isThisMyUrl(replicaUrls.get(idx))) {
replicaUrls.remove(idx);
} else {
idx++;
}
}
return replicaUrls;
}
/**
* Given new set of replica URLs, destroy {@link PeerEurekaNode}s no longer available, and
* create new ones.
*
* @param newPeerUrls peer node URLs; this collection should have local node's URL filtered out
*/
protected void updatePeerEurekaNodes(List<String> newPeerUrls) {
if (newPeerUrls.isEmpty()) {
logger.warn("The replica size seems to be empty. Check the route 53 DNS Registry");
return;
}
Set<String> toShutdown = new HashSet<>(peerEurekaNodeUrls);
toShutdown.removeAll(newPeerUrls);
Set<String> toAdd = new HashSet<>(newPeerUrls);
toAdd.removeAll(peerEurekaNodeUrls);
if (toShutdown.isEmpty() && toAdd.isEmpty()) { // No change
return;
}
// Remove peers no long available
List<PeerEurekaNode> newNodeList = new ArrayList<>(peerEurekaNodes);
if (!toShutdown.isEmpty()) {
logger.info("Removing no longer available peer nodes {}", toShutdown);
int i = 0;
while (i < newNodeList.size()) {
PeerEurekaNode eurekaNode = newNodeList.get(i);
if (toShutdown.contains(eurekaNode.getServiceUrl())) {
newNodeList.remove(i);
eurekaNode.shutDown();
} else {
i++;
}
}
}
// Add new peers
if (!toAdd.isEmpty()) {
logger.info("Adding new peer nodes {}", toAdd);
for (String peerUrl : toAdd) {
newNodeList.add(createPeerEurekaNode(peerUrl));
}
}
this.peerEurekaNodes = newNodeList;
this.peerEurekaNodeUrls = new HashSet<>(newPeerUrls);
}
@Override
public void register(final InstanceInfo info, final boolean isReplication) {
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方法 定义的事件是一个注册时间 定位1
// P2P 防止重复传播是通过 isReplication 来实现的
replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);
}
/**
* Replicates all eureka actions to peer eureka nodes except for replication
* traffic to this node.
*
*/
private void replicateToPeers(Action action, String appName, String id,
InstanceInfo info /* optional */,
InstanceStatus newStatus /* optional */, boolean isReplication) {
Stopwatch tracer = action.getTimer().start();
try {
if (isReplication) {
numberOfReplicationsLastMin.increment();
}
// If it is a replication already, do not replicate again as this will create a poison replication
if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
return;
}
for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
// If the url represents this host, do not replicate to yourself.
if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
continue;
}
// 将实例一个又一个的同步给客户端
replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node); // 定位2
}
} finally {
tracer.stop();
}
}
/**
* Replicates all instance changes to peer eureka nodes except for
* replication traffic to this node.
*
*/
private void replicateInstanceActionsToPeers(Action action, String appName,
String id, InstanceInfo info, InstanceStatus newStatus,
PeerEurekaNode node) {
try {
InstanceInfo infoFromRegistry = null;
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) {
logger.error("Cannot replicate information to {} for action {}", node.getServiceUrl(), action.name(), t);
}
}
public void start() { //
(new Thread(new Runnable() {
public void run() {
try {
// eurekaServerBootstrap.contextInitialized 这个方法 定位 2 contextInitialized
EurekaServerInitializerConfiguration.this.eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
EurekaServerInitializerConfiguration.log.info("Started Eureka Server");
EurekaServerInitializerConfiguration.this.publish(new EurekaRegistryAvailableEvent(EurekaServerInitializerConfiguration.this.getEurekaServerConfig()));
EurekaServerInitializerConfiguration.this.running = true;
EurekaServerInitializerConfiguration.this.publish(new EurekaServerStartedEvent(EurekaServerInitializerConfiguration.this.getEurekaServerConfig()));
} catch (Exception var2) {
EurekaServerInitializerConfiguration.log.error("Could not initialize Eureka servlet context", var2);
}
}
})).start();
}
public void contextInitialized(ServletContext context) {
try {
this.initEurekaEnvironment(); // 初始化环境
this.initEurekaServerContext(); // 初始化上下文 定位 3
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); // 与注册信息相关 applicationInfoManager 注册信息 registryCount 注册次数
EurekaMonitors.registerAllStats(); 定位4
}
@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(); // 定位5 updateRenewsPerMinThreshold
logger.info("Got {} instances from neighboring DS node", count);
logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
this.startupTime = System.currentTimeMillis();
if (count > 0) {
this.peerInstancesTransferEmptyOnStartup = false;
}
DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
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); // applicationInfoManager 放置实例对象的位置 在此将实例对象进行一次UP操作
super.postInit(); // 定位 6
}
protected void updateRenewsPerMinThreshold() {
// 给全局变量赋值 numberOfRenewsPerMinThreshold 预置变量 getExpectedClientRenewalIntervalSeconds 发送心跳的间隔
// 计算1分钟内应该发送几次心跳
this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfClientsSendingRenews
* (60.0 / serverConfig.getExpectedClientRenewalIntervalSeconds())
* serverConfig.getRenewalPercentThreshold()); // 默认的时 0.85D
}
protected void postInit() {
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
evictionTaskRef.set(new EvictionTask()); // 在这边又创建了一个新的线程 定位7
evictionTimer.schedule(evictionTaskRef.get(), // 这边定义好了一个定时任务
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}
/* visible for testing */ class EvictionTask extends TimerTask {
private final AtomicLong lastExecutionNanosRef = new AtomicLong(0l);
@Override
public void run() {
try {
long compensationTimeMs = getCompensationTimeMs();
logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
evict(compensationTimeMs); // 定位8
} catch (Throwable e) {
logger.error("Could not run the evict task", e);
}
}
public void evict(long additionalLeaseMs) {
logger.debug("Running the evict task");
if (!isLeaseExpirationEnabled()) { // 判断是否开启自我保护机制 一旦开启自我保护机制,注册中心永远不将服务剔除
logger.debug("DS: lease expiration is currently disabled.");
return;
}
// We collect first all expired items, to evict them in random order. For large eviction sets,
// if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,
// the impact should be evenly distributed across all applications.
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);
}
}
}
}
// To compensate for GC pauses or drifting local time, we need to use current registry size as a base for
// triggering self-preservation. Without that we would wipe out full registry.
int registrySize = (int) getLocalRegistrySize();
int registrySizeThreshold = (int) (registrySize * serverConfig.getRenewalPercentThreshold());
int evictionLimit = registrySize - registrySizeThreshold;
int toEvict = Math.min(expiredLeases.size(), evictionLimit);
if (toEvict > 0) {
logger.info("Evicting {} items (expired={}, evictionLimit={})", toEvict, expiredLeases.size(), evictionLimit);
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); // 定位9 随机剔除服务
}
}
}
protected boolean internalCancel(String appName, String id, boolean isReplication) {
try {
read.lock();
CANCEL.increment(isReplication);
Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
Lease<InstanceInfo> leaseToCancel = null;
if (gMap != null) {
leaseToCancel = gMap.remove(id);
}
synchronized (recentCanceledQueue) {
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);
return true;
}
} finally {
read.unlock();
}
}