天天看點

如何使用流處理器 Pipy 來建立網絡代理

作者 | Ali Naqvi

譯者 | 平川

策劃 | 丁曉昀

在這篇文章中,我們将介紹 Pipy,一個開源的雲原生網絡流處理器。本文将首先描述它的子產品化設計,然後介紹如何快速建構一個高性能的網絡代理來滿足特定的需求。Pipy 經過了實戰檢驗,已經被多個商業客戶所使用。

Pipy是一個 開源、輕量級、高性能、子產品化、可程式設計的雲原生網絡流處理器。它适用于衆多應用場景,包括但不限于邊緣路由器、負載均衡器 & 代了解決方案、API 網關、靜态 HTTP 伺服器、服務網格挎鬥等。

事實證明,這些屬性對 Pipy 來說都有特定的意義,讓我們逐項來看一下。

輕量級

編譯後的 Pipy 可執行檔案大小約為 6MB,隻需要很小的記憶體就能運作。

高性能

Pipy 是用 C++ 編寫的,以Asio異步 I/O 庫為基礎建構。

子產品化

Pipy 的核心采用了子產品化設計,有許多可重用的小子產品(過濾器),把它們連接配接在一起就可以形成一個管道,網絡資料在這個管道中流動并被處理。

流處理器

Pipy 使用一個事件驅動的管道來操作網絡流,它消耗輸入流,執行使用者提供的轉換,并輸出流。Pipy 流将資料位元組抽象為以下其中一類事件:

如何使用流處理器 Pipy 來建立網絡代理

可程式設計

Pipy 通過其定制開發的元件PipyJS提供内置的JavaScript支援,該元件是 Pipy 代碼庫的一部分,但不依賴于該代碼庫。PipyJS 具有高度的可定制性,性能可預測,而且沒有垃圾收集開銷。将來,PipyJS 可能會轉移到單獨的包中。

Pipy 的設計

Pipy 的内部工作原理類似于 Unix 管道),但不同的是,Unix 管道處理的是離散的位元組,而 Pipy 處理的是事件流。

Pipy 通過一個過濾器鍊來處理傳入的資料流,過濾器負責處理請求記錄、認證、SSL 解除安裝、請求轉發等正常問題。每個過濾器都從其輸入中讀取事件流并寫入輸出,一個過濾器的輸出與下一個過濾器的輸入相連。

管道

一條過濾器鍊即一個管道,Pipy 根據其輸入源将管道分為 3 個不同的類别。

端口管道

從一個網絡端口讀入資料事件,處理它們,然後将結果寫回同一端口。這就是最常用的請求和響應模式。

例如,當 Pipy 作為 HTTP 伺服器時,端口管道的輸入是來自用戶端的 HTTP 請求,而管道的輸出則是發回用戶端的 HTTP 響應。

計時器管道

周期性地擷取MessageStart和MessageEnd事件對作為輸入。當需要 類似于 cron job 的功能 時很有用。

子管道

它與連接配接過濾器(例如link)協同工作,它從前面的管道接收事件,将其送入子管道進行處理,然後再從子管道讀回輸出,并将其傳遞給下一個過濾器。

了解子管道和連接配接過濾器的最好方法是,把它們看成過程程式設計中子程式的被調用者和調用者。連接配接過濾器的輸入是子程式的參數,連接配接過濾器的輸出是其傳回值。

注意:像端口 & 計時器這樣的根管道不能從連接配接過濾器調用。

上下文

Pipy 另一個重要的概念是上下文。上下文是隸屬于一個管道的一組變量。在 Pipy 執行個體中,每條管道都可以通路相同的變量集。換句話說,上下文具有相同的形狀。當啟動一個 Pipy 執行個體時,所做的第一件事就是通過定義變量和它們的初始值來定義上下文的形狀。

每個根管道都會克隆你在開始時定義的初始上下文。當一個子管道啟動時,它要麼共享要麼克隆其父管道的上下文,這取決于你使用了哪一個連接配接過濾器。例如,link過濾器共享其父管道的上下文,而demux過濾器則克隆它。

對于嵌入管道的腳本來說,這些上下文變量就是它們的全局變量,也就是說,隻要這些變量存在于同一個腳本檔案中,這些腳本就可以從任何地方通路它們。

對于一名經驗豐富的程式員來說,這可能顯得很奇怪,因為全局變量通常意味着它們是全局唯一的。這些變量隻能有一組,但在 Pipy 中,我們可以有很多組這樣的變量(又稱上下文),這取決于針對傳入的網絡連接配接開放了多少管道,以及有多少子管道克隆了其父管道的上下文。

PipyJS

PipyJS 是一個嵌入式的小型 JavaScript 引擎,其設計旨在實作高性能,消除垃圾收集開銷。它支援 ECMAScript 标準的一個子集,某些方面有偏離。目前,它支援 JavaScript 表達式、函數,并實作了 JavaScript 标準 API,如 String、Array 等。

如上所述,上下文是 PipyJS 一個非常關鍵的特性,Pipy 用一種特定的方式對其進行了擴充,以滿足代理伺服器的特殊需求,後者需要為每個連接配接提供多組全局變量。每個上下文狀态對其他上下文都是不可見的,是以,它是唯一的,隻有它的定義者才能通路。

如果你熟悉多線程程式設計的概念,那麼你也可以把上下文看作是 TLS(線程本地存儲),其中全局變量在不同的線程中具有不同的值。

相容性

Pipy 的設計旨在跨不同的作業系統和 CPU 架構實作高度相容。Pipy 已經在以下這些平台上進行了全面測試:

CentOS 7

Ubuntu 18/20

FreeBSD 12/13

macOS Big Sur

在生産環境中,建議使用 CentOS7/REHL7 或 FreeBSD。

快速入門

對于那些缺乏耐心的讀者,可以使用 docker 運作 Pipy 的生産版本,使用 Pipy 官方 GitHub 倉庫提供的一個教程腳本即可。這裡,讓我們遵循經典示例HelloWorld!的規範,但把這句話改為Hithere!

Pipy 的 Docker 鏡像可以通過幾個環境變量來配置:

PIPY_CONFIG_FILE= 指定 Pipy 配置檔案的位置;

PIPY_SPAWN=n 是你想要啟動的 Pipy 執行個體的數量,其中 n 為執行個體數,這個數是從 0 開始的,0 代表 1 個執行個體。例如,如果想要啟動 4 個執行個體,則使用PIPY_SPAWN=3

上述指令會用提供的腳本啟動 Pipy 伺服器。敏銳的使用者可能已經注意到,我們通過環境變量PIPY_CONFIG_FILE提供了一個遠端 Pipy 腳本的連結,而不是一個本地檔案,Pipy 足夠智能,可以處理這種情況。

下面是tutorial/01-hello/hello.js檔案的内容,供參考:

在這個腳本中,我們定義了一個端口管道,它監聽 8080 端口,并為從監聽端口收到的每個 HTTP 請求傳回“Hi, there!”。

既然我們已經通過上面的dockerrun指令暴露了本地 8080 端口,那麼我們可以在同一端口上進行測試了:

執行上述指令,控制台中應該顯示“Hi, there!”。

如果是出于學習、開發或調試的目的,建議在本地安裝 Pipy(從源代碼建構 Pipy 或針對你的作業系統下載下傳一個預建構版本),因為它提供了 Web 管理控制台以及相關的文檔和教程。

安裝到本地後,運作pipy,不需要任何參數,就可以在6060端口啟動管理控制台,但如果要監聽不同的端口,可以通過--admin-port=參數配置。

如何使用流處理器 Pipy 來建立網絡代理

監聽 6060 端口的 Pipy 管理控制台

要從源代碼建構 Pipy 或針對你的作業系統安裝預編譯的二進制檔案,請參考 PipyGithub 庫的 README.md 檔案。

通過 CLI 運作

要啟動 Pipy 代理,可以用一個 PipyJS 腳本檔案運作 Pipy。例如,如果需要一個簡單的回顯伺服器,針對每個傳入的請求都用所接收到的消息體進行響應,那麼就用腳本tutorial/01-hello/hello.js:

另外,在開發和調試時,可以啟動帶有内置 Web UI 的 Pipy:

顯示指令行選項

列出内置過濾器及其參數

前文從概念和技術上對 Pipy 做了一個簡短的介紹,這些内容也是我們實作一個支援緩存和負載均衡的網絡代理所需要了解的,這一點我們在下一節會看到。

編寫一個網絡代理

假設我們正在運作不同服務的單獨執行個體,我們想要添加一個代理,根據請求的 URL 路徑将流量轉發到相關服務。這樣做的好處是,我們隻需要提供一個 URL,并在後端擴充我們的服務,而使用者不需要分别記住不同服務的 URL。在正常情況下,服務會在不同的節點上運作,每個服務可以有多個執行個體在運作。假設在這個例子中,我們正在運作下面的服務,我們希望根據 URI 将流量配置設定給它們。

如何使用流處理器 Pipy 來建立網絡代理

Pipy 的腳本是用 JavaScript 編寫的,你可以用任何文本編輯器來編輯它們。另外,如果你在本地安裝了 Pipy,就可以使用 Pipy 提供的 Web 端管理 UI,它提供了文法高亮、自動完成、提示等特性,你甚至可以運作腳本,所有這些都在同一個控制台上。

好了,讓我們啟動一個 Pipy 執行個體,不需要任何參數,這樣,Pipy 管理控制台将在 6060 端口啟動。現在,打開你喜歡的 Web 浏覽器,導航到 http://localhost:6060,就會看到 Pipy 内置的 Web 端管理 UI(如圖 1)。

建立一個 Pipy 程式

将代碼和配置分開是一種很好的設計實踐。Pipy 通過插件(你可以把它想成是 JavaScript 子產品)來支援這種子產品化設計。也就是說,我們将把配置資料存儲在 config 檔案夾下,把編碼邏輯存儲在 plugins 檔案夾下不同的檔案中。主代理伺服器腳本将存儲在根目錄下,主代理腳本(proxy.js)将包含并組合這些單獨的子產品所定義的功能。一旦我們完成了下述步驟,最終的檔案夾結構将是下面這個樣子:

讓我們開始吧:

重複步驟 2 和 3,建立另一個檔案/config/router.json,它将存儲路由資訊,配置資料如下:

重複步驟 2 和 3,建立另一個檔案/config/balancer.json,它将存儲服務到目标的映射資訊,内容如下:

現在,我們編寫第一個 Pipy 腳本,當我們收到一個沒有配置任何目标(端點 /url)的請求時,它将被用作預設的後備選項。重複上述步驟,建立檔案/plugins/default.js。使用 default 作為檔案名隻是一個習慣做法,并不是 Pipy 的要求,你可以選擇任何你喜歡的名字。該腳本将包含如下代碼,傳回 HTTP 狀态代碼 404,資訊為 No handler found:

建立/plugins/router.js檔案,存儲路由邏輯:

建立檔案/plugins/balancer.js,存儲了我們的負載均衡邏輯。順便說明一下,Pipy 提供了多種負載均衡算法,但簡單起見,我們這裡将使用 Round Robin 算法。

現在,我們來編寫入口點或代理伺服器腳本,它會使用上述插件。建立一個新的代碼庫(步驟 1),這個過程會建立一個預設的main.js檔案作為入口點。我們可以用它作為我們的主入口點,或者如果你希望換個名字,可以随時删除,然後用你選的名字建立一個檔案。讓我們删除它并建立一個名為/proxy.js的檔案。務必點下頂部的旗标,将其設定為主入口點,這可以確定在你點選運作按鈕(右側的箭頭圖示)時開始執行腳本:

如果你已經按照上面的步驟進行了操作,就可以看到類似于以下截圖的東西:

如何使用流處理器 Pipy 來建立網絡代理

現在,我們點選播放圖示按鈕(右起第四個)來運作我們的腳本。如果腳本沒有任何錯誤,我們将看到 Pipy 運作我們的代理腳本,輸出類似下面這樣:

如何使用流處理器 Pipy 來建立網絡代理

這表明我們的代理伺服器正在監聽 8000 端口(這是在/config/proxy.json中配置的)。我們用 curl 來運作一個測試:

這沒問題,因為我們沒有為 root 配置任何目标。讓我們試下配置過的路由,如/hi:

我們看到了 502 Connection Refused 這個消息,因為我們沒有在配置的目标端口上運作服務。

你可以更新/config/balancer.json,加入你已經運作的服務的主機、端口等細節,以比對你的實際情況,或者我們在 Pipy 中編寫一個腳本,監聽我們配置的端口,并傳回簡單的消息。

将以下代碼片段儲存到你本地計算機上的一個檔案中,命名為mock-proxy.js,并記住檔案的存儲位置。

打開一個新的終端視窗,通過 Pipy 運作這個腳本(其中/path/to是存儲該腳本檔案的位置):

現在,我們已經模拟了監聽 8080、8081 和 8082 端口的服務。讓我們在代理伺服器上再做一次測試,你會看到,模拟服務傳回了正确的響應。

小 結

我們使用了 Pipy 的許多特性,包括變量聲明、導入 / 導出變量、插件、管道、子管道、過濾器鍊、handleMessageStart、handleStreamStart和link等 Pipy 過濾器,以及JSON、algo.URLRouter、algo.RoundRobinLoadBalancer和algo.Cache等 Pipy 類。徹底解釋所有這些概念超出了本文的範圍,如果你希望了解更多資訊,請閱讀 Pipy 的文檔。你可以通過 Pipy 的 Web 端管理 UI 檢視這些文檔,并按照入門教程一步步操作。

結 語

來自 Flomesh 的 Pipy 是一個開源、高性能、輕量級的網絡流量處理器,适用于多種場景,包括邊緣路由器、負載平衡 & 代理(正向 / 反向)、API 網關、靜态 HTTP 伺服器、服務網格挎鬥等。Pipy 仍在積極開發之中,并由全職的送出者和貢獻者維護,雖然仍是早期版本,但已有多個商業客戶完成了測試并投入生産應用。它的建立者和維護者 Flomesh.cn 提供的商用解決方案就是以 Pipy 為核心。

這篇文章對 Pipy 做了一個非常簡要的介紹和概述。GitHub 上提供了入門教程和文檔,你也可以通過 Pipy 管理控制台的 Web UI 檢視。社群非常歡迎大家為 Pipy 的發展做貢獻,也歡迎大家在自己特定的場景下進行試用,或者提供回報和意見。

作者簡介:

Ali Naqvi 是一位擁有超過 20 年 IT 行業經驗的專業人士。他非常熱衷于開發以及為開源軟體做貢獻。他主要關注開發、軟體架構、DevOps 等領域。他經常發表演講,是當地社群 / 分會的活躍成員,緻力于傳播 OSS、DevOps 和 Agile 理念和知識。

https://www.infoq.com/articles/network-proxy-stream-processor-pipy/

繼續閱讀