eureka server端啟動分析
eureka server在啟動時會打印日志,追蹤日志發(fā)現(xiàn),打印“Initializing …”的類為DefaultEurekaServerContext的initialize()方法。
@PostConstruct
@Override
public void initialize() {
logger.info("Initializing ...");
peerEurekaNodes.start();
try {
registry.init(peerEurekaNodes);
} catch (Exception e) {
throw new RuntimeException(e);
}
logger.info("Initialized");
}
先看start()方法
public void start() {
//創(chuàng)造一個擁有單線程的線程池
taskExecutor = Executors.newSingleThreadScheduledExecutor(
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "Eureka-PeerNodesUpdater");
thread.setDaemon(true);
return thread;
}
}
);
try {
//先更新一下所有eureka節(jié)點的狀態(tài),包括新增eureka節(jié)點,下線eureka節(jié)點等
updatePeerEurekaNodes(resolvePeerUrls());
//新建一個線程,線程做的事情就是更新所有eureka節(jié)點的狀態(tài)
Runnable peersUpdateTask = new Runnable() {
@Override
public void run() {
try {
updatePeerEurekaNodes(resolvePeerUrls());
} catch (Throwable e) {
logger.error("Cannot update the replica Nodes", e);
}
}
};
//啟動定時線程池執(zhí)行任務,默認是10分鐘一次
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());
}
}
總結一下PeerEurekaNodes的start()方法:
- 更新所有eureka server節(jié)點的狀態(tài),新增或者下線部分eureka server節(jié)點。
- 創(chuàng)建一個擁有單個線程的線程池,定時更新所有eureka server節(jié)點的狀態(tài)。默認情況下,是15分鐘一次。
再看一下registry.init()方法。
這個registry實例為InstanceRegistry,init方法實際上是父類PeerAwareInstanceRegistryImpl的init()方法。
public void init(PeerEurekaNodes peerEurekaNodes) throws Exception {
this.numberOfReplicationsLastMin.start();
this.peerEurekaNodes = peerEurekaNodes;
//初始化緩存注冊實例信息
initializedResponseCache();
//定時修改更新注冊信息的閾值,防止短時間內(nèi)下線太多注冊服務
scheduleRenewalThresholdUpdateTask();
//初始化遠程區(qū)域注冊,region、zone、cluster區(qū)別聯(lián)系,請看https://github.com/Netflix/eureka/issues/881
initRemoteRegionRegistry();
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register the JMX monitor for the InstanceRegistry :", e);
}
}
先研究下initializedResponseCache()方法。
public synchronized void initializedResponseCache() {
if (responseCache == null) {
responseCache = new ResponseCacheImpl(serverConfig, serverCodecs, this);
}
}
這里如果為null的時候,創(chuàng)建了一個新的ResponseCacheImpl實例,我們看下它的構造方法。
這個ResponseCacheImpl其實就是一個實例注冊信息的緩存類,可能會被客戶端訪問,支持gzip壓縮。
這個ResponseCacheImpl在eureka-core工程的resource包里會被訪問到。
ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
this.serverConfig = serverConfig;
this.serverCodecs = serverCodecs;
//是否使用只讀緩存
this.shouldUseReadOnlyResponseCache = serverConfig.shouldUseReadOnlyResponseCache();
this.registry = registry;
//只讀緩存更新間隔
long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();
//讀寫緩存map
this.readWriteCacheMap =
CacheBuilder.newBuilder().initialCapacity(1000)
.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);
}
Value value = generatePayload(key);
return value;
}
});
if (shouldUseReadOnlyResponseCache) {
//使用只讀緩存的話,創(chuàng)建定時任務定時更新
timer.schedule(getCacheUpdateTask(),
new Date(((System.currentTimeMillis() / responseCacheUpdateIntervalMs) * responseCacheUpdateIntervalMs)
+ responseCacheUpdateIntervalMs),
responseCacheUpdateIntervalMs);
}
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register the JMX monitor for the InstanceRegistry", e);
}
}
總結一下initializedResponseCache()方法:
- 創(chuàng)建讀寫緩存
- 創(chuàng)建定時任務,每隔一定時間同步讀寫緩存到只讀緩存中
再看一下scheduleRenewalThresholdUpdateTask()方法:
private void scheduleRenewalThresholdUpdateTask() {
timer.schedule(new TimerTask() {
@Override
public void run() {
updateRenewalThreshold();
}
}, serverConfig.getRenewalThresholdUpdateIntervalMs(),
serverConfig.getRenewalThresholdUpdateIntervalMs());
}
private void updateRenewalThreshold() {
try {
Applications apps = eurekaClient.getApplications();
int count = 0;
for (Application app : apps.getRegisteredApplications()) {
for (InstanceInfo instance : app.getInstances()) {
//如果沒有指定datacenter,或者datacenter不是AWS,那么都是可注冊的
if (this.isRegisterable(instance)) {
++count;
}
}
}
synchronized (lock) {
// Update threshold only if the threshold is greater than the
// current expected threshold or if self preservation is disabled.
if ((count * 2) > (serverConfig.getRenewalPercentThreshold() * expectedNumberOfRenewsPerMin)
|| (!this.isSelfPreservationModeEnabled())) {
this.expectedNumberOfRenewsPerMin = count * 2;
this.numberOfRenewsPerMinThreshold = (int) ((count * 2) * serverConfig.getRenewalPercentThreshold());
}
}
logger.info("Current renewal threshold is : {}", numberOfRenewsPerMinThreshold);
} catch (Throwable e) {
logger.error("Cannot update renewal threshold", e);
}
}
scheduleRenewalThresholdUpdateTask()方法,就是創(chuàng)建一個定時任務,定時更新每分鐘注冊的實例的閾值。
再看一下EurekaServerInitializerConfiguration,這個類實現(xiàn)了SmartLifecycle,會在spring容器初始化時調(diào)用。
public void start() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//這里就是調(diào)用了EurekaServerBootstrap的contextInitialized()方法
eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");
//發(fā)布eureka服務可用事件
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
EurekaServerInitializerConfiguration.this.running = true;
//發(fā)布eureka服務啟動事件
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
}
catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}
}).start();
}
EurekaServerBootstrap。
public void contextInitialized(ServletContext context) {
try {
//讀取配置文件,初始化環(huán)境信息
initEurekaEnvironment();
//初始化eureka server 上下文
initEurekaServerContext();
context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
}
catch (Throwable e) {
log.error("Cannot bootstrap eureka server :", e);
throw new RuntimeException("Cannot bootstrap eureka server :", e);
}
}
/**
* Users can override to initialize the environment themselves.
*/
protected void initEurekaEnvironment() throws Exception {
logger.info("Setting the eureka configuration..");
//EUREKA_DATACENTER===eureka.datacenter 這個GitHub上eureka描述是為了在AWS云上部署,自動初始化一些特定信息的。暫時無需關注
String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER);
if (dataCenter == null) {
ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT);
} else {
ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter);
}
//...EUREKA_ENVIRONMENT=eureka.environment,這個是為了說明環(huán)境是test,prod等等,用來指定eureka-client.properties配置文件,不過一般我們會將這些配置放入application.properties,這個貌似也用不著
String environment = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT);
if (environment == null) {
ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST);
logger.info("Eureka environment value eureka.environment is not set, defaulting to test");
}else {
ConfigurationManager.getConfigInstance()
.setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, environment);
}
}
從上面代碼可以看出initEurekaEnvironment()方法主要是初始化和環(huán)境一些相關的信息,比如設置了eureka.datacenter(只針對AWS云部署)以及eureka.environment(用來指定eureka-client.properties,一般用不著)等。
接下來分析initEurekaServerContext()方法。
protected void initEurekaServerContext() throws Exception {
//...
log.info("Initialized server context");
// Copy registry from neighboring eureka node 從其他eureka節(jié)點復制實例注冊信息,并注冊到自己上
int registryCount = this.registry.syncUp();
//
this.registry.openForTraffic(this.applicationInfoManager, registryCount);
// Register all monitoring statistics.
EurekaMonitors.registerAllStats();
}
PeerAwareInstanceRegistryImpl.syncUp()方法
public int syncUp() {
// Copy entire entry from neighboring DS node
int count = 0;
for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
//....從eurekaClient獲取所有應用信息,遍歷,注冊到當前eureka server上
Applications apps = eurekaClient.getApplications();
for (Application app : apps.getRegisteredApplications()) {
for (InstanceInfo instance : app.getInstances()) {
try {
if (isRegisterable(instance)) {
register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
count++;
}
} catch (Throwable t) {
logger.error("During DS init copy", t);
}
}
}
}
return count;
}
看一下具體的注冊到eureka server的 方法,AbstractInstanceRegistry.register()方法
public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
try {
read.lock();
//獲取相同appName的已注冊實例信息,集群內(nèi)的其他實例注冊信息
Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
REGISTER.increment(isReplication);
if (gMap == null) {
//如果是第一個注冊的實例,新建一個concurrentHashMap用來存放相同appName的實例信息
final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
//在并發(fā)情況下,可能會有兩個同時操作
gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
if (gMap == null) {
//如果并發(fā)時,只會有一個key為appName的concurrentHashMap創(chuàng)建
gMap = gNewMap;
}
}
//獲取這個map下以instanceInfo的id為key的Lease(租約)
//這里說明一下,registrant.getId()如果是非AWS應用,就是InstanceInfo的instanceId
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)) {
//如果已存在相同InstanceInfo id的Lease租約,比較兩者的LastDirtyTimestamp,選擇最新的Lease關聯(lián)的InstanceInfo
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 {
//不存在相同instanceInfo id的Lease租約,更新expectedNumberOfRenewsPerMin和閾值
// The lease does not exist and hence it is a new registration
synchronized (lock) {
if (this.expectedNumberOfRenewsPerMin > 0) {
// Since the client wants to cancel it, reduce the threshold
// (1
// for 30 seconds, 2 for a minute)
this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2;
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
}
}
logger.debug("No previous lease information found; it is new registration");
}
//重新構造一個Lease,并放入相同appName的map中,key為InstanceInfo的id,value為Lease本身
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() + ")"));
}
//更新注冊實例的狀態(tài)
// 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();
}
}
總結一下register()方法:
- 從注冊信息緩存map中獲取以注冊的實例的appName為key的實例集合map,如果沒有則新建一個map
- 從相同appName的實例map中獲取以當前InstanceInfo的id的租約信息,如果有,和當前要注冊的實例信息比較,選擇最新的實例信息
- 更新實例信息的狀態(tài)
再看一下openForTraffic()方法
@Override
public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// Renewals happen every 30 seconds and for a minute it should be a factor of 2.
//計算每分鐘最大續(xù)約次數(shù)
this.expectedNumberOfRenewsPerMin = count * 2;
//計算每分鐘最小續(xù)約次數(shù)=最大續(xù)約次數(shù)*啟動自我保護模式的百分比閾值
this.numberOfRenewsPerMinThreshold =
(int) (this.expectedNumberOfRenewsPerMin * serverConfig.getRenewalPercentThreshold());
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()) {
//如果是AWS亞馬遜云服務,做一些兼容
logger.info("Priming AWS connections for all replicas..");
primeAwsReplicas(applicationInfoManager);
}
logger.info("Changing status to UP");
//更新狀態(tài)
applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
super.postInit();
}
protected void postInit() {
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
//重點在于EvictionTask.run()方法
evictionTaskRef.set(new EvictionTask());
//創(chuàng)建定時任務,定時清理過期的注冊實例
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}
public void run() {
try {
long compensationTimeMs = getCompensationTimeMs();
logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
//下線已過期的實例
evict(compensationTimeMs);
} catch (Throwable e) {
logger.error("Could not run the evict task", e);
}
}
到這里基本就分析完了。
最后補充下,不從日志分析,如何確定啟動的流程。
從@EnableEurekaServer入手,發(fā)現(xiàn)@Import(EurekaServerMarkerConfiguration.class),import一個配置類。
/**
* Responsible for adding in a marker bean to activate
* {@link EurekaServerAutoConfiguration}
*
* @author Biju Kunjummen
*/
@Configuration
public class EurekaServerMarkerConfiguration {
@Bean
public Marker eurekaServerMarkerBean() {
return new Marker();
}
class Marker {
}
}
可以看到這個類link到了EurekaServerAutoConfiguration,這里聲明了EurekaServerBootstrap、peerAwareInstanceRegistry等Bean。
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(
ServerCodecs serverCodecs) {
this.eurekaClient.getApplications(); // force initialization
return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig,
serverCodecs, this.eurekaClient,
this.instanceRegistryProperties.getExpectedNumberOfRenewsPerMin(),
this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
}
@Bean
@ConditionalOnMissingBean
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
ServerCodecs serverCodecs) {
return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
}
@Bean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs,
PeerAwareInstanceRegistry registry, PeerEurekaNodes peerEurekaNodes) {
return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs,
registry, peerEurekaNodes, this.applicationInfoManager);
}
@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
EurekaServerContext serverContext) {
return new EurekaServerBootstrap(this.applicationInfoManager,
this.eurekaClientConfig, this.eurekaServerConfig, registry,
serverContext);
}