最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Cloud Gateway實(shí)現(xiàn)灰度發(fā)布方案

 更新時(shí)間:2023年12月21日 14:42:16   作者:公眾號(hào)-WU雙  
灰度發(fā)布是在微服務(wù)中的表現(xiàn)為同一服務(wù)同時(shí)上線不同版本,讓一部分用戶使用新版本來驗(yàn)證新特性,如果驗(yàn)證沒有問題,則將所有用戶都遷移到新版本上,本文就來介紹一下如何實(shí)現(xiàn),感興趣的可以了解一下

灰度發(fā)布又名金絲雀發(fā)布,在微服務(wù)中的表現(xiàn)為同一服務(wù)同時(shí)上線不同版本,讓一部分用戶使用新版本來驗(yàn)證新特性,如果驗(yàn)證沒有問題,則將所有用戶都遷移到新版本上。

在微服務(wù)架構(gòu)中,網(wǎng)關(guān)負(fù)責(zé)請(qǐng)求的統(tǒng)一入口,主要功能之一是請(qǐng)求路由。而灰度發(fā)布實(shí)質(zhì)就是讓指定用戶路由到指定版本的服務(wù)上。所以該功能可以在網(wǎng)關(guān)這一層實(shí)現(xiàn)。

今天就分享下Spring Cloud Gateway如何實(shí)現(xiàn)灰度發(fā)布。

1 Spring Cloud Gateway的路由邏輯

既然要讓指定用戶路由到指定服務(wù)版本,我們需要先了解Spring Cloud Gateway的路由邏輯。

Spring Cloud Gateway通過Predicate來匹配路由。

  - id: user-route
      uri: lb://user-login
      predicates:
        - Path=/user/**

上述路由規(guī)則表示只要請(qǐng)求URL符合/user/**則都會(huì)匹配到user-route這條路由規(guī)則中。(根據(jù)Predicate尋找路由匹配規(guī)則的源碼在RoutePredicateHandlerMapping#lookupRoute方法中)。

那么要實(shí)現(xiàn)灰度發(fā)布該怎么做?我們這里可以自己寫一個(gè)Predicate,來實(shí)現(xiàn)指定用戶匹配到指定的路由規(guī)則當(dāng)中。假設(shè)我們自己寫的Predicate叫HeaderUserNameRoutePredicateFactory(相應(yīng)源碼在文后),相應(yīng)的配置如下:

  - id: user-route-gray
      uri: lb://user-login
      predicates:
        - Path=/user/**
        - HeaderUsername=Jack

上述路由規(guī)則表示請(qǐng)求URL符合/user/**并且請(qǐng)求的HTTP Header中的Username屬性值為Jack則會(huì)匹配到user-route-gray這條路由規(guī)則中。

實(shí)現(xiàn)了指定用戶匹配到指定規(guī)則只是第一步,下一步要實(shí)現(xiàn)的是如何讓指定用戶路由到指定版本的服務(wù)中,想要實(shí)現(xiàn)這一點(diǎn),就需要先了解Spring Cloud Gateway的負(fù)載均衡邏輯,也就是Spring Cloud Gateway是如何選取要調(diào)用的服務(wù)的。

2 Spring Cloud Gateway的負(fù)載均衡邏輯

負(fù)載均衡的邏輯如下:

1、 從注冊中心獲取服務(wù)實(shí)例列表(實(shí)際實(shí)現(xiàn)中服務(wù)實(shí)例列表是后臺(tái)定時(shí)刷新緩存在內(nèi)存中的);

2、根據(jù)負(fù)載均衡算法從實(shí)例列表中選取服務(wù)。

在Spring Cloud Gateway中,相應(yīng)的代碼在ReactiveLoadBalancerClientFilter#choose方法中。

默認(rèn)情況下,Spring Cloud Gateway負(fù)載均衡策略會(huì)從注冊中心所有服務(wù)實(shí)例中輪詢選擇一個(gè)服務(wù)實(shí)例。由此可以看出,默認(rèn)實(shí)現(xiàn)無法滿足我們的需求,因?yàn)槲覀兿胍囟ㄓ脩袈酚傻教囟ǖ姆?wù)版本上。

那么該如何解決呢?答案是重寫負(fù)載均衡算法,來實(shí)現(xiàn)選擇特定版本的服務(wù)實(shí)例功能。

3 版本號(hào)如何指定

灰度發(fā)布的目的是實(shí)現(xiàn)指定用戶訪問指定版本,用戶信息可以在HTTP Header中帶過來,那么版本號(hào)如何指定?

這里有兩種方案。

第一種方案也是通過請(qǐng)求的HTTP Header帶過來,缺點(diǎn)是需要客戶端修改;

第二種方案是在網(wǎng)關(guān)層修改請(qǐng)求,動(dòng)態(tài)為請(qǐng)求加上版本號(hào)信息,此方案較好,對(duì)客戶端透明。

4 灰度發(fā)布的實(shí)現(xiàn)

看到這里,整個(gè)灰度發(fā)布的實(shí)現(xiàn)思路應(yīng)該比較清晰了。

1、首先編寫自己的Predicate,實(shí)現(xiàn)指定用戶匹配到指定的路由規(guī)則中;

2、動(dòng)態(tài)修改請(qǐng)求,添加版本號(hào)信息,版本號(hào)信息可以放在HTTP Header中(此處可以通過原生AddRequestHeaderGatewayFilterFactory來實(shí)現(xiàn),無需自己寫代碼);

3、重寫負(fù)載均衡算法,根據(jù)版本號(hào)信息從注冊中心的服務(wù)實(shí)例上選擇相應(yīng)的服務(wù)版本進(jìn)行請(qǐng)求的轉(zhuǎn)發(fā)。

思路如上,下面附上關(guān)鍵代碼:

自定義HeaderUsernameRoutePredicateFactory源碼如下:

@Component
public class HeaderUsernameRoutePredicateFactory extends AbstractRoutePredicateFactory<HeaderUsernameRoutePredicateFactory.Config> {

    public static final String USERNAME = "Username";

    public HeaderUsernameRoutePredicateFactory() {
        super(Config.class);
    }

    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.GATHER_LIST;
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("username");
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        List<String> usernames = config.getUsername();
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange serverWebExchange) {
                String username = serverWebExchange.getRequest().getHeaders().getFirst(USERNAME);
                if (!StringUtils.isEmpty(username)) {
                    return usernames.contains(username);
                }
                return false;
            }

            @Override
            public String toString() {
                return String.format("Header: Username=%s", config.getUsername());
            }
        };
    }

    @NoArgsConstructor
    @Getter
    @Setter
    @ToString
    public static class Config {
        private List<String> username;
    }
}

自定義負(fù)載均衡算法GrayRoundRobinLoadBalancer如下:

@Slf4j
public class GrayRoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {

	private static final position = new AtomicInteger(new Random().nextInt(1000));
    private final ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
    private final String serviceId;
    private final AtomicInteger position;

    public GrayRoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        HttpHeaders headers = (HttpHeaders) request.getContext();
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next().map(list -> getInstanceResponse(list, headers));
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, HttpHeaders headers) {
        List<ServiceInstance> serviceInstances = instances.stream()
                .filter(instance -> {
                    //根據(jù)請(qǐng)求頭中的版本號(hào)信息,選取注冊中心中的相應(yīng)服務(wù)實(shí)例
                    String version = headers.getFirst("Version");
                    if (version != null) {
                        return version.equals(instance.getMetadata().get("version"));
                    } else {
                        return true;
                    }
                }).collect(Collectors.toList());
        if (serviceInstances.isEmpty()) {
            if (log.isWarnEnabled()) {
                log.warn("No servers available for service: " + serviceId);
            }
            return new EmptyResponse();
        }
        int pos = Math.abs(this.position.incrementAndGet());
        ServiceInstance instance = serviceInstances.get(pos % serviceInstances.size());
        return new DefaultResponse(instance);
    }
}

自定義GrayReactiveLoadBalancerClientFilter,調(diào)用自定義的負(fù)責(zé)均衡算法:

@Slf4j
@Component
public class GrayReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered {

    private static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150;

    private final LoadBalancerClientFactory clientFactory;

    public GrayReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        URI url = (URI) exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        String schemePrefix = (String) exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
        if (url != null && ("grayLb".equals(url.getScheme()) || "grayLb".equals(schemePrefix))) {
            ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
            if (log.isTraceEnabled()) {
                log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);
            }
            return this.choose(exchange).doOnNext((response) -> {
                if (!response.hasServer()) {
                    throw NotFoundException.create(true, "Unable to find instance for " + url.getHost());
                } else {
                    URI uri = exchange.getRequest().getURI();
                    String overrideScheme = null;
                    if (schemePrefix != null) {
                        overrideScheme = url.getScheme();
                    }

                    DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance((ServiceInstance) response.getServer(), overrideScheme);
                    URI requestUrl = this.reconstructURI(serviceInstance, uri);
                    if (log.isTraceEnabled()) {
                        log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
                    }

                    exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
                }
            }).then(chain.filter(exchange));
        } else {
            return chain.filter(exchange);
        }
    }

    private Mono<Response<ServiceInstance>> choose(ServerWebExchange exchange) {
        URI uri = (URI) exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        GrayRoundRobinLoadBalancer loadBalancer = new GrayRoundRobinLoadBalancer(clientFactory.getLazyProvider(uri.getHost(), ServiceInstanceListSupplier.class), uri.getHost());
        return loadBalancer.choose(this.createRequest(exchange));
    }

    private Request createRequest(ServerWebExchange exchange) {
        HttpHeaders headers = exchange.getRequest().getHeaders();
        return new DefaultRequest<>(headers);
    }

    protected URI reconstructURI(ServiceInstance serviceInstance, URI original) {
        return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
    }

    @Override
    public int getOrder() {
        return LOAD_BALANCER_CLIENT_FILTER_ORDER;
    }
}

最后的路由規(guī)則配置如下,表示用戶Jack走V2版本,其他用戶走V1版本:

 - id: user-route-gray
      uri: grayLb://user-login
      predicates:
        - Path=/user/**
        - HeaderUsername=Jack
      filters:
        - AddRequestHeader=Version,v2
 - id: user-route
      uri: grayLb://user-login
      predicates:
        - Path=/user/**
      filters:
        - AddRequestHeader=Version,v1

寫在最后

微服務(wù)中的灰度發(fā)布功能如上所述,相比實(shí)現(xiàn),思路是大家更需要關(guān)注的地方。思路清晰了,即使換個(gè)網(wǎng)關(guān)實(shí)現(xiàn),換個(gè)注冊中心實(shí)現(xiàn),都是一樣的。

灰度發(fā)布實(shí)質(zhì)是讓指定用戶訪問指定版本的服務(wù)。

所以首先需要指定用戶匹配到指定的路由規(guī)則。

其次,服務(wù)的版本號(hào)信息可以通過HTTP請(qǐng)求頭字段來指定。

最后,負(fù)載均衡算法需要能夠根據(jù)版本號(hào)信息來做服務(wù)實(shí)例的選擇。

到此這篇關(guān)于Spring Cloud Gateway實(shí)現(xiàn)灰度發(fā)布方案的文章就介紹到這了,更多相關(guān)Spring Cloud Gateway灰度發(fā)布內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot RestTemplate GET POST請(qǐng)求的實(shí)例講解

    SpringBoot RestTemplate GET POST請(qǐng)求的實(shí)例講解

    這篇文章主要介紹了SpringBoot RestTemplate GET POST請(qǐng)求的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Java安全-ClassLoader

    Java安全-ClassLoader

    這篇文章主要介紹了Java安全ClassLoader,Java類初始化的時(shí)候會(huì)調(diào)用java.lang.ClassLoader加載字節(jié)碼,ClassLoader就是用來動(dòng)態(tài)加載class文件到內(nèi)存當(dāng)中用的,下面詳細(xì)內(nèi)容,需要的小伙伴可以參考一下
    2022-01-01
  • SpringMVC注解全攻略之從請(qǐng)求到響應(yīng)的過程詳解

    SpringMVC注解全攻略之從請(qǐng)求到響應(yīng)的過程詳解

    這篇文章主要介紹了SpringMVC的注解使用,包括建立連接、請(qǐng)求、響應(yīng)等方面的詳細(xì)內(nèi)容,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2026-03-03
  • Java C++題解leetcode816模糊坐標(biāo)示例

    Java C++題解leetcode816模糊坐標(biāo)示例

    這篇文章主要為大家介紹了Java C++題解leetcode816模糊坐標(biāo)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • Java超詳細(xì)講解多態(tài)的調(diào)用

    Java超詳細(xì)講解多態(tài)的調(diào)用

    多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時(shí)并不確定,而是在程序運(yùn)行期間才確定,即一個(gè)引用變量到底會(huì)指向哪個(gè)類的實(shí)例對(duì)象,該引用變量發(fā)出的方法調(diào)用到底是哪個(gè)類中實(shí)現(xiàn)的方法,必須在由程序運(yùn)行期間才能決定
    2022-05-05
  • SpringBoot讀取資源目錄中JSON文件的方法實(shí)例

    SpringBoot讀取資源目錄中JSON文件的方法實(shí)例

    最近做項(xiàng)目遇到需要將json類型的配置文件引用到項(xiàng)目中,已經(jīng)將讀取json文件的方法封裝成工具類,下面這篇文章主要給大家介紹了關(guān)于SpringBoot讀取資源目錄中JSON文件的相關(guān)資料,需要的朋友可以參考下
    2023-04-04
  • Java數(shù)據(jù)結(jié)構(gòu)之鏈表相關(guān)知識(shí)總結(jié)

    Java數(shù)據(jù)結(jié)構(gòu)之鏈表相關(guān)知識(shí)總結(jié)

    今天給大家?guī)黻P(guān)于Java數(shù)據(jù)結(jié)構(gòu)的相關(guān)知識(shí),文章圍繞Java鏈表展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • Java解決青蛙跳臺(tái)階問題流程

    Java解決青蛙跳臺(tái)階問題流程

    所謂的青蛙跳臺(tái)階問題,就是指一只青蛙一次可以跳上1級(jí)臺(tái)階,也可以跳上2級(jí)。求該青蛙跳上一個(gè)n級(jí)的臺(tái)階總共有多少種跳法。本文將用Java解決這一問題,需要的可以參考一下
    2022-03-03
  • Java基礎(chǔ)學(xué)習(xí)之方法的重載知識(shí)總結(jié)

    Java基礎(chǔ)學(xué)習(xí)之方法的重載知識(shí)總結(jié)

    今天帶大家來回顧Java基礎(chǔ)知識(shí),文中對(duì)Java方法的重載相關(guān)知識(shí)作了非常詳細(xì)的介紹,對(duì)正在學(xué)習(xí)java的小伙伴們有很好的幫助,需要的朋友可以參考下
    2021-05-05
  • Java中關(guān)鍵字synchronized的使用方法詳解

    Java中關(guān)鍵字synchronized的使用方法詳解

    synchronized關(guān)鍵字可以作為函數(shù)的修飾符,也可作為函數(shù)內(nèi)的語句,也就是平時(shí)說的同步方法和同步語句塊,下面這篇文章主要給大家介紹了關(guān)于Java中synchronized使用的相關(guān)資料,需要的朋友可以參考下
    2021-08-08

最新評(píng)論

旬阳县| 鸡西市| 裕民县| 临高县| 逊克县| 丽水市| 涡阳县| 阜阳市| 德惠市| 门头沟区| 长兴县| 广州市| 望城县| 庆安县| 西乌| 稻城县| 宜宾市| 汉川市| 五寨县| 灵山县| 和顺县| 万州区| 通榆县| 蒙自县| 枝江市| 措美县| 贵德县| 淮滨县| 毕节市| 土默特左旗| 佳木斯市| 雷山县| 疏附县| 石景山区| 贵港市| 黑水县| 自治县| 岚皋县| 博罗县| 清镇市| 衡东县|