Ganglia 等監控系統採用 RRD (Round Robin Database) 循環覆蓋存儲,無法長期保留高精度數據。本文提供完整 Python 自動化方案,定時將 RRD 數據增量導入 MySQL,實現長期保留與多維分析。
| 特性 | RRD | MySQL |
|---|---|---|
| 存儲模式 | 循環覆蓋 (固定大小) | 增量增長 |
| 精度保留 | 高精度短期、低精度長期 | 全精度永久保留 |
| 查詢靈活性 | 僅支援 RRDtool 查詢 | SQL 任意聚合、JOIN、窗口函數 |
| 適用場景 | 實時監控圖表 | 趨勢分析、審計、機器學習特徵 |
導入策略:Cron 定時 (如每 5 分鐘) 執行 Python 腳本,增量讀取 RRD 最新數據,UPSERT 寫入 MySQL。
pip install mysqlclient rrdtool # 或 pip install pymysql rrdtool-python
RRD 檔案路徑 (Ganglia 預設):/var/lib/ganglia/rrds/{cluster}/{host}/{metric}.rrd
import os, MySQLdb, string, rrdtool
ROOT = "/var/lib/ganglia/rrds/hap-clu"
MAP_DOT = str.maketrans('.', '_')
MAP_DASH = str.maketrans('-', '_')
conn = MySQLdb.connect(host='localhost', user='root', passwd='123456')
cursor = conn.cursor()
for host_dir in os.listdir(ROOT):
db_name = host_dir.translate(MAP_DOT).translate(MAP_DASH)
cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{db_name}`")
conn.select_db(db_name)
host_path = os.path.join(ROOT, host_dir)
for rrd_file in os.listdir(host_path):
table_name = rrd_file[:-4].translate(MAP_DOT) # 去 .rrd
if db_name == "__SummaryInfo__":
cursor.execute(f"""
CREATE TABLE IF NOT EXISTS `{table_name}` (
time_id INT NOT NULL PRIMARY KEY,
value VARCHAR(30),
num VARCHAR(30)
) ENGINE=InnoDB
""")
else:
cursor.execute(f"""
CREATE TABLE IF NOT EXISTS `{table_name}` (
time_id INT NOT NULL PRIMARY KEY,
value VARCHAR(30)
) ENGINE=INNODB
""")
conn.commit()
print(f"DB: {db_name} created.")
cursor.close()
關鍵點:
. -,用 str.maketrans 替換為 _num 欄位import os, MySQLdb, string, rrdtool
ROOT = "/var/lib/ganglia/rrds/hap-clu"
MAP_DOT = str.maketrans('.', '_')
MAP_DASH = str.maketrans('-', '_')
conn = MySQLdb.connect(host='localhost', user='root', passwd='123456')
cursor = conn.cursor()
for host_dir in os.listdir(ROOT):
db_name = host_dir.translate(MAP_DOT).translate(MAP_DASH)
conn.select_db(db_name)
host_path = os.path.join(ROOT, host_dir)
os.chdir(host_path)
for rrd_file in os.listdir(host_path):
table_name = rrd_file[:-4].translate(MAP_DOT)
# 讀取所有 AVERAGE 值
data = rrdtool.fetch(rrd_file, "AVERAGE")
start_time, end_time, step = data[0]
values = data[2] # list of (value,) or (value, num)
batch = []
for i, row in enumerate(values):
timestamp = start_time + i * step
if row[0] is None:
continue
if db_name == "__SummaryInfo__":
batch.append((timestamp, str(row[0]), str(row[1])))
else:
batch.append((timestamp, str(row[0])))
# 批量 UPSERT (主鍵衝突忽略)
if batch:
if db_name == "__SummaryInfo__":
sql = f"INSERT IGNORE INTO `{table_name}` (time_id, value, num) VALUES (%s, %s, %s)"
else:
sql = f"INSERT IGNORE INTO `{table_name}` (time_id, value) VALUES (%s, %s)"
cursor.executemany(sql, batch)
conn.commit()
print(f"UPDATED DB: {db_name}")
cursor.close()
executemany + 單次 commit,避免逐行提交極慢time_id 為 Unix 時間戳主鍵,INSERT IGNORE 自動跳過重複# /etc/cron.d/rrd2mysql
# 每 5 分鐘執行,先初始化再導入
*/5 * * * * root /usr/bin/python3 /opt/scripts/rrd_init.py
*/5 * * * * root /usr/bin/python3 /opt/scripts/rrd_import.py
| 階段 | 方案 | 優勢 |
|---|---|---|
| 初期 | MySQL + Python Cron | 零依賴、開發快、SQL 靈活 |
| 中期 | MySQL 分表 + 定時歸檔 | 單表控制在 500 萬行 |
| 成熟期 | TimescaleDB / ClickHouse / Apache IoTDB | 原生時序優化、壓縮率高、聚合快 |
RRD 到 MySQL 的橋樑,讓監控數據從「看圖」進化為「做分析」。