Apache Hertzbeat:開箱即用的實時監控系統
一 系統介紹
1.1 介紹
1.2 特點
1.3 系統架構圖
二 實踐
2.1 快速入門(演示英文,系統支持中文/英文)
2.2 Grafana可視化集成 (可選)
2.3 插件管理
三 原理
3.1 任務采集調度
3.2 高性能集群、云邊協同
3.3 插件管理
四 總結
一、系統介紹
1.1 介紹
Apache HertzBeat (incubating) 是一個易用友好的開源實時監控告警系統,無需 Agent,高性能集群,兼容 Prometheus,提供強大的自定義監控和狀態頁構建能力。
1.2 特點
- 集 監控+告警+通知 為一體,支持對應用服務,應用程序,數據庫,緩存,操作系統,大數據,中間件,Web服務器,云原生,網絡,自定義等監控閾值告警通知一步到位。
- 易用友好,無需 Agent,全 WEB 頁面操作,鼠標點一點就能監控告警,無需學習成本。
- 將 Http, Jmx, Ssh, Snmp, Jdbc, Prometheus 等協議規范可配置化,只需在瀏覽器配置監控模板 YML 就能使用這些協議去自定義采集想要的指標。您相信只需簡單配置即可快速適配一款 K8s 或 Docker 等新的監控類型嗎?
- 兼容 Prometheus 的系統生態并且更多,只需頁面操作就可以監控 Prometheus 所能監控的。
- 高性能,支持多采集器集群橫向擴展,支持多隔離網絡監控,云邊協同。
- 自由的告警閾值規則,郵件 Discord Slack Telegram 釘釘 微信 飛書 短信 Webhook Server醬 等方式消息及時送達。
- 提供強大的狀態頁構建能力,輕松向用戶傳達您產品服務的實時狀態。
1.3 系統架構圖
圖片
二、實踐
2.1 快速入門(演示英文,系統支持中文/英文)
1. 開啟Actuator配置
在項目 pom.xml 中添加依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>配置 application.yml 暴露端點:
management:
endpoints:
web:
exposure:
include: '*'
enabled-by-default: true
metrics:
export:
prometheus:
enabled: true
注意:如果你的項目里還引入了認證相關的依賴,比如 springboot-security ,那么 SpringBoot Actuator 暴露出的接口可能會被攔截,此時需要你手動放開這些接口,以 springboot-security 為例,需要在 SecurityConfig 配置類中加入以下代碼:
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity
// 配置要放開的接口
.antMatchers("/actuator/**").permitAll()
.antMatchers("/metrics/**").permitAll()
.antMatchers("/trace").permitAll()
.antMatchers("/heapdump").permitAll()
// ...
}
}2. 新增監控
系統頁面 -> 監控中心 -> 新增監控 -> AUTO -> Prometheus任務

3. 參數填寫
- 目標Host:SpringBoot 應用服務器地址(不帶協議頭,例如: https://, http:// )
- 端口:應用服務端口(例如: 8080)
- 端點路徑:/actuator/prometheus 可以使用標簽分類來管理任務,如添加env=test等業務相關標簽。
圖片
4. 查看監控數據
圖片

5. 告警配置
系統頁面 -> 告警 -> 閾值規則 -> 新增 -> 新增閾值
圖片
HertzBeat 提供了 實時計算 和 計劃周期 兩種類型的閾值規則設置,這里我們以 計劃周期 閾值規則為例。
- 閾值名稱:閾值規則名稱
- 閾值規則:填寫指標監測的規則(支持 PromQL)
- 執行周期:周期性執行閾值計算的時間間隔
- 告警級別:觸發閾值的告警級別,從低到高依次為: 警告-warning,嚴重-critical,緊急-emergency
- 觸發次數:設置觸發閾值多少次之后才會發送告警
- 告警內容:填寫監測告警的內容(支持填寫變量)
6. 設置閾值規則
比如監測 SpringBoot 應用程序的 CPU 占用,添加閾值規則:system_cpu_usage{job="Jolly_Vulture_43vT"} > 0.01
圖片
最后可以在 告警中心 看到已觸發的告警。
圖片
7. 設置告警通知
系統頁面 -> 消息通知 -> 通知媒介 -> 新增接收對象
圖片
系統頁面 -> 消息通知 -> 通知策略 -> 新增通知策略 -> 選擇接收對象并啟用通知
圖片
2.2 Grafana可視化集成 (可選)
1. Grafana 圖表配置
需啟用 Grafana 可嵌入功能,并開啟匿名訪問。
2. HertzBeat 監控中嵌入 Grafana 儀表盤
配置啟用 Grafana 后,重啟 HertzBeat 服務,在新增的 AUTO 監控中啟用并上傳 Grafana 模板。 比如:Grafana 數據源選擇hertzbeat-victoria-metrics,然后在儀表盤點擊:「Share」→「Export」→「Save to file」下載模板并上傳至 HertzBeat 監控中。
圖片
3. 查看 Grafana 圖表
進入新增 AUTO 監控頁面,點擊 Grafana 圖標按鈕,即可查看 Grafana 圖表。
圖片
2.3 插件管理
2.3.1 簡介
- HertzBeat 現有交互局限于告警后的通知功能,而插件功能可支持用戶在其生命周期各階段添加自定義操作,例如告警后執行 SQL、Shell 腳本,或采集監控數據后發送至其他系統。
- 用戶按自定義插件流程開發并打包后,通過 “插件管理 - 上傳插件” 功能上傳并啟用,即可在不重啟 HertzBeat(熱更新) 的情況下擴展自定義功能。
2.3.2 支持插件類型
Post-Alert插件
- 作用:在告警后執行自定義操作
- 實現接口:org.apache.hertzbeat.plugin.PostAlertPlugin
Post-Collect插件
- 作用:在采集后執行自定義操作
- 實現接口:org.apache.hertzbeat.plugin.PostCollectPlugin
2.3.3 Demo
- 定位到plugin模塊的 Plugin接口。
- 在org.apache.hertzbeat.plugin.impl目錄下, 新建一個PostAlertPlugin 實現類,如DemoPlugin,在實現類中接收Alert 類作為參數,實現execute方法,邏輯由用戶自定義.
- 在 META-INF/services/org.apache.hertzbeat.plugin.PostAlertPlugin 文件中增加接口實現類的全限定名,每個實現類全限定名單獨成行。 例如:org.apache.hertzbeat.plugin.impl.DemoPluginImpl
- 打包 hertzbeat-plugin 模塊。
- 通過 插件管理-上傳插件 功能,上傳以 -jar-with-lib.jar 結尾的插件包,啟用插件即可在告警后執行自定義操作
2.3.4 自定義插件參數
- 插件功能支持自定義參數,并且在使用插件時可以通過插件管理 - 編輯參數 功能填寫插件運行時需要的參數。 下面以定義一個包含兩個參數的插件為例,詳細介紹定義插件參數的流程:
- 在 define 目錄下增加參數定義文件 ,注意參數定義文件必須是名稱為 define 開頭的 yml 文件,例如 define-demo.yml; 在 define-demo.yml 中定義參數,如下所示:
params:
- field: host
# name-param field display i18n name
name:
zh-CN: 目標 Host
en-US: Target Host
# type-param field type(most mapping the html input type)
type: text
# required-true or false
required: true
# field-param field key
- field: port
# name-param field display i18n name
name:
zh-CN: 端口
en-US: Port
# type-param field type(most mapping the html input type)
type: number
# when type is number, range is required
range: '[0,65535]'- 在插件邏輯中使用參數
@Override
public void execute(Alert alert, PluginContext pluginContext) {
log.info("param host:{}",pluginContext.getString("host"));
log.info("param port:{}",pluginContext.getInteger("port"));
}三 原理
3.1 任務采集調度
3.1.1 時間輪算法
HertzBeat的監控任務調度內部使用的是時間輪算法。
- 時間輪定義: 時間輪是一個 存儲定時任務的環形隊列,底層采用數組實現,數組中的每個元素可以存放一個定時任務列表。TimerTaskList 是一個環形的雙向鏈表,鏈表中的每一項表示的都是定時任務項
- 概念圖
圖片
3.1.2 業務流程圖
圖片
3.1.3 代碼詳解
任務采集器
//初始化桶大小為512 -1,用于取模
int mask = 511;
// 初始化起始時間
startTime = System.nanoTime();
do {
// 獲取下一個時間指針
long deadline = waitForNextTick();
if (deadline > 0) {
// 計算當前桶索引(取模)
int idx = (int) (tick & mask);
// 清理已取消任務
processCancelledTasks();
// 獲取當前桶
HashedWheelBucket bucket = wheel[idx];
// 將新任務添加到桶桶
transferTimeoutsToBuckets();
// 執行到期任務
bucket.expireTimeouts(deadline);
// 推進時間+1
tick++;
}
} while (isRunning());獲取時間指針(waitForNextTick)
- 作用:確保每秒精確推進一個時間槽(tickDuration 默認為 1 秒)。
- 細節:通過休眠補償時間差,避免忙等待。
private long waitForNextTick() {
// 計算下一個tick的絕對時間點(單位:納秒)
// tickDuration:每個tick的時間長度(默認1秒=1e9納秒)
// tick:當前已執行的tick計數
long deadline = tickDuration * (tick + 1);
// 自旋等待直到達到下一個tick時間點
for (; ; ) {
// 計算當前已過去的時間(納秒)
final long currentTime = System.nanoTime() - startTime;
// 計算需要休眠的時間(毫秒)
// +999999:實現納秒到毫秒的向上取整轉換(避免精度丟失)
long sleepTimeMs = (deadline - currentTime + 999999) / 1000000;
// 檢查是否已達到或超過deadline
if (sleepTimeMs <= 0) {
// 處理極端邊界情況
if (currentTime == Long.MIN_VALUE) {
return -Long.MAX_VALUE; // 異常返回值
} else {
return currentTime; // 返回實際經過的時間
}
}
// Windows平臺特殊處理:調整休眠精度
// 原因:Windows默認系統定時器精度約15ms,調整為10ms倍數可減少無效喚醒
if (NetworkUtil.isWindowsPlatform()) {
sleepTimeMs = sleepTimeMs / 10 * 10; // 向下對齊到10ms
}
try {
// 精確休眠直到下一個tick
Thread.sleep(sleepTimeMs);
} catch (InterruptedException ignored) {
// 被中斷時檢查是否處于關閉狀態
if (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_SHUTDOWN) {
return Long.MIN_VALUE; // 返回關閉標識
}
// 否則忽略中斷繼續等待
}
}
}移除取消任務(processCancelledTasks)
- 作用:刪除桶中已取消任務節點
- 細節:timeout實現remove從雙向鏈表中刪除當前節點
private void processCancelledTasks() {
for (; ; ) {
// 從取消任務隊列獲取取消任務
HashedWheelTimeout timeout = cancelledTimeouts.poll();
if (timeout == null) {
// all processed
break;
}
try {
// timeout實現的從雙向鏈表中刪除當前節點
timeout.remove();
} catch (Throwable t) {
if (logger.isWarnEnabled()) {
logger.warn("An exception was thrown while process a cancellation task", t);
}
}
}
}任務分配邏輯(transferTimeoutsToBuckets)
deadline賦值邏輯:long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;
短期任務(如 30 秒):remainingRounds = 0,直接放入對應桶。
長期任務(超出一輪范圍,如 1000 秒): 假設桶總數 512,當前 tick=0:
- calculated = 1000 / 1 = 1000
- remainingRounds = (1000 - 0) / 512 = 1(需 1 輪)
- 桶索引:1000 % 512 = 488(放入第 488 號桶)
private void transferTimeoutsToBuckets() {
// 每次tick最多轉移100,000個任務,防止添加任務的線程循環添加導致工作線程阻塞
for (int i = 0; i < 100000; i++) {
// 從任務隊列取出任務
HashedWheelTimeout timeout = timeouts.poll();
// 隊列為空時結束轉移
if (timeout == null) {
break;
}
// 跳過已取消的任務
if (timeout.state() == HashedWheelTimeout.ST_CANCELLED) {
continue;
}
// 計算任務的理論觸發時間槽(單位:tick)
long calculated = timeout.deadline / tickDuration;
// 計算任務需要經歷的完整輪數
timeout.remainingRounds = (calculated - tick) / wheel.length;
// 確保任務不會分配到過去的時間槽:
// 如果計算的時間槽小于當前tick,則使用當前tick
final long ticks = Math.max(calculated, tick);
// 計算桶索引(通過位運算替代取模,要求wheel.length是2的冪)
int stopIndex = (int) (ticks & mask);
// 將任務添加到對應桶中
HashedWheelBucket bucket = wheel[stopIndex];
bucket.addTimeout(timeout);
}
}執行到期任務(expireTimeouts)
- 執行條件: remainingRounds <= 0 且未取消的任務。
- 輪數更新:未到期的任務減少剩余輪數(每輪掃到桶時-1)。
void expireTimeouts(long deadline) {
HashedWheelTimeout timeout = head;
// 遍歷桶中任務鏈表
while (timeout != null) {
HashedWheelTimeout next = timeout.next;
// 剩余輪數為 0
if (timeout.remainingRounds <= 0) {
// 從鏈表移除
remove(timeout);
if (timeout.deadline <= deadline) {
// 執行任務
timeout.expire();
}
} elseif (timeout.isCancelled()) {
// 移除已取消任務
remove(timeout);
} else {
// 減少剩余輪數
timeout.remainingRounds--;
}
// 指針next
timeout = next;
}
}3.2 高性能集群、云邊協同
3.2.1 介紹
高性能集群:
- HertzBeat 支持部署采集器集群,多采集器集群橫向擴展,指數級提高可監控數量與采集性能。
- 監控任務在采集器集群中自調度,單采集器掛掉無感知故障遷移采集任務,新加入采集器節點自動調度分擔采集壓力。
- 單機模式與集群模式相互切換部署非常方便,無需額外組件部署。
云邊協同
- HertzBeat 支持部署邊緣采集器集群,與主 HertzBeat 服務云邊協同提升采集能力。
- 多個隔離網絡部署邊緣采集器,采集器在隔離網絡內部進行監控任務采集,采集數據上報,由主服務統一調度管理展示
3.2.2 架構圖
圖片
3.2.3 自動調度
- 實現原理: 采用一致性哈希算法,構建虛擬節點環,未指定采集器的任務通過哈希值自動映射至最近虛擬節點
- 哈希環結構
圖片
key:虛擬節點hash值
value:任務id集合
創建哈希環流程圖
圖片
創建哈希環代碼詳解
// 1. 添加物理節點
public void addNode(Node newNode) {
if (!isPrivateMode(newNode)) {
int vnodes = getVirtualNodeCount(newNode);
for (int i = 0; i < vnodes; i++) {
addVirtualNode(newNode, newNode.id + i);
}
}
nodeMap.put(newNode.id, newNode);
rebalanceJobs();
}
// 2. 添加虛擬節點(核心遷移邏輯)
public synchronized void addVirtualNode(Node node, String vnodeId) {
int vHash = hash(vnodeId);
hashRing.put(vHash, node); // 加入哈希環
// 初始化虛擬節點任務集
node.initVirtualNode(vHash);
// 獲取順時針后繼節點
Node nextNode = getNextNode(vHash);
// 遷移任務:從后繼節點獲取任務集合
Set<Long[]> jobs = nextNode.getJobsForMigration(vHash);
if (!jobs.isEmpty()) {
// 拆分任務:需要遷移的部分
Set<Long[]> migrateJobs = extractJobsToMigrate(jobs, vHash);
// 更新節點任務映射
nextNode.updateJobs(jobs); // 更新原節點
node.addJobsToVirtualNode(vHash, migrateJobs); // 添加到新節點
// 更新物理節點狀態
if (node != nextNode) {
migrateJobStatus(nextNode, node, migrateJobs);
}
}
}
// 3. 管理虛擬節點任務
private void addVirtualNodeJobs(int vHash, Set<Long[]> jobs) {
if (vNodeJobs == null) vNodeJobs = new ConcurrentHashMap<>();
// 合并或創建任務集
if (vNodeJobs.containsKey(vHash)) {
vNodeJobs.get(vHash).addAll(jobs);
} else {
vNodeJobs.put(vHash, jobs);
}
}任務分配代碼詳解
public Node dispatchJob(Integer dispatchHash, Long jobId, boolean isFlushed) {
if (dispatchHash == null || hashCircle == null || hashCircle.isEmpty()) {
log.warn("There is no available collector registered. Cache the job {}.", jobId);
dispatchJobCache.add(new DispatchJob(dispatchHash, jobId));
return null;
}
// 獲取哈希環中第一個大于等于dispatchHash的節點
Map.Entry<Integer, Node> ceilEntry = hashCircle.ceilingOrFirstEntry(dispatchHash);
int virtualKey = ceilEntry.getKey();
Node curNode = ceilEntry.getValue();
curNode.addJob(virtualKey, dispatchHash, jobId, isFlushed);
return curNode;
}3.2.4 無感知故障遷移
- 實現原理:通過netty監控心跳每5秒一次,如果某采集器節點心跳失效,HertzBeat重新分配任務
- 代碼詳解:
- 感知到采集器下線后,會調用collectorGoOffline
public void collectorGoOffline(String identity) {
// ... 更新數據庫狀態
// 關鍵步驟:從一致性哈希環移除
consistentHash.removeNode(identity);
// 關鍵步驟:重新平衡任務分配
reBalanceCollectorAssignJobs();
}3.3 插件管理
3.3.1 實現原理:
通過SPI結合自定義類加載器實現插件熱更新
3.3.2 代碼詳解
- 插件新增邏輯
public void savePlugin(PluginUpload pluginUpload) {
// 1. 保存JAR文件到plugin-lib目錄
String jarPath = new File(this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath()).getAbsolutePath();
Path extLibPath = Paths.get(new File(jarPath).getParent(), "plugin-lib");
File extLibDir = extLibPath.toFile();
String fileName = UUID.randomUUID().toString().replace("-", "") + "_" + fileName;
File destFile = new File(extLibDir, fileName);
// 2. 驗證JAR文件內容
PluginMetadata parsed = validateJarFile(destFile);
// 3. 保存插件元數據到數據庫
metadataDao.save(pluginMetadata);
itemDao.saveAll(pluginItems);
// 4. 重新加載類加載器
loadJarToClassLoader();
// 5. 同步插件狀態
syncPluginStatus();
}- 插件更新
@PostConstruct
private void loadJarToClassLoader() {
// 1. 關閉舊的類加載器
for (URLClassLoader pluginClassLoader : pluginClassLoaders) {
if (pluginClassLoader != null) {
pluginClassLoader.close();
}
}
// 2. 清理內存
if (!pluginClassLoaders.isEmpty()) {
pluginClassLoaders.clear();
System.gc();
}
// 3. 重新加載所有啟用的插件
List<PluginMetadata> plugins = metadataDao.findPluginMetadataByEnableStatusTrue();
for (PluginMetadata metadata : plugins) {
List<URL> urls = loadLibInPlugin(metadata.getJarFilePath(), metadata.getId());
urls.add(new File(metadata.getJarFilePath()).toURI().toURL());
pluginClassLoaders.add(new URLClassLoader(urls.toArray(new URL[0]), Plugin.class.getClassLoader()));
}
}- 插件調用
@Override
public <T> void pluginExecute(Class<T> clazz, Consumer<T> execute) {
for (URLClassLoader pluginClassLoader : pluginClassLoaders) {
// 使用ServiceLoader發現插件實現
ServiceLoader<T> load = ServiceLoader.load(clazz, pluginClassLoader);
for (T t : load) {
if (pluginIsEnable(t.getClass())) {
execute.accept(t);
}
}
}
}四 總結
- 開箱即用:Docker 快速部署,端口映射即可使用;集成監控、告警、通知,覆蓋多類對象。
- 極簡易用:部署簡單,配置量少,無需 Agent 即可直接監控;全 WEB 操作,零學習成本。
- 界面直觀:菜單簡潔,無深層嵌套,核心功能集中在一級菜單;用戶配置在配置文件中完成,操作便捷。
- 安全可靠:數據密鑰全鏈路加密。
- 高性能與自定義:支持多協議模板化配置,YML 自定義指標;集群橫向擴展,適配多網絡場景;靈活告警與多渠道通知,可快速適配新 K8s 監控類型。
關于作者
閆書銘,轉轉回收技術部 后端工程師,Apache HertzBeat Committer































