<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Prometheus 存儲層的演進

          共 9377字,需瀏覽 19分鐘

           ·

          2021-06-19 16:13


          Prometheus 是當(dāng)下最流行的監(jiān)控平臺之一,它的主要職責(zé)是從各個目標(biāo)節(jié)點中采集監(jiān)控數(shù)據(jù),后持久化到本地的時序數(shù)據(jù)庫中,并向外部提供便捷的查詢接口。本文嘗試探討 Prometheus 存儲層的演進過程,信息源主要來自于 Prometheus 團隊在歷屆 PromConf 上的分享。

          時序數(shù)據(jù)庫是 Promtheus 監(jiān)控平臺的一部分,在了解其存儲層的演化過程之前,我們需要先了解時序數(shù)據(jù)庫及其要解決的根本問題。

          TSDB

          時序數(shù)據(jù)庫 (Time Series Database, TSDB) 是數(shù)據(jù)庫大家庭中的一員,專門存儲隨時間變化的數(shù)據(jù),如股票價格、傳感器數(shù)據(jù)、機器狀態(tài)監(jiān)控等等。時序 (Time Series) 指的是某個變量隨時間變化的所有歷史,而樣本 (Sample) 指的是歷史中該變量的瞬時值:

          每個樣本由時序標(biāo)識、時間戳數(shù)值 3 部分構(gòu)成,其所屬的時序就由一系列樣本構(gòu)成。由于時間是連續(xù)的,我們不可能、也沒有必要記錄時序在每個時刻的數(shù)值,因此采樣間隔 (Interval) 也是時序的重要組成部分。采樣間隔越小、樣本總量越大、捕獲細節(jié)越多;采樣間隔越大、樣本總量越小、遺漏細節(jié)越多。以服務(wù)器機器監(jiān)控為例,通常采樣間隔為 15 秒。

          數(shù)據(jù)的高效查詢離不開索引,對于時序數(shù)據(jù)而言,唯一的、天然的索引就是時間 (戳)。因此通常時序數(shù)據(jù)庫的存儲層相比于關(guān)系型數(shù)據(jù)庫要簡單得多。仔細思考,你可能會發(fā)現(xiàn)時序數(shù)據(jù)在某種程度上就是鍵值數(shù)據(jù)的一個子集,因此鍵值數(shù)據(jù)庫天然地可以作為時序數(shù)據(jù)的載體。通常一個時序數(shù)據(jù)庫能容納百萬量級以上的時序數(shù)據(jù),要從其中搜索到其中少量的幾個時序也非易事,因此對時序本身建立高效的索引也很重要。

          The Fundamental Problem of TSDBs

          TSDB 要解決的基本問題,可以概括為下圖:

          研究過存儲引擎結(jié)構(gòu)和性能優(yōu)化的工程師都會知道:

          許多數(shù)據(jù)庫的奇技淫巧都是在解決內(nèi)存與磁盤的讀寫模式、性能的不匹配問題

          時序數(shù)據(jù)庫也是數(shù)據(jù)庫的一種,只要它想持久化,自然不能例外。但與鍵值數(shù)據(jù)庫相比,時序數(shù)據(jù)庫存儲的數(shù)據(jù)有更特殊的讀寫特征,Bj?rn Rabenstein 將稱其為:

          Vertical writes, horizontal(-ish) reads
          垂直寫,水平讀

          圖中每條橫線就是一個時序,每個時序由按照 (準(zhǔn)) 固定間隔采集的樣本數(shù)據(jù)構(gòu)成,通常在時序數(shù)據(jù)庫中會有很多活躍時序,因此數(shù)據(jù)寫入可以用一個垂直的窄方框表示,即每個時序都要寫入新的樣本數(shù)據(jù);用戶在查詢時,通常會觀察某個、某幾個時序在某個時間段內(nèi)的變化趨勢,或?qū)ζ溥M行聚合計算,因此數(shù)據(jù)讀取可以用一個水平的方框表示。是謂 “垂直寫、水平讀”。

          Storage Layer of Prometheus

          Prometheus 是為云原生環(huán)境中的數(shù)據(jù)監(jiān)控而生,在其設(shè)計過程中至少需要考慮以下兩個方面:

          1、在云原生環(huán)境中,實例可能隨時出現(xiàn)、消失,因此時序也可能隨時出現(xiàn)或消失,即系統(tǒng)中存在大量時序,其中部分處于活躍狀態(tài),這會在多方面帶來挑戰(zhàn):

          • 如何存儲大量時序避免資源浪費
          • 如何定位被查詢的少數(shù)幾個時序

          2、監(jiān)控系統(tǒng)本身應(yīng)該盡量少地依賴外部服務(wù),否則外部服務(wù)失效將引發(fā)監(jiān)控系統(tǒng)失效

          對于第 2 點,Prometheus 團隊選擇放棄集群,使用單機架構(gòu),并且在單機系統(tǒng)中使用本地 TSDB 做數(shù)據(jù)持久化,完全不依賴外部服務(wù);第 1 點是需要存儲、索引、查詢引擎層合作解決的問題,在下文中我們將進一步分析存儲層在其中的作用。Prometheus 存儲層的演進可以分成 3 個階段:

          • 1st Generation: Prototype
          • 2nd Generation: Prometheus V1
          • 3rd Generation: Prometheus V2

          注意:本節(jié)只關(guān)注 Prometheus 時序數(shù)據(jù)的存儲,不涉及索引、WAL 等其它數(shù)據(jù)的存儲。

          Data Model

          盡管數(shù)據(jù)模型是存儲層之上的抽象,理論上它不應(yīng)該影響存儲層的設(shè)計。但理解數(shù)據(jù)模型能夠幫助我們更快地理解存儲層。

          在 Prometheus 中,每個時序?qū)嶋H上由多個標(biāo)簽 (labels) 標(biāo)識,如:

          api_http_requests_total{path="/users",status=200,method="GET",instance="10.111.201.26"}

          該時序的名字為 api_http_requests_total,標(biāo)簽為 path、status、method 和 instance,只有時序名字和標(biāo)簽鍵值完全相同的時序才是同一個時序。事實上,時序名字就是一個隱藏標(biāo)簽:

          {name="api_http_requests_total",path="/users",status=200,method="GET",
          instance="10.111.201.26"}

          對于用戶來說,標(biāo)簽之間不存在先后順序,用戶可能關(guān)注:

          • 所有 api 調(diào)用的 status
          • 某個 path 調(diào)用的成功率、QPS
          • 某個實例、某個 path 調(diào)用的成功率

          1st Generation: Prototype

          在 Prototype 階段,Prometheus 直接利用開源的鍵值數(shù)據(jù)庫 (LevelDB) 作為本地持久化存儲,并采用與 BigTable 推薦的時序數(shù)據(jù)方案[1] 類似的 schema 設(shè)計:

          時序名稱、標(biāo)簽 (固定順序)、時間戳拼接成每個樣本的鍵,于是同一個時序的數(shù)據(jù)就能夠連續(xù)存儲在鍵值數(shù)據(jù)庫中,提高范圍查詢的效率。但從圖中可以看出,這種方式存儲的鍵很長,盡管鍵值數(shù)據(jù)庫內(nèi)部會對數(shù)據(jù)進行壓縮,但是在內(nèi)存中這樣存儲數(shù)據(jù)很浪費空間,這無法滿足項目的設(shè)計要求。Prometheus 希望在內(nèi)存中壓縮數(shù)據(jù),使得內(nèi)存中可以容納更多活躍的時序數(shù)據(jù),同時在磁盤中也能按類似的方式壓縮編碼,提高效率。時序數(shù)據(jù)比通用鍵值數(shù)據(jù)有更顯著的特征。即使鍵值數(shù)據(jù)庫能夠壓縮數(shù)據(jù),但針對時序數(shù)據(jù)的特征,使用特殊的壓縮算法能夠取得更好的壓縮率。因此在 Prototype 階段,使用三方鍵值數(shù)據(jù)庫的方案最終流產(chǎn)。

          2nd Generation: Prometheus V1

          Compression

          Why Compression

          假設(shè)監(jiān)控系統(tǒng)的需求如下:

          • 500 萬活躍時序
          • 30 秒采樣間隔
          • 1 個月數(shù)據(jù)留存

          那么經(jīng)過計算可以得到具體的存儲要求:

          • 平均每秒采集 166000 個樣本
          • 存儲樣本總量為 4320 億個樣本

          假設(shè)沒有任何壓縮,不算時序標(biāo)識,每個樣本需要 16 個字節(jié)存儲空間 (時間戳 8 個字節(jié)、數(shù)值 8 個字節(jié)),整個系統(tǒng)的存儲總量為 7TB,假設(shè)數(shù)據(jù)需要留存 6 個月,則總量為 42 TB,那么如果能找到一種有效的方式壓縮數(shù)據(jù),就能在單機的內(nèi)存和磁盤中存放更多、更長的時序數(shù)據(jù)。

          Chunked Storage Abstraction

          上文提到 TSDB 的根本問題是 “垂直寫,水平讀”,每次采樣都會需要為每個活躍時序?qū)懭胍粭l樣本數(shù)據(jù),但如果每次為每個時序?qū)懭?16 個字節(jié)到 HDD/SSD 中,顯然這對塊存儲設(shè)備十分不友好,效率低下。因此 Prometheus V2 將數(shù)據(jù)按固定長度切割相同大小的分段 (Chunks),方便壓縮、批量讀寫。

          訪問時序數(shù)據(jù)時,Prometheus 使用 3 層抽象,如下圖所示:

          應(yīng)用層使用 Series Iterator 順序訪問時序中的樣本,而 Series Iterator 底下由一個個 Chunk Iterator 拼接而成,每個 Chunk Iterator 負責(zé)將壓縮編碼的時序數(shù)據(jù)解碼返回。這樣做的好處是,每個 Chunk 甚至可以使用完全不同的方式編碼,方便開發(fā)團隊嘗試不同的編碼方案。

          Timestamp Compression: Double Delta

          由于通常數(shù)據(jù)采樣間隔是固定值,因此前后時間戳的差值幾乎固定,如 15s,30s。但如果我們更近一步,只存儲差值的差值,那么幾乎不用再為新的時間戳花費額外的空間,這便是所謂的 “Double Delta“。本質(zhì)上,如果未來所有的采集時間戳都可以精準(zhǔn)預(yù)測,那么每個新時間戳的信息熵為 0 bit。但現(xiàn)實并不完美,網(wǎng)絡(luò)可能延遲、中斷,實例可能遇到 GC、重啟,采樣間隔隨時有可能波動:

          但這種波動的幅度有限,Prometheus 采用了和 FB 的內(nèi)存時序數(shù)據(jù)庫 Gorilla 類似的方式編碼時間戳,詳情可以參考 Gorilla) 以及 Bj?rn Rabenstein 在 PromConn 2016 的演講 ppt[2] ,細節(jié)比較瑣碎,這里不贅述。

          Value Compression

          Prometheus 和 Gorilla 中的每個樣本值都是 float64 類型。Gorilla 利用 float64 的二進制表示 (IEEE754) 將前后兩個樣本值 XOR 來尋找壓縮的空間,能獲得 1.37 bytes/sample 的壓縮能力。Prometheus V2 采用的方式比較簡單:

          • 如果可能的話,使用整型 (8/16/32 位) 存儲,否則用 float32,最后實在不行就直接存儲 float64
          • 如果數(shù)值增長得很規(guī)律,則不使用額外的空間存儲

          以上做法給 Prometheus V1 帶來了 3.3 bytes/sample 的壓縮能力。相比于為完全存儲于內(nèi)存中的 Gorilla 相比,這樣的壓縮能力對于 Prometheus 已經(jīng)夠用,但在 V2 中,Prometheus 也融合了 Gorilla 采用的壓縮技術(shù)。

          Chunk Encoding

          Prometheus V1 將每個時序分割成大小為 1KB 的 chunks,如下圖所示:

          在內(nèi)存中保留著最近寫入的 chunk,其中 head chunk 正在接收新的樣本。每當(dāng)一個 head chunk 寫滿 1KB 時,會立即被凍結(jié),我們稱之為完整的 chunk,從此刻開始該 chunk 中的數(shù)據(jù)就是不可變的 (immutable) ,同時生成一個新的 head chunk 負責(zé)消化新的請求。每個完整的 chunk 會被盡快地持久化到磁盤中。內(nèi)存中保存著每個時序最近被寫入或被訪問的 chunks,當(dāng) chunks 數(shù)量過多時,存儲引擎會將超過的 chunks 通過 LRU 策略清出。

          在 Prometheus V1 中,每個時序都會被存儲到在一個獨占的文件中,這也意味著大量的時序?qū)a(chǎn)生大量的文件。存儲引擎會定期地去檢查磁盤中的時序文件,是否已經(jīng)有 chunk 數(shù)據(jù)超過保留時間,如果有則將其刪除 (復(fù)制后刪除)。

          Prometheus 的查詢引擎的查詢過程必須完全在內(nèi)存中進行。因此在執(zhí)行之前,存儲引擎需要將不在內(nèi)存中的 chunks 預(yù)加載到內(nèi)存中:

          如果在內(nèi)存中的 chunks 持久化之前系統(tǒng)發(fā)生崩潰,則會產(chǎn)生數(shù)據(jù)丟失。為了減少數(shù)據(jù)丟失,Prometheus V1 還使用了額外的 checkpoint 文件,用于存儲各個時序中尚未寫入磁盤的 chunks:

          Prometheus V1 vs. Gorilla

          正因為 Prometheus V1 與 Gorilla 的設(shè)計理念、需求有所不同,我們可以通過對比二者來理解其設(shè)計過程中使用不同決策的原因。

          3rd Generation: Prometheus V2

          The Main Problem With 2nd Generation

          Prometheus V1 中,每個時序數(shù)據(jù)對應(yīng)一個磁盤文件的方式給系統(tǒng)帶來了比較大的麻煩:

          • 由于在云原生環(huán)境下,會不斷產(chǎn)生新的時序、廢棄舊的時序 (Series Churn),因此實際上存儲層需要的文件數(shù)量遠遠高于活躍的時序數(shù)量。任其發(fā)展遲早會將文件系統(tǒng)的 inodes 消耗殆盡。而且一旦發(fā)生,恢復(fù)系統(tǒng)將異常麻煩。不僅如此,在新舊時序大量更迭時,由于舊時序數(shù)據(jù)尚未從內(nèi)存中清出,系統(tǒng)的內(nèi)存消耗量也會飆升,造成 OOM。
          • 即便使用 chunks 來批量讀寫數(shù)據(jù),從整體上看,系統(tǒng)每秒鐘仍要向磁盤寫入數(shù)千個 chunks,造成 I/O 壓力;如果通過增大每批寫入的量來減少 I/O 次數(shù),又將造成內(nèi)存的壓力。
          • 同時將所有時序文件保持打開狀態(tài)很不合理,需要消耗大量的資源。如果在查詢前后打開、關(guān)閉文件,又會增加查詢的時延。
          • 當(dāng)數(shù)據(jù)超過留存時間時需要刪除相關(guān)的 chunks,這意味著每隔一段時間就要對數(shù)百萬的文件執(zhí)行一次刪除數(shù)據(jù)操作,這個過程可能需要持續(xù)數(shù)小時。
          • 通過周期性地將未持久化的 chunks 寫入 checkpoint 文件理論上確實可以減少數(shù)據(jù)丟失,但是如果執(zhí)行數(shù)據(jù)恢復(fù)需要很長時間,那么實際上又錯過了新的數(shù)據(jù),還不如不恢復(fù)。

          因此 Prometheus 的第三代存儲引擎,主要改變就是放棄 “一個時序?qū)?yīng)一個文件” 的設(shè)計理念。

          Macro Design

          第三代存儲引擎在磁盤中的文件結(jié)構(gòu)如下圖所示:

          根目錄下,順序排列著編了號的 blocks,每個 block 中包含 index 和 chunk 文件夾,后者里面包含編了號的 chunks,每個 chunk 包含許多不同時序的樣本數(shù)據(jù)。其中 index 文件中的信息可以幫我我們快速鎖定時序的標(biāo)簽及其可能的取值,進而找到相關(guān)的時序和持有該時序樣本數(shù)據(jù)的 chunks。值得注意的是,最新的 block 文件夾中還包含一個 wal 文件夾,后者將承擔(dān)故障恢復(fù)的職責(zé)。

          Many Little Databases

          第三代存儲引擎將所有時序數(shù)據(jù)按時間分片,即在時間維度上將數(shù)據(jù)劃分成互不重疊的 blocks,如下圖所示:

          每個 block 實際上就是一個小型數(shù)據(jù)庫,內(nèi)部存儲著該時間窗口內(nèi)的所有時序數(shù)據(jù),因此它需要擁有自己的 index 和 chunks。除了最新的、正在接收新鮮數(shù)據(jù)的 block 之外,其它 blocks 都是不可變的。由于新數(shù)據(jù)的寫入都在內(nèi)存中,數(shù)據(jù)的寫效率較高:

          為了防止數(shù)據(jù)丟失,所有新采集的數(shù)據(jù)都會被寫入到 WAL 日志中,在系統(tǒng)恢復(fù)時能快速地將其中的數(shù)據(jù)恢復(fù)到內(nèi)存中。在查詢時,我們需要將查詢發(fā)送到不同的 block 中,再將結(jié)果聚合。

          按時間將數(shù)據(jù)分片賦予了存儲引擎新的能力:

          • 當(dāng)查詢某個時間范圍內(nèi)的數(shù)據(jù),我們可以直接忽略在時間范圍外的 blocks
          • 寫完一個 block 后,我們可以將輕易地其持久化到磁盤中,因為只涉及到少量幾個文件的寫入
          • 新的數(shù)據(jù),也是最常被查詢的數(shù)據(jù)會處在內(nèi)存中,提高查詢效率 (第二代同樣支持)
          • 每個 chunk 不再是固定的 1KB 大小,我們可以選擇任意合適的大小,選擇合適的壓縮方式
          • 刪除超過留存時間的數(shù)據(jù)變得異常簡單,直接刪除整個文件夾即可

          mmap

          第三代引擎將數(shù)百萬的小文件合并成少量大文件,也讓 mmap 成為可能。利用 mmap 將文件 I/O 、緩存管理交給操作系統(tǒng),降低 OOM 發(fā)生的頻率。

          Compaction

          在 Macro Design 中,我們將所有時序數(shù)據(jù)按時間切割成許多 blocks,當(dāng)新寫滿的 block 持久化到磁盤后,相應(yīng)的 WAL 文件也會被清除。寫入數(shù)據(jù)時,我們希望每個 block 不要太大,比如 2 小時左右,來避免在內(nèi)存中積累過多的數(shù)據(jù)。讀取數(shù)據(jù)時,若查詢涉及到多個時間段,就需要對許多個 block 分別執(zhí)行查詢,然后再合并結(jié)果。假如需要查詢一周的數(shù)據(jù),那么這個查詢將涉及到 80 多個 blocks,降低數(shù)據(jù)讀取的效率。

          為了既能寫得快,又能讀得快,我們就得引入 compaction,后者將一個或多個 blocks 中的數(shù)據(jù)合并成一個更大的 block,在合并的過程中會自動丟棄被刪除的數(shù)據(jù)、合并多個版本的數(shù)據(jù)、重新結(jié)構(gòu)化 chunks 來優(yōu)化查詢效率,如下圖所示:

          Retention

          當(dāng)數(shù)據(jù)超過留存時間時,刪除舊數(shù)據(jù)非常容易:

          直接刪除在邊界之外的 block 文件夾即可。如果邊界在某個 block 之內(nèi),則暫時將它留存,知道邊界超出為止。當(dāng)然,在 Compaction 中,我們會將舊的 blocks 合并成更大的 block;在 Retention 時,我們又希望能夠粒度更小。所以 Compaction 與 Retention 的策略之間存在著一定的互斥關(guān)系。Prometheus 的系統(tǒng)參數(shù)可以對單個 block 的大小作出限制,來尋找二者之間的平衡。

          看到這里,相信你已經(jīng)發(fā)現(xiàn)了,這不就是 LSM Tree **嗎?**每個 block 就是按時間排序的 SSTable,內(nèi)存中的 block 就是 MemTable。

          Compression

          第三代存儲引擎融合了 Gorilla 的 XOR float encoding 方案,將壓縮能力提升到 1-2 bytes/sample。具體方案可以概括為:按順序采用以下第一條適用的策略

          1. Zero encoding:如果完全可預(yù)測,則無需額外空間
          2. Integer double-delta encoding:如果是整型,可以利用 double-delta 原理,將不等的前后間隔分成 6/13/20/33 bits 幾種,來優(yōu)化空間使用
          3. XOR float encoding:參考 Gorilla
          4. Direct encoding:直接存 float64

          平均下來能取得 1.28 bytes/sample 的壓縮能力。

          References

          • PromCon 2017: Storing 16 Bytes at Scale - Fabian Reinartz[3], slides[4]
          • Writing a Time Series Database from Scratch[5]
          • PromCon 2016: The Prometheus Time Series Database - Bj?rn Rabenstein[6], slides[7]
          • Percona Live Open Source Database Conference 2017: Life of a PromQL query[8]
          • Prometheus 1.8 doc: storage[9]
          • Prometheus 2.16 doc: storage[10]
          • Google Cloud: Schema Design for Time Series Data[11]

          腳注

          [1]

          BigTable 推薦的時序數(shù)據(jù)方案: https://link.zhihu.com/?target=https%3A//cloud.google.com/bigtable/docs/schema-design-time-series%3Fhl%3Den%23server_metrics

          [2]

          ppt: https://link.zhihu.com/?target=https%3A//docs.google.com/presentation/d/1TMvzwdaS8Vw9MtscI9ehDyiMngII8iB_Z5D4QW4U4ho/edit%23slide%3Did.g15afea0287_0_16

          [3]

          PromCon 2017: Storing 16 Bytes at Scale - Fabian Reinartz: https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3Db_pEevMAC3I%26feature%3Dyoutu.be

          [4]

          slides: https://link.zhihu.com/?target=https%3A//promcon.io/2017-munich/slides/storing-16-bytes-at-scale.pdf

          [5]

          Writing a Time Series Database from Scratch: https://link.zhihu.com/?target=https%3A//fabxc.org/tsdb/

          [6]

          PromCon 2016: The Prometheus Time Series Database - Bj?rn Rabenstein: https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3DHbnGSNEjhUc

          [7]

          slides: https://link.zhihu.com/?target=https%3A//docs.google.com/presentation/d/1TMvzwdaS8Vw9MtscI9ehDyiMngII8iB_Z5D4QW4U4ho/edit%23slide%3Did.g59e2f6081_1_0

          [8]

          Percona Live Open Source Database Conference 2017: Life of a PromQL query: https://link.zhihu.com/?target=https%3A//www.youtube.com/watch%3Fv%3DevPYwNzoltU%26t%3D782s

          [9]

          Prometheus 1.8 doc: storage: https://link.zhihu.com/?target=https%3A//prometheus.io/docs/prometheus/1.8/storage/

          [10]

          Prometheus 2.16 doc: storage: https://link.zhihu.com/?target=https%3A//prometheus.io/docs/prometheus/latest/storage/

          [11]

          Google Cloud: Schema Design for Time Series Data: https://link.zhihu.com/?target=https%3A//cloud.google.com/bigtable/docs/schema-design-time-series%3Fhl%3Den%23server_metrics


          原文鏈接:https://zhuanlan.zhihu.com/p/155719693


          你可能還喜歡

          點擊下方圖片即可閱讀

          Litmus 實踐:讓群魔在混沌中亂舞,看 K8s 能撐到何時

          云原生是一種信仰 ??

          關(guān)注公眾號

          后臺回復(fù)?k8s?獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!



          點擊 "閱讀原文" 獲取更好的閱讀體驗!


          發(fā)現(xiàn)朋友圈變“安靜”了嗎?

          瀏覽 58
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  男人露大鸡巴无遮挡免费视频 | 欧美亚洲9 1 | 老熟女重囗味x88AV | 国产一级免费在线观看 | 国产伦子伦一级A片在线 |