自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我去,你竟然還不會用API網(wǎng)關!

原創(chuàng)
網(wǎng)絡 通信技術 開發(fā)工具
從應用程序架構的變遷過程可以發(fā)現(xiàn),隨著業(yè)務多變性、靈活性的不斷提高,應用程序需要以更加靈活的組合來應對。

【51CTO.com原創(chuàng)稿件】從應用程序架構的變遷過程可以發(fā)現(xiàn),隨著業(yè)務多變性、靈活性的不斷提高,應用程序需要以更加靈活的組合來應對。

[[326027]]

圖片來自 Pexels

同時為了應對業(yè)務的細分以及高并發(fā)的挑戰(zhàn),微服務的架構被廣泛使用,由于微服務架構中應用會被拆分成多個服務。

為了方便客戶端對這些服務的調用于是引入了 API 的概念。今天我們就來看看API 網(wǎng)關的原理以及它是如何應用的。

API 網(wǎng)關的定義

網(wǎng)關一詞最早出現(xiàn)在網(wǎng)絡設備,比如兩個相互獨立的局域網(wǎng)之間通過路由器進行通信, 中間的路由被稱之為網(wǎng)關。

落實在開發(fā)層面來說,就是客戶端與微服務系統(tǒng)之間存在的網(wǎng)關。從業(yè)務層面來說,當客戶端完成某個業(yè)務的時候,需要同時調用多個微服務。

如圖 1 所示,當客戶端發(fā)起下單請求需要調用:商品查詢、庫存扣減以及訂單更新等服務。

 

圖1 :API 網(wǎng)關加入前后對比

如果這些服務需要客戶端分別調用才能完成,會增加請求的復雜度,同時也會帶來網(wǎng)絡調用性能的損耗。因此,針對微服務的應用場景就推出了 API 網(wǎng)關的調用。

在客戶端與微服務之間加入下單 API 網(wǎng)關,客戶端直接給這個 API 網(wǎng)關下達命令,由于后者完成對其他三個微服務的調用并且返回結果給客戶端。

從系統(tǒng)層面來說,任何一個應用系統(tǒng)如果需要被其他系統(tǒng)調用,就需要暴露 API,這些 API 代表著的功能點。

正如上面下單的例子中提到的,如果一個下單的功能點需要調用多個服務的時候,在這個下單的 API 網(wǎng)關中就需要聚合多個服務的調用。

這個聚合的方式有點像設計模式中的門面模式(Facade),它為外部的調用提供了一個統(tǒng)一的訪問入口。

不僅如此,如圖 2 所示,API 網(wǎng)關還可以協(xié)助兩個系統(tǒng)的通信,在系統(tǒng)之間加上一個中介者協(xié)助 API 的調用。

 

圖 2:對接兩個系統(tǒng)的 API 網(wǎng)關

從客戶端類型層面來說,為了屏蔽不同客戶端調用差異也可以加入 API 網(wǎng)關。

如圖 3 所示,在實際開發(fā)過程中 API 網(wǎng)關還可以根據(jù)不同的客戶端類型(iOS、Android、PC、小程序),提供不同的 API 網(wǎng)關與之對應。

 

圖 3:對接客戶端和服務端的 API 網(wǎng)關

由于 API 網(wǎng)關所處的位置是客戶端與微服務交界的地方,因此從功能上它還包括:路由,負載均衡,限流,緩存,日志,發(fā)布等等。

Spring Cloud Gateway 概念與定義

API 網(wǎng)關的定義中我們提到了為什么要使用 API 網(wǎng)關,是為了解決客戶端對多個微服務進行訪問的問題。

由于服務的切分導致一個操作需要同時調用多個服務,因此為這些服務的聚合提供一個統(tǒng)一的門面,這個門面就是 API 網(wǎng)關。

針對于 API 網(wǎng)關有很多的實現(xiàn)方式,例如:Zuul,Kong 等等。這里我們以及 Spring Cloud Gateway 為例展開給大家介紹其具體實現(xiàn)。

一般來說,API 網(wǎng)關對內將微服務進行集合,對外暴露的統(tǒng)一 URL 或者接口信息供客戶端調用。

那么客戶端是如何與微服務進行連接,并且進行溝通的,需要引入下面幾個重要概念 。

 

圖 4:路由、斷言和過濾器

如圖 4 所示,Spring Cloud Gateway 由三部分組成:

①路由(Route):任何一個來自于客戶端的請求都會經(jīng)過路由,然后到對應的微服務中。

每個路由會有一個唯一的 ID 和對應的目的 URL。同時包含若干個斷言(Predicate)和過濾器(Filter)。

②斷言(Predicate):當客戶端通過 Http Request 請求進入 Spring Cloud Gateway 的時候,斷言會根據(jù)配置的路由規(guī)則,對 Http Request 請求進行斷言匹配。

說白了就是進行一次或者多次 if 判斷,如果匹配成功則進行下一步處理,否則斷言失敗直接返回錯誤信息。

③過濾器( Filter):簡單來說就是對流經(jīng)的請求進行過濾,或者說對其進行獲取以及修改的操作。注意過濾器的功能是雙向的,也就是對請求和響應都會進行修改處理 。

一般來說 Spring Cloud Gateway 中的過濾器有兩種類型:

  • Gateway Filter
  • Global Filter

Gateway Filter 用在單個路由和分組路由上。Global Filter 可以作用于所有路由,是一個全局的 Filter。

Spring Cloud Gateway 工作原理

說完了 Spring Cloud Gateway 定義和要素,再來看看其工作原理??偟膩碚f是對客戶端請求的處理過程。

 

圖 5:Spring Cloud Gateway 處理請求流程圖

如圖 5 所示,當客戶端向 Spring Cloud Gateway 發(fā)起請求,該請求會被 HttpWebHandlerAdapter 獲取,并且對請求進行提取,從而組裝成網(wǎng)關上下文。

將組成的上下文信息傳遞到 DispatcherHandler 組件。DispatcherHandler 作為請求分發(fā)處理器,主要負責將請求分發(fā)到對應的處理器進行處理。

這里請求的處理器包括 RoutePredicate HandlerMapping (路由斷言處理映射器) 。

路由斷言處理映射器用于路由的查找,以及找到 路由后返回對應的 FilteringWebHandler。

其負責組裝 Filter 鏈表并執(zhí)行過濾處理,之后再將請求轉交給應用服務,應用服務處理完后,最后返回 Response 給客戶端 。

其中 FilteringWebHandler 處理請求的時候會交給 Filter 進行過濾的處理。

這里需要注意的是由于 Filter 是雙向的所以,當客戶端請求服務的時候,會通過 Pre Filter 中的 Filter 處理請求。

當服務處理完請求以后返回客戶端的時候,會通過 Post Filter 再進行一次處理。

Spring Cloud Gateway 最佳實踐

上面介紹了 Spring Cloud Gateway 的定義和實現(xiàn)原理,下面根據(jù)幾個常用的場景介紹一下 Spring Cloud Gateway 如何實現(xiàn)網(wǎng)關功能的。

我們會根據(jù)基本路由、權重路由、限流、動態(tài)路由幾個方面給大家展開介紹。

基本路由

基本路由,主要功能就是在客戶端請求的時候,根據(jù)定義好的路徑指向到對應的 URI。這個過程中需要用到 Predicates(斷言)中的 Path 路由斷言處理器。

首先在 POM 文件中加入對應的依賴,如下:

  1. <dependency> 
  2.     <groupId>org.springframework.cloud</groupId> 
  3.     <artifactId>spring-cloud-starter-gateway</artifactId> 
  4. </dependency> 

加入如下代碼,其中定義的 Path 的路徑“/baidu”就是請求時的路徑地址。對應的 URI,http://www.baidu.com/ 就是要跳轉到的目標地址。

  1. @Bean 
  2. public RouteLocator routeLocator(RouteLocatorBuilder builder) { 
  3.    return builder.routes() 
  4.          .route(r ->r.path("/baidu"
  5.                .uri("http://www.baidu.com/").id("baidu_route"
  6.          ).build(); 

同樣上面的功能也可以在 yml 文件中實現(xiàn)。配置文件如下,說白了就是對 Path 和 URI 參數(shù)的設置,實現(xiàn)的功能和上面代碼保持一致。

  1. spring: 
  2.   cloud: 
  3.     gateway: 
  4.       routes: 
  5.       - id: baidu_route 
  6.         uri: http://baidu.com:80/ 
  7.         predicates: 
  8.         - Path=/baidu 

此時啟動 API 網(wǎng)關,假設網(wǎng)關的訪問地址是“localhost:8080/baidu”,當用戶請求這個地址的時候就會自動請求“www.baidu.com”這個網(wǎng)站。這個配置起來很簡單,有 Nginx 基礎的朋友應該很快就能上手。

權重路由

這個使用場景相對于上面的簡單路由要多一些。由于每個微服務發(fā)布新版本的時候,通常會保持老版本與新版版同時存在。

然后通過網(wǎng)關將流量逐步從老版本的服務切換到新版本的服務。這個逐步切換的過程就是常說的灰度發(fā)布。

此時,API 網(wǎng)關就起到了流量分發(fā)的作用,通常來說最開始的老版本會承載多一些的流量,例如 90% 的請求會被路由到老版本的服務上,只有 10% 的請求會路由到新服務上去。

從而觀察新服務的穩(wěn)定性,或者得到用戶的反饋。當新服務穩(wěn)定以后,再將剩下的流量一起導入過去。

 

圖 6:灰度發(fā)布,路由到新/老服務

如下代碼所示,假設 API 網(wǎng)關還是采用 8080 端口,需要針對兩個不同的服務配置路由權重。因此在 routes 下面分別配置 service_old 和 service_new。

  1. server.port: 8080 
  2. spring: 
  3.   application: 
  4.     name: gateway-test 
  5.   cloud: 
  6.     gateway: 
  7.       routes: 
  8.       - id: service_old 
  9.         uri: http://localhost:8888/v1 
  10.         predicates: 
  11.         - Path=/gatewaytest 
  12.         - Weight=service, 90 
  13.       - id: service_new 
  14.         uri: http://localhost:8888/v2 
  15.         predicates: 
  16.         - Path=/gatewaytest 
  17.         - Weight=service, 10 

在兩個配置中對應的 URI 分別是新老兩個服務的訪問地址,通過“http://localhost:8888/v1”和“http://localhost:8888/v2”來區(qū)別。

在 Predicates(斷言)中定義了的 Path 是想通的都是“/gatewaytest”,也就是說對于客戶端來說訪問的路徑都是一樣的,從路徑上客戶不會感知他們訪問的是新服務或者是老服務。

主要參數(shù)是在 Weight,針對老/新服務分別配置的是 90 和 10。也就是有 90% 的流量會請求老服務,有 10% 的流量會請求新服務。

簡單點說,如果有 100 次請求,其中 90 次會請求 v1(老服務),另外的 10 次會請求 v2(新服務)。

限流

當服務在短時間內迎來高并發(fā),并發(fā)量超過服務承受的范圍就需要使用限流。例如:秒殺、搶購、下單服務。

通過請求限速或者對一個時間窗口內的請求進行限速來保護服務。當達到限制速率則可以拒絕請求,返回錯誤代碼,或者定向到友好頁面。

一般的中間件都會有單機限流框架,支持兩種限流模式:

  • 控制速率
  • 控制并發(fā)

這里通過 Guava 中的 Bucket4j 來實現(xiàn)限流操作。按照慣例引入 Bucket4j 的依賴:

  1. <dependency> 
  2.     <groupId>com.github.vladimir-bukhtoyarov</groupId> 
  3.     <artifactId>bucket4j-core</artifactId> 
  4.     <version>4.0.0</version> 
  5. </dependency> 

由于需要對于用戶請求進行監(jiān)控,因此通過實現(xiàn) GatewayFilter 的方式自定義 Filter,然后再通過 Gateway API Application 應用這個自定義的 Filter。

這里我們使用的是令牌桶的方式進行限流,因此需要設置桶的容量(capacity),每次填充的令牌數(shù)量(refillTokens)以及填充令牌的間隔時間(refillDuration)。

初始化這三個參數(shù)以后,通過 createNewBucket 方法針對請求建立令牌桶(bucket),在 Filter 方法中實現(xiàn)限流的主要邏輯。

通過 ServerWebExchange 獲取請求的上下文中的 IP 信息,針對 IP 建立對應的令牌桶,這個 IP 與令牌桶的對應關系放到了 LOCAL_CACHE 中。

每次請求經(jīng)過的時候通過 tryConsume(1) 方法消費一個令牌,直到?jīng)]有令牌的時候返回 HttpStatus.TOO_MANY_REQUESTS 的狀態(tài)碼(429),此時網(wǎng)關直接返回請求次數(shù)太多,即便是再有請求進來也不會路由到對應的服務了。

只有等待下一個時間間隔,一定數(shù)量的令牌放到桶里的時候,請求拿到桶中的令牌才能再次請求服務。

  1. public class GatewayRateLimitFilterByIp implements GatewayFilter, Ordered { 
  2.     private static final Map<String, Bucket> LOCAL_CACHE = new ConcurrentHashMap<>(); 
  3.     int capacity; 
  4.     int refillTokens; 
  5.     Duration refillDuration; 
  6.     public GatewayRateLimitFilterByIp() { 
  7.     } 
  8.  
  9.     public GatewayRateLimitFilterByIp(int capacity, int refillTokens, Duration refillDuration) { 
  10.         this.capacity = capacity; 
  11.         this.refillTokens = refillTokens; 
  12.         this.refillDuration = refillDuration; 
  13.     } 
  14.  
  15.     private Bucket createNewBucket() { 
  16.         Refill refill = Refill.of(refillTokens, refillDuration); 
  17.         Bandwidth limit = Bandwidth.classic(capacity, refill); 
  18.         return Bucket4j.builder().addLimit(limit).build(); 
  19.     } 
  20.  
  21.     @Override 
  22.     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { 
  23.         String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress(); 
  24.         Bucket bucket = LOCAL_CACHE.computeIfAbsent(ip, k -> createNewBucket()); 
  25.         if (bucket.tryConsume(1)) { 
  26.             return chain.filter(exchange); 
  27.         } else { 
  28. exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); 
  29.             return exchange.getResponse().setComplete(); 
  30.         } 
  31.     } 

上面的代碼定義了 Filter 其中針對訪問的 IP 生成令牌桶,并且定義了桶的大小、每次放入桶令牌的個數(shù)、放入令牌的間隔時間。

并且通過 Filter 方法重寫了過濾的邏輯,那么下面只需要將這個 Filter 應用到 Spring Cloud Gateway 的規(guī)則上去就可以了。通過下面代碼定義網(wǎng)關的路由斷言和過濾器。

在 Filters 中新建一個上面代碼定義的過濾器,指定容量是 20,每兩秒放入令牌,每次放入一個令牌。

那么當用戶訪問 rateLimit 路徑的時候就會根據(jù)客制化的 Filter 進行限流。

  1. @Bean 
  2. public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { 
  3.     return builder.routes() 
  4.             .route(r -> r.path("/rateLimit"
  5.                     .filters(f -> f.filter(new GatewayRateLimitFilterByIp(20,1,Duration.ofSeconds(2)))) 
  6.                     .uri("http://localhost:8888/rateLimit"
  7.                     .id("rateLimit_route"
  8.             ).build(); 

這里的限流只是給大家提供一種思路,通過實現(xiàn) GatewayFilter,重寫其中的 Filter 方法,加入對流量的控制代碼,然后在 Spring Cloud Gateway 中進行應用就可以了。

動態(tài)路由

由于 Spring Cloud Gateway 本身也是一個服務,一旦啟動以后路由配置就無法修改了。

無論是上面提到的編碼注入的方式還是配置的方式,如果需要修改都需要重新啟動服務。

如果回到 Spring Cloud Gateway 最初的定義,我們會發(fā)現(xiàn)每個用戶的請求都是通過 Route 訪問對應的微服務,在 Route 中包括 Predicates 和 Filters 的定義。

只要實現(xiàn) Route 以及其包含的 Predicates 和 Filters 的定義,然后再提供一個 API 接口去更新這個定義就可以動態(tài)地修改路由信息了。

按照這個思路需要做以下幾步來實現(xiàn):

①定義 Route、Predicates 和 Filters

其中 Predicates 和 Filters 包含在 Route 中。實際上就是 Route 實體的定義,針對 Route 進行路由規(guī)則的配置。

  1. public class FilterDefinition { 
  2.     //Filter Name 
  3.     private String name
  4.     //對應的路由規(guī)則 
  5.     private Map<String, String> args = new LinkedHashMap<>(); 
  6. public class PredicateDefinition { 
  7.     //Predicate Name 
  8.     private String name
  9.     //對應的斷言規(guī)則 
  10.     private Map<String, String> args = new LinkedHashMap<>(); 
  11. public class RouteDefinition { 
  12.     //斷言集合 
  13. private List<PredicateDefinition> predicates = new ArrayList<>(); 
  14. //路由集合 
  15. private List< FilterDefinition > filters= new ArrayList<>(); 
  16. //uri 
  17. private String uri; 
  18. //執(zhí)行次序 
  19. private int order = 0; 

②實現(xiàn)路由規(guī)則的操作,包括添加,更新,刪除

有了路由的定義(Route,Predicates,F(xiàn)ilters),然后再編寫針對路由定義的操作。

例如:添加路由,刪除路由,更新路由之類的。編寫 RouteServiceImpl 實現(xiàn) ApplicationEventPublisherAware。

主要需要 override 其中的 setApplicationEventPublisher 方法,這里會傳入 ApplicationEventPublisher 對象,通過這個對象發(fā)布路由定義的事件包括:add,update,delete。

貼出部分代碼如下:

  1. @Service 
  2. public class RouteServiceImpl implements ApplicationEventPublisherAware { 
  3.     @Autowired 
  4.     private RouteDefinitionWriter routeDefinitionWriter; 
  5.     private ApplicationEventPublisher publisher; 
  6.     //添加路由規(guī)則 
  7.     public String add(RouteDefinition definition) { 
  8.         routeDefinitionWriter.save(Mono.just(definition)).subscribe(); 
  9.         this.publisher.publishEvent(new RefreshRoutesEvent(this)); 
  10.         return "success"
  11.     } 
  12.     public String update(RouteDefinition definition) { 
  13.         try { 
  14.           this.routeDefinitionWriter.delete(Mono.just(definition.getId())); 
  15.         } catch (Exception e) { 
  16.  
  17.         } 
  18.         try { 
  19.             routeDefinitionWriter.save(Mono.just(definition)).subscribe(); 
  20.             this.publisher.publishEvent(new RefreshRoutesEvent(this)); 
  21.             return "success"
  22.         } catch (Exception e) { 
  23.  
  24.         } 
  25.     } 
  26.     public String delete(String id) { 
  27.         try { 
  28.             this.routeDefinitionWriter.delete(Mono.just(id)); 
  29.             return "delete success"
  30.         } catch (Exception e) { 
  31.  
  32.         } 
  33.  
  34.     } 
  35.  
  36.     @Override 
  37.     public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { 
  38.         this.publisher = applicationEventPublisher; 
  39.     } 

③對外部提供 API 接口能夠讓用戶或者程序動態(tài)修改路由規(guī)則

從代碼上來說就是一個 Controller。這個 Controller 中只需要調用 routeServiceImpl 就行了,主要也是用到客制化路由實現(xiàn)類中的 add,update,delete 方法。

說白了就是對其進行了一次包裝,讓外部系統(tǒng)可以調用,并且修改路由的配置。

經(jīng)過簡化以后的代碼如下,這里只對 add 方法進行了包裝,關于 update 和 delete 方法在這里不展開說明,調用方式類似 add。

  1. public class RouteController { 
  2.  
  3.     @Autowired 
  4.     private routeServiceImpl routeService; 
  5.  
  6.     @PostMapping("/add"
  7.     public String add(@RequestBody RouteDefinition routeDefinition) { 
  8.         try { 
  9.             RouteDefinition definition = assembleRouteDefinition(routeDefinition); 
  10.             return this.dynamicRouteService.add(definition); 
  11.         } catch (Exception e) { 
  12.                    } 
  13.         return "succss"
  14.     } 

④啟動程序進行路由的添加和更新操作

假設更新 API 網(wǎng)關配置的服務在 8888 端口上。于是通過 http://localhost:8888/actuator/gateway/routes 訪問當前的路由信息,由于現(xiàn)在沒有配置路由這個信息是空。

那么通過 http://localhost:8888/route/add 方式添加一條路由規(guī)則,這里選擇 Post 請求,輸入類型為 Json 如下:

  1.     "filter":[], 
  2.     "id":"baidu_route"
  3.     "order":0, 
  4.     "predicates":[{ 
  5.         "args":{ 
  6.             "pattern":"/baidu" 
  7.         }, 
  8.         "name":"Path" 
  9.     }], 
  10.     "uri":"https://www.baidu.com" 

Json 中配置的內容和簡單路由配置的內容非常相似。設置了 Route,當 Predicates 為 baidu 的時候,將請求引導到 www.baidu.com 的網(wǎng)站進行響應。

此時再通過訪問 http://localhost:8888/baidu 的路徑訪問的時候,就會被路由到 www.baidu.com 的網(wǎng)站。

此時如果需要修改路由配置,可以通過訪問 http://localhost:8888/route/update 的 API 接口,通過 Post 方式傳入 Json 結構,例如:

  1.     "filter":[], 
  2.     "id":"CTO_route"
  3.     "order":0, 
  4.     "predicates":[{ 
  5.         "args":{ 
  6.             "pattern":"/CTO" 
  7.         }, 
  8.         "name":"Path" 
  9.     }], 
  10.     "uri":"http://www.scjtxx.cn" 

在更新完成以后,再訪問 http://localhost:8888/CTO 的時候就會把引導到 www.scjtxx.cn 的網(wǎng)站了。

通過上面四步操作,即使不重啟 Spring Cloud Gateway 服務也可以動態(tài)更改路由的配置信息。

總結

由于微服務的盛行,API 網(wǎng)關悄然興起。針對 API 網(wǎng)關本身講述了其存在的原因,它不僅提供了服務的門面,而且可以協(xié)調不同的系統(tǒng)之間的通訊以及服務不同的客戶端接口。

針對 API 網(wǎng)關的最佳時間 Spring Cloud Gateway 的定義和概念的解釋,其實現(xiàn)了路由、過濾器、斷言,針對不同的客戶端請求可以路由到不同的微服務,以及其中幾個組件是如何分工合作完成路由工作的。

在最佳實踐的介紹中分別從:基本路由、權重路由、限流和動態(tài)路由幾個方面進行了闡述。

【51CTO原創(chuàng)稿件,合作站點轉載請注明原文作者和出處為51CTO.com】

 

責任編輯:武曉燕 來源: 51CTO技術棧
相關推薦

2021-03-16 15:12:57

CompletableFuture機制java

2022-02-22 08:25:51

typeScript泛型概念泛型使用

2024-09-09 08:36:36

Java操作遠程服務器

2024-08-12 12:25:25

SpringMVC開發(fā)

2020-09-01 14:17:03

WindowsDefender微軟

2020-10-21 10:02:16

架構運維技術

2020-09-27 06:50:56

Java互聯(lián)網(wǎng)注解

2019-09-03 09:30:46

ss 命令SocketLinux

2020-09-15 09:50:47

程序員技能開發(fā)者

2020-12-18 09:45:33

DockerLinux命令

2020-08-26 14:40:38

explainMySQL數(shù)據(jù)庫

2022-08-01 08:17:46

mysqlwindows系統(tǒng)

2020-11-09 09:03:35

高并發(fā)多線程ThreadLocal

2012-05-02 15:38:49

金山快盤網(wǎng)盤

2022-11-18 17:36:38

Spring架構

2019-11-28 16:48:00

華為Mate X

2022-08-12 15:58:34

Docker

2022-11-07 17:50:36

2018-09-13 10:40:40

Linux命令find

2020-12-07 09:15:00

JavaScript數(shù)組 reduce
點贊
收藏

51CTO技術棧公眾號