如果大家清楚“网关”这个概念,那就很容易理解“API网关“,即所有API的入口。 从面向对象设计的角度看,它与外观模式类似,封装了系统内部架构。在单体应用架构中,没有「 API网关 」的概念,每个项目都会用到filter/过滤器之类的东西,filter的作用就是把项目中的一些非业务逻辑的功能抽离出来独立处理,避免与业务逻辑混在一起增加代码复杂度。比如 鉴权认证功能、Session处理、安全检查、日志处理等等。
如果采用微服务架构,那一个项目中微服务节点很多,如果让每一个节点都去处理上面这些 “鉴权认证功能、Session处理、安全检查、日志处理等” 会多出很多冗余的代码,也会给增加业务代码的复杂度,因此就需要有一个API网关把这些公共的功能独立出来成为一个服务来统一的处理这些事情。
API网关就像是微服务的一扇门,是连通外部客户端与内部微服务之间的一个桥梁。
API网关最主要的功能实现就是请求拦截,在网络请求的整个生命阶段加入各种filter/过滤器, OpenResty提供了这样的功能。
OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
OpenResty 处理一个请求,它的处理流程请参考下图(从 Request start 开始):
依据OpenResty的请求处理流程,和各种第三方模块,就可以在流程节点中加入我们的API网关逻辑代码。例如,对API请求数量进行统计:
lua_package_path "module/lua-resty-hmac/lib/?.lua;module/lua-resty-redis/lib/?.lua;module/lua-resty-mysql/lib/?.lua;module/lua-resty-jwt/lib/?.lua;;"; server { listen 80; server_name gw.gitlib.cn; access_log /var/log/nginx/gw.gitlib.cn.access.log access; # lua_code_cache off; set $redis_host "192.168.1.106"; set $redis_port "6379"; set $redis_incrkey "api:access:num"; access_by_lua_file gateway/intercept.lua; # 对所有请求进行拦截处理 location = /num { content_by_lua_block { local _redis = require "resty.redis" local redis = _redis:new() redis:set_timeout(1000) local ok, err = redis:connect(ngx.var.redis_host, ngx.var.redis_port) if not ok then ngx.say("failed to connect: ", err) return end local res, err = redis:get(ngx.var.redis_incrkey) if not res then ngx.say("failed to get key: ", err) return end if res == ngx.null then ngx.say("key not found.") return end ngx.say("api:access:num:", res) } } location ~ ^/api/([/w]+) { default_type text/html; content_by_lua_file /web/gw/api/$1.lua; } }
上面是我们的nginx配置,引入了redis模块,用于存储API请求数量,接下来,我们在gateway/intercept.lua中实现API请求数量统计的处理逻辑:
local function increseNum(key) -- get key from rediskey local _redis = require "resty.redis" local redis = _redis:new() redis:set_timeout(100) local ok, err = redis:connect(ngx.var.redis_host, ngx.var.redis_port) if not ok then ngx.log(ngx.ERR, "failed to connect to redis: ", err) return nil end if ngx.var.redis_auth then local ok, err = redis:auth(ngx.var.redis_auth) if not ok then ngx.log(ngx.ERR, "failed to authenticate: ", err) return nil end end if ngx.var.redis_db then local ok, err = redis:select(ngx.var.redis_db) if not ok then ngx.log(ngx.ERR, "failed to select db: ", ngx.var.reddb, " ", err) return nil end end local res, err = redis:incr(key) if not res then ngx.log(ngx.ERR, "failed to incr key: ", key ,", ", err) return nil end if res == ngx.null then ngx.log(ngx.ERR, "key ", key, " not found") return ngx.null end local ok, err = redis:close() if not ok then ngx.log(ngx.ERR, "failed to close: ", err) end return res end increseNum(ngx.var.redis_incrkey)
就这样,我们实现了API网关的一个小功能,其他功能实现,就靠大家去摸索了。目前市面上成熟的API网关实现方案有很多,采用openresty 开发出的api网关,比如比较流行的kong、orange等, 大家可以自行了解。