Spring Cloud Gateway動(dòng)態(tài)路由實(shí)現(xiàn)

Gateway上線部署分析

當(dāng)你的網(wǎng)關(guān)程序開發(fā)完成之后沮趣,需要部署到生產(chǎn)環(huán)境,這個(gè)時(shí)候你的程序不能是單點(diǎn)運(yùn)行的塔猾,肯定是多節(jié)點(diǎn)啟動(dòng)(獨(dú)立部署或者docker等容器部署)耻瑟,防止單節(jié)點(diǎn)故障導(dǎo)致整個(gè)服務(wù)不能訪問(wèn),網(wǎng)關(guān)是對(duì)客戶端的入口與出口厦取,在生產(chǎn)運(yùn)行中極為重要潮太,哪怕是簡(jiǎn)單的重啟也會(huì)導(dǎo)致部分請(qǐng)求的丟失。

網(wǎng)關(guān)的路由配置這個(gè)時(shí)候就是一個(gè)大問(wèn)題虾攻,是代碼里面編寫還是配置文件配置铡买?他們都有一個(gè)致命的缺點(diǎn),當(dāng)有新的程序需要接入到網(wǎng)關(guān)進(jìn)行路由或者有服務(wù)需要下線時(shí)候需要修改代碼或者配置霎箍,然后重啟整個(gè)網(wǎng)關(guān)程序奇钞,導(dǎo)致其他正常的服務(wù)路由受到了影響。各個(gè)網(wǎng)關(guān)是否都進(jìn)行了配置更新漂坏?又如何查看當(dāng)前有哪些配置呢景埃?

Spring Boot Admin對(duì)Gateway的支持

Spring Boot Admin是一個(gè)管理和監(jiān)控Spring Boot應(yīng)用程序的開源軟件媒至。它與應(yīng)用中的Spring Boot Actuator的無(wú)縫對(duì)接,提供了方便的管理界面直接管理應(yīng)用程序谷徙,支持客戶端直連模式與注冊(cè)中心配置模式拒啰。本文暫不介紹Spring Boot Admin的相關(guān)配置,會(huì)在其他的文檔中單獨(dú)講解完慧。
Spring Boot Admin很好的支持了Gateway谋旦,可以直接在管理界面中查看相關(guān)的路由配置,添加或者刪除屈尼。


路由列表
添加路由

為什么Spring Boot Admin程序中能有這些功能册着,是因?yàn)镚ateway提供了相應(yīng)的Actuator Endpoint接口來(lái)管理路由配置,那又為什么不用呢脾歧?下面一步一步分析

Gateway提供的Actuator接口

接口列表

官方默認(rèn)提供了這些接口進(jìn)行網(wǎng)關(guān)的管理甲捏,例如獲取所有的路由:
GET http://ip:port/actuator/gateway/routes

問(wèn)題分析

在Spring Boot Admin的管理平臺(tái)中刪除路由,會(huì)發(fā)現(xiàn)刪除失敗鞭执,添加的成功后路由配置又是存放到了哪里呢司顿?配置文件?
如果添加的路由配置不能夠落地蚕冬,就會(huì)在網(wǎng)關(guān)重啟之后丟失免猾,這樣明顯沒法實(shí)現(xiàn)穩(wěn)定的動(dòng)態(tài)路由。

Spring Gateway Actuator源碼分析

在GatewayControllerEndpoint類中囤热,定義了相關(guān)的api猎提,比如新增或者刪除

@PostMapping("/routes/{id}")
@SuppressWarnings("unchecked")
public Mono<ResponseEntity<Void>> save(@PathVariable String id,
        @RequestBody Mono<RouteDefinition> route) {
    return this.routeDefinitionWriter.save(route.map(r -> {
        r.setId(id);
        log.debug("Saving route: " + route);
        return r;
    })).then(Mono.defer(() -> Mono
            .just(ResponseEntity.created(URI.create("/routes/" + id)).build())));
}

@DeleteMapping("/routes/{id}")
public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
    return this.routeDefinitionWriter.delete(Mono.just(id))
            .then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
            .onErrorResume(t -> t instanceof NotFoundException,
                    t -> Mono.just(ResponseEntity.notFound().build()));
}

這里面的核心是routeDefinitionWriter這個(gè)對(duì)象,他是一個(gè)RouteDefinitionWriter接口的對(duì)象旁蔼,RouteDefinitionWriter唯一的繼承是RouteDefinitionRepository類锨苏,RouteDefinitionRepository唯一的繼承是InMemoryRouteDefinitionRepository,在InMemoryRouteDefinitionRepository中有這樣的一段代碼

//創(chuàng)建了一個(gè)以路由id為key的路由存儲(chǔ)Map
private final Map<String, RouteDefinition> routes = synchronizedMap(
            new LinkedHashMap<String, RouteDefinition>());

在GatewayAutoConfiguration自動(dòng)配置類中棺聊,有這樣的一段代碼伞租,就是routeDefinitionWriter的申明。

@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
    return new InMemoryRouteDefinitionRepository();
}

原來(lái)我們通過(guò)actuator接口在新增或者刪除路由配置的時(shí)候限佩,都是對(duì)routeDefinitionWriter對(duì)象中的routes這個(gè)Map進(jìn)行操作葵诈。
為什么我們能看到在配置文件中配置的路由,但是又刪除不了呢祟同?如果你仔細(xì)的閱讀源碼作喘,你會(huì)發(fā)現(xiàn)/actuator/gateway/routes這個(gè)接口獲取的是routeDefinitionLocator中的路由配置,routeDefinitionLocator的類型是CompositeRouteDefinitionLocator晕城,并且他的邏輯是把其他的所有RouteDefinitionLocator類型的都包含進(jìn)去了泞坦,讀取接口/actuator/gateway/routes時(shí),你獲取的是整個(gè)系統(tǒng)的全部路由配置砖顷。


routes中的routeDefinitionLocator
RouteDefinitionLocator子類
@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(
        List<RouteDefinitionLocator> routeDefinitionLocators) {
    return new CompositeRouteDefinitionLocator(
            Flux.fromIterable(routeDefinitionLocators));
}

根據(jù)上面的分析贰锁,我們現(xiàn)在有幾個(gè)問(wèn)題需要處理
1赃梧、增加的路由配置是保存在內(nèi)存中的,我們沒有辦法保存它
2豌熄、刪除只能刪除通過(guò)接口增加的路由配置授嘀,配置文件中定義的不能刪除

自定義路由配置存儲(chǔ)

我們需要自定義自己的路由存儲(chǔ),統(tǒng)一管理房轿,全部路由配置都放在一起粤攒,除了一個(gè)默認(rèn)的路由用于最后的默認(rèn)攔截(其他路由斷言匹配不上的統(tǒng)一走默認(rèn)的格式返回)

你可以將你的路由配置放到數(shù)據(jù)庫(kù)所森、mongo囱持、redis等等你方便的地方,這里我已文件系統(tǒng)為例介紹如何自定義路由配置存儲(chǔ)焕济。

@Component
public class FileRouteDefinitionRepository implements RouteDefinitionRepository, ApplicationEventPublisherAware {
    private static final Logger LOGGER = LoggerFactory.getLogger(FileRouteDefinitionRepository.class);
    private ApplicationEventPublisher publisher;
    private List<RouteDefinition> routeDefinitionList = new ArrayList<>();

    @Value("${gateway.route.config.file}")
    private String file;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    @PostConstruct
    public void init() {
        load();
    }

    /**
     * 監(jiān)聽事件刷新配置
     */
    @EventListener
    public void listenEvent(RouteConfigRefreshEvent event) {
        load();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     * 加載
     */
    private void load() {
        try {
            String jsonStr = Files.lines(Paths.get(file)).collect(Collectors.joining());
            routeDefinitionList = JSON.parseArray(jsonStr, RouteDefinition.class);
            LOGGER.info("路由配置已加載,加載條數(shù):{}", routeDefinitionList.size());
        } catch (Exception e) {
            LOGGER.error("從文件加載路由配置異常", e);
        }
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return Mono.defer(() -> Mono.error(new NotFoundException("Unsupported operation")));
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return Mono.defer(() -> Mono.error(new NotFoundException("Unsupported operation")));
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        return Flux.fromIterable(routeDefinitionList);
    }
}

這里我們對(duì)于save與delete的操作都返回了拒絕的操作纷妆,因?yàn)槲覀兊穆酚膳渲檬墙y(tǒng)一的管理,同一份配置對(duì)應(yīng)的是n個(gè)Gateway節(jié)點(diǎn)晴弃,增刪需要額外的統(tǒng)一操作掩幢,對(duì)于路由的獲取根據(jù)Event事件加載,這是因?yàn)樾薷牧寺酚膳渲貌⒉皇切枰⒓窗l(fā)布到運(yùn)行環(huán)境中上鞠,可能還需要在某一個(gè)測(cè)試節(jié)點(diǎn)上驗(yàn)證過(guò)后在統(tǒng)一的進(jìn)行上線际邻。

新增的Actuator Endpoint,刷新路由的時(shí)候芍阎,先加載路由配置到內(nèi)存中世曾,然后再使用RefreshRoutesEvent事件刷新內(nèi)存中路由配置。

@Component
@RestControllerEndpoint(id = "demoGateway")
public class CustomGatewayControllerEndpoint implements ApplicationEventPublisherAware {
    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    @PostMapping("/refreshRouteConfig")
    public Mono<Void> refreshRoutes() {
        this.publisher.publishEvent(new RouteConfigRefreshEvent(this));
        return Mono.empty();
    }
}

官方文檔配置與json的轉(zhuǎn)換

[
  {
    "filters": [
      {
        "args": {
          "name": "hystrix",
          "fallbackUri": "forward:/hystrix"
        },
        "name": "Hystrix"
      },
      {
        "args": {},
        "name": "RateLimit"
      }
    ],
    "id": "DEMO_API_ROUTE",
    "order": 1,
    "predicates": [
      {
        "args": {
          "_genkey_0": "/demoApi/**"
        },
        "name": "Path"
      }
    ],
    "uri": "lb://demo-api"
  }
]

其實(shí)路由的定義就是RouteDefine對(duì)象的創(chuàng)建谴咸,根據(jù)json反序列化成一個(gè)對(duì)象即可
id 路由配置的id名字
uri 跳轉(zhuǎn)的地址轮听,lb://表示基于服務(wù)注冊(cè)的負(fù)載均衡
order 路由的順序,越小越先匹配
predicates 斷言列表岭佳,比如根據(jù)post并且path是什么開頭
filters 過(guò)濾器列表血巍,匹配后需要做的一些操作,比如增加一個(gè)請(qǐng)求頭字段

_genkey_0這個(gè)name很奇怪珊随,是因?yàn)楣俜皆诙x各種各樣的PredicateFactory時(shí)述寡,有些PredicateFactory并沒有字段名稱


genkey名字生成

其實(shí)這個(gè)算是官方的不規(guī)范

線上的推薦方案

路由配置已經(jīng)統(tǒng)一的進(jìn)行管理了,可能你放到穩(wěn)妥的數(shù)據(jù)庫(kù)中叶洞,你必須得有一個(gè)完善的管理界面來(lái)管理路由配置鲫凶,并且支持一鍵發(fā)布到所有節(jié)點(diǎn),在這之前你還需要讀取發(fā)布到一臺(tái)測(cè)試機(jī)驗(yàn)證所有的路由配置都是ok的京办,路由的配置存儲(chǔ)應(yīng)該加入版本控制掀序。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惭婿,隨后出現(xiàn)的幾起案子不恭,更是在濱河造成了極大的恐慌叶雹,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件换吧,死亡現(xiàn)場(chǎng)離奇詭異折晦,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)沾瓦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門满着,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人贯莺,你說(shuō)我怎么就攤上這事风喇。” “怎么了缕探?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵魂莫,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我爹耗,道長(zhǎng)耙考,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任潭兽,我火速辦了婚禮倦始,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘山卦。我一直安慰自己鞋邑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布怒坯。 她就那樣靜靜地躺著炫狱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪剔猿。 梳的紋絲不亂的頭發(fā)上视译,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音归敬,去河邊找鬼酷含。 笑死,一個(gè)胖子當(dāng)著我的面吹牛汪茧,可吹牛的內(nèi)容都是我干的椅亚。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼舱污,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼呀舔!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起扩灯,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤媚赖,失蹤者是張志新(化名)和其女友劉穎霜瘪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惧磺,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颖对,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了磨隘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缤底。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖番捂,靈堂內(nèi)的尸體忽然破棺而出个唧,到底是詐尸還是另有隱情,我是刑警寧澤白嘁,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布坑鱼,位于F島的核電站膘流,受9級(jí)特大地震影響絮缅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜呼股,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一耕魄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彭谁,春花似錦吸奴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至狭园,卻和暖如春读处,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背唱矛。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工罚舱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人绎谦。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓管闷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親窃肠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子包个,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 微服務(wù)架構(gòu)模式的核心在于如何識(shí)別服務(wù)的邊界,設(shè)計(jì)出合理的微服務(wù)冤留。但如果要將微服務(wù)架構(gòu)運(yùn)用到生產(chǎn)項(xiàng)目上碧囊,并且能夠發(fā)揮...
    java菜閱讀 2,940評(píng)論 0 6
  • Spring在因Netflix開源流產(chǎn)事件后恃锉,在不斷的更換Netflix相關(guān)的組件,比如:Eureka呕臂、Zuul破托、...
    恒宇少年閱讀 38,626評(píng)論 6 35
  • Spring Cloud Gateway 是什么 Gateway是Spring Cloud 第二代網(wǎng)關(guān),第一代是Z...
    BeautifulHao閱讀 2,839評(píng)論 0 3
  • 在構(gòu)建微服務(wù)的架構(gòu)體系過(guò)程中歧蒋,API網(wǎng)關(guān)是一個(gè)非常重要的組件土砂。那我們應(yīng)該怎樣實(shí)現(xiàn)一個(gè)微服務(wù)API網(wǎng)關(guān),本文主要介紹...
    rabbitGYK閱讀 4,898評(píng)論 0 19
  • 從毛呆子家吃完飯走回來(lái)谜洽,大概走了一個(gè)小時(shí)萝映,一路上不知道想什么,大腦亂哄哄的阐虚。路上一條瘸腿的柴犬序臂,望著我,似...
    如一不如一閱讀 203評(píng)論 1 0