国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

TiKV 源碼解析系列文章(四)Prometheus(下)

Nino / 1447人閱讀

摘要:由上述代碼可見,為了在線程安全的條件下實現(xiàn)各個具有獨立的時間序列,內(nèi)部采用了進行同步,也就是說及類似函數(shù)內(nèi)部是具有鎖的。

作者: Breezewish

本文為 TiKV 源碼解析系列的第四篇,接上篇繼續(xù)為大家介紹 [rust-prometheus]。上篇 主要介紹了基礎(chǔ)知識以及最基本的幾個指標(biāo)的內(nèi)部工作機制,本篇會進一步介紹更多高級功能的實現(xiàn)原理。

與上篇一樣,以下內(nèi)部實現(xiàn)都基于本文發(fā)布時最新的 rust-prometheus 0.5 版本代碼,目前我們正在開發(fā) 1.0 版本,API 設(shè)計上會進行一些簡化,實現(xiàn)上出于效率考慮也會和這里講解的略微有一些出入,因此請讀者注意甄別。

指標(biāo)向量(Metric Vector)

Metric Vector 用于支持帶 Label 的指標(biāo)。由于各種指標(biāo)都可以帶上 Label,因此 Metric Vector 本身實現(xiàn)為了一種泛型結(jié)構(gòu)體,[Counter]、[Gauge] 和 [Histogram] 在這之上實現(xiàn)了 [CounterVec]、[GaugeVec] 和 [HistogramVec]。Metric Vector 主要實現(xiàn)位于 src/vec.rs。

以 [HistogramVec] 為例,調(diào)用 [HistogramVec::with_label_values] 可獲得一個 [Histogram] 實例,而 [HistogramVec] 定義為:

pub type HistogramVec = MetricVec;

pub struct MetricVec {
   pub(crate) v: Arc>,
}

impl MetricVec {
   pub fn with_label_values(&self, vals: &[&str]) -> T::M {
       self.get_metric_with_label_values(vals).unwrap()
   }
}

因此 [HistogramVec::with_label_values] 的核心邏輯其實在 MetricVecCore::get_metric_with_label_values。這么做的原因是為了讓 MetricVec 是一個線程安全、可以被全局共享但又不會在共享的時候具有很大開銷的結(jié)構(gòu),因此將內(nèi)部邏輯實現(xiàn)在 MetricVecCore,外層(即在 MetricVec)套一個 Arc 后再提供給用戶。進一步可以觀察 MetricVecCore 的實現(xiàn),其核心邏輯如下:

pub trait MetricVecBuilder: Send + Sync + Clone {
   type M: Metric;
   type P: Describer + Sync + Send + Clone;

   fn build(&self, &Self::P, &[&str]) -> Result;
}

pub(crate) struct MetricVecCore {
   pub children: RwLock>,
   // Some fields are omitted.
}

impl MetricVecCore {
   // Some functions are omitted.

   pub fn get_metric_with_label_values(&self, vals: &[&str]) -> Result {
       let h = self.hash_label_values(vals)?;

       if let Some(metric) = self.children.read().get(&h).cloned() {
           return Ok(metric);
       }

       self.get_or_create_metric(h, vals)
   }

   pub(crate) fn hash_label_values(&self, vals: &[&str]) -> Result {
       if vals.len() != self.desc.variable_labels.len() {
           return Err(Error::InconsistentCardinality(
               self.desc.variable_labels.len(),
               vals.len(),
           ));
       }

       let mut h = FnvHasher::default();
       for val in vals {
           h.write(val.as_bytes());
       }

       Ok(h.finish())
   }

   fn get_or_create_metric(&self, hash: u64, label_values: &[&str]) -> Result {
       let mut children = self.children.write();
       // Check exist first.
       if let Some(metric) = children.get(&hash).cloned() {
           return Ok(metric);
       }

       let metric = self.new_metric.build(&self.opts, label_values)?;
       children.insert(hash, metric.clone());
       Ok(metric)
   }
}

現(xiàn)在看代碼就很簡單了,它首先會依據(jù)所有 Label Values 構(gòu)造一個 Hash,接下來用這個 Hash 在 RwLock> 中查找,如果找到了,說明給定的這個 Label Values 之前已經(jīng)出現(xiàn)過、相應(yīng)的 Metric 指標(biāo)結(jié)構(gòu)體已經(jīng)初始化過,因此直接返回對應(yīng)的實例;如果不存在,則要利用給定的 [MetricVecBuilder] 構(gòu)造新的指標(biāo)加入哈希表,并返回這個新的指標(biāo)。

由上述代碼可見,為了在線程安全的條件下實現(xiàn) Metric Vector 各個 Label Values 具有獨立的時間序列,Metric Vector 內(nèi)部采用了 RwLock 進行同步,也就是說 with_label_values() 及類似函數(shù)內(nèi)部是具有鎖的。這在多線程環(huán)境下會有一定的效率影響,不過因為大部分情況下都是讀鎖,因此影響不大。當(dāng)然,還可以發(fā)現(xiàn)其實給定 Label Values 之后調(diào)用 with_label_values() 得到的指標(biāo)實例是可以被緩存起來的,只訪問緩存起來的這個指標(biāo)實例是不會有任何同步開銷的,也繞開了計算哈希值等比較占 CPU 的操作。基于這個思想,就有了 Static Metrics,讀者可以在本文的后半部分了解 Static Metrics 的詳細(xì)情況。

另外讀者也可以發(fā)現(xiàn),Label Values 的取值應(yīng)當(dāng)是一個有限的、封閉的小集合,不應(yīng)該是一個開放的或取值空間很大的集合,因為每一個值都會對應(yīng)一個內(nèi)存中指標(biāo)實例,并且不會被釋放。例如 HTTP Method 是一個很好的 Label,因為它只可能是 GET / POST / PUT / DELETE 等;而 Client Address 則很多情況下并不適合作為 Label,因為它是一個開放的集合,或者有非常巨大的取值空間,如果將它作為 Label 很可能會有容易 OOM 的風(fēng)險。這個風(fēng)險在 Prometheus 官方文檔中也明確指出了。

整型指標(biāo)(Integer Metric)

在講解 Counter / Gauge 的實現(xiàn)時我們提到,[rust-prometheus] 使用 CAS 操作實現(xiàn) [AtomicF64] 中的原子遞增和遞減,如果改用 atomic fetch-and-add 操作則一般可以取得更高效率。考慮到大部分情況下指標(biāo)都可以是整數(shù)而不需要是小數(shù),例如對于簡單的次數(shù)計數(shù)器來說它只可能是整數(shù),因此 [rust-prometheus] 額外地提供了整型指標(biāo),允許用戶自由地選擇,針對整數(shù)指標(biāo)情況提供更高的效率。

為了增強代碼的復(fù)用,[rust-prometheus] 實際上采用了泛型來實現(xiàn) [Counter] 和 [Gauge]。通過對不同的 Atomic(如 [AtomicF64]、[AtomicI64])進行泛化,就可以采用同一份代碼實現(xiàn)整數(shù)的指標(biāo)和(傳統(tǒng)的)浮點數(shù)指標(biāo)。

[Atomic] trait 定義如下(src/atomic64/mod.rs):

pub trait Atomic: Send + Sync {
   /// The numeric type associated with this atomic.
   type T: Number;
   /// Create a new atomic value.
   fn new(val: Self::T) -> Self;
   /// Set the value to the provided value.
   fn set(&self, val: Self::T);
   /// Get the value.
   fn get(&self) -> Self::T;
   /// Increment the value by a given amount.
   fn inc_by(&self, delta: Self::T);
   /// Decrement the value by a given amount.
   fn dec_by(&self, delta: Self::T);
}

原生的 [AtomicU64]、[AtomicI64] 及我們自行實現(xiàn)的 [AtomicF64] 都實現(xiàn)了 [Atomic] trait。進而,[Counter] 和 [Gauge] 都可以利用上 [Atomic] trait:

pub struct Value {
   pub val: P,
   // Some fields are omitted.
}

pub struct GenericCounter {
   v: Arc>,
}

pub type Counter = GenericCounter;
pub type IntCounter = GenericCounter;
本地指標(biāo)(Local Metrics)

由前面這些源碼解析可以知道,指標(biāo)內(nèi)部的實現(xiàn)是原子變量,用于支持線程安全的并發(fā)更新,但這在需要頻繁更新指標(biāo)的場景下相比簡單地更新本地變量仍然具有顯著的開銷(大約有 10 倍的差距)。為了進一步優(yōu)化、支持高效率的指標(biāo)更新操作,[rust-prometheus] 提供了 Local Metrics 功能。

rust-prometheus 中 Counter 和 Histogram 指標(biāo)支持 local() 函數(shù),該函數(shù)會返回一個該指標(biāo)的本地實例。本地實例是一個非線程安全的實例,不能多個線程共享。例如,[Histogram::local()] 會返回 [LocalHistogram]。由于 Local Metrics 使用是本地變量,開銷極小,因此可以放心地頻繁更新 Local Metrics。用戶只需定期調(diào)用 Local Metrics 的 flush() 函數(shù)將其數(shù)據(jù)定期同步到全局指標(biāo)即可。一般來說 Prometheus 收集數(shù)據(jù)的間隔是 15s 到 1 分鐘左右(由用戶自行配置),因此即使是以 1s 為間隔進行 flush() 精度也足夠了。

普通的全局指標(biāo)使用流程如下圖所示,多個線程直接利用原子操作更新全局指標(biāo):

本地指標(biāo)使用流程如下圖所示,每個要用到該指標(biāo)的線程都保存一份本地指標(biāo)。更新本地指標(biāo)操作開銷很小,可以在頻繁的操作中使用。隨后,只需再定期將這個本地指標(biāo) flush 到全局指標(biāo),就能使得指標(biāo)的更新操作真正生效。

TiKV 中大量運用了本地指標(biāo)提升性能。例如,TiKV 的線程池一般都提供 Context 變量,Context 中存儲了本地指標(biāo)。線程池上運行的任務(wù)都能訪問到一個和當(dāng)前 worker thread 綁定的 Context,因此它們都可以安全地更新 Context 中的這些本地指標(biāo)。最后,線程池一般提供 tick() 函數(shù),允許以一定間隔觸發(fā)任務(wù),在 tick() 中 TiKV 會對這些 Context 中的本地指標(biāo)進行 flush()

Local Counter

[Counter] 的本地指標(biāo) [LocalCounter] 實現(xiàn)很簡單,它是一個包含了計數(shù)器的結(jié)構(gòu)體,該結(jié)構(gòu)體提供了與 [Counter] 一致的接口方便用戶使用。該結(jié)構(gòu)體額外提供了 flush(),將保存的計數(shù)器的值作為增量值更新到全局指標(biāo):

pub struct GenericLocalCounter {
   counter: GenericCounter

, val: P::T, } pub type LocalCounter = GenericLocalCounter; pub type LocalIntCounter = GenericLocalCounter; impl GenericLocalCounter

{ // Some functions are omitted. pub fn flush(&mut self) { if self.val == P::T::from_i64(0) { return; } self.counter.inc_by(self.val); self.val = P::T::from_i64(0); } }

Local Histogram

由于 [Histogram] 本質(zhì)也是對各種計數(shù)器進行累加操作,因此 [LocalHistogram] 的實現(xiàn)也很類似,例如 observe(x) 的實現(xiàn)與 [Histogram] 如出一轍,除了它不是原子操作;flush() 也是將所有值累加到全局指標(biāo)上去:

pub struct LocalHistogramCore {
   histogram: Histogram,
   counts: Vec,
   count: u64,
   sum: f64,
}

impl LocalHistogramCore {
   // Some functions are omitted.

   pub fn observe(&mut self, v: f64) {
       // Try find the bucket.
       let mut iter = self
           .histogram
           .core
           .upper_bounds
           .iter()
           .enumerate()
           .filter(|&(_, f)| v <= *f);
       if let Some((i, _)) = iter.next() {
           self.counts[i] += 1;
       }

       self.count += 1;
       self.sum += v;
   }

   pub fn flush(&mut self) {
       // No cached metric, return.
       if self.count == 0 {
           return;
       }
       {
           let h = &self.histogram;
           for (i, v) in self.counts.iter().enumerate() {
               if *v > 0 {
                   h.core.counts[i].inc_by(*v);
               }
           }
           h.core.count.inc_by(self.count);
           h.core.sum.inc_by(self.sum);
       }
       self.clear();
   }
}
靜態(tài)指標(biāo)(Static Metrics)

之前解釋過,對于 Metric Vector 來說,由于每一個 Label Values 取值都是獨立的指標(biāo)實例,因此為了線程安全實現(xiàn)上采用了 HashMap + RwLock。為了提升效率,可以將 with_label_values 訪問獲得的指標(biāo)保存下來,以后直接訪問。另外使用姿勢正確的話,Label Values 取值是一個有限的、確定的、小的集合,甚至大多數(shù)情況下在編譯期就知道取值內(nèi)容(例如 HTTP Method)。綜上,我們可以直接寫代碼將各種已知的 Label Values 提前保存下來,之后可以以靜態(tài)的方式訪問,這就是靜態(tài)指標(biāo)。

以 TiKV 為例,有 Contributor 為 TiKV 提過這個 PR:#2765 server: precreate some labal metrics。這個 PR 改進了 TiKV 中統(tǒng)計各種 gRPC 接口消息次數(shù)的指標(biāo),由于 gRPC 接口是固定的、已知的,因此可以提前將它們緩存起來:

struct Metrics {
   kv_get: Histogram,
   kv_scan: Histogram,
   kv_prewrite: Histogram,
   kv_commit: Histogram,
   // ...
}

impl Metrics {
   fn new() -> Metrics {
       Metrics {
           kv_get: GRPC_MSG_HISTOGRAM_VEC.with_label_values(&["kv_get"]),
           kv_scan: GRPC_MSG_HISTOGRAM_VEC.with_label_values(&["kv_scan"]),
           kv_prewrite: GRPC_MSG_HISTOGRAM_VEC.with_label_values(&["kv_prewrite"]),
           kv_commit: GRPC_MSG_HISTOGRAM_VEC.with_label_values(&["kv_commit"]),
           // ...
       }
   }
}

使用的時候也很簡單,直接訪問即可:

@@ -102,10 +155,8 @@ fn make_callback() -> (Box, oneshot:

impl tikvpb_grpc::Tikv for Service {
    fn kv_get(&self, ctx: RpcContext, mut req: GetRequest, sink: UnarySink) {
-        let label = "kv_get";
-        let timer = GRPC_MSG_HISTOGRAM_VEC
-            .with_label_values(&[label])
-            .start_coarse_timer();
+        const LABEL: &str = "kv_get";
+        let timer = self.metrics.kv_get.start_coarse_timer();

        let (cb, future) = make_callback();
        let res = self.storage.async_get(

這樣一個簡單的優(yōu)化可以為 TiKV 提升 7% 的 Raw Get 效率,可以說是很超值了(主要原因是 Raw Get 本身開銷極小,因此在指標(biāo)上花費的時間就顯得有一些顯著了)。但這個優(yōu)化方案其實還有一些問題:

代碼繁瑣,有大量重復(fù)的、或滿足某些 pattern 的代碼;

如果還有另一個 Label 維度,那么需要維護的字段數(shù)量就會急劇膨脹(因為每一種值的組合都需要分配一個字段)。

為了解決以上兩個問題,[rust-prometheus] 提供了 Static Metric 宏。例如對于剛才的 TiKV 改進 PR #2765 來說,使用 Static Metric 宏可以簡化為:

make_static_metric! {
   pub struct GrpcMsgHistogram: Histogram {
       "type" => {
           kv_get,
           kv_scan,
           kv_prewrite,
           kv_commit,
           // ...
       },
   }
}

let metrics = GrpcMsgHistogram::from(GRPC_MSG_HISTOGRAM_VEC);

// Usage:
metrics.kv_get.start_coarse_timer();

可以看到,使用宏之后,需要維護的繁瑣的代碼量大大減少了。這個宏也能正常地支持多個 Label 同時存在的情況。

限于篇幅,這里就不具體講解這個宏是如何寫的了,感興趣的同學(xué)可以觀看我司同學(xué)最近在 FOSDEM 2019 上的技術(shù)分享 視頻(進度條 19:54 開始介紹 Static Metrics)和 Slide,里面詳細(xì)地介紹了如何從零開始寫出一個這樣的宏(的簡化版本)。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/17943.html

相關(guān)文章

  • TiKV 源碼解析系列文章(三)Prometheus(上)

    摘要:指標(biāo)實際上最終會提供一系列時序數(shù)據(jù)觀測值落在各個桶上的累計數(shù)量,如落在各個區(qū)間上的數(shù)量。我們將在發(fā)布后為也使用上原生的原子操作從而提高效率。例如,假設(shè)用戶定義了個桶邊界,分別是,則每個桶對應(yīng)的數(shù)值范圍是,對于觀測值來說需要更新四個桶。 作者:Breezewish 本文為 TiKV 源碼解析系列的第三篇,繼續(xù)為大家介紹 TiKV 依賴的周邊庫 [rust-prometheus],本篇主要...

    TNFE 評論0 收藏0
  • TiKV 源碼解析系列文章(一)序

    摘要:而源碼解析系列文章則是會從源碼層面給大家抽絲剝繭,讓大家知道我們內(nèi)部到底是如何實現(xiàn)的。我們希望通過該源碼解析系列,能讓大家對有一個更深刻的理解。 作者:唐劉 TiKV 是一個支持事務(wù)的分布式 Key-Value 數(shù)據(jù)庫,有很多社區(qū)開發(fā)者基于 TiKV 來開發(fā)自己的應(yīng)用,譬如 titan、tidis。尤其是在 TiKV 成為 CNCF 的 Sandbox 項目之后,吸引了越來越多開發(fā)者的...

    LeviDing 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<