title: “1.0.0-m1版本中的监控” lang: cn ref: metrics permalink: /cn/docs/users/metrics-in-1.0.0-m1/ excerpt: “1.0.0-m1版本中的监控” last_modified_at: 2017-12-30T10:01:43-04:00 redirect_from:
{% include toc %} 微服务框架从0.5.0版本开始支持监控功能Metrics,1.0.0-m1版本正式发布,请通过查看用户手册和Release Note获取更多信息,我们也会继续追加新特性新功能,欢迎订阅ServiceComb邮件列表(dev-subscribe@servicecomb.apache.org)参与讨论。
将系统微服务化是技术潮流和趋势,但是它解决了很多问题的同时也带来了新的问题。
这是传统单体系统架构图,对运维人员友好,但是对开发人员不友好,系统维护升级困难。
这是微服务化后的系统架构图,经过功能切分,开发人员获得了架构独立、快速迭代等诸多微服务带来的好处,但是运维人员却需要维护海量的微服务实例,所以如果不进行性能监控,当系统出现故障或用户体验下降时,就无法快速定位问题,也无法制定策略(例如弹性伸缩)来预防。
在0.5.0版本的实现介绍0.5.0版本中的监控中,存在一些问题:
因此,从0.5.0版本升级到1.0.0-m1版本,我们进行了一次全面的重构,重构后的Metrics将分为如下两个个模块
Module名 | 描述 |
---|---|
foundation-metrics | Metrics机制层模块,提供Metrics基础能力 |
metrics-core | Metrics核心模块,引入后即启用Metrics数据收集功能 |
metrics-integration | 包含Metrics与其它三方系统集成 |
它们的依赖关系如下图所示:
1.0.0-m1版本不再从Hystrix获取调用的TPS和Latency,避免了不配置Java Chassis Bizkeeper Handler就不会输出这两项数据的问题;使用foundation-common中的EventBus作为事件总线,EventBus初始化的时候会通过SPI(Service Provider Interface)的机制将所有的EventListener注册进来,已实现的EventListener包括:
事件监听处理类名 | 功能 |
---|---|
InvocationStartedEventListener | 处理Consumer调用或Producer接收开始时触发的InvocationStartedEvent |
InvocationStartExecutionEventListener | 处理Producer从队列中取出调用开始处理时触发的InvocationStartExecutionEvent |
InvocationFinishedEventListener | 处理Consumer调用返回或Producer处理完毕触发的InvocationFinishedEvent |
特别说明,Java Chassis的Reactor框架基于Vertx,在同步调用模式下,微服务Producer端收到Invocation后,并不会马上同步处理请求,而是将它放入一个处理队列中,Invocation在队列中的时间称为LifeTimeInQueue,队列的长度称为waitInQueue,这是衡量微服务压力的两个重要指标,可以参考操作系统磁盘读写队列的概念;Consumer端并不会有队列,因此永远不会触发InvocationStartExecutionEvent。
事件触发的代码分布在Java Chassis的RestInvocation、HighwayServerInvoke和InvokerUtils中,如果微服务没有启用Metrics,EventBus中就不会注册Metrics事件监听处理器,因此对性能的影响微乎其微。
Netflix Servo具有性能极高的计数器(Monitor),我们使用了五种:
Monitor名 | 描述 |
---|---|
BasicCounter | 基本累加计数器(永续累加) |
StepCounter | 周期累加计数器(以前曾经称为ResettableCounter) |
BasicTimer | 时间计数器 |
BasicGauge | 基本计量器 |
MaxGauge | 周期最大值计数器 |
依赖的Servo版本为0.10.1
Metrics有很多种分类方式,在技术实现上我们偏向以取值方式区分为两种:
提示:Servo已经被Netflix标记为DEPRECATED,我们将在1.0.0-m2中使用Netflix spectator替换,将不需要设置周期
Java Chassis Metrics内置两种类型的Metric输出:
输出ID格式为:jvm(statistic=gauge,name={name}) name包括:
name | 描述 |
---|---|
cpuLoad | CPU使用率 |
cpuRunningThreads | 线程数 |
heapInit,heapMax,heapCommit,heapUsed | 内存heap使用情况 |
nonHeapInit,nonHeapMax,nonHeapCommit,nonHeapUsed | 内存nonHeap使用情况 |
输出ID格式为:servicecomb.invocation(operation={operationName},role={role},stage={stage},statistic={statistic},status={status},unit={unit}) 标签含义及值如下:
Tag名 | 描述 | 值 |
---|---|---|
operationName | Operation全名 | MicroserviceQualifiedName |
role | Consumer端统计还是Producer端统计 | consume,producer |
stage | 统计的阶段 | queue(在队列中,仅producer),execution(执行阶段,仅producer),total(整体) |
statistic | 统计项 | tps,count(总调用次数),max,waitInQueue(在队列中等待数,仅producer),latency |
status | 调用结果状态值 | 200, 404等等 |
unit | 如果是时延统计,单位 | MILLISECONDS,SECONDS等等 |
请在microservice.yaml中添加如下配置项:
APPLICATION_ID: demo service_description: name: demoService version: 0.0.1 servicecomb: metrics: #时间窗间隔,单位毫秒,默认为5000(5秒) window_time: 5000
为了降低Metrics理解和使用难度,我们暂时不支持多周期
只需要添加metrics-core依赖即可:
<dependency> <groupId>org.apache.servicecomb</groupId> <artifactId>metrics-core</artifactId> <version>1.0.0-m1</version> </dependency>
配置好Metrics后,你可以通过如下两种方式获取Metrics数据:
当微服务启动后,metrics-core会自动以Springmvc的方式发布服务:
@RestSchema(schemaId = "metricsEndpoint") @RequestMapping(path = "/metrics") public class MetricsPublisher { @ApiResponses({ @ApiResponse(code = 400, response = String.class, message = "illegal request content"), }) @RequestMapping(path = "/", method = RequestMethod.GET) @CrossOrigin public Map<String, Double> measure() { return MonitorManager.getInstance().measure(); } }
因此,如果你在microservice.yaml中配置了rest provider,例如:
servicecomb: service: registry: address: http://127.0.0.1:30100 rest: address: 0.0.0.0:8080
你就可以通过http://localhost:8080/metrics 直接获取到数据,打开浏览器输入此URL就可以看到返回结果。
从上面的代码可以看到,数据提供的入口是org.apache.servicecomb.metrics.core.MonitorManager,因此如果你希望自己开发数据发布程序,只需要获取它即可。
MonitorManager manager = MonitorManager.getInstance(); Map<String, Double> metrics = manager.measure();
提示:Servo已经被Netflix标记为DEPRECATED,我们将在1.0.0-m2中使用Netflix spectator替换,获取数据的接口会有调整
Metrics数据将以Map<String, Double>的形式输出,为了能够方便用户获取指定Metric的值,提供了org.apache.servicecomb.foundation.metrics.publish.MetricsLoader工具类:
//模拟MonitorManager.getInstance().measure()获取所有的Metrics值 Map<String, Double> metrics = new HashMap<>(); metrics.put("X(K1=1,K2=2,K3=3)", 100.0); metrics.put("X(K1=1,K2=20,K3=30)", 200.0); metrics.put("X(K1=2,K2=200,K3=300)", 300.0); metrics.put("X(K1=2,K2=2000,K3=3000)", 400.0); metrics.put("Y(K1=1,K2=2,K3=3)", 500.0); metrics.put("Y(K1=10,K2=20,K3=30)", 600.0); metrics.put("Y(K1=100,K2=200,K3=300)", 700.0); metrics.put("Y(K1=1000,K2=2000,K3=3000)", 800.0); //创建一个MetricsLoader加载所有的Metrics值 MetricsLoader loader = new MetricsLoader(metrics); //获取name为X的所有Metrics并且按K1,K2两个Tag层次分组 MetricNode node = loader.getMetricTree("X","K1","K2"); //获取K1=1且K2=20的所有Metrics,因为node是按K1和K2的层次分组的 node.getChildrenNode("1").getChildrenNode("20").getMetrics(); //从层次结构中通过Tag匹配获取Metric的值 node.getChildrenNode("1").getChildrenNode("20").getFirstMatchMetricValue("K3","30");
demo/perf/PerfMetricsFilePublisher.java提供了MetricsLoader更详细的使用示例
Java Chassis Metrics支持自定义Metrics扩展,MonitorManager包含一组获取各类Monitor的方法:
方法名 | 描述 |
---|---|
getCounter | 获取一个计数器类的Monitor |
getMaxGauge | 获取一个最大值统计Monitor |
getGauge | 获取基本计量Monitor |
getTimer | 获取一个时间计数器类的Monitor |
以处理订单这个场景为例:
public class OrderController { private final Counter orderCount; private final Counter orderTps; private final Timer averageLatency; private final MaxGauge maxLatency; OrderController() { MonitorManager manager = MonitorManager.getInstance(); //"商品名","levis jeans"与"型号","512" 是两个自定义Tag的name和value,支持定义多Tag this.orderCount = manager.getCounter("订单数量", "商品名", "levis jeans", "型号", "512"); this.orderTps = manager.getCounter(StepCounter::new, "生成订单", "统计项", "事务每秒"); this.averageLatency = manager.getTimer("生成订单", "统计项", "平均生成时间", "单位", "MILLISECONDS"); this.maxLatency = manager.getMaxGauge("生成订单", "统计项", "最大生成时间", "单位", "MILLISECONDS"); } public void makeOrder() { long startTime = System.nanoTime(); //处理订单逻辑 //... //处理完毕 long totalTime = System.nanoTime() - startTime; //增加订单数量 this.orderCount.increment(); //更新Tps this.orderTps.increment(); //记录订单生成处理时间 this.averageLatency.record(totalTime, TimeUnit.NANOSECONDS); //记录最大订单生成时间,因为惯用毫秒作为最终输出,因此我们转换一下单位 this.maxLatency.update(TimeUnit.NANOSECONDS.toMillis(totalTime)); } }
注意事项:
1.通过MonitorManager获取Monitor传递name和tag数组,最终输出的ID是它们连接后的字符串,所以请保持唯一性,上面的例子输出的Metrics为:
Map<String,Double> metrics = MonitorManager.getInstance().measure(); //metrics的keySet()将包含: // 订单数量(商品名=levis jeans,型号=512) // 生成订单(统计项=事务每秒) // 生成订单(统计项=平均生成时间,单位=MILLISECONDS) // 生成订单(统计项=最大生成时间,单位=MILLISECONDS)
2.MonitorManager获取Monitor的方法均为获取或创建,因此多次传递相同的name和tag数组返回的是同一个计数器:
Counter counter1 = MonitorManager.getInstance().getCounter("订单数量", "商品名", "levis jeans", "型号", "512"); Counter counter2 = MonitorManager.getInstance().getCounter("订单数量", "商品名", "levis jeans", "型号", "512"); counter1.increment(); counter2.increment(); Assert.assertEquals(2,counter1.getValue()); Assert.assertEquals(2,counter2.getValue()); Assert.assertEquals(2.0,MonitorManager.getInstance().measure().get("订单数量(商品名=levis jeans,型号=512)"),0);
获取Monitor的方法性能较低,请在初始化阶段一次获取所需的Monitor,然后将它们缓存起来,请参照前面OrderController的做法。
提示:Servo已经被Netflix标记为DEPRECATED,我们将在1.0.0-m2中使用Netflix spectator替换,扩展自定义Metrics的方式会有调整
我们已经开发完成了两个使用场景可以作为参考: