浅入浅出监控系统

如何搭建一个监控系统

生产环境必须是可监控的,一个对开发者黑盒的线上应用无异于灾难。

  1. 采集数据
  2. 保存数据
  3. 可视化数据
  4. 产生告警
m0

从一个熟悉的画面开始:

m1

这是javaer每天都会看到的一个画面,当然为了减少bug,有时候也需要借助一下来自东方的神秘力量


m2

大家注意看console的第一行,灰色字体&被折叠,看起来很不起眼,就被忽略了。

m3
/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/bin/java 
-XX:TieredStopAtLevel=1 
-noverify 
-Dspring.output.ansi.enabled=always 
-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.port=61764 
-Dcom.sun.management.jmxremote.authenticate=false 
-Dcom.sun.management.jmxremote.ssl=false 
-Djava.rmi.server.hostname=127.0.0.1 
-Dspring.liveBeansView.mbeanDomain 
-Dspring.application.admin.enabled=true 
"-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=61765:/Applications/IntelliJ IDEA.app/Contents/bin" 
-Dfile.encoding=UTF-8 
-classpath ...

出现频率最高的单词是jmxremote,这就是我给大家介绍的第一个概念JMX

JMX

JMX(Java Management Extensions,即Java管理扩展)是Java平台上为应用程序、设备、系统等植入管理功能的框架。 --wikipedia

如何做到管理功能呢?

  1. 监控指标,包括业务监控&系统性能监控
  2. 执行方法

我们通过架构图来看一下,JMX如何实现这两个功能。


m4
  1. 接入层,提供远程访问接口
  2. 适配层,对资源的管理和注册
  3. MBean,提供变量or函数

还是不够直观,我们来具体看一下jmx能做什么。

在控制台中输入jconsole,你可以看到一个java GUI风格的工具窗口,这是jdk自带用于jmx连接&展示的工具。

m5

可以通过JDK提供的MBean查看线程、内存、CPU占用,检测死锁、执行GC。也可以通过三方按照JMX标准提供的MBean,查看or执行封装的函数方法。

m6

SpringApplicationAdminMXBean为例,声明了一个包含函数的interface作为MBean,并将实现类注册到MBeanServer服务中。用到了一个委托模式。

public interface SpringApplicationAdminMXBean {

    boolean isReady();
    boolean isEmbeddedWebApplication();
    String getProperty(String key);
    void shutdown();
}
public class SpringApplicationAdminMXBeanRegistrar
        implements ApplicationContextAware, EnvironmentAware, InitializingBean,
        DisposableBean, ApplicationListener<ApplicationReadyEvent> {

    private static final Log logger = LogFactory.getLog(SpringApplicationAdmin.class);

    private ConfigurableApplicationContext applicationContext;

    private Environment environment = new StandardEnvironment();

    private final ObjectName objectName;

    private boolean ready = false;

    public SpringApplicationAdminMXBeanRegistrar(String name)
            throws MalformedObjectNameException {
        this.objectName = new ObjectName(name);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        Assert.state(applicationContext instanceof ConfigurableApplicationContext,
                "ApplicationContext does not implement ConfigurableApplicationContext");
        this.applicationContext = (ConfigurableApplicationContext) applicationContext;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        if (this.applicationContext.equals(event.getApplicationContext())) {
            this.ready = true;
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        server.registerMBean(new SpringApplicationAdmin(), this.objectName);
        if (logger.isDebugEnabled()) {
            logger.debug("Application Admin MBean registered with name '"
                    + this.objectName + "'");
        }
    }

    @Override
    public void destroy() throws Exception {
        ManagementFactory.getPlatformMBeanServer().unregisterMBean(this.objectName);
    }

    private class SpringApplicationAdmin implements SpringApplicationAdminMXBean {

        @Override
        public boolean isReady() {
            return SpringApplicationAdminMXBeanRegistrar.this.ready;
        }

        @Override
        public boolean isEmbeddedWebApplication() {
            return (SpringApplicationAdminMXBeanRegistrar.this.applicationContext != null
                    && SpringApplicationAdminMXBeanRegistrar.this.applicationContext instanceof EmbeddedWebApplicationContext);
        }

        @Override
        public String getProperty(String key) {
            return SpringApplicationAdminMXBeanRegistrar.this.environment
                    .getProperty(key);
        }

        @Override
        public void shutdown() {
            logger.info("Application shutdown requested.");
            SpringApplicationAdminMXBeanRegistrar.this.applicationContext.close();
        }

    }

}
  1. JConsole会根据方法及返回值,判断是指标还是可执行函数。
  2. 除了指标和函数,还有通知。但是JMX并不保证所有通知都会被监听器接收

influxdb

知道了数据如何产生,接下来需要考虑数据如何持久化。

InfluxDB是一个由InfluxData开发的开源时序型数据库。它由Go写成,着力于高性能地查询与存储时序型数据。InfluxDB被广泛应用于存储系统的监控数据,IoT行业的实时数据等场景。

选型原因是influxdb有以下特点

  1. 可度量性:你可以实时对大量数据进行计算
  2. 无结构(无模式):可以是任意数量的列
  3. 原生的HTTP支持,内置HTTP API
  4. 基于时间序列,支持与时间有关的相关函数,如min, max, sum, count, mean, median 等一系列函数
  5. 强大的类SQL语法

强大的类SQL语法 & 无结构

    influx
    show databases;
    create database wyh_dev;
    use wyh_dev;
    show Measurements;

Measurement等价于mysql中的table,区别在于mysql表中存储字段,字段既可以作为展示也可以建立索引。但是influxdb存储的数据从逻辑上由Measurementtag组field组以及一个时间戳组成的。

  1. tag信息是默认被索引的。
  2. Field信息用于展示,是无法被索引的。
  3. time表示该条记录的时间属性。

Line Protocol 语法
利用逗号和空格,简化插入语句, 如果插入数据时没有明确指定时间戳,则默认存储在数据库中的时间戳则为该条记录的入库时间。(纳秒)

weather,location=us-midwest temperature=82 1465839830100400200
  |    -------------------- --------------  |
  |             |             |             |
  |             |             |             |
+-----------+--------+-+---------+-+---------+
|measurement|,tag_set| |field_set| |timestamp|
+-----------+--------+-+---------+-+---------+
insert table1,tag1=a field1="fieldA" 
insert table1,tag1=tagB field1="fieldA",field2="fieldB" 
insert table1,tag1=a,tag2=b field1="fieldA",field2="fieldB",field3="fieldC" 
select * from table1

show series from table1
SHOW FIELD KEYS FROM table1
SHOW TAG KEYS FROM table1
drop measurement table1

insert table2,tagVal=tagA fieldVal="fieldA" 
insert table2,tagVal=tagB fieldVal="fieldA" 
insert table2,tagVal=tagA fieldVal="fieldB" 

select * from table2 where fieldVal='fieldA'
select * from table2 where tagVal='tagA'
select * from table2 group by fieldVal
select * from table2 group by tagVal

select * from table1 group by *
select count(*) from table2 group by time(2d)

  1. field 不可以作为group by条件,但是可以作为where条件
  2. 8083端口停用,web管理界面通过独立组件chronograf实现

原生的HTTP支持,内置HTTP API

curl -i -X POST 'http://localhost:8086/write?db=wyh_dev' --data-binary 'table2,tagVal=tagA fieldVal="http" '

curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=wyh_dev" --data-urlencode "q=SELECT * FROM table2 "

基于时间序列,支持与时间有关的相关函数,如min, max, sum, count, mean, median 等一系列函数

每次insert记录,如果没有指定,默认会保存数据库当前时间(单位纳秒)。复杂的函数计算不符合浅入浅出的定位,我们换一种直观的角度。

Grafana

The open platform for beautiful
analytics and monitoring

很炫很好很强大,没什么好讲的。

配置数据源

m8

支持多种数据源。Access两种形式

  1. Server 服务器请求后渲染
  2. Browser 浏览器直接请求

时间函数

m9

where 不能选择field,只能选择tag

画图参考官方文档

配置告警

配置告警通道,支持email、钉钉,支持webhook=anything,e.g. 企业微信

m10

设定告警规则,包括统计方法、安全阈值。

m11

jmxtrans

上面介绍完JMX之后,其实缺少了一个通道,将JMX指标输出给influxdb。放到后面介绍的原因是因为独立组件,不依赖JMX,数据来源可以是http、日志、kafka

This is effectively the missing connector between speaking to a JVM via JMX on one end and whatever logging / monitoring / graphing package that you can dream up on the other end.

m12

可以通过 JSOM | YAML 配置读取地址、查询线程数、采集指标以及持久化方式。

{
    "servers": [
        {
            "port": "12000",
            "host": "users",
            "numQueryThreads" : 2, 
            "queries": [
                {
                    "obj": "java.lang:type=Memory",
                    "attr": [
                        "HeapMemoryUsage",
                        "NonHeapMemoryUsage"
                    ],
                    "resultAlias": "MemoryUsage",
                    "outputWriters": [
                        {
                            "@class": "com.googlecode.jmxtrans.model.output.InfluxDbWriterFactory",
                            "url": "http://influxdb:8086/",
                            "username": "wyh",
                            "password": "wyh",
                            "database": "wyh",
                            "tags": {
                                "application": "MemoryUsage"
                            }
                        }
                    ]
                },
                {
                    "obj": "kafka.consumer:type=consumer-metrics,client-id=*",
                    "attr": [
                        "connection-close-rate",
                        "connection-creation-rate",
                        "network-io-rate",
                        "outgoing-byte-rate",
                        "request-rate",
                        "request-size-avg",
                        "request-size-max",
                        "incoming-byte-rate",
                        "response-rate",
                        "select-rate",
                        "io-wait-time-ns-avg",
                        "io-wait-ratio",
                        "io-time-ns-avg",
                        "io-ratio",
                        "connection-count",
                        "successful-authentication-rate",
                        "failed-authentication-rate"
                    ],
                    "resultAlias": "ConsumerMetrics",
                    "outputWriters": [
                        {
                            "@class": "com.googlecode.jmxtrans.model.output.InfluxDbWriterFactory",
                            "url": "http://influxdb:8086/",
                            "username": "wyh",
                            "password": "wyh",
                            "database": "consumer",
                            "tags": {
                                "application": "ConsumerMetrics"
                            }
                        }
                    ]
                }
            ]
        }
    ]
}

也可以通过java程序引入依赖包,增加扩展。

public class MemoryPool {

    public static void main(String[] args) throws Exception {
        Injector injector = JmxTransModule.createInjector(new JmxTransConfiguration());
        ProcessConfigUtils processConfigUtils = injector.getInstance(ProcessConfigUtils.class);
        JmxProcess process = processConfigUtils.parseProcess(new File("memorypool.json"));
        new JsonPrinter(System.out).print(process);
        JmxTransformer transformer = injector.getInstance(JmxTransformer.class);
        transformer.executeStandalone(process);
    }
}

总结

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

推荐阅读更多精彩内容