[/metrics] — getUuidsForGetMetricQuery精讲
# 一、函数定位与目标
List<byte[]> uuids = metricMetadataManager.getUuidsForGetMetricQuery(
metricFunctions.keySet(),
hostnames,
applicationId,
instanceId,
transientMetricNames);
1
2
3
4
5
6
2
3
4
5
6
提示
本函数的职责是:把“请求参数”映射为“一组待查询的 UUID”。
- 指标 UUID:16B
- 主机 UUID:4B
- 复合 UUID(指标+主机):20B(=16B + 4B) 是否走通配符扫描、是否拼接主机 UUID,完全由参数与通配符决定。
# 二、入参回顾:metricFunctions.keySet()
该参数是什么?在此文解读了。
- 输入示例
[
"cpu.usage",
"cpu.usage._avg",
"cpu.usage._max",
"rpc.rpcdetailed.numOpen._diff._sum"
]
1
2
3
4
5
6
2
3
4
5
6
解析后的
metricsFunctions
:key =
cpu.usage
- value = [ (VALUE, NONE), (AVG, NONE), (MAX, NONE) ]
key =
rpc.rpcdetailed.numOpen
- value = [ (SUM, DIFF) ]
因此:
metricsFunctions.keySet() = ["cpu.usage", "rpc.rpcdetailed.numOpen"]
1
后续查询只围绕“干净的指标名”开展,函数/后缀不参与元数据匹配。
# 三、完整源码与分支判定
public List<byte[]> getUuidsForGetMetricQuery(Collection<String> metricNames,
List<String> hostnames,
String appId,
String instanceId,
List<String> transientMetricNames) {
List<byte[]> uuids = new ArrayList<>();
boolean metricNameHasWildcard = false;
for (String metricName : metricNames) {
if (hasWildCard(metricName)) {
metricNameHasWildcard = true;
break;
}
}
boolean hostNameHasWildcard = false;
if (CollectionUtils.isNotEmpty(hostnames)) {
for (String hostname : hostnames) {
if (hasWildCard(hostname)) {
hostNameHasWildcard = true;
break;
}
}
}
if (hasWildCard(instanceId) || hasWildCard(appId) || hostNameHasWildcard || metricNameHasWildcard) {
try {
List<TimelineMetricMetadata> metricMetadataFromStore =
hBaseAccessor.scanMetricMetadataForWildCardRequest(metricNames, appId, instanceId);
List<byte[]> hostUuidsFromStore =
hBaseAccessor.scanHostMetadataForWildCardRequest(hostnames);
for (TimelineMetricMetadata matchedEntry : metricMetadataFromStore) {
if (matchedEntry.getUuid() != null) {
if (CollectionUtils.isNotEmpty(hostnames)) {
for (byte[] hostUuidEntry : hostUuidsFromStore) {
uuids.add(ArrayUtils.addAll(matchedEntry.getUuid(), hostUuidEntry));
}
} else {
uuids.add(matchedEntry.getUuid());
}
} else if (isTransientMetric(matchedEntry.getMetricName(), matchedEntry.getAppId())) {
transientMetricNames.add(matchedEntry.getMetricName());
}
}
return uuids;
} catch (SQLException e) {
LOG.error("Unable to query metadata table to check satisfying metric keys for wildcard request : " + e);
return uuids;
}
} else {
if (CollectionUtils.isNotEmpty(hostnames)) {
if (CollectionUtils.isNotEmpty(metricNames)) {
// Skip transient metrics
for (String metricName : metricNames) {
if (isTransientMetric(metricName, appId)) {
transientMetricNames.add(metricName);
continue;
}
TimelineMetric metric = new TimelineMetric();
metric.setMetricName(metricName);
metric.setAppId(appId);
metric.setInstanceId(instanceId);
for (String hostname : hostnames) {
metric.setHostName(hostname);
byte[] uuid = getUuid(metric, false);
if (uuid != null) {
uuids.add(uuid);
}
}
}
} else {
for (String hostname : hostnames) {
byte[] uuid = getUuidForHostname(hostname, false);
if (uuid != null) {
uuids.add(uuid);
}
}
}
} else {
for (String metricName : metricNames) {
if (isTransientMetric(metricName, appId)) {
transientMetricNames.add(metricName);
continue;
}
TimelineClusterMetric metric = new TimelineClusterMetric(metricName, appId, instanceId, -1l);
byte[] uuid = getUuid(metric, false);
if (uuid != null) {
uuids.add(uuid);
}
}
}
}
return uuids;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
判定规则(结论先行)
任一参数含通配符 → 走元数据扫描(metric 与 host 分表扫描)→ 命中后拼接成 20B 或仅取 16B;
无通配符 → 走直取:
- 传 host 且传 metric → 直接算出 20B;
- 仅传 host → 获取 4B host UUID;
- 仅传 metric → 获取 16B cluster UUID;
transient 指标 → 不取 UUID,记录到
transientMetricNames
。
# 四、通配符场景:扫描 + SQL 拼装 + 复合 UUID
我们以这个请求为例:
放到 Apifox 中:
关键逻辑调用:
# 1、元数据分表扫描(基础 SQL)
- 指标元数据(有
METRIC_NAME
的场景):
SELECT METRIC_NAME, APP_ID, INSTANCE_ID, UUID
FROM METRICS_METADATA_UUID
1
2
2
- 主机元数据(只关心 HOSTNAME → UUID 的映射):
SELECT HOSTNAME, UUID
FROM HOSTED_APPS_METADATA_UUID
1
2
2
# 2、WHERE 条件:根据 IN / LIKE 动态拼装
指标名条件(appendMetricNameClause
)核心实现:
protected boolean appendMetricNameClause(StringBuilder sb) {
boolean appendConjunction = false;
List<String> metricsLike = new ArrayList<>();
List<String> metricsIn = new ArrayList<>();
if (getMetricNames() != null) {
for (String name : getMetricNames()) {
if (name.contains("%")) { metricsLike.add(name); }
else { metricsIn.add(name); }
}
sb.append("(");
// IN 子句
if (CollectionUtils.isNotEmpty(metricsIn)) {
sb.append("METRIC_NAME");
if (metricNamesNotCondition) { sb.append(" NOT"); }
sb.append(" IN (");
for (int i = 0; i < metricsIn.size(); i++) {
sb.append("?");
if (i < metricsIn.size() - 1) { sb.append(", "); }
}
sb.append(")");
appendConjunction = true;
}
// IN 与 LIKE 间的 OR/AND
if (CollectionUtils.isNotEmpty(metricsIn) && CollectionUtils.isNotEmpty(metricsLike)) {
sb.append(metricNamesNotCondition ? " AND " : " OR ");
}
// LIKE 子句(可多项 OR/AND 串联)
if (CollectionUtils.isNotEmpty(metricsLike)) {
for (int i = 0; i < metricsLike.size(); i++) {
sb.append("METRIC_NAME");
if (metricNamesNotCondition) { sb.append(" NOT"); }
sb.append(" LIKE ?");
if (i < metricsLike.size() - 1) {
sb.append(metricNamesNotCondition ? " AND " : " OR ");
}
}
appendConjunction = true;
}
if (appendConjunction) { sb.append(")"); }
// 绑定参数顺序:先 IN,后 LIKE
metricNames.clear();
if (CollectionUtils.isNotEmpty(metricsIn)) { metricNames.addAll(metricsIn); }
if (CollectionUtils.isNotEmpty(metricsLike)) { metricNames.addAll(metricsLike); }
}
return appendConjunction;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
等价 SQL 形态(仅示意):
SELECT METRIC_NAME, APP_ID, INSTANCE_ID, UUID
FROM METRICS_METADATA_UUID
WHERE (
METRIC_NAME [NOT] IN (?,?,?)
[AND|OR] METRIC_NAME [NOT] LIKE ?
[AND|OR] METRIC_NAME [NOT] LIKE ?
)
1
2
3
4
5
6
7
2
3
4
5
6
7
实际查询结果示意:
主机条件(appendHostnameClause
)核心实现:
protected boolean appendHostnameClause(StringBuilder sb, boolean appendConjunction) {
boolean hostnameContainsRegex = false;
if (hostnames != null) {
for (String hostname : hostnames) {
if (hostname.contains("%")) { hostnameContainsRegex = true; break; }
}
}
StringBuilder hostnamesCondition = new StringBuilder();
if (hostnameContainsRegex) {
hostnamesCondition.append(" (");
for (String hostname : getHostnames()) {
if (hostnamesCondition.length() > 2) { hostnamesCondition.append(" OR "); }
hostnamesCondition.append("HOSTNAME LIKE ?");
}
hostnamesCondition.append(")");
appendConjunction = append(sb, appendConjunction, getHostnames(), hostnamesCondition.toString());
} else if (CollectionUtils.isNotEmpty(hostnames)) {
for (String hostname : getHostnames()) {
if (hostnamesCondition.length() > 0) { hostnamesCondition.append(" ,"); }
else { hostnamesCondition.append(" HOSTNAME IN ("); }
hostnamesCondition.append('?');
}
hostnamesCondition.append(')');
appendConjunction = append(sb, appendConjunction, getHostnames(), hostnamesCondition.toString());
} else {
appendConjunction = append(sb, appendConjunction, getHostnames(), " HOSTNAME = ?");
}
return appendConjunction;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
等价 SQL 形态(仅示意):
SELECT HOSTNAME, UUID
FROM HOSTED_APPS_METADATA_UUID
WHERE (
HOSTNAME [NOT] IN (?, ?, ?)
[AND|OR] HOSTNAME [NOT] LIKE ?
[AND|OR] HOSTNAME [NOT] LIKE ?
)
1
2
3
4
5
6
7
2
3
4
5
6
7
查询结果示意:
# 3、复合 UUID 拼装(20B)
当同时命中“指标元数据(16B)”与“主机元数据(4B)”时,最终会将二者 addAll 拼接 为 20B:
性能提醒
通配符越多,扫描范围越大。优先使用精确指标名与明确主机列表;必要时配合 limit/precision
控参,降低无效命中与元数据表压力。
# 五、无通配符场景:直取路径与分支
源码中的另一条主干逻辑如下(节选,已按注释归纳):
if (CollectionUtils.isNotEmpty(hostnames)) {
if (CollectionUtils.isNotEmpty(metricNames)) {
// metric + host → 计算出 20B
for (String metricName : metricNames) {
if (isTransientMetric(metricName, appId)) {
transientMetricNames.add(metricName);
continue;
}
TimelineMetric metric = new TimelineMetric();
metric.setMetricName(metricName);
metric.setAppId(appId);
metric.setInstanceId(instanceId);
for (String hostname : hostnames) {
metric.setHostName(hostname);
byte[] uuid = getUuid(metric, false);
if (uuid != null) { uuids.add(uuid); }
}
}
} else {
// 只有 host → 取 4B host UUID
for (String hostname : hostnames) {
byte[] uuid = getUuidForHostname(hostname, false);
if (uuid != null) { uuids.add(uuid); }
}
}
} else {
// 只有 metric → 取 16B cluster UUID
for (String metricName : metricNames) {
if (isTransientMetric(metricName, appId)) {
transientMetricNames.add(metricName);
continue;
}
TimelineClusterMetric metric = new TimelineClusterMetric(metricName, appId, instanceId, -1L);
byte[] uuid = getUuid(metric, false);
if (uuid != null) { uuids.add(uuid); }
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
直取的优势
- 无额外扫描:不访问元数据表;
- 定位清晰:参数 → UUID 一步到位;
- 更稳定:规避 LIKE/OR 过多导致的执行计划不稳定。
# 六、参数 → UUID 形态映射表
场景 | metricNames | hostnames | appId/instanceId | 通配符 | SQL 路径 | 返回 UUID 形态 |
---|---|---|---|---|---|---|
A | 有 | 有 | 任意 | 否 | 直取 | 20B(16B 指标 + 4B 主机) |
B | 有 | 无 | 任意 | 否 | 直取 | 16B(Cluster 级) |
C | 无 | 有 | 任意 | 否 | 直取 | 4B(仅主机) |
D | 有/无 | 有/无 | 任意 | 是 | 扫描 METRICS_METADATA_UUID / HOSTED_APPS_METADATA_UUID | 命中后:20B 或 16B |
E | transient | 任意 | 任意 | 任意 | 跳过 | 不返回 UUID,记录到 transientMetricNames |
注:是否返回 20B 取决于是否传入/命中主机;仅指标时为 16B。
- 01
- [/metrics/aggregated] — 聚合数据范围 检查点09-19
- 02
- [/metrics] — 反向分析接口参数 请求抓包09-17
- 03
- [/metrics] — 普通指标写入方法 POST09-17