netdev 第一天：IPsec！

Summary: 在今天的会议中，我学到了很多有关 IPsec 的知识，所以下面我将介绍它们！

在今天的会议中，我学到了很多有关 IPsec 的知识，所以下面我将介绍它们！

![](/data/attachment/album/201808/15/225414k7rz2sh5596rlz3s.jpg)

嗨！和去年一样，今年我又参加了 [netdev 会议](https://www.netdevconf.org/0x12/)。（[这里](https://jvns.ca/categories/netdev/)是我上一年所做的笔记）。

在今天的会议中，我学到了很多有关 IPsec 的知识，所以下面我将介绍它们！其中 Sowmini Varadhan 和 [Paul Wouters](https://nohats.ca/) 做了一场关于 IPsec 的专题研讨会。本文中的错误 100% 都是我的错 :)。

### 什么是 IPsec？

IPsec 是一个用来加密 IP 包的协议。某些 VPN 已经是通过使用 IPsec 来实现的。直到今天我才真正意识到 VPN 使用了不只一种协议，原来我以为 VPN 只是一个通用术语，指的是“你的数据包将被加密，然后通过另一台服务器去发送“。VPN 可以使用一系列不同的协议（OpenVPN、PPTP、SSTP、IPsec 等）以不同的方式来实现。

为什么 IPsec 和其他的 VPN 协议如此不同呢？（或者说，为什么在本次 netdev 会议会有 IPsec 的教程，而不是其他的协议呢？）我的理解是有 2 点使得它如此不同：

* 它是一个 IETF 标准，例如可以在文档 [RFC 6071](https://tools.ietf.org/html/rfc6071) 等中查到（你知道 IETF 是制定 RFC 标准的组织吗？我也是直到今天才知道的！）。
* 它在 Linux 内核中被实现了（所以这才是为什么本次 netdev 会议中有关于它的教程，因为 netdev 是一个跟 Linux 内核网络有关的会议 :)）。

### IPsec 是如何工作的？

假如说你的笔记本正使用 IPsec 来加密数据包并通过另一台设备来发送它们，那这是怎么工作的呢？对于 IPsec 来说，它有 2 个部分：一个是用户空间部分，另一个是内核空间部分。

IPsec 的用户空间部分负责**密钥的交换**，使用名为 [IKE](https://en.wikipedia.org/wiki/Internet_Key_Exchange) （<ruby> 网络密钥传输 <rt>  internet key exchange </rt></ruby>）的协议。总的来说，当你打开一个 VPN 连接的时候，你需要与 VPN 服务器通信，并且和它协商使用一个密钥来进行加密。

IPsec 的内核部分负责数据包的实际加密工作 —— 一旦使用 IKE 生成了一个密钥，IPsec 的用户空间部分便会告诉内核使用哪个密钥来进行加密。然后内核便会使用该密钥来加密数据包！

### 安全策略以及安全关联

（LCTT 译注：security association 我翻译为安全关联， 参考自 <https://zh.wikipedia.org/wiki/%E5%AE%89%E5%85%A8%E9%97%9C%E8%81%AF> ）

IPSec 的内核部分有两个数据库：**安全策略数据库**（SPD）和**安全关联数据库**（SAD）。

安全策略数据库包含 IP 范围和用于该范围的数据包需要执行的操作（对其执行 IPsec、丢弃数据包、让数据包通过）。对于这点我有点迷糊，因为针对不同 IP 范围的数据包所采取的规则已经在路由表（`sudo ip route list`）中使用过，但显然你也可以设定 IPsec 规则，但它们位于不同的地方！

而在我眼中，安全关联数据库存放有用于各种不同 IP 的加密密钥。

查看这些数据库的方式却是非常不直观的，需要使用一个名为 `ip xfrm` 的命令，至于 `xfrm` 是什么意思呢？我也不知道！

（LCTT 译注：我在 <https://www.allacronyms.com/XFMR/Transformer> 上查到 xfmr 是 Transformer 的简写，又根据 [man7](http://man7.org/linux/man-pages/man8/ip-xfrm.8.html) 上的简介， 我认为这个说法可信。）

```
# security policy database
$ sudo ip xfrm policy
$ sudo ip x p

# security association database
$ sudo ip xfrm state
$ sudo ip x s

```

### 为什么 IPsec 被实现在 Linux 内核中而 TLS 没有？

对于 TLS 和 IPsec 来说，当打开一个连接时，它们都需要做密钥交换（使用 Diffie-Hellman 或者其他算法）。基于某些可能很明显但我现在还没有理解（？？）的原因，在内核中人们并不想做密钥的交换。

IPsec 更容易在内核实现的原因是使用 IPsec 你可以更少频率地协商密钥的交换（对于每个你想通过 VPN 来连接的 IP 只需要一次），并且 IPsec 会话存活得更长。所以对于用户空间来说，使用 IPsec 来做密钥交换、密钥的获取和将密钥传递给内核将更容易，内核得到密钥后将使用该密钥来处理每个 IP 数据包。

而对于 TLS 来说，则存在一些问题：

a. 当你每打开一个 TLS 连接时，每次你都要做新的密钥交换，并且 TLS 连接存活时间较短。 b. 当你需要开始做加密时，使用 IPsec 没有一个自然的协议边界，你只需要加密给定 IP 范围内的每个 IP 包即可，但如果使用 TLS，你需要查看 TCP 流，辨别 TCP 包是否是一个数据包，然后决定是否加密它。

实际上有一个补丁用于 [在 Linux 内核中实现 TLS](https://blog.filippo.io/playing-with-kernel-tls-in-linux-4-13-and-go/)，它让用户空间做密钥交换，然后传给内核密钥，所以很明显，使用 TLS 不是不可能的，但它是一个新事物，并且我认为相比使用 IPsec，使用 TLS 更加复杂。

### 使用什么软件来实现 IPsec 呢？

据我所知有 Libreswan 和 Strongswan 两个软件。今天的教程关注的是 libreswan。

有些让人迷糊的是，尽管 Libreswan 和 Strongswan 是不同的程序包，但它们都会安装一个名为 `ipsec` 的二进制文件来管理 IPsec 连接，并且这两个 `ipsec` 二进制文件并不是相同的程序（尽管它们担任同样的角色）。

在上面的“IPsec 如何工作”部分，我已经描述了 Strongswan 和 Libreswan 做了什么 —— 使用 IKE 做密钥交换，并告诉内核有关如何使用密钥来做加密。

### VPN 不是只能使用 IPsec 来实现！

在本文的开头我说“IPsec 是一个 VPN 协议”，这是对的，但你并不必须使用 IPsec 来实现 VPN！实际上有两种方式来使用 IPsec：

1. “传输模式”，其中 IP 表头没有改变，只有 IP 数据包的内容被加密。这种模式有点类似于使用 TLS —— 你直接告诉服务器你正在通信（而不是通过一个 VPN 服务器或其他设备），只有 IP 包里的内容被加密。
2. ”隧道模式“，其中 IP 表头和它的内容都被加密了，并且被封装进另一个 UDP 包内。这个模式被 VPN 所使用 —— 你获取你正传送给一个秘密网站的包，然后加密它，并将它送给你的 VPN 服务器，然后 VPN 服务器再传送给你。

### 投机的 IPsec

今天我学到了 IPsec “传输模式”的一个有趣应用，它叫做 “投机的 IPsec”（通过它，你可以通过开启一个 IPsec 连接来直接和你要通信的主机连接，而不是通过其他的中介服务器），现在已经有一个“投机的 IPsec” 服务器了，它位于 <http://oe.libreswan.org/>。

我认为当你在你的电脑上设定好 `libreswan` 和 `unbound` DNS 程序后，当你连接到 [http://oe.libreswan.org](http://oe.libreswan.org/) 时，主要发生了如下的几件事：

1. `unbound` 做一次 DNS 查询来获取 `oe.libreswan.org` (`dig ipseckey oe.libreswan.org`) 的 IPSECKEY 记录，以便获取到公钥来用于该网站（这需要 DNSSEC 是安全的，并且当我获得足够多这方面的知识后，我将用另一篇文章来说明它。假如你想看到相关的结果，并且如果你只是使用 dig 命令来运行此次 DNS 查询的话，它也可以工作）。
2. `unbound` 将公钥传给 `libreswan` 程序，然后 `libreswan` 使用它来和运行在 `oe.libreswan.org` 网站上的 IKE 服务器做一次密钥交换。
3. `libreswan` 完成了密钥交换，将加密密钥传给内核并告诉内核当和 `oe.libreswan.org` 做通信时使用该密钥。
4. 你的连接现在被加密了！即便它是 HTTP 连接！有趣吧！

### IPsec 和 TLS 相互借鉴

在今天的教程中听到一个有趣的花絮是 IPsec 和 TLS 协议实际上总是从对方学习 —— 正如他们说在 TLS 出现前， IPsec 的 IKE 协议有着完美的正向加密，而 IPsec 也从 TLS 那里学了很多。很高兴能听到不同的网络协议之间是如何从对方那里学习并与时俱进的事实！

### IPsec 是有趣的！

我已经花了很长时间来学习 TLS，很明显它是一个超级重要的网络协议（让我们来加密网络吧！:D）。但 IPsec 也是一个很重要的网络加密协议，它与 TLS 有着不同的角色！很明显有些移动电话协议（例如 5G/LTE）使用 IPsec 来加密它们的网络流量！

现在我很高兴我知道更多关于 IPsec 的知识！和以前一样可能本文有些错误，但希望不会错的太多 :)

---

via: <https://jvns.ca/blog/2018/07/11/netdev-day-1--ipsec/>

作者：[Julia Evans](https://jvns.ca/about) 译者：[FSSlc](https://github.com/FSSlc) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Linux 下如何通过两个或多个输出设备播放声音

![](/data/attachment/album/201808/16/111157buxjrssdp2w4z4zt.jpg)

在 Linux 上处理音频是一件很痛苦的事情。Pulseaudio 的出现则是利弊参半。虽然有些事情 Pluseaudio 能够做的更好，但有些事情则反而变得更复杂了。处理音频的输出就是这么一件事情。

如果你想要在 Linux PC 上启用多个音频输出，你只需要利用一个简单的工具就能在一个虚拟j接口上启用另一个声音设备。这比看起来要简单的多。

你可能会好奇为什么要这么做，一个很常见的情况是用电脑在电视上播放视频，你可以同时使用电脑和电视上的扬声器。

### 安装 Paprefs

实现从多个来源启用音频播放的最简单的方法是是一款名为 “paprefs” 的简单图形化工具。它是 PulseAudio Preferences 的缩写。

该软件包含在 Ubuntu 仓库中，可以直接用 apt 来进行安装。

```
sudo apt install paprefs

```

安装后就能够启动这款程序了。

### 启动双音频播放

虽然这款工具是图形化的，但作为普通用户在命令行中输入 `paprefs` 来启动它恐怕还是要更容易一些。

打开的窗口中有一些标签页，这些标签页内有一些可以调整的设置项。我们这里选择最后那个标签页，“Simultaneous Output。”

![Paprefs on Ubuntu](/data/attachment/album/201808/16/111159iluyds2wd4igsmdl.jpg "Paprefs on Ubuntu")

这个标签页中没有什么内容，只是一个复选框用来启用该设置。

下一步，打开常规的声音首选项。这在不同的发行版中位于不同的位置。在 Ubuntu 上，它位于 GNOME 系统设置内。

![Enable Simultaneous Audio](/data/attachment/album/201808/16/111201u19s5gtyyjjxeetz.jpg "Enable Simultaneous Audio")

打开声音首选项后，选择 “output” 标签页。勾选 “Simultaneous output” 单选按钮。现在它就成了你的默认输出了。

### 测试一下

用什么东西进行测试随你喜欢，不过播放音乐总是可行的。如果你像前面建议的一样，用视频来进行测试也没问题。

一切顺利的话，你就能从所有连接的设备中听到有声音传出了。

这就是所有要做的事了。此功能最适用于有多个设备（如 HDMI 端口和标准模拟输出）时。你当然也可以试一下其他配置。你还需要注意，只有一个音量控制器，因此你需要根据实际情况调整物理输出设备。

---

via: <https://www.maketecheasier.com/play-sound-through-multiple-devices-linux/>

作者：[Nick Congleton](https://www.maketecheasier.com/author/nickcongleton/) 译者：[lujun9972](https://github.com/lujun9972) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Yaourt 已死！在 Arch 上使用这些替代品

aurman也停止开发了，aurman的开发人员现在推荐yay

标题党

yay好用

**前略：Yaourt 曾是最流行的 AUR 助手，但现已停止开发。在这篇文章中，我们会为 Arch 衍生发行版们列出 Yaourt 最佳的替代品。**

[Arch User Repository](https://wiki.archlinux.org/index.php/Arch_User_Repository) （常被称作 AUR），是一个为 Arch 用户而生的社区驱动软件仓库。Debian/Ubuntu 用户的对应类比是 PPA。

AUR 包含了不直接被 [Arch Linux](https://www.archlinux.org/) 官方所背书的软件。如果有人想在 Arch 上发布软件或者包，它可以通过这个社区仓库提供。这让最终用户们可以使用到比默认仓库里更多的软件。

所以你该如何使用 AUR 呢？简单来说，你需要另外的工具以从 AUR 中安装软件。Arch 的包管理器 [pacman](https://wiki.archlinux.org/index.php/pacman) 不直接支持 AUR。那些支持 AUR 的“特殊工具”我们称之为 [AUR 助手](https://wiki.archlinux.org/index.php/AUR_helpers)。

Yaourt （Yet AnOther User Repository Tool）（曾经）是 `pacman` 的一个封装，便于用户在 Arch Linux 上安装 AUR 软件。它基本上采用和 `pacman` 一样的语法。Yaourt 对于 AUR 的搜索、安装，乃至冲突解决和包依赖关系维护都有着良好的支持。

然而，Yaourt 的开发进度近来十分缓慢，甚至在 Arch Wiki 上已经被[列为](https://wiki.archlinux.org/index.php/AUR_helpers#Comparison_table)“停止或有问题”。[许多 Arch 用户认为它不安全](https://www.reddit.com/r/archlinux/comments/4azqyb/whats_so_bad_with_yaourt/) 进而开始寻找其它的 AUR 助手。

![Yaourt 以外的 AUR Helpers](/data/attachment/album/201808/16/123706vsuasswnrdsudg41.jpg)

在这篇文章中，我们会介绍 Yaourt 最佳的替代品以便于你从 AUR 安装软件。

### 最好的 AUR 助手

我刻意忽略掉了例如 Trizen 和 Packer 这样的流行的选择，因为它们也被列为“停止或有问题”的了。

#### 1、 aurman

[aurman](https://github.com/polygamma/aurman) 是最好的 AUR 助手之一，也能胜任 Yaourt 替代品的地位。它有非常类似于 `pacman` 的语法，可以支持所有的 `pacman` 操作。你可以搜索 AUR、解决包依赖，在构建 AUR 包前检查 PKGBUILD 的内容等等。

aurman 的特性：

* aurman 支持所有 `pacman` 操作，并且引入了可靠的包依赖解决方案、冲突判定和<ruby> 分包 <rt>  split package </rt></ruby>支持
* 线程化的 sudo 循环会在后台运行，所以你每次安装只需要输入一次管理员密码
* 提供开发包支持，并且可以区分显性安装和隐性安装的包
* 支持搜索 AUR 包和仓库
* 在构建 AUR 包之前，你可以检视并编辑 PKGBUILD 的内容
* 可以用作单独的 [包依赖解决工具](https://github.com/polygamma/aurman/wiki/Using-aurman-as-dependency-solver)

安装 aurman：

```
git clone https://aur.archlinux.org/aurman.git
cd aurman
makepkg -si

```

使用 aurman：

用名字搜索：

```
aurman -Ss <package-name>

```

安装：

```
aurman -S <package-name>

```

#### 2、 yay

[yay](https://github.com/Jguer/yay) 是下一个最好的 AUR 助手。它使用 Go 语言写成，宗旨是提供最少化用户输入的 `pacman` 界面、yaourt 式的搜索，而几乎没有任何依赖软件。

yay 的特性：

* `yay` 提供 AUR 表格补全，并且从 ABS 或 AUR 下载 PKGBUILD
* 支持收窄搜索，并且不需要引用 PKGBUILD 源
* `yay` 的二进制文件除了 `pacman` 以外别无依赖
* 提供先进的包依赖解决方案，以及在编译安装之后移除编译时的依赖
* 当在 `/etc/pacman.conf` 文件配置中启用了色彩时支持色彩输出
* `yay` 可被配置成只支持 AUR 或者 repo 里的软件包

安装 yay：

你可以从 `git` 克隆并编译安装。

```
git clone https://aur.archlinux.org/yay.git
cd yay
makepkg -si

```

使用 yay：

搜索：

```
yay -Ss <package-name>

```

安装：

```
yay -S <package-name>

```

#### 3、 pakku

[Pakku](https://github.com/kitsunyan/pakku) 是另一个还处于开发早期的 pacman 封装，虽然它还处于开放早期，但这不说明它逊于其它任何 AUR 助手。Pakku 能很好地支持从 AUR 搜索和安装，并且也可以在安装后移除不必要的编译依赖。

pakku 的特性：

* 从 AUR 搜索和安装软件
* 检视不同构建之间的文件和变化
* 从官方仓库编译，并事后移除编译依赖
* 获取 PKGBUILD 以及 pacman 整合
* 类 pacman 的用户界面和选项支持
* 支持pacman 配置文件以及无需 PKGBUILD 源

安装 pakku：

```
git clone https://aur.archlinux.org/pakku.git
cd pakku
makepkg -si

```

使用 pakku：

搜索：

```
pakku -Ss spotify

```

安装：

```
pakku -S spotify

```

#### 4、 aurutils

[aurutils](https://github.com/AladW/aurutils) 本质上是一堆使用 AUR 的自动化脚本的集合。它可以搜索 AUR、检查更新，并且解决包依赖。

aurutils 的特性：

* aurutils 使用本地仓库以支持 pacman 文件，所有的包都支持 `–asdeps`
* 不同的任务可以有多个仓库
* `aursync -u` 一键同步本地代码库
* `aursearch` 搜索提供 pkgbase、长格式和 raw 支持
* 能忽略指定包

安装 aurutils：

```
git clone https://aur.archlinux.org/aurutils.git
cd aurutils
makepkg -si

```

使用 aurutils：

搜索：

```
aurutils -Ss <package-name>

```

安装：

```
aurutils -S <package-name>

```

所有这些包，在有 Yaourt 或者其它 AUR 助手的情况下都可以直接安装。

### 写在最后

Arch Linux 有着[很多 AUR 助手](https://wiki.archlinux.org/index.php/AUR_helpers) 可以自动完成 AUR 各方面的日常任务。很多用户依然使用 Yaourt 来完成 AUR 相关任务，每个人都有自己不一样的偏好，欢迎留言告诉我们你在 Arch 里使用什么，又有什么心得？

---

via: <https://itsfoss.com/best-aur-helpers/>

作者：[Ambarish Kumar](https://itsfoss.com/author/ambarish/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[Moelf](https://github.com/Moelf) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


什么是 CI/CD？

Summary: 在软件开发中经常会提到持续集成（CI）和持续交付（CD）这几个术语。但它们真正的意思是什么呢？

学习了

学习了

学习了

学习了

知识点，太特么硬了

提个建议，pipeline可以翻译成流水线更合适

涨姿势了

来自浙江杭州的 Microsoft Edge 17.17134|Windows 10 用户

涨姿势了

可以的

可以的

太硬了。很难看懂

看不懂啊，，困了。。。

看不懂啊，，困了。。。

看不懂啊，，困了。。。

看不懂啊，，困了。。。

写的不错，学习学习

写的不错，学习学习

涨姿势了

画重点，太厉害了。

看不懂。。

来自上海的 Microsoft Edge 18.18362|Windows 10 用户

看不懂呀！看着看着就睡着了！

学习了。。

看看我的小尾巴

小尾巴不错啊

看不懂哇

头晕眼花

涨知识了!

好牛

涨姿势~

涨姿势~

好文，排版不错，最重要的是翻译不易，内容真心好！

感觉废话太多了，好多互联网黑话

感觉废话太多了，好多互联网黑话

在软件开发中经常会提到<ruby> 持续集成 <rt>  Continuous Integration </rt></ruby>（CI）和<ruby> 持续交付 <rt>  Continuous Delivery </rt></ruby>（CD）这几个术语。但它们真正的意思是什么呢？

![](/data/attachment/album/201808/16/184248q5r0j7c5jcms76qs.png)

在谈论软件开发时，经常会提到<ruby> 持续集成 <rt>  Continuous Integration </rt></ruby>（CI）和<ruby> 持续交付 <rt>  Continuous Delivery </rt></ruby>（CD）这几个术语。但它们真正的意思是什么呢？在本文中，我将解释这些和相关术语背后的含义和意义，例如<ruby> 持续测试 <rt>  Continuous Testing </rt></ruby>和<ruby> 持续部署 <rt>  Continuous Deployment </rt></ruby>。

### 概览

工厂里的装配线以快速、自动化、可重复的方式从原材料生产出消费品。同样，软件交付管道以快速、自动化和可重复的方式从源代码生成发布版本。如何完成这项工作的总体设计称为“持续交付”（CD）。启动装配线的过程称为“持续集成”（CI）。确保质量的过程称为“持续测试”，将最终产品提供给用户的过程称为“持续部署”。一些专家让这一切简单、顺畅、高效地运行，这些人被称为<ruby> 运维开发 <rt>  DevOps </rt></ruby>践行者。

### “持续”是什么意思？

“持续”用于描述遵循我在此提到的许多不同流程实践。这并不意味着“一直在运行”，而是“随时可运行”。在软件开发领域，它还包括几个核心概念/最佳实践。这些是：

* **频繁发布**：持续实践背后的目标是能够频繁地交付高质量的软件。此处的交付频率是可变的，可由开发团队或公司定义。对于某些产品，一季度、一个月、一周或一天交付一次可能已经足够频繁了。对于另一些来说，一天可能需要多次交付也是可行的。所谓持续也有“偶尔、按需”的方面。最终目标是相同的：在可重复、可靠的过程中为最终用户提供高质量的软件更新。通常，这可以通过很少甚至无需用户的交互或掌握的知识来完成（想想设备更新）。
* **自动化流程**：实现此频率的关键是用自动化流程来处理软件生产中的方方面面。这包括构建、测试、分析、版本控制，以及在某些情况下的部署。
* **可重复**：如果我们使用的自动化流程在给定相同输入的情况下始终具有相同的行为，则这个过程应该是可重复的。也就是说，如果我们把某个历史版本的代码作为输入，我们应该得到对应相同的可交付产出。这也假设我们有相同版本的外部依赖项（即我们不创建该版本代码使用的其它交付物）。理想情况下，这也意味着可以对管道中的流程进行版本控制和重建（请参阅稍后的 DevOps 讨论）。
* **快速迭代**：“快速”在这里是个相对术语，但无论软件更新/发布的频率如何，预期的持续过程都会以高效的方式将源代码转换为交付物。自动化负责大部分工作，但自动化处理的过程可能仍然很慢。例如，对于每天需要多次发布候选版更新的产品来说，一轮<ruby> 集成测试 <rt>  integrated testing </rt></ruby>下来耗时就要大半天可能就太慢了。

### 什么是“持续交付管道”？

将源代码转换为可发布产品的多个不同的<ruby> 任务 <rt>  task </rt></ruby>和<ruby> 作业 <rt>  job </rt></ruby>通常串联成一个软件“管道”，一个自动流程成功完成后会启动管道中的下一个流程。这些管道有许多不同的叫法，例如持续交付管道、部署管道和软件开发管道。大体上讲，程序管理者在管道执行时管理管道各部分的定义、运行、监控和报告。

### 持续交付管道是如何工作的？

软件交付管道的实际实现可以有很大不同。有许多程序可用在管道中，用于源代码跟踪、构建、测试、指标采集，版本管理等各个方面。但整体工作流程通常是相同的。单个业务流程/工作流应用程序管理整个管道，每个流程作为独立的作业运行或由该应用程序进行阶段管理。通常，在业务流程中，这些独立作业是以应用程序可理解并可作为工作流程管理的语法和结构定义的。

这些作业被用于一个或多个功能（构建、测试、部署等）。每个作业可能使用不同的技术或多种技术。关键是作业是自动化的、高效的，并且可重复的。如果作业成功，则工作流管理器将触发管道中的下一个作业。如果作业失败，工作流管理器会向开发人员、测试人员和其他人发出警报，以便他们尽快纠正问题。这个过程是自动化的，所以比手动运行一组过程可更快地找到错误。这种快速排错称为<ruby> 快速失败 <rt>  fail fast </rt></ruby>，并且在抵达管道端点方面同样有价值。

### “快速失败”是什么意思？

管道的工作之一就是快速处理变更。另一个是监视创建发布的不同任务/作业。由于编译失败或测试未通过的代码可以阻止管道继续运行，因此快速通知用户此类情况非常重要。快速失败指的是在管道流程中尽快发现问题并快速通知用户的方式，这样可以及时修正问题并重新提交代码以便使管道再次运行。通常在管道流程中可通过查看历史记录来确定是谁做了那次修改并通知此人及其团队。

### 所有持续交付管道都应该被自动化吗？

管道的几乎所有部分都是应该自动化的。对于某些部分，有一些人为干预/互动的地方可能是有意义的。一个例子可能是<ruby> 用户验收测试 <rt>  user-acceptance testing </rt></ruby>（让最终用户试用软件并确保它能达到他们想要/期望的水平）。另一种情况可能是部署到生产环境时用户希望拥有更多的人为控制。当然，如果代码不正确或不能运行，则需要人工干预。

有了对“持续”含义理解的背景，让我们看看不同类型的持续流程以及它们在软件管道上下文中的含义。

### 什么是“持续集成”？

持续集成（CI）是在源代码变更后自动检测、拉取、构建和（在大多数情况下）进行单元测试的过程。持续集成是启动管道的环节（尽管某些预验证 —— 通常称为<ruby> 上线前检查 <rt>  pre-flight checks </rt></ruby> —— 有时会被归在持续集成之前）。

持续集成的目标是快速确保开发人员新提交的变更是好的，并且适合在代码库中进一步使用。

### 持续集成是如何工作的？

持续集成的基本思想是让一个自动化过程监测一个或多个源代码仓库是否有变更。当变更被推送到仓库时，它会监测到更改、下载副本、构建并运行任何相关的单元测试。

### 持续集成如何监测变更？

目前，监测程序通常是像 [Jenkins](https://jenkins.io) 这样的应用程序，它还协调管道中运行的所有（或大多数）进程，监视变更是其功能之一。监测程序可以以几种不同方式监测变更。这些包括：

* **轮询**：监测程序反复询问代码管理系统，“代码仓库里有什么我感兴趣的新东西吗？”当代码管理系统有新的变更时，监测程序会“唤醒”并完成其工作以获取新代码并构建/测试它。
* **定期**：监测程序配置为定期启动构建，无论源码是否有变更。理想情况下，如果没有变更，则不会构建任何新内容，因此这不会增加额外的成本。
* **推送**：这与用于代码管理系统检查的监测程序相反。在这种情况下，代码管理系统被配置为提交变更到仓库时将“推送”一个通知到监测程序。最常见的是，这可以以 webhook 的形式完成 —— 在新代码被推送时一个<ruby> 挂勾 <rt>  hook </rt></ruby>的程序通过互联网向监测程序发送通知。为此，监测程序必须具有可以通过网络接收 webhook 信息的开放端口。

### 什么是“预检查”（又称“上线前检查”）？

在将代码引入仓库并触发持续集成之前，可以进行其它验证。这遵循了最佳实践，例如<ruby> 测试构建 <rt>  test build </rt></ruby>和<ruby> 代码审查 <rt>  code review </rt></ruby>。它们通常在代码引入管道之前构建到开发过程中。但是一些管道也可能将它们作为其监控流程或工作流的一部分。

例如，一个名为 [Gerrit](https://www.gerritcodereview.com) 的工具允许在开发人员推送代码之后但在允许进入（[Git](https://opensource.com/resources/what-is-git) 远程）仓库之前进行正式的代码审查、验证和测试构建。Gerrit 位于开发人员的工作区和 Git 远程仓库之间。它会“接收”来自开发人员的推送，并且可以执行通过/失败验证以确保它们在被允许进入仓库之前的检查是通过的。这可以包括检测新变更并启动构建测试（CI 的一种形式）。它还允许开发者在那时进行正式的代码审查。这种方式有一种额外的可信度评估机制，即当变更的代码被合并到代码库中时不会破坏任何内容。

### 什么是“单元测试”？

单元测试（也称为“提交测试”），是由开发人员编写的小型的专项测试，以确保新代码独立工作。“独立”这里意味着不依赖或调用其它不可直接访问的代码，也不依赖外部数据源或其它模块。如果运行代码需要这样的依赖关系，那么这些资源可以用<ruby> 模拟 <rt>  mock </rt></ruby>来表示。模拟是指使用看起来像资源的<ruby> 代码存根 <rt>  code stub </rt></ruby>，可以返回值，但不实现任何功能。

在大多数组织中，开发人员负责创建单元测试以证明其代码正确。事实上，一种称为<ruby> 测试驱动开发 <rt>  test-driven develop </rt></ruby>（TDD）的模型要求将首先设计单元测试作为清楚地验证代码功能的基础。因为这样的代码可以更改速度快且改动量大，所以它们也必须执行很快。

由于这与持续集成工作流有关，因此开发人员在本地工作环境中编写或更新代码，并通单元测试来确保新开发的功能或方法正确。通常，这些测试采用断言形式，即函数或方法的给定输入集产生给定的输出集。它们通常进行测试以确保正确标记和处理出错条件。有很多单元测试框架都很有用，例如用于 Java 开发的 [JUnit](https://junit.org/junit5/)。

### 什么是“持续测试”？

持续测试是指在代码通过持续交付管道时运行扩展范围的自动化测试的实践。单元测试通常与构建过程集成，作为持续集成阶段的一部分，并专注于和其它与之交互的代码隔离的测试。

除此之外，可以有或者应该有各种形式的测试。这些可包括：

* **集成测试** 验证组件和服务组合在一起是否正常。
* **功能测试** 验证产品中执行功能的结果是否符合预期。
* **验收测试** 根据可接受的标准验证产品的某些特征。如性能、可伸缩性、抗压能力和容量。

所有这些可能不存在于自动化的管道中，并且一些不同类型的测试分类界限也不是很清晰。但是，在交付管道中持续测试的目标始终是相同的：通过持续的测试级别证明代码的质量可以在正在进行的发布中使用。在持续集成快速的原则基础上，第二个目标是快速发现问题并提醒开发团队。这通常被称为快速失败。

### 除了测试之外，还可以对管道中的代码进行哪些其它类型的验证？

除了测试是否通过之外，还有一些应用程序可以告诉我们测试用例执行（覆盖）的源代码行数。这是一个可以衡量代码量指标的例子。这个指标称为<ruby> 代码覆盖率 <rt>  code-coverage </rt></ruby>，可以通过工具（例如用于 Java 的 [JaCoCo](https://www.eclemma.org/jacoco/)）进行统计。

还有很多其它类型的指标统计，例如代码行数、复杂度以及代码结构对比分析等。诸如 [SonarQube](https://www.sonarqube.org/) 之类的工具可以检查源代码并计算这些指标。此外，用户还可以为他们可接受的“合格”范围的指标设置阈值。然后可以在管道中针对这些阈值设置一个检查，如果结果不在可接受范围内，则流程终端上。SonarQube 等应用程序具有很高的可配置性，可以设置仅检查团队感兴趣的内容。

### 什么是“持续交付”？

持续交付（CD）通常是指整个流程链（管道），它自动监测源代码变更并通过构建、测试、打包和相关操作运行它们以生成可部署的版本，基本上没有任何人为干预。

持续交付在软件开发过程中的目标是自动化、效率、可靠性、可重复性和质量保障（通过持续测试）。

持续交付包含持续集成（自动检测源代码变更、执行构建过程、运行单元测试以验证变更），持续测试（对代码运行各种测试以保障代码质量），和（可选）持续部署（通过管道发布版本自动提供给用户）。

### 如何在管道中识别/跟踪多个版本？

版本控制是持续交付和管道的关键概念。持续意味着能够经常集成新代码并提供更新版本。但这并不意味着每个人都想要“最新、最好的”。对于想要开发或测试已知的稳定版本的内部团队来说尤其如此。因此，管道创建并轻松存储和访问的这些版本化对象非常重要。

在管道中从源代码创建的对象通常可以称为<ruby> 工件 <rt>  artifact </rt></ruby>。工件在构建时应该有应用于它们的版本。将版本号分配给工件的推荐策略称为<ruby> 语义化版本控制 <rt>  semantic versioning </rt></ruby>。（这也适用于从外部源引入的依赖工件的版本。）

语义版本号有三个部分：<ruby> 主要版本 <rt>  major </rt></ruby>、<ruby> 次要版本 <rt>  minor </rt></ruby> 和 <ruby> 补丁版本 <rt>  patch </rt></ruby>。（例如，1.4.3 反映了主要版本 1，次要版本 4 和补丁版本 3。）这个想法是，其中一个部分的更改表示工件中的更新级别。主要版本仅针对不兼容的 API 更改而递增。当以<ruby> 向后兼容 <rt>  backward-compatible </rt></ruby>的方式添加功能时，次要版本会增加。当进行向后兼容的版本 bug 修复时，补丁版本会增加。这些是建议的指导方针，但只要团队在整个组织内以一致且易于理解的方式这样做，团队就可以自由地改变这种方法。例如，每次为发布完成构建时增加的数字可以放在补丁字段中。

### 如何“分销”工件？

团队可以为工件分配<ruby> 分销 <rt>  promotion </rt></ruby>级别以指示适用于测试、生产等环境或用途。有很多方法。可以用 Jenkins 或 [Artifactory](https://jfrog.com/artifactory/) 等应用程序进行分销。或者一个简单的方案可以在版本号字符串的末尾添加标签。例如，`-snapshot` 可以指示用于构建工件的代码的最新版本（快照）。可以使用各种分销策略或工具将工件“提升”到其它级别，例如 `-milestone` 或 `-production`，作为工件稳定性和完备性版本的标记。

### 如何存储和访问多个工件版本？

从源代码构建的版本化工件可以通过管理<ruby> 工件仓库 <rt>  artifact repository </rt></ruby>的应用程序进行存储。工件仓库就像构建工件的版本控制工具一样。像 Artifactory 或 [Nexus](https://www.sonatype.com/nexus-repository-sonatype) 这类应用可以接受版本化工件，存储和跟踪它们，并提供检索的方法。

管道用户可以指定他们想要使用的版本，并在这些版本中使用管道。

### 什么是“持续部署”？

持续部署（CD）是指能够自动提供持续交付管道中发布版本给最终用户使用的想法。根据用户的安装方式，可能是在云环境中自动部署、app 升级（如手机上的应用程序）、更新网站或只更新可用版本列表。

这里的一个重点是，仅仅因为可以进行持续部署并不意味着始终部署来自管道的每组可交付成果。它实际上指，通过管道每套可交付成果都被证明是“可部署的”。这在很大程度上是由持续测试的连续级别完成的（参见本文中的持续测试部分）。

管道构建的发布成果是否被部署可以通过人工决策，或利用在完全部署之前“试用”发布的各种方法来进行控制。

### 在完全部署到所有用户之前，有哪些方法可以测试部署？

由于必须回滚/撤消对所有用户的部署可能是一种代价高昂的情况（无论是技术上还是用户的感知），已经有许多技术允许“尝试”部署新功能并在发现问题时轻松“撤消”它们。这些包括：

#### 蓝/绿测试/部署

在这种部署软件的方法中，维护了两个相同的主机环境 —— 一个“蓝色” 和一个“绿色”。（颜色并不重要，仅作为标识。）对应来说，其中一个是“生产环境”，另一个是“预发布环境”。

在这些实例的前面是调度系统，它们充当产品或应用程序的客户“网关”。通过将调度系统指向蓝色或绿色实例，可以将客户流量引流到期望的部署环境。通过这种方式，切换指向哪个部署实例（蓝色或绿色）对用户来说是快速，简单和透明的。

当新版本准备好进行测试时，可以将其部署到非生产环境中。在经过测试和批准后，可以更改调度系统设置以将传入的线上流量指向它（因此它将成为新的生产站点）。现在，曾作为生产环境实例可供下一次候选发布使用。

同理，如果在最新部署中发现问题并且之前的生产实例仍然可用，则简单的更改可以将客户流量引流回到之前的生产实例 —— 有效地将问题实例“下线”并且回滚到以前的版本。然后有问题的新实例可以在其它区域中修复。

#### 金丝雀测试/部署

在某些情况下，通过蓝/绿发布切换整个部署可能不可行或不是期望的那样。另一种方法是为<ruby> 金丝雀 <rt>  canary </rt></ruby>测试/部署。在这种模型中，一部分客户流量被重新引流到新的版本部署中。例如，新版本的搜索服务可以与当前服务的生产版本一起部署。然后，可以将 10％ 的搜索查询引流到新版本，以在生产环境中对其进行测试。

如果服务那些流量的新版本没问题，那么可能会有更多的流量会被逐渐引流过去。如果仍然没有问题出现，那么随着时间的推移，可以对新版本增量部署，直到 100％ 的流量都调度到新版本。这有效地“更替”了以前版本的服务，并让新版本对所有客户生效。

#### 功能开关

对于可能需要轻松关掉的新功能（如果发现问题），开发人员可以添加<ruby> 功能开关 <rt>  feature toggles </rt></ruby>。这是代码中的 `if-then` 软件功能开关，仅在设置数据值时才激活新代码。此数据值可以是全局可访问的位置，部署的应用程序将检查该位置是否应执行新代码。如果设置了数据值，则执行代码；如果没有，则不执行。

这为开发人员提供了一个远程“终止开关”，以便在部署到生产环境后发现问题时关闭新功能。

#### 暗箱发布

在<ruby> 暗箱发布 <rt>  dark launch </rt></ruby>中，代码被逐步测试/部署到生产环境中，但是用户不会看到更改（因此名称中有<ruby> 暗箱 <rt>  dark </rt></ruby>一词）。例如，在生产版本中，网页查询的某些部分可能会重定向到查询新数据源的服务。开发人员可收集此信息进行分析，而不会将有关接口，事务或结果的任何信息暴露给用户。

这个想法是想获取候选版本在生产环境负载下如何执行的真实信息，而不会影响用户或改变他们的经验。随着时间的推移，可以调度更多负载，直到遇到问题或认为新功能已准备好供所有人使用。实际上功能开关标志可用于这种暗箱发布机制。

### 什么是“运维开发”？

<ruby> <a href="https://opensource.com/resources/devops">  运维开发 </a> <rt>  DevOps </rt></ruby> 是关于如何使开发和运维团队更容易合作开发和发布软件的一系列想法和推荐的实践。从历史上看，开发团队研发了产品，但没有像客户那样以常规、可重复的方式安装/部署它们。在整个周期中，这组安装/部署任务（以及其它支持任务）留给运维团队负责。这经常导致很多混乱和问题，因为运维团队在后期才开始介入，并且必须在短时间内完成他们的工作。同样，开发团队经常处于不利地位 —— 因为他们没有充分测试产品的安装/部署功能，他们可能会对该过程中出现的问题感到惊讶。

这往往导致开发和运维团队之间严重脱节和缺乏合作。DevOps 理念主张是贯穿整个开发周期的开发和运维综合协作的工作方式，就像持续交付那样。

### 持续交付如何与运维开发相交？

持续交付管道是几个 DevOps 理念的实现。产品开发的后期阶段（如打包和部署）始终可以在管道的每次运行中完成，而不是等待产品开发周期中的特定时间。同样，从开发到部署过程中，开发和运维都可以清楚地看到事情何时起作用，何时不起作用。要使持续交付管道循环成功，不仅要通过与开发相关的流程，还要通过与运维相关的流程。

说得更远一些，DevOps 建议实现管道的基础架构也会被视为代码。也就是说，它应该自动配置、可跟踪、易于修改，并在管道发生变化时触发新一轮运行。这可以通过将管道实现为代码来完成。

### 什么是“管道即代码”？

<ruby> 管道即代码 <rt>  pipeline-as-code </rt></ruby>是通过编写代码创建管道作业/任务的通用术语，就像开发人员编写代码一样。它的目标是将管道实现表示为代码，以便它可以与代码一起存储、评审、跟踪，如果出现问题并且必须终止管道，则可以轻松地重建。有几个工具允许这样做，如 [Jenkins 2](https://jenkins.io)。

### DevOps 如何影响生产软件的基础设施？

传统意义上，管道中使用的各个硬件系统都有配套的软件（操作系统、应用程序、开发工具等）。在极端情况下，每个系统都是手工设置来定制的。这意味着当系统出现问题或需要更新时，这通常也是一项自定义任务。这种方法违背了持续交付的基本理念，即具有易于重现和可跟踪的环境。

多年来，很多应用被开发用于标准化交付（安装和配置）系统。同样，<ruby> 虚拟机 <rt>  virtual machine </rt></ruby>被开发为模拟在其它计算机之上运行的计算机程序。这些 VM 要有管理程序才能在底层主机系统上运行，并且它们需要自己的操作系统副本才能运行。

后来有了<ruby> 容器 <rt>  container </rt></ruby>。容器虽然在概念上与 VM 类似，但工作方式不同。它们只需使用一些现有的操作系统结构来划分隔离空间，而不需要运行单独的程序和操作系统的副本。因此，它们的行为类似于 VM 以提供隔离但不需要过多的开销。

VM 和容器是根据配置定义创建的，因此可以轻易地销毁和重建，而不会影响运行它们的主机系统。这允许运行管道的系统也可重建。此外，对于容器，我们可以跟踪其构建定义文件的更改 —— 就像对源代码一样。

因此，如果遇到 VM 或容器中的问题，我们可以更容易、更快速地销毁和重建它们，而不是在当前环境尝试调试和修复。

这也意味着对管道代码的任何更改都可以触发管道新一轮运行（通过 CI），就像对代码的更改一样。这是 DevOps 关于基础架构的核心理念之一。

---

via: <https://opensource.com/article/18/8/what-cicd>

作者：[Brent Laster](https://opensource.com/users/bclaster) 选题：[lujun9972](https://github.com/lujun9972) 译者：[pityonline](https://github.com/pityonline) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


使用 Kafka 和 MongoDB 进行 Go 异步处理

![](/data/attachment/album/201808/17/003015f11bgzwcfcbmb1i9.png)

在我前面的博客文章 “[我的第一个 Go 微服务：使用 MongoDB 和 Docker 多阶段构建](https://www.melvinvivas.com/my-first-go-microservice/)” 中，我创建了一个 Go 微服务示例，它发布一个 REST 式的 http 端点，并将从 HTTP POST 中接收到的数据保存到 MongoDB 数据库。

在这个示例中，我将数据的保存和 MongoDB 分离，并创建另一个微服务去处理它。我还添加了 Kafka 为消息层服务，这样微服务就可以异步处理它自己关心的东西了。

>
> 如果你有时间去看，我将这个博客文章的整个过程录制到 [这个视频中了](https://youtu.be/xa0Yia1jdu8) :)
>
>
>

下面是这个使用了两个微服务的简单的异步处理示例的上层架构图。

![rest-kafka-mongo-microservice-draw-io](/data/attachment/album/201808/17/003027x0suw8drsu8mefoa.jpg)

微服务 1 —— 是一个 REST 式微服务，它从一个 /POST http 调用中接收数据。接收到请求之后，它从 http 请求中检索数据，并将它保存到 Kafka。保存之后，它通过 /POST 发送相同的数据去响应调用者。

微服务 2 —— 是一个订阅了 Kafka 中的一个主题的微服务，微服务 1 的数据保存在该主题。一旦消息被微服务消费之后，它接着保存数据到 MongoDB 中。

在你继续之前，我们需要能够去运行这些微服务的几件东西：

1. [下载 Kafka](https://kafka.apache.org/downloads) —— 我使用的版本是 kafka\_2.11-1.1.0
2. 安装 [librdkafka](https://github.com/confluentinc/confluent-kafka-go) —— 不幸的是，这个库应该在目标系统中
3. 安装 [Kafka Go 客户端](https://github.com/confluentinc/confluent-kafka-go)
4. 运行 MongoDB。你可以去看我的 [以前的文章](https://www.melvinvivas.com/my-first-go-microservice/) 中关于这一块的内容，那篇文章中我使用了一个 MongoDB docker 镜像。

我们开始吧！

首先，启动 Kafka，在你运行 Kafka 服务器之前，你需要运行 Zookeeper。下面是示例：

```
$ cd /<download path>/kafka_2.11-1.1.0
$ bin/zookeeper-server-start.sh config/zookeeper.properties

```

接着运行 Kafka —— 我使用 9092 端口连接到 Kafka。如果你需要改变端口，只需要在 `config/server.properties` 中配置即可。如果你像我一样是个新手，我建议你现在还是使用默认端口。

```
$ bin/kafka-server-start.sh config/server.properties

```

Kafka 跑起来之后，我们需要 MongoDB。它很简单，只需要使用这个 `docker-compose.yml` 即可。

```
version: '3'
services:
  mongodb:
    image: mongo
    ports:
      - "27017:27017"
    volumes:
      - "mongodata:/data/db"
    networks:
      - network1

volumes:
   mongodata:

networks:
   network1:

```

使用 Docker Compose 去运行 MongoDB docker 容器。

```
docker-compose up

```

这里是微服务 1 的相关代码。我只是修改了我前面的示例去保存到 Kafka 而不是 MongoDB：

[rest-to-kafka/rest-kafka-sample.go](https://github.com/donvito/learngo/tree/master/rest-kafka-mongo-microservice/rest-to-kafka)

```
func jobsPostHandler(w http.ResponseWriter, r *http.Request) {

    //Retrieve body from http request
    b, err := ioutil.ReadAll(r.Body)
    defer r.Body.Close()
    if err != nil {
        panic(err)
    }

    //Save data into Job struct
    var _job Job
    err = json.Unmarshal(b, &_job)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    saveJobToKafka(_job)

    //Convert job struct into json
    jsonString, err := json.Marshal(_job)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }

    //Set content-type http header
    w.Header().Set("content-type", "application/json")

    //Send back data as response
    w.Write(jsonString)

}

func saveJobToKafka(job Job) {

    fmt.Println("save to kafka")

    jsonString, err := json.Marshal(job)

    jobString := string(jsonString)
    fmt.Print(jobString)

    p, err := kafka.NewProducer(&kafka.ConfigMap{"bootstrap.servers": "localhost:9092"})
    if err != nil {
        panic(err)
    }

    // Produce messages to topic (asynchronously)
    topic := "jobs-topic1"
    for _, word := range []string{string(jobString)} {
        p.Produce(&kafka.Message{
            TopicPartition: kafka.TopicPartition{Topic: &topic, Partition: kafka.PartitionAny},
            Value:          []byte(word),
        }, nil)
    }
}

```

这里是微服务 2 的代码。在这个代码中最重要的东西是从 Kafka 中消费数据，保存部分我已经在前面的博客文章中讨论过了。这里代码的重点部分是从 Kafka 中消费数据：

[kafka-to-mongo/kafka-mongo-sample.go](https://github.com/donvito/learngo/tree/master/rest-kafka-mongo-microservice/kafka-to-mongo)

```
func main() {

    //Create MongoDB session
    session := initialiseMongo()
    mongoStore.session = session

    receiveFromKafka()

}

func receiveFromKafka() {

    fmt.Println("Start receiving from Kafka")
    c, err := kafka.NewConsumer(&kafka.ConfigMap{
        "bootstrap.servers": "localhost:9092",
        "group.id":          "group-id-1",
        "auto.offset.reset": "earliest",
    })

    if err != nil {
        panic(err)
    }

    c.SubscribeTopics([]string{"jobs-topic1"}, nil)

    for {
        msg, err := c.ReadMessage(-1)

        if err == nil {
            fmt.Printf("Received from Kafka %s: %s\n", msg.TopicPartition, string(msg.Value))
            job := string(msg.Value)
            saveJobToMongo(job)
        } else {
            fmt.Printf("Consumer error: %v (%v)\n", err, msg)
            break
        }
    }

    c.Close()

}

func saveJobToMongo(jobString string) {

    fmt.Println("Save to MongoDB")
    col := mongoStore.session.DB(database).C(collection)

    //Save data into Job struct
    var _job Job
    b := []byte(jobString)
    err := json.Unmarshal(b, &_job)
    if err != nil {
        panic(err)
    }

    //Insert job into MongoDB
    errMongo := col.Insert(_job)
    if errMongo != nil {
        panic(errMongo)
    }

    fmt.Printf("Saved to MongoDB : %s", jobString)

}

```

我们来演示一下，运行微服务 1。确保 Kafka 已经运行了。

```
$ go run rest-kafka-sample.go

```

我使用 Postman 向微服务 1 发送数据。

![Screenshot-2018-04-29-22.20.33](/data/attachment/album/201808/17/003028mn1rtexr0rwer1z1.png)

这里是日志，你可以在微服务 1 中看到。当你看到这些的时候，说明已经接收到了来自 Postman 发送的数据，并且已经保存到了 Kafka。

![Screenshot-2018-04-29-22.22.00](/data/attachment/album/201808/17/003029yl56iivizi5v5toy.png)

因为我们尚未运行微服务 2，数据被微服务 1 只保存在了 Kafka。我们来消费它并通过运行的微服务 2 来将它保存到 MongoDB。

```
$ go run kafka-mongo-sample.go

```

现在，你将在微服务 2 上看到消费的数据，并将它保存到了 MongoDB。

![Screenshot-2018-04-29-22.24.15](/data/attachment/album/201808/17/003030irn5ddzkk2rd05d1.png)

检查一下数据是否保存到了 MongoDB。如果有数据，我们成功了！

![Screenshot-2018-04-29-22.26.39](/data/attachment/album/201808/17/003030xuajm6iijt9jtuja.png)

完整的源代码可以在这里找到：

<https://github.com/donvito/learngo/tree/master/rest-kafka-mongo-microservice>

现在是广告时间：如果你喜欢这篇文章，请在 Twitter [@donvito](https://twitter.com/donvito) 上关注我。我的 Twitter 上有关于 Docker、Kubernetes、GoLang、Cloud、DevOps、Agile 和 Startups 的内容。欢迎你们在 [GitHub](https://github.com/donvito) 和 [LinkedIn](https://www.linkedin.com/in/melvinvivas/) 关注我。

开心地玩吧！

---

via: <https://www.melvinvivas.com/developing-microservices-using-kafka-and-mongodb/>

作者：[Melvin Vivas](https://www.melvinvivas.com/author/melvin/)  译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


UNIX 的怪东西

![](/data/attachment/album/201808/17/170055yst2s94aslim9hbe.jpg)

最近我在用我编写的各种工具做更多 UNIX 下的事情，我遇到了两个有趣的问题。这些都不是 “bug”，而是我没想到的行为。

### 线程安全的 printf

我有一个 C 程序从磁盘读取一些图像，进行一些处理，并将有关这些图像的输出写入 STDOUT。伪代码：

```
for(imagefilename in images)
{
    results = process(imagefilename);
    printf(results);
}

```

对于每个图像都是独立处理的，因此我自然希望将处理任务分配在各个 CPU 之间以加快速度。我通常使用 `fork()`，所以我写了这个：

```
for(child in children)
{
    pipe = create_pipe();
    worker(pipe);
}

// main parent process
for(imagefilename in images)
{
    write(pipe[i_image % N_children], imagefilename)
}

worker()
{
    while(1)
    {
        imagefilename = read(pipe);
        results = process(imagefilename);
        printf(results);
    }
}

```

这是正常的做法：我为 IPC 创建管道，并通过这些管道给子进程 worker 发送图像名。每个 worker *能够*通过另一组管道将其结果写回主进程，但这很痛苦，所以每个 worker 都直接写入共享 STDOUT。这工作正常，但正如人们所预料的那样，对 STDOUT 的写入发生冲突，因此各种图像的结果最终会混杂在一起。那很糟糕。我不想自己设置个锁，但幸运的是 GNU libc 为它提供了函数：[flockfile()](https://www.gnu.org/software/libc/manual/html_node/Streams-and-Threads.html)。我把它们放进去了……但是没有用！为什么？因为 `flockfile()` 最终因为 `fork()` 的写时复制行为而被限制在单个子进程中。即 `fork()`提供的额外安全性（与线程相比），这实际上最终破坏了锁。

我没有尝试使用其他锁机制（例如 pthread 互斥锁），但我可以想象它们会遇到类似的问题。我想保持简单，所以将输出发送回父输出是不可能的：这给程序员和运行程序的计算机制造了更多的工作。

解决方案：使用线程而不是 `fork()`。这有制造冗余管道的好的副作用。最终的伪代码：

```
for(children)
{
    pthread_create(worker, child_index);
}
for(children)
{
    pthread_join(child);
}

worker(child_index)
{
    for(i_image = child_index; i_image < N_images; i_image += N_children)
    {
        results = process(images[i_image]);
        flockfile(stdout);
        printf(results);
        funlockfile(stdout);
    }
}

```

这更简单，如预期的那样工作。我猜有时线程更好。

### 将部分读取的文件传递给子进程

对于各种 [vnlog](http://www.github.com/dkogan/vnlog) 工具，我需要实现这个操作序列：

1. 进程打开一个关闭 `O_CLOEXEC` 标志的文件
2. 进程读取此文件的一部分（在 vnlog 的情况下直到图例的末尾）
3. 进程调用 `exec()` 以调用另一个程序来处理已经打开的文件的其余部分

第二个程序可能需要命令行中的文件名而不是已打开的文件描述符，因为第二个程序可能自己调用 `open()`。如果我传递文件名，这个新程序将重新打开文件，然后从头开始读取文件，而不是从原始程序停止的位置开始读取。在我的程序上不可以这样做，因此将文件名传递给第二个程序是行不通的。

所以我真的需要以某种方式传递已经打开的文件描述符。我在使用 Linux（其他操作系统可能在这里表现不同），所以我理论上可以通过传递 `/dev/fd/N` 而不是文件名来实现。但事实证明这也不起作用。在 Linux上（再说一次，也许是特定于 Linux）对于普通文件 `/dev/fd/N` 是原始文件的符号链接。所以这最终做的是与传递文件名完全相同的事情。

但有一个临时方案！如果我们正在读取管道而不是文件，那么没有什么可以符号链接，并且 `/dev/fd/N` 最终将原始管道传递给第二个进程，然后程序正常工作。我可以通过将上面的 `open("filename")` 更改为 `popen("cat filename")` 之类的东西来伪装。呸！这真的是我们所能做到最好的吗？这在 BSD 上看上去会怎么样？

---

via: <http://notes.secretsauce.net/notes/2018/08/03_unix-curiosities.html>

作者：[Dima Kogan](http://notes.secretsauce.net/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


ArchI0：Arch Linux 应用自动安装脚本

Summary: 使用此脚本是为基于 Arch 的发行版安装所有必要的应用最简易的方式。请注意此脚本仅适用于菜鸟级使用者。

![](/data/attachment/album/201808/17/231711lrsyevxfev852sfi.png)

Arch 用户你们好！今天，我偶然发现了一个叫做 “**ArchI0**” 的实用工具，它是基于命令行菜单的 Arch Linux 应用自动安装脚本。使用此脚本是为基于 Arch 的发行版安装所有必要的应用最简易的方式。请注意**此脚本仅适用于菜鸟级使用者**。中高级使用者可以轻松掌握[如何使用 pacman](http://www.ostechnix.com/getting-started-pacman/)来完成这件事。如果你想学习如何使用 Arch Linux，我建议你一个个手动安装所有的软件。对那些仍是菜鸟并且希望为自己基于 Arch 的系统快速安装所有必要应用的用户，可以使用此脚本。

### ArchI0 – Arch Linux 应用自动安装脚本

此脚本的开发者已经制作了 **ArchI0live** 和 **ArchI0** 两个脚本。你可以通过 ArchI0live 测试应用，无需安装。这可能有助于在将脚本安装到系统之前了解其实际内容。

### 安装 ArchI0

要安装此脚本，使用如下命令通过 Git 克隆 ArchI0 脚本仓库：

```
$ git clone https://github.com/SifoHamlaoui/ArchI0.git

```

上面的命令会克隆 ArchI0 的 Github 仓库内容，在你当前目录的一个名为 ArchI0 的文件夹里。使用如下命令进入此目录：

```
$ cd ArchI0/

```

使用如下命令赋予脚本可执行权限：

```
$ chmod +x ArchI0live.sh

```

使用如下命令执行脚本：

```
$ sudo ./ArchI0live.sh

```

此脚本需要以 root 或 sudo 用户身份执行，因为安装应用需要 root 权限。

>
> **注意：** 有些人想知道此脚本中开头的那些命令是做什么的，第一个命令是下载 **figlet**，因为此脚本的 logo 是使用 figlet 显示的。第二个命令是安装用来打开并查看许可协议文件的 **Leafpad**。第三个命令是安装用于从 sourceforge 下载文件的 **wget**。第四和第五个命令是下载许可协议文件并用 leafpad 打开。此外，最后的第 6 条命令是在阅读许可协议文件之后关闭它。
>
>
>

输入你的 Arch Linux 系统架构然后按回车键。当其请求安装此脚本时，键入 `y` 然后按回车键。

![](/data/attachment/album/201808/17/231713dhn6nmrm6mttmnit.png)

一旦开始安装，将会重定向至主菜单。

![](/data/attachment/album/201808/17/231714u9zg7sso7xliug92.png)

正如前面的截图， ArchI0 包含有 13 类、90 个容易安装的程序。这 90 个程序刚好足够配置一个完整的 Arch Linux 桌面，可执行日常活动。键入 `a` 可查看关于此脚本的信息，键入 `q` 可退出此脚本。

安装后无需执行 ArchI0live 脚本。可以直接使用如下命令启动：

```
$ sudo ArchI0

```

它会每次询问你选择 Arch Linux 发行版的架构。

```
This script Is under GPLv3 License

Preparing To Run Script
 Checking For ROOT: PASSED
 What Is Your OS Architecture? {32/64} 64

```

从现在开始，你可以从主菜单列出的类别选择要安装的程序。要查看特定类别下的可用程序列表，输入类别号即可。举个例子，要查看**文本编辑器**分类下的可用程序列表，输入 **1** 然后按回车键。

```
This script Is under GPLv3 License

[ R00T MENU ]
Make A Choice
 1) Text Editors
 2) FTP/Torrent Applications
 3) Download Managers
 4) Network managers
 5) VPN clients
 6) Chat Applications
 7) Image Editors
 8) Video editors/Record
 9) Archive Handlers
 10) Audio Applications
 11) Other Applications
 12) Development Environments
 13) Browser/Web Plugins
 14) Dotfiles
 15) Usefull Links
 ------------------------
 a) About ArchI0 Script
 q) Leave ArchI0 Script

Choose An Option: 1

```

接下来，选择你想安装的程序。要返回至主菜单，输入 `q` 然后按回车键。

我想安装 Emacs，所以我输入 `3`。

```
This script Is under GPLv3 License

[ TEXT EDITORS ]
 [ Option ] [ Description ]
 1) GEdit
 2) Geany
 3) Emacs
 4) VIM
 5) Kate
 ---------------------------
 q) Return To Main Menu

Choose An Option: 3

```

现在，Emacs 将会安装至你的 Arch Linux 系统。

![](/data/attachment/album/201808/17/231715domlz0v0tj5og7vz.png)

所选择的应用安装完成后，你可以按回车键返回主菜单。

### 结论

毫无疑问，此脚本让 Arch Linux 用户使用起来更加容易，特别是刚开始使用的人。如果你正寻找快速简单无需使用 pacman 安装应用的方法，此脚本是一个不错的选择。试用一下并在下面的评论区让我们知道你对此脚本的看法。

就这些。希望这个工具能帮到你。我们每天都会推送实用的指南。如果你觉得我们的指南挺实用，请分享至你的社交网络，专业圈子并支持我们。

干杯！

---

via: <https://www.ostechnix.com/archi0-arch-linux-applications-automatic-installation-script/>

作者：[SK](https://www.ostechnix.com/author/sk/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[fuowang](https://github.com/fuowang) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


极客漫画：AI 监管

![](/data/attachment/album/201808/18/153221lbm9awqy88qj6yw2.png)

台上某疑似“钢铁侠”正在宣讲“AI 监管”，遭到了群嘲——怒极之下，却暴露出了其原本是一个“AI”。

---

via: <http://turnoff.us/geek/ai-regulation>

作者：[Daniel Stori](http://turnoff.us/about/) 译者：[wxy](https://github.com/wxy) 合成：[Bestony](https://github.com/Bestony)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


包管理器的进化

>
> 包管理器在 Linux 软件管理中扮演了重要角色。这里对一些主要的包管理器进行了对比。
>
>
>

![](/data/attachment/album/201808/18/160231qg3on8zok6nh4gh6.jpg)

今天，每个可计算设备都会使用某种软件来完成预定的任务。在软件开发的上古时期，为了找出软件中的“虫”和其它缺陷，软件会被严格的测试。在近十年间，软件被通过互联网来频繁分发，以试图通过持续不断的安装新版本的软件来解决软件的缺陷问题。在很多情况下，每个独立的应用软件都有其自带的更新器。而其它一些软件则让用户自己去搞明白如何获取和升级软件。

Linux 较早采用了维护一个中心化的软件仓库来发布软件更新这种做法，用户可以在这个软件仓库里查找并安装软件。在这篇文章里， 笔者将回顾在 Linux 上的如何进行软件安装的历史，以及现代操作系统如何保持更新以应对[软件安全漏洞（CVE）](https://en.wikipedia.org/wiki/Common_Vulnerabilities_and_Exposures)不断的曝光。

### 那么在包管理器出现之前在 Linux 上是如何安装软件的呢？

曾几何时，软件都是通过 FTP 或邮件列表（LCTT 译注：即通过邮件列表发布源代码的补丁包）来分发的（最终这些发布方式在互联网的迅猛发展下都演化成为一个个现今常见的软件发布网站）。（一般在一个 tar 文件中）只有一个非常小的文件包含了创建二进制的说明。你需要做的是先解压这个包，然后仔细阅读当中的 `README` 文件， 如果你的系统上恰好有 GCC（LCTT 译注：GNU C Compiler）或者其它厂商的 C 编译器的话，你得首先运行 `./configure` 脚本，并在脚本后添加相应的参数，如库函数的路径、创建可执行文件的路径等等。除此之外，这个配置过程也会检查你操作系统上的软件依赖是否满足安装要求。如果缺失了任何主要的依赖，该配置脚本会退出不再继续安装，直到你满足了该依赖。如果该配置脚本正常执行完毕，将会创建一个 `Makefile` 文件。

当有了一个 `Makefile` 文件时， 你就可以接下去执行 `make` 命令（该命令由你所使用的编译器提供）。`make` 命令也有很多参数，被称为 `make` <ruby> 标识 <rt>  flag </rt></ruby>，这些标识能为你的系统优化最终生成出来的二进制可执行文件。在计算机世界的早期，这些优化是非常重要的，因为彼时的计算机硬件正在为了跟上软件迅速的发展而疲于奔命。今日今时，编译标识变得更加通用而不是为了优化哪些具体的硬件型号，这得益于现代硬件和现代软件相比已经变得成本低廉，唾手可得。

最后，在 `make` 完成之后， 你需要运行 `make install` （或 `sudo make install`）（LCTT 译注：依赖于你的用户权限） 来“真正”将这个软件安装到你的系统上。可以想象，为你系统上的每一个软件都执行上述的流程将是多么无聊费时，更不用说如果更新一个已经安装的软件将会多复杂，多么需要精力投入。（LCTT 译注：上述流程也称 CMMI 安装， 即Configure、Make、Make Install）

### 那么软件包是什么？

<ruby> 软件包 <rt>  package </rt></ruby>（LCTT 译注：下文简称“包”）这个概念是用来解决在软件安装、升级过程中的复杂性的。包将软件安装升级中需要的多个数据文件合并成一个单独的文件，这将便于传输和（通过压缩文件来）减小存储空间（LCTT 译注：减少存储空间这一点在现在已经不再重要），包中的二进制可执行文件已根据开发者所选择的编译标识预编译。包本身包括了所有需要的元数据，如软件的名字、软件的说明、版本号，以及要运行这个软件所需要的依赖包等等。

不同流派的 Linux 发行版都创造了它们自己的包格式，其中最常用的包格式有：

* .deb：这种包格式由 Debian、Ubuntu、Linux Mint 以及其它的变种使用。这是最早被发明的包类型。
* .rpm：这种包格式最初被称作<ruby> 红帽包管理器 <rt>  Red Hat Package Manager </rt></ruby>（LCTT 译注： 取自英文的首字母）。使用这种包的 Linux 发行版有 Red Hat、Fedora、SUSE 以及其它一些较小的发行版。
* .tar.xz：这种包格式只是一个软件压缩包而已，这是 Arch Linux 所使用的格式。

尽管上述的包格式自身并不能直接管理软件的依赖问题，但是它们的出现将 Linux 软件包管理向前推进了一大步。

### 软件仓库到底是什么？

多年以前（当智能电话还没有像现在这样流行时），非 Linux 世界的用户是很难理解软件仓库的概念的。甚至今时今日，大多数完全工作在 Windows 下的用户还是习惯于打开浏览器，搜索要安装的软件（或升级包），下载然后安装。但是，智能电话传播了软件“商店”（LCTT 译注： 对应 Linux 里的软件仓库）这样一个概念。智能电话用户获取软件的方式和包管理器的工作方式已经非常相近了。些许不同的是，尽管大多数软件商店还在费力美化它的图形界面来吸引用户，大多数 Linux 用户还是愿意使用命令行来安装软件。总而言之，软件仓库是一个中心化的可安装软件列表，上面列举了在当前系统中预先配置好的软件仓库里所有可以安装的软件。下面我们举一些例子来说在各个不同的 Linux 发行版下如何在对应的软件仓库里搜寻某个特定的软件（输出有截断）。

在 Arch Linux 下使用 `aurman`：

```
user@arch ~ $  aurman -Ss kate

extra/kate 18.04.2-2 (kde-applications kdebase)
    Advanced Text Editor
aur/kate-root 18.04.0-1 (11, 1.139399)
    Advanced Text Editor, patched to be able to run as root
aur/kate-git r15288.15d26a7-1 (1, 1e-06)
    An advanced editor component which is used in numerous KDE applications requiring a text editing component

```

在 CentOS 7 下使用 `yum`：

```
[user@centos ~]$ yum search kate

kate-devel.x86_64 : Development files for kate
kate-libs.x86_64 : Runtime files for kate
kate-part.x86_64 : Kate kpart plugin

```

在 Ubuntu 下使用 `apt`：

```
user@ubuntu ~ $ apt search kate
Sorting... Done
Full Text Search... Done

kate/xenial 4:15.12.3-0ubuntu2 amd64
  powerful text editor

kate-data/xenial,xenial 4:4.14.3-0ubuntu4 all
  shared data files for Kate text editor

kate-dbg/xenial 4:15.12.3-0ubuntu2 amd64
  debugging symbols for Kate

kate5-data/xenial,xenial 4:15.12.3-0ubuntu2 all
  shared data files for Kate text editor

```

### 最好用的包管理器有哪些？

如上示例的输出，包管理器用来和相应的软件仓库交互，获取软件的相应信息。下面对它们做一个简短介绍。

#### 基于 PRM 包格式的包管理器

更新基于 RPM 的系统，特别是那些基于 Red Hat 技术的系统，有着非常有趣而又详实的历史。实际上，现在的 [YUM](https://en.wikipedia.org/wiki/Yum_(software)) 版本（用于 企业级发行版）和 [DNF](https://fedoraproject.org/wiki/DNF)（用于社区版）就融合了好几个开源项目来提供它们现在的功能。

Red Hat 最初使用的包管理器，被称为 [RPM](https://en.wikipedia.org/wiki/Rpm_(software))（<ruby> 红帽包管理器 <rt>  Red Hat Package Manager </rt></ruby>），时至今日还在使用着。不过，它的主要作用是安装本地的 RPM 包，而不是去在软件仓库搜索软件。后来开发了一个叫 `up2date` 的包管理器，它被用来通知用户包的最新更新，还能让用户在远程仓库里搜索软件并便捷的安装软件的依赖。尽管这个包管理器尽职尽责，但一些社区成员还是感觉 `up2date` 有着明显的不足。

现在的 YUM 来自于好几个不同社区的努力。1999-2001 年一群在 Terra Soft Solution 的伙计们开发了<ruby> 黄狗更新器 <rt>  Yellowdog Updater </rt></ruby>（YUP），将其作为 [Yellow Dog Linux](https://en.wikipedia.org/wiki/Yellow_Dog_Linux) 图形安装器的后端。<ruby> 杜克大学 <rt>  Duke University </rt></ruby>喜欢这个主意就决定去增强它的功能，它们开发了<ruby>   黄狗更新器--修改版  <rt>  Yellowdog Updater, Modified </rt></ruby>（YUM），这最终被用来帮助管理杜克大学的 Red Hat 系统。Yum 壮大的很快，到 2005 年，它已经被超过一半的 Linux 市场所采用。今日，几乎所有的使用 RPM 的的 Linux 都会使用 YUM 来进行包管理（当然也有一些例外）。

##### 使用 YUM

为了能让 YUM 正常工作，比如从一个软件仓库里下载和安装包，仓库说明文件必须放在 `/etc/yum.repos.d/` 目录下且必须以 `.repo` 作为扩展名。如下是一个示例文件的内容：

```
[local_base]
name=Base CentOS  (local)
baseurl=http://7-repo.apps.home.local/yum-repo/7/
enabled=1
gpgcheck=0

```

这是笔者本地仓库之一，这也是为什么 gpgcheck 值为 0 的原因。如果这个值为 1 的话，每个包都需要被密钥签名，相应的密钥（的公钥）也要导入到安装软件的系统上。因为这个软件仓库是笔者本人维护的且笔者信任这个仓库里的包，所以就不去对它们一一签名了。

当一个仓库文件准备好时，你就能开始从远程软件仓库开始安装文件了。最基本的命令是 `yum update`，这将会更新所有已安装的包。你也不需要用特殊的命令来更新仓库本身，所有这一切都已自动完成了。运行命令示例如下：

```
[user@centos ~]$ sudo yum update
Loaded plugins: fastestmirror, product-id, search-disabled-repos, subscription-manager
local_base                             | 3.6 kB  00:00:00
local_epel                             | 2.9 kB  00:00:00
local_rpm_forge                        | 1.9 kB  00:00:00
local_updates                          | 3.4 kB  00:00:00
spideroak-one-stable                   | 2.9 kB  00:00:00
zfs                                    | 2.9 kB  00:00:00
(1/6): local_base/group_gz             | 166 kB  00:00:00
(2/6): local_updates/primary_db        | 2.7 MB  00:00:00
(3/6): local_base/primary_db           | 5.9 MB  00:00:00
(4/6): spideroak-one-stable/primary_db |  12 kB  00:00:00
(5/6): local_epel/primary_db           | 6.3 MB  00:00:00
(6/6): zfs/x86_64/primary_db           |  78 kB  00:00:00
local_rpm_forge/primary_db             | 125 kB  00:00:00
Determining fastest mirrors
Resolving Dependencies
--> Running transaction check

```

如果你确定想让 YUM 在执行任何命令时不要停下来等待用户输入，你可以命令里放 `-y` 标志，如 `yum update -y`。

安装一个新包很简单。首先，用 `yum search` 搜索包的名字。

```
[user@centos ~]$ yum search kate

artwiz-aleczapka-kates-fonts.noarch : Kates font in Artwiz family
ghc-highlighting-kate-devel.x86_64 : Haskell highlighting-kate library development files
kate-devel.i686 : Development files for kate
kate-devel.x86_64 : Development files for kate
kate-libs.i686 : Runtime files for kate
kate-libs.x86_64 : Runtime files for kate
kate-part.i686 : Kate kpart plugin

```

当你找到你要安装的包后，你可以用 `sudo yum install kate-devel -y` 来安装。如果你安装了你不需要的软件，可以用 `sudo yum remove kdate-devel -y` 来从系统上删除它，默认情况下，YUM 会删除软件包以及它的依赖。

有些时候你甚至都不清楚要安装的包的名称，你只知道某个实用程序的名字。（LCTT 译注：可以理解实用程序是安装包的子集）。例如，你想找实用程序 `updatedb`（它是用来创建/更新由 `locate` 命令所使用的数据库的），直接试图安装 `updatedb` 会返回下面的结果：

```
[user@centos ~]$ sudo yum install updatedb
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile
No package updatedb available.
Error: Nothing to do

```

你可以搜索实用程序来自哪个包：

```
[user@centos ~]$ yum whatprovides *updatedb
Loaded plugins: fastestmirror, langpacks
Loading mirror speeds from cached hostfile

bacula-director-5.2.13-23.1.el7.x86_64 : Bacula Director files
Repo        : local_base
Matched from:
Filename    : /usr/share/doc/bacula-director-5.2.13/updatedb

mlocate-0.26-8.el7.x86_64 : An utility for finding files by name
Repo        : local_base
Matched from:
Filename    : /usr/bin/updatedb

```

笔者在前面使用星号的原因是 `yum whatprovides` 使用路径去匹配文件。笔者不确定文件在哪里，所以使用星号去指代任意路径。

当然 YUM 还有很多其它的可选项。这里笔者希望你能够自己查看 YUM 的手册来找到其它额外的可选项。

<ruby>   时髦的 Yum  <rt>  Dandified Yum </rt></ruby>（DNF）是 YUM 的下一代接班人。从 Fedora 18 开始被作为包管理器引入系统，不过它并没有被企业版所采用，所以它只在 Fedora（以及变种）上占据了主导地位。DNF 的用法和 YUM 几乎一模一样，它主要是用来解决性能问题、晦涩无说明的API、缓慢/不可靠的依赖解析，以及偶尔的高内存占用。DNF 是作为 YUM 的直接替代品来开发的，因此这里笔者就不重复它的用法了，你只用简单的将 `yum` 替换为 `dnf` 就行了。

##### 使用 Zypper

[Zypper](https://en.opensuse.org/Portal:Zypper) 是用来管理 RPM 包的另外一个包管理器。这个包管理器主要用于 [SUSE](https://www.suse.com/)（和 [openSUSE](https://www.opensuse.org/)），在[MeeGo](https://en.wikipedia.org/wiki/MeeGo)、[Sailfish OS](https://sailfishos.org/)、[Tizen](https://www.tizen.org/) 上也有使用。它最初开发于 2006 年，已经经过了多次迭代。除了作为系统管理工具 [YaST](https://en.wikipedia.org/wiki/YaST) 的后端和有些用户认为它比 YUM 要快之外也没有什么好多说的。

Zypper 使用与 YUM 非常相像。它被用来搜索、更新、安装和删除包，简单的使用命令如下：

```
zypper search kate
zypper update
zypper install kate
zypper remove kate

```

主要的不同来自于使用 Zypper 的系统在添加软件仓库的做法上，Zypper 使用包管理器本身来添加软件仓库。最通用的方法是通过一个 URL，但是 Zypper 也支持从仓库文件里导入。

```
suse:~ # zypper addrepo http://download.videolan.org/pub/vlc/SuSE/15.0 vlc
Adding repository 'vlc' [done]
Repository 'vlc' successfully added

Enabled     : Yes
Autorefresh : No
GPG Check   : Yes
URI         : http://download.videolan.org/pub/vlc/SuSE/15.0
Priority    : 99

```

你也能用相似的手段来删除软件仓库：

```
suse:~ # zypper removerepo vlc
Removing repository 'vlc' ...................................[done]
Repository 'vlc' has been removed.

```

使用 `zypper repos` 命令来查看当前系统上的软件仓库的状态：

```
suse:~ # zypper repos
Repository priorities are without effect. All enabled repositories share the same priority.

#  | Alias                     | Name                                    | Enabled | GPG Check | Refresh
---|---------------------------|-----------------------------------------|---------|-----------|--------
 1 | repo-debug                | openSUSE-Leap-15.0-Debug                | No      | ----      | ----
 2 | repo-debug-non-oss        | openSUSE-Leap-15.0-Debug-Non-Oss        | No      | ----      | ----
 3 | repo-debug-update         | openSUSE-Leap-15.0-Update-Debug         | No      | ----      | ----
 4 | repo-debug-update-non-oss | openSUSE-Leap-15.0-Update-Debug-Non-Oss | No      | ----      | ----
 5 | repo-non-oss              | openSUSE-Leap-15.0-Non-Oss              | Yes     | ( p) Yes  | Yes
 6 | repo-oss                  | openSUSE-Leap-15.0-Oss                  | Yes     | ( p) Yes  | Yes

```

`zypper` 甚至还有和 YUM 相同的功能：搜索包含文件或二进制的包。和 YUM 有所不同的是，它在命令行里使用破折号（但是这个搜索方法现在被废除了……）

```
localhost:~ # zypper what-provides kate
Command 'what-provides' is replaced by 'search --provides --match-exact'.
See 'help search' for all available options.
Loading repository data...
Reading installed packages...

S  | Name | Summary              | Type
---|------|----------------------|------------
i+ | Kate | Advanced Text Editor | application
i  | kate | Advanced Text Editor | package

```

YUM、DNF 和 Zypper 三剑客拥有的功能比在这篇小文里讨论的要多得多，请查看官方文档来得到更深入的信息。

#### 基于 Debian 的包管理器

作为一个现今仍在被积极维护的最古老的 Linux 发行版之一，Debian 的包管理系统和基于 RPM 的系统的包管理系统非常类似。它使用扩展名为 “.deb” 的包，这种文件能被一个叫做 `dpkg` 的工具所管理。`dpgk` 同 `rpm` 非常相似，它被设计成用来管理在存在于本地（硬盘）的包。它不会去做包依赖关系解析（它会做依赖关系检查，不过仅此而已），而且在同远程软件仓库交互上也并无可靠的途径。为了提高用户体验并便于使用，Debian 项目开始了一个软件项目：Deity，最终这个代号被丢弃并改成了现在的 <ruby>   高级打包工具  <rt>  Advanced Pack Tool </rt></ruby>（APT）。

在 1998 年，APT 测试版本发布（甚至早于 1999 年的 Debian 2.1 发布），许多用户认为 APT 是基于 Debian 系统标配功能之一。APT 使用了和 RPM 一样的风格来管理仓库，不过和 YUM 使用单独的 .repo 文件不同，APT 曾经使用 `/etc/apt/sources.list` 文件来管理软件仓库，后来的变成也可以使用 `/etc/apt/sources.d` 目录来管理。如同基于 RPM 的系统一样，你也有很多很多选项配置来完成同样的事情。你可以编辑和创建前述的文件，或者使用图形界面来完成上述工作（如 Ubuntu 的“Software & Updates”），为了给所有的 Linux 发行版统一的待遇，笔者将会只介绍命令行的选项。 要想不直接编辑文件内容而直接增加软件仓库的话，可以用如下命令：

```
user@ubuntu:~$ sudo apt-add-repository "deb http://APT.spideroak.com/ubuntu-spideroak-hardy/ release restricted"

```

这个命令将会在 `/etc/apt/sources.list.d` 目录里创建一个 `spideroakone.list` 文件。显而易见，文件里的内容依赖于所添加的软件仓库，如果你想加一个<ruby> 个人软件包存档 <rt>  Personal Package Archive </rt></ruby>（PPA）的话，你可以用如下的办法：

```
user@ubuntu:~$ sudo apt-add-repository ppa:gnome-desktop

```

注意： Debian 原生并不支持本地 PPA 。

在添加了一个软件仓库后，需要通知基于 Debian 的系统有一个新的仓库可以用来搜索包，可以运行 `apt-get update` 来完成：

```
user@ubuntu:~$ sudo apt-get update
Get:1 http://security.ubuntu.com/ubuntu xenial-security InRelease [107 kB]
Hit:2 http://APT.spideroak.com/ubuntu-spideroak-hardy release InRelease
Hit:3 http://ca.archive.ubuntu.com/ubuntu xenial InRelease
Get:4 http://ca.archive.ubuntu.com/ubuntu xenial-updates InRelease [109 kB]
Get:5 http://security.ubuntu.com/ubuntu xenial-security/main amd64 Packages [517 kB]
Get:6 http://security.ubuntu.com/ubuntu xenial-security/main i386 Packages [455 kB]
Get:7 http://security.ubuntu.com/ubuntu xenial-security/main Translation-en [221 kB]
...

Fetched 6,399 kB in 3s (2,017 kB/s)
Reading package lists... Done

```

现在新的软件仓库已经在你的系统里安装并更新好了，你可以用 `apt-cache` 来搜索你想要的包了。

```
user@ubuntu:~$ apt-cache search kate
aterm-ml - Afterstep XVT - a VT102 emulator for the X window system
frescobaldi - Qt4 LilyPond sheet music editor
gitit - Wiki engine backed by a git or darcs filestore
jedit - Plugin-based editor for programmers
kate - powerful text editor
kate-data - shared data files for Kate text editor
kate-dbg - debugging symbols for Kate
katepart - embeddable text editor component

```

要安装 kate，简单的运行下面的命令：

```
user@ubuntu:~$ sudo apt-get install kate

```

要是删除一个包，使用 `apt-get remove`：

```
user@ubuntu:~$ sudo apt-get remove kate

```

要探索一个包的话，APT 并没有提供一个类似于 `yum whatprovides` 的功能，如果你想深入包内部去确定一个特定的文件的话，也有一些别的方法能帮你完成这个目标，

如： 用 `dpkg`

```
user@ubuntu:~$ dpkg -S /bin/ls
coreutils: /bin/ls

```

或者： `apt-file`

```
user@ubuntu:~$ sudo apt-get install apt-file -y
user@ubuntu:~$ sudo apt-file update
user@ubuntu:~$ apt-file search kate

```

与 `yum whatprovides` 不同的是，`apt-file search` 的问题是因为自动添加了通配符搜索而输出过于详细（除非你知道确切的路径），最终在结果里包括了所有包含有 “kate” 的结果。

```
kate: /usr/bin/kate
kate: /usr/lib/x86_64-linux-gnu/qt5/plugins/ktexteditor/katebacktracebrowserplugin.so
kate: /usr/lib/x86_64-linux-gnu/qt5/plugins/ktexteditor/katebuildplugin.so
kate: /usr/lib/x86_64-linux-gnu/qt5/plugins/ktexteditor/katecloseexceptplugin.so
kate: /usr/lib/x86_64-linux-gnu/qt5/plugins/ktexteditor/katectagsplugin.so

```

上面这些例子大部分都使用了 `apt-get`。请注意现今大多数的 Ubuntu 教程里都径直使用了 `apt`。 单独一个 `apt` 设计用来实现那些最常用的 APT 命令的。`apt` 命令看上去是用来整合那些被分散在 `apt-get`、`apt-cache` 以及其它一些命令的的功能的。它还加上了一些额外的改进，如色彩、进度条以及其它一些小功能。上述的常用命令都能被 `apt` 替代，但是并不是所有的基于 Debian 的系统都能使用 `apt` 接受安全包补丁的，你有可能要安装额外的包的实现上述功能。

#### 基于 Arch 的包管理器

[Arch Linux](https://www.archlinux.org/) 使用称为 [packman](https://wiki.archlinux.org/index.php/pacman) 的包管理器。和 .deb 以及 .rpm 不同，它使用更为传统的 LZMA2 压缩包形式 .tar.xz 。这可以使 Arch Linux 包能够比其它形式的压缩包（如 gzip）有更小的尺寸。自从 2002 年首次发布以来， `pacman` 一直在稳定发布和改善。使用它最大的好处之一是它支持 [Arch Build System](https://wiki.archlinux.org/index.php/Arch_Build_System)，这是一个从源代码级别构建包的构建系统。该构建系统借助一个叫 `PKGBUILD` 的文件，这个文件包含了如版本号、发布号、依赖等等的元数据，以及一个为编译遵守 Arch Linux 需求的包所需要的带有必要的编译选项的脚本。而编译的结果就是前文所提的被 `pacman` 所使用的 .tar.xz 的文件。

上述的这套系统技术上导致了 <ruby>   Arch 用户仓库  <rt>  Arch User Respository </rt></ruby>（AUR）的产生，这是一个社区驱动的软件仓库，仓库里包括有 `PKGBUILD` 文件以及支持补丁或脚本。这给 Arch Linux 带了无穷无尽的软件资源。最为明显的好处是如果一个用户（或开发者）希望他开发的软件能被广大公众所使用，他不必通过官方途径去在主流软件仓库获得许可。而不利之处则是它必须将依赖社区的流程，类似于 [Docker Hub](https://hub.docker.com/)、 Canonical 的 Snap Packages（LCTT 译注： Canonical 是 Ubuntu 的发行公司），或者其它类似的机制。有很多特定于 AUR 的包管理器能被用来从 AUR 里的 `PGKBUILD` 文件下载、编译、安装，下面我们来仔细看看怎么做。

##### 使用 pacman 和官方软件仓库

Arch 的主要包管理器：`pacman`，使用标识位而不是像 `yum` 或 `apt` 一样使用命令词。例如，要搜索一个包，你要用 `pacman -Ss` 。和 Linux 上别的命令一样，你可以找到 pacman 的手册页和在线帮助。`pacman` 大多数的命令都使用了同步（`-S`）这个标识位。例如：

```
user@arch ~ $ pacman -Ss kate

extra/kate 18.04.2-2 (kde-applications kdebase)
    Advanced Text Editor
extra/libkate 0.4.1-6 [installed]
    A karaoke and text codec for embedding in ogg
extra/libtiger 0.3.4-5 [installed]
    A rendering library for Kate streams using Pango and Cairo
extra/ttf-cheapskate 2.0-12
    TTFonts collection from dustimo.com
community/haskell-cheapskate 0.1.1-100
    Experimental markdown processor.

```

Arch 也使用和别的包管理器类似的软件仓库。在上面的输出中，搜索结果前面有标明它是从哪个仓库里搜索到的（这里是 `extra/` 和 `community/`）。同 Red Hat 和 Debian 系统一样，Arch 依靠用户将软件仓库的信息加入到一个特定的文件里：`/etc/pacman.conf`。下面的例子非常接近一个仓库系统。笔者还打开了 `[multilib]` 仓库来支持 Steam：

```
[options]
Architecture = auto

Color
CheckSpace

SigLevel    = Required DatabaseOptional
LocalFileSigLevel = Optional

[core]
Include = /etc/pacman.d/mirrorlist

[extra]
Include = /etc/pacman.d/mirrorlist

[community]
Include = /etc/pacman.d/mirrorlist

[multilib]
Include = /etc/pacman.d/mirrorlist

```

你也可以在 `pacman.conf` 里指定具体的 URL。这个功能可以用来确保在某一时刻所有的包来自一个确定的地方，比如，如果一个安装包存在严重的功能缺陷并且很不幸它恰好还有几个包依赖，你能及时回滚到一个安全点，如果你已经在 `pacman.conf` 里加入了具体的 URL 的话，你就用用这个命令降级你的系统。

```
[core]
Server=https://archive.archlinux.org/repos/2017/12/22/$repo/os/$arch

```

和 Debian 系统一样，Arch 并不会自动更新它的本地仓库。你可以用下面的命令来刷新包管理器的数据库：

```
user@arch ~ $ sudo pacman -Sy

:: Synchronizing package databases...
 core                                                                     130.2 KiB   851K/s 00:00 [##########################################################] 100%
 extra                                                                   1645.3 KiB  2.69M/s 00:01 [##########################################################] 100%
 community                                                                  4.5 MiB  2.27M/s 00:02 [##########################################################] 100%
 multilib is up to date

```

你可以看到在上述的输出中，`pacman` 认为 multilib 包数据库是更新到最新状态的。如果你认为这个结果不正确的话，你可以强制运行刷新：`pacman -Syy`。如果你想升级你的整个系统的话（不包括从 AUR 安装的包），你可以运行 `pacman -Syu`：

```
user@arch ~ $ sudo pacman -Syu

:: Synchronizing package databases...
 core is up to date
 extra is up to date
 community is up to date
 multilib is up to date
:: Starting full system upgrade...
resolving dependencies...
looking for conflicting packages...

Packages (45) ceph-13.2.0-2  ceph-libs-13.2.0-2  debootstrap-1.0.105-1  guile-2.2.4-1  harfbuzz-1.8.2-1  harfbuzz-icu-1.8.2-1  haskell-aeson-1.3.1.1-20
              haskell-attoparsec-0.13.2.2-24  haskell-tagged-0.8.6-1  imagemagick-7.0.8.4-1  lib32-harfbuzz-1.8.2-1  lib32-libgusb-0.3.0-1  lib32-systemd-239.0-1
              libgit2-1:0.27.2-1  libinput-1.11.2-1  libmagick-7.0.8.4-1  libmagick6-6.9.10.4-1  libopenshot-0.2.0-1  libopenshot-audio-0.1.6-1  libosinfo-1.2.0-1
              libxfce4util-4.13.2-1  minetest-0.4.17.1-1  minetest-common-0.4.17.1-1  mlt-6.10.0-1  mlt-python-bindings-6.10.0-1  ndctl-61.1-1  netctl-1.17-1
              nodejs-10.6.0-1

Total Download Size:      2.66 MiB
Total Installed Size:   879.15 MiB
Net Upgrade Size:      -365.27 MiB

:: Proceed with installation? [Y/n]

```

在前面提到的降级系统的情景中，你可以运行 `pacman -Syyuu` 来强行降级系统。你必须重视这一点：虽然在大多数情况下这不会引起问题，但是这种可能性还是存在，即降级一个包或几个包将会引起级联传播的失败并会将你的系统处于不一致的状态（LCTT 译注：即系统进入无法正常使用的状态），请务必小心！

运行 `pacman -S kate` 来安装一个包。

```
user@arch ~ $ sudo pacman -S kate

resolving dependencies...
looking for conflicting packages...

Packages (7) editorconfig-core-c-0.12.2-1  kactivities-5.47.0-1  kparts-5.47.0-1  ktexteditor-5.47.0-2  syntax-highlighting-5.47.0-1  threadweaver-5.47.0-1
             kate-18.04.2-2

Total Download Size:   10.94 MiB
Total Installed Size:  38.91 MiB

:: Proceed with installation? [Y/n]

```

你可以运行 `pacman -R kate` 来删除一个包。这将会只删除这个包自身而不会去删除它的依赖包。

```
user@arch ~ $ sudo pacman -S kate

checking dependencies...

Packages (1) kate-18.04.2-2

Total Removed Size:  20.30 MiB

:: Do you want to remove these packages? [Y/n]

```

如果你想删除没有被其它包依赖的包，你可以运行 `pacman -Rs`：

```
user@arch ~ $ sudo pacman -Rs kate

checking dependencies...

Packages (7) editorconfig-core-c-0.12.2-1  kactivities-5.47.0-1  kparts-5.47.0-1  ktexteditor-5.47.0-2  syntax-highlighting-5.47.0-1  threadweaver-5.47.0-1
             kate-18.04.2-2

Total Removed Size:  38.91 MiB

:: Do you want to remove these packages? [Y/n]

```

在笔者看来，Pacman 是搜索一个指定实用程序中的包名的最齐全的工具。如上所示，YUM 和 APT 都依赖于‘路径’去搜索到有用的结果，而 Pacman 则做了一些智能的猜测，它会去猜测你最有可能想搜索的包。

```
user@arch ~ $ sudo pacman -Fs updatedb
core/mlocate 0.26.git.20170220-1
    usr/bin/updatedb

user@arch ~ $ sudo pacman -Fs kate
extra/kate 18.04.2-2
    usr/bin/kate

```

##### 使用 AUR

有很多流行的 AUR 包管理器助手。其中 `yaourt` 和 `pacaur` 颇为流行。不过，这两个项目已经被 [Arch Wiki](https://wiki.archlinux.org/index.php/AUR_helpers#Discontinued_or_problematic) 列为“不继续开发以及有已知的问题未解决”。因为这个原因，这里直接讨论 `aurman`，除了会搜索 AUR 以及包含几个有帮助的（其实很危险）的选项之外，它的工作机制和 `pacman` 极其类似。从 AUR 安装一个包将会初始化包维护者的构建脚本。你将会被要求输入几次授权以便让程序继续进行下去（为了简短起见，笔者截断了输出）。

```
aurman -S telegram-desktop-bin
~~ initializing aurman...
~~ the following packages are neither in known repos nor in the aur
...
~~ calculating solutions...

:: The following 1 package(s) are getting updated:
   aur/telegram-desktop-bin  1.3.0-1  ->  1.3.9-1

?? Do you want to continue? Y/n: Y

~~ looking for new pkgbuilds and fetching them...
Cloning into 'telegram-desktop-bin'...

remote: Counting objects: 301, done.
remote: Compressing objects: 100% (152/152), done.
remote: Total 301 (delta 161), reused 286 (delta 147)
Receiving objects: 100% (301/301), 76.17 KiB | 639.00 KiB/s, done.
Resolving deltas: 100% (161/161), done.
?? Do you want to see the changes of telegram-desktop-bin? N/y: N

[sudo] password for user:

...
==> Leaving fakeroot environment.
==> Finished making: telegram-desktop-bin 1.3.9-1 (Thu 05 Jul 2018 11:22:02 AM EDT)
==> Cleaning up...
loading packages...
resolving dependencies...
looking for conflicting packages...

Packages (1) telegram-desktop-bin-1.3.9-1

Total Installed Size:  88.81 MiB
Net Upgrade Size:       5.33 MiB

:: Proceed with installation? [Y/n]

```

依照你所安装的包的复杂性程度的高低，有时你将会被要求给出进一步的输入，为了避免这些反复的输入，`aurman` 允许你使用 `--noconfirm` 和 `--noedit` 选项。这相当于说“接受所有的预定设置，并相信包管理器不会干坏事”。**使用这两个选项时请务必小心！！**，虽然这些选项本身不太会破坏你的系统，你也不能盲目的接受他人的脚本程序。

### 总结

这篇文章当然只能触及包管理器的皮毛。还有很多别的包管理器笔者没有在这篇文章里谈及。有些 Linux 发布版，如 Ubuntu 或 Elementary OS，已经在图形版的包管理器的开发上有了长远的进展。

如果你对包管理器的更高级功能有进一步的兴趣，请在评论区留言，笔者很乐意进一步的写一写相关的文章。

### 附录

```
# search for packages
yum search <package>
dnf search <package>
zypper search <package>
apt-cache search <package>
apt search <package>
pacman -Ss <package>

# install packages
yum install <package>
dnf install <package>
zypper install <package>
apt-get install <package>
apt install <package>
pacman -Ss <package>

# update package database, not required by yum, dnf and zypper
apt-get update
apt update
pacman -Sy

# update all system packages
yum update
dnf update
zypper update
apt-get upgrade
apt upgrade
pacman -Su

# remove an installed package
yum remove <package>
dnf remove <package>
apt-get remove <package>
apt remove <package>
pacman -R <package>
pacman -Rs <package>

# search for the package name containing specific file or folder
yum whatprovides *<binary>
dnf whatprovides *<binary>
zypper what-provides <binary>
zypper search --provides <binary>
apt-file search <binary>
pacman -Sf <binary>

```

---

via: <https://opensource.com/article/18/7/evolution-package-managers>

作者：[Steve Ovens](https://opensource.com/users/stratusss) 选题：[lujun9972](https://github.com/lujun9972) 译者：[DavidChenLiang](https://github.com/davidchenliang) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


如何在 Linux 中查看系统硬件制造商、型号和序列号

![](/data/attachment/album/201808/19/101700kyth5iil4d4huhaz.jpg)

对于 Linux 图形界面用户和 Windows 用户来说获取系统硬件信息都不算问题，但是对命令行用户来说想要获取这些细节时有点儿麻烦。

甚至我们中的大多数都不知道获取这些信息最好的命令是什么。Linux 中有许多可用的工具集来获取诸如制造商、型号和序列号等硬件信息。

在这里我尝试写下获取这些细节的可能的方式，你可以挑选一种最好用的。

你必须知道所有这些信息，因为当你向硬件制造商提交任何硬件问题时，你会需要它们。

这可以通过 6 种方法来实现，下面我来演示一下怎么做。

### 方法一：使用 dmidecode 命令

`dmidecode` 是一个读取电脑 DMI（<ruby> 桌面管理接口 <rt>  Desktop Management Interface </rt></ruby>）表内容并且以人类可读的格式显示系统硬件信息的工具。（也有人说是读取 SMBIOS —— <ruby> 系统管理 BIOS <rt>  System Management BIOS </rt></ruby>）

这个表包含系统硬件组件的说明，也包含如序列号、制造商、发布日期以及 BIOS 修订版本号等其它有用的信息。

DMI 表不仅描述了当前的系统构成，还可以报告可能的升级信息（比如可以支持的最快的 CPU 或者最大的内存容量）。

这将有助于分析你的硬件兼容性，比如是否支持最新版本的程序。

```
# dmidecode -t system

# dmidecode 2.12
# SMBIOS entry point at 0x7e7bf000
SMBIOS 2.7 present.

Handle 0x0024, DMI type 1, 27 bytes
System Information
 Manufacturer: IBM
 Product Name: System x2530 M4: -[1214AC1]-
 Version: 0B
 Serial Number: MK2RL11
 UUID: 762A99BF-6916-450F-80A6-B2E9E78FC9A1
 Wake-up Type: Power Switch
 SKU Number: Not Specified
 Family: System X

Handle 0x004B, DMI type 12, 5 bytes
System Configuration Options
 Option 1: JP20 pin1-2: TPM PP Disable, pin2-3: TPM PP Enable

Handle 0x004D, DMI type 32, 20 bytes
System Boot Information
 Status: No errors detected

```

**推荐阅读：** [Dmidecode –– 获取 Linux 系统硬件信息的简单方式](https://www.2daygeek.com/dmidecode-get-print-display-check-linux-system-hardware-information/)

### 方法二：使用 inxi 命令

`inxi` 是 Linux 上查看硬件信息的一个灵巧的小工具，它提供了大量的选项来获取所有硬件信息，这是我在现有的其它 Linux 工具集里所没见到过的。它是从 locsmif 编写的古老的但至今看来都异常灵活的 `infobash` fork 出来的。

`inxi` 是一个可以快速显示系统硬件、CPU、驱动、Xorg、桌面、内核、GCC 版本、进程、内存使用以及大量其它有用信息的脚本，也可以用来做技术支持和调试工具。

```
# inxi -M
Machine: Device: server System: IBM product: N/A v: 0B serial: MK2RL11
 Mobo: IBM model: 00Y8494 serial: 37M17D UEFI: IBM v: -[VVE134MUS-1.50]- date: 08/30/2013

```

**推荐阅读：** [inxi —— 一个很棒的查看 Linux 硬件信息的工具](https://www.2daygeek.com/inxi-system-hardware-information-on-linux/)

### 方法三：使用 lshw 命令

`lshw`（指<ruby> 硬件监听器 <rt>  Hardware Lister </rt></ruby>）是一个小巧灵活的工具，可以生成如内存配置、固件版本、主板配置、CPU 版本和速度、缓存配置、USB、网卡、显卡、多媒体、打印机以及总线速度等机器中各种硬件组件的详细报告。

它通过读取 `/proc` 目录下各种文件的内容和 DMI 表来生成硬件信息。

`lshw` 必须以超级用户的权限运行来检测完整的硬件信息，否则它只汇报部分信息。`lshw` 里有一个叫做 `class` 的特殊选项，它可以以详细的模式显示特定的硬件信息。

```
# lshw -C system
enal-dbo01t
 description: Blade
 product: System x2530 M4: -[1214AC1]-
 vendor: IBM
 version: 0B
 serial: MK2RL11
 width: 64 bits
 capabilities: smbios-2.7 dmi-2.7 vsyscall32
 configuration: boot=normal chassis=enclosure family=System X uuid=762A99BF-6916-450F-80A6-B2E9E78FC9A1

```

**推荐阅读：** [LSHW (Hardware Lister) –– 获取 Linux 硬件信息的灵巧的小工具](https://www.2daygeek.com/lshw-find-check-system-hardware-information-details-linux/)

### 方法四：使用 /sys 文件系统

内核在 `/sys` 目录下的文件中公开了一些 DMI 信息。因此，我们可以通过如下方式运行 `grep` 命令来轻易地获取机器类型。

```
# grep "" /sys/class/dmi/id/[pbs]*

```

或者，可以使用 `cat` 命令仅打印出特定的详细信息。

```
# cat /sys/class/dmi/id/board_vendor
IBM

# cat /sys/class/dmi/id/product_name
System x2530 M4: -[1214AC1]-

# cat /sys/class/dmi/id/product_serial
MK2RL11

# cat /sys/class/dmi/id/bios_version
-[VVE134MUS-1.50]-

```

### 方法五：使用 dmesg 命令

`dmesg` 命令是在 Linux 上 `syslogd` 或 `klogd` 启动前用来记录内核消息（启动阶段的消息）的。它通过读取内核的环形缓冲区来获取数据。在排查问题或只是尝试获取系统硬件信息时，`dmesg` 非常有用。

```
# dmesg | grep -i DMI
DMI: System x2530 M4: -[1214AC1]-/00Y8494, BIOS -[VVE134MUS-1.50]- 08/30/2013

```

### 方法六：使用 hwinfo 命令

`hwinfo`（<ruby> 硬件信息 <rt>  hardware information </rt></ruby>）是另一个很棒的工具，用于检测当前系统存的硬件，并以人类可读的方式显示各种硬件模块的详细信息。

它报告关于 CPU、内存、键盘、鼠标、显卡、声卡、存储、网络接口、磁盘、分区、BIOS 以及桥接器等信息。它可以比其它像 `lshw`、`dmidecode` 或 `inxi` 等工具显示更为详细的信息。

`hwinfo` 使用 libhd 库 `libhd.so` 来收集系统上的硬件信息。该工具是为 openSuse 特别设计的，后来其它发行版也将它包含在其官方仓库中。

```
# hwinfo | egrep "system.hardware.vendor|system.hardware.product"
 system.hardware.vendor = 'IBM'
 system.hardware.product = 'System x2530 M4: -[1214AC1]-'

```

**推荐阅读：** [hwinfo (Hardware Info) –– 一款灵活的检测 Linux 系统硬件信息的工具](https://www.2daygeek.com/hwinfo-check-display-detect-system-hardware-information-linux/)

---

via: <https://www.2daygeek.com/how-to-check-system-hardware-manufacturer-model-and-serial-number-in-linux/>

作者：[VINOTH KUMAR](https://www.2daygeek.com/author/vinoth/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[icecoobe](https://github.com/icecoobe) 校对：[pityonline](https://github.com/pityonline)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


从零开始学习 Go 语言的切片

Summary: 这篇文章受到了我与同事讨论使用切片slice作为栈stack的一次聊天的启发。后来话题聊到了 Go 语言中的切片是如何工作的。我认为这些信息对别人也有用，所以就把它记录了下来。

![](/data/attachment/album/201808/19/215935hm6cc01zr0ickppc.jpg)

这篇文章受到了我与同事讨论使用<ruby> 切片 <rt>  slice </rt></ruby>作为<ruby> 栈 <rt>  stack </rt></ruby>的一次聊天的启发。后来话题聊到了 Go 语言中的切片是如何工作的。我认为这些信息对别人也有用，所以就把它记录了下来。

### 数组

任何关于 Go 语言切片的讨论都要从另一个数据结构也就是<ruby> 数组 <rt>  array </rt></ruby>开始。Go 的数组有两个特性：

1. 数组的长度是固定的；`[5]int` 是由 5 个 `int` 构成的数组，和 `[3]int` 不同。
2. 数组是值类型。看下面这个示例：

```
package main

import "fmt"

func main() {
        var a [5]int
        b := a
        b[2] = 7
        fmt.Println(a, b) // prints [0 0 0 0 0] [0 0 7 0 0]
}
```

语句 `b := a` 定义了一个类型是 `[5]int` 的新变量 `b`，然后把 `a` 中的内容 *复制* 到 `b` 中。改变 `b` 对 `a` 中的内容没有影响，因为 `a` 和 `b` 是相互独立的值。   1

### 切片

Go 语言的切片和数组的主要有如下两个区别：

1. 切片没有一个固定的长度。切片的长度不是它类型定义的一部分，而是由切片内部自己维护的。我们可以使用内置的 `len` 函数知道它的长度。   2
2. 将一个切片赋值给另一个切片时 *不会* 对切片内容进行复制操作。这是因为切片没有直接持有其内部数据，而是保留了一个指向 *底层数组*    3  的指针。数据都保留在底层数组里。

基于第二个特性，两个切片可以享有共同的底层数组。看下面的示例：

1. 对切片取切片

```
package main

import "fmt"

func main() {
        var a = []int{1,2,3,4,5}
        b := a[2:]
        b[0] = 0
        fmt.Println(a, b) // prints [1 2 0 4 5] [0 4 5]
}
```

在这个例子里，`a` 和 `b` 享有共同的底层数组 —— 尽管 `b` 在数组里的起始偏移量不同，两者的长度也不同。通过 `b` 修改底层数组的值也会导致 `a` 里的值的改变。
2. 将切片传进函数

```
package main

import "fmt"

func negate(s []int) {
        for i := range s {
                s[i] = -s[i]
        }
}

func main() {
        var a = []int{1, 2, 3, 4, 5}
        negate(a)
        fmt.Println(a) // prints [-1 -2 -3 -4 -5]
}
```

在这个例子里，`a` 作为形参 `s` 的实参传进了 `negate` 函数，这个函数遍历 `s` 内的元素并改变其符号。尽管 `nagate` 没有返回值，且没有访问到 `main` 函数里的 `a`。但是当将之传进 `negate` 函数内时，`a` 里面的值却被改变了。

大多数程序员都能直观地了解 Go 语言切片的底层数组是如何工作的，因为它与其它语言中类似数组的工作方式类似。比如下面就是使用 Python 重写的这一小节的第一个示例：

```
Python 2.7.10 (default, Feb  7 2017, 00:08:15)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.34)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> a = [1,2,3,4,5]
>>> b = a
>>> b[2] = 0
>>> a
[1, 2, 0, 4, 5]
```

以及使用 Ruby 重写的版本:

```
irb(main):001:0> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> b = a
=> [1, 2, 3, 4, 5]
irb(main):003:0> b[2] = 0
=> 0
irb(main):004:0> a
=> [1, 2, 0, 4, 5]
```

在大多数将数组视为对象或者是引用类型的语言也是如此。   4

### 切片头

切片同时拥有值和指针特性的神奇之处在于理解切片实际上是一个<ruby> 结构体 <rt>  struct </rt></ruby>类型。通常在[<ruby> 反射 <rt>  reflect </rt></ruby>包内相应部分之后](https://golang.org/pkg/reflect/#SliceHeader)的这个结构体被称作<ruby> 切片头 <rt>  slice header </rt></ruby>。切片头的定义大致如下：

![](/data/attachment/album/201808/19/215957yqhveimh59ro9mz9.png)

```
package runtime

type slice struct {
        ptr   unsafe.Pointer
        len   int
        cap   int
}
```

这很重要，因为和 [`map` 以及 `chan` 这两个类型不同](https://golang.org/pkg/reflect/#SliceHeader)，切片是值类型，当被赋值或者被作为参数传入函数时候会被复制过去。

程序员们都能理解 `square` 的形参 `v` 和 `main` 中声明的 `v` 的是相互独立的。请看下面的例子：

```
package main

import "fmt"

func square(v int) {
        v = v * v
}

func main() {
        v := 3
        square(v)
        fmt.Println(v) // prints 3, not 9
}
```

因此 `square` 对自己的形参 `v` 的操作没有影响到 `main` 中的 `v`。下面这个示例中的 `s` 也是 `main` 中声明的切片 `s` 的独立副本， *而不是* 指向 `main` 的 `s` 的指针。

```
package main

import "fmt"

func double(s []int) {
        s = append(s, s...)
}

func main() {
        s := []int{1, 2, 3}
        double(s)
        fmt.Println(s, len(s)) // prints [1 2 3] 3
}
```

Go 的切片是作为值传递而不是指针这一点不太寻常。当你在 Go 内定义一个结构体时，90% 的时间里传递的都是这个结构体的指针   5  。切片的传递方式真的很不寻常，我能想到的唯一与之相同的例子只有 `time.Time`。

切片作为值传递而不是作为指针传递这一特殊行为会让很多想要理解切片的工作原理的 Go 程序员感到困惑。你只需要记住，当你对切片进行赋值、取切片、传参或者作为返回值等操作时，你是在复制切片头结构的三个字段：指向底层数组的指针、长度，以及容量。

### 总结

我们来用引出这一话题的切片作为栈的例子来总结下本文的内容：

```
package main

import "fmt"

func f(s []string, level int) {
        if level > 5 {
               return
        }
        s = append(s, fmt.Sprint(level))
        f(s, level+1)
        fmt.Println("level:", level, "slice:", s)
}

func main() {
        f(nil, 0)
}
```

在 `main` 函数的最开始我们把一个 `nil` 切片传给了函数 `f` 作为 `level` 0 。在函数 `f` 里我们把当前的 `level` 添加到切片的后面，之后增加 `level` 的值并进行递归。一旦 `level` 大于 5，函数返回，打印出当前的 `level` 以及它们复制到的 `s` 的内容。

```
level: 5 slice: [0 1 2 3 4 5]
level: 4 slice: [0 1 2 3 4]
level: 3 slice: [0 1 2 3]
level: 2 slice: [0 1 2]
level: 1 slice: [0 1]
level: 0 slice: [0]
```

你可以注意到在每一个 `level` 内 `s` 的值没有被别的 `f` 的调用影响，尽管当计算更高的 `level` 时作为 `append` 的副产品，调用栈内的四个 `f` 函数创建了四个底层数组   6  ，但是没有影响到当前各自的切片。

### 扩展阅读

如果你想要了解更多 Go 语言内切片运行的原理，我建议看看 Go 博客里的这些文章：

* [Go Slices: usage and internals](https://blog.golang.org/go-slices-usage-and-internals) (blog.golang.org)
* [Arrays, slices (and strings): The mechanics of 'append'](https://blog.golang.org/slices) (blog.golang.org)

### 相关文章：

1. [If a map isn't a reference variable, what is it?](https://dave.cheney.net/2017/04/30/if-a-map-isnt-a-reference-variable-what-is-it)
2. [What is the zero value, and why is it useful?](https://dave.cheney.net/2013/01/19/what-is-the-zero-value-and-why-is-it-useful)
3. [The empty struct](https://dave.cheney.net/2014/03/25/the-empty-struct)
4. [Should methods be declared on T or \*T](https://dave.cheney.net/2016/03/19/should-methods-be-declared-on-t-or-t)

---

1. 这不是数组才有的特性，在 Go 语言里中 *一切* 赋值都是复制过去的。 [↩](#fnref1)
2. 你也可以在对数组使用 `len` 函数，但是其结果本来就人尽皆知。 [↩](#fnref2)
3. 有时也叫做<ruby> 后台数组 <rt>  backing array </rt></ruby>，以及更不严谨的说法是后台切片。 [↩](#fnref3)
4. Go 语言里我们倾向于说值类型以及指针类型，因为 C++ 的<ruby> 引用 <rt>  reference </rt></ruby>类型这个词产生误会。但在这里我认为调用数组作为引用类型是没有问题的。 [↩](#fnref4)
5. 如果你的结构体有[定义在其上的方法或者用于满足某个接口](https://golang.org/pkg/reflect/#SliceHeader)，那么你传入结构体指针的比率可以飙升到接近 100%。 [↩](#fnref5)
6. 证明留做习题。 [↩](#fnref6)

---

via: <https://dave.cheney.net/2018/07/12/slices-from-the-ground-up>

作者：[Dave Cheney](https://dave.cheney.net/) 译者：[name1e5s](https://github.com/name1e5s) 校对：[pityonline](https://github.com/pityonline)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


什么数据对于云服务器来说风险很大

>
> 在这个关于混合多云陷阱的系列文章的最后一篇当中，让我们来学习一下如何设计一个低风险的云迁移战略。
>
>
>

![](/data/attachment/album/201808/20/124744rl09ou91sorsl0fu.jpg)

在这四篇系列文章中，我们了解到了每个组织在做云迁移的时候所应该避免的陷阱 —— 特别是混合多云的情况下。

[在第一部分](https://opensource.com/article/18/4/pitfalls-hybrid-multi-cloud)，我们介绍了一些基本的定义以及我们对于混合云以及多云的观点，确保展示了两者之间的区别。[在第二部分](https://opensource.com/article/18/6/reasons-move-to-cloud)，我们会对三个陷阱之一进行讨论：为什么成本并不是迁移到云端的显然的推动因素。而且，[在第三部分](https://opensource.com/article/18/7/why-you-cant-move-everything-cloud)，我们考察了将所有工作向云端进行迁移的可行性。

最后，在这个第四部分中，我们来看看数据上云要做什么。您应该把数据向云端迁移吗？迁移多少？什么数据可以放在云端？又是什么会造成上云风险很大？

### 数据…数据…数据

影响您对云端数据的所有决策的关键因素在于确定您的带宽以及存储需求。 Gartner 预计 “数据存储在 2018 年将成为一项[$173 亿美元](http://www.businessinsider.com/companies-waste-62-billion-on-the-cloud-by-paying-for-storage-they-dont-need-according-to-a-report-2017-11)的业务”，并且大部分资金是浪费在一些不必要的存储容量上：“但是只需要优化一下工作负载，全球的所有公司就可以节约 620 亿美元的不必要 IT 成本。”根据 Gartner 的研究，非常令人惊讶的是，全球所有的公司“为云服务器平均支付的费用比他们实际的费用多达 36% 。”

如果你已经阅读了本系列的前三个章节，那么你应该不会为此感到惊讶。然而令人更惊讶的是 Gartner 的结论是 “如果全球的公司将他们的服务器数据直接迁移到云端上，仅仅只有 25% 的公司能够做到省钱。”

等一下……工作负载是可以针对云进行优化的，但是只有一小部分公司能通过将数据向云端迁移而节省资金吗？这个又是什么意思？

如果你去认为云服务商会根据带宽来收取云产生的费用，那么将所有的公司内部数据移至云端很快就会成为他们的成本负担。在以下三种情况，公司才可能会觉得值得把数据放在云端中：

* 单个云包括存储和应用程序
* 应用程序在云端，存储在本地
* 应用程序在云端，而且数据缓存也在云端，存储在本地

在第一种情况下，通过将所有的内容都放在单个云服务商来节省带宽成本，但是这会产生一些（供应商）锁定，这个通常与 CIO 的云战略或者风险防范计划所冲突。

第二种方案是仅仅保留应用程序在云端所收集的数据，并且以最小的方式传输到本地存储。这就需要仔细的考虑策略，其中只有最少使用数据的应用程序部署在云端。

第三种情况就是将数据缓存在云端，应用程序和存储的数据被存储在本地。这也就意味着分析、人工智能、机器学习可以在内部运行而无需把数据向云服务商上传，然后处理之后再返回。缓存的数据仅仅基于应用程序对云的需求，甚至进行跨多云的部署缓存。

要想获得更多信息，请下载红帽[案例研究](https://www.redhat.com/en/resources/amsterdam-airport-schiphol-case-study)，其中描述了跨混合多云环境下的阿姆斯特丹的史基浦机场的数据以及云和部署策略。

### 数据危险

大多数公司都认识到了他们的数据是在其市场中的专有优势以及知识能力。因此他们会非常仔细的考虑它在云存储的地点。

想象一下这种情况：如果你是一个零售商，全球十大零售商之一。而且你已经计划了很长一段时间云存储战略，并且考虑使用亚马逊的云服务。但是突然间， [亚马逊收购了全食超市](https://www.forbes.com/sites/ciocentral/2017/06/23/amazon-buys-whole-foods-now-what-the-story-behind-the-story/#33e9cc6be898)，并且准备进入你的市场。

一夜之间，亚马逊已经增长了 50% 的零售规模，你是否还会去信任把零售数据放到他们的云上？如果您的数据已经就在亚马逊云中，你会打算怎么做？您创建云计划时是否考虑过退出策略？虽然亚马逊可能永远不会去利用您的数据潜在价值 —— 该公司可能甚至有针对此的条款 —— 但你能相信世界上任何人的话吗？

### 陷阱分享，避免陷阱

分享我们在以前经验中看到的一些陷阱来帮助您的公司规划更安全、更持久的云端策略。了解了[成本不是显然的推动因素](https://opensource.com/article/18/6/reasons-move-to-cloud)，[并非一切东西都应该在云端](https://opensource.com/article/18/7/why-you-cant-move-everything-cloud)，而是你必须在云端有效管理数据才是您成功的关键所在。

---

via: <https://opensource.com/article/18/8/data-risky-cloud>

作者：[Eric D.Schabell](https://opensource.com/users/eschabell) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekmar](https://github.com/geekmar) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


netdev 第二天：从网络代码中移除“尽可能快”这个目标

![](/data/attachment/album/201808/20/130017eprhpqq0nz0ronkr.jpg)

嗨！今天是 netdev 会议的第 2 天，我只参加了早上的会议，但它*非常有趣*。今早会议的主角是 [Van Jacobson](https://en.wikipedia.org/wiki/Van_Jacobson) 给出的一场名为 “从尽可能快中变化：教网卡以时间”的演讲，它的主题是关于互联网中拥塞控制的未来！！！

下面我将尝试着对我从这次演讲中学到的东西做总结，我几乎肯定下面的内容有些错误，但不管怎样，让我们开始吧！

这次演讲是关于互联网是如何从 1988 开始改变的，为什么现在我们需要新的算法，以及我们可以怎样改变 Linux 的网络栈来更容易地实现这些算法。

### 什么是拥塞控制？

在网络上的任何成员总是无时无刻地发送信息包，而在互联网上的连接之间有着极其不同的速度（某些相比其他极其缓慢），而有时候它们将被塞满！当互联网的一个设备以超过它能处理的速率接收信息包时，它将丢弃某些信息包。

你所能想象的最天真的发送信息包方式是：

1. 将你必须发送的信息包一次性发送完。
2. 假如你发现其中有的信息包被丢弃了，就马上重新发送这些包。

结果表明假如你按照上面的思路来实现 TCP，互联网将会崩溃并停止运转。我们知道它会崩溃是因为在 1986 年确实发生了崩溃的现象。为了解决这个问题，专家发明了拥塞控制算法 —— 描述如何避免互联网的崩溃的原始论文是 Van Jacobson 于 1988 年发表的 [拥塞避免与控制](https://cs162.eecs.berkeley.edu/static/readings/jacobson-congestion.pdf)（30 年前！）。

### 从 1988 年后互联网发生了什么改变？

在演讲中，Van Jacobson 说互联网的这些已经发生了改变：在以前的互联网上，交换机可能总是拥有比服务器更快的网卡，所以这些位于互联网中间层的服务器也可能比客户端更快，并且并不能对客户端发送信息包的速率有多大影响。

很显然今天已经不是这样的了！众所周知，今天的计算机相比于 5 年前的计算机在速度上并没有多大的提升（我们遇到了某些有关光速的问题）。所以我想路由器上的大型交换机并不会在速度上大幅领先于数据中心里服务器上的网卡。

这听起来有些糟糕，因为这意味着客户端更容易在中间层的连接中达到饱和，而这将导致互联网变慢（而且 [缓冲膨胀](https://apenwarr.ca/log/?m=201101#10) 将带来更高的延迟）。

所以为了提高互联网的性能且不让每个路由上的任务队列都达到饱和，客户端需要表现得更好并且在发送信息包的时候慢一点。

### 以更慢的速率发送更多的信息包以达到更好的性能

下面的结论真的让我非常意外 —— 以更慢的速率发送信息包实际上可能会带来更好的性能（即便你是在整个传输过程中，这样做的唯一的人），下面是原因：

假设你打算发送 10MB 的数据，在你和你需要连接的客户端之间有一个中间层，并且它的传输速率*非常低*，例如 1MB/s。假设你可以辨别这个慢连接（或者更多的后续中间层）的速度，那么你有 2 个选择：

1. 一次性将这 10MB 的数据发送完，然后看看会发生什么。
2. 减慢速率使得你能够以 1MB/s 的速率传给它。

现在，无论你选择何种方式，你可能都会发生丢包的现象。所以这样看起来，你可能需要选择一次性发送所有的信息包这种方式，对吧？不！！实际上在你的数据流的中间环节丢包要比在你的数据流的最后丢包要好得多。假如在中间环节有些包被丢弃了，你需要送往的那个客户端可以察觉到这个事情，然后再告诉你，这样你就可以再次发送那些被丢弃的包，这样便没有多大的损失。但假如信息包在最末端被丢弃，那么客户端将完全没有办法知道你一次性发送了所有的信息包！所以基本上在某个时刻被丢弃的包没有让你收到 ACK 信号时，你需要启用超时机制，并且还得重新发送它们。而超时往往意味着需要花费很长时间！

所以为什么以更慢的速率发送数据会更好呢？假如你发送数据的速率快于连接中的瓶颈，这时所有的信息包将会在某个地方堆积成一个队列，这个队列将会被塞满，然后在你的数据流的最末端的信息包将会被丢弃。并且像我们刚才解释的那样，处于数据流最后面的信息包很有可能丢弃！所以相比于最初以合适的速率发送信息包，一次性发送它们将会触发超时机制，发送 10MB 的数据将会花费更长的时间。

我认为这非常酷，因为这个过程并不需要与互联网中的其他人合作 —— 即便其他的所有人都已非常快的速率传送他们的信息包，对你来说以合适的速率（中间层的瓶颈速率）传送你自己的信息包*仍然*更有优势。

### 如何辨别发送数据的合适速率：BBR！

在上面我说过：“假设你可以辨别出位于你的终端和服务器之间慢连接的速率……”，那么如何做到呢？来自 Google（Jacobson 工作的地方）的某些专家已经提出了一个算法来估计瓶颈的速率！它叫做 BBR，由于本次的分享已经很长了，所以这里不做具体介绍，但你可以参考 [BBR：基于拥塞的拥塞控制](https://queue.acm.org/detail.cfm?id=3022184) 和 [来自晨读论文的总结](https://blog.acolyer.org/2017/03/31/bbr-congestion-based-congestion-control/) 这两处链接。

（另外，[https://blog.acolyer.org](https://blog.acolyer.org/) 的每日“晨读论文”总结基本上是我学习和理解计算机科学论文的唯一方式，它有可能是整个互联网上最好的博客之一！）

### 网络代码被设计为运行得“尽可能快“

所以，假设我们相信我们想以一个更慢的速率（例如以我们连接中的瓶颈速率）来传输数据。这很好，但网络软件并不是被设计为以一个可控速率来传输数据的！下面是我所理解的大多数网络软件怎么做的：

1. 现在有一个队列的信息包来临；
2. 然后软件读取队列并尽可能快地发送信息包；
3. 就这样，没有了。

这个过程非常呆板 —— 假设我以一个非常快的速率发送信息包，而另一端的连接却非常慢。假如我所拥有的就是一个放置所有信息包的队列，当我实际要发送数据时，我并没有办法来控制这个发送过程，所以我便不能减慢这个队列传输的速率。

### 一个更好的方式：给每个信息包一个“最早的出发时间”

BBR 协议将会修改 Linux 内核中 skb 的数据结构（这个数据结构被用来表达网络信息包），使得它有一个时间戳，这个时间戳代表着这个信息包应该被发送出去的最早时间。

对于 Linux 网络栈我不知道更多的详情了，但对于我来说，这个协议最有趣的地方是这个改动并不是一个非常大的改动！它只是添加了一个额外的时间戳而已。

### 用时间轮盘替换队列！！！

一旦我们将时间戳打到这些信息包上，我们怎样在合适的时间将它们发送出去呢？使用*时间轮盘*！

在前不久的“我们喜爱的论文”活动中（这是关于这次聚会描述的[某些好的链接](https://www.meetup.com/Papers-We-Love-Montreal/events/235100825/)），有一个演讲谈论了关于时间轮盘的话题。时间轮盘是一类用来指导 Linux 的进程调度器决定何时运行进程的算法。

Van Jacobson 说道：时间轮盘实际上比队列调度工作得更好 —— 它们都提供常数时间的操作，但因为某些缓存机制，时间轮盘的常数要更小一些。我真的没有太明白这里他说的关于性能的解释。

他说道：关于时间轮盘的一个关键点是你可以很轻松地用时间轮盘实现一个队列（但反之不能！）—— 假如每次你增加一个新的信息包，在最开始你说我想让它*现在*就被发送走，很显然这样你就可以得到一个队列了。而这个时间轮盘方法是向后兼容的，它使得你可以更容易地实现某些更加复杂的对流量非常敏感的算法，例如让你针对不同的信息包以不同的速率去发送它们。

### 或许我们可以通过改善 Linux 来修复互联网！

对于任何影响到整个互联网规模的问题，最为棘手的问题是当你要做出改善时，例如改变互联网协议的实现，你需要面对各种不同的设备。你要面对 Linux 的机器、BSD 的机器、Windows 的机器、各种各样的手机、瞻博或者思科的路由器以及数量繁多的其他设备！

但是在网络环境中 Linux 处于某种有趣的位置上！

* Android 手机运行着 Linux
* 大多数的消费级 WiFi 路由器运行着 Linux
* 无数的服务器运行着 Linux

所以在任何给定的网络连接中，实际上很有可能在两端都有一台 Linux 机器（例如一个 Linux 服务器、或者一个 Linux 路由器、一台 Android 设备）。

所以重点是假如你想大幅改善互联网上的拥塞状况，只需要改变 Linux 网络栈就会大所不同（或许 iOS 网络栈也是类似的）。这也就是为什么在本次的 Linux 网络会议上有这样的一个演讲！

### 互联网仍在改变！酷！

通常我以为 TCP/IP 仍然是上世纪 80 年代的东西，所以当从这些专家口中听说这些我们正在设计的网路协议仍然有许多严重的问题时，真的是非常有趣，并且听说现在有不同的方式来设计它们。

当然也确实是这样 —— 网络硬件以及和速度相关的任何设备，以及人们使用网络来干的各种事情（例如观看 Netflix 的节目）等等，一直都在随着时间发生着改变，所以正因为这样，我们需要为 2018 年的互联网而不是为 1988 年的互联网设计我们不同的算法。

---

via: <https://jvns.ca/blog/2018/07/12/netdev-day-2--moving-away-from--as-fast-as-possible/>

作者：[Julia Evans](https://jvns.ca/about) 译者：[FSSlc](https://github.com/FSSlc) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


盘点 Python 的目标受众

Summary: Python 是为谁设计的？

Python 是为谁设计的？

![](/data/attachment/album/201808/20/233621ldmjdlztketwtwtk.jpg)

Python 是为谁设计的？

几年前，我在 python-dev 邮件列表中，以及在活跃的 CPython 核心开发人员和认为参与这一过程不是有效利用个人时间和精力的人中[强调](http://www.curiousefficiency.org/posts/2011/04/musings-on-culture-of-python-dev.html)说，“CPython 的发展太快了也太慢了” 是很多冲突的原因之一。

我一直认为事实确实如此，但这也是一个要点，在这几年中我也花费了很多时间去反思它。在我写那篇文章的时候，我还在波音防务澳大利亚公司（Boeing Defence Australia）工作。下个月，我离开了波音进入红帽亚太（Red Hat Asia-Pacific），并且开始在大企业的[开源供应链管理](http://community.redhat.com/blog/2015/02/the-quid-pro-quo-of-open-infrastructure/)方面取得了<ruby> 再分发者 <rt>  redistributor </rt></ruby>层面的视角。

### Python 的参考解析器使用情况

我尝试将 CPython 的使用情况分解如下，尽管看起来有些过于简化（注意，这些分类的界线并不是很清晰，他们仅关注于考虑新软件特性和版本发布后不同因素的影响）：

* 教育类：教育工作者的主要兴趣在于建模方法的教学和计算操作方面，*不会去*编写或维护生产级别的软件。例如：
	+ 澳大利亚的[数字课程](https://aca.edu.au/#home-unpack)
	+ Lorena A. Barba 的 [AeroPython](https://github.com/barbagroup/AeroPython)
* 个人类的自动化和爱好者的项目：主要且经常是一类自写自用的软件。例如：
	+ my Digital Blasphemy [图片下载器](https://nbviewer.jupyter.org/urls/bitbucket.org/ncoghlan/misc/raw/default/notebooks/Digital%20Blasphemy.ipynb)
	+ Paul Fenwick 的 (Inter)National [Rick Astley Hotline](https://github.com/pjf/rickastley)
* <ruby> 组织 <rt>  organisational </rt></ruby>过程自动化：主要且经常是为组织利益而编写的。例如：
	+ CPython 的[核心工作流工具](https://github.com/python/core-workflow)
	+ Linux 发行版的开发、构建和发行管理工具
* “<ruby> 一劳永逸 <rt>  Set-and-forget </rt></ruby>” 的基础设施：这类软件在其生命周期中几乎不会升级，但在底层平台可能会升级（这种说法有时候有些争议）。例如：
	+ 大多数自我管理的企业或机构的基础设施（在那些资金充足的可持续工程计划中，这种情况是让人非常不安的）
	+ 拨款资助的软件（当最初的拨款耗尽时，维护通常会终止）
	+ 有严格认证要求的软件（如果没有绝对必要的话，从经济性考虑，重新认证比常规升级来说要昂贵很多）
	+ 没有自动升级功能的嵌入式软件系统
* 持续升级的基础设施：具有健壮支撑的工程学模型的软件，对于依赖和平台升级通常是例行的，不必关心源码变更。例如：
	+ Facebook 的 Python 服务基础设施
	+ 滚动发布的 Linux 分发版
	+ 大多数的公共 PaaS 无服务器环境（Heroku、OpenShift、AWS Lambda、Google Cloud Functions、Azure Cloud Functions 等等）
* 长周期性升级的标准的操作环境：对其核心组件进行常规升级，但这些升级以年为单位进行，而不是周或月。例如：
	+ [VFX 平台](http://www.vfxplatform.com/)
	+ 长期支持（LTS）的 Linux 分发版
	+ CPython 和 Python 标准库
	+ 基础设施管理和编排工具（如 OpenStack、Ansible）
	+ 硬件控制系统
* 短生命周期的软件：软件仅被使用一次，然后就丢弃或忽略，而不是随后接着升级。例如：
	+ <ruby> 临时 <rt>  Ad hoc </rt></ruby>自动化脚本
	+ 被确定为 “终止” 的单用户游戏（你玩了一次后，甚至都忘了去卸载它们，或许在一个新的设备上都不打算再去安装了）
	+ 不具备（或不完整）状态保存的单用户游戏（如果你卸载并重安装它们，游戏体验也不会有什么大的变化）
	+ 特定事件的应用程序（这些应用程序与特定的事件捆绑，一旦事件结束，这些应用程序就不再有用了）
* 常规用途的应用程序：部署后定期升级的软件。例如：
	+ 业务管理软件
	+ 个人和专业的生产力应用程序（如 Blender）
	+ 开发工具和服务（如 Mercurial、Buildbot、Roundup）
	+ 多用户游戏，和其它明显处于持续状态还没有被定义为 “终止” 的游戏
	+ 有自动升级功能的嵌入式软件系统
* 共享的抽象层：在一个特定的问题领域中，设计用于让工作更高效的软件组件。即便是你没有亲自掌握该领域的所有错综复杂的东西。例如：
	+ 大多数的<ruby> 运行时 <rt>  runtime </rt></ruby>库和框架都归入这一类（如 Django、Flask、Pyramid、SQL Alchemy、NumPy、SciPy、requests)
	+ 适合归入这一类的许多测试和类型推断工具（如 pytest、Hypothesis、vcrpy、behave、mypy）
	+ 其它应用程序的插件（如 Blender plugins、OpenStack hardware adapters）
	+ 本身就代表了 “Python 世界” 基准的标准库（那是一个[难以置信的复杂](http://www.curiousefficiency.org/posts/2015/10/languages-to-improve-your-python.html#broadening-our-horizons)的世界观）

### CPython 主要服务于哪些受众？

从根本上说，CPython 和标准库的主要受众是哪些呢？是那些不管出于什么原因，将有限的标准库和从 PyPI 显式声明安装的第三方库组合起来所提供的服务还不能够满足需求的那些人。

为了更进一步简化上面回顾的不同用法和部署模型，宏观地将最大的 Python 用户群体分开来看，一类是在一些感兴趣的环境中将 Python 作为一种 *脚本语言* 使用的人；另外一种是将它用作一个 *应用程序开发语言* 的人，他们最终发布的是一种产品而不是他们的脚本。

把 Python 作为一种脚本语言来使用的开发者的典型特性包括：

* 主要的工作单元是由一个 Python 文件组成的（或 Jupyter notebook)，而不是一个 Python 和元数据文件的目录
* 没有任何形式的单独的构建步骤 —— 是*作为*一个脚本分发的，类似于分发一个独立的 shell 脚本的方式
* 没有单独的安装步骤（除了下载这个文件到一个合适的位置），因为在目标系统上要求预配置运行时环境
* 没有显式的规定依赖关系，除了最低的 Python 版本，或一个预期的运行环境声明。如果需要一个标准库以外的依赖项，他们会通过一个环境脚本去提供（无论是操作系统、数据分析平台、还是嵌入 Python 运行时的应用程序）
* 没有单独的测试套件，使用 “通过你给定的输入，这个脚本是否给出了你期望的结果?” 这种方式来进行测试
* 如果在执行前需要测试，它将以<ruby> 试运行 <rt>  dry run> </rt></ruby>和<ruby> 预览 <rt>  preview </rt></ruby>模式来向用户展示软件*将*怎样运行
* 如果使用静态代码分析工具，则通过集成到用户的软件开发环境中，而不是为每个脚本单独设置

相比之下，使用 Python 作为一个应用程序开发语言的开发者特征包括：

* 主要的工作单元是由 Python 和元数据文件组成的目录，而不是单个 Python 文件
* 在发布之前有一个单独的构建步骤去预处理应用程序，哪怕是把它的这些文件一起打包进一个 Python sdist、wheel 或 zipapp 中
* 应用程序是否有独立的安装步骤做预处理，取决于它是如何打包的，和支持的目标环境
* 外部的依赖明确存在于项目目录下的一个元数据文件中，要么是直接在项目目录中（如 `pyproject.toml`、`requirements.txt`、`Pipfile`），要么是作为生成的发行包的一部分（如 `setup.py`、`flit.ini`）
* 有独立的测试套件，或者作为一个 Python API 的一个单元测试，或者作为功能接口的集成测试，或者是两者都有
* 静态分析工具的使用是在项目级配置的，并作为测试管理的一部分，而不是作为依赖

作为以上分类的一个结果，CPython 和标准库的主要用途是，在相应的 CPython 特性发布后，为教育和<ruby> 临时 <rt>  ad hoc </rt></ruby>的 Python 脚本环境提供 3-5 年基础维护服务。

对于临时脚本使用的情况，这个 3-5 年的延迟是由于再分发者给用户开发新版本的延迟造成的，以及那些再分发版本的用户们花在修改他们的标准操作环境上的时间。

对于教育环境中的情况是，教育工作者需要一些时间去评估新特性，然后决定是否将它们包含进教学的课程中。

### 这些相关问题的原因是什么？

这篇文章很大程度上是受 Twitter 上对[我的这个评论](https://twitter.com/ncoghlan_dev/status/916994106819088384)的讨论的启发，它援引了定义在 [PEP 411](https://www.python.org/dev/peps/pep-0411/) 中<ruby> 临时 <rt>  Provisional </rt></ruby> API 的情形，作为一个开源项目的例子，对用户发出事实上的邀请，请其作为共同开发者去积极参与设计和开发过程，而不是仅被动使用已准备好的最终设计。

这些回复包括一些在更高级别的库中支持临时 API 的困难程度的一些沮丧性表述，没有这些库做临时状态的传递，因此而被限制为只有临时 API 的最新版本才支持这些相关特性，而不是任何早期版本的迭代。

我的[主要回应](https://twitter.com/ncoghlan_dev/status/917092464355241984)是，建议开源提供者应该强制实施有限支持，通过这种强制的有限支持可以让个人的维护努力变得可持续。这意味着，如果对临时 API 的老版本提供迭代支持是非常痛苦的，那么，只有在项目开发人员自己需要、或有人为此支付费用时，他们才会去提供支持。这与我的这个观点是类似的，那就是，志愿者提供的项目是否应该免费支持老的、商业性质的、长周期的 Python 版本，这对他们来说是非常麻烦的事，我[不认为他们应该这样做](http://www.curiousefficiency.org/posts/2015/04/stop-supporting-python26.html)，正如我所期望的那样，大多数这样的需求都来自于管理差劲的惯性，而不是真正的需求（真正的需求，应该去支付费用来解决问题）。

而我的[第二个回应](https://twitter.com/ncoghlan_dev/status/917088410162012160)是去实现这一点，尽管多年来一直在讨论这个问题（比如，在上面链接中最早在 2011 年的一篇的文章中，以及在 Python 3 问答的回复中的[这里](http://python-notes.curiousefficiency.org/en/latest/python3/questions_and_answers.html#wouldn-t-a-python-2-8-release-help-ease-the-transition)、[这里](http://python-notes.curiousefficiency.org/en/latest/python3/questions_and_answers.html#doesn-t-this-make-python-look-like-an-immature-and-unstable-platform)、和[这里](http://python-notes.curiousefficiency.org/en/latest/python3/questions_and_answers.html#what-about-insert-other-shiny-new-feature-here)，以及去年的这篇文章 [Python 包生态系统](http://www.curiousefficiency.org/posts/2016/09/python-packaging-ecosystem.html)中也提到了一些)，但我从来没有真实地尝试直接去解释它在标准库设计过程中的影响。

如果没有这些背景，设计过程中的一部分，比如临时 API 的引入，或者是<ruby> 受启发而不同于它 <rt>  inspired-by-not-the-same-as </rt></ruby>的引入，看起来似乎是完全没有意义的，因为他们看起来似乎是在尝试对 API 进行标准化，而实际上并没有。

### 适合进入 PyPI 规划的方面有哪些？

*任何*提交给 python-ideas 或 python-dev 的提案所面临的第一个门槛就是清楚地回答这个问题：“为什么 PyPI 上的模块不够好？”。绝大多数的提案都在这一步失败了，为了通过这一步，这里有几个常见的话题：

* 比起下载一个合适的第三方库，新手一般可能更倾向于从互联网上 “复制粘贴” 错误的指导。（这就是为什么存在 `secrets` 库的原因：它使得人们很少去使用 `random` 模块，由于安全敏感的原因，它预期用于游戏和模拟统计）
* 该模块旨在提供一个参考实现，并允许与其它的竞争实现之间提供互操作性，而不是对所有人的所有事物都是必要的。（如 `asyncio`、`wsgiref`、`unittest`、和 `logging` 都是这种情况）
* 该模块是预期用于标准库的其它部分（如 `enum` 就是这种情况，像 `unittest` 一样）
* 该模块是被设计用于支持语言之外的一些语法（如 `contextlib`、`asyncio` 和 `typing`）
* 该模块只是普通的临时的脚本用途（如 `pathlib` 和 `ipaddress`）
* 该模块被用于一个教育环境（例如，`statistics` 模块允许进行交互式地探索统计的概念，尽管你可能根本就不会用它来做完整的统计分析）

只通过了前面的 “PyPI 是不是明显不够好” 的检查，一个模块还不足以确保被纳入标准库中，但它已经足以将问题转变为 “在未来几年中，你所推荐的要包含的库能否对一般的入门级 Python 开发人员的经验有所提升？”

标准库中的 `ensurepip` 和 `venv` 模块的引入也明确地告诉再分发者，我们期望的 Python 级别的打包和安装工具在任何平台的特定分发机制中都予以支持。

### 当添加它们到标准库中时，为什么一些 API 会被修改？

现有的第三方模块有时候会被批量地采用到标准库中，在其它情况下，实际上添加的是吸收了用户对现有 API 体验之后进行重新设计和重新实现的 API，但是会根据另外的设计考虑和已经成为其中一部分的语言实现参考来进行一些删除或细节修改。

例如，与流行的第三方库 `path.py`、`pathlib` 的前身不同，它们并*没有*定义字符串子类，而是以独立的类型替代。作为解决文件互操作性问题的结果，定义了文件系统路径协议，它允许使用文件系统路径的接口去使用更多的对象。

为了在 “IP 地址” 这个概念的教学上提供一个更好的工具，`ipaddress` 模块设计调整为明确地将主机接口定义与地址和网络的定义区分开（IP 地址被关联到特定的 IP 网络)，而最原始的 `ipaddr` 模块中，在网络术语的使用方式上不那么严格。

另外的情况是，标准库将综合多种现有的方法的来构建，以及为早已存在的库定义 API 时，还有可能依赖不存在的语法特性。比如，`asyncio` 和 `typing` 模块就全部考虑了这些因素，虽然在 PEP 557 中正在考虑将后者所考虑的因素应用到 `dataclasses` API 上。（它可以被总结为 “像属性一样，但是使用可变注释作为字段声明”）。

这类修改的原理是，这类库不会消失，并且它们的维护者对标准库维护相关的那些限制通常并不感兴趣（特别是相对缓慢的发布节奏）。在这种情况下，在标准库文档的更新版本中使用 “See Also” 链接指向原始模块的做法非常常见，尤其是在第三方版本额外提供了标准库模块中忽略的那些特性时。

### 为什么一些 API 是以临时的形式被添加的？

虽然 CPython 维护了 API 的弃用策略，但在没有正当理由的情况下，我们通常不会去使用该策略（在其他项目试图与 Python 2.7 保持兼容性时，尤其如此）。

然而在实践中，当添加这种受已有的第三方启发而不是直接精确拷贝第三方设计的新 API 时，所承担的风险要高于一些正常设计决定可能出现问题的风险。

当我们考虑到这种改变的风险比平常要高，我们将相关的 API 标记为临时，表示保守的终端用户要避免完全依赖它们，而共享抽象层的开发者可能希望对他们准备去支持的那个临时 API 的版本考虑实施比平时更严格的限制。

### 为什么只有一些标准库 API 被升级？

这里简短的回答得到升级的主要 API 有哪些：

* 不太可能有大量的外部因素干扰的附加更新的
* 无论是对临时脚本用例还是对促进将来多个第三方解决方案之间的互操作性，都有明显好处的
* 对这方面感兴趣的人提交了一个可接受的建议的

如果在将模块用于应用程序开发目的时（如 `datetime`），现有模块的限制主要是显而易见的，如果再分发者通过第三方方案很容易地实现了改进，（如 `requests`），或者如果标准库的发布节奏与所需要的包之间真的存在冲突，（如 `certifi`），那么，建议对标准库版本进行改变的因素将显著减少。

从本质上说，这和上面关于 PyPI 问题正好相反：因为从应用程序开发人员体验的角度来说，PyPI 的分发机制通常已经够好了，这种分发方式的改进是有意义的，允许再分发者和平台提供者自行决定将哪些内容作为他们缺省提供的一部分。

假设在 3-5 年时间内，缺省出现了被认为是改变带来的可感知的价值时，才会将这些改变纳入到 CPython 和标准库中。

### 标准库任何部分都有独立的版本吗？

是的，就像是 `ensurepip` 使用的捆绑模式（CPython 发行了一个 `pip` 的最新捆绑版本，而并没有把它放进标准库中），将来可能被应用到其它模块中。

最有可能的第一个候选者是 `distutils` 构建系统，因为切换到这种模式将允许构建系统在多个发行版本之间保持一致。

这种处理方式的其它可能候选者是 Tcl/Tk 图形套件和 IDLE 编辑器，它们已经被拆分，并且一些开发者将其改为可选安装项。

### 这些注意事项为什么很重要？

从本质上说，那些积极参与开源开发的人就是那些致力于开源应用程序和共享抽象层的人。

那些写一些临时脚本或为学生设计一些教学习题的人，通常不认为他们是软件开发人员 —— 他们是教师、系统管理员、数据分析人员、金融工程师、流行病学家、物理学家、生物学家、市场研究员、动画师、平面设计师等等。

对于一种语言，当我们全部的担心都是开发人员的经验时，那么我们就可以根据人们所知道的内容、他们使用的工具种类、他们所遵循的开发流程种类、构建和部署他们软件的方法等假定，来做大量的简化。

当应用程序运行时作为脚本引擎广泛流行时，事情会变得更加复杂。做好任何一项工作已经很困难，并且作为单个项目的一部分来平衡两个受众的需求会导致双方经常不理解和不相信。

这篇文章不是为了说明我们在开发 CPython 过程中从来没有做出过不正确的决定 —— 它只是去合理地回应那些对添加到 Python 标准库中的看上去很荒谬的特性的质疑，它将是 “我不是那个特性的预期目标受众的一部分”，而不是 “我对它没有兴趣，因此它对所有人都是毫无用处和没有价值的，添加它纯属骚扰我”。

---

via: <http://www.curiousefficiency.org/posts/2017/10/considering-pythons-target-audience.html>

作者：[Nick Coghlan](http://www.curiousefficiency.org/pages/about.html) 译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)、[pityonline](https://github.com/pityonline)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


在 Linux 中使用 top 命令的建议

Summary: 通过这篇教程提升你的 top 命令的知识。

最近看到这个知识点，文章很详细，感谢。

通过这篇教程提升你的 top 命令的知识。

>
> 通过这篇教程提升你的 `top` 命令的知识。
>
>
>

![](/data/attachment/album/201808/21/085602sp8lg15nxbp9l18b.jpg)

尝试找出你的机器正在运行什么程序，以及哪个进程耗尽了内存导致系统非常非常慢 —— 这是 `top` 命令所能胜任的工作。

`top` 是一个非常有用的程序，其作用类似于 Windows 任务管理器或 MacOS 的活动监视器。在 \*nix 机器上运行 `top` 将实时显示系统上运行的进程的情况。

```
$ top
```

取决于你运行的 `top` 版本，你会看到类似如下内容：

```
top - 08:31:32 up 1 day,  4:09,  0 users,  load average: 0.20, 0.12, 0.10
Tasks:   3 total,   1 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.5 us,  0.3 sy,  0.0 ni, 99.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   4042284 total,  2523744 used,  1518540 free,   263776 buffers
KiB Swap:  1048572 total,        0 used,  1048572 free.  1804264 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
    1 root      20   0   21964   3632   3124 S   0.0  0.1   0:00.23 bash
  193 root      20   0  123520  29636   8640 S   0.0  0.7   0:00.58 flask
  195 root      20   0   23608   2724   2400 R   0.0  0.1   0:00.21 top
```

你所用的 `top` 版本可能跟这个看起来不一样，特别是在显示的列上。

### 如何阅读输出的内容

你可以根据输出判断你正在运行的内容，但尝试去解释结果你可能会有些困惑。

前几行包含一堆统计信息（详细信息），后跟一个包含结果列的表（列）。让我们从后者开始吧。

#### 列

这些是系统正在运行的进程。默认按 CPU 使用率降序排序。这意味着在列表顶部的程序正使用更多的 CPU 资源并对你的系统造成更重的负担。对于资源使用而言，这些程序是字面上的消耗资源最多的（top）进程。不得不说，`top` 这个名字起得很妙。

最右边的 `COMMAND` 一列报告进程名（启动它们的命令）。在这个例子里，进程名是 `bash`（一个我们正在运行 `top` 的命令解释器）、`flask`（一个 Python 写的 web 框架）和 `top` 自身。

其它列提供了关于进程的有用信息：

* `PID`：进程 ID，一个用来定位进程的唯一标识符
* `USER`：运行进程的用户
* `PR`：任务的优先级
* `NI`：Nice 值，优先级的一个更好的表现形式
* `VIRT`：虚拟内存的大小，单位是 KiB（kibibytes）
* `RES`：常驻内存大小，单位是 KiB（物理内存和虚拟内存的一部分）
* `SHR`：共享内存大小，单位是 KiB（共享内存和虚拟内存的一部分）
* `S`：进程状态，一般 **I** 代表空闲，**R** 代表运行，**S** 代表休眠，**Z** 代表僵尸进程，**T** 或 **t** 代表停止（还有其它更少见的选项）
* `%CPU`：自从上次屏幕更新后的 CPU 使用率
* `%MEM`：自从上次屏幕更新后的 `RES` 常驻内存使用率
* `TIME+`：自从程序启动后总的 CPU 使用时间
* `COMMAND`：启动命令，如之前描述那样

确切知道 `VIRT`，`RES` 和 `SHR` 值代表什么在日常操作中并不重要。重要的是要知道 `VIRT` 值最高的进程就是内存使用最多的进程。当你在用 `top` 排查为什么你的电脑运行无比卡的时候，那个 `VIRT` 数值最大的进程就是元凶。如果你想要知道共享内存和物理内存的确切意思，请查阅 [top 手册](http://man7.org/linux/man-pages/man1/top.1.html)的 Linux Memory Types 段落。

是的，我说的是 kibibytes 而不是 kilobytes。通常称为 kilobyte 的 1024 值实际上是 kibibyte。希腊语的 kilo（χίλιοι）意思是一千（例如一千米是 1000 米，一千克是 1000 克）。Kibi 是 kilo 和 binary 的合成词，意思是 1024 字节（或者 2<sup> 10</sup> ）。但是，因为这个词很难说，所以很多人在说 1024 字节的时候会说 kilobyte。`top` 试图在这里使用恰当的术语，所以按它说的理解就好。

#### 屏幕更新说明

实时屏幕更新是 Linux 程序可以做的 **非常酷** 的事之一。这意味着程序能实时更新它们显示的内容，所以看起来是动态的，即使它们用的是文本。非常酷！在我们的例子中，更新时间间隔很重要，因为一些统计数据（`%CPU` 和 `%MEM`）是基于上次屏幕更新的数值的。

因为我们运行在一个持久性的程序中，我们就可以输入一些命令来实时修改配置（而不是停止应用，然后用一个不同的命令行选项再次运行）。

按下 `h` 调用帮助界面，该界面也显示了默认延迟（屏幕更新的时间间隔）。这个值默认（大约）是 3 秒，但你可以输入 `d`（大概是 delay 的意思）或者 `s`（可能是 screen 或 seconds 的意思）来修改它。

#### 细节

在进程列表上面有一大堆有用的信息。有些细节看起来有点儿奇怪，让人困惑。但是一旦你花点儿时间来逐个过一遍，你会发现，在紧要关头，这些是非常有用的。

第一行包含系统的大致信息：

* `top`：我们正在运行 `top`！你好！`top`！
* `XX:YY:XX`：当前时间，每次屏幕更新的时候更新
* `up`（接下去是 `X day, YY:ZZ`）：系统的 [uptime](https://en.wikipedia.org/wiki/Uptime)，或者自从系统启动后已经过去了多长时间
* `load average`（后跟三个数字）：分别是过去一分钟、五分钟、15 分钟的[系统负载](https://en.wikipedia.org/wiki/Load_(computing))

第二行（`Task`）显示了正在运行的任务的信息，不用解释。它显示了进程总数和正在运行的、休眠中的、停止的进程数和僵尸进程数。这实际上是上述 `S`（状态）列的总和。

第三行（`%Cpu(s)`）显示了按类型划分的 CPU 使用情况。数据是屏幕刷新之间的值。这些值是：

* `us`：用户进程
* `sy`：系统进程
* `ni`：[nice](https://en.wikipedia.org/wiki/Nice_(Unix)#Etymology) 用户进程
* `id`：CPU 的空闲时间，这个值比较高时说明系统比较空闲
* `wa`：等待时间，或者消耗在等待 I/O 完成的时间
* `hi`：消耗在硬件中断的时间
* `si`：消耗在软件中断的时间
* `st`：“虚拟机管理程序从该虚拟机窃取的时间”

你可以通过点击 `t`（toggle）来展开或折叠 `Task` 和 `%Cpu(s)` 行。

第四行（`Kib Mem`）和第五行（`KiB Swap`）提供了内存和交换空间的信息。这些数值是：

* 总内存容量
* 已用内存
* 空闲内存
* 内存的缓冲值
* 交换空间的缓存值

默认它们是用 KiB 为单位展示的，但是按下 `E`（扩展内存缩放 extend memory scaling）可以轮换不同的单位：KiB、MiB、GiB、TiB、PiB、EiB（kilobytes、megabytes、gigabytes、terabytes、petabytes 和 exabytes）

`top` 用户手册有更多选项和配置项信息。你可以运行 `man top` 来查看你系统上的文档。还有很多 [HTML 版的 man 手册](http://man7.org/linux/man-pages/man1/top.1.html)，但是请留意，这些手册可能是针对不同 top 版本的。

### 两个 top 的替代品

你不必总是用 `top` 查看系统状态。你可以根据你的情况用其它工具来协助排查问题，尤其是当你想要更图形化或更专业的界面的时候。

#### htop

`htop` 很像 `top`，但是它带来了一些非常有用的东西：它可以以图形界面展示 CPU 和内存使用情况。

![](/data/attachment/album/201808/21/085623piirpzvx4rpxpyry.png)

这是我们在刚才运行 `top` 的同一环境中 `htop` 的样子。显示更简洁，但功能却很丰富。

任务统计、负载、uptime 和进程列表仍然在，但是它有了漂亮、彩色、动态的每核 CPU 使用情况，还有图形化的内存使用情况。

以下是不同颜色的含义（你也可以通过按下 `h` 来获得这些信息的帮助）。

CPU 任务优先级或类型：

* 蓝色：低优先级
* 绿色：正常优先级
* 红色：内核任务
* 蓝色：虚拟任务
* 条状图末尾的值是已用 CPU 的百分比

内存：

* 绿色：已经使用的内存
* 蓝色：缓冲的内存
* 黄色：缓存内存
* 条状图末尾的值显示已用内存和总内存

如果颜色对你没用，你可以运行 `htop -C` 来禁用它们；那样 `htop` 将使用不同的符号来展示 CPU 和内存类型。

它的底部有一组激活的快捷键提示，可以用来操作过滤结果或改变排序顺序。试着按一些快捷键看看它们能做什么。不过尝试 `F9` 时要小心，它会调出一个信号列表，这些信号会杀死（即停止）一个过程。我建议在生产环境之外探索这些选项。

`htop` 的作者 Hisham Muhammad（是的，`htop` 的名字就是源自 Hisham 的）在二月份的 [FOSDEM 2018](https://fosdem.org/2018/schedule/event/htop/) 做了一个[简短的演讲](https://www.youtube.com/watch?v=L25waVhy78o)。他阐述了 `htop` 不仅有简洁的图形界面，还有更现代的进程信息统计展示方式，这都是之前的工具（如 `top`）所不具备的。

你可以在[手册页面](https://linux.die.net/man/1/htop)或 [htop 网站](https://hisham.hm/htop/index.php)阅读更多关于 `htop` 的信息。（提示：网站背景是一个动态的 `htop`。）

#### docker stats

如果你在用 Docker，你可以运行 `docker stats` 来为容器状态生成一个有丰富上下文的界面。

这可能比 `top` 更有帮助，因为它不是按进程分类，而是按容器分类的。这点特别有用，当某个容器运行缓慢时，查看哪个容器耗资源最多比运行 `top` 再找到容器的进程要快。

借助于上面对 `top` 和 `htop` 术语的解释，你应该会更容易理解 `docker stats` 中的那些。然而，[docker stats 文档](https://docs.docker.com/engine/reference/commandline/stats/)对每一列都提供了详尽的描述。

---

via: <https://opensource.com/article/18/8/top-tips-speed-up-computer>

作者：[Katie McLaughlin](https://opensource.com/users/glasnt) 选题：[lujun9972](https://github.com/lujun9972) 译者：[ypingcn](https://github.com/ypingcn) 校对：[pityonline](https://github.com/pityonline)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


用于与非 Linux 用户一同工作的 Linux 命令行工具

用desktop版tg / session / matrix

>
> 如果你在 Linux 终端工作，那么与非 Linux 用户一同工作时可能遇到困难。这些工具有助于文档兼容性和企业即时消息。
>
>
>

![](/data/attachment/album/201808/21/093640ox5xzq4lddz33d2a.jpg)

我大部分时间都在使用 Shell（命令行、终端或其它随便什么你使用的平台）上。但是，当我需要与大量其他人合作时，这可能会有点挑战，特别是在大型企业公司中 —— 除了 shell 外其他都使用。

当公司内的其他人使用与你不同的平台时，问题就会变得更加严重。我倾向于使用 Linux。如果我在 Linux 终端上做了很多日常工作，而我的大多数同事都使用 Windows 10（完全使用 GUI 端），那么事情就会变得……有问题。

* **Network World 上另外一篇文章：**[**11 个没用但很酷的 Linux 终端技巧**](http://www.networkworld.com/article/2926630/linux/11-pointless-but-awesome-linux-terminal-tricks.html#tk.nww-fsb)

幸运的是，在过去的几年里，我已经想出如何处理这些问题。我已经找到了在非 Unix 的企业环境中使用 Linux（或其他类 Unix 系统）Shell 的方法。这些工具/技巧同样适用于在公司服务器上工作的系统管理员们，就像对开发人员或营销人员一样。

让我们首先关注对于大公司中的许多人来说似乎最难解决的两个方面：文档兼容性和企业即时消息。

### Linux 和非 Linux 系统之间的文档兼容性

出现的最大问题之一是简单的文字处理文档的兼容性。

假设你的公司已在 Microsoft Office 上进行了标准化。这让你难过。但不要失去希望！有很多方法可以使它（基本）可用 —— 甚至在 shell 中。

两个工具在我的武器库中至关重要：[Pandoc](https://www.youtube.com/watch?v=BkTYHChkDoE) 和 [Wordgrinder](https://www.youtube.com/watch?v=WnMyamBgKFE)。

Wordgrinder 是一个简单、直观的文字处理器。它可能不像 LibreOffice 那样功能齐全（或者，实际上，任何主要的 GUI 文字处理应用程序），但速度很快。它很稳定。它有足够的功能（和文件格式）来完成工作。事实上，我完全在 Wordgrinder 中写了我的大部分文章和书籍。

但是有一个问题（你知道肯定会有）。

Wordgrinder 不支持 .doc（或 .docx）文件。这意味着它无法读取使用 Windows 和 MS Office 的同事发送给你的大多数文件。

这就是 Pandoc 的用武之地。它是一个简单的文档转换器，可以将各种文件作为输入（MS Word、LibreOffice、HTML、markdown 等）并将它们转换为其他内容。它支持的格式数量绝对是惊人的 —— PDF、ePub、各种幻灯片格式。它确实使格式之间的文档转换变得轻而易举。

这并不是说我不会偶尔遇到格式或功能问题。要转换有大量自定义格式、某些脚本和嵌入式图表的 Word 文档？是的，在这个过程中会丢失很多。

但实际上，Pandoc（用于转换文件）和 Wordgrinder（用于文档编辑）的组合已经证明非常有用和强大。

### Linux 和非 Linux 系统之间的企业即时消息传递

每家公司都喜欢在即时通讯系统上实现标准化 —— 所有员工都可以使用它来保持实时联系。

在命令行中，这可能会变得棘手。如果贵公司使用 Google 环聊怎么办？或者 Novell GroupWise Messenger 怎么样？既没有官方支持，也没有基于终端的客户端。

谢天谢地，还有 [Finch 和 Hangups](https://www.youtube.com/watch?v=19lbWnYOsTc)。

Finch 是 Pidgin（开源，多协议消息客户端）的终端版本。它支持各种协议，包括 Novell GroupWise、（很快会死的）AOL Instant Messenger 以及其他一些协议。

而 Hangups 是 Google Hangouts 客户端的开源实现 —— 包含消息历史记录和精美的标签界面。

这些方案都不会为你提供语音或视频聊天功能，但对于基于文本的消息，它们的工作得非常好。它们并不完美（Finch 的用户界面需要时间习惯），但它们肯定足以与你的同事保持联系。

这些方案能否让你在纯文本 shell 中舒适地过完一个工作日？可能不会。就个人而言，我发现（使用这些工具和其他工具）我可以轻松地将 80％ 的时间花在纯文本界面上。

这感觉很棒。

---

via: <https://www.networkworld.com/article/3235688/linux/linux-command-line-tools-for-working-with-non-linux-users.html>

作者：[Bryan Lunduke](https://www.networkworld.com/author/Bryan-Lunduke/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出