發佈模式
藍綠髮布
在發佈的過程中用戶無感知服務的重啓,通常情況下是通過新舊版本並存的方式實現,也就是說在發佈的流程中,新的版本和舊的版本是相互熱備的,通過切換路由權重的方式(非0即100)實現不同的應用的上線或者下線。
金絲雀發佈
通過在線上運行的服務中,新加入少量的新版本的服務,然後從這少量的新版本中快速獲得反饋,根據反饋決定最後的交付形態。
灰度發佈
灰度發佈是通過切換線上並存版本之間的路由權重,逐步從一個版本切換爲另一個版本的過程。雖然有很多人包括專業大牛認爲灰度發佈與金絲雀發佈是等同的,但是在具體的操作和目的上面個還是有些許差別的。金絲雀發佈更傾向於獲取快速的反饋,而灰度發佈更傾向於從一個版本到另一個版本平穩的切換。
AB測試
AB測試和灰度發佈非常像,但是從發佈的目的上,可以簡單的區分灰度發佈與AB測試,AB測試側重的是從A版本或者B版本之間的差異,並根據這個結果進行決策。最終選擇一個版本進行部署。因此和灰度發佈相比,AB測試更傾向於去決策,和金絲雀發佈相比,AB測試在權重和流量的切換上更靈活。
應用場景
背景介紹
我們的應用是基於Spring Cloud搭建,每當我們的後臺部署應用時,都需要停止tomcat,由於Eureka自身的緩存和Ribbon的刷新不及時,導致服務下線不能做到及時感知,讓服務在這段時間調用已停服務,這樣出現了異常的業務現象。
追溯原因
首先理解Eureka的緩存機制,響應緩存實現類。
com.netflix.eureka.registry.ResponseCacheImpl
在 ResponseCacheImpl 裏,將緩存拆分成兩層 :
- 只讀緩存( readOnlyCacheMap )
- 固定過期 + 固定大小的讀寫緩存( readWriteCacheMap )
默認配置下,緩存讀取策略如下:
readWriteCacheMap用了谷歌的guava。
- expireAfterWrite設置緩存多少時間後失效。
- CacheLoader裏的load方法是個抽象方法。當從readWriteCacheMap獲取指定的key時,就會觸發這個方法。
- generatePayload 是具體的實現。
ResponseCacheImpl(EurekaServerConfig serverConfig, ServerCodecs serverCodecs, AbstractInstanceRegistry registry) {
this.serverConfig = serverConfig;
this.serverCodecs = serverCodecs;
this.shouldUseReadOnlyResponseCache = serverConfig.shouldUseReadOnlyResponseCache();
this.registry = registry;
long responseCacheUpdateIntervalMs = serverConfig.getResponseCacheUpdateIntervalMs();
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) {
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);
}
}
備註 通過Eureka請求的方式實現服務的上下線,對應的在eureka-core包裏的resource目錄下。獲取所有的節點ApplicationsResource的getContainers,下線節點InstanceResource的cancelLease,它採用的Jersey實現的,所以不是很直觀。
- AbstractInstanceRegistry 裏面幾個重要的方法 internalCancel 和 register。這裏學習了一個新的事件功能,
這樣我們可以監聽 EurekaInstanceCanceledEvent 下線節點事件,這裏會執行倆次。
@Component
public class EurekaStateChangeListener {
@EventListener
public void listen(EurekaInstanceCanceledEvent eurekaInstanceCanceledEvent)
{
String appName = eurekaInstanceCanceledEvent.getAppName();
String serverId = eurekaInstanceCanceledEvent.getServerId();
System.out.println(appName);
System.out.println(serverId);
}
}
ribbon的處理
ribbon有倆種更新ServerList的方式一個是基於Java裏ScheduledThreadPoolExecutor類的定時任務由PollingServerListUpdater實現。一個是EurekaNotificationServerListUpdater類,基於事件的通知。
監測方法
我是通過wireshake監控,當我們手動下線服務的時候,用Jmeter進行壓力測試,看ribbon的負載均衡是不是還有流量流向我們的下線服務。