Bootstrap

HickWall 详解

优质博文:IT-BLOG-CN

一、监控分类

【1】Tracing调用链:
【2】Logging日志:
【3】Metrics指标:在应用发布之后,会长时间存在的度量维度。某个接口的请求量、响应时间。

Metrics数据模型

二、Metirc 接入

【1】pom.xml中添加metric-client依赖

<dependency>
    <groupId>com.ctrip.flight.intl.common</groupId>
    <artifactId>metric-client</artifactId>
    <version>4.0.5</version>
</dependency>

【2】执行命令mvn -DskipTests=true compile
【3】编辑MetricClientExampleApplication.java

@SpringBootApplication
 public class MetricClientExampleApplication {
    SpringApplication.run(MetricClientExampleApplication.class, args);
}

@Bean
public CommandLineRunner commandLineRunner() {
    return args -> {
        TimeUnit time = TimeUnit.SECONDS;
        while (true) {
            Metric metric = Metric.create("hickwall_metric_client_example", "100016249", "SYS");
           ThreadLocalRandom random = ThreadLocalRandom.current();
           metric.withTag("caller", "Dante")
                 .withTag("bu", "SYS")
                 .recordOne("run_query", random.nextLong(100, 60000));
           metric.recordSize("run_times", random.nextLong(0, 100));
           metric.addGauge("run_changes", () -> random.nextLong(0, 100));
           time.sleep(120);
       }
    };
}

【4】Tomcat应用

hickwall.prefix=数据库名.+自定义名字(用于区分不同开发组组、不同项目)

推荐做法:数据库名.小组名.项目名

eg: hickwall.prefix=FLT.backendservice.offline.screenpopup

三、埋点

【1】普通记录: 下面用一个例子来演示recordOnerecordSize的用法。假如有一个航班查询接口,它根据传入的参数,返回匹配的所有航班。接口定义如下:

public List<Flight> searchFlights(String dep, String arr, String date);

现在,我们关心这个接口的表现,具体为如下指标:
 ■ 这个接口每秒调用了多少次。
 ■ 这个接口每次调用花了多少时间。
 ■ 这个接口每次调用返回了多少条结果。

做法如下:

public List<Flight> searchFlights(String dep, String arr, String date){
    // 记录请求开始的时间
    long startTime = System.currentTimeMillis();
     
    // 实际的搜索航班
    List<Flight> flights = doSearchFlights(dep, arr, date);
     
    // 当前时间减去开始时间,计算得到搜索航班实际执行的时间
    long timeUsed = System.currentTimeMillis() - startTime;
     
    // 埋点记录这个方法被调用了一次,以及这次调用使用的时间
    Metrics.recordOne("flight.search", timeUsed);
    // 埋点记录这次调用返回的航班的条数。
    Metrics.recordSize("flight.search", flights.size());
     
    return flights;
}

recordOnerecordSize的区别:
 ■ recordOne记录的值是qps。场景:引擎每秒成功多少次。
 ■ recordSize记录的值是平均值。场景:引擎每次返回的结果数量。

【2】下面演示addGauge的用法:假如系统内部有一个缓存,缓存的key的数量随着时间而改变。我们关心缓存的表现,指标如下:缓存的key随着时间是如何变化的。针对这种需求,recordOnerecordSize可能就不够用了,这时需要用addGauge。如下:

@Service
public class Test {
 
    Map cache = new HashMap();
 
    @PostConstruct
    public void init() {
        Metrics.addGauge("cache", () -> cache.size());
    }
}

addGauge方法要求传入一个Supplier,这个Supplier应该返回一个整数值。Metrics会每分钟调用一次Supplier,并把它返回的整数值发送给hickwall

如果在3.2中自己new了一个Metric,上面例子中的Metrics要替换成new出来的metric对象。

四、使用 Tag

相同的指标名,可以记录不同的tag。在hickwall展示的时候,可以根据tag来分别展示指标。

提示:自定义的Tag名称 如果DB无法查询得到,目前是需要找Hickwall Support手动添加。
tag的使用如下:

Metrics.withTag("clientAppId", "100000").recordOne("metricName");

五、超高频调用

如果调用的频率非常高(1000+qps),recordOnerecordSize的性能可能会不够用。这种情况下,需要使用forRecordOneforRecordSize来提高性能。

沿用4.1中的例子,代码要修改如下(注意第1/2行和第15/17行):

private MetricOne COUNT_METRIC = Metrics.forRecordOne("flight.search");
private MetricSize SIZE_METRIC = Metrics.forRecordSize("flight.search");
 
public List<Flight> searchFlights(String dep, String arr, String date){
    // 记录请求开始的时间
    long startTime = System.currentTimeMillis();
 
    // 实际的搜索航班
    List<Flight> flights = doSearchFlights(dep, arr, date);
 
    // 当前时间减去开始时间,计算得到搜索航班实际执行的时间
    long timeUsed = System.currentTimeMillis() - startTime;
 
    // 埋点记录这个方法被调用了一次,以及这次调用使用的时间
    COUNT_METRIC.recordOne(timeUsed);
    // 埋点记录这次调用返回的航班的条数。
    SIZE_METRIC.recordSize(flights.size());
 
    return flights;
}

六、指标命名方式

前面第四节中,埋点使用的指标名并不是最终在hickwall中存储的指标名。

hickwall中存储的指标名规则如下:metricName=appPrefix.metricName.{count,time,size,value}

例如,假设appPrefixintlengine.common

记录方式最终名称
recordOne(“query”)intlengine.common.query.count
recordOne(“query”, time)intlengine.common.query.count
intlengine.common.query.time
recordSize(“queryResult”)intlengine.common.queryResult.size
addGauge(“resource”, ()-> resources.size())intlengine.common.resources.value

七、检查写入情况

打开指标查询,选择数据源和搜索指标名称,我看主要看两部分"查询"和"生成看板到Grafana"。下图为查询相关信息

数据源名的层次结构

生成看板到Grafana:

【1】数据源和指标查询语句必填
【2】dashboard选择必填:
 ■ 添加到已有的dashboard: 将当前的指标添加到已经存在的hickwall grafana dashboard中。
 ■ 新建dashbaord: 新建一个dashboard,并将当前的指标添加到新建的这个dashbord中。需要指定新的dashboard名称,并且重名的dashboard会新建失败。
【3】panel配置必填
 ■ 新建panel: 需要指定新的panel的名称
 ■ 已有的panel: 为已有的panel添加当前的指标语句
【4】别名legendFormat: 即这条线的名称,支持这种tag匹配格式非必填
【5】预览: 可以查看当前指标生成的线以及paneljson 配置。

效果图

八、尝试一下 PromQL

Grafana上创建一个看板,然后点击Add Query,在这个看起来有点唬人的页面里把Queries to改成APM-SYS,然后在那个大大的输入框里写下生命、宇宙与一切的答案:

不管多复杂的promQL表达式,返回值只有三种
1.瞬时向量,同一时间点的数据点
2.范围向量,同一个指标的一段时间范围内的数据点
3.数值,字面量,没有标签、时间戳

PromQL的运算逻辑是这样的:浏览器会按照选取的时间范围和显示器的大小选取合适的间隔,例如显示的曲线是3min一个数据点。后端会根据这个时间间隔分别计算每个时间段上的值,并赋予该时间段的起始时间戳。最后所有的段连接起来就是一条曲线。

九、看板

当我们使用PromQL查出了需要的数据,就可以继续调整看板的其它配置。常用的有:
 ■ Legend:图例。如果我们只希望显示数据中的caller这个tag的值,就可以在Legend中填写 ‘’
 ■ Min step:步长。如果希望图中每小时只显示一个点,就在这里填写1h
 ■ Visualization:点击左侧第二个图标进入,这里可以修改的配置项很多,比如:
  ● Visualization:可以从默认的 Graph 改成多种其它图表,每种图表的使用方式各异,请自行探索 / 查阅文档
  ● Axes -> Left Y -> Unit:改变默认的数值单位
  ● Legend -> Values -> Total / Avg / Min / Max ...
 ■ General:左侧第三个图标,在这里可以给Panel取一个温暖的名字
 ■ Alert:左侧第四个图标,进入我们接下来要做的:配置告警

配置告警之前,记得点击右上角的Save Dashboard保存一下。

十、告警

点击Create Alert来创建一个新的告警规则。安心,在我们保存整个Dashboard之前,它都不会生效。

如果你看到很多大红的警示文字,不用担心,这只是审美问题。

首先,选择BU、产品线、AppID

然后给告警命名,Evaluate every 表示每隔多久执行一次这条告警规则,For 则表示在持续多久满足(我们马上就会填的)告警条件之后才真正发出告警。

接下来随便填一个阈值:

当使用avg()这个函数去算A这个查询语句取5m(也就是 5 分钟)内的数据的均值,要是它的结果IS ABOVE(大于) 1,就满足告警条件了,告警级别P3,而且不管现在几点都生效(Alert Time = All)。

对于一个已经保存过的Dashboard,填好以后我们就可以点右上角的Test Rule来试试它能不能正常运行。

最后,万一告警触发了,该通知谁呢?

这里看到,我们可以选: 各种方式的任意组合
 ■ AppID的管理员
 ■ 用户
 ■ 邮件组
 ■ Oncall

如果这样仍不能满足需求,还可以配置 自定义告警通知,来根据不同的告警级别进行配置,并选择Send to(通知方式):
 ■ 邮件
 ■ 短信
 ■ TTS语音
 ■ TripPal

;