文章目录
前言
在RocketMQ中有很多关于指标数据统计的实现,本篇文章就来总结下其实现的方式以及都统计了哪些数据(源码是4.9.1版本)。
一、BrokerStatsManager
1.BrokerStatsManager初始化及启动
(1)BrokerStatsManager初始化
在broker启动过程中会创建BrokerController对象,在初始化BrokerController时就会初始化BrokerStatsManager,具体如下:
public static BrokerController createBrokerController(String[] args) {
...
final BrokerController controller = new BrokerController(
brokerConfig,
nettyServerConfig,
nettyClientConfig,
messageStoreConfig);
// remember all configs to prevent discard
controller.getConfiguration().registerConfig(properties);
...
}
//在构造BrokerController对象时会初始化BrokerStatsManager
public BrokerController(
final BrokerConfig brokerConfig,
final NettyServerConfig nettyServerConfig,
final NettyClientConfig nettyClientConfig,
final MessageStoreConfig messageStoreConfig
) {
...
this.brokerStatsManager = new BrokerStatsManager(this.brokerConfig.getBrokerClusterName());
...
}
在BrokerStatsManager中有一个map结构的statsTable是用于存储BrokerStatsManager统计的指标名称及其对应的指标数据,在BrokerStatsManager初始化过程中最重要的操作就是将其统计的所有指标放入这个map结构中。
(2)BrokerStatsManager启动
在启动BrokerController时会启动BrokerStatsManager,具体如下:
public static BrokerController start(BrokerController controller) {
try {
controller.start();
String tip = "The broker[" + controller.getBrokerConfig().getBrokerName() + ", "
+ controller.getBrokerAddr() + "] boot success. serializeType=" + RemotingCommand.getSerializeTypeConfigInThisServer();
if (null != controller.getBrokerConfig().getNamesrvAddr()) {
tip += " and name server is " + controller.getBrokerConfig().getNamesrvAddr();
}
log.info(tip);
System.out.printf("%s%n", tip);
return controller;
} catch (Throwable e) {
e.printStackTrace();
System.exit(-1);
}
return null;
}
public void start() throws Exception {
...
if (this.brokerStatsManager != null) {
this.brokerStatsManager.start();
}
...
}
2.BrokerStatsManager运转
想要搞清楚BrokerStatsManager是如何统计数据的,需要搞清楚HashMap<String, StatsItemSet> statsTable、StatsItemSet及StatsItem这三个数据结构。在统计指标数据时当然会有指标名称,在每个指标名称下还会有具体的对象,例如topic,最后才是该指标的值。所以在设计上首先是由statsTable来缓存所有的指标,每个指标对应的StatsItemSet中会有一个map结构statsItemTable用来缓存该指标下具体的对象及其指标值,指标值被封装成StatsItem。这三个数据结构详解具体如下。
2.1 statsTable
statsTable在BrokerStatsManager中是用于缓存统计的指标数据,statsTable的key表示统计的指标名称,value是StatsItemSet类型的数据,在BrokerStatsManager初始化时向statsTable中放入了统计的指标及其对应的StatsItemSet。
2.2 StatsItemSet
(1)StatsItemSet初始化
在BrokerStatsManager中每个指标都对应一条StatsItemSet类型的数据,其结构具体如下,其中statsName表示指标的名称,statsItemTable缓存的是该指标下具体的指标对象及其对应的StatsItem类型对象,举个例子,TOPIC_PUT_NUMS指标是在统计写入commitlog的消息数,此时会统计各个topic对应的消息数,所以topic的名称就是statsItemTable的key。
public class StatsItemSet {
private final ConcurrentMap<String/* key */, StatsItem> statsItemTable =
new ConcurrentHashMap<String, StatsItem>(128);
private final String statsName;
private final ScheduledExecutorService scheduledExecutorService;
private final InternalLogger log;
public StatsItemSet(String statsName, ScheduledExecutorService scheduledExecutorService, InternalLogger log) {
this.statsName = statsName;
this.scheduledExecutorService = scheduledExecutorService;
this.log = log;
this.init();
}
}
(2)init()
在StatsItemSet的初始化中有个很重要的方法就是init(),该方法中主要是一些定时任务,定时任务可以分为两大类,分别是samplingInxxx()和printAtxxx()。
- samplingInxxx()方法主要完成的功能是定时根据StatsItem中的指标值value、次数times以及当前的时间点构建一个指标的快照并将该指标快照存储在一个LinkedList中。
- printAtxxx()方法主要完成的功能是在broker端的日志文件stats.log中打印指标数据,按照定时任务的时间频率即每分钟、每小时、每天打印各指标数据,后面输出的指标数据SUM、TPS以及AVGPT是根据LinkedList中缓存的快照数据进行计算,具体计算逻辑见computeStatsData方法。
[statsName] [statsKey] Stats In One Minute, SUM: xxx TPS: xxx AVGPT: xxx
[statsName] [statsKey] Stats In One Hour, SUM: xxx TPS: xxx AVGPT: xxx
[statsName] [statsKey] Stats In One Day, SUM: xxx TPS: xxx AVGPT: xxx
private static StatsSnapshot computeStatsData(final LinkedList<CallSnapshot> csList) {
StatsSnapshot statsSnapshot = new StatsSnapshot();
synchronized (csList) {
double tps = 0;
double avgpt = 0;
long sum = 0;
long timesDiff = 0;
if (!csList.isEmpty()) {
CallSnapshot first = csList.getFirst();
CallSnapshot last = csList.getLast();
sum = last.getValue() - first.getValue();
tps = (sum * 1000.0d) / (last.getTimestamp() - first.getTimestamp());
timesDiff = last.getTimes() - first.getTimes();
if (timesDiff > 0) {
avgpt = (sum * 1.0d) / timesDiff;
}
}
statsSnapshot.setSum(sum);
statsSnapshot.setTps(tps);
statsSnapshot.setAvgpt(avgpt);
statsSnapshot.setTimes(timesDiff);
}
return statsSnapshot;
}
2.3 StatsItem
StatsItem的数据结构如下,其中value表示指标值,times表示次数,csListMiute、csListHour和csListDay是用来缓存指标快照的,statsName表示指标名称,statsKey表示某一个指标下的具体对象。
public class StatsItem {
private final AtomicLong value = new AtomicLong(0);
private final AtomicLong times = new AtomicLong(0);
private final LinkedList<CallSnapshot> csListMinute = new LinkedList<CallSnapshot>();
private final LinkedList<CallSnapshot> csListHour = new LinkedList<CallSnapshot>();
private final LinkedList<CallSnapshot> csListDay = new LinkedList<CallSnapshot>();
private final String statsName;
private final String statsKey;
}
3.统计的指标及其含义
指标名称 | 含义 |
---|---|
TOPIC_PUT_NUMS | 成功写入commitlog中的消息条数,按topic维度统计,其statsKey是topicName |
TOPIC_PUT_SIZE | 成功写入commitlog中的消息大小(单位是字节),按topic维度统计,其statsKey是topicName |
GROUP_GET_NUMS | 成功从broker获取消息的条数,其statsKey是topic@group |
GROUP_GET_SIZE | 成功从broker获取消息的大小(单位是字节),其statsKey是topic@group |
GROUP_GET_LATENCY | 消息的消费延迟时间 ,其statsKey是queueId@topic@group |
SNDBCK_PUT_NUMS | consumer消费失败的消息成功写入SCHEDULE_TOPIC_XXXX中的消息条数,其statsKey是topic@group |
BROKER_PUT_NUMS | 统计消息存储到broker端的条数 ,按broker维度统计,其statsKey是clusterName |
BROKER_GET_NUMS | 统计从broker端成功获取消息的条数 ,按broker维度统计,其statsKey是clusterName |
二、StoreStatsService
StoreStatsService继承了ServiceThread,其统计的是和存储相关的指标数据。由于它本质上是个线程,所以我们直接看其run方法,可以看到它每1分钟会执行sampling和printTps方法。其中sampling方法主要完成的功能是往putTimesList、getTimesFoundList、getTimesMissList和transferedMsgCountList中添加指标的快照数据。
public void run() {
log.info(this.getServiceName() + " service started");
while (!this.isStopped()) {
try {
this.waitForRunning(FREQUENCY_OF_SAMPLING);
this.sampling();
this.printTps();
} catch (Exception e) {
log.warn(this.getServiceName() + " service has exception. ", e);
}
}
log.info(this.getServiceName() + " service end");
}
sampling方法中存储各指标快照的含义具体如下:
指标 | 含义 |
---|---|
putTimesList | 成功写入broker的数据总条数 |
getTimesFoundList | 从broker端成功获取数据的次数 |
getTimesMissList | 从broker端获取数据失败的次数 |
transferedMsgCountList | 该指标含义和getTimesFoundList貌似是一样的,没啥区别,都是在成功获取到消息时自增 |
printTps方法会每1分钟在日志中打印以下信息:
[STORETPS] put_tps XXX get_found_tps XXX get_miss_tps XXX get_transfered_tps XXX
StoreStatsService - [PAGECACHERT] TotalPut 0, PutMessageDistributeTime [<=0ms]:xxx [0~10ms]:xxx [10~50ms]:xxx [50~100ms]:xxx [100~200ms]:xxx [200~500ms]:xxx [500ms~1s]:xxx [1~2s]:xxx [2~3s]:xxx [3~4s]:xxx [4~5s]:xxx [5~10s]:xxx [10s~]:xxx
指标 | 含义 |
---|---|
put_tps | 成功写入消息的TPS(根据putTimesList中的快照数据计算) |
get_found_tps | 从broker端成功获取到数据的TPS(根据getTimesFoundList中的快照数据计算) |
get_miss_tps | 从broker端获取数据失败的TPS(根据getTimesMissList中的快照数据计算) |
get_transfered_tps | 与get_found_tps类似(根据transferedMsgCountList中的快照数据计算) |
TotalPut | 一分钟内在broker端一共写入消息多少次 |
PutMessageDistributeTime | broker端普通消息或者批量消息写入内存中花费时间的统计,花费的时间一共分为13个级别 ,分别是[<=0ms]、[0-10ms]、[10-50ms]、[50-100ms]、[100-200ms]、[200-500ms]、[500ms-1s]、[1-2s]、[2-3s]、[3-4s]、[4-5s]、[5-10s]、[10s-] |
三、BrokerStats
在创建BrokerController时会初始化BrokerStats,并且还会周期性执行record方法,周期是一天一次,具体如下:
public boolean initialize() throws CloneNotSupportedException {
boolean result = this.topicConfigManager.load();
result = result && this.consumerOffsetManager.load();
result = result && this.subscriptionGroupManager.load();
result = result && this.consumerFilterManager.load();
if (result) {
try {
this.messageStore =
new DefaultMessageStore(this.messageStoreConfig, this.brokerStatsManager, this.messageArrivingListener,
this.brokerConfig);
if (messageStoreConfig.isEnableDLegerCommitLog()) {
DLedgerRoleChangeHandler roleChangeHandler = new DLedgerRoleChangeHandler(this, (DefaultMessageStore) messageStore);
((DLedgerCommitLog)((DefaultMessageStore) messageStore).getCommitLog()).getdLedgerServer().getdLedgerLeaderElector().addRoleChangeHandler(roleChangeHandler);
}
this.brokerStats = new BrokerStats((DefaultMessageStore) this.messageStore);
...
} catch (IOException e) {
result = false;
log.error("Failed to initialize", e);
}
}
...
if (result) {
...
final long initialDelay = UtilAll.computeNextMorningTimeMillis() - System.currentTimeMillis();
final long period = 1000 * 60 * 60 * 24;
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
BrokerController.this.getBrokerStats().record();
} catch (Throwable e) {
log.error("schedule record error.", e);
}
}
}, initialDelay, period, TimeUnit.MILLISECONDS);
...
}
return result;
}
record方法主要是在日志中输出以下信息:
yesterday put message total: xxx (其含义是昨天成功写入broker中的消息总条数)
yesterday get message total: xxx (其含义是昨天成功broker中获取消息的次数)
BrokerStats中包含的指标信息如下:
指标 | 含义 |
---|---|
msgPutTotalYesterdayMorning | 昨天成功写入broker的数据总条数 |
msgPutTotalTodayMorning | 今天成功写入broker的数据总条数 |
msgGetTotalYesterdayMorning | 昨天成功从broker获取消息的次数 |
msgGetTotalTodayMorning | 今天成功从broker获取消息的次数 |
public void record() {
this.msgPutTotalYesterdayMorning = this.msgPutTotalTodayMorning;
this.msgGetTotalYesterdayMorning = this.msgGetTotalTodayMorning;
this.msgPutTotalTodayMorning =
this.defaultMessageStore.getStoreStatsService().getPutMessageTimesTotal();
this.msgGetTotalTodayMorning =
this.defaultMessageStore.getStoreStatsService().getGetMessageTransferedMsgCount().get();
log.info("yesterday put message total: {}", msgPutTotalTodayMorning - msgPutTotalYesterdayMorning);
log.info("yesterday get message total: {}", msgGetTotalTodayMorning - msgGetTotalYesterdayMorning);
}
四、RocketMQ-Exporter
在RocketMQ-Exporter中有5个定时任务来获取指标数据,定时任务的周期是每分钟一次并且是在每分钟的第15秒开始执行,定时任务的周期可以在其配置文件中进行修改。
task:
count: 5 # num of scheduled-tasks
collectTopicOffset:
cron: 15 0/1 * * * ?
collectConsumerOffset:
cron: 15 0/1 * * * ?
collectBrokerStatsTopic:
cron: 15 0/1 * * * ?
collectBrokerStats:
cron: 15 0/1 * * * ?
collectBrokerRuntimeStats:
cron: 15 0/1 * * * ?
RocketMQ-Exporter中的指标及其含义具体如下:
指标名称 | 含义 |
---|---|
rocketmq_producer_offset | 记录了topic在clusterName→brokerName维度上的offset,这里的offset不是物理偏移量,而是将该topic在broker上各个messagequeue上最大的条数之和 |
rocketmq_topic_dlq_offset | 记录了死信队列在clusterName→brokerName维度上的offset,这里的offset不是物理偏移量,而是将该topic在broker上各个messagequeue上最大的条数之和 |
rocketmq_topic_retry_offset | 记录了重试队列在clusterName→brokerName维度上的offset,这里的offset不是物理偏移量,而是将该topic在broker上各个messagequeue上最大的条数之和 |
rocketmq_group_count | 记录了每个订阅关系中consumerGroup下的在线客户端数量 |
rocketmq_client_consume_fail_msg_count | 一小时内消费失败的消息条数 |
rocketmq_client_consume_fail_msg_tps | 该指标含义是消费失败TPS,statsKey形式为topic@group |
rocketmq_client_consume_ok_msg_tps | consumer在消费完消息后会对消费结果进行处理,在该处理过程中会统计consumer消费成功以及失败的消息条数,该指标含义是消费成功TPS,statsKey形式为topic@group |
rocketmq_client_consume_rt | 该指标含义是consumer消费消息花费的时间,statsKey形式为topic@group |
rocketmq_client_consumer_pull_rt | consumer在从broker端成功拉取到消息后会在consumer端统计本次拉取消息花费的时间(时间差是用拉取到消息的时间减去发送请求前的时间),statsKey形式为topic@group,该指标的含义是consumer拉取消息的响应时间 |
rocketmq_client_consumer_pull_tps | consumer在从broker端成功拉取到消息后会在consumer端统计本次拉取的消息条数,statsKey形式为topic@group,该指标的含义是consumer拉取消息的TPS |
rocketmq_group_retrydiff | 记录了group在该重试队列上brokerOffset和consumerOffset之间的差距 |
rocketmq_group_dlqdiff | 记录了group在该死信队列上brokerOffset和consumerOffset之间的差距 |
rocketmq_group_diff | 记录了group在除重试队列和死信队列外其他的topic上brokerOffset和consumerOffset之间的差距 |
rocketmq_consumer_offset | 记录了group在topic的每个broker上consumerOffset,注意这里是以brokerName为维度 |
rocketmq_group_get_latency_by_storetime | 记录了group订阅的某个topic在broker级别上的消费延迟时间 |
rocketmq_producer_tps | 每秒钟成功写入commitlog中的消息条数,维度是cluster→brokerName |
rocketmq_producer_message_size | 每秒钟成功写入commitlog中的消息大小,维度是cluster→brokerName |
rocketmq_consumer_tps | 每秒钟成功从broker获取的消息条数,维度是cluster→brokerName,group订阅的topic |
rocketmq_consumer_message_size | 每秒钟成功从broker获取的消息大小,维度是cluster→brokerName,group订阅的topic |
rocketmq_send_back_nums | consumer消费失败的消息成功写入SCHEDULE_TOPIC_XXXX中的消息条数,维度是cluster→brokerName |
rocketmq_broker_tps | BROKER_PUT_NUMS在broker端是统计消息存储到broker端的条数,这里该指标数据是表示单位时间存储到broker端的消息条数 |
rocketmq_broker_qps | BROKER_GET_NUMS在broker端是统计从broker端成功获取消息的条数,这里该指标数据是表示单位时间内成功从broker端成功获取消息的条数 |
rocketmq_brokeruntime_msg_put_total_today_now | 以broker为维度,统计了消息写入commitlog的次数 |
rocketmq_brokeruntime_msg_gettotal_today_now | GetMessage方法中统计的,是在成功获取到数据后自增的,可以理解为从broker获取消息的次数 |
rocketmq_brokeruntime_msg_puttotal_todaymorning | 以broker为维度,统计了消息写入commitlog的次数 |
rocketmq_brokeruntime_msg_gettotal_todaymorning | GetMessage方法中统计的,是在成功获取到数据后自增的,可以理解为从broker获取消息的次数 |
rocketmq_brokeruntime_msg_puttotal_yesterdaymorning | 以broker为维度,统计了消息写入commitlog的次数 |
rocketmq_brokeruntime_msg_gettotal_yesterdaymorning | GetMessage方法中统计的,是在成功获取到数据后自增的,可以理解为从broker获取消息的次数 |
rocketmq_brokeruntime_send_threadpoolqueue_headwait_timemills | sendMessageExecutor其队列中头部元素等待时间(计算方式是用现在的时间-队列头部元素创建时间) |
brokerRuntimeQueryThreadPoolQueueHeadWaitTimeMills | pullMessageExecutor其队列中头部元素等待时间(计算方式是用现在的时间-队列头部元素创建时间) |
rocketmq_brokeruntime_pull_threadpoolqueue_headwait_timemills | queryMessageExecutor其队列中头部元素等待时间(计算方式是用现在的时间-队列头部元素创建时间) |
rocketmq_brokeruntime_query_threadpoolqueue_size | QueryMessageProccessor队列中包含发送消息请求数 |
rocketmq_brokeruntime_pull_threadpoolqueue_size | PullMessageProccessor队列中包含拉取消息请求数 |
rocketmq_brokeruntime_send_threadpoolqueue_capacity | SendMessageProccessor队列中最大能缓存的发消息请求数 |
rocketmq_brokeruntime_pull_threadpoolqueue_capacity | PullMessageProccessor队列中最大能缓存的拉取消息请求数 |
rocketmq_brokeruntime_remain_howmanydata_toflush | commitlog中还有多少消息需要flush |
rocketmq_brokeruntime_commitlog_minoffset | commitlog中最小物理offset |
rocketmq_brokeruntime_commitlog_maxoffset | commitlog中最大物理offset |
rocketmq_brokeruntime_dispatch_maxbuffer | 4.9.1版本中没有找到使用的地方 |
rocketmq_brokeruntime_consumequeue_disk_ratio | consumequeue的空间使用率 |
rocketmq_brokeruntime_commitlog_disk_ratio | commitlog的空间使用率 |
rocketmq_brokeruntime_pagecache_lock_time_mills | RocketMQ在写消息时会加锁,这个是用来计算当前时间与上次加锁时的时间差值 |
rocketmq_brokeruntime_getmessage_entire_time_max | getMessage(…)方法中会统计获取消息的花费时间,该指标记录获取消息花费时间的最大值 |
rocketmq_brokeruntime_putmessage_times_total | 该指标是对putMessageTopicTimesTotal中的的value进行求和,即表示成功追加到commitlog中的消息总条数,如果和为0,则该指标的值被赋值为1 |
rocketmq_brokeruntime_send_threadpool_queue_size | SendMessageProccessor队列中包含发消息请求数 |
rocketmq_brokeruntime_start_accept_sendrequest_time | broker开始接收请求的时间,该字段的set方法没有被调用,初始值是0 |
rocketmq_brokeruntime_putmessage_entire_time_max | broker端普通消息或者批量消息写入内存中花费的时间统计,花费的时间分13级别,同时会记录所有花费的时间中的最大值,该指标含义就是统计消息写入broker内存中花费的最大时间 |
rocketmq_brokeruntime_earliest_message_timestamp | RocketMQ中最早的一条消息的存储时间 |
rocketmq_brokeruntime_remain_transientstore_buffer_numbs | 保存异步刷新消息到磁盘之前存储消息的缓冲区大小 |
rocketmq_brokeruntime_query_threadpool_queue_capacity | QueryMessageProccessor队列中最大能缓存的请求数 |
rocketmq_brokeruntime_put_message_average_size | 平均一次写入broker内存的消息大小 |
rocketmq_brokeruntime_put_message_size_total | 消息写入broker的总大小 |
rocketmq_brokeruntime_dispatch_behind_bytes | RocketMQ会对每条消息构建对应的consumequeue和index,该指标表示剩余多少字节消息待创建consumequeue和index |
rocketmq_brokeruntime_pmdt_0ms | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[<=0ms]范围的消息条数 |
rocketmq_brokeruntime_pmdt_0to10ms | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[0-10ms]范围的消息条数 |
rocketmq_brokeruntime_pmdt_10to50ms | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[10-50ms]范围的消息条数 |
rocketmq_brokeruntime_pmdt_50to100ms | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[50-100ms] 范围的消息条数 |
rocketmq_brokeruntime_pmdt_100to200ms | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[100-200ms]范围的消息条数 |
rocketmq_brokeruntime_pmdt_200to500ms | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[200-500ms] 范围的消息条数 |
rocketmq_brokeruntime_pmdt_500to1s | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[500ms-1s]范围的消息条数 |
rocketmq_brokeruntime_pmdt_1to2s | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[1-2s] 范围的消息条数 |
rocketmq_brokeruntime_pmdt_2to3s | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[2-3s]范围的消息条数 |
rocketmq_brokeruntime_pmdt_3to4s | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[3-4s] 范围的消息条数 |
rocketmq_brokeruntime_pmdt_4to5s | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[4-5s]范围的消息条数 |
rocketmq_brokeruntime_pmdt_5to10s | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[5-10s] 范围的消息条数 |
rocketmq_brokeruntime_pmdt_10stomore | broker端普通消息或者批量消息写入内存中花费的时间统计,该指标统计的是花费时间在[10s-] 范围的消息条数 |
rocketmq_brokeruntime_commitlogdir_capacity_total | commitlog目录空间总大小 |
rocketmq_brokeruntime_commitlogdir_capacity_free | commitlog目录还有多少空间可用 |