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

          池化技術(shù)有多牛?來,告訴你阿里的Druid為啥如此牛逼!

          共 11112字,需瀏覽 23分鐘

           ·

          2021-03-31 23:00

          今天無聊帶你們撩一下Druid,從底層的分析Druid是如何管理數(shù)據(jù)庫連接?

          零、類圖&流程預覽

          本文會通過getConnection作為入口,探索在druid里,一個連接的生命周期。大體流程被劃分成了以下幾個主流程:

          主流程1:獲取連接流程

          首先從入口來看看它在獲取連接時做了哪些操作:

          上述為獲取連接時的流程圖,首先會調(diào)用init進行連接池的初始化,然后運行責任鏈上的每一個filter,最終執(zhí)行getConnectionDirect獲取真正的連接對象,如果開啟了testOnBorrow,則每次都會去測試連接是否可用(這也是官方不建議設置testOnBorrowtrue的原因,影響性能,這里的測試是指測試mysql服務端的長連接是否斷開,一般mysql服務端長連?;顣r間是8h,被使用一次則刷新一次使用時間,若一個連接距離上次被使用超過了保活時間,那么再次使用時將無法與mysql服務端通信)。

          如果testOnBorrow沒有被置為true,則會進行testWhileIdle的檢查(這一項官方建議設置為true,缺省值也是true),檢查時會判斷當前連接對象距離上次被使用的時間是否超過規(guī)定檢查的時間,若超過,則進行檢查一次,這個檢查時間通過timeBetweenEvictionRunsMillis來控制,默認60s。

          每個連接對象會記錄下上次被使用的時間,用當前時間減去上一次的使用時間得出閑置時間,閑置時間再跟timeBetweenEvictionRunsMillis比較,超過這個時間就做一次連接可用性檢查,這個相比testOnBorrow每次都檢查來說,性能會提升很多,用的時候無需關(guān)注該值,因為缺省值是true,經(jīng)測試如果將該值設置為false,testOnBorrow也設置為false,數(shù)據(jù)庫服務端長連?;顣r間改為60s,60s內(nèi)不使用連接,超過60s后使用將會報連接錯誤。

          若使用testConnectionInternal方法測試長連接結(jié)果為false,則證明該連接已被服務端斷開或者有其他的網(wǎng)絡原因?qū)е略撨B接不可用,則會觸發(fā)discardConnection進行連接回收(對應流程1.4,因為丟棄了一個連接,因此該方法會喚醒主流程3進行檢查是否需要新建連接)。整個流程運行在一個死循環(huán)內(nèi),直到取到可用連接或者超過重試上限報錯退出(在連接沒有超過連接池上限的話,最多重試一次(重試次數(shù)默認重試1次,可以通過notFullTimeoutRetryCount屬性來控制),所以取連接這里一旦發(fā)生等待,在連接池沒有滿的情況下,最大等待 2 × maxWait 的時間 ←這個有待驗證)。

          特別說明①

          為了保證性能,不建議將testOnBorrow設置為true,或者說牽扯到長連接可用檢測的那幾項配置使用druid默認的配置就可以保證性能是最好的,如上所說,默認長連接檢查是60s一次,所以不啟用testOnBorrow的情況下要想保證萬無一失,自己要確認下所連的那個mysql服務端的長連接?;顣r間(雖然默認是8h,但是dba可能給測試環(huán)境設置的時間遠小于這個時間,所以如果這個時間小于60s,就需要手動設置timeBetweenEvictionRunsMillis了,如果mysql服務端長連接時間是8h或者更長,則用默認值即可。

          特別說明②

          為了防止不必要的擴容,在mysql服務端長連接夠用的情況下,對于一些qps較高的服務、網(wǎng)關(guān)業(yè)務,建議把池子的最小閑置連接數(shù)minIdle和最大連接數(shù)maxActive設置成一樣的,且按照需要調(diào)大,且開啟keepAlive進行連接活性檢查(參考流程4.1),這樣就不會后期發(fā)生動態(tài)新建連接的情況(建連還是個比較重的操作,所以不如一開始就申請好所有需要的連接,個人意見,僅供參考),但是像管理后臺這種,長期qps非常低,但是有的時候需要用管理后臺做一些巨大的操作(比如導數(shù)據(jù)什么的)導致需要的連接暴增,且管理后臺不會特別要求性能,就適合將minIdle的值設置的比maxActive小,這樣不會造成不必要的連接浪費,也不會在需要暴增連接的時候無法動態(tài)擴增連接。

          主流程2:初始化連接池

          通過上面的流程圖可以看到,在獲取一個連接的時候首先會檢查連接池是否已經(jīng)初始化完畢(通過inited來控制,bool類型,未初始化為flase,初始化完畢為true,這個判斷過程在init方法內(nèi)完成),若沒有初始化,則調(diào)用init進行初始化(圖主流程1中的紫色部分),下面來看看init方法里又做了哪些操作:

          可以看到,實例化的時候會初始化全局的重入鎖lock,在初始化過程中包括后續(xù)的連接池操作都會利用該鎖保證線程安全,初始化連接池的時候首先會進行雙重檢查是否已經(jīng)初始化過,若沒有,則進行連接池的初始化,這時候還會通過SPI機制額外加載責任鏈上的filter。

          但是這類filter需要在類上加上@AutoLoad注解。然后初始化了三個數(shù)組,容積都為maxActive,首先connections就是用來存放池子里連接對象的,evictConnections用來存放每次檢查需要拋棄的連接(結(jié)合流程4.1理解),keepAliveConnections用于存放需要連接檢查的存活連接(同樣結(jié)合流程4.1理解),然后生成初始化數(shù)(initialSize)個連接,放進connections,然后生成兩個必須的守護線程,用來添加連接進池以及從池子里摘除不需要的連接,這倆過程較復雜,因此拆出來單說(主流程3和主流程4)。

          特別說明①

          從流程上看如果一開始實例化的時候不對連接池進行初始化(這個初始化是指對池子本身的初始化,并非單純的指druid對象屬性的初始化),那么在第一次調(diào)用getConnection時就會走上圖那么多邏輯,尤其是耗時較久的建立連接操作,被重復執(zhí)行了很多次,導致第一次getConnection時耗時過久,如果你的程序并發(fā)量很大,那么第一次獲取連接時就會因為初始化流程而發(fā)生排隊,所以建議在實例化連接池后對其進行預熱,通過調(diào)用init方法或者getConnection方法都可以。

          特別說明②

          在構(gòu)建全局重入鎖的時候,利用lock對象生成了倆Condition,對這倆Condition解釋如下:

          當連接池連接夠用時,利用empty阻塞添加連接的守護線程(主流程3),當連接池連接不夠用時,獲取連接的那個線程(這里記為業(yè)務線程A)就會阻塞在notEmpty上,且喚起阻塞在empty上的添加連接的守護線程,走完添加連接的流程,走完后會重新喚起阻塞在notEmpty上的業(yè)務線程A,業(yè)務線程A就會繼續(xù)嘗試獲取連接。

          三、流程1.1:責任鏈

          WARN:這塊東西結(jié)合源碼看更容易理解

          這里對應流程1里獲取連接時需要執(zhí)行的責任鏈,每個DruidAbstractDataSource里都包含filters屬性,filters是對Druid里Filters接口的實現(xiàn),里面有很多對應著連接池里的映射方法,比如例子中dataSource的getConnection方法在觸發(fā)的時候就會利用FilterChain把每個filter里的dataSource_getConnection給執(zhí)行一遍,這里也要說明下FilterChain,通過流程1.1可以看出來,datasource是利用FilterChain來觸發(fā)各個filter的執(zhí)行的,F(xiàn)ilterChain里也有一堆datasource里的映射方法,比如上圖里的dataSource_connect,這個方法會把datasource里的filters全部執(zhí)行一遍直到nextFilter取不到值,才會觸發(fā)dataSource.getConnectionDirect,這個結(jié)合代碼會比較容易理解。

          四、流程1.2:從池中獲取連接的流程

          通過getConnectionInternal方法從池子里獲取真正的連接對象,druid支持兩種方式新增連接,一種是通過開啟不同的守護線程通過await、signal通信實現(xiàn)(本文啟用的方式,也是默認的方式),另一種是直接通過線程池異步新增,這個方式通過在初始化druid時傳入asyncInit=true,再把一個線程池對象賦值給createScheduler,就成功啟用了這種模式,沒仔細研究這種方式,所以本文的流程圖和代碼塊都會規(guī)避這個模式。

          上面的流程很簡單,連接足夠時就直接poolingCount-1,數(shù)組取值,返回,activeCount+1,整體復雜度為O(1),關(guān)鍵還是看取不到連接時的做法,取不到連接時,druid會先喚起新增連接的守護線程新增連接,然后陷入等待狀態(tài),然后喚醒該等待的點有兩處,一個是用完了連接recycle(主流程5)進池子后觸發(fā),另外一個就是新增連接的守護線程成功新增了一個連接后觸發(fā),await被喚起后繼續(xù)加入鎖競爭,然后往下走如果發(fā)現(xiàn)池子里的連接數(shù)仍然是0(說明在喚醒后參與鎖競爭里剛被放進來的連接又被別的線程拿去了),則繼續(xù)下一次的await,這里采用的是awaitNanos方法,初始值是maxWait,然后下次被刷新后就是maxWait減去上次阻塞花費的實際時間,每次await的時間會逐步減少,直到歸零,整體時間是約等于maxWait的,但實際比maxActive要大,因為程序本身存在耗時以及被喚醒后又要參與鎖競爭導致也存在一定的耗時。

          如果最終都沒辦法拿到連接則返回null出去,緊接著觸發(fā)主流程1中的重試邏輯。

          druid如何防止在獲取不到連接時阻塞過多的業(yè)務線程?

          通過上面的流程圖和流程描述,如果非常極端的情況,池子里的連接完全不夠用時,會阻塞過多的業(yè)務線程,甚至會阻塞超過maxWait這么久,有沒有一種措施是可以在連接不夠用的時候控制阻塞線程的個數(shù),超過這個限制后直接報錯,而不是陷入等待呢?

          druid其實支持這種策略的,在maxWaitThreadCount屬性為默認值(-1)的情況下不啟用,如果maxWaitThreadCount配置大于0,表示啟用,這是druid做的一種丟棄措施,如果你不希望在池子里的連接完全不夠用導阻塞的業(yè)務線程過多,就可以考慮配置該項,這個屬性的意思是說在連接不夠用時最多讓多少個業(yè)務線程發(fā)生阻塞,流程1.2的圖里沒有體現(xiàn)這個開關(guān)的用途,可以在代碼里查看,每次在pollLast方法里陷入等待前會把屬性notEmptyWaitThreadCount進行累加,阻塞結(jié)束后會遞減,由此可見notEmptyWaitThreadCount就是表示當前等待可用連接時阻塞的業(yè)務線程的總個數(shù),而getConnectionInternal在每次調(diào)用pollLast前都會判斷這樣一段代碼:

          if (maxWaitThreadCount > 0 && notEmptyWaitThreadCount >= maxWaitThreadCount) {
                              connectErrorCountUpdater.incrementAndGet(this);
                              throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
                                      + lock.getQueueLength()); //直接拋異常,而不是陷入等待狀態(tài)阻塞業(yè)務線程
                          }

          可以看到,如果配置了maxWaitThreadCount所限制的等待線程個數(shù),那么會直接判斷當前陷入等待的業(yè)務線程是否超過了maxWaitThreadCount,一旦超過甚至不觸發(fā)pollLast的調(diào)用(防止新增等待線程),直接拋錯。

          一般情況下不需要啟用該項,一定要啟用建議考慮好maxWaitThreadCount的取值,一般來說發(fā)生大量等待說明代碼里存在不合理的地方:比如典型的連接池基本配置不合理,高qps的系統(tǒng)里maxActive配置過??;比如借出去的連接沒有及時close歸還;比如存在慢查詢或者慢事務導致連接借出時間過久。這些要比配置maxWaitThreadCount更值得優(yōu)先考慮,當然配置這個做一個極限保護也是沒問題的,只是要結(jié)合實際情況考慮好取值。

          五、流程1.3:連接可用性測試

          ①init-checker

          講這塊的東西之前,先來了解下如何初始化檢測連接用的checker,整個流程參考下圖:

          初始化checker發(fā)生在init階段(限于篇幅,沒有在主流程2(init階段)里體現(xiàn)出來,只需要記住初始化checker也是發(fā)生在init階段就好),druid支持多種數(shù)據(jù)庫的連接源,所以checker針對不同的驅(qū)動程序都做了適配,所以才看到圖中checker有不同的實現(xiàn),我們根據(jù)加載到的驅(qū)動類名匹配不同的數(shù)據(jù)庫checker,上圖匹配至mysql的checker,checker的初始化里做了一件事情,就是判斷驅(qū)動內(nèi)是否有ping方法(jdbc4開始支持,mysql-connector-java早在3.x的版本就有ping方法的實現(xiàn)了),如果有,則把usePingMethod置為true,用于后續(xù)啟用checker時做判斷用(下面會講,這里置為true,則通過反射的方式調(diào)用驅(qū)動程序的ping方法,如果為false,則觸發(fā)普通的SELECT 1查詢檢測,SELECT 1就是我們非常熟悉的那個東西啦,新建statement,然后執(zhí)行SELECT 1,然后再判斷連接是否可用)。

          ②testConnectionInternal

          然后回到本節(jié)探討的方法:流程1.3對應的testConnectionInternal

          這個方法會利用主流程2(init階段)里初始化好的checker對象(流程參考init-checker)里的isValidConnection方法,如果啟用ping,則該方法會利用invoke觸發(fā)驅(qū)動程序里的ping方法,如果不啟用ping,就采用SELECT 1方式(從init-checker里可以看出啟不啟用取決于加載到的驅(qū)動程序里是否存在相應的方法)。

          六、流程1.4:拋棄連接

          經(jīng)過流程1.3返回的測試結(jié)果,如果發(fā)現(xiàn)連接不可用,則直接觸發(fā)拋棄連接邏輯,這個過程非常簡單,如上圖所示,由流程1.2獲取到該連接時累加上去的activeCount,在本流程里會再次減一,表示被取出來的連接不可用,并不能active狀態(tài)。其次這里的close是拿著驅(qū)動那個連接對象進行close,正常情況下一個連接對象會被druid封裝成DruidPooledConnection對象,內(nèi)部持有的conn就是真正的驅(qū)動Connection對象,上圖中的關(guān)閉連接就是獲取的該對象進行close,如果使用包裝類DruidPooledConnection進行close,則代表回收連接對象(recycle,參考主流程5)。

          七、主流程3:添加連接的守護線程

          在主流程2(init初始化階段)時就開啟了該流程,該流程獨立運行,大部分時間處于等待狀態(tài),不會搶占cpu,但是當連接不夠用時,就會被喚起追加連接,成功創(chuàng)建連接后將會喚醒其他正在等待獲取可用連接的線程,比如:

          結(jié)合流程1.2來看,當連接不夠用時,會通過empty.signal喚醒該線程進行補充連接(阻塞在empty上的線程只有主流程3的單線程),然后通過notEmpty阻塞自己,當該線程補充連接成功后,又會對阻塞在notEmpty上的線程進行喚醒,讓其進入鎖競爭狀態(tài),簡單理解就是一個生產(chǎn)-消費模型。這里有一些細節(jié),比如池子里的連接使用中(activeCount)加上池子里剩余連接數(shù)(poolingCount)就是指當前一共生成了多少個連接,這個數(shù)不能比maxActive還大,如果比maxActive還大,則再次陷入等待。而在往池子里put連接時,則判斷poolingCount是否大于maxActive來決定最終是否入池。

          八、主流程4:拋棄連接的守護線程

          流程4.1:連接池瘦身,檢查連接是否可用以及丟棄多余連接

          整個過程如下:

          整個流程分成圖中主要的幾步,首先利用poolingCount減去minIdle計算出需要做丟棄檢查的連接對象區(qū)間,意味著這個區(qū)間的對象有被丟棄的可能,具體要不要放進丟棄隊列evictConnections,要判斷兩個屬性:

          minEvictableIdleTimeMillis:最小檢查間隙,缺省值30min,官方解釋:一個連接在池中最小生存的時間(結(jié)合檢查區(qū)間來看,閑置時間超過這個時間,才會被丟棄)。

          maxEvictableIdleTimeMillis:最大檢查間隙,缺省值7h,官方解釋:一個連接在池中最大生存的時間(無視檢查區(qū)間,只要閑置時間超過這個時間,就一定會被丟棄)。

          如果當前連接對象閑置時間超過minEvictableIdleTimeMillis且下標在evictCheck區(qū)間內(nèi),則加入丟棄隊列evictConnections,如果閑置時間超過maxEvictableIdleTimeMillis,則直接放入evictConnections(一般情況下會命中第一個判斷條件,除非一個連接不在檢查區(qū)間,且閑置時間超過maxEvictableIdleTimeMillis)。

          如果連接對象不在evictCheck區(qū)間內(nèi),且keepAlive屬性為true,則判斷該對象閑置時間是否超出keepAliveBetweenTimeMillis(缺省值60s),若超出,則意味著該連接需要進行連接可用性檢查,則將該對象放入keepAliveConnections隊列。

          兩個隊列賦值完成后,則池子會進行一次壓縮,沒有涉及到的連接對象會被壓縮到隊首。

          然后就是處理evictConnections和keepAliveConnections兩個隊列了,evictConnections里的對象會被close最后釋放掉,keepAliveConnections里面的對象將會其進行檢測(流程參考流程1.3的isValidConnection),碰到不可用的連接會調(diào)用discard(流程1.4)拋棄掉,可用的連接會再次被放進連接池。

          整個流程可以看出,連接閑置后,也并非一下子就減少到minIdle的,如果之前產(chǎn)生一堆的連接(不超過maxActive),突然閑置了下來,則至少需要花minEvictableIdleTimeMillis的時間才可以被移出連接池,如果一個連接閑置時間超過maxEvictableIdleTimeMillis則必定被回收,所以極端情況下(比如一個連接池從初始化后就沒有再被使用過),連接池里并不會一直保持minIdle個連接,而是一個都沒有,生產(chǎn)環(huán)境下這是非常不常見的,默認的maxEvictableIdleTimeMillis都有7h,除非是極度冷門的系統(tǒng)才會出現(xiàn)這種情況,而開啟keepAlive也不會推翻這個規(guī)則,keepAlive的優(yōu)先級是低于maxEvictableIdleTimeMillis的,keepAlive只是保證了那些檢查中不需要被移出連接池的連接在指定檢測時間內(nèi)去檢測其連接活性,從而決定是否放入池子或者直接discard。

          流程4.2:主動回收連接,防止內(nèi)存泄漏

          過程如下:

          這個流程在removeAbandoned設置為true的情況下才會觸發(fā),用于回收那些拿出去的使用長期未歸還(歸還:調(diào)用close方法觸發(fā)主流程5)的連接。

          先來看看activeConnections是什么,activeConnections用來保存當前從池子里被借出去的連接,這個可以通過主流程1看出來,每次調(diào)用getConnection時,如果開啟removeAbandoned,則會把連接對象放到activeConnections,然后如果長期不調(diào)用close,那么這個被借出去的連接將永遠無法被重新放回池子,這是一件很麻煩的事情,這將存在內(nèi)存泄漏的風險,因為不close,意味著池子會不斷產(chǎn)生新的連接放進connections,不符合連接池預期(連接池出發(fā)點是盡可能少的創(chuàng)建連接),然后之前被借出去的連接對象還有一直無法被回收的風險,存在內(nèi)存泄漏的風險,因此為了解決這個問題,就有了這個流程,流程整體很簡單,就是將現(xiàn)在借出去還沒有歸還的連接,做一次判斷,符合條件的將會被放進abandonedList進行連接回收(這個list里的連接對象里的abandoned將會被置為true,標記已被該流程處理過,防止主流程5再次處理)。

          這個如果在實踐中能保證每次都可以正常close,完全不用設置removeAbandoned=true,目前如果使用了類似mybatis、spring等開源框架,框架內(nèi)部是一定會close的,所以此項是不建議設置的,視情況而定。

          九、主流程5:回收連接

          這個流程通常是靠連接包裝類DruidPooledConnection的close方法觸發(fā)的,目標方法為recycle,流程圖如下:

          這也是非常重要的一個流程,連接用完要歸還,就是利用該流程完成歸還的動作,利用druid對外包裝的Connecion包裝類DruidPooledConnection的close方法觸發(fā),該方法會通過自己內(nèi)部的close或者syncClose方法來間接觸發(fā)dataSource對象的recycle方法,從而達到回收的目的。

          最終的recycle方法:

          ①如果removeAbandoned被設置為true,則通過traceEnable判斷是否需要從activeConnections移除該連接對象,防止流程4.2再次檢測到該連接對象,當然如果是流程4.2主動觸發(fā)的該流程,那么意味著流程4.2里已經(jīng)remove過該對象了,traceEnable會被置為false,本流程就不再觸發(fā)remove了(這個流程都是在removeAbandoned=true的情況下進行的,在主流程1里連接被放進activeConnections時traceEnable被置為true,而在removeAbandoned=false的情況下traceEnable恒等于false)。

          ②如果回收過程中發(fā)現(xiàn)存在有未處理完的事務,則觸發(fā)回滾(比較有可能觸發(fā)這一條的是流程4.2里強制歸還連接,也有可能是單純使用連接,開啟事務卻沒有提交事務就直接close的情況),然后利用holder.reset進行恢復連接對象里一些屬性的默認值,除此之外,holder對象還會把由它產(chǎn)生的statement對象放到自己的一個arraylist里面,reset方法會循環(huán)著關(guān)閉內(nèi)部未關(guān)閉的statement對象,最后清空list,當然,statement對象自己也會記錄下其產(chǎn)生的所有的resultSet對象,然后關(guān)閉statement時同樣也會循環(huán)關(guān)閉內(nèi)部未關(guān)閉的resultSet對象,這是連接池做的一種保護措施,防止用戶拿著連接對象做完一些操作沒有對打開的資源關(guān)閉。

          ③判斷是否開啟testOnReturn,這個跟testOnBorrow一樣,官方默認不開啟,也不建議開啟,影響性能,理由參考主流程1里針對testOnBorrow的解釋。

          ④直接放回池子(當前connections的尾部),然后需要注意的是putLast方法和put方法的不同之處,putLast會把lastActiveTimeMillis置為當前時間,也就是說不管一個連接被借出去過久,只要歸還了,最后活躍時間就是當前時間,這就會有造成某種特殊異常情況的發(fā)生(非常極端,幾乎不會觸發(fā),可以選擇不看):

          如果不開啟testOnBorrow和testOnReturn,并且keepAlive設置為false,那么長連接可用測試的間隔依據(jù)就是利用當前時間減去上次活躍時間(lastActiveTimeMillis)得出閑置時間,然后再利用閑置時間跟timeBetweenEvictionRunsMillis(默認60s)進行對比,超過才進行長連接可用測試。

          那么如果一個mysql服務端的長連接?;顣r間被人為調(diào)整為60s,然后timeBetweenEvictionRunsMillis被設置為59s,這個設置是非常合理的,保證了測試間隔小于長連接實際?;顣r間,然后如果這時一個連接被拿出去后一直過了61s才被close回收,該連接對象的lastActiveTimeMillis被刷為當前時間,如果在59s內(nèi)再次拿到該連接對象,就會繞過連接檢查直接報連接不可用的錯誤。

          十、結(jié)束

          到這里針對druid連接池的初始化以及其內(nèi)部一個連接從生產(chǎn)到消亡的整個流程就已經(jīng)整理完了,主要是列出其運行流程以及一些主要的監(jiān)控數(shù)據(jù)都是如何產(chǎn)生的,沒有涉及到的是一個sql的執(zhí)行,因為這個基本上就跟使用原生驅(qū)動程序差不多,只是druid又包裝了一層Statement等,用于完成一些自己的操作。


          Spring Native beta 版發(fā)布,  技術(shù)融合越來越快

          2021-03-22

          螞蟻一面:談談你對讀寫分離以及分庫分表的理解,回答出這些應該是不錯的

          2021-03-22

          深入Hotspot源碼與Linux內(nèi)核理解NIO與Epoll

          2021-03-19

          面試長知識了!Java 關(guān)鍵字 transient 竟然還能這么用

          2021-03-19

          Java編譯和反編譯那些事

          2021-03-15



          瀏覽 41
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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页 | 麻豆91麻豆国产传媒的特点 | 日本在线国产 | 日日夜夜草| 1级片黄页网站 |