作了這 3 個調整,輕鬆撐起 5 倍流量!

文/LeanCloud DevOps 王濱

javascript

背景交代

雲引擎(LeanEngine)是 LeanCloud 推出的服務端託管平臺。提供了多種運行環境(Node.js, Python, PHP, Java 等)來運行服務端程序。你只須要提供服務端的業務邏輯(網站或雲函數等),而服務端的多實例負載均衡,不中斷服務的平滑升級等都由雲引擎提供支持。java

總之你能夠在 LeanCloud 上跑本身的代碼,處理 HTTP 請求。用最近的 buzzword 說,LeanEngine 是 LeanCloud 提供的一個 CaaS(Container as a Service)平臺(笑nginx

用戶的容器不可能直接暴露在公網上的,在用戶的容器與公網入口之間的東西,就是這篇文章要說明的。

git

改造以前

可能不少人以爲,這不就是一個 nginx 的事麼…… 答對了!github

確實在改造前,雲引擎的入口就是幾臺 nginx,而後路由到集羣中的 hipache 上,nginx 僅僅負責 SSL 卸載以及域名綁定的工做,剩下的複雜的路由工做就交給了 hipache,由 hipache 來尋找用戶的容器到底在哪,以及路由過去。數據庫

這個架構很是簡單,可是有 3 點問題:緩存

  • 入口處的 nginx 不易擴展。咱們的用戶仍是很厲害的,作一下活動,流量一會兒變成了平時高峯的 5 倍以上,所以 nginx 的機器配置都是很過量了,平時的負載很低,也不敢減掉,高峯的時候卻看着 70% 的 CPU 佔用額頭上冒汗。
  • 剛纔說過 nginx 這一層要負責 SSL 卸載,因此也必需要負責用戶綁定域名的解析(想一想爲何?),不能丟給 hipache。這樣每一次有用戶來綁定域名時,後臺會走一個流程(咱們用了 rundeck ),從數據庫裏讀取綁定的信息,生成 nginx 配置,而後 ssh 到入口機器上對 nginx 作 reload。每一次域名綁定速度都很是慢,也常常性的須要人工干預。
  • LeanCloud 內部全部的服務都是跑在 Mesos 集羣上的,hipache 也不例外。當 hipache 部署或者擴容縮容的時候,nginx 的 upstream 也要跟着變。如今的實現跟域名綁定同樣,有一個腳本監控着 Marathon 上的實例信息,若是變化了,就生成新的 nginx upstream 配置文件,而後 reload nginx。由於 LeanEngine 目前容許 WebSocket 鏈接,nginx 在 reload 以後老 worker 會殘留很長時間,可能在兩次部署以後還會殘留。老 worker 會擠佔內存,直到最後 nginx 沒法再 reload,須要人工干預。

目標

可擴展的問題咱們已經解決了,咱們要作的就是把 nginx 搬到 Mesos 上,隨時按照需求增減實例。安全

而用戶的域名綁定不能再走這麼複雜的流程了,應該要作到數據庫中有記錄,nginx 就能正確的服務。架構

最後一條 reload 的問題,則須要 nginx 能夠動態的調整 upstream,不能依靠 reload。

app

引入 OpenResty

由於 hipache 是 Node.js 項目,代碼不太好維護,也很容易出現故障(以前有一次故障,用戶請求觸發了一個 hipache 的 bug,並且異常沒有 catch 住,致使 hipache 實例不停重啓,沒法服務),所以咱們的 95 後 dev @王子亭 用 OpenResty 重寫了 hipache,新項目在咱們內部叫 hogplum。hogplum 上線後效果很好,效率明顯的比魔改 hipache 要高,代碼量也減小了很是多(如今只有幾百行),能夠很容易的掌控。

有了以前的成功經驗作背書,咱們決定把入口處的 nginx 也改爲 OpenResty,將域名綁定的工做以及 upstream 更新作成動態的。

開發的過程並無什麼值得一說的,都是一些經常使用的邏輯,經過 API 取數據什麼的。這裏列舉一些比較有意思的點:

  • 動態更新 upstream 是經過 ngx_http_dyups_module 模塊實現的
  • 爲了保留來源 IP 的信息,咱們用了 proxy 協議,後文會說道
  • 在實現元數據緩存的時候,發現 lua-nginx-lrucache 不能保存 false 值,提了一個 PR;另外 ngx.shared 的共享存儲提供了 TTL,可是會比較激進地剔除掉過時的值(ngx.shared 的 API 提供了得到過時值的方法),這並非咱們想要的,因而用了 flags 存了過時時間本身實現了 TTL。

最終的效果大概是這樣:

server {
    include listen.conf;
    include common/ssl-common.conf;
    server_name _;
    ssl_certificate path/to/leanapp.cn.crt;
    ssl_certificate_key path/to/leanapp.cn.key;
    root leanapp.cn/static/;

    # 用來處理自定義域名的 SSL 證書
    ssl_certificate_by_lua_file 'lua/leanapp/ssl.lua';

    location / {
        # 用來解析自定義域名與用戶容器之間的關係
        set $domain '';
        rewrite_by_lua_file 'lua/leanapp/domain.lua';
        proxy_set_header X-LC-Domain $domain;

        # 動態代理到 Mesos 集羣裏的 hogplum 實例上
        dyups_interface;
        set $marathon_app 'marathon:8080#lean-engine/hogplum:8080';
        set $upstream '';
        access_by_lua_file 'lua/marathon-app.lua';
        proxy_pass http://$upstream;
    }
}複製代碼
-- lua/leanapp/domain.lua
local meta = require 'leanapp.meta'

local host = ngx.var.http_host;
if not host then
    ngx.redirect 'http://leanapp.cn'
    return
end

local domain = meta.get_subdomain(host)
if not domain then
    ngx.redirect 'http://leanapp.cn'
else
    -- ngx.var.logname = host
    ngx.var.logname = domain
    ngx.var.domain = domain
end複製代碼
-- lua/leanapp/ssl.lua
local meta = require 'leanapp.meta'
local ngx_ssl = require 'ngx.ssl'

local host = ngx_ssl.server_name()
if not host then
    return
end
local cert, key = meta.get_ssl(host)
if not cert or not key then
    return
end

ngx_ssl.set_cert(cert)
ngx_ssl.set_priv_key(key)複製代碼
-- lua/marathon-app.lua
local dyups = require 'ngx.dyups'

local marathon = require 'marathon'
local die = require('utils').die

local marathon_app = ngx.var.marathon_app;
local upstream = marathon.get_upstream(marathon_app)

if not upstream then
  die('No upstream for ' .. marathon_app)
end

local target = marathon_app:gsub('[/:#]', '-')
dyups.update(target, upstream)
ngx.var.upstream = target複製代碼

marathon 模塊由於代碼太長,以及 leanapp.meta 裏面有涉及敏感的信息,就不貼在這裏了。

在文章尾部附上了 marathon 路由的地址,感興趣的能夠調研下。

那入口在哪?

折騰了半天,終於把 nginx 放到 Mesos 上了。那麼用戶怎麼訪問呢?

仍是缺一個固定的入口,只不過改造以後這個入口只須要作轉發就夠了,須要的資源很是少。

由於須要經過 Proxy 協議保留客戶端的 ip 地址信息,咱們沒有用 LVS 而是用 HAProxy 來作轉發,在調優以後(減小 HAProxy 的緩衝區大小,打開 splice),HAProxy 在滿載的時候只佔用了不多的用戶態內存(10M),剩下的都是內核 TCP 佔用了。實際上每臺機器 1核1G 就能夠了,給了 2G 只是爲了安全。目前咱們的入口有4個,能夠經過 dig leanapp.cn 看到。

在 nginx 擴容/縮容後,會有一個相似以前的機制對 haproxy 作 reload,能夠看成是簡易的 marathon-lb

額外的優化空間

改造後的整個入口是這個樣子的


能夠看到一個請求走了兩個 OpenResty 實例(nginx 和 hogplum)。這是由於 nginx 和 hogplum 是分別由運維組和雲引擎組負責的,hipache 在改形成 hogplum 的時候並無從新考慮負責的邊界。以後自定義域名的路由(以及 SSL 卸載)會合併到 hogplum 中,就不會再看到 hogplum 以前的 nginx 了。

nginx 這一層也能夠去掉雲引擎的業務代碼,僅作 nginx 應該作的路由工做,代替如今的 API 入口(這也是最開始的目標)。

效果

改造以後,咱們又等了一波用戶的活動,果真 nginx 開始過載。

由於 LeanCloud 的統計服務有重試機制,暫時的上報失敗不會有影響,因而咱們迅速的對統計服務進行縮容,將集羣中的資源讓給 nginx,整個過程只點了幾回鼠標。 HAProxy 由於只轉發沒有業務邏輯,消耗的資源不多,還有不少餘量,不是整個系統的瓶頸。

開源

marathon 路由的部分已經開源,能夠戳這裏: leancloud/resty-marathon-lb

相關文章
相關標籤/搜索