精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

一個(gè)開發(fā)者自述:我是如何設(shè)計(jì)針對冷熱讀寫場景的 RocketMQ 存儲(chǔ)系統(tǒng)

存儲(chǔ) 存儲(chǔ)架構(gòu)
Apache RocketMQ 作為一款分布式的消息中間件,歷年雙十一承載了萬億級(jí)的消息流轉(zhuǎn),其中,實(shí)時(shí)讀取寫入數(shù)據(jù)和讀取歷史數(shù)據(jù)都是業(yè)務(wù)常見的存儲(chǔ)訪問場景,針對這個(gè)混合讀寫場景進(jìn)行優(yōu)化,可以極大的提升存儲(chǔ)系統(tǒng)的穩(wěn)定性。

作者 |  Ninety Percent

一、悸動(dòng)

32 歲,碼農(nóng)的倒數(shù)第二個(gè)本命年,平淡無奇的生活總覺得缺少了點(diǎn)什么。

想要去創(chuàng)業(yè),卻害怕家庭承受不住再次失敗的挫折,想要生二胎,帶娃的壓力讓我想著還不如去創(chuàng)業(yè);所以我只好在生活中尋找一些小感動(dòng),去看一些老掉牙的電影,然后把自己感動(dòng)得稀里嘩啦,去翻一些泛黃的書籍,在回憶里尋找一絲絲曾經(jīng)的深情滿滿;去學(xué)習(xí)一些冷門的知識(shí),最后把自己搞得暈頭轉(zhuǎn)向,去參加一些有意思的比賽,撿起那 10 年走來,早已被刻在基因里的悸動(dòng)。

那是去年夏末的一個(gè)傍晚,我和同事正閑聊著西湖的美好,他們說看到了阿里云發(fā)布云原生編程挑戰(zhàn)賽,問我要不要試試。我說我只有九成的把握,另外一成得找我媳婦兒要;那一天,我們繞著西湖走了好久,最后終于達(dá)成一致,Ninety

Percent 戰(zhàn)隊(duì)?wèi)?yīng)運(yùn)而生,云原生 MQ 的賽道上,又多了一個(gè)艱難卻堅(jiān)強(qiáng)的選手。

人到中年,仍然會(huì)做出一些沖動(dòng)的決定,那種屁股決定腦袋的做法,像極了領(lǐng)導(dǎo)們的睿智和 18 歲時(shí)我朝三暮四的日子;夏季的 ADB 比賽,已經(jīng)讓我和女兒有些疏遠(yuǎn),讓老婆對我有些成見;此次參賽,必然是要暗度陳倉,臥薪嘗膽,不到關(guān)鍵時(shí)刻,不能讓家里人知道我又在賣肝。

二、開工

你還別說,或許是人類的本性使然,這種背著老婆偷偷干壞事情的感覺還真不錯(cuò),從上路到上分,一路順風(fēng)順?biāo)?,極速狂奔;斷斷續(xù)續(xù)花了大概兩天的時(shí)間,成功地在 A 榜拿下了 first blood;再一次把第一名和最后一名同時(shí)納入囊中;快男總是不會(huì)讓大家失望了,800 秒的成績,成為了比賽的 base line。

第一個(gè)版本并沒有做什么設(shè)計(jì),基本上就是拍腦門的方案,目的就是把流程跑通,盡快出分,然后在保證正確性的前提下,逐步去優(yōu)化方案,避免一開始就過度設(shè)計(jì),導(dǎo)致遲遲不能出分,影響士氣。

三、整體設(shè)計(jì)

先回顧下賽題:Apache RocketMQ 作為一款分布式的消息中間件,歷年雙十一承載了萬億級(jí)的消息流轉(zhuǎn),其中,實(shí)時(shí)讀取寫入數(shù)據(jù)和讀取歷史數(shù)據(jù)都是業(yè)務(wù)常見的存儲(chǔ)訪問場景,針對這個(gè)混合讀寫場景進(jìn)行優(yōu)化,可以極大的提升存儲(chǔ)系統(tǒng)的穩(wěn)定性。

圖片

基本思路是:當(dāng) append 方法被調(diào)用時(shí),會(huì)將傳入的相關(guān)參數(shù)包裝成一個(gè) Request 對象,put 到請求隊(duì)列中,然后當(dāng)前線程進(jìn)入等待狀態(tài)。

聚合線程會(huì)循環(huán)從請求隊(duì)列里面消費(fèi) Request 對象,放入一個(gè)列表中,當(dāng)列表長度到達(dá)一定數(shù)量時(shí),就將該列表放入到聚合隊(duì)列中。這樣在后續(xù)的刷盤線程中,列表中的多個(gè)請求,就能進(jìn)行一次性刷盤了,增大刷盤的數(shù)據(jù)塊的大小,提升刷盤速度;當(dāng)刷盤線程處理完一個(gè)請求列表的持久化邏輯之后,會(huì)依次對列表中個(gè)各個(gè)請求進(jìn)行喚醒操作,使等待的測評(píng)線程進(jìn)行返回。

圖片

1.內(nèi)存級(jí)別的元數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)

圖片

首先用一個(gè)二維數(shù)組來存儲(chǔ)各個(gè) topicId+queueId 對應(yīng)的 DataMeta 對象,DataMeta 對象里面有一個(gè) MetaItem 的列表,每一個(gè) MetaItem 代表的一條消息,里面包含了消息所在的文件下標(biāo)、文件位置、數(shù)據(jù)長度、以及緩存位置。

2.SSD 上數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu)

圖片

總共使用了 15 個(gè) byte 來存儲(chǔ)消息的元數(shù)據(jù),消息的實(shí)際數(shù)據(jù)和元數(shù)據(jù)放在一起,這種混合存儲(chǔ)的方式雖然看起來不太優(yōu)雅,但比起獨(dú)立存儲(chǔ),可以減少一半的 force 操作。

3.數(shù)據(jù)恢復(fù)

依次遍歷讀取各個(gè)數(shù)據(jù)文件,按照上述的數(shù)據(jù)存儲(chǔ)協(xié)議生成內(nèi)存級(jí)別的元數(shù)據(jù)信息,供后續(xù)查詢時(shí)使用。

4.數(shù)據(jù)消費(fèi)

數(shù)據(jù)消費(fèi)時(shí),通過 topic+queueId 從二維數(shù)組中定位到對應(yīng)的 DataMeta 對象,然后根據(jù) offset 和 fetchNum,從 MetaItem 列表中找到對應(yīng)的 MetaItem 對象,通過 MetaItem 中所記錄的文件存儲(chǔ)信息,進(jìn)行文件加載。

總的來說,第一個(gè)版本在大方向上沒有太大的問題,使用 queue 進(jìn)行異步聚合和刷盤,讓整個(gè)程序更加靈活,為后續(xù)的一些功能擴(kuò)展打下了很好的基礎(chǔ)。

四、緩存

60 個(gè) G的 AEP,我垂涎已久,國慶七天,沒有出遠(yuǎn)門的計(jì)劃,一定要好好卷一卷 llpl。下載了 llpl 的源碼,一頓看,發(fā)現(xiàn)比我想象的要簡單得多,本質(zhì)上和用 unsafe 訪問普通內(nèi)存是一模一樣的。卷完 llpl,緩存設(shè)計(jì)方案呼之欲出。

1.緩存分級(jí)

緩存的寫入用了隊(duì)列進(jìn)行異步化,避免對主線程造成阻塞(到比賽后期才發(fā)現(xiàn)云 SSD 的奧秘,就算同步寫也不會(huì)影響整體的速度,后面我會(huì)講原因);程序可以用作緩存的存儲(chǔ)介質(zhì)有 AEP 和 Dram,兩者在訪問速度上有一定的差異,賽題所描述的場景中,會(huì)有大量的熱讀,因此我對緩存進(jìn)行了分級(jí),分為了 AEP 緩存和 Dram 緩存,Dram 緩存又分為了堆內(nèi)緩存、堆外緩存、MMAP 緩存(后期加入),在申請緩存時(shí),優(yōu)先使用 Dram 緩存,提升高性能緩存的使用頻度。

Dram 緩存最后申請了 7G,AEP 申請了 61G,Dram 的容量占比為 10%;本次比賽總共會(huì)讀取(61+7)/2+50=84G 的數(shù)據(jù),根據(jù)日志統(tǒng)計(jì),整個(gè)測評(píng)過程中,有 30G 的數(shù)據(jù)使用了 Dram 緩存,占比 35%;因?yàn)榍?75G 的數(shù)據(jù)不會(huì)有讀取操作,沒有緩存釋放與復(fù)用動(dòng)作,所以嚴(yán)格意義上來講,在寫入與查詢混合操作階段,總共使用了 50G 的緩存,其中滾動(dòng)使用了 30-7/2=26.5G 的 Dram 緩存,占比 53%。10%的容量占比,卻滾動(dòng)提供了 53%的緩存服務(wù),說明熱讀現(xiàn)象非常嚴(yán)重,說明緩存分級(jí)非常有必要。

但是,現(xiàn)實(shí)總是殘酷的,這些看似無懈可擊的優(yōu)化點(diǎn)在測評(píng)中作用并不大,畢竟這種優(yōu)化只能提升查詢速度,在讀寫混合階段,讀緩存總耗時(shí)是 10 秒或者是 20 秒,對最后的成績其實(shí)沒有任何影響!很神奇吧,后面我會(huì)講原因。

2.緩存結(jié)構(gòu)

圖片

當(dāng)獲取到一個(gè)緩存請求后,會(huì)根據(jù) topic+queueId 從二維數(shù)組中獲取到對應(yīng)的緩存上下文對象;該對象中維護(hù)了一個(gè)緩存塊列表、以及最后一個(gè)緩存塊的寫入指針位置;如果最后一個(gè)緩存塊的余量足夠放下當(dāng)前的數(shù)據(jù),則直接將數(shù)據(jù)寫入緩存塊;如果放不下,則申請一個(gè)新的緩存塊,放在緩存塊列表的最后,同時(shí)將寫不下的數(shù)據(jù)放到新緩存塊中;若申請不到新的緩存塊,則直接按緩存寫入失敗進(jìn)行處理。

在寫完緩存后,需要將緩存的位置信息回寫到內(nèi)存中的Meta中;比如本條數(shù)據(jù)是從第三個(gè)緩存塊中的 123B 開始寫入的,則回寫的緩存位置為:(3-1)*每個(gè)緩存塊的大小+123。在讀取緩存數(shù)據(jù)時(shí),按照 meta 數(shù)據(jù)中的緩存位置新,定位到對應(yīng)的緩存塊、以及塊內(nèi)位置,進(jìn)行數(shù)據(jù)讀取(需要考慮跨塊的邏輯)。

由于緩存的寫入是單線程完成的,對于一個(gè) queueId,前面的緩存塊的消息一定早于后面的緩存塊,所以當(dāng)讀取完緩存數(shù)據(jù)后,就可以將當(dāng)前緩存塊之前的所有緩存都釋放掉(放入緩存資源池),這樣 75G 中被跳過的那 37.5G 的數(shù)據(jù)也能快速地被釋放掉。

緩存功能加上去后,成績來到了 520 秒左右,程序的主體結(jié)構(gòu)也基本完成了,接下來就是精裝了。

五、優(yōu)化

1.緩存準(zhǔn)入策略

一個(gè) 32k 的緩存塊,是放 2 個(gè) 16k 的數(shù)據(jù)合適,還是放 16 個(gè) 2k 的數(shù)據(jù)合適?毫無疑問是后者,將小數(shù)據(jù)塊盡量都放到緩存中,可以使得最后只有較大的塊才會(huì)查 ssd,減少查詢時(shí) ssd 的 io 次數(shù)。

那么閾值為多少時(shí),可以保證小于該閾值的數(shù)據(jù)塊放入緩存,能夠使得緩存剛好被填滿呢?(若不填滿,緩存利用率就低了,若放不下,就會(huì)有小塊的數(shù)據(jù)無法放緩存,讀取時(shí)必須走 ssd,io 次數(shù)就上去了)。

一般來說,通過多次參數(shù)調(diào)整和測評(píng)嘗試,就能找到這個(gè)閾值,但是這種方式不具備通用性,如果總的可用的緩存大小出現(xiàn)變化,就又需要進(jìn)行嘗試了,不具備生產(chǎn)價(jià)值。

這個(gè)時(shí)候,中學(xué)時(shí)代的數(shù)學(xué)知識(shí)就派上用途了,如下圖:

圖片

由于消息的大小實(shí)際是以 100B 開始的,為了簡化,直接按照從 0B 進(jìn)行了計(jì)算,這樣會(huì)導(dǎo)致算出來的閾值偏大,也就是最后會(huì)出現(xiàn)緩存存不下從而小塊走 ssd 查詢的情況,所以我在算出來的閾值上減去了 100B*0.75(由于影響不大,基本是憑直覺拍腦門的)。如果要嚴(yán)格計(jì)算真正準(zhǔn)確的閾值,需要將上圖中的三角形面積問題,轉(zhuǎn)換成梯形面積問題,但是感覺意義不大,因?yàn)?100B 本來就只有 17K 的 1/170,比例非常小,所以影響也非常的小。

梯形面積和三角形面積的比為:(17K+100)(17K-100)/(17k17K)=0.999965,完全在數(shù)據(jù)波動(dòng)的范圍之內(nèi)。

在程序運(yùn)行時(shí),根據(jù)動(dòng)態(tài)計(jì)算出來的閾值,大于該閾值的就直接跳過緩存的寫入邏輯,最后不管緩存配置為多大,都能保證小于該閾值的數(shù)據(jù)塊全部寫入了緩存,且緩存最后的利用率達(dá)到 99.5%以上。

2.共享緩存

在剛開始的時(shí)候,按照算出來的閾值進(jìn)行緩存規(guī)劃,仍然會(huì)出現(xiàn)緩存容量不足的情況,實(shí)際用到的緩存的大小總是比總緩存塊的大小小一些,通過各種排查,才恍然大悟,每個(gè) queueId 所擁有的最后一個(gè)緩存塊大概率是不會(huì)被寫滿的,宏觀上來說,平均只會(huì)被寫一半。一個(gè)緩存塊是32k,queueId 的數(shù)量大概是 20w,那么就會(huì)有 20w*32k/2=3G 的緩存沒有被用到;3G/2=1.5G(前 75G 之后隨機(jī)讀一半,所以要除以 2),就算是順序讀大塊,1.5G 也會(huì)帶來 5 秒左右的耗時(shí),更別說隨機(jī)讀了,所以不管有多復(fù)雜,這部分緩存一定要用起來。

既然自己用不完,那就共享出來吧,整體方案如下:

圖片

在緩存塊用盡時(shí),對所有的 queueId 的最后一個(gè)緩存塊進(jìn)行自增編號(hào),然后放入到一個(gè)一維數(shù)組中,緩存塊的編號(hào),即為該塊在以為數(shù)字中的下標(biāo);然后根據(jù)緩存塊的余量大小,放到對應(yīng)的余量集合中,余量大于等于

2k 小于 3k 的緩存塊,放到 2k 的集合中,以此類推,余量大于最大消息體大小(賽題中為 17K)的塊,統(tǒng)一放在 maxLen 的集合中。

當(dāng)某一次緩存請求獲取不到私有的緩存塊時(shí),將根據(jù)當(dāng)前消息體的大小,從共享緩存集合中獲取共享緩存進(jìn)行寫入。比如當(dāng)前消息體大小為 3.5K,將會(huì)從 4K 的集合中獲取緩存塊,若獲取不到,則繼續(xù)從 5k 的集合中獲取,依次類推,直到獲取到共享緩存塊,或者沒有滿足任何滿足條件的緩存塊為止。

往共享緩存塊寫入緩存數(shù)據(jù)后,該緩存塊的余量將發(fā)生變化,需要將該緩存塊從之前的集合中移除,然后放入新的余量集合中(若余量級(jí)別未發(fā)生變化,則不需要執(zhí)行該動(dòng)作)。

訪問共享緩存時(shí),會(huì)根據(jù)Meta中記錄的共享緩存編號(hào),從索引數(shù)組中獲取到對應(yīng)的共享塊,進(jìn)行數(shù)據(jù)的讀取。

在緩存的釋放邏輯里,會(huì)直接忽略共享緩存塊(理論上可以通過一個(gè)計(jì)數(shù)器來控制何時(shí)該釋放一個(gè)共享緩存塊,但實(shí)現(xiàn)起來比較復(fù)雜,因?yàn)橐紤]到有些消息不會(huì)被消費(fèi)的情況,且收益也不會(huì)太大(因?yàn)槎A段緩存是完全夠用的,所以就沒做嘗試)。

3.MMAP 緩存

測評(píng)程序的 jvm 參數(shù)不允許選手自己控制,這是攔在選手面前的一道障礙,由于老年代和年輕代之間的比例為 2 比 1,那意味著如果我使用 3G 來作為堆內(nèi)緩存,加上內(nèi)存中的 Meta 等對象,老年代基本要用 4G 左右,那就會(huì)有 2G 的新生代,這完全是浪費(fèi),因?yàn)樵撡愵}對新生代要求并不高。

所以為了避免浪費(fèi),一定要減少老年代的大小,那也就意味著不能使用太多的堆內(nèi)緩存;由于堆外內(nèi)存也被限定在了 2G,如果減小堆內(nèi)的使用量,那空余的緩存就只能給系統(tǒng)做 pageCache,但賽題的背景下,pageCache 的命中率并不高,所以這條路也是走不通的。

有沒有什么內(nèi)存既不是堆內(nèi),申請時(shí)又不受堆外參數(shù)的限制?自然而然想到了 unsafe,當(dāng)然也想到官方導(dǎo)師說的那句:用 unsafe 申請內(nèi)存直接取消成績。。。這條路只好作罷。

花了一個(gè)下午的時(shí)間,通讀了 nio 相關(guān)的代碼,意外發(fā)現(xiàn) MappedByteBuffer 是不受堆外參數(shù)的限制的,這就意味著可以使用 MappedByteBuffer 來替代堆內(nèi)緩存;由于緩存都會(huì)頻繁地被進(jìn)行寫與讀,如果使用 Write_read 模式,會(huì)導(dǎo)致刷盤動(dòng)作,就得不償失了,自然而然就想到了 PRIVATE 模式(copy on write),在該模式下,會(huì)在某個(gè) 4k 區(qū)首次寫入數(shù)據(jù)時(shí),和 pageCache 解耦,生成一個(gè)獨(dú)享的內(nèi)存副本;所以只要在程序初始化的時(shí)候,將 mmap 寫一遍,就能得到一塊獨(dú)享的,和磁盤無關(guān)的內(nèi)存了。

所以我將堆內(nèi)緩存的大小配置成了 32M(因?yàn)樵摴δ芤呀?jīng)開發(fā)好了,所以還是要意思一下,用起來),堆外申請了 1700M(算上測評(píng)代碼的 300M,差不多 2G)、mmap 申請了 5G;總共有 7G 的 Dram 作為了緩存(不使用 mmap 的話,大概只能用到 5G),內(nèi)存中的Meta大概有700M左右,所以堆內(nèi)的內(nèi)存差不多在 1G 左右,2G+5G+1G=8G,操作系統(tǒng)給 200M 左右基本就夠了,所以還剩 800M 沒用,這800M其實(shí)是可以用來作為 mmap 緩存的,主要是考慮到大家都只能用 8G,超過 8G 容易被挑戰(zhàn),所以最后最優(yōu)成績里面總的內(nèi)存的使用量并沒有超過 8G。

4.基于末尾填補(bǔ)的 4K 對齊

由于 ssd 的寫入是以 4K 為最小單位的,但每次聚合的消息的總大小又不是 4k 的整數(shù)倍,所以這會(huì)導(dǎo)致每次寫入都會(huì)有額外的開銷。

比較常規(guī)的方案是進(jìn)行 4k 填補(bǔ),當(dāng)某一批數(shù)據(jù)不是 4k 對齊時(shí),在末尾進(jìn)行填充,保證寫入的數(shù)據(jù)的總大小是 4k 的整數(shù)倍。聽起來有些不可思議,額外寫入一些數(shù)據(jù)會(huì)導(dǎo)致整體效益更高?

是的,推導(dǎo)邏輯是這樣的:“如果不填補(bǔ),下次寫入的時(shí)候,一定會(huì)寫這未滿的4k區(qū),如果填補(bǔ)了,下次寫入的時(shí)候,只有 50%的概率會(huì)往后多寫一個(gè) 4k 區(qū)(因?yàn)榍懊嫣钛a(bǔ),導(dǎo)致本次數(shù)據(jù)后移,尾部多垮了一個(gè) 4k 區(qū))”,所以整體來說,填補(bǔ)后會(huì)賺 50%。或者換一個(gè)角度,填補(bǔ)對于當(dāng)前的這次寫入是沒有副作用的(也就多 copy<4k 的數(shù)據(jù)),對于下一次寫入也是沒有副作用的,但是如果下一次寫入是這種情況,就會(huì)因?yàn)樘钛a(bǔ)而少寫一個(gè) 4k。

圖片

5.基于末尾剪切的 4k 對齊

填補(bǔ)的方案確實(shí)能帶來不錯(cuò)的提升,但是最后落盤的文件大概有 128G 左右,比實(shí)際的數(shù)據(jù)量多了 3 個(gè) G,如果能把這 3 個(gè) G 用起來,又是一個(gè)不小的提升。

自然而然就想到了末尾剪切的方案,將尾部未 4k 對齊的數(shù)據(jù)剪切下來,放到下一批數(shù)據(jù)里面,剪切下來的數(shù)據(jù)對應(yīng)的請求,也在下一批數(shù)據(jù)刷盤的時(shí)候進(jìn)行喚醒。

方案如下:

圖片

6.填補(bǔ)與剪切共

剪切的方案固然優(yōu)秀,但在一些極端的情況下,會(huì)存在一些消極的影響;比如聚合的一批數(shù)據(jù)整體大小沒有操作 4k,那就需要扣留整批的請求了,在這一刻,這將變向?qū)е滤⒈P線程大幅降低、請求線程大幅降低;對于這種情況,剪切對齊帶來的優(yōu)勢,無法彌補(bǔ)扣留請求帶來的劣勢(基于直觀感受),因此需要直接使用填補(bǔ)的方式來保證 4k 對齊。

嚴(yán)格意義上來講,應(yīng)該有一個(gè)扣留線程數(shù)代價(jià)、和填補(bǔ)代價(jià)的量化公式,以決定何種時(shí)候需要進(jìn)行填補(bǔ),何種時(shí)候需要進(jìn)行剪切;但是其本質(zhì)太過復(fù)雜,涉及到非同質(zhì)因子的整合(要在磁盤吞吐、磁盤 io、測評(píng)線程耗時(shí)三個(gè)概念之間做轉(zhuǎn)換);做了一些嘗試,效果都不是很理想,沒能跑出最高分。

當(dāng)然中間還有一些邊界處理,比如當(dāng) poll 上游數(shù)據(jù)超時(shí)的時(shí)候,需要將扣留的數(shù)據(jù)進(jìn)行填充落盤,避免收尾階段,最后一批扣留的數(shù)據(jù)得不到處理。

7.SSD 的預(yù)寫

得此優(yōu)化點(diǎn)者,得前 10,該優(yōu)化點(diǎn)能大幅提升寫入速度(280m/s 到 320m/s),這個(gè)優(yōu)化點(diǎn)很多同學(xué)在一些技術(shù)貼上看到過,或者自己意外發(fā)現(xiàn)過,但是大部分人應(yīng)該對本質(zhì)的原因不甚了解;接下來我便循序漸進(jìn),按照自己的理解進(jìn)行 yy 了。

假設(shè)某塊磁盤上被寫滿了 1,然后文件都被刪除了,這個(gè)時(shí)候磁盤上的物理狀態(tài)肯定都還是 1(因?yàn)閯h除文件并不會(huì)對文件區(qū)域進(jìn)行格式化)。然后你又新建了一個(gè)空白文件,將文件大小設(shè)置成了 1G(比如通過 RandomAccessFile.position(1G));這個(gè)時(shí)候這 1G 的區(qū)域?qū)?yīng)的磁盤空間上仍然還是 1,因?yàn)樵谏a(chǎn)空白文件的時(shí)候也并不會(huì)對對應(yīng)的區(qū)域進(jìn)行格式化。

但是,當(dāng)我們此時(shí)對這個(gè)文件進(jìn)行訪問的時(shí)候,讀取到的會(huì)全是 0;這說明文件系統(tǒng)里面記載了,對于一個(gè)文件,哪些地方是被寫過的,哪些地方是沒有被寫過的(以 4k 為單位),沒被寫過的地方會(huì)直接返回 0;這些信息被記載在一個(gè)叫做 inode 的東西上,inode 當(dāng)然也是需要落盤進(jìn)行持久化的。

所以如果我們不預(yù)寫文件,inode 會(huì)在文件的某個(gè) 4k 區(qū)首次被寫入時(shí)發(fā)生性變更,這將造成額外的邏輯開銷以及磁盤開銷。因此,在構(gòu)造方法里面一頓 for 循環(huán),按照預(yù)估的總文件大小,先寫一遍數(shù)據(jù),后續(xù)寫入時(shí)就能起飛了。

8.大消息體的優(yōu)化策略

由于磁盤的讀寫都是以 4k 為單位,這就意味著讀取一個(gè) 16k+2B 的數(shù)據(jù),極端情況下會(huì)產(chǎn)生 16k+2*4k=24k 的磁盤 io,會(huì)多加載將近 8k 的數(shù)據(jù)。

顯然如果能夠在讀取的時(shí)候都按 4k 對齊進(jìn)行讀取,且加載出來的數(shù)據(jù)都是有意義的(后續(xù)能夠被用到),就能解決而上述的問題;我依次做了以下優(yōu)化(有些優(yōu)化點(diǎn)在后面被廢棄掉了,因?yàn)樗鸵恍┢渌玫膬?yōu)化點(diǎn)沖突了)。

(1)大塊置頂

由于每一批聚合的消息都是 4k 對齊的落盤的(剪切扣留方案之前),所以我將每批數(shù)據(jù)中最大的那條消息放在了頭部(基于緩存規(guī)劃策略,大消息大概率是不會(huì)進(jìn)緩存的,消費(fèi)時(shí)會(huì)從 ssd 讀取),這樣這條消息至少有一端是 4k 對齊的,讀取的時(shí)候能緩解 50%的對齊問題,該種方式在剪切扣留方案之前確實(shí)帶來了 3 秒左右的提升。

(2)消息順序重組

通過算法,讓大塊數(shù)據(jù)盡量少地出現(xiàn)兩端不對齊的情況,減少讀取時(shí)額外的數(shù)據(jù)加載量;比如針對下面的例子:

圖片

在整理之前,加載三個(gè)大塊總共會(huì)涉及到 8 個(gè) 4k 區(qū),整理之后,就變成了 6 個(gè)。

由于自己在算法這一塊兒實(shí)在太弱了,加上這是一個(gè) NP 問題,折騰了幾個(gè)小時(shí),效果總是差強(qiáng)人意,最后只好放棄。

(3)基于內(nèi)存的 pageCache

在數(shù)據(jù)讀取階段,每次加載數(shù)據(jù)時(shí),若加載的數(shù)據(jù)兩端不是 4k 對齊的,就主動(dòng)向前后延伸打到 4k 對齊的地方;然后將首尾兩個(gè) 4k 區(qū)放到內(nèi)存里面,這樣當(dāng)后續(xù)要訪問這些4k區(qū)的時(shí)候,就可以直接從內(nèi)存里面獲取了。

該方案最后的效果和預(yù)估的一樣差,一點(diǎn)驚喜都沒有。因?yàn)橹粫?huì)有少量的數(shù)據(jù)會(huì)走 ssd,首尾兩個(gè) 4k 里面大概率都是那些不需要走ssd的消息,所以被復(fù)用的概率極小。

(4)部分緩存

既然自己沒能力對消息的存儲(chǔ)順序進(jìn)行調(diào)整優(yōu)化,那就把那些兩端不對齊的數(shù)據(jù)剪下來放到緩存里面吧:

圖片

某條消息在落盤的時(shí)候,若某一端(也有可能是兩端)沒有 4k 對齊,且在未對齊的 4k 區(qū)的數(shù)據(jù)量很少,就將其剪切下來存放到緩存里,這樣查詢的時(shí)候,就不會(huì)因?yàn)檫@少量的數(shù)據(jù),去讀取一個(gè)額外的 4k 區(qū)了。

剪切的閾值設(shè)置成了 1k,由于數(shù)據(jù)大小是隨機(jī)的,所以從宏觀上來看,剪切下來的數(shù)據(jù)片的平均大小為 0.5k,這意味著只需要使用 0.5k 的緩存,就能減少 4k 的 io,是常規(guī)緩存效益的 8 倍,加上緩存部分的余量分級(jí)策略,會(huì)導(dǎo)致有很多碎片化的小內(nèi)存用不到,該方案剛好可以把這些碎片內(nèi)存利用起來。

9.測評(píng)線程的聚合策略

每次聚合多少條消息進(jìn)行刷盤合適?是按消息條數(shù)進(jìn)行聚合,還是按照消息的大小進(jìn)行聚合?

剛開始的時(shí)候并沒有想那么多,通過日志得知總共有 40 個(gè)線程,所以就寫死了一次聚合 10 條,然后四個(gè)線程進(jìn)行刷盤;但這會(huì)帶來兩個(gè)問題,一個(gè)是若線程數(shù)發(fā)生變化,性能會(huì)大幅下降;第二是在收尾階段,會(huì)有一些跑得慢的線程還有不少數(shù)據(jù)未寫入的情況,導(dǎo)致收尾時(shí)間較長,特別是加入了尾部剪切與扣留邏輯后,該現(xiàn)象尤為嚴(yán)重。

為了解決收尾耗時(shí)長的問題,我嘗試了同步聚合的方案,在第一次寫入之后的 500ms,對寫入線程數(shù)進(jìn)行統(tǒng)計(jì),然后分組,后續(xù)就按組進(jìn)行聚合;這種方式可以完美解決收尾的問題,因?yàn)橥粋€(gè)組里面的所有線程都是同時(shí)完成寫入任務(wù)的,大概是因?yàn)槊總€(gè)線程的寫入次數(shù)是固定的吧;但是使用這種方式,尾部剪切+扣留的邏輯就非常難融合進(jìn)來了;加上在程序一開始就固定線程數(shù),看起來也有那么一些不優(yōu)雅;所以我就引入了“線程控制器”的概念。

圖片

10.聚合策略迭代-針對剪切扣的留方案的定向優(yōu)化

假設(shè)當(dāng)前動(dòng)態(tài)計(jì)算出來的聚合數(shù)量是 10,對于聚合出來的 10 條消息,如果本批次被扣留了 2 條,下次聚合時(shí)應(yīng)該聚合多少條?

在之前的策略里面,還是會(huì)聚合 10 條,這就意味著一旦出現(xiàn)了消息扣留,聚合邏輯就會(huì)產(chǎn)生抖動(dòng),會(huì)出現(xiàn)某個(gè)線程聚合不到指定的消息數(shù)據(jù)量的情況(這種情況會(huì)有 poll 超時(shí)方式進(jìn)行兜底,但是整體速度就慢了)。

所以聚合參數(shù)不能是一個(gè)單純的、統(tǒng)一化的值,得針對不同的刷盤線程的扣留數(shù),進(jìn)行調(diào)整,假設(shè)聚合數(shù)為 n,某個(gè)刷盤線程的上批次扣留數(shù)量為 m,那針對這個(gè)刷盤線程的下批次的聚合數(shù)量就應(yīng)該是 n-m。

那么問題就來了,聚合線程(生產(chǎn)者)只有一個(gè),刷盤線程(消費(fèi)者)有好幾個(gè),都是搶占式地進(jìn)行消費(fèi),沒辦法將聚合到的特定數(shù)量的消息,給到指定的刷盤線程;所以聚合消息隊(duì)列需要拆分,拆分成以刷盤線程為維度。

由于改動(dòng)比較大,為了保留以前的邏輯,就引入了聚合數(shù)量的“嚴(yán)格模式”的概念,通過參數(shù)進(jìn)行控制,如果是“嚴(yán)格模式”,就使用上述的邏輯,若不是,則使用之前的邏輯;設(shè)計(jì)圖如下:

圖片

將聚合隊(duì)列換成了聚合隊(duì)列數(shù)組,在非嚴(yán)格模式下,數(shù)組里面的原始指向的是同一個(gè)隊(duì)列對象,這樣很多代碼邏輯就能統(tǒng)一。

聚合線程需要先從扣留信息隊(duì)列里面獲取一個(gè)對象,然后根據(jù)扣留數(shù)和最新的聚合參數(shù),決定要聚合多少條消息,聚合好消息后,放到扣留信息所描述的隊(duì)列中。

六、完美的收尾策略,一行代碼帶來 5s 的提升

引入了線程控制器后,收尾時(shí)間被降低到了 2 秒多,兩次收尾,也就是 5 秒左右(這些信息來源于最后一個(gè)晚上對 A 榜時(shí)的日志的分析),在賽點(diǎn)位置上,這 5 秒的重要性不言而喻。

比賽結(jié)束前的最后一晚,分?jǐn)?shù)徘徊在了 423 秒左右,前面的大佬在很多天前就從 430 一次性優(yōu)化到了 420,然后分?jǐn)?shù)就沒有太大變化了;我當(dāng)時(shí)抱著僥幸的態(tài)度,斷定應(yīng)該是 hack 了,直到那天晚上在釘釘群里和他聊了幾句,直覺告訴我,420

的成績是有效的。當(dāng)時(shí)是有些慌的,畢竟比賽第二天早上 10 點(diǎn)就結(jié)束了。

我開始陷入深深的反思,我都卷到極致了,從 432 到 423 花費(fèi)了大量的精力,為何大神能夠一擊致命?不對,一定是我忽略了什么。

我開始回看歷史提交記錄,然后對照分析每次提交后的測評(píng)得分(由于歷史成績都有一定的抖動(dòng),所以這個(gè)工作非常的上頭);花費(fèi)了大概兩個(gè)小時(shí),總算發(fā)現(xiàn)了一個(gè)異常點(diǎn),在 432 秒附近的時(shí)候,我從同步聚合切換成了異步聚合,然后融合了剪切扣留+4k 填補(bǔ)的方案,按理說這個(gè)優(yōu)化能減少 3G 多的落盤數(shù)據(jù)量,成績應(yīng)該是可以提升 10 秒左右的,但是當(dāng)時(shí)成績只提升了 5 秒多,由于當(dāng)時(shí)還有不少?zèng)]有落地的優(yōu)化點(diǎn),所以就沒有太在意。

扣留策略會(huì)會(huì)將尾部的請求扣留下來,尾部的請求本來就是慢一拍(對應(yīng)的測評(píng)線程慢)的請求(隊(duì)列是順序消費(fèi)),這一扣留,進(jìn)度就更慢了!!!

聚合到一批消息后,按照消息對應(yīng)的線程被扣留的次數(shù),從大到小排個(gè)序,讓那些慢的、扣留多的線程,盡可能不被扣留,讓那些快的、扣留少的請求,盡可能被扣留;最后所有的線程幾乎都是同時(shí)完成(基于假想)。

趕緊提交代碼、開始測評(píng),抖了兩把就破 420 了,最好成績到達(dá)了 418,比優(yōu)化前高出 5 秒左右,非常符合預(yù)期.

1.查詢優(yōu)化

  • 多線程讀 ssd

由于只有少量的數(shù)據(jù)會(huì)讀 ssd,這使得在讀寫混合階段,sdd 查詢的并發(fā)量并不大,所以在加載數(shù)據(jù)時(shí)進(jìn)行了判斷,如果需要從 ssd 加載的數(shù)量大于一定量時(shí),則進(jìn)行多線程加載,充分利用 ssd 并發(fā)隨機(jī)讀的能力。

為什么要大于一定的量才多線程加載,如果只需要加載兩條數(shù)據(jù),用兩個(gè)線程來加載會(huì)有提升嗎?當(dāng)存儲(chǔ)介質(zhì)夠快、加載的數(shù)據(jù)量夠小時(shí),多線程加載數(shù)據(jù)帶來的 io 時(shí)間的提升,還不足以彌補(bǔ)多線程執(zhí)行本身帶來的程序開銷。

2.緩存的批量 copy

若某次查詢時(shí)需要加載的數(shù)據(jù),在緩存上是連續(xù)的,則不需要一條一條從緩存進(jìn)行復(fù)制,可以以緩存塊的大小為最小粒度,進(jìn)行復(fù)制,提升緩存讀取的效益。?

圖片

上面的例子中,使用批量 copy 的方式,可以將 copy 的次數(shù)從 5 次降到 2 次。

這樣做的前提是:用于返回的各條消息對應(yīng)的 byteBuffer,在內(nèi)存上需要是連續(xù)的(通過反射實(shí)現(xiàn),給每個(gè) byteBuffer 都注入同一個(gè) bytes 對象);批量復(fù)制完畢后,根據(jù)各條消息的大小,動(dòng)態(tài)設(shè)置各自 byteBuffer 的 position 和 limit,以保證 retain 區(qū)域剛好指向自己所對應(yīng)的內(nèi)存區(qū)間。

該功能一直有偶現(xiàn)的 bug,本地又復(fù)現(xiàn)不了,A 榜的時(shí)候沒太在意,B 榜的時(shí)候又不能看日志,一直沒得到解決;怕因?yàn)榇a質(zhì)量影響最后的代碼分,所以后來就注釋掉了。

3遺失的美好

在比賽開始的時(shí)候,看了金融通的賽題解析,里面提到了一個(gè)對數(shù)據(jù)進(jìn)行遷移的點(diǎn);10 月中旬的時(shí)候進(jìn)行了嘗試,在開始讀取數(shù)據(jù)時(shí),陸續(xù)把那些緩存中沒有的數(shù)據(jù)讀取到緩存中(因?yàn)橐坏╅_始讀取,就會(huì)有大量的緩存被釋放出來,緩存容量完全夠用),總共進(jìn)行了兩個(gè)方案的嘗試:

(1)基于順序讀的異步遷移方案

在第一階段,當(dāng)緩存用盡時(shí),記錄當(dāng)前存儲(chǔ)文件的位置,然后遷移的時(shí)候,從該位置開始進(jìn)行順序讀取,將后續(xù)的所有數(shù)據(jù)都讀取到緩存中;這樣做的好處是大幅降低查詢階段的隨機(jī)讀次數(shù);但是也有不足,因?yàn)榍?75G 數(shù)據(jù)中有一般的數(shù)據(jù)是不會(huì)被消費(fèi)的,這意味著遷移到緩存中的數(shù)據(jù),有 50%都是沒有意義的,當(dāng)時(shí)測下來該方案基本沒有提升(由于成績有一定的抖動(dòng),具體是有一部分提升、沒提升、還是負(fù)優(yōu)化,也不得而知);后來引入了緩存準(zhǔn)入策略后,該方案就徹底被廢棄了,因?yàn)樾枰獜?ssd 中讀取的數(shù)據(jù)會(huì)完全散列在存儲(chǔ)文件中。

(2)基于懶加載的異步遷移方案

上面有講到,由于一階段的數(shù)據(jù)中有一半都不會(huì)被消費(fèi)到,想要不做無用功,就必須要在保證遷移的數(shù)據(jù)都是會(huì)被消費(fèi)的數(shù)據(jù)。

所以加了一個(gè)邏輯,當(dāng)某個(gè) queueId 第一次被消費(fèi)的時(shí)候,就異步將該 queueId 中不存在緩存中的消息,從 ssd 中加載到緩存中;由于當(dāng)時(shí)覺得就算是異步遷移,也是要隨機(jī)讀的,讀的次數(shù)并不會(huì)減少,一段時(shí)間內(nèi)磁盤的壓力也并不會(huì)減少;所以對該方案就沒怎么重視,完全是抱著寫著玩的態(tài)度;并且在遷移的準(zhǔn)入邏輯上加了一個(gè)判斷:“當(dāng)本次查詢的消息中包含有從磁盤中加載的數(shù)據(jù)時(shí),才異步對該 queueId 中剩下的 ssd 中的數(shù)據(jù)進(jìn)行遷移”;至今我都沒相透當(dāng)時(shí)自己為什么要加上這個(gè)一個(gè)判斷。也就是因?yàn)檫@個(gè)判斷,導(dǎo)致遷移效果仍然不理想(會(huì)導(dǎo)致遷移不夠集中、并且很多 queueId 在某次查詢的時(shí)候讀了 ssd,后續(xù)就沒有需要從 ssd 上讀取的數(shù)據(jù)了),對成績沒有明顯的提升;在一次版本回退中,徹底將遷移的方案給抹掉了(相信打比賽的小伙伴對版本回退深有感觸,特別是對于這種有較大成績抖動(dòng)的比賽)。

比賽結(jié)束后我在想,如果當(dāng)時(shí)在遷移邏輯上沒有加上那個(gè)神奇的邏輯判斷,我的成績能到多少?或許能到 410,或許突破不了 420;正式因?yàn)殄e(cuò)過了那個(gè)大的優(yōu)化點(diǎn),才讓我在其他點(diǎn)上做到了極致;那些錯(cuò)過的美好,會(huì)讓大家在未來的日子里更加努力地奔跑。

接下來我們講一下為什么異步遷移會(huì)快。

ssd 的多線程隨機(jī)讀是很快的,但是我上面有講到,如果查詢的數(shù)據(jù)量比較小,多線程分批查詢效果并不一定就好,因?yàn)槊恳慌臄?shù)據(jù)量實(shí)在太小了;所以想要在查詢階段開很多的線程來提升整體的查詢速度并不能取的很好的效果。異步遷移能夠完美地解決這個(gè)問題,并且在 io 次數(shù)一定的情況下,集中進(jìn)行 ssd 的隨機(jī)讀,比散列進(jìn)行隨機(jī)讀,pageCache 命中率更高,且對寫入速度造成的整體影響更小(這個(gè)觀點(diǎn)純屬個(gè)人感悟,只保證 Ninety Percent 的正確率)。

4.SSD 云盤的奧秘

我也是個(gè)小白,以下內(nèi)容很多都是猜測,大家看一看就可以了。

(1)云 ssd 的運(yùn)作機(jī)制

SSD 云盤和傳統(tǒng)的 ssd 盤擁有著相同的特性,但是卻是不同的東西;可以理解成 SSD 云盤,是傳統(tǒng) ssd 盤的一個(gè)放大版。

圖片

SSD 云盤的底層存儲(chǔ)介質(zhì)是多個(gè)普通的物理硬盤,這些物理硬盤就類似于傳統(tǒng) ssd 中的存儲(chǔ)顆粒,在進(jìn)行寫入或讀取的時(shí)候,會(huì)將任務(wù)分配到多個(gè)物理設(shè)備上并行進(jìn)行處理。同時(shí),在云 ssd 中,對數(shù)據(jù)的更新采用了 append 的方式,即在進(jìn)行更新時(shí),是順序追加寫一塊數(shù)據(jù),然后將位置的引用從原有的數(shù)據(jù)塊指向新的數(shù)據(jù)塊(我們訪問的文件的position和硬盤的物理地址之間有一層映射,所以就算硬盤上有很多的碎片,我們也仍然能獲取到一個(gè)“連續(xù)”的大文件)。

阿里云官網(wǎng)上有云 ssd 的 iops 和吞吐的計(jì)算公式:

iops = min{1800+50 容量, 50000}; 吞吐= min{120+0.5 容量, 350}。

我們看到無論是 iops 和吞吐,都和容量呈正相關(guān)的關(guān)系,并且都有一個(gè)上限。這是因?yàn)?,容量越大,底層的物理設(shè)備就會(huì)越多,并發(fā)處理的能力就越強(qiáng),所以速度就越快;但是當(dāng)物理設(shè)備多到一定的數(shù)量時(shí),文件系統(tǒng)的“總控“就會(huì)成為瓶頸;這個(gè)總控肯定也是需要存儲(chǔ)能力的(比如存儲(chǔ)位置映射、歷史數(shù)據(jù)的 compact 等等),所以當(dāng)給總控配置不同性能的存儲(chǔ)介質(zhì)時(shí),就得到了 PL0、PL1 等不同性能的云盤(當(dāng)然,除此之外,網(wǎng)絡(luò)帶寬、運(yùn)算能力也是云 ssd 速度的影響因子)。

(2)云 ssd 的 buffer 現(xiàn)象

在過程中發(fā)現(xiàn)了一個(gè)有趣的現(xiàn)象,就算是 force 落盤,在剛開始寫入時(shí),速度也是遠(yuǎn)大于 320m/s 的(能達(dá)到 400+),幾秒之后,會(huì)降下來,穩(wěn)定在 320 左右(像極了不 force 時(shí),pageCache 帶來的 buffer 現(xiàn)象)。

針對這種奇怪的現(xiàn)象,我進(jìn)行了進(jìn)一步的探索,每寫 2 秒的數(shù)據(jù),就 sleep 2 秒,結(jié)果是:在寫入的這兩秒時(shí)間里,速度能達(dá)到 400+,整體平均速度也遠(yuǎn)超過了 160m/s;后來我又做了很多實(shí)驗(yàn),包括在每次寫完數(shù)據(jù)之后直接進(jìn)行短暫的 sleep,但是這根本不會(huì)影響到 320m/s 的整體速度。測試代碼中,雖然是 4 線程寫入,但是總會(huì)有那么一些時(shí)刻,大部分甚至所有線程都處于 sleep 狀態(tài),這必然會(huì)使得在這個(gè)時(shí)間點(diǎn)上,應(yīng)用程序到硬盤的寫入速度是極低的;但是時(shí)間拉長了看,這個(gè)速度又是能恒定在 320m/s 的。這說明云 ssd 上有一層 buffer,類似操作系統(tǒng)的 pageCache,只是這個(gè)“pageCache”是可靠存儲(chǔ)的,應(yīng)用程序到這個(gè) buffer 之間的速度是可以超過 320 的,320 的閾值,是下游所導(dǎo)致的(比如 buffer 到硬盤陣列)。

對于這個(gè)“pageCache”有幾種猜測:

  • 物理設(shè)備本身就有 buffer 效應(yīng),因?yàn)槲锢碓O(shè)備的存儲(chǔ)狀態(tài)本質(zhì)上是通過電刺激,改變存儲(chǔ)介質(zhì)的化學(xué)狀態(tài)或者物理狀態(tài)的實(shí)現(xiàn)的,驅(qū)動(dòng)這種變化的工業(yè)本質(zhì),產(chǎn)生了這種 buffer 現(xiàn)象‘;
  • 云 ssd 里面有一塊較小的高性能存介質(zhì)作為緩沖區(qū),以提供更好的突擊寫的性能;
  • 邏輯限速,哈哈,這個(gè)純屬開玩笑了。

由于有了這個(gè) buffer 效應(yīng),程序?qū)用婢涂梢詾樗麨榱?,比如寫緩存的?dòng)作,整體會(huì)花費(fèi)幾十秒,但是就算是在只有 4 個(gè)寫入線程的情況下,不管是異步寫還是同步寫,都不會(huì)影響整體的落盤速度,因?yàn)樵谕綄懢彺娴臅r(shí)候,云 ssd 能夠進(jìn)行短暫的停歇,在接下來的寫入時(shí),速度會(huì)短暫地超過 320m/s;查詢的時(shí)候也類似,非 io 以外的時(shí)間開銷,無論長短,都不會(huì)影響整體的速度,這也就是我之前提到的,批量復(fù)制緩存,理論上有不小提升,但是實(shí)際上卻沒多大提升的原因。

當(dāng)然,這個(gè) buffer 現(xiàn)象其實(shí)是可以利用起來的,我們可以在寫數(shù)據(jù)的時(shí)候多花一些時(shí)間來做一些其他的事情,反正這樣的時(shí)間開銷并不會(huì)影響整體的速度;比如我之前提到的 NP 問題,可以 for 循環(huán)暴力破解。

責(zé)任編輯:武曉燕 來源: 阿里巴巴中間件
相關(guān)推薦

2017-07-13 17:33:18

生成對抗網(wǎng)絡(luò)GANIan Goodfel

2015-09-01 09:53:04

Java Web開發(fā)者

2017-07-18 10:16:27

強(qiáng)化學(xué)習(xí)決策問題監(jiān)督學(xué)習(xí)

2014-04-17 10:42:50

DevOps

2009-09-11 08:44:36

2021-03-16 07:56:26

開發(fā)者入職技術(shù)

2017-05-19 16:40:41

AndroidKotlin開發(fā)者

2012-10-23 14:01:21

Yibo 客戶端已經(jīng)停

2010-08-24 08:58:42

開發(fā)者

2025-10-13 01:50:00

2019-01-28 11:46:53

架構(gòu)運(yùn)維技術(shù)

2009-12-14 09:43:58

App Store開發(fā)者

2016-12-30 17:17:38

華為HDG開發(fā)者

2018-05-14 11:24:20

Python開發(fā)者工具

2017-02-23 14:30:09

SpringhibernateJava

2019-06-27 10:15:46

架構(gòu)代碼項(xiàng)目

2013-07-25 17:28:02

2019-03-22 09:51:35

數(shù)據(jù)開發(fā)系統(tǒng)

2015-06-05 09:15:37

移動(dòng)開發(fā)者

2014-08-01 10:24:11

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

国产经典一区| 污污的视频网站在线观看| 免费毛片在线不卡| 欧美在线一二三| 亚洲一区二区精品在线观看| 亚洲性生活大片| 欧美一区亚洲| 亚洲男人天堂2023| 尤物国产在线观看| 黄色成人在线网| 91在线码无精品| 国产精品无av码在线观看| 永久免费看mv网站入口| 国产精品久久久久久久久久白浆| 亚洲国产视频网站| 日本成人黄色| 亚洲AV无码成人片在线观看| 国产毛片久久| 欧美成人激情视频免费观看| 色呦呦一区二区| 日本在线视频一区二区| 亚洲午夜羞羞片| 婷婷五月色综合| 亚洲欧美强伦一区二区| 蜜桃av噜噜一区二区三区小说| 欧美福利视频在线观看| 国产在线综合视频| 国产主播性色av福利精品一区| 欧美中文字幕一二三区视频| 欧美乱做爰xxxⅹ久久久| 国产女主播在线写真| 国产麻豆精品久久一二三| 国产99视频在线观看| 朝桐光av在线| 欧美色婷婷久久99精品红桃| 精品国产电影一区二区| www.精品在线| 亚洲欧美韩国| 亚洲小说欧美激情另类| 国产精品夜夜夜爽张柏芝| 国产一级二级三级在线观看| 国产 欧美在线| 国产免费一区二区三区在线能观看| 国产午夜免费视频| 亚洲久久久久| 综合国产在线视频| 成人免费毛片糖心| 免费成人蒂法| 欧美不卡激情三级在线观看| 日韩一区二区三区不卡视频| 亚洲淫成人影院| 午夜精品久久久久久久99樱桃| 18视频在线观看娇喘| 日韩美女网站| 中文字幕免费观看一区| 欧美一区二区福利| 天天在线女人的天堂视频| 成人综合激情网| 亚洲在线第一页| 国产绿帽刺激高潮对白| 久久99久久99小草精品免视看| 国产精品第1页| 99精品人妻国产毛片| 亚洲视频www| 午夜精品久久久久久久久久久久| 久久综合综合久久| 激情亚洲成人| 97视频在线观看视频免费视频| 国产一级特黄毛片| 亚洲国产一区二区三区高清| 久久久久久高潮国产精品视| 久久老司机精品视频| 欧美精品色网| 欧美激情网友自拍| 国产午夜视频在线| 国产亚洲激情| 青草青草久热精品视频在线观看| 亚洲精品男人的天堂| 久久久久免费| 国产精品一区二区女厕厕| 在线观看av大片| 狠狠久久亚洲欧美| 成人国产1314www色视频| 亚洲精品久久久久久久久久 | 五月天国产一区| av在线电影观看| 亚洲欧洲精品一区二区三区| 99中文字幕在线观看| 日本色护士高潮视频在线观看| 亚洲国产综合91精品麻豆| 国产午夜福利在线播放| 日日夜夜天天综合| 欧美日韩免费不卡视频一区二区三区| 三上悠亚av一区二区三区| 精品午夜av| 亚洲黄页网在线观看| 国产美女永久免费无遮挡| 外国成人免费视频| 97色在线视频观看| 一级欧美一级日韩| 成人免费的视频| 日韩经典在线视频| 18videosex性欧美麻豆| 欧美日韩亚洲高清| 天堂一区在线观看| 国产一区福利| 精品国内产的精品视频在线观看| 激情视频在线播放| 久久深夜福利| 成人在线免费观看一区| 国产午夜视频在线观看| 一二三区精品视频| 毛片毛片毛片毛片毛片毛片毛片毛片毛片 | 亚洲欧美日韩色| 精品在线播放| 欧美国产乱视频| 波多野结衣电车痴汉| 国产成人丝袜美腿| 日韩免费一区二区三区| 欧美激情成人动漫| 欧美日韩一二三| 亚洲av成人无码一二三在线观看| 色综合咪咪久久网| 日本欧美爱爱爱| 亚洲精品视频91| 中国av一区二区三区| 日韩 欧美 视频| **国产精品| 亚洲欧美制服第一页| 免费日韩在线视频| 久久99精品国产麻豆不卡| 久久99精品国产99久久| 亚洲淫性视频| 欧美日韩国产一级片| 亚洲AV无码片久久精品| 在线观看视频免费一区二区三区| 成人在线视频网| 国产日产精品久久久久久婷婷| 午夜伊人狠狠久久| 日本泡妞xxxx免费视频软件| 99热精品久久| 国产国语videosex另类| 亚洲欧美日韩免费| 亚洲3atv精品一区二区三区| 91欧美一区二区三区| 日韩av在线播放网址| 国产99久久精品一区二区 夜夜躁日日躁 | 欧美一级在线免费观看| 亚洲欧美激情视频在线观看一区二区三区| 一本久道综合色婷婷五月| 日韩美女国产精品| 97涩涩爰在线观看亚洲| 天天av天天翘| 午夜婷婷国产麻豆精品| 最新版天堂资源在线| 欧美午夜不卡影院在线观看完整版免费| 国产日韩在线精品av| 成人免费视频| 欧洲一区二区三区免费视频| 亚洲精品乱码久久久久久久久久久久 | 男人资源在线播放| 欧美日韩国产综合一区二区| www.涩涩爱| 另类的小说在线视频另类成人小视频在线 | 91久久午夜| 国产精品国产精品国产专区不卡| 91福利国产在线观看菠萝蜜| 4438x成人网最大色成网站| 久久高清内射无套| 国产成人综合精品三级| 日产精品久久久久久久蜜臀| 91成人午夜| 91产国在线观看动作片喷水| 欧美精品少妇| 欧美亚洲动漫精品| 熟女少妇a性色生活片毛片| 国产又粗又猛又爽又黄91精品| 九一免费在线观看| 国内毛片久久| 国产成人亚洲精品| 欧美成人性生活视频| 日韩一卡二卡三卡| 国产特黄大片aaaa毛片| 国产亚洲一区字幕| 欧美国产日韩另类| 很黄很黄激情成人| 欧美日韩视频在线一区二区观看视频| 97人人做人人爽香蕉精品| 久久亚洲欧美日韩精品专区| 欧美一级在线免费观看| 色成人在线视频| 熟女少妇a性色生活片毛片| 懂色一区二区三区免费观看| 成人综合视频在线| 欧美成人直播| 国产日韩精品推荐| 日本综合视频| 久久久久中文字幕| www.国产精品.com| 精品剧情在线观看| 中文字幕免费高清网站| 亚洲欧美日韩一区| 欧美 日韩 国产 成人 在线观看 | 人妻熟女一二三区夜夜爱| 成人免费在线播放| 国产精品久久久久久久久久久久午夜片 | 日韩欧美一区二区在线视频| 精品美女久久久久| ...xxx性欧美| 黄色在线观看av| 激情亚洲综合在线| 六月丁香婷婷在线| 欧美色综合网| 无码免费一区二区三区免费播放 | 天堂网2014av| 欧美日韩国产免费| 午夜毛片在线观看| 亚洲欧美日韩一区二区| 永久免费av无码网站性色av| 成人99免费视频| 一级做a免费视频| 久久精品一区二区国产| 免费不卡av在线| 91成人看片| 色噜噜一区二区| 欧美综合精品| 成人动漫视频在线观看完整版| 成人深夜福利| 国产激情综合五月久久| 国产777精品精品热热热一区二区| www.色综合| 国产天堂在线| 亚洲加勒比久久88色综合| 99国产精品久久久久久久成人| 在线视频欧美区| 中文字幕在线观看免费视频| 亚洲卡通欧美制服中文| 一级片黄色录像| 国产欧美精品在线观看| 99久久久久久久久久| hitomi一区二区三区精品| 中文字幕一二三| 精品一区二区在线播放| 高清av免费看| 美女任你摸久久| 男人搞女人网站| 日韩国产在线观看一区| 男人天堂网视频| 亚洲一区国产| 国产二级片在线观看| 亚洲日本国产| 国产二区视频在线| 99国产精品视频免费观看一公开 | 可以免费看污视频的网站在线| 亚洲国产精品成人av| 亚洲精品网站在线| 日韩午夜中文字幕| 成 人 免费 黄 色| 精品国产伦一区二区三区观看体验 | 日本人dh亚洲人ⅹxx| 国产一区二区三区美女| 久久久久亚洲av片无码v| 国产酒店精品激情| 久久久久亚洲av无码网站| 国产成人精品一区二| 韩国三级视频在线观看| 处破女av一区二区| 久久午夜夜伦鲁鲁片| 久久嫩草精品久久久精品| 亚洲精品国产91| 国产精品私房写真福利视频| 国产91在线播放九色| 亚洲欧洲中文日韩久久av乱码| 久久黄色免费网站| 亚洲mv大片欧洲mv大片精品| 亚洲婷婷综合网| 欧美三级在线看| 国产毛片久久久久| 亚洲精品一线二线三线| 日本在线一二三| 中文字幕成人精品久久不卡| 欧美a免费在线| 欧美—级a级欧美特级ar全黄| www视频在线观看| 国产成人中文字幕| 亚洲免费一区| 粉嫩高清一区二区三区精品视频| 欧美三级午夜理伦三级在线观看| 日本电影一区二区三区| 婷婷精品进入| 国产av麻豆mag剧集| 三级一区在线视频先锋| 91视频福利网| 93久久精品日日躁夜夜躁欧美| 变态另类ts人妖一区二区| 亚洲视频狠狠干| 午夜毛片在线观看| 欧美久久久久久久久久| 天天操天天射天天| 色婷婷久久av| 国产高清自产拍av在线| 国产日韩欧美影视| 六月丁香久久丫| 夜夜爽99久久国产综合精品女不卡| 欧美精品观看| 色七七在线观看| 粉嫩av一区二区三区粉嫩| 国产黄色片在线| 婷婷中文字幕综合| 国产孕妇孕交大片孕| 日韩国产精品一区| av在线免费播放| 国产成人久久久精品一区| 大陆精大陆国产国语精品| 午夜一区二区三视频在线观看| 黄色亚洲精品| 在线观看免费污视频| 91在线观看下载| 99精品久久久久| 欧美日免费三级在线| 神马电影在线观看| 欧美激情日韩图片| 999精品视频在线观看| 欧美一区2区三区4区公司二百| 欧美视频导航| 少妇一级淫免费播放| 国产亚洲综合在线| 日韩在线观看第一页| 日韩免费电影一区| 欧美日韩在线看片| 国产精品久久精品| 自拍亚洲一区| 国产免费观看高清视频| 盗摄精品av一区二区三区| 日本午夜在线观看| 精品污污网站免费看| 黄色av免费在线看| 97超级碰碰人国产在线观看| 97久久亚洲| 成人av在线播放观看| 国产一区二区三区四区在线观看| 超碰人人人人人人人| 日本韩国欧美一区二区三区| 四虎精品成人影院观看地址| 97精品国产97久久久久久| heyzo欧美激情| 日本香蕉视频在线观看| 国产成人一区在线| 暗呦丨小u女国产精品| 91精品国产乱码| 国产丝袜在线| 亚洲综合成人婷婷小说| 亚洲最新av| 亚洲精品无码久久久久久久| 亚洲欧美日韩电影| 国产成人三级一区二区在线观看一| 久久精品成人欧美大片古装| 欧美aaa级| 中文网丁香综合网| 经典三级在线一区| 日韩在线中文字幕视频| 日韩一级完整毛片| 97人人在线视频| 久久久精彩视频| 老**午夜毛片一区二区三区 | 日韩中文在线视频| 台湾天天综合人成在线| 免费看污污视频| 成人免费高清在线| 亚洲婷婷综合网| 中文字幕亚洲专区| 成人51免费| 黄色三级中文字幕| 91啪九色porn原创视频在线观看| 国产又黄又猛又粗又爽| 一个人看的www久久| av国产精品| 日韩欧美不卡在线| 久久久激情视频| 6—12呦国产精品| 九九久久国产精品| 免费久久精品| 亚洲一级片av| 亚洲1区2区3区视频| 国产www.大片在线| 亚洲综合日韩在线| 国产乱码精品| 国产传媒免费在线观看| 亚洲二区在线播放视频| 国产综合av| 青草网在线观看| 久久男人中文字幕资源站| 11024精品一区二区三区日韩| 久久久久久久久91| 狠狠色狠狠色综合婷婷tag| 亚洲高清av一区二区三区| 欧美日韩国产专区| 精品麻豆一区二区三区| 精品国产乱码久久久久久108| 蜜臀av性久久久久蜜臀av麻豆|