SpringCloud Gateway 動態路由

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"查看SringCloud Gateway ","attrs":{}},{"type":"link","attrs":{"href":"https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-how-it-works","title":"","type":null},"content":[{"type":"text","text":"官方文檔","attrs":{}}]},{"type":"text","text":",Gateway 工作原理如下圖:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/b7/b7ce351fa77f50d6f7d332f8a7f160e9.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Clients make requests to Spring Cloud Gateway. If the Gateway Handler Mapping determines that a request matches a route, it is sent to the Gateway Web Handler. This handler runs the request through a filter chain that is specific to the request. The reason the filters are divided by the dotted line is that filters can run logic both before and after the proxy request is sent. All “pre” filter logic is executed. Then the proxy request is made. After the proxy request is made, the “post” filter logic is run.","attrs":{}}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客戶端請求,首先會被","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Gateway Handler Mapping","attrs":{}}],"attrs":{}},{"type":"text","text":"處理,用以在 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"color","attrs":{"color":"#D46B08","name":"brown"}}],"text":"路由表 ","attrs":{}},{"type":"text","text":"中查找一個與請求匹配的 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"color","attrs":{"color":"#D46B08","name":"brown"}}],"text":"路由 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":",","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":"然後將請求交由 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":"Web Handler","attrs":{}}],"attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":" 處理,","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"Web Handler","attrs":{}}],"attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":" 維護了一個過濾器鏈,鏈式執行這些過濾器,這些過濾器在邏輯上存在兩個執行階段 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":"pre ","attrs":{}}],"attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":"與 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":"post","attrs":{}}],"attrs":{}},{"type":"text","text":" 。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文重點探究 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"路由查找 ","attrs":{}},{"type":"text","text":"的過程,並在此基礎上,探究 ","attrs":{}},{"type":"text","marks":[{"type":"italic","attrs":{}},{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"動態路由表 ","attrs":{}},{"type":"text","text":"的實現方式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"RoutePredicateHandlerMapping","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Gateway中實現路由查找邏輯的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"Gateway Handler Mapping","attrs":{}}],"attrs":{}},{"type":"text","text":" 是 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java","title":"","type":null},"content":[{"type":"text","text":"RoutePredicateHandlerMapping","attrs":{}}]},{"type":"text","text":" 類,該類在","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java","title":"","type":null},"content":[{"type":"text","text":"GatewayAutoConfiguration","attrs":{}}]},{"type":"text","text":" 中實現自動裝配(Gateway的Bean自動裝備都是由此類實現) ,源碼260-266行如下","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Bean\npublic RoutePredicateHandlerMapping routePredicateHandlerMapping(\n\t\tFilteringWebHandler webHandler, RouteLocator routeLocator,\n\t\tGlobalCorsProperties globalCorsProperties, Environment environment) {\n\treturn new RoutePredicateHandlerMapping(webHandler, routeLocator,\n\t\t\tglobalCorsProperties, environment);\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先可以看到,這裏裝配是無條件的,沒有留出拓展點(我","attrs":{}},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/bebacc42bad0712638ba3231e","title":"","type":null},"content":[{"type":"text","text":"之前文章","attrs":{}}]},{"type":"text","text":"對此用了特殊的方法進行了拓展),重點是兩個Bean的注入:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java","title":"","type":null},"content":[{"type":"text","text":"FilteringWebHandler","attrs":{}}]},{"type":"text","text":" :創建過濾器鏈,加載","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"全局過濾器","attrs":{}},{"type":"text","text":"並轉化爲","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"網關過濾器","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":",組合二者,並執行過濾器鏈,本文不展開這部分","attrs":{}},{"type":"text","text":";","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/main/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteLocator.java","title":"","type":null},"content":[{"type":"text","text":"RouteLocator","attrs":{}}]},{"type":"text","text":" :有多個實現類,本文重點 。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5b/5b2198d09767bbb1b90498a0fe18f1b1.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Override\nprotected Mono> getHandlerInternal(ServerWebExchange exchange) {\n//... 忽略之上代碼\n\n // lookupRoute 用於查找路由\n return lookupRoute(exchange)\n // 將查找到的路由記錄到 ServerWebExchange 上下文中,然後,返回 FilteringWebHandler\n .flatMap((Function>) r -> {\n exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);\n if (logger.isDebugEnabled()) {\n logger.debug(\n \"Mapping [\" + getExchangeDesc(exchange) + \"] to \" + r);\n }\n // 後續會從Attributes獲取到路由對象,進而獲取路由過濾器,執行過濾器等列操作\n exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);\n return Mono.just(webHandler);\n\n//... 忽略後續代碼","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" protected Mono lookupRoute(ServerWebExchange exchange) {\n // this.routeLocator.getRoutes() 該方法是重點,後續需要繼續分析 \n // RouteLocator 的實現類 是如何 getRoutes()\n return this.routeLocator.getRoutes()\n .concatMap(route -> Mono.just(route).filterWhen(r -> {\n exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());\n // 根據請求、當前路由的斷言,判斷當前當前路由斷言是否命中,非本文重點,不展開\n return r.getPredicate().apply(exchange);\n })\n//... 忽略後續代碼","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"RouteLocator 實現類","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a1/a1489897674bdf256c7d8b1293e3be6d.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteDefinitionLocator.java","title":"","type":null},"content":[{"type":"text","text":"CompositeRouteLocator ","attrs":{}}]},{"type":"text","text":": 合併組合其他RouterLocator。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteLocator.java","title":"","type":null},"content":[{"type":"text","text":"CachingRouteLocator","attrs":{}}]},{"type":"text","text":" : 帶有緩存和基於事件刷新緩存機制的RouterLocator。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java","title":"","type":null},"content":[{"type":"text","text":"RouteDefinitionRouteLocator","attrs":{}}]},{"type":"text","text":" : 實現了將","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinition.java","title":"","type":null},"content":[{"type":"text","text":"RouteDefinition","attrs":{}}]},{"type":"text","text":" 轉化爲 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/Route.java","title":"","type":null},"content":[{"type":"text","text":"Route ","attrs":{}}]},{"type":"text","text":"的 RouterLocator,具體轉化過程本文不展開,該類具有重要的橋樑作用。","attrs":{}}]}]}],"attrs":{}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":" // 轉化方法 \n\t@Override\n\tpublic Flux getRoutes() {\n // 注意這裏的 getRouteDefinitions() \n\t\tFlux routes = this.routeDefinitionLocator.getRouteDefinitions()\n\t\t\t\t.map(this::convertToRoute);\n // ... 下略","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們在次看下","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java","title":"","type":null},"content":[{"type":"text","text":"GatewayAutoConfiguration","attrs":{}}]},{"type":"text","text":" 中自動裝配的情況,源碼223-240行","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Bean\npublic RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,\n // 注入所有過濾器工廠(不含全局過濾器),用於根據RouteDefinition組裝Route\n\t\tList gatewayFilters,\n // 注入所有斷言工廠,用於根據RouteDefinition組裝Route\n\t\tList predicates,\n // 注入RouteDefinition的加載器,這裏是下文重點\n\t\tRouteDefinitionLocator routeDefinitionLocator,\n\t\tConfigurationService configurationService) {\n\treturn new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates,\n\t\t\tgatewayFilters, properties, configurationService);\n}\n\n@Bean\n@Primary\n@ConditionalOnMissingBean(name = \"cachedCompositeRouteLocator\")\npublic RouteLocator cachedCompositeRouteLocator(List routeLocators) {\n\treturn new CachingRouteLocator(\n\t\t\tnew CompositeRouteLocator(Flux.fromIterable(routeLocators)));\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java","title":"","type":null},"content":[{"type":"text","text":"GatewayAutoConfiguration","attrs":{}}]},{"type":"text","text":" 中並沒有直接裝配 CompositeRouteLocator,而是嵌套在了CachingRouteLocator 中,上述代碼塊17行表示所有RouteLocator的實現類都會被裝配到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cachedCompositeRouteLocator","attrs":{}}],"attrs":{}},{"type":"text","text":" 中(也包含","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cachedCompositeRouteLocator","attrs":{}}],"attrs":{}},{"type":"text","text":",16行巧妙的通過一個Conditional避免了自身對自身的循環依賴),這樣注入到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"cachedCompositeRouteLocator","attrs":{}}],"attrs":{}},{"type":"text","text":" 其實只有 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"routeDefinitionRouteLocator","attrs":{}}],"attrs":{}},{"type":"text","text":" ,這裏可以由玩家進行拓展。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteLocator.java","title":"","type":null},"content":[{"type":"text","text":"CachingRouteLocator","attrs":{}}]},{"type":"text","text":" 本文不進行展開說明,在你創建路由後,需發佈一個","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/RefreshRoutesEvent.java","title":"","type":null},"content":[{"type":"text","text":"RefreshRoutesEvent","attrs":{}}]},{"type":"text","text":" 事件,然後這個Locator就可以監聽到該事件,並刷新路由。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們看下一個RouteDefinitionRouteLocator的構造方法","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\tpublic RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,\n\t\t\tList predicates,\n\t\t\tList gatewayFilterFactories,\n\t\t\tGatewayProperties gatewayProperties,\n\t\t\tConfigurationService configurationService) {\n\t\tthis.routeDefinitionLocator = routeDefinitionLocator;\n\t\tthis.configurationService = configurationService;\n // 斷言工廠初始化,同下面過濾器器工廠類似,也有截去RoutePredicateFactory的操作\n\t\tinitFactories(predicates);\n\t\tgatewayFilterFactories.forEach(\n // factory.name() 用以獲取過濾器工廠的名字,具體代碼實現就是 末尾截去\"GatewayFilterFactory\"\n // 這也是爲什麼我們自定義過濾器工廠時需要以GatewayFilterFactory結尾命名\n\t\t\t\tfactory -> this.gatewayFilterFactories.put(factory.name(), factory));\n\t\tthis.gatewayProperties = gatewayProperties;\n\t}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們在使用SpringCloud Gateway 時定義一個路由如下","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"spring:\n cloud:\n gateway:\n routes:\n - id: path_route\n uri: https://example.org\n predicates:\n # Path 對應 PathRoutePredicateFactory\n - Path=/red/{segment},/blue/{segment}\n filters:\n # AddRequestHeader 對應 AddRequestHeaderGatewayFilterFactory\n - AddRequestHeader=X-Request-red, blue","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"RouteDefinitionLocator 實現類","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"裝配 RouteDefinitionRouteLocator 時注入了一個 name 爲 ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"orange"}}],"text":"routeDefinitionLocator ","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"black"}}],"text":"的 Bean","attrs":{}},{"type":"text","text":",回到","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java","title":"","type":null},"content":[{"type":"text","text":"GatewayAutoConfiguration","attrs":{}}]},{"type":"text","text":" 看 RouteDefinitionLocator 是如何裝配的,源碼208-214行","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Bean\n@Primary\npublic RouteDefinitionLocator routeDefinitionLocator(\n List routeDefinitionLocators) {\n return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏採用了一個 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/86142f2448ec75f0634df7f7e29ac532764941d9/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteDefinitionLocator.java#L31","title":"","type":null},"content":[{"type":"text","text":"CompositeRouteDefinitionLocator ","attrs":{}}]},{"type":"text","text":"對所有 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/86142f2448ec75f0634df7f7e29ac532764941d9/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionLocator.java#L24","title":"","type":null},"content":[{"type":"text","text":"RouteDefinitionLocator ","attrs":{}}]},{"type":"text","text":"的實現類進行了組合封裝,這些實現了,都實現了具體的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"getRouteDefinitions()","attrs":{}}],"attrs":{}},{"type":"text","text":" 方法。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7796fc89007acf2ea0892ce9851a6a8.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":true,"pastePass":true}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteDefinitionLocator.java","title":"","type":null},"content":[{"type":"text","text":"PropertiesRouteDefinitionLocator","attrs":{}}]},{"type":"text","text":" 基於配置文件實現的路由加載器,配置文件修改後不支持動態加載","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java","title":"","type":null},"content":[{"type":"text","text":"DiscoveryClientRouteDefinitionLocator","attrs":{}}]},{"type":"text","text":" 基於服務發現實現的路由加載器。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":2,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/GatewayDiscoveryClientAutoConfiguration.java","title":"","type":null},"content":[{"type":"text","text":"GatewayDiscoveryClientAutoConfiguration","attrs":{}}]},{"type":"text","text":" 是對上面RouteDefinitionLocator的配套","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#FF7021","name":"user"}}],"text":"過濾器、斷言","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"的自動裝配,默認關閉此加載器,需要通過配置文件開啓。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"# 開啓此locator\nspring.cloud.gateway.discovery.locator.enabled=true\n# 開啓默認爲 reactive 模式,需顯示關閉可調整爲阻塞模式\nspring.cloud.discovery.reactive.enabled=false","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteDefinitionLocator.java","title":"","type":null},"content":[{"type":"text","text":"CachingRouteDefinitionLocator","attrs":{}}]},{"type":"text","text":" 通過翻閱源碼,發現此加載器並未實裝,可能是考慮在底層RouteLocator已經具備了緩存。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/InMemoryRouteDefinitionRepository.java","title":"","type":null},"content":[{"type":"text","text":"InMemoryRouteDefinitionRepository","attrs":{}}]},{"type":"text","text":" 基於內存存儲的路由加載器,可以通過SpringCloud Gateway提供的 management endpoint 進行路由管理,但由於基於內存實現,並未持久化。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java","title":"","type":null},"content":[{"type":"text","text":"GatewayAutoConfiguration","attrs":{}}]},{"type":"text","text":" 查看其裝配代碼,源碼202-206行:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"@Bean\n// 只有在沒有裝配RouteDefinitionRepository的其他實現Bean時,才生效,\n// 我們可以通過該擴展點實現動態路由及持久化\n@ConditionalOnMissingBean(RouteDefinitionRepository.class)\npublic InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {\n\treturn new InMemoryRouteDefinitionRepository();\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Management Endpoints:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ed/edb15208a094daa77b85657a58224824.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":true,"pastePass":true}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/86142f2448ec75f0634df7f7e29ac532764941d9/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteDefinitionLocator.java","title":"","type":null},"content":[{"type":"text","text":"CompositeRouteDefinitionLocator","attrs":{}}]},{"type":"text","text":" 是對其他 RouteDefinitionLocator 的組合,通過","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java","title":"","type":null},"content":[{"type":"text","text":"GatewayAutoConfiguration","attrs":{}}]},{"type":"text","text":" 可以看到其裝配代碼,源碼208-214行","attrs":{}}]}]}],"attrs":{}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"@Bean\n@Primary\npublic RouteDefinitionLocator routeDefinitionLocator(\n\t\tList routeDefinitionLocators) {\n\treturn new CompositeRouteDefinitionLocator(\n\t\t\tFlux.fromIterable(routeDefinitionLocators));\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到前面 ","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/spring-cloud/spring-cloud-gateway/blob/v2.2.8.RELEASE/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java","title":"","type":null},"content":[{"type":"text","text":"RouteDefinitionRouteLocator","attrs":{}}]},{"type":"text","text":" 中注入的 ","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"routeDefinitionLocator","attrs":{}}],"attrs":{}},{"type":"text","text":" 就是CompositeRouteDefinitionLocator,而裝配它是注入了所有RouteDefinitionLocator的實現,其中包括RouteDefinitionRepository的實現(默認情況下爲InMemoryRouteDefinitionRepository)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到這裏就比較明確了,如果需要實現可持久化存儲的動態路由,我們只需基於數據庫(或其他持久化存儲),參考InMemoryRouteDefinitionRepository實現一個RouteDefinitionRepository即可。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"基於MongoDB的動態路由","attrs":{}}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上文歸納總結如下:","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"從RoutePredicateHandlerMapping入手,注入其中的RouteLocator爲CachingRouteLocator ;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"CachingRouteLocator 封裝了CompositeRouteLocator ;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"CompositeRouteLocator 組合的唯一RouteLocator就是 RouteDefinitionRouteLocator ;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"RouteDefinitionRouteLocator 實現了DefinitionRoute到Route的轉換,並注入了一個name爲routeDefinitionLocator的Bean,即CompositeRouteDefinitionLocator ;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"CompositeRouteDefinitionLocator 組合了所有的RouteDefinitionLocator,其中包括RouteDefinitionRepository的一個實現,即InMemoryRouteDefinitionRepository ;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"InMemoryRouteDefinitionRepository 裝配是有條件的,僅在不存在其他RouteDefinitionRepository的Bean才生效;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"參考 InMemoryRouteDefinitionRepository,實現一個基於數據庫的路由存儲。","attrs":{}}]}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":8,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":8,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":8,"align":null,"origin":null},"content":[{"type":"text","text":"全部代碼見:","attrs":{}},{"type":"link","attrs":{"href":"https://gitee.com/eblog/scg-dynamic-route","title":"","type":null},"content":[{"type":"text","text":"scg-dynamic-route","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":8,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"主要代碼片段","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Slf4j\n@Component\npublic class MongoRouteDefinitionRepository\n implements RouteDefinitionRepository, ApplicationEventPublisherAware {\n\n private static final String CACHE_KEY = \"routes\";\n\n private ApplicationEventPublisher eventPublisher;\n\n private Map cache = new ConcurrentHashMap<>();\n\n private final RouteRepositoryOperations repositoryOperation;\n\n public MongoRouteDefinitionRepository(RouteRepositoryOperations repositoryOperation) {\n this.repositoryOperation = repositoryOperation;\n }\n\n @Override\n public Flux getRouteDefinitions() {\n return Flux.fromIterable(cache.values());\n }\n\n @Override\n public Mono save(Mono route) {\n return route.flatMap(\n r -> repositoryOperation.save(MongoRouteDefinition.from(r))\n .log()\n .doOnNext(this::addCache)\n .then(Mono.empty())\n );\n }\n\n @Override\n public Mono delete(Mono routeId) {\n return repositoryOperation.findById(routeId)\n .log()\n .map(RouteDefinition::getId)\n .doOnNext(this::removeCache)\n .flatMap(repositoryOperation::deleteById);\n }\n\n @Override\n public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {\n this.eventPublisher = eventPublisher;\n }\n\n /**\n * 將指定路由加入到緩存中。\n *

\n * 爲了能實時加載路由,可以通過MongoDB的ChangeStream,監聽到數據變化後調用此方法\n */\n public void addCache(RouteDefinition route) {\n this.cache.putIfAbsent(route.getId(), route);\n this.publishEvent();\n }\n\n /**\n * 將指定路由從緩存中刪除。\n *

\n * 爲了能實時加載路由,可以通過MongoDB的ChangeStream,監聽到數據變化後調用此方法\n */\n public void removeCache(String routeId) {\n if (this.cache.remove(routeId) != null) {\n this.publishEvent();\n }\n }\n\n void publishEvent() {\n eventPublisher.publishEvent(new RefreshRoutesEvent(this));\n }\n\n RouteRepositoryOperations getRepositoryOperation() {\n return repositoryOperation;\n }\n\n\n Map getCache() {\n return cache;\n }\n\n void setCache(\n Map cache) {\n this.cache = cache;\n }\n\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Slf4j\n@Component\n@ConditionalOnProperty(value = \"route.schedule.enabled\", havingValue = \"true\", matchIfMissing = true)\npublic class RouteRefresher {\n\n private final MongoRouteDefinitionRepository repository;\n\n public RouteRefresher(\n MongoRouteDefinitionRepository repository) {\n this.repository = repository;\n }\n\n /**\n * 固定間隔重新加載一次緩存\n */\n @Scheduled(initialDelay = 10000, fixedDelay = 60 * 60 * 1001)\n private void refresh() {\n RouteRepositoryOperations operation = repository.getRepositoryOperation();\n\n int page = 0;\n int pageSize = 1000;\n int total = Math.toIntExact(operation.count().blockOptional().orElse(0L));\n Map oldCache = repository.getCache();\n Map newCache = new ConcurrentHashMap<>(total);\n int oldTotal = oldCache.size();\n if (oldTotal < 1) {\n // 首次同步刷新\n repository.setCache(newCache);\n }\n while (page * pageSize < total) {\n operation.findAll(PageRequest.of(page++, pageSize))\n .doOnNext(route -> newCache.putIfAbsent(route.getId(), route))\n .blockLast();\n log.info(\"動態路由表當前總大小爲:{}, 新路由表當前大小爲:{}\", oldTotal, newCache.size());\n }\n repository.setCache(newCache);\n log.info(\"新路由表加載完成,當前大小爲:{}\", newCache.size());\n repository.publishEvent();\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Component\n@ConditionalOnProperty(value = \"changeStream.enabled\", havingValue = \"true\", matchIfMissing = true)\npublic class RouteChangeStreamHandler implements CommandLineRunner {\n\n private final ReactiveMongoTemplate mongoTemplate;\n private final MongoRouteDefinitionRepository routeRepository;\n\n public RouteChangeStreamHandler(\n MongoRouteDefinitionRepository routeRepository, ReactiveMongoTemplate mongoTemplate) {\n this.routeRepository = routeRepository;\n this.mongoTemplate = mongoTemplate;\n }\n\n @Override\n public void run(String... args) {\n new Thread(this::startMonitor, \"ChangeStream-Monitor-routes\").start();\n }\n\n public void startMonitor() {\n Aggregation aggregation = Aggregation.newAggregation(Aggregation\n .match(Criteria.where(\"operationType\").in(\"insert\", \"delete\", \"update\", \"replace\")));\n\n ChangeStreamOptions options = ChangeStreamOptions.builder()\n .filter(aggregation)\n .returnFullDocumentOnUpdate()\n .build();\n\n String collectionName = MongoRouteDefinition.class.getAnnotation(Document.class).value();\n Flux> changeStream = mongoTemplate\n .changeStream(collectionName, options, MongoRouteDefinition.class);\n\n changeStream\n .log()\n .doOnNext(e -> {\n if (OperationType.INSERT == e.getOperationType()\n || OperationType.UPDATE == e.getOperationType()\n || OperationType.REPLACE == e.getOperationType()) {\n Optional.ofNullable(e.getBody()).ifPresent(routeRepository::addCache);\n } else if (OperationType.DELETE == e.getOperationType()) {\n getId(e).ifPresent(routeRepository::removeCache);\n }\n }).blockLast();\n }\n\n private Optional getId(ChangeStreamEvent e) {\n return Optional.ofNullable(e.getRaw())\n .flatMap(raw -> Optional.ofNullable(raw.getDocumentKey()))\n .flatMap(docKey -> Optional.ofNullable(docKey.getObjectId(\"_id\")))\n .flatMap(bson -> Optional.of(bson.getValue().toHexString()));\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"@Document(\"gwRoutes\")\npublic class MongoRouteDefinition extends RouteDefinition {\n\n public static MongoRouteDefinition from(RouteDefinition route) {\n MongoRouteDefinition newRoute = new MongoRouteDefinition();\n BeanUtils.copyProperties(route, newRoute);\n return newRoute;\n }\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public interface RouteRepositoryOperations extends\n ReactiveMongoRepository {\n\n /**\n * 分頁查詢\n *\n * @param pageable 分頁\n * @return 當前頁\n */\n @Query(value = \"{}\", sort = \"{_id:1}\")\n Flux findAll(Pageable pageable);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章