微服務(wù)入門:Openresty實現(xiàn)API網(wǎng)關(guān)

概念介紹
如果大家清楚“網(wǎng)關(guān)”這個概念,那就很容易理解“API網(wǎng)關(guān)“,即所有API的入口。 從面向?qū)ο笤O(shè)計的角度看,它與外觀模式類似,封裝了系統(tǒng)內(nèi)部架構(gòu)。在單體應(yīng)用架構(gòu)中,沒有「 API網(wǎng)關(guān) 」的概念,每個項目都會用到filter/過濾器之類的東西,filter的作用就是把項目中的一些非業(yè)務(wù)邏輯的功能抽離出來獨立處理,避免與業(yè)務(wù)邏輯混在一起增加代碼復(fù)雜度。比如 鑒權(quán)認(rèn)證功能、Session處理、安全檢查、日志處理等等。
如果采用微服務(wù)架構(gòu),那一個項目中微服務(wù)節(jié)點很多,如果讓每一個節(jié)點都去處理上面這些 “鑒權(quán)認(rèn)證功能、Session處理、安全檢查、日志處理等” 會多出很多冗余的代碼,也會給增加業(yè)務(wù)代碼的復(fù)雜度,因此就需要有一個API網(wǎng)關(guān)把這些公共的功能獨立出來成為一個服務(wù)來統(tǒng)一的處理這些事情。

主要功能
API網(wǎng)關(guān)就像是微服務(wù)的一扇門,是連通外部客戶端與內(nèi)部微服務(wù)之間的一個橋梁。
其主要功能有:
- 路由轉(zhuǎn)發(fā) API網(wǎng)關(guān)是內(nèi)部微服務(wù)的對外唯一入口,所以外面全部的請求都會先發(fā)到API網(wǎng)上,然后由API網(wǎng)關(guān)來根據(jù)不同的請求去路由到不同的微服務(wù)節(jié)點上。
 - 負(fù)載均衡 API網(wǎng)關(guān)收到外部請求之后,可以根據(jù)內(nèi)部微服務(wù)每個實例的負(fù)荷情況進(jìn)行動態(tài)的負(fù)載均衡調(diào)節(jié)。一旦內(nèi)部的某個微服務(wù)實例負(fù)載很高,甚至是不能及時響應(yīng),則API網(wǎng)關(guān)就通過負(fù)載均衡策略減少或停止向這個實例轉(zhuǎn)發(fā)請求。當(dāng)所有的內(nèi)部微服務(wù)實例都處理不過來的時候,API網(wǎng)關(guān)還可以采用限流或熔斷的形式阻止外部請求,以保障整個系統(tǒng)的可用性。
 - 安全認(rèn)證 API網(wǎng)關(guān)就像是微服務(wù)的大門守衛(wèi),每一個請求進(jìn)來之后,都必須先在API網(wǎng)關(guān)上進(jìn)行安全驗證或身份驗證,驗證通過后才轉(zhuǎn)發(fā)給后面的服務(wù)。
 - 日志記錄 所有的請求都需要走API網(wǎng)關(guān),那么就可以在API網(wǎng)關(guān)上統(tǒng)一集中的記錄下這些行為日志。
 - 數(shù)據(jù)轉(zhuǎn)換 因為API網(wǎng)關(guān)對外是面向多種不同的客戶端,不同的客戶端所傳輸?shù)臄?shù)據(jù)類型可能是不一樣的。因此API網(wǎng)關(guān)還需要具備數(shù)據(jù)轉(zhuǎn)換的功能,將不同客戶端傳輸進(jìn)來的數(shù)據(jù)轉(zhuǎn)換成同一種類型再轉(zhuǎn)發(fā)給內(nèi)部微服務(wù)上,這樣,兼容了這些請求的多樣性,保證了微服務(wù)的靈活性。
 
OpenResty
API網(wǎng)關(guān)最主要的功能實現(xiàn)就是請求攔截,在網(wǎng)絡(luò)請求的整個生命階段加入各種filter/過濾器, OpenResty提供了這樣的功能。
OpenResty® 是一個基于 Nginx 與 Lua 的高性能 Web 平臺,其內(nèi)部集成了大量精良的 Lua 庫、第三方模塊以及大多數(shù)的依賴項。用于方便地搭建能夠處理超高并發(fā)、擴(kuò)展性極高的動態(tài) Web 應(yīng)用、Web 服務(wù)和動態(tài)網(wǎng)關(guān)。
OpenResty 處理一個請求,它的處理流程請參考下圖(從 Request start 開始):

依據(jù)OpenResty的請求處理流程,和各種第三方模塊,就可以在流程節(jié)點中加入我們的API網(wǎng)關(guān)邏輯代碼。例如,對API請求數(shù)量進(jìn)行統(tǒng)計:
- 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; # 對所有請求進(jìn)行攔截處理
 - 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請求數(shù)量,接下來,我們在gateway/intercept.lua中實現(xiàn)API請求數(shù)量統(tǒng)計的處理邏輯:
- 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)
 
就這樣,我們實現(xiàn)了API網(wǎng)關(guān)的一個小功能,其他功能實現(xiàn),就靠大家去摸索了。目前市面上成熟的API網(wǎng)關(guān)實現(xiàn)方案有很多,采用openresty 開發(fā)出的api網(wǎng)關(guān),比如比較流行的kong、orange等, 大家可以自行了解。















 
 
 















 
 
 
 