日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

Loki 作為一個新興的日志解決方案,現(xiàn)在越來越受到關(guān)注。經(jīng)過調(diào)研比較,我們正在將日志服務(wù)底層逐步從 ES 替換為 Loki 。

再見,ELK

圖片來自 Pexels

本文基于我們對 Loki 的使用和理解,從它產(chǎn)生的背景、解決的問題、采用的方案、系統(tǒng)架構(gòu)、實現(xiàn)邏輯等做一些剖析,希望對關(guān)注 Loki 的小伙伴們提供一些幫助。

在日常的系統(tǒng)可視化監(jiān)控過程中,當(dāng)監(jiān)控探知到指標(biāo)異常時,我們往往需要對問題的根因做出定位。

但監(jiān)控數(shù)據(jù)所暴露的信息是提前預(yù)設(shè)、高度提煉的,在信息量上存在著很大的不足,它需要結(jié)合能夠承載豐富信息的日志系統(tǒng)一起使用。

當(dāng)監(jiān)控系統(tǒng)探知到異常告警,我們通常在 Dashboard 上根據(jù)異常指標(biāo)所屬的集群、主機、實例、應(yīng)用、時間等信息圈定問題的大致方向,然后跳轉(zhuǎn)到日志系統(tǒng)做更精細(xì)的查詢,獲取更豐富的信息來最終判斷問題根因。

在如上流程中,監(jiān)控系統(tǒng)和日志系統(tǒng)往往是獨立的,使用方式具有很大差異。比如監(jiān)控系統(tǒng) Prometheus 比較受歡迎,日志系統(tǒng)多采用 ES+Kibana 。

他們具有完全不同的概念、不同的搜索語法和界面,這不僅給使用者增加了學(xué)習(xí)成本,也使得在使用時需在兩套系統(tǒng)中頻繁做上下文切換,對問題的定位遲滯。

此外,日志系統(tǒng)多采用全文索引來支撐搜索服務(wù),它需要為日志的原文建立反向索引,這會導(dǎo)致最終存儲數(shù)據(jù)相較原始內(nèi)容成倍增長,產(chǎn)生不可小覷的存儲成本。

并且,不管數(shù)據(jù)將來是否會被搜索,都會在寫入時因為索引操作而占用大量的計算資源,這對于日志這種寫多讀少的服務(wù)無疑也是一種計算資源的浪費。

Loki 則是為了應(yīng)對上述問題而產(chǎn)生的解決方案,它的目標(biāo)是打造能夠與監(jiān)控深度集成、成本極度低廉的日志系統(tǒng)。

Loki 日志方案

低使用成本

①數(shù)據(jù)模型

在數(shù)據(jù)模型上,Loki 參考了 Prometheus ,數(shù)據(jù)由標(biāo)簽、時間戳、內(nèi)容組成,所有標(biāo)簽相同的數(shù)據(jù)屬于同一日志流:

  • 標(biāo)簽,描述日志所屬集群、服務(wù)、主機、應(yīng)用、類型等元信息, 用于后期搜索服務(wù)。
  • 時間戳,日志的產(chǎn)生時間。
  • 內(nèi)容,日志的原始內(nèi)容。

具有如下結(jié)構(gòu):

{ 
  "stream": {  
    "label1": "value1", 
    "label1": "value2" 
  }, # 標(biāo)簽 
  "values": [ 
    ["<timestamp nanoseconds>","log content"], # 時間戳,內(nèi)容 
    ["<timestamp nanoseconds>","log content"] 
  ] 
} 

Loki 還支持多租戶,同一租戶下具有完全相同標(biāo)簽的日志所組成的集合稱為一個日志流。

在日志的采集端使用和監(jiān)控時序數(shù)據(jù)一致的標(biāo)簽,這樣在可以后續(xù)與監(jiān)控系統(tǒng)結(jié)合時使用相同的標(biāo)簽,也為在 UI 界面中與監(jiān)控結(jié)合使用做快速上下文切換提供數(shù)據(jù)基礎(chǔ)。

LogQL:Loki 使用類似 Prometheus 的 PromQL 的查詢語句 logQL ,語法簡單并貼近社區(qū)使用習(xí)慣,降低用戶學(xué)習(xí)和使用成本。

語法例子如下:

{file="debug.log""} |= "err" 

流選擇器:{label1="value1", label2="value2"}, 通過標(biāo)簽選擇日志流, 支持等、不等、匹配、不匹配等選擇方式。過濾器:|= "err",過濾日志內(nèi)容,支持包含、不包含、匹配、不匹配等過濾方式。

這種工作方式類似于 find+grep,find 找出文件,grep 從文件中逐行匹配:

find . -name "debug.log" | grep err 

logQL 除支持日志內(nèi)容查詢外,還支持對日志總量、頻率等聚合計算。

Grafana:在 Grafana 中原生支持 Loki 插件,將監(jiān)控和日志查詢集成在一起,在同一 UI 界面中可以對監(jiān)控數(shù)據(jù)和日志進行 side-by-side 的下鉆查詢探索,比使用不同系統(tǒng)反復(fù)進行切換更直觀、更便捷。

再見,ELK

 

此外,在 Dashboard 中可以將監(jiān)控和日志查詢配置在一起,這樣可同時查看監(jiān)控數(shù)據(jù)走勢和日志內(nèi)容,為捕捉可能存在的問題提供更直觀的途徑。

低存儲成本

只索引與日志相關(guān)的元數(shù)據(jù)標(biāo)簽,而日志內(nèi)容則以壓縮方式存儲于對象存儲中, 不做任何索引。

相較于 ES 這種全文索引的系統(tǒng),數(shù)據(jù)可在十倍量級上降低,加上使用對象存儲,最終存儲成本可降低數(shù)十倍甚至更低。

方案不解決復(fù)雜的存儲系統(tǒng)問題,而是直接應(yīng)用現(xiàn)有成熟的分布式存儲系統(tǒng),比如 S3、GCS、Cassandra、BigTable 。

Loki 架構(gòu)

整體上 Loki 采用了讀寫分離的架構(gòu),由多個模塊組成:

  • Promtail、Fluent-bit、Fluentd、Rsyslog 等開源客戶端負(fù)責(zé)采集并上報日志。
  • Distributor:日志寫入入口,將數(shù)據(jù)轉(zhuǎn)發(fā)到 Ingester。
  • Ingester:日志的寫入服務(wù),緩存并寫入日志內(nèi)容和索引到底層存儲。
  • Querier:日志讀取服務(wù),執(zhí)行搜索請求。
  • QueryFrontend:日志讀取入口,分發(fā)讀取請求到 Querier 并返回結(jié)果。
  • Cassandra/BigTable/DnyamoDB/S3/GCS:索引、日志內(nèi)容底層存儲。
  • Cache:緩存,支持 redis/Memcache/本地 Cache。

其主體結(jié)構(gòu)如下圖所示:

再見,ELK

 

Distributor:作為日志寫入的入口服務(wù),其負(fù)責(zé)對上報數(shù)據(jù)進行解析、校驗與轉(zhuǎn)發(fā)。

它將接收到的上報數(shù)解析完成后會進行大小、條目、頻率、標(biāo)簽、租戶等參數(shù)校驗,然后將合法數(shù)據(jù)轉(zhuǎn)發(fā)到 Ingester 服務(wù),其在轉(zhuǎn)發(fā)之前最重要的任務(wù)是確保同一日志流的數(shù)據(jù)必須轉(zhuǎn)發(fā)到相同 Ingester 上,以確保數(shù)據(jù)的順序性。

Hash 環(huán):Distributor 采用一致性哈希與副本因子相結(jié)合的辦法來決定數(shù)據(jù)轉(zhuǎn)發(fā)到哪些 Ingester 上。

Ingester 在啟動后,會生成一系列的 32 位隨機數(shù)作為自己的 Token ,然后與這一組 Token 一起將自己注冊到 Hash 環(huán)中。

在選擇數(shù)據(jù)轉(zhuǎn)發(fā)目的地時,Distributor 根據(jù)日志的標(biāo)簽和租戶 ID 生成 Hash,然后在 Hash 環(huán)中按 Token 的升序查找第一個大于這個 Hash 的 Token ,這個 Token 所對應(yīng)的 Ingester 即為這條日志需要轉(zhuǎn)發(fā)的目的地。

如果設(shè)置了副本因子,順序的在之后的 Token 中查找不同的 Ingester 做為副本的目的地。

Hash 環(huán)可存儲于 etcd、consul 中。另外 Loki 使用 Memberlist 實現(xiàn)了集群內(nèi)部的 KV 存儲,如不想依賴 etcd 或 consul ,可采用此方案。

輸入輸出:Distributor 的輸入主要是以 HTTP 協(xié)議批量的方式接受上報日志,日志封裝格式支持 JSON 和 PB ,數(shù)據(jù)封裝結(jié)構(gòu):

[ 
  { 
   "stream": {  
     "label1": "value1", 
     "label1": "value2" 
   }, 
   "values": [ 
     ["<timestamp nanoseconds>","log content"], 
     ["<timestamp nanoseconds>","log content"] 
   ] 
  }, 
  ...... 
] 

Distributor 以 grpc 方式向 ingester 發(fā)送數(shù)據(jù),數(shù)據(jù)封裝結(jié)構(gòu):

{ 
  "streams": [ 
    { 
      "labels": "{label1=value1, label2=value2}", 
      "entries": [ 
          {"ts": <unix epoch in nanoseconds>, "line:":"<log line>" }, 
          {"ts": <unix epoch in nanoseconds>, "line:":"<log line>" }, 
      ] 
    } 
    .... 
   ] 
} 

①Ingester

作為 Loki 的寫入模塊,Ingester 主要任務(wù)是緩存并寫入數(shù)據(jù)到底層存儲。根據(jù)寫入數(shù)據(jù)在模塊中的生命周期,ingester 大體上分為校驗、緩存、存儲適配三層結(jié)構(gòu)。

②校驗

Loki 有個重要的特性是它不整理數(shù)據(jù)亂序,要求同一日志流的數(shù)據(jù)必須嚴(yán)格遵守時間戳單調(diào)遞增順序?qū)懭搿?/p>

所以除對數(shù)據(jù)的長度、頻率等做校驗外,至關(guān)重要的是日志順序檢查。

Ingester 對每個日志流里每一條日志都會和上一條進行時間戳和內(nèi)容的對比,策略如下:

  • 與上一條日志相比,本條日志時間戳更新,接收本條日志。
  • 與上一條日志相比,時間戳相同內(nèi)容不同,接收本條日志。
  • 與上一條日志相比,時間戳和內(nèi)容都相同,忽略本條日志。
  • 與上一條日志相比,本條日志時間戳更老,返回亂序錯誤。

③緩存

日志在內(nèi)存中的緩存采用多層樹形結(jié)構(gòu)對不同租戶、日志流做出隔離。同一日志流采用順序追加方式寫入分塊:

  • Instances:以租戶的 userID 為鍵 Instance 為值的 Map 結(jié)構(gòu)。
  • Instance:一個租戶下所有日志流(stream)的容器。
  • Streams:以日志流的指紋(streamFP)為鍵,Stream 為值的 Map 結(jié)構(gòu)。
  • Stream:一個日志流所有 Chunk 的容器。
  • Chunks:Chunk 的列表。
  • Chunk:持久存儲讀寫最小單元在內(nèi)存態(tài)的結(jié)構(gòu)。
  • Block:Chunk 的分塊,為已壓縮歸檔的數(shù)據(jù)。
  • HeadBlock:尚在開放寫入的分塊。
  • Entry:單條日志單元,包含時間戳(timestamp)和日志內(nèi)容(line)

整體結(jié)構(gòu)如下:

再見,ELK

 

Chunks:在向內(nèi)存寫入數(shù)據(jù)前,ingester 首先會根據(jù)租戶ID(userID)和由標(biāo)簽計算的指紋(streamPF)定位到日志流(stream)及 Chunks。

Chunks 由按時間升序排列的 chunk 組成,最后一個 chunk 接收最新寫入的數(shù)據(jù),其他則等刷寫到底層存儲。

當(dāng)最后一個 chunk 的存活時間或數(shù)據(jù)大小超過指定閾值時,Chunks 尾部追加新的 chunk 。

Chunk:Chunk 為 Loki 在底層存儲上讀寫的最小單元在內(nèi)存態(tài)下的結(jié)構(gòu)。其由若干 block 組成,其中 headBlock 為正在開放寫入的 block ,而其他 Block 則已經(jīng)歸檔壓縮的數(shù)據(jù)。

Block:Block 為數(shù)據(jù)的壓縮單元,目的是為了在讀取操作那里避免因為每次解壓整個 Chunk 而浪費計算資源,因為很多情況下是讀取一個 chunk 的部分?jǐn)?shù)據(jù)就滿足所需數(shù)據(jù)量而返回結(jié)果了。

Block 存儲的是日志的壓縮數(shù)據(jù),其結(jié)構(gòu)為按時間順序的日志時間戳和原始內(nèi)容,壓縮可采用 gzip、snAppy 、lz4 等方式。

HeadBlock:正在接收寫入的特殊 block ,它在滿足一定大小后會被壓縮歸檔為 Block ,然后新 headBlock 會被創(chuàng)建。

存儲適配:由于底層存儲要支持 S3、Cassandra、BigTable、DnyamoDB 等系統(tǒng),適配層將各種系統(tǒng)的讀寫操作抽象成統(tǒng)一接口,負(fù)責(zé)與他們進行數(shù)據(jù)交互。

④輸出

Loki 以 Chunk 為單位在存儲系統(tǒng)中讀寫數(shù)據(jù)。在持久存儲態(tài)下的 Chunk 具有如下結(jié)構(gòu):

  • meta:封裝 chunk 所屬 stream 的指紋、租戶 ID,開始截止時間等元信息。
  • data:封裝日志內(nèi)容,其中一些重要字段。
  • encode 保存數(shù)據(jù)的壓縮方式。
  • block-N bytes 保存一個 block 的日志數(shù)據(jù)。
  • #blocks section byte offset 單元記錄 #block 單元的偏移量。
  • #block 單元記錄一共有多少個 block。
  • #entries 和 block-N bytes 一一對應(yīng),記錄每個 block 里有日式行數(shù)、時間起始點,blokc-N bytes 的開始位置和長度等元信息。
再見,ELK

 

Chunk 數(shù)據(jù)的解析順序:

  • 根據(jù)尾部的 #blocks section byte offset 單元得到 #block 單元的位置。
  • 根據(jù) #block 單元記錄得出 chunk 里 block 數(shù)量。
  • 從 #block 單元所在位置開始讀取所有 block 的 entries、mint、maxt、offset、len 等元信息。
  • 順序的根據(jù)每個 block 元信息解析出 block 的數(shù)據(jù)。

⑤索引

Loki 只索引了標(biāo)簽數(shù)據(jù),用于實現(xiàn)標(biāo)簽→日志流→Chunk 的索引映射, 以分表形式在存儲層存儲。

表結(jié)構(gòu)如下:

CREATE TABLE IF NOT EXISTS Table_N ( 
    hash text, 
    range blob, 
    value blob, 
    PRIMARY KEY (hash, range) 
 ) 

Table_N,根據(jù)時間周期分表名;hash, 不同查詢類型時使用的索引;range,范圍查詢字段;value,日志標(biāo)簽的值。

數(shù)據(jù)類型:Loki 保存了不同類型的索引數(shù)據(jù)用以實現(xiàn)不同映射場景,對于每種類型的映射數(shù)據(jù),Hash/Range/Value 三個字段的數(shù)據(jù)組成如下圖所示:

再見,ELK

 

seriesID 為日志流 ID,shard 為分片,userID 為租戶 ID,labelName 為標(biāo)簽名,labelValueHash 為標(biāo)簽值 hash,chunkID 為 chunk 的 ID,chunkThrough 為 chunk 里最后一條數(shù)據(jù)的時間這些數(shù)據(jù)元素在映射過程中的作用在 Querier 環(huán)節(jié)的[查詢流程]((null))做詳細(xì)介紹。

上圖中三種顏色標(biāo)識的索引類型從上到下分別為:

  • 數(shù)據(jù)類型 1:用于根據(jù)用戶 ID 搜索查詢所有日志流的 ID。
  • 數(shù)據(jù)類型 2:用于根據(jù)用戶 ID 和標(biāo)簽查詢?nèi)罩玖鞯?ID。
  • 數(shù)據(jù)類型 3:用于根據(jù)日志流 ID 查詢底層存儲 Chunk 的 ID。

除了采用分表外,Loki 還采用分桶、分片的方式優(yōu)化索引查詢速度。

分桶:

再見,ELK

 

以天分割:bucketID = timestamp / secondsInDay。

以小時分割:bucketID = timestamp / secondsInHour。

分片:將不同日志流的索引分散到不同分片,shard = seriesID% 分片數(shù)。

Chunk 狀態(tài):Chunk 作為在 Ingester 中重要的數(shù)據(jù)單元,其在內(nèi)存中的生命周期內(nèi)分如下四種狀態(tài):

  • Writing:正在寫入新數(shù)據(jù)。
  • Waiting flush:停止寫入新數(shù)據(jù),等待寫入到存儲。
  • Retain:已經(jīng)寫入存儲,等待銷毀。
  • Destroy:已經(jīng)銷毀。

四種狀態(tài)之間的轉(zhuǎn)換以 writing→waiting flush→retain→destroy 順序進行。

狀態(tài)轉(zhuǎn)換時機:

  • 協(xié)作觸發(fā):有新的數(shù)據(jù)寫入請求。
  • 定時觸發(fā):刷寫周期觸發(fā)將 chunk 寫入存儲,回收周期觸發(fā)將 chunk 銷毀。

writing 轉(zhuǎn)為 waiting flush:chunk 初始狀態(tài)為 writing,標(biāo)識正在接受數(shù)據(jù)的寫入,滿足如下條件則進入到等待刷寫狀態(tài):

  • chunk 空間滿(協(xié)作觸發(fā))。
  • chunk 的存活時間(首末兩條數(shù)據(jù)時間差)超過閾值 (定時觸發(fā))。
  • chunk 的空閑時間(連續(xù)未寫入數(shù)據(jù)時長)超過設(shè)置 (定時觸發(fā))。

waiting flush 轉(zhuǎn)為 etain:Ingester 會定時的將等待刷寫的 chunk 寫到底層存儲,之后這些 chunk 會處于”retain“狀態(tài),這是因為 ingester 提供了對最新數(shù)據(jù)的搜索服務(wù),需要在內(nèi)存里保留一段時間,retain 狀態(tài)則解耦了數(shù)據(jù)的刷寫時間以及在內(nèi)存中的保留時間,方便視不同選項優(yōu)化內(nèi)存配置。

destroy,被回收等待 GC 銷毀:總體上,Loki 由于針對日志的使用場景,采用了順序追加方式寫入,只索引元信息,極大程度上簡化了它的數(shù)據(jù)結(jié)構(gòu)和處理邏輯,這也為 Ingester 能夠應(yīng)對高速寫入提供了基礎(chǔ)。

Querier:查詢服務(wù)的執(zhí)行組件,其負(fù)責(zé)從底層存儲拉取數(shù)據(jù)并按照 LogQL 語言所描述的篩選條件過濾。它可以直接通過 API 提供查詢服務(wù),也可以與 queryFrontend 結(jié)合使用實現(xiàn)分布式并發(fā)查詢。

⑥查詢類型

查詢類型如下:

  • 范圍日志查詢
  • 單日志查詢
  • 統(tǒng)計查詢
  • 元信息查詢

在這些查詢類型中,范圍日志查詢應(yīng)用最為廣泛,所以下文只對范圍日志查詢做詳細(xì)介紹。

并發(fā)查詢:對于單個查詢請求,雖然可以直接調(diào)用 Querier 的 API 進行查詢,但很容易會由于大查詢導(dǎo)致 OOM,為應(yīng)對此種問題 querier 與 queryFrontend 結(jié)合一起實現(xiàn)查詢分解與多 querier 并發(fā)執(zhí)行。

再見,ELK

 

每個 querier 都與所有 queryFrontend 建立 grpc 雙向流式連接,實時從 queryFrontend 中獲取已經(jīng)分割的子查詢求,執(zhí)行后將結(jié)果發(fā)送回 queryFrontend。

具體如何分割查詢及在 querier 間調(diào)度子查詢將在 queryFrontend 環(huán)節(jié)介紹。

⑧查詢流程

先解析 logQL 指令,然后查詢?nèi)罩玖?ID 列表。

Loki 根據(jù)不同的標(biāo)簽選擇器語法使用了不同的索引查詢邏輯,大體分為兩種:

=,或多值的正則匹配=~,工作過程如下:

以類似下 SQL 所描述的語義查詢出標(biāo)簽選擇器里引用的每個標(biāo)簽鍵值對所對應(yīng)的日志流 ID(seriesID)的集合。

SELECT * FROM Table_N WHERE hash=? AND range>=?    AND value=labelValue 

hash 為租戶 ID(userID)、分桶(bucketID)、標(biāo)簽名(labelName)組合計算的哈希值;range 為標(biāo)簽值(labelValue)計算的哈希值。

將根據(jù)標(biāo)簽鍵值對所查詢的多個 seriesID 集合取并集或交集求最終集合。

比如,標(biāo)簽選擇器{file="app.log", level=~"debug|error"}的工作過程如下:

  • 查詢出 file="app.log",level="debug", level="error" 三個標(biāo)簽鍵值所對應(yīng)的 seriesID 集合,S1 、S2、S3。
  • 根據(jù)三個集合計算最終 seriesID 集合 S = S1∩cap (S2∪S3)。

!=,=~,!~,工作過程如下:

以如下 SQL 所描述的語義查詢出標(biāo)簽選擇器里引用的每個標(biāo)簽所對應(yīng) seriesID 集合。

SELECT * FROM Table_N WHERE hash = ? 

hash 為租戶 ID(userID)、分桶(bucketID)、標(biāo)簽名(labelName)。

根據(jù)標(biāo)簽選擇語法對每個 seriesID 集合進行過濾。

將過濾后的集合進行并集、交集等操作求最終集合。

比如,{file~="MySQL*", level!="error"} 的工作過程如下:

  • 查詢出標(biāo)簽“file”和標(biāo)簽"level"對應(yīng)的 seriesID 的集合,S1、S2。
  • 求出 S1 中 file 的值匹配 mysql*的子集 SS1,S2 中 level 的值!="error"的子集 SS2。
  • 計算最終 seriesID 集合 S = SS1∩SS2。

以如下 SQL 所描述的語義查詢出所有日志流所包含的 chunk 的 ID:

SELECT * FROM Table_N Where hash = ? 

hash 為分桶(bucketID)和日志流(seriesID)計算的哈希值。

根據(jù) chunkID 列表生成遍歷器來順序讀取日志行:遍歷器作為數(shù)據(jù)讀取的組件,其主要功能為從存儲系統(tǒng)中拉取 chunk 并從中讀取日志行。其采用多層樹形結(jié)構(gòu),自頂向下逐層遞歸觸發(fā)方式彈出數(shù)據(jù)。

再見,ELK

 

具體結(jié)構(gòu)如上圖所示:

  • batch Iterator:以批量的方式從存儲中下載 chunk 原始數(shù)據(jù),并生成 iterator 樹。
  • stream Iterator:多個 stream 數(shù)據(jù)的遍歷器,其采用堆排序確保多個 stream 之間數(shù)據(jù)的保序;
  • chunks Iterator:多個 chunk 數(shù)據(jù)的遍歷器,同樣采用堆排序確保多個 chunk 之間保序及多副本之間的去重。
  • blocks Iterator:多個 block 數(shù)據(jù)的遍歷器。
  • block bytes Iterator:block 里日志行的遍歷器。

從 Ingester 查詢在內(nèi)存中尚未寫入到存儲中的數(shù)據(jù):由于 Ingester 是定時的將緩存數(shù)據(jù)寫入到存儲中,所以 Querier 在查詢時間范圍較新的數(shù)據(jù)時,還會通過 grpc 協(xié)議從每個 ingester 中查詢出內(nèi)存數(shù)據(jù)。

需要在 ingester 中查詢的時間范圍是可配置的,視 ingester 緩存數(shù)據(jù)時長而定。

上面是日志內(nèi)容查詢的主要流程。至于指標(biāo)查詢的流程與其大同小異,只是增加了指標(biāo)計算的遍歷器層用于從查詢出的日志計算指標(biāo)數(shù)據(jù)。其他兩種則更為簡單,這里不再詳細(xì)展開。

QueryFrontend:Loki 對查詢采用了計算后置的方式,類似于在大量原始數(shù)據(jù)上做 grep,所以查詢勢必會消耗比較多的計算和內(nèi)存資源。

如果以單節(jié)點執(zhí)行一個查詢請求的話很容易因為大查詢造成 OOM、速度慢等性能瓶頸。

為解決此問題,Loki 采用了將單個查詢分解在多個 querier 上并發(fā)執(zhí)行方式,其中查詢請求的分解和調(diào)度則由 queryFrontend 完成。

再見,ELK

 

queryFrontend 在 Loki 的整體架構(gòu)上處于 querier 的前端,它作為數(shù)據(jù)讀取操作的入口服務(wù),其主要的組件及工作流程如上圖所示:

  • 分割 Request:將單個查詢分割成子查詢 subReq 的列表。
  • Feeder:將子查詢順序注入到緩存隊列 Buf Queue。
  • Runner:多個并發(fā)的運行器將 Buf Queue 中的查詢并注入到子查詢隊列,并等待返回查詢結(jié)果。
  • Querier 通過 grpc 協(xié)議實時從子查詢隊列彈出子查詢,執(zhí)行后將結(jié)果返回給相應(yīng)的 Runner。
  • 所有子請求在 Runner 執(zhí)行完畢后匯總結(jié)果返回 API 響應(yīng)。

⑨查詢分割

queryFrontend 按照固定時間跨度將查詢請求分割成多個子查詢。比如,一個查詢的時間范圍是 6 小時,分割跨度為 15 分鐘,則查詢會被分為 6*60/15=24 個子查詢。

⑩查詢調(diào)度

Feeder:Feeder 負(fù)責(zé)將分割好的子查詢逐一的寫入到緩存隊列 Buf Queue,以生產(chǎn)者/消費者模式與下游的 Runner 實現(xiàn)可控的子查詢并發(fā)。

Runner:從 Buf Queue 中競爭方式讀取子查詢并寫入到下游的請求隊列中,并處理來自 Querier 的返回結(jié)果。

Runner 的并發(fā)個數(shù)通過全局配置控制,避免因為一次分解過多子查詢而對 Querier 造成巨大的徒流量,影響其穩(wěn)定性。

子查詢隊列:隊列是一個二維結(jié)構(gòu),第一維存儲的是不同租戶的隊列,第二維存儲同一租戶子查詢列表,它們都是以 FIFO 的順序組織里面的元素的入隊出隊。

分配請求:queryFrontend 是以被動方式分配查詢請求,后端 Querier 與 queryFrontend 實時的通過 grpc 監(jiān)聽子查詢隊列,當(dāng)有新請求時以如下順序在隊列中彈出下一個請求:

  • 以循環(huán)的方式遍歷隊列中的租戶列表,尋找下一個有數(shù)據(jù)的租戶隊列。
  • 彈出該租戶隊列中的最老的請求。

總結(jié)

Loki 作為一個正在快速發(fā)展的項目,最新版本已到 2.0,相較 1.6 增強了諸如日志解析、Ruler、Boltdb-shipper 等新功能,不過基本的模塊、架構(gòu)、數(shù)據(jù)模型、工作原理上已處于穩(wěn)定狀態(tài)。

希望本文的這些嘗試性的剖析能夠能夠為大家提供一些幫助,如文中有理解錯誤之處,歡迎批評指正。

作者:張海軍

編輯:陶家龍

出處:轉(zhuǎn)載自公眾號京東智聯(lián)云開發(fā)者(ID:JDC_Developers)

分享到:
標(biāo)簽:ELK
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達(dá)人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定