譯《Time, Clocks, and the Ordering of Events in a Distributed System》


Motivation

《Time, Clocks, and the Ordering of Events in a Distributed System》大概是在分布式領域被引用的最多的一篇Paper了。

這篇Paper自己去年讀過兩次,最近嘗試翻譯了一下。第一是覺得太經典了,分布式領域必讀論文;第二是想再加深下自己的理解。

英文水平有限,有興趣還是建議讀一下原文。


Abstract

本文審視了在分布式系統中,一個事件發生在另一個事件之前(“happening before”)的概念,並用它描述了事件的偏序關系。 給出了一種分布式算法,該算法用於同步邏輯時鍾系統,邏輯時鍾系統可以用於確定事件的全序關系。 全序關系的用途被作為一種解決同步問題的方法給出。 這個方法之后被專門用於解決同步物理時鍾,並且限定同步時鍾的誤差。

Introduction

在我們的思維中,時間是一個非常基礎的概念。它來源於更基礎的概念——時間的發生順序。我們說事件發生在3:15,假如時鍾已經走到3:15但是每到3:16。事件的時間順序的概念貫穿於我們對系統的思考。比如,在航空預定系統中,如果預定操作在航班滿員之前,那么預定操作需要被允許。但是,在分布式系統中,我們需要仔細的重新審視這個概念。

分布式系統由一系列空間上分離,通過交換信息來通信的進程組成。一個換聯網的計算機網絡,如ARPA,是一個分布式系統。單台計算機也可以被看做是由中心控制單元,內存單元,輸入輸出等不同的進程組成的分布式系統。如果與單個進程內事件發生的間隔時間相比,系統中消息的傳遞延遲不可以被忽略,那么這個系統可以被認為是一個分布式系統。

我們主要關注空間上分離的計算機。但是我們的很多結論能被廣泛的應用。特別是因為不可預知的事件發生順序,單個計算機上的多處理系統涉及的問題和分布式系統的問題非常相似。

在一個分布式系統中,有時候無法確定一個事件發生於另一個事件之前。而“發生之前”的關系只是系統中事件的一個偏序關系。我們發現由於人們沒有弄清楚這一點而導致一些問題問題的出現。

在這篇論文中,我們討論偏序關系,並給出一個分布式算法將其拓展為所有事件的全序關系。這個算法能夠提供有效的機制來實現一個分布式系統。我們通過一個解決同步問題的簡單方法來說明它的用法。不幸的是,如果通過這個算法得出的順序和用於感知的不一樣,可能會發生異常的行為。這可以通過引入真實的物理時鍾來避免。我們描述了同於同步這些時鍾的簡單方法,並且推導出了時鍾偏差的范圍。

The Partial Ordering

大部分人會認為事件a發生於事件b之前,如果事件a發生的時間早於事件b。他們可能通過物理理論事件來證明這個定義。但是,如果一個系統需要正確的符合規范,那么必須根據系統中可觀察的時間給出該規范。假如規范是在物理時間的條款下給出的,那么系統中必須包含一個真實的時鍾。即使真的包含物理時鍾,依舊會存在問題,因為時鍾會存在誤差。因此我們將不通過物理時鍾來定義“事件a發生於事件b之前”。換言之,單個進程中的事件是有預先的順序關系的。這看起來符合單進程的實際情況。當然,我們可以對我們的定義進行拓展,從而將當個進程划分為多個子進程,但我們沒有必要這么做。

我們假設發送和接收一條消息時一個進程中的一個事件。我們將“a發生於b之前”的關系定義為a→b,即→表示“發生於...之前”。

定義→關系需要滿足一下三個條件:(1)如果事件a、b在一個進程中,a發生於b之前,那么a→b;(2)如果a是發送一條消息的事件,而b是接收這條消息的事件,那么a→b;(3)假如a→b成立,且b→c成立,那么a→c成立。如果兩個事件a、b不滿足a→b或者b→a,那么認為a、b兩個事件是並發的。

我們假設a→a是不成立的(一個事件發生於自己之前顯得沒有什么意義),那么→是不具備自反性的偏序關系。

通過圖1這樣的“時空圖”對理解這個定義是非常有幫助的。水平方向表示空間,垂直方向表示時間——越遲的時間在垂直方向上的位置更高。點表示事件,垂直線表示進程,波浪線表示消息。顯而易見的,a→b意味着可以沿着進程線和消息線從a點到達b點。比如圖一中的p1→r4(p1→q2→q4→r3→r4)。

也可以換一種方式來理解該定義,比如可以認為a->b意味着是事件a導致了事件b的發生。如果兩個事件相互之間沒有因果關系,我們就認為它們是並行發生的。比如圖1中的p3和q3就是concurrent的。盡管圖畫地讓人覺得q3比p3發生的物理時間要早,但是進程P在p4那一點收到消息之前是根本不知道進程Q何時執行了q3的。(在事件p4之前,P進程最多能知道計划要在q3做的事情)。

這個定義對於那些熟悉狹義相對論時空觀的人應該會顯得比較自然。在相對論中,事件間的順序是通過可能被消息來定義的。但是,我們采用了更實用的方法,只考慮那些實際中真的被發送的消息。當然如果知道了那些實際發生的事件,我們就可以判斷系統是否工作正常,而不需要知道那些可能會發生的事件。

Logical Clocks

現在我們將邏輯時鍾引入到系統中。我們將邏輯時鍾抽象成只是給事件分配編號,編號代表事件發生的時間。更准確的說,我們為進程Pi定義一個時鍾Ci為進程中的每個事件分配一個編號Ci<a>。系統的全局時鍾為C,對任意事件b,它的時間為C<b>,如果b是進程j中的事件,那么C<b>=Cj<b>。目前為止,對Ci我們沒有引入任何物理時鍾的依賴,所以我們可以認為Ci是邏輯上的時鍾,和物理時鍾無關。它可以采用計數器的方式實現而不需要任何計時機制。

我們現在來考慮對於這樣一個時鍾系統的正確性的涵義。我們不能將定義的正確性基於物理時間之上,因為這需要引入持有物理時間的時鍾。我們的定義必須基於事件發生的順序。合理的條件是,如果事件a發生在另一個事件b之前,那么a發生的時間應該早於b。將該條件更形式化地表述如下:

*Clock Condition.* For any event a and b:

if a → b then C<a> < C<b>

需要注意的是我們不能期望這個條件的逆命題成立,即C<a> < C<b>並不意味着a → b。因為如果相反的條件也成立,那么意味着並發的事件必須發生在同一時間(命題和逆命題都成立的條件下,並發就只能是C<a>=C<b>)。比如圖1中,p2、p3都和q3並行,如果逆命題成立,那么p2、p3發生在同一時間,這和實際p2→p3的關系沖突。

從我們隊“→”關系的定義可以看出,如果滿足一下兩個條件,那么久滿足Clock Condition:

  1. 如果a、b是同一個進程中的事件,且a發生在b之前,那么C<a> < C<b>
  2. 如果a、b是不同進程中的事件,且a是發送一條消息的事件,而b是接收這條消息的事件,那么C<a> < C<b>

從時空圖的角度考慮時鍾。我們假設進程的時鍾在每個事件之間tick,且每次tick增加1。比如a、b是Pi進程中的事件,如果Ci<a> = 4,C1<b> = 7,那么這兩個事件之間時鍾會走過5、6、7。如果我們通過虛線(時間線)將所有tick的點連接起來,那么圖1看起來會類似圖2。Condition C1意味着同一個進程中任何兩個事件之間都有一條虛線;Condition C2意味着消息線必然和虛線交叉。

我們可以將時間線作為空間時間上一些笛卡爾坐標系的時間坐標線。我們可以將圖2中的時間線拉直,那么可以得到圖3。圖3是對圖2的另一種更好理解的描述。在沒有引入物理時間的情況下,沒有辦法判斷這3幅圖哪一幅圖更好的表示了系統的狀態。

讀者會發現如果將進程想象成一個二維的網絡圖,那么將產生一個三維的時空圖。進程和消息仍然由線表示,時間線則變成了一個二維的平面。

現在讓我們假設這些進程是一些算法,事件表示算法執行過程中的一些行為。我們將如何進入滿足Clock Condition的時鍾。進程Pi的時鍾由Ci表示,Ci<a>表示Pi中a發生的時間,Ci的值在發生事件時會改變,Ci的改變本身不包含事件。

為了滿足Clock Condition,我們需要確保滿足C1、C2條件。C1非常簡單,進程只需要按照以下步驟:

IR1:任何一個進程Pi在兩個成功事件之間遞增Ci

為了滿足C2,我們需要每條消息m包含一個時間戳Tm,Tm表示消息被發送的時間。收到該消息的進程需要將時鍾調整到大於Tm。更准確的,需要滿足一下規則:

IR2:(a) 如果事件a表示Pi進程發送消息m,那么m包含一個時間戳Tm,Tm=Ci<a>。(b)進程Pj收到消息m,Pj需要將Cj設置為等於或大於當前值,且大於Tm的值。

在IR2中,我們認為代表消息m被接收的時間在設置Cj之后(其實含義是需要將時間進行調整,之后再處理這條消息)。很明顯,IR2保證了條件C2的滿足。因此,IR1和IR2保證了Clock Condition的滿足,這樣它們就保證了得到的是一個正確的邏輯時鍾系統。

Ordering the Events Totally

我們可以使用滿足Clock Condition的時鍾系統來獲取系統中所有事件的全序關系。我們簡單的將他們按照發生的時間進行排序。為了打破僵局,我們使用系統的任意一個全序關系:“<”。更精確的,我們定義一個關系"=>":對於Pi中的事件a和Pj中的事件b,如果C<a> < C<\b>或C<a> = C<\b>,且Pi < Pj,那么a=>b。顯而易見的,這是一個全序關系,同時如果a->b,那么a=>b。換言之,=>將“發生於...之前”的偏序關系變成了全序關系。

=>的順序關系依賴於系統的時鍾Ci,並且不是唯一的。選擇滿足Clock Condition的不同系統時鍾將得到不同的全序關系。對於給定的全序關系,一定會有一個滿足Clock Condition的系統時鍾。只有偏序關系是由系統的事件唯一決定的。

能確定事件的全序關系對實現分布式系統是非常有用的。實際上,實現一個系統邏輯時鍾的目的就是為了獲得全序關系。我們將通過解決版本互斥的問題來說明全序關系的使用。考慮一個由多個進程組成,且貢獻一個資源的系統。一個時刻只能有一個線程使用資源,所以線程間需要同步來避免沖突。我們希望尋找一個滿足如下三個條件的算法來將資源分配給線程:1. 資源在分配給其他進程前,需要獲取了資源的進程進行釋放資源的操作;2. 資源的請求必須按照請求的發生順序進行分配;3. 如果每個被授予資源的進程最終都釋放了資源,那么每個請求最終都會被授予資源。

我們假設初始狀態資源只會被分配給一個進程。

這些事非常自然的需求。他們准確的定義了什么樣的解決方案是正確的。觀察這些條件是如何涉及到事件順序的。對於條件2,它並沒指定對於並發請求的授權順序。

認識到這是一個不平凡的問題非常重要。采用中心進程來統一授權的方式並不能解決這個問題,除非添加額外的假設。來看這樣的場景,P0為中心授權的進程;如果P1像P0發起申請資源的請求,之后給P2進程發送消息;P2進程收到后也想P0進程申請資源;P2的請求可能先於P1的請求到達P0,那么條件2將被違反(沒有按照請求發生的時間來授權)。

為了解決這個問題,我們實現了一個滿足IR1和IR2的系統時鍾,然后用它來定義一個全序關系=>。這提供了所有request和release操作的順序。利用這個順序就可以比較容易的找到一個解。它只需要確保每個進程知道其他進程的操作。

為了簡化問題,我們做了一些假設。這些假設不是必須的,但是引入他們可以避免陷入細節的討論當中。首先,我們假設所有任何兩個進程Pi和Pj,所有從Pi發送到Pj的消息都將按照他們的發送順序被Pj接收。其次,我們假設所有的消息都會被接收(這些假設可以避免引入消息序號和確認機制)。最后,我們假設一個進程能直接的向所有的其他進程發送消息。

每個進程會維護一個它自己的,對其他進程不可見的請求隊列。我們假設請求隊列初始狀態只有一個消息T0:P0資源請求,P0代表初始時刻獲得資源授權的進程,T0小於任意時鍾初始值。

算法由以下5個規則定義。方便起見,假設每個規則定義的行為為一個獨立的事件。

  1. 為了申請資源,Pi發送資源申請請求Tm:Pi給所有其他的進程,並將消息放入自己的請求隊列。Tm表示消息的時間。
  2. 當Pj收到Tm:pi的請求,將其放入請求隊列並發送一個帶有時間戳的ACK給Pi。
  3. 釋放資源時,Pi將Tm:Pi從請求隊列中移除,並發送一個帶有時間戳的Pi釋放資源的消息給所有的其他進程。
  4. 當Pj接收到Pi釋放資源的消息時,它將Tm:Pj請求資源的消息從請求隊列中移除。
  5. 當以下兩個條件被滿足時Pi獲得資源:(a)按=>的順序,Tm:Pi的消息在請求隊列的最前面;(b)Pi從其他每個進程至少收到了一條時間戳大於Tm的消息。

條件5中的(a)、(b)都只需要在Pi本地進行驗證。

很容易驗證滿足以上規則的算法滿足之前的資源分配算法的1、2、3條件(1. 資源在分配給其他進程前,需要獲取了資源的進程進行釋放資源的操作;2. 資源的請求必須按照請求的發生順序進行分配;3. 如果每個被授予資源的進程最終都釋放了資源,那么每個請求最終都會被授予資源)。首先觀察規則5的條件b,假設消息是順序接收的,就可以保證Pi已經收到了所有排在它當前請求之前的所有請求。只有規則3和規則4會從請求隊列中刪除消息,因此可以很容易的看出條件1是滿足的。條件2可以通過如下事實得出:全序關系=>是對偏序關系->的拓展。規則2保證了在Pi發出資源請求后,規則5的條件(b)最終一定會成立。規則3和4意味着如果獲取了資源授權的所有進程最終釋放掉資源后,那么規則5的條件(a)最終也一定會成立,這就證明了條件(3)。

這是一個分布式算法。每個獨立的進程遵循這些規則,算法中沒有中心同步進程,也沒有中心存儲設備。該方法可以被通用化,來實現分布式的多進程系統所需要的任意同步機制。同步過程可以通過狀態機來描述,該狀態機包含一個命令集合C,一個可能的狀態集合S,以及一個函數e:C×S->S。關系e(C,S)=S’的含義是,在處於狀態S的狀態機執行命令C,將會使狀態機轉移到狀態S’。在我們的例子中,C包括所有的Pi資源請求和資源釋放命令,狀態包括一個處於等待狀態的請求命令隊列,同時處於隊列頂端的那個請求就是要被授權的那個。請求命令的執行將會將該請求添加到隊列末尾,釋放命令的執行將會從隊列中刪除一個命令。

每個進程獨立地通過使用所有進程產生的命令來驅動該狀態機的執行。所有的進程按照命令的時間戳激進行排序,因此所有的進程都會使用相同的命令序列。當一個進程收到所有的其他進程發出的小於或者等於T的命令之后他就能執行T的命令。具體的算法已經很明了了,因此我們不再進行描述。

該方法使得我們可以在分布式系統中實現多進程的同步。但是,該算法要求所有進程都參與其中。每個進程需要知道所有其他進程的所有命令,所以單個進程的故障將使其他進程的狀態機無法正確變更,從而導致系統停止工作。

錯誤處理是一個很困難的問題,同時對它進行細節性的討論已經超出了本篇文章的范圍。只是需要指出的是,failure這個概念只有在物理時間上下文中才有意義。如果沒有物理時間,就沒有辦法去區分進程是出錯了還是只是處於事件之間的間歇。用戶只能通過系統很長時間都沒有響應來判斷系統出了問題。在進程或通信線路出錯時也能正常工作的方法,我們將會在[3]中進行描述。(Lamport, L. The implementation of reliable distributed multiprocess systems. To appear in Computer Networks.)

Anomalous Behavior

我們的資源調度算法按照全序關系對請求進行排序。這允許下面這種異常行為的發生。考慮一個由相互連接的計算機組成的全國性系統。假設某人在計算機A上產生了一個請求A,然后他打電話告訴住在另一個城市里的朋友B,讓它在計算機B上產生一個請求B。對於請求B來說很有可能會獲得一個更小的時間戳然后被排在A前面。這是有可能發生的,因為系統沒有辦法知道A實際上發生在B之前,因為該信息是基於位於系統外部的消息的。

讓我們更深入地考察下該問題產生的根源。令∮代表所有系統事件組成的集合,然后我們再引入一個包含了∮中的事件以及所有其他外部事件(比如上面例子中的打電話)的集合∮’。令->(注意這個是加粗的->,與前面的->不同)表示∮’中的happen before關系。在上面的例子中,有A->B,但是A!->B。很明顯一個完全基於∮中的事件,而對的∮’中的其他事件沒有任何聯系的算法,是無法保證請求A會被排在請求B前面的。

有兩種可能的方式可以避免這種異常行為。第一種方式是將關於->順序的必要信息顯式地引入到系統中。比如在上面的例子中,該用戶在產生請求A時可以獲取它在系統中的時間戳T,然后他可以在打電話通知他朋友的時候,告訴他這個時間戳,然后在他朋友產生請求B的時候,告知系統產生一個晚於T的時間戳。這樣就將這種異常行為交給用戶自己來負責。

第二種方法就是構造一個滿足如下條件的時鍾系統:

Strong Clock Condition.對於∮’中的任意兩個事件,如果a->b,那么C<a> < C<b>。

該條件要比之前的Clock Condition強,因為->比->要強。通常我們的邏輯時鍾無法滿足該條件。

如果我們令∮’表示物理時空中真實事件的集合,令->代表由狹義相對論所定義的事件偏序關系。在我們所在的宇宙中,是有可能構造出一個由相互之間獨立運行的多個物理時鍾構成的滿足Strong Clock Condition的時鍾系統的。因此我們可以使用物理時鍾來避免這種異常行為。下面我們就將注意力轉移到這類時鍾之上。

Physical Clocks

現在我們將物理時間引入到我們的時空圖中,令Ci(t)表示在物理時間t所讀取到的時鍾Ci的值。為了方便數學表述,我們假設時鍾是以連續而非離散的滴答走動的。更准確地說,我們假設Ci(t)是一個在時間t上的連續的可微分函數,除了那些時鍾被重置時的孤立突變點之外。dCi(t)/dt代表了時鍾在時間t時的速率。

如果將時鍾Ci作為一個真實的物理時鍾,那么它還必須以一個近似正確的速率來運行。也就是說,必須要保對於所有的t,dCi(t)/dt≈1。更准確地說,我們要保證滿足如下條件:

PC1.存在一個常數k,對於所有的i:| dCi(t)/dt -1|<k

對於典型的由晶體控制的時鍾來說,k<=10^-6.

如果時鍾只是單單地以近似正確的速率運行是不夠的。它們還必須是同步的,即對於所有的i,j,t來說,Ci(t)≈Cj(t)。更准確地說,必須存在一個足夠小的常數e,滿足如下條件:

PC2.對於所有的i,j:| Ci(t)-Cj(t)|<e.

如果我們讓圖2中的垂直距離來表示物理時間的話,那么PC2意味着單個滴答線上的高度差異要小於e。

由於兩個不同的時鍾永遠都不會以相同的速率走動,這意味着它們之間的偏差會越來越大。但是,首先我們來看一下k和e要多小才能避免異常行為。我們必須要保證系統∮’中的所有相關事件都滿足Strong Clock Condition。首先我們假設我們的時鍾滿足普通的Clock Condition,那么只需要保證∮’中那些不滿足a->b的事件滿足Strong Clock Condition。因此我們只需要考慮發生在不同進程中的事件。

令u表示滿足如下條件的值:如果事件a發生在物理時間t,同時b是發生在另一個進程中的滿足a->b事件,那么b肯定發生在物理時間t+u之后。換句話說,u需要小於進程間消息傳輸的最短時間。我們可以用進程間的距離除以光速的值作為u的值。當然,這取決於∮’中消息是如何傳輸的,u的值也可以很大。

為避免異常行為,我們必須保證對於任意i,j,t:Ci(t+u)-Cj(t)>0,再結合PC1和PC2,我們就可以建立起所需要的最小的k和e值與u之間的關系。同時我們假設時鍾被重置時,它的時間只會超前而絕不會后退(后退會導致條件C1被違反)。PC1意味着Ci(t+u)-Ci(t)>(1-k)u。再結合PC2,就可以很容易地得出如果如下不等式成立,那么Ci(t+u)-Cj(t)>0就成立:e(1-k) <= u。該不等式再加上PC1和PC2就可以保證異常行為不會發生。

現在我們來描述下用來保證PC2成立的算法。令m表示一個在物理時間t發送和時間t’被接收的消息。我們定義Vm=t’-t來表示消息m的總延遲。當然,接收消息m的進程並不知道該延遲。但是我們假設接收進程知道某個最小延遲取值Um>=0,並且Um<=Vm。我們稱Em=Vm-Um為消息的不可預測的延遲部分。

現在我們將規則IR1和IR2針對物理時鍾修改如下:

IR1’.對於每個i,如果Pi在物理時間t未收到消息,那么Ci是在時間t就是可微分的並且dCi(t)/dt>0.

IR2’.(a)如果Pi在物理時間t發送了一個消息m,那么m將包含一個時間戳Tm=Ci(t).(b)當在時間t’接收到消息m時,進程Pj將設置Cj(t’)等於max(Cj(t’-0),Tm+Um).(注:Cj(t’-0)=[limCj(t’-|&|),&->0])

盡管這些規則的描述使用的都是物理時間,進程只需要知道它自己的時鍾值以及它接收到的消息中的時間戳即可。為了數學描述的方便,我們假設事件均發生在一個精確的物理時間點上,同時相同進程的不同事件發生在不同的時間。這些規則僅僅是針對規則IR1和IR2的一個特化版本,因此我們的時鍾系統是滿足Clock Condition的。實際上,由於真實的事件都會持續一段有限的時間,這就使得該算法實現起來沒有什么困難。實現唯一需要注意的是要確保離散的時鍾滴答是足夠頻繁的,可以保證滿足C1。

現在我們來展示一下如何用該時鍾同步算法來保證條件PC2的滿足。我們假設該系統可以用一個有向圖來表示,圖中從進程Pi到進程Pj的邊表示一個通信線路,消息可以直接通過該線路從Pi發送到Pj。同時我們假設每τ秒就會有一條消息通過該線路,這樣對於任意的時間t,在物理時間t到t+τ之間,Pi至少發送了一條消息到Pj。有向圖的直徑是滿足如下條件的最小值d:對於任意的兩個進程Pj,Pk,都存在一條從Pj到Pk的路徑,該路徑最多具有d條邊。

除了建立起了PC2,下面的定理還給出了系統首次啟動時令時鍾達到同步狀態所花時間的上界。

THEOREM假設一個半徑為d的由多個進程組成的強連通圖始終滿足規則IR1’和IR2’。同時對於任意消息m,Um<=某常數U,以及任意時刻t>=t0來說:(a)PC1成立.(b)存在常數τ和ε,每τ秒都會有一個消息在不可預測的延遲部分小於ε的情況下在每條邊上傳送。那么PC2就能被滿足,同時對於所有的t>t0+τd,e≈d(2kτ+ε),這里我們假設U+ε<<τ。

該定理的證明令人吃驚地困難,詳細證明過程見附錄。目前針對物理時鍾的同步問題人們已經進行了大量的研究。推薦讀者閱讀[4]了解下這個問題(Ellingson, C, and Kulpinski, R.J. Dissemination of system-time. 1EEE Trans. Comm. Com-23, 5 (May 1973), 605-624.)。該領域提出的很多方法,都可以用來估計消息傳輸Um以及調整時鍾頻率dCi/dt(適用於那些允許進行調整的時鍾)。但是,看起來時鍾不能回退的這個要求使得我們的場景與之前被研究的那些有所不同,同時我們相信該定理是一個全新的結論。

Conclusion

可以看到“happening before”定義了分布式系統中事件的一個偏序關系。我們描述了一個可以將偏序關系拓展為某個全序關系的算法,同時展示了這個算法如何去解決簡單的同步問題。我們將會在未來的一篇paper里展示如何對這種方法進行擴展以用來解決任意的同步問題。

該算法定義的全序關系有些隨意。當系統與用戶觀察到的順序不一致時會產生異常行為。該問題可以通過使用一個被正確同步的物理時鍾系統來避免。我們的定理還展示了時鍾可以被同步到怎樣的程度。

在分布式系統中,認識到事件的發生順序是只是一個偏序關系是非常重要的。我們認為這個觀點對理解所有多進程的系統非常有幫助。它可以幫助人們理解多進程系統中的基本問題,撇開那些用於解決這些問題的各種機制。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM