成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Spring Cloud Gateway實現灰度發布實現原理

開發 前端
灰度發布(又名金絲雀發布)是指在黑與白之間,能夠平滑過渡的一種發布方式。在其上可以進行A/B testing,即讓一部分用戶繼續用產品特性A,一部分用戶開始用產品特性B,如果用戶對B沒有什么反對意見,那么逐步擴大范圍,把所有用戶都遷移到B上面來。

什么是灰度發布

灰度發布(又名金絲雀發布)是指在黑與白之間,能夠平滑過渡的一種發布方式。在其上可以進行A/B testing,即讓一部分用戶繼續用產品特性A,一部分用戶開始用產品特性B,如果用戶對B沒有什么反對意見,那么逐步擴大范圍,把所有用戶都遷移到B上面來。灰度發布可以保證整體系統的穩定,在初始灰度的時候就可以發現、調整問題,以保證其影響度。

灰度發布類型

  • 金絲雀發布

將少量的請求引流到新版本上,因此部署新版本服務只需極小數的機器。驗證新版本符合預期后,逐步調整流量權重比例,使得流量慢慢從老版本遷移至新版本,期間可以根據設置的流量比例,對新版本服務進行擴容,同時對老版本服務進行縮容,使得底層資源得到最大化利用。

圖片


金絲雀發布的優點:

  1. 按比例將流量無差別地導向新版本,新版本故障影響范圍小;
  2. 發布期間逐步對新版本擴容,同時對老版本縮容,資源利用率高。

金絲雀發布的缺點:

  1. 流量無差別地導向新版本,可能會影響重要用戶的體驗;
  2. 發布周期長。

  • A/B測試

A/B 測試基于用戶請求的元信息將流量路由到新版本,這是一種基于請求內容匹配的灰度發布策略。只有匹配特定規則的請求才會被引流到新版本,常見的做法包括基于 Header 和 Cookie。基于 Header 方式例子,例如 User-Agent 的值為 Android 的請求 (來自安卓系統的請求)可以訪問新版本,其他系統仍然訪問舊版本。基于 Cookie 方式的例子,Cookie 中通常包含具有業務語義的用戶信息,例如普通用戶可以訪問新版本,VIP 用戶仍然訪問舊版本。

圖片


  • 藍綠發布

藍綠發布需要對服務的新版本進行冗余部署,一般新版本的機器規格和數量與舊版本保持一致,相當于該服務有兩套完全相同的部署環境,只不過此時只有舊版本在對外提供服務,新版本作為熱備。當服務進行版本升級時,我們只需將流量全部切換到新版本即可,舊版本作為熱備。由于冗余部署的緣故,所以不必擔心新版本的資源不夠。如果新版本上線后出現嚴重的程序 BUG,那么我們只需將流量全部切回至舊版本,大大縮短故障恢復的時間。

圖片

Gateway實現灰度發布

本篇將文章將通過A/B測試方式實現灰度發布。接下來將展示在Spring Cloud Gateway中實現A/B測試核心組件。

  • 引入依賴?
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>3.1.4</version>
</dependency>
  • 自定義負載均衡器

自定義負載均衡器作用是根據請求的header中的v進行服務實例的篩選。?

public class GrayRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {


private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class);


final AtomicInteger position;


final String serviceId;


ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;


public GrayRoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId) {
this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000));
}


public GrayRoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId, int seedPosition) {
this.serviceId = serviceId;
this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
this.position = new AtomicInteger(seedPosition);
}


@SuppressWarnings("rawtypes")
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances, request));
}


@SuppressWarnings("rawtypes")
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances, Request request) {
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances, request);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}


@SuppressWarnings("rawtypes")
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, Request request) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
List<ServiceInstance> result = instances.stream().filter(instance -> {
Map<String, String> 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);
}


}

以上負載均衡器將從header或者請求參數中獲取v參數,然后根據v參數的值從服務實例列表中獲取metadata信息進行比對。

全局過濾器

該過濾器的作用是通過上面的負載均衡器從其中選擇一個服務實例進行服務的調用?

@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<Void> 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<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
.getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
RequestDataContext.class, ResponseData.class, ServiceInstance.class);
DefaultRequest<RequestDataContext> 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:<scheme>` mechanism was used, use `<scheme>` 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<ResponseData, ServiceInstance, RequestDataContext>(
CompletionContext.Status.FAILED, throwable, lbRequest,
exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR)))))
.doOnSuccess(aVoid -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
.onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(
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<Response<ServiceInstance>> choose(Request<RequestDataContext> lbRequest, String serviceId,
Set<LoadBalancerLifecycle> supportedLifecycleProcessors) {
ReactorLoadBalancer<ServiceInstance> 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<String, String> 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);
}

// 由于沒有使用服務注冊及發現,這里通過編碼的方式定義服務實例
@Bean
public ServiceInstanceListSupplier sscServiceInstanceListSupplier() {
return new ServiceInstanceListSupplier() {
@Override
public Flux<List<ServiceInstance>> get() {
List<ServiceInstance> instances = new ArrayList<>() ;
Map<String, String> metadata1 = new HashMap<>() ;
metadata1.put("v", "1") ;
ServiceInstance s1 = new DefaultServiceInstance("s1", "ssc", "localhost", 8088 , false, metadata1) ;
instances.add(s1) ;

Map<String, String> metadata2 = 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" ;
}
};
}

}

負載均衡客戶端設置默認的配置

@LoadBalancerClients(defaultConfiguration = GrayDefaultConfiguration.class)
public class SpringCloudGatewayApplication {
}

以上就是實現灰度發布的核心組件。

圖片

測試,設置不同的v返回不同服務的結果數據

完畢!!!

責任編輯:武曉燕 來源: 實戰案例錦集
相關推薦

2024-01-29 08:00:00

架構微服務開發

2019-08-22 09:55:17

RedisAPI數據

2024-05-13 18:35:06

負載均衡主機端口

2024-12-16 13:34:35

2023-02-28 08:57:06

Spring上下線緩存

2025-03-04 08:53:10

2025-06-09 01:01:00

2022-08-15 09:22:12

JWT認證系統

2021-12-27 15:01:21

KubernetesLinux命令

2023-07-24 08:00:56

客戶端訪問指定

2022-01-07 07:29:08

Rbac權限模型

2022-08-02 08:32:21

Spring項目網關

2023-09-15 08:18:49

cookie網關代理

2023-02-13 08:10:40

Gateway網關Spring

2023-07-13 09:01:39

Bean接口容器

2023-03-08 09:03:55

2022-02-15 14:22:46

灰度發布互聯網業務

2021-06-04 08:48:46

Spring ClouMaven Centr版本

2023-01-26 01:41:27

核心全局過濾器

2021-01-14 07:54:19

Spring Clou應用路由
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 人妖av| 成人av在线网站 | 亚洲国产aⅴ成人精品无吗 欧美激情欧美激情在线五月 | 天天操天天摸天天爽 | 日本精品在线观看 | 日本 欧美 国产 | 美女一级毛片 | 国产91在线播放 | 国产高清精品一区二区三区 | 成人在线免费电影 | 欧美一级二级视频 | 99re6在线| 中文字字幕在线中文乱码范文 | 97人人澡人人爽91综合色 | 成人妇女免费播放久久久 | 日韩精品一区二区三区四区视频 | 欧美精品一区在线 | 综合中文字幕 | 人人干在线视频 | 一本大道久久a久久精二百 欧洲一区二区三区 | 国产在线h| 九九精品在线 | 农村黄性色生活片 | 国产玖玖 | 91精品福利 | 精品国产免费人成在线观看 | 日韩在线免费观看视频 | jlzzjlzz国产精品久久 | 久草在线影 | 天天色图 | 日韩精品一区二区三区中文在线 | 午夜无码国产理论在线 | 在线小视频 | 欧美一级二级视频 | 欧美在线成人影院 | 国产精品欧美一区二区三区不卡 | 日韩一区二区三区在线观看 | 狠狠天天 | 精品视频在线播放 | 中文字幕在线精品 | 黄色网址在线免费观看 |