meerkat 是用于服务监控以及服务降级基础组件,主要为了解决调用外部接口的时候进行成功率,响应时间,QPS指标的监控,同时在成功率降低到预设的阈值如下的时候自动切断外部接口的调用,外部接口成功率恢复后自动恢复请求。本文将对使用方式以及进阶特性进行介绍。java
项目主页: https://github.com/ChanningBJ...git
在咱们的Java服务中,常常会调用外部的一些接口进行数据的获取操做,当这些外部接口的成功率比较低的时候会直接影响到服务自己的成功率,所以咱们添加了对外部接口调用的成功率和响应时间监控,这样能够在形成大量用户影响以前预先发现并解决问题。同时,对于接口中的非关键数据,咱们采起了更具成功率判断进行触发熔断的方式,当成功率降低到预约的阀值如下的时候自动中止对这个外部接口的访问以便保证关键数据可以正常提供,当成功率恢复之后自动恢复请求。github
<dependency> <groupId>com.github.channingbj</groupId> <artifactId>meerkat</artifactId> <version>1.2</version> </dependency>
假设咱们的服务中须要从HTTP接口查询一个节目的播放次数,为了防止这个HTTP接口大量超时影响咱们自身服务的质量,能够定义一个查询Command:apache
public class GetPlayCountCommand extends FusingCommand<Long> { private final Long videoID; public GetPlayCountCommand(Long videoID) { this.videoID = videoID; } protected Optional<Long> run() { Long result = 0l; // 调用HTTP接口获取视频的播放次数信息 // 若是调用失败,返回 null 或者抛出异常,会将此次操做记录为失败 // 若是ID非法,返回 Optional.absent(),会将此次操做记录为成功 return Optional.fromNullable(result); } }
执行查询:segmentfault
//获取视频ID为123的视频的播放次数 GetPlayCountCommand command = new GetPlayCountCommand(123l); Long result = command.execute(); // 执行查询操做,若是执行失败或者处于熔断状态,返回 null
在服务初始化的时候须要对监控上报进行设置。下面的例子中开启了监控数据向日志文件的打印服务器
MeterCenter.INSTANCE .enableReporter(new EnablingLogReporter("org.apache.log4j.RollingFileAppender")) .init();
统计结果会以熔断命令类名为进行分组。例如前面咱们定义的 GetPlayCountCommand 类,package name 是 com.test,那么在日志中的输出将会是这个样子:app
type=GAUGE, name=com.test.GetPlayCountCommand.normal-rate, value=0.0 type=GAUGE, name=com.test.GetPlayCountCommand.success-rate, value=61.0 type=TIMER, name=com.test.GetPlayCountCommand.time, count=25866500, min=0.0, max=0.001, mean=3.963926781047921E-5, stddev=1.951102156677818E-4, median=0.0, p75=0.0, p95=0.0, p98=0.001, p99=0.001, p999=0.001, mean_rate=649806.0831335272, m1=1665370.7316699813, m5=2315813.300713087, m15=2446572.324069477, rate_unit=events/second, duration_unit=milliseconds
监控项 | 含义 |
---|---|
[classname].success-rate | 成功率 |
[classname].time.m1 | QPS |
[classname].time.mean | 平均响应时间 |
[classname].normal-rate | 过去1分钟内处于正常访问(非熔断)的时间比例 |
若是不想使用熔断功能,只是想监控Java方法调用的耗时和成功率,能够直接使用 OperationMeter 进行实现,只须要在函数调用的先后添加开始和结束的调用便可:maven
//建立一个操做的计数器 OperationMeter meter = MeterCenter.INSTANCE.getOrCreateMeter(OperationMeterTest.class, OperationMeter.class); //模拟成功率60% for(int k=0; k<100; k++){ Timer.Context context = meter.startOperation(); if(k%10<6){ meter.endOperation(context, OperationMeter.Result.SUCCESS); } else { meter.endOperation(context, OperationMeter.Result.FAILURE); } }
# 开启熔断并配置阀值和持续时间ide
首先建立一个接口,继承自FusingConfig,用于指定配置文件的加载路径,同时还能够设定配置文件的刷新时间,具体定义方法请参照 owner 文档函数
@Config.Sources("classpath:app_config.properties") @Config.HotReload( value = 1, unit = java.util.concurrent.TimeUnit.MINUTES, type = Config.HotReloadType.ASYNC) public interface APPFusingConfig extends FusingConfig { }
建立查询Command的时候在构造函数中传入
public class GetPlayCountCommand extends FusingCommand<Long> { private final Long videoID; public GetPlayCountCommand(Long videoID) { super( APPFusingConfig.class); //设定配置文件 this.videoID = videoID; } protected Optional<Long> run() { Long result = 0l; // 调用HTTP接口获取视频的播放次数信息 // 若是调用失败,返回 null 或者抛出异常,会将此次操做记录为失败 // 若是ID非法,返回 Optional.absent(),会将此次操做记录为成功 return Optional.fromNullable(result); } }
配置文件内容以下:
监控项 | 含义 | 默认值 |
---|---|---|
fusing.[CommandClassName].mode | 熔断模式: FORCE_NORMAL-关闭熔断功能; AUTO_FUSING-自动进入熔断模式; FORCE_NORMAL-强制进行熔断 |
FORCE_NORMAL |
fusing.[CommandClassName].duration | 触发一次熔断之后持续的时间,支持ms,sec,min 单位。例如 10sec | 50sec |
fusing.[CommandClassName].success_rate_threshold | 触发熔断的成功率阀值,下降到这个成功率如下将触发熔断,例如0.9表示成功率90% | 0.9 |
配置文件中的 CommandClassName 是每一个操做类的名称,能够为每一个操做单独设置上述参数。同时,这个配置文件支持动态加载,乐意经过修改fusing.[CommandClassName].mode 手工触发或者关闭熔断。
咱们的服务中使用的是Metric+Graphite+Gafana进行监控数据的采集存储和展示,下面将介绍如何配置监控数据上报Grafana,关于Graphite+Grafana的配置,能够参考文章:使用graphite和grafana进行应用程序监控
首先定义一个接口,继承自GraphiteReporterConfig,经过这个接口定义配置文件的加载路径。配置文件路径的定义方法请参照 owner 文档, 下面是一个例子:
@Config.Sources("classpath:config.properties") public interface MyConfig extends GraphiteReporterConfig { }
配置文件中须要定义下列内容:
配置项 | 含义 |
---|---|
meter.reporter.enabled.hosts | 开启监控上报的服务器列表 |
meter.reporter.perfix | 上报使用的前缀 |
meter.reporter.carbon.host | grafana(carbon-cache) 的 IP 地址,用于存储监控数据 |
meter.reporter.carbon.port | grafana(carbon-cache) 的端口 |
下面这个例子是在192.168.0.0.1和192.168.0.0.2两台服务器上开启监控数据上报,上报监控指标的前缀是project_name.dc:
meter.reporter.enabled.hosts = 192.168.0.0.1,192.168.0.0.2 meter.reporter.perfix = project_name.dc meter.reporter.carbon.host = hostname.graphite
因为相同机房的不一样服务器对外部接口的访问状况通常比较相似,因此仅选取部分机器上报,也是为了节省资源。仅选择部分机器上报不影响熔断效果。
在服务初始化的时候须要对监控上报进行设置。下面的例子中开启了监控数据向日志文件的打印,同时经过MyConfig指定的配置文件加载Graphite配置信息。
MeterCenter.INSTANCE .enableReporter(new EnablingLogReporter("org.apache.log4j.RollingFileAppender")) .enableReporter(new EnablingGraphiteReporter(MyConfig.class)) //监控数据上报Grafana .init();
统计结果会以熔断命令类名为进行分组。例如前面咱们定义的 GetPlayCountCommand 类,package name 是 com.test,那么在日志中的输出将会是这个样子:
type=GAUGE, name=com.test.GetPlayCountCommand.normal-rate, value=0.0 type=GAUGE, name=com.test.GetPlayCountCommand.success-rate, value=61.0 type=TIMER, name=com.test.GetPlayCountCommand.time, count=25866500, min=0.0, max=0.001, mean=3.963926781047921E-5, stddev=1.951102156677818E-4, median=0.0, p75=0.0, p95=0.0, p98=0.001, p99=0.001, p999=0.001, mean_rate=649806.0831335272, m1=1665370.7316699813, m5=2315813.300713087, m15=2446572.324069477, rate_unit=events/second, duration_unit=milliseconds
监控项 | 含义 |
---|---|
[classname].success-rate | 成功率 |
[classname].time.m1 | QPS |
[classname].time.mean | 平均响应时间 |
[classname].normal-rate | 过去1分钟内处于正常访问(非熔断)的时间比例 |
在Grafanna中能够看到下面的监控图:
meerkat使用Metrics进行监控数据的统计,所以可使用Metrics支持的全部reporter进行上报。添加一种上报的时候,只须要实现 EnablingReporter 并在 MeterCenter 初始化以前进行调用便可。下面是log reporter的实现,能够做为参考
public class EnablingLogReporter implements EnablingReporter { private String loggername; public EnablingLogReporter(String loggername) { this.loggername = loggername; } @Override public void invoke(MetricRegistry metricRegistry, long period, TimeUnit timeUnit) { Slf4jReporter.forRegistry(metricRegistry) .outputTo(LoggerFactory.getLogger(loggername)) .convertRatesTo(java.util.concurrent.TimeUnit.SECONDS) .convertDurationsTo(java.util.concurrent.TimeUnit.MILLISECONDS) .build().start(period, timeUnit); } }
MeterCenter 初始化的时候开启reporter
MeterCenter.INSTANCE .enableReporter(new EnablingLogReporter("org.apache.log4j.RollingFileAppender")) .init();
多实例监控主要是为了解决一个被监控操做的实现类须要根据输入参数的不一样分别进行监控和熔断的状况,经过定义实例的名称进行实现。例如获取视频播放次数的例子,获取视频播放次数的接口对于不一样的视频类型而言请求逻辑是同样的,因此使用同一个类进行实现;可是对于不一样的视频类型,接口实现的复杂程度不一样致使成功率不一样,当用户上传的视频的播次接口大量失败的时候咱们不但愿同时熔断电影电视剧这类视频的播放次数获取,这时就须要使用多实例这种特性进行监控和熔断。
下面是一个单实例的实现:
public class GetPlayCountCommand extends FusingCommand<Long> { private final Long videoID; public GetPlayCountCommand(Long videoID) { super( APPFusingConfig.class); this.videoID = videoID; } protected Optional<Long> run() { Long result = 0l; // 调用HTTP接口获取视频的播放次数信息 // 若是调用失败,返回 null 或者抛出异常,会将此次操做记录为失败 // 若是ID非法,返回 Optional.absent(),会将此次操做记录为成功 return Optional.fromNullable(result); } }
假设业务上咱们能够根据视频ID判断视频类型,能够在类初始化的时候根据类型建立多种监控实例,添加了多实例支持的实现以下:
public class GetPlayCountCommand extends FusingCommand<Long> { private final Long videoID; public GetPlayCountCommand(Long videoID) { super( getVideoType(videoID), APPFusingConfig.class); this.videoID = videoID; } private static String getVideoType(Long videoID){ return "PGC"; //根据videoID进行判断,返回 "PGC" 或者 "UGC" 这两个类别 } protected Optional<Long> run() { Long result = 0l; // 调用HTTP接口获取视频的播放次数信息 // 若是调用失败,返回 null 或者抛出异常,会将此次操做记录为失败 // 若是ID非法,返回 Optional.absent(),会将此次操做记录为成功 return Optional.fromNullable(result); }
因为每一个实例独享一个监控指标,日志中的监控个结果是这个样子:
type=GAUGE, name=com.test.GetPlayCountCommand.PGC.normal-rate, value=100.0 type=GAUGE, name=com.test.GetPlayCountCommand.PGC.success-rate, value=100.0 type=GAUGE, name=com.test.GetPlayCountCommand.UGC.normal-rate, value=100.0 type=GAUGE, name=com.test.GetPlayCountCommand.UGC.success-rate, value=60.0 type=TIMER, name=com.test.GetPlayCountCommand.PGC.time, count=100, min=0.0, max=0.509, mean=0.00635, stddev=0.05052135687013958, median=0.001, p75=0.002, p95=0.002, p98=0.003, p99=0.003, p999=0.509, mean_rate=1.6680162586215173, m1=8.691964170141569, m5=16.929634497812284, m15=18.919189378135307, rate_unit=events/second, duration_unit=milliseconds type=TIMER, name=com.test.GetPlayCountCommand.UGC.time, count=100, min=0.0, max=0.027, mean=0.00132, stddev=0.0026939933184772376, median=0.001, p75=0.001, p95=0.002, p98=0.005, p99=0.006, p999=0.027, mean_rate=1.6715904477699361, m1=8.691964170141569, m5=16.929634497812284, m15=18.919189378135307, rate_unit=events/second, duration_unit=milliseconds
相应的,对熔断阀值以及持续时间的配置也须要明确指出实例的名字:
fusing.GetPlayCountCommand.UGC.mode = AUTO_FUSING fusing.GetPlayCountCommand.UGC.duration = 50sec fusing.GetPlayCountCommand.UGC.success_rate_threshold = 0.9 fusing.GetPlayCountCommand.PGC.mode = AUTO_FUSING fusing.GetPlayCountCommand.PGC.duration = 50sec fusing.GetPlayCountCommand.PGC.success_rate_threshold = 0.9