利用AppMetrics对Web进行监控教程
利用AppMetrics对Web进行监控教程
一、基础准备
1. 安装依赖
这里可以通过nuget或使用命令行进行安装,具体需要安装的类库如下(注意版本):
Install-Package App.Metrics.AspNetCore.Mvc -Version 2.0.0
由于我们需要兼容Prometheus进行监控,所以我们还需要安装对应的格式化库,具体如下:
Install-Package App.Metrics.Formatters.Prometheus -Version 2.0.0
以上就是需要的类库,接下来我们开始进行其他初始化部分。
2. 初始配置
为了保证其能够正常工作,我们需要根据不同的环境设定对应的appsettings.json
文件从而让度量指标可以根据不同的环境进行输出,这里考虑到实际情况尚未存在不同的配置可能性故统一配置即可,打开appsettings.json
输入下配置项:
{
"MetricsOptions": {
"DefaultContextLabel": "MetricsApplication",
"Enabled": true
},
"MetricsWebTrackingOptions": {
"ApdexTrackingEnabled": true,
"ApdexTSeconds": 0.3,
"IgnoredHttpStatusCodes": [ 404 ],
"IgnoreRoutesRegexPatterns": [],
"OAuth2TrackingEnabled": false
},
"MetricEndpointsOptions": {
"MetricsEndpointEnabled": true,
"MetricsTextEndpointEnabled": true,
"EnvironmentInfoEndpointEnabled": true
}
}
参数DefaultContextLabel
可以设定为我们期望其他名称,这里建议采用项目的简写名称,保证项目之间不存在冲突即可。参数ApdexTSeconds
用于设定应用的响应能力标准,其采用了当前流行的Apdex标准,这里使用者可以根据自身应用的实际情况调整对应的参数,其他相关参数建议默认即可。
3. 启用度量指标
因为我们的数据需要符合Promethues格式,所以后续教程我们会替换默认的格式采用符合的格式。首先我们需要Program.cs
里输入以下内容:
public static IWebHost BuildWebHost(string[] args)
{
Metrics = AppMetrics.CreateDefaultBuilder()
.OutputMetrics.AsPrometheusPlainText()
.OutputMetrics.AsPrometheusProtobuf()
.Build();
return WebHost.CreateDefaultBuilder(args)
.ConfigureMetrics(Metrics)
.UseMetrics(options =>
{
options.EndpointOptions = endpointsOptions =>
{
endpointsOptions.MetricsTextEndpointOutputFormatter = Metrics.OutputMetricsFormatters.OfType<MetricsPrometheusTextOutputFormatter>().First();
endpointsOptions.MetricsEndpointOutputFormatter = Metrics.OutputMetricsFormatters.OfType<MetricsPrometheusProtobufOutputFormatter>().First();
};
})
.UseStartup<Startup>()
.Build();
}
其中为了能够支持其他格式,我们需要手动实例化Metrics
对象完成相关初始化然后将其注入到asp.net core中,其中相关格式的代码主要是由以下这几部分组成:
OutputMetrics.AsPrometheusPlainText()
OutputMetrics.AsPrometheusProtobuf()
endpointsOptions.MetricsTextEndpointOutputFormatter = Metrics.OutputMetricsFormatters.OfType<MetricsPrometheusTextOutputFormatter>().First();
endpointsOptions.MetricsEndpointOutputFormatter = Metrics.OutputMetricsFormatters.OfType<MetricsPrometheusProtobufOutputFormatter>().First();
完成以上操作后,我们最后还需要进行其他配置,打开Startup.cs
文件增加如下内容:
services.AddMvc().AddMetrics();
至此我们就完成了基本的初始化了,通过启动程序并访问localhost:5000/metrics-text
即可查看最终的输出内容。
二、自定义指标
由于其内部已经默认提供了若干的指标,但是并不能符合实际业务的需求故以下将对常用的度量指标类型以及用法进行介绍,这里这里大家通过注入IMetrics
接口对象即可访问,所以下面这部分代码不在阐述。
1. 仪表盘(Gauge)
最常见的类型,主要是用于直接反应当前的指标情况,比如我们常见的CPU和内存基本都是使用这种方式进行显示的,可以直观的看到当前的实际的状态情况。对于所有的指标我们都需要定义对应的Options,当然这可以完成携程静态变量供应用程序全局使用。
比如下面我们定义一个表示当前发生错误次数的指标:
GaugeOptions Errors = new GaugeOptions()
{
Name = "Errors"
};
完成指标的定义后,我们就可以在需要使用的地方进行指标数据的修改,比如下面我们将错误数量设置为10:
metrics.Measure.Gauge.SetValue(MyMetricsRegistry.Errors, 10);
这样我们就完成了指标的设定,但是有时候我们还想却分具体的Error是那个层面发起的,这个时候我们需要使用到Tag了,下面我们在设定值的同时设定指标,当然也可以在新建指标的时候通过Tags
变量,并且通用于其他所有指标:
var tags = new MetricTags("fromt", "db");
metrics.Measure.Gauge.SetValue(MyMetricsRegistry.Errors, tags, 10);
至此我们就完成了一个基本的指标,下面我们继续其他类型指标。
2. 计数值(Counter)
对于HTTP类型的网站来说,存在非常多的数量需要统计记录,所以计数值此时就特别适合这类情况,比如我们需要统计请求数量就可以利用这类指标类型,下面我们就以请求数来定义这个指标:
var requestCounter = new CounterOptions()
{
Name = "httpRequest",
MeasurementUnit = Unit.Calls
};
以上我们定义了一个计数指标,其中我们可以看到我们这里使用了一个新变量MeasurementUnit
主要是用于定义指标单位的,当然这个只是辅助信息会一同输出到结果,下面我们需要进行增加和减少,考虑到大多数情况都是减1和增1的情况:
metrics.Measure.Counter.Increment(requestCounter);
实际情况可能我们都是统计请求但是期望还能单独统计特定接口的请求,这个时候我们在原本调用方式基础上增加额外的参数:
metrics.Measure.Counter.Increment(requestCounter, "api");
如果嫌每次增长1比较慢,我们通过其函数的重载形式填写我们希望增长的具体值。
3. 计量值(Meter)
有点类似于计数值但是相比来说,它可以提供更加丰富的信息,比如每1、5、15分钟的增长率等,所以对于一些需要通过增长率观察的数据特别时候,这里我们以请求的反应状态码进行记录来体现其用途:
var httpStatusMeter = new MeterOptions()
{
Name = "Http Status",
MeasurementUnit = Unit.Calls
};
以上我们完成了一个指标的定义,下面我们开始使用其并且定义不同的状态的码的发生情况,具体如下:
metrics.Measure.Meter.Mark(httpStatusMeter, "200");
metrics.Measure.Meter.Mark(httpStatusMeter, "500");
metrics.Measure.Meter.Mark(httpStatusMeter, "401");
当然如果希望增加的数量自定控制也可以使用其提供的重载形式进行。
4. 柱状图(Histogram)
顾名思义,主要反应数据的分布情况,所以这里不在重复阐述,大家对于这种数据表现形式还是比较了解的,所以下面就直接以实际代码的实列进行介绍,便于大家的理解:
var postAndPutRequestSize = new HistogramOptions()
{
Name = "Web Request Post & Put Size",
MeasurementUnit = Unit.Bytes
};
以上我们定义一个体现Post和Put请求的数据尺寸的指标,下面我们利用随机数来进行数据的模拟对其进行数据填充,便于显示数据:
var rnd = new Random();
foreach (var i in Enumerable.Range(0, 50))
{
var t = rnd.Next(0, 10);
metrics.Measure.Histogram.Update(postAndPutRequestSize, t);
}
5. 时间线(Timer)
对应指标的监控闭然少不了对于时间的记录,特别对于HTTP来说,直接影响到用户的体验就是响应时间,素以我们也需要时刻关于这类指标的变化情况及时做出反应,下面我们就以数据库的响应时间的情况作为指标进行监控:
TimerOptions DatabaseTimer = new TimerOptions()
{
Name = "Database Timer",
MeasurementUnit = Unit.Items,
DurationUnit = TimeUnit.Milliseconds,
RateUnit = TimeUnit.Milliseconds
};
上面我们通过特别的属性指定了改指标记录时间的单位,下面我们使用其指标进行数据的记录:
using(metrics.Measure.Timer.Time(DatabaseTimer))
{
//to do sonmething
}
我们可以看到为了方便的记录请求的时间,我们使用using
进行涵括,并将需要记录耗时的请求操作放入其中,在请求完成操作后就可以正确的记录其需要的时间。
6. apdex
采用了一种标准的性能指标计算方式,用法类似与上述,这里仅仅列举用法:
ApdexOptions SampleApdex = new ApdexOptions
{
Name = "Sample Apdex"
};
using(metrics.Measure.Apdex.Track(SampleApdex))
{
Thread.Sleep(100);
}
三、高级指标
1. 平均响应
很多时候我们仅仅依靠一个指标很难完成一个实际的需求,是所以我们就需要将多个指标进行组合进行,比如我们期望得到请求次数,同时还有请求的总时间和平均响应时间等,为此我们可以特殊的指标将多个指标进行组合,具体操作如下:
var cacheHitRatioGauge = new GaugeOptions
{
Name = "Cache Gauge",
MeasurementUnit = Unit.Calls
};
var cacheHitsMeter = new MeterOptions
{
Name = "Cache Hits Meter",
MeasurementUnit = Unit.Calls
};
var databaseQueryTimer = new TimerOptions
{
Name = "Database Query Timer",
MeasurementUnit = Unit.Calls,
DurationUnit = TimeUnit.Milliseconds,
RateUnit = TimeUnit.Milliseconds
};
var cacheHits = metrics.Provider.Meter.Instance(cacheHitsMeter);
var calls = metrics.Provider.Timer.Instance(databaseQueryTimer);
var cacheHit = new Random().Next(0, 2) == 0;
using(calls.NewContext())
{
if (cacheHit)
{
cacheHits.Mark(5);
}
Thread.Sleep(cacheHit ? 10 : 100);
}
metrics.Measure.Gauge.SetValue(cacheHitRatioGauge, () => new HitRatioGauge(cacheHits, calls, m => m.OneMinuteRate));
四、利用Promethues和Grafana进行监控
1. 环境准备
这里需要使用到Prometheus
和Grafana
,为了避免版本导致的区别这里提供了对应百度云的下载地址,大家可以自行进行下载。
Prometheus对应提取码为2b1r
Grafana对应提取码为mjym
完成以上下载后需要解压到对应文件夹下即可。
2. 配置服务
首先我们需要针对Prometheus
进行配置,我们打开prometheus.yml
文件新增基于AppMetrics
的监控指标。
- job_name: 'appweb'
scrape_interval: 5s
metrics_path: '/metrics-text'
static_configs:
- targets: ['localhost:5000']
完成之后我们可以先打开采集让其在后台持续采集,后面我们需要针对AppMetrics
暴露的数据进行调整。
3. 应用指标输出
通过实际的测试发现基于2.0.0
版本的Prometheus存在问题,因为指标类型被大写了,导致Prometheus
无法正确读取,所以我们需要将源码复制出来进行操作,这里直接给出了对应的源码文件,
主要的工作就是将AsciiFormatter.cs
中的HELP
和TYPE
进行了小写而已,对应文件如下。
PS:考虑到很多基于2.0的所以这里保留了基于HTTP的文本实现方式发布了一个对应的版本库:
Install-Package Sino.Metrics.Formatters.Prometheus -Version 0.1.2
- AsciiFormatter.cs
internal static class AsciiFormatter
{
public static void Format(Stream destination, IEnumerable<MetricFamily> metrics)
{
var metricFamilys = metrics.ToArray();
using (var streamWriter = new StreamWriter(destination, Encoding.UTF8))
{
streamWriter.NewLine = "\n";
foreach (var metricFamily in metricFamilys)
{
WriteFamily(streamWriter, metricFamily);
}
}
}
internal static string Format(IEnumerable<MetricFamily> metrics, NewLineFormat newLine)
{
var newLineChar = GetNewLineChar(newLine);
var metricFamilys = metrics.ToArray();
var s = new StringBuilder();
foreach (var metricFamily in metricFamilys)
{
s.Append(WriteFamily(metricFamily, newLineChar));
}
return s.ToString();
}
private static void WriteFamily(StreamWriter streamWriter, MetricFamily metricFamily)
{
streamWriter.WriteLine("# HELP {0} {1}", metricFamily.name, metricFamily.help.ToLower());
streamWriter.WriteLine("# TYPE {0} {1}", metricFamily.name, metricFamily.type.ToString().ToLower());
foreach (var metric in metricFamily.metric)
{
WriteMetric(streamWriter, metricFamily, metric);
}
}
private static string WriteFamily(MetricFamily metricFamily, string newLine)
{
var s = new StringBuilder();
s.Append(string.Format("# HELP {0} {1}", metricFamily.name, metricFamily.help.ToLower()), newLine);
s.Append(string.Format("# TYPE {0} {1}", metricFamily.name, metricFamily.type.ToString().ToLower()), newLine);
foreach (var metric in metricFamily.metric)
{
s.Append(WriteMetric(metricFamily, metric, newLine), newLine);
}
return s.ToString();
}
private static void WriteMetric(StreamWriter streamWriter, MetricFamily family, Metric metric)
{
var familyName = family.name;
if (metric.gauge != null)
{
streamWriter.WriteLine(SimpleValue(familyName, metric.gauge.value, metric.label));
}
else if (metric.counter != null)
{
streamWriter.WriteLine(SimpleValue(familyName, metric.counter.value, metric.label));
}
else if (metric.summary != null)
{
streamWriter.WriteLine(SimpleValue(familyName, metric.summary.sample_sum, metric.label, "_sum"));
streamWriter.WriteLine(SimpleValue(familyName, metric.summary.sample_count, metric.label, "_count"));
foreach (var quantileValuePair in metric.summary.quantile)
{
var quantile = double.IsPositiveInfinity(quantileValuePair.quantile)
? "+Inf"
: quantileValuePair.quantile.ToString(CultureInfo.InvariantCulture);
streamWriter.WriteLine(
SimpleValue(
familyName,
quantileValuePair.value,
metric.label.Concat(new[] { new LabelPair { name = "quantile", value = quantile } })));
}
}
else if (metric.histogram != null)
{
streamWriter.WriteLine(SimpleValue(familyName, metric.histogram.sample_sum, metric.label, "_sum"));
streamWriter.WriteLine(SimpleValue(familyName, metric.histogram.sample_count, metric.label, "_count"));
foreach (var bucket in metric.histogram.bucket)
{
var value = double.IsPositiveInfinity(bucket.upper_bound) ? "+Inf" : bucket.upper_bound.ToString(CultureInfo.InvariantCulture);
streamWriter.WriteLine(
SimpleValue(
familyName,
bucket.cumulative_count,
metric.label.Concat(new[] { new LabelPair { name = "le", value = value } }),
"_bucket"));
}
}
else
{
// not supported
}
}
private static string WriteMetric(MetricFamily family, Metric metric, string newLine)
{
var s = new StringBuilder();
var familyName = family.name;
if (metric.gauge != null)
{
s.Append(SimpleValue(familyName, metric.gauge.value, metric.label), newLine);
}
else if (metric.counter != null)
{
s.Append(SimpleValue(familyName, metric.counter.value, metric.label), newLine);
}
else if (metric.summary != null)
{
s.Append(SimpleValue(familyName, metric.summary.sample_sum, metric.label, "_sum"), newLine);
s.Append(SimpleValue(familyName, metric.summary.sample_count, metric.label, "_count"), newLine);
foreach (var quantileValuePair in metric.summary.quantile)
{
var quantile = double.IsPositiveInfinity(quantileValuePair.quantile)
? "+Inf"
: quantileValuePair.quantile.ToString(CultureInfo.InvariantCulture);
s.Append(
SimpleValue(
familyName,
quantileValuePair.value,
metric.label.Concat(new[] { new LabelPair { name = "quantile", value = quantile } })), newLine);
}
}
else if (metric.histogram != null)
{
s.Append(SimpleValue(familyName, metric.histogram.sample_sum, metric.label, "_sum"), newLine);
s.Append(SimpleValue(familyName, metric.histogram.sample_count, metric.label, "_count"), newLine);
foreach (var bucket in metric.histogram.bucket)
{
var value = double.IsPositiveInfinity(bucket.upper_bound) ? "+Inf" : bucket.upper_bound.ToString(CultureInfo.InvariantCulture);
s.Append(
SimpleValue(
familyName,
bucket.cumulative_count,
metric.label.Concat(new[] { new LabelPair { name = "le", value = value } }),
"_bucket"), newLine);
}
}
else
{
// not supported
}
return s.ToString();
}
private static string WithLabels(string familyName, IEnumerable<LabelPair> labels)
{
var labelPairs = labels as LabelPair[] ?? labels.ToArray();
if (labelPairs.Length == 0)
{
return familyName;
}
return string.Format("{0}{{{1}}}", familyName, string.Join(",", labelPairs.Select(l => string.Format("{0}=\"{1}\"", l.name, l.value))));
}
private static string SimpleValue(string family, double value, IEnumerable<LabelPair> labels, string namePostfix = null)
{
return string.Format("{0} {1}", WithLabels(family + (namePostfix ?? string.Empty), labels), value.ToString(CultureInfo.InvariantCulture));
}
private static string GetNewLineChar(NewLineFormat newLine)
{
switch (newLine)
{
case NewLineFormat.Auto:
return Environment.NewLine;
case NewLineFormat.Windows:
return "\r\n";
case NewLineFormat.Unix:
case NewLineFormat.Default:
return "\n";
default:
throw new ArgumentOutOfRangeException(nameof(newLine), newLine, null);
}
}
private static void Append(this StringBuilder sb, string line, string newLineChar)
{
sb.Append(line + newLineChar);
}
}
- MetricsPrometheusTextOutputFormatter.cs
public class MetricsPrometheusTextOutputFormatter : IMetricsOutputFormatter
{
private readonly MetricsPrometheusOptions _options;
public MetricsPrometheusTextOutputFormatter()
{
_options = new MetricsPrometheusOptions();
}
public MetricsPrometheusTextOutputFormatter(MetricsPrometheusOptions options) { _options = options ?? throw new ArgumentNullException(nameof(options)); }
/// <inheritdoc/>
public MetricsMediaTypeValue MediaType => new MetricsMediaTypeValue("text", "vnd.appmetrics.metrics.prometheus", "v1", "plain");
/// <inheritdoc/>
public async Task WriteAsync(
Stream output,
MetricsDataValueSource metricsData,
CancellationToken cancellationToken = default(CancellationToken))
{
if (output == null)
{
throw new ArgumentNullException(nameof(output));
}
using (var streamWriter = new StreamWriter(output))
{
await streamWriter.WriteAsync(AsciiFormatter.Format(metricsData.GetPrometheusMetricsSnapshot(_options.MetricNameFormatter), _options.NewLineFormat));
}
}
}
新建好以上两个文件后我们接着需要修改Program.cs
文件,具体内容如下:
public static IWebHost BuildWebHost(string[] args)
{
Metrics = AppMetrics.CreateDefaultBuilder()
.OutputMetrics.AsPrometheusPlainText()
.Build();
return WebHost.CreateDefaultBuilder(args)
.ConfigureMetrics(Metrics)
.UseMetrics(options =>
{
options.EndpointOptions = endpointsOptions =>
{
endpointsOptions.MetricsTextEndpointOutputFormatter = new MetricsPrometheusTextOutputFormatter();
};
})
.UseStartup<Startup>()
.Build();
}
完成以上操作后我们可以启用应用,此时可以看到不断用请求到/metrics-text
表示已经开始采集指标了。
4. 指标可视化
此时我们打开Grafana
文件夹,通过其中的bin
目录下的grafana-server.exe
启动服务,然后访问localhost:3000
利用初始账户密码进行登录(admin/admin)。
进入后添加Prometheus
数据源。由于AppMetrics已经提供了对应的看板所以我们可以通过ID2204
直接导入,并选择正确的数据源就可以看到最终的效果了。
利用AppMetrics对Web进行监控教程的更多相关文章
- Sentry Web 前端监控 - 最佳实践(官方教程)
系列 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentry-CLI - 30 秒上手 Source Maps Sentry For ...
- zabbix利用自带的模板监控mysql数据库
zabbix利用自带的模板监控mysql数据库 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 有些东西你不会的时候觉得它特别难,但是当你去做的时候就发现如此的简单~zabbix功能 ...
- RESTful Web 服务:教程
RESTful Web 服务:教程 随着 REST 成为大多数 Web 和 Mobile 应用的默认选择,势必要对它的基本原理有所了解. 在它提出十多年后的今天,REST 已经成为最重要的 Web ...
- Web攻防系列教程之文件上传攻防解析(转载)
Web攻防系列教程之文件上传攻防解析: 文件上传是WEB应用很常见的一种功能,本身是一项正常的业务需求,不存在什么问题.但如果在上传时没有对文件进行正确处理,则很可能会发生安全问题.本文将对文件上传的 ...
- 全面解读Python Web开发框架Django,利用Django构建web应用及其部署
全面解读Python Web开发框架Django Django是一个开源的Web应用框架,由Python写成.采用MVC的软件设计模式,主要目标是使得开发复杂的.数据库驱动的网站变得简单.Django ...
- 基于Web的监控系统的开发进行分布式和现代生产(外文翻译)
摘要 近年来,Web技术发展迅速.尤其是网络浏览器增强了其功能因为JavaScript,CSS3和HTML5的改进.因此,功能越来越丰富的基于Web的软件解决方案功能范围可用.通过使用响应式网页设计( ...
- 要web开发精品教程吗?免费无广告一百期连讲的那种-逐浪CMS前端开发100期入门教程全面开放
要web开发精品教程吗?免费无广告一百期连讲的那种-逐浪CMS前端开发100期入门教程全面开放 大师主讲 经验难得 由逐浪CMS首席架构师发哥老师,亲自主理讲解. 历时一年精心打造, 汇聚了互联网诞生 ...
- 利用Swashbuckle生成Web API Help Pages
利用Swashbuckle生成Web API Help Pages 这系列文章是参考了.NET Core文档和源码,可能有人要问,直接看官方的英文文档不就可以了吗,为什么还要写这些文章呢? 原因如下: ...
- [Python] 利用Django进行Web开发系列(一)
1 写在前面 在没有接触互联网这个行业的时候,我就一直很好奇网站是怎么构建的.现在虽然从事互联网相关的工作,但是也一直没有接触过Web开发之类的东西,但是兴趣终归还是要有的,而且是需要自己动手去实践的 ...
随机推荐
- Redis事务、持久化、发布订阅
一.Redis事物 1. 概念 Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证: 事务是一个单独的隔离操作:事务中的所有命令都会序列化.按顺序地执行.事务在执行的过程中,不会被其他 ...
- 20191121-10 Scrum立会报告+燃尽图 06
此作业要求参见:https://edu.cnblogs.com/campus/nenu/2019fall/homework/10070 一:组名: 组长组 组长:杨天宇 组员:魏新 罗杨美慧 王歆 ...
- Java迭代器源码解析
private class Itr implements Iterator<E> { int cursor; // 调用next方法返回的元素的索引 int lastRet = -1; / ...
- Q: 字符串的修改
题目描述 怎么样,前面的题还可以吧~ 依旧是字符串处理,设A和B是两个字符串.我们要用最少的字符操作次数,将字符串A转换为字符串B.这里所说的字符操作共有三种: 1. 删除一个字符: 2. 插入一个字 ...
- 运维必会之MySQL篇
第一章 SQL语句 语言分类 1)DDL(data definition language)数据定义语言(create.alter.drop)管理基础数据例如:库.表 #<==运维要熟练, ...
- 聊聊密码学中的DES算法
用心分享,共同成长 没有什么比你每天进步一点点更实在了 本文已经收录至我的github,欢迎大家踊跃star 和 issues. https://github.com/midou-tech/artic ...
- Spring Boot2.X整合消息中间件RabbitMQ原理简浅探析
目录 1.简单概述RabbitMQ重要作用 2.简单概述RabbitMQ重要概念 3.Spring Boot整合RabbitMQ 前言 RabbitMQ是一个消息队列,主要是用来实现应用程序的异步和解 ...
- vnpy源码阅读学习(3):学习vnpy的界面的实现
学习vnpy的界面的实现 通过简单的学习了PyQt5的一些代码以后,我们基本上可以理解PyQt的一些用法,下面让我们来先研究下vnpy的UI部分的代码. 首先回到上一节看到的run.py(/vnpy/ ...
- CTO说|非容器化应用怎么玩多云?Kubernetes不管我们管啊
Kubernetes已经成为容器编排系统的事实标准,是现在主流的跨云容器化应用操作系统. 但是,Kubernetes的目标并不是容器本身,而是承载其上的应用,本质上是为了解决(容器化)应用上云这个难题 ...
- LR Java脚本编写方法
之前在某一家银行也接触过java写的性能接口脚本,最近因项目,也需编写java接口性能测试脚本,脑袋一下懵逼了,有点不知道从何入手.随后上网查了相关资料,自己又稍微总结了一下,与大家共同分享哈~ 首先 ...