标签 时间 下的文章

当你打开系统电源时,你会等待制造商的徽标出现,屏幕上可能会显示一些消息(以非安全模式启动),然后是 Grub 屏幕、操作系统加载屏幕以及最后的登录屏。

你检查过这花费了多长时间么?也许没有。除非你真的需要知道,否则你不会在意开机时间。

但是如果你很想知道你的 Linux 系统需要很长时间才能启动完成呢?使用秒表是一种方法,但在 Linux 中,你有一种更好、更轻松地了解系统启动时间的方法。

在 Linux 中使用 systemd-analyze 检查启动时间

无论你是否喜欢,systemd 运行在大多数流行的 Linux 发行版中。systemd 有许多管理 Linux 系统的工具。其中一个就是 systemd-analyze

systemd-analyze 命令为你提供最近一次启动时运行的服务数量以及消耗时间的详细信息。

如果在终端中运行以下命令:

systemd-analyze

你将获得总启动时间以及固件、引导加载程序、内核和用户空间所消耗的时间:

Startup finished in 7.275s (firmware) + 13.136s (loader) + 2.803s (kernel) + 12.488s (userspace) = 35.704s

graphical.target reached after 12.408s in userspace

正如你在上面的输出中所看到的,我的系统花了大约 35 秒才进入可以输入密码的页面。我正在使用戴尔 XPS Ubuntu。它使用 SSD 存储,尽管如此,它还需要很长时间才能启动。

不是那么令人印象深刻,是吗?为什么不共享你们系统的启动时间?我们来比较吧。

你可以使用以下命令将启动时间进一步细分为每个单元:

systemd-analyze blame

这将生成大量输出,所有服务按所用时间的降序列出。

7.347s plymouth-quit-wait.service
6.198s NetworkManager-wait-online.service
3.602s plymouth-start.service
3.271s plymouth-read-write.service
2.120s apparmor.service
1.503s [email protected]
1.213s motd-news.service
 908ms snapd.service
 861ms keyboard-setup.service
 739ms fwupd.service
 702ms bolt.service
 672ms dev-nvme0n1p3.device
 608ms [email protected]:intel_backlight.service
 539ms snap-core-7270.mount
 504ms snap-midori-451.mount
 463ms snap-screencloud-1.mount
 446ms snapd.seeded.service
 440ms snap-gtk\x2dcommon\x2dthemes-1313.mount
 420ms snap-core18-1066.mount
 416ms snap-scrcpy-133.mount
 412ms snap-gnome\x2dcharacters-296.mount

额外提示:改善启动时间

如果查看此输出,你可以看到网络管理器和 plymouth 都消耗了大量的启动时间。

Plymouth 负责你在 Ubuntu 和其他发行版中在登录页面出现之前的引导页面。网络管理器负责互联网连接,可以关闭它来加快启动时间。不要担心,在你登录后,你可以正常使用 wifi。

sudo systemctl disable NetworkManager-wait-online.service

如果要还原更改,可以使用以下命令:

sudo systemctl enable NetworkManager-wait-online.service

请不要在不知道用途的情况下自行禁用各种服务。这可能会产生危险的后果。

现在你知道了如何检查 Linux 系统的启动时间,为什么不在评论栏分享你的系统的启动时间?


via: https://itsfoss.com/check-boot-time-linux/

作者:Abhishek Prakash 选题:lujun9972 译者:geekpi 校对:wxy

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

你可能设置过 cron 任务 来在特定时间备份重要文件或执行系统相关任务。也许你配置了一个日志服务器在特定时间间隔轮转日志。但如果你的时钟不同步,这些任务将无法按时执行。这就是要在 Linux 系统上设置正确的时区并保持时钟与互联网同步的原因。本指南介绍如何在 Ubuntu Linux 上设置时间同步。下面的步骤已经在 Ubuntu 18.04 上进行了测试,但是对于使用 systemd 的 timesyncd 服务的其他基于 Ubuntu 的系统它们是相同的。

在 Ubuntu 上设置时间同步

通常,我们在安装时设置时区。但是,你可以根据需要更改或设置不同的时区。

首先,让我们使用 date 命令查看 Ubuntu 系统中的当前时区:

$ date

示例输出:

Tue Jul 30 11:47:39 UTC 2019

如上所见,date 命令显示实际日期和当前时间。这里,我当前的时区是 UTC,代表协调世界时

或者,你可以在 /etc/timezone 文件中查找当前时区。

$ cat /etc/timezone
UTC

现在,让我们看看时钟是否与互联网同步。只需运行:

$ timedatectl

示例输出:

Local time: Tue 2019-07-30 11:53:58 UTC
Universal time: Tue 2019-07-30 11:53:58 UTC
RTC time: Tue 2019-07-30 11:53:59
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
systemd-timesyncd.service active: yes
RTC in local TZ: no

如你所见,timedatectl 命令显示本地时间、世界时、时区以及系统时钟是否与互联网服务器同步,以及 systemd-timesyncd.service 是处于活动状态还是非活动状态。就我而言,系统时钟已与互联网时间服务器同步。

如果时钟不同步,你会看到下面截图中显示的 System clock synchronized: no

时间同步已禁用。

注意:上面的截图是旧截图。这就是你看到不同日期的原因。

如果你看到 System clock synchronized: 值设置为 no,那么 timesyncd 服务可能处于非活动状态。因此,只需重启服务并看下是否正常。

$ sudo systemctl restart systemd-timesyncd.service

现在检查 timesyncd 服务状态:

$ sudo systemctl status systemd-timesyncd.service
● systemd-timesyncd.service - Network Time Synchronization
Loaded: loaded (/lib/systemd/system/systemd-timesyncd.service; enabled; vendor preset: enabled)
Active: active (running) since Tue 2019-07-30 10:50:18 UTC; 1h 11min ago
Docs: man:systemd-timesyncd.service(8)
Main PID: 498 (systemd-timesyn)
Status: "Synchronized to time server [2001:67c:1560:8003::c7]:123 (ntp.ubuntu.com)."
Tasks: 2 (limit: 2319)
CGroup: /system.slice/systemd-timesyncd.service
└─498 /lib/systemd/systemd-timesyncd

Jul 30 10:50:30 ubuntuserver systemd-timesyncd[498]: Network configuration changed, trying to estab
Jul 30 10:50:31 ubuntuserver systemd-timesyncd[498]: Network configuration changed, trying to estab
Jul 30 10:50:31 ubuntuserver systemd-timesyncd[498]: Network configuration changed, trying to estab
Jul 30 10:50:32 ubuntuserver systemd-timesyncd[498]: Network configuration changed, trying to estab
Jul 30 10:50:32 ubuntuserver systemd-timesyncd[498]: Network configuration changed, trying to estab
Jul 30 10:50:35 ubuntuserver systemd-timesyncd[498]: Network configuration changed, trying to estab
Jul 30 10:50:35 ubuntuserver systemd-timesyncd[498]: Network configuration changed, trying to estab
Jul 30 10:50:35 ubuntuserver systemd-timesyncd[498]: Network configuration changed, trying to estab
Jul 30 10:50:35 ubuntuserver systemd-timesyncd[498]: Network configuration changed, trying to estab
Jul 30 10:51:06 ubuntuserver systemd-timesyncd[498]: Synchronized to time server [2001:67c:1560:800

如果此服务已启用并处于活动状态,那么系统时钟应与互联网时间服务器同步。

你可以使用命令验证是否启用了时间同步:

$ timedatectl

如果仍然不起作用,请运行以下命令以启用时间同步:

$ sudo timedatectl set-ntp true

现在,你的系统时钟将与互联网时间服务器同步。

使用 timedatectl 命令更改时区

如果我想使用 UTC 以外的其他时区怎么办?这很容易!

首先,使用命令列出可用时区:

$ timedatectl list-timezones

你将看到类似于下图的输出。

使用 timedatectl 命令列出时区

你可以使用以下命令设置所需的时区(例如,Asia/Shanghai):

(LCTT 译注:本文原文使用印度时区作为示例,这里为了便于使用,换为中国标准时区 CST。另外,在时区设置中,要注意 CST 这个缩写会代表四个不同的时区,因此建议使用城市和 UTC+8 来说设置。)

$ sudo timedatectl set-timezone Asia/Shanghai

使用 date 命令再次检查时区是否已真正更改:

$ date
Tue Jul 30 20:22:33 CST 2019

或者,如果需要详细输出,请使用 timedatectl 命令:

$ timedatectl
Local time: Tue 2019-07-30 20:22:35 CST
Universal time: Tue 2019-07-30 12:22:35 UTC
RTC time: Tue 2019-07-30 12:22:36
Time zone: Asia/Shanghai (CST, +0800)
System clock synchronized: yes
systemd-timesyncd.service active: yes
RTC in local TZ: no

如你所见,我已将时区从 UTC 更改为 CST(中国标准时间)。()

要切换回 UTC 时区,只需运行:

$ sudo timedatectl set-timezone UTC

使用 tzdata 更改时区

在较旧的 Ubuntu 版本中,没有 timedatectl 命令。这种情况下,你可以使用 tzdata(Time zone data)来设置时间同步。

$ sudo dpkg-reconfigure tzdata

选择你居住的地理区域。对我而言,我选择 Asia。选择 OK,然后按回车键。

接下来,选择与你的时区对应的城市或地区。这里,我选择了 Kolkata(LCTT 译注:中国用户请相应使用 Shanghai 等城市)。

最后,你将在终端中看到类似下面的输出。

Current default time zone: 'Asia/Shanghai'
Local time is now: Tue Jul 30 21:59:25 CST 2019.
Universal Time is now: Tue Jul 30 13:59:25 UTC 2019.

在图形模式下配置时区

有些用户可能对命令行方式不太满意。如果你是其中之一,那么你可以轻松地在图形模式的系统设置面板中进行设置。

点击 Super 键(Windows 键),在 Ubuntu dash 中输入 settings,然后点击设置图标。

从 Ubuntu dash 启动系统的设置

或者,单击位于 Ubuntu 桌面右上角的向下箭头,然后单击左上角的“设置”图标。

从顶部面板启动系统的设置

在下一个窗口中,选择“细节”,然后单击“日期与时间”选项。打开“自动的日期与时间”和“自动的时区”。

在 Ubuntu 中设置自动时区

关闭设置窗口就行了!你的系统始终应该与互联网时间服务器同步了。


via: https://www.ostechnix.com/how-to-set-up-time-synchronization-on-ubuntu/

作者:sk 选题:lujun9972 译者:geekpi 校对: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中国 荣誉推出

你也许听说过这个词很多次或者你可能已经在使用它了。在这篇文章中我将会清晰的告诉你 NTP 服务器和客户端的安装。

之后我们将会了解 Chrony NTP 客户端的安装

什么是 NTP 服务?

NTP 意即 网络时间协议 Network Time Protocol 。它是通过网络在计算机系统之间进行时钟同步的网络协议。换言之,它可以让那些通过 NTP 或者 Chrony 客户端连接到 NTP 服务器的系统保持时间上的一致(它能保持一个精确的时间)。

NTP 在公共互联网上通常能够保持时间延迟在几十毫秒以内的精度,并在理想条件下,它能在局域网下达到低于一毫秒的延迟精度。

它使用用户数据报协议(UDP)在端口 123 上发送和接受时间戳。它是个 C/S 架构的应用程序。

NTP 客户端

NTP 客户端将其时钟与网络时间服务器同步。

Chrony 客户端

Chrony 是 NTP 客户端的替代品。它能以更精确的时间更快的同步系统时钟,并且它对于那些不总是在线的系统很有用。

为什么我们需要 NTP 服务?

为了使你组织中的所有服务器与基于时间的作业保持精确的时间同步。

为了说明这点,我将告诉你一个场景。比如说,我们有两个服务器(服务器 1 和服务器 2)。服务器 1 通常在 10:55 完成离线作业,然后服务器 2 在 11:00 需要基于服务器 1 完成的作业报告去运行其他作业。

如果两个服务器正在使用不同的时间(如果服务器 2 时间比服务器 1 提前,服务器 1 的时间就落后于服务器 2),然后我们就不能去执行这个作业。为了达到时间一致,我们应该安装 NTP。

希望上述能清除你对于 NTP 的疑惑。

在这篇文章中,我们将使用下列设置去测试。

  • NTP 服务器: 主机名:CentOS7.2daygeek.com,IP:192.168.1.8,OS:CentOS 7
  • NTP 客户端: 主机名:Ubuntu18.2daygeek.com,IP:192.168.1.5,OS:Ubuntu 18.04

NTP 服务器端:如何在 Linux 上安装 NTP?

因为它是 C/S 架构,所以 NTP 服务器端和客户端的安装包没有什么不同。在发行版的官方仓库中都有 NTP 安装包,因此可以使用发行版的包管理器安装它。

对于 Fedora 系统,使用 DNF 命令 去安装 ntp。

$ sudo dnf install ntp

对于 Debian/Ubuntu 系统,使用 APT-GET 命令 或者 APT 命令 去安装 ntp。

$ sudo apt install ntp

对基于 Arch Linux 的系统,使用 Pacman 命令 去安装 ntp。

$ sudo pacman -S ntp

对 RHEL/CentOS 系统,使用 YUM 命令 去安装 ntp。

$ sudo yum install ntp

对于 openSUSE Leap 系统,使用 Zypper 命令 去安装 ntp。

$ sudo zypper install ntp

如何在 Linux 上配置 NTP 服务器?

安装 NTP 软件包后,请确保在服务器端的 /etc/ntp.conf 文件中取消以下配置的注释。

默认情况下,NTP 服务器配置依赖于 X.distribution_name.pool.ntp.org。 如果有必要,可以使用默认配置,也可以访问https://www.ntppool.org/zone/@站点,根据你所在的位置(特定国家/地区)进行更改。

比如说如果你在印度,然后你的 NTP 服务器将是 0.in.pool.ntp.org,并且这个地址适用于大多数国家。

# vi /etc/ntp.conf

restrict default kod nomodify notrap nopeer noquery
restrict -6 default kod nomodify notrap nopeer noquery
restrict 127.0.0.1
restrict -6 ::1
server 0.asia.pool.ntp.org
server 1.asia.pool.ntp.org
server 2.asia.pool.ntp.org
server 3.asia.pool.ntp.org
restrict 192.168.1.0 mask 255.255.255.0 nomodify notrap
driftfile /var/lib/ntp/drift
keys /etc/ntp/keys

我们仅允许 192.168.1.0/24 子网的客户端访问这个 NTP 服务器。

由于默认情况下基于 RHEL7 的发行版的防火墙是打开的,因此要允许 ntp 服务通过。

# firewall-cmd --add-service=ntp --permanent
# firewall-cmd --reload

更新配置后要重启服务:

对于 sysvinit 系统。基于 Debian 的系统需要去运行 ntp 而不是 ntpd

# service ntpd restart
# chkconfig ntpd on

对于 systemctl 系统。基于 Debian 的需要去运行 ntpntpd

# systemctl restart ntpd
# systemctl enable ntpd

NTP 客户端:如何在 Linux 上安装 NTP 客户端?

正如我在这篇文章中前面所说的。NTP 服务器端和客户端的安装包没有什么不同。因此在客户端上也安装同样的软件包。

对于 Fedora 系统,使用 DNF 命令 去安装 ntp。

$ sudo dnf install ntp

对于 Debian/Ubuntu 系统,使用 APT-GET 命令 或者 APT 命令 去安装 ntp。

$ sudo apt install ntp

对基于 Arch Linux 的系统,使用 Pacman 命令 去安装 ntp。

$ sudo pacman -S ntp

对 RHEL/CentOS 系统,使用 YUM 命令 去安装 ntp。

$ sudo yum install ntp

对于 openSUSE Leap 系统,使用 Zypper 命令 去安装 ntp。

$ sudo zypper install ntp

我已经在 CentOS7.2daygeek.com` 这台主机上安装和配置了 NTP 服务器,因此将其附加到所有的客户端机器上。

# vi /etc/ntp.conf
restrict default kod nomodify notrap nopeer noquery
restrict -6 default kod nomodify notrap nopeer noquery
restrict 127.0.0.1
restrict -6 ::1
server CentOS7.2daygeek.com prefer iburst
driftfile /var/lib/ntp/drift
keys /etc/ntp/keys

更新配置后重启服务:

对于 sysvinit 系统。基于 Debian 的系统需要去运行 ntp 而不是 ntpd

# service ntpd restart
# chkconfig ntpd on

对于 systemctl 系统。基于 Debian 的需要去运行 ntpntpd

# systemctl restart ntpd
# systemctl enable ntpd

重新启动 NTP 服务后等待几分钟以便从 NTP 服务器获取同步的时间。

在 Linux 上运行下列命令去验证 NTP 服务的同步状态。

# ntpq –p
或
# ntpq -pn

     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

运行下列命令去得到 ntpd 的当前状态。

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

最后运行 date 命令。

# date
Tue Mar 26 23:17:05 CDT 2019

如果你观察到 NTP 中输出的时间偏移很大。运行下列命令从 NTP 服务器手动同步时钟。当你执行下列命令的时候,确保你的 NTP 客户端应该为未活动状态。(LCTT 译注:当时间偏差很大时,客户端的自动校正需要花费很长时间才能逐步追上,因此应该手动运行以更新)

# ntpdate –uv CentOS7.2daygeek.com

via: https://www.2daygeek.com/install-configure-ntp-server-ntp-client-in-linux/

作者:Magesh Maruthamuthu 选题:lujun9972 译者:arrowfeng 校对:wxy

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

你是否想知道自己的 Linux 系统正常运行了多长时间而没有宕机?系统是什么时候启动的?

Linux 上有多个查看服务器/系统运行时间的命令,大多数用户喜欢使用标准并且很有名的 uptime 命令获取这些具体的信息。

服务器的运行时间对一些用户来说不那么重要,但是当服务器运行诸如在线商城 门户 portal 、网上银行门户等 关键任务应用 mission-critical applications 时,它对于 服务器管理员 server adminstrators 来说就至关重要。

它必须做到零宕机,因为一旦停机就会影响到数百万用户。

正如我所说,许多命令都可以让用户看到 Linux 服务器的运行时间。在这篇教程里我会教你如何使用下面 11 种方式来查看。

正常运行时间 uptime 指的是服务器自从上次关闭或重启以来经过的时间。

uptime 命令获取 /proc 文件中的详细信息并输出正常运行时间,而 /proc 文件并不适合人直接看。

以下这些命令会输出系统运行和启动的时间。也会显示一些额外的信息。

方法 1:使用 uptime 命令

uptime 命令会告诉你系统运行了多长时间。它会用一行显示以下信息。

当前时间、系统运行时间、当前登录用户的数量、过去 1 分钟/5 分钟/15 分钟系统负载的均值。

# uptime

 08:34:29 up 21 days,  5:46,  1 user,  load average: 0.06, 0.04, 0.00

方法 2:使用 w 命令

w 命令为每个登录进系统的用户,每个用户当前所做的事情,所有活动的负载对计算机的影响提供了一个快速的概要。这个单一命令结合了多个 Unix 程序:whouptime,和 ps -a 的结果。

# w

 08:35:14 up 21 days,  5:47,  1 user,  load average: 0.26, 0.09, 0.02
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT
root     pts/1    103.5.134.167    08:34    0.00s  0.01s  0.00s w

方法 3:使用 top 命令

top 命令是 Linux 上监视实时系统进程的基础命令之一。它显示系统信息和运行进程的信息,例如正常运行时间、平均负载、运行的任务、登录用户数量、CPU 数量 & CPU 利用率、内存 & 交换空间信息。

推荐阅读:TOP 命令监视服务器性能的例子

# top -c

top - 08:36:01 up 21 days,  5:48,  1 user,  load average: 0.12, 0.08, 0.02
Tasks:  98 total,   1 running,  97 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0%us,  0.3%sy,  0.0%ni, 99.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   1872888k total,  1454644k used,   418244k free,   175804k buffers
Swap:  2097148k total,        0k used,  2097148k free,  1098140k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
    1 root      20   0 19340 1492 1172 S  0.0  0.1   0:01.04 /sbin/init
    2 root      20   0     0    0    0 S  0.0  0.0   0:00.00 [kthreadd]
    3 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 [migration/0]
    4 root      20   0     0    0    0 S  0.0  0.0   0:34.32 [ksoftirqd/0]
    5 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 [stopper/0]

方法 4:使用 who 命令

who 命令列出当前登录进计算机的用户。who 命令与 w 命令类似,但后者还包含额外的数据和统计信息。

# who -b
system boot 2018-04-12 02:48

方法 5:使用 last 命令

last 命令列出最近登录过的用户。last 回溯 /var/log/wtmp 文件并显示自从文件创建后登录进(出)的用户。

# last reboot -F | head -1 | awk '{print $5,$6,$7,$8,$9}'
Thu Apr 12 02:48:04 2018

方法 6:使用 /proc/uptime 文件

这个文件中包含系统上次启动后运行时间的详细信息。/proc/uptime 的输出相当精简。

第一个数字是系统自从启动的总秒数。第二个数字是总时间中系统空闲所花费的时间,以秒为单位。

# cat /proc/uptime
1835457.68 1809207.16

方法 7:使用 tuptime 命令

tuptime 是一个汇报系统运行时间的工具,输出历史信息并作以统计,保留重启之间的数据。和 uptime 命令很像,但输出更有意思一些。

$ tuptime

方法 8:使用 htop 命令

htop 是运行在 Linux 上的一个交互式进程查看器,是 Hisham 使用 ncurses 库开发的。htop 比起 top 有很多的特性和选项。

推荐阅读: 使用 Htop 命令监控系统资源

# htop

  CPU[|                         0.5%]     Tasks: 48, 5 thr; 1 running
  Mem[|||||||||||||||     165/1828MB]     Load average: 0.10 0.05 0.01
  Swp[                      0/2047MB]     Uptime: 21 days, 05:52:35

  PID USER      PRI  NI  VIRT   RES   SHR S CPU% MEM%   TIME+  Command
29166 root       20   0  110M  2484  1240 R  0.0  0.1  0:00.03 htop
29580 root       20   0 11464  3500  1032 S  0.0  0.2 55:15.97 /bin/sh ./OSWatcher.sh 10 1
    1 root       20   0 19340  1492  1172 S  0.0  0.1  0:01.04 /sbin/init
  486 root       16  -4 10780   900   348 S  0.0  0.0  0:00.07 /sbin/udevd -d
  748 root       18  -2 10780   932   360 S  0.0  0.0  0:00.00 /sbin/udevd -d

方法 9:使用 glances 命令

glances 是一个跨平台的基于 curses 库的监控工具,它是使用 python 编写的。可以说它非常强大,仅用一点空间就能获得很多信息。它使用 psutil 库从系统中获取信息。

glances 可以监控 CPU、内存、负载、进程、网络接口、磁盘 I/O、 磁盘阵列 RAID 、传感器、文件系统(与文件夹)、容器、监视器、Alert 日志、系统信息、运行时间、 快速查看 Quicklook (CPU,内存、负载)等。

推荐阅读: Glances (集大成)– Linux 上高级的实时系统运行监控工具

glances

ubuntu (Ubuntu 17.10 64bit / Linux 4.13.0-37-generic) - IP 192.168.1.6/24                Uptime: 21 days, 05:55:15

CPU  [|||||||||||||||||||||    90.6%]   CPU -    90.6%  nice:     0.0%  ctx_sw:    4K      MEM \   78.4%  active:     942M      SWAP -    5.9%      LOAD    2-core
MEM  [||||||||||||||||         78.0%]   user:    55.1%  irq:      0.0%  inter:   1797      total:  1.95G  inactive:   562M      total:   12.4G      1 min:    4.35
SWAP [|                         5.9%]   system:  32.4%  iowait:   1.8%  sw_int:   897      used:   1.53G  buffers:   14.8M      used:     749M      5 min:    4.38
                                        idle:     7.6%  steal:    0.0%                     free:    431M  cached:     273M      free:    11.7G      15 min:   3.38

NETWORK     Rx/s   Tx/s   TASKS 211 (735 thr), 4 run, 207 slp, 0 oth sorted automatically by memory_percent, flat view
docker0       0b   232b
enp0s3      12Kb    4Kb   Systemd          7    Services loaded: 197 active: 196 failed: 1 
lo          616b   616b
_h478e48e     0b   232b     CPU%  MEM%  VIRT   RES   PID USER        NI S     TIME+   R/s   W/s Command 
                            63.8  18.9 2.33G  377M  2536 daygeek      0 R   5:57.78     0     0 /usr/lib/firefox/firefox -contentproc -childID 1 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51
DefaultGateway     83ms     78.5  10.9 3.46G  217M  2039 daygeek      0 S  21:07.46     0     0 /usr/bin/gnome-shell
                             8.5  10.1 2.32G  201M  2464 daygeek      0 S   8:45.69     0     0 /usr/lib/firefox/firefox -new-window
DISK I/O     R/s    W/s      1.1   8.5 2.19G  170M  2653 daygeek      0 S   2:56.29     0     0 /usr/lib/firefox/firefox -contentproc -childID 4 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51
dm-0           0      0      1.7   7.2 2.15G  143M  2880 daygeek      0 S   7:10.46     0     0 /usr/lib/firefox/firefox -contentproc -childID 6 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51
sda1       9.46M    12K      0.0   4.9 1.78G 97.2M  6125 daygeek      0 S   1:36.57     0     0 /usr/lib/firefox/firefox -contentproc -childID 7 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51

方法 10:使用 stat 命令

stat 命令显示指定文件或文件系统的详细状态。

# stat /var/log/dmesg | grep Modify
Modify: 2018-04-12 02:48:04.027999943 -0400

方法 11:使用 procinfo 命令

procinfo/proc 文件夹下收集一些系统数据并将其很好的格式化输出在标准输出设备上。

# procinfo | grep Bootup
Bootup: Fri Apr 20 19:40:14 2018 Load average: 0.16 0.05 0.06 1/138 16615

via: https://www.2daygeek.com/11-methods-to-find-check-system-server-uptime-in-linux/

作者:Magesh Maruthamuthu 选题:lujun9972 译者:LuuMing 校对:wxy

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

在类 Unix 系统中,你可能知道一个命令或进程开始执行的时间,以及一个进程运行了多久。 但是,你如何知道这个命令或进程何时结束或者它完成运行所花费的总时长呢? 在类 Unix 系统中,这是非常容易的! 有一个专门为此设计的程序名叫 GNU time。 使用 time 程序,我们可以轻松地测量 Linux 操作系统中命令或程序的总执行时间。 time 命令在大多数 Linux 发行版中都有预装,所以你不必去安装它。

在 Linux 中查找一个命令或进程的执行时间

要测量一个命令或程序的执行时间,运行:

$ /usr/bin/time -p ls

或者,

$ time ls

输出样例:

dir1 dir2 file1 file2 mcelog

real 0m0.007s
user 0m0.001s
sys 0m0.004s
$ time ls -a
. .bash_logout dir1 file2 mcelog .sudo_as_admin_successful
.. .bashrc dir2 .gnupg .profile .wget-hsts
.bash_history .cache file1 .local .stack

real 0m0.008s
user 0m0.001s
sys 0m0.005s

以上命令显示出了 ls 命令的总执行时间。 你可以将 ls 替换为任何命令或进程,以查找总的执行时间。

输出详解:

  1. real —— 指的是命令或程序所花费的总时间
  2. user —— 指的是在用户模式下程序所花费的时间
  3. sys —— 指的是在内核模式下程序所花费的时间

我们也可以将命令限制为仅运行一段时间。参考如下教程了解更多细节:

time 与 /usr/bin/time

你可能注意到了, 我们在上面的例子中使用了两个命令 time/usr/bin/time 。 所以,你可能会想知道他们的不同。

首先, 让我们使用 type 命令看看 time 命令到底是什么。对于那些我们不了解的 Linux 命令,type 命令用于查找相关命令的信息。 更多详细信息,请参阅本指南

$ type -a time
time is a shell keyword
time is /usr/bin/time

正如你在上面的输出中看到的一样,time 是两个东西:

  • 一个是 BASH shell 中内建的关键字
  • 一个是可执行文件,如 /usr/bin/time

由于 shell 关键字的优先级高于可执行文件,当你没有给出完整路径只运行 time 命令时,你运行的是 shell 内建的命令。 但是,当你运行 /usr/bin/time 时,你运行的是真正的 GNU time 命令。 因此,为了执行真正的命令你可能需要给出完整路径。

在大多数 shell 中如 BASH、ZSH、CSH、KSH、TCSH 等,内建的关键字 time 是可用的。 time 关键字的选项少于该可执行文件,你可以使用的唯一选项是 -p

你现在知道了如何使用 time 命令查找给定命令或进程的总执行时间。 想进一步了解 GNU time 工具吗? 继续阅读吧!

关于 GNU time 程序的简要介绍

GNU time 程序运行带有给定参数的命令或程序,并在命令完成后将系统资源使用情况汇总到标准输出。 与 time 关键字不同,GNU time 程序不仅显示命令或进程的执行时间,还显示内存、I/O 和 IPC 调用等其他资源。

time 命令的语法是:

/usr/bin/time [options] command [arguments...]

上述语法中的 options 是指一组可以与 time 命令一起使用去执行特定功能的选项。 下面给出了可用的选项:

  • -f, –format —— 使用此选项可以根据需求指定输出格式。
  • -p, –portability —— 使用简要的输出格式。
  • -o file, –output=FILE —— 将输出写到指定文件中而不是到标准输出。
  • -a, –append —— 将输出追加到文件中而不是覆盖它。
  • -v, –verbose —— 此选项显示 time 命令输出的详细信息。
  • –quiet – 此选项可以防止 time 命令报告程序的状态.

当不带任何选项使用 GNU time 命令时,你将看到以下输出。

$ /usr/bin/time wc /etc/hosts
9 28 273 /etc/hosts
0.00user 0.00system 0:00.00elapsed 66%CPU (0avgtext+0avgdata 2024maxresident)k
0inputs+0outputs (0major+73minor)pagefaults 0swaps

如果你用 shell 关键字 time 运行相同的命令, 输出会有一点儿不同:

$ time wc /etc/hosts
9 28 273 /etc/hosts

real 0m0.006s
user 0m0.001s
sys 0m0.004s

有时,你可能希望将系统资源使用情况输出到文件中而不是终端上。 为此, 你可以使用 -o 选项,如下所示。

$ /usr/bin/time -o file.txt ls
dir1 dir2 file1 file2 file.txt mcelog

正如你看到的,time 命令不会显示到终端上。因为我们将输出写到了file.txt 的文件中。 让我们看一下这个文件的内容:

$ cat file.txt
0.00user 0.00system 0:00.00elapsed 66%CPU (0avgtext+0avgdata 2512maxresident)k
0inputs+0outputs (0major+106minor)pagefaults 0swaps

当你使用 -o 选项时, 如果你没有一个名为 file.txt 的文件,它会创建一个并把输出写进去。如果文件存在,它会覆盖文件原来的内容。

你可以使用 -a 选项将输出追加到文件后面,而不是覆盖它的内容。

$ /usr/bin/time -a file.txt ls

-f 选项允许用户根据自己的喜好控制输出格式。 比如说,以下命令的输出仅显示用户,系统和总时间。

$ /usr/bin/time -f "\t%E real,\t%U user,\t%S sys" ls
dir1 dir2 file1 file2 mcelog
0:00.00 real, 0.00 user, 0.00 sys

请注意 shell 中内建的 time 命令并不具有 GNU time 程序的所有功能。

有关 GNU time 程序的详细说明可以使用 man 命令来查看。

$ man time

想要了解有关 Bash 内建 time 关键字的更多信息,请运行:

$ help time

就到这里吧。 希望对你有所帮助。

会有更多好东西分享哦。 请关注我们!

加油哦!


via: https://www.ostechnix.com/how-to-find-the-execution-time-of-a-command-or-process-in-linux/

作者:SK 选题:lujun9972 译者:caixiangyue 校对:wxy

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