重新审视 2018 年的 Unix 哲学

在现代微服务环境中,构建小型、专注的应用程序的旧策略再次焕发生机。
411 位读者喜欢这篇文章。
Getting started with SQL

Opensource.com

1984 年,Rob Pike 和 Brian W. Kernighan 在 AT&T 贝尔实验室技术期刊上发表了一篇名为“Unix 环境中的程序设计”的文章,其中他们以 BSD 的 cat -v 实现为例,论证了 Unix 哲学。简而言之,该哲学是:构建小型、专注的程序——无论使用何种语言——只做一件事,但要把这件事做好,通过 stdin/stdout 进行通信,并通过管道连接。

听起来耳熟吗?

是的,我也这么认为。这与 James Lewis 和 Martin Fowler 提供的 微服务定义 非常相似

简而言之,微服务架构风格是将单个应用程序开发为一组小型服务的方法,每个服务都在自己的进程中运行,并使用轻量级机制(通常是 HTTP 资源 API)进行通信。

虽然一个 *nix 程序或一个微服务本身可能非常有限,甚至不是很有趣,但正是这些独立工作单元的组合揭示了它们的真正优势,以及因此而来的力量。

*nix 与微服务

下表比较了 *nix 环境(如 catlsof)中的程序与微服务环境中的程序。

  *nix 微服务
执行单元 使用 stdin/stdout 的程序 具有 HTTP 或 gRPC API 的服务
数据流 管道 ?
配置和参数化 命令行参数、

环境变量、配置文件
JSON/YAML 文档
发现 包管理器、man、make DNS、环境变量、OpenAPI

让我们更详细地探讨每一行。

执行单元

*nix(如 Linux)中的执行单元是可执行文件(二进制文件或解释型脚本),理想情况下,它从 stdin 读取输入并将输出写入 stdout。微服务设置处理的是暴露一个或多个通信接口(如 HTTP 或 gRPC API)的服务。在这两种情况下,您都会找到无状态示例(本质上是纯粹的功能行为)和有状态示例,在有状态示例中,除了输入之外,一些内部(持久化)状态决定了会发生什么。

数据流

传统上,*nix 程序可以通过管道进行通信。换句话说,感谢 Doug McIlroy,您无需创建临时文件来传递,并且每个程序都可以在进程之间处理几乎无尽的数据流。据我所知,除了我 2017 年的 基于 Apache Kafka 的分布式命名管道和其他服务间通信实验 之外,微服务中没有任何与管道标准化的东西。

配置和参数化

您如何配置程序或服务——无论是永久性的还是按调用进行的?嗯,对于 *nix 程序,您基本上有三个选项:命令行参数、环境变量或完整的配置文件。在微服务中,您通常处理 YAML(甚至更糟的是 JSON)文档,这些文档定义了单个微服务的布局和配置,以及依赖项和通信、存储和运行时设置。示例包括 Kubernetes 资源定义Nomad 作业规范Docker Compose 文件。这些可能是参数化的,也可能不是参数化的;也就是说,要么您有一些模板语言,例如 Kubernetes 中的 Helm,要么您发现自己做了大量的 sed -i 命令。

发现

您如何知道有哪些程序或服务可用以及它们应该如何使用?嗯,在 *nix 中,您通常有一个包管理器以及古老的 man 手册;它们之间应该能够回答您可能提出的所有问题。在微服务设置中,在查找服务方面有更多的自动化。除了像 Airbnb 的 SmartStackNetflix 的 Eureka 这样的定制方法之外,通常还有基于环境变量或基于 DNS 的 方法,允许您动态地发现服务。同样重要的是,OpenAPI 为 HTTP API 文档和设计提供了事实上的标准,而 gRPC 为更紧密耦合的高性能案例做了同样的事情。最后但并非最不重要的一点是,考虑开发者体验 (DX),从编写良好的 Makefile 开始,到使用(或在?)style 编写文档结束。

优点和缺点

*nix 和微服务都提供了一些挑战和机遇

可组合性

设计既有清晰、鲜明的焦点又能与他人良好协作的东西是很困难的。在不同版本中正确实现它,并引入相应的错误处理能力就更难了。在微服务中,这可能意味着重试逻辑和超时——也许将这些功能外包到服务网格是一个更好的选择?这很困难,但如果您做得对,它的可重用性将是巨大的。

可观察性

在单体应用(2018 年)或试图完成所有任务的大型程序(1984 年)中,当事情出错时,找到罪魁祸首是相当直接的。但是,在

yes | tr \\n x | head -c 450m | grep n

或微服务设置中的请求路径中,假设涉及 20 个服务,您甚至如何开始弄清楚哪个服务行为不端?幸运的是,我们有标准,特别是 OpenCensusOpenTracing。如果您希望迁移到微服务,可观察性仍然可能是最大的单一障碍。

全局状态

虽然对于 *nix 程序来说,全局状态可能不是什么大问题,但在微服务中,全局状态仍然是一个讨论的问题。即,如何确保有效地管理本地(持久化)状态,以及如何以尽可能小的代价使全局状态保持一致。

总结

最后,问题仍然是:您是否为给定的任务使用了正确的工具?也就是说,就像专门的 *nix 程序实现一系列功能可能是某些用例或阶段的更好选择一样,单体应用 可能是您的组织或工作负载的最佳选择。无论如何,我希望本文能帮助您看到 Unix 哲学和微服务之间许多强大的相似之处——也许我们可以从前者中学习一些东西来造福后者。

mh9 pic
Michael 是 Red Hat 的 Kubernetes 和 OpenShift 的开发者布道师,他在那里帮助应用程序运维人员构建和运营应用程序。他的背景是大规模数据处理和容器编排,并且在 W3C 和 IETF 的倡导和标准化方面经验丰富。在 Red Hat 之前,Michael 曾在 Mesosphere、MapR 以及爱尔兰和奥地利的两家研究机构工作。

5 条评论

我认为,unix 哲学的真正要点不是进程和管道。如果您查看他们使用的任何示例,他们都没有构建工具然后将它们连接起来。他们利用现有、强大的工具并将它们组合在一起。真正的要点是通过利用现有工作来避免做太多工作。大多数微服务教学法都不是这样工作的。您自己构建所有服务,然后将它们组合在一起。Unix 工具使用管道是为了更容易组合工具。如果一切都基于文本操作,那么两个工具很可能能够协同工作。如果您自己构建工具,您可以完全控制它们的接口方式。您可以使用 JSON 或二进制 IPC,或者您语言的调用约定。主要的相似之处可能在于,两种方法都将工作单元划分为不同的进程。但我认为,部分原因是这样做的原因可能不同。Unix 这样做是为了节省内存。如果管道中的第二个进程在第一个进程完成之前无法运行,那么您必须将第一个进程的所有输出保存在内存中。通过并行操作,您只需一次存储一行。另一方面,微服务对使用并行性来跨 CPU 核心和跨机器分配工作感兴趣。

话虽如此,您可以将微服务用于此目的。我最近调查了在我的服务器上使用一个名为 stunnel 的工具。前提很简单:stunnel 接受多播上的 tls 请求,然后将原始 tcp 转发到本地主机上运行的某些东西。您无需在您编写的每个应用程序中处理 tls,您可以让 stunnel 开发人员为您处理它。这是类似 unix 的组合,接口是 tcp 套接字(毕竟 tcp 套接字是仿照管道建模的)。

感谢您的评论 syrrim,信不信由你,Doug McIlroy 本人通过电子邮件对此进行了评论。以下是我今天收到的他的原话

> 一位评论者对您的“重新审视 Unix 哲学”的评论没有抓住管道的要点。
> 我写了以下回复,但在被要求注册时犹豫了。我不会躲藏,最
> 不会躲在社交媒体的围墙后面。欢迎您转达。
>
> “Unix 这样做是为了节省内存。” 我以前从未听说过这种说法。
> 管道当然避免了分配内存的麻烦,用它的名称弄乱程序,
> 并释放它。管道也可能节省内存带宽和延迟。但节省内存本身
> 很少,甚至从不是重点。
>
> 然而,您的观察,“通过并行操作,您只需一次存储一行,”
> 至关重要。它允许人们实时地与管道进行交互。它也允许管道提供不间断的
> 服务。在无 IPC 中间文件模型中,这两者都不可能实现。

回复 作者:syrrim (未验证)

我想这就是为什么 Linux 正被一个几乎完成所有事情的单一进程接管的原因。Unix 哲学真是荡然无存。

我不确定您所说的“Linux 正被一个几乎完成所有事情的单一进程接管”是什么意思。您愿意详细说明吗?

回复 作者:Jay Sanders (未验证)

他的意思是 systemd。

回复 作者:asgs (未验证)

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© 2025 open-source.net.cn. All rights reserved.