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

三年之久的 etcd3 數(shù)據(jù)不一致 bug 分析

存儲 存儲軟件
etcd 作為 Kubernetes 集群的元數(shù)據(jù)存儲,是被業(yè)界廣泛使用的強(qiáng)一致性 KV 存儲,但近日被挖掘出一個(gè)存在 3 年之久的數(shù)據(jù)不一致 bug——client 寫入后無法在異常節(jié)點(diǎn)讀取到數(shù)據(jù),即數(shù)據(jù)丟失。

 etcd 作為 Kubernetes 集群的元數(shù)據(jù)存儲,是被業(yè)界廣泛使用的強(qiáng)一致性 KV 存儲,但近日被挖掘出一個(gè)存在 3 年之久的數(shù)據(jù)不一致 bug——client 寫入后無法在異常節(jié)點(diǎn)讀取到數(shù)據(jù),即數(shù)據(jù)丟失。本文介紹了我們是如何從問題分析、大膽猜測、嚴(yán)謹(jǐn)驗(yàn)證、排除、工程化復(fù)現(xiàn),從 raft 到 boltdb,從源碼定制再到 chaos monkey,一步步定位并解決 etcd 數(shù)據(jù)不一致 bug 的詳細(xì)流程,并將解決方案提交給社區(qū),移植到 etcd 3.4/3.3 生產(chǎn)環(huán)境分支。希望通過本文,能夠揭開 etcd 的神秘面紗,讓大家對 etcd 的原理和問題定位有一個(gè)較為深入的了解。

[[323922]]

問題背景

 

詭異的 K8S 滾動(dòng)更新異常

筆者某天收到同事反饋,測試環(huán)境中 K8S 集群進(jìn)行滾動(dòng)更新發(fā)布時(shí)未生效。通過 kube-apiserver 查看發(fā)現(xiàn),對應(yīng)的 Deployment 版本已經(jīng)是最新版,但是這個(gè)最新版本的 Pod 并未創(chuàng)建出來。

針對該現(xiàn)象,我們最開始猜測可能是 kube-controller-manager 的 bug 導(dǎo)致,但是觀察 controller-manager 日志并未發(fā)現(xiàn)明顯異常。第一次調(diào)高 controller-manager 的日志等級并進(jìn)行重啟操作之后,似乎由于 controller-manager 并沒有 watch 到這個(gè)更新事件,我們?nèi)匀粵]有發(fā)現(xiàn)問題所在。此時(shí),觀察 kube-apiserver 日志,同樣也沒有出現(xiàn)明顯異常。

于是,再次調(diào)高日志等級并重啟 kube-apiserver,詭異的事情發(fā)生了,之前的 Deployment 正常滾動(dòng)更新了!

etcd 數(shù)據(jù)不一致 ?

由于從 kube-apiserver 的日志中同樣無法提取出能夠幫助解決問題的有用信息,起初我們只能猜測可能是 kube-apiserver 的緩存更新異常導(dǎo)致的。正當(dāng)我們要從這個(gè)切入點(diǎn)去解決問題時(shí),該同事反饋了一個(gè)更詭異的問題——自己新創(chuàng)建的 Pod,通過 kubectl查詢 Pod 列表,突然消失了!納尼?這是什么騷操作?經(jīng)過我們多次測試查詢發(fā)現(xiàn),通過 kubectl 來 list pod 列表,該 pod 有時(shí)候能查到,有時(shí)候查不到。那么問題來了,K8s api 的 list 操作是沒有緩存的,數(shù)據(jù)是 kube-apiserver 直接從 etcd 拉取返回給客戶端的,難道是 etcd 本身出了問題?

眾所周知,etcd 本身是一個(gè)強(qiáng)一致性的 KV 存儲,在寫操作成功的情況下,兩次讀請求不應(yīng)該讀取到不一樣的數(shù)據(jù)。懷著不信邪的態(tài)度,我們通過 etcdctl 直接查詢了 etcd 集群狀態(tài)和集群數(shù)據(jù),返回結(jié)果顯示 3 個(gè)節(jié)點(diǎn)狀態(tài)都正常,且 RaftIndex 一致,觀察 etcd 的日志也并未發(fā)現(xiàn)報(bào)錯(cuò)信息,唯一可疑的地方是 3 個(gè)節(jié)點(diǎn)的 dbsize 差別較大。接著,我們又將 client 訪問的 endpoint 指定為不同節(jié)點(diǎn)地址來查詢每個(gè)節(jié)點(diǎn)的 key 的數(shù)量,結(jié)果發(fā)現(xiàn) 3 個(gè)節(jié)點(diǎn)返回的 key 的數(shù)量不一致,甚至兩個(gè)不同節(jié)點(diǎn)上 Key 的數(shù)量差最大可達(dá)到幾千!而直接通過 etcdctl 查詢剛才創(chuàng)建的 Pod,發(fā)現(xiàn)訪問某些 endpoint 能夠查詢到該 pod,而訪問其他 endpoint 則查不到。至此,基本可以確定 etcd 集群的節(jié)點(diǎn)之間確實(shí)存在數(shù)據(jù)不一致現(xiàn)象。

問題分析和排查過程

 

遇事不決問Google

強(qiáng)一致性的存儲突然數(shù)據(jù)不一致了,這么嚴(yán)重的問題,想必日志里肯定會(huì)有所體現(xiàn)。然而,可能是 etcd 開發(fā)者擔(dān)心日志太多會(huì)影響性能的緣故,etcd 的日志打印的比較少,以至于我們排查了 etcd 各個(gè)節(jié)點(diǎn)的日志,也沒有發(fā)現(xiàn)有用的報(bào)錯(cuò)日志。甚至是在我們調(diào)高日志級別之后,仍沒有發(fā)現(xiàn)異常信息。

作為一個(gè)21世紀(jì)的程序員,遇到這種詭異且暫時(shí)沒頭緒的問題,第一反應(yīng)當(dāng)然是先 Google 一下啦,畢竟不會(huì) StackOverFlow 的程序員不是好運(yùn)維!Google 輸入“etcd data inconsistent” 搜索發(fā)現(xiàn),并不是只有我們遇到過該問題,之前也有其他人向 etcd 社區(qū)反饋過類似問題,只是苦于沒有提供穩(wěn)定的復(fù)現(xiàn)方式,最后都不了了之。如 issue

  • https://github.com/etcd-io/etcd/issues/9630
  • https://github.com/etcd-io/etcd/issues/10407
  • https://github.com/etcd-io/etcd/issues/10594
  • https://github.com/etcd-io/etcd/issues/11643

由于這個(gè)問題比較嚴(yán)重,會(huì)影響到數(shù)據(jù)的一致性,而我們生產(chǎn)環(huán)境中當(dāng)前使用了數(shù)百套 etcd 集群,為了避免出現(xiàn)類似問題,我們決定深入定位一番。

etcd 工作原理和術(shù)語簡介

在開始之前,為方便讀者理解,這里先簡單介紹下 etcd 的常用術(shù)語和基本讀寫原理。

術(shù)語表:

etcd 是一個(gè)強(qiáng)一致性的分布式 KV 存儲,所謂強(qiáng)一致性,簡單來說就是一個(gè)寫操作成功后,從任何一個(gè)節(jié)點(diǎn)讀出來的數(shù)據(jù)都是最新值,而不會(huì)出現(xiàn)寫數(shù)據(jù)成功后讀不出來或者讀到舊數(shù)據(jù)的情況。etcd 通過 raft 協(xié)議來實(shí)現(xiàn) leader 選舉、配置變更以及保證數(shù)據(jù)讀寫的一致性。下面簡單介紹下 etcd 的讀寫流程:

寫數(shù)據(jù)流程(以 leader 節(jié)點(diǎn)為例,見上圖):

 

  1. etcd 任一節(jié)點(diǎn)的 etcd server 模塊收到 Client 寫請求(如果是 follower 節(jié)點(diǎn),會(huì)先通過 Raft 模塊將請求轉(zhuǎn)發(fā)至 leader 節(jié)點(diǎn)處理)。
  2. etcd server 將請求封裝為 Raft 請求,然后提交給 Raft 模塊處理。
  3. leader 通過 Raft 協(xié)議與集群中 follower 節(jié)點(diǎn)進(jìn)行交互,將消息復(fù)制到follower 節(jié)點(diǎn),于此同時(shí),并行將日志持久化到 WAL。
  4. follower 節(jié)點(diǎn)對該請求進(jìn)行響應(yīng),回復(fù)自己是否同意該請求。
  5. 當(dāng)集群中超過半數(shù)節(jié)點(diǎn)((n/2)+1 members )同意接收這條日志數(shù)據(jù)時(shí),表示該請求可以被Commit,Raft 模塊通知 etcd server 該日志數(shù)據(jù)已經(jīng) Commit,可以進(jìn)行 Apply。
  6. 各個(gè)節(jié)點(diǎn)的 etcd server 的 applierV3 模塊異步進(jìn)行 Apply 操作,并通過 MVCC 模塊寫入后端存儲 BoltDB。
  7. 當(dāng) client 所連接的節(jié)點(diǎn)數(shù)據(jù) apply 成功后,會(huì)返回給客戶端 apply 的結(jié)果。

讀數(shù)據(jù)流程:

  • etcd 任一節(jié)點(diǎn)的 etcd server 模塊收到客戶端讀請求(Range 請求)
  • 判斷讀請求類型,如果是串行化讀(serializable)則直接進(jìn)入 Apply 流程
  • 如果是線性一致性讀(linearizable),則進(jìn)入 Raft 模塊
  • Raft 模塊向 leader 發(fā)出 ReadIndex 請求,獲取當(dāng)前集群已經(jīng)提交的最新數(shù)據(jù) Index
  • 等待本地 AppliedIndex 大于或等于 ReadIndex 獲取的 CommittedIndex 時(shí),進(jìn)入 Apply 流程
  • Apply 流程:通過 Key 名從 KV Index 模塊獲取 Key 最新的 Revision,再通過 Revision 從 BoltDB 獲取對應(yīng)的 Key 和 Value。

初步驗(yàn)證

通常集群正常運(yùn)行情況下,如果沒有外部變更的話,一般不會(huì)出現(xiàn)這么嚴(yán)重的問題。我們查詢故障 etcd 集群近幾天的發(fā)布記錄時(shí)發(fā)現(xiàn),故障前一天對該集群進(jìn)行的一次發(fā)布中,由于之前 dbsize 配置不合理,導(dǎo)致 db 被寫滿,集群無法寫入新的數(shù)據(jù),為此運(yùn)維人員更新了集群 dbsize 和 compaction 相關(guān)配置,并重啟了 etcd。重啟后,運(yùn)維同學(xué)繼續(xù)對 etcd 手動(dòng)執(zhí)行了 compact 和 defrag 操作,來壓縮 db 空間。通過上述場景,我們可以初步判斷出以下幾個(gè)可疑的觸發(fā)條件:

  1. dbsize 滿
  2. dbsize 和 compaction 配置更新
  3. compaction 操作和 defrag 操作
  4. 重啟 etcd

出了問題肯定要能夠復(fù)現(xiàn)才更有利于解決問題,正所謂能夠復(fù)現(xiàn)的 bug 就不叫 bug。復(fù)現(xiàn)問題之前,我們通過分析 etcd 社區(qū)之前的相關(guān) issue 發(fā)現(xiàn),觸發(fā)該 bug 的共同條件都包含執(zhí)行過 compaction 和 defrag 操作,同時(shí)重啟過 etcd 節(jié)點(diǎn)。因此,我們計(jì)劃首先嘗試同時(shí)模擬這幾個(gè)操作,觀察是否能夠在新的環(huán)境中復(fù)現(xiàn)。為此我們新建了一個(gè)集群,然后通過編寫腳本向集群中不停的寫入和刪除數(shù)據(jù),直到 dbsize 達(dá)到一定程度后,對節(jié)點(diǎn)依次進(jìn)行配置更新和重啟,并觸發(fā) compaction 和 defrag 操作。然而經(jīng)過多次嘗試,我們并沒有復(fù)現(xiàn)出類似于上述數(shù)據(jù)不一致的場景。

抽絲剝繭,初現(xiàn)端倪

緊接著,在之后的測試中無意發(fā)現(xiàn),client 指定不同的 endpoint 寫數(shù)據(jù),能夠查到數(shù)據(jù)的節(jié)點(diǎn)也不同。比如,endpoint 指定為 node1 進(jìn)行寫數(shù)據(jù),3個(gè)節(jié)點(diǎn)都可以查到數(shù)據(jù);endpoint 指定為 node2 進(jìn)行寫數(shù)據(jù),node2 和 node3 可以查到;endpoint 指定為 node3 寫數(shù)據(jù),只有 node3 自己能夠查到。具體情況如下表:

 

 


于是我們做出了初步的猜測,有以下幾種可能:

 

 

  1. 集群可能分裂了,leader 未將消息發(fā)送給 follower 節(jié)點(diǎn)。
  2. leader 給 follower 節(jié)點(diǎn)發(fā)送了消息,但是 log 異常,沒有對應(yīng)的 command 。
  3. leader 給 follower 節(jié)點(diǎn)發(fā)送了消息,有對應(yīng)的 command,但是 apply 異常,操作還未到 KV Index 和 boltdb 就異常了。
  4. leader 給 follower 節(jié)點(diǎn)發(fā)送了消息, 有對應(yīng)的 command,但是 apply 異常,KV Index 出現(xiàn)了問題。
  5. leader 給 follower 節(jié)點(diǎn)發(fā)送了消息, 有對應(yīng)的 command,但是 apply 異常,boltdb 出現(xiàn)了問題。

為了驗(yàn)證我們的猜測,我們進(jìn)行了一系列測試來縮小問題范圍:首先,我們通過 endpoint status 查看集群信息,發(fā)現(xiàn) 3 個(gè)節(jié)點(diǎn)的 clusterId,leader,raftTerm,raftIndex 信息均一致,而 dbSize 大小和 revision 信息不一致。clusterId 和 leader 一致,基本排除了集群分裂的猜測,而 raftTerm 和 raftIndex 一致,說明 leader 是有向 follower 同步消息的,也進(jìn)一步排除了第一個(gè)猜測,但是 WAL落盤有沒有異常還不確定。dbSize 和 revision 不一致則進(jìn)一步說明了 3 個(gè)節(jié)點(diǎn)數(shù)據(jù)已經(jīng)發(fā)生不一致了。

其次,由于 etcd 本身提供了一些 dump 工具,例如 etcd-dump-log 和 etcd-dump-db。我們可以像下圖一樣,使用 etcd-dump-log dump 出指定 WAL 文件的內(nèi)容,使用 etcd-dump-db dump 出 db 文件的數(shù)據(jù),方便對 WAL 和 db 數(shù)據(jù)進(jìn)行分析。

于是,我們向 node3 寫了一條便于區(qū)分的數(shù)據(jù),然后通過 etcd-dump-log 來分析 3 個(gè)節(jié)點(diǎn)的 WAL,按照剛才的測試,endpoint 指定為 node3 寫入的數(shù)據(jù),通過其他兩個(gè)節(jié)點(diǎn)應(yīng)該查不到的。但是我們發(fā)現(xiàn) 3 個(gè)節(jié)點(diǎn)都收到了 WAL log,也就是說 WAL 并沒有丟,因此排除了第二個(gè)猜測。

接下來我們 dump 了 db 的數(shù)據(jù)進(jìn)行分析,發(fā)現(xiàn) endpoint 指定為 node3 寫入的數(shù)據(jù),在其他兩個(gè)節(jié)點(diǎn)的 db 文件里找不到,也就是說數(shù)據(jù)確實(shí)沒有落到 db,而不是寫進(jìn)去了查不出來。

 

既然 WAL 里有而 db 里沒有,因此很大可能是 apply 流程異常了,數(shù)據(jù)可能在 apply 時(shí)被丟棄了。

由于現(xiàn)有日志無法提供更有效的信息,我們打算在 etcd 里新加一些日志來更好地幫助我們定位問題。etcd 在做 apply 操作時(shí),trace 日志會(huì)打印超過每個(gè)超過 100ms 的請求,我們首先把 100ms 這個(gè)閾值調(diào)低,調(diào)整到 1ns,這樣每個(gè) apply 的請求都能夠記錄下來,可以更好的幫助我們定位問題。

編譯好新版本之后,我們替換了其中一個(gè) etcd 節(jié)點(diǎn),然后向不同 node 發(fā)起寫請求測試。果然,我們發(fā)現(xiàn)了一條不同尋常的錯(cuò)誤日志:"error":"auth:revision in header is old",因此我們斷定問題很可能是因?yàn)?mdash;—發(fā)出這條錯(cuò)誤日志的節(jié)點(diǎn),對應(yīng)的 key 剛好沒有寫進(jìn)去。

搜索代碼后,我們發(fā)現(xiàn) etcd 在進(jìn)行 apply 操作時(shí),如果開啟了鑒權(quán),在鑒權(quán)時(shí)會(huì)判斷 raft 請求 header 中的 AuthRevision,如果請求中的 AuthRevision 小于當(dāng)前 node 的AuthRevision,則會(huì)認(rèn)為 AuthRevision 太老而導(dǎo)致 Apply 失敗。

  1. func (as *authStore) isOpPermitted(userName string, revision uint64, key, rangeEnd []byte, permTyp authpb.Permission_Type) error { 
  2.     // ... 
  3.   if revision < as.Revision() { 
  4.     return ErrAuthOldRevision 
  5.   } 
  6.   // ... 

這樣看來,很可能是不同節(jié)點(diǎn)之間的 AuthRevision 不一致了,AuthRevision 是 etcd 啟動(dòng)時(shí)直接從 db 讀取的,每次變更后也會(huì)及時(shí)的寫入 db,于是我們簡單修改了下 etcd-dump-db工具,將每個(gè)節(jié)點(diǎn) db 內(nèi)存儲的 AuthRevision 解碼出來對比了下,發(fā)現(xiàn) 3 個(gè)節(jié)點(diǎn)的 AuthRevision 確實(shí)不一致,node1 的 AuthRevision 最高,node3 的 AuthRevision 最低,這正好能夠解釋之前的現(xiàn)象,endpoint 指定為 node1 寫入的數(shù)據(jù),3 個(gè)節(jié)點(diǎn)都能查到,指定為 node3 寫入的數(shù)據(jù),只有 node3 能夠查到,因?yàn)?AuthRevision 較低的節(jié)點(diǎn)發(fā)起的 Raft 請求,會(huì)被 AuthRevision 較高的節(jié)點(diǎn)在 Apply 的過程中丟棄掉(如下表)。

 

源碼之前,了無秘密?

目前為止我們已經(jīng)可以明確,新寫入的數(shù)據(jù)通過訪問某些 endpoint 查不出來的原因是由于 AuthRevision 不一致。但是,數(shù)據(jù)最開始發(fā)生不一致問題是否是由 AuthRevision 造成,還暫時(shí)不能斷定。為什么這么說呢?因?yàn)?AuthRevision 很可能也是受害者,比如 AuthRevision 和數(shù)據(jù)的不一致都是由同一個(gè)原因?qū)е碌模徊贿^是 AuthRevision 的不一致放大了數(shù)據(jù)不一致的問題。但是,為更進(jìn)一步接近真相,我們先假設(shè) AuthRevision 就是導(dǎo)致數(shù)據(jù)不一致的罪魁禍?zhǔn)祝M(jìn)而找出導(dǎo)致 AuthRevision 不一致的真實(shí)原因。

原因到底如何去找呢?正所謂,源碼之前了無秘密,我們首先想到了分析代碼。于是,我們走讀了一遍 Auth 操作相關(guān)的代碼(如下),發(fā)現(xiàn)只有在進(jìn)行權(quán)限相關(guān)的寫操作(如增刪用戶/角色,為角色授權(quán)等操作)時(shí),AuthRevision 才會(huì)增加。AuthRevision 增加后,會(huì)和寫權(quán)限操作一起,寫入 backend 緩存,當(dāng)寫操作超過一定閾值(默認(rèn) 10000 條記錄)或者每隔100ms(默認(rèn)值),會(huì)執(zhí)行刷盤操作寫入 db。由于 AuthRevision 的持久化和創(chuàng)建用戶等操作的持久化放在一個(gè)事務(wù)內(nèi),因此基本不會(huì)存在創(chuàng)建用戶成功了,而 AuthRevision 沒有正常增加的情況。

  1. func (as *authStore) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) { 
  2.     // ... 
  3.     tx := as.be.BatchTx() 
  4.     tx.Lock() 
  5.     defer tx.Unlock()  // Unlock時(shí)滿足條件會(huì)觸發(fā)commit操作 
  6.     // ... 
  7.     putUser(tx, newUser) 
  8.     as.commitRevision(tx) 
  9.     return &pb.AuthUserAddResponse{}, nil 
  10. func (t *batchTxBuffered) Unlock() { 
  11.     if t.pending != 0 { 
  12.         t.backend.readTx.Lock() // blocks txReadBuffer for writing. 
  13.         t.buf.writeback(&t.backend.readTx.buf) 
  14.         t.backend.readTx.Unlock() 
  15.         if t.pending >= t.backend.batchLimit { 
  16.             t.commit(false
  17.         } 
  18.     } 
  19.     t.batchTx.Unlock() 

那么,既然 3 個(gè)節(jié)點(diǎn)的 AuthRevision 不一致,會(huì)不會(huì)是因?yàn)槟承┕?jié)點(diǎn)寫權(quán)限相關(guān)的操作丟失了,從而沒有寫入 db ?如果該猜測成立,3 個(gè)節(jié)點(diǎn)的 db 里 authUser 和 authRole 的 bucket 內(nèi)容應(yīng)該有所不同才對。于是為進(jìn)一步驗(yàn)證,我們繼續(xù)修改了下 etcd-dump-db 這個(gè)工具,加入了對比不同 db 文件 bucket 內(nèi)容的功能。遺憾的是,通過對比發(fā)現(xiàn),3 個(gè)節(jié)點(diǎn)之間的 authUser 和 authRole bucket 的內(nèi)容并沒有什么不同。

既然節(jié)點(diǎn)寫權(quán)限相關(guān)的操作沒有丟,那會(huì)不會(huì)是命令重復(fù)執(zhí)行了呢?查看異常時(shí)間段內(nèi)日志時(shí)發(fā)現(xiàn),其中包含了較多的 auth 操作;進(jìn)一步分別比對 3 個(gè)節(jié)點(diǎn)的 auth 操作相關(guān)的日志又發(fā)現(xiàn),部分節(jié)點(diǎn)日志較多,而部分節(jié)點(diǎn)日志較少,看起來像是存在命令重復(fù)執(zhí)行現(xiàn)象。由于日志壓縮,雖然暫時(shí)還不能確定是重復(fù)執(zhí)行還是操作丟失,但是這些信息能夠?yàn)槲覀兒罄m(xù)的排查帶來很大啟發(fā)。

我們繼續(xù)觀察發(fā)現(xiàn),不同節(jié)點(diǎn)之間的 AuthRevison雖有差異,但是差異較小,而且差異值在我們壓測期間沒有變過。既然不同節(jié)點(diǎn)之間的 AuthRevision 差異值沒有進(jìn)一步放大,那么通過新增的日志基本上也看不出什么問題,因?yàn)椴灰恢卢F(xiàn)象的出現(xiàn)很可能是在過去的某個(gè)時(shí)間點(diǎn)瞬時(shí)造成的。這就造成我們?nèi)绻胍l(fā)現(xiàn)問題根因,還是要能夠復(fù)現(xiàn) AuthRevison 不一致或者數(shù)據(jù)不一致問題才行,并且要能夠抓到復(fù)現(xiàn)瞬間的現(xiàn)場。

問題似乎又回到了原點(diǎn),但好在我們目前已經(jīng)排除了很多干擾信息,將目標(biāo)聚焦在了 auth 操作上。

混沌工程,成功復(fù)現(xiàn)

鑒于之前多次手動(dòng)模擬各種場景都沒能成功復(fù)現(xiàn),我們打算搞一套自動(dòng)化的壓測方案來復(fù)現(xiàn)這個(gè)問題,方案制定時(shí)主要考慮的點(diǎn)有以下幾個(gè):

  • 如何增大復(fù)現(xiàn)的概率?

根據(jù)之前的排查結(jié)果,很有可能是 auth 操作導(dǎo)致的數(shù)據(jù)不一致,因此我們實(shí)現(xiàn)了一個(gè) monkey 腳本,每隔一段時(shí)間,會(huì)向集群寫入隨機(jī)的用戶、角色,并向角色授權(quán),同時(shí)進(jìn)行寫數(shù)據(jù)操作,以及隨機(jī)的重啟集群中的節(jié)點(diǎn),詳細(xì)記錄每次一操作的時(shí)間點(diǎn)和執(zhí)行日志。

  • 怎樣保證在復(fù)現(xiàn)的情況下,能夠盡可能的定位到問題的根因?

根據(jù)之前的分析得出,問題根因大概率是 apply 過程中出了問題,于是我們在 apply 的流程里加入了詳細(xì)的日志,并打印了每次 apply 操作committedIndex、appliedIndex、consistentIndex 等信息。

  • 如果復(fù)現(xiàn)成功,如何能夠在第一時(shí)間發(fā)現(xiàn)?

由于日志量太大,只有第一時(shí)間發(fā)現(xiàn)問題,才能夠更精確的縮小問題范圍,才能更利于我們定位問題。于是我們實(shí)現(xiàn)了一個(gè)簡單的 metric-server,每隔一分鐘拉取各個(gè)節(jié)點(diǎn)的 key 數(shù)量,并進(jìn)行對比,將差異值暴露為 metric,通過 prometheus 進(jìn)行拉取,并用 grafana 進(jìn)行展示,一旦差異值超過一定閾值(寫入數(shù)據(jù)量大的情況下,就算并發(fā)統(tǒng)計(jì)各個(gè)節(jié)點(diǎn)的 key 數(shù)量,也可能會(huì)有少量的差異,所以這里有一個(gè)容忍誤差),則立刻通過統(tǒng)一告警平臺向我們推送告警,以便于及時(shí)發(fā)現(xiàn)。

方案搞好后,我們新建了一套 etcd 集群,部署了我們的壓測方案,打算進(jìn)行長期觀察。結(jié)果第二天中午,我們就收到了微信告警——集群中存在數(shù)據(jù)不一致現(xiàn)象。

于是,我們立刻登錄壓測機(jī)器進(jìn)行分析,首先停掉了壓測腳本,然后查看了集群中各個(gè)節(jié)點(diǎn)的 AuthRevision,發(fā)現(xiàn) 3 個(gè)節(jié)點(diǎn)的 AuthRevision 果然不一樣!根據(jù) grafana 監(jiān)控面板上的監(jiān)控?cái)?shù)據(jù),我們將數(shù)據(jù)不一致出現(xiàn)的時(shí)間范圍縮小到了 10 分鐘內(nèi),然后重點(diǎn)分析了下這 10 分鐘的日志,發(fā)現(xiàn)在某個(gè)節(jié)點(diǎn)重啟后,consistentIndex 的值比重啟前要小。然而 etcd 的所有 apply 操作,冪等性都依賴 consistentIndex 來保證,當(dāng)進(jìn)行 apply 操作時(shí),會(huì)判斷當(dāng)前要 apply 的 Entry 的 Index 是否大于 consistentIndex,如果 Index 大于 consistentIndex,則會(huì)將 consistentIndex 設(shè)為 Index,并允許該條記錄被 apply。否則,則認(rèn)為該請求被重復(fù)執(zhí)行了,不會(huì)進(jìn)行實(shí)際的 apply 操作。

  1. // applyEntryNormal apples an EntryNormal type raftpb request to the EtcdServer 
  2. func (s *EtcdServer) applyEntryNormal(e *raftpb.Entry) { 
  3.     shouldApplyV3 := false 
  4.     if e.Index > s.consistIndex.ConsistentIndex() { 
  5.         // set the consistent index of current executing entry 
  6.         s.consistIndex.setConsistentIndex(e.Index
  7.         shouldApplyV3 = true 
  8.     } 
  9.         // ... 
  10.         // do not re-apply applied entries. 
  11.     if !shouldApplyV3 { 
  12.         return 
  13.     } 
  14.         // ... 

也就是說,由于 consistentIndex 的減小,etcd 本身依賴它的冪等操作將變得不再冪等,導(dǎo)致權(quán)限相關(guān)的操作在 etcd 重啟后被重復(fù) apply 了,即一共apply 了兩次!

問題原理分析

為何 consistentIndex 會(huì)減小?走讀了 consistentIndex 相關(guān)代碼后,我們終于發(fā)現(xiàn)了問題的根因:consistentIndex 本身的持久化,依賴于 mvcc 的寫數(shù)據(jù)操作;通過 mvcc 寫入數(shù)據(jù)時(shí),會(huì)調(diào)用 saveIndex 來持久化 consistentIndex 到 backend,而 auth 相關(guān)的操作,是直接讀寫的 backend,并沒有經(jīng)過 mvcc。這就導(dǎo)致,如果做了權(quán)限相關(guān)的寫操作,并且之后沒有通過 mvcc 寫入數(shù)據(jù),那么這期間 consistentIndex 將不會(huì)被持久化,如果這時(shí)候重啟了 etcd,就會(huì)造成權(quán)限相關(guān)的寫操作被 apply 兩次,帶來的副作用可能會(huì)導(dǎo)致 AuthRevision 重復(fù)增加,從而直接造成不同節(jié)點(diǎn)的 AuthRevision 不一致,而 AuthRevision 不一致又會(huì)造成數(shù)據(jù)不一致。

  1. func putUser(lg *zap.Logger, tx backend.BatchTx, user *authpb.User) { 
  2.     b, err := user.Marshal() 
  3.     tx.UnsafePut(authUsersBucketName, user.Name, b) // 直接寫入Backend,未經(jīng)過MVCC,此時(shí)不會(huì)持久化consistentIndex 
  4. func (tw *storeTxnWrite) End() { 
  5.     // only update index if the txn modifies the mvcc state. 
  6.     if len(tw.changes) != 0 { 
  7.         tw.s.saveIndex(tw.tx)    // 當(dāng)通過MVCC寫數(shù)據(jù)時(shí),會(huì)觸發(fā)consistentIndex持久化 
  8.         tw.s.revMu.Lock() 
  9.         tw.s.currentRev++ 
  10.     } 
  11.     tw.tx.Unlock() 
  12.     if len(tw.changes) != 0 { 
  13.         tw.s.revMu.Unlock() 
  14.     } 
  15.     tw.s.mu.RUnlock() 

再回過頭來,為什么數(shù)據(jù)不一致了還可以讀出來,而且讀出來的數(shù)據(jù)還可能不一樣?etcd 不是使用了 raft 算法嗎,難道不能夠保證強(qiáng)一致性嗎?其實(shí)這和 etcd 本身的讀操作實(shí)現(xiàn)有關(guān)。上邊我們已經(jīng)講過,etcd 為了提升讀數(shù)據(jù)的性能,使用了 ReadIndex 操作來實(shí)現(xiàn)從當(dāng)前節(jié)點(diǎn)讀取數(shù)據(jù),而不是每次都從 leader 讀。而影響 ReadIndex 操作的,一個(gè)是 leader 節(jié)點(diǎn)的 CommittedIndex,一個(gè)是當(dāng)前節(jié)點(diǎn)的 AppliedIndex,etcd 在 apply 過程中,無論 apply 是否成功,都會(huì)更新 AppliedIndex,這就造成,雖然當(dāng)前節(jié)點(diǎn) apply 失敗了,但讀操作在判斷的時(shí)候,并不會(huì)感知到這個(gè)失敗,從而導(dǎo)致某些節(jié)點(diǎn)可能讀不出來數(shù)據(jù);而且 etcd 支持多版本并發(fā)控制,同一個(gè) key 可以存在多個(gè)版本的數(shù)據(jù),apply 失敗可能只是更新某個(gè)版本的數(shù)據(jù)失敗,這就造成不同節(jié)點(diǎn)之間最新的數(shù)據(jù)版本不一致,導(dǎo)致讀出不一樣的數(shù)據(jù)。

影響范圍

該問題從 2016 年引入,所有開啟鑒權(quán)的 etcd3 集群都會(huì)受到影響,在特定場景下,會(huì)導(dǎo)致 etcd 集群多個(gè)節(jié)點(diǎn)之間的數(shù)據(jù)不一致,并且 etcd 對外表現(xiàn)還可以正常讀寫,日志無明顯報(bào)錯(cuò)。

觸發(fā)條件

 

  1. 使用的為 etcd3 集群,并且開啟了鑒權(quán)。
  2. etcd 集群中節(jié)點(diǎn)發(fā)生重啟。
  3. 節(jié)點(diǎn)重啟之前,有 grant-permission 操作(或短時(shí)間內(nèi)對同一個(gè)權(quán)限操作連續(xù)多次增刪),且操作之后重啟之前無其他數(shù)據(jù)寫入。
  4. 通過非重啟節(jié)點(diǎn)向集群發(fā)起寫數(shù)據(jù)請求。

修復(fù)方案

 

了解了問題的根因,修復(fù)方案就比較明確了,我們只需要在 auth 操作調(diào)用 commitRevision 后,觸發(fā) consistentIndex 的持久化操作,就能夠保證 etcd 在重啟的時(shí)候 consistentIndex 的本身的正確性,從而保證 auth 操作的冪等性。具體的修復(fù)方式我們已經(jīng)向 etcd 社區(qū)提交了 PR #11652,目前這個(gè)特性已經(jīng) backport 到 3.4,3.3 等版本,將會(huì)在最近一個(gè) release 更新。

那么如果數(shù)據(jù)已經(jīng)不一致了怎么辦,有辦法恢復(fù)嗎?在 etcd 進(jìn)程沒有頻繁重啟的情況下,可以先找到 authRevision 最小的節(jié)點(diǎn),它的數(shù)據(jù)應(yīng)該是最全的,然后利用 etcd 的 move-leader 命令,將 leader 轉(zhuǎn)移到這個(gè)節(jié)點(diǎn),再依次將其他節(jié)點(diǎn)移出集群,備份并刪除數(shù)據(jù)目錄,然后將節(jié)點(diǎn)重新加進(jìn)來,此時(shí)它會(huì)從 leader 同步一份最新的數(shù)據(jù),這樣就可以使集群其他節(jié)點(diǎn)的數(shù)據(jù)都和 leader 保持一致,即最大可能地不丟數(shù)據(jù)。

升級建議

需要注意的是,升級有風(fēng)險(xiǎn),新版本雖然解決了這個(gè)問題,但由于升級過程中需要重啟 etcd,這個(gè)重啟過程仍可能觸發(fā)這個(gè) bug。因此升級修復(fù)版本前建議停止寫權(quán)限相關(guān)操作,并且手動(dòng)觸發(fā)一次寫數(shù)據(jù)操作之后再重啟節(jié)點(diǎn),避免因?yàn)樯壴斐蓡栴}。

 

另外,不建議直接跨大版本升級,例如從 etcd3.2 → etcd3.3。大版本升級有一定的風(fēng)險(xiǎn),需謹(jǐn)慎測試評估,我們之前發(fā)現(xiàn)過由 lease 和 auth 引起的另一個(gè)不一致問題,具體可見 issue #11689,以及相關(guān) PR #11691。

問題總結(jié)

導(dǎo)致該問題的直接原因,是由于 consistentIndex 在進(jìn)行權(quán)限相關(guān)操作時(shí)未持久化,從而導(dǎo)致 auth 相關(guān)的操作不冪等,造成了數(shù)據(jù)的不一致。

 

而造成 auth 模塊未持久化 consistentIndex 的一個(gè)因素,是因?yàn)?consistentIndex 目前是在 mvcc 模塊實(shí)現(xiàn)的,并未對外暴露持久化接口,只能通過間接的方式來調(diào)用,很容易漏掉。為了優(yōu)化這個(gè)問題,我們重構(gòu)了 consistentIndex 相關(guān)操作,將它獨(dú)立為一個(gè)單獨(dú)的模塊,其他依賴它的模塊可以直接調(diào)用,一定程度上可以避免將來再出現(xiàn)類似問題,具體見 PR #11699。

另一方面,etcd 的 apply 操作本身是個(gè)異步流程,而且失敗之后沒有打印任何錯(cuò)誤日志,很大程度上增加了排障的難度,因此我們后邊也向社區(qū)提交了相關(guān) PR #11670,來優(yōu)化 apply 失敗時(shí)的日志打印,便于定位問題。

造成問題定位困難的另一個(gè)原因,是 auth revision 目前沒有對外暴露 metric 或者 api,每次查詢只能通過 etcd-dump-db 工具來直接從 db 獲取,為方便 debug,我們也增加了相關(guān)的 metric 和 status api,增強(qiáng)了 auth revision 的可觀測性和可測試性。

相關(guān) PR/issue 查看地址

 

PR #11652 :https://github.com/etcd-io/etcd/pull/11652
issue #11689:https://github.com/etcd-io/etcd/issues/11689
PR #11691:https://github.com/etcd-io/etcd/pull/11691
PR #11699 :https://github.com/etcd-io/etcd/pull/11699
PR #11670 :https://github.com/etcd-io/etcd/pull/11670
metric :https://github.com/etcd-io/etcd/pull/11652/commits/f14d2a087f7b0fd6f7980b95b5e0b945109c95f3
status api :https://github.com/etcd-io/etcd/pull/11659

參考資料

 

etcd 源碼:https://github.com/etcd-io/etcd

etcd 存儲實(shí)現(xiàn):https://www.codedump.info/post/20181125-etcd-server/

高可用分布存儲 etcd 的實(shí)現(xiàn)原理:https://draveness.me/etcd-introduction/

 

etcd raft 設(shè)計(jì)與實(shí)現(xiàn):https://zhuanlan.zhihu.com/p/51063866,https://zhuanlan.zhihu.com/p/51065416

責(zé)任編輯:武曉燕 來源: 騰訊技術(shù)工程
相關(guān)推薦

2024-05-11 07:37:43

數(shù)據(jù)Redis策略

2017-06-20 09:42:52

網(wǎng)絡(luò)安全法數(shù)據(jù)隱私法網(wǎng)絡(luò)安全

2022-03-18 10:53:49

數(shù)據(jù)系統(tǒng)架構(gòu)

2024-04-07 09:00:00

MySQL

2018-07-15 08:18:44

緩存數(shù)據(jù)庫數(shù)據(jù)

2025-04-03 09:51:37

2021-04-18 15:01:56

緩存系統(tǒng)數(shù)據(jù)

2017-08-25 17:59:41

浮點(diǎn)運(yùn)算C語言

2020-07-20 14:06:38

數(shù)據(jù)庫主從同步服務(wù)

2018-07-08 07:38:28

數(shù)據(jù)庫緩存數(shù)據(jù)

2021-05-27 18:06:30

MySQL編碼數(shù)據(jù)

2010-06-02 10:53:28

MySQL版本

2021-01-19 10:39:03

Redis緩存數(shù)據(jù)

2024-11-18 08:00:00

數(shù)據(jù)倉庫通用語義層商業(yè)智能

2022-03-16 15:54:52

MySQL數(shù)據(jù)format

2023-05-10 09:42:39

代碼開源

2013-12-13 14:46:55

OSPFMTU鄰接關(guān)系

2023-12-22 10:19:19

數(shù)據(jù)庫鎖機(jī)制

2021-09-02 07:56:46

HDFSHIVE元數(shù)據(jù)

2025-04-08 09:00:00

數(shù)據(jù)庫緩存架構(gòu)
點(diǎn)贊
收藏

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

日韩精品一区二区三区四区视频 | 一本久道久久综合无码中文| 欧美日韩国产一区二区三区不卡| 欧美曰成人黄网| 91精品国产毛片武则天| 日韩大片b站免费观看直播| 日韩不卡免费视频| 欧美激情精品久久久久久久变态| 蜜桃传媒一区二区亚洲av| 欧美极品免费| 一区二区三区91| 日产国产精品精品a∨| 99精品免费观看| 日韩电影在线免费观看| 欧美激情乱人伦| 色www亚洲国产阿娇yao| 欧美人与动xxxxz0oz| 欧美福利一区二区| 久久精品一区二| segui88久久综合| 亚洲欧洲av另类| 欧美另类一区| 女人18毛片水真多18精品| 老司机免费视频一区二区| 午夜伦理精品一区| 欧美三根一起进三p| 色婷婷色综合| 亚洲精品自拍第一页| 中文字幕一二三| 欧美激情福利| 欧洲亚洲国产日韩| 青青草原av在线播放| 久久不射影院| 亚洲免费观看高清| 一区国产精品| 北岛玲日韩精品一区二区三区| 99久久伊人久久99| 国产麻豆乱码精品一区二区三区| 国产强伦人妻毛片| 久久精品国产在热久久| 国产精品天天狠天天看| 日韩精品久久久久久免费| 亚洲少妇在线| 97超碰蝌蚪网人人做人人爽| 国产中文字幕免费| 国产精品v亚洲精品v日韩精品| 亚洲午夜国产成人av电影男同| 无套内谢大学处破女www小说| 国产福利一区二区精品秒拍| 精品久久久久久最新网址| 国产伦精品一区二区三区妓女下载| 亚洲精品成a人ⅴ香蕉片| 精品视频999| 色婷婷狠狠18| 婷婷成人av| 欧美日韩精品电影| 日韩成人精品视频在线观看| 国产精品蜜月aⅴ在线| 欧美视频一区二区三区| 日韩高清第一页| 国产精品高清一区二区| 欧美一区二区三区四区久久| 色91精品久久久久久久久| 日韩三级精品| 亚洲大胆人体在线| 成人免费毛片日本片视频| 美日韩中文字幕| 中文字幕日本精品| 黄色香蕉视频在线观看| 精品免费一区二区| 久久视频免费在线播放| 国产亚洲精品av| 国产偷自视频区视频一区二区| 日韩av免费在线播放| 亚洲午夜精品久久久| 国产精品影视天天线| 国产一区福利视频| 国外av在线| 亚洲日本va午夜在线影院| 国产精品视频一二三四区| caoporn视频在线| 91久久免费观看| 青青青在线视频免费观看| 亚洲精品777| 亚洲国内精品视频| 国产传媒视频在线| 欧美日韩一区二区国产| 日本久久久久亚洲中字幕| 伊人精品在线视频| 国产69精品久久久久毛片| 欧美性大战久久久久| 国产二区三区在线| 欧美日韩美女在线观看| 亚洲欧美天堂在线| 日韩高清在线免费观看| 久久精品成人欧美大片古装| 国产成人啪精品午夜在线观看| 日韩国产欧美在线播放| 成人欧美一区二区三区视频| 黄网在线观看| 亚洲一区在线观看视频| 狠狠躁狠狠躁视频专区| 成人动态视频| 日韩中文字幕在线| 国产免费观看av| 国产乱国产乱300精品| 欧洲精品在线一区| 欧美大片黄色| 欧美高清视频一二三区| 美女洗澡无遮挡| 国产精品草草| 成人久久久久爱| 国产在线一在线二| 亚洲成a人v欧美综合天堂| 我要看一级黄色大片| 天美av一区二区三区久久| 久热精品视频在线免费观看| 国产成人精品一区二三区| 国产在线看一区| 日韩精品福利视频| 欧美裸体视频| 欧美va在线播放| 亚洲一级生活片| 七七婷婷婷婷精品国产| 欧美高清视频一区| 国内精彩免费自拍视频在线观看网址| 91精品国产综合久久久蜜臀图片| 国产又黄又粗视频| 噜噜噜久久亚洲精品国产品小说| 国产精品初高中精品久久| 成人黄色网址| 91精品视频网| 免费成人深夜夜行网站| 美女视频网站久久| 日韩福利影院| 日韩欧美2区| 亚洲天堂第一页| 日韩三级一区二区| 久久久国际精品| 99福利在线观看| 香蕉久久夜色精品国产更新时间| 午夜伦理精品一区| 亚洲av电影一区| 天天色图综合网| 成人免费毛片日本片视频| 亚洲影音一区| 久久婷婷国产综合尤物精品| 少妇视频在线观看| 精品一区二区三区电影| 国产又大又黄又粗| 国产夜色精品一区二区av| 成年人黄色片视频| 波多野结衣在线播放一区| 国产精品久久久久久久久久久久久久 | 天堂av一区二区| 国产精品原创视频| 最好看的2019年中文视频| 伊人久久国产精品| 亚洲男同性恋视频| 香蕉视频免费网站| 亚洲国产国产亚洲一二三| 精品久久sese| 美女18一级毛片一品久道久久综合| 精品在线欧美视频| 亚洲精品国产欧美在线观看| 国产精品成人午夜| 中文字幕欧美视频| 99热这里只有成人精品国产| 蜜桃视频日韩| 日日狠狠久久| 欧美交受高潮1| 肉丝一区二区| 欧美日韩国产一区二区三区地区| 人人澡人人澡人人看| 国产sm精品调教视频网站| 国内性生活视频| 成人av动漫在线观看| 91免费高清视频| av不卡高清| 在线观看久久久久久| 99久久精品日本一区二区免费| 亚洲精品福利视频网站| 给我看免费高清在线观看| 蜜臀av国产精品久久久久| 人人妻人人澡人人爽欧美一区双| 婷婷成人在线| 成人国产精品日本在线| 都市激情国产精品| 最近2019年日本中文免费字幕| 亚洲精品字幕在线观看| 91黄色在线观看| 久久免费在线观看视频| 国产人成一区二区三区影院| 欧美熟妇精品一区二区| 久久婷婷影院| 国产成a人亚洲精v品在线观看| 国内精品久久久久久久久电影网 | 欧美变态网站| 成人网在线免费观看| 神马久久午夜| 另类专区欧美制服同性| 欧美成人综合在线| 欧美mv日韩mv| 91超薄丝袜肉丝一区二区| 精品国产91久久久久久老师| 动漫性做爰视频| 日本一区二区视频在线观看| 色哟哟视频在线| 精品一区二区精品| 国产综合免费视频| 亚洲第一黄网| 成人午夜免费剧场| 日韩精品一区二区三区免费观影| 精品视频免费观看| 亚洲精品aⅴ| 91精品久久久久久久久中文字幕 | 国产一级二级三级精品| 精品国产不卡一区二区| 国产精品国产亚洲伊人久久| 国产激情在线播放| 久久久久久91| 18+激情视频在线| 日韩视频在线免费| 成av人电影在线观看| 亚洲精品自产拍| 日韩精品一二| 亚洲精品电影在线| 噜噜噜久久,亚洲精品国产品| 制服丝袜在线91| 91av久久久| 欧美日韩国产小视频| 最近中文字幕免费观看| 色偷偷一区二区三区| 中文字幕超碰在线| 欧美日韩精品在线| 日韩三级小视频| 午夜日韩在线电影| 国产在线视频二区| 亚洲第一福利视频在线| 日韩免费一二三区| 亚洲www啪成人一区二区麻豆| 久久久久噜噜噜亚洲熟女综合| 亚洲欧美电影院| 校园春色 亚洲| 亚洲精品国产无天堂网2021 | 久久久久久久久久久国产精品| 99这里只有久久精品视频| av电影在线播放| 99麻豆久久久国产精品免费| xxxx黄色片| 久久只精品国产| 国产伦理片在线观看| 国产欧美日韩在线| 国产一二三av| 亚洲视频狠狠干| 国产这里有精品| 亚洲高清免费在线| 九九九在线观看| 日本韩国一区二区| 亚洲综合五月天婷婷丁香| 欧美一区二区三区在线观看视频| 国产成人三级一区二区在线观看一| 日韩精品中文字幕在线一区| 日本美女一级视频| 亚洲欧美国产一区二区三区| 在线激情免费视频| 久青草国产97香蕉在线视频| 国产剧情av在线播放| 日韩av电影国产| 伊人久久大香伊蕉在人线观看热v 伊人久久大香线蕉综合影院首页 伊人久久大香 | 最新国产精品久久久| 特大黑人娇小亚洲女mp4| 在线播放精品| 国产福利影院在线观看| 国产伦精一区二区三区| 久久人妻少妇嫩草av无码专区 | 精品一区二区三区视频在线观看| 在线免费黄色小视频| 99精品视频免费在线观看| 女女互磨互喷水高潮les呻吟 | 亚洲欧洲av一区二区| 麻豆传媒视频在线观看| 午夜精品美女自拍福到在线| 成人免费av电影| 成人18视频| 精品国产91乱码一区二区三区四区 | 不卡一区二区三区视频| 亚洲欧美tv| 日本丰满少妇黄大片在线观看| 国产日韩亚洲欧美精品| 亚洲一级免费在线观看| 成人激情校园春色| 国产精品久久久视频| 欧美日韩国产一二三区| 亚洲图片欧美视频| 在线观看中文字幕码| 亚洲精品99999| 看女生喷水的网站在线观看| 欧美中文字幕在线观看| 精品一区二区三区视频在线播放| 欧美精品一区二区三区久久| 一区二区三区四区日韩| 99免费视频观看| 成人精品国产免费网站| 手机免费观看av| 色综合激情久久| 欧美一级淫片aaaaaa| 久久韩国免费视频| 精品视频在线一区二区在线| 国内视频一区| 欧美久久视频| 三区视频在线观看| 国产日韩欧美亚洲| 丰满少妇乱子伦精品看片| 日韩一级二级三级精品视频| 成年人在线看| 日韩av电影国产| 中国av一区| 亚洲熟妇无码一区二区三区导航| 国产精品一区在线观看乱码 | 国产精品稀缺呦系列在线| 蜜桃一区av| 国产精品久久久久久久乖乖| 国产美女娇喘av呻吟久久| 日韩av片在线免费观看| 欧美一a一片一级一片| 天堂在线一二区| 97超级碰碰人国产在线观看| 国产精品一线| 成人性免费视频| 成人精品电影在线观看| 国产中文字字幕乱码无限| 亚洲成人激情图| 欧洲在线视频| 亚洲综合中文字幕68页| 亚洲综合色站| www.污网站| 亚洲欧美电影一区二区| va视频在线观看| 欧美成人黑人xx视频免费观看| 99久久久成人国产精品| 精品日韩在线播放| 国产中文字幕精品| 五月婷婷一区二区| 欧美成人午夜电影| 超碰在线最新网址| 韩国成人一区| 亚洲一区二区免费看| 久久国产精品影院| 91福利视频久久久久| 色大18成网站www在线观看| 国产欧美亚洲视频| 欧美91精品| 国产一线在线观看| 欧美视频在线观看免费网址| 天堂中文在线视频| 国产精品99久久久久久久久| 三区四区不卡| 亚洲天堂一区二区在线观看| 亚洲最色的网站| 天天操天天干天天| 国产成人亚洲综合青青| 99久久婷婷| 色诱av手机版| 欧美日韩亚洲一区二| 成人在线视频成人| 91亚洲精品久久久久久久久久久久| 欧美喷水视频| 国产吞精囗交久久久| 欧美日韩一区高清| 青草av在线| 日产国产精品精品a∨| 国产精品综合一区二区| 四虎成人精品永久免费av| 亚洲香蕉成人av网站在线观看| 香蕉久久久久久| 免费在线观看视频a| 久久久777精品电影网影网| 国产又粗又猛又色又| 久久久久亚洲精品成人网小说| 精品在线91| 又黄又爽又色的视频| 黑人精品xxx一区| 麻豆网站在线免费观看| 国产麻豆一区二区三区在线观看| 日韩成人一区二区| 美女毛片在线观看| 亚洲日韩中文字幕| 日韩欧美中文字幕一区二区三区| 亚洲精品无码久久久久久| 亚洲色图丝袜美腿| 色视频在线观看免费| 亚洲综合色av| 久久午夜av| 久久精品人妻一区二区三区| 中文字幕亚洲欧美日韩2019| 欧美a大片欧美片| 欧美午夜精品理论片| 色婷婷综合久久久| 青春草免费在线视频|