我们如何联系普罗米修斯

我不得不以某种方式处理API的指标,就像往常一样(没有时间?!)在以后添加-这很困难并且尚未实现-这意味着是时候实现它了。 在网上徘徊了一段时间之后,在我看来,最流行的监视系统是普罗米修斯。


使用Prometheus,我们可以监视各种计算机资源,例如:内存,处理器,磁盘,网络负载。 对我们而言,计算对API方法的调用次数或衡量其执行时间可能也很重要,因为系统上的负载越大,其停机时间就越昂贵。 普罗米修斯在这里为我们提供帮助。 在我看来,本文提供了理解Prometheus的工作以及向API添加一组度量标准的要点。 因此,我们以最平庸的方式开始,并进行少量描述。


Prometheus是用Go语言编写并由SoundCloud开发的开源系统和时间序列DBMS。 它具有官方文档和对以下语言的支持:Go,Java或Scala,Python,Ruby。 非官方支持其他语言,例如:C#,C ++,C,Bash,用于Nginx的Lua,用于Tarantool的Lua等。整个列表位于Prometheus官方网站上。


所有Prometheus服务都可以在Docker Hub或Quay.io上以Docker映像的形式获得。


Prometheus由docker run -p 9090:9090 prom/prometheus启动,该docker run -p 9090:9090 prom/prometheus以默认配置启动并设置端口localhost:9090 ,之后,Prometheus UI将在localhost:9090可用。


Prometheus是一个监视系统,其中包括用于使用HTTP协议配置对应用程序(端点)的监视的各种工具。 连接到Prometheus时,HTTP API不支持“基本身份验证”。 如果要使用基本身份验证连接到Prometheus,建议将Prometheus与反向代理服务器结合使用,并在代理级别使用身份验证。 您可以对Prometheus使用任何反向代理。


普罗米修斯的主要组成部分:


  • 收集指标并将其保存到数据库并进行清理的服务器;
  • 用于在API中收集指标的软件包;
  • Pushgateway-用于从无法使用Pull请求的应用程序接收指标的组件;
  • 导出器-安装在目标计算机上的用于从第三方应用程序和服务导出指标的工具;
  • AlertManager-通知管理器(警报),警报在配置文件中定义,并由一组度量标准规则设置。
    如果在操作过程中遵守规则,则会触发警报,并通过电子邮件,Slack等将警报发送给指定的收件人。

Prometheus使用的对象称为通过Pushgateway或通过Exporters从目标接收的度量。


收集指标时,使用了几种传输指标的方法:


  • Prometheus通过“拉”请求从目标请求度量,“拉”请求的设置在每个作业的scrape_config部分的配置文件中指定。
    当系统收集数据时,您可以控制收集的频率并创建数据收集的几种配置,以为不同的对象选择不同的频率。
  • 导出程序允许您从各种对象收集指标,例如:数据库(MongoDB,SQL等),消息代理(RabbitMQ,EMQ,NSQ等),HTTP负载平衡器等;
  • Pushgateway。 当应用程序无法提供直接向Prometheus提供指标的功能时,可以在必要时使用它。 或者使用无法使用Prometheus拉取请求的批处理作业时。

因此,所有接收到的指标将由Prometheus存储在带有时间戳的数据库中。


构型


使用YAML格式提供的命令行标志和配置文件配置Prometheus。 命令行标志允许您配置不可变的参数,例如:路径,存储在磁盘和内存中的数据量等。 该配置文件允许您配置与作业相关的所有内容,并设置已加载的规则Yaml文件。 所有内容都写入全局配置文件中,它使您可以为每个人设置常规设置,并分别突出显示不同配置部分的设置。 Prometheus轮询的设置在scrape_configs部分的配置文件中进行配置。


Prometheus可以在操作过程中重新加载配置文件,如果新配置无效,则将不会应用它。 如果设置了--web.enable-lifecycle标志,则通过发送SIGHUP Prometheus命令或向/-/reload发送HTTP POST请求来触发重新启动配置文件。 它还将重新加载所有已配置的规则文件。


使用什么类型的数据


Prometheus存储自定义的多维数据模型,并对称为PromQL的多维数据使用查询语言。 Prometheus以时间序列的形式存储数据;它支持几种存储选项:


  • 本地磁盘存储:每2个小时,将压缩已缓存在内存中的数据并将其存储在磁盘上。 默认情况下,工作目录中使用./data目录保存压缩文件;
  • 远程存储库:Prometheus支持通过协议缓冲区适配器与第三方存储库(例如:Kafka,PostgreSQL,Amazon S3等)集成。

存储的时间序列由度量和元数据以键值对的形式确定,尽管在必要时可以不使用度量​​的名称,并且度量本身仅由元数据组成。 时间序列可以正式定义为<度量名称> {<元数据>}。 关键是<metric name> {<metadata>}-我们正在测量的 ,该是带有float64类型的数字的实际值(Prometheus仅支持此类型)。 键说明包含元数据(标签),也由键值对描述:<标签名称> =“ <标签值>”,<标签名称> =“ <标签值>”,...


存储指标时,使用以下数据类型:


  • 计数器 -计算一段时间内的金额。 这种类型的指标只能增加(不能使用负值)或重置值。
    例如,它可能适用于计算每分钟的请求数或每天的错误数,已发送/已接收的网络数据包的数量等。
  • 量表 -存储随时间推移可能减少或增加的值。
    量表未显示一段时间内指标的发展。 使用量规时,随着时间的流逝,您可能会丢失不规则的指标变化。
  • 直方图 -保存多个时间序列:所有观测值的总和; 观察到的事件数;
    累积计数器(存储桶)-在标签中表示为le="<upper inclusive bound>"
    在具有自定义上限(存储桶)的区域中收集值。
  • 摘要 -保存几个时间序列:所有观测值的总和; 观察到的事件数;
    观察到的事件的流量φ分位数(0≤φ≤1)-在标签中表示为quantile="<φ>"

数据如何保存?


Prometheus建议为正在运行的应用程序“提供” 2/3的RAM。
为了将数据存储在内存中,Prometheus使用了称为块的文件;每个度量都有其自己的文件。 除了最后一个写入数据的块文件之外,所有块文件都是不可变的。 新数据将保存在块中,并且每隔2小时,后台流会将这些数据合并并写入磁盘。 每个两小时的时间段由一个目录组成,该目录包含一个或多个块文件,该文件包含该时间段的所有时间序列样本,还包括一个元数据文件和一个索引文件(该索引文件对块文件中时间序列的度量标准和标签进行索引)。 如果Prometheus在一个小时内没有将数据写入块,则它将被保存到磁盘,并且将创建一个新的块来写入数据。 Prometheus中的最大数据保留期为〜21天。


因为 内存大小是固定的,因此系统的写入和读取性能将受到此内存量的限制。 PTSDB内存量由最小时间段,收集时间段和时间度量标准的数量确定。


Prometheus还具有WAL机制以防止数据丢失。


预写日志(WAL)以日志文件的形式在永久介质上序列化存储的操作。 发生故障时,可以使用WAL文件通过从日志还原将数据库恢复到其一致状态。


日志文件以128 MB的段存储在wal目录中。 这些文件包含尚未压缩的原始数据,因此它们比常规片段文件大得多。


Prometheus将至少存储3个日志文件,但是流量较高的服务器可以看到三个以上的WAL文件,因为它需要存储至少两个小时的原始数据。


使用WAL的结果是大大减少了对磁盘的写请求数量,因为 仅日志文件需要写入磁盘,而不是操作后已更改的所有数据。 日志文件是按顺序写入的,因此同步日志的成本要比将片段与数据写入的成本低得多。


Prometheus保存了定期断点,默认情况下,通过压缩过去的时间段并将其保存到磁盘,每2小时添加一次。


所有断点都与checkpoint.ddd存储在同一目录中,其中ddd是单调递增的数字。 因此,从故障中恢复时,它可以从断点目录中恢复断点并指示顺序(.ddd)。
通过编写WAL日志,您可以返回到数据日志可用的任何检查点。


实践中发生了什么?


当添加到项目(.Net Framework)中时,我们使用Prometheus.Client.3.0.2包来收集度量。 为了收集度量,已在项目中添加了必要的方法和类以存储度量,直到Prometheus收到它们为止。


最初定义了IMetricsService接口,该接口包含用于测量方法工作时间的计时器方法:


 public interface IMetricsService { Stopwatch StartTimer(); void StopTimer(Stopwatch timer, string controllerName, string actionName, string methodName = "POST"); } 

我们添加了MetricsService类,该类实现了IMetricsService接口并临时存储指标。


 public class MetricsService : IMetricsService { private static Histogram _histogram; static MetricsService() { _histogram = CreateHistogram(); } public Stopwatch StartTimer() { try { var timer = new Stopwatch(); timer.Start(); return timer; } catch (Exception exception) { Logger.Error(exception); } return null; } public void StopTimer(Stopwatch timer, string controllerName, string actionName, string methodName = "POST") { try { if (timer == null) { throw new ArgumentException($"{nameof(timer)} can't be null."); } timer.Stop(); _histogram .WithLabels(controllerName, actionName, methodName) .Observe(timer.ElapsedMilliseconds, DateTimeOffset.UtcNow); } catch (Exception exception) { Logger.Error(exception); } } public static List<string> GetAllLabels() { var metricsList = new List<string>(); try { foreach (var keyValuePair in _histogram.Labelled) { var controllerName = keyValuePair.Key.Labels[0].Value; var actionName = keyValuePair.Key.Labels[1].Value; var methodName = keyValuePair.Key.Labels[2].Value; var requestDurationSum = keyValuePair.Value.Value.Sum; var requestCount = keyValuePair.Value.Value.Count; metricsList.Add($"http_request_duration_widget_sum{{controller={controllerName},action={actionName},method={methodName}}} {requestDurationSum}"); metricsList.Add($"http_request_duration_widget_count{{controller={controllerName},action={actionName},method={methodName}}} {requestCount}"); } _histogram = CreateHistogram(); } catch (Exception exception) { Logger.Error(exception); } return metricsList; } private static Histogram CreateHistogram() { var newMetrics = Metrics .WithCustomRegistry(new CollectorRegistry()) .CreateHistogram(name: "http_request_duration_web_api", help: "Histogram metrics of Web.Api", includeTimestamp: true, labelNames: new[] { "controller", "action", "method" }); var oldValue = _histogram; for (var i = 0; i < 10; i++) { var oldValue = Interlocked.Exchange<Histogram>(ref oldValue, newMetrics); if (oldValue != null) { return oldValue; } } return null; } } 

现在,我们可以使用我们的类将计划收集的指标存储在Application_BeginRequest,Application_Error,Application_EndRequest方法中。 在Global.cs类中,我们向上述方法添加了一组指标。


 private IMetricsService _metricsService; protected virtual void Application_BeginRequest(object sender, EventArgs e) { var context = new HttpContextWrapper(HttpContext.Current); var metricServiceTimer = _metricsService.StartTimer(); context.Items.Add("metricsService", _metricsService); context.Items.Add("metricServiceTimer", metricServiceTimer); } protected virtual void Application_EndRequest(object sender, EventArgs e) { WriteMetrics(new HttpContextWrapper(HttpContext.Current)); } protected void Application_Error(object sender, EventArgs e) { WriteMetrics(new HttpContextWrapper(HttpContext.Current)); } private void WriteMetrics(HttpContextBase context) { try { _metricsService = context.Items["metricsService"] as IMetricsService; if (_metricsService != null) { var timer = context.Items["metricServiceTimer"] as Stopwatch; string controllerName = null; string actionName = null; var rd = RouteTable.Routes.GetRouteData(context); if (rd != null) { controllerName = rd.GetRequiredString("controller"); actionName = rd.GetRequiredString("action"); } _metricsService.StopTimer(timer, controllerName, actionName, context.Request.HttpMethod); } } catch (Exception exception) { Logger.Error("Can't write metrics.", exception); } } 

添加一个新的控制器,它将作为将我们的API指标发送给Prometheus的参考点:


 public class MetricsController : Controller { [HttpGet] public string[] GetAllMetrics() { try { var metrics = MetricsService.GetAllLabels(); return metrics.ToArray(); } catch (Exception exception) { Logger.Error(exception); } return new string[] { }; } } 

最后一步是将Prometheus配置配置为在scrape_configs部分中收集指标,此后我们可以在Prometheus或Grafana UI中看到已收集的指标。


我们对Prometheus感兴趣的关键功能:


多维数据模型:指标和标签。
灵活的PromQL查询语言。 在同一个查询运算符中,我们可以使用诸如乘法,加法,串联等操作; 可以使用多个指标执行。
使用pull方法收集基于HTTP的数据。
与通过Pushgateway的推送方法兼容。
可以通过导出器从其他应用程序收集指标。
提供防止数据丢失的机制。
支持数据的各种图形表示。

Source: https://habr.com/ru/post/zh-CN475600/


All Articles