在前面的文章介紹了,如何使用服務註冊發現組件: Eureka,並給出使用示例。本文在此基礎上,將會講解 Eureka 客戶端實現的內幕,結合源碼深刻實現的細節,知其因此然。客戶端須要重點關注如下幾點:html
本文摘錄於筆者出版的書籍 《Spring Cloud 微服務架構進階》java
在Finchley版本的SpringCloud中,不須要添加任何的額外的註解就能夠登記爲Eureka Client,只須要在pom文件中添加spring-cloud-starter-netflix-eureka-client
的依賴。spring
爲了跟蹤Eureka的運行機制,讀者能夠打開SpringBoot的Debug模式來查看更多的輸出日誌:bootstrap
logging:
level:
org.springframework: DEBUG
複製代碼
查看spring-cloud-netflix-eureka-client
的src/main/resource.META-INF/spring.factories
,查看Eureka Client有哪些自動配置類:設計模式
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaDiscoveryClientConfigServiceBootstrapConfiguration
複製代碼
排除掉與配置中心相關的自動配置類,從中能夠找到三個與Eureka Client密切相關的自動配置類:緩存
下面將對這些類進行分析,看看一個正常的Eureka Client須要作哪一些初始化配置。服務器
Eureke Client的自動配置類,負責了Eureka Client中關鍵的bean的配置和初始化,如下是其內比較重要的bean的介紹與做用。微信
EurekaClientConfig架構
提供了Eureka Client註冊到Eureka Server所須要的配置信息,SpringCloud爲其提供了一個默認配置類的EurekaClientConfigBean
,能夠在配置文件中經過前綴eureka.client
+屬性名進行覆蓋。app
ApplicationInfoManager
該類管理了服務實例的信息類InstanceInfo
,其內包括Eureka Server上的註冊表所須要的信息,表明了每一個Eureka Client提交到註冊中心的數據,用以供服務發現。同時管理了實例的配置信息EurekaInstanceConfig
,SpringCloud提供了一個EurekaInstanceConfigBean
的配置類進行默認配置,也能夠在配置文件application.yml
中經過eureka.instance
+屬性名進行自定義配置。
EurekaInstanceConfigBean
繼承了EurekaInstanceConfig
接口,是Eureka Client註冊到服務器上須要提交的關於服務實例自身的相關信息,主要用於服務發現:
一般這些信息在配置文件中的eureka.instance
前綴下進行設置,SpringCloud經過EurekaInstanceConfigBean
配置類提供了相關的默認配置。如下是一些比較關鍵的屬性,這些信息都將註冊到註冊中心上。
public class EurekaInstanceConfigBean implements CloudEurekaInstanceConfig, EnvironmentAware {
// 服務實例的應用名
private String appname;
// 服務實例的Id,一般和appname共同惟一標記一個服務實例
private String instanceId;
// 自定義添加的元數據,由用戶使用以適配擴展業務需求
private Map<String, String> metadataMap;
// 若是服務實例部署在AWS上,該類將持有服務實例部署所在的數據中心的準確信息
private DataCenterInfo dataCenterInfo;
// 服務實例的Ip地址
private String ipAddress;
// 服務實例主頁地址
private String homePageUrl;
// 服務實例健康檢查地址
private String healthCheckUrlPath;
// 服務實例的狀態地址
private String statusPageUrlPath
.....
}
複製代碼
DiscoveryClient
這是SpringCloud定義的用來服務發現的頂級接口,在Netflix Eureka或者consul都有相應的具體實現類,提供的方法以下:
public interface DiscoveryClient {
// 獲取實現類的描述
String description();
// 經過服務Id獲取服務實例的信息
List<ServiceInstance> getInstances(String serviceId);
// 獲取全部的服務實例的Id
List<String> getServices();
}
複製代碼
其在Eureka方面的實現的相關的類結構圖:
EurekaDiscoveryClient
繼承了DiscoveryClient
,可是經過查看EurekaDiscoveryClient
中的代碼,會發現它是經過組合類EurekaClient
實現接口的功能,以下的getInstance
接口:
@Override
public List<ServiceInstance> getInstances(String serviceId) {
List<InstanceInfo> infos = this.eurekaClient.getInstancesByVipAddress(serviceId,false);
List<ServiceInstance> instances = new ArrayList<>();
for (InstanceInfo info : infos) {
instances.add(new EurekaServiceInstance(info));
}
return instances;
}
複製代碼
EurekaClient
來自於com.netflix.discovery
包中,其默認實現爲com.netflix.discovery.DiscoveryClient
,這屬於eureka-client的源代碼,它提供了Eureka Client註冊到Server上、續租,下線以及獲取Server中註冊表信息等諸多關鍵功能。SpringCloud經過組合方式調用了Eureka中的的服務發現方法,關於EurekaClient
的詳細代碼分析將放在客戶端核心代碼中介紹。爲了適配spring-cloud
,spring提供了一個CloudEurekaClient
繼承了com.netflix.discovery.DiscoveryClient
,同時覆蓋了onCacheRefreshed
防止在spring-boot
還沒初始化時調用該接口出現NullPointException
。
上述的幾個配置類之間的關係很是緊密,數據之間存在必定的耦合,因此下面介紹一下它們之間的關係
首先是EurekaInstanceConfig
,代碼位於EurekaClientAutoConfiguration
@Bean
@ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils, ManagementMetadataProvider managementMetadataProvider) {
// 從配置文件中讀取屬性
String hostname = getProperty("eureka.instance.hostname");
boolean preferIpAddress = Boolean.parseBoolean(getProperty("eureka.instance.prefer-ip-address"));
String ipAddress = getProperty("eureka.instance.ipAddress");
boolean isSecurePortEnabled = Boolean.parseBoolean(getProperty("eureka.instance.secure-port-enabled"));
String serverContextPath = env.getProperty("server.context-path", "/");
int serverPort = Integer.valueOf(env.getProperty("server.port", env.getProperty("port", "8080")));
Integer managementPort = env.getProperty("management.server.port", Integer.class);// nullable. should be wrapped into optional
String managementContextPath = env.getProperty("management.server.context-path");// nullable. should be wrapped into optional
Integer jmxPort = env.getProperty("com.sun.management.jmxremote.port", Integer.class);//nullable
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
// 設置非空屬性
instance.setNonSecurePort(serverPort);
instance.setInstanceId(getDefaultInstanceId(env));
instance.setPreferIpAddress(preferIpAddress);
if (StringUtils.hasText(ipAddress)) {
instance.setIpAddress(ipAddress);
}
if(isSecurePortEnabled) {
instance.setSecurePort(serverPort);
}
if (StringUtils.hasText(hostname)) {
instance.setHostname(hostname);
}
String statusPageUrlPath = getProperty("eureka.instance.status-page-url-path");
String healthCheckUrlPath = getProperty("eureka.instance.health-check-url-path");
if (StringUtils.hasText(statusPageUrlPath)) {
instance.setStatusPageUrlPath(statusPageUrlPath);
}
if (StringUtils.hasText(healthCheckUrlPath)) {
instance.setHealthCheckUrlPath(healthCheckUrlPath);
}
ManagementMetadata metadata = managementMetadataProvider.get(instance, serverPort, serverContextPath, managementContextPath, managementPort);
.....
return instance;
}
複製代碼
從上面的代碼能夠發現,EurekaInstanceConfig
的屬性主要經過EurekaInstanceConfigBean
的實現提供,同時也會嘗試從配置文件中讀取一部分配置,在例如eureka.instance.hostname
、eureka.instance.status-page-url-path
、eureka.instance.health-check-url-path
等等,它表明了應用實例的應該具有的信息,而後這部分信息會被封裝成InstanceInfo
,被註冊到Eureka Server中。
InstanceInfo
是經過InstanceInfoFactory
(org.springframework.cloud.netflix.eureka)封裝EurekaInstanceConfig
中的屬性建立的,其中InstanceInfo
的屬性基本是volatile
,保證了內存中的該類信息的一致性和原子性。
代碼位於InstanceInfoFactory
。
public InstanceInfo create(EurekaInstanceConfig config) {
LeaseInfo.Builder leaseInfoBuilder = LeaseInfo.Builder.newBuilder()
.setRenewalIntervalInSecs(config.getLeaseRenewalIntervalInSeconds())
.setDurationInSecs(config.getLeaseExpirationDurationInSeconds());
// 建立服務實例的信息用來註冊到eureka server上
InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();
String namespace = config.getNamespace();
if (!namespace.endsWith(".")) {
namespace = namespace + ".";
}
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(InstanceInfo.PortType.UNSECURE,
config.isNonSecurePortEnabled())
.setSecurePort(config.getSecurePort())
.enablePort(InstanceInfo.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());
....
InstanceInfo instanceInfo = builder.build();
instanceInfo.setLeaseInfo(leaseInfoBuilder.build());
return instanceInfo;
}
複製代碼
接着是ApplicationInfoManager
,代碼位於EurekaClientAutoConfiguration
。
@Bean
@ConditionalOnMissingBean(value = ApplicationInfoManager.class, search = SearchStrategy.CURRENT)
public ApplicationInfoManager eurekaApplicationInfoManager(EurekaInstanceConfig config) {
InstanceInfo instanceInfo = new InstanceInfoFactory().create(config);
return new ApplicationInfoManager(config, instanceInfo);
}
複製代碼
經過組合EurekaInstanceConfig
和InstanceInfo
建立了ApplicationInfoManager
,屬於應用信息管理器。
@Bean
@ConditionalOnMissingBean(value = EurekaClientConfig.class, search = SearchStrategy.CURRENT)
public EurekaClientConfigBean eurekaClientConfigBean() {
EurekaClientConfigBean client = new EurekaClientConfigBean();
if ("bootstrap".equals(propertyResolver.getProperty("spring.config.name"))) {
// We don't register during bootstrap by default, but there will be another
// chance later.
client.setRegisterWithEureka(false);
}
return client;
}
複製代碼
前面有講到,EurekaClientConfig
持有Eureka Client與Eureka Server進行交互的關鍵性配置信息,相似serviceUrl
(Server地址),EurekaClient
經過EurekaClientConfig
中配置信息與Eureka Server進行服務註冊與發現。
最後是EurekaClient
,經過ApplicationInfoManager
與EurekaClientConfig
組合建立,即EurekaClient
同時持有了client的服務實例信息用於服務發現,與Eureka Server註冊的配置用於服務註冊。
@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);
}
複製代碼
總體的類結構以下
EurekaRegistration、EurekaServiceRegistry、EurekaAutoServiceRegistration這是SpringCloud適配Eureka所加的將服務註冊到服務註冊中心的相關類,先來看一下相關的類結構
Registration
繼承了
ServiceInstance
,表明了一個被註冊到服務發現系統的一個服務實例,必須具有的信息如hostname和port等,
Registration
是
ServiceInstance
的一個門面類
public interface ServiceInstance {
//獲取服務實例的serviceId
String getServiceId();
//獲取服務實例的hostname
String getHost();
//獲取服務實例的端口號
int getPort();
boolean isSecure();
//獲取服務實例的uri地址
URI getUri();
//獲取服務實例的元數據key-value對
Map<String, String> getMetadata();
}
複製代碼
對應Eureka,EurekaRegistration
實現了Registration
,查看其中的代碼,只是照搬了EurekaInstanceConfigBean
中的配置信息,同時注入了EurekaClient
,爲Eureka Client的服務註冊提供實現。
@Bean
@ConditionalOnBean(AutoServiceRegistrationProperties.class)
@ConditionalOnProperty(value = "spring.cloud.service-registry.auto-registration.enabled", matchIfMissing = true)
public EurekaRegistration eurekaRegistration(EurekaClient eurekaClient, CloudEurekaInstanceConfig instanceConfig, ApplicationInfoManager applicationInfoManager, ObjectProvider<HealthCheckHandler> healthCheckHandler) {
return EurekaRegistration.builder(instanceConfig)
.with(applicationInfoManager)
.with(eurekaClient)
.with(healthCheckHandler)
.build();
}
複製代碼
ServiceRegistry
裏面提供了將服務實例註冊到服務註冊中心的相關接口:
public interface ServiceRegistry<R extends Registration> {
//註冊服務實例,registration當中一般有關於服務實例的信息,例如hostname和port
void register(R registration);
//註銷服務實例
void deregister(R registration);
//關閉ServiceRegistry,一般在服務關閉的時候被調用
void close();
//設置服務實例的狀態
void setStatus(R registration, String status);
//獲取服務實例的狀態
<T> T getStatus(R registration);
}
複製代碼
其中在EurekaServiceRegistry
的註冊和下線的實現以下:
@Override
public void register(EurekaRegistration reg) {
// 初始化EurekaRegistration中的EurekaClient,若是爲null
maybeInitializeClient(reg);
// 修改服務的狀態爲UP
reg.getApplicationInfoManager()
.setInstanceStatus(reg.getInstanceConfig().getInitialStatus());
// 設置健康檢查
reg.getHealthCheckHandler().ifAvailable(healthCheckHandler ->
reg.getEurekaClient().registerHealthCheck(healthCheckHandler));
}
private void maybeInitializeClient(EurekaRegistration reg) {
reg.getApplicationInfoManager().getInfo();
reg.getEurekaClient().getApplications();
}
@Override
public void deregister(EurekaRegistration reg) {
if (reg.getApplicationInfoManager().getInfo() != null) {
// 設置服務的狀態爲DOWN
reg.getApplicationInfoManager().setInstanceStatus(InstanceInfo.InstanceStatus.DOWN);
}
}
複製代碼
在上面的代碼中能夠發現,對服務的註冊和下線僅僅是修改了服務當前的狀態,其實在EurekaClient
的接口實現類中有專門對InstanceStatus
狀態修改的監聽,當服務實例的信息改變時就會觸發不一樣的事件進行處理。
EurekaAutoServiceRegistration
,顧名思義,就是服務實例的自動註冊,由前面的類圖可知,該類繼承了SmartLifecycle
的接口,這是org.springframework.context
包中的相關類,說明EurekaAutoServiceRegistration
類受到了Spring的生命週期的管理。
...
@EventListener(ServletWebServerInitializedEvent.class)
public void onApplicationEvent(ServletWebServerInitializedEvent event) {
int localPort = event.getWebServer().getPort();
if (this.port.get() == 0) {
log.info("Updating port to " + localPort);
this.port.compareAndSet(0, localPort);
start();
}
}
@EventListener(ContextClosedEvent.class)
public void onApplicationEvent(ContextClosedEvent event) {
if( event.getApplicationContext() == context ) {
stop();
}
}
複製代碼
在上述代碼中,該類監聽了ServletWebServerInitializedEvent
和ContextClosedEvent
兩個事件,即在應用初始化階段調用start()
和應用上下文關閉階段調用stop()
,其實就是在應用啓動和關閉時分別進行服務的註冊和下線的自動操做。
EurekaAutoServiceRegistration
的服務註冊和下線是直接調用了EurekaServiceRegistry中的方法。
@Override
public void start() {
// only set the port if the nonSecurePort or securePort is 0 and this.port != 0
if (this.port.get() != 0) {
if (this.registration.getNonSecurePort() == 0) {
this.registration.setNonSecurePort(this.port.get());
}
if (this.registration.getSecurePort() == 0 && this.registration.isSecure()) {
this.registration.setSecurePort(this.port.get());
}
}
if (!this.running.get() && this.registration.getNonSecurePort() > 0) {
// 註冊服務
this.serviceRegistry.register(this.registration);
this.context.publishEvent(
new InstanceRegisteredEvent<>(this, this.registration.getInstanceConfig()));
this.running.set(true);
}
}
@Override
public void stop() {
// 服務下線
this.serviceRegistry.deregister(this.registration);
this.running.set(false);
}
複製代碼
EurekaDiscoveryClientConfiguration
EurekaDiscoveryClientConfiguration
只作了兩件事,監聽了RefreshScopeRefreshedEvent
事件以及注入EurekaHealthCheckHandler
接口的實現類。
RefreshScopeRefreshedEvent
事件通常在spring管理的bean被刷新的時候被拋出,此時說明應用環境的配置和參數有可能發生變化,因而須要從新註冊服務,防止註冊中心的服務實例信息與本地信息不一致。
@EventListener(RefreshScopeRefreshedEvent.class)
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
// 保證了一個刷新事件發生後client的從新註冊
if(eurekaClient != null) {
eurekaClient.getApplications();
}
if (autoRegistration != null) {
// 從新註冊防止本地信息與註冊表中的信息不一致
this.autoRegistration.stop();
this.autoRegistration.start();
}
}
複製代碼
RibbonEurekaAutoConfiguration
Eureka中配置負載均衡的配置類,具體關於Ribbon的內容將在其餘章節進行講解,這裏就略過
主要的代碼位於eureka-client
中,項目的module爲eureka-client
,版本爲v1.8.7,這是Finchley版本的Spring Cloud所依賴的eureka版本
包結構以下
簡要的包介紹:
com.netflix.appinfo
: 主要是關於eureka-client的配置信息類,如上面說起的EurekaInstanceConfig
,InstanceInfo
,其中也包含了相似AmazonInfo
、DataCenterInfo
等與AWS中的架構適配密切相關的接口,在此不作詳解的介紹,有興趣讀者能夠自行去了解。com.netflix.discovery
: 主要實現Eureka-Client的服務發現和服務註冊功能。
com.netflix.discovery.converters
: 主要解決Eureka服務之間的數據傳輸的編碼與解碼,支持JSON、XML等格式。com.netflix.discovery.guice
: Google
的guice
依賴注入配置包,相似Spring
的configuration。com.netflix.discovery.provider
: 提供的Jersey中請求與響應的序列化與反序列化實現,默認實現是DefaultJerseyProvider
。com.netflix.discovery.providers
: 目前只有DefaultEurekaClientConfigProvider
,提供EurekaClientConfig
工廠生成方法。com.netflix.discovery.shared
: Eureka Client與Eureka Server共享重用的方法。
com.netflix.discovery.shared.dns
: DNS解析器。com.netflix.discovery.shared.resolver
: Euraka Endpoint解析器,EurekaEndpoint
指的是服務端點,通常指的是Eureka Server的訪問地址,默認實現爲DefaultEndpoint
,ClusterResolver
將配置的Eureka Server地址解析爲EurekaEndpoint
,這裏面用到了委託者設計模式,類圖以下,有很明顯的請求委託的處理過程。com.netflix.discovery.shared.transport
: Eureka Client與Eureka Server之間進行HTTP通訊的客戶端以及通訊的request和response的封裝類。DiscoveryClient
能夠說是Eureka Client的核心類,負責了與Eureka Server交互的關鍵邏輯,具有了如下的職能:
DiscoverClient
的核心類圖以下:
DiscoveryClient
的頂層接口爲LookupService
,主要的目的是爲了發現活躍中的服務實例。
public interface LookupService<T> {
//根據服務實例註冊的appName來獲取,獲取一個封裝有相同appName的服務實例信息的容器
Application getApplication(String appName);
//返回當前註冊的全部的服務實例信息
Applications getApplications();
//根據服務實例的id獲取
List<InstanceInfo> getInstancesById(String id);
//獲取下一個可能的Eureka Server來處理當前對註冊表信息的處理,通常是經過循環的方式來獲取下一個Server
InstanceInfo getNextServerFromEureka(String virtualHostname, boolean secure);
}
複製代碼
Application
中持有一個特定應用的多個實例的列表,能夠理解成同一個服務的集羣信息,它們都掛在同一個服務名appName下,InstanceInfo
表明一個服務實例,部分代碼以下:
public class Application {
private static Random shuffleRandom = new Random();
//服務名
private String name;
@XStreamOmitField
private volatile boolean isDirty = false;
@XStreamImplicit
private final Set<InstanceInfo> instances;
private final AtomicReference<List<InstanceInfo>> shuffledInstances;
private final Map<String, InstanceInfo> instancesMap;
.....
}
複製代碼
爲了保證原子性操做以及數據的惟一性,防止髒數據,Application
中對InstanceInfo
的操做都是同步操做,感覺一下Application.addInstance
方法。
public void addInstance(InstanceInfo i) {
instancesMap.put(i.getId(), i);
synchronized (instances) {
instances.remove(i);
instances.add(i);
isDirty = true;
}
}
複製代碼
經過同步代碼塊,保證每次只有有一個線程對instances進行修改,同時注意instancesMap採用的是ConcurrentHashMap
實現,保證了原子性的操做,因此不須要經過同步代碼塊進行控制。
Applications
中表明的是Eureka Server中已註冊的服務實例的集合信息,主要是對Application
的封裝,裏面的操做大多也是的同步操做。
EurekaClient
繼承了LookupService
接口,爲DiscoveryClient
提供了一個上層的接口,目的是試圖方便從eureka 1.x 到eureka 2.x 的過渡,這說明EurekaClient
這個接口屬於比較穩定的接口,即便在下一大階段也會被依舊保留。
EurekaCient
在LookupService
的基礎上擴充了更多的接口,提供了更豐富的獲取服務實例的功能,主要有:
InstanceInfo
,例如根據region,Eureka Server地址等獲取;除去查詢相關的接口,關注EurekaClient
中的如下兩個接口:
// 爲Eureka Client註冊健康檢查處理器
// 一旦註冊,客戶端將經過調用新註冊的健康檢查處理器來對註冊中instanceInfo
// 進行一個按需更新,隨後按照eurekaclientconfig.getinstanceinforeplicationintervalseconds()
// 中配置的指定時間調用HealthCheckHandler
public void registerHealthCheck(HealthCheckHandler healthCheckHandler);
// 爲eureka client註冊一個EurekaEventListener(事件監聽器)
// 一旦註冊,當eureka client的內部狀態發生改變的時候,將會調用EurekaEventListener.onEvent()
// 觸發必定的事件。能夠經過這種方式監聽client的更新而非經過輪詢的方式詢問client
public void registerEventListener(EurekaEventListener eventListener);
複製代碼
Eureka Server通常經過心跳(heartbeats)來識別一個實例的狀態。Eureka Client中存在一個定時任務定時經過HealthCheckHandler
檢測當前client的狀態,若是client的狀態發生改變,將會觸發新的註冊事件,同步Eureka Server的註冊表中該服務實例的相關信息。
public interface HealthCheckHandler {
InstanceInfo.InstanceStatus getStatus(InstanceInfo.InstanceStatus currentStatus);
}
複製代碼
在spring-cloud-netflix-eureka-client
中實現了這個的接口,EurekaHealthCheckHandler
,主要的組合了spring-boot-actuator
中的HealthAggregator
和HealthIndicator
實現了對spring-boot
應用的狀態檢測。
主要有如下的狀態:
public enum InstanceStatus {
UP, // 能夠接受服務請求
DOWN, // 沒法發送流量-健康檢查失敗
STARTING, // 正在啓動,沒法發送流量
OUT_OF_SERVICE, // 服務關閉,不接受流量
UNKNOWN; // 未知狀態
}
複製代碼
Eureka中的事件模式,這是一個很明顯的觀察者模式,如下爲它的類圖類圖:
在DiscoveryClient
的代碼中,有實現服務註冊與發現的功能的具體代碼。在DiscoveryClient
構造函數中,Eureka Client會執行從Eureka Server中拉取註冊表信息,註冊自身等操做。 DiscoveryClient
的構造函數以下:
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config,
AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider)
複製代碼
ApplicationInfoManager
和EurekaClientConfig
在前面的介紹中已經瞭解,一個是封裝當前服務實例的配置信息的類,另外一個是封裝了client與server交互配置信息的類, AbstractDiscoveryClientOptionalArgs
和BackupRegistry
是未介紹過的
BackupRegistry
的接口代碼以下:
@ImplementedBy(NotImplementedRegistryImpl.class)
public interface BackupRegistry {
Applications fetchRegistry();
Applications fetchRegistry(String[] includeRemoteRegions);
}
複製代碼
它充當了備份註冊中心的職責,當Eureka Client沒法從任何一個Eureka Server中獲取註冊表信息時,BackupRegistry
將被調用以獲取註冊表信息,可是默認的實現是NotImplementedRegistryImpl
,即沒有實現。
public abstract class AbstractDiscoveryClientOptionalArgs<T> {
// 生成健康檢查回調的工廠類,HealthCheckCallback已廢棄
Provider<HealthCheckCallback> healthCheckCallbackProvider;
// 生成健康處理器的工廠類
Provider<HealthCheckHandler> healthCheckHandlerProvider;
// 向Eureka Server註冊以前的預處理器
PreRegistrationHandler preRegistrationHandler;
// Jersey過濾器集合,Jersey1和Jersey2都可使用
Collection<T> additionalFilters;
// Jersey客戶端,主要用於client與server之間的HTTP交互
EurekaJerseyClient eurekaJerseyClient;
// 生成Jersey客戶端的工廠
TransportClientFactory transportClientFactory;
// 生成Jersey客戶端的工廠的工廠
TransportClientFactories transportClientFactories;
// Eureka事件的監聽器
private Set<EurekaEventListener> eventListeners;
....
}
複製代碼
AbstractDiscoveryClientOptionalArgs
是用於注入一些可選參數的,以及一些jersey1
和jersey2
通用的過濾器,@Inject(optional = true)
屬性說明了該參數的可選性
在構造方法中,忽略掉大部分的賦值操做,逐步瞭解配置類中的屬性會對DiscoveryClient
的行爲形成什麼影響
if (config.shouldFetchRegistry()) {
this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
} else {
this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
}
if (config.shouldRegisterWithEureka()) {
this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L});
} else {
this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC;
}
複製代碼
config.shouldFetchRegistry()(對應配置爲eureka.client.fetch-register
),爲true表示Eureka Client將從Eureka Server中拉取註冊表的信息,config.shouldRegisterWithEureka(對應配置爲eureka.client.register-with-eureka
),爲true表示Eureka Client將註冊到Eureka Server中。
若是上述的兩個配置均爲false,那麼Discovery的初始化就直接結束,表示該客戶端既不進行服務註冊也不進行服務發現
接着初始化一個基於線程池的定時器線程池ScheduledExecutorService
,線程池大小爲2,一個用於心跳,一個用於緩存刷新,同時初始化了心跳和緩存刷新線程池(ThreadPoolExecutor)。關於ScheduledExecutorService
與ThreadPoolExecutor
之間的關係在此不展開。
scheduler = Executors.newScheduledThreadPool(2,
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-%d")
.setDaemon(true)
.build());
heartbeatExecutor = new ThreadPoolExecutor(
1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
cacheRefreshExecutor = new ThreadPoolExecutor(
1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactoryBuilder()
.setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d")
.setDaemon(true)
.build()
); // use direct handoff
複製代碼
接着初始化了Eureka Client與Eureka Server進行HTTP交互的Jersy客戶端,將AbstractDiscoveryClientOptionalArgs
中的屬性用來構建EurekaTransport
。
eurekaTransport = new EurekaTransport();
scheduleServerEndpointTask(eurekaTransport, args);
複製代碼
EurekaTransport
是DiscoveryClient
中的一個內部類,其內封裝了DiscoveryClient
與Eureka Server進行HTTP調用的Jersy客戶端:
private static final class EurekaTransport {
// Server endPoint解析器
private ClosableResolver bootstrapResolver;
// Jersy客戶端生成工廠
private TransportClientFactory transportClientFactory;
// 註冊客戶端
private EurekaHttpClient registrationClient;
// 註冊客戶端生成工廠
private EurekaHttpClientFactory registrationClientFactory;
// 發現服務客戶端
private EurekaHttpClient queryClient;
// 發現服務客戶端生成工廠
private EurekaHttpClientFactory queryClientFactory;
....
}
複製代碼
關於AWSregion
中的相關配置略過。
客戶端的更多內容,將會在下篇文章介紹,敬請關注。
詳細瞭解本書:地址