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

          3萬字聊聊什么是Redis(三)

          共 7783字,需瀏覽 16分鐘

           ·

          2021-11-29 17:16

          大家好,我是Leo

          繼上篇Redis技術(shù)總結(jié)二,我們繼續(xù)聊聊Redis的相關(guān)技術(shù)!

          上一篇我們介紹了

          1. 主從庫的由來
          2. 主從數(shù)據(jù)一致性原理
          3. 哨兵集群
          4. 哨兵投票機(jī)制
          5. pub/sub機(jī)制
          6. CAP原理

          這篇主要是深入Redis的數(shù)據(jù)結(jié)構(gòu),五大類型的學(xué)習(xí)。

          推薦閱讀

          3萬字聊聊什么是MySQL

          3萬字聊聊什么是Redis(一)

          3萬字聊聊什么是Redis(二)

          思路

          前幾天在群里,看到幾個朋友在聊Redis,我感覺很多人把Redis看的太簡單了,總覺得Redis就是get,set。其實表面上確實是get,set。但是這個get,set也不是隨便用的,也涉及很多底層技術(shù)的了解才能在項目上得心應(yīng)手!

          整篇文章大概的思路就是把Redis的五大常用數(shù)據(jù)類型以及三個擴(kuò)展類型從頭梳理一下。希望能幫助你在真實的業(yè)務(wù)場景中,可以選擇更優(yōu)的數(shù)據(jù)類型。

          String

          String類型是我們大多數(shù)業(yè)務(wù)需求中,最先考慮到的一個類型,但是這個類型也是歧義最大的,Java中string字符串是由字符決定的,每個漢字是2個字節(jié),每個英文是1個字節(jié)。在Redis中不是2個字節(jié),也不是1個,4個。而是?64個字節(jié)

          優(yōu)點:?String類型可以保存字符串,JSON字符串,二進(jìn)制字節(jié)流等,幾乎用String類型可以解決萬事萬物。簡單,省事,粗暴。

          缺點:?內(nèi)存占用過大

          String除了記錄實際數(shù)據(jù)以外,還需要額外的空間存儲數(shù)據(jù)長度,空間使用等信息,這些信息也叫過元信息。如果保存的數(shù)據(jù)比較大還好,還劃算一些。如果保存的是一些比較小的數(shù)值就非常不劃算了。

          String類型主要有三種編碼方式保存數(shù)據(jù),如下圖(圖片來自蔣德均老師)

          1. 當(dāng)保存64位有符號整數(shù)時,String類型會把它保存為一個8個字節(jié)的Long類型整數(shù),這種方式也叫?int編碼
          2. 當(dāng)保存是字符時,String類型就會用簡單動態(tài)字符串結(jié)構(gòu)體來保存,(下文中的簡單動態(tài)字符串我們就用SDS來保存了)。如下圖所示。

          • len 表示buf的已用長度
          • alloc 表示buf 的實際分配長度
          • buf 表示真實數(shù)據(jù)(因為Redis是C寫的,所以末尾結(jié)束符是 ‘\0’ )

          除了String類型自身的結(jié)構(gòu)占用,還有Redis外層的結(jié)構(gòu)占用,外層的結(jié)構(gòu)下文我們就稱為RedisObject(主要包含最后一次的訪問時間,被引用的次數(shù)等)。

          一個RedisObject包含8個字節(jié)的元數(shù)據(jù)和8字節(jié)指針,這個指針再指向上面的SDS。

          Redis在保存Long類型整數(shù)時,RedisObject中的指針直接賦值為整數(shù)數(shù)據(jù),這樣就不用額外的指針再指向整數(shù)了,同時也節(jié)省了指針的空間開銷。

          Redis在保存的字符串?dāng)?shù)據(jù)小于44字節(jié),RedisObject中的元數(shù)據(jù),指針和SDS是一塊連續(xù)的區(qū)域,這樣可以避免內(nèi)存碎片,這種布局方式也被稱為?enbstr編碼

          1. Redis保存的字符串?dāng)?shù)據(jù)大于44字節(jié)時,SDS的數(shù)據(jù)量就越來越多了,為了內(nèi)存考慮,Redis就不把SDS與RedisObject放一塊了,而是會給SDS分配獨立的空間,并用指針指向SDS結(jié)構(gòu)。這種布局方式也被稱為?raw編碼
          img

          介紹完String類型的三種編碼方式,RedisObject,SDS,這兩個結(jié)構(gòu)體占用不了64字節(jié),那么剩余的內(nèi)存是由什么占用的呢?dictEntry結(jié)構(gòu)體?和?jemalloc

          Redis會用一個全局哈希表保存所有的鍵值對關(guān)系,哈希表的每一項是一個?dictEntry?的結(jié)構(gòu)體,用來指向一個鍵值對。dictEntry 結(jié)構(gòu)中有三個 8 字節(jié)的指針,分別指向 key、value 以及下一個 dictEntry,三個指針共 24 字節(jié),如下圖所示

          jemalloc是Redis使用的內(nèi)存分配庫,他會根據(jù)我們申請的字節(jié)數(shù)A,找一個比A大的,但是最接近A的2的冪次數(shù)作為分配空間,這樣可以減少頻繁分配的次數(shù)。

          舉個例子,如果我們要存放20位的訂單號,我們申請一個8個字節(jié)的Long類型,jemalloc實際會分配16個字節(jié)。所以dictEntry會占用16個字節(jié)。16(數(shù)據(jù)分配的長度)+24(dictEntry指針)+16(RedisObject)+12(SDS)

          以上就是為什么Redis的String類型占用64字節(jié)的原理。從SDS,RedisObject,dictEntry,jemalloc分配。

          ziplist

          ziplist是壓縮列表,list類型的底層實現(xiàn)是雙向列表+壓縮列表!

          String內(nèi)存過大的這個缺點,可以考慮部分需求采用壓縮列表ziplist來存儲。這是一種非常節(jié)省內(nèi)存的結(jié)構(gòu)。

          壓縮列表主要有表頭,數(shù)據(jù),表尾,列表結(jié)束。表頭:有三個字段 zlbytes、zltail 和 zllen,分別表示列表長度、列表尾的偏移量,以及列表中的 entry 個數(shù)。之所以能節(jié)省內(nèi)存是因為它是使用一條連續(xù)的entry保存數(shù)據(jù)的。這些enrty會挨個位置放在內(nèi)存中,不需要額外的指針進(jìn)行連接,這樣就節(jié)省了指針的空間占用。

          entry

          • prev_len,表示前一個 entry 的長度。prev_len 有兩種取值情況:1 字節(jié)或 5 字節(jié)。取值 1 字節(jié)時,表示上一個 entry 的長度小于 254 字節(jié)。雖然 1 字節(jié)的值能表示的數(shù)值范圍是 0 到 255,但是壓縮列表中 zlend 的取值默認(rèn)是 255,因此,就默認(rèn)用 255 表示整個壓縮列表的結(jié)束,其他表示長度的地方就不能再用 255 這個值了。所以,當(dāng)上一個 entry 長度小于 254 字節(jié)時,prev_len 取值為 1 字節(jié),否則,就取值為 5 字節(jié)。
          • len:表示自身長度,占用4 字節(jié);
          • encoding:表示編碼方式,占用1 字節(jié);
          • content:保存實際數(shù)據(jù)。

          Hash

          在處理單值的鍵值對時,可以采用基于 Hash 類型的二級編碼方法。二級編碼就是把一個單值的數(shù)據(jù)拆分成兩部分,前一部分作為 Hash 集合的 key,后一部分作為 Hash 集合的 value,這樣我們就可以把單值數(shù)據(jù)保存到 Hash 集合中了。

          String類型消耗了64字節(jié),采用Hash二級編碼的方式只用了16字節(jié)。滿足了我們節(jié)省內(nèi)存空間的需求。我們在第一篇中介紹了Hash的底層是由壓縮列表+哈希表實現(xiàn)的。那么什么時候使用壓縮列表,什么時候使用哈希表呢?

          用壓縮列表

          Hash 類型設(shè)置了用壓縮列表保存數(shù)據(jù)時的兩個閾值,一旦超過了閾值,Hash 類型就會用哈希表來保存數(shù)據(jù)了。

          • hash-max-ziplist-entries:表示用壓縮列表保存時哈希集合中的最大元素個數(shù)。
          • hash-max-ziplist-value:表示用壓縮列表保存時哈希集合中單個元素的最大長度。

          聚合統(tǒng)計

          聚合統(tǒng)計,就是指統(tǒng)計多個集合元素的聚合結(jié)果。交集統(tǒng)計,差集統(tǒng)計,并集統(tǒng)計等。

          交集統(tǒng)計:處理有些留存用戶,連續(xù)簽到,連續(xù)登錄這些業(yè)務(wù)需求,共同好友,共同關(guān)注等

          差集統(tǒng)計:處理每天新增的用戶

          并集統(tǒng)計:處理每種情況的總和數(shù)據(jù)

          交,差,并集處理中,我們首選的還是Set集合。但是Set集合的計算復(fù)雜度較高,在數(shù)據(jù)量較大的情況下,如果直接計執(zhí)行這些計算會導(dǎo)致Redis實例阻塞。所以在平時處理時,可以從主從集群中選擇一個從庫,讓它專門負(fù)責(zé)聚合計算,或者是把數(shù)據(jù)讀取到客戶端,在客戶端來完成聚合統(tǒng)計,這樣就可以規(guī)避阻塞主庫實例和其他從庫實例的風(fēng)險了。

          排序統(tǒng)計

          排序的話能處理的類型就多了。Redis有4種集合類型(List、Hash、Set、Sorted Set)。list和sorted set屬于有序集合,hash和set屬于無序集合。

          list是根據(jù)插入的順序進(jìn)行排序的,sorted set屬于根據(jù)某個字段值的權(quán)重進(jìn)行排序的。具體選擇哪個還是要看業(yè)務(wù)需求,下面我們分別介紹一下兩種的利弊。

          list

          舉一個商品評論的例子,如果處理商品商品的例子時,list恐怕就不行了,簡單來說list處理不了帶有分頁需求的場景。

          假設(shè)當(dāng)前的評論 List 是{A, B, C, D, E, F}(其中,A 是最新的評論,以此類推,F(xiàn) 是最早的評論),在展示第一頁的 3 個評論時,可以得到 A、B、C。展示第二頁的3個評論時,可以得到D、E、F。如果在展示第二頁前,來了一條新評論,這個時候的集合就為{G, A, B, C, D, E, F}。這個時候就變成了C、D、E。

          這個時候C就被展示了兩遍,如果新增數(shù)據(jù)量大的話,有可能第一頁展示一遍的數(shù)據(jù)之后,第二頁還會再顯示一遍。這樣的業(yè)務(wù)場景就不符合了。

          sorted set

          和list相比,sort不存在這個問題,它是根據(jù)實際權(quán)重來排序和獲取數(shù)據(jù)的。我們可以按照評論的創(chuàng)建時間排序然后保存到sorted set集合中。這樣的話即使數(shù)據(jù)頻繁更新,每次插入時也會根據(jù)評論的創(chuàng)建時間排序重新獲取。

          所以,在面對需要展示最新列表、排行榜等場景時,如果數(shù)據(jù)更新頻繁或者需要分頁顯示,建議你優(yōu)先考慮使用 Sorted Set。

          二值狀態(tài)統(tǒng)計

          二值狀態(tài)統(tǒng)計可以采用Bitmap。Bitmap本身是用String類型作為底層數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的一種統(tǒng)計二值狀態(tài)的數(shù)據(jù)類型。String 類型是會保存為二進(jìn)制的字節(jié)數(shù)組,所以,Redis 就把字節(jié)數(shù)組的每個 bit 位利用起來,用來表示一個元素的二值狀態(tài)。

          二值狀態(tài)的起始值是從0開始的,當(dāng)對這個bit進(jìn)行寫操作時,就會變成1。如果只需要統(tǒng)計數(shù)據(jù)的二值狀態(tài),例如商品有沒有、用戶在不在等,就可以使用 Bitmap,因為它只用一個 bit 位就能表示 0 或 1。在記錄海量數(shù)據(jù)時,Bitmap 能夠有效地節(jié)省內(nèi)存空間。

          基數(shù)統(tǒng)計

          基數(shù)統(tǒng)計就是指統(tǒng)計一個集合中不重復(fù)的元素個數(shù)。對應(yīng)到我們剛才介紹的場景中,就是統(tǒng)計網(wǎng)頁的 UV。我們首先考慮的數(shù)據(jù)類型就是Set類型。Set的去重功能保證了不會重復(fù)記錄一個用戶的訪問次數(shù)。這樣用戶就是獨立的檔案了。

          set雖好但是有一個缺點就是如果每個頁面的火爆程度不一致,就會導(dǎo)致某個頁面的訪問用戶達(dá)到了千萬,某個冷門界面寥寥無幾的幾個人。這樣的確是可能存在的,雙十一就是一個很好的例子。

          我們可以考慮一下HyperLogLog類型。HyperLogLog 是一種用于統(tǒng)計基數(shù)的數(shù)據(jù)集合類型,它的最大優(yōu)勢就在于,當(dāng)集合元素數(shù)量非常多時,它計算基數(shù)所需的空間總是固定的,而且還很小。

          在 Redis 中,每個 HyperLogLog 只需要花費(fèi) 12 KB 內(nèi)存,就可以計算接近 2^64 個元素的基數(shù)。你看,和元素越多就越耗費(fèi)內(nèi)存的 Set 和 Hash 類型相比,HyperLogLog 就非常節(jié)省空間。

          不過,有一點需要注意一下,HyperLogLog 的統(tǒng)計規(guī)則是基于概率完成的,所以它給出的統(tǒng)計結(jié)果是有一定誤差的,標(biāo)準(zhǔn)誤算率是 0.81%。這也就意味著,你使用 HyperLogLog 統(tǒng)計的 UV 是 100 萬,但實際的 UV 可能是 101 萬。雖然誤差率不算大,但是,如果你需要精確統(tǒng)計結(jié)果的話,最好還是繼續(xù)用 Set 或 Hash 類型。

          GEO地理位置

          日常生活中,附近的餐館,附件的王者榮耀隊友,微信搖一搖附近的人,打車軟件等都是GEO的應(yīng)用。

          聊到GEO必然離不開GeoHash編碼了,為了能高效地對經(jīng)緯度進(jìn)行比較,Redis 采用了業(yè)界廣泛使用的 GeoHash 編碼方法,這個方法的基本原理就是?“二分區(qū)間,區(qū)間編碼”。

          我們要對一組經(jīng)緯度編碼,肯定要分別把經(jīng)度編碼,再把緯度編碼,最終再把經(jīng)緯度的編碼合成一個編碼。這個來了解一下編碼過程吧。

          GeoHash編碼會把一個經(jīng)度編碼成一個N位的二進(jìn)制值,它的經(jīng)度范圍是[-180,180],我們對經(jīng)緯度范圍做N次二分區(qū)操作時,經(jīng)度范圍[-180,180]會被分成兩個子區(qū)間:[-180,0) 和[0,180](我稱之為左、右分區(qū))。此時,我們可以查看一下要編碼的經(jīng)度值落在了左分區(qū)還是右分區(qū)。如果是落在左分區(qū),我們就用 0 表示;如果落在右分區(qū),就用 1 表示。這樣一來,每做完一次二分區(qū),我們就可以得到 1 位編碼值。

          得出經(jīng)緯度的各自編碼值,下面的工作就是把兩個編碼值合成一個了。

          • 拿經(jīng)度的第0位,充當(dāng)新編碼的第0位。
          • 拿緯度的第0位,充當(dāng)新編碼的第1位。
          • 以此類推。。。。

          用了 GeoHash 編碼后,原來無法用一個權(quán)重分?jǐn)?shù)表示的一組經(jīng)緯度(116.37,39.86)就可以用 1110011101 這一個值來表示,就可以保存為 Sorted Set 的權(quán)重分?jǐn)?shù)了。

          如何保存時間序列數(shù)據(jù)

          這個應(yīng)該算是一道面試題了,也算是對上述介紹的Redis數(shù)據(jù)類型的一個檢驗吧。下面我們了解一下

          如何保存時間序列數(shù)據(jù)呢?我舉一個類似的業(yè)務(wù)場景的例子吧。養(yǎng)殖棚里要記錄一天的氣溫變化,或者工廠里的氣溫變化。如果是你,你會怎么處理呢?可以先思考一下!

          分析思路:數(shù)據(jù)是源源不斷寫入的,所以通常是持續(xù)的高并發(fā)寫入,比如數(shù)萬個設(shè)備的實時狀態(tài)值。一旦插入之后就不會有改動,也就是說修改幾乎沒有。這種業(yè)務(wù)需求還是比較好處理的,只需要寫入快就好了!

          我們可以采用Hash類型和SortedSet類型共同實現(xiàn)這個需求。

          1. Hash類型的插入復(fù)雜度是O(1),可以解決插入速度的要求
          2. Hash很快但是致命的缺點就是不支持范圍查詢,所以我們需要借助SorttedSet這個有序的特性實現(xiàn)范圍查找**(把時間戳作為Hash的key)**
          3. 數(shù)據(jù)的原子性我們可以依賴Redis的 MULTI 和 EXEC 幫我們完成

          一路順風(fēng),但是拿到的數(shù)據(jù)我們無法得到有效的分析,因為SortedSet支持范圍查找,無法直接進(jìn)行聚合計算,所以這里的唯一辦法就是把Redis的數(shù)據(jù)拿到客戶端通過程序我們來處理。

          如果是這樣的話另一個風(fēng)險點又出現(xiàn)了,客戶端與Redis大量的交互數(shù)據(jù),這會和其他操作命令競爭網(wǎng)絡(luò)資源,導(dǎo)致其他操作變慢。

          無奈之下,我們采用了?RedisTimeSeries?,上面那種方案還是可以的,可以學(xué)習(xí)那種各取所長的特性完成我們的業(yè)務(wù)需求。

          RedisTimeSeries是Redis的一個擴(kuò)展模塊,它專門面向時間序列數(shù)據(jù)提供了數(shù)據(jù)類型和訪問接口,并且支持在Redis實例直接對數(shù)據(jù)進(jìn)行按時間范圍的聚合計算。

          這個類型了解一下即可!

          因為 RedisTimeSeries 不屬于 Redis 的內(nèi)建功能模塊,在使用時,我們需要先把它的源碼單獨編譯成動態(tài)鏈接庫 redistimeseries.so,再使用 loadmodule 命令進(jìn)行加載。

          當(dāng)用于時間序列數(shù)據(jù)存取時,RedisTimeSeries 的操作主要有 5 個:

          • 用 TS.CREATE 命令創(chuàng)建時間序列數(shù)據(jù)集合;
          • 用 TS.ADD 命令插入數(shù)據(jù);
          • 用 TS.GET 命令讀取最新數(shù)據(jù);
          • 用 TS.MGET 命令按標(biāo)簽過濾查詢數(shù)據(jù)集合;
          • 用 TS.RANGE 支持聚合計算的范圍查詢。

          Redis可以作為消息隊列?

          Redis是可以作為消息隊列的,它是一個非常優(yōu)秀的輕量級消息隊列。

          Redis在存儲數(shù)據(jù)時,必須滿足消息保序,重復(fù)消息處理,消息可靠性保證

          消息保序

          一定要確保每個消息進(jìn)入Redis的順序的一致的,因為如果不是有序的話,整個數(shù)據(jù)就亂了。比如修改庫存一樣,第一條記錄修改為5,第二條記錄修改為3。如果順序又問題,就會先執(zhí)行修改為3再修改為5。這樣就和我們預(yù)定的效果不一樣了。

          所以一定要保證每條消息進(jìn)入的順序以及排序順序。這里我們可以優(yōu)先想到List集合處理。

          重復(fù)消息處理

          這點也是MQ的致命問題,這里簡單介紹一下,后續(xù)介紹MQ的時候再詳細(xì)聊一下。

          消費(fèi)者從消息隊列讀取消息時,有時會因為網(wǎng)絡(luò)問題出現(xiàn)消息重傳。如果消費(fèi)者收到了消息并且多次執(zhí)行同一個重復(fù)消息就嗝屁了。我們還是舉上述商品的庫存為例。如果購買商品,原本買了一件,庫存減一件。消費(fèi)兩次的話,庫存的值就不對了。這樣肯定是不行的。

          消息可靠性保證

          一旦服務(wù)宕機(jī),肯定要有恢復(fù)數(shù)據(jù)的能力,要不然毫無安全措施,怎么用呢?

          技術(shù)選型分析

          通過上述三種要求,我們可以定位到List類型和Stream兩種數(shù)據(jù)類型。我們先來了解一下List如何處理消息隊列問題。

          List集合本身就是按先進(jìn)先出的順序?qū)?shù)據(jù)進(jìn)行存取的,支持LPUSH,LPOP,RPUSH,RPOP。所以如果用List作為消息隊列的保序問題是再合適不過的了。

          擴(kuò)展?:list不會主動通知消費(fèi)者的功能,如果想要及時的處理的話可以采用while1的方式,這種方式非常損耗性能。

          關(guān)于List的消息通知問題可以采用Redis提供的BRPOP命令。也稱阻塞式讀取,客戶端在沒有讀到隊列數(shù)據(jù)時,自動阻塞,直到有新的數(shù)據(jù)寫入隊列,再開始讀取新數(shù)據(jù)。和消費(fèi)者程序自己不停地調(diào)用 RPOP 命令相比,這種方式能節(jié)省 CPU 開銷。

          接著再解決消息重復(fù)的問題。消息隊列要給每一個消息提供全局唯一ID,同時消費(fèi)者也要把處理的每一個消息的ID都記錄下來。通過雙重校驗的方式就不會重復(fù)消費(fèi)了。

          下面就是消息可靠性問題了,List是不支持可靠性的。Redis提供了BRPOPLPUSH命令。這個命令的作用是讓消費(fèi)者程序從一個 List 中讀取消息,同時,Redis 會把這個消息再插入到另一個 List(可以叫作備份 List)留存。這樣一來,如果消費(fèi)者程序讀了消息但沒能正常處理,等它重啟后,就可以從備份 List 中重新讀取消息并進(jìn)行處理了。

          當(dāng)生產(chǎn)者消息發(fā)送很快,而消費(fèi)者處理消息的速度比較慢,這就導(dǎo)致 List 中的消息越積越多,給 Redis 的內(nèi)存帶來很大壓力。這個時候,我們希望啟動多個消費(fèi)者程序組成一個消費(fèi)組,一起分擔(dān)處理 List 中的消息。但是,List 類型并不支持消費(fèi)組的實現(xiàn),Stream就誕生了!

          介紹完用List實現(xiàn)的相關(guān)原理,我們介紹一下Stream的相關(guān)實現(xiàn)吧。

          Streams 是 Redis 專門為消息隊列設(shè)計的數(shù)據(jù)類型,它提供了豐富的消息隊列操作命令。

          • XADD:插入消息,保證有序,可以自動生成全局唯一 ID;
          • XREAD:用于讀取消息,可以按 ID 讀取數(shù)據(jù);
          • XREADGROUP:按消費(fèi)組形式讀取消息;
          • XPENDING 和 XACK:XPENDING 命令可以用來查詢每個消費(fèi)組內(nèi)所有消費(fèi)者已讀取但尚未確認(rèn)的消息,而 XACK 命令用于向消息隊列確認(rèn)消息處理已完成。

          不論是全局ID問題,還是重復(fù)消費(fèi)問題,Redis都提供了,暫時沒啥好講的。后續(xù)有知識的深入再回頭輸出吧!

          結(jié)尾

          1. 主要介紹了Redis的類型的底層實現(xiàn)以及技術(shù),類型選擇的依據(jù)
          2. 通過時間序列數(shù)據(jù)引出多種類型的搭配使用思路以及擴(kuò)展一下RedisTimeSeries模塊的使用。
          3. Redis作為消息隊列也是高頻的面試問題,通過這一問題延伸了List的優(yōu)劣和Streams的應(yīng)用

          每個知識點都是自己整理濃縮表達(dá)出來的,部分有些不容易懂的地方請及時指出,我們一起共同進(jìn)步!

          點贊 分享 在看 關(guān)注?就是對我最大支持。歡少的成長之路 感謝你


          瀏覽 49
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  青青操激情视频 | 最好看的MV中文字幕国语 | 午夜福利在线视频 | 人妻懂色av粉嫩av浪潮av | 亚洲一区二区三区人妻 |