Eureka簡介
Eureka是什麼?
Eureka是基於REST(Representational State Transfer)服務,主要以AWS雲服務為支撐,提供服務發現並實現負載均衡和故障轉移。我們稱此服務為Eureka服務。Eureka提供了Java客戶端元件,Eureka Client,方便與服務端的互動。客戶端內建了基於round-robin實現的簡單負載均衡。在Netflix,為Eureka提供更為複雜的負載均衡方案進行封裝,以實現高可用,它包括基於流量、資源利用率以及請求返回狀態的加權負載均衡。
Eureka架構
Eureka架構從CAP理論看,Eureka是一個AP系統,優先保證可用性(A)和分割槽容錯性(P),Eureka裡使用了大量的快取。
Eureka中的一些概念
- Register :服務註冊
Eureka客戶端向Eureka Server註冊時,它提供自身的後設資料,比如IP地址、埠等
- Renew:服務續約
Eureka客戶端會每隔30秒傳送一次心跳來續約。通過續約來告知Eureka Server該客戶端仍然存在。
- Fetch Registries:獲取註冊列表資訊
Eureka客戶端從伺服器獲取登錄檔資訊,將其快取到本地。客戶端會使用該資訊查詢其他服務,從而進行遠端呼叫。該註冊列表資訊定期(每30秒)更新一次。
- Cancel:服務下線
Eureka客戶端在程式關閉時向Eureka伺服器傳送取消請求。
- Eviction:服務剔除
在預設情況下,當Eureka客戶端90秒沒有向Eureka伺服器傳送續約,Eureka伺服器就會將該服務例項從服務註冊列表刪除。
除了以上的特性外,Eureka的快取機制也非常經典,下面詳細介紹一下。
Eureka快取
Eureka Server裡存在三個變數(registry、readWriteCacheMap、readOnlyCacheMap)儲存服務註冊資訊。
Eureka客戶端向服務端註冊之後,資料會立即同步到readWriteCacheMap和registry。
Eureka客戶端想檢視註冊資訊,每隔30秒從readOnlyCacheMap拉取。
readOnlyCacheMap會通過定時器每30秒從readWriteCacheMap拉取。
還有一個執行緒每隔60會將90秒都沒有續約的服務剔除出去。
變數 | 型別 | 說明 |
---|---|---|
registry | ConcurrentHashMap | 實時更新,類AbstractInstanceRegistry成員變數,UI端請求的是這裡的服務註冊資訊 |
readWriteCacheMap | Guava Cache | 實時更新,類ResponseCacheImpl成員變數,快取時間180秒 |
readOnlyCacheMap | ConcurrentHashMap | 週期更新,類ResponseCacheImpl成員變數,預設每30s從readWriteCacheMap更新,Eureka client預設從這裡更新服務註冊資訊,可配置直接從readWriteCacheMap更新 |
Eureka Client
本文使用的是2.0.2.RELEASE版本
接下來開始分析Eureka Client的原始碼。引入spring-cloud-starter-netflix-eureka-client後,Eureka Client會自動啟用。EurekaClientAutoConfiguration配置類生效,會注入Bean CloudEurekaClient,然後呼叫父類DiscoveryClient的構造方法。
@Inject
DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args,
Provider<BackupRegistry> backupRegistryProvider) {
//省略部分程式碼
//如果配置不用註冊到Eureka && 配置不用從註冊中心獲取配置,則不用初始化相關元件
if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) {
logger.info("Client configured to neither register nor query for data.");
scheduler = null;
heartbeatExecutor = null;
cacheRefreshExecutor = null;
eurekaTransport = null;
instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion());
// This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
// to work with DI'd DiscoveryClient
DiscoveryManager.getInstance().setDiscoveryClient(this);
DiscoveryManager.getInstance().setEurekaClientConfig(config);
initTimestampMs = System.currentTimeMillis();
logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
initTimestampMs, this.getApplications().size());
return; // no need to setup up an network tasks and we are done
}
try {
// default size of 2 - 1 each for heartbeat and cacheRefresh
//初始化定時排程器
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
eurekaTransport = new EurekaTransport();
scheduleServerEndpointTask(eurekaTransport, args);
AzToRegionMapper azToRegionMapper;
if (clientConfig.shouldUseDnsForFetchingServiceUrls()) {
azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig);
} else {
azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig);
}
if (null != remoteRegionsToFetch.get()) {
azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(","));
}
instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion());
} catch (Throwable e) {
throw new RuntimeException("Failed to initialize DiscoveryClient!", e);
}
if (clientConfig.shouldFetchRegistry() && !fetchRegistry(false)) {
fetchRegistryFromBackup();
}
// call and execute the pre registration handler before all background tasks (inc registration) is started
if (this.preRegistrationHandler != null) {
this.preRegistrationHandler.beforeRegistration();
}
if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) {
try {
if (!register() ) {
throw new IllegalStateException("Registration error at startup. Invalid server response.");
}
} catch (Throwable th) {
logger.error("Registration error at startup: {}", th.getMessage());
throw new IllegalStateException(th);
}
}
// finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch
//初始化定時任務,服務心跳、服務註冊、服務列表獲取等功能在此處完成
initScheduledTasks();
try {
Monitors.registerObject(this);
} catch (Throwable e) {
logger.warn("Cannot register timers", e);
}
// This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
// to work with DI'd DiscoveryClient
DiscoveryManager.getInstance().setDiscoveryClient(this);
DiscoveryManager.getInstance().setEurekaClientConfig(config);
initTimestampMs = System.currentTimeMillis();
logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}",
initTimestampMs, this.getApplications().size());
}
接下來看initScheduledTasks方法
private void initScheduledTasks() {
if (clientConfig.shouldFetchRegistry()) {
// registry cache refresh timer
//預設30s
int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
//快取重新整理定時任務
scheduler.schedule(
new TimedSupervisorTask(
"cacheRefresh",
scheduler,
cacheRefreshExecutor,
registryFetchIntervalSeconds,
TimeUnit.SECONDS,
expBackOffBound,
//1. 快取重新整理具體邏輯
new CacheRefreshThread()
),
registryFetchIntervalSeconds, TimeUnit.SECONDS);
}
if (clientConfig.shouldRegisterWithEureka()) {
int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
// Heartbeat timer
scheduler.schedule(
new TimedSupervisorTask(
"heartbeat",
scheduler,
heartbeatExecutor,
renewalIntervalInSecs,
TimeUnit.SECONDS,
expBackOffBound,
//2. 心跳具體邏輯
new HeartbeatThread()
),
renewalIntervalInSecs, TimeUnit.SECONDS);
// InstanceInfo replicator
instanceInfoReplicator = new InstanceInfoReplicator(
this,
instanceInfo,
clientConfig.getInstanceInfoReplicationIntervalSeconds(),
2); // burstSize
//狀態改變監聽
statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
@Override
public String getId() {
return "statusChangeListener";
}
@Override
public void notify(StatusChangeEvent statusChangeEvent) {
if (InstanceStatus.DOWN == statusChangeEvent.getStatus() ||
InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) {
// log at warn level if DOWN was involved
logger.warn("Saw local status change event {}", statusChangeEvent);
} else {
logger.info("Saw local status change event {}", statusChangeEvent);
}
instanceInfoReplicator.onDemandUpdate();
}
};
if (clientConfig.shouldOnDemandUpdateStatusChange()) {
applicationInfoManager.registerStatusChangeListener(statusChangeListener);
}
//3. clientConfig.getInitialInstanceInfoReplicationIntervalSeconds()預設40s,服務註冊就在這個方法裡完成。
instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
} else {
logger.info("Not registering with Eureka server per configuration");
}
}
再來細看上面3個主要方法的具體邏輯。
1. 快取重新整理
class CacheRefreshThread implements Runnable {
public void run() {
refreshRegistry();
}
}
void refreshRegistry() {
//省略部分程式碼
//獲取服務列表資訊
boolean success = fetchRegistry(remoteRegionsModified);
//省略部分程式碼
private boolean fetchRegistry(boolean forceFullRegistryFetch) {
Stopwatch tracer = FETCH_REGISTRY_TIMER.start();
try {
// If the delta is disabled or if it is the first time, get all
// applications
Applications applications = getApplications();
if (clientConfig.shouldDisableDelta()
|| (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress()))
|| forceFullRegistryFetch
|| (applications == null)
|| (applications.getRegisteredApplications().size() == 0)
|| (applications.getVersion() == -1)) //Client application does not have latest library supporting delta
{
//全量獲取服務列表快取在本地
getAndStoreFullRegistry();
} else {
//更新服務列表 getAndUpdateDelta(applications);
}
applications.setAppsHashCode(applications.getReconcileHashCode());
logTotalInstances();
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to refresh its cache! status = {}", appPathIdentifier, e.getMessage(), e);
return false;
} finally {
if (tracer != null) {
tracer.stop();
}
}
// Notify about cache refresh before updating the instance remote status
onCacheRefreshed();
// Update remote status based on refreshed data held in the cache
updateInstanceRemoteStatus();
// registry was fetched successfully, so return true
return true;
}
然後呼叫EurekaHttpClient介面的方法去獲取服務列表。請求傳送通過jersey
2. 服務心跳
繼續跟蹤HeartbeatThread方法
private class HeartbeatThread implements Runnable {
public void run() {
if (renew()) {
lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
}
}
}
boolean renew() {
EurekaHttpResponse<InstanceInfo> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
if (httpResponse.getStatusCode() == 404) {
REREGISTER_COUNTER.increment();
logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
long timestamp = instanceInfo.setIsDirtyWithTime();
//呼叫註冊方法
boolean success = register();
if (success) {
instanceInfo.unsetIsDirty(timestamp);
}
return success;
}
return httpResponse.getStatusCode() == 200;
} catch (Throwable e) {
logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
return false;
}
}
3. 服務註冊
跟蹤instanceInfoReplicator.start方法
public void start(int initialDelayMs) {
if (started.compareAndSet(false, true)) {
//設定標識,為了啟動時進行服務註冊
instanceInfo.setIsDirty(); // for initial register
//延遲40s執行,執行的是this物件的run方法
Future next = scheduler.schedule(this, initialDelayMs, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
public void run() {
try {
discoveryClient.refreshInstanceInfo();
//剛才start方法中,設定了標識,所以此處dirtyTimestamp不為空
Long dirtyTimestamp = instanceInfo.isDirtyWithTime();
if (dirtyTimestamp != null) {
//服務註冊
discoveryClient.register();
instanceInfo.unsetIsDirty(dirtyTimestamp);
}
} catch (Throwable t) {
logger.warn("There was a problem with the instance info replicator", t);
} finally {
//遞迴性的延遲30s執行當前run方法
Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS);
scheduledPeriodicRef.set(next);
}
}
4. 服務關閉
服務關閉之後,會回撥到EurekaAutoServiceRegistration類的stop方法,回撥的方法是:
@EventListener(ContextClosedEvent.class)
public void onApplicationEvent(ContextClosedEvent event) {
if( event.getApplicationContext() == context ) {
//服務關閉
stop();
}
}
SmartLifecycle介面也有這個作用,不過我本地使用了一下,是通過ContextClosedEvent來回撥的。
public void stop() {
this.serviceRegistry.deregister(this.registration);
this.running.set(false);
}
@Override
public void deregister(EurekaRegistration reg) {
if (reg.getApplicationInfoManager().getInfo() != null) {
if (log.isInfoEnabled()) {
log.info("Unregistering application " + reg.getApplicationInfoManager().getInfo().getAppName()
+ " with eureka with status DOWN");
}
//狀態改為DOWN
reg.getApplicationInfoManager().setInstanceStatus(InstanceInfo.InstanceStatus.DOWN);
//shutdown of eureka client should happen with EurekaRegistration.close()
//auto registration will create a bean which will be properly disposed
//manual registrations will need to call close()
}
}
從上文分析得知Eureka Client調取服務端的介面都是通過EurekaHttpClient介面,而最終傳送請求的httpClient是jersey裡面的ApacheHttpClient4。
public interface EurekaHttpClient {
EurekaHttpResponse<Void> register(InstanceInfo info);
EurekaHttpResponse<Void> cancel(String appName, String id);
EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus);
EurekaHttpResponse<Void> statusUpdate(String appName, String id, InstanceStatus newStatus, InstanceInfo info);
EurekaHttpResponse<Void> deleteStatusOverride(String appName, String id, InstanceInfo info);
EurekaHttpResponse<Applications> getApplications(String... regions);
EurekaHttpResponse<Applications> getDelta(String... regions);
EurekaHttpResponse<Applications> getVip(String vipAddress, String... regions);
EurekaHttpResponse<Applications> getSecureVip(String secureVipAddress, String... regions);
EurekaHttpResponse<Application> getApplication(String appName);
EurekaHttpResponse<InstanceInfo> getInstance(String appName, String id);
EurekaHttpResponse<InstanceInfo> getInstance(String id);
void shutdown();
}
Eureka Server
Eureka Server需要做的事有:
- 維護服務註冊資訊列表
- 接收客戶端的register、renew、cancel等請求
- Eureka Server多節點之間的資料複製同步
專案啟動時,EurekaServerAutoConfiguration會被自動注入到容器中。
@Configuration
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration extends WebMvcConfigurerAdapter {
//省略部分程式碼
@Configuration
protected static class EurekaServerConfigBeanConfiguration {
@Bean
@ConditionalOnMissingBean
public EurekaServerConfig eurekaServerConfig(EurekaClientConfig clientConfig) {
EurekaServerConfigBean server = new EurekaServerConfigBean();
if (clientConfig.shouldRegisterWithEureka()) {
// Set a sensible default if we are supposed to replicate
server.setRegistrySyncRetries(5);
}
return server;
}
}
//Eureka管理頁面的Controller
@Bean
@ConditionalOnProperty(prefix = "eureka.dashboard", name = "enabled", matchIfMissing = true)
public EurekaController eurekaController() {
return new EurekaController(this.applicationInfoManager);
}
static {
CodecWrappers.registerWrapper(JACKSON_JSON);
EurekaJacksonCodec.setInstance(JACKSON_JSON.getCodec());
}
@Bean
public ServerCodecs serverCodecs() {
return new CloudServerCodecs(this.eurekaServerConfig);
}
private static CodecWrapper getFullJson(EurekaServerConfig serverConfig) {
CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getJsonCodecName());
return codec == null ? CodecWrappers.getCodec(JACKSON_JSON.codecName()) : codec;
}
private static CodecWrapper getFullXml(EurekaServerConfig serverConfig) {
CodecWrapper codec = CodecWrappers.getCodec(serverConfig.getXmlCodecName());
return codec == null ? CodecWrappers.getCodec(CodecWrappers.XStreamXml.class)
: codec;
}
class CloudServerCodecs extends DefaultServerCodecs {
public CloudServerCodecs(EurekaServerConfig serverConfig) {
super(getFullJson(serverConfig),
CodecWrappers.getCodec(CodecWrappers.JacksonJsonMini.class),
getFullXml(serverConfig),
CodecWrappers.getCodec(CodecWrappers.JacksonXmlMini.class));
}
}
//處理Eureka Client的register、renew、cancel等請求
@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());
}
//處理Eureka Server多節點同步
@Bean
@ConditionalOnMissingBean
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry,
ServerCodecs serverCodecs) {
return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig,
this.eurekaClientConfig, serverCodecs, this.applicationInfoManager);
}
//省略部分程式碼
1. 請求接受處理
InstanceResource類主要用於接受請求,收到請求後呼叫InstanceRegistry類的方法進行處理。以renew為例:
@PUT
public Response renewLease(
@HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication,
@QueryParam("overriddenstatus") String overriddenStatus,
@QueryParam("status") String status,
@QueryParam("lastDirtyTimestamp") String lastDirtyTimestamp) {
boolean isFromReplicaNode = "true".equals(isReplication);
boolean isSuccess = registry.renew(app.getName(), id, isFromReplicaNode);
//...
2. 服務剔除
EurekaServerAutoConfiguration類引入了EurekaServerInitializerConfiguration類。容器初始化會觸發start方法,start方法如下:
@Override
public void start() {
new Thread(new Runnable() {
@Override
public void run() {
try {
//TODO: is this class even needed now?
//初始化方法
eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server");
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig()));
EurekaServerInitializerConfiguration.this.running = true;
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
}
catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}
}).start();
}
eurekaServerBootstrap.contextInitialized()
-》this.registry.openForTraffic(this.applicationInfoManager, registryCount);
-》 super.postInit();
postInit程式碼如下:
protected void postInit() {
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
//服務剔除定時任務
evictionTaskRef.set(new EvictionTask());
evictionTimer.schedule(evictionTaskRef.get(),
//延遲60s,每60執行一次
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}
3. readOnlyCacheMap快取週期更新
DefaultEurekaServerContext類的initialize方法上加了@PostConstruct註解,會在bean構造後被執行:
@PostConstruct
@Override
public void initialize() {
logger.info("Initializing ...");
peerEurekaNodes.start();
try {
registry.init(peerEurekaNodes);
} catch (Exception e) {
throw new RuntimeException(e);
}
logger.info("Initialized");
}
init()-》 initializedResponseCache()-》new ResponseCacheImpl
ResponseCacheImpl方法如下:
ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
this.serverConfig = serverConfig;
this.serverCodecs = serverCodecs;
this.shouldUseReadOnlyResponseCache = serverConfig.shouldUseReadOnlyResponseCache();
this.registry = registry;
long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();
//readWriteCacheMap是guava快取,快取載入是用的load方法裡的實現
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) {
//定時30s重新整理快取,具體邏輯在getCacheUpdateTask
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);
}
}
private TimerTask getCacheUpdateTask() {
return new TimerTask() {
@Override
public void run() {
logger.debug("Updating the client cache from response cache");
for (Key key : readOnlyCacheMap.keySet()) {
if (logger.isDebugEnabled()) {
logger.debug("Updating the client cache from response cache for key : {} {} {} {}",
key.getEntityType(), key.getName(), key.getVersion(), key.getType());
}
try {
CurrentRequestVersion.set(key.getVersion());
Value cacheValue = readWriteCacheMap.get(key);
Value currentCacheValue = readOnlyCacheMap.get(key);
//對比值,不同的話readOnlyCacheMap取readWriteCacheMap裡的值放入。
if (cacheValue != currentCacheValue) {
readOnlyCacheMap.put(key, cacheValue);
}
} catch (Throwable th) {
logger.error("Error while updating the client cache from response cache for key {}", key.toStringCompact(), th);
}
}
}
};
}
番外:原始碼裡有一個這個東西,存最近的資料,如果有相同需求可以借鑑
private class CircularQueue<E> extends ConcurrentLinkedQueue<E> {
private int size = 0;
public CircularQueue(int size) {
this.size = size;
}
@Override
public boolean add(E e) {
this.makeSpaceIfNotAvailable();
return super.add(e);
}
private void makeSpaceIfNotAvailable() {
if (this.size() == size) {
this.remove();
}
}
public boolean offer(E e) {
this.makeSpaceIfNotAvailable();
return super.offer(e);
}
}