Metrics+Influxdb+Grafana构建监控系统(可能全网最详)

开篇

  一周一篇技术博文又来了,这周我们讲点什么呢?看标题就知道了,那就是利用Metrics,influxdb,Grafana构建一套生产可用的监控系统,至于为什么选择这套方案呢,因为简单易实现,并且公司有现成的环境可以使用,至于这三个技术的一些简单介绍还有使用方法....那当然还是Google去喽。
  这篇博文的重点是帮助你实现一个生产可用的监控系统,将遇到的坑告诉你,至于枯燥的知识当然需要你们自己补充了,这也是我以后博文的风格,只讲一些在其他博文中看不到的知识,嘿嘿。ok,凌云小课堂正式开始啦,今天要介绍的就是如何利用这三种技术搭建生产可用的监控系统

Metrics的使用和注意事项

  这节点我们讲Metrics的实现和需要的关注点

导包
 <!--metrics相关-->
        <dependency>
            <groupId>io.dropwizard.metrics</groupId>
            <artifactId>metrics-core</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.davidb</groupId>
            <artifactId>metrics-influxdb</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
            <version>0.8.2</version>
        </dependency>

  这里Metrics的版本是4.0.0,metrics-influxdb版本是0.8,2,这两个包都需要JDK1.8版本的支持,如果项目Java版本不是1.8是会有问题的。所以导包是要根据的项目java版本选择相关版本

相关数据写入配置文件
#influxdb 配置参数 
influxdb.ip=172.19.160.94
influxdb.port=8086
influxdb.database=metrics
#metrics数据上报时间间隔
metrics.reporterInterval=10

  这里主要有两个部分

  1. influxdb 的ip,端口号,和数据库名称 类似mysql
  2. metrics采集的项目数据上传influxdb的间隔时间,太过密集对项目本身和influxdb都是一种负担,实践10s是一个较为合理的时间
Spring注册MetricsRegistry
<!-- metrics-->
    <bean id="metricRegistry" class="com.codahale.metrics.MetricRegistry"/>
Metrics和influxdb初始化

  我们直接贴代码

/**
 * Influxdb初始化
 *
 * @author Lingyun
 * @Date 2018-12-10 23:56
 */
@Component
@Data
public class MetricsInitializer implements InitializingBean {

    @Autowired
    private MetricRegistry metricRegistry;

    @Value("#{settings['influxdb.ip']}")
    private String ip;
    @Value("#{settings['influxdb.port']}")
    private int port;
    @Value("#{settings['influxdb.database']}")
    private String dataBase;
    @Value("#{settings['metrics.reporterInterval']}")
    private long reporterInterval;

    @Override
    public void afterPropertiesSet() throws Exception {
    
        // 统计维度:部门和规则名称
        CategoriesMetricMeasurementTransformer example = new CategoriesMetricMeasurementTransformer("noticeName", "ruleName", "measurement");

        // 统计数据上报influxdb调度配置
        ScheduledReporter report = InfluxdbReporter
                .forRegistry(metricRegistry)// 注册metrics
                .protocol(InfluxdbProtocols.http(ip, port, dataBase))//数据库配置
                .tag("ip", InetAddress.getLocalHost().getHostAddress())//标签绑定单机IP
                .transformer(example)//表定义
                .convertRatesTo(TimeUnit.SECONDS)
                .convertDurationsTo(TimeUnit.MILLISECONDS)
                .filter(MetricFilter.ALL)
                .skipIdleMetrics(false)
                .build();

        long initalDelay = getBeginTime().getTimeInMillis() - System.currentTimeMillis();//延迟X(ms)启动
        long period = reporterInterval * 1000;//上报间隔时间(ms)

        // 启动
        report.start(initalDelay, period, TimeUnit.MILLISECONDS);
    }

    /**
     * 获取Metrics报告时间:
     * Metrics报告时间设定为启动后1分钟0秒开始,
     * 保证所有机器上的数据的开始时间都是从某分钟开始
     *
     * @return
     */
    private Calendar getBeginTime() {
        Calendar beginTime = Calendar.getInstance();
        beginTime.setTime(new Date());
        beginTime.add(Calendar.MINUTE, 1);
        beginTime.set(Calendar.SECOND, 0);// 秒
        beginTime.set(Calendar.MILLISECOND, 0);// 毫秒
        return beginTime;
    }
}

  初始化阶段需要着重注意的有以下几点

  1. CategoriesMetricMeasurementTransformer 对象的创建,这个对象代表的是influxdb中的表结构,而代码中noticeName和ruleName相当于表字段,是项目监控关注的维度,比如ruleName字段,是我想关注系统在规则这个维度的tps和耗时,而measurement是influxdb的表名,在后面会提到
  2. .tag("ip", InetAddress.getLocalHost().getHostAddress())//标签绑定单机IP 这段代码意思将单机的ip地址绑定到influxdb的表中,可以通过ip统计单机的各维度数据,不如单机的tps
  3. 最后启动方式使用了延迟加载,可以保证所有上传influxdb的数据都是从整点整分钟开始记录的
Metrics工厂方法

  ....我们还是直接贴代码

/**
 * Metrics工厂方法
 */
@Slf4j
public class MetricsFactory {

    private final static String TIMER_MEASUREMENTS = "xxxTimer";
    private final static String METER_MEASUREMENTS = "xxxMeter";
    private final ConcurrentHashMap<String, Meter> meterMap = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, Timer> timerMap = new ConcurrentHashMap<>();

    private MetricsFactory() {
    }

    private static class MetricsFactoryInstance {
        private static final MetricsFactory INSTANCE = new MetricsFactory();
    }

    public static MetricsFactory getInstance() {
        return MetricsFactoryInstance.INSTANCE;
    }

    /**
     * xxx接口TPS统计
     *
     * @param noticeName
     * @param ruleName
     * @param metricRegistry
     * @return
     */
    public void getMeter(String noticeName, String ruleName, MetricRegistry metricRegistry) {
        try {
            String metricKey = MetricRegistry.name(noticeName, ruleName, METER_MEASUREMENTS);
            getMeter(metricKey, metricRegistry).mark();
        } catch (Exception e) {
            log.error("[NoticeCenter]create metrics(meter) error", e);
        }
    }

    /**
     * xxx接口耗时统计
     *
     * @param noticeName
     * @param ruleName
     * @param metricRegistry
     * @return
     */
    public Timer.Context getTimer(String noticeName, String ruleName, MetricRegistry metricRegistry) {
        try {
            String metricKey = MetricRegistry.name(noticeName, ruleName, TIMER_MEASUREMENTS);
            return getTimer(metricKey, metricRegistry).time();
        } catch (Exception e) {
            log.error("[NoticeCenter]create metrics(timer) error", e);
            return null;
        }
    }

    /**
     * 获取Meter实例
     *
     * @param metricsKey
     * @return
     */
    private Meter getMeter(String metricsKey, MetricRegistry metricRegistry) {
        Meter m = meterMap.get(metricsKey);
        if (m != null) {
            return m;
        }
        synchronized (MetricsFactory.class) {
            Meter metrics = meterMap.get(metricsKey);
            if (metrics != null) {
                return metrics;
            } else {
                Meter object = metricRegistry.meter(metricsKey);
                meterMap.putIfAbsent(metricsKey, object);
                return object;
            }
        }
    }

    /**
     * 获取Timer实例
     *
     * @param metricsKey
     * @return
     */
    private Timer getTimer(String metricsKey, MetricRegistry metricRegistry) {
        Timer t = timerMap.get(metricsKey);
        if (t != null) {
            return t;
        }
        synchronized (MetricsFactory.class) {
            Timer timer = timerMap.get(metricsKey);
            if (timer != null) {
                return timer;
            } else {
                Timer object = metricRegistry.timer(metricsKey);
                timerMap.putIfAbsent(metricsKey, object);
                return object;
            }
        }
    }

}

  这个工厂方法很简单,就是获取或者创建我们需要的metrics类型(metrics有五种类型,各个类型的用处含义自行Google),还是有几点需要你关心的地方

  1. xxxTimer 这个String字段代表influxdb中的表名
  2. String metricKey = MetricRegistry.name(noticeName, ruleName, TIMER_MEASUREMENTS); 这行代码是获取一个Metrics的Key值,metricRegistry本质是一个map,需要key建去读取和保存Metrics类型对象
  3. 这里一定要加锁保证并发量大的情况下项目数据不会因为取到同一个metrics对象,而导致统计数据的丢失
项目埋点

  这里因为保密原因我们只暴露部分代码,但保证逻辑的连贯性

// 注入metricRegistry
@Autowired
    private MetricRegistry metricRegistry;

  项目埋点

 //Metrics.Timer(请求时长)埋点
            Timer.Context context = MetricsFactory.getInstance().getNoticeTimer(noticeInfo.getNoticeName(), notifyRuleName, metricRegistry);
            // 3.接口逻辑执行点
            result = xxxxService.execute(xxxx);
            //Metrics.Meter(TPS)埋点
            MetricsFactory.getInstance().getNoticeMeter(noticeInfo.getNoticeName(), notifyRuleName, metricRegistry);
            //测量调用时间-结束
            stopTimerContext(context);

  stopTimerContext方法

 /**
     * 停止Metrics.Timer记录
     *
     * @param context
     */
    private void stopTimerContext(Timer.Context context) {
        try {
            if (context != null) {
                context.stop();
            }
        } catch (Exception e) {
            log.error("[NoticeCenter]metrics(timer) stop error", e);
        }
    }

  Timer主要记录接口耗时,Meter主要记录接口TPS
  到这里整个项目中Metrics相关的点都结束了,是不是很简单,只要将以上代码Copy根据实际项目改造一下就是一个生产可用的Metrics代码了

Influxdb相关

  influxdb这里只简单讲几点

  1. influxdb没有建表语句,所有不用跟我一样去找建表语句了,Metrics上报数据时第一条的插入数据就会自动建表。。。。
  2. influxdb 本身不提供数据的删除操作,因此用来控制数据量的方式就是定义数据保留策略。时间策略越短统计数据越准确。我的项目设定的策略为72h,大家可以根据自己系统的需求自行决定保存策略
  3. 最后就是influxdb 的 语法类似sql,但是我可悲的没有仔细去看。。。但不影响构建整个系统。。。
  4. 对于influxdb语法推荐几篇博文,有兴趣的可以去看看
1. https://www.linuxdaxue.com/influxdb-study-influxdb-selectors-funcitons.html
2. https://www.jianshu.com/p/a1344ca86e9b

Grafana相关

  Grafana是一个可视化系统。主要的功能和配置方式大家可以看一下这篇博文

https://www.cnblogs.com/Leo_wl/p/8506377.html 

  而我要讲的是如何在Grafana中配置一个可视化TPS监控页面


测试环境监控

  接下来我们就开始疯狂贴图,首先是系统整体TPS的配置


系统整体TPS.png

  接下来我们就开始疯狂贴图,各个细化维度的TPS配置,主要添加了分组标签 tag(部门),剩下的大家自行发挥,通过通知分组标签把各个单一维度结合起来

部门级TPS

  接下来的就是如何在Grafana中配置一个可视化接口耗时监控页面
单机ITC监控

单机ITC配置项

  最后就是如何在Grafana中配置一个可视化接口调用量饼图监控页


接口调用占比

部门维度配置项
简述

  整个监控系统中,metrics负责采集数据,influxdb负责保存数据,而Grafana负责展示数据,所以influxdb和Grafana只是辅助工具,重点还是数据的采集和维度的选择,数据采集到后,至于如何展示,就是对系统关注点的侧重了。下周再见,拜拜,希望看到这篇文章的你收获到了呢。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容