開源 API 網關,到底哪個強?
我今天就在和大家探討一下 API Gateway。在微服務的架構下,API 網關是一個常見的架構設計模式。
以下是微服務中常見的問題,需要引入 API 網關來協助解決:
-
微服務提供的 API 的粒度通常與客戶端所需的粒度不同。微服務通常提供細粒度的 API,這意味着客戶端需要與多個服務進行交互。例如,如上所述,需要產品詳細信息的客戶需要從衆多服務中獲取數據。
-
不同的客戶端需要不同的數據。例如,產品詳細信息頁面桌面的桌面瀏覽器版本通常比移動版本更爲詳盡。
-
對於不同類型的客戶端,網絡性能是不同的。例如,與非移動網絡相比,移動網絡通常要慢得多並且具有更高的延遲。而且,當然,任何 WAN 都比 LAN 慢得多。
這意味着本機移動客戶端使用的網絡性能與服務器端 Web 應用程序使用的 LAN 的性能差異很大。服務器端 Web 應用程序可以向後端服務發出多個請求,而不會影響用戶體驗,而移動客戶端只能提供幾個請求。
-
微服務實例數量及其位置(主機 + 端口)動態變化。
-
服務劃分會隨着時間的推移而變化,應該對客戶端隱藏。
-
服務可能會使用多種協議,其中一些協議可能對網絡不友好。
常見的 API 網關主要提供以下的功能:
-
**反向代理和路由:**大多數項目採用網關的解決方案的最主要的原因。給出了訪問後端 API 的所有客戶端的單一入口,並隱藏內部服務部署的細節。
-
**負載均衡:**網關可以將單個傳入的請求路由到多個後端目的地。
-
**身份驗證和授權:**網關應該能夠成功進行身份驗證並僅允許可信客戶端訪問 API,並且還能夠使用類似 RBAC 等方式來授權。
-
**IP 列表白名單 / 黑名單:**允許或阻止某些 IP 地址通過。
-
**性能分析:**提供一種記錄與 API 調用相關的使用和其他有用度量的方法。
-
**限速和流控:**控制 API 調用的能力。
-
**請求變形:**在進一步轉發之前,能夠在轉發之前轉換請求和響應(包括 Header 和 Body)。
-
**版本控制:**同時使用不同版本的 API 選項或可能以金絲雀發佈或藍 / 綠部署的形式提供慢速推出 API。
-
**斷路器:**微服務架構模式有用,以避免使用中斷。
-
**多協議支持:**WebSocket/GRPC。
-
**緩存:**減少網絡帶寬和往返時間消耗,如果可以緩存頻繁要求的數據,則可以提高性能和響應時間
-
**API 文檔:**如果計劃將 API 暴露給組織以外的開發人員,那麼必須考慮使用 API 文檔,例如 Swagger 或 OpenAPI。
有很多的開源軟件可以提供 API 網關的支持,下面我們就看看他們各自的架構和功能。
爲了對這些開源網關進行基本功能的驗證,我創建了一些代碼,使用 OpenAPI 生成了四個基本的 API 服務,包含 Golang,Nodejs,Python Flask 和 Java Spring。
API 使用了常見的寵物商店的樣例,聲明如下:
openapi: "3.0.0"
info:
version: 1.0.0
title: Swagger Petstore
license:
name: MIT
servers:
- url: http://petstore.swagger.io/v1
paths:
/pets:
get:
summary: List all pets
operationId: listPets
tags:
- pets
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
required: false
schema:
type: integer
format: int32
responses:
'200':
description: A paged array of pets
headers:
x-next:
description: A link to the next page of responses
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/Pets"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
post:
summary: Create a pet
operationId: createPets
tags:
- pets
responses:
'201':
description: Null response
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/pets/{petId}:
get:
summary: Info for a specific pet
operationId: showPetById
tags:
- pets
parameters:
- name: petId
in: path
required: true
description: The id of the pet to retrieve
schema:
type: string
responses:
'200':
description: Expected response to a valid request
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
default:
description: unexpected error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Pet:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
name:
type: string
tag:
type: string
Pets:
type: array
items:
$ref: "#/components/schemas/Pet"
Error:
type: object
required:
- code
- message
properties:
code:
type: integer
format: int32
message:
type: string
構建好的 Web 服務通過 Docker Compose 來進行容器化的部署。
version: "3.7"
services:
goapi:
container_name: goapi
image: naughtytao/goapi:0.1
ports:
- "18000:8080"
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
memory: 256M
nodeapi:
container_name: nodeapi
image: naughtytao/nodeapi:0.1
ports:
- "18001:8080"
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
memory: 256M
flaskapi:
container_name: flaskapi
image: naughtytao/flaskapi:0.1
ports:
- "18002:8080"
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
memory: 256M
springapi:
container_name: springapi
image: naughtytao/springapi:0.1
ports:
- "18003:8080"
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
memory: 256M
我們在學習這些開源網關架構的同時,也會對其最基本的路由轉發功能作出驗證。
這裏用戶發送的請求 server/service_name/v1/ 會發送給 API 網關,網關通過 service name 來路由到不同的後端服務。
我們使用 K6 用 100 個併發跑 1000 次測試的結果如上圖,我們看到直連的綜合響應,每秒可以處理的請求數量大概是 1100+。
Nginx
Nginx 是異步框架的網頁服務器,也可以用作反向代理、負載平衡器和 HTTP 緩存。
該軟件由伊戈爾 · 賽索耶夫創建並於 2004 年首次公開發布。2011 年成立同名公司以提供支持。2019 年 3 月 11 日,Nginx 公司被 F5 Networks 以 6.7 億美元收購。
Nginx 有以下的特點:
-
由 C 編寫,佔用的資源和內存低,性能高。
-
單進程多線程,當啓動 Nginx 服務器,會生成一個 master 進程,master 進程會 fork 出多個 worker 進程,由 worker 線程處理客戶端的請求。
-
支持反向代理,支持 7 層負載均衡(拓展負載均衡的好處)。
-
高併發,Nginx 是異步非阻塞型處理請求,採用的 epollandqueue 模式。
-
處理靜態文件速度快。
-
高度模塊化,配置簡單。社區活躍,各種高性能模塊出品迅速。
如上圖所示,Nginx 主要由 Master,Worker 和 Proxy Cache 三個部分組成。
**Master 主控:**NGINX 遵循主從架構。它將根據客戶的要求爲 Worker 分配工作。
將工作分配給 Worker 後,Master 將尋找客戶的下一個請求,因爲它不會等待 Worker 的響應。一旦響應來自 Worker,Master 就會將響應發送給客戶端。
**Worker 工作單元:**Worker 是 NGINX 架構中的 Slave。每個工作單元可以單線程方式一次處理 1000 個以上的請求。
一旦處理完成,響應將被髮送到主服務器。單線程將通過在相同的內存空間而不是不同的內存空間上工作來節省 RAM 和 ROM 的大小。多線程將在不同的內存空間上工作。
**Cache 緩存:**Nginx 緩存用於通過從緩存而不是從服務器獲取來非常快速地呈現頁面。在第一個頁面請求時,頁面將被存儲在高速緩存中。
爲了實現 API 的路由轉發,需要只需要對 Nginx 作出如下的配置:
server {
listen 80 default_server;
location /goapi {
rewrite ^/goapi(.*) $1 break;
proxy_pass http://goapi:8080;
}
location /nodeapi {
rewrite ^/nodeapi(.*) $1 break;
proxy_pass http://nodeapi:8080;
}
location /flaskapi {
rewrite ^/flaskapi(.*) $1 break;
proxy_pass http://flaskapi:8080;
}
location /springapi {
rewrite ^/springapi(.*) $1 break;
proxy_pass http://springapi:8080;
}
}
我們基於不同的服務 goapi,nodeapi,flaskapi 和 springapi,分別配置一條路由,在轉發之前,需要利用 rewrite 來去掉服務名,併發送給對應的服務。
使用容器把 Nginx 和後端的四個服務部署在同一個網絡下,通過網關連接路由轉發的。
Nginx 的部署如下:
version: "3.7"
services:
web:
container_name: nginx
image: nginx
volumes:
- ./templates:/etc/nginx/templates
- ./conf/default.conf:/etc/nginx/conf.d/default.conf
ports:
- "8080:80"
environment:
- NGINX_HOST=localhost
- NGINX_PORT=80
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
memory: 256M
K6 通過 Nginx 網關的測試結果如下:
每秒處理的請求數量是 1093,和不通過網關轉發相比非常接近。
從功能上看,Nginx 可以滿足用戶對於 API 網關的大部分需求,可以通過配置和插件的方式來支持不同的功能,性能非常優秀。
缺點是沒有管理的 UI 和管理 API,大部分的工作都需要手工配置 config 文件的方式來進行。商業版本的功能會更加完善。
Kong
Kong 是基於 NGINX 和 OpenResty 的開源 API 網關。
Kong 的總體基礎結構由三個主要部分組成:NGINX 提供協議實現和工作進程管理,OpenResty 提供 Lua 集成並掛鉤到 NGINX 的請求處理階段。
而 Kong 本身利用這些掛鉤來路由和轉換請求。數據庫支持 Cassandra 或 Postgres 存儲所有配置。
Kong 附帶各種插件,提供訪問控制,安全性,緩存和文檔等功能。它還允許使用 Lua 語言編寫和使用自定義插件。
Kong 也可以部署爲 Kubernetes Ingress 並支持 GRPC 和 WebSockets 代理。
NGINX 提供了強大的 HTTP 服務器基礎結構。它處理 HTTP 請求處理,TLS 加密,請求日誌記錄和操作系統資源分配(例如,偵聽和管理客戶端連接以及產生新進程)。
NGINX 具有一個聲明性配置文件,該文件位於其主機操作系統的文件系統中。
雖然僅通過 NGINX 配置就可以實現某些 Kong 功能(例如,基於請求的 URL 確定上游請求路由),但修改該配置需要一定級別的操作系統訪問權限,以編輯配置文件並要求 NGINX 重新加載它們。
而 Kong 允許用戶執行以下操作:通過 RESTful HTTP API 更新配置。Kong 的 NGINX 配置是相當基本的:除了配置標準標頭,偵聽端口和日誌路徑外,大多數配置都委託給 OpenResty。
在某些情況下,在 Kong 的旁邊添加自己的 NGINX 配置非常有用,例如在 API 網關旁邊提供靜態網站。在這種情況下,您可以修改 Kong 使用的配置模板。
NGINX 處理的請求經過一系列階段。NGINX 的許多功能(例如,使用 C 語言編寫的模塊)都提供了進入這些階段的功能(例如,使用 gzip 壓縮的功能)。
雖然可以編寫自己的模塊,但是每次添加或更新模塊時都必須重新編譯 NGINX。爲了簡化添加新功能的過程,Kong 使用了 OpenResty。
OpenResty 是一個軟件套件,捆綁了 NGINX,一組模塊,LuaJIT 和一組 Lua 庫。
其中最主要的是 ngx_http_lua_module 一個 NGINX 模塊,該模塊嵌入 Lua 併爲大多數 NGINX 請求階段提供 Lua 等效項。
這有效地允許在 Lua 中開發 NGINX 模塊,同時保持高性能(LuaJIT 相當快),並且 Kong 用它來提供其核心配置管理和插件管理基礎結構。
Kong 通過其插件體系結構提供了一個框架,可以掛接到上述請求階段。從上面的示例開始,Key Auth 和 ACL 插件都控制客戶端(也稱爲使用者)是否應該能夠發出請求。
每個插件都在其處理程序中定義了自己的訪問函數,並且該函數針對通過給定路由或服務啓用的每個插件執行 kong.access()。
執行順序由優先級值決定,如果 Key Auth 的優先級爲 1003,ACL 的優先級爲 950,則 Kong 將首先執行 Key Auth 的訪問功能,如果它不放棄請求,則將執行 ACL,然後再通過將該 ACL 傳遞給上游 proxy_pass。
由於 Kong 的請求路由和處理配置是通過其 admin API 控制的,因此可以在不編輯底層 NGINX 配置的情況下即時添加和刪除插件配置。
因爲 Kong 本質上提供了一種在 API 中注入位置塊(通過 API 定義)和配置的方法。它們通過將插件,證書等分配給這些 API。
我們使用以下的配置部署 Kong 到容器中(省略四個微服務的部署):
version: '3.7'
volumes:
kong_data: {}
networks:
kong-net:
external: false
services:
kong:
image: "${KONG_DOCKER_TAG:-kong:latest}"
user: "${KONG_USER:-kong}"
depends_on:
- db
environment:
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_ADMIN_LISTEN: '0.0.0.0:8001'
KONG_CASSANDRA_CONTACT_POINTS: db
KONG_DATABASE: postgres
KONG_PG_DATABASE: ${KONG_PG_DATABASE:-kong}
KONG_PG_HOST: db
KONG_PG_USER: ${KONG_PG_USER:-kong}
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_PG_PASSWORD_FILE: /run/secrets/kong_postgres_password
secrets:
- kong_postgres_password
networks:
- kong-net
ports:
- "8080:8000/tcp"
- "127.0.0.1:8001:8001/tcp"
- "8443:8443/tcp"
- "127.0.0.1:8444:8444/tcp"
healthcheck:
test: ["CMD", "kong", "health"]
interval: 10s
timeout: 10s
retries: 10
restart: on-failure
deploy:
restart_policy:
condition: on-failure
db:
image: postgres:9.5
environment:
POSTGRES_DB: ${KONG_PG_DATABASE:-kong}
POSTGRES_USER: ${KONG_PG_USER:-kong}
POSTGRES_PASSWORD_FILE: /run/secrets/kong_postgres_password
secrets:
- kong_postgres_password
healthcheck:
test: ["CMD", "pg_isready", "-U", "${KONG_PG_USER:-kong}"]
interval: 30s
timeout: 30s
retries: 3
restart: on-failure
deploy:
restart_policy:
condition: on-failure
stdin_open: true
tty: true
networks:
- kong-net
volumes:
- kong_data:/var/lib/postgresql/data
secrets:
kong_postgres_password:
file: ./POSTGRES_PASSWORD
數據庫選擇了 PostgreSQL,開源版本沒有 Dashboard,我們使用 RestAPI 創建所有的網關路由:
curl -i -X POST http://localhost:8001/services \
--data name=goapi \
--data url='http://goapi:8080'
curl -i -X POST http://localhost:8001/services/goapi/routes \
--data 'paths[]=/goapi' \
--data name=goapi
需要先創建一個 service,然後在該 service 下創建一條路由。
使用 K6 壓力測試的結果如下:
每秒請求數 705 要明顯弱於 Nginx,所以所有的功能都是有成本的。
APISIX
Apache APISIX 是一個動態、實時、高性能的 API 網關, 提供負載均衡、動態上游、灰度發佈、服務熔斷、身份認證、可觀測性等豐富的流量管理功能。
APISIX 於 2019 年 4 月由中國的支流科技創建,於 6 月開源,並於同年 10 月進入 Apache 孵化器。
支流科技對應的商業化產品的名字叫 API7 。APISIX 旨在處理大量請求,並具有較低的二次開發門檻。
APISIX 的主要功能和特點有:
-
雲原生設計,輕巧且易於容器化。
-
集成了統計和監視組件,例如 Prometheus,Apache Skywalking 和 Zipkin。
-
支持 gRPC,Dubbo,WebSocket,MQTT 等代理協議,以及從 HTTP 到 gRPC 的協議轉碼,以適應各種情況。
-
擔當 OpenID 依賴方的角色,與 Auth0,Okta 和其他身份驗證提供程序的服務連接。
-
通過在運行時動態執行用戶功能來支持無服務器,從而使網關的邊緣節點更加靈活。
-
支持插件熱加載。
-
不鎖定用戶,支持混合雲部署架構。
-
網關節點無狀態,可以靈活擴展。
從這個角度來看,API 網關可以替代 Nginx 來處理南北流量,也可以扮演 Istio 控制平面和 Envoy 數據平面的角色來處理東西向流量。
APISIX 的架構如下圖所示:
APISIX 包含一個數據平面,用於動態控制請求流量;一個用於存儲和同步網關數據配置的控制平面,一個用於協調插件的 AI 平面,以及對請求流量的實時分析和處理。
它構建在 Nginx 反向代理服務器和鍵值存儲 etcd 的之上,以提供輕量級的網關。
它主要用 Lua 編寫,Lua 是類似於 Python 的編程語言。它使用 Radix 樹進行路由,並使用前綴樹進行 IP 匹配。
使用 etcd 而不是關係數據庫來存儲配置可以使它更接近雲原生,但是即使在任何服務器宕機的情況下,也可以確保整個網關係統的可用性。
所有組件都是作爲插件編寫的,因此其模塊化設計意味着功能開發人員只需要關心自己的項目即可。
內置的插件包括流控和速度限制,身份認證,請求重寫,URI 重定向,開放式跟蹤和無服務器。
APISIX 支持 OpenResty 和 Tengine 運行環境,並且可以在 Kubernetes 的裸機上運行。它同時支持 X86 和 ARM64。
我們同樣使用 Docker Compose 來部署 APISIX:
version: "3.7"
services:
apisix-dashboard:
image: apache/apisix-dashboard:2.4
restart: always
volumes:
- ./dashboard_conf/conf.yaml:/usr/local/apisix-dashboard/conf/conf.yaml
ports:
- "9000:9000"
networks:
apisix:
ipv4_address: 172.18.5.18
apisix:
image: apache/apisix:2.3-alpine
restart: always
volumes:
- ./apisix_log:/usr/local/apisix/logs
- ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro
depends_on:
- etcd
##network_mode: host
ports:
- "8080:9080/tcp"
- "9443:9443/tcp"
networks:
apisix:
ipv4_address: 172.18.5.11
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
memory: 256M
etcd:
image: bitnami/etcd:3.4.9
user: root
restart: always
volumes:
- ./etcd_data:/etcd_data
environment:
ETCD_DATA_DIR: /etcd_data
ETCD_ENABLE_V2: "true"
ALLOW_NONE_AUTHENTICATION: "yes"
ETCD_ADVERTISE_CLIENT_URLS: "http://0.0.0.0:2379"
ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379"
ports:
- "2379:2379/tcp"
networks:
apisix:
ipv4_address: 172.18.5.10
networks:
apisix:
driver: bridge
ipam:
config:
- subnet: 172.18.0.0/16
開源的 APISIX 支持 Dashboard 的方式來管理路由,而不是像 KONG 把儀表盤功能限制在商業版本中。
但是 APISIX 的儀表盤不支持對路由 URI 進行改寫,所以我們只好使用 RestAPI 來創建路由。
創建一個服務的路由的命令如下:
curl --location --request PUT 'http://127.0.0.1:8080/apisix/admin/routes/1' \
--header 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
--header 'Content-Type: text/plain' \
--data-raw '{
"uri": "/goapi/*",
"plugins": {
"proxy-rewrite": {
"regex_uri": ["^/goapi(.*)$","$1"]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"goapi:8080": 1
}
}
}'
使用 K6 壓力測試的結果如下:
APISix 取得了 1155 的好成績,表現出接近不經過網關的性能,可能緩存起到了很好的效果。
Tyk
Tyk 是一款基於 Golang 和 Redis 構建的開源 API 網關。它於 2014 年創建,比 AWS 的 API 網關即服務功能早。Tyk 用 Golang 編寫,並使用 Golang 自己的 HTTP 服務器。
Tyk 支持不同的運行方式:雲,混合(在自己的基礎架構中爲 GW)和本地。
Tyk 由 3 個組件組成:
-
**網關:**處理所有應用流量的代理。
-
**儀表板:**可以從中管理 Tyk,顯示指標和組織 API 的界面。
-
**Pump:**負責持久保存指標數據,並將其導出到 MongoDB(內置),ElasticSearch 或 InfluxDB 等。
我們同樣使用 Docker Compose 來創建 Tyk 網關來進行功能驗證。
version: '3.7'
services:
tyk-gateway:
image: tykio/tyk-gateway:v3.1.1
ports:
- 8080:8080
volumes:
- ./tyk.standalone.conf:/opt/tyk-gateway/tyk.conf
- ./apps:/opt/tyk-gateway/apps
- ./middleware:/opt/tyk-gateway/middleware
- ./certs:/opt/tyk-gateway/certs
environment:
- TYK_GW_SECRET=foo
depends_on:
- tyk-redis
tyk-redis:
image: redis:5.0-alpine
ports:
- 6379:6379
Tyk 的 Dashboard 也是屬於商業版本的範疇,所我們又一次需要藉助 API 來創建路由,Tyk 是通過 app 的概念來創建和管理路由的,你也可以直接寫 app 文件。
curl --location --request POST 'http://localhost:8080/tyk/apis/' \
--header 'x-tyk-authorization: foo' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "GO API",
"slug": "go-api",
"api_id": "goapi",
"org_id": "goapi",
"use_keyless": true,
"auth": {
"auth_header_name": "Authorization"
},
"definition": {
"location": "header",
"key": "x-api-version"
},
"version_data": {
"not_versioned": true,
"versions": {
"Default": {
"name": "Default",
"use_extended_paths": true
}
}
},
"proxy": {
"listen_path": "/goapi/",
"target_url": "http://host.docker.internal:18000/",
"strip_listen_path": true
},
"active": true
}'
使用 K6 壓力測試的結果如下:
Tyk 的結果在 400-600 左右,性能上和 KONG 接近。
Zuul
Zuul 是 Netflix 開源的基於 Java 的 API 網關組件。
Zuul 包含多個組件:
-
**zuul-core:**該庫包含編譯和執行過濾器的核心功能。
-
**zuul-simple-webapp:**該 Webapp 展示了一個簡單的示例,說明如何使用 zuul-core 構建應用程序。
-
**zuul-netflix:**將其他 NetflixOSS 組件添加到 Zuul 的庫,例如,使用 Ribbon 路由請求。
-
**zuul-netflix-webapp:**將 zuul-core 和 zuul-netflix 打包到一個易於使用的程序包中的 webapp。
Zuul 提供了靈活性和彈性,部分是通過利用其他 Netflix OSS 組件進行的:
-
Hystrix 用於流控。包裝對始發地的呼叫,這使我們可以在發生問題時丟棄流量並確定流量的優先級。
-
Ribbon 是來自 Zuul 的所有出站請求的客戶,它提供有關網絡性能和錯誤的詳細信息,並處理軟件負載平衡以實現均勻的負載分配。
-
Turbine 實時彙總細粒度的指標,以便我們可以快速觀察問題並做出反應。
-
Archaius 處理配置並提供動態更改屬性的能力。
Zuul 的核心是一系列過濾器,它們能夠在路由 HTTP 請求和響應期間執行一系列操作。
以下是 Zuul 過濾器的主要特徵:
-
**類型:**通常定義路由流程中應用過濾器的階段。(儘管它可以是任何自定義字符串)
-
**執行順序:**在類型中應用,定義跨多個過濾器的執行順序。
-
**準則:**執行過濾器所需的條件。
-
**動作:**如果符合條件,則要執行的動作。
class DeviceDelayFilter extends ZuulFilter {
def static Random rand = new Random()
@Override
String filterType() {
return 'pre'
}
@Override
int filterOrder() {
return 5
}
@Override
boolean shouldFilter() {
return RequestContext.getRequest().
getParameter("deviceType")?equals("BrokenDevice"):false
}
@Override
Object run() {
sleep(rand.nextInt(20000)) // Sleep for a random number of
// seconds between [0-20]
}
}
Zuul 提供了一個框架來動態讀取,編譯和運行這些過濾器。過濾器不直接相互通信。
而是通過每個請求唯一的 RequestContext 共享狀態。過濾器使用 Groovy 編寫。
有幾種與請求的典型生命週期相對應的標準過濾器類型:
-
Pre 過濾器在路由到原點之前執行。示例包括請求身份驗證,選擇原始服務器以及記錄調試信息。
-
Route 路由過濾器處理將請求路由到源。這是使用 Apache HttpClient 或 Netflix Ribbon 構建和發送原始 HTTP 請求的地方。
-
在將請求路由到源之後,將執行 Post 過濾器。示例包括將標準 HTTP 標頭添加到響應,收集統計信息和指標以及將響應從源流傳輸到客戶端。
-
在其他階段之一發生錯誤時,將執行 Error 過濾器。
Spring Cloud 創建了一個嵌入式 Zuul 代理,以簡化一個非常常見的用例的開發,在該用例中,UI 應用程序希望代理對一個或多個後端服務的調用。
此功能對於用戶界面代理所需的後端服務很有用,從而避免了爲所有後端獨立管理 CORS 和身份驗證問題的需求 。
要啓用它,請使用 @EnableZuulProxy 註解一個 Spring Boot 主類,這會將本地調用轉發到適當的服務。
Zuul 是 Java 的一個庫,他並不是一款開箱即用的 API 網關,所以需要用 Zuul 開發一個應用來對其功能進行測試。
對應的 Java 的 POM 如下:
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>naughtytao.apigateway</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.7.RELEASE</version>
<relativePath />
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!-- Dependencies -->
<spring-cloud.version>Camden.SR7</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- enable authentication if security is included -->
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency> -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- API, java.xml.bind module -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>2.3.2</version>
</dependency>
<!-- Runtime, com.sun.xml.bind module -->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.0.0-M5</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
主要應用代碼如下:
package naughtytao.apigateway.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Bean;
import naughtytao.apigateway.demo.filters.ErrorFilter;
import naughtytao.apigateway.demo.filters.PostFilter;
import naughtytao.apigateway.demo.filters.PreFilter;
import naughtytao.apigateway.demo.filters.RouteFilter;
@SpringBootApplication
@EnableAutoConfiguration(exclude = { RabbitAutoConfiguration.class })
@EnableZuulProxy
@ComponentScan("naughtytao.apigateway.demo")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Docker 構建文件如下:
FROM maven:3.6.3-openjdk-11
WORKDIR /usr/src/app
COPY src ./src
COPY pom.xml ./
RUN mvn -f ./pom.xml clean package -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -Dmaven.wagon.http.ssl.ignore.validity.dates=true
EXPOSE 8080
ENTRYPOINT ["java","-jar","/usr/src/app/target/demo-0.0.1-SNAPSHOT.jar"]
#Zuul routes.
zuul.routes.goapi.url=http://goapi:8080
zuul.routes.nodeapi.url=http://nodeapi:8080
zuul.routes.flaskapi.url=http://flaskapi:8080
zuul.routes.springapi.url=http://springapi:8080
ribbon.eureka.enabled=false
server.port=8080
我們同樣使用 Docker Compose 運行 Zuul 的網關來進行驗證:
version: '3.7'
services:
gateway:
image: naughtytao/zuulgateway:0.1
ports:
- 8080:8080
volumes:
- ./config/application.properties:/usr/src/app/config/application.properties
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
memory: 256M
使用 K6 壓力測試的結果如下:
在相同的配置條件下(單核,256M),Zuul 的壓測結果要明顯差於其它幾個,只有 200 左右。
在分配更多資源的情況下,4 核 2G,Zuul 的性能提升到 600-800,所以 Zuul 對於資源的需求還是比較明顯的。
另外需要提及的是,我們使用的是 Zuul1,Netflix 已經推出了 Zuul2。Zuul2 對架構做出了較大的改進。
Zuul1 本質上就是一個同步 Servlet,採用多線程阻塞模型。Zuul2 的基於 Netty 實現了異步非阻塞編程模型。
同步的方式,比較容易調試,但是多線程本身需要消耗 CPU 和內存資源,所以它的性能要差一些。
而採用非阻塞模式的 Zuul,因爲線程開銷小,所支持的鏈接數量要更多,也更節省資源。
Gravitee
Gravitee 是 Gravitee.io 開源的,基於 Java 的,簡單易用,性能高,且具成本效益的開源 API 平臺,可幫助組織保護,發佈和分析您的 API。
Gravitee 可以通過設計工作室和路徑的兩種方式來創建和管理 API:
Gravity 提供網關,API 門戶和 API 管理,其中網關和管理 API 部分是開源的,門戶需要註冊許可證來使用。
後臺使用 MongoDB 作爲存儲,支持 ES 接入。
我們同樣使用 Docker Compose 來部署整個 Gravitee 的棧:
#
# Copyright (C) 2015 The Gravitee team (http://gravitee.io)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
version: '3.7'
networks:
frontend:
name: frontend
storage:
name: storage
volumes:
data-elasticsearch:
data-mongo:
services:
mongodb:
image: mongo:${MONGODB_VERSION:-3.6}
container_name: gio_apim_mongodb
restart: always
volumes:
- data-mongo:/data/db
- ./logs/apim-mongodb:/var/log/mongodb
networks:
- storage
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:${ELASTIC_VERSION:-7.7.0}
container_name: gio_apim_elasticsearch
restart: always
volumes:
- data-elasticsearch:/usr/share/elasticsearch/data
environment:
- http.host=0.0.0.0
- transport.host=0.0.0.0
- xpack.security.enabled=false
- xpack.monitoring.enabled=false
- cluster.name=elasticsearch
- bootstrap.memory_lock=true
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
nofile: 65536
networks:
- storage
gateway:
image: graviteeio/apim-gateway:${APIM_VERSION:-3}
container_name: gio_apim_gateway
restart: always
ports:
- "8082:8082"
depends_on:
- mongodb
- elasticsearch
volumes:
- ./logs/apim-gateway:/opt/graviteeio-gateway/logs
environment:
- gravitee_management_mongodb_uri=mongodb://mongodb:27017/gravitee?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000
- gravitee_ratelimit_mongodb_uri=mongodb://mongodb:27017/gravitee?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000
- gravitee_reporters_elasticsearch_endpoints_0=http://elasticsearch:9200
networks:
- storage
- frontend
deploy:
resources:
limits:
cpus: '1'
memory: 256M
reservations:
memory: 256M
management_api:
image: graviteeio/apim-management-api:${APIM_VERSION:-3}
container_name: gio_apim_management_api
restart: always
ports:
- "8083:8083"
links:
- mongodb
- elasticsearch
depends_on:
- mongodb
- elasticsearch
volumes:
- ./logs/apim-management-api:/opt/graviteeio-management-api/logs
environment:
- gravitee_management_mongodb_uri=mongodb://mongodb:27017/gravitee?serverSelectionTimeoutMS=5000&connectTimeoutMS=5000&socketTimeoutMS=5000
- gravitee_analytics_elasticsearch_endpoints_0=http://elasticsearch:9200
networks:
- storage
- frontend
management_ui:
image: graviteeio/apim-management-ui:${APIM_VERSION:-3}
container_name: gio_apim_management_ui
restart: always
ports:
- "8084:8080"
depends_on:
- management_api
environment:
- MGMT_API_URL=http://localhost:8083/management/organizations/DEFAULT/environments/DEFAULT/
volumes:
- ./logs/apim-management-ui:/var/log/nginx
networks:
- frontend
portal_ui:
image: graviteeio/apim-portal-ui:${APIM_VERSION:-3}
container_name: gio_apim_portal_ui
restart: always
ports:
- "8085:8080"
depends_on:
- management_api
environment:
- PORTAL_API_URL=http://localhost:8083/portal/environments/DEFAULT
volumes:
- ./logs/apim-portal-ui:/var/log/nginx
networks:
- frontend
我們使用管理 UI 來創建四個對應的 API 來進行網關的路由,也可以用 API 的方式,Gravitee 是這個開源網關中,唯一管理 UI 也開源的產品。
使用 K6 壓力測試的結果如下:
和同樣採用 Java 的 Zuul 類似,Gravitee 的響應只能達到 200 左右,而且還出現了一些錯誤。我們只好再一次提高網關的資源分配到 4 核 2G。
提高資源分配後的性能來到了 500-700,稍微好於 Zuul。
總結
本文分析了幾種開源 API 網關的架構和基本功能,爲大家在架構選型的時候提供一些基本的參考信息,本文做作的測試數據比較簡單,場景也比較單一,不能作爲實際選型的依據。
**Nginx:**基於 C 開發的高性能 API 網關,擁有衆多的插件,如果你的 API 管理的需求比較簡單,接受手工配置路由,Nginx 是個不錯的選擇。
**Kong:**是基於 Nginx 的 API 網關,使用 OpenResty 和 Lua 擴展,後臺使用 PostgreSQL,功能衆多,社區的熱度很高,但是性能上看比起 Nginx 有相當的損失。如果你對功能和擴展性有要求,可以考慮 Kong。
**APISIX:**和 Kong 的架構類似,但是採用了雲原生的設計,使用 ETCD 作爲後臺,性能上比起 Kong 有相當的優勢,適合對性能要求高的雲原生部署的場景。特別提一下,APISIX 支持 MQTT 協議,對於構建 IOT 應用非常友好。
**Tyk:**使用 Golang 開發,後臺使用 Redis,性能不錯,如果你喜歡 Golang,可以考慮一下。
要注意的是 Tyk 的開源協議是 MPL,是屬於修改代碼後不能閉源,對於商業化應用不是很友好。
**Zuul:**是 Netflix 開源的基於 Java 的 API 網關組件,他並不是一款開箱即用的 API 網關,需要和你的 Java 應用一起構建,所有的功能都是通過集成其他組件的方式來使用。
適合對於 Java 比較熟悉,用 Java 構建的應用的場景,缺點是性能其他的開源產品要差一些,同樣的性能條件下,對於資源的要求會更多。
**Gravitee:**是 Gravitee.io 開源的基於 Java 的 API 管理平臺,它能對 API 的生命週期進行管理,即使是開源版本,也有很好的 UI 支持。
但是因爲採用了 Java 構建,性能同樣是短板,適合對於 API 管理有強烈需求的場景。
本文所有的代碼可以從這裏獲得:
https://github.com/gangtao/api-gateway
作者:Gang Tao
編輯:陶家龍
出處:zhuanlan.zhihu.com/p/358862217
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/R1rIhuesIv6AaaFJdaFKog