springcloud----eureka原始碼探索
註冊中心理解
註冊中心到底是什麼?從學習微服務的時候就一直在想,又有什麼作用?從開始的單機伺服器,到多臺伺服器的協同呼叫。漸漸地明白了註冊中心的重要性。
在小型專案中,用單臺伺服器完全可以滿足需求;再大一點的專案,單臺伺服器可以通過擴充套件伺服器的配置滿足需求,直至到伺服器的瓶頸;當超過了單臺伺服器的瓶頸後,可以使用多個伺服器的請求來進行協同。
因為spring-cloud使用的是http請求來進行多伺服器的協同合作,這裡也只用http來進行說明。當我們通過http請求進行多個伺服器來進行合作的時候,可能出現被請求的伺服器宕機爾無響應,可能出現被請求的伺服器伺服器執行緒滿了無法處理請求等,這時候就體現到了註冊中心的重要性了。
eureka作為註冊中心之一,只是將將每臺伺服器的資訊的存入到eureka-server中,通過eureka-client來連線eureka-server,其中所有的請求都是通過http來完成。當需要遠端呼叫的時候,直接獲取client中存放的需要呼叫的伺服器地址,實現遠端呼叫即可。
eureka配置啟動解析
@EnableEurekaServer //次註解只是向容器中注入一個Bean //EurekaServerMarkerConfiguration類中的 @Bean public EurekaServerMarkerConfiguration.Marker eurekaServerMarkerBean() { return new EurekaServerMarkerConfiguration.Marker(); } //在通過eureka-server的jar包中的spring.factory檔案的配置 //org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ //org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration //啟動eureka伺服器。 //EurekaServerAutoConfiguration中, //配置了http的入口controlller @Bean @ConditionalOnProperty( prefix = "eureka.dashboard", name = {"enabled"}, matchIfMissing = true ) public EurekaController eurekaController() { return new EurekaController(this.applicationInfoManager); } //配置了例項註冊器,內部維護的就是eureka-client的資訊 @Bean public PeerAwareInstanceRegistry peerAwareInstanceRegistry(ServerCodecs serverCodecs) { this.eurekaClient.getApplications(); return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.eurekaClient, this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(), this.instanceRegistryProperties.getDefaultOpenForTrafficCount()); } //jersy應用,eureka-client可以通過http請求,修改eureka維護的註冊資訊。 //jersy也是一個web框架,專注於restFull,eureka作為註冊中心將會使用到兩個web框架 //一個webMvc提供dashboar,一個jersy提供了以http請求方式維護註冊資訊 @Bean public Application jerseyApplication(Environment environment, ResourceLoader resourceLoader) { ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false, environment); provider.addIncludeFilter(new AnnotationTypeFilter(Path.class)); provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class)); Set<Class<?>> classes = new HashSet(); String[] var5 = EUREKA_PACKAGES; int var6 = var5.length; for(int var7 = 0; var7 < var6; ++var7) { String basePackage = var5[var7]; Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage); Iterator var10 = beans.iterator(); while(var10.hasNext()) { BeanDefinition bd = (BeanDefinition)var10.next(); Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(), resourceLoader.getClassLoader()); classes.add(cls); } } Map<String, Object> propsAndFeatures = new HashMap(); propsAndFeatures.put("com.sun.jersey.config.property.WebPageContentRegex", "/eureka/(fonts|images|css|js)/.*"); DefaultResourceConfig rc = new DefaultResourceConfig(classes); rc.setPropertiesAndFeatures(propsAndFeatures); return rc; } //等等還有許多bean,暫不一一說明
dashboard控制檯
我們通過訪問dashboard面板是通過兩個請求完成的,一種是EurekaController 提供的請求為“/”的資源,還有一個就是jersy提供的資料"eureka/status"的資源提供資料,返回dashboard面板頁面。我們可以自己在EurekaController的資源和ApplicationResource的eureka/status中打斷點,檢視返回的結果來進行驗證,不得不說設計的很漂亮,需要找半天。有興趣的人可以自己看看jersy開了多少個web介面,自己請求看看分別有哪些東西。jersy的資源都在eureka-core的com.netflix.eureka.resources包中。
註冊例項
例項註冊是jersy提供的一個比較重要的介面,在applicationResource中,為一個post請求,在這個其你去裡面可以看見eureka-client傳給伺服器有哪些資訊,通過這些資訊,eureka可以提供哪些功能。
@POST
@Consumes({"application/json", "application/xml"})
public Response addInstance(InstanceInfo info, @HeaderParam("x-netflix-discovery-replication") String isReplication) {
logger.debug("Registering instance {} (replication={})", info.getId(), isReplication);
if (this.isBlank(info.getId())) {
return Response.status(400).entity("Missing instanceId").build();
} else if (this.isBlank(info.getHostName())) {
return Response.status(400).entity("Missing hostname").build();
} else if (this.isBlank(info.getIPAddr())) {
return Response.status(400).entity("Missing ip address").build();
} else if (this.isBlank(info.getAppName())) {
return Response.status(400).entity("Missing appName").build();
} else if (!this.appName.equals(info.getAppName())) {
return Response.status(400).entity("Mismatched appName, expecting " + this.appName + " but was " + info.getAppName()).build();
} else if (info.getDataCenterInfo() == null) {
return Response.status(400).entity("Missing dataCenterInfo").build();
} else if (info.getDataCenterInfo().getName() == null) {
return Response.status(400).entity("Missing dataCenterInfo Name").build();
} else {
DataCenterInfo dataCenterInfo = info.getDataCenterInfo();
if (dataCenterInfo instanceof UniqueIdentifier) {
String dataCenterInfoId = ((UniqueIdentifier)dataCenterInfo).getId();
if (this.isBlank(dataCenterInfoId)) {
boolean experimental = "true".equalsIgnoreCase(this.serverConfig.getExperimental("registration.validation.dataCenterInfoId"));
if (experimental) {
String entity = "DataCenterInfo of type " + dataCenterInfo.getClass() + " must contain a valid id";
return Response.status(400).entity(entity).build();
}
if (dataCenterInfo instanceof AmazonInfo) {
AmazonInfo amazonInfo = (AmazonInfo)dataCenterInfo;
String effectiveId = amazonInfo.get(MetaDataKey.instanceId);
if (effectiveId == null) {
amazonInfo.getMetadata().put(MetaDataKey.instanceId.getName(), info.getId());
}
} else {
logger.warn("Registering DataCenterInfo of type {} without an appropriate id", dataCenterInfo.getClass());
}
}
}
this.registry.register(info, "true".equals(isReplication));
return Response.status(204).build();
}
}
//abstractInstanceRegistry類中註冊功能
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
this.read.lock();
Map<String, Lease<InstanceInfo>> gMap = (Map)this.registry.get(registrant.getAppName());
EurekaMonitors.REGISTER.increment(isReplication);
if (gMap == null) {
ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap();
gMap = (Map)this.registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
gMap = gNewMap;
}
}
Lease<InstanceInfo> existingLease = (Lease)((Map)gMap).get(registrant.getId());
if (existingLease != null && existingLease.getHolder() != null) {
Long existingLastDirtyTimestamp = ((InstanceInfo)existingLease.getHolder()).getLastDirtyTimestamp();
Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
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 = (InstanceInfo)existingLease.getHolder();
}
} else {
synchronized(this.lock) {
if (this.expectedNumberOfClientsSendingRenews > 0) {
++this.expectedNumberOfClientsSendingRenews;
this.updateRenewsPerMinThreshold();
}
}
logger.debug("No previous lease information found; it is new registration");
}
Lease<InstanceInfo> lease = new Lease(registrant, leaseDuration);
if (existingLease != null) {
lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
}
((Map)gMap).put(registrant.getId(), lease);
synchronized(this.recentRegisteredQueue) {
this.recentRegisteredQueue.add(new Pair(System.currentTimeMillis(), registrant.getAppName() + "(" + registrant.getId() + ")"));
}
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 (!this.overriddenInstanceStatusMap.containsKey(registrant.getId())) {
logger.info("Not found overridden id {} and hence adding it", registrant.getId());
this.overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
}
}
InstanceStatus overriddenStatusFromMap = (InstanceStatus)this.overriddenInstanceStatusMap.get(registrant.getId());
if (overriddenStatusFromMap != null) {
logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
registrant.setOverriddenStatus(overriddenStatusFromMap);
}
InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(registrant, existingLease, isReplication);
registrant.setStatusWithoutDirty(overriddenInstanceStatus);
if (InstanceStatus.UP.equals(registrant.getStatus())) {
lease.serviceUp();
}
registrant.setActionType(ActionType.ADDED);
this.recentlyChangedQueue.add(new AbstractInstanceRegistry.RecentlyChangedItem(lease));
registrant.setLastUpdatedTimestamp();
this.invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
logger.info("Registered instance {}/{} with status {} (replication={})", new Object[]{registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication});
} finally {
this.read.unlock();
}
}
這個裡面實際就是維護了一個Map集合,通過http請求,將需要客戶端的資訊放入到Map中,如此簡單。還記得開始學的時候,就有人說過eureka的介面很醜,git中曾有大佬自己做了個eureka頁面。對比下nacos中的手動剔除實力等功能,想想只用提出一個map例項即可。
例項自動剔除
eureka例項自動剔除,至於在那個類中定義的定時任務,一時上我也忘了。但是定時任務是30s呼叫一次這個方法,呼叫的方法在PeerAwareInstanceRegistryImpl類中的
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
this.expectedNumberOfClientsSendingRenews = count;
this.updateRenewsPerMinThreshold();
logger.info("Got {} instances from neighboring DS node", count);
logger.info("Renew threshold is: {}", this.numberOfRenewsPerMinThreshold);
this.startupTime = System.currentTimeMillis();
if (count > 0) {
this.peerInstancesTransferEmptyOnStartup = false;
}
Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName();
boolean isAws = Name.Amazon == selfName;
if (isAws && this.serverConfig.shouldPrimeAwsReplicaConnections()) {
logger.info("Priming AWS connections for all replicas..");
this.primeAwsReplicas(applicationInfoManager);
}
logger.info("Changing status to UP");
applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
super.postInit();
}
學習使我快樂,希望對你也有所幫助,一起將這份快樂傳遞。