<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>

          【參賽總結(jié)】第二屆云原生編程挑戰(zhàn)賽-冷熱讀寫場景的RocketMQ存儲系統(tǒng)設(shè)計

          共 10151字,需瀏覽 21分鐘

           ·

          2021-11-04 07:24

          前言

          人總是這樣,年少時,怨恨自己年少,年邁時,怨恨自己年邁,就連參加一場比賽,都會糾結(jié),工作太忙怎么辦,周末休息怎么辦,成年人的任性往往就在那一瞬間,我只是單純地想經(jīng)歷一場酣暢的性能挑戰(zhàn)賽。所以,云原生挑戰(zhàn)賽,我來了,Kirito 帶著它的公眾號來了。

          讀完寥寥數(shù)百多字的賽題描述,四分之一炷香之后一個靈感出現(xiàn)在腦海中,本以為這個靈感是開篇,沒想到卻是終章。臨近結(jié)束,測試出了緩存命中率更高的方案,但評測已經(jīng)沒有了日志,在茫茫的方案之中,我錯過了最大的那一顆麥穗,但在一個月不長不短的競賽中,我挑選到了一顆不錯的麥穗,從此只有眼前路,沒有身后身,最終僥幸跑出了內(nèi)部賽第一的成績。

          傳統(tǒng)存儲引擎類型的比賽,主要是圍繞著兩種存儲介質(zhì):SSD 和 DRAM,不知道這倆有沒有熬過七年之癢,Intel 就已經(jīng)引入了第三類存儲介質(zhì):AEP(PMem 的一種實現(xiàn))。AEP 的出現(xiàn),讓原本各司其職的 SSD 和 DRAM 關(guān)系變得若即若離起來,它既可以當做 DRAM 用,也可以當做 SSD 用。蘊含在賽題中的”冷熱存儲“這一關(guān)鍵詞,為后續(xù)風起云涌的賽程埋下了伏筆,同時給了 AEP 一個名分。

          AEP 這種存儲介質(zhì)不是第一次出現(xiàn)在我眼前,在 ADB 比賽中就遇到過它,此次比賽開始時,腦子里面對它僅存的印象便是"快"。這個快是以 SSD 為參照物,無論是讀還是寫,都高出傳統(tǒng) SSD 1~n 個數(shù)量級。但更多的認知,只能用 SSD 來類比,AEP 特性的理解和使用方法,無疑是這次的決勝點之一。

          曾經(jīng)的我喜歡問,現(xiàn)在的我喜歡試。一副鍵盤,一個深夜,我窺探到了 AEP 的奧秘,多線程讀寫必不可少,讀取速度和寫入速度近似 DRAM,但細究之下寫比讀慢,從整體吞吐來看,DRAM 的讀寫性能略優(yōu)于 AEP,但 DRAM 和 AEP 的讀寫都比 SSD 快得多的多。我的麥穗也有了初步的模樣:第一優(yōu)先級是降低 SSD 命中率,在此基礎(chǔ)上,提高 DRAM 命中率,AEP 起到平衡的效果,初期不用特別顧忌 AEP 和 DRAM 的命中比例。

          參賽歷程

          萬事開頭難,中間難,后段也難。

          和參加比賽的大多數(shù)人一樣,我成功卡在了第一步:出分。AEP 先別用了,緩存方案也暫且擱置,不愧是 RocketMQ 團隊出的題目,隊列眾多,那就先用 RocketMQ 的經(jīng)典方案,“一階段多線程綁定分區(qū)文件,順序追加寫,索引好辦,內(nèi)存和文件各存一份;二階段隨機讀”,說干就干,兩個小時后,果然得到了“正確性檢測失敗”的提示。把時間軸拉到去年,相比之下今年的參賽隊伍并沒有降低很多,但出分的隊伍明顯下降,大多數(shù)人是卡在了正確性檢測這一關(guān)。咨詢出題人,得知這一次的正確性檢測是實實在在的”掉電“,PageCache 是指望不上了,只能派 FileChannel#force() 出場,成功獲得了第一份成績:1200s,一份幾乎快超時的成績。

          使用 force 解決掉電數(shù)據(jù)不丟失可以參考我賽程初段寫的文章:https://www.cnkirito.moe/filechannel_force/

          優(yōu)化成功的喜悅片刻消散,初次出分的悸動至死不渝。那種感覺就像一覺醒來,發(fā)現(xiàn)今天是周六,可以睡到中午,甚至后面還有一個周日一樣放肆。有了 baseline,下面便可以開始著手優(yōu)化了,剛起頭兒,有的是功夫,有的是希望。

          掉電的限制,使得 SSD 的寫入方案十分受限,每一條數(shù)據(jù)都要 force,使得 pageCache 近似無效,pageCache 從來沒有受過這樣的委屈,我走還不行嗎?

          隨著賽程持續(xù)推進,也就推進了一天吧,我開始著手優(yōu)化合并寫入方案。傳統(tǒng)方案中,聚合寫入往往使用 DRAM 作為寫入緩沖,聚合同一線程內(nèi)前后幾條消息,以減少寫入放大問題,也有諸如 MongoDB 之流,會選擇合并同一批次多個線程之間的數(shù)據(jù),按照題意,多個線程合并寫入,一起 force 已然是題目的唯一解。團結(jié)就是力量,經(jīng)過測試,n 個線程聚合在一起 force,一起返回,可以顯著降低 force 的總次數(shù),緩解寫入放大問題,有效地提升了分數(shù)。n 多少合適呢?理論分析,n 過大,實際 IO 線程就會變少;n 過小,force 次數(shù)多,解決調(diào)參問題,最合適的是機器學習,其次是 benchmark,經(jīng)過多輪 benchmark,4 組 x 10 線程最為合適,4 個 IO 線程正好 = CPU core,這非常的合理。

          合并寫入后,效果顯著,成績來到了 700~800s。如果沒有 AEP,這個比賽也就到頭了,AEP 的加入,像草一樣,一切都卷了起來。

          賽程中段,有朋友在微信交流群中問我,AEP 你有用起來嗎?每個人都會經(jīng)歷這個階段:看見一座山,就想知道山后面是什么。我很想告訴他,可能翻過去山后面,你會發(fā)覺沒有什么特別?;仡^看,會覺得這一邊更好。但我知道他不會聽,以他的性格,自己不試過,又怎么會甘心?我的 trick 無足輕重,他最終還是使用了 AEP,感受到了蒸汽火車到高鐵那般速度的提升??倲?shù)據(jù) 125G,AEP 容量 60G,即使固定存儲最后的 60G 數(shù)據(jù),也可以確保熱讀部分的數(shù)據(jù)全部命中 AEP,SSD 會因為你的刻意保持距離而感到失落,你的分數(shù)不會。

          即便是這樣不經(jīng)任何設(shè)計的 AEP 緩存方案,得益于 AEP 的讀寫速度和較大的容量加持,也可以獲得 600s+ 的分數(shù)。

          分數(shù)這個東西,總是比昨天高一點,比明天少一點,但要想維持這樣的分數(shù)增長,需要持續(xù)付出極大的努力。600s 顯然不足以支撐進入決賽,AEP 緩存固定的數(shù)據(jù)也顯得有點呆,就像你意外獲得了一塊金剛石,不經(jīng)雕琢,則無法成為耀眼奪目的鉆石。必須優(yōu)化 AEP 的使用方案!因為有熱數(shù)據(jù)的存在,寫入的一部分數(shù)據(jù)會在較短的一段時間內(nèi)被消費,緩存方案也需要聯(lián)動起來,寫入-> 消費 -> 寫入 -> 消費,大腦中飛速地模擬、推測評測程序的流程,操作系統(tǒng)、計算機網(wǎng)絡(luò)的概念被一遍遍檢索,最終鎖定在了一個耳熟能詳?shù)母拍睿篢CP 滑動窗口。AEP 保存最熱的 60G 數(shù)據(jù),使得熱讀全部命中,根據(jù)測試,發(fā)現(xiàn)冷讀也會很快變成熱讀,在思路和方案連接的那一刻,代碼流程也直接顯現(xiàn)了出來,三又二分之一小時后,我提交了這份 AEP 滑動窗口的方案,沒有什么比一次 Acccept 更爽的事了,一邊贊嘆自己的編碼能力,一邊自負地停止了優(yōu)化,成績停留在了 504s。

          十月八號零點八分,鐘樓敲響后的八分鐘,我手握著一杯水,打開了排行榜,看到不少 500+ 的分數(shù),懊惱、恐慌、焦慮一下子涌上了心頭,水也越飲越寒。我本有七天時間,優(yōu)化我的方案,但我沒有;我在等一個奇跡,期待大家忘掉這場比賽,放棄優(yōu)化,讓排行榜鎖定在十月一號那一天,但它沒有。我將這份煩惱傾訴給妻子,換來了她的安慰,在她心目中,我永遠是最棒的。我內(nèi)心忐忑地依附道,那當然...在之后的晚上,世上少了一個 WOT 的玩家,多了一個埋頭在 IDEA 中追求極致性能的碼農(nóng)。

          在很長的一段時間里,我一直在追求降低 SSD 的命中率,每降低一點,我的分數(shù)總能夠提升幾秒。不知道從哪一天起,我看到排行榜中出現(xiàn)了一些 450s 的成績,起初這并沒有引起的我的警覺,因為 hack 可以很容易達到 300s,我一開始預(yù)估的極限成績,不過也就是 470s,對,這一定是 hack,心里一遍遍地默念著。但,萬一不是呢?

          太想伸手摘取星星的人,常常忘記腳下的鮮花。我開始翻閱賽題描述,以尋找是否有遺漏的信息;一遍遍 review 自己的代碼,調(diào)整代碼結(jié)構(gòu),優(yōu)化代碼細節(jié);檢索自己過往的博文,以尋找可能擦肩而過的優(yōu)化點。往后的幾個晚上,我做的是同一個夢,夢里面重復播放著自己曾經(jīng)的優(yōu)化經(jīng)驗:4kb 對齊、文件預(yù)分配、緩存行填充...忽然間想起,自己總結(jié)的優(yōu)化經(jīng)驗還沒有完全嘗試過。這次比賽是從第一次寫入開始計時的,選手們可以在構(gòu)造函數(shù)中恣意地預(yù)先分配一些數(shù)據(jù),例如對象提前 new 好,內(nèi)存提前 allocate,減少 runtime 時期的耗時,而這其中最有用的優(yōu)化,當屬 SSD 文件的預(yù)分配和 4kb 填充了。在 append 之前,事先把文件用 0 填充,得到總長度略大于 125G 的空白文件,在寫入時,不足 4kb 的部分使用 4kb 填充,即使多寫了一部分數(shù)據(jù),速度還是能夠提升,換算成實際的寫入速度,可以達到 310M/s,而在此之前,force 的存在使得寫入瓶頸始終卡在 275M/s。寧可一思進,莫在一思留,方案調(diào)通后,成績鎖定在了 440s。

          內(nèi)部賽結(jié)束前的兩周,我又萌生了一個大膽的想法,考慮到 getRange 命中 SSD 時,系統(tǒng)采用的是抽樣檢測,那是不是意味著,用 mmap 讀取就變成了一種懶加載呢?這個思路雖然在實際生產(chǎn)中不太通用,但在賽場上,那可以一把利器,這把利器斬下了 412s 的分數(shù),也割傷了自己,評委不讓用!我的天,我浪費了寶貴的兩周,浪費在了一個無法通過的方案上。天知道評測是在抽樣檢測,我只是認為 mmap 讀會更快呀 :)

          不知道從什么時候開始,在什么東西上面都有個日期,秋刀魚會過期,肉罐頭會過期,比賽也在 10.26 號這天迎來了結(jié)束。未竟的優(yōu)化,設(shè)想的思路,沒能完成方案改造的遺憾都在這一刻失去了意義。我已經(jīng)很久沒有打過比賽了,也很久沒有這樣為一個方案絞盡腦汁了,這場比賽就這樣任性地畫上了一個句號。

          最終方案

          SSD 寫入方案

          寫入方案

          緩存架構(gòu)是制勝點,SSD 的寫入方案則是基本面,相信絕大多數(shù)前排的選手,都采用了上述的架構(gòu)。性能評測階段固定有 40 個線程,我將線程分為了 4 組,每組 10 個線程,進行 IO 合并。為什么是 4組在參賽歷程中也介紹過,尊重 benchmark 的結(jié)果。1~9 號線程寫入緩沖區(qū)完畢之后就 await 進入阻塞態(tài),留下 10 號線程進行 IO,刷盤之后,notify 其他線程返回結(jié)果,如此往復,是一個非常經(jīng)典的生產(chǎn)者消費者模式。

          由于這次比賽,recover 階段是不計入得分的,為了降低 force 的開銷,我選擇將索引的持久化和數(shù)據(jù)存在一起,這樣避免了單獨維護索引文件。在我的方案中,索引需要維護 topic,queue,length 三個信息,只需要定長的 5 個字節(jié),和 100b~17Kb 的數(shù)據(jù)相比,微不足道,合并之后收益是很明顯的。

          選擇使用 JUC 提供的 Lock + Condition 實現(xiàn) wait/notify,一則是自己比較習慣這么控制并發(fā),二則是復用其中一個 append 線程做刷盤的 IO 線程,相比其他并發(fā)方案的線程切換,要少一點。事實上,這次比賽中,CPU 是非常富余的,不會成為瓶頸,該模式的優(yōu)勢并沒有完全發(fā)揮出來。

          4kb 對齊是 SSD 經(jīng)典的優(yōu)化技巧,盡管并不是每一次性能挑戰(zhàn)賽它都能排上用場,但請務(wù)必不要忘記嘗試它。它對于人們的啟發(fā)是使用 4kb 整數(shù)倍的寫入緩沖聚合數(shù)據(jù),整體刷盤,從而避免讀寫放大問題。此次比賽稍顯特殊,由于賽題數(shù)據(jù)的隨機分布特性,10 個線程聚合后的數(shù)據(jù),往往不是 4KB 的整數(shù)倍,但這不妨礙我們做填充,看似多寫入了一部分無意義的數(shù)據(jù),但實際上會使得寫入速度得到提升,尤其是在 force 情況下。

          我曾和 @chender 交流過 4KB 填充這個話題,嘗試分析出背后的原因,這里的結(jié)論不一定百分之百正確。4KB 是 SSD 的最小讀寫單元,這涉及硬件的操作,如果不填充,考慮以下執(zhí)行流程,寫入 9KB,force,寫入 9 Kb,force,如果不做填充,相當于 force 了 9+3+3+9+3=27 kb,中間交叉的 3 kb,在 force 時會被重復計算,而填充過后,一定是 force 了 9+3+9+3=24 kb,整體 force 量降低。還有一個可能的依據(jù)是,沒有填充的情況下,其實一定程度破壞了順序?qū)?,寫入實際寫入了 12kb,但第二次寫入并沒有從 12kb 開始寫入,而是從 9kb 寫入??傊?benchmark 下,4kb 對齊確實帶來了 15s+ 的收益。

          寫入階段還有一個致勝的優(yōu)化,文件預(yù)分配。在 C 里面,有 fallocate,而 Java 并沒有對應(yīng)的 JNI 調(diào)用,不過可以取巧,利用 append 開始計分這個評測特性,在 SSD 上先使用字節(jié) 0 填充整個文件。在預(yù)分配過后,使用 force 也可以獲得跟不使用 force 一樣的寫入速度,幾乎打滿了 320M/s 的 IO 速度。這個優(yōu)化點,我在之前的博客中也分享過,不知道有沒有其他選手看到并利用了起來,如果漏掉了這個優(yōu)化,真的有點可惜,因為它足足可以讓方案快 50s 左右。

          緩存架構(gòu)

          全局架構(gòu)

          上圖是全局緩存架構(gòu),整體方案的思路是多級緩存,采用滑動窗口的思想,AEP 永遠緩存最新的 60G 數(shù)據(jù),以確保熱數(shù)據(jù)一定不會命中 SSD。同時,堆外的 2G DRAM 與 AEP 息息相關(guān),這部分 DRAM 有兩個作用,其一是作為 AEP 的寫入緩沖,規(guī)避 AEP 寫入放大的問題,其二是作為熱數(shù)據(jù)的 DRAM 緩存,最熱的一部分數(shù)據(jù),可以保證直接命中 DRAM,規(guī)避 AEP 的訪問。另外富余的 3G 的堆內(nèi)內(nèi)存,可以用于緩存由于滑動而導致被覆蓋的數(shù)據(jù),這部分 DRAM 同時配備引用計數(shù),從而達到復用的效果。

          ![緩存讀寫流程](/Users/xujingfeng/Library/Application Support/typora-user-images/image-20211101000127272.png)

          在具體實現(xiàn)中,我將 60G 平均分配給 40 個線程,每個線程持有 1.5G 的 AEP 可用緩存,50M 的 DRAM 緩存。可以發(fā)現(xiàn),在我的方案中,SSD 寫入方案和 AEP 是不同的,SSD 由于 force 的限制,采用了線程合并寫入,而 AEP 本身就是可以丟失的緩存,所以不需要進行合并,每個線程維護自身的 AEP 和 DRAM 緩存即可。

          每個線程除了配備 1.5G 的AEP,還分配了 50M 的 DRAM。這部分 DRAM 永遠被優(yōu)先寫入,同樣的,也會優(yōu)先被讀取,前提是命中了的話。50M 顯然不是一個特別大的空間,所以在其充滿時,將 50M 數(shù)據(jù)整體刷入 AEP 中,使用 ByteBuffer 作為 DRAM 的 manager,還可以利用其邏輯 clear 的操作,使得 DRAM 和 AEP 一樣變成了一個 RingBuffer。這部分設(shè)計算是我方案中比較巧妙的一點。

          當然,你永遠可以相信 SSD,它是最后一道兜底邏輯,無論緩存設(shè)計的多么糟糕,保證最后能夠命中 SSD 才能出分,所有人都清楚這一點。

          AEP 滑動窗口的實現(xiàn)其實并不復雜,詳見文末的代碼,我就不過多介紹了。

          程序優(yōu)化

          預(yù)分配文件

          public static void preAllocateFile(FileChannel fileChannel, long threadGroupPerhapsFileSize) {
              int bufferSize = 4 * 1024;
              ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bufferSize);
              for (int i = 0; i < bufferSize; i++) {
                  byteBuffer.put((byte)0);
              }
              byteBuffer.flip();
              long loopTimes = threadGroupPerhapsFileSize / bufferSize;
              for (long i = 0; i < loopTimes; i++) {
                  try {
                      fileChannel.write(byteBuffer);
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
                  byteBuffer.flip();
              }
              try {
                  fileChannel.force(true);
                  fileChannel.position(0);
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }

          簡單實用的一個優(yōu)化技巧,屬于發(fā)現(xiàn)就可以很快實現(xiàn)的一個優(yōu)化,但可能不容易發(fā)現(xiàn)。

          4KB 對齊

          private void force() {
              // 4kb 對齊
              int position = writeBuffer.position();
              int mod = position % Constants._4kb;
              if (mod != 0) {
                writeBuffer.position(position + Constants._4kb - mod);
              }

              writeBuffer.flip();
              fileChannel.write(writeBuffer);
              fileChannel.force(false);
              writeBuffer.clear();
          }

          同上

          Unsafe

          其實感覺用了 Unsafe 也沒有多少提升,因為后期抖動太大了,但最優(yōu)成績的確是用 Unsafe 跑出來的,還是羅列出來,萬一下次有用呢?詳見代碼實現(xiàn) io.openmessaging.NativeMemoryByteBuffer。

          推薦閱讀:《聊聊Unsafe的一些使用技巧》

          ArrayMap

          可以使用數(shù)組重寫一遍 Map,反正此次比賽調(diào)用到的 Map 的 API 也不多,換成數(shù)組實現(xiàn)之后,可以降低 HashMap 的 overheap,再優(yōu)秀的實現(xiàn),在數(shù)組面前也會變得黯淡無光,僅限于比賽。

          詳見代碼實現(xiàn) io.openmessaging.ArrayMap。

          預(yù)初始化

          充分利用 append 之前的耗時不計入總分這一特性。除了將文件提前分配出來之外,Runtime 需要 new 的對象、DRAM 空間等,都提前在構(gòu)造函數(shù)中完成,蚊子腿也是肉,分數(shù)總是這么一點點摳出來的。

          并發(fā) getRange

          讀取階段 fetchNum 最大為 100,串行訪問的話,如果是命中緩存還好,要是 100 次 SSD 的 IO 都是串行,那可就太糟糕了。經(jīng)過測試,僅當命中 SSD 時并發(fā)訪問,和不區(qū)分內(nèi)存、AEP、SSD 命中,均并發(fā)訪問,效果差不多,但無論如何,并發(fā) getRange 總是比串行好的。

          ThreadLocal 復用

          性能挑戰(zhàn)賽中務(wù)必要 check 的一個環(huán)節(jié),便是在運行時有沒有動態(tài) new 對象,有沒有動態(tài) allocate 內(nèi)存,出現(xiàn)這些可是大忌,建議全部用 ThreadLocal 緩存。這次的賽題中有很多關(guān)鍵性的數(shù)字,100 個 Topic、40 個線程,稍加不留意,可能把線程級別的一些操作,錯當成 Topic 級別來設(shè)計,例如分配的寫入緩沖也好,getRange 階段復用的讀取緩沖也好,都應(yīng)該設(shè)計成線程級別的數(shù)據(jù)。ThreadLocal 第一是方便管理線程級別的資源,第二是因為線程相對于 Topic 是要少的,需要搞清楚,哪些資源是線程級別的,哪些是 Topic 級別的,避免資源浪費。

          合并寫入

          詳見源碼io.openmessaging.ThreadGroupManager#append

          AEP 滑動窗口

          詳見源碼io.openmessaging.AepManager

          總結(jié)與反思

          一言以蔽之,滑動窗口緩存是我整個方案的核心,雖然這個方案經(jīng)過細節(jié)的優(yōu)化,讓我取得了內(nèi)部賽第一的成績,但開篇我也提到過,它并不是緩存命中率最高的方案,在這個方案中,第一個明顯的問題便是,堆外 DRAM 和 AEP 可能緩存了同一批數(shù)據(jù),實際上,DRAM 和 AEP 緩存不重疊的方案肯定會有更高的緩存命中率;第二個問題,也是問題一連帶的問題,在該方案中,堆內(nèi)的 DRAM 無法被很高效地利用起來,所以我在本文中,只是稍帶提了一下堆內(nèi)的設(shè)計,沒有詳細介紹引用技術(shù)的邏輯。

          我在賽程后半段,也嘗試設(shè)計過 DRAM 和 AEP 不重疊并且動態(tài)分配回收的方案,緩存利用率的確會更高,但這意味著我要放棄滑動窗口方案中所有的細節(jié)調(diào)優(yōu)!業(yè)余時間搞比賽,實在是精力時間有限,最終選擇了放棄。

          這像極了項目開發(fā)的技術(shù)債,如果你選擇忍受,你可以得到一個尚可使用的系統(tǒng),但你知道,重構(gòu)之后,它可以更好;當然你也可以選擇重構(gòu),死著皮,連著肉。

          重賞之下,必有卷夫。內(nèi)部賽還好,外部賽實在是卷,每次這種性能挑戰(zhàn)賽,打到最后都是拼了命的摳細節(jié),你被別人卷到了,就很累,你想到了優(yōu)化點,卷到了別人,就很爽,這也太真實了。

          最后說說收獲,這次比賽,讓我對 AEP 這個新概念有了比較深的理解,對存儲設(shè)計、文件 IO 也有了更深的體會。這類比賽偶爾打打還是挺有意思的,一方面寫項目代碼容易疲乏,二是寫出這么一個小的工程,還是挺有成就感的一件事。如果有下一場,也歡迎讀者們一起來卷。

          源碼

          https://github.com/lexburner/aliyun-cloudnative-race-mq-2021.git

          END -

          「技術(shù)分享」某種程度上,是讓作者和讀者,不那么孤獨的東西。歡迎關(guān)注我的微信公眾號:「Kirito的技術(shù)分享」

          瀏覽 111
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  毛片在线观看网站 | 婷婷丁香五月社区亚洲 | 大香蕉久久视频 | 风流少妇一区二区三区91 | 国产一区二区激情小说片 |