TT Bigdata TT Bigdata
首页
  • 部署专题

    • 常规安装
    • 一键部署
  • 组件专题

    • 安装教程
    • 魔改分享
  • 版本专题

    • 更新说明
    • BUG临时处理
  • Ambari-Env

    • 环境准备
    • 开始使用
  • 组件编译

    • 专区—Ambari
    • 专区—Bigtop-官方组件
    • 专区—Bigtop-扩展组件
  • 报错解决

    • 专区—Ambari
    • 专区—Bigtop
  • 其他技巧

    • APT仓库增量更新
    • Maven镜像加速
    • Gradle镜像加速
    • Bower镜像加速
    • 虚拟环境思路
    • R环境安装+一键安装脚本
    • Ivy配置私有镜像仓库
    • Node.js 多版本共存方案
    • Ambari Web本地启动
    • Npm镜像加速
    • PostgreSQL快速安装
    • Temurin JDK 23快速安装
  • 成神之路

    • 专区—Ambari
    • 专区—Ambari-Metrics
    • 专区—Bigtop
  • 集成案例

    • Redis集成教学
    • Dolphin集成教学
    • Doris集成教学
    • 持续整理...
  • 核心代码

    • 各组件代码
    • 通用代码模板
  • 国产化&其他系统

    • Rocky系列
    • Ubuntu系列
  • Grafana监控方案

    • Ambari-Metrics插件
    • Infinity插件
  • 支持&共建

    • 蓝图愿景
    • 合作共建
登陆
GitHub (opens new window)

JaneTTR

数据酿造智慧,每一滴都是沉淀!
首页
  • 部署专题

    • 常规安装
    • 一键部署
  • 组件专题

    • 安装教程
    • 魔改分享
  • 版本专题

    • 更新说明
    • BUG临时处理
  • Ambari-Env

    • 环境准备
    • 开始使用
  • 组件编译

    • 专区—Ambari
    • 专区—Bigtop-官方组件
    • 专区—Bigtop-扩展组件
  • 报错解决

    • 专区—Ambari
    • 专区—Bigtop
  • 其他技巧

    • APT仓库增量更新
    • Maven镜像加速
    • Gradle镜像加速
    • Bower镜像加速
    • 虚拟环境思路
    • R环境安装+一键安装脚本
    • Ivy配置私有镜像仓库
    • Node.js 多版本共存方案
    • Ambari Web本地启动
    • Npm镜像加速
    • PostgreSQL快速安装
    • Temurin JDK 23快速安装
  • 成神之路

    • 专区—Ambari
    • 专区—Ambari-Metrics
    • 专区—Bigtop
  • 集成案例

    • Redis集成教学
    • Dolphin集成教学
    • Doris集成教学
    • 持续整理...
  • 核心代码

    • 各组件代码
    • 通用代码模板
  • 国产化&其他系统

    • Rocky系列
    • Ubuntu系列
  • Grafana监控方案

    • Ambari-Metrics插件
    • Infinity插件
  • 支持&共建

    • 蓝图愿景
    • 合作共建
登陆
GitHub (opens new window)
  • 试读&介绍

  • Ambari-Metrics解读【简写AMS】

    • 源码下载及环境初始化
    • 项目目录及模块解读
    • AMS-Collector剖析

    • AMS-Collector表结构实战

      • [监控表] — 基础表结构梳理
      • [监控表] — 业务表结构梳理
      • [监控表] — 基础表和业务表关联关系
      • [监控表] — Java代码验证表关系
      • [监控表] — 建表切分策略
        • 一、前序章节回忆
        • 二、章节引入:为何要在建表阶段“切分”?
        • 三、总体流程鸟瞰(从“参数→计算→建表”)
        • 四、关键类与方法定位
          • 1、代码落点与职责
          • 2、示意源码(节选 + 释义)
        • 五、参数来源与公式解释
          • 1、参数来自哪里?
          • 2、目标 Region 数如何给?
        • 六、指标清单如何构造与排序?
          • 1、源数据来自哪里?
          • 2、Master/Slave 差异
          • 3、指标体量直观示例
        • 七、如何“从清单到切分点”?
          • 1、步长与取点规则
          • 2、为什么用“cluster 级 UUID”作为切分点?
          • 3、均衡性分析
        • 八、Phoenix 建表与 SPLIT ON 拼接
          • 1、主键与切分维度
          • 2、字节序与格式
          • 3、示例 SQL(示意)
        • 九、写热点是如何被彻底规避的?
          • 1、RowKey 层面
          • 2、组件/指标层面
          • 3、资源承载层面
        • 十、验证与运维检查(强烈建议掌握)
          • 1、建表后核对 Region 列表
          • 2、Phoenix Explain 观察“并行度”
          • 3、监控维度
        • 十一、参数调优与规模化建议
          • 1、不同规模集群的经验映射
          • 2、手工加 Region 的正确姿势
          • 3、何时重建更合适?
        • 十二、常见误区与规避清单
        • 十三、落地清单(Checklist)
        • 十四、配图与源码定位(便捷回顾)
          • 1、SPLIT ON 实参注入点
          • 2、计算切分点核心方法
          • 3、Master/Slave 步长与指标体量示例
      • [监控表] — Master组件和Slave组件判别
      • [监控表] — Uuid 16 位构成全解析
    • AMS-Collector接口实战

    • AMS-Monitor剖析

  • Metrics2协议解读

  • Hadoop-SINK剖析

  • Hbase-SINK剖析

  • Kafka-SINK剖析

  • 自定义组件接入监控

  • 其他监控方案

  • GOD-Ambari-Metrics
  • Ambari-Metrics解读【简写AMS】
  • AMS-Collector表结构实战
JaneTTR
2025-09-09
目录

[监控表] — 建表切分策略避免热点

# 一、前序章节回忆

笔记

我们已在前文完成三件事:

  • 业务表(秒表、分表、聚合表)的执行逻辑梳理;
  • 基础表与业务表的关联关系;
  • 控制器/Provider 协作模式与启动主链路。

本文继续深入 hBaseAccessor.initMetricSchema(),把 “建表切分策略” 拆到每一步,让“如何避免热点”变成具体可操作的工程实践。

// 入口之一:初始化业务表结构与分区
hBaseAccessor.initMetricSchema();
1
2

# 二、章节引入:为何要在建表阶段“切分”?

读写热点的本质

AMS 的高频写表(如秒表、分钟表)若只建为单 Region,大量相同前缀的 RowKey 会在同一 Region 聚集,写入与 compact 压力集中到单台 RegionServer,形成热点。

如下图所示,高频表在建表时会添加 SPLIT ON(...),使其天然跨多个 Region:

这里的“切分点”是什么?

Phoenix 的 SPLIT ON(...) 接收有序的 rowkey 前缀边界(字节序),HBase 会按该边界创建多个 Region。AMS 的做法是: 以Metric 维度的 UUID 前缀作为切分点,将不同 Metric 的写入均摊到不同 Region。

# 三、总体流程鸟瞰(从“参数→计算→建表”)

flowchart TD
  A[加载 hbase-site.xml / ams-site.xml] --> B[读取内存阈值与 flush 策略]
  B --> C[估算 maxInMemoryRegions]
  C --> D[确定目标 Region 数: 精度表/聚合表]
  D --> E[收集并排序 Metric 清单: Master/Slave, appId, metricName]
  E --> F[按步长选点: idx=ceil(total/regionCount)]
  F --> G[将选中 Metric 转换为 UUID (cluster-level)]
  G --> H[得到 splitPoints(precision/aggregate)]
  H --> I[拼接 Phoenix 建表 SQL: SPLIT ON(...)]
1
2
3
4
5
6
7
8
9

关键边界

  • RowKey 设计:精度表通常以 UUID + SERVER_TIME DESC 作为联合主键 → 以 UUID 做切分最“稳”。
  • SPLIT ON 顺序:切分点必须有序且去重,否则建表报错或 Region 不如预期。

# 四、关键类与方法定位

# 1、代码落点与职责

模块/类名 作用 备注
hBaseAccessor.initMetricSchema() 入口 初始化所有业务表(精度/聚合/临时)
computeSplitPoints() 计算切分点 决定目标 Region 数与具体 split 列表
getSortedMetricListForSplitPoint() 构造并排序 Metric 清单 按组件(Master/Slave)、指标体量构造列表
timelineMetricMetadataManager.getUuid(...) 度量标识映射 将(metricName, appId, …)映射到 UUID
CREATE TABLE ... SPLIT ON(...) 最终建表 Phoenix/HBase 侧实际生效

# 2、示意源码(节选 + 释义)

protected void computeSplitPoints() {
  // 4.1 依据 RegionServer 内存与 flush 策略估算“可承载的活跃 Region 数”
  double memstoreMaxMemory = hbaseMemstoreUpperLimit * hbaseTotalHeapsize;
  int maxInMemoryRegions = (int) ((memstoreMaxMemory / hbaseMemstoreFlushSize) - OTHER_TABLE_STATIC_REGIONS);

  // 4.2 分别给“精度表/聚合表”设定目标 Region 数(默认值 + 动态放大系数)
  int targetPrecisionTableRegionCount = MINIMUM_PRECISION_TABLE_REGIONS;
  int targetAggregateTableRegionCount = MINIMUM_AGGREGATE_TABLE_REGIONS;

  if (maxInMemoryRegions > 2) {
    targetPrecisionTableRegionCount = Math.max(4, (int)(0.70 * maxInMemoryRegions));
    targetAggregateTableRegionCount = Math.max(2, (int)(0.15 * maxInMemoryRegions));
  }

  // 4.3 收集待切分的 Metric 清单(Master/Slave 组件)
  List<MetricApp> metricList = new ArrayList<>();
  for (String component : masterComponents) {
    metricList.addAll(getSortedMetricListForSplitPoint(component, false)); // false=master
  }
  for (String component : slaveComponents) {
    metricList.addAll(getSortedMetricListForSplitPoint(component, true));  // true=slave
  }

  // 4.4 按“步长 idx = ceil(total / regionCount)”选取分隔点
  int totalMetricLength = metricList.size();

  if (targetPrecisionTableRegionCount > 1) {
    int idx = (int) Math.ceil(totalMetricLength / targetPrecisionTableRegionCount);
    int index = idx;
    for (int i = 0; i < targetPrecisionTableRegionCount; i++) {
      if (index < totalMetricLength - 1) {
        MetricApp m = metricList.get(index);
        byte[] uuid = timelineMetricMetadataManager.getUuid(
            new TimelineClusterMetric(m.metricName, m.appId, null, -1), true);
        precisionSplitPoints.add(uuid);
        index += idx;
      }
    }
  }

  if (targetAggregateTableRegionCount > 1) {
    int idx = (int) Math.ceil(totalMetricLength / targetAggregateTableRegionCount);
    int index = idx;
    for (int i = 0; i < targetAggregateTableRegionCount; i++) {
      if (index < totalMetricLength - 1) {
        MetricApp m = metricList.get(index);
        byte[] uuid = timelineMetricMetadataManager.getUuid(
            new TimelineClusterMetric(m.metricName, m.appId, null, -1), true);
        aggregateSplitPoints.add(uuid);
        index += idx;
      }
    }
  }
}
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

# 五、参数来源与公式解释

# 1、参数来自哪里?

参数 含义 建议来源
hbaseTotalHeapsize RegionServer JVM 堆大小 hbase-env.sh / 监控探测
hbaseMemstoreUpperLimit memstore 使用上限(比例) hbase-site.xml:hbase.regionserver.global.memstore.upperLimit
hbaseMemstoreFlushSize 单 Region flush 阈值 hbase-site.xml:hbase.hregion.memstore.flush.size
OTHER_TABLE_STATIC_REGIONS 预留给其他系统表的 Region 数 代码常量/经验值
MINIMUM_*_TABLE_REGIONS 最小 Region 数 代码常量/配置

估算含义

maxInMemoryRegions ≈ (可用 memstore 总量 / 单 Region flush 阈值) - 预留 它是单机能稳定承载的活跃 Region 数近似值,后续映射为“目标分区数”。

# 2、目标 Region 数如何给?

  • 精度表(秒/分级):默认至少 4 个,若内存足够 → 取 max * 70%;
  • 聚合表(5m/1h/1d):默认至少 2 个,若内存足够 → 取 max * 15%。

为什么比例不同?

精度表写入频次高,必须更强的并发承载能力;聚合表写入相对低,但读查询较多,Region 数偏保守更有利于 scan 合并。

# 六、指标清单如何构造与排序?

# 1、源数据来自哪里?

  • 组件与指标的“定义清单”通常来自 指标定义文件(dat) 与 组件注册信息(appId);
  • getSortedMetricListForSplitPoint(component, isSlave) 会把同一组件下的metricName + appId拼成候选列表。

# 2、Master/Slave 差异

  • Slave 组件(如 Datanode、NodeManager):数量多、指标体量大 → 步长粗(≈50 条/点);
  • Master 组件(如 HBase Master、RM):实例少、指标相对精简 → 步长细(≈10 条/点)。

这样做的好处

  • 大体量组件不会把切分点“挤”在少数几个指标附近;
  • 小体量但关键的 Master 指标也能得到较均匀的 Region 覆盖。

# 3、指标体量直观示例

# 七、如何“从清单到切分点”?

# 1、步长与取点规则

公式

设 N = metricList.size(),目标 Region 数为 R (>1), 则步长 idx = ceil(N / R); 取点索引序列:idx, 2*idx, 3*idx, ...(越界/末尾会跳过)。

# 2、为什么用“cluster 级 UUID”作为切分点?

// 注意:以 cluster 级维度构造 UUID —— 不带 instanceId/host
new TimelineClusterMetric(metricName, appId, null, -1)
1
2
  • RowKey 前缀:精度表主键一般是 (UUID, SERVER_TIME DESC);
  • 切分点只需对“首维”生效 → 用 UUID 作为边界即可把写入均衡到不同 Region;
  • 不带实例:避免千万级 instanceId/host 维度导致切分点数量失控。

# 3、均衡性分析

  • 指标越多的组件,越有机会被选为“分界点” → 数据量与 Region 覆盖大致正相关;
  • Master/Slave 的不同“步长”进一步减少极端倾斜;
  • 结合 precision/aggregate 两套切分点集合,精度与聚合层分别达成合理均衡。

# 八、Phoenix 建表与 SPLIT ON 拼接

# 1、主键与切分维度

表 主键示例 切分维度
METRIC_RECORD_UUID(秒/分精度) (UUID, SERVER_TIME DESC) UUID
METRIC_AGGREGATE_UUID(聚合层) (UUID, SERVER_TIME) UUID

提示

若主键首列不是 UUID,则需要相应调整切分点生成逻辑,确保切分点落在主键排序最前沿的维度。

# 2、字节序与格式

  • Phoenix 的 SPLIT ON 期望字节序(通常写为 X'..' 或 \x.. 形式);
  • 由 timelineMetricMetadataManager.getUuid(...) 返回的 byte[] 会按升序写入到 SPLIT ON(...)。

# 3、示例 SQL(示意)

CREATE TABLE METRIC_RECORD_UUID (
  UUID BINARY(16) NOT NULL,
  SERVER_TIME UNSIGNED_LONG NOT NULL,
  METRIC_SUM DOUBLE,
  METRIC_COUNT DOUBLE,
  METRIC_MIN DOUBLE,
  METRIC_MAX DOUBLE,
  CONSTRAINT PK PRIMARY KEY (UUID, SERVER_TIME DESC)
)
SPLIT ON (
  X'1009A3...', X'201F7B...', X'3090CC...', X'40AF12...'
);
1
2
3
4
5
6
7
8
9
10
11
12

常见坑

  • 切分点无序 → Phoenix 报错或被重排;
  • 切分点太少 → Region 不足,热点仍在;
  • 切分点太多 → Region 过多,增加 compaction/开销;
  • RowKey 设计与切分维度不一致 → 切分“无效化”。

# 九、写热点是如何被彻底规避的?

# 1、RowKey 层面

  • 以 UUID 为前缀的联合主键使得同一时间片的数据不会按时间聚集在同一 Region;
  • SERVER_TIME 放在二级列(且常为 DESC)→ 写入在“同一 UUID”内部更趋向时间散列。

# 2、组件/指标层面

  • 以 metric/appId 映射到 UUID,天然把不同指标族路由到不同 Region;
  • Master/Slave 的步长差异 + 指标体量排序 → 缩小“超大组件吞噬 Region”的风险。

# 3、资源承载层面

  • 目标 Region 数与 maxInMemoryRegions 挂钩 → 保证单机不会过载;
  • 精度表/聚合表独立计算 → 写多与读多层面各取所需。

# 十、验证与运维检查(强烈建议掌握)

# 1、建表后核对 Region 列表

HBase Shell

  • list_namespace_tables 'default' / describe 'METRIC_RECORD_UUID'
  • scan 'hbase:meta' 过滤该表,确认 Region 边界是否与 SPLIT ON 对齐

# 2、Phoenix Explain 观察“并行度”

  • EXPLAIN SELECT ... FROM METRIC_RECORD_UUID ...
  • 关注输出中的 N-WAY PARALLEL SCAN、RANGE SCAN、FIRST KEY ONLY 等关键词,确认是否多 Region 扫描。

# 3、监控维度

观察项 工具/位置 判定要点
Region 分布 HBase UI / hbase shell Region 个数 ≈ 目标;分布均衡
写入吞吐 AMS Collector 日志/指标 队列无持续积压
Compact 频次 HBase UI 频次与时长是否异常
GC/堆使用 RegionServer/JVM 符合期望阈值,无频繁 Full GC

# 十一、参数调优与规模化建议

# 1、不同规模集群的经验映射

规模 RS 堆(示例) upperLimit flush.size 精度表 Region 目标 聚合表 Region 目标
小型(≤5 RS) 8–16G 0.4–0.5 128–256MB 4–8 2–4
中型(5–20 RS) 16–32G 0.4–0.6 256–512MB 8–32 4–12
大型(20+ RS) 32–64G 0.5–0.7 512–1024MB 32–96 8–24

笔记

此表为起步建议,以“写入峰值/查询模式/硬件配置”为准微调,重点观察 compaction 与 GC。

# 2、手工加 Region 的正确姿势

  • 业务量增长后,可用 SPLIT 指令在线加 Region;
  • 优先对写热点明显的 UUID 前缀区间进行二分;
  • 逐步分裂,避免一次性倍增导致 compaction 风暴。

# 3、何时重建更合适?

  • 早期未使用 SPLIT ON、RowKey 选型不当造成“天然热点”;
  • 单表已达超大 Region 数(> 数千)且历史数据多,可结合归档/冷热分层策略重建。

# 十二、常见误区与规避清单

误区清单

  • 只看 QPS,不核对 RowKey 维度 与 切分维度 是否一致;
  • 误把 SERVER_TIME 当切分点 → 时间热点没解决;
  • 所有表统一用同一套切分点 → 忽略“精度 vs 聚合”的模式差异;
  • 忽略 upperLimit / flush.size,盲目堆 Region 数 → 反而导致频繁 flush/compact。

# 十三、落地清单(Checklist)

  • [ ] 明确 RowKey:(UUID, SERVER_TIME[ DESC ])
  • [ ] 校核 HBase 内存参数:upperLimit / flush.size / 堆
  • [ ] 估算 maxInMemoryRegions
  • [ ] 设定精度/聚合的目标 Region 数(≥最小值)
  • [ ] 收集 Metric 清单(Master/Slave + appId + metricName)并排序
  • [ ] 用 idx=ceil(N/R) 取点 → 生成 UUID 切分点
  • [ ] Phoenix 建表附带 SPLIT ON(...)(有序/去重)
  • [ ] HBase/Phoenix 校验 Region 与 Explain Plan
  • [ ] 运行期监控写入/compact/GC,必要时逐步加 Region

# 十四、配图与源码定位(便捷回顾)

# 1、SPLIT ON 实参注入点

# 2、计算切分点核心方法

# 3、Master/Slave 步长与指标体量示例

#Ambari#Ambari-Metrics#TimelineService#HBase#Phoenix#表模型初始化#预分区#热点规避#Split策略
[监控表] — Java代码验证表关系
[监控表] — Master组件和Slave组件判别

← [监控表] — Java代码验证表关系 [监控表] — Master组件和Slave组件判别→

最近更新
01
[/metrics/metadata] — 元数据查询和使用 GET
09-12
02
[/metrics/metadata] — 请求完整链路解读
09-12
03
[/metrics/metadata] — 缓存数据装载 Phoenix
09-12
更多文章>
Theme by Vdoing | Copyright © 2017-2025 JaneTTR | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式