日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

基于Redis實現Spring Cloud Gateway的動態管理

 

轉載本文需注明出處:微信公眾號EAWorld,違者必究。

引言:

Spring Cloud Gateway是當前使用非常廣泛的一種API網關。它本身能力并不能完全滿足企業對網關的期望,人們希望它可以提供更多的服務治理能力。但Spring Cloud Gateway并不提供數據的動態管理,甚至修改個路由都需要重啟。我們如何解決它這個短板,同時實現治理配置數據的高效動態管理呢?本文將帶來我們網關與redis組合的實踐。

目錄:

1.Spring Cloud Gateway 簡介

2.網關數據管理

3.實現細節

1.Spring Cloud Gateway 簡介

API 網關

API 網關出現的原因是微服務架構的出現,不同的微服務一般會有不同的網絡地址,而外部客戶端可能需要調用多個服務的接口才能完成一個業務需求,如果讓客戶端直接與各個微服務通信,會有以下的問題:

  • 客戶端會多次請求不同的微服務,增加了客戶端的復雜性。
  • 存在跨域請求,在一定場景下處理相對復雜。
  • 認證復雜,每個服務都需要獨立認證。
  • 難以重構,隨著項目的迭代,可能需要重新劃分微服務。例如,可能將多個服務合并成一個或者將一個服務拆分成多個。如果客戶端直接與微服務通信,那么重構將會很難實施。
  • 某些微服務可能使用了防火墻 / 瀏覽器不友好的協議,直接訪問會有一定的困難。

以上這些問題可以借助 API 網關解決。API 網關是介于客戶端和服務器端之間的中間層,所有的外部請求都會先經過 API 網關這一層。也就是說,API 的實現方面更多的考慮。

 

基于Redis實現Spring Cloud Gateway的動態管理

 

使用 API 網關后的優點如下:

  • 易于監控。可以在網關收集監控數據并將其推送到外部系統進行分析。
  • 易于認證。可以在網關上進行認證,然后再將請求轉發到后端的微服務,而無須在每個微服務中進行認證。
  • 減少了客戶端與各個微服務之間的交互次數。

 

Spring Cloud Gateway

Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技術開發的網關,Spring Cloud Gateway旨在為微服務架構提供一種簡單而有效的統一的API路由管理方式。

Spring Cloud Gateway作為Spring Cloud生態系中的網關,目標是替代Netflix ZUUL,其不僅提供統一的路由方式,并且基于Filter鏈的方式提供了網關基本的功能,例如:安全,監控/埋點,和限流等。

基于Redis實現Spring Cloud Gateway的動態管理

SCG架構

如圖所示,SCG的架構看起來很簡單。

首先,它內部包含了一個高性能的Netty Server,用來接收各類網絡請求。請求進來之后,會根據配置的各個路由進行匹配并處理請求。每個路由都可以定義多個斷言(Predicate),用于路由匹配。

SCG默認提供了10多個內建的斷言,可以基于請求的各個方面(請求頭,路徑,路徑,時間,Cookie,http方法等)進行路由匹配。如果還不夠,用戶還可以自已擴展。

請求匹配到了合適的路由之后,就會按照路由中配置的各過濾器(filter),按順序對請求進行處理。Filter也基本上可以對請求的所有屬性做處理,修改,添加或者除請求頭,修改請求數據,修改返回的數據等,幾乎無所不能。當然,修改請求也只是一方面的用途,認證,鑒權,記錄日志等也都可以在網關中統一來做。

所有filter形成處理鏈,直到所有的filter處理完,才會交給最后面的 Netty Client,由它將處理過的請求發送至對應的微服務。

在請求發送至微服務之前,還可以定義它的負載均衡策略(LoadBalancerRule),以決定請求至底發往微服務的哪個實例。

Filter 與 LoadBalancerRule 都支持自行擴展。

2.網關數據管理

實現一個適合自已的網關,對數據管理需要考慮哪些方面的東西呢?

1.首先,我們要考慮一下,我們需要管理些什么數據。

SCG本身對數據管理的管理是很弱的。它沒有提供數據的持久化方案,它所有的數據都來自初始化,來自它的配置文件(Application.yml)。它本身雖然也對外提供了一些管理接口(Actuator API)能力不夠,但能力不夠,且這些修改都是暫時的,網關一停,數據就消失了。這就要求我們要用一套更完善的方案,把網關的這些數據管理起來,不能讓它只能寫在配置文件中,而要支持持久化,支持動態變更。再有就是我們對各微服務的治理數據。網關只用來做路由轉發,那就太浪費了,統一認證,統一鑒權,訪問日志記錄,應用訪問統計,黑白名單過濾,API訂閱管理,流量限制,甚至數據格式轉換,網絡協議轉換,都可以在網關中來做。而所有的這些能力,無不需要數據的支持。因此,這些服務的治理配置,也是網關需要管理的數據。

2.數據有了,我們還得考慮怎么把它保存起來,不能網關一重啟,所有數據就沒了。

3.還得再考慮一下數據的讀取。網關對性能的要求是很高的,每次對過關的數據進行治理,都需要去讀取這些配置信息。如果配置信息讀取太消耗資源,無疑對網關是不利的。所以,我們還得考慮數據如何緩存,以提高數據的讀取性能。

4.單個網關,可以處理的請求量是有上限的。為了應對大的流量,我們可能會需要對網關做水平擴容。當多個網關實例共存時,如何保障對網關的修改,能快速同步到每個網關實例呢?數據變更通知也得考慮。

5.最多,我們還得考慮一下方案的擴展,數據存儲能不能改個地方,通知能不能換種方式?

綜合考慮了這些方面之后,我們的網關的架構如下:

基于Redis實現Spring Cloud Gateway的動態管理

gateway-arch

如圖,以上就是我們網關的整體設計。方案設計要點如下:

  1. 網關對外提供治理數據管理接口, 微服務治理平臺可通過這些接口, 將治理配置推送到網關
  2. 網關通過治理數據統一存儲接口, 將治理配置數據保持至治理數據持久存儲(這里我們默認為Redis)
  3. Redis通過發布訂閱能力, 將數據的變更通知到各網關實例
  4. 各網關實例收到通知后, 將數據從持久存儲同步至內部高速緩存
  5. 內部緩存在網關啟動時, 會自動從持久存儲加載對應配置進入緩存. 同時它也支持清空, 以及按需加載
  6. 外部業務請求經過網關時, 對數據執行鑒權,處理轉換, 以及灰度策略時,所需要治理配置,都從內部緩存中獲取, 以提升性能
  7. 方案中, 外部持久存儲(默認用的Redis, 可以換成MySQL, 文件, Appolo等), 以及數據變更通知(默認使用的是Redis的發布訂閱, 可以換成Appolo通知, 消息隊列, 定時掃描等), 都是可以擴展的

3.實現細節

動態路由管理

Spring Cloud Gateway作為所有請求流量的入口,在實際生產環境中為了保證高可靠和高可用,盡量避免重啟, 需要實現Spring Cloud Gateway動態路由配置。實現動態路由其實很簡單, 重點在于 RouteDefinitionRepository 這個接口. 這個接口繼承自兩個接口, 其中 RouteDefinitionLocator 是用來加載路由的. 它有很多實現類, 其中的 PropertiesRouteDefinitionLocator 就用來實現從yml中加載路由. 另一個 RouteDefinitionWriter 用來實現路由的添加與刪除. 通過查看spring cloud gateway的源碼可以發現, 在 org.springframework.cloud.gateway.config.GatewayAutoConfiguration中這么一段:

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

可以看出, 網關中如果沒有RouteDefinitionRepository的Bean, 就會采用InMemoryRouteDefinitionRepository做為實現。這個 InMemoryRouteDefinitionRepository有一個問題, 就是數據沒有持久化, 網關重啟之后,原來通過接口設置的路由就會丟失了。

這當然是不可接受的, 所以我們需要實現自已的 RouteDefinitionRepository, 來提供路由配置信息。如使用redis做為存儲, 來實現路由的存儲。實現請參考文章:https://dwz.cn/tsHfKwMe

除此以外, 每當路由更改之后, 還需要通知網關刷新路由。這需要發送 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));
 }
}

刷新可以通過消息通知機制來觸發, 當然, 也可以對外接供rest接口, 手動觸發。### 數據存儲

基于Redis實現Spring Cloud Gateway的動態管理

 

如上述類圖所示, IGovernDataRepository為治理數據統一存儲接口。RedisGovernDataRepository為實現的它的抽像類, 它需要依賴兩個, 一個是StringRedisTemplate,用來實現redis數據的存儲。另一個為 RedisKeyGenerator, 用來為各治理對象生成對應的key。RedisGovernDataRepository下面則為各個治理數據存儲的實現類。使用Redis做為持久存儲時, 需要注意以下幾點:

  1. 為對象生成key時, 建議為key添加一個命名空間(就是加一段有意義的前綴)
  2. 在redis中進行模糊搜索時, 提供給Redis的pattern, 不能是一個正則的通配, 它支持三種通配 *(多個), ?(單個)
  3. 如果數據量比較大, 不建議使用keys進行模糊查詢, 應該使用scan方式

數據緩存

我們提供了內部緩存,它處于使用者與持久存儲之間,緩存數據以提升性能。緩存的實現主要有如下幾點:

  1. 實現了 InitializingBean 以實現在網關啟動時, 自動加載數據
  2. 內部使用了ConcurrentHashMap, 保證寫時的線程同步, 又保證了get時的高效(get整個過程不需要加鎖)
  3. 從緩存中取數據時, 如果需要懶加載, 當從持久存儲中加載不到數據時, 建議使用空數據, 或空集合占位, 避免每次都去持久存儲中查詢

代碼示例如下:

/**
 * 根據 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());
}

事件通知

事件通知,這里我們使用的是redis的發布與訂閱能力。Redis默認是不發送事件的,要讓它發布事件,需要先修改它的配置文件redis.conf,添加一個配置:

notify-keyspace-events "K$g"

上面的配置將使得Redis中發生數據的添加,修改或刪除時,發送set或del事件。

然后,我們需要配置一個RedisMessageListenerContainer,用來訂閱我們感興趣的事件。

@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 參考:http://redisdoc.com/topic/notification.html
 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);
 }
 }
 ...
}

接收到事件后,會調用相應的內部緩存,更新內部緩存中的數據,以實現治理數據變更的及時生效。

基于Redis實現Spring Cloud Gateway的動態管理

 

關于作者:將曉漁,現任普元云計算架構師。曾在PDM,云計算,數據備份,移動互聯相關領域公司工作,十年以上IT工作經驗。曾為科企桌面虛擬化產品的核心工程師,愛數容災備份云柜系統設計師,萬達信息的食安管理與追溯平臺開發經理。國內IAAS云計算的早期實踐者,容器技術專家。

關于EAWorld:微服務,DevOps,數據治理,移動架構原創技術分享。

分享到:
標簽:Redis
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定