2019 年 5 月 11 日,OpenResty 社區聯合又拍雲,舉辦 OpenResty × Open Talk 全國巡迴沙龍武漢站,又拍雲首席佈道師在活動上作了《 基於 OpenResty 的動態服務路由方案 》的分享。html
OpenResty x Open Talk 全國巡迴沙龍是由 OpenResty 社區、又拍雲發起,邀請業內資深的 OpenResty 技術專家,分享 OpenResty 實戰經驗,增進 OpenResty 使用者的交流與學習,推進 OpenResty 開源項目的發展。活動已前後在深圳、北京、武漢舉辦,後續還將陸續在上海、廣州、杭州等城市巡迴舉辦。node
邵海楊,又拍雲首席佈道師,運維總監,資深系統運維架構師,多年 CDN 行業架構設計、運維開發、團隊管理相關經驗,精通 Linux 系統及嵌入式系統,互聯網高性能架構設計、CDN 加速、KVM 虛擬化及 OpenStack 雲平臺的研究,目前專一於容器及虛擬化技術在又拍雲的私有云實踐。git
如下是分享全文:github
今天和你們介紹一個基於 ngx_lua 的動態服務路由解決方案,它是整個容器化過程當中的組件,容器化在服務路由上有很大的挑戰,又拍雲經過本身的方案來實現了,而且已經穩定運行了三年左右。目前這個方案已經開源,若是你們後續也碰到同樣的問題,能夠直接使用這個方案。算法
在更新服務時,如何能作到讓服務不斷掉呢?又拍雲作服務更新的時候,是不容許有失敗的,若是由於咱們的更新失敗致使請求失敗,即便請求很是少,口碑上也會很差,並且若是形成了事故,是要賠錢的。這也是咱們作動態服務路由的重要緣由。數據庫
服務路由主要包括如下幾個部分:後端
服務發現有不少方案,可是它們的應用場景和語言都不太同樣。Zookeeper 是一個比較老牌的開源項目,相對比較成熟,但對資源的要求比較高,是咱們最先使用的一個方案,包括咱們如今的 kafka、消息隊列都是依賴 Zookeeper;etcd 和 Consul 是後起之秀,K8S 是依賴 etcd 的,etcd 在容器編排裏面是依賴的;又拍雲在服務註冊和發現環節用了 Consul ,它是一站式的技術站,部署、可視化、維護等環節都比較方便,它不但支持 KV 存儲,還有原生的服務監控、多數據中心、DNS 功能等。緩存
負載均衡也有不少方案, LVS 有一個優點是在作完前面兩層後,若是性能很差能夠再加一個 LVS,由於它在四層,更加底層,不會破壞原來的網絡結構,可是它的擴展很是難。HA_PROXY 和 Nginx 各有千秋,HA_PROXY 對 HTTP 頭部解析消耗的 CPU 更少,若是作純轉發,如 WAF 可使用 HA_PROXY,HA_PROXY 大概佔 CPU 10% 左右 ,而 Nginx 作純頭部轉發基本上是佔 CPU 20%-25%,可是 Nginx 可擴展性更強,Nginx 能夠作 TCP、UDP、HTTP 三種協議的轉發和負載均衡,可是 HA_PROXY 只支持 TCP、HTTP。 HA_PROXY 最大的變化是它已經用 lua 重構,後續的發展也會與 lua 緊密結合,這至關因而又多了一種能力,它們也在擁抱 K8S 的生態圈。咱們的方案是選擇了 Nginx ,由於它專一於作 HTTP ,擴展性好,支持 TCP。網絡
如上圖,咱們把 Nginx 和 Consul 放在一張圖裏。爲了突出服務,這裏把一些跟服務不太相關的都省略掉了。咱們基於 Mesos、Docker、 Marathon 作了服務管理。其中有一個特殊的服務是 Registrator,它會經過 Docker API 在每一個物理機上起一個容器,經過 Docker API,把容器的狀態定時的彙報給 Consul。上面的 Nginx 作負載均衡,由於咱們的服務目前都是基於 Nginx 直接到容器裏面。架構
在前面的圖裏,Nginx 到容器、服務註冊到配置文件都沒有問題,可是從 Consul 到 Nginx 會出現問題,由於 Consul 有全部的信息,可是這些信息如何通知給 Nginx 呢?一個新的服務起來,或者是一個服務掛掉,這些信息 Consul 知道後怎麼讓 Nginx 把這些有問題的服務刪掉,再把一些新寫的服務加進去,這就是咱們要解決的問題。
這裏的問題就是 Consul 裏的服務如何更新到 Nginx,若是解決了這個問題,Nginx +Consul+Registrator 的模式就圓滿了。目前也有不少方案能夠來解決這個問題:
一、方案一:Consul_template
監聽 Consul 裏的 key,觸發執行一個腳本,利用這個特性的服務,服務發生變更,會根據預先配置好的模板從新生成配置,這個就是最後要執行的一個腳本。
上圖是一個例子,有模板生成 upstream.conf,中間都是未來要被渲染的一些變量,若是 K/v 發生變更,模板化生成一份真實的配置文件,而後再執行一個本地的命令,Nginx -s reload,從新生成配置文件,Reload 一下,這樣新的服務就生效了。
固然 Reload 也會有一些缺點:
二、方案二:內部 NDS 方案
DNS 的方案也是比較經常使用的,好比把以前是一個 IP 地址的 Server,如今改爲一個域名,只要把它解析掉一批 IP 就行了,這個聽起來已經很完美了,並且 Consul 自己支持DNS,咱們也不用維護另外的 DNS 了,只要把這個 ID 換成域名就行了。
可是咱們感受使用 DNS 方案還不如作 Reload,緣由是
咱們想要的是經過 HTTP 接口,動態修改 Nginx 的上游服務列表,咱們找到了現成的方案,叫 ngx_http_dyups_module。
ngx_http_dyups_module 能夠經過 GET 接口查詢當前的一些信息;POST 能夠更新上游;也能經過 Delete 刪除上游。
上圖是一個例子,這個例子有三個請求:
在這個過程裏沒有任何 Reload 的操做,也沒有改配置,它就完成了一個功能。
這個模塊寫得很是好,可是咱們用了一段時間後把它下掉了,主要緣由不是由於它很差,而是咱們結合了一些自身的狀況,發現了一些問題:
基於以上這些緣由,咱們開始造本身的輪子。
這個輪子有四個部分:
一、lua-resty-checkups
簡單介紹下 lua_resty_checkups 這個模板,它有幾個功能:
二、服務區分
以 Host 區分服務:好比上圖兩個 curl 往同一個地址去發,這二者之間是不同的。
三、請求流程
簡單介紹下請求的流程,它能夠分爲三個部分,最上面是接收請求,會加載一個 worker 代碼,worker 代碼執行完根據 host 找對應的列表,而後把這個請求代理給服務端。
四、動態 upstream 更新
這個跟 dyups 的 C 模塊同樣,也是經過 HTTP 接口來動態更新 upstream 列表,加完後能夠在管理頁面看到剛加進去的兩個服務,這裏會有 server 地址、一些健康檢查的消息、狀態變動的時間,以及它失敗的次數,下圖是一次主動健康檢查的一個記錄。
爲何會有主動健康檢查呢?你們平時用的就是一些被動的健康檢查,也就是請求發出去以後失敗了才知道失敗了,主動的檢查是發心跳包,在請求以前就能夠知道服務是否是出問題了。
五、動態 lua 加載
動態 lua 加載在作遊戲的時候會常常用到。一開始程序裏面跑了一些 lua 的代碼,給後端的程序作參數轉化和作兼容,好比有一個小調整不樂意去改,就拿前面的路由去作,首先能夠對請求作改寫,由於我能夠拿到整個請求,它的請求體能夠作任意的事情。
此外,咱們還能夠跟一些權限控制結合,作一些簡單的參數檢查。據咱們的統計,咱們至少有 10% 是重複請求,若是這些重複請求都去執行就是無謂的消耗,咱們會返 304,表示結果跟以前的同樣,能夠直接用以前的結果。在返 304 的同時,若是咱們須要後端的服務去判斷,會把整個請求收下來,而後再日後面發,至關於內網帶寬要增長一些,這樣其實已經節省了帶寬,能夠不日後面發了。
這是一個動態負載加載的例子,若是把這段代碼推到 Slardar 裏面,它會執行,若是進行一個刪除操做,它會返 403,便可以當即經過這個代碼禁掉這個操做,那還有什麼功能呢?你能夠想象到的功能均可以作,並且這個過程是動態的,若是代碼加載,也能夠從狀態頁裏看到它的信息。
前面介紹都是 Slardar 的特性,接下來簡單介紹一下實現過程,一共分爲三個部分: 動態 upstream 管理、負載均衡和動態 lua 代碼加載。
一、動態 upstream 管理
啓動時經過 luasocket 從 consul 加載配置文件,服務若是沒有任何理由的掛了,掛了以後你剛起來時,你怎麼知道剛剛怎麼了呢?因此得有一個方式去固化這些東西,而咱們選的是 consul,因此它啓動的時候必須從 consul 加載,啓動以後要監聽管理的端口,接收 upstream 更新指令,還要啓動一個定時器,這個定時器作 worker 間的同步,定時從共享內存看一下有沒有更新,有更新就能夠同步在本身的 worker 裏。
這是一個簡單的流程圖,最開始的時候從 consul 加載,在完成 fork 後到了 worker 進程,也就是剛剛初始化加載的那些 worker 都有了,另一部分啓動定時器,一旦有更新就會進入到這個裏面。
二、負載均衡
負載均衡咱們主要用到了 balance_by_lua_*,一個請求過來,經過 upstream 的 C 模塊把這個請求往這裏發,如圖是配置文件,剛剛也有一個相似的,就是在這裏寫了地址。經過 balance_by_lua_* 指令,咱們會把它攔到這個文件裏,就能夠在這個 lua 文件裏用 lua 代碼選一個,這就是自身的一個 checkups 的選擇的過程。
上圖是大概的流程,能夠先看下邊部分,一開始的時候,checkups.select_peer 是咱們的模塊,而後根據這個 host 再到當前的 peer 就跳出去了,這就實現了用 lua 控制。上面部分是要知道它是成功仍是失敗的,若是它失敗了,要對這個狀態進行反饋。
三、動態 lua 加載
這個主要是用到 lua 的三個函數,分別是 loadfile、loadstring 和 setfenv。loadfile 是加載本地的 lua 代碼,loadstring 是從 consul 或 HTTP 請求 body 加載代碼,setfenv 設置代碼的執行環境,經過這三個函數就能夠加載,具體的實踐細節這裏就再也不介紹。
四、動態負載均衡 Slardar 的優點
這就是咱們造的輪子,主要用到 lua-resty-checkups 的模塊和 balance_by_lua_* ,它有如下的優點:
咱們目前也在把以前的一些服務改形成微服務模式。微服務其實就是源於一個比較大的服務,把它拆分紅一些小的服務,它的擴容跟遷移也不同,微服務的擴容能夠只擴容其中一部分,擴容多少能夠根據需求。
咱們如今正在嘗試一個方案,這個方案背景是咱們有作圖的需求,作圖這個功能有不少,好比說美化、縮略、水印等,若是要對作圖的服務進行優化是很是困難的,由於它功能太多了,若是咱們把它拆成微服務就不同了,好比上圖虛線上面的是咱們如今的服務,這個是微服務的一個網關,下面是一些小的服務。好比說美化,它的運算比較複雜,耗 CPU 比較多,咱們確定選擇一些 CPU 比較好的機器;用 GPU 來作縮略圖,這個性能可能提升幾十倍;最後是一箇中規中矩的作圖,那就普通的一些就夠了。
還有一些比較偏門的,好比說梯度,可能只要保證服務能夠用就好了,經過這個微服務的路由,咱們根據後面的區分把以前的一個服務,以及它的參數拆成三個小的服務,這樣經過三個步驟能夠完成一個作圖的服務。
固然咱們在嘗試這個方案其實也有不少的問題,好比一個服務原來用一個程序就能夠作了,如今變成了三個,勢必內網的帶寬要增長了,中間的圖片要被導來導去,這個怎麼辦呢?咱們如今想到的辦法就是作一些本地優先的調度策略,即作完以後,本地有一些水印的,那就優先用本地的。
最後套用大師的一句話:Talk is cheap,Show me the code。目前咱們已經將 Sladar 項目開源,項目地址是:https://github.com/upyun/slardar 。
演講視頻及PPT: