来源:https://zhuanlan.zhihu.com/p/358862217 强烈推荐大家试试国产开源的 API 网关 https://github.com/apache/apisix,非常不错。 本文,我们会看到 APISIX 和其它开源的网关对比,给胖友的武器库提供更多选择! “ 这篇文章由刚哥授权分享,刚哥是 Splunk Information Technology 的架构师,Linkedin:https://www.linkedin.com/in/taogang/。 本文主要分析了 NGINX、Kong、APISIX、Tyk、Zuul、Gravitee 几个开源 API 网关架构及基本功能,测试了一定场景下各个 API 网关的性能,文末附有源码地址。” 正文从这里开始: 春未老,风细柳斜斜。试上超然台上望,半壕春水一城花。烟雨暗千家。 寒食后,酒醒却咨嗟。休对故人思故国,且将新火试新茶。诗酒趁年华。 苏轼·送《望江南·超然台作》 温哥华的春天来了,上面的图就是我家门口的 Marine Gaetway,我今天就在这春色中和大家探讨一下 API Gateway。
图片在微服务的架构下,API 网关是一个常见的架构设计模式。以下是微服务中常见的问题,需要引入 API 网关来协助解决。
常见的 API 网关主要提供以下的功能:
有很多的开源软件可以提供 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
图片我们在学习这些开源网关架构的同时,也会对其最基本的路由转发功能作出验证。这里用户发送的请求http://server/service_name/v1/pets会发送给 API 网关,网关通过 service name 来路由到不同的后端服务。
图片我们使用 K6 用 100 个并发跑 1000 次测试的结果如上图,我们看到直连的综合响应,每秒可以处理的请求数量大概是 1100+ Nginx Nginx 是异步框架的网页服务器,也可以用作反向代理、负载平衡器和 HTTP 缓存。该软件由伊戈尔·赛索耶夫创建并于 2004 年首次公开发布。2011 年成立同名公司以提供支持。2019 年 3 月 11 日,Nginx 公司被 F5 Networks 以 6.7 亿美元收购。 Nginx 有以下的特点:
图片如上图所示,Nginx 主要由 Master,Worker 和 Proxy Cache 三个部分组成。
为了实现 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 来去掉服务名,并发送给对应的服务。 使用容器把 ngnix 和后端的四个服务部署在同一个网络下,通过网关连接路由转发的。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 的主要功能和特点有:
从这个角度来看,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 个组件组成:
我们同样使用 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 提供了灵活性和弹性,部分是通过利用其他 Netflix OSS 组件进行的:
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 编写。
图片有几种与请求的典型生命周期相对应的标准过滤器类型:
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-11WORKDIR /usr/src/appCOPY src ./srcCOPY 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 8080ENTRYPOINT ["java","-jar","/usr/src/app/target/demo-0.0.1-SNAPSHOT.jar"] 路由的配置写在 application.properties 中 #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 是 http://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 网关的架构和基本功能,为大家在架构选型的时候提供一些基本的参考信息,本文做作的测试数据比较简单,场景也比较单一,不能作为实际选型的依据。
本文所有的代码可以从这里获得 https://github.com/gangtao/api-gateway
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有