基於Redis實現Spring Cloud Gateway的動態管理
引言:
目錄:
1.Spring Cloud Gateway 簡介
2.閘道器資料管理
3.實現細節
客戶端會多次請求不同的微服務,增加了客戶端的複雜性。
存在跨域請求,在一定場景下處理相對複雜。
認證複雜,每個服務都需要獨立認證。
難以重構,隨著專案的迭代,可能需要重新劃分微服務。例如,可能將多個服務合併成一個或者將一個服務拆分成多個。如果客戶端直接與微服務通訊,那麼重構將會很難實施。
某些微服務可能使用了防火牆 / 瀏覽器不友好的協議,直接訪問會有一定的困難。
使用 API 閘道器後的優點如下:
易於監控。可以在閘道器收集監控資料並將其推送到外部系統進行分析。
易於認證。可以在閘道器上進行認證,然後再將請求轉發到後端的微服務,而無須在每個微服務中進行認證。
減少了客戶端與各個微服務之間的互動次數。
SCG架構
閘道器對外提供治理資料管理介面, 微服務治理平臺可透過這些介面, 將治理配置推送到閘道器
閘道器透過治理資料統一儲存介面, 將治理配置資料保持至治理資料持久儲存(這裡我們預設為Redis)
Redis透過釋出訂閱能力, 將資料的變更通知到各閘道器例項
各閘道器例項收到通知後, 將資料從持久儲存同步至內部快取記憶體
內部快取在閘道器啟動時, 會自動從持久儲存載入對應配置進入快取. 同時它也支援清空, 以及按需載入
外部業務請求經過閘道器時, 對資料執行鑑權,處理轉換, 以及灰度策略時,所需要治理配置,都從內部快取中獲取, 以提升效能
方案中, 外部持久儲存(預設用的Redis, 可以換成Mysql, 檔案, Appolo等), 以及資料變更通知(預設使用的是Redis的釋出訂閱, 可以換成Appolo通知, 訊息佇列, 定時掃描等), 都是可以擴充套件的
@Bean @ConditionalOnMissingBean(RouteDefinitionRepository.class) public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() { return new InMemoryRouteDefinitionRepository(); }
可以看出, 閘道器中如果沒有RouteDefinitionRepository的Bean, 就會採用InMemoryRouteDefinitionRepository做為實現。這個 InMemoryRouteDefinitionRepository有一個問題, 就是資料沒有持久化, 閘道器重啟之後,原來透過介面設定的路由就會丟失了。
除此以外, 每當路由更改之後, 還需要通知閘道器重新整理路由。這需要傳送 RefreshRoutesEvent 來通知閘道器。如下列示例:
@Component public class RouteDynamicService implements ApplicationEventPublisherAware { private ApplicationEventPublisher publisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } /** * 重新整理路由表 */ public void refreshRoutes() { publisher.publishEvent(new RefreshRoutesEvent(this)); } }
為物件生成key時, 建議為key新增一個名稱空間(就是加一段有意義的字首)
在redis中進行模糊搜尋時, 提供給Redis的pattern, 不能是一個正則的通配, 它支援三種通配 *(多個), ?(單個)
如果資料量比較大, 不建議使用keys進行模糊查詢, 應該使用scan方式
實現了 InitializingBean 以實現在閘道器啟動時, 自動載入資料
內部使用了ConcurrentHashMap, 保證寫時的執行緒同步, 又保證了get時的高效(get整個過程不需要加鎖)
從快取中取資料時, 如果需要懶載入, 當從持久儲存中載入不到資料時, 建議使用空資料, 或空集合佔位, 避免每次都去持久儲存中查詢
/** * 根據 appCode 獲取流量策略 * * @param appCode * @return */ public Set<ApplicationTrafficPolicy> getAppTrafficPolicies(String appCode) { // 從快取載入 Map<String, ApplicationTrafficPolicy> map = policyMap.get(appCode); // 快取中沒有 if (map == null) { // 嘗試從持久儲存中載入所有此閘道器的流量策略 Set<ApplicationTrafficPolicy> policies = trafficPolicyRepository.fuzzyQuery(); // 持久儲存中沒有任何流量策略,佔個位置,防止快取重複去載入 if (policies == null || policies.size() == 0) { map = new ConcurrentHashMap<>(); policyMap.put(appCode, map); } else { // 持久儲存中有流量策略,放入快取 for (ApplicationTrafficPolicy policy : policies) { setTrafficPolicy(policy); } // 重新從快取中載入一次 map = policyMap.get(appCode); // 如果還是沒有,使用空 map 佔位子 if (map == null) { map = new ConcurrentHashMap<>(); policyMap.put(appCode, map); } } } return map.values().stream().collect(Collectors.toSet()); }
notify-keyspace-events "K$g"
@Bean RedisMessageListenerContainer container(MessageListenerAdapter listenerAdapter) { String gtwReidsPattern = "__keyspace@*__:" + GTW + keyGenerator.getGatewayCode() + "]*"; String cofRedisPattern = "__keyspace@*__:" + COF + cacheKey.getKeyNameSpace() + USER_NAME + "*"; log.info("Add gateway redis message listener, patternTopic is {}", gtwReidsPattern); log.info("Add coframe redis message listener, patternTopic is {}", cofRedisPattern); RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(redisTemplate.getConnectionFactory()); // PatternTopic 參考: container.addMessageListener(listenerAdapter, Arrays.asList(new PatternTopic(PatternUtil.fmt(gtwReidsPattern)), new PatternTopic(PatternUtil.fmt(cofRedisPattern)))); return container; } 當redis事件訂閱好了之後, 每次其中我們關心的資料有變更, 都會傳送set或del事件. 我們需要定義一個 MessageListener, 來接收事件: @Service(value = RedisMessageListener.REDIS_LISTENER_NAME) public class RedisMessageListener implements MessageListener { @Override public void onMessage(Message message, byte[] pattern) { String ops = new String(message.getBody()); String channel = new String(message.getChannel()); String key = channel.split(":")[1]; if ("set".equals(ops)) { String value = redisTemplate.opsForValue().get(key); handleSet(key, value); } else if ("del".equals(ops)) { handleDel(key); } } ... }
接收到事件後,會呼叫相應的內部快取,更新內部快取中的資料,以實現治理資料變更的及時生效。
精選提問:
問1:當前閘道器例項因為網路的原因,如果沒有訂閱到訊息,訊息會重發嗎?
答:不會。但記憶體快取會定期清理,以解決這種資料不同步的問題。也可以主動清理。
問2:閘道器使用了zuul了嗎?還是自己實現的閘道器?
答:閘道器於Spring Cloud Gateway開發,他就是一個類似於zuul的API閘道器。
問3:netttyserver是幹嘛的?
答:那是Spring Cloud Gateway本身使用的元件, 用來接收與處理請求的。
問4:檔案上傳的介面也透過閘道器嗎?
答:這個要看具體需求。也可以走閘道器, 但會對效能有一定影響。不走閘道器, 就得在應用那一層來控制許可權。閘道器控制許可權, 只是相當於把許可權校驗前移與統一化了。
問5:在微服務化之後,閘道器路由到服務,呼叫會有超時的情況怎麼處理?有些介面是必須要這麼長時間,例如批次操作 。只能透過加大超時時間嗎?
答:這個一個考慮適當增大超時時間, 另一個, 你可以考慮採用非同步模式, 比如用任務來處理。
問6:我想提問下,目前gateway我看實現是基於netty實現的http協議的,透過相關的mapping處理斷言然後處理過濾器。那有基於netty的tcp協議的實現方案嗎?基於tcp怎麼整合斷言和過濾器呢?
答:TCP的我們也在考慮, 有這方面的需求. 但是直接基於TCP實現斷言與過濾, 工作量估計會比較大. 現在傾向的方案是在閘道器前做一層TCP的協議轉換, 將TCP將成 http 再發往閘道器. 這樣可以直接利用閘道器現有能力。
關於作者:將曉漁,現任普元雲端計算架構師。曾在PDM,雲端計算,資料備份,移動互聯相關領域公司工作,十年以上IT工作經驗。曾為科企桌面虛擬化產品的核心工程師,愛數容災備份雲櫃系統設計師,萬達資訊的食安管理與追溯平臺開發經理。國內IAAS雲端計算的早期實踐者,容器技術專家。
轉載本文需註明出處:微信公眾號EAWorld,違者必究。
關於EAWorld:微服務,DevOps,資料治理,移動架構原創技術分享。長按二維碼關注!
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31562043/viewspace-2654632/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Spring Cloud Gateway實戰之三:動態路由SpringCloudGateway路由
- Spring Cloud Alibaba實戰(七) - Gateway搭配Nacos實現動態路由SpringCloudGateway路由
- Spring Cloud Gateway 實現 gRpc 代理SpringCloudGatewayRPC
- Spring Cloud Gateway 擴充套件支援動態限流SpringCloudGateway套件
- spring cloud gateway 原始碼解析(2)動態路由SpringCloudGateway原始碼路由
- spring-cloud-gateway靜態路由SpringCloudGateway路由
- 構建api gateway之 基於etcd實現動態配置同步APIGateway
- 最全面的改造Zuul閘道器為Spring Cloud Gateway(包含Zuul核心實現和Spring Cloud Gateway核心實現)ZuulSpringCloudGateway
- Spring Cloud Gateway限流實戰SpringCloudGateway
- 基於Spring-Cloud-Gateway開發API閘道器的思路SpringCloudGatewayAPI
- Spring Cloud Gateway + oauth2 跨域配置實現SpringCloudGatewayOAuth跨域
- Spring Cloud Gateway 深入SpringCloudGateway
- Spring cloud 之GatewaySpringCloudGateway
- Spring Cloud Gateway 實現簡單自定義過濾器SpringCloudGateway過濾器
- Spring Cloud Gateway實戰之一:初探SpringCloudGateway
- 基於Spring Boot和Spring Cloud實現微服務架構Spring BootCloud微服務架構
- Spring Cloud Nacos實現動態配置載入的原始碼分析SpringCloud原始碼
- Spring Cloud Gateway示例 | DevGlanSpringCloudGatewaydev
- spring cloud gateway 不生效SpringCloudGateway
- Spring Cloud Gateway 限流操作SpringCloudGateway
- Spring Cloud Gateway 入門SpringCloudGateway
- 基於spring實現事件驅動Spring事件
- 聊聊spring cloud gateway的XForwardedHeadersFilterSpringCloudGatewayForwardHeaderFilter
- 基於.net standard 的動態編譯實現編譯
- Spring Cloud Seata系列:基於AT模式實現分散式事務SpringCloud模式分散式
- 快速突擊 Spring Cloud GatewaySpringCloudGateway
- Spring Cloud Gateway初體驗SpringCloudGateway
- Spring Cloud Gateway 入門案例SpringCloudGateway
- Spring Cloud Gateway入坑記SpringCloudGateway
- Spring Cloud Gateway使用簡介SpringCloudGateway
- spring cloud gateway之filter篇SpringCloudGatewayFilter
- spring cloud gateway 之限流篇SpringCloudGateway
- 微服務閘道器實戰——Spring Cloud Gateway微服務SpringCloudGateway
- Spring Cloud Gateway實戰之五:內建filterSpringCloudGatewayFilter
- 【夯實Spring Cloud】Spring Cloud中基於maven的分散式專案框架的搭建SpringCloudMaven分散式框架
- 基於jquery-treeview的動態選單實現jQueryView
- spring cloud gateway之服務註冊與發現SpringCloudGateway
- Spring Security實現基於RBAC的許可權表示式動態訪問控制Spring