基于openresty+nginx开发的api网关,实现了基本的动态路由、动态upstream、负载均衡、限流、降级的功能,借鉴kong的插件扩展策略,提供了可供自由添加的插件机制
- nginx 配置
- 在nginx配置中添加如下内容
# 需要安装luajit/lua,xxx表示项目根路径,需要在nginx/tengine编译时添加如下模块lua_nginx_module(0.10.11)和lua_upstream_nginx_module(0.07),安装方式参考install/install.sh lua_package_path '../openresty/?.lua;../phi/?.lua;../lib/?.lua;;'; lua_package_cpath '../openresty/?.so;../lib/?.so;;'; # 如果使用了openresty,那么只需引入下面的包 #lua_package_path '../phi/?.lua;../lib/?.lua;;'; #lua_package_cpath '../lib/?.so;;'; lua_code_cache on; # 共享内存的大小按需调整 lua_shared_dict phi 5m; lua_shared_dict phi_events 5m; lua_shared_dict phi_lock 5m; lua_shared_dict phi_router 10m; lua_shared_dict phi_upstream 10m; lua_shared_dict phi_limiter 10m; lua_shared_dict phi_limit_req 128m; lua_shared_dict phi_limit_conn 128m; lua_shared_dict phi_limit_count 128m; init_by_lua_file ../entry_lua_file/init_by_lua.lua; init_worker_by_lua_file ../entry_lua_file/init_worker_by_lua.lua; server { listen 8080; location / { set $backend ''; rewrite_by_lua_file ../entry_lua_file/rewrite_by_lua.lua; access_by_lua_file ../entry_lua_file/access_by_lua.lua; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_pass http://$backend; log_by_lua_file ../entry_lua_file/log_by_lua.lua; } } upstream phi_upstream { server 0.0.0.1; balancer_by_lua_file ../entry_lua_file/balancer_by_lua.lua; # 请按需调整 keepalive 200; }
- phi的配置
- phi/config/default_config.lua中定义了默认的配置项
return { debug = true, -- 是否开启远程debug debug_host = "127.0.0.1", -- debug的host debug_port = 8172, -- 远程debug占用端口 enabled_admin = true, -- 是否启动管理控制台 enabled_policies = { "UNIQUE", "RANGE", "PREFIX", "SUFFIX", "COMPOSITE", "MODULO", "REGEX" }, -- 启用的policy规则 enabled_mappers = { "HEADER", "URI_ARGS", "IP", "URI" }, -- 启用的mapper类型 default_paths = { -- 默认的配置文件加载路径 conf/phi.ini 或者./phi.ini current_path .. "phi.ini", conf_path .. "phi.ini" }, application_context_conf = { -- 默认的application容器配置路径 conf/application.ini 或者./application.ini current_path .. "application.ini", conf_path .. "application.ini" } }
- 配置项读取优先级
- 环境变量中对应的值,环境变量取值会在所有的key前面加PHI_,例如debug对应环境变量中的PHI_DEBUG的值
- default_paths中指定的ini文件中配置的值
- default_config.lua中指定的默认值
在nginx配置中添加如下server
server {
listen 12345;
location / {
default_type application/json;
content_by_lua_file ../entry_lua_file/admin_content_by_lua.lua;
log_by_lua_file ../entry_lua_file/log_by_lua.lua;
}
}- bin目录下的phi:
- 用法: phi start|stop|restart|reload|test
- 可选参数 :
- -D: 启动远程debug模式,默认占用端口8172,感谢[ZeroBrane Studio],[EmmyLua]
- -H: 远程debug 主机名,默认localhost
- -P: 远程debug 端口,默认8172
- -p: nginx启动目录,同nginx -p参数
- -c: nginx启动配置文件名称,同nginx -c参数
- 你也可以使用nginx启动命令来启动项目
- 添加一个api-server
curl -H 'content-type:application/json' -X POST --data \
'{"hostkey": "sample.com","data": {"default": "127.0.0.1:8081"}}' \
http://localhost:12345/router/add
- 访问刚才添加的api server
curl -H 'content-type:application/json' -H 'Host:sample.com' http://localhost:8080
-
-
mapper是一组映射函数,会将请求映射到一个具体的值,以便将这个值交给policy进行运算, 请求参数中如果有mapper字段只需要传递相应的枚举值即可,如"mapper":"ip" 有一些mapper需要指定tag,此时mapper字段应传递一个json对象,如:{"type":"uri_args","tag":"uid"},会取出请求参数中的uid的值
value Description header取请求头中指定字段,配合tag字段使用,tag值表示请求头的名称 host取请求头/请求行中的host,无需指定tag ip取请求头中的用户ip,无需指定tag uri_args取请求中的uri参数,配合tag字段使用,tag值表示参数名称 uri取请求行中的uri cookie取请求头中的cookie,配合tag字段使用,tag值表示cookie名称 -
policy是一组计算函数,
string calculate(arg, hashTable),此函数接收一个具体值和一个规则对象, 该对象中的数据结构应符合{"result":"xxx","expression":"xxxxxxxx"}的规则(组合规则除外), calculate函数会根据expression字段的表达式计算出一个boolean值,如果为true就立即返回result的值,否则就继续执行value Description 哈希表结构示例 modulo对10取模运算 [{"result":"upstream1","expression":1},{"result":"upstream2","expression":2},{"result":"upstream3","expression":3}] range范围运算 [{"result":"hehehehe","expression":[1001,2000]},{"result":"upstream1","expression":[min,"NONE"]},{"result":"upstream2","expression":[min,max]},{"result":"upstream3","expression":["NONE",max]}] prefix取前缀匹配 [{"result":"upstream1","expression":"/api"},{"result":"upstream2","expression":"/openApi"}] suffix取后缀匹配 [{"result":"upstream1","expression":".js"},{"result":"upstream2","expression":".css"}] regexlua正则匹配 [{"result":"upstream1","expression":"regex1"},{"result":"upstream2","expression":"regex2"}] composite组合规则 {"primary":{"order":2,mapper":"ip","policy":"range_policy","routerTable":[{"result":"secondary","expression":[1,100]},{"result":"upstream","expression":["NONE",1000]}]},"secondary":{"order":2,"mapper":"ip","policy":"range_policy","routerTable":[{"result":"secondary","expression":[1,100]}]}} unique返回唯一结果 {"result":"upstream1"}
-
-
- 动态路由,实现分流/灰度功能
- 添加路由规则
- 请求路径:/router/add
- 请求方式:POST
- 返回数据格式:JSON
- 请求字段说明:
- 请求数据示例:
- 以下示例表示:从uri参数中提取uid的值,根据值的范围进行匹配,小于2000的将会被路由到upstream1, 2001到3000之间的将被路由到upstream2,大于3000的将被路由到upstream3
{ "hostkey": "www.sample.com", "data": { "default": "an upstream name", "policies": [{ "order": 3, "policy": "range", "mapper": {"type":"uri_args","tag":"uid"}, "routerTable": { "upstream1": [ 2000, "NONE" ], "upstream2": [ 2001, 3000 ], "upstream3": [ "NONE", 3000 ] } }] } }
- 查询路由规则
- 请求路径:/router/get
- 请求方式:GET
- 请求数据格式:uri参数
- 请求数据示例:
hostkey=xxx - 返回数据格式:JSON
- 返回数据示例:
{ "status": { "message": "ok", "success": true }, "code": 200, "data": { "policies": [ { "order": 3, "policy": "range", "mapper": {"type":"uri_args","tag":"uid"}, "routerTable": { "upstream4": [ 1001, 2000 ] } }, { "order": 2, "policy": "range", "mapper": "ip", "routerTable": { "upstream1": [ 1001, 100 ] } } ], "default": "phi_upstream" } }
- 查询所有路由规则
- 请求路径:/router/getAll
- 请求方式:GET
- 请求数据格式:uri参数
- 请求数据示例:
hostkey=xxx - 返回数据格式:JSON
- 删除路由规则
- 请求路径:/router/del
- 请求方式:GET
- 请求数据格式: uri参数
- 请求数据示例:
hostkey=xxx
-
- 动态的upstream列表,简单的负载均衡功能
- 查询所有运行中的upstream信息
- 请求路径:/upstream/getAllRuntimeInfo
- 请求方式:GET
- 返回数据格式:JSON
- 返回数据示例:
{ "status": { "message": "ok", "success": true }, "code": 200, "data": { "stable_ups": { "primary": [ { "weight": 1, "id": 0, "conns": 0, "fails": 0, "current_weight": 0, "fail_timeout": 10, "effective_weight": 1, "name": "127.0.0.1:8888", "max_fails": 1 } ], "backup": {} }, "dynamic_ups": { "127.0.0.1:12346": { "weight": 100 }, "127.0.0.1:7777": { "weight": 10 }, "127.0.0.1:8888": { "weight": 10 }, "strategy": "resty_chash", "mapper": "ip" } } }
- 查询所有upstream信息
- 请求路径:/upstream/getAllUpsInfo
- 请求方式:GET
- 返回数据格式:JSON
- 返回数据示例:
{ "status": { "message": "ok", "success": true }, "code": 200, "data": { "stable_ups": { "primary": [ { "weight": 1, "id": 0, "conns": 0, "fails": 0, "current_weight": 0, "fail_timeout": 10, "effective_weight": 1, "name": "127.0.0.1:8888", "max_fails": 1 } ], "backup": {} }, "dynamic_ups": { "mapper": "ip", "servers": [ { "name": "127.0.0.1:8989", "weight": 10 } ], "strategy": "resty_chash" } } }
- 查询指定upstream的server列表
- 请求路径:/upstream/getUpstreamServers
- 请求方式:GET/POST
- 请求数据格式:uri参数/表单参数
- 请求数据示例:
upstreamName=xxxx - 返回数据格式:JSON
- 返回数据示例:
{ "status": { "message": "ok", "success": true }, "code": 200, "data": { "127.0.0.1:12346": { "weight": 100 }, "127.0.0.1:7777": { "weight": 10 }, "127.0.0.1:8888": { "weight": 10 }, "strategy": "resty_chash", "mapper": "ip" } }
- 从server列表中摘除/启动指定server,暂时不参与/重新参与负载均衡
- 请求路径:/upstream/setPeerDown
- 请求方式:GET/POST
- 请求数据格式:uri参数/表单参数
- 请求数据示例:
upstreamName=xxxx&serverName=xxx&down=true
- 添加或者更新指定upstream
- 请求路径:/upstream/addOrUpdateUps
- 请求方式:POST
- 请求数据格式:JSON
- 字段说明:
- 请求数据示例:
{ "upstreamName": "a new upstream", "strategy": "resty_chash", "mapper": "ip", "servers": [ { "name": "127.0.0.1:8989", "info": { "weight": 10 } }, { "name": "127.0.0.1:8989", "info": { "weight": 10 } } ] }
- 删除指定upstream
- 请求路径:/upstream/delUps
- 请求方式:GET
- 请求数据格式:uri参数
- 请求数据示例:
upstreamName=xxxx
- 从指定upstream中删除servers
- 请求路径:/upstream/delUpstreamServers
- 请求方式:POST
- 请求数据格式:JSON
- 请求数据示例:
{ "upstreamName": "a new upstream", "servers": [ "127.0.0.1:8989" ] }
- 向指定upstream中添加server,upstreamName不存在时会返回错误
- 请求路径:/upstream/addUpstreamServers
- 请求方式:POST
- 请求数据格式:JSON
- 请求数据示例:
{ "upstreamName":"an exists upstream name", "servers":[{ "name":"127.0.0.1:8989", "info":{ "weight":100 } }] }
-
- 动态路由,实现分流/灰度功能
- 添加路由规则
- 请求路径:/rate/add
- 请求方式:POST
- 返回数据格式:JSON
- 请求字段说明
-
type:枚举值,多个规则排序,数值越大优先级越高
value Description req限制请求速率,type=req时,需传递rate(速率),burst(允许突发值)两个参数
例如:rate=200,burst=100,表示允许200req/sec,流量突发时最多允许达到300req/sec,其中超过200req的请求将被delayconn限制并发连接数,type=conn时,需传递conn(连接数),burst(允许突发连接数),delay(延迟时间)三个参数
例如conn=100,burst=100,delay=0.05,表示正常允许100并发连接,突发流量下最大允许200并发连接,其中超出的部分,将被deplaycount限制单位时间窗口内的调用次数,type=count时,需传递rate(速率),time_window(单位时间窗口)两个参数
例如rate=1000,time_window=60,表示60秒内最多允许调用1000次,超出的请求会被拒绝traffic组合限流,启动组合限流时,组合限流需在请求数据中添加policies字段,数组类型[{"time_window": 10,"rate": 1,"}],每一条数据为一条限流规则,如需对多个规则进行排序,请在每一条中添加order字段,数字越大优先级越高 -
mapper:可选值,未传递此参数的情况下,所有限流粒度都是以host作为限流key,请求映射函数,对应枚举mapper
-
tag:参考mapper中对tag的说明
-
rejected:拒绝策略,rejected:直接拒绝访问,degrade:查询降级策略
-
- 请求数据示例:
{ "hostkey": "www.sample.com", "data": { "time_window": 10, "rejected": "xxx", "rate": 1, "type": "count" } }
- 查询路由规则
- 请求路径:/rate/get
- 请求方式:GET
- 请求数据格式:uri参数
- 请求数据示例:
hostkey=xxx - 返回数据格式:JSON
- 查询所有路由规则
- 请求路径:/rate/getAll
- 请求方式:GET
- 请求数据格式:uri参数
- 请求数据示例:
hostkey=xxx - 返回数据格式:JSON
- 删除路由规则
- 请求路径:/rate/del
- 请求方式:GET
- 请求数据格式: uri参数
- 请求数据示例:
hostkey=xxx
-
- 接入层的简易降级开关,所有的降级都是以host+uri为依据
- 添加降级数据
- 请求路径:/degrade/add
- 请求方式:POST
- 返回数据格式:JSON
- 请求字段说明
-
type:枚举值
value Description fake返回target中指定的数据 redirect重定向到target指定的路径 -
"enabled": true/false,针对此uri的降级开关
-
- 请求数据示例:
{ "hostkey": "auto.deploy.com", "infos": [ { "uri": "/uri", "info": { "type": "redirect", "enabled": true, "target": "http://sample.com" } } ] }
- 查询路由规则
- 请求路径:/degrade/enabled
- 请求方式:GET
- 请求数据格式:uri参数
- 请求数据示例:
hostkey=xxx&uri=/xxxx - 返回数据格式:JSON
- 查询路由规则
- 请求路径:/degrade/get
- 请求方式:GET
- 请求数据格式:uri参数
- 请求数据示例:
hostkey=xxx - 返回数据格式:JSON
- 查询所有路由规则
- 请求路径:/degrade/getAll
- 请求方式:GET
- 请求数据格式:uri参数
- 返回数据格式:JSON
- 删除路由规则
- 请求路径:/degrade/del
- 请求方式:GET
- 请求数据格式: uri参数
- 请求数据示例:
hostkey=xxx&uri=/xxxx
我在自己的笔记本上使用ab进行了简单的负载测试:
- 测试准备
- nginx配置:开启一个worker进程,并且最多占用一个cpu核心
worker_processes 1; worker_cpu_affinity auto;
- 开启另外一台nginx,作为一个静态文件服务器,目标文件大小为1Kb
- 测试机器配置 I7-6700HQ + 16GB
- nginx配置:开启一个worker进程,并且最多占用一个cpu核心
- 使用AB进行压力测试,20个并发,发起1000000次请求,使worker进程cpu达到100%
- 我测试了原生的nginx反向代理进行对比:50K T/S
- 开启router功能后测试结果:32K T/S
- 开启dynamic ups和balancer之后: 25K T/S - 此测试应该可以反映,服务器在满负荷下的吞吐量极限值,在开启多核情况下性能会有更高的提升
- 测试在2000并发连接数下的系统稳定性:
- TODO
- mobdebug lua的远程调试工具
- classic 使用lua的模拟面向对象编程
- kong kong的一些lua库
- lua-resty-mlcache lua的多级缓存工具,封装了lrucache和shared_dict的操作
- lua-resty-worker-events worker间异步通信
- EmmyLua IntelliJ idea的lua插件
- ZeroBrane Studio 轻量级的lua ide