API网关作为外部流量访问内部服务的入口,可以屏蔽内部微服务之间的差异,提供动态路由、身份认证、流量控制、协议转换、负载均衡等公共能力,在服务治理中起到非常重要的作用。
在收钱吧业务发展过程中,各业务团队基于自身需求,开发了多个API网关。这些网关使用了多种开发语言和不同的技术栈,管理平台也比较简陋。管理平台功能缺失,不方便API配置和管理,也具有一些安全隐患;API网关使用了多种开发语言和技术栈,导致维护和升级非常困难;还有重复造轮子,消耗了很大的人力和时间成本。随着业务增长,一些业务API网关也暴露出性能问题。
为了解决上述问题,统一API网关技术栈,完善API管理平台,收钱吧在经过调研和评估后,决定基于 Apache APISIX [1] 自研新一代网关。
以上是APISIX的优点总结,也是收钱吧选择基于APISIX开发新一代网关的原因。
收钱吧APISIX二次开发主要围绕插件和管理平台两方面进行。
APISIX官方插件虽然丰富,但是不能满足我们全部的使用需求。一些插件还存在配置过于繁琐的问题,需要对插件裁剪,简化配置。
收钱吧APISIX一部分插件是根据业务需求进行开发的,一部分插件是对APISIX官方插件进行简化和改造。相比官方插件,自研插件只使用 check_schema 、 init 、 access 、 header_filter 和 body_filter 五个扩展点。
收钱吧自研插件分为通用插件和业务线定制插件。相比通用插件,一些业务线插件只是在参数转换和解析响应部分有所区别。为了减少代码冗余,提高开发效率,我们在编写插件时做了一些继承和复用。
收钱吧APISIX对接口响应格式进行了定义: 返回值HTTP状态码为200,格式为{code, data, msg},code=10000时表示成功响应 。这就需要APISIX对上游接口响应进行二次解析和包装。
根据OpenResty lua-nginx-module [2] 文档,Nginx响应是流式输出,并不会等到整个响应体都生成了才往客户端发送数据。 body_filter_by_lua 在一次请求可能会被多次调用。
Nginx output filters may be called multiple times for a single request because response body may be delivered in chunks. Thus, the Lua code specified by in this directive may also run multiple times in the lifetime of a single HTTP request.
插件在解析上游接口响应时,如果每个插件都在 body_filter 中实现获取完整流式响应数据的代码则会过于冗余。基于这点原因,我们设计了一个名称为 wosai_plugin 的基础插件。 wosai_plugin 插件在 header_filter 中设置响应HTTP状态码为200,在 body_filter 中实现了获取完整流式响应数据的处理逻辑,并提供回调方法 resolve_response 由具体插件实现。这样其他插件只需要实现业务逻辑即可。
以下是插件代码复用示例。可以看到,通过代码复用,降低了代码冗余,提高了插件开发效率。
-- 基础插件wosai-plugin示例代码 --
local _M = {}
function _M.header_filter(conf, ctx)
-- access阶段调用ngx.exit返回 不需要处理返回值 --
if ngx.ctx.access_exit then
return
end
ngx.ctx.origin_http_status = ngx.status
ngx.ctx.origin_content_type = ngx.header.content_type or ''
ngx.status = 200
core.response.clear_header_as_body_modified()
ngx.header.content_type = "application/json;charset=utf-8"
end
function _M.body_filter(conf, ctx, resolve_response)
-- access阶段调用ngx.exit返回 不需要处理返回值 --
if ngx.ctx.access_exit then
return
end
local chunk, eof = ngx.arg[1], ngx.arg[2]
if ngx.ctx.buffered == nil then
ngx.ctx.buffered = {}
end
if chunk ~= "" and not ngx.is_subrequest then
table.insert(ngx.ctx.buffered, chunk)
ngx.arg[1] = nil
end
if eof then
local resp_str = table.concat(ngx.ctx.buffered)
ngx.ctx.buffered = nil
-- 处理上游接口返回值,resolve_response由具体插件实现 --
local resp = resolve_response(conf, ctx, resp_str)
ngx.arg[1] = cjson.encode(resp)
ngx.arg[2] = true
end
end
return _M
-- 插件wosai-jsonrpc示例代码 --
local _M = {}
function _M.resolve_response(conf, ctx, resp_str)
-- 解析包装返回值 --
end
function _M.header_filter(conf, ctx)
wosai_plugin.header_filter(conf, ctx)
end
-- 复用wosai-jsonrpc的插件, 可传入自定义resolve_response方法 --
function _M.body_filter(conf, ctx, resolve_response)
if not resolve_response then
resolve_response = _M.resolve_response
end
wosai_plugin.body_filter(conf, ctx, resolve_response)
end
return _M
收钱吧APISIX一些接口会进行协议转换,生成日志时又需要记录接口原始请求。在插件log阶段使用 ngx.req.get_body_data 获取的请求数据为协议转换修改过的,基于APISIX插件机制的日志插件不能满足需求。另一方面,每一个接口配置日志插件过于重复和繁琐。
为了解决这个问题,收钱吧APISIX在OpenResty的 access_by_lua_block 、 body_filter_by_lua_block 和 log_by_lua_block 进行埋点,分别记录原始请求、接口响应和生成日志。
access_by_lua_block {
wosai_logger.log_request() --- 记录原始请求
apisix.http_access_phase()
}
body_filter_by_lua_block {
apisix.http_body_filter_phase()
wosai_logger.log_response() --- 记录接口响应
}
log_by_lua_block {
wosai_logger.log() --- 生成日志
apisix.http_log_phase()
}
APISIX在初始化阶段使用ngx_tpl.lua生成nginx.conf,日志埋点是通过修改ngx_tpl.lua完成的。一些深度定制开发需求,可通过此种方式进行,这样对APISIX的代码侵入会很小。
APISIX虽然自带了Dashboard,但是其没有用户权限控制,无法动态新增和修改插件配置,也不能进行多网关管理,无法满足我们的使用需求。
基于上述原因,收钱吧基于APISIX Admin API开发了网关管理平台,自研管理平台开发工作主要围绕以下几方面进行:
在动态管理插件方面,前端使用了 XRender [3] 根据插件schema动态渲染生成UI,这样极大简化了前端的开发工作。
├── conf # apisix配置文件, 构建镜像时复制到/usr/local/apisix/conf目录
│ ├── README.md
│ ├── config-beta.yaml
│ ├── config-default.yaml
│ ├── config-prod.yaml
│ └── config.yaml
├── lua
│ ├── cli # 修改源码ngx_tpl.lua, 此模板文件用于生成nginx.conf, 在此文件中添加了wosai_logger相关代码
│ ├── plugins # 自研插件,构建镜像时复制到/usr/local/apisix/apisix/plugins目录
│ └── constants.lua # 修改源码constants.lua, 构建镜像时覆盖原文件
├── CHANGELOG.md
├── Dockerfile
└── README.md
上面为收钱吧APISIX的代码结构。收钱吧APISIX自研插件代码与APISIX核心代码是分离的,这样的代码结构比较干净,既方便APISIX版本升级,也方便我们的自研插件进行迭代。
下面为收钱吧APISIX项目的Dockerfile。在构建镜像时, 把自研插件、配置文件等复制到APISIX源码中,这样就形成了完整的收钱吧APISIX代码。
FROM apache/apisix:2.14.0-alpine
WORKDIR /usr/local/apisix
ENV TZ Asia/Shanghai
COPY conf conf
COPY lua/plugins apisix/plugins
COPY lua/cli/ngx_tpl.lua apisix/cli
COPY lua/constants.lua apisix
EXPOSE 9080 9443
CMD ["sh", "-c", "/usr/bin/apisix init && /usr/bin/apisix init_etcd && /usr/local/openresty/bin/openresty -p /usr/local/apisix -c /usr/local/apisix/conf/nginx.conf -g 'daemon off;'"]
STOPSIGNAL SIGQUIT
在版本升级时,只需要本地安装APISIX新版本或者从GitHub获取新版本代码,然后根据新版本同步更新项目中ngx_tpl.lua、config-defaullt.yaml等文件, 再更新Dockerfile中的APISIX镜像版本,就可以完成升级了。
收钱吧APISIX采用了 共享代码,资源隔离 的方式进行部署:
共享代码,使收钱吧API网关技术栈得到统一,既方便管理,也方便维护。资源隔离,可以防止某一业务线API网关出现故障影响到其他业务,提高了网关的可靠性。
收钱吧APISIX二次开发是从去年5月份开始的。到目前为止,收钱吧已经把超过一半的业务线API迁移到了APISIX网关,其他业务线API正在进行迁移测试。在使用过程中,APISIX网关基本上没有出现过故障,APISIX的性能和可靠性经受住了生产环境的验证。
通过使用APISIX,统一了收钱吧API网关技术栈和管理平台,把业务团队从网关开发和维护这项繁重的工作中解放出来,使业务团队专注业务开发,提高了业务开发和上线效率。
在APISIX二次开发过程中,我们体会到APISIX设计和架构非常优秀,插件开发也比较简单。基于Apache APISIX,用户可以快速DIY自己的高性能API网关。
[1]
Apache APISIX: https://github.com/apache/apisix
[2]
lua-nginx-module: https://github.com/openresty/lua-nginx-module#body_filter_by_lua_block
[3]
XRender: https://xrender.fun
留言与评论(共有 0 条评论) “” |