通過 Consul+OpenResty 實現(xiàn)無reload動態(tài)負(fù)載均衡

【轉(zhuǎn)載請注明出處】:http://www.lxweimin.com/p/bee45550781e

動態(tài)Nginx負(fù)載均衡的配置,可以通過Consul+Consul-Template方式,但是這種方案有個缺點:每次發(fā)現(xiàn)配置變更都需要reload Nginx,而reload是有一定損耗的。而且,如果你需要長連接支持的話,那么當(dāng)reload時Nginx長連接所在worker進程會進行優(yōu)雅退出,并當(dāng)該worker進程上的所有連接都釋放時,進程才真正退出(表現(xiàn)為worker進程處于worker process is shutting down)。因此,如果能做到不reload就能動態(tài)更改upstream,那么就完美了。

目前的開源解決方法有3種:
1、Tengine的Dyups模塊
2、微博的Upsync模塊+Consul
3、使用OpenResty的balancer_by_lua。

這里使用的是Consul+OpenResty 來實現(xiàn)動態(tài)負(fù)載均衡。Consul的安裝這里將不再介紹。

OpenResty簡介

OpenResty 是一個基于 Nginx 與 Lua 的高性能 Web 平臺,其內(nèi)部集成了大量精良的 Lua 庫、第三方模塊以及大多數(shù)的依賴項。用于方便地搭建能夠處理超高并發(fā)、擴展性極高的動態(tài) Web 應(yīng)用、Web 服務(wù)和動態(tài)網(wǎng)關(guān)。

Lua簡介

Lua是一個簡潔、輕量、可擴展的程序設(shè)計語言,其設(shè)計目的是為了嵌入應(yīng)用程序中,從而為應(yīng)用程序提供靈活的擴展和定制功能。Lua由標(biāo)準(zhǔn)C編寫而成,代碼簡潔優(yōu)美,幾乎在所有操作系統(tǒng)和平臺上都可以編譯,運行。

如何做到動態(tài)呢?

正常使用Nginx作為API網(wǎng)關(guān), 需要如下配置, 當(dāng)加后端實例時, reload一下Nginx, 使其生效

upstream backend {
    server 192.168.0.1;
    server 192.168.0.2;
}

那么只要讓upstream變成動態(tài)可編程就OK了, 當(dāng)新增后端實例時, 無需reload, 自動生效。
在OpenResty通過長輪訓(xùn)和版本號及時獲取Consul的kv store變化。Consul提供了time_wait和修改版本號概念,如果Consul發(fā)現(xiàn)該kv沒有變化就會hang住這個請求5分鐘,在這5分鐘內(nèi)如果有任何變化都會及時返回結(jié)果。通過比較版本號我們就知道是超時了還是kv的確被修改了。
Consul的node和service也支持阻塞查詢,相對來說用service更好一點,畢竟支持服務(wù)的健康檢查。阻塞api和kv一樣,加一個index就好了。

OpenResty安裝

筆者使用的是MAC,下面重點介紹MAC系統(tǒng)上的操作。

Mac
brew tap openresty/brew
brew install openresty
Linux
sudo yum install yum-utils
sudo yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo

sudo yum install openresty

#命令行工具 resty
sudo yum install openresty-resty

Mac OS系統(tǒng)上安裝完之后Nginx的配置文件在目錄/usr/local/etc/openresty,啟動命令在目錄/usr/local/opt/openresty/nginx/sbin

啟動
openresty

在瀏覽器輸入localhost

image.png

openresty -h
image.png

到這里OpenResty就已經(jīng)安裝好了。
重啟

openresty -s reload

安裝Lua的包管理器LuaRocks

LuaRocks是Lua模塊的包管理器,安裝LuaRocks之后可以通過LuaRocks來快速安裝Lua的模塊,官網(wǎng)地址是https://luarocks.org/
進入OpenResty的安裝目錄/usr/local/opt/openresty可以看到已經(jīng)安裝了luajit,也是OpenResty默認(rèn)使用的lua環(huán)境。

image.png

下載LuaRocks安裝包http://luarocks.github.io/luarocks/releases/luarocks-3.0.4.tar.gz,解壓之后進入LuaRocks源碼目錄編譯安裝到OpenResty的安裝目錄。

./configure --prefix=/usr/local/opt/openresty/luajit --with-lua=/usr/local/opt/openresty/luajit --lua-suffix=luajit --with-lua-include=/usr/local/opt/openresty/luajit/include/luajit-2.1
make build
make install

安裝完LuaRocks之后,可以看到LuaRocks的執(zhí)行命令在目錄/usr/local/opt/openresty/luajit

image.png

安裝完LuaRocks之后就可以安裝搭建環(huán)境需要的依賴包了。

./luarocks install luasocket

修改配置文件

在Consul注冊好服務(wù)之后通過Consul的http://127.0.0.1:8500/v1/catalog/service/api_tomcat2接口就可以獲取到服務(wù)的列表。這里不再講述Consul的服務(wù)注冊。
在默認(rèn)配置文件目錄/usr/local/etc/openresty創(chuàng)建一個servers文件夾來放新的配置文件,創(chuàng)建lualib文件夾來放lua腳本,修改配置文件nginx.conf,添加include servers/*.conf;
lualib文件夾下創(chuàng)建腳本upstreams.lua

local http = require "socket.http"
local ltn12 = require "ltn12"
local cjson = require "cjson"

local _M = {}

_M._VERSION="0.1"

function _M:update_upstreams()
    local resp = {}

    http.request{
        url = "http://127.0.0.1:8500/v1/catalog/service/api_tomcat2", sink = ltn12.sink.table(resp)
    }
       
        local resp = table.concat(resp);
    local resp = cjson.decode(resp);

    local upstreams = {}
    for i, v in ipairs(resp) do
        upstreams[i] = {ip=v.Address, port=v.ServicePort}
    end
        
    ngx.shared.upstream_list:set("api_tomcat2", cjson.encode(upstreams))
end

function _M:get_upstreams()
   local upstreams_str = ngx.shared.upstream_list:get("api_tomcat2");
   local tmp_upstreams = cjson.decode(upstreams_str);
   return tmp_upstreams;
end

return _M

過luasockets查詢Consul來發(fā)現(xiàn)服務(wù),update_upstreams用于更新upstream列表,get_upstreams用于返回upstream列表,此處可以考慮worker進程級別的緩存,減少因為json的反序列化造成的性能開銷。
還要注意使用的luasocket是阻塞API,這可能會阻塞我們的服務(wù),使用時要慎重。
創(chuàng)建文件test_openresty.conf

lua_package_path "/usr/local/etc/openresty/lualib/?.lua;;";
lua_package_cpath "/usr/local/etc/openresty/lualib/?.so;;";

lua_shared_dict upstream_list 10m;

# 第一次初始化
init_by_lua_block {
    local upstreams = require "upstreams";
    upstreams.update_upstreams();
}

# 定時拉取配置
init_worker_by_lua_block {
    local upstreams = require "upstreams";
    local handle = nil;

    handle = function ()
        --TODO:控制每次只有一個worker執(zhí)行
        upstreams.update_upstreams();
        ngx.timer.at(5, handle);
    end
    ngx.timer.at(5, handle);
}

upstream api_server {
    server 0.0.0.1 down; #占位server

    balancer_by_lua_block {
        local balancer = require "ngx.balancer";
        local upstreams = require "upstreams";    
        local tmp_upstreams = upstreams.get_upstreams();
        local ip_port = tmp_upstreams[math.random(1, table.getn(tmp_upstreams))];
        balancer.set_current_peer(ip_port.ip, ip_port.port);
    }
}

server {
    listen       8000;
    server_name  localhost;
    charset utf-8;
    location / {
         proxy_pass http://api_server;
         access_log  /usr/local/etc/openresty/logs/api.log  main;
    }
}

init_worker_by_lua是每個Nginx Worker進程都會執(zhí)行的代碼,所以實際實現(xiàn)時可考慮使用鎖機制,保證一次只有一個人處理配置拉取。另外ngx.timer.at是定時輪詢,不是走的長輪詢,有一定的時延。有個解決方案,是在Nginx上暴露HTTP API,通過主動推送的方式解決。


image.png

Agent可以長輪詢拉取,然后調(diào)用HTTPAPI推送到Nginx上,Agent可以部署在Nginx本機或者遠(yuǎn)程。
對于拉取的配置,除了放在內(nèi)存里,請考慮在本地文件系統(tǒng)中存儲一份,在網(wǎng)絡(luò)出問題時作為托底。

獲取upstream列表,實現(xiàn)自己的負(fù)載均衡算法,通過ngx.balancer API進行動態(tài)設(shè)置本次upstream server。通過balancer_by_lua除可以實現(xiàn)動態(tài)負(fù)載均衡外,還可以實現(xiàn)個性化負(fù)載均衡算法。

可以使用lua-resty-upstream-healthcheck模塊進行健康檢查,后面會單獨介紹。
到這里,其實還有一個問題沒有解決掉,雖然upstream中的占位server是下線的,但是nginx在檢測upstream列表中server的健康狀態(tài)的時候是會去檢測這個占位server的,最好的方式還是在啟動之后把它徹底從upstream列表中給移除掉。

【轉(zhuǎn)載請注明出處】:http://www.lxweimin.com/p/bee45550781e

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,621評論 2 380