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

          面試官問我:分布式事務(wù)是什么?

          共 8581字,需瀏覽 18分鐘

           ·

          2021-06-07 10:19



          事務(wù)

          事務(wù)其實大家應(yīng)該不陌生,尤其是對于程序員來說,如果你連事務(wù)都沒聽說過,沒關(guān)系,因為你遇到了聰明和才智于一體的我,事務(wù)其實就是為了處理多種混合操作,涉及到多方面業(yè)務(wù)的情景

          重點是事務(wù)應(yīng)用的場景就是為了解決多種事務(wù)必須要么同時完成,要么同時不能完成的場景,也就是做到真正意義上的"同生共死"

          嚴(yán)格意義上來說事務(wù)其實具有原子性、一致性、隔離性和持久性四種特性,也就是大家老生常談的ACID

          • 原子性(Atomicity),可以理解為一個事務(wù)內(nèi)的所有操作要么都執(zhí)行,要么都不執(zhí)行

          • 一致性(Consistency),可以理解為數(shù)據(jù)是滿足完整性約束的,也就是不會存在中間狀態(tài)的數(shù)據(jù),比如你賬上有400,我賬上有100,你給我打200塊,此時你賬上的錢應(yīng)該是200,我賬上的錢應(yīng)該是300,不會存在我賬上錢加了,你賬上錢沒扣的中間狀態(tài)

          • 隔離性(Isolation),指的是多個事務(wù)并發(fā)執(zhí)行的時候不會互相干擾,即一個事務(wù)內(nèi)部的數(shù)據(jù)對于其他事務(wù)來說是隔離的

          • 持久性(Durability),指的是一個事務(wù)完成了之后數(shù)據(jù)就被永遠(yuǎn)保存下來,之后的其他操作或故障都不會對事務(wù)的結(jié)果產(chǎn)生影響

          嚴(yán)格意義上來說事務(wù)其實具有原子性、一致性、隔離性和持久性四種特性,也就是大家老生常談的ACID

          其實在我們印象中,應(yīng)該對這個事務(wù)再熟悉不過了,大家都知道事務(wù)就是為了使得一些數(shù)據(jù)庫層面的更新操作要么全部成功,要么全部失敗。

          不知道大家學(xué)過Redis沒有,如果學(xué)過Redis的其實可能會有疑問,因為Redis的事務(wù)不能保證所有操作要么都執(zhí)行,要么都不執(zhí)行,但是也叫做事務(wù)。Redis其實在官網(wǎng)就已經(jīng)說明白了,官網(wǎng)中告訴大家事務(wù)中的某個命令失敗了,之后的命令還是會被處理,Redis不會停止執(zhí)行命令,也就是意味著不會回滾

          Redis解釋為什么不支持回滾

          他們給出的回的就是首先如果命令出錯那就是語法的錯誤,是屬于個人的編程錯誤,而且這種情況應(yīng)該被檢測出來,而不是在生產(chǎn)環(huán)境出現(xiàn),于是乎Redis為了速度更快不支持回滾操作

          感覺很有道理的樣子,但是又有點不對勁

          好了,這下大家都知道事務(wù)是啥了,那么我們一起來看看分布式事務(wù)吧

           


          分布式事務(wù)

          剛才說的事務(wù)都是屬于單體程序中,單機中這樣是沒問題的,通過普通的事務(wù)操作就可以來解決;當(dāng)我們的系統(tǒng)逐漸變大,日益變強的同時,并發(fā)量和系統(tǒng)都隨之而增加,當(dāng)涉及到多個系統(tǒng)之間的配合來完成一個事務(wù)的時候,這就比較難辦了,因為無法直接通過一個系統(tǒng)的數(shù)據(jù)庫來完成

          假設(shè)現(xiàn)在有訂單系統(tǒng)、扣款系統(tǒng)、積分系統(tǒng),這是屬于三個系統(tǒng),也就是分別在不同的數(shù)據(jù)庫中,但是我需要保證三個系統(tǒng)中的服務(wù)要么全部成功、要么全部失敗,其實像這種設(shè)計到多個庫、多個系統(tǒng)之間的事務(wù)操作,也就是分布式事務(wù)了

          分布式事務(wù)其實說簡單也簡單,其實就是有多個本地事務(wù)組合而成,對于分布式事務(wù)而言幾乎滿足不了ACID,其實對于單機事務(wù)大多是情況下也是無法全部滿足ACID的,否則哪里來的四種隔離級別?所以更別說分布在不同數(shù)據(jù)庫、不同系統(tǒng)之間的分布式事務(wù)了

          分布式事務(wù)大致可以分為六種,但是其實這六種又可以按照三種思想來分類,接下來一起看看吧

          2PC和3PC是一種強一致性事務(wù),不過還是有數(shù)據(jù)的不一致、阻塞等風(fēng)險,而且只能應(yīng)用在數(shù)據(jù)庫層面;而TCC是一種補償性事務(wù)的思想,適用的范圍應(yīng)該是比較廣,不過這種補償性機制一般對業(yè)務(wù)的侵入性比較大,每一個操作都需要實現(xiàn)對應(yīng)的三種方法;還有一種思想就是努力實現(xiàn)最終一致性事務(wù),有本地消息、事務(wù)消息、和最大努力通知這三種方法,都是實現(xiàn)最終一致性事務(wù),因此適用于于一些對于時間不敏感的業(yè)務(wù)

          大致了解了這三類,接下來來細(xì)細(xì)學(xué)習(xí)每一種吧

          2PC二階段提交:準(zhǔn)備階段、提交階段

          2PC,又叫做二階段提交,二階段指的是準(zhǔn)備階段和提交兩個階段

          二階段提交屬于一種強一致性的設(shè)計,2PC引入一個事務(wù)協(xié)調(diào)者的角色來協(xié)調(diào)管理各參與者的提交和回滾機制,我們來看下具體流程

          準(zhǔn)備階段協(xié)調(diào)者會向各個參與者發(fā)送準(zhǔn)備的命令,這個準(zhǔn)備其實就是準(zhǔn)備環(huán)境,可以理解成提交之前的準(zhǔn)備工作

          同步的等待所有的資源的響應(yīng)之后,就到了萬事俱備,只欠提交的狀態(tài)了

          提交階段,提交階段并不一定是提交事務(wù),也有可能是回滾事務(wù),如果第一階段都準(zhǔn)備成功,則第二階段的提交就是提交事務(wù);同理如果第一階段未全部準(zhǔn)備成功,則第二階段提交的就是回滾事務(wù)了。假設(shè)第一階段都準(zhǔn)備成功,則協(xié)調(diào)者向所有參與者發(fā)送提交命令,然后接下來等待所有參與者都成功之后,返回事務(wù)執(zhí)行成功

          假設(shè)第一階段有部分參與者返回失敗的話,那么協(xié)調(diào)者則會向所有參與者都發(fā)送回滾事務(wù)的請求,即類似上圖,向全部參與者發(fā)送回滾事務(wù)

          說到這里其實有些小伙伴已經(jīng)開始有疑問了,我知道了第一階段有失敗的如何處理了,但是如果第二階段出現(xiàn)失敗了咋整呢

          其實這里分了兩種情況,分別是第二階段執(zhí)行的是提交階段、第二階段執(zhí)行的是回滾操作,這兩種情況的處理方式其實是一樣的,都是屬于不斷地重試,直到重試成功;對于提交來說,可以根據(jù)業(yè)務(wù)場景,執(zhí)行一定次數(shù)的重試之后,嘗試回滾;但是對于回滾操作,總不能執(zhí)行成功操作吧

          所以,如果第二階段是回滾操作有失敗,當(dāng)失敗次數(shù)達(dá)到一定次數(shù)的時候,最好的方法就是人工介入了

          提交流程大致也分析的差不多了,接下來一起看看細(xì)節(jié)部分,2PC可以看成同步阻塞協(xié)議,同步阻塞的等待所有參與者的第一階段都有響應(yīng)之后,才會進(jìn)行第二階段的操作;對于Java基礎(chǔ)很熟悉的小伙伴是不是很快想起來Java并發(fā)包中的一個工具類CountDownLatch,以及功能類似的CyclicBarrier,忘記的趕緊回憶下

          其實2PC中對于這里的同步阻塞是有超時機制的,協(xié)調(diào)者等待參與者的響應(yīng)超時的情況下,會默認(rèn)失敗,然后協(xié)調(diào)者直接向所有參與者發(fā)起回滾的命令,知道這次事務(wù)失敗

          上面這些都是基于參與者的角度來考慮的,那如果協(xié)調(diào)者出問題了呢

          協(xié)調(diào)者如果是單點的,出現(xiàn)故障之后,可能會出現(xiàn)一些系統(tǒng)的問題,我們從流程的角度分析下:

          準(zhǔn)備階段命令未發(fā)出,協(xié)調(diào)者故障,事務(wù)還沒開始,問題不大;

          準(zhǔn)備階段命令發(fā)出了,協(xié)調(diào)者故障,事務(wù)開始了,無論參與者都是成功還是失敗,最終情況都很糟糕,因為參與者無法等到下一步的指令了,也就是卡碟了,不僅事務(wù)無法執(zhí)行,還會鎖定一些公用資源而阻塞其它系統(tǒng);準(zhǔn)備階段命令發(fā)出,全部成功,第二階段執(zhí)行提交階段命令發(fā)出,這種情況也是不行的,因為也可能因為分區(qū)和網(wǎng)絡(luò)阻塞,某些參與者未收到提交命令,理想情況下如果參與者一次性全部收到提交命令,但是參與者有可能提交失敗,這樣還是需要重試,此時協(xié)調(diào)者掛了,也是不行

          準(zhǔn)備階段命令發(fā)出,部分失敗,第二階段回滾命令發(fā)出,其實和上面情況類似,也是會出現(xiàn)各式各樣的問題

          既然單點協(xié)調(diào)者不行,那就來個多個的吧,通過選舉機制再選一個新協(xié)調(diào)者

          如果都處于第一階段,其實都還好,事務(wù)還沒提交,直接都會滾就好了;如果處于第二階段,假設(shè)參與者都沒掛,此時新協(xié)調(diào)者可以向所有參與者來進(jìn)一步確認(rèn)他們自身的情況來推斷下一步該如何操作,如果個別參與者掛了,就比較尷尬了。比如協(xié)調(diào)者發(fā)送了回滾的命令,此時第一個參與者收到了并執(zhí)行了,然后協(xié)調(diào)者和第一個參與者都掛掉了,此時其它參與者都沒收到請求,然后新協(xié)調(diào)者來了,它詢問了其它的參與者都回答OK,但是它不知道其中第一個參與者掛了,此時要是按照全部OK來處理,直接發(fā)送提交命令,就糟糕了,這不是我們想要的結(jié)果

          其實雖然2PC協(xié)議上沒說,但是在實現(xiàn)的時候我們需要靈活的讓協(xié)調(diào)者將自己發(fā)過的請求在哪些地方都記一下,也就類似于日志記錄,這樣新的協(xié)調(diào)者來的時候就不、知道此時該不該發(fā)了

          即使協(xié)調(diào)者知道自己應(yīng)該發(fā)提交還是回滾請求,但是在參與者也一起掛了的情況下也是沒用的,因為協(xié)調(diào)者無法知道參與者在掛之前有沒有提交事務(wù),其實這里最靠譜的方法,就是對每一步都進(jìn)行相應(yīng)的日志記錄,重要的步驟最好還是強綁定日志記錄的,否則操作成功了,日志記錄失敗那也很糟糕,總之就是要考慮各種極端的情況,盡最大努力去做到每個細(xì)節(jié)都考慮到

          2PC是一種盡量保證強一致性的分布式事務(wù),因為它是同步阻塞的,而同步阻塞就意味著在某些情況下會出現(xiàn)鎖定資源的情況,而且單點一旦出現(xiàn)故障,就會造成資源鎖定的情況

          以下代碼取自 <<Distributed System: Principles and Paradigms>>

          協(xié)調(diào)者:


          write START_2PC to local log; //開始事務(wù)
          multicast VOTE_REQUEST to all participants; //廣播通知參與者投票
          while not all votes have been collected {
          wait for any incoming vote;
          if timeout { //協(xié)調(diào)者超時
          write GLOBAL_ABORT to local log; //寫日志
          multicast GLOBAL_ABORT to all participants; //通知事務(wù)中斷
          exit;
          }
          record vote;
          }
          //如果所有參與者都o(jì)k
          if all participants sent VOTE_COMMIT and coordinator votes COMMIT {
          write GLOBAL_COMMIT to local log;
          multicast GLOBAL_COMMIT to all participants;
          } else {
          write GLOBAL_ABORT to local log;
          multicast GLOBAL_ABORT to all participants;

          }

          參與者:

          write INIT to local log; //寫日志
          wait for VOTE_REQUEST from coordinator;
          if timeout { //等待超時
          write VOTE_ABORT to local log;
          exit;
          }
          if participant votes COMMIT {
          write VOTE_COMMIT to local log; //記錄自己的決策
          send VOTE_COMMIT to coordinator;
          wait for DECISION from coordinator;
          if timeout {
          multicast DECISION_REQUEST to other participants; //超時通知
          wait until DECISION is received; /* remain blocked*/
          write DECISION to local log;
          }
          if DECISION == GLOBAL_COMMIT
          write GLOBAL_COMMIT to local log;
          else if DECISION == GLOBAL_ABORT
          write GLOBAL_ABORT to local log;
          } else {
          write VOTE_ABORT to local log;
          send VOTE_ABORT to coordinator;
          }
          每個參與者維護(hù)一個線程處理其它參與者的DECISION_REQUEST請求:

          while true {
          wait until any incoming DECISION_REQUEST is received;
          read most recently recorded STATE from the local log;
          if STATE == GLOBAL_COMMIT
          send GLOBAL_COMMIT to requesting participant;
          else if STATE == INIT or STATE == GLOBAL_ABORT;
          send GLOBAL_ABORT to requesting participant;
          else
          skip; /* participant remains blocked */
          }


          3PC三階段提交:準(zhǔn)備階段、預(yù)提交階段、提交階段

          3PC其實就是2PC的升級版,相比于2PC,參與者也引入了超時機制,并且還新增了一個階段使得參與者可以利用這一階段來統(tǒng)一各自的狀態(tài)

          3PC分為三個階段:準(zhǔn)備階段、預(yù)提交階段、提交階段??雌饋砀袷前?PC中的提交階段分為了預(yù)提交和提交的兩個階段, 但是這里的準(zhǔn)備階段其實就是詢問參與者的自身狀況,就是問你現(xiàn)在的狀況如何,負(fù)載是不是超載,還可以再接受新的任務(wù)嗎

          而預(yù)提交階段其實就是類似于2PC的準(zhǔn)備階段,就是除了事務(wù)的提交該做的都做了,就是之前的準(zhǔn)備工作,但是在3PC中叫做預(yù)提交階段

          3PC是首先準(zhǔn)備階段并不會直接執(zhí)行事務(wù),而是先去詢問此時的參與者是否有條件可以執(zhí)行這個事務(wù),因此不會直接鎖住資源,而預(yù)提交階段的引入則是為了起到了一個統(tǒng)狀態(tài)的作用,在預(yù)處理階段表面所有參與者都已經(jīng)回應(yīng)了

          其實這也多引入了一個階段,因此性能會差一些,而且絕大部分的情況下資源也都是沒問題的,也就是可用的,這樣等于每次明知可用但是還是得詢問一次

          當(dāng)然,這其中哪一個階段的參與者返回失敗都會宣布事務(wù)失敗,這個2PC也是一樣的,當(dāng)然到最后的提交階段和2PC一樣都是只要是提交請求也就只能通過不斷的重試咯

          我們上面說過2PC是同步阻塞的,協(xié)調(diào)者掛在了提交請求還未發(fā)出去的時候是最尷尬的,所有參與者都已經(jīng)鎖定了資源并且阻塞的等待著,于是引入了超時機制,參與者則不用直接干干的等著了,如果是等待提交命令超時,那么參與者就會提交事務(wù)了,因為到了這一階段大概率都是提交的,如果是等待預(yù)提交超時,接下來也沒啥影響

          這里其實有一個問題,然后超時機制會帶來數(shù)據(jù)不一致的問題,就是在等待提交命令的時候超時,那么參與者自動提交事務(wù)了,但是呢,也可能執(zhí)行的是回滾機制,這樣一來數(shù)據(jù)便出現(xiàn)了不一致了

          3PC的引入是為了解決提交階段2PC協(xié)調(diào)者和其中的部分參與者都掛了的情況下,然后之后的新選舉的協(xié)調(diào)者不知道當(dāng)前應(yīng)該是該提交還是回滾的問題,新協(xié)調(diào)者來的時候發(fā)現(xiàn)有一個參與者處于預(yù)提交或者提交階段,那么表明所以參與者都已經(jīng)經(jīng)過確認(rèn)了,所以此時執(zhí)行的就是提交命令了

          3PC就是通過引入預(yù)提交階段來是的參與者之間的狀態(tài)得到真正的統(tǒng)一,也就是留了一個階段讓大家都同步,但是這也是只能讓協(xié)調(diào)者知道如何做,并不能保證這樣做一定是對的,這其實和上面的2PC的分析一直,因為掛了的參與者到底有沒有執(zhí)行事務(wù)是無法斷定的,所以說呢,3PC通過預(yù)提交階段可以減少故障時候的復(fù)雜性,但是并不能保證數(shù)據(jù)真正的一致,處理掛了的那個參與者也恢復(fù)了

          一句話總結(jié):3PC相比于2PC做了一定的參與者超時機制的改進(jìn),并且增加了預(yù)提交階段,可以使故障恢復(fù)之后的協(xié)調(diào)者的決策復(fù)雜度降低,但整體的交互過程會變得更長,性能會有所下降,而且還會出現(xiàn)數(shù)據(jù)不一致的情況

          TCC:Try-Confirm-Cancel

          TCC屬于業(yè)務(wù)層面的分布式事務(wù),分布式事務(wù)不僅僅包含數(shù)據(jù)庫層面的操作,還包括業(yè)務(wù)層面的操作,這時候TCC就要排上用場了

          TCC指的就是Try、Confirm、Cancel三個步驟,Try指的是預(yù)留,指的是資源的預(yù)留和鎖定;Confirm指的就是確認(rèn)操作,這一步其實就是屬于真正的執(zhí)行了,真正的消耗資源來進(jìn)行相應(yīng)的業(yè)務(wù)提交操作;Cancel指的是撤銷操作,可以理解為把預(yù)留階段的動作銷毀了,就是一個回滾操作

          從思想上來看,其實是和2PC、3PC是類似的,都是先試探性的執(zhí)行,先試探性的鎖定資源,如果每一個參與者都沒問題了,就可以執(zhí)行真正的操作了,提交或者回滾


          舉個例子:一個事務(wù)要執(zhí)行A、B、C三個操作,那么先對三個操作執(zhí)行預(yù)留動作,如果所有都預(yù)留成功了那么就執(zhí)行確認(rèn)提交操作,如果其中至少有一個預(yù)留失敗,那就都執(zhí)行撤銷的動作

          TCC模型其中還有一個事務(wù)管理者的角色,用來記錄TCC有關(guān)的全局事務(wù)操作的狀態(tài),并且準(zhǔn)備提交或者回滾事務(wù),其實這個是比較容易理解的,難點在于業(yè)務(wù)上的定義

          怎么說呢,TCC這種是對業(yè)務(wù)的侵入較大和業(yè)務(wù)緊耦合,需要根據(jù)相應(yīng)的特定的業(yè)務(wù)場景和業(yè)務(wù)邏輯來設(shè)定的響應(yīng)操作,其實還有一點需要注意的是,撤銷和確認(rèn)的操作的執(zhí)行的就是需要重試,就是需要保證操作的冪等性

          TCC相對來說,適用的范圍應(yīng)該是更廣的,但是這個是有一個缺點的,就是這個和業(yè)務(wù)是耦合的,需要大量的開發(fā),因為都是在業(yè)務(wù)上的實現(xiàn),等同于每個場景都需要三個方法來實現(xiàn),就是嵌入業(yè)務(wù),所以TCC是可以跨業(yè)務(wù)系統(tǒng)、跨數(shù)據(jù)庫來實現(xiàn)事務(wù)

          本地消息表

          本地消息表,就是利用了各個系統(tǒng)的本地事務(wù)來實現(xiàn)分布式事務(wù),這個呢,其實很簡單的道理,其實就是會有一張存放本地消息的表,一般都是放在數(shù)據(jù)庫中,然后在執(zhí)行業(yè)務(wù)的時候,必須把業(yè)務(wù)的真正的執(zhí)行操作和相應(yīng)的這個操作的消息放入到消息表中這個操作,存放到同一個事務(wù)中,就是只要操作成功了,就必須保證該消息也成功的放入到本地的消息表中了

          接下來調(diào)用下一個操作的時候,如果下一個操作調(diào)用成功了,就可以直接把消息的狀態(tài)改成已成功,調(diào)用失敗也沒有關(guān)系,我們可以寫一個定時任務(wù)來讀取本地的消息表,然后篩選出未執(zhí)行成功的消息再調(diào)用對應(yīng)的服務(wù),服務(wù)更新成功了,再改變消息的狀態(tài)

          其實這里也是需要重試機制,重試就得保證對應(yīng)服務(wù)的方法是冪等的,而且一般重試也會有最大的次數(shù),超過最大次數(shù)的時候可以人工介入

          本地消息表實現(xiàn)的是業(yè)務(wù)的最終一致性,需要能夠容忍數(shù)據(jù)暫時不一致的情況

          消息事務(wù)

          其實消息事務(wù),最典型的就是屬于RocketMQ中的實現(xiàn)了,而且應(yīng)用的場景也是比較多的

          RocketMQ的機制就是先給Broker發(fā)送事務(wù)消息,也就是半消息,半消息指的是這個消息對消費者來說不可見,然后發(fā)送成功后,發(fā)送之后會繼續(xù)執(zhí)行本地事務(wù)

          第二步就是根據(jù)本地事務(wù)的執(zhí)行結(jié)果向Broker發(fā)送Commit和Rollback命令,如果一直不發(fā)送,RocketMQ的發(fā)送方會提供一個反查事務(wù)狀態(tài)的接口,用來反查相應(yīng)的事務(wù)的結(jié)果到底是成功還是回滾

          其實這也就是個超時機制,在一段時間內(nèi)沒有收到任何的操作請求,那么Broker就會通過相應(yīng)的結(jié)果查出該事務(wù)是否成功執(zhí)行呢,是Commit還是Rollback

          如果是Commit,則broker就會發(fā)送這個消息到訂閱方,然后再做對應(yīng)的操作,做完了之后就可以消費這個消息,如果是Rollback則訂閱方即收不到這個消息,等同于事務(wù)沒有執(zhí)行過

          最大努力通知

          其實最大努力通知我個人認(rèn)為是一種思想,像上面的本地消息表、事務(wù)消息也是屬于最大努力通知類型的


          本地消息表會有后臺任務(wù)定時查看未完成的任務(wù)的消息,然后去調(diào)用對應(yīng)的服務(wù),進(jìn)行多次重試,當(dāng)多次失敗的時候就需要引入人工,這也是屬于最大努力

          事務(wù)消息也是屬于類似,半消息被Commit之后就會發(fā)送到消費端了,如果消費端一直不消費或者消費不了則會一直重試,如果重試次數(shù)達(dá)到一定數(shù)量,該消息變回進(jìn)入到私信隊列,也是屬于盡最大努力通知吧

          這應(yīng)該是屬于一種思想,盡最大努力的達(dá)到事務(wù)的最終一致,適用于對時間不敏感的業(yè)務(wù)場景

           


          求贊

           

          好了,以上就是全部內(nèi)容了,我是小魚仙,你們的學(xué)習(xí)成長小伙伴

          我希望有一天能夠靠寫字養(yǎng)活自己,現(xiàn)在還在磨練,這個時間可能會有很多年,感謝你們做我最初的讀者和傳播者。請大家相信,只要給我一份愛,我終究會還你們一頁情的。

          再次感謝大家能夠讀到這里,我后面會持續(xù)的更新技術(shù)文章以及一些記錄生活的靈魂文章,如果覺得不錯的,覺得【小仙】有點東西的話,求點贊、關(guān)注、分享三連

          哦,對了!后續(xù)的更新文章我都會及時放到這里,歡迎大家點擊觀看,都是干貨文章啊,建議收藏,以后隨時翻閱查看

          https://github.com/DayuMM2021/Java


          推薦閱讀

          ● 面試官問我:你確定用了BigDecimal后,計算結(jié)果一定精確?

             ●這個GitHub地址,真香

          瀏覽 74
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产无码av在线 国产一级内射视频 | 九九色在线视频 | 无码一区二区免费 | 一区二区三区视屏 | 啪啪啪免费视频网站 |