每分鐘寫入六億條數據,攜程監控系統存儲升級實踐
一、背景概述
框架Dashboard是一款攜程內部歷史悠久的自研監控產品,其定位是企業級Metrics監控場景,主要提供用戶自定義Metrics接入,并基于此提供實時數據分析和視圖展現的面板服務,提供可定制的基于時間序列的各類系統級性能數據和業務指標數據的看板。還可以提供靈活的數據收集接口、分布式的大容量存儲和靈活的展現方式。
由于時間較早,那時候業界還沒有像樣的TSDB產品,類似Prometheus,InfluxDB都是后起之秀,所以Dashboard選型主要使用了HBase來存儲Metrics數據。并且基于HBase來實現了TSDB,解決了一些HBase熱點問題,同時將部分查詢聚合下放到HBase,目的是優化其查詢性能,目前看來總體方案依賴HBase/HDFS還是有點重。
近些年,隨著攜程監控All-in-One產品的提出。對于內部的Metrics存儲統一也提出了新的要求。由于Dashboard查詢目前存在的諸多問題以及Metrics統一的目標,我們決定替換升級Dashboard現有的HBase存儲方案,并且在Metrics場景提供統一的查詢層API。
二、整體架構
Dashboard產品主要分了6個組件,包括dashboard-engine,dashboard-gateway,dashboard-writer,dashboard-HBase存儲,dashboard-collector,dashboard-agent。目前實時寫入數據行數6億條/分鐘,架構圖如下:
- dashboard-engine是查詢引擎。
- dashboard-gateway是提供給用戶的查詢界面。
- dashboard-writer是數據寫入HBase的組件。
- dashboard-collector是基于Netty實現的Metrics數據收集的服務端。
- dashboard-agent是用戶打點的客戶端,支持sum,avg,max,min這幾種聚合方式。
- dashboard-HBase是基于HBase實現的Metrics存儲組件。
產品主要特性如下:?
- 支持存儲精確到分鐘級的基于時間序列的數據。
- 單個指標數據可支持多個tag。
- 展現提供任意形式的視圖同時可靈活基于tag進行分組。
三、目前的存在問題
基于HBase的Metrics存儲方案雖然具有良好的擴展性,比較高的吞吐,但是隨著時間發展,已經不是最優的TSDB方案了,可以歸納總結為如下幾個痛點。
- 在TSDB場景查詢慢,整體表現不如專業的TSDB。
- HBase熱點問題,容易影響數據寫入。
- HBase技術棧運維操作很重。
- 采用自研協議,不支持業界標準的Prometheus協議,無法和內部All-in-one監控產品較好的融合。
四、替換難點
- 系統寫入數據量大,6億條/分鐘。
- Dashboard數據缺乏治理,很多不合理高維的metrics數據,日志型數據,經過統計,整體基數達上千億,這對TSDB不友好,這部分需要寫入程序做治理。如圖2所示是top20基數統計,有很多Metric基數已經上億。
- Dashboard系統存在時間久,內部有很多程序調用,替換需要做到對用戶透明。
五、替換升級方案
從上面的架構來看,目前我們替換的主要是dashboard-writer和dashboard-HBase這兩個最核心的組件。為了對用戶的平滑遷移,其他組件稍作改動,在dashboard-engine組件上對接新的查詢API即可替換升級成功。對于用戶側,查詢的界面dashboard-gateway和打點的客戶端dashboard-agent還是原有的模式不變,因此整個的替換方案對用戶透明。具體如下:
1、dashboard-HBase升級為dashboard-vm
存儲從HBase方案替換成VictoriaMetrics+ClickHouse混合存儲方案:?
- VictoriaMetrics是兼容主流Prometheus協議的TSDB,在TSDB場景下查詢效果好,所以會接入絕大多數TSDB數據。
- 基于ClickHouse提供元數據服務,主要為界面的adhoc查詢服務,原來這部分元數據是存儲在HBase里面,新的方案采用ClickHouse來存儲。元數據主要存儲了measurement列表,measurement-tagKey列表,measurement-tagKey-tagValue列表這三種結構,目前在ClickHouse創建了一張表來存這些元數據。
本地表結構為:
CREATE TABLE hickwall.downsample_mtv
(`timestamp` DateTime,
`metricName` String,
`tagKey` String,
`tagValue` String,
`datasourceId` UInt8 DEFAULT 40)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/hickwall_cluster-{shard}/downsample_mtv', '{replica}')
PARTITION BY toYYYYMMDD(timestamp)
ORDER BY (timestamp, metricName, tagKey)
TTL timestamp + toIntervalDay(7)
SETTINGS index_granularity = 8192
?分布式表結構為:
CREATE TABLE hickwall.downsample_mtv__dt
(`timestamp` DateTime,
`metricName` String,
`tagKey` String,
`tagValue` String,
`datasourceId` UInt8 DEFAULT 40)
ENGINE = Distributed(hickwall_cluster, hickwall, downsample_mtv, rand())
- ClickHouse存儲少量日志型的數據
由于長期缺乏一些治理,Dashboard還存儲了一些日志型數據,這類數據是一些基數很大但數據量少的數據,不適合存儲在VictoriaMetrics。為了實現所有數據透明遷移,這部分數據經過評估,通過白名單配置的方式接入ClickHouse來存儲,需要針對每一個接入的日志型指標來創建表和字段。目前的做法是按照BU維度來建表,并且針對指標tag來創建字段,考慮到接入的日志型指標數量少,所以表的字段數量會相對可控。用機票FLT的表結構舉例如下圖。
2、Dashboard-writer升級為Dashboard-vmwriter
Dashboard-collector會分流全量的數據到Kafka,Dashboard-vmwriter的工作流程大致是消費Kafka->數據處理->數據寫入存儲。Dashboard-vmwriter主要實現了以下幾個核心的功能:
- Metrics元數據抽取功能,負責抽取出measurement,tagKey,tagValue寫入ClickHouse的mtv本地表。這塊元數據存儲主要依賴了Redis(用于實時寫入)和ClickHouse(用于查詢)。
- 指標預聚合功能,用于加速查詢。對接公司內部的配置中心來下發預聚合的配置,配置格式如下。
下面的配置會生成ClusterName和appid這兩個維度組合的credis預聚合指標。
{
"metricName": "credis.java.latency",
"tagNames": [
"ClusterName",
"appid"
]
}
?配置下發后,Dashboard-vmwriter會自動聚合一份預聚合指標存入VictoriaMetrics,指標命名規則為hi_agg.{measurement}_{tag1}_{tag2}_{聚合field}。同樣的,查詢層API會讀取同樣的預聚合配置來決定查詢預聚合的指標還是原始的指標,默認為所有的measurement維度都開啟了一份預聚合的配置,因為在TSDB實現中,查一個measurement的數據會掃描所有的timeseries,查詢開銷很大,所以這部分直接去查預聚合好的measurement比較合理。
- 數據治理:異常數據自動檢測及封禁,目前主要涉及以下方面:
1)基于HyperLogLog的算法來統計measurement級別的基數,如果measurement的基數超級大,比如超過500萬,那么就會丟棄一些tag維度。
2)基于Redis和內存cache來統計measurement-tagKey-tagValue的基數,如果某個tagValue增長過快,那么就丟棄這個tag的維度,并且記錄下丟棄這種埋點。Redis主要使用了set集合,key的命名是{measurement}_{tagKey},成員是[tagValue1,tagValue2,… , tagValueN],主要是通過sismember來判斷成員是否存在,sadd來添加成員,scard判斷key的成員數量。
寫入程序會先在本地內存Cache查找Key的成員是否存在,沒有的話會去Redis查找,對Redis的qps是可控的,本地Cache是基于LRU的淘汰策略,本地內存可控。整個過程是在寫入的時候實時進行的,也能保證數據的及時性和高性能,寫入Redis的元數據也會實時增量同步到ClickHouse的mtv表,這樣用戶界面也能實時查詢到元數據。
3)數據高性能寫入,整個消費的線程模型大概是一個進程一個kafka消費線程n個數據處理線程m個數據寫入線程。線程之間通過隊列來通信,為了在同一個進程內方便數據做預聚合操作。假設配置了4個數據處理線程,那么就會按照measurement做hash,分到4個bucket里面處理,這樣同一個measurement的數據會在一個bucket里面處理,也方便后續的指標預聚合處理。
private int computeMetricNameHash(byte[] metricName) {
int hash = Arrays.hashCode(metricName);
hash = (hash == Integer.MIN_VALUE ? 0 : hash);
return hash;
}
byte[] metricName = metricEvent.getName();
hash = computeMetricNameHash(metricName);
buckets[Math.abs(hash) % bucketCount].add(metricEvent);
?經過程序埋點測算,正常情況下整體鏈路的數據寫入延遲控制在1s內,大約在百毫秒級。
3、Metrics統一查詢層
契約上,兼容了Dashboard原來的查詢協議,也支持標準的prometheus協議。
實現上,封裝了VictoriaMetics+ClickHouse的統一查詢,支持元數據管理,預聚合管理,限流,rollup策略等。
查詢層主要提供了以下四個核心接口。?
- Data接口:根據measurement,tagKey,tagValue返回時序數據,數據源是VictoriaMetrics。
- Measurement接口:返回limit數量的measurement列表,數據源是ClickHouse。
- Measurement-tagKey接口:返回指定measurement的tagKey列表,數據源是ClickHouse。
- Measurement-tagKey-tagValue接口:返回指定measurement和tagkey的tagValue的列表,數據源是ClickHouse。
如下圖第一張所示是新的存儲架構,第二張是VictoriaMetrics自身的架構。
需要注意到,整個數據寫入層是單機房寫單機房的存儲集群,是完全的單元化結構。最上層通過統一的數據查詢層匯總多個機房的數據進行聚合輸出。在可用性方面,任何單一機房的故障僅會影響單機房的數據。
六、替換前后效果對比
1)替換后的查詢耗時從MAX,AVG,STD提升近4倍。查詢耗時大多落在10-50ms之間。相比之前HBase經常查詢超時,整體查詢的穩定性也好了很多,見圖6,7。
2)寫入穩定性提升,徹底解決了因為HBase熱點引發的數據積壓。
3)替換后支持了更多的優秀的特性,可以基于promQL實現指標的邏輯計算,同比環比,模糊匹配等。
七、未來規劃
1)統一查詢層接入所有Metrics數據,除了Dashboard,目前內部還有HickWall,Cat有大量Metrics數據沒有接入統一查詢層,目前采用的是直連openrestry+VictoriaMetrics的方式,openrestry上面做了一些簡單的查詢邏輯,這塊計劃后續接入統一查詢層,這樣內部可以提供統一的元信息管理,預聚合策略等,達到Metrics架構統一。
2)提供統一寫入層,總體Metrics目前是近億級/秒,這塊寫入目前主要是基于Kafka消費進存儲的方式,內部這塊寫入是有多個應用在處理,如果有統一的寫入層那么就能做到寫入邏輯統一,和查詢層的查詢策略也能做到聯動,減少重復建設。
3)Metrics的存儲統一層提供了較好的典范,內部的日志存儲層統一也在如火如荼的進行中,也會往這樣的一個方向發展。