我如何构建正常运行时间监控服务架构 目录

2025-06-07

我如何构建正常运行时间监控服务架构

目录

目录

简介
要求
VPS 提供商和服务器
使用的技术
节点如何工作
核心服务器如何工作
总结
什么不起作用
为什么花了这么长时间?

简介

你好呀!

这是我的第一篇文章,所以可能不完美😇

大约一年前,我决定构建一个正常运行时间监控服务,于是就有了https://pingr.io

它是一个网络应用程序,它会持续检查您的 URL 是否以 200 OK 或您喜欢的任何其他响应代码进行响应。

这个想法其实很简单。你可能会觉得发起一个 HTTP 请求其实并不难。

但我花了一年时间。😱

在本文中我想描述它是如何工作的。

我并不是说这个架构很好。

架构在某种程度上是在尽快推出产品和拥有优秀架构之间的妥协。

我不想再花一年的时间来使其完美,因为在开发过程中保持动力相当困难。

我很想听听哪些方面可以改进以及哪些方面我做错了。

让我们开始吧。

要求

乍一看,发起 HTTP 请求似乎很简单。然而,服务端需要满足很多要求。以下是其中一些:

  1. 应该从分布在世界各地的多个节点检查 URL。
  2. 最低检查频率为 1 分钟
  3. 您应该能够添加新节点
  4. 每个节点每分钟都应该发出一定数量的 HTTP 请求。起初,500-1000 个请求我还可以接受。但随着用户数量的增长,这个数字可能会更高。
  5. 除了 HTTP 检查之外,该服务还应检查 SSL 证书(是否有效/是否即将过期等)
  6. 显示 URL 状态的 UI 应该实时更新
  7. 应根据用户的选择,通过不同的方式通知用户正常运行/停机事件

VPS提供商和服务器

我认为,如果您希望尽快启动并运行您的产品,那么为您满意的服务支付更多费用是可以的。

因此我尝试了 ScaleWay 和 Digital Ocean,但最终我将我的所有服务器都转移到了 Digital Ocean,因为我更喜欢它。

我所拥有的:

  1. Ubuntu VPS 带 MySQL。3 GB / 1 vCPU
  2. 5 个 Ubuntu VPS,位于世界各地。3 GB / 1 vCPU
  3. Ubuntu VPS 作为核心服务器。4 GB / 2 vCPU
  4. Redis 数据库:1 GB RAM / 1vCPU

关于 MySQL。一开始我用的是他们的 RDS,因为它本来就是用来做数据库的,应该没什么问题。但当我想修改 my.cnf 文件时,我意识到我没法这么做。我喜欢掌控一切,至少在学习初期是这样。

因此,目前,我决定只安装带有 MySQL 的 VPS,因为它可以给我更多的控制权。

为什么是 MySQL?不知道。我一直只用这个数据库。
为什么是 Ubuntu?不知道。我一直只用这个操作系统。😝

我使用的技术

  1. Laravel 用于后端
  2. 前端的 VueJS
  3. MySQL 数据库
  4. Redis 用于队列
  5. 用于发送请求的 cURL(Guzzle 库)
  6. Supervisord 帮助我的员工保持正常运行
  7. ...
  8. 利润!

artisan 命令列表

当我写这篇文章时,我意识到如果我在这里列出我使用的所有 Laravel 命令可能会有很大帮助:

  1. php artisan monitor:run-uptime-checks {--frequency=1}frequency- 调度正常运行时间检查任务。每分钟由 cron 运行一次
  2. php artisan checks:push- 从本地 Redis 数据库获取检查结果到临时 MySQL 表。由 Supervisord 持续运行
  3. php artisan checks: pull- 从临时 MySQL 表中获取检查结果,并计算监控状态/正常运行时间/其他指标。由 Supervisord 持续运行。

节点如何工作

这是文章最重要的部分之一,描述了单个节点的工作原理。

我应该注意我有一个Monitor实体,代表应该检查的 URL。

让显示器做好正常运行时间检查的准备

替代文本

php artisan monitor:run-uptime-checks命令根据某些条件从数据库中获取监视器。

其中一个要求是正常运行时间检查频率,也就是我们应该多久检查一次监控器。并非每个用户都希望每分钟都检查一次他们的网站。

然后,使用Laravel 调度机制,可以轻松设置以不同的频率运行此命令。

将频率作为参数传递可以帮助我仅获取需要检查的监视器,具体取决于用户设置的频率。



// In the RunUptimeChecks command, we fetch monitors by frequency specified by the user

$schedule->command(RunUptimeChecks::class, ['--frequency=1'])
         ->everyMinute();

$schedule->command(RunUptimeChecks::class, ['--frequency=5'])
         ->everyFiveMinutes();

$schedule->command(RunUptimeChecks::class, ['--frequency=10'])
        ->everyTenMinutes();

$schedule->command(RunUptimeChecks::class, ['--frequency=15'])
         ->everyFifteenMinutes();

$schedule->command(RunUptimeChecks::class, ['--frequency=30'])
         ->everyThirtyMinutes();

$schedule->command(RunUptimeChecks::class, ['--frequency=60'])
         ->hourly();


Enter fullscreen mode Exit fullscreen mode

排队人数

然后,我将正常运行时间检查任务放入每个监视器的 Redis 队列中。命令如下RunUptimeChecks



foreach ($monitors as $monitor) {
    RunUptimeCheck::dispatch(
        (object) $monitor->toArray(), 
        $node->id
   );
}


Enter fullscreen mode Exit fullscreen mode

👉 您可能会在这里注意到一些奇怪的事情:(对象)$monitor->toArray()。

起初,我将 Monitor 模型传递给了作业。然而,两者之间有一个显著的区别:当你将模型传递给作业时,它只会在队列中存储一个模型 ID。然后,当作业执行时,Laravel 会连接到数据库来获取该模型,这导致了数百个不必要的连接

这就是为什么我传递一个对象而不是实际模型,其序列化得相当好。

另一种可能的方法是将这种SerializesModels特质从工作中移除,这也可能有效,但我还没有尝试过。

因此,完成此操作后,队列中就会有一定数量的作业准备执行。

运行正常运行时间检查

替代文本

要执行作业,我们需要运行php artisan queue:work命令。

我们还需要的是:

  1. 运行许多命令实例,因为我们需要每分钟进行尽可能多的检查
  2. 如果命令失败,我们需要撤销它并再次运行。

为了这个目的,我使用了Supervisord

它的作用是生成 N 个 PHP 进程,每个进程都是queue:work一个命令。如果失败,Supervisord 会重新运行该命令。

根据 VPS 内存和 CPU 核心数,我们可能会调整进程数量。这样,我们就可以增加每分钟执行的正常运行时间检查次数。

存储临时结果

替代文本

现在,我们进行检查后,需要将其存储在某个地方。

首先,我将检查结果存储在本地 Redis 数据库中。
由于可能有很多进程会不断地将数据推送到队列,因此 Redis 非常适合此用途,因为它是一个内存数据库,速度非常快。

然后我有另一个命令php artisan checks:push从 Redis 数据库获取支票并将其批量插入到raw_checksMySQL 表中。

所以我得到了两个表:monitor_checksraw_checks。第一个表包含监视器最近一次成功的检查及其所有失败的检查。我不会存储每分钟每个节点的每次检查,因为这会导致数十亿条记录,而且对最终用户来说价值不大。

raw_checks表充当核心服务器和所有节点服务器之间的桥梁。

每次检查后我们需要做很多事情:

  1. 重新计算每个节点的正常运行时间
  2. 重新计算每个节点的平均响应时间
  3. 根据节点信息重新计算监视器正常运行时间/响应时间
  4. 如果需要,发送通知

一次获取多张支票并在靠近 MySQL 服务器的服务器上进行所有计算会更加合理。

例如,印度节点与位于德国的MySQL服务器之间的连接相对较慢。

因此目标是使节点尽可能独立。

它们针对 MySQL 连接所做的一切就是:获取需要检查的监视器并存储检查结果。就是这样。

核心服务器的工作原理

替代文本

在核心服务器上,我运行php artisan:checks-pull命令,它的行为类似于守护进程:它有一个无限循环,从raw_checks表中获取检查并计算平均正常运行时间、响应时间等。

除此之外,它还负责对停机通知作业进行排队。

事实上就是这样:我们有更新状态、正常运行时间和响应时间属性的监视器。

更新实时数据

为了更新 Web 应用程序上的监控状态,我使用了PusherLaravel 的广播功能。

因此设置很简单。

  1. php artisan checks:pull从原始表中获取检查,查看监视器是否在线,如果不在线,则触发MonitorOffline事件,并使用 Pusher 进行广播。

  2. Web 应用程序看到 Pushed 的新事件并将监视器标记为离线

总结

总结一下:

  1. 每个节点都有一个 cron 作业,用于获取监控列表并将正常运行时间检查作业放入本地 Redis 队列
  2. Supevrisord 运行的许多线程检查队列并发出 HTTP 请求
  3. 结果存储在Redis中
  4. 然后将存储在本地 Redis 中的一堆支票移至 MySQL 服务器中的原始支票表
  5. 核心服务器获取支票并进行计算。

每个节点都具有相同的机制。现在,有了来自原始表的持续检查流,核心服务器可以执行许多操作,例如计算节点的平均响应时间等等。

如果我想扩展节点数量,我只需克隆节点服务器,做一些小配置,就这样。

哪些地方没有发挥作用

  1. 起初,我把所有检查都存进了数据库,包括失败的和成功的。结果产生了数十亿条记录。但用户大多不需要它。现在,我有一个聚合表,用来存储每小时的正常运行时间和响应时间。

  2. 起初,正常运行时间检查作业自行完成所有逻辑:我没有任何临时/桥接数据库。因此,它连接到 MySQL 服务器,计算新的正常运行时间等等。当监视器数量增加到 100 个左右时,这种方法就立即失效了。因为每个检查作业可能执行 10-20 个查询。10-20 个查询 * 5 个节点 * 100 个监视器。所以,它完全不可扩展。

  3. 使用专用的 Redis 服务器代替raw_checks数据表。由于原始支票表的行为类似于缓存,因此考虑使用 Redis 来实现此目的可能是合理的。但不知何故,我一直丢失支票数据。

我尝试了 Redis 的订阅/发布功能,以及单纯的数据存储功能。最终我放弃了,转而使用了一种我熟悉的机制:MySQL。

另外,我想我在某处读到过,如果我们需要 100% 的信心来存储数据,那么 Redis 并不是最好的解决方案。

为什么花了这么长时间?

由于多种因素。

  1. 我有一份全职工作,所以我只能在晚上工作。
  2. 我以前没有过这样的经历。每个项目都是独一无二的。
  3. 我有一些心理问题,我在这篇文章中描述了这些问题
  4. 我尝试过很多不同的方法,让它在多台显示器的情况下也能正常工作。所以我几乎重复了好几次同样的操作。
  5. 考虑到我也构建了一个 UI 并独自完成了所有工作。
文章来源:https://dev.to/vponamariov/how-i-built-architecture-of-uptime-monitoring-service-54h8
PREV
如何设计几乎所有的 UI 元素。精选 58 篇文章。
NEXT
18张包含表单设计技巧的卡片