2019年6月

分析日志文件对于 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 命令APT 命令在你的系统上安装 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 包管理器在你的系统上安装 GoAccess。

# yum install goaccess

对于 Fedora 系统,使用 DNF 包管理器在你的系统上安装 GoAccess。

# dnf install goaccess

对于基于 ArchLinux / Manjaro 的系统,使用 Pacman 包管理器在你的系统上安装 GoAccess。

# pacman -S goaccess

对于 openSUSE Leap 系统,使用Zypper 包管理器在你的系统上安装 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

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

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

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

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

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

如果要生成 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]

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 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

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

在 Linux 上玩游戏 正变得越来越容易。Steam 正在开发中的 特性可以帮助你实现 在 Linux 上玩 Windows 游戏

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

Lutris 是一款为 Linux 开发的开源游戏平台,提供诸如 Origin、Steam、战网等平台的游戏安装器。它使用 Wine 来运行 Linux 不能支持的程序。

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

Lutris 为 Linux 带来了 Epic 游戏

Epic Games Store Lutris Linux

Epic 游戏商城 是一个类似 Steam 的电子游戏分销平台。它目前只支持 Windows 和 macOS。

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

好消息! 你现在可以通过 Lutris 安装获得 @EpicGames 商城在 Linux 下的全功能支持!没有发现任何问题。 https://t.co/cYmd7PcYdG@TimSweeneyEpic 可能会很喜欢 ?

— Lutris Gaming (@LutrisGaming) April 17, 2019

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

备注: 我使用 Ubuntu 19.04 来测试 Linux 环境下的游戏运行情况。

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

为了在你的 Linux 系统中安装 Epic 游戏商城,请确保你已经安装了 Wine 和 Python 3。接下来,在 Ubuntu 中安装 Wine ,或任何你正在使用的 Linux 发行版本也都可以。然后, 从官方网站下载 Lutris.

安装 Epic 游戏商城

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

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

感谢 Abhishek,我了解到了这是一个常见问题 (你可以在 GitHub 上查看这个问题)。

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

export LC_ALL=C

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

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

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

Epic Games Store in Lutris

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

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

Epic Games Store

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

但这真会奏效吗?

是的,Epic 游戏商城可以运行。 但是所有游戏都不能玩。(LCTT 译注:莫生气,请看文末的进一步解释!)

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

Transistor – Epic Games Store

很不幸,游戏没有启动。当我运行时界面显示了 “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 选题:lujun9972 译者:Modrisco 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

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

在我寻求创意时,我偶然发现了一个请求,要求构建一个由 Mathias Bynens 提出的“按 Web 标准构建的 Man 手册页查看器”。没有考虑太多,我开始使用 JavaScript 编写一个手册页解析器,经过大量的反复思考,最终做出了一个 Jroff

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

man 手册页是如何写的

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

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

troff 及其 GNU 实现 groff 是处理文档的文本描述以生成适合打印的排版版本的程序。它更像是“你所描述的即你得到的”,而不是你所见即所得的。

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

groff-compressor

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

为什么 groff 和 man 手册页绑在一起是有历史原因的,其格式随时间有变化,它的血统由一系列类似命名的程序组成:RUNOFF > roff > nroff > troff > groff。

但这并不一定意味着 groff 与手册页有多紧密的关系,它是一种通用格式,已被用于书籍,甚至用于照相排版

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

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

鉴于 *nix 系统的悠久历史,有几个可以根据你想要生成的输出而将特定功能组合在一起的宏包,例如 manmdocmommsmm 等等。

手册页通常使用 manmdoc 宏包编写。

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

groff-example

挑战

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

上下文敏感的语法

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

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

嵌套的宏

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

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

缺乏适合初学者的资源

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

有趣的宏

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

man 宏包:

  • .TH:用 man 宏包编写手册页时,你的第一个不是注释的行必须是这个宏,它接受五个参数:titlesectiondatesourcemanual
  • .BI:粗体加斜体(特别适用于函数格式)
  • .BR:粗体加正体(特别适用于参考其他手册页)

mdoc 宏包:

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

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

作者:Roberto Dip 译者:wxy 校对:wxy 选题:lujun9972

本文由 LCTT 原创编译,Linux中国 荣誉推出

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

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

使用开源推进仿生学

我们这一代人从电视剧《六百万美元人》和《仿生女人》中学到了仿生学一词。让科幻小说(尽管基于事实)正在成为现实的,要归功于由密歇根大学和 Shirley Ryan AbilityLab 设计的假肢。

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

你可以从开源腿网站了解有该腿的更多信息并下载该设计。

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

咨询巨头麦肯锡公司最近发布了其第一个开源工具,名为 Kedro,它是一个用于创建机器学习和数据管道的 Python 库。

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

如果你有兴趣了解一下,可以从 GitHub 上获取 Kedro 的源代码

新联盟推进开源医学成像

一组专家和患者倡导者聚集在一起组成了开源成像联盟。该联盟旨在“通过数字成像和机器学习帮助推进特发性肺纤维化和其他间质性肺病的诊断。”

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

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

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

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

其它新闻


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

作者:Scott Nesbitt 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

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


我从事监控工作。特别是在 Prometheus 上,监控系统包含一个自定义的时间序列数据库,并且集成在 Kubernetes 上。

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

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

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

问题,难题,问题域

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

时间序列数据

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

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

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

这是一个典型的 序列标识符 series identifier 集,它是统计请求指标的一部分:

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 的页。这一行为就是所谓的写入放大,这种特性会损耗你的 SSD。因此它不仅影响速度,而且还毫不夸张地在几天或几个周内破坏掉你的硬件。

关于此问题更深层次的资料,“Coding for SSDs”系列博客是极好的资源。让我们想想主要的用处:顺序写入和批量写入分别对于旋转式磁盘和 SSD 来说都是理想的写入模式。大道至简。

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

记录规则可以减轻已知查询的问题,但对于 点对点 ad-hoc 查询来说并不是一个通用的解决方法。

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

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

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

当前的解决方法

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

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

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

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

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

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

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

序列分流

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

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

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

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

当前解决方案

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

例如,从所有的序列中选择标签 __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- 为前缀编号的 block 。每个块中显然保存了索引文件和含有更多编号文件的 chunk 文件夹。chunks 目录只包含不同序列 数据点的原始块 raw chunks of data points 。与 V2 存储系统一样,这使得通过时间窗口读取序列数据非常高效并且允许我们使用相同的有效压缩算法。这一点被证实行之有效,我们也打算沿用。显然,这里并不存在含有单个序列的文件,而是一堆保存着许多序列的数据块。

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

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

许多小型数据库

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

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

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

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

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

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

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

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

mmap

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

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

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

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

压缩

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

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

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

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”课上提醒我们的,在理论上它并未带来任何好处。如果之前就很糟糕,那么现在也一样。理论是如此的残酷。

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

倒排索引基于数据项内容的子集提供了一种快速的查找方式。简单地说,我可以通过标签 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),所以在嵌套地为每个列表进行 暴力求解 brute force solution 下,运行时间为 O(n^2) 。相同的成本也适用于其他的集合操作,例如取并集( app="foo" OR app="bar" )。当在查询语句上添加更多标签选择子,耗费就会指数增长到 O(n^3) O(n^4) O(n^5) …… O(n^k) 。通过改变执行顺序,可以使用很多技巧以优化运行效率。越复杂,越是需要关于数据特征和标签之间相关性的知识。这引入了大量的复杂度,但是并没有减少算法的最坏运行时间。

这便是 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)

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

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

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

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

基准测试

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

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

我们实际上没有可重现的 Prometheus 基准测试配置,特别是没有对于不同版本的 A/B 测试。亡羊补牢为时不晚,不过现在就有一个了

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

我们部署了两个 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

堆内存使用(GB)

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

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

CPU usage cores

CPU 使用(核心/秒)

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

Disk writes

磁盘写入(MB/秒)

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

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

Disk usage

磁盘大小(GB)

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

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

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

Query latency

第 99 个百分位查询延迟(秒)

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

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

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

Ingestion rate

摄入的样本/秒

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

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

那么现在每秒可以摄入的 绝对最大 absolute maximum 样本数是多少?

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

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

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

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

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

总结

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

第一个配备 V3 存储系统的 alpha 版本 Prometheus 2.0 已经可以用来测试了。在早期阶段预计还会出现崩溃,死锁和其他 bug。

存储系统的代码可以在这个单独的项目中找到。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 译者:LuuMing 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

GNU Screen 是一个终端多路复用器(窗口管理器)。顾名思义,Screen 可以在多个交互式 shell 之间复用物理终端,因此我们可以在每个终端会话中执行不同的任务。所有的 Screen 会话都完全独立地运行程序。因此,即使会话意外关闭或断开连接,在 Screen 会话内运行的程序或进程也将继续运行。例如,当通过 SSH 升级 Ubuntu 服务器时,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 + ad。你无需同时按下两个组合键。首先按 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 + ac 创建另一个会话。只需重复此操作即可创建任意数量的嵌套 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 + ax。 输入你的 Linux 密码以锁定。

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

记录会话

你可能希望记录 Screen 会话中的所有内容。为此,只需按 Ctrl + aH 即可。

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

screen -L

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

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

记录 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 执行相同的工作。要了解更多信息,请参阅以下指南。

资源


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

作者:sk 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出