在本文中,我们将介绍 Pipy,一个开源的云原生网络流处理器,解释它的模块化设计和架构,并将看到一个快速示例,说明我们如何让高性能网络代理快速启动并运行以提供服务我们的具体需求。Pipy 已经过实战考验,并且已经被多个商业客户使用。
Pipy是一种开源、轻量级、高性能、模块化、可编程、云原生网络流处理器,非常适合各种用例,包括(但不限于)边缘路由器、负载均衡器和代理解决方案, API 网关、静态 HTTP 服务器、服务网格边车和其他应用程序。
事实证明,Pipy 的每个属性都有其自己的特定含义,所以让我们来看看。
编译后的 Pipy 可执行文件大小约为 6MB,运行所需的内存占用非常小。
Pipy 是用 C++ 编写的,构建在Asio异步 I/O 库之上。
Pipy 的核心是模块化设计,许多小的可重用模块(过滤器)可以链接在一起形成一个管道,网络数据通过该管道流动和处理。
Pipy 使用事件驱动的管道对网络流进行操作,在该管道中它使用输入流、执行用户提供的转换并输出流。Pipy 流将数据字节抽象为属于以下四个类别之一的事件:
Pipy通过其定制开发的组件PipyJS提供了内置的JavaScript支持,它是 Pipy 代码库的一部分,但不依赖它。PipyJS 在性能上是高度可定制和可预测的,没有垃圾收集开销。将来,PipyJS 可能会转移到它的独立包中。
Pipy 的内部工作类似于 Unix 管道,但与处理离散字节的 Unix 管道不同,Pipy 处理事件流。
Pipy 通过一系列过滤器处理传入流,其中每个过滤器处理一般问题,如请求日志记录、身份验证、SSL 卸载、请求转发等。每个过滤器从其输入读取并写入其输出,连接一个过滤器的输出到下一个的输入。
过滤器链称为管道,Pipy 根据输入源将管道分为 3 个不同的类别。
港口管道从网络端口读取数据事件,处理它们,然后将结果写回同一端口。这是最常用的请求和响应模型。
例如,当 Pipy 像 HTTP 服务器一样工作时,端口管道的输入是来自客户端的 HTTP 请求,而管道的输出将是发送回客户端的 HTTP 响应。
定时器管道定期获取一对MessageStart和MessageEnd事件作为其输入。当需要类似 cron 作业的功能时很有用。
子管道这与连接过滤器(例如链接)结合使用,它从其前身管道接收事件,将它们馈送到子管道进行处理,从子管道读回输出,然后将其泵送到下一个管道筛选。
查看子管道和连接过滤器的最佳方法是将它们视为过程编程中子例程的被调用者和调用者。联合过滤器的输入是子程序的参数,联合过滤器的输出是它的返回值。
注意:不能从连接过滤器调用像Port & Timer这样的根管道。
Pipy 中的另一个重要概念是上下文。上下文是附加到管道的一组变量。每个管道都可以跨 Pipy 实例访问同一组变量。换句话说,上下文具有相同的形状。当您启动 Pipy 实例时,您要做的第一件事是通过定义变量及其初始值来定义上下文的形状。
每个根管道都会克隆您在开始时定义的初始上下文。当子管道启动时,它会共享或克隆其父级的上下文,具体取决于您使用的联合过滤器。例如,链接过滤器共享其父级的上下文,而demux过滤器克隆它。
对于嵌入在管道中的脚本,这些上下文变量是它们的全局变量,这意味着这些变量总是可以从任何地方被脚本访问,只要它们位于同一个脚本文件中。
对于经验丰富的程序员来说,这可能看起来很奇怪,因为全局变量通常意味着它们是全局唯一的。您只有一组这些变量,而在 Pipy 中,我们可以拥有多组变量(也称为上下文),具体取决于为传入网络连接打开了多少根管道以及有多少子管道克隆了它们父级的上下文。
PipyJS 是一个小型且可嵌入的 JavaScript 引擎,专为高性能而设计,没有垃圾收集开销。它支持 ECMAScript 标准的一个子集,并且在某些领域偏离了该标准。目前它支持 JavaScript 表达式、函数,并实现了 JavaScript 标准 API,如 String、Array 等。
如上所述的上下文是 PipyJS 的一个非常重要的特性,Pipy 以一种特定于代理服务器特定需求的方式扩展它们,每个连接都需要多组全局变量。每个上下文状态对其他上下文不可见,因此使其唯一并且只能由定义它的状态访问。
如果您熟悉多线程编程概念,您还可以将上下文视为TLS(线程局部存储),其中全局变量在不同线程之间具有不同的值。
Pipy 旨在实现跨不同操作系统和 CPU 架构的高兼容性。Pipy 已经在这些平台上进行了全面测试:
CentOS 7Ubuntu 18/20FreeBSD 12/13macOS 大苏尔生产环境推荐使用 CentOS7/REHL7 或 FreeBSD。
对于不耐烦的人,我们可以使用官方 pipy GitHub 存储库中提供的教程脚本之一,通过 docker 运行 pipy 的生产版本。让我们遵循经典的规范Hello World!,但让我们将措辞改为Hi there!
Pipy Docker 镜像可以配置一些环境变量:
PIPY_CONFIG_FILE=表示 Pipy 配置文件的位置PIPY_SPAWN=n对于您要启动的 Pipy 实例数,其中 n 是实例数,这是一个从零开始的索引,其中值 0 表示 1 个实例。例如,您使用PIPY_SPAWN=34 个实例。$ docker run --rm -e PIPY_CONFIG_FILE=\https://raw.githubusercontent.com/flomesh-io/pipy/main/tutorial/01-hello/hello.js \-e PIPY_SPAWN=1 -p 8080:8080 flomesh/pipy-pjs:latest这将使用提供的脚本启动 Pipy 服务器。敏锐的用户可能已经注意到,我们通过环境变量提供了远程 Pipy 脚本的链接,而不是本地文件,PIPY_CONFIG_FILE并且 Pipy 足够聪明,可以处理这种情况。
供您参考,这是文件 tutorial/01-hello/hello.js 的内容:
pipy().listen(8080) .serveHTTP( new Message('Hi, there!\n') )在这个脚本中,我们定义了一个端口管道,它在端口 8080 上侦听并返回“嗨,那里!” 对于在侦听端口上收到的每个 HTTP 请求。
由于我们通过上面的 docker run 命令暴露了本地端口 8080,我们可以在同一个端口上进行测试:
$ curl http://localhost:8080执行上述命令应显示Hi, there! 进入控制台。
出于学习、开发或调试目的,建议继续进行 Pipy 的本地安装(从源代码构建 Pipy 或为您的特定操作系统下载预构建的版本),因为它带有管理 Web 控制台以及文档和教程。
在本地安装后,pipy不带任何参数运行会在 port 上启动管理控制台6060,但可以将其配置为通过 -- admin-port= 参数侦听不同的端口。
Pipy admin console listening on port 6060
要从其源代码构建 Pipy 或为您的操作系统安装预编译的二进制文件,请参阅Pipy github 存储库上的 README.md。
要启动 Pipy 代理,请使用 PipyJS 脚本文件运行 Pipy,例如,tutorial/01-hello/hello.js如果您需要一个简单的回显服务器,该脚本会以与每个传入请求接收到的相同消息体进行响应:
$ pipy tutorial/01-hello/hello.js或者,在开发和调试时,可以使用内置的 Web UI 启动 Pipy:
$ pipy tutorial/01-hello/hello.js --admin-port=6060$ pipy --help$ pipy --list-filters$ pipy --help-filters 这是对 Pipy 的快速概念和技术介绍,是我们编写具有缓存和负载平衡支持的网络代理所必需的,我们将在下一节中看到。
假设我们正在运行不同服务的单独实例,并且我们想添加一个代理以根据请求 URL 路径将流量转发到相关服务。这将使我们能够公开单个 URL 并在后端扩展我们的服务,而无需用户记住不同服务的 URL。在正常情况下,您的服务将在不同的节点上运行,并且每个服务可以运行多个实例。但是,在此示例中,我们假设我们正在运行以下服务,并且我们希望根据 URI 将流量分配给它们。
Pipy 脚本是用 JavaScript 编写的,您可以使用您选择的任何文本编辑器来编辑它们。或者,如果您在本地安装了 Pipy,您可以使用 Pipy 管理 Web UI,它带有语法突出显示、自动完成、提示以及运行脚本的可能性,所有这些都来自同一个控制台。
所以让我们启动一个 Pipy 实例,不带任何参数,因此 Pipy 管理控制台将在端口 6060 上启动。现在打开您最喜欢的 Web 浏览器并导航到http://localhost:6060。您将看到内置的 Pipy Administration Web UI(图 1)。
一个好的设计实践是代码和配置是分开的。Pipy 通过其插件支持这种模块化设计,您可以将其视为 JavaScript 模块。也就是说,我们将在 config 文件夹下存储我们的配置数据,并将我们的编码逻辑存储在 plugins 文件夹下的单独文件中。主代理服务器脚本将存储在根文件夹中,主代理脚本 (proxy.js) 将包含并组合在单独模块中定义的功能。完成下面详述的步骤后,我们的最终文件夹结构将如下所示:
├── config│ ├── balancer.json│ ├── proxy.json│ └── router.json├── plugins│ ├── balancer.js│ ├── default.js│ └── router.js└── proxy.js那么让我们开始吧:
单击New Codebase,在对话框中为 Codebase 名称输入 /proxy(或您想为代码库提供的任何名称),然后单击Create。您将被带到新创建的代码库的代码编辑器中。单击上面的 + 按钮以添加新文件。输入/config/proxy.json(这是我们将用于配置代理的配置文件)的文件名,然后单击Create。您现在将看到 proxy.json 在左侧窗格的 config 文件夹下列出。单击文件将其打开并添加如下所示的配置,并确保通过点击顶部面板上的磁盘图标来保存文件。{ "listen": 8000, "plugins": [ "plugins/router.js", "plugins/balancer.js", "plugins/default.js" ]}重复步骤 2 和 3 以创建另一个文件 ,/config/router.json该文件将存储路由信息,其中包含以下配置数据:{ "routes": { "/hi/*": "service-hi", "/echo": "service-echo", "/ip/*": "service-tell-ip" }}重复步骤 2 和 3 以创建另一个文件 ,/config/balancer.json它将存储我们的服务到目标映射,其中包含以下数据:{ "services": { "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], "service-echo" : ["127.0.0.1:8081"], "service-tell-ip" : ["127.0.0.1:8082"] }}让我们编写我们的第一个 Pipy 脚本,当我们收到没有配置任何目标(端点/url)的请求时,它将用作默认后备。重复以上步骤创建文件/plugins/default.js。这里的名字只是一个约定,Pipy 不依赖名字,所以你可以选择任何你喜欢的名字。该脚本将包含如下所示的代码,该代码返回 HTTP 状态代码 404,并带有未找到处理程序的消息:pipy().pipeline('request') .replaceMessage( new Message({ status: 404 }, 'No handler found') )7. 创建文件/plugins/router.js,存储我们的路由逻辑:
(config =>pipy({ _router: new algo.URLRouter(config.routes),}).export('router', { __serviceID: '',}).pipeline('request') .handleMessageStart( msg => ( __serviceID = _router.find( msg.head.headers.host, msg.head.path, ) ) ))(JSON.decode(pipy.load('config/router.json')))创建文件/plugins/balancer.js,它存储我们的负载平衡逻辑作为旁注,Pipy 带有多种负载平衡算法,但为了简单起见,我们将在这里使用循环算法。(config =>pipy({ _services: ( Object.fromEntries( Object.entries(config.services).map( ([k, v]) => [ k, new algo.RoundRobinLoadBalancer(v) ] ) ) ), _balancer: null, _balancerCache: null, _target: '',}).import({ __turnDown: 'proxy', __serviceID: 'router',}).pipeline('session') .handleStreamStart( () => ( _balancerCache = new algo.Cache( // k is a balancer, v is a target (k ) => k.select(), (k,v) => k.deselect(v), ) ) ) .handleStreamEnd( () => ( _balancerCache.clear() ) ).pipeline('request') .handleMessageStart( () => ( _balancer = _services[__serviceID], _balancer && (_target = _balancerCache.get(_balancer)), _target && (__turnDown = true) ) ) .link( 'forward', () => Boolean(_target), '' ).pipeline('forward') .muxHTTP( 'connection', () => _target ).pipeline('connection') .connect( () => _target ))(JSON.decode(pipy.load('config/balancer.json')))现在让我们编写将使用上述插件的入口点或代理服务器脚本。创建一个新的代码库(步骤 1)将创建一个默认main.js文件作为入口点。我们可以使用它作为我们的主要入口点,或者如果您更喜欢使用不同的名称,请随意删除main.js并使用您选择的名称创建一个新文件。让我们删除它并创建一个名为/proxy.js. 确保单击顶部的标志图标以使其成为主要入口点,因为这将确保在您点击运行按钮(右侧的箭头图标)时开始执行脚本。(config =>pipy().export('proxy', { __turnDown: false,}).listen(config.listen) .use(config.plugins, 'session') .demuxHTTP('request').pipeline('request') .use( config.plugins, 'request', 'response', () => __turnDown ))(JSON.decode(pipy.load('config/proxy.json')))如果您已按照上述步骤操作,那么您将获得类似于您在下面的屏幕截图中看到的内容:
现在让我们通过点击播放图标按钮(右起第四个)来运行我们的脚本。如果我们的脚本没有出错,我们将看到 Pipy 运行我们的代理脚本,我们将看到如下输出:
这表明我们的代理服务器正在侦听端口 8000(我们在 中配置/config/proxy.json)。让我们使用 curl 来运行一个测试:
$ curl -i http://localhost:8000HTTP/1.1 404 Not Foundcontent-length: 10connection: keep-aliveNo handler found这是有道理的,因为我们没有为 root 配置任何目标。让我们尝试我们配置的路线之一,例如/hi:
$ curl -i http://localhost:8000/hiHTTP/1.1 502 Connection Refusedcontent-length: 0connection: keep-alive我们得到 502 Connection Refused,因为我们配置的目标端口上没有运行任何服务。
您可以/config/balancer.json使用主机、已运行服务的端口等详细信息进行更新,以使其适合您的用例,或者让我们在 Pipy 中编写一个脚本,该脚本将侦听我们配置的端口并返回简单的消息。
将下面的代码段保存到本地计算机上名为 的文件中mock-proxy.js,并记住存储它的位置。
pipy().listen(8080) .serveHTTP( new Message('Hi, there!\n') ).listen(8081) .serveHTTP( msg => new Message(msg.body) ).listen(8082) .serveHTTP( msg => new Message( `You are requesting ${msg.head.path} from ${__inbound.remoteAddress}\n` ) )打开一个新的终端窗口并通过 Pipy 运行此脚本(其中 /path/to 是指您存储此脚本文件的位置):
$ pipy /path/to/mock-proxy.js2022-01-11 18:56:31 [INF] [config]2022-01-11 18:56:31 [INF] [config] Module /mock-proxy.js2022-01-11 18:56:31 [INF] [config] ================2022-01-11 18:56:31 [INF] [config]2022-01-11 18:56:31 [INF] [config] [Listen on :::8080]2022-01-11 18:56:31 [INF] [config] ----->|2022-01-11 18:56:31 [INF] [config] |2022-01-11 18:56:31 [INF] [config] serveHTTP2022-01-11 18:56:31 [INF] [config] |2022-01-11 18:56:31 [INF] [config] <-----|2022-01-11 18:56:31 [INF] [config] 2022-01-11 18:56:31 [INF] [config] [Listen on :::8081]2022-01-11 18:56:31 [INF] [config] ----->|2022-01-11 18:56:31 [INF] [config] |2022-01-11 18:56:31 [INF] [config] serveHTTP2022-01-11 18:56:31 [INF] [config] |2022-01-11 18:56:31 [INF] [config] <-----|2022-01-11 18:56:31 [INF] [config] 2022-01-11 18:56:31 [INF] [config] [Listen on :::8082]2022-01-11 18:56:31 [INF] [config] ----->|2022-01-11 18:56:31 [INF] [config] |2022-01-11 18:56:31 [INF] [config] serveHTTP2022-01-11 18:56:31 [INF] [config] |2022-01-11 18:56:31 [INF] [config] <-----|2022-01-11 18:56:31 [INF] [config] 2022-01-11 18:56:31 [INF] [listener] Listening on port 8080 at ::2022-01-11 18:56:31 [INF] [listener] Listening on port 8081 at ::2022-01-11 18:56:31 [INF] [listener] Listening on port 8082 at ::现在我们的模拟服务在端口 8080、8081 和 8082 上进行监听。所以让我们在代理服务器上再次进行测试,您将看到模拟服务返回的正确响应。
我们使用了许多 Pipy 功能,包括变量声明、导入/导出变量、插件、管道、子管道、过滤器链接、Pipy 过滤器(如handleMessageStart、、handleStreamStart和link)以及 pipy 类(如 JSON, algo.URLRouter, algo.RoundRobinLoadBalancer, algo.Cache)等。对所有这些概念的全面解释超出了本文的范围,但我们鼓励您阅读 Pipy 文档,该文档可通过 Pipy 的管理 Web UI 访问,并按照随附的分步教程进行操作。
来自Flomesh 的Pipy是一个开源、速度极快且轻量级的网络流量处理器,可用于各种用例,包括边缘路由器、负载平衡和代理(正向/反向)、API 网关、静态 HTTP 服务器、服务网状边车和许多其他应用程序。Pipy 正在积极开发并由全职提交者和贡献者维护,尽管仍是早期版本,但它已经过实战测试,并已被多个商业客户用于生产。它的创建者和维护者Flomesh.cn提供了以 Pipy 为核心运行的商业级解决方案。
版权声明:我们致力于保护作者版权,注重分享,被刊用文章【代理端口(架构实战)】因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理!;
工作时间:8:00-18:00
客服电话
电子邮件
beimuxi@protonmail.com
扫码二维码
获取最新动态
