基於 Nginx 的動態代理

在實際應用中,遇到了這樣一個場景:

已有一個手機 APP 客戶端,需要在該 APP 客戶端中實現通過 Web 的形式接入其他的應用頁面。按照常規的流程,在 APP 中爲應用設置入口鏈接按鈕,當用戶點擊應用入口按鈕時,APP 啓動 WebView 並打開設置的應用鏈接即可。

但在該場景中,接入 APP 的應用均部署在內網服務器,外網無法直接訪問,因此在 APP 中配置的鏈接是內網地址,當用戶通過外網使用 APP 時,將無法訪問接入的 Web 應用。

針對如上場景中遇到的問題,本文中提出了基於 Nginx 實現動態代理的解決方案。

使用代理

在前面的場景中,要實現內網應用能夠被外網訪問,一般有兩種方式:

  • 將應用部署到可被外網訪問的服務器,通常爲 DMZ 區服務器

  • 使用反向代理服務器,將外網請求代理轉發到內網的應用服務器

其中,將內網應用部署到可被外網訪問的服務器上的方法,通常受限於可提供的硬件環境、安全控制等方面的問題,並不是解決該類問題的首選方案。因此,通常會在可被外網訪問的服務器上部署反向代理服務器,使用代理轉發來解決。

目前最常用的軟件反向代理服務器有 Apache 和 Nginx 。通過配置文件設置,就可以將特定的鏈接嚮應用服務器轉發。例如 Nginx 可通過以下簡單的配置,即可實現代理轉發:

server
{
    listen 80;
    server_name domain.com;
    location /app1 {
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://192.168.10.38:3000/app1;
    }
    access_log logs/domain.com.access.log;
}

在以上實例中,Nginx 配置完成後,即可將 http://domain.com/app 的所有請求代理轉發到 http://192.168.10.38:3000/app1 地址,這樣就可以實現針對 app1 的從外網訪問內網應用的代理轉發功能。此時 app1 對於外網的訪問地址就變成了 http://domain.com/app 。

通過使用代理服務器, 所有的應用將擁有統一域名 ,而通過 二級目錄區分 不同的應用。

在 Apache 中同樣可以採用類似的策略進行代理轉發,本文中主要以 Nginx 作爲實例。

在多個接入多個應用的情況下,只要按照上述實例中的配置,爲不同的應用分配不同的二級目錄,即可實現向不同應用進行代理轉發。這樣就可以解決文章開始提出的問題。

但是這一解決方案也存在問題:

  • 當需要新增/修改/刪除應用的代理轉發配置時,需要人工修改配置文件,並重啓代理轉發服務器,這將導致服務的暫時不可用。

  • 當接入應用較多時,配置文件將越來越大,對用人工維護造成極大的不便。

  • 當代理轉發服務器進行集羣化部署時,每次對配置文件的更新,都需要更新所有代理轉發服務器,並進行重啓,這將增大維護的風險。

針對以上的問題,需要對該訪問進行進一步改進。

使用動態代理

如果能夠使反向代理服務器動態的通過集中的配置數據更新針對應用的代理配置,就可以解決上述方案中存在的問題。

經過研究分析,本文中提出 動態代理 方案,流程如下:

當請求進入反向代理服務器時,反向代理服務器將分析進入的請求 URL ,識別 URL 中的二級目錄(用於區分不同的應用),然後使用該二級目錄作爲應用標識,到代理配置數據數據中進行查詢,獲得代理地址的返回結果,然後將該請求轉發到對應的應用服務器。

同時,管理人員可以通過特定的管理端,對代理配置數據進行 CRUD 操作,方便管理人員對代理應用配置的實時管理。

通過以上流程,即可實現在不中斷服務的情況下,動態修改代理配置。同時也可以確保集羣化的反向代理服務器同步更新,都可以獲得最新的配置數據。

使用動態代理方案,即可以解決在文章開頭提出的問題。

基於 Nginx 實現動態代理

爲了實現動態代理方案,需要在反向代理服務器中增加定製的功能。針對這一目的,研究了目前主流的反向代理服務器 Apache 和 Nginx ,結論如下:

Apache 和 Nginx 均可以增加定製的模塊以實現定製的功能。但是 Apache 目前必須使用 C 語言按照 Apache 的要求編寫模塊,這對於開發者要求相對較高。而 Nginx 同樣可以使用 C 語言開發擴展模塊,但除此之外,目前已有針對 Nginx 開發的 Lua 語言解釋器模塊,即可以在 Nginx 的配置文件中直接調用 Lua 語言開發的腳本程序,這種方式極大的降低了定製功能開發的難度。因此,採用 Nginx 作爲反向代理服務器,使用 Lua 語言作爲定製功能開發語言,進行動態代理功能實現。

同時,由於反向代理服務器需要處理大量的代理請求,因此會頻繁的讀取反向代理配置數據。基於這一情況,選用 Redis 作爲數據庫,利用其高性能的數據讀寫,支撐代理配置數據的頻繁訪問。

根據以上的技術選型,設計流程圖如下:

在 Nginx 的配置文件中通過 Lua 解釋器模塊,調用 Lua 腳本。請求進入 Nginx 後,通過 Lua 腳本處理請求,並連接 Redis 獲取當前 URL 對應的應用的代理地址,處理完成後,將代理地址回寫到 Nginx 的配置塊,由 Nginx 完成後續的代理轉發工作。

在運行期間,管理人員可以使用代理管理端,對 Redis 中的代理配置數據進行操作,操作完成後,Nginx 代理服務器將及時讀取到最新的配置數據進行轉發。

經過調研,在具體開發過程中,採用了基於 Nginx 進行了模塊擴展的 OpenResty。OpenResty 基於 Nginx 擴展了大量的模塊,其中非常核心的特點就是在 Nginx 中集成了 Lua 解釋器,實現了 Nginx 調用 Lua 腳本。同時 OpenResty 還提供了 Lua 語言實現的訪問 Redis 的代碼模塊。

Nginx 動態代理優化

代理配置數據緩存

在實際測試過程中,當訪問量較大時,由於 Nginx 服務器每次代理都會查詢 Redis ,可能是導致 Redis 壓力過大而無法響應,導致請求被阻塞。

爲了應對這一問題,在 Nginx 中,使用 Lua 腳本設置內存緩存,從 Redis 獲取的代理數據將在一定時間內保留在 Nginx 服務器的內存中,在內存緩存數據有效期內,將不會再重複向 Redis 請求數據。

經過測試,在進行數據緩存優化後,極大的提高了 Redis 訪問的穩定性。

Redis 集羣化

由於單點 Redis 一旦無法提供服務,將導致 Nginx 代理服務無法正常使用。針對這一問題,需要對 Redis 進行集羣化。

目前比較成熟的解決方案是對 Redis 進行主從備份,一個主節點提供對外服務,多個從節點進行數據備份,並在主節點停止服務後產生新的主節點繼續提供服務。使用 Redis 提供的 Redis Sentinel 進行主從節點監控,並向 Nginx 提供最新的主節點信息。

Nginx 集羣化

隨着訪問量逐漸增大,單機的 Nginx 將無法再支持過大的訪問量,同時單機 Nginx 一旦停止服務,將影響整個系統的正常運行。因此需要將 Nginx 進行集羣化,部署多個 Nginx 反向代理服務器,提供同樣的服務。

基於 Nginx 的動態代理方案,提供的代理服務爲無狀態服務,因此可以直接複製 Nginx 以實現集羣化。

想學習分佈式、微服務、JVM、多線程、架構、java、python的童鞋,千萬不要掃碼,否則後果自負~

林老師帶你學編程https://wolzq.com

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章