灰度發(fā)布--spring-cloud-gateway動態(tài)路由實現(xiàn)

架構(gòu)圖

image.png

配置

image.png

api 網(wǎng)關(guān)

主要依賴

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.2.3.RELEASE</version>
        </dependency>

自定義局部過濾器

public class ABGatewayFilterFactory extends AbstractGatewayFilterFactory<ABGatewayFilterFactory.Config>{

    //必須將配置類傳遞給超類
    public ABGatewayFilterFactory(){
        super(Config.class);
    }
  • 類名必須是xxGatewayFilterFactory,其中xx用于配置
  • 必須注冊為bean
  • 必須在構(gòu)造方法中將配置參數(shù)類型傳遞給父類
  • 必須顯示聲明配置參數(shù)的順序,實現(xiàn)shortcutFieldOrder()
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("hosts","weights");
    }

  • 必須實現(xiàn)public GatewayFilter apply(Config config)方法

灰度發(fā)布局部過濾器實現(xiàn)

@Component
@Slf4j
public class ABGatewayFilterFactory extends AbstractGatewayFilterFactory<ABGatewayFilterFactory.Config>{

    //必須將配置類傳遞給超類
    public ABGatewayFilterFactory(){
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new ABGatewayFilter(config);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("hosts","weights");
    }

    /**
     * 客戶端鏈接過濾器
     */
    private static class ABGatewayFilter implements GatewayFilter, Ordered{
        private List<Map.Entry<String, Integer>> list;
        private Map<String, Integer> weight;

        public ABGatewayFilter(Config config){
            String[] hosts = config.getHosts().split("[|]");
            String[] weights = config.getWeights().split("[|]");

            this.weight = new HashMap<>();
            for (int i = 0; i < hosts.length; i++) {
                weight.put(hosts[i].trim(),Integer.parseInt(weights[i].trim()));
            }

            if (weight.values().stream().mapToInt(e -> e).sum() != 100) {
                //這里不能用浮點數(shù),浮點數(shù)不是精確計算
                throw new RuntimeException("權(quán)重分布有誤,所有權(quán)重總和應該為100");
            }

            //排序
            this.list = weight.entrySet().stream()
                    .sorted((a, b) -> a.getValue() - b.getValue())
                    .collect(Collectors.toList());

            //分段
            for (int i = 0; i < this.list.size() -1; i++) {
                int sum = 0;
                for (int j = 0; j < i; j++) {
                    sum += this.list.get(j).getValue();
                }
                this.list.get(i).setValue(this.list.get(i).getValue() + sum);
            }
        }

        @SneakyThrows
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            String host = getHost();

            ServerHttpRequest request = exchange.getRequest();
            URI uri = request.getURI();
            addOriginalRequestUrl(exchange, uri);

            URI newUri = URI.create(host + uri.getRawPath());
            ServerHttpRequest newRequest = request.mutate().uri(newUri).build();

            Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);

            //動態(tài)設(shè)置路由,選擇服務器
            Route newRout = Route.async()
                    .asyncPredicate(route.getPredicate())
                    .id(route.getId())
                    .order(route.getOrder())
                    .uri(newUri)
                    .build();

            exchange.getAttributes().put(GATEWAY_ROUTE_ATTR,newRout);
            return chain.filter(exchange.mutate().request(newRequest).build());
        }

        private String getHost() {
            int random = (int) (100 * Math.random());
            for (int i = 0; i < list.size() - 1; i++) {
                if (list.get(i).getValue() > random) {
                    return list.get(i).getKey();
                }
            }

            return list.get(list.size() - 1).getKey();
        }

        public static void addOriginalRequestUrl(ServerWebExchange exchange, URI url) {
            exchange.getAttributes().computeIfAbsent(GATEWAY_ORIGINAL_REQUEST_URL_ATTR,
                    s -> new LinkedHashSet<>());
            LinkedHashSet<URI> uris = exchange
                    .getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
            uris.add(url);
        }


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

    /**
     * - AB=http://localhost:11057|http://localhost:11058|http://localhost:11059,1|10|89
     */
    @Data
    public static class Config{
        private String hosts;
        private String weights;
    }
}

靜態(tài)配置灰度路由

      routes:
        - id: gray
          #目標服務器
          uri: http://localhost:11600
          predicates:
            - Header=gray,true
          filters:
            #裁剪一級目錄前綴
#            - StripPrefix=1
            #灰度過濾器
            - AB=http://192.168.3.174:8888|http://192.168.3.174:8889,30|70

動態(tài)配置路由服務

@Service
public class DynamicRouteService implements ApplicationEventPublisherAware {

    @Resource
    private InMemoryRouteDefinitionRepository routeDefinitionWriter;

    private ApplicationEventPublisher publisher;

    private void notifyChanged() {
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }


    /**
     * 獲取路由列表
     * @return
     */
    public Flux<RouteDefinition> list(){
        return routeDefinitionWriter.getRouteDefinitions();
    }

    /**
     * 增加路由
     *
     */
    public String add(RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        notifyChanged();
        return "success";
    }


    /**
     * 更新路由
     */
    public String update(RouteDefinition definition) {
        try {
            this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
        } catch (Exception e) {
            return "update fail,not find route  routeId: " + definition.getId();
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            notifyChanged();
            return "success";
        } catch (Exception e) {
            return "update route  fail";
        }


    }

    /**
     * 刪除路由
     *
     */
    public String delete(String id) {
        try {
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();

            notifyChanged();
            return "delete success";
        } catch (Exception e) {
            e.printStackTrace();
            return "delete fail";
        }

    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
}
注意:動態(tài)配置路由存在于InMemoryRouteDefinitionRepository中,不影響靜態(tài)配置的路由

全局過濾器示例

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

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("全局過濾器 filter");
        return chain.filter( exchange );
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末抵卫,一起剝皮案震驚了整個濱河市狮荔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌介粘,老刑警劉巖殖氏,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異姻采,居然都是意外死亡雅采,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來婚瓜,“玉大人宝鼓,你說我怎么就攤上這事“涂蹋” “怎么了愚铡?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長胡陪。 經(jīng)常有香客問我沥寥,道長,這世上最難降的妖魔是什么柠座? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任邑雅,我火速辦了婚禮,結(jié)果婚禮上妈经,老公的妹妹穿的比我還像新娘淮野。我一直安慰自己,他們只是感情好狂塘,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布录煤。 她就那樣靜靜地躺著,像睡著了一般荞胡。 火紅的嫁衣襯著肌膚如雪妈踊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天泪漂,我揣著相機與錄音廊营,去河邊找鬼。 笑死萝勤,一個胖子當著我的面吹牛露筒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播敌卓,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼慎式,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了趟径?” 一聲冷哼從身側(cè)響起瘪吏,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜗巧,沒想到半個月后掌眠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡幕屹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年蓝丙,在試婚紗的時候發(fā)現(xiàn)自己被綠了级遭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡渺尘,死狀恐怖挫鸽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沧烈,我是刑警寧澤掠兄,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站锌雀,受9級特大地震影響蚂夕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜腋逆,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一婿牍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惩歉,春花似錦等脂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至争涌,卻和暖如春粉楚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亮垫。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工模软, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饮潦。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓燃异,卻偏偏與公主長得像,于是被迫代替她去往敵國和親继蜡。 傳聞我的和親對象是個殘疾皇子回俐,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344