OpenObserve 可觀測平臺探究

OpenObserve 是一套基於 Rust 語言的開源可觀測數據融合處理平臺。通過不同的 api 接口,支持 metrics、log、trace 等數據的持久化。本文嘗試探究一下 OpenObserve 的運行原理。

安裝

官方給了 2 種典型的安裝方式,包括單機模式和 HA 高可用模式。詳細可參考 https://openobserve.ai/docs/quickstart/

 單機模式

運行很簡單,攜帶用戶名和密碼,啓動即可。

ZO_ROOT_USER_EMAIL="root@example.com" ZO_ROOT_USER_PASSWORD="Complexpass#123" ./openobserve

 HA 模式

與 Cortex/Thanos 等時序數據持久化解決方案類似,OpenObserve 的高可用模式也包括多個功能組件:ingester、compactor、querier、alert。特別之處在於,etcd 存放 series 的 schema 信息,S3 存放數據的 parquet 文件。在 k8s 集羣中,參考官方指引部署即可:

helm repo add openobserve https://charts.openobserve.ai
helm repo update
kubectl create ns openobserve
helm --namespace openobserve -f values.yaml install zo1 openobserve/openobserve

#暴露Web前端頁面
kubectl --namespace openobserve port-forward svc/zo1-openobserve-router 5080:5080

本例使用到的 values.yaml 除了使用官方文件進行修改,也可以參考本文相關信息 https://github.com/grafanafans/club/tree/master/OpenObserve。

數據注入

 metrics 寫入

接口調用

POST /api/{organization}/prometheus/api/v1/write

數據寫入支持 OTEL collector 和 Prometheus 等方式。本文以 Prometheus 數據導入爲例。參考 Prometheus 的 remote write 配置如下:

remote_write:
  - url: "http://127.0.0.1:5080/api/org_name/prometheus/api/v1/write"
    basic_auth:
      username: root@example.com
      password: Complexpass#123

log 寫入

接口調用

POST /api/{organization}/{stream}/_json

在 vector、Filebeat、Syslog、Curl 等多種方式下快捷導入日誌信息。

 trace 寫入

接口調用

POST /api/{organization}/traces

支持 opentelemetry SDK 的原生格式,導入 trace 數據,包括但不限於 Go、Python、Java 等語言。

數據流轉

諮詢 OpenObserve 後,他們分享了兩點信息:

1. OpenObserve stores files temporarily as json before being converted to parquet files. 
All series pertaining to a single metric are stored together.
2. sled/etcd eventually stores metadata in physical files, but OpenObserve never stores 
data directly to files.

所以,OpenObserve 不再像 Prometheus 一樣,去維護 index/tsdb 信息,而是交由第三方工具,來進行 meta 信息,以及落盤文件的格式管理。下面我們以 metrics 的寫入和查詢,來了解一下這個過程。

 數據寫入

1)以邏輯日誌的形式,將原始 series 信息記錄到 wal (json 格式) 文件,同時更新 etcd 中的 schema 信息

2)隨後週期到達後,會將此 json 批量轉儲成 parquet 文件,上傳到 S3。

將 series 的 schema 信息存儲到 etcd

代碼入口:

D:\opensource\openobserve\src\service\metrics\prom.rs
remote_write
        set_schema_metadata(org_id, &metric_name, StreamType::Metrics, extra_metadata)
            .await
            .unwrap();

可以在 etcd 中查詢到 series 的 schema 信息,schema 只記錄 series 的 lable-key,metrics name 等信息。

#etcd查看所有的key
etcdctl get /  --prefix --keys-only 
/zinc/observe/schema/org_name/metrics/avalanche_metric_mmmmm_0_0
...
# etcd查看avalanche_metric_mmmmm_0_0的schema信息
etcdctl get   --prefix /zinc/observe/schema/org_name/metrics/avalanche_metric_mmmmm_0_0
部分字段信息如下:
[{"fields":[],"metadata":{"start_dt":"1695868345620467","prom_metadata":"{\"metric_type\":\"Gauge\",\"metric_family_name\":\"avalanche_metric_mmmmm_0_0\",\"help\":\"A tasty metric morsel\",\"unit\":\"\"}","created_at":"1695868345620467","end_dt":"1695868297371000"}},
{"fields":[
  {"name":"__hash__","data_type":"Utf8","nullable":true,
"dict_id":0,"dict_is_ordered":false,"metadata":{}},
  {"name":"__name__","data_type":"Utf8","nullable":true,"dict_id":0,"dict_is_ordered":false,"metadata":{}},
  {"name":"_timestamp","data_type":"Int64","nullable":true,"dict_id":0,"dict_is_ordered":false,"metadata":{}},

series 數據寫入 wal

代碼入口:

D:\opensource\openobserve\src\common\infra\wal.rs
pub fn write_file(
let file = get_or_create(
impl RwFile {
let file_name = format!("{thread_id}_{key}_{id}{}", FILE_EXT_JSON);

通過上述流程,生成如下 wal 文件

json 文件內容,和下文的 parquet 文件,在內容上一致。

轉成 parquet 格式並上傳到 S3

週期到達後,會將 wal 文件 (如果記錄在內存,則從內存中提取邏輯日誌信息)組裝成 parquet 文件。

關於 parquet 如何進行嵌套數據存儲(更適合 trace 數據存儲),可以參考相關 https://github.com/apache/parquet-format 和 https://blog.csdn.net/Night_ZW/article/details/108359619 更深入瞭解。

代碼入口:

D:\opensource\openobserve\src\job\files\memory.rs
D:\opensource\openobserve\src\job\files\disk.rs
move_files_to_storage

可以週期性的看到 ingester 有如下上傳信息:

[2023-10-03T02:06:58Z INFO  openobserve::job::files::disk] [JOB] convert disk file: data/wal/files/org_name/metrics/up/0_2023_09_28_00_keeping_7113098315302051840hFwd3b.json
[2023-10-03T02:06:58Z INFO  openobserve::job::files::disk] [JOB] File upload begin: disk: data/wal/files/org_name/metrics/up/0_2023_09_28_00_keeping_7113098315302051840hFwd3b.json
[2023-10-03T02:06:58Z INFO  openobserve::service::schema] service:schema:schema_evolution; org_id="org_name" stream_ stream_type=Metrics min_ts=1695894790904000
[2023-10-03T02:06:58Z INFO  openobserve::job::files::disk] [JOB] disk file upload succeeded: files/org_name/metrics/up/2023/09/28/00/7113098315302051840hFwd3b.parquet

上傳到 S3 的 parquet 文件,解析出來如下:

數據查詢

在 Web 觸發一次 metric 查詢,avalanche_metric_mmmmm_0_0{label_key_kkkkk_0="label_val_vvvvv_0"}

Querier 執行查詢

Querier 側的入口日誌如下:

[2023-10-06T08:25:14Z INFO  openobserve::service::promql::search::grpc::storage] promql:search:grpc:storage:create_context; org_id="org_name" stream_
[2023-10-06T08:25:14Z INFO  openobserve::service::promql::search::grpc::storage] promql:search:grpc:storage:get_file_list; org_id="org_name" stream_
[2023-10-06T08:25:14Z INFO  openobserve::service::promql::search::grpc::wal] promql:search:grpc:wal:get_file_list; org_id="org_name" stream_ time_range=(1696573200343000, 1696580700343000)
[2023-10-06T08:25:14Z INFO  actix_web::middleware::logger] 10.244.0.7 "GET /api/org_name/prometheus/api/v1/query_range?start=1696573500343000&end=1696580700343000&step=0&query=avalanche_metric_mmmmm_0_0%7Blabel_key_kkkkk_0%3D%22label_val_vvvvv_0%22%7D HTTP/1.1" 200 4356 "-" "http://127.0.0.1:5080/web/metrics?stream=avalanche_metric_mmmmm_0_12&period=2h&refresh=0&query=YXZhbGFuY2hlX21ldHJpY19tbW1tbV8wXzEye30=&org_identifier=org_name" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0" 0.063740

代碼邏輯流程如下:

create_context 
  get_file_list // 獲取包含metric的files列表
  cache_parquet_files  // 下載parquet文件
  db::schema::get(org_id, stream_name, stream_type) // 從etcd獲取當前metric最新的schema信息
  register_table  // 註冊 datafusion 以適配parquet和json的數據查詢

首先從 filelist 中找到 metric 做爲 stream name 的 files 列表,

files內容
{"key":"files/org_name/metrics/avalanche_metric_mmmmm_0_1/2023/09/28/00/71129873952131031049nQPk2.parquet","meta":{"min_ts":1695868297371000,"max_ts":1695868942371000,"records":430,"original_size":274733,"compressed_size":9729},"deleted":false}

然後去 S3 下載這些 parquet 文件。

由於 metric 的 schema 信息可能會發生變化,但頻率不高,所以從 etcd 獲取到 metric 的所有 metric label 標籤後,做爲查詢索引,去搜索 parquet 文件和當前 wal 文件中的內容。

OpenObserve 使用 datafusion 將 json 和 parquet 數據一起進行 SQL 查詢,進行 metric 的過濾。

D:\opensource\openobserve\src\service\search\datafusion\exec.rs
    datasource::{
        file_format::{
            file_type::{FileType, GetExt},
            json::JsonFormat,
            parquet::ParquetFormat,

可以參考 datafusion 官方文檔 https://arrow.apache.org/datafusion/ ,瞭解查詢引擎如何將各類型文件,進行 SQL 查詢分析。

其他查詢

OpenObserve 也提供了 series、labels、values 等查詢接口,可以通過如下函數了解。

D:\opensource\openobserve\src\handler\http\request\prom\mod.rs
#[get("/{org_id}/prometheus/api/v1/series")]
pub async fn series_get(
    org_id: web::Path<String>,
    req: web::Query<meta::prom::RequestSeries>,
) -> Result<HttpResponse, Error> {
    series(&org_id, req.into_inner()).await
}
D:\opensource\openobserve\src\service\metrics\prom.rs
pub(crate) async fn get_series
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/WULnyNlhnzyu5UxuonitEg