如何写好 C main 函数

良好的编程习惯很重要

SYSSEGV  这个是书写错误吗， SIGSEGV  ？

有可能不是笔误，但是我目前没找到更多资料。

据我初步了解，此处是指 SYSSEGV 这种段错误，但是如果是说信号，则叫做  SIGSEGV。估计是作者写糊涂了。

受教了

来自广东深圳的 Microsoft Edge 18.18363|Windows 10 用户

>
> 学习如何构造一个 C 文件并编写一个 C main 函数来成功地处理命令行参数。
>
>
>

![](/data/attachment/album/201906/08/211422umrzznnvmapcwuc3.jpg)

我知道，现在孩子们用 Python 和 JavaScript 编写他们的疯狂“应用程序”。但是不要这么快就否定 C 语言 —— 它能够提供很多东西，并且简洁。如果你需要速度，用 C 语言编写可能就是你的答案。如果你正在寻找稳定的职业或者想学习如何捕获[空指针解引用](https://www.owasp.org/index.php/Null_Dereference)，C 语言也可能是你的答案！在本文中，我将解释如何构造一个 C 文件并编写一个 C main 函数来成功地处理命令行参数。

我：一个顽固的 Unix 系统程序员。

你：一个有编辑器、C 编译器，并有时间打发的人。

让我们开工吧。

### 一个无聊但正确的 C 程序

![Parody O'Reilly book cover, "Hating Other People's Code"](/data/attachment/album/201906/08/211510o46awqki5uwc8rd9.png "Parody O'Reilly book cover, \"Hating Other People's Code\"")

C 程序以 `main()` 函数开头，通常保存在名为 `main.c` 的文件中。

```
/* main.c */
int main(int argc, char *argv[]) {

}
```

这个程序可以*编译*但不*干*任何事。

```
$ gcc main.c
$ ./a.out -o foo -vv
$
```

正确但无聊。

### main 函数是唯一的。

`main()` 函数是开始执行时所执行的程序的第一个函数，但不是第一个执行的函数。*第一个*函数是 `_start()`，它通常由 C 运行库提供，在编译程序时自动链入。此细节高度依赖于操作系统和编译器工具链，所以我假装没有提到它。

`main()` 函数有两个参数，通常称为 `argc` 和 `argv`，并返回一个有符号整数。大多数 Unix 环境都希望程序在成功时返回 `0`（零），失败时返回 `-1`（负一）。

| 参数 | 名称 | 描述 |
| --- | --- | --- |
| `argc` | 参数个数 | 参数向量的个数 |
| `argv` | 参数向量 | 字符指针数组 |

参数向量 `argv` 是调用你的程序的命令行的标记化表示形式。在上面的例子中，`argv` 将是以下字符串的列表：

```
argv = [ "/path/to/a.out", "-o", "foo", "-vv" ];
```

参数向量在其第一个索引 `argv[0]` 中确保至少会有一个字符串，这是执行程序的完整路径。

### main.c 文件的剖析

当我从头开始编写 `main.c` 时，它的结构通常如下：

```
/* main.c */
/* 0 版权/许可证 */
/* 1 包含 */
/* 2 定义 */
/* 3 外部声明 */
/* 4 类型定义 */
/* 5 全局变量声明 */
/* 6 函数原型 */

int main(int argc, char *argv[]) {
/* 7 命令行解析 */
}

/* 8 函数声明 */
```

下面我将讨论这些编号的各个部分，除了编号为 0 的那部分。如果你必须把版权或许可文本放在源代码中，那就放在那里。

另一件我不想讨论的事情是注释。

```
“评论谎言。”
- 一个愤世嫉俗但聪明又好看的程序员。
```

与其使用注释，不如使用有意义的函数名和变量名。

鉴于程序员固有的惰性，一旦添加了注释，维护负担就会增加一倍。如果更改或重构代码，则需要更新或扩充注释。随着时间的推移，代码会变得面目全非，与注释所描述的内容完全不同。

如果你必须写注释，不要写关于代码正在做*什么*，相反，写下代码*为什么*要这样写。写一些你将要在五年后读到的注释，那时你已经将这段代码忘得一干二净。世界的命运取决于你。*不要有压力。*

#### 1、包含

我添加到 `main.c` 文件的第一个东西是包含文件，它们为程序提供大量标准 C 标准库函数和变量。C 标准库做了很多事情。浏览 `/usr/include` 中的头文件，你可以了解到它们可以做些什么。

`#include` 字符串是 [C 预处理程序](https://en.wikipedia.org/wiki/C_preprocessor)（cpp）指令，它会将引用的文件完整地包含在当前文件中。C 中的头文件通常以 `.h` 扩展名命名，且不应包含任何可执行代码。它只有宏、定义、类型定义、外部变量和函数原型。字符串 `<header.h>` 告诉 cpp 在系统定义的头文件路径中查找名为 `header.h` 的文件，它通常在 `/usr/include` 目录中。

```
/* main.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <sys/types.h>
```

这是我默认会全局包含的最小包含集合，它将引入：

| #include 文件 | 提供的东西 |
| --- | --- |
| stdio | 提供 `FILE`、`stdin`、`stdout`、`stderr` 和 `fprint()` 函数系列 |
| stdlib | 提供 `malloc()`、`calloc()` 和 `realloc()` |
| unistd | 提供 `EXIT_FAILURE`、`EXIT_SUCCESS` |
| libgen | 提供 `basename()` 函数 |
| errno | 定义外部 `errno` 变量及其可以接受的所有值 |
| string | 提供 `memcpy()`、`memset()` 和 `strlen()` 函数系列 |
| getopt | 提供外部 `optarg`、`opterr`、`optind` 和 `getopt()` 函数 |
| sys/types | 类型定义快捷方式，如 `uint32_t` 和 `uint64_t` |

#### 2、定义

```
/* main.c */
<...>

#define OPTSTR "vi:o:f:h"
#define USAGE_FMT  "%s [-v] [-f hexflag] [-i inputfile] [-o outputfile] [-h]"
#define ERR_FOPEN_INPUT  "fopen(input, r)"
#define ERR_FOPEN_OUTPUT "fopen(output, w)"
#define ERR_DO_THE_NEEDFUL "do_the_needful blew up"
#define DEFAULT_PROGNAME "george"
```

这在现在没有多大意义，但 `OPTSTR` 定义我这里会说明一下，它是程序推荐的命令行开关。参考 [getopt(3)](https://linux.die.net/man/3/getopt) man 页面，了解 `OPTSTR` 将如何影响 `getopt()` 的行为。

`USAGE_FMT` 定义了一个 `printf()` 风格的格式字符串，它用在 `usage()` 函数中。

我还喜欢将字符串常量放在文件的 `#define` 这一部分。如果需要，把它们收集在一起可以更容易地修正拼写、重用消息和国际化消息。

最后，在命名 `#define` 时全部使用大写字母，以区别变量和函数名。如果需要，可以将单词放连在一起或使用下划线分隔，只要确保它们都是大写的就行。

#### 3、外部声明

```
/* main.c */
<...>

extern int errno;
extern char *optarg;
extern int opterr, optind;
```

`extern` 声明将该名称带入当前编译单元的命名空间（即 “文件”），并允许程序访问该变量。这里我们引入了三个整数变量和一个字符指针的定义。`opt` 前缀的几个变量是由 `getopt()` 函数使用的，C 标准库使用 `errno` 作为带外通信通道来传达函数可能的失败原因。

#### 4、类型定义

```
/* main.c */
<...>

typedef struct {
  int           verbose;
  uint32_t      flags;
  FILE         *input;
  FILE         *output;
} options_t;
```

在外部声明之后，我喜欢为结构、联合和枚举声明 `typedef`。命名一个 `typedef` 是一种传统习惯。我非常喜欢使用 `_t` 后缀来表示该名称是一种类型。在这个例子中，我将 `options_t` 声明为一个包含 4 个成员的 `struct`。C 是一种空格无关的编程语言，因此我使用空格将字段名排列在同一列中。我只是喜欢它看起来的样子。对于指针声明，我在名称前面加上星号，以明确它是一个指针。

#### 5、全局变量声明

```
/* main.c */
<...>

int dumb_global_variable = -11;
```

全局变量是一个坏主意，你永远不应该使用它们。但如果你必须使用全局变量，请在这里声明，并确保给它们一个默认值。说真的，*不要使用全局变量*。

#### 6、函数原型

```
/* main.c */
<...>

void usage(char *progname, int opt);
int do_the_needful(options_t *options);
```

在编写函数时，将它们添加到 `main()` 函数之后而不是之前，在这里放函数原型。早期的 C 编译器使用单遍策略，这意味着你在程序中使用的每个符号（变量或函数名称）必须在使用之前声明。现代编译器几乎都是多遍编译器，它们在生成代码之前构建一个完整的符号表，因此并不严格要求使用函数原型。但是，有时你无法选择代码要使用的编译器，所以请编写函数原型并继续这样做下去。

当然，我总是包含一个 `usage()` 函数，当 `main()` 函数不理解你从命令行传入的内容时，它会调用这个函数。

#### 7、命令行解析

```
/* main.c */
<...>

int main(int argc, char *argv[]) {
    int opt;
    options_t options = { 0, 0x0, stdin, stdout };

    opterr = 0;

    while ((opt = getopt(argc, argv, OPTSTR)) != EOF)
       switch(opt) {
           case 'i':
              if (!(options.input = fopen(optarg, "r")) ){
                 perror(ERR_FOPEN_INPUT);
                 exit(EXIT_FAILURE);
                 /* NOTREACHED */
              }
              break;

           case 'o':
              if (!(options.output = fopen(optarg, "w")) ){
                 perror(ERR_FOPEN_OUTPUT);
                 exit(EXIT_FAILURE);
                 /* NOTREACHED */
              }
              break;

           case 'f':
              options.flags = (uint32_t )strtoul(optarg, NULL, 16);
              break;

           case 'v':
              options.verbose += 1;
              break;

           case 'h':
           default:
              usage(basename(argv[0]), opt);
              /* NOTREACHED */
              break;
       }

    if (do_the_needful(&options) != EXIT_SUCCESS) {
       perror(ERR_DO_THE_NEEDFUL);
       exit(EXIT_FAILURE);
       /* NOTREACHED */
    }

    return EXIT_SUCCESS;
}
```

好吧，代码有点多。这个 `main()` 函数的目的是收集用户提供的参数，执行最基本的输入验证，然后将收集到的参数传递给使用它们的函数。这个示例声明一个使用默认值初始化的 `options` 变量，并解析命令行，根据需要更新 `options`。

`main()` 函数的核心是一个 `while` 循环，它使用 `getopt()` 来遍历 `argv`，寻找命令行选项及其参数（如果有的话）。文件前面定义的 `OPTSTR` 是驱动 `getopt()` 行为的模板。`opt` 变量接受 `getopt()` 找到的任何命令行选项的字符值，程序对检测命令行选项的响应发生在 `switch` 语句中。

如果你注意到了可能会问，为什么 `opt` 被声明为 32 位 `int`，但是预期是 8 位 `char`？事实上 `getopt()` 返回一个 `int`，当它到达 `argv` 末尾时取负值，我会使用 `EOF`（*文件末尾*标记）匹配。`char` 是有符号的，但我喜欢将变量匹配到它们的函数返回值。

当检测到一个已知的命令行选项时，会发生特定的行为。在 `OPTSTR` 中指定一个以冒号结尾的参数，这些选项可以有一个参数。当一个选项有一个参数时，`argv` 中的下一个字符串可以通过外部定义的变量 `optarg` 提供给程序。我使用 `optarg` 来打开文件进行读写，或者将命令行参数从字符串转换为整数值。

这里有几个关于代码风格的要点：

* 将 `opterr` 初始化为 `0`，禁止 `getopt` 触发 `?`。
* 在 `main()` 的中间使用 `exit(EXIT_FAILURE);` 或 `exit(EXIT_SUCCESS);`。
* `/* NOTREACHED */` 是我喜欢的一个 lint 指令。
* 在返回 int 类型的函数末尾使用 `return EXIT_SUCCESS;`。
* 显示强制转换隐式类型。

这个程序的命令行格式，经过编译如下所示：

```
$ ./a.out -h
a.out [-v] [-f hexflag] [-i inputfile] [-o outputfile] [-h]
```

事实上，在编译后 `usage()` 就会向 `stderr` 发出这样的内容。

#### 8、函数声明

```
/* main.c */
<...>

void usage(char *progname, int opt) {
   fprintf(stderr, USAGE_FMT, progname?progname:DEFAULT_PROGNAME);
   exit(EXIT_FAILURE);
   /* NOTREACHED */
}

int do_the_needful(options_t *options) {

   if (!options) {
     errno = EINVAL;
     return EXIT_FAILURE;
   }

   if (!options->input || !options->output) {
     errno = ENOENT;
     return EXIT_FAILURE;
   }

   /* XXX do needful stuff */

   return EXIT_SUCCESS;
}
```

我最后编写的函数不是个样板函数。在本例中，函数 `do_the_needful()` 接受一个指向 `options_t` 结构的指针。我验证 `options` 指针不为 `NULL`，然后继续验证 `input` 和 `output` 结构成员。如果其中一个测试失败，返回 `EXIT_FAILURE`，并且通过将外部全局变量 `errno` 设置为常规错误代码，我可以告知调用者常规的错误原因。调用者可以使用便捷函数 `perror()` 来根据 `errno` 的值发出便于阅读的错误消息。

函数几乎总是以某种方式验证它们的输入。如果完全验证代价很大，那么尝试执行一次并将验证后的数据视为不可变。`usage()` 函数使用 `fprintf()` 调用中的条件赋值验证 `progname` 参数。接下来 `usage()` 函数就退出了，所以我不会费心设置 `errno`，也不用操心是否使用正确的程序名。

在这里，我要避免的最大错误是解引用 `NULL` 指针。这将导致操作系统向我的进程发送一个名为 `SYSSEGV` 的特殊信号，导致不可避免的死亡。用户最不希望看到的是由 `SYSSEGV` 而导致的崩溃。最好是捕获 `NULL` 指针以发出更合适的错误消息并优雅地关闭程序。

有些人抱怨在函数体中有多个 `return` 语句，他们喋喋不休地说些“控制流的连续性”之类的东西。老实说，如果函数中间出现错误，那就应该返回这个错误条件。写一大堆嵌套的 `if` 语句只有一个 `return` 绝不是一个“好主意”™。

最后，如果你编写的函数接受四个以上的参数，请考虑将它们绑定到一个结构中，并传递一个指向该结构的指针。这使得函数签名更简单，更容易记住，并且在以后调用时不会出错。它还可以使调用函数速度稍微快一些，因为需要复制到函数堆栈中的东西更少。在实践中，只有在函数被调用数百万或数十亿次时，才会考虑这个问题。如果认为这没有意义，那也无所谓。

### 等等，你不是说没有注释吗！？！！

在 `do_the_needful()` 函数中，我写了一种特殊类型的注释，它被是作为占位符设计的，而不是为了说明代码：

```
/* XXX do needful stuff */
```

当你写到这里时，有时你不想停下来编写一些特别复杂的代码，你会之后再写，而不是现在。那就是我留给自己再次回来的地方。我插入一个带有 `XXX` 前缀的注释和一个描述需要做什么的简短注释。之后，当我有更多时间的时候，我会在源代码中寻找 `XXX`。使用什么前缀并不重要，只要确保它不太可能在另一个上下文环境（如函数名或变量）中出现在你代码库里。

### 把它们组合在一起

好吧，当你编译这个程序后，它*仍然*几乎没有任何作用。但是现在你有了一个坚实的骨架来构建你自己的命令行解析 C 程序。

```
/* main.c - the complete listing */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libgen.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>

#define OPTSTR "vi:o:f:h"
#define USAGE_FMT  "%s [-v] [-f hexflag] [-i inputfile] [-o outputfile] [-h]"
#define ERR_FOPEN_INPUT  "fopen(input, r)"
#define ERR_FOPEN_OUTPUT "fopen(output, w)"
#define ERR_DO_THE_NEEDFUL "do_the_needful blew up"
#define DEFAULT_PROGNAME "george"

extern int errno;
extern char *optarg;
extern int opterr, optind;

typedef struct {
  int           verbose;
  uint32_t      flags;
  FILE         *input;
  FILE         *output;
} options_t;

int dumb_global_variable = -11;

void usage(char *progname, int opt);
int  do_the_needful(options_t *options);

int main(int argc, char *argv[]) {
    int opt;
    options_t options = { 0, 0x0, stdin, stdout };

    opterr = 0;

    while ((opt = getopt(argc, argv, OPTSTR)) != EOF)
       switch(opt) {
           case 'i':
              if (!(options.input = fopen(optarg, "r")) ){
                 perror(ERR_FOPEN_INPUT);
                 exit(EXIT_FAILURE);
                 /* NOTREACHED */
              }
              break;

           case 'o':
              if (!(options.output = fopen(optarg, "w")) ){
                 perror(ERR_FOPEN_OUTPUT);
                 exit(EXIT_FAILURE);
                 /* NOTREACHED */
              }
              break;

           case 'f':
              options.flags = (uint32_t )strtoul(optarg, NULL, 16);
              break;

           case 'v':
              options.verbose += 1;
              break;

           case 'h':
           default:
              usage(basename(argv[0]), opt);
              /* NOTREACHED */
              break;
       }

    if (do_the_needful(&options) != EXIT_SUCCESS) {
       perror(ERR_DO_THE_NEEDFUL);
       exit(EXIT_FAILURE);
       /* NOTREACHED */
    }

    return EXIT_SUCCESS;
}

void usage(char *progname, int opt) {
   fprintf(stderr, USAGE_FMT, progname?progname:DEFAULT_PROGNAME);
   exit(EXIT_FAILURE);
   /* NOTREACHED */
}

int do_the_needful(options_t *options) {

   if (!options) {
     errno = EINVAL;
     return EXIT_FAILURE;
   }

   if (!options->input || !options->output) {
     errno = ENOENT;
     return EXIT_FAILURE;
   }

   /* XXX do needful stuff */

   return EXIT_SUCCESS;
}
```

现在，你已经准备好编写更易于维护的 C 语言。如果你有任何问题或反馈，请在评论中分享。

---

via: <https://opensource.com/article/19/5/how-write-good-c-main-function>

作者：[Erik O'Shaughnessy](https://opensource.com/users/jnyjny) 选题：[lujun9972](https://github.com/lujun9972) 译者：[MjSeven](https://github.com/MjSeven) 校对：[wxy](https://github.com/wxy)

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


如何在 Linux 下确认 NTP 是否同步？

Summary: 我假设我你经使用上述链接设置了 NTP 服务器和 NTP 客户端。现在，如何验证 NTP 设置是否正常工作？

good

good

![](/data/attachment/album/201906/08/215622oqdhiuhocsndijlu.jpg)

NTP 意即<ruby> 网络时间协议 <rt>  Network Time Protocol </rt></ruby>，它通过网络同步计算机系统之间的时钟。NTP 服务器可以使组织中的所有服务器保持同步，以准确时间执行基于时间的作业。NTP 客户端会将其时钟与 NTP 服务器同步。

我们已经写了一篇关于 NTP 服务器和客户端安装和配置的文章。如果你想查看这些文章，请导航至以下链接。

* [如何在 Linux 上安装、配置 NTP 服务器和客户端？](/article-10811-1.html)
* [如何安装和配置 Chrony 作为 NTP 客户端？](/article-10820-1.html)

我假设你已经使用上述链接设置了 NTP 服务器和 NTP 客户端。现在，如何验证 NTP 设置是否正常工作？

Linux 中有三个命令可用于验证 NTP 同步情况。详情如下。在本文中，我们将告诉您如何使用所有这些命令验证 NTP 同步。

* `ntpq`：ntpq 是一个标准的 NTP 查询程序。
* `ntpstat`：显示网络世界同步状态。
* `timedatectl`：它控制 systemd 系统中的系统时间和日期。

### 方法 1：如何使用 ntpq 命令检查 NTP 状态？

`ntpq` 实用程序用于监视 NTP 守护程序 `ntpd` 的操作并确定性能。

该程序可以以交互模式运行，也可以使用命令行参数进行控制。它通过向服务器发送多个查询来打印出连接的对等项列表。如果 NTP 正常工作，你将获得类似于下面的输出。

```
# ntpq -p

     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*CentOS7.2daygee 133.243.238.163  2 u   14   64   37    0.686    0.151  16.432
```

细节：

* `-p`：打印服务器已知的对等项列表以及其状态摘要。

### 方法 2：如何使用 ntpstat 命令检查 NTP 状态？

`ntpstat` 将报告在本地计算机上运行的 NTP 守护程序（`ntpd`）的同步状态。如果发现本地系统与参考时间源保持同步，则 `ntpstat` 将报告大致的时间精度。

`ntpstat` 命令根据 NTP 同步状态返回三种状态码。详情如下。

* `0`：如果时钟同步则返回 0。
* `1`：如果时钟不同步则返回 1。
* `2`：如果时钟状态不确定，则返回 2，例如 ntpd 不可联系时。

```
# ntpstat

synchronised to NTP server (192.168.1.8) at stratum 3
   time correct to within 508 ms
   polling server every 64 s
```

### 方法 3：如何使用 timedatectl 命令检查 NTP 状态？

[timedatectl 命令](https://www.2daygeek.com/change-set-time-date-and-timezone-on-linux/)用于查询和更改系统时钟及其在 systmed 系统中的设置。

```
# timedatectl
或
# timedatectl status

      Local time: Thu 2019-05-30 05:01:05 CDT
  Universal time: Thu 2019-05-30 10:01:05 UTC
        RTC time: Thu 2019-05-30 10:01:05
       Time zone: America/Chicago (CDT, -0500)
     NTP enabled: yes
NTP synchronized: yes
 RTC in local TZ: no
      DST active: yes
 Last DST change: DST began at
                  Sun 2019-03-10 01:59:59 CST
                  Sun 2019-03-10 03:00:00 CDT
 Next DST change: DST ends (the clock jumps one hour backwards) at
                  Sun 2019-11-03 01:59:59 CDT
                  Sun 2019-11-03 01:00:00 CST
```

### 更多技巧

Chrony 是一个 NTP 客户端的替代品。它可以更快地同步系统时钟，时间精度更高，对于一直不在线的系统尤其有用。

chronyd 较小，它使用较少的内存，只在必要时才唤醒 CPU，这样可以更好地节省电能。即使网络拥塞较长时间，它也能很好地运行。

你可以使用以下任何命令来检查 Chrony 状态。

检查 Chrony 跟踪状态。

```
# chronyc tracking

Reference ID    : C0A80105 (CentOS7.2daygeek.com)
Stratum         : 3
Ref time (UTC)  : Thu Mar 28 05:57:27 2019
System time     : 0.000002545 seconds slow of NTP time
Last offset     : +0.001194361 seconds
RMS offset      : 0.001194361 seconds
Frequency       : 1.650 ppm fast
Residual freq   : +184.101 ppm
Skew            : 2.962 ppm
Root delay      : 0.107966967 seconds
Root dispersion : 1.060455322 seconds
Update interval : 2.0 seconds
Leap status     : Normal
```

运行 `sources` 命令以显示有关当前时间源的信息。

```
# chronyc sources

210 Number of sources = 1
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^* CentOS7.2daygeek.com          2   6    17    62    +36us[+1230us] +/- 1111ms
```

---

via: <https://www.2daygeek.com/check-verify-ntp-sync-is-working-or-not-in-linux-using-ntpq-ntpstat-timedatectl/>

作者：[Magesh Maruthamuthu](https://www.2daygeek.com/author/magesh/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[校对者ID](https://github.com/%E6%A0%A1%E5%AF%B9%E8%80%85ID)

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


5 个 Linux 新手会犯的失误

Summary: Linux 爱好者们分享了他们犯下的一些最大错误。

Linux 爱好者们分享了他们犯下的一些最大错误。

>
> Linux 爱好者们分享了他们犯下的一些最大错误。
>
>
>

![](/data/attachment/album/201906/09/103635akfkghwh5mp58g68.jpg)

终身学习是明智的 —— 它可以让你的思维敏捷，让你在就业市场上更具竞争力。但是有些技能比其他技能更难学，尤其是那些小菜鸟错误，当你尝试修复它们时可能会花费你很多时间，给你带来很大困扰。

以学习 [Linux](https://opensource.com/resources/linux) 为例。如果你习惯于在 Windows 或 MacOS 图形界面中工作，那么转移到 Linux，要将不熟悉的命令输入到终端中，可能会有很大的学习曲线。但是，其回报是值得的，因为已经有数以百万计的人们已经证明了这一点。

也就是说，这趟学习之旅并不是一帆风顺的。我们让一些 Linux 爱好者回想了一下他们刚开始使用 Linux 的时候，并告诉我们他们犯下的最大错误。

“不要进入[任何类型的命令行界面（CLI）工作]时就期望命令会以合理或一致的方式工作，因为这可能会导致你感到挫折。这不是因为设计选择不当 —— 虽然当你在键盘上敲击时就像在敲在你的脑袋上一样 —— 而是反映了这些系统是历经了几代的软件和操作系统的发展而陆续添加完成的事实。顺其自然，写下或记住你需要的命令，并且（尽量不要）在[事情不是你所期望的](https://lintqueen.com/2017/07/02/learning-while-frustrated/)时感到沮丧。” —— [Gina Likins](https://opensource.com/users/lintqueen)

“尽可能简单地复制和粘贴命令以使事情顺利进行，首先阅读命令，至少对将要执行的操作有一个大致的了解，特别是如果有管道命令时，如果有多个管道更要特别注意。有很多破坏性的命令看起来无害 —— 直到你意识到它们能做什么（例如 `rm`、`dd`），而你不会想要意外破坏什么东西（别问我怎么知道）。” —— [Katie McLaughlin](https://opensource.com/users/glasnt)

“在我的 Linux 之旅的早期，我并不知道我所处在文件系统中的位置的重要性。我正在删除一些我认为是我的主目录的文件，我输入了 `sudo rm -rf *`，然后就删除了我系统上的所有启动文件。现在，我经常使用 `pwd` 来确保我在发出这样的命令之前确认我在哪里。幸运的是，我能够使用 USB 驱动器启动被搞坏的笔记本电脑并恢复我的文件。” —— [Don Watkins](https://opensource.com/users/don-watkins)

“不要因为你认为‘权限很难理解’而你希望应用程序可以访问某些内容时就将整个文件系统的权限重置为 [777](https://www.maketecheasier.com/file-permissions-what-does-chmod-777-means/)。”—— [Matthew Helmke](https://twitter.com/matthewhelmke)

“我从我的系统中删除一个软件包，而我没有检查它依赖的其他软件包。我只是让它删除它想删除要的东西，最终导致我的一些重要程序崩溃并变得不可用。” —— [Kedar Vijay Kulkarni](https://opensource.com/users/kkulkarn)

你在学习使用 Linux 时犯过什么错误？请在评论中分享。

---

via: <https://opensource.com/article/19/4/linux-rookie-mistakes>

作者：[Jen Wike Huger](https://opensource.com/users/jen-wike/users/bcotton/users/petercheer/users/greg-p/users/greg-p) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

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


什么是翻译平台最重要的地方？

Summary: 技术上的考虑并不是判断一个好的翻译平台的最佳方式。

技术上的考虑并不是判断一个好的翻译平台的最佳方式。

>
> 技术上的考虑并不是判断一个好的翻译平台的最佳方式。
>
>
>

![](/data/attachment/album/201906/09/112224nvvkrv16qv60vwpv.jpg)

语言翻译可以使开源软件能够被世界各地的人们使用，这是非开发人员参与他们喜欢的（开源）项目的好方法。有许多[翻译工具](https://opensource.com/article/17/6/open-source-localization-tools)，你可以根据他们处理翻译中涉及的主要功能区域的能力来评估：技术交互能力、团队支持能力和翻译支持能力。

技术交互方面包括：

* 支持的文件格式
* 与开源存储库的同步
* 自动化支持工具
* 接口可能性

对团队合作（也可称为“社区活力”）的支持包括该平台如何：

* 监控变更（按译者、项目等）
* 跟进由项目推动的更新
* 显示进度状态
* 是否启用审核和验证步骤
* 协助（来自同一团队和跨语言的）翻译人员和项目维护人员之间的讨论
* 平台支持的全球通讯（新闻等）

翻译协助包括：

* 清晰、符合人体工程学的界面
* 简单几步就可以找到项目并开始工作
* 可以简单地了解到翻译和分发之间流程
* 可以使用翻译记忆机
* 词汇表丰富

前两个功能区域与源代码管理平台的差别不大，只有一些小的差别。我觉得最后一个区域也主要与源代码有关。但是，它们处理的数据非常不同，翻译平台的用户通常也不如开发人员了解技术，而数量也更多。

### 我的推荐

在我看来，GNOME 平台提供了最好的翻译平台，原因如下：

* 其网站包含了团队组织和翻译平台。很容易看出谁在负责以及他们在团队中的角色。一切都集中在几个屏幕之内。
* 很容易找到要处理的内容，并且你会很快意识到你必须将文件下载到本地计算机并在修改后将其发回。这个流程不是很先进，但逻辑很容易理解。
* 一旦你发回文件，平台就可以向邮件列表发送通告，以便团队知道后续步骤，并且可以全局轻松讨论翻译（而不是评论特定句子）。
* 它支持多达 297 种语言。
* 它显示了基本句子、高级菜单和文档的明确的进度百分比。 再加上可预测的 GNOME 发布计划，社区可以使用一切可以促进社区工作的工具。

如果我们看看 Debian 翻译团队，他们多年来一直在为 Debian （LCTT 译注：此处原文是“Fedora”，疑为笔误）翻译了难以想象的大量内容（尤其是新闻），我们看到他们有一个高度以来于规则的翻译流程，完全基于电子邮件，手动推送到存储库。该团队还将所有内容都放在流程中，而不是工具中，尽管这似乎需要相当大的技术能力，但它已成为领先的语言群体之一，已经运作多年。

我认为，成功的翻译平台的主要问题不是基于单一的（技术、翻译）工作的能力，而是基于如何构建和支持翻译团队的流程。这就是可持续性的原因。

生产过程是构建团队最重要的方式；通过正确地将它们组合在一起，新手很容易理解该过程是如何工作的，采用它们，并将它们解释给下一组新人。

要建立一个可持续发展的社区，首先要考虑的是支持协同工作的工具，然后是可用性。

这解释了我为什么对 [Zanata](http://zanata.org/) 工具沮丧，从技术和界面的角度来看，这是有效的，但在帮助构建社区方面却很差。我认为翻译是一个社区驱动的过程（可能是开源软件开发中最受社区驱动的过程之一），这对我来说是一个关键问题。

---

本文改编自“[什么是一个好的翻译平台？](https://jibecfed.fedorapeople.org/blog-hugo/en/2016/09/whats-a-good-translation-platform/)”，最初发表在 Jibec 期刊上，并经许可重复使用。

---

via: <https://opensource.com/article/19/5/translation-platforms>

作者：[Jean-Baptiste Holcroft](https://opensource.com/users/jibec/users/annegentle/users/bcotton) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

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


如何在 Linux 上识别同样内容的文件

>
> 有时文件副本相当于对硬盘空间的巨大浪费，并会在你想要更新文件时造成困扰。以下是用来识别这些文件的六个命令。
>
>
>

![Vinoth Chandar \(CC BY 2.0\)](/data/attachment/album/201906/09/234433wbvpd2vj1jpdfuud.jpg)

在最近的帖子中，我们看了[如何识别并定位硬链接的文件](https://www.networkworld.com/article/3387961/how-to-identify-duplicate-files-on-linux.html)（即，指向同一硬盘内容并共享 inode）。在本文中，我们将查看能找到具有相同*内容*，却不相链接的文件的命令。

硬链接很有用是因为它们能够使文件存放在文件系统内的多个地方却不会占用额外的硬盘空间。另一方面，有时文件副本相当于对硬盘空间的巨大浪费，在你想要更新文件时也会有造成困扰之虞。在本文中，我们将看一下多种识别这些文件的方式。

### 用 diff 命令比较文件

可能比较两个文件最简单的方法是使用 `diff` 命令。输出会显示你文件的不同之处。`<` 和 `>` 符号代表在当参数传过来的第一个（`<`）或第二个（`>`）文件中是否有额外的文字行。在这个例子中，在 `backup.html` 中有额外的文字行。

```
$ diff index.html backup.html
2438a2439,2441
> <pre>
> That's all there is to report.
> </pre>
```

如果 `diff` 没有输出那代表两个文件相同。

```
$ diff home.html index.html
$
```

`diff` 的唯一缺点是它一次只能比较两个文件并且你必须指定用来比较的文件，这篇帖子中的一些命令可以为你找到多个重复文件。

### 使用校验和

`cksum`（checksum） 命令计算文件的校验和。校验和是一种将文字内容转化成一个长数字（例如2819078353 228029）的数学简化。虽然校验和并不是完全独有的，但是文件内容不同校验和却相同的概率微乎其微。

```
$ cksum *.html
2819078353 228029 backup.html
4073570409 227985 home.html
4073570409 227985 index.html
```

在上述示例中，你可以看到产生同样校验和的第二个和第三个文件是如何可以被默认为相同的。

### 使用 find 命令

虽然 `find` 命令并没有寻找重复文件的选项，它依然可以被用来通过名字或类型寻找文件并运行 `cksum` 命令。例如：

```
$ find . -name "*.html" -exec cksum {} \;
4073570409 227985 ./home.html
2819078353 228029 ./backup.html
4073570409 227985 ./index.html
```

### 使用 fslint 命令

`fslint` 命令可以被特地用来寻找重复文件。注意我们给了它一个起始位置。如果它需要遍历相当多的文件，这就需要花点时间来完成。注意它是如何列出重复文件并寻找其它问题的，比如空目录和坏 ID。

```
$ fslint .
-----------------------------------file name lint
-------------------------------Invalid utf8 names
-----------------------------------file case lint
----------------------------------DUPlicate files   <==
home.html
index.html
-----------------------------------Dangling links
--------------------redundant characters in links
------------------------------------suspect links
--------------------------------Empty Directories
./.gnupg
----------------------------------Temporary Files
----------------------duplicate/conflicting Names
------------------------------------------Bad ids
-------------------------Non Stripped executables
```

你可能需要在你的系统上安装 `fslint`。你可能也需要将它加入你的命令搜索路径：

```
$ export PATH=$PATH:/usr/share/fslint/fslint
```

### 使用 rdfind 命令

`rdfind` 命令也会寻找重复（相同内容的）文件。它的名字意即“重复数据搜寻”，并且它能够基于文件日期判断哪个文件是原件——这在你选择删除副本时很有用因为它会移除较新的文件。

```
$ rdfind ~
Now scanning "/home/shark", found 12 files.
Now have 12 files in total.
Removed 1 files due to nonunique device and inode.
Total size is 699498 bytes or 683 KiB
Removed 9 files due to unique sizes from list.2 files left.
Now eliminating candidates based on first bytes:removed 0 files from list.2 files left.
Now eliminating candidates based on last bytes:removed 0 files from list.2 files left.
Now eliminating candidates based on sha1 checksum:removed 0 files from list.2 files left.
It seems like you have 2 files that are not unique
Totally, 223 KiB can be reduced.
Now making results file results.txt
```

你可以在 `dryrun` 模式中运行这个命令 （换句话说，仅仅汇报可能会另外被做出的改动）。

```
$ rdfind -dryrun true ~
(DRYRUN MODE) Now scanning "/home/shark", found 12 files.
(DRYRUN MODE) Now have 12 files in total.
(DRYRUN MODE) Removed 1 files due to nonunique device and inode.
(DRYRUN MODE) Total size is 699352 bytes or 683 KiB
Removed 9 files due to unique sizes from list.2 files left.
(DRYRUN MODE) Now eliminating candidates based on first bytes:removed 0 files from list.2 files left.
(DRYRUN MODE) Now eliminating candidates based on last bytes:removed 0 files from list.2 files left.
(DRYRUN MODE) Now eliminating candidates based on sha1 checksum:removed 0 files from list.2 files left.
(DRYRUN MODE) It seems like you have 2 files that are not unique
(DRYRUN MODE) Totally, 223 KiB can be reduced.
(DRYRUN MODE) Now making results file results.txt
```

`rdfind` 命令同样提供了类似忽略空文档（`-ignoreempty`）和跟踪符号链接（`-followsymlinks`）的功能。查看 man 页面获取解释。

```
-ignoreempty       ignore empty files
-minsize        ignore files smaller than speficied size
-followsymlinks     follow symbolic links
-removeidentinode   remove files referring to identical inode
-checksum       identify checksum type to be used
-deterministic      determiness how to sort files
-makesymlinks       turn duplicate files into symbolic links
-makehardlinks      replace duplicate files with hard links
-makeresultsfile    create a results file in the current directory
-outputname     provide name for results file
-deleteduplicates   delete/unlink duplicate files
-sleep          set sleep time between reading files (milliseconds)
-n, -dryrun     display what would have been done, but don't do it
```

注意 `rdfind` 命令提供了 `-deleteduplicates true` 的设置选项以删除副本。希望这个命令语法上的小问题不会惹恼你。;-)

```
$ rdfind -deleteduplicates true .
...
Deleted 1 files.    <==
```

你将可能需要在你的系统上安装 `rdfind` 命令。试验它以熟悉如何使用它可能是一个好主意。

### 使用 fdupes 命令

`fdupes` 命令同样使得识别重复文件变得简单。它同时提供了大量有用的选项——例如用来迭代的 `-r`。在这个例子中，它像这样将重复文件分组到一起：

```
$ fdupes ~
/home/shs/UPGRADE
/home/shs/mytwin

/home/shs/lp.txt
/home/shs/lp.man

/home/shs/penguin.png
/home/shs/penguin0.png
/home/shs/hideme.png
```

这是使用迭代的一个例子，注意许多重复文件是重要的（用户的 `.bashrc` 和 `.profile` 文件）并且不应被删除。

```
# fdupes -r /home
/home/shark/home.html
/home/shark/index.html

/home/dory/.bashrc
/home/eel/.bashrc

/home/nemo/.profile
/home/dory/.profile
/home/shark/.profile

/home/nemo/tryme
/home/shs/tryme

/home/shs/arrow.png
/home/shs/PNGs/arrow.png

/home/shs/11/files_11.zip
/home/shs/ERIC/file_11.zip

/home/shs/penguin0.jpg
/home/shs/PNGs/penguin.jpg
/home/shs/PNGs/penguin0.jpg

/home/shs/Sandra_rotated.png
/home/shs/PNGs/Sandra_rotated.png
```

`fdupe` 命令的许多选项列如下。使用 `fdupes -h` 命令或者阅读 man 页面获取详情。

```
-r --recurse     recurse
-R --recurse:    recurse through specified directories
-s --symlinks    follow symlinked directories
-H --hardlinks   treat hard links as duplicates
-n --noempty     ignore empty files
-f --omitfirst   omit the first file in each set of matches
-A --nohidden    ignore hidden files
-1 --sameline    list matches on a single line
-S --size        show size of duplicate files
-m --summarize   summarize duplicate files information
-q --quiet       hide progress indicator
-d --delete      prompt user for files to preserve
-N --noprompt    when used with --delete, preserve the first file in set
-I --immediate   delete duplicates as they are encountered
-p --permissions don't soncider files with different owner/group or
                 permission bits as duplicates
-o --order=WORD  order files according to specification
-i --reverse     reverse order while sorting
-v --version     display fdupes version
-h --help        displays help
```

`fdupes` 命令是另一个你可能需要安装并使用一段时间才能熟悉其众多选项的命令。

### 总结

Linux 系统提供能够定位并（潜在地）能移除重复文件的一系列的好工具，以及能让你指定搜索区域及当对你所发现的重复文件时的处理方式的选项。

---

via: <https://www.networkworld.com/article/3390204/how-to-identify-same-content-files-on-linux.html#tk.rss_all>

作者：[Sandra Henry-Stocker](https://www.networkworld.com/author/Sandra-Henry_Stocker/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[tomjlw](https://github.com/tomjlw) 校对：[wxy](https://github.com/wxy)

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


区块链 2.0：智能合约及其类型（五）

![](/data/attachment/album/201906/10/005551zhhrh8actjhjttyg.png)

这是 区块链 2.0 系列的第 5 篇文章。本系列的前一篇文章探讨了我们如何[在房地产行业实现区块链](/article-10914-1.html)。本文简要探讨了区块链及相关技术领域内的<ruby> 智能合约 <rt>  Smart Contract </rt></ruby>主题。智能合约是在区块链上验证和创建新“数据块”的基本协议，它被吹捧为该系统未来发展和应用的焦点。 然而，像所有“万灵药”一样，它不是一切的答案。我们将从基础知识中探索这个概念，以了解“智能合约”是什么以及它们不是什么。

### 不断发展的合同

这个世界建立在合同（合约）之上。在当前社会，没有合约的使用和再利用，地球上任何个人或公司都无法运作。订立、维护和执行合同的任务变得如此复杂，以至于整个司法和法律系统都必须以“合同法”的名义建立起来以支持它。事实上，大多数合同都是由一个“可信的”第三方监督，以确保最终的利益攸关者按照达成的条件得到妥善处理。有些合同甚至涉及到了第三方受益人。此类合同旨在对不是合同的活跃（或参与）方的第三方产生影响。解决和争论合同义务占据了民事诉讼所涉及的大部分法律纠纷。当然，更好的处理合同的方式来对于个人和企业来说都是天赐之物。更不用说它将以核查和证明的名义节省政府的巨大的[文书工作](http://www.legal-glossary.org/)    1 。

本系列中的大多数文章都研究了如何利用现有的区块链技术。相比之下，这篇文章将更多地讲述对未来几年的预期。关于“智能合约”的讨论源于前一篇文章中提出的财产讨论。当前这篇文章旨在概述区块链自动执行“智能”可执行程序的能力。务实地处理这个问题意味着我们首先必须定义和探索这些“智能合约”是什么，以及它们如何适应现有的合同系统。我们将在下一篇题为“区块链 2.0：正在进行的项目”的文章中查看当前该领域正在进行的主要应用和项目。

### 定义智能合约

[本系列的第一篇文章](/article-10650-1.html)从基本的角度来看待区块链，将其看作由数据块组成的“分布式分类账本”，这些数据块是：

* 防篡改
* 不可否认（意味着每个数据块都是由某人显式创建的，并且该人不能否认相同的责任）
* 安全，且能抵御传统的网络攻击方法
* 几乎是永久性的（当然这取决于区块链协议层）
* 高度冗余，通过存在于多个网络节点或参与者系统上，其中一个节点的故障不会以任何方式影响系统的功能，并且，
* 根据应用的不同可以提供更快的处理速度。

由于每个数据实例都是安全存储和通过适当的凭证访问的，因此区块链网络可以为精确验证事实和信息提供简便的基础，而无需第三方监督。区块链 2.0 开发也允许“分布式应用程序（DApp）”（我们将在接下来的文章中详细介绍这个术语）。这些分布式应用程序要求存在网络上并在其上运行。当用户需要它们时就会调用它们，并通过使用已经过审核并存储在区块链上的信息来执行它们。

上面的最后一段为智能合约的定义提供了基础。<ruby> 数字商会 <rt>  The Chamber for Digital Commerce </rt></ruby>提供了一个许多专家都同意的智能合约定义。

>
> “（智能合约是一种）计算机代码，在发生指定条件时，能够根据预先指定的功能自动运行。该代码可以在分布式分类帐本上存储和处理，并将产生的任何更改写入分布式分类帐本”    2 。
>
>
>

智能合约如上所述是一种简单的计算机程序，就像 “if-then” 或 “if-else if” 语句一样工作。关于其“智能”的方面来自这样一个事实，即该程序的预定义输入来自区块链分类账本，如上所述，它是一个记录信息的安全可靠的来源。如有必要，程序可以调用外部服务或来源以获取信息，以验证操作条款，并且仅在满足所有预定义条件后才执行。

必须记住，与其名称所暗示的不同，智能合约通常不是自治实体，严格来说，也不是合同。1996 年，Nick Szabo 很早就提到了智能合约，他将其与接受付款并交付用户选择的产品的自动售货机进行了比较。可以在[这里](http://www.fon.hum.uva.nl/rob/Courses/InformationInSpeech/CDROM/Literature/LOTwinterschool2006/szabo.best.vwh.net/smart_contracts_2.html)查看全文。此外，人们正在制定允许智能合约进入主流合同使用的法律框架，因此目前该技术的使用仅限于法律监督不那么明确和严格的领域    3 。

### 智能合约的主要类型

假设读者对合同和计算机编程有基本的了解，并且基于我们对智能合约的定义，我们可以将智能合约和协议粗略地分类为以下主要类别。

#### 1、智能法律合约

这大概是最明显的一种。大多数（如果不是全部）合同都具有法律效力。在不涉及太多技术问题的情况下，智能法律合约是涉及到严格的法律追索权的合同，以防参与合同的当事人不履行其交易的目的。如前所述，不同国家和地区的现行法律框架对区块链上的智能和自动化合约缺乏足够的支持，其法律地位也不明确。但是，一旦制定了法律，就可以订立智能合约，以简化目前涉及严格监管的流程，如金融和房地产市场交易、政府补贴、国际贸易等。

#### 2、DAO

<ruby> 去中心化自治组织 <rt>  Decentralized Autonomous Organization </rt></ruby>，即DAO，可以粗略地定义为区块链上存在的社区。该社区可以通过一组规则来定义，这些规则通过智能合约来体现并放入代码中。然后，每个参与者的每一个行动都将受到这些规则的约束，其任务是在程序中断的情况下执行并获得追索权。许多智能合约构成了这些规则，它们协同监管和监督参与者。

名为“创世纪 DAO” 的 DAO 是由以太坊参与者于 2016 年 5 月创建。该社区旨在成为众筹和风险投资平台。在极短的时间内，他们设法筹集了惊人的 1.5 亿美元。然而，由于黑客在系统中发现了漏洞，并设法从众筹投资者手中窃取价值约 5000 万美元的以太币。这次黑客破坏的后果导致以太坊区块链[分裂为两个](https://futurism.com/the-dao-heist-undone-97-of-eth-holders-vote-for-the-hard-fork/)，以太坊和以太坊经典。

#### 3、应用逻辑合约（ALC）

如果你已经听说过与区块链相结合的物联网，那么很可能它涉及到了<ruby> 应用逻辑合约 <rt>  Application logic contract </rt></ruby>，即 ALC。此类智能合约包含特定于应用的代码，这些代码可以与区块链上的其他智能合约和程序一起工作。它们有助于与设备进行通信并验证设备之间的通信（在物联网领域）。ALC 是每个多功能智能合约的关键部分，并且大多数都是在一个管理程序下工作。在这里引用的大多数例子中，它们到处都能找到[应用](https://www.everestgrp.com/2016-10-types-smart-contracts-based-applications-market-insights-36573.html/)    4 。

*由于该领域还在开发中，因此目前所说的任何定义或标准最多只能说是变化而模糊的。*

### 智能合约是如何工作的？

为简化起见，让我们用个例子来说明。

约翰和彼得是两个争论足球比赛得分的人。他们对比赛结果持有相互矛盾的看法，他们都支持不同的球队（这是背景情况）。由于他们两个都需要去其他地方并且无法看完比赛，所以约翰认为如果 A 队在比赛中击败 B 队，他就*支付*给彼得 100 美元。彼得*考虑*之后*接受*了该赌注，同时明确表示他们必须接受这些条款。但是，他们没有兑现该赌注的相互信任，也没有时间和钱来指定第三方监督赌注。

假设约翰和彼得都使用像 [Etherparty](https://etherparty.com/) 这样的智能合约平台，它可以在合约谈判时自动结算赌注，他们都会将基于区块链的身份链接到该合约，并设置条款，明确表示一旦比赛结束，该程序将找出获胜方是谁，并自动将该金额从输家中归入获胜者银行账户。一旦比赛结束并且媒体报道同样的结果，该程序将在互联网上搜索规定的来源，确定哪支球队获胜，将其与合约条款联系起来，在这种情况下，如果 A 队赢了彼得将从约翰哪里得到钱，也就是说将约翰的 100 美元转移到彼得的账户。执行完毕后，除非另有说明，否则智能合约将终止并在未来所有的时间内处于非活动状态。

抛开例子的简单不说，这种情况涉及到一个经典的合同，而参与者选择使用智能合约实现了相同目的。所有的智能合约基本上都遵循类似的原则，对程序进行编码，以便在预定义的参数上执行，并且只抛出预期的输出。智能合同咨询的外部来源可以是有时被称为 IT 世界中的<ruby> 神谕 <rt>  Oracle </rt></ruby>。神谕是当今全球许多智能合约系统的常见部分。

在这种情况下使用智能合约使参与者可以获得以下好处：

* 它比在一起并手动结算更快。
* 从其中删除了信任问题。
* 消除了受信任的第三方代表有关各方处理和解的必要性。
* 执行时无需任何费用。
* 在如何处理参数和敏感数据方面是安全的。
* 相关数据将永久保留在他们运行的区块链平台中，未来可以通过调用相同的函数并为其提供更多输入来设置投注。
* 随着时间的推移，假设约翰和彼得变得赌博成瘾，该程序可以帮助他们开发可靠的统计数据来衡量他们的连胜纪录。 现在我们知道**什么是智能合约**和**它们如何工作**，我们还没有解决**为什么我们需要它们**。

### 智能合约的需要

正如之前的例子我们重点提到过的，出于各种原因，我们需要智能合约。

#### 透明度

交易对手非常清楚所涉及的条款和条件。此外，由于程序或智能合约的执行涉及某些明确的输入，因此用户可以非常直接地核实会影响他们和合约受益人的因素。

#### 时间效率

如上所述，智能合约一旦被控制变量或用户调用所触发，就立即开始工作。由于数据是通过区块链和网络中的其它来源即时提供给系统，因此执行不需要任何时间来验证和处理信息并解决交易。例如，转移土地所有权契约，这是一个涉及手工核实大量文书工作并且需要数周时间的过程，可以在几分钟甚至几秒钟内通过智能合约程序来处理文件和相关各方。

#### 精度

由于平台基本上只是计算机代码和预定义的内容，因此不存在主观错误，所有结果都是精确的，完全没有人为错误。

#### 安全

区块链的一个固有特征是每个数据块都是安全加密的。这意味着为了实现冗余，即使数据存储在网络上的多个节点上，**也只有数据所有者才能访问以查看和使用数据**。类似地，利用区块链在过程中存储重要变量和结果，所有过程都将是完全安全和防篡改的。同样也通过按时间顺序为审计人员提供原始的、未经更改的和不可否认的数据版本，简化了审计和法规事务。

#### 信任

这个文章系列开篇说到区块链为互联网及其上运行的服务增加了急需的信任层。智能合约在任何情况下都不会在执行协议时表现出偏见或主观性，这意味着所涉及的各方对结果完全有约束力，并且可以不附带任何条件地信任该系统。这也意味着，在具有重要价值的传统合同中所需的“可信第三方”，在此处不需要。当事人之间的犯规和监督将成为过去的问题。

#### 成本效益

如示例中所强调的，使用智能合约需要最低的成本。企业通常有专门从事使其交易合法并遵守法规的行政人员。如果交易涉及多方，则重复工作是不可避免的。智能合约基本上使前者无关紧要，并且消除了重复，因为双方可以同时完成尽职调查。

### 智能合约的应用

基本上，如果两个或多个参与方使用共同的区块链平台，并就一组原则或业务逻辑达成一致，他们可以一起在区块链上创建一个智能合约，并且在没有人为干预的情况下执行。没有人可以篡改所设置的条件，如果原始代码允许，任何更改都会加上时间戳并带有编辑者的指纹，从而增加了问责制。想象一下，在更大的企业级规模上出现类似的情况，你就会明白智能合约的能力是什么，实际上从 2016 年开始的 **Capgemini 研究** 发现智能合约实际上可能是**“未来几年的”**    5  商业主流。商业的应用涉及保险、金融市场、物联网、贷款、身份管理系统、托管账户、雇佣合同以及专利和版税合同等用途。像以太坊这样的区块链平台，是一个设计时就考虑了智能合约的系统，它允许个人私人用户免费使用智能合约。

通过对处理智能合约的公司的探讨，本系列的下一篇文章中将更全面地概述智能合约在当前技术问题上的应用。

### 那么，它有什么缺点呢？

这并不是说对智能合约的使用没有任何顾虑。这种担忧实际上也减缓了这方面的发展。所有区块链的防篡改性质实质上使得，如果所涉及的各方需要在没有重大改革或法律追索的情况下，几乎不可能修改或添加现有条款的新条款。

其次，即使公有链上的活动是开放的，所有人都可以看到和观察。交易中涉及的各方的个人身份并不总是已知的。这种匿名性造成在任何一方违约的情况下法律有罪不罚的问题，特别是因为现行法律和立法者并不完全适应现代技术。

第三，区块链和智能合约在很多方面仍然存在安全缺陷，因为对其所以涉及的技术仍处于发展的初期阶段。 对代码和平台的这种缺乏经验最终导致了 2016 年的 DAO 事件。

所有这些都可能导致企业或公司在需要调整区块链以供其使用时需要大量的初始投资。然而，这些是最初的一次性投资，并且随之而来的是潜在的节约，这才是人们感兴趣的。

### 结论

目前的法律框架并没有真正支持一个全面的智能合约的社会，并且由于显然的原因，在不久的将来也不会支持。一个解决方案是选择**“混合”合约**，它将传统的法律文本和文件与在为此目的设计的区块链上运行的智能合约代码相结合。然而，即使是混合合约仍然很大程度上尚未得到探索，因为需要创新的立法机构才能实现这些合约。这里简要提到的应用以及更多内容将在[本系列的下一篇文章](https://www.ostechnix.com/blockchain-2-0-ongoing-projects-the-state-of-smart-contracts-now/)中详细探讨。

---

1. S. C. A. Chamber of Digital Commerce, “Smart contracts – Is the law ready,” no. September, 2018. [↩](#fnref1)
2. S. C. A. Chamber of Digital Commerce, “Smart contracts – Is the law ready,” no. September, 2018. [↩](#fnref2)
3. Cardozo Blockchain Project, “‘Smart Contracts’ & Legal Enforceability,” vol. 2, p. 28, 2018. [↩](#fnref3)
4. F. Idelberger, G. Governatori, R. Riveret, and G. Sartor, “Evaluation of Logic-Based Smart Contracts for Blockchain Systems,” 2016, pp. 167–183. [↩](#fnref4)
5. B. Cant et al., “Smart Contracts in Financial Services : Getting from Hype to Reality,” Capgemini Consult., pp. 1–24, 2016. [↩](#fnref5)

---

via: <https://www.ostechnix.com/blockchain-2-0-explaining-smart-contracts-and-its-types/>

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

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


我们是如何使用 Electron 构建 Linux 桌面应用程序的

>
> 这是借助 Electron 框架，构建一个在 Linux 桌面上原生运行的开源电子邮件服务的故事。
>
>
>

![](/data/attachment/album/201906/10/123114abz0lvbllktkulx7.jpg)

[Tutanota](https://tutanota.com/) 是一种安全的开源电子邮件服务，它可通过浏览器使用，也有 iOS 和 Android 应用。其客户端代码在 GPLv3 下发布，Android 应用程序可在 [F-Droid](https://f-droid.org/en/packages/de.tutao.tutanota/) 上找到，以便每个人都可以使用完全与 Google 无关的版本。

由于 Tutanota 关注开源和 Linux 客户端开发，因此我们希望为 Linux 和其他平台发布一个桌面应用程序。作为一个小团队，我们很快就排除了为 Linux、Windows 和 MacOS 构建原生应用程序的可能性，并决定使用 [Electron](https://electronjs.org/) 来构建我们的应用程序。

对于任何想要快速交付视觉一致的跨平台应用程序的人来说，Electron 是最适合的选择，尤其是如果你已经有一个 Web 应用程序，想要从浏览器 API 的束缚中摆脱出来时。Tutanota 就是这样一个案例。

Tutanota 基于 [SystemJS](https://github.com/systemjs/systemjs) 和 [Mithril](https://mithril.js.org/)，旨在为每个人提供简单、安全的电子邮件通信。 因此，它必须提供很多用户期望从电子邮件客户端获得的标准功能。

由于采用了现代 API 和标准，其中一些功能（如基本的推送通知、搜索文本和联系人以及支持双因素身份验证）很容易在浏览器中提供。其它功能（例如自动备份或无需我们的服务器中转的 IMAP 支持）需要对系统资源的限制性访问，而这正是 Electron 框架提供的功能。

虽然有人批评 Electron “只是一个基本的包装”，但它有明显的好处：

* Electron 可以使你能够快速地为 Linux、Windows 和 MacOS 桌面构造 Web 应用。事实上，大多数 Linux 桌面应用都是使用 Electron 构建的。
* Electron 可以轻松地将桌面客户端与 Web 应用程序达到同样的功能水准。
* 发布桌面应用程序后，你可以自由使用开发功能添加桌面端特定的功能，从而增强可用性和安全性。
* 最后但同样重要的是，这是让应用程序具备原生的感觉、融入用户系统，而同时保持其识别度的好方法。 ### 满足用户的需求

Tutanota 不依靠于大笔的投资资金，而是依靠社区驱动的项目。基于越来越多的用户升级到我们的免费服务的付费计划，我们有机地发展我们的团队。倾听用户的需求不仅对我们很重要，而且对我们的成功至关重要。

提供桌面客户端是 Tutanota 用户[最想要的功能](https://tutanota.uservoice.com/forums/237921-general/filters/top?status_id=1177482)，我们感到自豪的是，我们现在可以为所有用户提供免费的桌面客户端测试版。（我们还实现了另一个高度要求的功能 —— [搜索加密数据](https://tutanota.com/blog/posts/first-search-encrypted-data/) —— 但这是另一个主题了。）

我们喜欢为用户提供签名版本的 Tutanota 并支持浏览器中无法实现的功能，例如通过后台进程推送通知。 现在，我们计划添加更多特定于桌面的功能，例如 IMAP 支持（而不依赖于我们的服务器充当代理），自动备份和离线可用性。

我们选择 Electron 是因为它的 Chromium 和 Node.js 的组合最适合我们的小型开发团队，因为它只需要对我们的 Web 应用程序进行最小的更改。在我们开始使用时，可以将浏览器 API 用于所有功能特别有用，随着我们的进展，慢慢地用更多原生版本替换这些组件。这种方法对附件下载和通知特别方便。

### 调整安全性

我们知道有些人关注 Electron 的安全问题，但我们发现 Electron 在 Web 应用程序中微调访问的选项非常令人满意。你可以使用 Electron 的[安全文档](https://electronjs.org/docs/tutorial/security)和 Luca Carettoni 的[Electron 安全清单](https://www.blackhat.com/docs/us-17/thursday/us-17-Carettoni-Electronegativity-A-Study-Of-Electron-Security-wp.pdf)等资源，来帮助防止 Web 应用程序中不受信任的内容发生灾难性事故。

### 实现特定功能

Tutanota Web 客户端从一开始就构建了一个用于进程间通信的可靠协议。我们利用 Web 线程在加密和请求数据时保持用户界面（UI）响应性。当我们开始实现我们的移动应用时，这就派上用场，这些应用程序使用相同的协议在原生部分和 Web 视图之间进行通信。

这就是为什么当我们开始构建桌面客户端时，很多用于本机推送通知、打开邮箱和使用文件系统的部分等已经存在，因此只需要实现原生端（Node.js）。

另一个便利是我们的构建过程使用 [Babel 转译器](https://babeljs.io/)，它允许我们以现代 ES6 JavaScript 编写整个代码库，并在不同环境之间混合和匹配功能模块。这使我们能够快速调整基于 Electron 的桌面应用程序的代码。但是，我们也遇到了一些挑战。

### 克服挑战

虽然 Electron 允许我们很容易地与不同平台的桌面环境集成，但你不能低估投入的时间！最后，正是这些小事情占用了比我们预期更多的时间，但对完成桌面客户端项目也至关重要。

特定于平台的代码导致了大部分阻碍：

* 例如，窗口管理和托盘仍然在三个平台上以略有不同的方式处理。
* 注册 Tutanota 作为默认邮件程序并设置自动启动需要深入 Windows 注册表，同时确保以 [UAC](https://en.wikipedia.org/wiki/User_Account_Control) 兼容的方式提示用户进行管理员访问。
* 我们需要使用 Electron 的 API 作为快捷方式和菜单，以提供复制、粘贴、撤消和重做等标准功能。

由于用户对不同平台上的应用程序的某些（有时不直接兼容）行为的期望，此过程有点复杂。使三个版本感觉像原生的需要一些迭代，甚至需要对 Web 应用程序进行一些适度的补充，以提供类似于浏览器中的文本搜索的功能。

### 总结

我们在 Electron 方面的经验基本上是积极的，我们在不到四个月的时间内完成了该项目。尽管有一些相当耗时的功能，但我们感到惊讶的是，我们可以轻松地为 Linux 提供一个测试版的 [Tutanota 桌面客户端](https://tutanota.com/blog/posts/desktop-clients/)。如果你有兴趣，可以深入了解 [GitHub](https://www.github.com/tutao/tutanota) 上的源代码。

---

via: <https://opensource.com/article/19/4/linux-desktop-electron>

作者：[Nils Ganther](https://opensource.com/users/nils-ganther) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

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


在 RHEL 和 CentOS 上检查或列出已安装的安全更新的两种方法

![](/data/attachment/album/201906/11/100735bdnjzkkmjbxbttmm.jpg)

我们过去曾写过两篇关于这个主题的文章，每篇文章都是根据不同的要求发表的。如果你想在开始之前浏览这些文章。请通过以下链接：

* [如何检查 RHEL 和 CentOS 上的可用安全更新？](/article-10938-1.html)
* [在 RHEL 和 CentOS 上安装安全更新的四种方法？](https://www.2daygeek.com/install-security-updates-on-redhat-rhel-centos-system/)

这些文章与其他文章相互关联，因此，在深入研究之前，最好先阅读这些文章。

在本文中，我们将向你展示如何检查已安装的安全更新。我会介绍两种方法，你可以选择最适合你的。

此外，我还添加了一个小的 shell 脚本，它为你提供已安装的安全包计数。

运行以下命令获取系统上已安装的安全更新的列表。

```
# yum updateinfo list security installed

Loaded plugins: changelog, package_upload, product-id, search-disabled-repos,
              : subscription-manager, verify, versionlock
RHSA-2015:2315 Moderate/Sec.  ModemManager-glib-1.1.0-8.git20130913.el7.x86_64
RHSA-2015:2315 Moderate/Sec.  NetworkManager-1:1.0.6-27.el7.x86_64
RHSA-2016:2581 Low/Sec.       NetworkManager-1:1.4.0-12.el7.x86_64
RHSA-2017:2299 Moderate/Sec.  NetworkManager-1:1.8.0-9.el7.x86_64
RHSA-2015:2315 Moderate/Sec.  NetworkManager-adsl-1:1.0.6-27.el7.x86_64
RHSA-2016:2581 Low/Sec.       NetworkManager-adsl-1:1.4.0-12.el7.x86_64
RHSA-2017:2299 Moderate/Sec.  NetworkManager-adsl-1:1.8.0-9.el7.x86_64
RHSA-2015:2315 Moderate/Sec.  NetworkManager-bluetooth-1:1.0.6-27.el7.x86_64
```

要计算已安装的安全包的数量，请运行以下命令：

```
# yum updateinfo list security installed | wc -l
1046
```

仅打印安装包列表：

```
# yum updateinfo list security all | grep -w "i"

i RHSA-2015:2315 Moderate/Sec.  ModemManager-glib-1.1.0-8.git20130913.el7.x86_64
i RHSA-2015:2315 Moderate/Sec.  NetworkManager-1:1.0.6-27.el7.x86_64
i RHSA-2016:2581 Low/Sec.       NetworkManager-1:1.4.0-12.el7.x86_64
i RHSA-2017:2299 Moderate/Sec.  NetworkManager-1:1.8.0-9.el7.x86_64
i RHSA-2015:2315 Moderate/Sec.  NetworkManager-adsl-1:1.0.6-27.el7.x86_64
i RHSA-2016:2581 Low/Sec.       NetworkManager-adsl-1:1.4.0-12.el7.x86_64
i RHSA-2017:2299 Moderate/Sec.  NetworkManager-adsl-1:1.8.0-9.el7.x86_64
i RHSA-2015:2315 Moderate/Sec.  NetworkManager-bluetooth-1:1.0.6-27.el7.x86_64
i RHSA-2016:2581 Low/Sec.       NetworkManager-bluetooth-1:1.4.0-12.el7.x86_64
i RHSA-2017:2299 Moderate/Sec.  NetworkManager-bluetooth-1:1.8.0-9.el7.x86_64
i RHSA-2015:2315 Moderate/Sec.  NetworkManager-config-server-1:1.0.6-27.el7.x86_64
i RHSA-2016:2581 Low/Sec.       NetworkManager-config-server-1:1.4.0-12.el7.x86_64
i RHSA-2017:2299 Moderate/Sec.  NetworkManager-config-server-1:1.8.0-9.el7.noarch
```

要计算已安装的安全包的数量，请运行以下命令：

```
# yum updateinfo list security all | grep -w "i" | wc -l
1043
```

或者，你可以检查指定包修复的漏洞列表。

在此例中，我们将检查 “openssh” 包中已修复的漏洞列表：

```
# rpm -q --changelog openssh | grep -i CVE

- Fix for CVE-2017-15906 (#1517226)
- CVE-2015-8325: privilege escalation via user's PAM environment and UseLogin=yes (#1329191)
- CVE-2016-1908: possible fallback from untrusted to trusted X11 forwarding (#1298741)
- CVE-2016-3115: missing sanitisation of input for X11 forwarding (#1317819)
- prevents CVE-2016-0777 and CVE-2016-0778
- Security fixes released with openssh-6.9 (CVE-2015-5352) (#1247864)
- only query each keyboard-interactive device once (CVE-2015-5600) (#1245971)
- add new option GSSAPIEnablek5users and disable using ~/.k5users by default CVE-2014-9278
- prevent a server from skipping SSHFP lookup - CVE-2014-2653 (#1081338)
- change default value of MaxStartups - CVE-2010-5107 (#908707)
- CVE-2010-4755
- merged cve-2007_3102 to audit patch
- fixed audit log injection problem (CVE-2007-3102)
- CVE-2006-5794 - properly detect failed key verify in monitor (#214641)
- CVE-2006-4924 - prevent DoS on deattack detector (#207957)
- CVE-2006-5051 - don't call cleanups from signal handler (#208459)
- use fork+exec instead of system in scp - CVE-2006-0225 (#168167)
```

同样，你可以通过运行以下命令来检查相应的包中是否修复了指定的漏洞：

```
# rpm -q --changelog openssh | grep -i CVE-2016-3115

- CVE-2016-3115: missing sanitisation of input for X11 forwarding (#1317819)
```

### 如何使用 Shell 脚本计算安装的安全包？

我添加了一个小的 shell 脚本，它可以帮助你计算已安装的安全包列表。

```
# vi /opt/scripts/security-check.sh

#!/bin/bash
echo "+-------------------------+"
echo "|Security Advisories Count|"
echo "+-------------------------+"
for i in Important Moderate Low
do
    sec=$(yum updateinfo list security installed | grep $i | wc -l)
    echo "$i: $sec"
done | column -t
echo "+-------------------------+"
```

给 `security-check.sh` 文件执行权限。

```
$ chmod +x security-check.sh
```

最后执行脚本统计。

```
# sh /opt/scripts/security-check.sh

+-------------------------+
|Security Advisories Count|
+-------------------------+
Important:  480
Moderate:   410
Low:        111
+-------------------------+
```

---

via: <https://www.2daygeek.com/check-installed-security-updates-on-redhat-rhel-and-centos-system/>

作者：[Magesh Maruthamuthu](https://www.2daygeek.com/author/magesh/) 选题：[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/) 荣誉推出


当物联网系统出现故障：使用低质量物联网数据的风险

>
> 伴随着物联网设备使用量的增长，这些设备产生的数据可以让消费者节约巨大的开支，也给商家带来新的机遇。但是当故障不可避免地出现的时候，会发生什么呢？
>
>
>

![Oonal / Getty Images](/data/attachment/album/201906/11/115144oabo8ytrrr2khyzd.jpg)

不管你看的是什么统计数字，很明显物联网正在走进个人和私人生活的方方面面。这种增长虽然有不少好处，但是也带来了新的风险。一个很重要的问题是，出现问题的时候谁来负责呢？

也许最大的问题出在基于物联网数据进行的个性化营销以及定价策略上。[保险公司长期以来致力于寻找利用物联网数据的最佳方式](https://www.networkworld.com/article/3264655/most-insurance-carriers-not-ready-to-use-iot-data.html)，我去年写过家庭财产保险公司是如何开始利用物联网传感器减少水灾带来的损失的。一些公司正在研究保险公司竞购消费者的可能性：这种业务基于智能家居数据所揭示的风险的高低。

但是最大的进步出现在汽车保险领域。许多汽车保险公司已经可以让客户在车辆上安装追踪设备，如果数据证明他们的驾驶习惯良好就可以获取保险折扣。

* 延伸阅读:[保险公司终于有了一个利用智能家居物联网的好办法](https://www.networkworld.com/article/3296706/finally-a-smart-way-for-insurers-to-leverage-iot-in-smart-homes.html)

### UBI 车险的崛起

UBI（<ruby> 基于使用的保险 <rt>  usage-based insurance </rt></ruby>）车险是一种“按需付费”的业务，可以通过追踪速度、位置，以及其他因素来评估风险并计算车险保费。到 2020 年，预计有 [5000 万美国司机](https://www.businessinsider.com/iot-is-changing-the-auto-insurance-industry-2015-8)会加入到 UBI 车险的项目中。

不出所料，保险公司对 UBI 车险青睐有加，因为 UBI 车险可以帮助他们更加精确地计算风险。事实上，[AIG 爱尔兰已经在尝试让国家向 25 岁以下的司机强制推行 UBI 车险](https://www.iotforall.com/iot-data-is-disrupting-the-insurance-industry/)。并且，被认定为驾驶习惯良好的司机自然也很乐意节省一笔费用。当然也有反对的声音了，大多数是来自于隐私权倡导者，以及会因此支付更多费用的群体。

### 出了故障会发生什么？

但是还有一个更加令人担忧的潜在问题：当物联网设备提供的数据有错误，或者在传输过程中出了问题会发生什么？因为尽管有自动化程序、错误检查等等，还是不可避免地会偶尔发生一些故障。

不幸的是，这并不是一个理论上某天会给细心的司机不小心多扣几块钱保费的问题。这已经是一个会带来严重后果的现实问题。就像[保险行业仍然没有想清楚谁应该“拥有”面向客户的物联网设备产生的数据](https://www.sas.com/en_us/insights/articles/big-data/5-challenges-for-iot-in-insurance-industry.html)一样，我们也不清楚谁将对这些数据所带来的问题负责。

计算机“故障”据说曾导致赫兹的出租车辆被误报为被盗（虽然在这个例子中这并不是一个严格意义上的物联网问题），并且导致无辜的租车人被逮捕并扣留。结果呢？刑事指控，多年的诉讼官司，以及舆论的指责。非常强烈的舆论指责。

我们非常容易想象一些类似的情况，比如说一个物联网传感器出了故障，然后报告说某辆车超速了，然而事实上并没有超速。想想为这件事打官司的麻烦吧，或者想想和你的保险公司如何争执不下。

（当然，这个问题还有另外一面：消费者可能会想办法篡改他们的物联网设备上的数据，以获得更低的费率或者转移事故责任。我们同样也没有可行的办法来应对*这个问题*。）

### 政府监管是否有必要

考虑到这些问题的潜在影响，以及所涉及公司对处理这些问题的无动于衷，我们似乎有理由猜想政府干预的必要性。

这可能是美国众议员 Bob Latta（俄亥俄州，共和党）[重新引入 SMART IOT（物联网现代应用、研究及趋势的现状）法案](https://www.multichannel.com/news/latta-re-ups-smart-iot-act)背后的一个动机。这项由 Latta 和美国众议员 Peter Welch（佛蒙特州，民主党）领导的两党合作物联网工作组提出的[法案](https://latta.house.gov/uploadedfiles/smart_iot_116th.pdf)，于去年秋天通过美国众议院，但被美国参议院驳回了。美国商务部需要研究物联网行业的状况，并在两年后向美国众议院能源与商业部和美国参议院商务委员会报告。

Latta 在一份声明中表示，“由于预计会有数万亿美元的经济影响，我们需要考虑物联网所带来的的政策，机遇和挑战。SMART IoT 法案会让人们更容易理解美国政府在物联网政策上的做法、可以改进的地方，以及美国联邦政策如何影响尖端技术的研究和发明。”

这项研究受到了欢迎，但该法案甚至可能不会被通过。即便通过了，物联网在两年的等待时间里也可能会翻天覆地，让美国政府还是无法跟上。

---

via: <https://www.networkworld.com/article/3396230/when-iot-systems-fail-the-risk-of-having-bad-iot-data.html>

作者：[Fredric Paul](https://www.networkworld.com/author/Fredric-Paul/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[chen-ni](https://github.com/chen-ni) 校对：[wxy](https://github.com/wxy)

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


screen 命令示例：管理多个终端会话

![](/data/attachment/album/201906/11/124801th0uy0hti3y211ha.jpg)

GNU Screen 是一个终端多路复用器（窗口管理器）。顾名思义，Screen 可以在多个交互式 shell 之间复用物理终端，因此我们可以在每个终端会话中执行不同的任务。所有的 Screen 会话都完全独立地运行程序。因此，即使会话意外关闭或断开连接，在 Screen 会话内运行的程序或进程也将继续运行。例如，当通过 SSH [升级 Ubuntu](https://www.ostechnix.com/how-to-upgrade-to-ubuntu-18-04-lts-desktop-and-server/) 服务器时，`screen` 命令将继续运行升级过程，以防万一 SSH 会话因任何原因而终止。

GNU Screen 允许我们轻松创建多个 Screen 会话，在不同会话之间切换，在会话之间复制文本，随时连上或脱离会话等等。它是每个 Linux 管理员应该在必要时学习和使用的重要命令行工具之一。在本简要指南中，我们将看到 `screen` 命令的基本用法以及在 Linux 中的示例。

### 安装 GNU Screen

GNU Screen 在大多数 Linux 操作系统的默认存储库中都可用。

要在 Arch Linux 上安装 GNU Screen，请运行：

```
$ sudo pacman -S screen
```

在 Debian、Ubuntu、Linux Mint 上：

```
$ sudo apt-get install screen
```

在 Fedora 上：

```
$ sudo dnf install screen
```

在 RHEL、CentOS 上：

```
$ sudo yum install screen
```

在 SUSE/openSUSE 上：

```
$ sudo zypper install screen
```

让我们继续看一些 `screen` 命令示例。

### 管理多个终端会话的 Screen 命令示例

在 Screen 中所有命令的默认前缀快捷方式是 `Ctrl + a`。使用 Screen 时，你需要经常使用此快捷方式。所以，要记住这个键盘快捷键。

#### 创建新的 Screen 会话

让我们创建一个新的 Screen 会话并连上它。为此，请在终端中键入以下命令：

```
screen
```

现在，在此会话中运行任何程序或进程，即使你与此会话断开连接，正在运行的进程或程序也将继续运行。

#### 从 Screen 会话脱离

要从屏幕会话中脱离，请按 `Ctrl + a` 和 `d`。你无需同时按下两个组合键。首先按 `Ctrl + a` 然后按 `d`。从会话中脱离后，你将看到类似下面的输出。

```
[detached from 29149.pts-0.sk]
```

这里，`29149` 是 Screen ID，`pts-0.sk` 是屏幕会话的名称。你可以使用 Screen ID 或相应的会话名称来连上、脱离和终止屏幕会话。

#### 创建命名会话

你还可以用你选择的任何自定义名称创建一个 Screen 会话，而不是默认用户名，如下所示。

```
screen -S ostechnix
```

上面的命令将创建一个名为 `xxxxx.ostechnix` 的新 Screen 会话，并立即连上它。要从当前会话中脱离，请按 `Ctrl + a`，然后按 `d`。

当你想要查找哪些进程在哪些会话上运行时，命名会话会很有用。例如，当在会话中设置 LAMP 系统时，你可以简单地将其命名为如下所示。

```
screen -S lampstack
```

#### 创建脱离的会话

有时，你可能想要创建一个会话，但不希望自动连上该会话。在这种情况下，运行以下命令来创建名为`senthil` 的已脱离会话：

```
screen -S senthil -d -m
```

也可以缩短为：

```
screen -dmS senthil
```

上面的命令将创建一个名为 `senthil` 的会话，但不会连上它。

#### 列出屏幕会话

要列出所有正在运行的会话（连上的或脱离的），请运行：

```
screen -ls
```

示例输出：

```
There are screens on:
    29700.senthil   (Detached)
    29415.ostechnix (Detached)
    29149.pts-0.sk  (Detached)
3 Sockets in /run/screens/S-sk.
```

如你所见，我有三个正在运行的会话，并且所有会话都已脱离。

#### 连上 Screen 会话

如果你想连上会话，例如 `29415.ostechnix`，只需运行：

```
screen -r 29415.ostechnix
```

或：

```
screen -r ostechnix
```

或使用 Screen ID：

```
screen -r 29415
```

要验证我们是否连上到上述会话，只需列出打开的会话并检查。

```
screen -ls
```

示例输出：

```
There are screens on:
        29700.senthil   (Detached)
        29415.ostechnix (Attached)
        29149.pts-0.sk  (Detached)
3 Sockets in /run/screens/S-sk.
```

如你所见，在上面的输出中，我们目前已连上到 `29415.ostechnix` 会话。要退出当前会话，请按 `ctrl + a d`。

#### 创建嵌套会话

当我们运行 `screen` 命令时，它将为我们创建一个会话。但是，我们可以创建嵌套会话（会话内的会话）。

首先，创建一个新会话或连上已打开的会话。然后我将创建一个名为 `nested` 的新会话。

```
screen -S nested
```

现在，在会话中按 `Ctrl + a` 和 `c` 创建另一个会话。只需重复此操作即可创建任意数量的嵌套 Screen 会话。每个会话都将分配一个号码。号码将从 `0` 开始。

你可以按 `Ctrl + n` 移动到下一个会话，然后按 `Ctrl + p` 移动到上一个会话。

以下是管理嵌套会话的重要键盘快捷键列表。

* `Ctrl + a "` - 列出所有会话
* `Ctrl + a 0` - 切换到会话号 0
* `Ctrl + a n` - 切换到下一个会话
* `Ctrl + a p` - 切换到上一个会话
* `Ctrl + a S` - 将当前区域水平分割为两个区域
* `Ctrl + a l` - 将当前区域垂直分割为两个区域
* `Ctrl + a Q` - 关闭除当前会话之外的所有会话
* `Ctrl + a X` - 关闭当前会话
* `Ctrl + a \` - 终止所有会话并终止 Screen
* `Ctrl + a ?` - 显示键绑定。要退出，请按回车 #### 锁定会话

Screen 有一个锁定会话的选项。为此，请按 `Ctrl + a` 和 `x`。 输入你的 Linux 密码以锁定。

```
Screen used by sk <sk> on ubuntuserver.
Password:
```

#### 记录会话

你可能希望记录 Screen 会话中的所有内容。为此，只需按 `Ctrl + a` 和 `H` 即可。

或者，你也可以使用 `-L` 参数启动新会话来启用日志记录。

```
screen -L
```

从现在开始，你在会话中做的所有活动都将记录并存储在 `$HOME` 目录中名为 `screenlog.x` 的文件中。这里，`x` 是一个数字。

你可以使用 `cat` 命令或任何文本查看器查看日志文件的内容。

![](/data/attachment/album/201906/11/124855umbptett8t7e7zqm.png)

*记录 Screen 会话*

#### 终止 Screen 会话

如果不再需要会话，只需杀死它。要杀死名为 `senthil` 的脱离会话：

```
screen -r senthil -X quit
```

或：

```
screen -X -S senthil quit
```

或：

```
screen -X -S 29415 quit
```

如果没有打开的会话，你将看到以下输出：

```
$ screen -ls
No Sockets found in /run/screens/S-sk.
```

更多细节请参照 man 手册页：

```
$ man screen
```

还有一个名为 Tmux 的类似的命令行实用程序，它与 GNU Screen 执行相同的工作。要了解更多信息，请参阅以下指南。

* [Tmux 命令示例：管理多个终端会话](https://www.ostechnix.com/tmux-command-examples-to-manage-multiple-terminal-sessions/)

### 资源

* [GNU Screen 主页](https://www.gnu.org/software/screen/)

---

via: <https://www.ostechnix.com/screen-command-examples-to-manage-multiple-terminal-sessions/>

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

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


从零写一个时间序列数据库

Summary: 这篇文章是一篇关于 Prometheus 中的时间序列数据库的设计思考，虽然写作时间有点久了，但是其中的考虑和思路非常值得参考。

编者按：Prometheus 是 CNCF 旗下的开源监控告警解决方案，它已经成为 Kubernetes 生态圈中的核心监控系统。本文作者 Fabian Reinartz 是 Prometheus 的核心开发者，这篇文章是其于 2017 年写的一篇关于 Prometheus 中的时间序列数据库的设计思考，虽然写作时间有点久了，但是其中的考虑和思路非常值得参考。长文预警，请坐下来慢慢品味。

---

![](/data/attachment/album/201906/11/180646l7cqbhazqs7nsqsn.jpg)

我从事监控工作。特别是在 [Prometheus](https://prometheus.io/) 上，监控系统包含一个自定义的时间序列数据库，并且集成在 [Kubernetes](https://kubernetes.io/) 上。

在许多方面上 Kubernetes 展现出了 Prometheus 所有的设计用途。它使得<ruby> 持续部署 <rt>  continuous deployments </rt></ruby>，<ruby> 弹性伸缩 <rt>  auto scaling </rt></ruby>和其他<ruby> 高动态环境 <rt>  highly dynamic environments </rt></ruby>下的功能可以轻易地访问。查询语句和操作模型以及其它概念决策使得 Prometheus 特别适合这种环境。但是，如果监控的工作负载动态程度显著地增加，这就会给监控系统本身带来新的压力。考虑到这一点，我们就可以特别致力于在高动态或<ruby> 瞬态服务 <rt>  transient services </rt></ruby>环境下提升它的表现，而不是回过头来解决 Prometheus 已经解决的很好的问题。

Prometheus 的存储层在历史以来都展现出卓越的性能，单一服务器就能够以每秒数百万个时间序列的速度摄入多达一百万个样本，同时只占用了很少的磁盘空间。尽管当前的存储做的很好，但我依旧提出一个新设计的存储子系统，它可以修正现存解决方案的缺点，并具备处理更大规模数据的能力。

>
> 备注：我没有数据库方面的背景。我说的东西可能是错的并让你误入歧途。你可以在 Freenode 的 #prometheus 频道上对我（fabxc）提出你的批评。
>
>
>

问题，难题，问题域
---------

首先，快速地概览一下我们要完成的东西和它的关键难题。我们可以先看一下 Prometheus 当前的做法 ，它为什么做的这么好，以及我们打算用新设计解决哪些问题。

### 时间序列数据

我们有一个收集一段时间数据的系统。

```
identifier -> (t0, v0), (t1, v1), (t2, v2), (t3, v3), ....
```

每个数据点是一个时间戳和值的元组。在监控中，时间戳是一个整数，值可以是任意数字。64 位浮点数对于计数器和测量值来说是一个好的表示方法，因此我们将会使用它。一系列严格单调递增的时间戳数据点是一个序列，它由标识符所引用。我们的标识符是一个带有<ruby> 标签维度 <rt>  label dimensions </rt></ruby>字典的度量名称。标签维度划分了单一指标的测量空间。每一个指标名称加上一个唯一标签集就成了它自己的时间序列，它有一个与之关联的<ruby> 数据流 <rt>  value stream </rt></ruby>。

这是一个典型的<ruby> 序列标识符 <rt>  series identifier </rt></ruby>集，它是统计请求指标的一部分：

```
requests_total{path="/status", method="GET", instance=”10.0.0.1:80”}
requests_total{path="/status", method="POST", instance=”10.0.0.3:80”}
requests_total{path="/", method="GET", instance=”10.0.0.2:80”}
```

让我们简化一下表示方法：度量名称可以当作另一个维度标签，在我们的例子中是 `__name__`。对于查询语句，可以对它进行特殊处理，但与我们存储的方式无关，我们后面也会见到。

```
{__name__="requests_total", path="/status", method="GET", instance=”10.0.0.1:80”}
{__name__="requests_total", path="/status", method="POST", instance=”10.0.0.3:80”}
{__name__="requests_total", path="/", method="GET", instance=”10.0.0.2:80”}
```

我们想通过标签来查询时间序列数据。在最简单的情况下，使用 `{__name__="requests_total"}` 选择所有属于 `requests_total` 指标的数据。对于所有选择的序列，我们在给定的时间窗口内获取数据点。

在更复杂的语句中，我们或许想一次性选择满足多个标签的序列，并且表示比相等条件更复杂的情况。例如，非语句（`method!="GET"`）或正则表达式匹配（`method=~"PUT|POST"`）。

这些在很大程度上定义了存储的数据和它的获取方式。

### 纵与横

在简化的视图中，所有的数据点可以分布在二维平面上。水平维度代表着时间，序列标识符域经纵轴展开。

```
series
  ^
  |   . . . . . . . . . . . . . . . . .   . . . . .   {__name__="request_total", method="GET"}
  |     . . . . . . . . . . . . . . . . . . . . . .   {__name__="request_total", method="POST"}
  |         . . . . . . .
  |       . . .     . . . . . . . . . . . . . . . .                  ...
  |     . . . . . . . . . . . . . . . . .   . . . .
  |     . . . . . . . . . .   . . . . . . . . . . .   {__name__="errors_total", method="POST"}
  |           . . .   . . . . . . . . .   . . . . .   {__name__="errors_total", method="GET"}
  |         . . . . . . . . .       . . . . .
  |       . . .     . . . . . . . . . . . . . . . .                  ...
  |     . . . . . . . . . . . . . . . .   . . . .
  v
    <-------------------- time --------------------->
```

Prometheus 通过定期地抓取一组时间序列的当前值来获取数据点。我们从中获取到的实体称为目标。因此，写入模式完全地垂直且高度并发，因为来自每个目标的样本是独立摄入的。

这里提供一些测量的规模：单一 Prometheus 实例从数万个目标中收集数据点，每个数据点都暴露在数百到数千个不同的时间序列中。

在每秒采集数百万数据点这种规模下，批量写入是一个不能妥协的性能要求。在磁盘上分散地写入单个数据点会相当地缓慢。因此，我们想要按顺序写入更大的数据块。

对于旋转式磁盘，它的磁头始终得在物理上向不同的扇区上移动，这是一个不足为奇的事实。而虽然我们都知道 SSD 具有快速随机写入的特点，但事实上它不能修改单个字节，只能写入一页或更多页的 4KiB 数据量。这就意味着写入 16 字节的样本相当于写入满满一个 4Kib 的页。这一行为就是所谓的[写入放大](https://en.wikipedia.org/wiki/Write_amplification)，这种特性会损耗你的 SSD。因此它不仅影响速度，而且还毫不夸张地在几天或几个周内破坏掉你的硬件。

关于此问题更深层次的资料，[“Coding for SSDs”系列](http://codecapsule.com/2014/02/12/coding-for-ssds-part-1-introduction-and-table-of-contents/)博客是极好的资源。让我们想想主要的用处：顺序写入和批量写入分别对于旋转式磁盘和 SSD 来说都是理想的写入模式。大道至简。

查询模式比起写入模式明显更不同。我们可以查询单一序列的一个数据点，也可以对 10000 个序列查询一个数据点，还可以查询一个序列几个周的数据点，甚至是 10000 个序列几个周的数据点。因此在我们的二维平面上，查询范围不是完全水平或垂直的，而是二者形成矩形似的组合。

[记录规则](https://prometheus.io/docs/practices/rules/)可以减轻已知查询的问题，但对于<ruby> 点对点 <rt>  ad-hoc </rt></ruby>查询来说并不是一个通用的解决方法。

我们知道我们想要批量地写入，但我们得到的仅仅是一系列垂直数据点的集合。当查询一段时间窗口内的数据点时，我们不仅很难弄清楚在哪才能找到这些单独的点，而且不得不从磁盘上大量随机的地方读取。也许一条查询语句会有数百万的样本，即使在最快的 SSD 上也会很慢。读入也会从磁盘上获取更多的数据而不仅仅是 16 字节的样本。SSD 会加载一整页，HDD 至少会读取整个扇区。不论哪一种，我们都在浪费宝贵的读取吞吐量。

因此在理想情况下，同一序列的样本将按顺序存储，这样我们就能通过尽可能少的读取来扫描它们。最重要的是，我们仅需要知道序列的起始位置就能访问所有的数据点。

显然，将收集到的数据写入磁盘的理想模式与能够显著提高查询效率的布局之间存在着明显的抵触。这是我们 TSDB 需要解决的一个基本问题。

#### 当前的解决方法

是时候看一下当前 Prometheus 是如何存储数据来解决这一问题的，让我们称它为“V2”。

我们创建一个时间序列的文件，它包含所有样本并按顺序存储。因为每几秒附加一个样本数据到所有文件中非常昂贵，我们在内存中打包 1Kib 样本序列的数据块，一旦打包完成就附加这些数据块到单独的文件中。这一方法解决了大部分问题。写入目前是批量的，样本也是按顺序存储的。基于给定的同一序列的样本相对之前的数据仅发生非常小的改变这一特性，它还支持非常高效的压缩格式。Facebook 在他们 Gorilla TSDB 上的论文中描述了一个相似的基于数据块的方法，并且[引入了一种压缩格式](http://www.vldb.org/pvldb/vol8/p1816-teller.pdf)，它能够减少 16 字节的样本到平均 1.37 字节。V2 存储使用了包含 Gorilla 变体等在内的各种压缩格式。

```
   +----------+---------+---------+---------+---------+           series A
   +----------+---------+---------+---------+---------+
          +----------+---------+---------+---------+---------+    series B
          +----------+---------+---------+---------+---------+
                              . . .
 +----------+---------+---------+---------+---------+---------+   series XYZ
 +----------+---------+---------+---------+---------+---------+
   chunk 1    chunk 2   chunk 3     ...
```

尽管基于块存储的方法非常棒，但为每个序列保存一个独立的文件会给 V2 存储带来麻烦，因为：

* 实际上，我们需要的文件比当前收集数据的时间序列数量要多得多。多出的部分在<ruby> 序列分流 <rt>  Series Churn </rt></ruby>上。有几百万个文件，迟早会使用光文件系统中的 [inode](https://en.wikipedia.org/wiki/Inode)。这种情况我们只能通过重新格式化来恢复磁盘，这种方式是最具有破坏性的。我们通常不想为了适应一个应用程序而格式化磁盘。
* 即使是分块写入，每秒也会产生数千块的数据块并且准备持久化。这依然需要每秒数千次的磁盘写入。尽管通过为每个序列打包好多个块来缓解，但这反过来还是增加了等待持久化数据的总内存占用。
* 要保持所有文件打开来进行读写是不可行的。特别是因为 99% 的数据在 24 小时之后不再会被查询到。如果查询它，我们就得打开数千个文件，找到并读取相关的数据点到内存中，然后再关掉。这样做就会引起很高的查询延迟，数据块缓存加剧会导致新的问题，这一点在“资源消耗”一节另作讲述。
* 最终，旧的数据需要被删除，并且数据需要从数百万文件的头部删除。这就意味着删除实际上是写密集型操作。此外，循环遍历数百万文件并且进行分析通常会导致这一过程花费数小时。当它完成时，可能又得重新来过。喔天，继续删除旧文件又会进一步导致 SSD 产生写入放大。
* 目前所积累的数据块仅维持在内存中。如果应用崩溃，数据就会丢失。为了避免这种情况，内存状态会定期的保存在磁盘上，这比我们能接受数据丢失窗口要长的多。恢复检查点也会花费数分钟，导致很长的重启周期。

我们能够从现有的设计中学到的关键部分是数据块的概念，我们当然希望保留这个概念。最新的数据块会保持在内存中一般也是好的主意。毕竟，最新的数据会大量的查询到。

一个时间序列对应一个文件，这个概念是我们想要替换掉的。

### 序列分流

在 Prometheus 的<ruby> 上下文 <rt>  context </rt></ruby>中，我们使用术语<ruby> 序列分流 <rt>  series churn </rt></ruby>来描述一个时间序列集合变得不活跃，即不再接收数据点，取而代之的是出现一组新的活跃序列。

例如，由给定微服务实例产生的所有序列都有一个相应的“instance”标签来标识其来源。如果我们为微服务执行了<ruby> 滚动更新 <rt>  rolling update </rt></ruby>，并且为每个实例替换一个新的版本，序列分流便会发生。在更加动态的环境中，这些事情基本上每小时都会发生。像 Kubernetes 这样的<ruby> 集群编排 <rt>  Cluster orchestration </rt></ruby>系统允许应用连续性的自动伸缩和频繁的滚动更新，这样也许会创建成千上万个新的应用程序实例，并且伴随着全新的时间序列集合，每天都是如此。

```
series
  ^
  |   . . . . . .
  |   . . . . . .
  |   . . . . . .
  |               . . . . . . .
  |               . . . . . . .
  |               . . . . . . .
  |                             . . . . . .
  |                             . . . . . .
  |                                         . . . . .
  |                                         . . . . .
  |                                         . . . . .
  v
    <-------------------- time --------------------->
```

所以即便整个基础设施的规模基本保持不变，过一段时间后数据库内的时间序列还是会成线性增长。尽管 Prometheus 很愿意采集 1000 万个时间序列数据，但要想在 10 亿个序列中找到数据，查询效果还是会受到严重的影响。

#### 当前解决方案

当前 Prometheus 的 V2 存储系统对所有当前保存的序列拥有基于 LevelDB 的索引。它允许查询语句含有给定的<ruby> 标签对 <rt>  label pair </rt></ruby>，但是缺乏可伸缩的方法来从不同的标签选集中组合查询结果。

例如，从所有的序列中选择标签 `__name__="requests_total"` 非常高效，但是选择 `instance="A" AND __name__="requests_total"` 就有了可伸缩性的问题。我们稍后会重新考虑导致这一点的原因和能够提升查找延迟的调整方法。

事实上正是这个问题才催生出了对更好的存储系统的最初探索。Prometheus 需要为查找亿万个时间序列改进索引方法。

### 资源消耗

当试图扩展 Prometheus（或其他任何事情，真的）时，资源消耗是永恒不变的话题之一。但真正困扰用户的并不是对资源的绝对渴求。事实上，由于给定的需求，Prometheus 管理着令人难以置信的吞吐量。问题更在于面对变化时的相对未知性与不稳定性。通过其架构设计，V2 存储系统缓慢地构建了样本数据块，这一点导致内存占用随时间递增。当数据块完成之后，它们可以写到磁盘上并从内存中清除。最终，Prometheus 的内存使用到达稳定状态。直到监测环境发生了改变——每次我们扩展应用或者进行滚动更新，序列分流都会增加内存、CPU、磁盘 I/O 的使用。

如果变更正在进行，那么它最终还是会到达一个稳定的状态，但比起更加静态的环境，它的资源消耗会显著地提高。过渡时间通常为数个小时，而且难以确定最大资源使用量。

为每个时间序列保存一个文件这种方法也使得一个单个查询就很容易崩溃 Prometheus 进程。当查询的数据没有缓存在内存中，查询的序列文件就会被打开，然后将含有相关数据点的数据块读入内存。如果数据量超出内存可用量，Prometheus 就会因 OOM 被杀死而退出。

在查询语句完成之后，加载的数据便可以被再次释放掉，但通常会缓存更长的时间，以便更快地查询相同的数据。后者看起来是件不错的事情。

最后，我们看看之前提到的 SSD 的写入放大，以及 Prometheus 是如何通过批量写入来解决这个问题的。尽管如此，在许多地方还是存在因为批量太小以及数据未精确对齐页边界而导致的写入放大。对于更大规模的 Prometheus 服务器，现实当中会发现缩减硬件寿命的问题。这一点对于高写入吞吐量的数据库应用来说仍然相当普遍，但我们应该放眼看看是否可以解决它。

### 重新开始

到目前为止我们对于问题域、V2 存储系统是如何解决它的，以及设计上存在的问题有了一个清晰的认识。我们也看到了许多很棒的想法，这些或多或少都可以拿来直接使用。V2 存储系统相当数量的问题都可以通过改进和部分的重新设计来解决，但为了好玩（当然，在我仔细的验证想法之后），我决定试着写一个完整的时间序列数据库——从头开始，即向文件系统写入字节。

性能与资源使用这种最关键的部分直接影响了存储格式的选取。我们需要为数据找到正确的算法和磁盘布局来实现一个高性能的存储层。

这就是我解决问题的捷径——跳过令人头疼、失败的想法，数不尽的草图，泪水与绝望。

### V3—宏观设计

我们存储系统的宏观布局是什么？简而言之，是当我们在数据文件夹里运行 `tree` 命令时显示的一切。看看它能给我们带来怎样一副惊喜的画面。

```
$ tree ./data
./data
+-- b-000001
|   +-- chunks
|   |   +-- 000001
|   |   +-- 000002
|   |   +-- 000003
|   +-- index
|   +-- meta.json
+-- b-000004
|   +-- chunks
|   |   +-- 000001
|   +-- index
|   +-- meta.json
+-- b-000005
|   +-- chunks
|   |   +-- 000001
|   +-- index
|   +-- meta.json
+-- b-000006
    +-- meta.json
    +-- wal
        +-- 000001
        +-- 000002
        +-- 000003
```

在最顶层，我们有一系列以 `b-` 为前缀编号的<ruby> 块 <rt>  block </rt></ruby>。每个块中显然保存了索引文件和含有更多编号文件的 `chunk` 文件夹。`chunks` 目录只包含不同序列<ruby> 数据点的原始块 <rt>  raw chunks of data points </rt> <ruby>  。与 V2 存储系统一样，这使得通过时间窗口读取序列数据非常高效并且允许我们使用相同的有效压缩算法。这一点被证实行之有效，我们也打算沿用。显然，这里并不存在含有单个序列的文件，而是一堆保存着许多序列的数据块。 </ruby></ruby>

`index` 文件的存在应该不足为奇。让我们假设它拥有黑魔法，可以让我们找到标签、可能的值、整个时间序列和存放数据点的数据块。

但为什么这里有好几个文件夹都是索引和块文件的布局？并且为什么存在最后一个包含 `wal` 文件夹？理解这两个疑问便能解决九成的问题。

#### 许多小型数据库

我们分割横轴，即将时间域分割为不重叠的块。每一块扮演着完全独立的数据库，它包含该时间窗口所有的时间序列数据。因此，它拥有自己的索引和一系列块文件。

```
t0            t1             t2             t3             now
 +-----------+  +-----------+  +-----------+  +-----------+
 |           |  |           |  |           |  |           |                 +------------+
 |           |  |           |  |           |  |  mutable  | <--- write ---- ┤ Prometheus |
 |           |  |           |  |           |  |           |                 +------------+
 +-----------+  +-----------+  +-----------+  +-----------+                        ^
       +--------------+-------+------+--------------+                              |
                              |                                                  query
                              |                                                    |
                            merge -------------------------------------------------+

```

每一块的数据都是<ruby> 不可变的 <rt>  immutable </rt></ruby>。当然，当我们采集新数据时，我们必须能向最近的块中添加新的序列和样本。对于该数据块，所有新的数据都将写入内存中的数据库中，它与我们的持久化的数据块一样提供了查找属性。内存中的数据结构可以高效地更新。为了防止数据丢失，所有传入的数据同样被写入临时的<ruby> 预写日志 <rt>  write ahead log </rt></ruby>中，这就是 `wal` 文件夹中的一些列文件，我们可以在重新启动时通过它们重新填充内存数据库。

所有这些文件都带有序列化格式，有我们所期望的所有东西：许多标志、偏移量、变体和 CRC32 校验和。纸上得来终觉浅，绝知此事要躬行。

这种布局允许我们扩展查询范围到所有相关的块上。每个块上的部分结果最终合并成完整的结果。

这种横向分割增加了一些很棒的功能：

* 当查询一个时间范围，我们可以简单地忽略所有范围之外的数据块。通过减少需要检查的数据集，它可以初步解决序列分流的问题。
* 当完成一个块，我们可以通过顺序的写入大文件从内存数据库中保存数据。这样可以避免任何的写入放大，并且 SSD 与 HDD 均适用。
* 我们延续了 V2 存储系统的一个好的特性，最近使用而被多次查询的数据块，总是保留在内存中。
* 很好，我们也不再受限于 1KiB 的数据块尺寸，以使数据在磁盘上更好地对齐。我们可以挑选对单个数据点和压缩格式最合理的尺寸。
* 删除旧数据变得极为简单快捷。我们仅仅只需删除一个文件夹。记住，在旧的存储系统中我们不得不花数个小时分析并重写数亿个文件。

每个块还包含了 `meta.json` 文件。它简单地保存了关于块的存储状态和包含的数据，以便轻松了解存储状态及其包含的数据。

##### mmap

将数百万个小文件合并为少数几个大文件使得我们用很小的开销就能保持所有的文件都打开。这就解除了对 [mmap(2)](https://en.wikipedia.org/wiki/Mmap) 的使用的阻碍，这是一个允许我们通过文件透明地回传虚拟内存的系统调用。简单起见，你可以将其视为<ruby> 交换空间 <rt>  swap space </rt></ruby>，只是我们所有的数据已经保存在了磁盘上，并且当数据换出内存后不再会发生写入。

这意味着我们可以当作所有数据库的内容都视为在内存中却不占用任何物理内存。仅当我们访问数据库文件某些字节范围时，操作系统才会从磁盘上<ruby> 惰性加载 <rt>  lazy load </rt></ruby>页数据。这使得我们将所有数据持久化相关的内存管理都交给了操作系统。通常，操作系统更有资格作出这样的决定，因为它可以全面了解整个机器和进程。查询的数据可以相当积极的缓存进内存，但内存压力会使得页被换出。如果机器拥有未使用的内存，Prometheus 目前将会高兴地缓存整个数据库，但是一旦其他进程需要，它就会立刻返回那些内存。

因此，查询不再轻易地使我们的进程 OOM，因为查询的是更多的持久化的数据而不是装入内存中的数据。内存缓存大小变得完全自适应，并且仅当查询真正需要时数据才会被加载。

就个人理解，这就是当今大多数数据库的工作方式，如果磁盘格式允许，这是一种理想的方式，——除非有人自信能在这个过程中超越操作系统。我们做了很少的工作但确实从外面获得了很多功能。

#### 压缩

存储系统需要定期“切”出新块并将之前完成的块写入到磁盘中。仅在块成功的持久化之后，才会被删除之前用来恢复内存块的日志文件（wal）。

我们希望将每个块的保存时间设置的相对短一些（通常配置为 2 小时），以避免内存中积累太多的数据。当查询多个块，我们必须将它们的结果合并为一个整体的结果。合并过程显然会消耗资源，一个星期的查询不应该由超过 80 个的部分结果所组成。

为了实现两者，我们引入<ruby> 压缩 <rt>  compaction </rt></ruby>。压缩描述了一个过程：取一个或更多个数据块并将其写入一个可能更大的块中。它也可以在此过程中修改现有的数据。例如，清除已经删除的数据，或重建样本块以提升查询性能。

```
t0             t1            t2             t3             t4             now
 +------------+  +----------+  +-----------+  +-----------+  +-----------+
 | 1          |  | 2        |  | 3         |  | 4         |  | 5 mutable |    before
 +------------+  +----------+  +-----------+  +-----------+  +-----------+
 +-----------------------------------------+  +-----------+  +-----------+
 | 1              compacted                |  | 4         |  | 5 mutable |    after (option A)
 +-----------------------------------------+  +-----------+  +-----------+
 +--------------------------+  +--------------------------+  +-----------+
 | 1       compacted        |  | 3      compacted         |  | 5 mutable |    after (option B)
 +--------------------------+  +--------------------------+  +-----------+
```

在这个例子中我们有顺序块 `[1,2,3,4]`。块 1、2、3 可以压缩在一起，新的布局将会是 `[1,4]`。或者，将它们成对压缩为 `[1,3]`。所有的时间序列数据仍然存在，但现在整体上保存在更少的块中。这极大程度地缩减了查询时间的消耗，因为需要合并的部分查询结果变得更少了。

#### 保留

我们看到了删除旧的数据在 V2 存储系统中是一个缓慢的过程，并且消耗 CPU、内存和磁盘。如何才能在我们基于块的设计上清除旧的数据？相当简单，只要删除我们配置的保留时间窗口里没有数据的块文件夹即可。在下面的例子中，块 1 可以被安全地删除，而块 2 则必须一直保留，直到它落在保留窗口边界之外。

```
                      |
 +------------+  +----+-----+  +-----------+  +-----------+  +-----------+
 | 1          |  | 2  |     |  | 3         |  | 4         |  | 5         |   . . .
 +------------+  +----+-----+  +-----------+  +-----------+  +-----------+
                      |
                      |
             retention boundary
```

随着我们不断压缩先前压缩的块，旧数据越大，块可能变得越大。因此必须为其设置一个上限，以防数据块扩展到整个数据库而损失我们设计的最初优势。

方便的是，这一点也限制了部分存在于保留窗口内部分存在于保留窗口外的块的磁盘消耗总量。例如上面例子中的块 2。当设置了最大块尺寸为总保留窗口的 10% 后，我们保留块 2 的总开销也有了 10% 的上限。

总结一下，保留与删除从非常昂贵到了几乎没有成本。

>
> 如果你读到这里并有一些数据库的背景知识，现在你也许会问：这些都是最新的技术吗？——并不是；而且可能还会做的更好。
>
>
> 在内存中批量处理数据，在预写日志中跟踪，并定期写入到磁盘的模式在现在相当普遍。
>
>
> 我们看到的好处无论在什么领域的数据里都是适用的。遵循这一方法最著名的开源案例是 LevelDB、Cassandra、InfluxDB 和 HBase。关键是避免重复发明劣质的轮子，采用经过验证的方法，并正确地运用它们。
>
>
> 脱离场景添加你自己的黑魔法是一种不太可能的情况。
>
>
>

### 索引

研究存储改进的最初想法是解决序列分流的问题。基于块的布局减少了查询所要考虑的序列总数。因此假设我们索引查找的复杂度是 `O(n^2)`，我们就要设法减少 n 个相当数量的复杂度，之后就相当于改进 `O(n^2)` 复杂度。——恩，等等……糟糕。

快速回顾一下“算法 101”课上提醒我们的，在理论上它并未带来任何好处。如果之前就很糟糕，那么现在也一样。理论是如此的残酷。

实际上，我们大多数的查询已经可以相当快响应。但是，跨越整个时间范围的查询仍然很慢，尽管只需要找到少部分数据。追溯到所有这些工作之前，最初我用来解决这个问题的想法是：我们需要一个更大容量的[倒排索引](https://en.wikipedia.org/wiki/Inverted_index)。

倒排索引基于数据项内容的子集提供了一种快速的查找方式。简单地说，我可以通过标签 `app="nginx"` 查找所有的序列而无需遍历每个文件来看它是否包含该标签。

为此，每个序列被赋上一个唯一的 ID ，通过该 ID 可以恒定时间内检索它（`O(1)`）。在这个例子中 ID 就是我们的正向索引。

>
> 示例：如果 ID 为 10、29、9 的序列包含标签 `app="nginx"`，那么 “nginx”的倒排索引就是简单的列表 `[10, 29, 9]`，它就能用来快速地获取所有包含标签的序列。即使有 200 多亿个数据序列也不会影响查找速度。
>
>
>

简而言之，如果 `n` 是我们序列总数，`m` 是给定查询结果的大小，使用索引的查询复杂度现在就是 `O(m)`。查询语句依据它获取数据的数量 `m` 而不是被搜索的数据体 `n` 进行缩放是一个很好的特性，因为 `m` 一般相当小。

为了简单起见，我们假设可以在恒定时间内查找到倒排索引对应的列表。

实际上，这几乎就是 V2 存储系统具有的倒排索引，也是提供在数百万序列中查询性能的最低需求。敏锐的人会注意到，在最坏情况下，所有的序列都含有标签，因此 `m` 又成了 `O(n)`。这一点在预料之中，也相当合理。如果你查询所有的数据，它自然就会花费更多时间。一旦我们牵扯上了更复杂的查询语句就会有问题出现。

#### 标签组合

与数百万个序列相关的标签很常见。假设横向扩展着数百个实例的“foo”微服务，并且每个实例拥有数千个序列。每个序列都会带有标签 `app="foo"`。当然，用户通常不会查询所有的序列而是会通过进一步的标签来限制查询。例如，我想知道服务实例接收到了多少请求，那么查询语句便是 `__name__="requests_total" AND app="foo"`。

为了找到满足两个标签选择子的所有序列，我们得到每一个标签的倒排索引列表并取其交集。结果集通常会比任何一个输入列表小一个数量级。因为每个输入列表最坏情况下的大小为 `O(n)`，所以在嵌套地为每个列表进行<ruby> 暴力求解 <rt>  brute force solution </rt> <ruby>  下，运行时间为  <code>   O(n^2)  </code>  。相同的成本也适用于其他的集合操作，例如取并集（  <code>   app="foo" OR app="bar"  </code>  ）。当在查询语句上添加更多标签选择子，耗费就会指数增长到  <code>   O(n^3)  </code>  、  <code>   O(n^4)  </code>  、  <code>   O(n^5)  </code>  ……  <code>   O(n^k)  </code>  。通过改变执行顺序，可以使用很多技巧以优化运行效率。越复杂，越是需要关于数据特征和标签之间相关性的知识。这引入了大量的复杂度，但是并没有减少算法的最坏运行时间。 </ruby></ruby>

这便是 V2 存储系统使用的基本方法，幸运的是，看似微小的改动就能获得显著的提升。如果我们假设倒排索引中的 ID 都是排序好的会怎么样？

假设这个例子的列表用于我们最初的查询：

```
__name__="requests_total"   ->   [ 9999, 1000, 1001, 2000000, 2000001, 2000002, 2000003 ]
     app="foo"              ->   [ 1, 3, 10, 11, 12, 100, 311, 320, 1000, 1001, 10002 ]

             intersection   =>   [ 1000, 1001 ]
```

它的交集相当小。我们可以为每个列表的起始位置设置游标，每次从最小的游标处移动来找到交集。当二者的数字相等，我们就添加它到结果中并移动二者的游标。总体上，我们以锯齿形扫描两个列表，因此总耗费是 `O(2n)=O(n)`，因为我们总是在一个列表上移动。

两个以上列表的不同集合操作也类似。因此 `k` 个集合操作仅仅改变了因子 `O(k*n)` 而不是最坏情况下查找运行时间的指数 `O(n^k)`。

我在这里所描述的是几乎所有[全文搜索引擎](https://en.wikipedia.org/wiki/Search_engine_indexing#Inverted_indices)使用的标准搜索索引的简化版本。每个序列描述符都视作一个简短的“文档”，每个标签（名称 + 固定值）作为其中的“单词”。我们可以忽略搜索引擎索引中通常遇到的很多附加数据，例如单词位置和和频率。

关于改进实际运行时间的方法似乎存在无穷无尽的研究，它们通常都是对输入数据做一些假设。不出意料的是，还有大量技术来压缩倒排索引，其中各有利弊。因为我们的“文档”比较小，而且“单词”在所有的序列里大量重复，压缩变得几乎无关紧要。例如，一个真实的数据集约有 440 万个序列与大约 12 个标签，每个标签拥有少于 5000 个单独的标签。对于最初的存储版本，我们坚持使用基本的方法而不压缩，仅做微小的调整来跳过大范围非交叉的 ID。

尽管维持排序好的 ID 听起来很简单，但实践过程中不是总能完成的。例如，V2 存储系统为新的序列赋上一个哈希值来当作 ID，我们就不能轻易地排序倒排索引。

另一个艰巨的任务是当磁盘上的数据被更新或删除掉后修改其索引。通常，最简单的方法是重新计算并写入，但是要保证数据库在此期间可查询且具有一致性。V3 存储系统通过每块上具有的独立不可变索引来解决这一问题，该索引仅通过压缩时的重写来进行修改。只有可变块上的索引需要被更新，它完全保存在内存中。

基准测试
----

我从存储的基准测试开始了初步的开发，它基于现实世界数据集中提取的大约 440 万个序列描述符，并生成合成数据点以输入到这些序列中。这个阶段的开发仅仅测试了单独的存储系统，对于快速找到性能瓶颈和高并发负载场景下的触发死锁至关重要。

在完成概念性的开发实施之后，该基准测试能够在我的 Macbook Pro 上维持每秒 2000 万的吞吐量 —— 并且这都是在打开着十几个 Chrome 的页面和 Slack 的时候。因此，尽管这听起来都很棒，它这也表明推动这项测试没有的进一步价值（或者是没有在高随机环境下运行）。毕竟，它是合成的数据，因此在除了良好的第一印象外没有多大价值。比起最初的设计目标高出 20 倍，是时候将它部署到真正的 Prometheus 服务器上了，为它添加更多现实环境中的开销和场景。

我们实际上没有可重现的 Prometheus 基准测试配置，特别是没有对于不同版本的 A/B 测试。亡羊补牢为时不晚，[不过现在就有一个了](https://github.com/prometheus/prombench)！

我们的工具可以让我们声明性地定义基准测试场景，然后部署到 AWS 的 Kubernetes 集群上。尽管对于全面的基准测试来说不是最好环境，但它肯定比 64 核 128GB 内存的专用<ruby> 裸机服务器 <rt>  bare metal servers </rt></ruby>更能反映出我们的用户群体。

我们部署了两个 Prometheus 1.5.2 服务器（V2 存储系统）和两个来自 2.0 开发分支的 Prometheus （V3 存储系统）。每个 Prometheus 运行在配备 SSD 的专用服务器上。我们将横向扩展的应用部署在了工作节点上，并且让其暴露典型的微服务度量。此外，Kubernetes 集群本身和节点也被监控着。整套系统由另一个 Meta-Prometheus 所监督，它监控每个 Prometheus 的健康状况和性能。

为了模拟序列分流，微服务定期的扩展和收缩来移除旧的 pod 并衍生新的 pod，生成新的序列。通过选择“典型”的查询来模拟查询负载，对每个 Prometheus 版本都执行一次。

总体上，伸缩与查询的负载以及采样频率极大的超出了 Prometheus 的生产部署。例如，我们每隔 15 分钟换出 60% 的微服务实例去产生序列分流。在现代的基础设施上，一天仅大约会发生 1-5 次。这就保证了我们的 V3 设计足以处理未来几年的工作负载。就结果而言，Prometheus 1.5.2 和 2.0 之间的性能差异在极端的环境下会变得更大。

总而言之，我们每秒从 850 个目标里收集大约 11 万份样本，每次暴露 50 万个序列。

在此系统运行一段时间之后，我们可以看一下数字。我们评估了两个版本在 12 个小时之后到达稳定时的几个指标。

>
> 请注意从 Prometheus 图形界面的截图中轻微截断的 Y 轴
>
>
>

![Heap usage GB](/data/attachment/album/201906/11/180818v78vvozde7kpg2w7.png)

*堆内存使用（GB）*

内存资源的使用对用户来说是最为困扰的问题，因为它相对的不可预测且可能导致进程崩溃。

显然，查询的服务器正在消耗内存，这很大程度上归咎于查询引擎的开销，这一点可以当作以后优化的主题。总的来说，Prometheus 2.0 的内存消耗减少了 3-4 倍。大约 6 小时之后，在 Prometheus 1.5 上有一个明显的峰值，与我们设置的 6 小时的保留边界相对应。因为删除操作成本非常高，所以资源消耗急剧提升。这一点在下面几张图中均有体现。

![CPU usage cores](/data/attachment/album/201906/11/180819b2jxxdln8p8u9pb2.png)

*CPU 使用（核心/秒）*

类似的模式也体现在 CPU 使用上，但是查询的服务器与非查询的服务器之间的差异尤为明显。每秒获取大约 11 万个数据需要 0.5 核心/秒的 CPU 资源，比起评估查询所花费的 CPU 时间，我们的新存储系统 CPU 消耗可忽略不计。总的来说，新存储需要的 CPU 资源减少了 3 到 10 倍。

![Disk writes](/data/attachment/album/201906/11/180820veq9tj2eedwv24ll.png)

*磁盘写入（MB/秒）*

迄今为止最引人注目和意想不到的改进表现在我们的磁盘写入利用率上。这就清楚的说明了为什么 Prometheus 1.5 很容易造成 SSD 损耗。我们看到最初的上升发生在第一个块被持久化到序列文件中的时期，然后一旦删除操作引发了重写就会带来第二个上升。令人惊讶的是，查询的服务器与非查询的服务器显示出了非常不同的利用率。

在另一方面，Prometheus 2.0 每秒仅向其预写日志写入大约一兆字节。当块被压缩到磁盘时，写入定期地出现峰值。这在总体上节省了：惊人的 97-99%。

![Disk usage](/data/attachment/album/201906/11/180821s8amft225g81gzf5.png)

*磁盘大小（GB）*

与磁盘写入密切相关的是总磁盘空间占用量。由于我们对样本（这是我们的大部分数据）几乎使用了相同的压缩算法，因此磁盘占用量应当相同。在更为稳定的系统中，这样做很大程度上是正确地，但是因为我们需要处理高的序列分流，所以还要考虑每个序列的开销。

如我们所见，Prometheus 1.5 在这两个版本达到稳定状态之前，使用的存储空间因其保留操作而急速上升。Prometheus 2.0 似乎在每个序列上的开销显著降低。我们可以清楚的看到预写日志线性地充满整个存储空间，然后当压缩完成后瞬间下降。事实上对于两个 Prometheus 2.0 服务器，它们的曲线并不是完全匹配的，这一点需要进一步的调查。

前景大好。剩下最重要的部分是查询延迟。新的索引应当优化了查找的复杂度。没有实质上发生改变的是处理数据的过程，例如 `rate()` 函数或聚合。这些就是查询引擎要做的东西了。

![Query latency](/data/attachment/album/201906/11/180823fwyw0w81w4dn8waw.png)

*第 99 个百分位查询延迟（秒）*

数据完全符合预期。在 Prometheus 1.5 上，查询延迟随着存储的序列而增加。只有在保留操作开始且旧的序列被删除后才会趋于稳定。作为对比，Prometheus 2.0 从一开始就保持在合适的位置。

我们需要花一些心思在数据是如何被采集上，对服务器发出的查询请求通过对以下方面的估计来选择：范围查询和即时查询的组合，进行更轻或更重的计算，访问更多或更少的文件。它并不需要代表真实世界里查询的分布。也不能代表冷数据的查询性能，我们可以假设所有的样本数据都是保存在内存中的热数据。

尽管如此，我们可以相当自信地说，整体查询效果对序列分流变得非常有弹性，并且在高压基准测试场景下提升了 4 倍的性能。在更为静态的环境下，我们可以假设查询时间大多数花费在了查询引擎上，改善程度明显较低。

![Ingestion rate](/data/attachment/album/201906/11/180824kzjqgj5gjm3gyfqe.png)

*摄入的样本/秒*

最后，快速地看一下不同 Prometheus 服务器的摄入率。我们可以看到搭载 V3 存储系统的两个服务器具有相同的摄入速率。在几个小时之后变得不稳定，这是因为不同的基准测试集群节点由于高负载变得无响应，与 Prometheus 实例无关。（两个 2.0 的曲线完全匹配这一事实希望足够具有说服力）

尽管还有更多 CPU 和内存资源，两个 Prometheus 1.5.2 服务器的摄入率大大降低。序列分流的高压导致了无法采集更多的数据。

那么现在每秒可以摄入的<ruby> 绝对最大 <rt>  absolute maximum </rt></ruby>样本数是多少？

但是现在你可以摄取的每秒绝对最大样本数是多少？

我不知道 —— 虽然这是一个相当容易的优化指标，但除了稳固的基线性能之外，它并不是特别有意义。

有很多因素都会影响 Prometheus 数据流量，而且没有一个单独的数字能够描述捕获质量。最大摄入率在历史上是一个导致基准出现偏差的度量，并且忽视了更多重要的层面，例如查询性能和对序列分流的弹性。关于资源使用线性增长的大致猜想通过一些基本的测试被证实。很容易推断出其中的原因。

我们的基准测试模拟了高动态环境下 Prometheus 的压力，它比起真实世界中的更大。结果表明，虽然运行在没有优化的云服务器上，但是已经超出了预期的效果。最终，成功将取决于用户反馈而不是基准数字。

>
> 注意：在撰写本文的同时，Prometheus 1.6 正在开发当中，它允许更可靠地配置最大内存使用量，并且可能会显著地减少整体的消耗，有利于稍微提高 CPU 使用率。我没有重复对此进行测试，因为整体结果变化不大，尤其是面对高序列分流的情况。
>
>
>

总结
--

Prometheus 开始应对高基数序列与单独样本的吞吐量。这仍然是一项富有挑战性的任务，但是新的存储系统似乎向我们展示了未来的一些好东西。

第一个配备 V3 存储系统的 [alpha 版本 Prometheus 2.0](https://prometheus.io/blog/2017/04/10/promehteus-20-sneak-peak/) 已经可以用来测试了。在早期阶段预计还会出现崩溃，死锁和其他 bug。

存储系统的代码可以在[这个单独的项目中找到](https://github.com/prometheus/tsdb)。Prometheus 对于寻找高效本地存储时间序列数据库的应用来说可能非常有用，这一点令人非常惊讶。

>
> 这里需要感谢很多人作出的贡献，以下排名不分先后：
>
>
> Bjoern Rabenstein 和 Julius Volz 在 V2 存储引擎上的打磨工作以及 V3 存储系统的反馈，这为新一代的设计奠定了基础。
>
>
> Wilhelm Bierbaum 对新设计不断的建议与见解作出了很大的贡献。Brian Brazil 不断的反馈确保了我们最终得到的是语义上合理的方法。与 Peter Bourgon 深刻的讨论验证了设计并形成了这篇文章。
>
>
> 别忘了我们整个 CoreOS 团队与公司对于这项工作的赞助与支持。感谢所有那些听我一遍遍唠叨 SSD、浮点数、序列化格式的同学。
>
>
>

---

via: <https://fabxc.org/blog/2017-04-10-writing-a-tsdb/>

作者：[Fabian Reinartz](https://twitter.com/fabxc) 译者：[LuuMing](https://github.com/LuuMing) 校对：[wxy](https://github.com/wxy)

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


开源新闻：开源仿生腿、Python 数据管道、数据泄露检测

Summary: 了解过去两周来最大的开源头条新闻。

了解过去两周来最大的开源头条新闻。

>
> 了解过去两周来最大的开源头条新闻。
>
>
>

![](/data/attachment/album/201906/11/221610g8jjp8hpp024v6pd.png)

在本期开源新闻综述中，我们将介绍一个开源仿生腿、一个新的开源医学影像组织，麦肯锡发布的首个开源软件，以及更多！

### 使用开源推进仿生学

我们这一代人从电视剧《六百万美元人》和《仿生女人》中学到了仿生学一词。让科幻小说（尽管基于事实）正在成为现实的，要归功于[由密歇根大学和 Shirley Ryan AbilityLab 设计](https://news.umich.edu/open-source-bionic-leg-first-of-its-kind-platform-aims-to-rapidly-advance-prosthetics/)的假肢。

该腿采用简单、低成本的模块化设计，“旨在通过为仿生学领域的零碎研究工作提供统一的平台，提高患者的生活质量并加速科学进步”。根据首席设计师 Elliot Rouse 的说法，它将“使研究人员能够有效地解决与一系列的实验室和社区活动中控制仿生腿相关的挑战。”

你可以从[开源腿](https://opensourceleg.com/)网站了解有该腿的更多信息并下载该设计。

### 麦肯锡发布了一个用于构建产品级数据管道的 Python 库

咨询巨头麦肯锡公司最近发布了其[第一个开源工具](https://www.information-age.com/kedro-mckinseys-open-source-software-tool-123482991/)，名为 Kedro，它是一个用于创建机器学习和数据管道的 Python 库。

Kedro 使得“管理大型工作流程更加容易，并确保整个项目的代码质量始终如一”，产品经理 Yetunde Dada 说。虽然它最初是作为一种专有的工具，但麦肯锡开源了 Kedro，因此“客户可以在我们离开项目后使用它 —— 这是我们回馈的方式，”工程师 Nikolaos Tsaousis 说。

如果你有兴趣了解一下，可以从 GitHub 上获取 [Kedro 的源代码](https://github.com/quantumblacklabs/kedro)。

### 新联盟推进开源医学成像

一组专家和患者倡导者聚集在一起组成了[开源成像联盟](https://pulmonaryfibrosisnews.com/2019/05/31/international-open-source-imaging-consortium-osic-launched-to-advance-ipf-diagnosis/)。该联盟旨在“通过数字成像和机器学习帮助推进特发性肺纤维化和其他间质性肺病的诊断。”

根据联盟执行董事 Elizabeth Estes 的说法，该项目旨在“协作加速诊断，帮助预后处置，最终让医生更有效地治疗患者”。为此，他们正在组织和分享“来自患者的 15,000 个匿名图像扫描和临床数据，这将作为机器学习程序的输入数据来开发算法。”

### Mozilla 发布了一种简单易用的方法，以确定你是否遭受过数据泄露

向不那么精通软件的人解释安全性始终是一项挑战，无论你的技能水平如何，都很难监控你的风险。Mozilla 发布了 [Firefox Monitor](https://monitor.firefox.com/)，其数据由 [Have I Been Pwned](https://haveibeenpwned.com/) 提供，它是一种查看你的任何电子邮件是否出现在重大数据泄露事件中的简单方式。你可以输入电子邮件逐个搜索，或注册他们的服务以便将来通知你。

该网站还提供了大量有用的教程，用于了解黑客如何做的，数据泄露后如何处理以及如何创建强密码。请务必将网站加入书签，以防家人要求你在假日期间提供建议。

### 其它新闻

* [想要一款去谷歌化的 Android？把你的手机发送给这个人](https://fossbytes.com/want-a-google-free-android-send-your-phone-to-this-guy/)
* [CockroachDB 发行版使用了非 OSI 批准的许可证，但仍然保持开源](https://www.cockroachlabs.com/blog/oss-relicensing-cockroachdb/)
* [基础设施自动化公司 Chef 承诺开源](https://www.infoq.com/news/2019/05/chef-open-source/)
* [俄罗斯的 Windows 替代品将获得安全升级](https://www.nextgov.com/cybersecurity/2019/05/russias-would-be-windows-replacement-gets-security-upgrade/157330/)
* [使用此代码在几分钟内从 Medium 切换到你自己的博客](https://github.com/mathieudutour/medium-to-own-blog)
* [开源推进联盟宣布与台湾自由软件协会建立新合作伙伴关系](https://opensource.org/node/994)

---

via: <https://opensource.com/article/19/6/news-june-8>

作者：[Scott Nesbitt](https://opensource.com/users/scottnesbitt) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

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


为 man 手册页编写解析器的备忘录

![](/data/attachment/album/201906/11/235607fiqfqapvpzqhh8n1.jpg)

我一般都很喜欢无所事事，但有时候太无聊了也不行 —— 2015 年的一个星期天下午就是这样，我决定开始写一个开源项目来让我不那么无聊。

在我寻求创意时，我偶然发现了一个请求，要求构建一个由 [Mathias Bynens](https://mathiasbynens.be/) 提出的“[按 Web 标准构建的 Man 手册页查看器](https://github.com/h5bp/lazyweb-requests/issues/114)”。没有考虑太多，我开始使用 JavaScript 编写一个手册页解析器，经过大量的反复思考，最终做出了一个 [Jroff](jroff)。

那时候，我非常熟悉手册页这个概念，而且使用过很多次，但我知道的仅止于此，我不知道它们是如何生成的，或者是否有一个标准。在经过两年后，我有了一些关于此事的想法。

### man 手册页是如何写的

当时令我感到惊讶的第一件事是，手册页的核心只是存储在系统某处的纯文本文件（你可以使用 `manpath` 命令检查这些目录）。

此文件中不仅包含文档，还包含使用了 20 世纪 70 年代名为 `troff` 的排版系统的格式化信息。

>
> troff 及其 GNU 实现 groff 是处理文档的文本描述以生成适合打印的排版版本的程序。**它更像是“你所描述的即你得到的”，而不是你所见即所得的。**
>
>
> * 摘自 [troff.org](https://www.troff.org/)
>
>
>

如果你对排版格式毫不熟悉，可以将它们视为 steroids 期刊用的 Markdown，但其灵活性带来的就是更复杂的语法：

![groff-compressor](/data/attachment/album/201906/11/235838yi66mppqyw166gb6.gif)

`groff` 文件可以手工编写，也可以使用许多不同的工具从其他格式生成，如 Markdown、Latex、HTML 等。

为什么 `groff` 和 man 手册页绑在一起是有历史原因的，其格式[随时间有变化](https://manpages.bsd.lv/history.html)，它的血统由一系列类似命名的程序组成：RUNOFF > roff > nroff > troff > groff。

但这并不一定意味着 `groff` 与手册页有多紧密的关系，它是一种通用格式，已被用于[书籍](https://rkrishnan.org/posts/2016-03-07-how-is-gopl-typeset.html)，甚至用于[照相排版](https://en.wikipedia.org/wiki/Phototypesetting)。

此外，值得注意的是 `groff` 也可以调用后处理器将其中间输出结果转换为最终格式，这对于终端显示来说不一定是 ascii ！一些支持的格式是：TeX DVI、HTML、Canon、HP LaserJet4 兼容格式、PostScript、utf8 等等。

### 宏

该格式的其他很酷的功能是它的可扩展性，你可以编写宏来增强其基本功能。

鉴于 \*nix 系统的悠久历史，有几个可以根据你想要生成的输出而将特定功能组合在一起的宏包，例如 `man`、`mdoc`、`mom`、`ms`、`mm` 等等。

手册页通常使用 `man` 和 `mdoc` 宏包编写。

区分原生的 `groff` 命令和宏的方式是通过标准 `groff` 包大写其宏名称。对于 `man` 宏包，每个宏的名称都是大写的，如 `.PP`、`.TH`、`.SH` 等。对于 `mdoc` 宏包，只有第一个字母是大写的： `.Pp`、`.Dt`、`.Sh`。

![groff-example](/data/attachment/album/201906/11/235758n8qvnoemzjonormq.jpg)

### 挑战

无论你是考虑编写自己的 `groff` 解析器，还是只是好奇，这些都是我发现的一些更具挑战性的问题。

#### 上下文敏感的语法

表面上，`groff` 的语法是上下文无关的，遗憾的是，因为宏描述的是主体不透明的令牌，所以包中的宏集合本身可能不会实现上下文无关的语法。

这导致我在那时做不出来一个解析器生成器（不管好坏）。

#### 嵌套的宏

`mdoc` 宏包中的大多数宏都是可调用的，这差不多意味着宏可以用作其他宏的参数，例如，你看看这个：

* 宏 `Fl`（Flag）会在其参数中添加破折号，因此 `Fl s` 会生成 `-s`
* 宏 `Ar`（Argument）提供了定义参数的工具
* 宏 `Op`（Optional）会将其参数括在括号中，因为这是将某些东西定义为可选的标准习惯用法
* 以下组合 `.Op Fl s Ar file` 将生成 `[-s file]`，因为 `Op` 宏可以嵌套。

#### 缺乏适合初学者的资源

让我感到困惑的是缺乏一个规范的、定义明确的、清晰的来源，网上有很多信息，这些信息对读者来说很重要，需要时间来掌握。

### 有趣的宏

总结一下，我会向你提供一个非常简短的宏列表，我在开发 jroff 时发现它很有趣：

`man` 宏包：

* `.TH`：用 `man` 宏包编写手册页时，你的第一个不是注释的行必须是这个宏，它接受五个参数：`title`、`section`、`date`、`source`、`manual`。
* `.BI`：粗体加斜体（特别适用于函数格式）
* `.BR`：粗体加正体（特别适用于参考其他手册页）

`mdoc` 宏包：

* `.Dd`、`.Dt`、`.Os`：类似于 `man` 宏包需要 `.TH`，`mdoc` 宏也需要这三个宏，需要按特定顺序使用。它们的缩写分别代表：文档日期、文档标题和操作系统。
* `.Bl`、`.It`、`.El`：这三个宏用于创建列表，它们的名称不言自明：开始列表、项目和结束列表。

---

via: <https://monades.roperzh.com/memories-writing-parser-man-pages/>

作者：[Roberto Dip](https://monades.roperzh.com) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy) 选题：[lujun9972](https://github.com/lujun9972)

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


有了 Lutris，Linux 现在也可以启动 Epic 游戏商城

目测有人会从搜索引擎来到这篇文章。

对于想在 Linux 玩 Epic 中的游戏，除了本文，你还有很多其它选择，可以试试看。

+ Dummerle/Rare

    - 非官方的 GUI 客服端，使用 PyQt5 编写。

    - 目前没中文，不过应该有志愿者在翻译。

    - [我个人不太喜欢这款，因为它启动时会检测一遍是否下载了已购游戏的 LOGO，但访问 Epic 网络又很慢，长时间等待就很烦]。

+ Heroic-Games-Launcher/HeroicGamesLauncher

    - 非官方的 GUI 客服端，用 Electron 搞的。

    - 完整中文支持。

    - [目前我个人在用这个]。

+ derrod/legendary

嗯？有字数限制？书接上文。

+ derrod/legendary

    - 非官方的 CLI 客服端。

    - 前两个都算是这个的 GUI 版，本质上都是在调用这个，但是参数超多，用着有点繁锁。

>
> 开源游戏平台 Lutris 现在使你能够在 Linux 上使用 Epic 游戏商城。我们使用 Ubuntu 19.04 版本进行了测试，以下是我们的使用体验。
>
>
>

[在 Linux 上玩游戏](/article-7316-1.html) 正变得越来越容易。Steam [正在开发中的](/article-10054-1.html) 特性可以帮助你实现 [在 Linux 上玩 Windows 游戏](/article-10061-1.html)。

如果说 Steam 在 Linux 运行 Windows 游戏领域还是新玩家，而 Lutris 却已从事多年。

[Lutris](https://lutris.net/) 是一款为 Linux 开发的开源游戏平台，提供诸如 Origin、Steam、战网等平台的游戏安装器。它使用 Wine 来运行 Linux 不能支持的程序。

Lutris 近期宣布你可以通过它来运行 Epic 游戏商店。

### Lutris 为 Linux 带来了 Epic 游戏

![Epic Games Store Lutris Linux](/data/attachment/album/201906/12/144822lowo6wpyvpkt1chk.png)

[Epic 游戏商城](https://www.epicgames.com/store/en-US/) 是一个类似 Steam 的电子游戏分销平台。它目前只支持 Windows 和 macOS。

Lutris 团队付出了大量努力使 Linux 用户可以通过 Lutris 使用 Epic 游戏商城。虽然我不用 Epic 商城，但可以通过 Lutris 在 Linux 上运行 Epic 商城终归是个好消息。

>
> 好消息！ 你现在可以通过 Lutris 安装获得 [@EpicGames](https://twitter.com/EpicGames?ref_src=twsrc%5Etfw) 商城在 Linux 下的全功能支持！没有发现任何问题。 <https://t.co/cYmd7PcYdG>[@TimSweeneyEpic](https://twitter.com/TimSweeneyEpic?ref_src=twsrc%5Etfw) 可能会很喜欢 ?
>
>
> ![](/data/attachment/album/201906/12/144955lg8kcgzboto9b5uo.jpg)
>
>
> — Lutris Gaming (@LutrisGaming) [April 17, 2019](https://twitter.com/LutrisGaming/status/1118552969816018948?ref_src=twsrc%5Etfw)
>
>
>

作为一名狂热的游戏玩家和 Linux 用户，我立即得到了这个消息，并安装了 Lutris 来运行 Epic 游戏。

**备注：** 我使用 [Ubuntu 19.04](https://itsfoss.com/ubuntu-19-04-release-features/) 来测试 Linux 环境下的游戏运行情况。

### 通过 Lutris 在 Linux 下使用 Epic 游戏商城

为了在你的 Linux 系统中安装 Epic 游戏商城，请确保你已经安装了 Wine 和 Python 3。接下来，[在 Ubuntu 中安装 Wine](https://itsfoss.com/install-latest-wine/) ，或任何你正在使用的 Linux 发行版本也都可以。然后， [从官方网站下载 Lutris](https://lutris.net/downloads/).

#### 安装 Epic 游戏商城

Lutris 安装成功后，直接启动它。

当我尝试时，我遇到了一个问题（当我用 GUI 启动时却没有遇到）。当我尝试在命令行输入 `lutris` 来启动时，我发现了下图所示的错误：

![](/data/attachment/album/201906/12/144828i41a4assjf4ksfux.jpg)

感谢 Abhishek，我了解到了这是一个常见问题 (你可以在 [GitHub](https://github.com/lutris/lutris/issues/660) 上查看这个问题)。

总之，为了解决这个问题，我需要在命令行中输入以下命令：

```
export LC_ALL=C
```

当你遇到同样的问题时，只要你输入这个命令，就能正常启动 Lutris 了。

**注意**：每次启动 Lutris 时都必须输入这个命令。因此，最好将其添加到 `.bashrc` 文件或环境变量列表中。

上述操作完成后，只要启动并搜索 “Epic Games Store” 会显示以下图片中的内容：

![Epic Games Store in Lutris](/data/attachment/album/201906/12/144828hq8v8bvzmhc3rfmm.jpg)

在这里，我已经安装过了，所以你将会看到“安装”选项，它会自动询问你是否需要安装需要的包。只需要继续操作就可以成功安装。就是这样，不需要任何黑科技。

#### 玩一款 Epic 游戏商城中的游戏

![Epic Games Store](/data/attachment/album/201906/12/144829qvlf75mve77zc7mw.jpg)

现在我们已经通过 Lutris 在 Linux 上安装了 Epic 游戏商城，启动它并登录你的账号就可以开始了。

但这真会奏效吗？

*是的，Epic 游戏商城可以运行。* **但是所有游戏都不能玩。**（LCTT 译注：莫生气，请看文末的进一步解释！）

好吧，我并没有尝试过所有内容，但是我拿了一个免费的游戏（Transistor —— 一款回合制 ARPG 游戏）来检查它是否工作。

![Transistor – Epic Games Store](/data/attachment/album/201906/12/144830ljy95yzycg85yvtx.jpg)

很不幸，游戏没有启动。当我运行时界面显示了 “Running” 不过什么都没有发生。

到目前为止，我还不知道有什么解决方案 —— 所以如果我找到解决方案，我会尽力让你们知道最新情况。

### 总结

通过 Lutris 这样的工具使 Linux 的游戏场景得到了改善，这终归是个好消息 。不过，仍有许多工作要做。

对于在 Linux 上运行的游戏来说，无障碍运行仍然是一个挑战。其中可能就会有我遇到的这种问题，或者其它类似的。但它正朝着正确的方向发展 —— 即使还存在着一些问题。

你有什么看法吗？你是否也尝试用 Lutris 在 Linux 上启动 Epic 游戏商城？在下方评论让我们看看你的意见。

### 补充

Transistor 实际上有一个原生的 Linux 移植版。到目前为止，我从 Epic 获得的所有游戏都是如此。它们都是免费的，所以我会试着压下我的郁闷，而因为 Epic 只让你玩你通过他们的商店/启动器购买的游戏，所以在 Linux 机器上用 Lutris 玩这个原生的 Linux 游戏是不可能的。这简直愚蠢极了。Steam 有一个原生的 Linux 启动器，虽然不是很理想，但它可以工作。GOG 允许你从网站下载购买的内容，可以在你喜欢的任何平台上玩这些游戏。他们的启动器完全是可选的。

我对此非常恼火，因为我在我的 Epic 库中的游戏都是可以在我的笔记本电脑上运行得很好的游戏，当我坐在桌面前时，玩起来很有趣。但是因为那台桌面机是我唯一拥有的 Windows 机器……

我选择使用 Linux 时已经知道会存在兼容性问题，并且我有一个专门用于游戏的 Windows 机器，而我通过 Epic 获得的游戏都是免费的，所以我在这里只是表示一下不满。但是，他们两个作为最著名的竞争对手，Epic 应该有在我的 Linux 机器上玩原生 Linux 移植版的机制。

---

via: <https://itsfoss.com/epic-games-lutris-linux/>

作者：[Ankush Das](https://itsfoss.com/author/ankush/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[Modrisco](https://github.com/Modrisco) 校对：[wxy](https://github.com/wxy)

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


GoAccess：一个实时的 Web 日志分析器及交互式查看器

确实很不错，还支持中文，实时日志很给力！

![](/data/attachment/album/201906/12/222616h80pl0k0tt811071.jpg)

分析日志文件对于 Linux 管理员来说是一件非常令人头疼的事情，因为它记录了很多东西。大多数新手和初级管理员都不知道如何分析。如果你在分析日志方面拥有很多知识，那么你就成了 \*NIX 系统高手。

Linux 中有许多工具可以轻松分析日志。GoAccess 是允许用户轻松分析 Web 服务器日志的工具之一。我们将在本文中详细讨论 GoAccess 工具。

### GoAccess

GoAccess 是一个实时 Web 日志分析器和交互式查看器，可以在 \*nix 系统中的终端运行或通过浏览器访问。

GoAccess 需要的依赖极少，它是用 C 语言编写的，只需要 ncurses。

它支持 Apache、Nginx 和 Lighttpd 日志。它为需要动态可视化服务器报告的系统管理员即时提供了快速且有价值的 HTTP 统计信息。

GoAccess 可以解析指定的 Web 日志文件并将数据输出到 X 终端和浏览器。

GoAccess 被设计成一个基于终端的快速日志分析器。其核心思想是实时快速分析和查看 Web 服务器统计信息，而无需使用浏览器。

默认输出是在终端输出，它也能够生成完整的、自包含的实时 HTML 报告，以及 JSON 和 CSV 报告。

GoAccess 支持任何自定义日志格式，并包含以下预定义日志格式选项：Apache/Nginx 中的组合日志格式 XLF/ELF，Apache 中的通用日志格式 CLF，但不限于此。

### GoAccess 功能

* 完全实时：所有指标在终端上每 200 毫秒更新一次，在 HTML 输出上每秒更新一次。
* 跟踪应用程序响应时间：跟踪服务请求所需的时间。如果你想跟踪减慢了网站速度的网页，则非常有用。
* 访问者：按小时或日期确定最慢运行的请求的点击量、访问者数、带宽数和指标。
* 按虚拟主机的度量标准：如果有多个虚拟主机（`Server`），它提供了一个面板，可显示哪些虚拟主机正在消耗大部分 Web 服务器资源。

### 如何安装 GoAccess？

我建议用户在包管理器的帮助下从发行版官方的存储库安装 GoAccess。它在大多数发行版官方存储库中都可用。

我们知道，我们在标准发行方式的发行版中得到的是过时的软件包，而滚动发行方式的发行版总是包含最新的软件包。

如果你使用标准发行方式的发行版运行操作系统，我建议你检查替代选项，如 PPA 或 GoAccess 官方维护者存储库等，以获取最新的软件包。

对于 Debian / Ubuntu 系统，使用 [APT-GET 命令](https://www.2daygeek.com/apt-get-apt-cache-command-examples-manage-packages-debian-ubuntu-systems/)或 [APT 命令](https://www.2daygeek.com/apt-command-examples-manage-packages-debian-ubuntu-systems/)在你的系统上安装 GoAccess。

```
# apt install goaccess
```

要获取最新的 GoAccess 包，请使用以下 GoAccess 官方存储库。

```
$ echo "deb https://deb.goaccess.io/ $(lsb_release -cs) main" | sudo tee -a /etc/apt/sources.list.d/goaccess.list
$ wget -O - https://deb.goaccess.io/gnugpg.key | sudo apt-key add -
$ sudo apt-get update
$ sudo apt-get install goaccess
```

对于 RHEL / CentOS 系统，使用 [YUM 包管理器](https://www.2daygeek.com/yum-command-examples-manage-packages-rhel-centos-systems/)在你的系统上安装 GoAccess。

```
# yum install goaccess
```

对于 Fedora 系统，使用 [DNF 包管理器](https://www.2daygeek.com/dnf-command-examples-manage-packages-fedora-system/)在你的系统上安装 GoAccess。

```
# dnf install goaccess
```

对于基于 ArchLinux / Manjaro 的系统，使用 [Pacman 包管理器](https://www.2daygeek.com/pacman-command-examples-manage-packages-arch-linux-system/)在你的系统上安装 GoAccess。

```
# pacman -S goaccess
```

对于 openSUSE Leap 系统，使用[Zypper 包管理器](https://www.2daygeek.com/zypper-command-examples-manage-packages-opensuse-system/)在你的系统上安装 GoAccess。

```
# zypper install goaccess
# zypper ar -f obs://server:http
# zypper ref && zypper in goaccess
```

### 如何使用 GoAccess？

成功安装 GoAccess 后。只需输入 `goaccess` 命令，然后输入 Web 服务器日志位置即可查看。

```
# goaccess [options] /path/to/Web Server/access.log
# goaccess /var/log/apache/2daygeek_access.log
```

执行上述命令时，它会要求您选择日志格式配置。

![](/data/attachment/album/201906/12/223413bpc10rb7x0ecyc7z.png)

我用 Apache 访问日志对此进行了测试。Apache 日志被分为十五个部分。详情如下。主要部分显示了这十五个部分的摘要。

以下屏幕截图包括四个部分，例如唯一身份访问者、请求的文件、静态请求、未找到的网址。

![](/data/attachment/album/201906/12/223738gy55op5eb8yceymx.jpg)

以下屏幕截图包括四个部分，例如访客主机名和 IP、操作系统、浏览器、时间分布。

![](/data/attachment/album/201906/12/223426pjj8cf8u1gsnn8rc.jpg)

以下屏幕截图包括四个部分，例如来源网址、来源网站，Google 的搜索引擎结果、HTTP状态代码。

![](/data/attachment/album/201906/12/223442wt8p81rpj6zrq3p8.jpg)

如果要生成 html 报告，请使用以下命令。最初我在尝试生成 html 报告时遇到错误。

```
# goaccess 2daygeek_access.log -a > report.html

GoAccess - version 1.3 - Nov 23 2018 11:28:19
Config file: No config file used

Fatal error has occurred
Error occurred at: src/parser.c - parse_log - 2764
No time format was found on your conf file.Parsing... [0] [0/s]
```

它说“你的 conf 文件没有找到时间格式”。要解决此问题，请为其添加 “COMBINED” 日志格式选项。

```
# goaccess -f 2daygeek_access.log --log-format=COMBINED -o 2daygeek.html
Parsing...[0,165] [50,165/s]
```

![](/data/attachment/album/201906/12/223508tlkfczlbwaamhdlf.jpg)

GoAccess 也允许你访问和分析实时日志并进行过滤和解析。

```
# tail -f /var/log/apache/2daygeek_access.log | goaccess -
```

更多细节请参考其 man 手册页或帮助。

```
# man goaccess
或
# goaccess --help
```

---

via: <https://www.2daygeek.com/goaccess-a-real-time-web-server-log-analyzer-and-interactive-viewer/>

作者：[Vinoth Kumar](https://www.2daygeek.com/author/vinoth/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[wxy](https://github.com/wxy) 校对：[wxy](https://github.com/wxy)

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


UCloud 叶理灯谈：Docker、K8S 和 Serverless

![](/data/attachment/album/201906/13/115623vegyyo8y85427gea.jpg)

前段时间，笔者参加了 UCloud 在京举办的 TIC 2019 大会，适逢 UCloud 实验室负责人叶理灯的演讲结束，就容器计算方面和他进行了短暂沟通。叶理灯是国内在云计算方面有深入研究和实践的资深专家，我觉得他的一些观点和看法值得分享给大家了解。

![](/data/attachment/album/201906/13/110618qrztdunuzt6n4zww.jpg)

*叶理灯，UCloud实验室负责人*

叶理灯，UCloud 实验室负责人。现负责 UCloud 创新产品研发，专注面向企业的云计算产品的研发及运营。叶理灯拥有 10 年以上丰富的互联网研发经验，先后任职于腾讯、盛大云等互联网公司，从事海量分布式后台系统研发及运营工作。

### 定制违背了 K8S 初衷，提供原生 K8S 产品

##### 记者：在官方的 K8S 发行版之上，各方云厂商提供 K8S 服务时都有一些自己的定制和调整，今天大会上提及的 UCloud 的 K8S 发行版 UK8S 主要做了哪些定制，有什么特色呢？

**叶理灯**：**如果说定制 K8S 的话，其实是违背了 K8S 的初衷。我们并没有定制 K8S，我们是基于公有云给用户提供了原生的 K8S 产品。**在公有云上提供原生的 K8S，其实要做很多的工作，例如与公有云的计算、网络和存储的整合，给用户提供一个开箱即用的原生K8S集群等等。

我为什么说不应该定制呢？因为大家知道 PaaS 发展到今天，一直存在的一个问题就是供应商绑定的问题。而 K8S 之所以那么有生命力，之所以迅速流行，是因为它提供了一个开源的标准，让用户使用 K8S PaaS 平台，可以避免厂商绑定。也就是说你的服务在某个服务商的 K8S 上运行，可以无缝的迁移到另外一个服务商。

作为云厂商其实最重要的工作是，基于我们自身云平台的体系，提供原生的 K8S 给用户使用，帮助他们减少在集群管理和资源整合方面的工作和投入。例如，我们网络能力、存储能力和计算能力的整合，就是让用户享受到原生K8S的好处，同时避免了很多运维的负担。

公有云的 K8S 处在底层 IaaS 和上层应用之间，一方面向下整合IaaS能力，一方面向上托管客户的应用。**在整合 IaaS 方面**，不改变 K8S原生特性，因为 K8S 本身架构足够开放，例如在我们实现的网络插件，是基于我们 IaaS 的 VPC 网络，让 pod 可以和我们托管区和物理云区域打通，这是我们 IaaS 能力在 K8S 产品上的体现，算是我们的特色之一，但这是在 K8S 体系支持下的插件方式实现的，不影响我们提供原生 K8S；**在应用层面**，厂商也可以基于 K8S 提供一些周边的功能以帮助用户提高效率，但它和提供一个一致的 K8S 环境不矛盾。

另外一方面，如果说定制的概念是指基于 K8S 本身开发体系所提供的插件机制去做二次开发，那每家厂商都要定制，因为 K8S 本身不是一个产品级就绪的环境，需要使用者去适配网络和存储还有计算，因为每个公有云厂商基于自己的 IaaS 去提供 K8S 产品，必然要去开发插件。

**综上，向用户应该提供原生的、标准的 K8S 产品，但底层应该基于自身 IaaS 平台去定制，本质还是为了提高用户使用 K8S 的效率，让用户开箱即用。**

### K8S 落地挑战：改造成本和人才问题

##### 记者：你觉得目前国内云厂商提供的 K8S 集群编排服务在推广方面目前欠缺的是什么？是什么阻碍了客户进一步去使用这些容器集群服务？

**叶理灯**：K8S 以容器技术为核心、以容器镜像为打包标准的特点，意味着如果要迁移到这个体系下，客户需要将软件做容器化打包和微服务改造，这个是有成本的。K8S 的特点决定它是运维和研发之间的桥梁，这样就要求公司的研发过程需要跟着改造。我们看到很多公司的运维人员有动力去推动，而研发人员则没有动力，因为它改变了研发的习惯和流程，增加了负担；当然也有的公司是研发希望用 K8S 管理应用，而需要运维跟着变。这样导致迁移到 K8S 的工作较重，但一旦这个阶段过去了，迁移后的效率和成本优势就体现出来了。

因此，这是个新技术落地的问题，涉及到用户教育和习惯的改变，这个需要社区和商业公司一起完成。而且每家公司的技术路线和文化不一样，上 K8S 的路径也不一样，所以没有一个放之四海皆准的最佳实践，但随着容器和微服务逐渐落地，K8S 作为事实标准，会逐步普及。

除了改造业务的成本，另外一方面是 K8S 人才比较缺乏，不同用户的情况不一样，有些用户的运维人员本来就很少，不可能专门抽出一两个人去做 K8S，以及用户又担心它出问题谁来帮我解决？其实，应该是云厂商再往前走一步，除了帮用户构建一个 K8S 平台，还要帮助解决很多技术上的问题。 UCloud 现在就是这么做的，一个用户进来，我们会拉一个群，他们所有技术问题我们帮他们解决。在落地方面，在人才和 K8S 本身对用户架构改造方面，我们可以多做点工作，帮助客户培养K8S的运维人员和为用户制定架构迁移的方案。但我相信随着技术的发展和普及，越来越多人懂 K8S。

最后，K8S 本身也在发展，K8S 刚推出的时候是为了让大家重新面向应用来运维，但是大部分用户用 K8S 同时管理集群里的节点和应用，就会同时有两个负担。我觉得目标应该是用户直接拿一个容器就可以跑起来，而不用知道它的节点在哪里，即 Serverless 形态。一旦这种技术更加成熟，对容器技术的落地也有很大的推动作用。另一方面，Serverless 形态也给用户节省了大量的成本，按需付费，免去用户的运维成本。我觉得 K8S 的落地已经是个趋势了，是不可避免的，但是 K8S 本身是要往前进步，它的革命还没有完成。

### 容器与 Serverless：覆盖场景不同，非替换关系

##### 记者：你觉得现在目前 Serverless 的发展对于容器这种创新技术的迭代升级有什么影响？是不是可以让容器更加的轻量化？

**叶理灯**：不完全是这样，其实 Serverless 刚出现的时候是针对计算的。计算分很多层次，有物理机、虚拟机、容器和 Serverless。Serverless 刚出来的时候，它等同于 FaaS，有一个对标的产品叫 Lambda。

Serverless 出现的动力是，由于云计算的发展，带来了如对象存储等很多丰富的中间件，Serverless 概念的提出是希望应用开发者可以不用写后端逻辑，直接把逻辑写在客户端，组合云上的一些服务来完成业务逻辑，这样就没有管理后端资源的负担了。但是后来发现很多时候还是需要后端代码的，所以就演变成如果有后端代码，就拆成函数，托管在 FaaS 服务中，这样的话，你依然是不用管理服务器的，你用的还是一个个服务，没有服务器管理负担。

这个概念在不断进步，2017 年的时候 AWS 提出了一个新的概念，重新定义了什么叫 Serverless，只要一个服务具备了四方面特性：**免运维、按需付费、高可用和自动扩容**，这个服务就是个 Serverless 的服务。所以 Serverless 这个概念可以对应 FaaS，也可以代表一种架构，也可以代表一种服务的形态，例如 Aurora Serverless 就是把一个数据库的服务变成 Serverless 的。

容器和 Serverless 的区别在于，Serverless 是无容器的，除了不关注服务器，也看不到容器。两者是面向不同场景的，并不是互相替代的关系。FaaS 的特点，接收一个请求拉起一个函数执行，函数是无状态的，它的执行地点也不固定，这意味着延时相对于常驻进程要高，对一些延时敏感的地方它是不合适的，但是有些场景是非常合适的。我举个例子，在 IoT 场景中，有几十万的设备，为了节省电源，它们大部分时候处于睡眠状态，如果用传统的架构去为这几十万设备服务的话，肯定要考虑并发连接的时候，应该有多少计算资源去服务，这很浪费成本。所以最好的方式，来一个请求就拉一个函数服务一下，这就很节省成本。

这是按需的，但它不是完整的替换，相当于说 IT 领域里面不同的场景其实是使用不同的服务。我们推出这个服务的原因，背后的动力还是成本和效率，在某个场景上用某个解决方案它的成本更低、效率更高，而不是说新的东西会替换老的，因为不同场景需求是不一样的。K8S 接受的用户比 FaaS 的用户更多，因为 K8S 的面更广，它覆盖的场景更多，但是它不是替换的。

##### 记者：目前，国内客户对 Serverless 和 PaaS 的接受和普及程度是怎么样的？

**叶理灯**：我觉得在国内还是处于起步和用户教育阶段。2014 年 Lambda 推出的时候，它的渗透率是 72%，什么原因呢？有 72% 的人用的 Lambda，我们有个 Serverless 产品叫 UGC，腾讯有 FaaS，阿里也有 PaaS，目前都不算是渗透率很高。

原因有几个。第一、国内用户对新技术接受程度是比较低的，可能是习惯问题，国内的IT的发展水平跟国外也有差距，有 5、6 年差距；其次，对国内用户来说，把一个架构改成 Serverless 架构，其实成本是很高，而且改造的收益和规模相关；最后， FaaS 本身不是一个独立能起作用的产品，你会看到 Lambda 推出时，不是个独立的产品，它是体系的副产品，例如其他产品要开放事件源，通过事件去触发 Lambda 函数执行。只有产品体系开放足够多的事件源，FaaS 才能渗透到整个平台里面去，才能覆盖更多场景。

我们国内的厂商还没有做到这一点。AWS 刚推出 FaaS 时，它主要是 S3 上的图片处理，不是每个用户都有这个场景，因此渗透率不高。随着 API 网关方式调用，和体系开放事件源越来越丰富，覆盖场景越来越多，我相信 FaaS 会逐步落地。

在现场的演讲中，提及了一个叫 USQL 的产品，就是 Serverless 方式的大数据分析工具，StepFlow 用 Serverless 的方式编排你的程序，这些都是 Serverless 的服务。

### 未来，虚拟化容器值得关注

##### 记者：目前 UK8S 的主要发展方向有什么路线图吗？UK8S 是否已经开源？

**叶理灯**：有的，例如说我们让 K8S 管理更多的资源、异构的资源，还有物理机、GPU 资源，希望用户可以通过 K8S 对这些资源进行统一地管理。另外，再往业务层面走会提供一些微服务的基础设施，通过产品化，一方面减轻用户的资源负担，另一方面减轻应用层的架构负担，从而尽量减轻用户迁移到 K8S 的负担。

目前 UK8S 插件还属于正在整理开源的阶段，还没有正式的发布，但我们已经小范围的开放了代码，把我们的插件代码分发给到很多用户，但公开的开源，我们希望做的更加规范一点再进行，因为我们的插件还在不断的迭代中，有一天我们觉得达到一定程度的稳定了，我们就会进行公开开源。

##### 记者：你认为未来 K8S 以及容器的技术方向上比较值得重点关注的技术点是什么？

**叶理灯**：虚拟化容器。未来的方向我们相信是 Serverless，这个也是云计算应该做的，持续地为了用户提高效率和降低成本。刚才我也说了，现在用户使用 K8S 还是有资源管理的负担的，不是完全的面向应用来运维，所以需要解决这个问题，让计算节点对用户透明。用户通过 Docker 镜像和配置文件就可以把一个应用跑起来，而不用去管资源问题。这需要我们去提供一个海量的集群去跑客户的应用，这就存在一个问题，多个客户的应用可能跑在一个节点上，考虑 Docker 本身的隔离问题，我们需要类似虚拟化容器的计算方式去做隔离，同时又希望拥有 Docker 本身轻量级快速启动的效率，现在看来，虚拟化容器是比较符合这个需求的。

### 尾声

通过和叶理灯的交谈，梳理了我对云计算、容器技术和 Serverless 等方面的一些认识，作为一个几年来亲自践行云计算发展，并有深入探讨和研究的专家，他的观点和认识或许值得从业云计算行业的技术人员参考。


在 Windows 中运行 Linux：WSL 2  使用入门

![](/data/attachment/album/201906/14/104921w7gkmsel0q7kzj0r.jpg)

继微软发布了 WSL 2 （Windows Subsystem for Linux 2）之后，前两天正式提供了 WSL2 更新，处于 Insider Fast 通道中的用户可以通过安装 Windows build 18917 更新来体验最新版本的 WSL2。

在这篇文章中，我们将介绍如何入门、新的 `wsl.exe` 命令以及一些重要提示。有关 WSL 2 的完整文档可在[微软的文档页面](https://docs.microsoft.com/en-us/windows/wsl/wsl2-index)上找到。

### 安装 WSL 2

要安装并开始使用 WSL 2，请完成以下步骤：

* 启用“<ruby> 虚拟机平台 <rp>  （ </rp> <rt>  Virtual Machine Platform </rt> <rp>  ） </rp></ruby>”可选组件
* 使用命令行设置要由 WSL 2 支持的发行版
* 验证你的发行版使用的 WSL 版本

请注意，你需要运行 Windows 10 build 18917 或更高版本才能使用 WSL 2，并且你需要安装 WSL（可以在[此处](https://docs.microsoft.com/en-us/windows/wsl/install-win10)找到相关说明）。

#### 启用“虚拟机平台”可选组件

以管理员身份打开 PowerShell 并运行：

```
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform
```

启用这些更改后，你需要重新启动计算机。

#### 使用命令行设置要由 WSL 2 支持的发行版

在 PowerShell 中运行：

```
wsl --set-version <Distro> 2
```

并确保将 `<Distro>` 替换为发行版的实际名称。 （你可以使用以下命令找到它们：`wsl -l`）。 你也可以通过运行与上面相同的命令随时更改回 WSL 1，但将“`2`”替换为“`1`”。

此外，如果你想使 WSL 2 成为默认架构，可以使用以下命令执行此操作：

```
wsl --set-default-version 2
```

这将使你安装的任何新发行版初始化为 WSL 2 发行版。

#### 完成验证发行版使用的 WSL 版本

要验证每个发行版使用的 WSL 版本，请使用以下命令：

```
wsl --list --verbose
或
wsl -l -v
```

你在上面选择的发行版现在应该在“version”列下显示“2”。 现在完成了，你随时可以开始使用你的 WSL 2 发行版了！

### WSL 2 的体验变化

首次开始使用 WSL 时，你会注意到一些用户体验变化。以下是此预览中两个最重要的变化。

#### 将 Linux 文件放在 Linux 根文件系统中

确保将 Linux 应用程序中经常访问的文件放在 Linux 根文件系统中，以享受文件性能优势。过去微软一直强调在使用 WSL 1 时将文件放入 C 盘，但 WSL 2 中的情况并非如此。要享受 WSL 2 中更快的文件系统访问权限，这些文件必须放在 Linux 根文件系统里面。而且现在 Windows 应用程序可以访问 Linux 根文件系统（如文件资源管理器！尝试在 Linux 发行版的主目录中运行：`explorer.exe .` 看看会发生什么），这将使这种转换变得更加容易。

#### 在初始构建中使用动态 IP 地址访问 Linux 网络应用程序

WSL 2 做了架构的巨大变更，使用了虚拟化技术，并仍在努力改进网络支持。由于 WSL 2 现在运行在虚拟机中，因此你从 Windows 访问 Linux 网络应用程序需要使用该 VM 的 IP 地址，反之亦然，你需要 Windows 主机的 IP 地址才能从 Linux 中访问 Windows 网络应用程序。 WSL 2 的目标是尽可能使用 `localhost` 访问网络应用程序！可以在[文档中](https://docs.microsoft.com/en-us/windows/wsl/wsl2-ux-changes#accessing-network-applications)找到有关如何执行此操作的完整详细信息和步骤。

要了解有关用户体验更改的更多信息，请参阅文档：[WSL 1 和 WSL 2 之间的用户体验变化](https://docs.microsoft.com/en-us/windows/wsl/wsl2-ux-changes)。

### 新的 WSL 命令

WSL 添加了一些新命令选项来帮助控制和查看 WSL 版本和发行版。

除了上面提到的 `--set-version` 和 `--set-default-version` 之外，还有：

* `wsl --shutdown`

立即终止所有正在运行的发行版和 WSL 2 轻量级实用程序虚拟机。

一般来说，支持 WSL 2 发行版的虚拟机是由 WSL 来管理的，因此会在需要时将其打开并在不需要时将其关闭。但也可能存在你希望手动关闭它的情况，此命令允许你通过终止所有发行版并关闭 WSL 2 虚拟机来执行此操作。
* `wsl --list --quiet`

仅列出发行版名称。此命令对于脚本编写很有用，因为它只会输出你已安装的发行版的名称，而不显示其他信息，如默认发行版、版本等。
* `wsl --list --verbose`

显示有关所有发行版的详细信息。此命令列出每个发行版的名称，发行版所处的状态以及正在运行的版本。默认发行版标以星号。

### 反馈意见

如果你遇到任何问题， 请在 Github 上提交问题： <https://github.com/microsoft/wsl/issues> ，如果你对 WSL 有一般性问题，你可以在这个[推特列表](https://twitter.com/craigaloewen/lists/wsl-team-members/members)上找到该团队所有成员的 Twitter。


使用 Kubernetes 的 5 个理由

Summary: Kubernetes 解决了一些开发和运维团队每天关注的的常见问题。

Kubernetes 解决了一些开发和运维团队每天关注的的常见问题。

>
> Kubernetes 解决了一些开发和运维团队每天关注的的常见问题。
>
>
>

![](/data/attachment/album/201906/14/110657gk2jz6f6kqff2kk4.jpg)

[Kubernetes](https://opensource.com/resources/what-is-kubernetes)（K8S）是面向企业的开源容器编排工具的事实标准。它提供了应用部署、扩展、容器管理和其他功能，使企业能够通过容错能力快速优化硬件资源利用率并延长生产环境运行时间。该项目最初由谷歌开发，并将该项目捐赠给[云原生计算基金会](https://www.cncf.io/projects/)（CNCF）。2018 年，它成为第一个从 CNCF [毕业](https://www.cncf.io/blog/2018/03/06/kubernetes-first-cncf-project-graduate/)的项目。

这一切都很好，但它并不能解释为什么开发者和运维人员应该在 Kubernetes 上投入宝贵的时间和精力。Kubernetes 之所以如此有用，是因为它有助于开发者和运维人员迅速解决他们每天都在努力解决的问题。

以下是 Kubernetes 帮助开发者和运维人员解决他们最常见问题的五种能力。

### 1、厂商无关

许多公有云提供商不仅提供托管 Kubernetes 服务，还提供许多基于这些服务构建的云产品，来用于本地应用容器编排。由于与供应商无关，使运营商能够轻松、安全地设计、构建和管理多云和混合云平台，而不会有供应商锁定的风险。Kubernetes 还消除了运维团队对复杂的多云/混合云战略的担忧。

### 2、服务发现

为了开发微服务应用，Java 开发人员必须控制服务可用性（就应用是否可以提供服务而言），并确保服务持续存在，以响应客户端的请求，而没有任何例外。Kubernetes 的[服务发现功能](https://kubernetes.io/docs/concepts/services-networking/service/)意味着开发人员不再需要自己管理这些东西。

### 3、触发

你的 DevOps 会如何在上千台虚拟机上部署多语言、云原生应用？理想情况下，开发和运维会在 bug 修复、功能增强、新功能、安全更新时触发部署。Kubernetes 的[部署功能](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)会自动化这个日常工作。更重要的时，它支持高级部署策略，例如[蓝绿部署和金丝雀部署](https://opensource.com/article/17/5/colorful-deployments)。

### 4、可伸缩性

自动扩展是处理云环境中大量工作负载所需的关键功能。通过构建容器平台，你可以为终端用户提高系统可靠性。[Kubernetes Horizontal Pod Autoscaler](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)（HPA）允许一个集群增加或减少应用程序（或 Pod）的数量，以应对峰值流量或性能峰值，从而减少对意外系统中断的担忧。

### 5、容错性

在现代应用体系结构中，应考虑故障处理代码来控制意外错误并快速从中恢复。但是开发人员需要花费大量的时间和精力来模拟偶然的错误。Kubernetes 的 [ReplicaSet](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/) 通过确保指定数量的 Pod 持续保持活动来帮助开发人员解决此问题。

### 结论

Kubernetes 使企业能够轻松、快速、安全地解决常见的开发和运维问题。它还提供其他好处，例如构建无缝的多云/混合云战略，节省基础架构成本以及加快产品上市时间。

---

via: <https://opensource.com/article/19/6/reasons-kubernetes>

作者：[Daniel Oh](https://opensource.com/users/daniel-oh) 选题：[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/) 荣誉推出


Unity 编辑器现已正式面向 Linux 推出

Summary: 如果你是设计师、开发者或艺术家，你可能一直在使用 Linux 上的实验性 Unity 编辑器。然而，不能一直用实验性版本 —— 开发者需要一个完整稳定的工作经验。

如果你是设计师、开发者或艺术家，你可能一直在使用 Linux 上的实验性 [Unity 编辑器](https://unity3d.com/unity/editor)。然而，不能一直用实验性版本 —— 开发者需要一个完整稳定的工作经验。

因此，他们最近宣布你可以在 Linux 上使用完整功能的 Unity 编辑器了。

虽然这是一个令人兴奋的消息，但它正式支持哪些 Linux 发行版？我们来谈谈更多细节……

>
> 非 FOSS 警告
>
>
> Linux （或任何其他平台）上的 Unity 编辑器不是开源软件。我们在这里介绍它是因为：
>
>
>

### 官方支持 Ubuntu 和 CentOS 7

![](/data/attachment/album/201906/14/112102oo7l7rjstuvisnus.png)

无论你拥有个人许可还是专业许可，如果你安装了 Unity 2019.1 或更高版本，都可以使用该编辑器。

此外，他们优先支持 Ubuntu 16.04、Ubuntu 18.04 和 CentOS 7。

在[公告](https://blogs.unity3d.com/2019/05/30/announcing-the-unity-editor-for-linux/)中，他们还提到了支持的配置：

* x86-64 架构
* 运行在 X11 窗口系统之上的 Gnome 桌面环境
* Nvidia 官方专有显卡驱动和 AMD Mesa 显卡驱动
* 桌面计算机，在没有仿真或兼容层的设备/硬件上运行

你可以尝试其他的 —— 但最好坚持官方要求以获得最佳体验。

>
> 关于第三方工具的说明
>
>
> 如果你碰巧在某个项目中使用了任何第三方工具，那么必须单独检查它们是否支持。
>
>
>

### 如何在 Linux 上安装 Unity 编辑器

现在你已经了解了，那么该如何安装？

要安装 Unity，你需要下载并安装 [Unity Hub](https://forum.unity.com/threads/unity-hub-v-1-6-0-is-now-available.640792/)。

![Unity Hub](/data/attachment/album/201906/14/112104hdua83vdxkjwutzv.jpg)

你需要完成以下步骤：

* 从[官方论坛页面](https://forum.unity.com/threads/unity-hub-v-1-6-0-is-now-available.640792/)下载适用于 Linux 的 Unity Hub。
* 它将下载一个 AppImage 文件。简单地说，让它可执行并运行它。如果你不了解，你应该查看关于[如何在 Linux 上使用 AppImage](https://itsfoss.com/use-appimage-linux/) 的指南。
* 启动 Unity Hub 后，它会要求你使用 Unity ID 登录（或注册）以激活许可证。有关许可证生效的更多信息，请参阅他们的 [FAQ 页面](https://support.unity3d.com/hc/en-us/categories/201268913-Licenses)。
* 使用 Unity ID 登录后，进入 “Installs” 选项（如上图所示）并添加所需的版本/组件。

就是这些了。这就是获取并快速安装的最佳方法。

### 总结

即使这是一个令人兴奋的消息，但官方配置支持似乎并不广泛。如果你在 Linux 上使用它，请在[他们的 Linux 论坛帖子](https://forum.unity.com/forums/linux-editor.93/)上分享你的反馈和意见。

你觉得怎么样？此外，你是使用 Unity Hub 安装它，还是有更好的方法来安装？

请在下面的评论中告诉我们你的想法。

---

via: <https://itsfoss.com/unity-editor-linux/>

作者：[Ankush Das](https://itsfoss.com/author/ankush/) 选题：[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/) 荣誉推出