一文讲解API网关核心功能——就是nginx,无非加入了安全、流控、转换、版本控制等功能
一文讲解API网关核心功能
API网关概述
在微服务架构体系里面,我们一般会使用到微服务网关或叫API网关。大家都比较清楚,在微服务架构体系下本身是去中心化的架构,通过服务注册中心来实现服务注册发现和消费调用,那么为何又需要使用API网关?
在传统的ESB总线进行服务集成的时候我们就经常谈到一个概念就是位置透明,即需要屏蔽底层业务模块提供API接口服务地址信息,并实现多个微服务API接口的统一出口。即类似设计模式里面经常谈到的门面模式。
如何给API网关一个定义?
简单来说API网关就是将所有的微服务提供的API接口服务能力全部汇聚进来,统一接入进行管理,也正是通过统一拦截,就可以通过网关实现对API接口的安全,日志,限流熔断等共性需求。如果再简单说下,通过网关实现了几个关键能力。- 内部的微服务对外部访问来说位置透明,外部应用只需和网关交互
- 统一拦截接口服务,实现安全,日志,限流熔断等需求
从这里,我们就可以看到API网关和传统架构里面的ESB总线是类似的,这些关键能力本身也是ESB服务总线的能力,但是ESB服务总线由于要考虑遗留系统的接入,因此增加了:
- 大量适配器实现对遗留系统的遗留接口适配,多协议转换能力
- 进行数据的复制映射,路由等能力
对于两者,我原来做过一个简单的对比,大家可以参考。
这个概念理解后,我们再回到微服务架构里面。
对于微服务架构大家经常说的最多的就是去中心化的架构,认为ESB中心化架构模式已经过时。而实际上经过上面分析你可以看到。在微服务架构里面的API网关仍然是中心化的架构模式,所有的API接口都要经过网关这个点。
- 非中心化架构-》走微服务里面的服务注册中心进行接口交互
- 中心化架构-》走网关进行接口服务暴露和共享交互
对于微服务架构里面有无去中心化的架构?当然是有的,即我们常说的微服务模块之间可以通过服务注册中心来实现服务发现查找,服务间的点对点调用即使去中心化的。
如果一个单体拆分为微服务后,完全不需要和外部应用打交道,也不需要共享自己的接口能力,那么这个微服务体系里面就不需要用API网关,仅仅使用服务注册中心即可。通过服务注册中心实现完全的去中心化和接口调用更高的性能。
什么时候需要使用API网关?
如果一个微服务架构下,虽然不会外部的其它应用进行交互和集成,但是整个应用本身存在APP应用端,而APP应用端通过前后端分析开发,同时需要通过互联网访问。本身存在需要一个统一访问API访问入口,同时也需要考虑和内部微服务模块进一步进行安全隔离。当我们谈到这里的时候,你会发现我们常说的API网关的服务代理或透传能力,实际和我们常说的Ngnix反向代理或路由是一个意思。
如果你仅仅是为了统一API接口的访问出口,并考虑类似DMZ区的安全隔离,那么在你架构前期完全不需要马上实施API网关,直接采用Ngnix进行服务路由代理即可。因为在这种架构下,API接口消费端,提供端全部是一个开发团队开发,各种问题分析排查都相当方便,类似API接口安全访问等也可以通过JWT,Auth2.0等统一实现,而且这个过程也并不复杂。
能力开放或多应用外部集成对API管控治理需要
但是当我们面临是和多个外部应用集成,或者说将自己的API接口服务能力开放给外部多个合作伙伴使用的时候,这个时候对于API接口的管控治理要求自然增加。即在常规的服务代理路由基础上,需要增加类似负载均衡,安全,日志,限流熔断等各种能力,而且我们不希望这些能力在API接口开发的时候考虑,而是希望这些能力是在API接入到网关的时候统一灵活配置来实现管控。
那么这个时候使用API网关作用就体现出来。
API网关核心功能说明
对于API网关实际上前面已经多次强调,可以看做是ESB总线的轻量化实现,不再需要复杂的协议转换,适配和数据映射等能力,但是提升了流量控制和安全,实时监控等方面的能力。
在微服务的架构下,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
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
构建好的 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
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
我们在学习这些开源网关架构的同时,也会对其最基本的路由转发功能作出验证。
这里用户发送的请求 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;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
我们基于不同的服务 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
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
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
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
数据库选择了 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
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
需要先创建一个 service,然后在该 service 下创建一条路由。
使用 K6 压力测试的结果如下:
每秒请求数 705 要明显弱于 Nginx,所以所有的功能都是有成本的。
更多开源的api网关见:https://network.51cto.com/article/674451.html