一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

深入NSQ 之旅

 richsky 2014-07-18

深入NSQ 之旅

介紹
 

NSQ 是一個(gè)實(shí)時(shí)的分布式消息平臺(tái)。它的設(shè)計(jì)目標(biāo)是為在多臺(tái)計(jì)算機(jī)上運(yùn)行的松散服務(wù)提供一個(gè)現(xiàn)代化的基礎(chǔ)設(shè)施骨架。
 

這篇文章介紹了 基于go語(yǔ)言的NSQ的內(nèi)部架構(gòu),它能夠?yàn)楦咄掏铝康木W(wǎng)絡(luò)服務(wù)器帶來(lái) 性能的優(yōu)化,穩(wěn)定性和魯棒性。
 

可以說, 如果不是因?yàn)槲覀冊(cè)? bitly 使用go語(yǔ)言,NSQ就不會(huì)存在。這里既會(huì)講NSQ的功能也會(huì)涉及語(yǔ)言提供的特征。當(dāng)然,語(yǔ)言會(huì)影響思維,這次也不例外。
 

現(xiàn)在回想起來(lái),選擇使用go語(yǔ)言已經(jīng)收到了十倍的回報(bào)。由語(yǔ)言帶來(lái)的興奮和社區(qū)的積極反饋為這個(gè)項(xiàng)目提供了極大的幫助。

概要
 

NSQ是由3個(gè)進(jìn)程組成的:

  • nsqd 是一個(gè)接收、排隊(duì)、然后轉(zhuǎn)發(fā)消息到客戶端的進(jìn)程。

  • nsqlookupd 管理拓?fù)湫畔⒉⑻峁┳罱K一致性的發(fā)現(xiàn)服務(wù)。

  • nsqadmin 用于實(shí)時(shí)查看集群的統(tǒng)計(jì)數(shù)據(jù)(并且執(zhí)行各種各樣的管理任務(wù))。

NSQ中的數(shù)據(jù)流 模型是由 streamsconsumers 組成的tree。topic是一種獨(dú)特的 stream。channel是一個(gè)訂閱了給定topic的consumers 邏輯分組。

單個(gè)nsqd可以有多個(gè)topic,每個(gè)topic可以有多個(gè)channel。channel接收這個(gè)topic所有消息的副本,從而實(shí)現(xiàn)多播分發(fā),而channel上的 每個(gè)消息 被分發(fā)給它的訂閱者,從而實(shí)現(xiàn)負(fù)載均衡。

這些基本成員組成了 一個(gè) 可以表示各種 簡(jiǎn)單和復(fù)雜拓?fù)浣Y(jié)構(gòu) 的 強(qiáng)大框架 。
有關(guān)NSQ的設(shè)計(jì)的更多信息請(qǐng)參見 設(shè)計(jì)文檔 。

Topics 和 Channels

Topics 和 channels,是NSQ的核心成員,它們是如何使用go語(yǔ)言的特點(diǎn)來(lái)設(shè)計(jì)系統(tǒng)的最好示例。

Go的channels(為防止歧義,以下簡(jiǎn)稱為“go-chan”)是表達(dá)隊(duì)列的一種自然方式,因此一個(gè)NSQ的topic/channel,其核心就是一個(gè)存放消息指針的go-chan緩沖區(qū)。緩沖區(qū)的大小由 --mem-queue-size 配置參數(shù)確定。

讀取數(shù)據(jù)后,向topic發(fā)布消息的行為包括:

  • 實(shí)例化消息結(jié)構(gòu) (并分配消息體的字節(jié)數(shù)組)

  • read-lock 并獲得 Topic

  • read-lock 并檢查是否可以發(fā)布

  • 發(fā)送到go-chan緩沖區(qū)

為了從一個(gè)topic和它的channels獲得消息,topic不能按典型的方式用go-chan來(lái)接收,因?yàn)槎鄠€(gè)goroutines在一個(gè)go-chan上接收將會(huì) 分發(fā) 消息,而期望的結(jié)果是把每個(gè)消息 復(fù)制 到所有channel(goroutine)中。

此外,每個(gè)topic維護(hù)3個(gè)主要goroutine。第一個(gè)叫做 router,負(fù)責(zé)從傳入的go-chan中讀取新發(fā)布的消息,并存儲(chǔ)到一個(gè)隊(duì)列里(內(nèi)存或硬盤)。

第二個(gè),稱為 messagePump, 它負(fù)責(zé)復(fù)制和推送消息到如上所述的channel中。

第三個(gè)負(fù)責(zé) DiskQueue IO,將在后面討論。

Channels稍微有點(diǎn)復(fù)雜,它的根本目的是向外暴露一個(gè)單輸入單輸出的go-chan(事實(shí)上從抽象的角度來(lái)說,消息可能存在內(nèi)存里或硬盤上);

另外,每一個(gè)channel維護(hù)2個(gè)時(shí)間優(yōu)先級(jí)隊(duì)列,用于延時(shí)和消息超時(shí)的處理(并有2個(gè)伴隨goroutine來(lái)監(jiān)視它們)。

并行化的改善是通過管理每個(gè)channel的數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn),而不是依靠go運(yùn)行時(shí)的全局定時(shí)器。

注意:在內(nèi)部,go運(yùn)行時(shí)使用一個(gè)優(yōu)先級(jí)隊(duì)列和goroutine來(lái)管理定時(shí)器。它為整個(gè)time包(但不局限于)提供了支持。它通常不需要用戶來(lái)管理時(shí)間優(yōu)先級(jí)隊(duì)列,但一定要記住,它是一個(gè)有鎖的數(shù)據(jù)結(jié)構(gòu),有可能會(huì)影響 GOMAXPROCS>1 的性能。請(qǐng)參閱 runtime/time.goc 。

Backend / DiskQueue

NSQ的一個(gè)設(shè)計(jì)目標(biāo)是綁定內(nèi)存中的消息數(shù)目。它是通過DiskQueue(它擁有前面提到的的topic或channel的第三個(gè)goroutine)透明的把消息寫入到磁盤上來(lái)實(shí)現(xiàn)的。

由于內(nèi)存隊(duì)列只是一個(gè)go-chan,沒必要先把消息放到內(nèi)存里,如果可能的話,退回到磁盤上:
 

for msg := range c.incomingMsgChan {
    select {
    case c.memoryMsgChan <- msg:
    default:
        err := WriteMessageToBackend(&msgBuf, msg, c.backend)
        if err != nil {
            // ... handle errors ...
        }
    }
}

利用go語(yǔ)言的select語(yǔ)句,只需要幾行代碼就可以實(shí)現(xiàn)這個(gè)功能:上面的default分支只有在memoryMsgChan 滿的情況下才會(huì)執(zhí)行。

NSQ也有臨時(shí)channel的概念。臨時(shí)channel會(huì)丟棄溢出的消息(而不是寫入到磁盤),當(dāng)沒有客戶訂閱后它就會(huì)消失。這是一個(gè)Go接口的完美用例。Topics和channels有一個(gè)的結(jié)構(gòu)成員被聲明為Backend接口,而不是一個(gè)具體的類型。一般的 topics 和channels使用DiskQueue,而臨時(shí)channel則使用了實(shí)現(xiàn)Backend接口的DummyBackendQueue。

減少垃圾回收的壓力

在任何帶有垃圾回收的環(huán)境里,你都會(huì)多多少少感受到吞吐量(工作有效性)、延遲(響應(yīng)能力)、駐留集大小(內(nèi)存使用量)的壓力。

就 Go 1.2 而言,垃圾回收有標(biāo)記-清除(并發(fā)的)、不再生、不緊湊、阻止一切運(yùn)行、大體精準(zhǔn)的特點(diǎn)。大體精準(zhǔn)是因?yàn)槭O碌墓ぷ鳑]有及時(shí)的完成(這是 Go 1.3 的計(jì)劃)。

Go 的垃圾回收機(jī)制當(dāng)然會(huì)持續(xù)改進(jìn),但普遍的真理是:創(chuàng)建的垃圾越少,回收垃圾的時(shí)間越少。

首先,理解垃圾回收是如何在實(shí)際的工作負(fù)載中運(yùn)行的是非常重要的。為此, nsqdstatsd 的格式 (與其它內(nèi)部指標(biāo)一起) 發(fā)布垃圾回收的統(tǒng)計(jì)信息。 nsqadmin 顯示這些指標(biāo)的圖表,可以讓你深入了解它在頻率和持續(xù)時(shí)間兩方面產(chǎn)生的影響:


 

為了減少垃圾,你需要知道它們是在哪生成的。再次回到Go的工具鏈,它提供的答案如下:

  • 使用 testing 包和go test -benchmen來(lái)基準(zhǔn)測(cè)試熱點(diǎn)代碼路徑。它配置了每個(gè)迭代分配的數(shù)字(基準(zhǔn)的運(yùn)行可與 benchcmp 進(jìn)行比較)。

  • 使用 go build -gcflags -m 創(chuàng)建,將會(huì)輸出 逃逸分析 的結(jié)果。

除此之外,它還提供了 nsqd 的如下優(yōu)化:

  • 避免把[]byte 轉(zhuǎn)化為字符串類型.

  • 重復(fù)使用緩存或者對(duì)象(有時(shí)也許是 sync.Pool 又稱為 issue4720 ).

  • 預(yù)分配切片(特別是make的能力)并總是知曉鏈中各個(gè)條目的數(shù)量和大小。

  • 提供各種配置面板(如消息大小)的限制。

  • 避免封裝(如使用interface{})或者不必要的包裝類(例如 用一struct給一個(gè)多值的go-chan).

  • 在熱代碼路徑(它指定的)中避免使用defer。

TCP 協(xié)議

NSQ的TCP協(xié)議 是一個(gè)閃亮的會(huì)話典范,在這個(gè)會(huì)話中垃圾回收優(yōu)化的理論發(fā)揮了極大的效用。

協(xié)議的結(jié)構(gòu)是一個(gè)有很長(zhǎng)的前綴框架,這使得協(xié)議更直接,易于編碼和解碼。

[x][x][x][x][x][x][x][x][x][x][x][x]...
|  (int32) ||  (int32) || (binary)
|  4-byte  ||  4-byte  || N-byte
------------------------------------...
    size      frame ID     data

因?yàn)榭蚣艿慕M成部分的確切類型和大小是提前知道的,所以我們可以規(guī)避了使用方便的編碼二進(jìn)制包的Read()和Write()封裝(及它們外部接口的查找和會(huì)話)反之我們使用直接調(diào)用 binary.BigEndian 方法。

為了消除socket 輸入輸出的系統(tǒng)調(diào)用,客戶端net.Conn被封裝了 bufio.Readerbufio.Writer 。這個(gè)Reader通過暴露 ReadSlice() ,復(fù)用了它自己的緩沖區(qū)。這樣幾乎消除了讀完socket時(shí)的分配,這極大的降低了垃圾回收的壓力。這可能是因?yàn)榕c數(shù)據(jù)相關(guān)的大多數(shù)命令并沒有逃逸(在邊緣情況下這是假的,數(shù)據(jù)被強(qiáng)制復(fù)制)。

在更低層,MessageID 被定義為 [16]byte,這樣可以將其作為 map 的 key(slice 無(wú)法用作 map 的 key)。然而,考慮到從 socket 讀取的數(shù)據(jù)被保存為 []byte,勝于通過分配字符串類型的 key 來(lái)產(chǎn)生垃圾,并且為了避免從 slice 到 MessageID 的支撐數(shù)組產(chǎn)生復(fù)制操作,unsafe 包被用來(lái)將 slice 直接轉(zhuǎn)換為 MessageID:

id := *(*nsq.MessageID)(unsafe.Pointer(&msgID))

注意: 這是個(gè)技巧。如果編譯器對(duì)此已經(jīng)做了優(yōu)化,或者 Issue 3512 被打開可能會(huì)解決這個(gè)問題,那就不需要它了。 issue 5376 也值得通讀,它講述了在無(wú)須分配和拷貝時(shí),和 string 類型可被接收的地方,可以交換使用的“類常量”的 byte 類型。

類似的,Go 標(biāo)準(zhǔn)庫(kù)僅僅在 string 上提供了數(shù)值轉(zhuǎn)換方法。為了避免 string 的分配, nsqd 使用了 慣用的十進(jìn)制轉(zhuǎn)換方法 ,用于對(duì) []byte 直接操作。

這些看起來(lái)像是微優(yōu)化,但 TCP 協(xié)議包含了一些最熱的代碼執(zhí)行路徑。總體來(lái)說,以每秒數(shù)萬(wàn)消息的速度來(lái)說,它們對(duì)分配和系統(tǒng)開銷的數(shù)量有著顯著的影響:

benchmark                    old ns/op    new ns/op    delta
BenchmarkProtocolV2Data           3575         1963  -45.09%

benchmark                    old ns/op    new ns/op    delta
BenchmarkProtocolV2Sub256        57964        14568  -74.87%
BenchmarkProtocolV2Sub512        58212        16193  -72.18%
BenchmarkProtocolV2Sub1k         58549        19490  -66.71%
BenchmarkProtocolV2Sub2k         63430        27840  -56.11%

benchmark                   old allocs   new allocs    delta
BenchmarkProtocolV2Sub256           56           39  -30.36%
BenchmarkProtocolV2Sub512           56           39  -30.36%
BenchmarkProtocolV2Sub1k            56           39  -30.36%
BenchmarkProtocolV2Sub2k            58           42  -27.59%

HTTP

NSQ的HTTP API是基于 Go's net/http 包實(shí)現(xiàn)的. 就是 常見的HTTP應(yīng)用,在大多數(shù)高級(jí)編程語(yǔ)言中都能直接使用而無(wú)需額外的三方包。

簡(jiǎn)潔就是它最有力的武器,Go的 HTTP tool-chest最強(qiáng)大的就是其調(diào)試功能. net/http/pprof 包直接集成了 HTTP server,可以方便的訪問 CPU, heap, goroutine, and OS 進(jìn)程文檔 .gotool就能直接實(shí)現(xiàn)上述操作:

$ go tool pprof http://127.0.0.1:4151/debug/pprof/profile

這對(duì)于調(diào)試和 實(shí)時(shí) 監(jiān)控進(jìn)程非常有用!

此外,/stats端端返回JSON或是美觀的文本格式信息,這讓管理員使用命令行實(shí)時(shí)監(jiān)控非常容易 :

$ watch -n 0.5 'curl -s http://127.0.0.1:4151/stats | grep -v connected'

打印出的結(jié)果如下:

此外, Go 1.2 還有很多監(jiān)控指標(biāo) measurable HTTP performance gains . 每次更新Go版本后都能看到性能方面的改進(jìn),真是讓人振奮!

依賴關(guān)系

源于其它生態(tài)系統(tǒng),使用GO(理論匱乏)語(yǔ)言的依賴管理還得花點(diǎn)時(shí)間去適應(yīng)

NSQ 就并不是單一的整個(gè) repo庫(kù), 通過 _relative imports_ 而無(wú)需區(qū)別內(nèi)部的包資源, 最終產(chǎn)生結(jié)構(gòu)化的依賴管理。

主流的觀點(diǎn)有以下兩個(gè):

  • Vendoring :拷貝應(yīng)用需要的正確版本號(hào)到本地倉(cāng)庫(kù)并修改 import 路徑到本地庫(kù)地址

  • Virtual Env : 列出構(gòu)建是需要的版本信息,創(chuàng)建包含相關(guān)信息的GOPATH環(huán)境變量

Note: 這僅僅應(yīng)用于二級(jí)制包,對(duì)于可導(dǎo)入的包版本不起作用

NSQ使用 godep 提供 (2) 中的實(shí)現(xiàn).

它的實(shí)現(xiàn)原理是復(fù)制依賴關(guān)系到 Godeps 文件中, 之后生成GOPATH環(huán)境變量。構(gòu)建時(shí),它使用Go環(huán)境中的工具鏈 來(lái)完成工作。

它還支持go的get. 例如,構(gòu)建一個(gè) NSQ版本:

$ godep get github.com/bitly/nsq/...

測(cè)試

Go語(yǔ)言提供了內(nèi)置的測(cè)試和基線。由于其簡(jiǎn)單的并發(fā)操作建模,在測(cè)試環(huán)境里加入 nsqd 實(shí)例輕而易舉。

但是,在測(cè)試初始化的時(shí)候會(huì)有個(gè)問題:全局狀態(tài)。最明顯的就是引用運(yùn)行態(tài) nsqd 實(shí)例的全局變量 i.e.var nsqd *NSQd.

于是某些測(cè)試就無(wú)可避免的使用局部變量去保存該值i.e.nsqd := NewNSQd(...).這也就意味著全局狀態(tài)并未指向運(yùn)行態(tài)的值,使測(cè)試失去了意義。

應(yīng)對(duì)這個(gè)問題,Context結(jié)構(gòu)體被引入以保存配置項(xiàng)metadata和實(shí)時(shí) nsqd 的父類。所有全局狀態(tài)的子引用都通過訪問該Context來(lái)安全的獲取相應(yīng)值(主題,渠道,協(xié)議處理等等),這樣測(cè)試起來(lái)也更有保障。

可靠性

一個(gè)系統(tǒng),如果在面對(duì)變幻的網(wǎng)絡(luò)環(huán)境和不可預(yù)知的事件時(shí)不具備可靠性,將不會(huì)是一個(gè)表現(xiàn)良好的分布式生產(chǎn)環(huán)境。

NSQ的設(shè)計(jì)和實(shí)現(xiàn)方式,使它能容忍錯(cuò)誤并以一種始終如一的,可預(yù)期的和穩(wěn)定的方式來(lái)運(yùn)行。

它的首要的設(shè)計(jì)哲學(xué)是快速失敗,認(rèn)為錯(cuò)誤都是致命的,并提供一種方式來(lái)調(diào)試遇到的任何問題。

不過,為了能有所行動(dòng),你必須要能夠檢測(cè)異常環(huán)境...

心跳檢測(cè)和 超時(shí)

NSQ的TCP協(xié)議是需要推送的.在經(jīng)過建立連接,三次握手,客戶在 aRDYstate的 訂閱數(shù)被置為0.當(dāng)準(zhǔn)備接受消息時(shí),通過更新 RDYstate來(lái)控制將要接受的消息數(shù)目。 NSQ 客戶端libraries將在后臺(tái)持續(xù)管理這一環(huán)節(jié),最終形成相應(yīng)的消息流。

周期性的, nsqd 會(huì)發(fā)送心跳檢測(cè)連接狀態(tài).客戶端可以設(shè)置這個(gè)間隔時(shí)間但 nsqd 需要在發(fā)送下調(diào)指令前收到上條請(qǐng)求的回復(fù)。

應(yīng)用層面的心跳檢測(cè)和RDYstate組合能夠避免 head-of-line blocking ,它會(huì)是心跳檢測(cè)失效 (i.e.如果用戶等待處理消息前OS的緩存已滿,則心跳檢測(cè)失效).

為了確保進(jìn)程的正常工作,所有的網(wǎng)絡(luò)IO都會(huì)依據(jù)心跳檢測(cè)的間隔時(shí)間來(lái)設(shè)置邊界.這意味著你甚至可以斷開客戶端和 nsqd 的網(wǎng)絡(luò)連接,而不必?fù)?dān)心問題被發(fā)現(xiàn)并恰當(dāng)?shù)奶幚怼?

一旦發(fā)現(xiàn)致命錯(cuò)誤,客戶連接將被強(qiáng)關(guān)。發(fā)送中的消息超時(shí)并從新加入新的客戶端接受隊(duì)列。最后,錯(cuò)誤日志會(huì)被保存并增加內(nèi)部評(píng)價(jià)矩陣內(nèi)容。

管理Goroutines

啟用goroutines很簡(jiǎn)單,但后續(xù)工作卻不是那么容易弄好的。避免出現(xiàn)死鎖是一個(gè)挑戰(zhàn)。通常都是因?yàn)樵谂判蛏铣隽藛栴},goroutine可能在接到上游的消息前就收到了go-chan的退出信號(hào)。

為啥提到這個(gè)?簡(jiǎn)單,一個(gè)未正確處理的goroutine就是內(nèi)存泄露。更深入的分析, nsqd 進(jìn)程含有多個(gè)激活的goroutines。從內(nèi)部情況來(lái)看,消息的所有權(quán)是不停在變得。為了能正確的關(guān)掉goroutines,實(shí)時(shí)統(tǒng)計(jì)所有的進(jìn)程信息是非常重要的。雖沒有什么神奇的方法,但下面的幾點(diǎn)能讓工作簡(jiǎn)單一點(diǎn) ...

WaitGroups

sync 包提供了 sync.WaitGroup , 它可以計(jì)算出激活態(tài)的goroutines數(shù)(比提供退出的平均等待時(shí)間)

為了使代碼簡(jiǎn)潔 nsqd 使用如下wrapper:

type WaitGroupWrapper struct {
    sync.WaitGroup
}

func (w *WaitGroupWrapper) Wrap(cb func()) {
    w.Add(1)
    go func() {
        cb()
        w.Done()
    }()
}

// can be used as follows:
wg := WaitGroupWrapper{}
wg.Wrap(func() { n.idPump() })
// ...
wg.Wait()

退出信號(hào)

在含有多個(gè)子goroutines中觸發(fā)事件最簡(jiǎn)單的辦法就是用一個(gè)go-chan,并在完成后關(guān)閉。所有當(dāng)中暫停的動(dòng)作將被激活,這就無(wú)需再向每個(gè)goroutine發(fā)送相關(guān)的信號(hào)了

type WaitGroupWrapper struct {
    sync.WaitGroup
}

func (w *WaitGroupWrapper) Wrap(cb func()) {
    w.Add(1)
    go func() {
        cb()
        w.Done()
    }()
}

// can be used as follows:
wg := WaitGroupWrapper{}
wg.Wrap(func() { n.idPump() })
// ...
wg.Wait()

同步退出

想可靠的,無(wú)死鎖,所有路徑都保有信息的實(shí)現(xiàn)是很難的。下面是一些提示:

  • 理想情況下,在go-chan發(fā)送消息的goroutine也應(yīng)為關(guān)閉消息負(fù)責(zé) .

  • 如果消息需要保留,確保相關(guān)go-chans被清空(尤其是無(wú)緩沖的?。?,以保證發(fā)送者可以繼續(xù)進(jìn)程 .

  • 另外,如果消息不再是相關(guān)的,在單個(gè)go-chan上的進(jìn)程應(yīng)該轉(zhuǎn)換到包含推出信號(hào)的select上 (如上所述)以保證發(fā)送者可以繼續(xù)進(jìn)程 .

一般的順序應(yīng)該是:

  • 停止接受新的連接(停止監(jiān)聽)

  • 向goroutines發(fā)出退出信號(hào)(見上文)

  • 等待WaitGroup的goroutine中退出(見上文)

  • 恢復(fù)緩沖數(shù)據(jù)

  • 剩下的部分保存到磁盤

時(shí)間: 2014-03-01 08:43 來(lái)源: 開源中國(guó)社區(qū) 作者: zhangkai 責(zé)任編輯: zhangkai

相關(guān)主題

如果你感興趣

分享該文章

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    国产av熟女一区二区三区四区| 中文字幕在线区中文色 | 精品熟女少妇av免费久久野外| 两性色午夜天堂免费视频| 国产高清一区二区白浆| 一区二区三区精品人妻| 亚洲欧美日韩熟女第一页| 国产欧美日韩精品一区二| 国产一区麻豆水好多高潮| 日本午夜免费观看视频| 日韩特级黄片免费在线观看| 亚洲熟女熟妇乱色一区| 中文字幕亚洲精品乱码加勒比| 午夜久久久精品国产精品| 国内九一激情白浆发布| 亚洲av首页免费在线观看| 免费在线播放不卡视频| 中文字幕一区二区熟女| 亚洲一区二区精品免费| 日韩特级黄色大片在线观看| 欧美一区二区三区99| 国产小青蛙全集免费看| 亚洲精品偷拍一区二区三区| 尤物久久91欧美人禽亚洲| 欧美国产日韩在线综合| 日本在线视频播放91| 91天堂素人精品系列全集| 成人精品亚洲欧美日韩| 日韩欧美三级视频在线| 成人精品亚洲欧美日韩| 国产成人精品一区二区在线看| 精品视频一区二区三区不卡| 伊人色综合久久伊人婷婷| 日本女优一色一伦一区二区三区| 青青操视频在线播放免费| 日本人妻丰满熟妇久久| 欧美国产在线观看精品| 精品香蕉国产一区二区三区| 亚洲欧美日本成人在线| 熟女免费视频一区二区| 国产精品二区三区免费播放心 |