使用meerkat进行服务监控和服务降级

标签: metrics 监控 java | 发表时间:2017-06-10 09:38 | 作者:channingbj
出处:https://segmentfault.com/blogs

meerkat 是爱奇艺移动服务端团队开发的服务监控以及服务降级基础组件,主要为了解决调用外部接口的时候进行成功率,响应时间,QPS指标的监控,同时在成功率下降到预设的阈值以下的时候自动切断外部接口的调用,外部接口成功率恢复后自动恢复请求。本文将对使用方式以及进阶特性进行介绍。

项目主页: https://github.com/qiyimbd/me...

为什么要进行监控和熔断

在我们的Java服务中,经常会调用外部的一些接口进行数据的获取操作,当这些外部接口的成功率比较低的时候会直接影响到服务本身的成功率,因此我们添加了对外部接口调用的成功率和响应时间监控,这样可以在造成大量用户影响之前预先发现并解决问题。同时,对于接口中的非关键数据,我们采取了更具成功率判断进行触发熔断的方式,当成功率下降到预定的阀值以下的时候自动停止对这个外部接口的访问以便保证关键数据能够正常提供,当成功率恢复以后自动恢复请求。

meerkat主要功能

  • 监控:监控Java内部操作的成功率以及响应时间指标

  • 上报:log文件和Grafhite两种监控指标上报方式,支持扩展其他的上报方式

  • 熔断:(可选功能)成功率下降到预设的阈值以下触发熔断保护,暂定对外部接口的访问,成功率恢复以后自动恢复访问

基本使用

maven

  <dependency>
    <groupId>com.github.qiyimbd</groupId>
    <artifactId>meerkat</artifactId>
    <version>1.0</version>
</dependency>

定义受监控的操作

假设我们的服务中需要从HTTP接口查询一个节目的播放次数,为了防止这个HTTP接口大量超时影响我们自身服务的质量,可以定义一个查询Command:

  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);
    }
}

执行查询:

  //获取视频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.qiyi.mbd.test,那么在日志中的输出将会是这个样子:

  type=GAUGE, name=com.qiyi.mbd.test.GetPlayCountCommand.normal-rate, value=0.0
type=GAUGE, name=com.qiyi.mbd.test.GetPlayCountCommand.success-rate, value=61.0
type=TIMER, name=com.qiyi.mbd.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 进行实现,只需要在函数调用的前后添加开始和结束的调用即可:

  //创建一个操作的计数器
    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);
        }
    }

# 开启熔断并配置阀值和持续时间

首先创建一个接口,继承自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 手工触发或者关闭熔断。

监控指标上报Graphite

我们的服务中使用的是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.qiyi.mbd.test,那么在日志中的输出将会是这个样子:

  type=GAUGE, name=com.qiyi.mbd.test.GetPlayCountCommand.normal-rate, value=0.0
type=GAUGE, name=com.qiyi.mbd.test.GetPlayCountCommand.success-rate, value=61.0
type=TIMER, name=com.qiyi.mbd.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.qiyi.mbd.test.GetPlayCountCommand.PGC.normal-rate, value=100.0
type=GAUGE, name=com.qiyi.mbd.test.GetPlayCountCommand.PGC.success-rate, value=100.0
type=GAUGE, name=com.qiyi.mbd.test.GetPlayCountCommand.UGC.normal-rate, value=100.0
type=GAUGE, name=com.qiyi.mbd.test.GetPlayCountCommand.UGC.success-rate, value=60.0
type=TIMER, name=com.qiyi.mbd.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.qiyi.mbd.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

相关 [meerkat 服务 和服] 推荐:

使用meerkat进行服务监控和服务降级

- - SegmentFault 最新的文章
meerkat 是爱奇艺移动服务端团队开发的服务监控以及服务降级基础组件,主要为了解决调用外部接口的时候进行成功率,响应时间,QPS指标的监控,同时在成功率下降到预设的阈值以下的时候自动切断外部接口的调用,外部接口成功率恢复后自动恢复请求. 本文将对使用方式以及进阶特性进行介绍. 项目主页: https://github.com/qiyimbd/me....

LinkedIn开源IndexTank,包括搜索引擎和服务

- - ITeye资讯频道
IndexTank是一家在今年10月份被LinkedIn收购的公司,它有三个主要产品:. IndexEngine:一个实时的全文搜索和索引系统. API:一个RESTful界面来处理授权、验证以及与IndexEngine(s)的通讯. Nebulizer:一个多重任务执行框架(multitenant framework),用来托管和管理无限数量的运行在IaaS层的索引.

普通PC和服务器的一些区别?

- - 脚本爱好者
1.性能.服务器主板支持多路处理.性能方面会是PC主板的数倍. 2.网卡方面,服务器主板一般都是双网卡千兆.PC一般都是单网卡百兆,当放到机房.服务器对服务器传输文件的时候往往千兆对千兆的传输特别快.或者内部传输读取文件特别快..像网吧.现在带宽都不止100M了.都过G的带宽.所以PC这方面差. 4.扩展方面.PC主板和服务器主板就有天大的一个差别.如可加硬盘数量,支不支持RAID,支持内存数量,支持CPU数量等等.

关于主机多IP和服务端口绑定IP的研究

- - CSDN博客研发管理推荐文章
      在网络编程中,经常需要在服务器里开一个端口,这里有一个问题,这个端口是开到哪个IP上面了,因为对于服务器来说经常是不止一个IP的,一个是127.0.0.1,一个是内网IP,比如192.168.1.3,一个是外网IP,比如211.19.56.39. 对于开发着来说,经常会忽略绑定到具体某个IP上面,类似于下面的代码.

URL编码,客户端和服务器之间发生了什么

- - 崔永键的博客
要知道,其中包含两种编码,一种是字符到字符的URL编码(可以理解为仅仅是给字符换了一种字符层面上的表示形式而已,可以理解为转义),一种是字符到二进制的传统意义上我们理解的字符编码. 先来看,服务器到客户端的消息. 服务器端对HTTP头内容,首先进行URL编码,比如其中的汉字,就使用UTF8转换为对应的UTF8编码值.

服务禁语

- tiancaicai - 白板报
前几天在一个公交汽车站拍到了一张规定,里面规定了服务禁语和礼貌用语,看了大乐. 3、乘车高峰车厢内拥挤时,禁语:“快往里走,站在前面又没有钞票检. ”文明语:“请尽量往里走,照顾没有上车的乘客”. 4、车子抛锚,禁语:“车子抛锚没有办法,人都要生毛病的,车子坏了也正常. ”文明语:“对不起,车子出现故障修一下,请大家理解.

服务熔断

- - CSDN博客推荐文章
服务熔断也称服务隔离,来自于Michael Nygard 的《Release It》中的CircuitBreaker应用模式,Martin Fowler在博文 CircuitBreaker中对此设计进行了比较详细说明. 本文认为服务熔断是服务降级的措施. 服务熔断对服务提供了proxy,防止服务不可能时,出现串联故障(cascading failure),导致雪崩效应.

面向服务与微服务架构

- - CSDN博客推荐文章
最近阅读了 Martin Fowler 和 James Lewis 合著的一篇文章  Microservices, 文中主要描述和探讨了最近流行起来的一种服务架构模式——微服务,和我最近几年工作的实践比较相关感觉深受启发. 本文吸收了部分原文观点,结合自身实践经验来探讨下服务架构模式的演化. 面向服务架构 SOA 思想概念的提出已不是什么新鲜事,大概在10年前就有不少相关书籍介绍过.

经理服务生

- netcasper - 坏脾气的小肥
2007年的时候,我和内容团队一起去报道上海车展,累得够呛,写稿子到凌晨一两点,早上八点钟又要爬起来去现场或更新早班. 有天上午,编辑都挤在大会议室里忙活着整理、发布、撰稿,而我搞完了竞品检查/数据分析/计划修订,一时间闲着,就打算去买些零食给大家. 环顾四周,没人有空,只好自己下楼,嘿咻嘿咻拎了两三百块钱的零食上来.

Kernel.org恢复服务

- Adam - Solidot
kernel.org 王者归来 写道 "Linux内核官网在八月份遭入侵,之后于9月11日linux.com linux.org kernel.org LinuxFoundation.org皆无法访问,进行安全维护. 经过紧张的修复,kernel.org终于恢复服务. LinuxFoundation.org也可以正常访问.