solon.cloud.micrometer插件使用指南
- solon是什么
- solon的cloud生态图
- 快速入门
- micrometer指南
- micrometer是什么
- 监控系统 Supported Monitoring Systems
- 注册表 Registry
- 度量 Meters
- 度量名 Naming Meters
- 度量标签 Tag Naming
- 通用标签 Common Tags
- 指标过滤器 MeterFilter
- 聚合速率
- 指标类型
- 计数器 Counters
- 函数式计数器 Function-tracking Counters
- 仪表 Gauges
- 时间仪表 TimeGauge
- Multi-gauge
- 计时器 Timers
- @Timed 计时器注解
- 函数式计时器 Function-tracking Timers
- 暂停检测 Pause Detection
- 内存统计 Memory Footprint Estimation
- Distribution Summaries
- 长任务计时器 Long Task Timers
- Histograms and Percentiles
- solon-micrometer插件使用
- 观测地址
- 如何使用
- 标签定制
- 增加度量器和过滤器
- prometheus
- 安装使用
solon是什么
- Java 全新的生态型应用开发框架:更快、更小、更简单。
相比 spring 启动快 5 ~ 10 倍;qps 高 2~ 3 倍;运行时内存节省 1/3 ~ 1/2;打包可以缩到 1/2 ~ 1/10;同时支持 jdk8, jdk11, jdk17, jdk20, graalvm native image。 - 独特的 IOC/AOP 容器设计。不会因为插件变多而启动变很慢
- Http、WebSocket、Socket 三种信号统一的开发体验(俗称:三源合一)
- Not Servlet,可以适配任何基础通讯框架(最小 0.3m 运行rpc架构)
- 支持 Web、Data、Job、Remoting、Cloud 等任何开发场景
- 兼顾 Handler + Context 和 Listener + Message 两种架构模式
- 强调插件式扩展,可扩展可切换;适应不同的应用场景
- 支持 GraalVm Native Image 打包
- 允许业务插件“热插”、“热拔”、“热管理”
- 采用插件式应用开发的,内部集成了200+主流插件,能胜任企业日常开发,至今发展已有6年时间,已经有很完整的生态体系
- 官网传送门:官网包括了solon的全生态教程,能让不同阶段的道友快速上手
- gitee https://gitee.com/noear/solon
- github https://github.com/noear/solon
solon的cloud生态图
快速入门
- pom.xml
<parent>
<groupId>org.noear</groupId>
<artifactId>solon-parent</artifactId>
<version>${solon.version}</version>
</parent>
<dependencies>
<dependency>
<!-- 引入 Web 快速开发集成包 -->
<groupId>org.noear</groupId>
<artifactId>solon-web</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<!-- 引入打包插件 -->
<groupId>org.noear</groupId>
<artifactId>solon-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 主启动类如下,@Controller加在启动类上,是为了更好的扫描mapping,正常的启动类是@SolonMain
@Controller
public class App {
public static void main(String[] args) {
Solon.start(App.class, args, app -> {
//handler模式
app.get("/hello1", ctx -> ctx.output("Hello world!"));
});
}
@Get
@Socket
@Mapping("/hello2")
public String hello2(String name) {
return String.format("Hello %s!", name);
}
}
- 配置文件
server.port: 8080
solon.app:
group: "demo"
name: "demoapp"
micrometer指南
io.micrometer是一种用于应用程序的度量库。它提供了一组简单且易于使用的API,用于收集和展示应用程序的各种度量指标,如计数器、计时器、分布式摘要和计量。io.micrometer能够与多种监控系统和工具集成,如Prometheus、Graphite、InfluxDB等,以便在应用程序运行时监控和可视化指标数据,并进行性能分析和故障排查。io.micrometer可以用于各种Java和JVM语言开发的应用程序,包括Spring Boot、Dropwizard、Quarkus等。
micrometer是什么
官网传送门
- Vendor-neutral application observability facade
- Micrometer provides a simple facade over the instrumentation clients for the most popular
observability systems, allowing you to instrument your JVM-based
application code without vendor lock-in. Think SLF4J, but for observability.
Micrometer 为最流行的可观察性系统提供了检测客户端的简单外观,允许您检测基于jvm的应用程序代码,而不受供应商的限制, 考虑SLF4J,但要考虑可观察性。
监控系统 Supported Monitoring Systems
Micrometer包含一个带有仪表SPl的核心模块、一组包含各种监控系统实现的模块(每个都称为注册表)和一个测试套件。你需要了解的三个重要特征监控系统:
- 维度(Dimensionality):描述系统是否支持多维度数据模型。
Dimensional | Hierarchical |
---|---|
AppOptics, Atlas, Azure Monitor, Cloudwatch, Datadog, Datadog StatsD, Dynatrace, Elastic, Humio, Influx, KairosDB, New Relic, Prometheus, SignalFx, Sysdig StatsD, Telegraf StatsD, Wavefront | Graphite, Ganglia, JMX, Etsy StatsD |
- 速率聚合(Rate Aggregation):指的是在规定的时间间隔内的一组样本聚合。一种是指标数据发送前在客户端做速率聚合,另一种是直接发送聚合值。
Client-side | Server-side |
---|---|
AppOptics, Atlas, Azure Monitor, Datadog, Dynatrace, Elastic, Graphite, Ganglia, Humio, Influx, JMX, Kairos, New Relic, all StatsD flavors, SignalFx | Prometheus, Wavefront |
- 发布(Publishing):描述的是指标数据的发布方式,一种是客户端定时将数据推送给监控系统,还有一种是监控系统在空闲时间自己调客户端接口拉数据。
Client pushes | Server polls |
---|---|
AppOptics, Atlas, Azure Monitor, Datadog, Dynatrace, Elastic, Graphite, Ganglia, Humio, Influx, JMX, Kairos, New Relic, SignalFx, Wavefront | Prometheus, all StatsD flavors |
从一个监视系统到另一个监视系统,还有其他更小的期望差异,例如它们的基本度量单位(特别是时间)的概念和度量标准的规范命名约定。Micrometer定制您的指标,以满足每个注册表的这些需求。
每一个监控系统模块都对应了不同的依赖,比如
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-appoptics</artifactId>
<version>${micrometer.version}</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${micrometer.version}</version>
</dependency>
注册表 Registry
Meter是收集关于应用程序的一组度量(我们单独称之为度量)的接口。Micrometer中的米是从MeterRegistry中创建并保存的。每个支持的监控系统都有一个MeterRegistry的实现。注册中心的创建方式因实现而异。
Micrometer包含一个SimpleMeterRegistry,它在内存中保存每个仪表的最新值,并且不会将数据导出到任何地方。如果你还没有一个首选的监控系统,你可以通过使用简单的注册表开始玩指标:
MeterRegistry registry = new SimpleMeterRegistry();
在Micrometer中还有其余几个注册表
- CompositeMeterRegistry 符合注册表,可以将多个注册表相关联
CompositeMeterRegistry composite = new CompositeMeterRegistry();
Counter compositeCounter = composite.counter("counter");
compositeCounter.increment(); (1)
SimpleMeterRegistry simple = new SimpleMeterRegistry();
composite.add(simple); (2)
- Global Registry 全局注册表
Micrometer提供了一个静态全局注册表Metrics.globalRegistry和一组基于此注册表生成仪表的静态构建器(请注意,globalRegistry是一个复合注册表):
Metrics.globalRegistry
在本插件中使用的就是全局注册表,如果开发者还想继续开发,可直接引入
度量 Meters
Micrometer支持一组Meter原语,包括Timer, Counter, Gauge, DistributionSummary, LongTaskTimer, FunctionCounter, FunctionTimer和TimeGauge。不同的度量类型会产生不同数量的时间序列度量。例如,虽然有一个表示Gauge的单一度量,但Timer测量计时事件的计数和所有计时事件的总时间。仪表由其名称和尺寸唯一标识。我们可以互换使用“尺寸”和“标签”这两个术语,而Micrometer接口就是“标签”,因为它更短。作为一般规则,应该可以使用名称作为枢轴。维度允许对特定的命名度量进行切片,以便向下钻取和推断数据。这意味着,如果只选择了名称,则可以通过使用其他维度向下钻取并推断所显示的值。
Metrics.gauge(meterName, getMeterTags(inv, anno.tags()), (Number) rst);
度量名 Naming Meters
Micrometer采用一种命名约定,用。(点)字符分隔小写单词。不同的监控系统对命名约定有不同的建议,有些命名约定在系统之间可能不兼容。监控系统的每个Micrometer实现都附带一个命名约定,该约定将小写点表示法名称转换为监控系统推荐的命名约定。此外,这个命名约定实现从度量名称和标记中删除了监视系统不允许的特殊字符。您可以通过实现NamingConvention并在注册表上设置它来覆盖注册表的默认命名约定。
registry.config().namingConvention(myCustomNamingConvention);
registry.counter("http.server.requests");
例如:
-
Prometheus - http_server_requests_duration_seconds
-
Atlas - httpServerRequests
-
Graphite - http.server.requests
-
InfluxDB - http_server_requests
度量标签 Tag Naming
对于 Tag 的命名,建议也采用跟 meter 一致的点号分隔小写单词的方式,这同样有助于将命名风格转换为各个监控系统推荐的命名模式。
registry.counter("counter.counter_name",
"uri", ctx.path(),
"method", ctx.method(),
"class", inv.target().getClass().getTypeName(),
"executable", inv.method().getMethod().getName());
错误示例
registry.counter("calls",
"class", "database",
"db", "users");
registry.counter("calls",
"class", "http",
"uri", "/api/users");
再来看一下上面这种命名方式,此时如果仅仅通过 name 属性calls来查看数据,得到的是包含了 db 访问和 http 调用的所有的指标数据。显然这种数据对于我们分析生产问题来说是毫无意义的,需要进一步选择class标签来细化数据维度。
注意:在tag标签中数量必须是偶数,用于做键对值匹配,如果存在相同的名字,那么只能通过标签来筛选结果
通用标签 Common Tags
common tags 属于 registry 级别的 tag,它会被应用到报告给监控系统的所有 metric 中,这类 tag 通常是系统维度的一些属性,比如 host、instance、region、堆栈信息等等。
附加一个公共标记列表,以应用于报告给监视系统的所有指标。必须是偶数个参数,表示标签的键/值对。
registry.config().commonTags("stack", "prod", "region", "us-east-1");
registry.config().commonTags(Arrays.asList(Tag.of("stack", "prod"), Tag.of("region", "us-east-1"))); // equivalently
common tags 必须在添加任何 meter 之前就被加入到 registry 中。
指标过滤器 MeterFilter
Meter Filter 用于控制meter注册时机、可以发布哪些类型的统计数据,我们可以给每一个 registry 配置过滤器。
过滤器提供以下三个基本功能:
拒绝/接受meter注册。
变更meter的 ID 信息(io.micrometer.core.instrument.Meter.Id)
针对某些类型的meter配置分布统计。
registry.config()
// 多个filter配置按顺序生效
.meterFilter(MeterFilter.ignoreTags("too.much.information"))
.meterFilter(MeterFilter.denyNameStartsWith("jvm"));
// 拒绝/接受Meters
// 用于配置只接受指定形式的meters,或者屏蔽某些meters。
new MeterFilter() {
@Override
public MeterFilterReply accept(Meter.Id id) {
if(id.getName().contains("test")) {
return MeterFilterReply.DENY;
}
return MeterFilterReply.NEUTRAL;
}
}
MeterFilterReply有三种可能的状态:
public enum MeterFilterReply {
// 拒绝meter注册请求,registry将会返回一个该meter的NOOP版本(如NoopCounter、NoopTimer)
DENY,
// 当没有任何过滤器返回DENY时,meter的注册流程继续向前推进
NEUTRAL,
// 表示meter注册成功,无需继续向下流转“询问”其他filter的accept(...)方法
ACCEPT
}
针对Meter的 deny/accept 策略, MeterFilter为我们提供了一些常用的方法:
- accept():接受所有的meter注册,该方法之后的任何 filter 都是无效的。
- accept(Predicate<Meter.Id>):接收满足给定条件的meter注册。
- acceptNameStartsWith(String):接收 name 以指定字符打头的meter注册。
- deny():拒绝所有meter的注册请求,该方法之后的任何 filter 都是无效的。
- denyNameStartsWith(String):拒绝所有 name 以指定字符串打头的meter的注册请求。
- deny(Predicate<Meter.Id>):拒绝满足特定条件的meter的注册请求。
- maximumAllowableMetrics(int):当已注册的meters数量达到允许的注册上限时,拒绝之后的所有注册请求。
- maximumAllowableTags(String meterNamePrefix, String tagKey, int maximumTagValues, MeterFilter onMaxReached):设置一个tags上限,达到这个上限时拒绝之后的注册请求。
- denyUnless(Predicate<Meter.Id>):白名单机制,拒绝不满足给定条件的所有meter的注册请求。
下面是MeterFilter中修改指标的方法
- map(Meter.Id id) : 默认方法,返回当前指标ID
- commonTags(Iterable):为所有指标添加一组公共 tags。通常建议开发者为应用程序名称、host、region 等信息添加公共 tags。
- ignoreTags(String…):用于从所有meter中去除指定的 tag key。比如当我们发现某个 tag 具有过高的基数,并且已经对监控系统构成压力,此时可以在无法立即改变所有检测点的前提下优先采用这种方式来快速减轻系统压力。
- replaceTagValues(String tagKey, Function<String, String> replacement, String… exceptions):替换满足指定条件的所有 tag 值。通过这种方式可以某个 tag 的基数大小。
renameTag(String meterNamePrefix, String fromTagKey, String toTagKey):重命名所有以给定前缀命名的metric的 tag key。 - configure(Meter.Id id, DistributionStatisticConfig config):这只在过滤新的计时器和分布摘要(即那些使用DistributionStatisticConfig的仪表类型)时调用
Metrics.globalRegistry.config().meterFilter(new MeterFilter() {
// 修改id
@Override
public Meter.Id map(Meter.Id id) {
if(id.getName().startsWith("test")) {
return id.withName("extra." + id.getName()).withTag("extra.tag", "value");
}
return id;
}
@Override
public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
if (id.getName().startsWith(prefix)) {
return DistributionStatisticConfig.builder()
// ID名称以指定前缀开头的请求提供指标统计直方图信息
.publishPercentiles(0.9, 0.95)
.build()
.merge(config);
}
return config;
}
});
MeterFilter 部分构建器如下:
- maxExpected(Duration/long): 控制从计时器或摘要发送的百分位数直方图桶的上限。
- minExpected(Duration/long): 控制从计时器或摘要发送的百分位数直方图桶的下界。
聚合速率
Micrometer知道特定的监视系统是希望在发布指标之前在客户端进行速率聚合,还是在服务器上作为查询的一部分进行临时聚合。它根据监视系统期望的样式累积度量并不是所有的测量都被报告或最好地被视为一个速率。例如,度量值和活动任务计数长任务计时器不是速率。
指标类型
计数器 Counters
计数器报告单个指标:计数。Counter接口允许您增加一个固定的数量,该数量必须为正。
Counter counter = Counter
.builder("counter")
.baseUnit("beans") // optional
.description("a description of what this counter does") // optional
.tags("region", "test") // optional
.register(registry);
//计数 + 1
counter.increment();
counter.increment(1.0);
永远不要计算那些你可以用Timer计时或用DistributionSummary总结的事情!Timer和DistributionSummary总是在发布其他度量之外发布事件计数。
函数式计数器 Function-tracking Counters
Micrometer还提供了一种更不常用的计数器模式,可以跟踪单调增加的函数(保持不变或随时间增加但从不减少的函数)。一些监视系统(如Prometheus)将计数器的累积值推送到后端,但其他监视系统则发布计数器在推送间隔内的增量速率。通过采用此模式,您可以让监视系统的Micrometer实现选择是否对计数器进行评级规范化,并且您的计数器在不同类型的监视系统之间保持可移植性。
FunctionCounter counter = FunctionCounter
.builder("counter", state, state -> state.count())
.baseUnit("beans") // optional
.description("a description of what this counter does") // optional
.tags("region", "test") // optional
.register(registry);
counter.count() // 自创建此计数器以来的累计计数。
仪表 Gauges
- 仪表是获取当前值的句柄。测量的典型示例是集合或映射的大小或处于运行状态的线程数。
- 千分尺采取的立场是,量规应该取样,而不是设置,所以没有关于什么可能发生在样品之间的信息。在度量值报告给度量后端时,在度量值上设置的任何中间值都将丢失,因此首先设置这些中间值几乎没有什么价值。把量规想象成“海森堡量规”:一个只有在被观察时才会改变的量规。
- 每一个其他的计量类型都会累积中间计数,直到数据被发送到计量后端。
List<String> list = registry.gauge("listGauge", Collections.emptyList(), new ArrayList<>(), List::size);
List<String> list2 = registry.gaugeCollectionSize("listSize2", Tags.empty(), new ArrayList<>());
Map<String, Integer> map = registry.gaugeMapSize("mapGauge", Tags.empty(), new HashMap<>());
// 创建一个gauge
Metrics.gauge(meterName, getMeterTags(inv, anno.tags()), (Number) rst);
AtomicInteger myGauge = registry.gauge("numberGauge", new AtomicInteger(0));
// 设置一个值
myGauge.set(27);
myGauge.set(11);
时间仪表 TimeGauge
TimeGauge是跟踪时间值的专用度量,该值将被缩放到每个注册中心实现所期望的基本时间单位。TimeGauge可以按照如下方式注册TimeUnit:
AtomicInteger msTimeGauge = new AtomicInteger(4000);
AtomicInteger usTimeGauge = new AtomicInteger(4000);
TimeGauge.builder("my.gauge", msTimeGauge, TimeUnit.MILLISECONDS, AtomicInteger::get).register(registry);
TimeGauge.builder("my.other.gauge", usTimeGauge, TimeUnit.MICROSECONDS, AtomicInteger::get).register(registry);
监控系统显示文本
# HELP my_gauge_seconds
# TYPE my_gauge_seconds gauge
my_gauge_seconds 4.0
# HELP my_other_gauge_seconds
# TYPE my_other_gauge_seconds gauge
my_other_gauge_seconds 0.004
Multi-gauge
千分尺支持最后一种特殊类型的量规,称为多量规,以帮助管理测量不断增长或缩小的标准列表。这个特性允许您从类中选择一组边界良好但稍有变化的标准SQL查询,并为每一行报告一些度量作为度量。下面的示例创建MultiGauge:
// SELECT count(*) from job group by status WHERE job = 'dirty'
MultiGauge statuses = MultiGauge.builder("statuses")
.tag("job", "dirty")
.description("The number of widgets in various statuses")
.baseUnit("widgets")
.register(registry);
...
// run this periodically whenever you re-run your query
statuses.register(
resultSet.stream()
.map(result -> Row.of(Tags.of("status", result.getAsString("status")), result.getAsInt("count")))
.collect(toList())
);
计时器 Timers
计时器用于测量短时间延迟和此类事件的频率。Timer的所有实现至少将总时间和事件计数作为单独的时间序列报告。虽然您可以在其他用例中使用计时器,但请注意不支持负值,并且记录更长的持续时间可能会导致Long的总时间溢出。MAX_VALUE纳秒(292.3年)。
Timer timer = Timer.builder(meterName)
.description(anno.description())
.tags(getMeterTags(inv, anno.tags()))
.publishPercentiles(anno.percentiles())
.register(Metrics.globalRegistry);
//计时
long start = System.currentTimeMillis();
long span = System.currentTimeMillis() - start;
timer.record(span, TimeUnit.MICROSECONDS);
timer.record(() -> span );
timer.recordCallable(() -> span );
Runnable r = timer.wrap(() -> span );
Callable c = timer.wrap(() -> span );
Timer.Sample sample = Timer.start(Metrics.globalRegistry);
sample.stop(registry.timer("my.timer", "response", data));
基本定时器实现(如culativetimer和StepTimer)的最大统计值是时间窗口最大值(TimewlindowMax)。这意味着它的值是一个时间窗口内的最大值。如果时间窗口长度没有记录新的值,则在新时间窗口开始时将最大值重置为0。时间窗口大小是仪表注册表的步长,除非在DistributionStatisticConfig中显式地将过期设置为其他值。时间窗口最大值用于捕获在沉重的资源压力触发延迟并阻止发布指标后的后续间隔内的最大延迟。百分位数也是时间窗口百分位数(时间窗口百分位数直方图)。
@Timed 计时器注解
Micrometer的Spring Boot配置不能识别任意方法上的@Timed。
在solon中支持,替换成@MeterTimer 即可,默认注册到全局
@Get
@Mapping("/hello")
@MeterTimer
public String hello(){
return "hello,world";
}
函数式计时器 Function-tracking Timers
Micrometer还提供了一个不太常用的计时器模式,它跟踪两个单调递增的函数(一个函数保持不变或随着时间的推移而增加,但从不减少):计数函数和总时间函数。一些监视系统,如Prometheus,将计数器的累积值(在本例中应用于计数和总时间函数)推送到后端,但其他监视系统则发布计数器在推送间隔内的增量速率。通过采用此模式,您可以让监视系统的Micrometer实现选择是否对计时器进行评级规范化,并且您的计时器在不同类型的监视系统之间保持可移植性。
/**
* 跟踪count和totalTime单调递增函数的计时器。
* @param name Name of the timer being registered.
* @param tags Sequence of dimensions for breaking down the name.
* @param obj State object used to compute a value.
* @param countFunction Function that produces a monotonically increasing counter
* value from the state object.
* @param totalTimeFunction Function that produces a monotonically increasing
* total time value from the state object.
* @param totalTimeFunctionUnit The base unit of time produced by the total time
* function.
* @param <T> The type of the state object from which the function values are
* extracted.
* @return A new or existing function timer.
*/
public <T> FunctionTimer timer(String name, Iterable<Tag> tags, T obj, ToLongFunction<T> countFunction,
ToDoubleFunction<T> totalTimeFunction, TimeUnit totalTimeFunctionUnit) {
return FunctionTimer.builder(name, obj, countFunction, totalTimeFunction, totalTimeFunctionUnit)
.tags(tags)
.register(MeterRegistry.this);
}
使用案例
FunctionTimer functionTimer = FunctionTimer.builder(meterName, Integer.parseInt("1"),
r->r.intValue(),
c->c.byteValue(),
TimeUnit.NANOSECONDS
).register(Metrics.globalRegistry);
暂停检测 Pause Detection
Micrometer使用LatencyUtils包来补偿协调遗漏——由系统和VM暂停引起的额外延迟,这会使延迟统计向下倾斜。
分布统计信息(如百分位数和SLO计数)受到暂停检测器实现的影响,该实现在这里或那里添加额外的延迟以补偿暂停。
Micrometer支持两种暂停检测器实现:基于时钟漂移的检测器和无op检测器。在Micrometer 1.0.10/1.1.4/1.2.0之前,时钟漂移检测器默认配置为报告尽可能精确的指标,而无需进一步配置。从1.0.10/1.1.4/1.2.0开始,默认配置no-op检测器,但可以配置时钟漂移检测器,如下例所示。
基于时钟漂移的检测器具有可配置的睡眠间隔和暂停阈值。CPU消耗与sleepInterval成反比,与暂停检测精度成反比。这两个值的默认值都是100ms,这是一个合理的默认值,可以很好地检测长暂停事件,同时消耗的CPU时间可以忽略不计。
registry.config().pauseDetector(new ClockDriftPauseDetector(sleepInterval, pauseThreshold));
registry.config().pauseDetector(new NoPauseDetector());
内存统计 Memory Footprint Estimation
计时器是消耗内存最多的仪表,它们的总占用可能会有很大的变化,这取决于您选择的选项。下表的内存消耗是基于各种功能的使用。这些数字假设没有标签,环形缓冲区长度为3。添加标记会在一定程度上增加总数,就像增加缓冲区长度一样。根据注册中心实现的不同,总存储空间也会有所不同。
- R =环缓冲区长度。在所有示例中,我们都假定默认值为3。R用Timer设置。Timer.Builder#distributionStatisticBufferLength.
- B =总直方图桶。可以是SLO边界或百分位直方图桶。默认情况下,计时器被限制为最小期望值1ms和最大期望值30秒,在适用的情况下,为百分位数直方图产生66个桶。
- I =暂停补偿的区间估计器。1.7 kb。
- M =时间衰减最大值。104字节。
- Fb=固定边界直方图。8bBR.Pp =百分位精度。缺省值是1。一般在[0,3]的范围内。Pp设置定时器。Timer.Builder#percentilePrecisio
- Hdr(Pp) =高动态范围直方图。
-
- 当Pp=0时:1.9kb*R+0.8kb
-
- 当Pp= 1:38 kb*R+1.1kb时
-
- 当Pp= 2时:18.2kb*R+4.7kb
暂停检测 | 客户端百分位数 | 直方图和/或slo | 公式 | 例子 |
---|---|---|---|---|
Yes | No | No | I + M | ~1.8kb |
Yes | No | Yes | I + M + Fb | For default percentile histogram, ~7.7kb |
Yes | Yes | Yes | I + M + Hdr(Pp) | For the addition of a 0.95 percentile with defaults otherwise, ~14.3kb |
No | No | No | M | ~0.1kb |
No | No | Yes | M + Fb | For default percentile histogram, ~6kb |
No | Yes | Yes | M + Hdr(Pp) | For the addition of a 0.95 percentile with defaults otherwise, ~12.6kb |
特别是对于Prometheus, R总是等于1,无论您如何尝试通过Timer配置它。构建器。这种特殊情况的存在是因为Prometheus期望累积的直方图数据永远不会滚动。
Distribution Summaries
分布摘要跟踪事件的分布。它在结构上类似于计时器,但记录的值不代表时间单位。例如,您可以使用分发摘要来度量到达服务器的请求的有效负载大小。
DistributionSummary summary = DistributionSummary
.builder("response.size")
.description("a description of what this summary does")
.baseUnit("bytes")
.tags("region", "test")
.scale(100) // 比例因子,乘以
.register(registry);
//以指定的金额更新汇总中保存的统计数据。
//参数:数量-正在测量的事件的数量。例如,如果来自服务器的响应的字节大小。如果数量小于0,则该值将被删除。
meter.record(1.0);
在插件中已经实现了@MeterSummary,在solon管理中使用即可,将函数返回值必须是Number的实现或者子类
对于基本的DistributionSummary实现,如CumulativeDistributionSummary和StepDistributionSummary,其最大值(命名为max)是一个时间窗口最大值(TimewindowMax)。这意味着它的值是一个时间窗口内的最大值。如果没有记录新的时间窗口长度,则在新的时间窗口开始时将最大值重置为0。时间窗口大小是仪表注册表的步长,除非在DistributionStatisticConfig中显式地将过期设置为另一个值。时间窗口最大值用于捕获在沉重的资源压力触发延迟并阻止发布指标后的后续间隔内的最大延迟。百分位数也是时间窗口百分位数(TimewindowPercentileHistogram)。
长任务计时器 Long Task Timers
长任务计时器是一种特殊类型的计时器,它允许您在正在测量的事件仍在运行时测量时间。普通定时器只记录任务完成后的持续时间。
长任务计时器至少发布以下统计信息:
- 活动任务数
- 活动任务的总持续时间
- 活动任务的最大持续时间
考虑一个从数据存储刷新元数据的后台进程。例如,Edda缓存AWS资源,如实例、卷、自动伸缩组等。正常情况下,几分钟内即可刷新所有数据。如果AWS服务出现问题,则可能需要更长的时间。可以使用长任务计时器来跟踪刷新元数据的活动时间。
LongTaskTimer longTaskTimer = LongTaskTimer.builder(getMeterName(inv, anno))
.tags(getMeterTags(inv, anno.tags()))
.publishPercentiles(anno.percentiles())
.description(anno.description())
.register(Metrics.globalRegistry);
//计时
return longTaskTimer.record(()->{
try {
return inv.invoke();
} catch (Throwable e) {
throw new RuntimeException(e);
}
});
例如,在Spring应用程序中,使用@Scheduled实现这种长时间运行的流程是很常见的。Micrometer提供了一个特殊的@Timed注释,用于使用长任务计时器来检测这些进程:
@Timed(value = "aws.scrape", longTask = true)
@Scheduled(fixedDelay = 360000)
void scrapeResources() {
// find instances, volumes, auto-scaling groups, etc...
}
在solon中,我们直接使用即可
@Mapping("/longtime")
@MeterLongTimer("demo.longtime")
public String MeterLongTimer() throws InterruptedException {
Thread.sleep(5000);
return "MeterSummary";
}
Histograms and Percentiles
计时器和分布摘要支持收集数据以观察其百分位数分布。查看百分位数有两种主要方法:百分位直方图:Micrometer将值累积到底层直方图中,并将一组预先确定的桶发送到监控系统。监控系统的查询语言负责计算该直方图的百分位数。目前,只有Prometheus、Atlas和Wavefront分别通过histogram_quantile、:percentile和hs()支持基于直方图的百分位数近似值。如果您的目标是Prometheus、Atlas或Wavefront,则更喜欢这种方法,因为您可以跨维度聚合直方图(通过将一组维度上的桶的值相加),并从直方图中获得可聚合的百分位数。客户端百分位数:Micrometer计算每个仪表ID(一组名称和标签)的百分位数近近值,并将百分位数值发送到监控系统。这并不像百分位数直方图那样灵活,因为不可能在标签之间汇总百分位数近似值。然而,对于不支持基于直方图的服务器端百分位数计算的监控系统,它提供了对百分位数分布的某种程度的了解。
Timer.builder("my.timer")
.publishPercentiles(0.5, 0.95) // median and 95th percentile (1)
.publishPercentileHistogram() // (2)
.serviceLevelObjectives(Duration.ofMillis(100)) // (3)
.minimumExpectedValue(Duration.ofMillis(1)) // (4)
.maximumExpectedValue(Duration.ofSeconds(10))
- publishPercentiles:用于发布应用程序中计算的百分位数值。这些值在各个维度上是不可聚合的。
- publishPercentileffistogran:用于在Prometheus(通过使用histogram_quantile)、Atlas(通过使用:percentile)和Wavefront(通过使用hs())中发布适合计算可聚合(跨维度)百分位数近似值的直方图。对于Prometheus和Atlas,结果直方图中的桶是由Micrometer基于一个生成器预先设置的,该生成器由Netflix根据经验确定,可以在大多数真实世界的计时器和分布摘要上产生合理的误差界限。默认情况下,生成器产生276个桶,但是Micrometer只包括mininumExpectedValue和maximumExpectedValue设置范围内的桶。默认情况下,Micrometer夹住计时器的范围为1毫秒到1分钟,每个计时器维度产生73个直方图桶。publishPercentilehistogran对不支持聚合百分位数近似值的系统没有影响。这些系统没有提供直方图。
- serviceLevelObjectives:用于发布由slo定义的桶的累积直方图。当在支持可聚合百分位数的监视系统上与publishPercentileHistogram一起使用时,此设置将向发布的直方图添加额外的桶。当在不支持可聚合百分位数的系统上使用时,此设置将导致仅使用这些桶发布直方图。
- minimumExpectedValue / maximumExpectedValue:控制publishPercentileHistogram发送的桶的数量,并控制底层HdrHistogram结构的准确性和内存占用。
solon-micrometer插件使用
分布式扩展插件。在 solon.cloud 插件的基础上,添加基于 micrometer 度量的支持。此插件类似 slf4j,使用时需要添加具体的记录方案。该插件内置了prometheus,可以继续添加其他的检测器
v2.4.2 后支持
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon.cloud.metrics</artifactId>
</dependency>
观测地址
GET /metrics/registrys 查看所有注册器
{
"_registrys": ["xxx.xxx.Name1", "xxx.xxx.Name2"]
}
GET /metrics/meters 查看所有度量
{
"_meters": ["name1", "name2"]
}
GET /metrics/meter/{meterName} 查看某个度量详情
{
"name": "name1",
"description": "",
"baseUnit": "",
"measurements": {},
"tags": {}
}
GET /metrics/prometheus 查看 prometheus 监控系统的输入数据
# HELP demo_longtime_seconds_max
# TYPE demo_longtime_seconds_max gauge
demo_longtime_seconds_max{solon_app_group="group",solon_app_name="name",} 0.0
# HELP demo_longtime_seconds
# TYPE demo_longtime_seconds summary
demo_longtime_seconds_active_count{solon_app_group="group",solon_app_name="name",} 0.0
demo_longtime_seconds_duration_sum{solon_app_group="group",solon_app_name="name",} 0.0
# HELP demo_MeterSummary
# TYPE demo_MeterSummary summary
demo_MeterSummary_count{solon_app_group="group",solon_app_name="name",} 2.0
demo_MeterSummary_sum{solon_app_group="group",solon_app_name="name",} 2.0
# HELP demo_MeterSummary_max
# TYPE demo_MeterSummary_max gauge
demo_MeterSummary_max{solon_app_group="group",solon_app_name="name",} 1.0
# HELP demo_hello_seconds
# TYPE demo_hello_seconds summary
demo_hello_seconds_count{solon_app_group="group",solon_app_name="name",} 2.0
demo_hello_seconds_sum{solon_app_group="group",solon_app_name="name",} 0.0
# HELP demo_hello_seconds_max
# TYPE demo_hello_seconds_max gauge
demo_hello_seconds_max{solon_app_group="group",solon_app_name="name",} 0.0
# HELP demo_test__total
# TYPE demo_test__total counter
demo_test__total{solon_app_group="group",solon_app_name="name",} 2.0
如何使用
@Controller
public class DemoController {
@Mapping("/counter")
@MeterCounter("demo.counter")
public String counter() {
return "counter";
}
@Mapping("/gauge")
@MeterGauge("demo.gauge")
public Long gauge() {
return System.currentTimeMillis() % 100;
}
@Mapping("/summary")
@MeterSummary(value = "demo.summary", maxValue = 88, minValue = 1, percentiles = {10, 20, 50})
public Long summary() {
return System.currentTimeMillis() % 100;
}
@Mapping("/timer")
@MeterTimer("demo.timer")
public String timer() {
return "timer";
}
}
标签定制
全局自动添加的 commandTags
solon.app.name //应用名
solon.app.group //应用组
solon.app.nameSpace //应用命名空间
使用注解时自动添加的 tags
uri //请求 uri
method //请求 method
class //执行类
executable //执行函数
增加度量器和过滤器
插件是直接使用全局注册器,我们在项目中直接引入即可
LongTaskTimer longTaskTimer = LongTaskTimer.builder(getMeterName(inv, anno))
.tags(getMeterTags(inv, anno.tags()))
.publishPercentiles(anno.percentiles())
.description(anno.description())
.register(Metrics.globalRegistry);
Metrics.globalRegistry.config().meterFilter(
new MeterFilter() {
@Override
public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
if(id.getName().startsWith("myservice")) {
return DistributionStatisticConfig.builder()
.percentiles(0.95)
.build()
.merge(config);
}
return config;
}
});
prometheus
Prometheus是一个开源系统监控和警报工具包,最初由SoundCloud构建。自2012年成立以来,许多公司和组织都采用了Prometheus,并且该项目拥有非常活跃的开发人员和用户社区。它现在是一个独立的开源项目,独立于任何公司进行维护。为了强调这一点,并澄清项目的治理结构,Prometheus于2016年加入了云原生计算基金会,成为继Kubernetes之后的第二个托管项目。
Prometheus收集并存储其指标作为时间序列数据,即指标信息与记录时间戳一起存储,以及称为标签的可选键值对
官网地址 https://prometheus.io
下载地址 https://prometheus.io/download/
特性普罗米修斯的主要特点是:
- 一个多维数据模型,
- 其时间序列数据由度量名称和键/值对标识PromQL
- 一种灵活的查询语言
- 可以利用这个维度不依赖分布式存储
- 单个服务器节点是自治的时间序列收集通过HTTP上的拉模型进行
- 推送时间序列通过中间网关支持通过服务发现或静态配置发现目标多种模式的绘图和仪表板支持
安装使用
下文以windows为例
https://github.com/prometheus/prometheus/releases/download/v2.46.0/prometheus-2.46.0.windows-amd64.zip
下载并解压
tar xvfz prometheus-*.tar.gz
cd prometheus-*
查看帮助
.\prometheus.exe --help
usage: prometheus.exe [<flags>]
The Prometheus monitoring server
Flags:
-h, --[no-]help Show context-sensitive help (also try
--help-long and --help-man).
--[no-]version Show application version.
--config.file="prometheus.yml"
Prometheus configuration file path.
--web.listen-address="0.0.0.0:9090"
Address to listen on for UI, API, and
telemetry.
--web.config.file="" [EXPERIMENTAL] Path to configuration file that
can enable TLS or authentication.
--web.read-timeout=5m Maximum duration before timing out read of the
request, and closing idle connections.
--web.max-connections=512 Maximum number of simultaneous connections.
--web.external-url=<URL> The URL under which Prometheus is externally
reachable (for example, if Prometheus is served
......
配置prometheus 的配置是YAML。
Prometheus下载文件附带了一个名为Prometheus的文件中的样例配置。这是一个开始的好地方。
为了使示例文件更简洁,我们去掉了示例文件中的大部分注释(注释是带有#前缀的行)。
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
# - "first.rules"
# - "second.rules"
scrape_configs:
- job_name: 'micrometer-example'
scrape_interval: 5s
metrics_path: '/metrics/prometheus' # solon中的默认路径
static_configs:
- targets: ['127.0.0.1:8080'] # 项目地址,支持集群
labels:
instance: 'example1' #实例名字
下面的 prometheus 的数据集
# HELP demo_longtime_seconds_max
# TYPE demo_longtime_seconds_max gauge
demo_longtime_seconds_max{solon_app_group="group",solon_app_name="name",} 0.0
# HELP demo_longtime_seconds
# TYPE demo_longtime_seconds summary
demo_longtime_seconds_active_count{solon_app_group="group",solon_app_name="name",} 0.0
demo_longtime_seconds_duration_sum{solon_app_group="group",solon_app_name="name",} 0.0
# HELP demo_MeterSummary
# TYPE demo_MeterSummary summary
demo_MeterSummary_count{solon_app_group="group",solon_app_name="name",} 2.0
demo_MeterSummary_sum{solon_app_group="group",solon_app_name="name",} 2.0
# HELP demo_MeterSummary_max
# TYPE demo_MeterSummary_max gauge
demo_MeterSummary_max{solon_app_group="group",solon_app_name="name",} 1.0
# HELP demo_hello_seconds
# TYPE demo_hello_seconds summary
demo_hello_seconds_count{solon_app_group="group",solon_app_name="name",} 2.0
demo_hello_seconds_sum{solon_app_group="group",solon_app_name="name",} 0.0
# HELP demo_hello_seconds_max
# TYPE demo_hello_seconds_max gauge
demo_hello_seconds_max{solon_app_group="group",solon_app_name="name",} 0.0
# HELP demo_test__total
# TYPE demo_test__total counter
demo_test__total{solon_app_group="group",solon_app_name="name",} 2.0
运行 prometheus
.\prometheus.exe
查看注册实例
根据 度量器名字来查看不同的图像
本文参考:
solon官网
micrometer官网