发布模式
蓝绿发布
在发布的过程中用户无感知服务的重启,通常情况下是通过新旧版本并存的方式实现,也就是说在发布的流程中,新的版本和旧的版本是相互热备的,通过切换路由权重的方式(非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的负载均衡是不是还有流量流向我们的下线服务。