作为一位 Linux 管理员，你需要跟踪所有用户的活动。当服务器发生故障时，你可以分析和查看用户活动，以便寻找问题的根源。监控 Linux 用户有多种方式。本指南将专门讨论 GNU 记账工具，这是一项用于监视 Linux 用户活动的实用工具。什么是记账工具？记账工具可以提供诸如 Linux 系统中的连接、已执行的程序以及系统资源的使用等有底系统使用信息。这些记账工具可以通过 psacct 或 acct 软件包安装。psacct 和 acct 实际上是相同的。在基于 RPM 的系统中，它以 psacct 的形式存在；而在基于 DEB 的系统中，它作为 acct 提供。你可能想知道 psacct 或 acct 工具的作用。通常，用户在命令行的历史记录会保存在他们的 $HOME 目录下的 .bash_history 文件中。有些用户可能会尝试编辑、修改或删除这些历史记录。然而，即使他们完全 清除了命令行历史，记账工具依然能够获取用户活动信息。这是因为，所有进程记账文件都由 root 用户拥有，而普通用户则无法进行编辑。Linux 中如何安装 psacct 或 acctpsacct/acct 被打包在多种流行的 Linux 发行版本中。如果要在 Alpine Linux 中安装 psacct，请运行以下命令：$ sudo apk add psacct 如果在 Arch Linux 及其变体版本（如 EndeavourOS 和 Manjaro Linux）中安装 acct，请运行以下命令：$ sudo pacman -S acct 在 Fedora、RHEL 及其衍生版本（如 CentOS、AlmaLinux 和 Rocky Linux）中安装 psacct，请运行以下命令：$ sudo dnf install psacct 在 RHEL 6 以及更早版本中，你应当使用 yum 命令而非 dnf 来安装 psacct。$ sudo yum install psacct 在 Debian、Ubuntu 以及 Linux Mint 中，通过如下命令来安装 acct：$ sudo apt install acct 若在 openSUSE 中安装 acct，则运行：$ sudo zypper install acct 启动 psacct/acct 服务要启用并开启 psacct 服务，请执行以下命令：$ sudo systemctl enable psacct 接着启动 psacct 服务：$ sudo systemctl start psacct 如果你需要检查 psacct 服务是否已加载和激活，可以运行：$ sudo systemctl status psacct 在基于 DEB 的系统中，安装完成之后，acct 服务会自动启动。如果你想验证 acct 服务是否已经启动，可以执行以下命令：$ sudo systemctl status acct ● acct.service - Kernel process accounting Loaded: loaded (/lib/systemd/system/acct.service; enabled; vendor preset: enabled) Active: active (exited) since Thu 2022-10-13 16:06:35 IST; 28s ago Docs: man:accton(8) Process: 3241 ExecStart=/usr/sbin/accton /var/log/account/pacct (code=exited, status=0/SUCCESS) Main PID: 3241 (code=exited, status=0/SUCCESS) CPU: 879us Oct 13 16:06:35 ubuntu2204 systemd[1]: Starting Kernel process accounting... Oct 13 16:06:35 ubuntu2204 accton[3241]: Turning on process accounting, file set to '/var/log/account/pacct'. Oct 13 16:06:35 ubuntu2204 systemd[1]: Finished Kernel process accounting. 利用 psacct 或 acct 来监测 Linux 中的用户活动psacct（进程记账）软件包包含以下用来监测用户和进程活动的工具：ac - 提供用户登录时间的统计信息。lastcomm - 展示先前执行过的命令的信息。accton - 开启或关闭进程记账。dump-acct - 把 accton 的输出文件转化为易读的格式。dump-utmp - 以易读的方式打印 utmp 文件。sa - 汇总信息，关于先前执行的命令。现在就让我们一起了解如何通过每个工具来监控 Linux 用户的活动。1、使用 ac 命令ac 工具可以为你提供以小时为单位的连接时间报告，这样你就能知道用户或一组用户连接到系统的时长。1.1、展示所有用户的总连接时间$ ac 上述命令会显示所有用户的总连接时间（单位为小时）。total 52.91 1.2、按日期排序显示所有用户的总连接时间你可以通过使用 -d 参数，按日期排序显示所有用户的连接时间，操作如下：$ ac -d 示例输出：May 11 total 4.29 May 13 total 3.23 May 14 total 7.66 May 15 total 8.97 May 16 total 0.52 May 20 total 4.09 May 24 total 1.32 Jun 9 total 15.18 Jun 10 total 2.97 Jun 22 total 2.61 Jul 19 total 1.95 Today total 0.29 1.3、获取各个用户的总连接时间使用 -p 参数，你可以查看每位用户各自在系统中总的连接时长。$ ac -p 示例输出：ostechnix 52.85 root 0.51 total 53.36 1.4、显示指定用户的总连接时间你还可以显示特定用户的总登录时间。$ ac ostechnix 示例输出：total 52.95 1.5、显示特定用户各日期的总连接时间要按日期查看某个用户的登录时间，可以运行：$ ac -d ostechnix 示例输出：May 11 total 4.29 May 13 total 3.23 May 14 total 7.66 May 15 total 8.97 May 16 total 0.01 May 20 total 4.09 May 24 total 1.32 Jun 9 total 15.18 Jun 10 total 2.97 Jun 22 total 2.61 Jul 19 total 1.95 Today total 0.68 如需更多详情，可参考手册页面。$ man ac 2、lastcomm 命令使用示例lastcomm 工具用于列出过去执行过的命令，它会按执行的最近程度将命令列在前面。2.1、展示过去执行的命令$ lastcomm 示例输出：systemd-hostnam S root __ 0.06 secs Thu Oct 13 17:21 systemd-localed S root __ 0.06 secs Thu Oct 13 17:22 bash F ostechni pts/1 0.00 secs Thu Oct 13 17:22 awk ostechni pts/1 0.00 secs Thu Oct 13 17:22 bash F ostechni pts/1 0.00 secs Thu Oct 13 17:22 uname ostechni pts/1 0.00 secs Thu Oct 13 17:22 bash F ostechni pts/1 0.00 secs Thu Oct 13 17:22 sed ostechni pts/1 0.00 secs Thu Oct 13 17:22 bash F ostechni pts/1 0.00 secs Thu Oct 13 17:22 bash F ostechni pts/1 0.00 secs Thu Oct 13 17:22 grep ostechni pts/1 0.00 secs Thu Oct 13 17:22 bash F ostechni pts/1 0.00 secs Thu Oct 13 17:22 bash F ostechni pts/1 0.00 secs Thu Oct 13 17:22 grep ostechni pts/1 0.00 secs Thu Oct 13 17:22 bash F ostechni pts/1 0.00 secs Thu Oct 13 17:22 bash F ostechni pts/1 0.00 secs Thu Oct 13 17:22 [...] 2.2、打印特定用户先前执行的命令上述命令显示的是所有用户的命令。要显示特定用户以前执行的命令，可以使用下面的命令：$ lastcomm ostechnix 示例输出：less ostechni pts/1 0.00 secs Thu Oct 13 17:26 lastcomm ostechni pts/1 0.00 secs Thu Oct 13 17:26 lastcomm ostechni pts/1 0.00 secs Thu Oct 13 17:26 lastcomm ostechni pts/1 0.00 secs Thu Oct 13 17:26 gdbus X ostechni __ 0.00 secs Thu Oct 13 17:24 lastcomm ostechni pts/1 0.00 secs Thu Oct 13 17:24 ac ostechni pts/1 0.00 secs Thu Oct 13 17:24 update-notifier F ostechni __ 0.00 secs Thu Oct 13 17:23 apport-checkrep ostechni __ 0.06 secs Thu Oct 13 17:23 apport-checkrep ostechni __ 0.05 secs Thu Oct 13 17:23 systemctl ostechni __ 0.00 secs Thu Oct 13 17:23 apt-check ostechni __ 0.81 secs Thu Oct 13 17:23 dpkg ostechni __ 0.00 secs Thu Oct 13 17:23 ischroot ostechni __ 0.00 secs Thu Oct 13 17:23 dpkg ostechni __ 0.00 secs Thu Oct 13 17:23 [...] 2.3、打印特定命令的执行次数你还可以查看特定命令被执行的次数。$ lastcomm apt 示例输出：apt S root pts/2 0.70 secs Thu Oct 13 16:06 apt F root pts/2 0.00 secs Thu Oct 13 16:06 apt F root pts/2 0.00 secs Thu Oct 13 16:06 如上述输出所示，root 用户执行了 apt 命令三次。更详细的信息，可参考手册页：$ man lastcomm 3、sa 命令示例sa 实用程序将总结关于先前执行的命令的信息。3.1、打印所有命令的总结$ sa 示例输出：1522 1598.63re 0.23cp 0avio 32712k 139 570.90re 0.05cp 0avio 36877k ***other* 38 163.63re 0.05cp 0avio 111445k gdbus 3 0.05re 0.04cp 0avio 12015k apt-check 27 264.27re 0.02cp 0avio 0k kworker/dying* 2 51.87re 0.01cp 0avio 5310464k Docker Desktop 5 0.03re 0.01cp 0avio 785k snap-confine 8 59.48re 0.01cp 0avio 85838k gmain 5 103.94re 0.01cp 0avio 112720k dconf worker 24 3.38re 0.00cp 0avio 2937k systemd-udevd* 7 0.01re 0.00cp 0avio 36208k 5 3 1.51re 0.00cp 0avio 3672k systemd-timedat 2 0.00re 0.00cp 0avio 10236k apport-checkrep 2 0.01re 0.00cp 0avio 4316160k ThreadPoolForeg* 2 0.00re 0.00cp 0avio 8550k package-data-do 3 0.79re 0.00cp 0avio 2156k dbus-daemon 12 0.00re 0.00cp 0avio 39631k ffmpeg [...] 3.2、查看进程数量和 CPU 分钟数要打印基于每个用户的进程数量和 CPU 分钟数，运行带 -m 标志的 sa 命令：$ sa -m 示例输出：1525 1598.63re 0.23cp 0avio 32651k root 561 647.23re 0.09cp 0avio 3847k ostechnix 825 780.79re 0.08cp 0avio 47788k gdm 117 13.43re 0.06cp 0avio 63715k colord 2 52.01re 0.00cp 0avio 89720k geoclue 1 1.01re 0.00cp 0avio 70608k jellyfin 12 0.00re 0.00cp 0avio 39631k man 1 0.00re 0.00cp 0avio 3124k kernoops 4 104.12re 0.00cp 0avio 3270k sshd 1 0.05re 0.00cp 0avio 3856k whoopsie 1 0.00re 0.00cp 0avio 8552k 3.3、打印用户 ID 和命令名称对于账户文件中的每个命令，使用 -u 标志打印用户 ID 和命令名称。$ sa -u 示例输出：root 0.00 cpu 693k mem 0 io accton root 0.00 cpu 3668k mem 0 io systemd-tty-ask root 0.00 cpu 3260k mem 0 io systemctl root 0.01 cpu 3764k mem 0 io deb-systemd-inv root 0.00 cpu 722k mem 0 io acct.postinst root 0.00 cpu 704k mem 0 io rm root 0.00 cpu 939k mem 0 io cp root 0.00 cpu 704k mem 0 io rm root 0.00 cpu 951k mem 0 io find root 0.00 cpu 911k mem 0 io gzip root 0.00 cpu 722k mem 0 io sh root 0.00 cpu 748k mem 0 io install-info root 0.00 cpu 911k mem 0 io gzip [...] 如需更多详细信息，请参考手册页：$ man sa 4、dump-acct 和 dump-utmp 命令dump-acct 实用工具将 accton 格式的输出文件显示为人类可读的格式。$ dump-acct /var/account/pacct dump-utmp 将 utmp 文件显示为人类可读的格式。$ dump-utmp /var/run/utmp 如需了解更多详情，请参考手册页：$ man dump-acct $ man dump-utmp 5、accton 命令accton 命令将允许你开启或关闭记账。要开启进程记账，请运行：$ accton on 要关闭它，运行：$ accton off 如需了解更多详情，请参考手册页：$ man accton 总结每个 Linux 管理员都应该知道 GNU 记账实用程序，以便注意所有用户的行为。在故障排除时，这些实用程序会非常有帮助。资源GNU 记账实用程序官网（题图：MJ/da3f7e79-2a53-4121-a2ed-d63a22c3d3f4）via: https://ostechnix.com/monitor-user-activity-linux/作者：sk 选题：lkxed 译者：ChatGPT 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
在不到 30 分钟内构建一个树莓派监控仪表盘
使用 Python 制作一个 API 来监控你的树莓派硬件，并使用 Appsmith 建立一个仪表盘。如果你想知道你的树莓派的性能如何，那么你可能需要一个树莓派的仪表盘。在本文中，我将演示如何快速构建一个按需监控仪表盘，以实时查看你的树莓派的 CPU 性能、内存和磁盘使用情况，并根据需要随时添加更多视图和操作。如果你已经使用 Appsmith，你还可以直接导入 示例应用程序 并开始使用。AppsmithAppsmith 是一个开源的 低代码 应用构建工具，帮助开发人员轻松快速地构建内部应用，如仪表盘和管理面板。它是一个用于仪表盘的很好选择，并减少了传统编码方法所需的时间和复杂性。在此示例的仪表盘中，我显示以下统计信息：CPU占用百分比频率或时钟速度计数温度内存占用百分比可用内存百分比总内存空闲内存磁盘磁盘使用百分比绝对磁盘空间使用量可用磁盘空间总磁盘空间创建一个端点你需要一种从树莓派获取这些数据并传递给 Appsmith 的方法。psutil 是一个用于监控和分析的 Python 库，而 Flask-RESTful 是一个创建 REST API 的 Flask 扩展。Appsmith 每隔几秒钟调用 REST API 以自动刷新数据，并以 JSON 对象作为响应，其中包含所有所需的统计信息，如下所示：{ "cpu_count": 4, "cpu_freq": [ 600.0, 600.0, 1200.0 ], "cpu_mem_avail": 463953920, "cpu_mem_free": 115789824, "cpu_mem_total": 971063296, "cpu_mem_used": 436252672, "cpu_percent": 1.8, "disk_usage_free": 24678121472, "disk_usage_percent": 17.7, "disk_usage_total": 31307206656, "disk_usage_used": 5292728320, "sensor_temperatures": 52.616 } 1、设置 REST API如果你的树莓派尚未安装 Python，请在树莓派上打开终端并运行此安装命令：$ sudo apt install python3 现在为你的开发设置一个 Python 虚拟环境：$ python -m venv PiData 接下来，激活该环境。你必须在重新启动树莓派后执行此操作。$ source PiData/bin/activate $ cd PiData 为了安装 Flask、Flask-RESTful 和以后需要的依赖项，请在你的 Python 虚拟环境中创建一个名为 requirements.txt 的文件，并将以下内容添加到其中：flask flask-restful gunicorn 保存文件，然后使用 pip 一次性安装它们。你必须在重新启动树莓派后执行此操作。(PyData)$ python -m pip install -r requirements.txt 接下来，创建一个名为 pi_stats.py 的文件来存放使用 psutil 检索树莓派系统统计信息的逻辑。将以下代码粘贴到 pi_stats.py 文件中：from flask import Flask from flask_restful import Resource, Api import psutil app = Flask(__name__) api = Api(app) class PiData(Resource): def get(self): return "RPI Stat dashboard" api.add_resource(PiData, '/get-stats') if __name__ == '__main__': app.run(debug=True) 这段代码的作用如下：使用 app = Flask(name) 来定义嵌套 API 对象的应用程序。使用 Flask-RESTful 的 API 方法 来定义 API 对象。在 Flask-RESTful 中将 PiData 定义为具体的 Resource 类 ，以公开每个支持的 HTTP 方法。使用 api.add_resource(PiData, '/get-stats') 将资源 PiData 附加到 API 对象 api。每当你访问 URL /get-stats 时，将返回 PiData 作为响应。2、使用 psutil 读取统计信息要从你的树莓派获取统计信息，你可以使用 psutil 提供的这些内置函数：cpu_percentage、cpu_count、cpu_freq 和 sensors_temperatures 函数分别用于获取 CPU 的占用百分比、计数、时钟速度和温度。sensors_temperatures 报告了与树莓派连接的所有设备的温度。要仅获取 CPU 的温度，请使用键 cpu-thermal。virtual_memory 函数可返回总内存、可用内存、已使用内存和空闲内存的统计信息（以字节为单位）。disk_usage 函数可返回总磁盘空间、已使用空间和可用空间的统计信息（以字节为单位）。将所有函数组合到一个 Python 字典中的示例如下：system_info_data = { 'cpu_percent': psutil.cpu_percent(1), 'cpu_count': psutil.cpu_count(), 'cpu_freq': psutil.cpu_freq(), 'cpu_mem_total': memory.total, 'cpu_mem_avail': memory.available, 'cpu_mem_used': memory.used, 'cpu_mem_free': memory.free, 'disk_usage_total': disk.total, 'disk_usage_used': disk.used, 'disk_usage_free': disk.free, 'disk_usage_percent': disk.percent, 'sensor_temperatures': psutil.sensors_temperatures()['cpu-thermal'][0].current, } 下一节将使用该字典。3、从 Flask-RESTful API 获取数据为了在 API 响应中看到来自树莓派的数据，请更新 pi_stats.py 文件，将字典 system_info_data 包含在 PiData 类中：from flask import Flask from flask_restful import Resource, Api import psutil app = Flask(__name__) api = Api(app) class PiData(Resource): def get(self): memory = psutil.virtual_memory() disk = psutil.disk_usage('/') system_info_data = { 'cpu_percent': psutil.cpu_percent(1), 'cpu_count': psutil.cpu_count(), 'cpu_freq': psutil.cpu_freq(), 'cpu_mem_total': memory.total, 'cpu_mem_avail': memory.available, 'cpu_mem_used': memory.used, 'cpu_mem_free': memory.free, 'disk_usage_total': disk.total, 'disk_usage_used': disk.used, 'disk_usage_free': disk.free, 'disk_usage_percent': disk.percent, 'sensor_temperatures': psutil.sensors_temperatures()['cpu-thermal'][0].current, } return system_info_data api.add_resource(PiData, '/get-stats') if __name__ == '__main__': app.run(debug=True) 你的脚本已经就绪，下面运行 PiData.py：$ python PyData.py * Serving Flask app "PiData" (lazy loading) * Environment: production WARNING: This is a development server. Do not run this in a production environment. * Debug mode: on * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) * Restarting with stat * Debugger is active! 你有了一个可以工作的 API。4、将 API 提供给互联网你可以在本地网络中与 API 进行交互。然而，要在互联网上访问它，你必须在防火墙中打开一个端口，并将传入的流量转发到由 Flask 提供的端口。然而，正如你的测试输出建议的那样，在 Flask 中运行 Flask 应用程序仅适用于开发，而不适用于生产。为了安全地将 API 提供给互联网，你可以使用安装过程中安装的 gunicorn 生产服务器。现在，你可以启动 Flask API。每次重新启动树莓派时都需要执行此操作。$ gunicorn -w 4 'PyData:app' Serving on http://0.0.0.0:8000 要从外部世界访问你的树莓派，请在网络防火墙中打开一个端口，并将流量定向到你树莓派的 IP 地址，端口为 8000。首先，获取树莓派的内部 IP 地址：$ ip addr show | grep inet 内部 IP 地址通常以 10 或 192 或 172 开头。接下来，你必须配置防火墙。通常，你从互联网服务提供商（ISP）获取的路由器中嵌入了防火墙。通常，你可以通过网络浏览器访问家用路由器。路由器的地址有时会打印在路由器的底部，它以 192.168 或 10 开头。不过，每个设备都不同，因此我无法告诉你需要点击哪些选项来调整设置。关于如何配置防火墙的完整描述，请阅读 Seth Kenlon 的文章 《打开端口并通过你的防火墙路由流量》。或者，你可以使用 localtunnel 来使用动态端口转发服务。一旦你的流量到达树莓派，你就可以查询你的 API：$ curl https://example.com/get-stats { "cpu_count": 4, "cpu_freq": [ 600.0, 600.0, 1200.0 ], "cpu_mem_avail": 386273280, ... 如果你已经执行到这一步，那么最困难的部分已经过去了。5、重复步骤如果你重新启动了树莓派，你必须按照以下步骤进行操作：使用 source 重新激活 Python 环境使用 pip 刷新应用程序的依赖项使用 gunicorn 启动 Flask 应用程序你的防火墙设置是持久的，但如果你使用了 localtunnel，则必须在重新启动后启动新的隧道。如果你愿意，可以自动化这些任务，但那是另一个教程的内容。本教程的最后一部分是在 Appsmith 上构建一个用户界面，使用拖放式小部件和一些 JavaScript，将你的树莓派数据绑定到用户界面。相信我，从现在开始很容易！在 Appsmith 上构建仪表盘要制作一个像这样的仪表盘，你需要将公开的 API 端点连接到 Appsmith，使用 Appsmith 的小部件库构建用户界面，并将 API 的响应绑定到小部件上。如果你已经使用 Appsmith，你可以直接导入 示例应用程序 并开始使用。如果你还没有，请 注册 一个免费的 Appsmith 帐户。或者，你可以选择 自托管 Appsmith。将 API 作为 Appsmith 数据源连接登录到你的 Appsmith 帐户。在左侧导航栏中找到并点击 “ 查询或 JS QUERIES/JS ” 旁边的 “+” 按钮。点击 “ 创建一个空白 API Create a blank API ”。在页面顶部，将你的项目命名为 “PiData”。获取你的 API 的 URL。如果你使用的是 localtunnel，则是一个 localtunnel.me 地址，总是在末尾添加 /get-stats 以获取统计数据。将其粘贴到页面的第一个空白字段中，然后点击 “RUN” 按钮。确保在 “ 响应 Response ” 面板中看到成功的响应。构建用户界面Appsmith 的界面非常直观，但如果你感到迷失，我建议你查看 在 Appsmith 上构建你的第一个应用程序 教程。对于标题，将 “ 文本 Text ”、“ 图像 Image ” 和 “ 分隔线 Divider ” 小部件拖放到画布上。将它们排列如下：“ 文本 Text ” 小部件包含你页面的实际标题。键入比“Raspberry Pi Stats”更酷的内容。“ 图像 Image ” 小部件用于展示仪表盘的独特标志。你可以使用你喜欢的任何标志。使用 “ 开关 Switch ” 小部件来切换实时数据模式。在 “ 属性 Property ” 面板中进行配置，以从你构建的 API 获取数据。对于主体部分，使用来自左侧的小部件库的以下小部件创建一个 CPU 统计信息区域，使用以下小部件： 进度条 Progress Bar 统计框 Stat Box 图表 Chart 对于内存和磁盘统计信息部分，重复相同的步骤。磁盘统计信息部分不需要图表，但如果你能找到用途，那也可以使用它。你的最终小部件布局应该类似于以下：最后一步是将 API 的数据绑定到你的小部件上。将数据绑定到小部件上返回到画布，并在三个类别的部分中找到你的小部件。首先设置 CPU 统计信息。要将数据绑定到 “ 进度条 Progress Bar ” 小部件：单击 “ 进度条 Progress Bar ” 小部件，以打开右侧的 “ 属性 Property ” 面板。查找 “ 进度 Progress ” 属性。单击 “JS” 按钮以激活 JavaScript。在 “ 进度 Progress ” 字段中粘贴 {{PiData.data.cpu_percent ?? 0}}。此代码引用了你的 API 的数据流，名为 PiData。Appsmith 将响应数据缓存在 PiData 的 .data 运算符内。键 cpu_percent 包含了 Appsmith 用来显示 CPU 利用率百分比的数据。在 “ 进度条 Progress Bar ” 小部件下方添加一个 “ 文本 Text ” 小部件作为标签。在 CPU 部分有三个 “ 统计框 Stat Box ” 小部件。将数据绑定到每个小部件的步骤与绑定 “ 进度条 Progress Bar ” 小部件的步骤完全相同，只是你需要从 .data 运算符中绑定不同的数据属性。按照相同的步骤进行操作，但有以下例外：使用 {{${PiData.data.cpu_freq[0]} ?? 0 }} 来显示时钟速度。使用 {{${PiData.data.cpu_count} ?? 0 }} 来显示 CPU 计数。使用 {{${(PiData.data.sensor_temperatures).toPrecision(3)} ?? 0 }} 来显示 CPU 温度数据。如果一切顺利，你将得到一个漂亮的仪表盘，如下所示：CPU 利用率趋势图你可以使用 “ 图表 Chart ” 小部件将 CPU 利用率显示为趋势线，并使其随时间自动更新。首先，单击小部件，在右侧找到 “ 图表类型 Chart Type ” 属性，并将其更改为 “ 折线图 LINE CHART ”。为了显示趋势线，需要将 cpu_percent 存储在数据点数组中。你的 API 目前将其作为单个时间数据点返回，因此可以使用 Appsmith 的 storeValue 函数（Appsmith 内置的 setItem 方法的一个原生实现）来获取一个数组。在 “ 查询或 JS QUERIES/JS ” 旁边单击 “+” 按钮，并将其命名为 “utils”。将以下 JavaScript 代码粘贴到 “ 代码 Code ” 字段中：export default { getLiveData: () => { //When switch is on: if (Switch1.isSwitchedOn) { setInterval(() => { let utilData = appsmith.store.cpu_util_data; PiData.run() storeValue("cpu_util_data", [...utilData, { x: PiData.data.cpu_percent, y: PiData.data.cpu_percent }]); }, 1500, 'timerId') } else { clearInterval('timerId'); } }, initialOnPageLoad: () => { storeValue("cpu_util_data", []); } } 为了初始化 Store，你在 initialOnPageLoad 对象中创建了一个 JavaScript 函数，并将 storeValue 函数放在其中。你使用 storeValue("cpu_util_data", []); 将 cpu_util_data 中的值存储到 storeValue 函数中。此函数在页面加载时运行。到目前为止，每次刷新页面时，代码都会将 cpu_util_data 中的一个数据点存储到 Store 中。为了存储一个数组，你使用了 x 和 y 的下标变量，两者都存储了来自 cpu_percent 数据属性的值。你还希望通过设定存储值之间的固定时间间隔来自动存储这些数据。当执行 setInterval 函数时：获取存储在 cpu_util_data 中的值。调用 API PiData。使用返回的最新 cpu_percent 数据将 cpu_util_data 更新为 x 和 y 变量。将 cpu_util_data 的值存储在键 utilData 中。仅当设置为自动执行函数时，才重复执行步骤 1 到 4。你使用 Switch 小部件将其设置为自动执行，这就解释了为什么有一个 getLiveData 父函数。在 “ 设置 Settings ” 选项卡中，找到对象中的所有父函数，并在 “ 页面加载时运行 RUN ON PAGE LOAD ” 选项中将 initialOnPageLoad 设置为 “Yes（是）”。现在刷新页面进行确认。返回到画布。单击 “ 图表 Chart ” 小部件，并找到 “ 图表数据 Chart Data ” 属性。将绑定 {{ appsmith.store.disk_util_data }} 粘贴到其中。这样，如果你自己多次运行对象 utils，就可以获得图表数据。要自动运行此操作：查找并单击仪表盘标题中的 “ 实时数据开关 Live Data Switch ” 小部件。查找 onChange 事件。将其绑定到 {{ utils.getLiveData() }}。JavaScript 对象是 utils，而 getLiveData 是在你切换开关时激活的函数，它会从你的树莓派获取实时数据。但是还有其他实时数据，因此同一开关也适用于它们。继续阅读以了解详情。将数据绑定到内存和磁盘部分的小部件与你在 CPU 统计信息部分所做的方式类似。对于内存部分，绑定如下所示：进度条中的绑定为：{{( PiData.data.cpu_mem_avail/1000000000).toPrecision(2) \* 100 ?? 0 }}。三个统计框小部件的绑定分别为：{{ \${(PiData.data.cpu_mem_used/1000000000).toPrecision(2)} ?? 0 }} GB、{{ \${(PiData.data.cpu_mem_free/1000000000).toPrecision(2)} ?? 0}} GB 和 {{ \${(PiData.data.cpu_mem_total/1000000000).toPrecision(2)} ?? 0 }} GB。对于磁盘部分，进度条和统计框小部件的绑定分别变为：进度条的绑定为：{{ PiData.data.disk_usage_percent ?? 0 }}。三个统计框小部件的绑定分别为：{{ \${(PiData.data.disk_usage_used/1000000000).toPrecision(2)} ?? 0 }} GB、{{ \${(PiData.data.disk_usage_free/1000000000).toPrecision(2)} ?? 0 }} GB 和 {{ \${(PiData.data.disk_usage_total/1000000000).toPrecision(2)} ?? 0 }} GB。这里的图表需要更新你为 CPU 统计信息创建的 utils 对象，使用 storeValue 键名为 disk_util_data，嵌套在 getLiveData 下面，其逻辑与 cpu_util_data 类似。对于磁盘利用率图表，我们存储的 disk_util_data 的逻辑与 CPU 利用率趋势图的逻辑相同。export default { getLiveData: () => { //When switch is on: if (Switch1.isSwitchedOn) { setInterval(() => { const cpuUtilData = appsmith.store.cpu_util_data; const diskUtilData = appsmith.store.disk_util_data; PiData.run(); storeValue("cpu_util_data", [...cpuUtilData, { x: PiData.data.cpu_percent,y: PiData.data.cpu_percent }]); storeValue("disk_util_data", [...diskUtilData, { x: PiData.data.disk_usage_percent,y: PiData.data.disk_usage_percent }]); }, 1500, 'timerId') } else { clearInterval('timerId'); } }, initialOnPageLoad: () => { storeValue("cpu_util_data", []); storeValue("disk_util_data", []); } } 通过使用 utils JavaScript 对象在打开和关闭真实数据开关时触发的数据流可视化如下所示：在打开实时数据开关时，图表会变成这样：整体上，它既漂亮，又简约，而且非常有用。祝你使用愉快！当你对 psutils、JavaScript 和 Appsmith 更加熟悉时，我相信你会发现可以轻松无限地调整你的仪表板，实现非常酷的功能，例如：查看先前一周、一个月、一个季度、一年或根据你的树莓派数据允许的任何自定义范围的趋势为任何统计数据的阈值违规构建报警机制监控连接到你的树莓派的其他设备将 psutils 扩展到另一台安装有 Python 的计算机上使用其他库监控你家庭或办公室的网络监控你的花园跟踪你自己的生活习惯在下一个令人兴奋的项目中，祝你玩得愉快！（题图：MJ/9754eb1f-1722-4897-9c35-3f20c285c332）via: https://opensource.com/article/23/3/build-raspberry-pi-dashboard-appsmith作者：Keyur Paralkar 选题：lkxed 译者：ChatGPT 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
Sniffnet：任何人都可以使用的有趣的开源网络监控工具
使用这个方便的应用查看你的网络连接。嗅探，嗅探，那是什么？ ?这是我在系统上看到的可疑网络连接吗？ ?❌你可能想知道我如何查看系统上的网络活动。 嗯，当然，一些 系统监控工具 应该有所帮助。但是，如果我想要更多详细信息怎么办？好吧，我遇到了一个不错的工具 Sniffnet。 它是一个用于实时监控网络活动的简洁的开源应用，也是专有的 GlassWire（如 Portmaster）的一个很好的替代品。那么，让我们深入挖掘吧！Sniffnet：概述 ⭐Sniffnet 是一个基于 Rust 的网络监控工具，可让你跟踪通过系统的所有互联网流量。 它提供了一些非常有用的功能，高级用户肯定会喜欢这些功能**。当你第一次启动 Sniffnet 时，你会看到一个菜单来选择网络适配器、过滤器和应用协议。完成选择后，单击“火箭”按钮开始。这是概览页的样子。它显示了所有当前的网络连接，以及一个方便的流量图，供你分析流量。? 你可以选择将数据显示为字节或单个数据包。要更深入地查看你的网络连接，你可以前往“ 检查 Inspect ”选项卡。在这里，你将看到一个包含所有重要信息的列表，例如 IP 地址、数据包数量、数据量、来源国家等等。你可以单击各个连接获得更详细的情况。如果你想为更具体的目的过滤结果怎么办？好吧，那么你可以使用“检查”选项卡下的过滤器选项根据你的需要过滤结果。你可以通过以下方式过滤列表： 应用、国家、域名等。这是它的样子：Sniffnet 还可以向你显示可以以文本格式导出的连接的详细报告。只需单击右下角带有页面/向右箭头的徽标即可开始。可定制性如何？好吧，Sniffnet 有一组不错的设置供你调整。第一个是“通知”选项卡，它允许你根据可以设置的各种阈值来设置通知的行为。然后是可以通过“ 样式 Style ”选项卡访问的主题。它提供四种选择，雪人之夜是我的最爱。最后，“ 语言 Language ”选项卡允许你设置你选择的界面语言。好了，就此结束。Sniffnet 是一个方便的工具，可以非常详细地了解系统上的网络活动。你可以使用它来监控你的网络。此外，与另一个网络监控工具 Nutty 相比，在我看来，Sniffnet 的使用和设置要直观得多。? 获取 SniffnetSniffnet 可跨多个平台使用，包括 Linux、Windows 和 macOS。 你可以前往官方网站获取你选择的安装包。Sniffnet如果愿意，你还可以在 GitHub 上仔细查看其源代码。via: https://news.itsfoss.com/sniffnet/作者：Sourav Rudra 选题：lkxed 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
你好! 我最近又开始运行一些服务器（nginx playground、mess with dns、dns lookup），所以我一直在考虑监控问题。最初我并不完全清楚如何监控这些网站，所以我想快速写下我是如何做到的。我根本不打算谈如何监控大型的、严肃的关键任务网站，只谈微型的不重要的网站。目标：在操作上几乎不花时间我希望网站大部分时间都能正常工作，但我也希望不用在持续的运营上花费时间。我最初对运行服务器非常警惕，因为在我的上一份工作中，我是 24/7 轮流值班，负责一些关键的服务，在我的印象中，“负责服务器”意味着“在凌晨 2 点被叫起来修理服务器”和“有很多复杂的仪表盘”。所以有一段时间我只做静态网站，这样我就不用考虑服务器的问题。但最终我意识到，我所要写的任何服务器的风险都很低，如果它们偶尔宕机 2 小时也没什么大不了的，我只需设置一些非常简单的监控来帮助它们保持运行。没有监控很糟糕起初，我根本没有为我的服务器设置任何监控。这样做的结果是非常可预见的：有时网站坏了，而我却没有发现，直到有人告诉我！步骤 1：uptime 检查器第一步是建立一个 uptime 检查器。外面有很多这样的东西，我现在使用的是 updown.io 和 uptime robot。我更喜欢 updown 的用户界面和 定价 结构（它是按请求而不是按月收费），但 uptime 机器人有一个更慷慨的免费套餐。它们会：检查网站是否正常如果出现故障，它会给我发电子邮件我发现电子邮件通知对我来说是一个很好的通知级别，如果网站宕机，我会很快发现，但它不会吵醒我或做其它的什么打扰。步骤 2：端到端的健康检查接下来，让我们谈谈“检查网站是否正常”到底是什么意思。起初，我只是把我的健康检查端点之一变成一个函数，无论如何都会返回 200 OK。这倒是挺有用的 – 它告诉我服务器是启动着的！但不出所料，我遇到了问题，因为它没有检查 API 是否真的在 工作 – 有时健康检查成功了，尽管服务的其他部分实际上已经进入了一个糟糕的状态。所以我更新了它，让它真正地发出 API 请求，并确保它成功了。我所有的服务都只做了很少的事情（nginx playground 只有一个端点），所以设置一个健康检查是非常容易的，它实际上贯穿了服务应该做的大部分动作。下面是 nginx playground 的端到端健康检查处理程序的样子。它非常基本：它只是发出一个 POST 请求（给自己），并检查该请求是成功还是失败。 func healthHandler(w http.ResponseWriter, r *http.Request) { // make a request to localhost:8080 with `healthcheckJSON` as the body // if it works, return 200 // if it doesn't, return 500 client := http.Client{} resp, err := client.Post("http://localhost:8080/", "application/json", strings.NewReader(healthcheckJSON)) if err != nil { log.Println(err) w.WriteHeader(http.StatusInternalServerError) return } if resp.StatusCode != http.StatusOK { log.Println(resp.StatusCode) w.WriteHeader(http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) } 健康检查频率：每小时一次现在，我大部分健康检查每小时运行一次，有些每 30 分钟运行一次。我每小时运行一次，因为 updown.io 的定价是按健康检查次数计算的，我正在监控 18 个不同的 URL，而且我想把我的健康检查预算保持在 5 美元/年的最低水平。花一个小时来发现这些网站中的一个出现故障，对我来说是可以的 – 如果有问题，我也不能保证能很快修复它。如果可以更频繁地运行它们，我可能会每 5-10 分钟运行一次。步骤 3：第三步：如果健康检查失败，自动重新启动我的一些网站在 fly.io 上，fly 有一个相当标准的功能，我可以为一个服务配置一个 HTTP 健康检查，如果健康检查失败，就重新启动服务。“经常重启”是一个非常有用的策略来弥补我尚未修复的 bug，有一段时间，nginx playground 有一个进程泄漏，nginx 进程没有被终止，所以服务器的内存一直在耗尽。通过健康检查，其结果是，每隔一天左右就会发生这样的情况：服务器的内存用完了健康检查开始失败它被重新启动一切又正常了几个小时后再次重复整个传奇最终，我开始实际修复进程泄漏，但很高兴有一个解决方法可以在我拖延修复 bug 时保持运行。这些用于决定是否重新启动服务的运行状况检查更频繁地运行：每 5 分钟左右。这不是监控大型服务的最佳方式这可能很明显，我在一开始就已经说过了，但是“编写一个 HTTP 健康检查”并不是监控大型复杂服务的最佳方法。 但我不会深入讨论，因为这不是这篇文章的主题。到目前为止一直运行良好！我最初在 3 个月前的四月写了这篇文章，但我一直等到现在才发布它以确保整个设置正常工作。这带来了很大的不同 – 在我遇到一些非常愚蠢的停机问题之前，现在在过去的几个月里，网站的运行时间达到了 99.95%！via: https://jvns.ca/blog/2022/07/09/monitoring-small-web-services/作者：Julia Evans 选题：lujun9972 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
Zeek 是一个开源的网络安全监控工具。本文讨论了如何将 Zeek 与 ELK 集成。在本杂志 2022 年 3 月版发表的题为“用 Zeek 轻松实现网络安全监控”的文章中，我们研究了 Zeek 的功能，并学习了如何开始使用它。现在我们将把我们的学习经验再进一步，看看如何将其与 ELK（即 Elasticsearch、Kibana、Beats 和 Logstash）整合。为此，我们将使用一个叫做 Filebeat 的工具，它可以监控、收集并转发日志到 Elasticsearch。我们将把 Filebeat 和 Zeek 配置在一起，这样后者收集的数据将被转发并集中到我们的 Kibana 仪表盘上。安装 Filebeat让我们首先将 Filebeat 与 Zeek 安装在一起。使用 apt 来安装 Filebeat，使用以下命令：sudo apt install filebeat 接下来，我们需要配置 .yml 文件，它位于 /etc/filebeat/ 文件夹中：sudo nano /etc/filebeat/filebeat.yml 我们只需要在这里配置两件事。在 Filebeat 输入部分，将类型改为 log，并取消对 enabled:false 的注释，将其改为 true。我们还需要指定存储日志的路径，也就是说，我们需要指定 /opt/zeek/logs/current/*.log。完成这些后，设置的第一部分应该类似于图 1 所示的内容。第二件要修改的事情是在输出下的 Elasticsearch 输出部分，取消对 output.elasticsearch 和 hosts 的注释。确保主机的 URL 和端口号与你安装 ELK 时配置的相似。我们把它保持为 localhost，端口号为 9200。在同一部分中，取消底部的用户名和密码的注释，输入安装后配置 ELK 时生成的 Elasticsearch 用户的用户名和密码。完成这些后，参考图 2，检查设置。现在我们已经完成了安装和配置，我们需要配置 Zeek，使其以 JSON 格式存储日志。为此，确保你的 Zeek 实例已经停止。如果没有，执行下面的命令来停止它：cd /opt/zeek/bin ./zeekctl stop 现在我们需要在 local.zeek 中添加一小行，它存在于 opt/zeek/share/zeek/site/ 目录中。以 root 身份打开该文件，添加以下行：@load policy/tuning/json-logs.zeek 参考图 3，确保设置正确。由于我们改变了 Zeek 的一些配置，我们需要重新部署它，这可以通过执行以下命令来完成：cd /opt/zeek/bin ./zeekctl deploy 现在我们需要在 Filebeat 中启用 Zeek 模块，以便它转发 Zeek 的日志。执行下面的命令：sudo filebeat modules enable zeek 我们几乎要好了。在最后一步，配置 zeek.yml 文件要记录什么类型的数据。这可以通过修改 /etc/filebeat/modules.d/zeek.yml 文件完成。在这个 .yml 文件中，我们必须提到这些指定的日志存放在哪个目录下。我们知道，这些日志存储在当前文件夹中，其中有几个文件，如 dns.log、conn.log、dhcp.log 等等。我们需要在每个部分提到每个路径。如果而且只有在你不需要该文件/程序的日志时，你可以通过把启用值改为 false 来舍弃不需要的文件。例如，对于 dns，确保启用值为 true，并且路径被配置：var.paths: [ “/opt/zeek/logs/current/dns.log”, “/opt/zeek/logs/*.dns.json” ] 对其余的文件重复这样做。我们对一些我们需要的文件做了这个处理。我们添加了所有主要需要的文件。你也可以这样做。请参考图 4。现在是启动 Filebeat 的时候了。执行以下命令：sudo filebeat setup sudo service filebeat start 现在一切都完成了，让我们移动到 Kibana 仪表板，检查我们是否通过 Filebeat 接收到来自 Zeek 的数据。进入仪表板。你可以看到它所捕获的数据的清晰统计分析（图 5 和图 6）。现在让我们进入发现选项卡，通过使用查询进行过滤来检查结果：event.module: "zeek" 这个查询将过滤它在一定时间内收到的所有数据，只向我们显示名为 Zeek 的模块的数据（图 7）。鸣谢作者感谢 VIT-AP 计算机科学与工程学院的 Sibi Chakkaravarthy Sethuraman、Sudhakar Ilango、Nandha Kumar R.和Anupama Namburu 的不断指导和支持。特别感谢人工智能和机器人技术卓越中心（AIR）。via: https://www.opensourceforu.com/2022/06/integrating-zeek-with-elk-stack/作者：Tridev Reddy 选题：lkxed 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
系统监控中心：一个理想的 Linux 任务管理器和资源监视器
“系统监控中心”是一个多合一的开源应用，不用使用多种工具就可以监控基本的系统资源情况。在 Linux 上以图形方式监控系统资源可能体验不是很好，这些与你的桌面环境相配套的系统监控工具可能细节有限。例如，GNOME 的系统监视器不显示 CPU 频率和温度。此外，Linux 的默认系统监控程序通常以简单为目标，而不是提供详细的信息。“ 系统监控中心 System Monitoring Center ” 是一个有用的 GUI 工具，它提供了大量必要的信息。在这篇文章中，让我给你详细介绍一下它。系统监控中心：显示基本系统统计信息的 Linux 应用系统监控中心是一个基于 GTK3 和 Python 3 的外观时尚的应用，它为你提供了你想要的所有资源使用数据。在我的例子中，我想在使用系统监控工具时关注 CPU 的频率，但 GNOME 的系统监控工具提供不了帮助。所以，这个应用就非常有用了。该应用的用户体验良好，并提供了大量的信息和功能。让我重点介绍一下它的主要功能。写这篇文章时，该应用程序仍处于测试阶段。因此，你可能会遇到一些错误。然而，我在简短的测试中没有注意到任何问题。系统监控中心的功能首先，它可以让你查看 CPU、内存、磁盘、网络、GPU 和传感器的单独统计数据。你可以在该工具中看到以下细节信息：显示 CPU 状态，包括频率能够显示平均使用率或每个核心的使用率可以选择 CPU 频率和其他统计的精度能够改变图表的颜色按用户过滤系统进程并轻松管理它们切换一个浮动的摘要小部件，以快速获得信息显示磁盘使用信息和连接的驱动器在同一个应用中显示详细的系统信息控制启动服务和程序能够控制状态更新的时间间隔应用本身的系统资源使用率较低适应系统主题虽然它为每个标签（或组件）提供了大量的选项和自定义功能，但我希望它能在未来的更新中包括 RAM 频率等东西。然而，考虑到它可以同时取代磁盘使用分析器和 neofetch 等终端工具，其余的数据似乎非常有用。请注意，如果你有多个机箱风扇、独立的排风扇或 AIO，你可能无法得到风扇的数据。温度也可能有或没有，但 CPU 的温度应该是可见的。在 Linux 中安装系统监控中心你可以使用可用的 deb 包在任何基于 Ubuntu 的发行版上轻松安装它。不幸的是，没有可用的其他软件包，只有一个 ZIP 文件，你得手动构建和编译才能安装它。你在 ZIP 文件中可以找到一个脚本来构建 RPM 包。deb 文件可以通过 SourceForge 获得。你点击下面的按钮来下载它，或者在他们的 GitLab 页面 找到。下载系统监控中心总结系统监控中心是一个开源应用，可以让用户详细了解他们的系统资源，并帮助管理进程。对于许多 Linux 用户来说，这是一个非常需要的应用，不需要使用单独的终端/GUI 程序就可以提供详细的信息。via: https://itsfoss.com/system-monitoring-center/作者：Ankush Das 选题：lujun9972 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
在这篇文章中，我们将介绍 below：一个用于现代 Linux 系统的 Apache 2.0 许可的资源监视器。below 可以让你重放以前记录的数据。背景内核的主要职责之一是调度对资源的访问。有时这可能意味着分配物理内存，使多个进程可以共享同一主机。其他时候，它可能意味着确保 CPU 时间的公平分配。在这些场景里，内核提供了机制，而将策略留给了“别人”。近来，这个“别人”通常是 systemd 或 dockerd 这样的运行时。运行时接受来自调度器或最终用户的输入（类似于运行什么和如何运行）并在内核上转动正确的旋钮和拉动正确的杠杆，从而使工作负载能够好好工作。在一个完美的世界里，故事就到此结束了。然而，现实情况是，资源管理是一个复杂的、相当不透明的技术混合体，在几十年里计算技术不断发展。尽管其中一些技术有各种缺陷和死角，但最终的结果是，容器运作得比较好。虽然用户通常不需要关心这些细节，但对于基础设施运营商来说，对他们的技术架构拥有可见性是至关重要的。可见性和可调试性对于检测和调查错误的配置、问题和系统性故障至关重要。让事情变得更加复杂的是，资源中断往往难以重现。经常需要花费数周时间等待一个问题重新出现，以便调查其根本原因。规模的扩大进一步加剧了这个问题：我们不能在每台主机上运行一个自定义脚本，希望在错误再次发生时记录下关键状态的片段。因此，需要更复杂的工具。这就出现了 below。动机历史上，Facebook 一直是 atop 的忠实用户。atop 是一个用于 Linux 的性能监视器，能够报告所有进程的活动以及各种系统级活动。与 htop 等工具相比，atop 最引人注目的功能之一是能够作为一个守护程序记录历史数据。这听起来是一个简单的功能，但在实践中，这使得调试无数的生产问题成为可能。有了足够长的数据保留，就有可能在时间上回溯，查看在问题或故障发生之前、期间和之后的主机状态。不幸的是，随着时间的推移，人们发现atop 有某些不足之处。首先， 控制组 cgroup 已经成为控制和监视 Linux 机器上资源的实际方式。atop 仍然缺乏对这一基本构建模块的支持。第二，atop 用自定义的 delta 压缩方法在磁盘上存储数据。这在正常情况下运行良好，但在沉重的资源压力下，主机很可能会丢失数据点。由于使用了 delta 压缩，在数据最重要的时间段内，数据可能会大面积丢失。第三，用户体验有一个陡峭的学习曲线。我们经常听到 atop 的资深用户说，他们喜欢密集的布局和众多的键盘绑定。然而，这也是一把双刃剑。当一个刚进入这个领域的人想要调试一个生产问题时，他们现在要同时解决两个问题：手头的问题和如何使用 atop。below 是由 Facebook 的资源控制团队为其设计和开发的，并得到了 atop 生产环境用户的支持。顾名思义，资源控制团队负责的是规模化的资源管理。该团队由内核开发人员、容器运行时开发人员和硬件人员组成。认识到下一代系统监控器的机会，我们在设计 below 时考虑到以下几点：易用性：below 必须既能为新用户提供直观的体验，又能为日常用户提供强大的功能。 *有意义的统计数据：below 显示准确和有用的统计数据。即便可以，但我们尽量避免收集和倾倒统计数字。灵活性：当默认设置不合适时，我们允许用户自定义他们的体验。例如包括可配置的键绑定、可配置的默认视图，以及脚本界面（默认为终端用户接口）。安装安装该软件包：# dnf install -y below 打开记录守护进程：# systemctl enable --now below 快速介绍below 最常用的模式是重放模式。顾名思义，重放模式是重放以前记录的数据。假设你已经启动了记录守护程序，那么通过运行以下程序启动一个会话：$ below replay --time "5 minutes ago" 然后你会看到控制组视图：如果你不知道该怎么操作，或者忘记了一个键位，按 ? 可以进入帮助菜单。屏幕的最上方是状态栏。状态栏显示关于当前样本的信息。你可以通过按 t 和 T 分别向前和向后移动样本。中间的部分是系统概览。系统概览包含了关于整个系统的统计数据，一般来说，这些数据总是很有用的。第三部分也是最下面的部分是多用途视图。上面的图片显示了控制组视图。此外，还有进程和系统视图，分别通过按 p 和s 来访问。按 ↑ 和 ↓ 来移动列表选择。按回车键来折叠和展开控制组。假设你发现了一个感兴趣的控制组，你想看看它里面有哪些进程在运行。要放大进程视图，选择控制组并按 z：再按 z 返回到控制组视图。这个视图有时会有点长。如果你对你要找的东西有一个模糊的概念，你可以通过按 / 并输入一个过滤器来过滤控制组名称。在这一点上，你可能已经注意到了一个我们还没有探索过的标签系统。要在标签中向前和向后循环，可以分别按 Tab 和 Shift + Tab。我们把这个问题留给读者去做练习。其他功能在底层，below 有一个强大的设计和架构。Facebook 正在不断升级到更新的内核，所以我们从不假设数据源是可用的。这种默契的假设使得内核和 below版本之间能够完全向前和向后兼容。此外，每个数据点都用 zstd 压缩并完整地存储。这解决了我们看到的 atop 在大规模时的 delta 压缩问题。根据我们的测试，我们的每个样本压缩可以达到平均 5 倍的压缩率。below 也使用 eBPF 来收集关于短暂进程（生存时间短于数据收集间隔的进程）的信息。相比之下，atop 使用 BSD 进程核算来实现这一功能，这是一个已知缓慢且容易发生优先级转换的内核接口。对于用户来说，below 还支持实时模式和一个转储接口。实时模式将记录守护程序和 TUI 会话合并到一个进程中。这对于浏览系统状态是很方便的，不需要为数据存储投入长期运行的守护程序或磁盘空间。转储接口是一个可编写脚本的接口，用于所有的 below 数据存储。转储既强大又灵活，详细的数据以 CSV、JSON 和人类可读格式提供。总结below 是一个 Apache 2.0 许可的开源项目，我们（below 的开发者）认为它比资源监控领域的现有工具具有引人注目的优势。我们已经花了大量的精力来准备 below，以提供开源使用，所以我们希望读者和社区有机会尝试 below，并报告错误和功能要求。via: https://fedoramagazine.org/below-a-time-traveling-resource-monitor/作者：Daniel Xu 选题：lujun9972 译者：wxy 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
如何找到一个程序的进程 ID（PID）。最常见的 Linux 工具是由 procps-ng 包提供的，包括 ps、pstree、pidof 和 pgrep 命令。在 POSIX 术语中， 进程 process 是一个正在进行的事件，由操作系统的内核管理。当你启动一个应用时就会产生一个进程，尽管还有许多其他的进程在你的计算机后台运行，包括保持系统时间准确的程序、监测新的文件系统、索引文件，等等。大多数操作系统都有某种类型的系统活动监视器，因此你可以了解在任何特定时刻有哪些进程在运行。Linux 有一些供你选择，包括 GNOME 系统监视器和 KSysGuard。这两个软件在桌面环境都很有用，但 Linux 也提供了在终端监控系统的能力。不管你选择哪一种，对于那些积极管理自己电脑的人来说，检查一个特定的进程是一项常见的任务。在这篇文章中，我演示了如何找到一个程序的进程 ID（PID）。最常见的工具是由 procps-ng 包提供的，包括 ps、pstree、pidof 和 pgrep 命令。查找一个正在运行的程序的 PID有时你想得到一个你知道正在运行的特定程序的进程 ID（PID）。pidof 和 pgrep 命令可以通过命令名称查找进程。pidof 命令返回一个命令的 PID，它按名称搜索确切的命令：$ pidof bash 1776 5736 pgrep 命令允许使用正则表达式：$ pgrep .sh 1605 1679 1688 1776 2333 5736 $ pgrep bash 5736 通过文件查找 PID你可以用 fuser 命令找到使用特定文件的进程的 PID。$ fuser --user ~/example.txt /home/tux/example.txt: 3234(tux) 通过 PID 获得进程名称如果你有一个进程的 PID 编号，但没有生成它的命令，你可以用 ps 做一个“反向查找”：$ ps 3234 PID TTY STAT TIME COMMAND 5736 pts/1 Ss 0:00 emacs 列出所有进程ps 命令列出进程。你可以用 -e 选项列出你系统上的每一个进程：PID TTY TIME CMD 1 ? 00:00:03 systemd 2 ? 00:00:00 kthreadd 3 ? 00:00:00 rcu_gp 4 ? 00:00:00 rcu_par_gp 6 ? 00:00:00 kworker/0:0H-events_highpri [...] 5648 ? 00:00:00 gnome-control-c 5656 ? 00:00:00 gnome-terminal- 5736 pts/1 00:00:00 bash 5791 pts/1 00:00:00 ps 5792 pts/1 00:00:00 less (END) 只列出你的进程ps -e 的输出可能会让人不知所措，所以使用 -U 来查看一个用户的进程：$ ps -U tux | less PID TTY TIME CMD 3545 ? 00:00:00 systemd 3548 ? 00:00:00 (sd-pam) 3566 ? 00:00:18 pulseaudio 3570 ? 00:00:00 gnome-keyring-d 3583 ? 00:00:00 dbus-daemon 3589 tty2 00:00:00 gdm-wayland-ses 3592 tty2 00:00:00 gnome-session-b 3613 ? 00:00:00 gvfsd 3618 ? 00:00:00 gvfsd-fuse 3665 tty2 00:01:03 gnome-shell [...] 这样就减少了 200 个（可能是 100 个，取决于你运行的系统）需要分类的进程。你可以用 pstree 命令以不同的格式查看同样的输出：$ pstree -U tux -u --show-pids [...] ├─gvfsd-metadata(3921)─┬─{gvfsd-metadata}(3923) │ └─{gvfsd-metadata}(3924) ├─ibus-portal(3836)─┬─{ibus-portal}(3840) │ └─{ibus-portal}(3842) ├─obexd(5214) ├─pulseaudio(3566)─┬─{pulseaudio}(3640) │ ├─{pulseaudio}(3649) │ └─{pulseaudio}(5258) ├─tracker-store(4150)─┬─{tracker-store}(4153) │ ├─{tracker-store}(4154) │ ├─{tracker-store}(4157) │ └─{tracker-store}(4178) └─xdg-permission-(3847)─┬─{xdg-permission-}(3848) └─{xdg-permission-}(3850) 列出进程的上下文你可以用 -u 选项查看你拥有的所有进程的额外上下文。$ ps -U tux -u USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND tux 3545 0.0 0.0 89656 9708 ? Ss 13:59 0:00 /usr/lib/systemd/systemd --user tux 3548 0.0 0.0 171416 5288 ? S 13:59 0:00 (sd-pam) tux 3566 0.9 0.1 1722212 17352 ? S<sl 13:59 0:29 /usr/bin/pulseaudio [...] tux 3570 0.0 0.0 664736 8036 ? SLl 13:59 0:00 /usr/bin/gnome-keyring-daemon [...] [...] tux 5736 0.0 0.0 235628 6036 pts/1 Ss 14:18 0:00 bash tux 6227 0.0 0.4 2816872 74512 tty2 Sl+14:30 0:00 /opt/firefox/firefox-bin [...] tux 6660 0.0 0.0 268524 3996 pts/1 R+ 14:50 0:00 ps -U tux -u tux 6661 0.0 0.0 219468 2460 pts/1 S+ 14:50 0:00 less 用 PID 排除故障如果你在某个特定的程序上有问题，或者你只是好奇某个程序在你的系统上还使用了什么资源，你可以用 pmap 查看运行中的进程的内存图。$ pmap 1776 5736: bash 000055f9060ec000 1056K r-x-- bash 000055f9063f3000 16K r---- bash 000055f906400000 40K rw--- [ anon ] 00007faf0fa67000 9040K r--s- passwd 00007faf1033b000 40K r-x-- libnss_sss.so.2 00007faf10345000 2044K ----- libnss_sss.so.2 00007faf10545000 4K rw--- libnss_sss.so.2 00007faf10546000 212692K r---- locale-archive 00007faf1d4fb000 1776K r-x-- libc-2.28.so 00007faf1d6b7000 2044K ----- libc-2.28.so 00007faf1d8ba000 8K rw--- libc-2.28.so [...] 处理进程 IDprocps-ng 软件包有你需要的所有命令，以调查和监控你的系统在任何时候的使用情况。无论你是对 Linux 系统中各个分散的部分如何结合在一起感到好奇，还是要对一个错误进行调查，或者你想优化你的计算机的性能，学习这些命令都会为你了解你的操作系统提供一个重要的优势。via: https://opensource.com/article/21/8/linux-procps-ng作者：Seth Kenlon 选题：lujun9972 译者：geekpi 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
使用微控制器、传感器、Python 以及 MQTT 持续追踪温室的温度、湿度以及环境光。CircuitPython 提供了一种和微控制器板进行交互的革命性方式。这篇文章介绍了如何使用 CircuitPython 来监测温室的温度、湿度以及环境光，并且使用 CircuitPython MQTT 客户端将结果发布到一个 MQTT 中介 broker 。你可以在若干个程序中订阅 MQTT 队列并进一步处理信息。这个项目使用一个简单的 Python 程序来运行 Web 服务器，它发布一个 Prometheus 格式的采集端点，拉取监控指标到 Prometheus 进行不间断的监控。关于 CircuitPythonCircuitPython 是一个由 Adafruit 创建的开源 Python 发行版，用于运行在低成本微控制器开发板上。CircuitPython 为与 兼容的开发板 的交互提供了简单的开发体验。你可以在连接你的开发板时挂载的 CIRCUITPYTHON 根驱动器上创建一个 code.py 文件来启动你的程序。CircuitPython 还为开发板提供了一个串行连接，包含一个交互式解释器（REPL）会话，你可以使用 Python 代码实时和开发板进行交互。Adafruit 的网站提供了大量的文档，可以帮助你开始使用 CircuitPython。首先，参考下《欢迎来到 CircuitPython》指南。这份指南能够帮助你开始使用 CircuitPython 在开发板上运行代码以及和 REPL 交互。它还记录了如何安装 Adafruit 的 CircuitPython 库合集和范例，可以用在它出售的许多开发板和传感器上。接下来，阅读《CircuitPython 基础》指南来学习更多关于其功能的信息，里面还有链接指向在特定及兼容的开发板上使用 CircuitPython 的相关信息。最后，就如所有开源软件一样，你可以深入 CircuitPython 的源码，发布议题，以及做出贡献。微控制器设置微控制器系统非常简单。要完成这个示例项目，你会需要：树莓派 4：你需要一台电脑来给微控制器系统编程，我用的是树莓派 4。CircuitPython 兼容的微控制器：我用的是 Adafruit Feather S2，带有内置 WiFi，环境光传感器，Qwiic 线缆输入。微控制器 WiFi：Feather S2 内置了 WiFi。如果你的微控制器没有，你需要给开发板找个 WiFi 扩展板。传感器：Feather S2 有个内置的环境光传感器，所以我还需要一个温湿度传感器。有很多不同厂商的产品可以选择，包括 Adafruit、SparkFun、亚马逊。我用的是一个 Adafruit 传感器，带有 Feather S2 输入兼容的 Qwiic 线缆。尽管多数 SparkFun 传感器可以在 Adafruit 库下工作，但如果你不是从 Adafruit 购买的传感器，你可能还是需要自己去找到它兼容 CircuitPython 的 Python 库。跳线和线缆：为了避免使用面包板或焊接，我使用 Adafruit Qwiic 线缆。SparkFun 销售的包含不同长度的线缆套装中也有它。在将微控制器连接到你的电脑之前，将传感器连接到微控制器上。现在你可以将微控制器用 USB 数据线连接到你的电脑。MQTT 中介你可以使用 这份说明 来在树莓派的系统上安装 Mosquitto MQTT 中介 和 Mosquitto 客户端。如果你想把树莓派做为长期服务器使用，在你的网络上给树莓派 4 设置一个静态 IP 地址。Mosquitto 中介运行起来之后，创建一份 用户名/密码文件，设置客户端向中介发布和订阅消息时用的认证信息。你可以用树莓派上的 Mosquitto 客户端来测试 MQTT 中介。打开两个终端（如果你是无界面运行的话打开两个 SSH 会话）：在终端一输入：mosquitto_sub -h localhost -u $user -P $pass -t "mqtt/test" 这条命令会启动一个持续运行的进程，监听发布到 mqtt/test 队列的消息。在终端二输入：mosquitto_pub -h localhost -u $user -P $pass -t "mqtt/test" -m hello` 这条命令会向 mqtt/test 队列发布一条消息，它应该会显示在终端一的输出里。现在你可以中止终端一运行的 sub 命令了。Mosquitto 中介允许客户端发布消息到任何队列，甚至没有任何订阅的队列也可以。这些消息会永久丢失，但这不会阻止客户端继续发布消息。打开第三个终端，订阅下列队列（你的控制器会发布消息到这些队列上）：greenhouse/temperaturegreenhouse/lightgreenhouse/humidity给微控制器编码现在你已经准备好给微控制器编码，发布它的监测指标到树莓派 4 上运行的 MQTT 中介上了。Adafruit 有 出色的文档，指导你使用 CircuitPython 库合集 的库来将你的微控制器连接到 WiFi 路由器，并发布监测指标到 MQTT 中介上。安装下列库到 CIRCUITPYTHON/lib 目录，温室监控会用到它们。这些库在 Adafruit 的 CircuitPython 库合集中都有提供：adafruit_bus_device：一个带有多个 .mpy 文件的 Python 包文件夹（.mpy 是经过压缩的 Python 文件，用以节省空间）adafruit_requests：单个 .mpy 文件adafruit_register：一个包文件夹adafruit_minimqtt：一个包文件夹adafruit_si7021：单个 .mpy 文件，用来支持温湿度传感器库装好了之后，将以下代码写入 CIRCUITPYTHON 文件夹的 code.py 文件中：import time import ssl import socketpool import wifi import adafruit_minimqtt.adafruit_minimqtt as MQTT import board from digitalio import DigitalInOut, Direction, Pull from analogio import AnalogIn import adafruit_si7021 # Add a secrets.py to your filesystem that has a dictionary called secrets with "ssid" and # "password" keys with your WiFi credentials. DO NOT share that file or commit it into Git or other # source control. # pylint: disable=no-name-in-module,wrong-import-order try: from secrets import secrets except ImportError: print("WiFi secrets are kept in secrets.py, please add them there!") raise print("Connecting to %s" % secrets["ssid"]) wifi.radio.connect(secrets["ssid"], secrets["password"]) print("Connected to %s!" % secrets["ssid"]) ### Feeds ### light_feed = "greenhouse/light" temp_feed = "greenhouse/temperature" humidity_feed = "greenhouse/humidity" # Define callback methods which are called when events occur # pylint: disable=unused-argument, redefined-outer-name def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. print("Connected to MQTT!") def disconnected(client, userdata, rc): # This method is called when the client is disconnected print("Disconnected from MQTT!") def get_voltage(pin): return (pin.value * 3.3) / 65536 # Create a socket pool pool = socketpool.SocketPool(wifi.radio) # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( broker=secrets["broker"], port=secrets["port"], username=secrets["aio_username"], password=secrets["aio_key"], socket_pool=pool, ssl_context=ssl.create_default_context(), ) # Setup the callback methods above mqtt_client.on_connect = connected mqtt_client.on_disconnect = disconnected # Connect the client to the MQTT broker. print("Connecting to MQTT...") mqtt_client.connect() # Create library object using our Bus I2C port sensor = adafruit_si7021.SI7021(board.I2C()) light_pin = AnalogIn(board.IO4) while True: # Poll the message queue mqtt_client.loop() # get the current temperature light_val = get_voltage(light_pin) temp_val = ((sensor.temperature * 9)/5) + 32 humidity_val = sensor.relative_humidity # Send a new messages mqtt_client.publish(light_feed, light_val) mqtt_client.publish(temp_feed, temp_val) mqtt_client.publish(humidity_feed, humidity_val) time.sleep(0.5) 保存你的代码。然后连接到串行监视器，看程序连接到你的 MQTT 中介。你还可以将树莓派 4 上的终端切换到订阅了它的发布队列的终端来查看输出。处理监测指标像 MQTT 这样的发布/订阅工作流给微控制器系统提供了诸多好处。你可以有多个微控制器 + 传感器来回报同一个系统的不同指标或并行回报相同指标的若干读数。你还可以有多个不同进程订阅各个队列，并行地对这些消息进行回应。甚至还可以有多个进程订阅相同的队列，对消息做出不同的动作，比如数值过高时发送通知邮件或将消息发送到另一个 MQTT 队列上去。另一个选项是让一个微控制器订阅一个外部队列，可以发送信号告诉微控制器做出动作，比如关闭或开始一个新会话。最后，发布/订阅工作流对低功耗微控制器系统更佳（比如那些使用电池或太阳能的系统），因为这些设备可以在更长的延迟周期后批量发布监测指标，并在回报的间隔期间关闭大量消耗电量的 WiFi 广播。要处理这些监测指标，我创建了一个 Python 客户端，使用 Paho Python MQTT 客户端 订阅监测指标队列。我还使用官方的 Prometheus Python 客户端 创建了一个 Web 服务器，它产生一个符合 Prometheus 标准的采集端点，使用这些监测指标作为面板信息。Prometheus 服务器和 Mosquitto MQTT 中介我都是运行在同一个树莓派 4 上的。from prometheus_client import start_http_server, Gauge import random import time import paho.mqtt.client as mqtt gauge = { "greenhouse/light": Gauge('light','light in lumens'), "greenhouse/temperature": Gauge('temperature', 'temperature in fahrenheit'), "greenhouse/humidity": Gauge('humidity','relative % humidity') } try: from mqtt_secrets import mqtt_secrets except ImportError: print("WiFi secrets are kept in secrets.py, please add them there!") raise def on_connect(client, userdata, flags, rc): print("Connected with result code "+str(rc)) # Subscribing in on_connect() means that if we lose the connection and # reconnect then subscriptions will be renewed. client.subscribe("greenhouse/light") client.subscribe('greenhouse/temperature') client.subscribe('greenhouse/humidity') def on_message(client, userdata, msg): topic = msg.topic payload = msg.payload gauge[topic].set(payload) client = mqtt.Client() client.username_pw_set(mqtt_secrets["mqtt_user"],mqtt_secrets['mqtt_password']) client.on_connect = on_connect client.on_message = on_message client.connect('localhost',1883,60) if __name__ == '__main__': # Start up the server to expose the metrics. client = mqtt.Client() client.username_pw_set('london','abc123') client.on_connect = on_connect client.on_message = on_message client.connect('localhost',1883,60) start_http_server(8000) client.loop_forever() 然后我配置 Prometheus 服务器采集端点数据到 localhost:8000。你可以在 Github 上访问 温室 MQTT 微控制器 这个项目的代码，项目采用 MIT 许可证授权。via: https://opensource.com/article/21/5/monitor-greenhouse-open-source作者：Darin London 选题：lujun9972 译者：alim0x 校对：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 轴堆内存使用（GB）内存资源的使用对用户来说是最为困扰的问题，因为它相对的不可预测且可能导致进程崩溃。显然，查询的服务器正在消耗内存，这很大程度上归咎于查询引擎的开销，这一点可以当作以后优化的主题。总的来说，Prometheus 2.0 的内存消耗减少了 3-4 倍。大约 6 小时之后，在 Prometheus 1.5 上有一个明显的峰值，与我们设置的 6 小时的保留边界相对应。因为删除操作成本非常高，所以资源消耗急剧提升。这一点在下面几张图中均有体现。CPU 使用（核心/秒）类似的模式也体现在 CPU 使用上，但是查询的服务器与非查询的服务器之间的差异尤为明显。每秒获取大约 11 万个数据需要 0.5 核心/秒的 CPU 资源，比起评估查询所花费的 CPU 时间，我们的新存储系统 CPU 消耗可忽略不计。总的来说，新存储需要的 CPU 资源减少了 3 到 10 倍。磁盘写入（MB/秒）迄今为止最引人注目和意想不到的改进表现在我们的磁盘写入利用率上。这就清楚的说明了为什么 Prometheus 1.5 很容易造成 SSD 损耗。我们看到最初的上升发生在第一个块被持久化到序列文件中的时期，然后一旦删除操作引发了重写就会带来第二个上升。令人惊讶的是，查询的服务器与非查询的服务器显示出了非常不同的利用率。在另一方面，Prometheus 2.0 每秒仅向其预写日志写入大约一兆字节。当块被压缩到磁盘时，写入定期地出现峰值。这在总体上节省了：惊人的 97-99%。磁盘大小（GB）与磁盘写入密切相关的是总磁盘空间占用量。由于我们对样本（这是我们的大部分数据）几乎使用了相同的压缩算法，因此磁盘占用量应当相同。在更为稳定的系统中，这样做很大程度上是正确地，但是因为我们需要处理高的序列分流，所以还要考虑每个序列的开销。如我们所见，Prometheus 1.5 在这两个版本达到稳定状态之前，使用的存储空间因其保留操作而急速上升。Prometheus 2.0 似乎在每个序列上的开销显著降低。我们可以清楚的看到预写日志线性地充满整个存储空间，然后当压缩完成后瞬间下降。事实上对于两个 Prometheus 2.0 服务器，它们的曲线并不是完全匹配的，这一点需要进一步的调查。前景大好。剩下最重要的部分是查询延迟。新的索引应当优化了查找的复杂度。没有实质上发生改变的是处理数据的过程，例如 rate() 函数或聚合。这些就是查询引擎要做的东西了。第 99 个百分位查询延迟（秒）数据完全符合预期。在 Prometheus 1.5 上，查询延迟随着存储的序列而增加。只有在保留操作开始且旧的序列被删除后才会趋于稳定。作为对比，Prometheus 2.0 从一开始就保持在合适的位置。我们需要花一些心思在数据是如何被采集上，对服务器发出的查询请求通过对以下方面的估计来选择：范围查询和即时查询的组合，进行更轻或更重的计算，访问更多或更少的文件。它并不需要代表真实世界里查询的分布。也不能代表冷数据的查询性能，我们可以假设所有的样本数据都是保存在内存中的热数据。尽管如此，我们可以相当自信地说，整体查询效果对序列分流变得非常有弹性，并且在高压基准测试场景下提升了 4 倍的性能。在更为静态的环境下，我们可以假设查询时间大多数花费在了查询引擎上，改善程度明显较低。摄入的样本/秒最后，快速地看一下不同 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中国 荣誉推出