新聞中心
什么是灰度發(fā)布
灰度發(fā)布(又名金絲雀發(fā)布)是指在黑與白之間,能夠平滑過渡的一種發(fā)布方式。在其上可以進(jìn)行A/B testing,即讓一部分用戶繼續(xù)用產(chǎn)品特性A,一部分用戶開始用產(chǎn)品特性B,如果用戶對B沒有什么反對意見,那么逐步擴(kuò)大范圍,把所有用戶都遷移到B上面來。灰度發(fā)布可以保證整體系統(tǒng)的穩(wěn)定,在初始灰度的時(shí)候就可以發(fā)現(xiàn)、調(diào)整問題,以保證其影響度。

成都創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比赤壁網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式赤壁網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋赤壁地區(qū)。費(fèi)用合理售后完善,10多年實(shí)體公司更值得信賴。
灰度發(fā)布類型
- 金絲雀發(fā)布
將少量的請求引流到新版本上,因此部署新版本服務(wù)只需極小數(shù)的機(jī)器。驗(yàn)證新版本符合預(yù)期后,逐步調(diào)整流量權(quán)重比例,使得流量慢慢從老版本遷移至新版本,期間可以根據(jù)設(shè)置的流量比例,對新版本服務(wù)進(jìn)行擴(kuò)容,同時(shí)對老版本服務(wù)進(jìn)行縮容,使得底層資源得到最大化利用。
金絲雀發(fā)布的優(yōu)點(diǎn):
- 按比例將流量無差別地導(dǎo)向新版本,新版本故障影響范圍??;
- 發(fā)布期間逐步對新版本擴(kuò)容,同時(shí)對老版本縮容,資源利用率高。
金絲雀發(fā)布的缺點(diǎn):
- 流量無差別地導(dǎo)向新版本,可能會(huì)影響重要用戶的體驗(yàn);
- 發(fā)布周期長。
- A/B測試
A/B 測試基于用戶請求的元信息將流量路由到新版本,這是一種基于請求內(nèi)容匹配的灰度發(fā)布策略。只有匹配特定規(guī)則的請求才會(huì)被引流到新版本,常見的做法包括基于 Header 和 Cookie?;?Header 方式例子,例如 User-Agent 的值為 Android 的請求 (來自安卓系統(tǒng)的請求)可以訪問新版本,其他系統(tǒng)仍然訪問舊版本?;?Cookie 方式的例子,Cookie 中通常包含具有業(yè)務(wù)語義的用戶信息,例如普通用戶可以訪問新版本,VIP 用戶仍然訪問舊版本。
- 藍(lán)綠發(fā)布
藍(lán)綠發(fā)布需要對服務(wù)的新版本進(jìn)行冗余部署,一般新版本的機(jī)器規(guī)格和數(shù)量與舊版本保持一致,相當(dāng)于該服務(wù)有兩套完全相同的部署環(huán)境,只不過此時(shí)只有舊版本在對外提供服務(wù),新版本作為熱備。當(dāng)服務(wù)進(jìn)行版本升級時(shí),我們只需將流量全部切換到新版本即可,舊版本作為熱備。由于冗余部署的緣故,所以不必?fù)?dān)心新版本的資源不夠。如果新版本上線后出現(xiàn)嚴(yán)重的程序 BUG,那么我們只需將流量全部切回至舊版本,大大縮短故障恢復(fù)的時(shí)間。
Gateway實(shí)現(xiàn)灰度發(fā)布
本篇將文章將通過A/B測試方式實(shí)現(xiàn)灰度發(fā)布。接下來將展示在Spring Cloud Gateway中實(shí)現(xiàn)A/B測試核心組件。
- 引入依賴?
org.springframework.cloud
spring-cloud-starter-gateway
3.1.4
org.springframework.cloud
spring-cloud-loadbalancer
3.1.4
- 自定義負(fù)載均衡器
自定義負(fù)載均衡器作用是根據(jù)請求的header中的v進(jìn)行服務(wù)實(shí)例的篩選。?
public class GrayRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class);
final AtomicInteger position;
final String serviceId;
ObjectProvider serviceInstanceListSupplierProvider;
public GrayRoundRobinLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider,
String serviceId) {
this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000));
}
public GrayRoundRobinLoadBalancer(ObjectProvider serviceInstanceListSupplierProvider,
String serviceId, int seedPosition) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.position = new AtomicInteger(seedPosition);
}
@SuppressWarnings("rawtypes")
@Override
public Mono> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances, request));
}
@SuppressWarnings("rawtypes")
private Response processInstanceResponse(ServiceInstanceListSupplier supplier,
List serviceInstances, Request request) {
Response serviceInstanceResponse = getInstanceResponse(serviceInstances, request);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
@SuppressWarnings("rawtypes")
private Response getInstanceResponse(List instances, Request request) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
List result = instances.stream().filter(instance -> {
Map metadata = instance.getMetadata();
Object orgId = metadata.get("v");
RequestDataContext context = (RequestDataContext) request.getContext() ;
RequestData requestData = context.getClientRequest() ;
String v = null ;
if (requestData instanceof GrayRequestData) {
GrayRequestData grayRequestData = (GrayRequestData) requestData ;
queryV = grayRequestData.getQueryParams().getFirst("v") ;
}
String value = requestData.getHeaders().getFirst("v") ;
return v != null && (v.equals(value) || v.equals(queryV)) ;
}).collect(Collectors.toList());
if (result.isEmpty()) {
result = instances;
}
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
ServiceInstance instance = result.get(pos % result.size());
return new DefaultResponse(instance);
}
} 以上負(fù)載均衡器將從header或者請求參數(shù)中獲取v參數(shù),然后根據(jù)v參數(shù)的值從服務(wù)實(shí)例列表中獲取metadata信息進(jìn)行比對。
全局過濾器
該過濾器的作用是通過上面的負(fù)載均衡器從其中選擇一個(gè)服務(wù)實(shí)例進(jìn)行服務(wù)的調(diào)用?
@SuppressWarnings({ "rawtypes", "unchecked" })
@Component
public class GrayReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {
private static final Log log = LogFactory.getLog(GrayReactiveLoadBalancerClientFilter.class);
/**
* Order of filter.
*/
public static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;
private final LoadBalancerClientFactory clientFactory;
private final GatewayLoadBalancerProperties properties;
public GrayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
GatewayLoadBalancerProperties properties) {
this.clientFactory = clientFactory;
this.properties = properties;
}
@Override
public int getOrder() {
return LOAD_BALANCER_CLIENT_FILTER_ORDER;
}
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
if (url == null || (!"packlb".equals(url.getScheme()) && !"packlb".equals(schemePrefix))) {
return chain.filter(exchange);
}
// preserve the original url
addOriginalRequestUrl(exchange, url);
URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
String serviceId = requestUri.getHost();
Set supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
DefaultRequest lbRequest = new DefaultRequest<>(
new RequestDataContext(new GrayRequestData(exchange.getRequest()), getHint(serviceId)));
LoadBalancerProperties loadBalancerProperties = clientFactory.getProperties(serviceId);
return choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext(response -> {
if (!response.hasServer()) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, response)));
throw NotFoundException.create(properties.isUse404(), "Unable to find instance for " + url.getHost());
}
ServiceInstance retrievedInstance = response.getServer();
URI uri = exchange.getRequest().getURI();
// if the `lb:` mechanism was used, use `` as the default,
// if the loadbalancer doesn't provide one.
String overrideScheme = retrievedInstance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(retrievedInstance,
overrideScheme);
URI requestUrl = reconstructURI(serviceInstance, uri);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
}
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, response);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, response));
}).then(chain.filter(exchange))
.doOnError(throwable -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext(
CompletionContext.Status.FAILED, throwable, lbRequest,
exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR)))))
.doOnSuccess(aVoid -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext(
CompletionContext.Status.SUCCESS, lbRequest,
exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR), buildResponseData(exchange,
loadBalancerProperties.isUseRawStatusCodeInResponseData())))));
}
@SuppressWarnings("deprecation")
private ResponseData buildResponseData(ServerWebExchange exchange, boolean useRawStatusCodes) {
if (useRawStatusCodes) {
return new ResponseData(new GrayRequestData(exchange.getRequest()), exchange.getResponse());
}
return new ResponseData(exchange.getResponse(), new RequestData(exchange.getRequest()));
}
protected URI reconstructURI(ServiceInstance serviceInstance, URI original) {
return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
}
private Mono> choose(Request lbRequest, String serviceId,
Set supportedLifecycleProcessors) {
ReactorLoadBalancer loadBalancer = this.clientFactory.getInstance(serviceId,
ReactorServiceInstanceLoadBalancer.class);
if (loadBalancer == null) {
throw new NotFoundException("No loadbalancer available for " + serviceId);
}
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
return loadBalancer.choose(lbRequest);
}
private String getHint(String serviceId) {
LoadBalancerProperties loadBalancerProperties = clientFactory.getProperties(serviceId);
Map hints = loadBalancerProperties.getHint();
String defaultHint = hints.getOrDefault("default", "default");
String hintPropertyValue = hints.get(serviceId);
return hintPropertyValue != null ? hintPropertyValue : defaultHint;
}
} 配置
@Configuration
public class GrayDefaultConfiguration {
@Bean
public GrayRoundRobinLoadBalancer grayRandomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new GrayRoundRobinLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
// 由于沒有使用服務(wù)注冊及發(fā)現(xiàn),這里通過編碼的方式定義服務(wù)實(shí)例
@Bean
public ServiceInstanceListSupplier sscServiceInstanceListSupplier() {
return new ServiceInstanceListSupplier() {
@Override
public Flux> get() {
Listinstances = new ArrayList<>() ;
Mapmetadata1 = new HashMap<>() ;
metadata1.put("v", "1") ;
ServiceInstance s1 = new DefaultServiceInstance("s1", "ssc", "localhost", 8088 , false, metadata1) ;
instances.add(s1) ;
Mapmetadata2 = new HashMap<>() ;
metadata2.put("v", "2") ;
ServiceInstance s2 = new DefaultServiceInstance("s2", "ssc", "localhost", 8099 , false, metadata2) ;
instances.add(s2) ;
return Flux.just(instances) ;
}
@Override
public String getServiceId() {
return "ssc" ;
}
};
}
}
負(fù)載均衡客戶端設(shè)置默認(rèn)的配置
@LoadBalancerClients(defaultConfiguration = GrayDefaultConfiguration.class)
public class SpringCloudGatewayApplication {
}
以上就是實(shí)現(xiàn)灰度發(fā)布的核心組件。
測試,設(shè)置不同的v返回不同服務(wù)的結(jié)果數(shù)據(jù)
完畢?。?!
本文標(biāo)題:SpringCloudGateway實(shí)現(xiàn)灰度發(fā)布實(shí)現(xiàn)原理
當(dāng)前地址:http://fisionsoft.com.cn/article/dpdjisi.html


咨詢
建站咨詢
