1. 程式人生 > 實用技巧 >Spring Cloud (七):API 閘道器

Spring Cloud (七):API 閘道器

在微服務架構中,不同服務有不同的地址,客戶端需要和多個服務互動,這會帶來一些問題

  • 客戶端需要多次請求不同的微服務,增加複雜性
  • 存在跨域請求
  • 每個微服務都有獨立認證
  • 微服務重構時,客戶端也要跟著改

API 閘道器就是客戶端和服務端的中間層,所有請求都會先經過 API 閘道器,再由 API 閘道器發給後端的微服務

使用了 API 閘道器後可以做統一的登入、認證、監控、日誌、限流、負載均衡、轉發,簡化了前後端的互動和開發

API 閘道器有 nginx,kong,zuul,Spring Cloud Gateway,F5 等




zuul

zuul 是 Netflix 開源的微服務閘道器元件,它可以和 Eureka、Robbin、Hystrix 等元件配合使用

zuul 的功能

  • 認證和安全
  • 預警和監控
  • 動態路由
  • 壓力測試
  • 負載均衡
  • 靜態處理
  • 多區域彈性

啟動 zuul 的程式碼如下

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
@SpringBootApplication
@EnableZuulProxy
public class ZuulServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }
}
server:
  port: 9000

zuul:
  prefix: /api   # 訪問閘道器路徑的字首
  routes:
    service-01:                       # 服務的名字,可以是任意名字,不會出現在 zuul 的 url 中
      path: /product-service/**       # 將服務的這個 path,對映到下面的 URL,或是 service id (如果使用了 Consul 或 Eureka)
      url: http://localhost:8501      # 比如 zuul-host:zuul-port/api/product-service/product/1 會被對映成 http://localhost:8501/product/1
      # service-id: consul-service-1
    service-02:
      path: /hello-service/**
      url: http://localhost:8505
      # service-id: consul-service-1
  retryable: true

訪問 http://localhost:9000/api/product-service/product/1
實際上會被轉發到 http://localhost:8501/product/1


Spring Cloud Gateway

zuul 有版本 1 和版本 2,spring cloud 整合的是版本 1,後續可能不會再改,而是推出 Spring Cloud Gateway

Spring Cloud Gateway 是基於 WebFlux 框架實現的,而 WebFlux 框架底層則使用了高效能的 Reactor 模式通訊框架 Netty

Spring Cloud Gateway 的特性 (https://github.com/spring-cloud/spring-cloud-gateway)

  • 基於Spring 5, Spring Boot 2 and Project Reactor
  • 動態路由
  • 基於 HTTP 請求的任意屬性(Path, Method, Header, Host, etc…)做路由匹配
  • Filters 作用於匹配路由
  • Filters 可以修改 HTTP 請求和響應
  • 支援 Spring Cloud DiscoveryClient
  • 支援負載均衡、限流熔斷等功能

spring cloud gateway 的匹配機制比較強大,啟動 spring cloud gateway 的程式碼如下

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

注意需要移除 spring-boot-starter-web,並且如果有一些 dependency 間接依賴 spring-boot-starter-web,那麼需要通過 exclude 排除掉,不然會報錯:Consider defining a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' in your configuration.

如果用到了 hystrix 的容錯功能,那麼還要把 hystrix 的依賴新增上

@SpringBootApplication
public class SpringCloudGatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudGatewayApplication.class, args);
    }
}
server:
  port: 9000

spring:
  cloud:
    gateway:
      routes:
      - id: service-01               ## 每個路由的 id,任意名字,不會出現在 url 中
        uri: http://localhost:8501   ## 和這個路由匹配的請求都轉發到 http://localhost:8501
        order: 0                     ## 優先順序,一個請求有可能和多個路由匹配,會執行高優先順序的路由
          
        predicates:                  ## 謂詞,滿足下面列舉的條件的請求,就和這個路由匹配上
        - Path=/product-service/**   ## 匹配 url 為 /product-service/** 的請求
                                     ## 即 gateway-host:gateway-port/product-service/** 會被轉發到 http://localhost:8501/**
          
        filters:                     ## 過濾,對請求做一些處理(下面這裡是兩個過濾器)
        - StripPrefix=1              ## 第一個過濾器:截斷路徑中的第一個欄位,比如 /test/abc 變成 /abc
        - name: Hystrix              ## 第二個過濾器:通過 Hystrix 做熔斷
          args:
            name: fallbackCmdA  
            fallbackUri: forward:/fallbackA    ## 將 forward 服務的 fallbackA 路徑的輸出作為熔斷的返回

hystrix:
  command:
    fallbackCmdA:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 5000

也可以通過程式碼實現這些配置功能

spring cloud gateway 的配置功能很強,下面再舉一些例子

# 請求中包含 id 引數的即可匹配路由,比如
# localhost:9000?id=2
predicates:
- Query=id
# 請求中包含 id 引數,且 id 引數以 abc 開頭的即可匹配路由,比如
# localhost:9000?id=abc123
# 注意這裡的 abc. 是正則表示式
predicates:
- Query=id, abc.
# 請求的 header 中包含 X-Request-Id 引數,且引數的值是數字,即可匹配路由,比如
# curl -H "X-Request-Id:88" localhost:9000
predicates:
- Header=X-Request-Id, \d+
# 請求的 cookie 中包含 sessionId,且值為 test,即可匹配路由,比如
# curl --cookie "sessionId=test" localhost:9000
predicates:
- Cookie=sessionId, test
# GET 請求即可匹配路由
predicates:
- Method=GET
# 請求的路徑滿足要求,即可匹配路由,比如
# curl localhost:9000/foo/1
# curl localhost:9000/foo/test
predicates:
- Path=/foo/{segment}
# 請求的遠端地址滿足要求即可匹配路由
predicates:
- RemoteAddr=192.168.1.1/24
# 可以組合使用
predicates:
- Path=/product-service
- Method=GET
- Query=id

Spring Cloud Gateway 的功能配置比 zuul 要多


nginx

nginx 的作用

  • 負載均衡
  • 地址對映和埠對映
  • 靜態檔案支援、偽靜態
  • 快取靜態和偽靜態頁面
  • 緩衝請求和響應
  • 高可用性,高併發抗壓能力,都比較強
  • 訪問控制、限速等功能

安裝 Nginx

sudo apt-get install nginx

Nginx 配置

/etc/nginx/nginx.conf

這個配置檔案會引入下面兩個目錄的配置檔案

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

建立 /etc/nginx/conf.d/test.conf (配置檔案可以有多個)

server {
    listen       80;
    server_name  localhost;

    # location 的 URL 匹配語法
    # ~* 表示正則表示式,不區分大小寫
    # ~  表示正則表示式,要區分大小寫
    # =  表示精確匹配
    # 沒有修飾符的,以指定模式開始,比如 location / 匹配所有以 / 開始的 URL

    # 靜態頁面,直接讀取 html 檔案
    location ~* .*\.html$ {
        gzip on;
        root /usr/share/nginx/html;
    }
        
    # 動態頁面,轉發給後端
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Nginx 效能強,穩定性高,適合作為全域性的閘道器,但有的功能不具備,比如熔斷等


kong

kong 是一個基於 nginx 的,用 nginx_lua 模組編寫擴充套件外掛的閘道器
kong 可以使用 Cassandra/PostgreSQL 資料庫來儲存資料

https://docs.konghq.com/install/ubuntu/

 $ sudo apt-get update
 $ sudo apt-get install -y apt-transport-https curl lsb-core
 $ echo "deb https://kong.bintray.com/kong-deb `lsb_release -sc` main" | sudo tee -a /etc/apt/sources.list
 $ curl -o bintray.key https://bintray.com/user/downloadSubjectPublicKey?username=bintray
 $ sudo apt-key add bintray.key
 $ sudo apt-get update
 $ sudo apt-get install -y kong

配置檔案

cp /etc/kong/kong.conf.default /etc/kong/kong.conf

如果配置資料庫

CREATE USER kong;
CREATE DATABASE kong OWNER kong;
kong migrations bootstrap [-c /etc/kong/kong.conf]

如果不配置資料庫

## 會在當前路徑生成 kong.yml
kong config init
## 在 kong.conf 檔案裡把資料庫選項關掉
database = off
declarative_config = /path/to/kong.yml

啟動 kong

kong start [-c /etc/kong/kong.conf] [--nginx-conf /path/to/custom_nginx.template]

kong 預設監聽以下埠

  • 8000:監聽客戶端的 HTTP 請求,轉發到後端服務
  • 8443:監聽客戶端的 HTTPS 請求,轉發到後端服務,可以 disable
  • 8001:監聽 Admin API HTTP 請求,用於配置 Kong
  • 8444:監聽 Admin API HTTPS 請求,用於配置 Kong

可以通過向 8001 埠傳送 POST 請求進行配置

過載 kong

kong reload

停止 kong

kong stop

kong 可以通過 --nginx-conf 指定 nginx 配置


F5

F5 是直接通過硬體實現,和系統及應用無關,只從網路層判斷處理,效能強,缺點是成本高,配置可能比其他的複雜