靈感
在當今的微服務架構中,反向代理在管理傳入請求並將其路由到各種後端服務方面發揮著至關重要的作用。
反向代理位於應用程式的 Web 伺服器前面,攔截來自客戶端電腦的請求。這有許多好處,例如負載平衡、隱藏來源伺服器 IP 位址,從而提高安全性、快取、速率限制等。
在分散式微服務架構中,單一入口點是必要的。像 Nginx 這樣的反向代理伺服器可以在這種情況下提供協助。如果我們有多個伺服器實例在運行,管理和確保有效的請求路由就會變得很棘手。在這種情況下,像 Nginx 這樣的反向代理程式是一個完美的解決方案。我們可以將網域名稱指向 Nginx 伺服器的 IP 位址,Nginx 將根據配置將傳入請求路由到其中一個實例,同時處理每個實例處理的負載。
Nginx 怎麼這麼好?
我建議閱讀 Nginx 的這篇文章,它詳細解釋了 Nginx 如何以超強的可靠性和速度支持大規模的請求:Nginx 架構
簡單來說,Nginx有一個Master進程和一堆worker進程。它還具有快取載入器和快取管理器等輔助進程。主進程和工作進程完成所有繁重的工作。
- 主程序:管理配置並產生子程序。
- 快取載入器/管理器:用最少的資源處理快取載入和修剪。
- 工作進程:管理連接、磁碟 I/O 和上游通信,非阻塞且獨立運作。
工作進程以非阻塞方式處理多個連接,從而減少上下文切換。它們是單線程的,獨立運行,並使用共享記憶體來共享快取和會話資料等資源。這種架構幫助 Nginx 減少上下文切換的次數,並比阻塞、多進程架構更快地提高速度。
從中得到啟發,我們將使用相同的主進程和工作進程概念,並將實現我們自己的事件驅動的反向代理伺服器,它將能夠處理每個工作進程數千個連接。
專案架構
我們的反向代理實作遵循以下關鍵設計原則:
- 設定驅動程式:所有代理行為都在YAML設定檔中定義,可以輕鬆修改路由規則。
- 類型安全性:TypeScript 和 Zod 架構確保配置有效性和運行時類型安全。
- 可擴充性:Node.js 叢集模組可以利用多個 CPU 核心以獲得更好的效能。
- 模組化:透過配置、伺服器邏輯和模式驗證的不同模組清晰地分離關注點。
專案結構
├── config.yaml # Server configuration ├── src/ │ ├── config-schema.ts # Configuration validation schemas │ ├── config.ts # Configuration parsing logic │ ├── index.ts # Application entry point │ ├── server-schema.ts # Server message schemas │ └── server.ts # Core server implementation └── tsconfig.json # TypeScript configuration
關鍵零件
- config.yaml:定義伺服器的配置,包括連接埠、工作進程、上游伺服器、標頭和路由規則。
- config-schema.ts:使用 Zod 函式庫定義驗證模式,以確保設定結構正確。
- server-schema.ts:指定主進程和工作進程之間交換的訊息格式。
- config.ts:提供解析和驗證YAML設定檔的函數。
- server.ts:實作反向代理伺服器邏輯,包括叢集設定、HTTP 處理和請求轉送。
- index.ts:作為入口點,解析命令列選項並啟動伺服器。
配置管理
設定係統使用YAML。其工作原理如下:
server: listen: 8080 # Port the server listens on. workers: 2 # Number of worker processes to handle requests. upstreams: # Define upstream servers (backend targets). - id: jsonplaceholder url: jsonplaceholder.typicode.com - id: dummy url: dummyjson.com headers: # Custom headers added to proxied requests. - key: x-forward-for value: $ip # Adds the client IP to the forwarded request. - key: Authorization value: Bearer xyz # Adds an authorization token to requests. rules: # Define routing rules for incoming requests. - path: /test upstreams: - dummy # Routes requests to "/test" to the "dummy" upstream. - path: / upstreams: - jsonplaceholder # Routes all other requests to "jsonplaceholder".
依照規則評估傳入請求。反向代理根據路徑決定將請求轉送到哪個上游伺服器。
設定驗證(config-schema.ts)
我們使用 Zod 定義嚴格的設定驗證模式:
import { z } from "zod"; const upstreamSchema = z.object({ id: z.string(), url: z.string(), }); const headerSchema = z.object({ key: z.string(), value: z.string(), }); const ruleSchema = z.object({ path: z.string(), upstreams: z.array(z.string()), }); const serverSchema = z.object({ listen: z.number(), workers: z.number().optional(), upstreams: z.array(upstreamSchema), headers: z.array(headerSchema).optional(), rules: z.array(ruleSchema), }); export const rootConfigSchema = z.object({ server: serverSchema, }); export type ConfigSchemaType = z.infer<typeof rootconfigschema>; </typeof>
解析和驗證配置(config.ts)
config.ts 模組提供實用函數來解析和驗證設定檔。
import fs from "node:fs/promises"; import { parse } from "yaml"; import { rootConfigSchema } from "./config-schema"; export async function parseYAMLConfig(filepath: string) { const configFileContent = await fs.readFile(filepath, "utf8"); const configParsed = parse(configFileContent); return JSON.stringify(configParsed); } export async function validateConfig(config: string) { const validatedConfig = await rootConfigSchema.parseAsync( JSON.parse(config) ); return validatedConfig; }
反向代理伺服器邏輯 (server.ts)
伺服器利用 Node.js 叢集模組來實現可擴充性,並利用 http 模組來處理請求。主進程將請求分發給工作進程,工作進程將請求轉發給上游伺服器。讓我們詳細探討一下 server.ts 文件,其中包含反向代理伺服器的核心邏輯。我們將分解每個元件並了解它們如何協同工作以創建可擴展的代理伺服器。
伺服器實作遵循使用 Node.js 的 cluster 模組的主從架構。這種設計使我們能夠:
- 利用多個CPU核心
- 同時處理請求
- 保持高可用性
- 隔離請求處理
-
主流程:
- 建立工作流程
- 在工作人員之間分配傳入的請求
- 管理工作池
- 處理工作進程崩潰與重啟
-
工作進程:
- 處理單一 HTTP 請求
- 根據路由規則來匹配請求
- 將請求轉送到上游伺服器
- 處理回應並將其發送回客戶
主流程設定
├── config.yaml # Server configuration ├── src/ │ ├── config-schema.ts # Configuration validation schemas │ ├── config.ts # Configuration parsing logic │ ├── index.ts # Application entry point │ ├── server-schema.ts # Server message schemas │ └── server.ts # Core server implementation └── tsconfig.json # TypeScript configuration
master程序建立一個worker池,並透過環境變數將配置傳遞給每個worker。這確保所有工作人員都可以存取相同的配置。
請求分發
server: listen: 8080 # Port the server listens on. workers: 2 # Number of worker processes to handle requests. upstreams: # Define upstream servers (backend targets). - id: jsonplaceholder url: jsonplaceholder.typicode.com - id: dummy url: dummyjson.com headers: # Custom headers added to proxied requests. - key: x-forward-for value: $ip # Adds the client IP to the forwarded request. - key: Authorization value: Bearer xyz # Adds an authorization token to requests. rules: # Define routing rules for incoming requests. - path: /test upstreams: - dummy # Routes requests to "/test" to the "dummy" upstream. - path: / upstreams: - jsonplaceholder # Routes all other requests to "jsonplaceholder".
master程序使用簡單的隨機分配策略將請求指派給worker。雖然不像循環演算法或最少連接演算法那麼複雜,但這種方法為大多數用例提供了良好的負載分配。請求分發邏輯:
- 從池中隨機選出一名工人
- 在員工之間創造平衡的工作負載
- 處理工人可能無法使用的邊緣情況
工作行程請求邏輯
每個工作人員都會偵聽訊息,根據路由規則匹配請求,並將它們轉送到適當的上游伺服器。
import { z } from "zod"; const upstreamSchema = z.object({ id: z.string(), url: z.string(), }); const headerSchema = z.object({ key: z.string(), value: z.string(), }); const ruleSchema = z.object({ path: z.string(), upstreams: z.array(z.string()), }); const serverSchema = z.object({ listen: z.number(), workers: z.number().optional(), upstreams: z.array(upstreamSchema), headers: z.array(headerSchema).optional(), rules: z.array(ruleSchema), }); export const rootConfigSchema = z.object({ server: serverSchema, }); export type ConfigSchemaType = z.infer<typeof rootconfigschema>; </typeof>
主進程透過建構標準化訊息有效負載(包括所有必要的請求資訊)與工作進程進行通信,使用 Node.js IPC(進程間通信)並使用 Zod 模式驗證訊息結構。
工作人員負責實際的請求處理和代理。每位工人:
- 從環境變數載入其配置
- 使用 Zod 模式驗證設定
- 維護自己的設定副本
工作人員透過以下方式選擇上游伺服器:
- 從規則中找出適當的上游ID
- 找到上游伺服器設定
- 驗證上游伺服器是否存在
請求轉送機制:
- 向上游伺服器建立新的 HTTP 請求
- 串流回應資料
- 聚合回應正文
- 將回應傳回主程序
運行伺服器
要執行伺服器,請依照下列步驟操作:
- 建置專案:
import fs from "node:fs/promises"; import { parse } from "yaml"; import { rootConfigSchema } from "./config-schema"; export async function parseYAMLConfig(filepath: string) { const configFileContent = await fs.readFile(filepath, "utf8"); const configParsed = parse(configFileContent); return JSON.stringify(configParsed); } export async function validateConfig(config: string) { const validatedConfig = await rootConfigSchema.parseAsync( JSON.parse(config) ); return validatedConfig; }
- 啟動伺服器:
if (cluster.isPrimary) { console.log("Master Process is up ?"); for (let i = 0; i { const index = Math.floor(Math.random() * WORKER_POOL.length); const worker = WORKER_POOL.at(index); if (!worker) throw new Error("Worker not found."); const payload: WorkerMessageSchemaType = { requestType: "HTTP", headers: req.headers, body: null, url: ${req.url}, }; worker.send(JSON.stringify(payload)); worker.once("message", async (workerReply: string) => { const reply = await workerMessageReplySchema.parseAsync( JSON.parse(workerReply) ); if (reply.errorCode) { res.writeHead(parseInt(reply.errorCode)); res.end(reply.error); } else { res.writeHead(200); res.end(reply.data); } }); }); server.listen(port, () => { console.log(Reverse Proxy listening on port: ${port}); }); }
- 開發模式:
const server = http.createServer(function (req, res) { const index = Math.floor(Math.random() * WORKER_POOL.length); const worker = WORKER_POOL.at(index); const payload: WorkerMessageSchemaType = { requestType: "HTTP", headers: req.headers, body: null, url: ${req.url}, }; worker.send(JSON.stringify(payload)); });
在上面的截圖中,我們可以看到有 1 個主節點和 2 個工作進程正在運行。我們的反向代理伺服器正在偵聽連接埠 8080。
在 config.yaml 檔案中,我們描述了兩個上游伺服器,分別是:jsonplaceholder 和 dummy。如果我們希望所有到達我們伺服器的請求都路由到 jsonplaceholder,我們將規則設定為:
├── config.yaml # Server configuration ├── src/ │ ├── config-schema.ts # Configuration validation schemas │ ├── config.ts # Configuration parsing logic │ ├── index.ts # Application entry point │ ├── server-schema.ts # Server message schemas │ └── server.ts # Core server implementation └── tsconfig.json # TypeScript configuration
類似地,如果我們希望對 /test 端點的請求應該路由到我們的虛擬上游伺服器,我們將規則設定為:
server: listen: 8080 # Port the server listens on. workers: 2 # Number of worker processes to handle requests. upstreams: # Define upstream servers (backend targets). - id: jsonplaceholder url: jsonplaceholder.typicode.com - id: dummy url: dummyjson.com headers: # Custom headers added to proxied requests. - key: x-forward-for value: $ip # Adds the client IP to the forwarded request. - key: Authorization value: Bearer xyz # Adds an authorization token to requests. rules: # Define routing rules for incoming requests. - path: /test upstreams: - dummy # Routes requests to "/test" to the "dummy" upstream. - path: / upstreams: - jsonplaceholder # Routes all other requests to "jsonplaceholder".
讓我們來測試一下!
哇,太酷了!我們正在導航到 localhost:8080,但作為回應,我們可以看到我們收到了 jsonplaceholder.typicode.com 的主頁。最終用戶甚至不知道我們正在看到來自單獨伺服器的回應。這就是反向代理伺服器很重要的原因。如果我們有多個伺服器運行相同的程式碼,並且不想將所有連接埠公開給最終用戶,請使用反向代理作為抽象層。用戶將訪問反向代理伺服器,這是一個非常強大且快速的伺服器,它將確定將請求路由到哪個伺服器。
現在讓我們點擊 localhost:8080/todos 看看會發生什麼。
我們的請求再次反向代理到 jsonplaceholder 伺服器,並從解析的 URL 收到 JSON 回應:jsonplaceholder.typicode.com/todos。
通訊流程
讓我們可視化完整的請求流程:
客戶端發送請求→主程序
主流程→選定的工人
Worker → 上游伺服器
上游伺服器 → Worker
工人 → 主進程
主流程 → 客戶端
性能考慮因素
多進程架構提供了多種效能優勢:
- CPU 使用率:工作進程可以在不同的 CPU 核心上運行,並利用可用的硬體資源。
- 進程隔離:一個工作進程的崩潰不會影響其他工作進程,從而提高了可靠性。
- 負載分佈:請求的隨機分佈有助於防止任何單一工作人員不堪重負。
未來的改進
雖然功能正常,但目前的實作可以透過以下方式增強:
- 更好的負載平衡:實作更複雜的演算法,如循環或最少連接。
- 健康檢查:為上游伺服器增加定期健康檢查。
- 快取:實現回應快取以減少上游伺服器負載。
- 指標:增加普羅米修斯風格的指標進行監控。
- WebSocket 支援:擴充代理程式以處理 WebSocket 連線。
- HTTPS 支援:新增 SSL/TLS 終止功能。
總結
從頭開始建立反向代理伺服器一開始可能看起來很嚇人,但正如我們所探索的,這是一次有益的體驗。透過結合 Node.js 叢集、TypeScript 和基於 YAML 的設定管理,我們創建了一個受 Nginx 啟發的可擴展且高效的系統。
此實作仍有增強的空間 - 更好的負載平衡、快取或 WebSocket 支援只是一些需要探索的想法。但目前的設計為進一步實驗和擴展奠定了堅實的基礎。如果您已經按照要求進行操作,那麼您現在就可以更深入地研究反向代理,甚至可以開始建立適合您需求的自訂解決方案。
如果您想聯絡或查看我的更多作品,請查看我的 GitHub、LinkedIn。
該項目的存儲庫可以在這裡找到。
我很想聽聽您的想法、回饋或改進想法。感謝您的閱讀,祝您編碼愉快! ?
以上是使用 Node.js 和 TypeScript 建立可擴充的反向代理伺服器,例如 Nginx的詳細內容。更多資訊請關注PHP中文網其他相關文章!

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

Atom編輯器mac版下載
最受歡迎的的開源編輯器

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。