ceph 譯文 RADOS:A Scalable, Reliable Storage Service for Petabyte-scale Storage Clusters

RADOS:A Scalable, Reliable Storage Service for Petabyte-scale Storage Clusters

論文翻譯

摘要

塊式和麪向對象的存儲架構形成了一種以提升擴展性的存儲cluster。然而,現存的系統繼續把存儲節點作爲一個被動的設備,儘管他們有能力展示智能和自治。我們提出RADOS的設計和實現,RADOS是一個可靠的面向對象服務,通過利用每個獨立節點的智能可以擴展到數千臺設備。當允許節點半自治地通過利用集羣地圖來進行自我複製,錯誤檢測,錯誤恢復,RADOS保護數據的一致性和強壯的安全語義。我們的實現提供了極好的性能,可靠性,可擴展性,同時,提供給客戶端一個邏輯的對象存儲。

1 介紹

提供可靠的高性能的規模不斷擴大的存儲給系統設計者們帶來了挑戰。高吞吐和低延時的面向文件系,數據庫和相關抽象的存儲對廣泛的應用來說是很重要的。基於磚式存儲或者對象存儲設備(OSD)的聚合集羣存儲架構正在尋求將低層次的塊分配解決方案和安全強制措施分佈到智能化存儲設備上,從而,通過促進客戶端直接訪問數據來解決智能化存儲設備,簡化數據分佈和消除IO瓶頸的相關問題。基於商用組件的OSD結合了CPU,網絡接口,本地緩存,基礎的磁盤或者RAID,把基於塊設備接口的存儲替換爲一個基於命名的變長的對象。

然而,採取這種架構的大規模系統地無法利用設備的智能。因爲協議化的存儲系統是基於本地或者存儲網絡的存儲設備或者這些兼容T10OSD協議的設備,設備被動地相應讀寫命令,儘管他們有潛力封裝明顯的智能。當存儲集羣增長到數千節點或者更多時,數據遷移的一致性管理,錯誤檢測,錯誤恢復將給客戶端,控制器,元數據目錄節點帶來極大的壓力,限制了可擴展性。

我們已經設計並實現了RADOS,一個可靠的自動的分佈式對象存儲,它能尋求將設備智能分佈到複雜的涉及數據一致性訪問,冗餘存儲,錯誤檢測和恢復登問題的數千節點規模的集羣。作爲Ceph分佈式系統的一部分,RADOS促成了一個優化的平衡的分佈式數據和負載,這些數據和負載分佈在動態的不均勻的存儲集羣上,與此同時,RADOS爲applications提供一個單一的具有良好的安全語義和強一致保障的邏輯對象存儲。

對於PB級的規模,存儲系統動態化是有必要的:它們被動態的擴建,它們增長並且與新部署的存儲建立聯繫或使老設備退役,設備出錯和恢復是基於連續的數據基礎,大量的數據被建立和刪除。RADOS確保數據分佈對系統的一致性,以及基於cluster map的對象讀寫的一致性。這個map被複制到所有的節點上(存儲和客戶端節點),並且被Lazy propagation的增量更新。

通過提供給存儲節點以完整的數據在系統中的分佈信息,設備能夠半自治地通過類似點對點的協議來自我管理數據複製,一致和安全的過程更新,參與錯誤檢測,及時響應錯誤和數據對象複製遷移帶來的數據分佈變化。這減輕了管理着cluster map主副本的小型monitor集羣上的壓力,從而,使得剩餘的存儲集羣能夠是系統無縫地從幾十個節點擴展到數千個節點。

我們的prototype實現提供可供在byte範圍可讀寫的對象接口(就像文件那樣),因爲那是我們最原始的Ceph的需求。爲了防止節點錯誤和保護數據對象,在OSD集羣中數據對象通過N條路被複制。然而,RADOS的可擴展性無法依賴在特定的對象接口或者冗餘策略;存儲Key-Value的對象和RAID冗餘都被計劃中。
這裏寫圖片描述

2 可擴展的集羣管理

一個RADOS的系統包括含有大量OSD節點的集合加上一個規模小的負責管理OSD cluster membership的Monitor 集合。每一個OSD節點包括一個CPU,一些易失性內存,一個網絡接口和本地磁盤或者RAID。Monitors是有單機進程並且需要少量的本地存儲。

2.1 Cluster Map

Monitor集羣通過管理Cluster Map從而使存儲集羣被互斥地管理。Cluster Map指定了集羣包括哪些OSDs,並簡潔地指定了全體數據在系統中的分佈。Cluster Map被每一個存儲節點和那些與RADOS系統交互的客戶端所複製。因爲Cluster Map完整地指定了數據分佈,客戶端提供一個簡單的接口用來把整個存儲集羣(可能一萬個節點)虛擬成一個單一的邏輯對象存儲。

每一次因爲OSD狀態變化(例如設備錯誤)或者其他的觸發數據分佈的事件所引起的Cluster Map的改變,都會觸發map epoch遞增。Map epochs允許通信雙方約定當前的數據分佈是什麼,決定什麼時候他們的信息超時。因爲Cluster Map改變很頻繁,在非常大的集羣中OSDs錯誤檢測和恢復很平常更新和分發map 增量(Cluster Map):小信息來表述兩個成功的map epoch間的差異。在大多數情況,這些更新只是說明了一個或者多個OSD節點出錯或者錯誤恢復,儘管一般情況這些更新可能包括很多設備的狀態改變,很多更新被集中捆綁以便描述間隔比較長的的map版本。

2.2 數據放置

RADOS使用一種將數據僞隨機地分佈到設備的協議。當新的設備被添加,一個隨機的現存數據的副本將遷徙到新的設備上以實現負載均衡。這種策略使系統維護了一定概率的均衡分佈,基本上,保持所有的設備有相似的負載,允許系統在任何可能的負載下都能很好的運行。最重要的,數據複製是一個兩階段的過程,計算了正確的對象存儲位置;而且不需要一個大型且笨重的集中分配表。

每個存在系統的對象首先先被映射到放置組(Placement Group),一個被同一組的設備複製的邏輯對象集合。每個對象的PG是由對象名o的Hash值,數據主從複製的等級r,和一個控制PG總數的位掩碼m來決定的。那麼說,pgid=(r,hash(o)& m),m = 2的k次方 - 1,從而限制PG的數量是2的N次方。
對於集羣來說,它是週期性的必要, 通過改變m來調整放置組的總數,這是逐漸進行的來減少設備間的PG的遷移.

基於Cluster Map,Placement Group被分配給OSD節點,每個PG被映射到有序的包含r個OSD節點的鏈表中,數據副本將存儲在這些映射的PG上。我們的實現利用了CRUSH,一個魯棒性的副本分佈算法,用來計算出穩定的,僞隨機地映射。(其他可能的複製策略;對於超大型集羣,即使是一個映射PG到設備的表也仍然相對較小(MB級。)從高層上看,CRUSH的行爲類似於HASH函數:Placement Groups是確定且是僞隨機分佈的。和Hash函數不同的是,CRUSH是穩定的:當一個節點加入或者離開集羣,PGs仍然在原來的存儲位置不變;CRUSH只轉移足夠的數據來維護一個均衡的分佈。相對的,HASHING更傾向於強制地對主要的映射進行重新洗牌。CRUSH也會通過權值控制那些基於容量和性能被分配到每個設備的數據。

PG提供了一種方法控制副本分佈策略的級別,分佈析散(replication declustering)。也就是說,一個OSD不是與另外一個或者多個設備共享所有副本(鏡像),也不是與其他不同的設備共享每個對象(complete declustering),副本Peer數量與它存儲的PG數量u有關,典型的是順序100PG每個OSD。由於分佈是隨機的,u也會影響設備利用率的方差:OSD上的PG越多分佈越均衡。更重要的是,declustering有利用分佈式,通過允許每個PG獨立被重新複製到其他OSD可以並行錯誤恢復。在同一時間,系統可以限制同時出現故障的設備,通過限制每個設備共享相同數據的數量。

2.3 Device State 設備狀態

Cluster Map中包含了設備的描述信息,設備狀態信息,以及數據分佈信息。包含所有OSD的當前在線的網絡地址,並指明哪些設備是不可達的(down)。RADOS會把OSD的活躍度考慮進去。

對於每個PG,CRUSH會從mapping中找到r個OSD。RADOS然後會過濾掉那些down狀態的設備,爲PG產生一個avtive狀態的OSD列表。如果這個active列表是空的,PG數據就會不可用,IO就會被Block。

對應活躍的IO服務,OSD通常是up和in狀態。如果它出錯,應該是down和out狀態,產生一個actvie列表對應 r個OSD。OSD也可能處在down但是仍然是in 某個Mapping,意味着他們目前是不可達的,但是PG數據還沒有被remapped到其他OSD(類似於degraded mode在RAID系統中)。反過來,它們也可能是up和out狀態,意味着他們是在線的,但是仍然處在空閒狀態。這有利於應對各種場景,包括對間歇性的不可用容忍(例如:OSD重啓或網絡間歇性中斷)而不初始化任何數據遷移;新的部署存儲而暫時不使用(例如:用來測試),並且在就設備推出前,將數據安全的遷移出去。

2.4 Map傳播(Map Propagation)

由於RADOS集羣坑內包含成千上萬的設備,簡單的廣播Map更新消息到每個節點是不實際的。幸運的是不同ma版本的不同是很明顯的,只有當兩個通信的OSD(或者一個客戶端和OSD)不同時,他們纔會根據適當的規則更新。這個特性可以是RADOS分發延遲分發Map更新,通過經OSD內部消息結合,高效的轉移分佈負載。

每個OSD都會維護map更新的歷史記錄,爲每個消息帶一個epoch的tag,並持續跟蹤出現在每個peer中的最新的epoch。如果OSD接收到一個peer帶來一個老的map,它就會將必要的增量帶給這個peer以保持同步。類似的當發送peer有一個老的map,增量更新也會從對端共享。心跳消息會週期的交換以檢測異常保證更新快速擴散,對於一個有n個OSD的集羣,用到的時間爲O(logn)。

例如,當一個OSD啓動時,它會通過OSDboot消息通知一個monitor,這個消息中包含了其最新的map epoch。Monitor集羣更新該OSD的狀態爲up,然後將更新後的cluster Map帶給該OSD,當這個新的OSD與其他OSD通信時,這個狀態更新後的Map就會共享給這些OSD。因爲這個新OSD不知道其他Peer擁有的epoch,它會共享一個安全的當前增量更新歷史。

這種Map共享機制是保守的:一個OSD與其他Peer聯繫時都會共享和更新Map,另外,當這個peer以及看到它時,就會導致OSD接收到充分的更新信息。然而,一個OSD接收到重複的Map的數量是與它有多少Peer有關,這個數量又由它管理PG的數量u決定。實際情況下,我們發現update重複的級別原少於這個值。

3 智能存儲設備

數據分佈的信息封裝在了Cluster Map中,這使得RADOS可以將存儲集羣的數據冗餘管理、故障檢測和故障恢復分佈到各個OSD上。通過採用類似P2P的協議,在高性能的集羣環境中,充分利用了OSD節點的智能性。

RADOS實現了結合每個對象版本和短的日誌的多路複製。複製是由OSD自己完成的:客戶端只會提交一個寫的操作到主OSD,主OSD複製一致性和安全更新所有副本。這樣移動和複製相關的操作利用的是存儲集羣內部的網絡帶寬,同時這樣也可以簡化客戶端的設計。對象版本和日誌有助於節點故障時快速恢復。

我們將主要描述RADOS集羣架構,特別是Cluster Map是如何將複製和故障恢復分佈化,已經如何將這個能力推廣到引入其他冗餘機制(例如基於RAID碼的分片)。
這裏寫圖片描述
圖2

3.1 複製

RADOS實現了三種不同的複製策略:Primary copy、chain、splay。更新操作的消息交換可以參考圖2。在所有的情況下,客戶端發送IO操作到一個OSD,集羣來保證副本能夠安全地更新以及一致性的讀和寫。一旦所有的副本被更新,就會返回一個ACK消息給客戶端。

Primary-copy 複製並行地更新所有的副本,並處理讀和寫在primary OSD。Chain採用串行更新的方式:當寫命令發送給Primary節點,而讀命令發送給Tail節點,保證read總是可以反映整個副本的更新。Splay是將Primary-copy中的並行更新和chain-copy中的讀寫角色分離想結合,這個好處是減少了消息的跳數對於兩路鏡像。

3.2 強一致性

所有的RADOS消息(包括從客戶端發起的消息,以及從其他OSD發起的消息)都攜帶了發送端的map epoch,以保證所有更新操作都能夠在最新的版本上保持一致。如果一個客戶端因爲使用了一個過期的map,發送一個IO到一個錯誤的OSD,OSD會回覆一個適當的增量,客戶端再重新發送這個請求到正確的OSD。這就避免了主動共享map到客戶端,客戶端會在與Cluster聯繫的時候更新。大部分時候,他們都會在不影響當前操作的時候學到更新,讓接下來的IO能夠準確的定位。

如果cluster map的主拷貝已經更新,改變了一個特定的PG的成員,老的成員仍然可以處理更新,就像沒有感覺到變化一樣。如果這個改變先被一個PG副本節點知道,它將被發現,當主OSD轉發更新消息到副本節點,副本節點就會返回map的增量給主OSD。這是完全安全的,因爲任何新負責一個PG的一組OSD都需要與之前負責這個PG的OSD聯繫,以保證PG的內容是正確的,這樣就可以保證之前的OSD能夠學到這個Map的改變,並在新的OSD接手之前停止執行IO操作。

完成類似讀的一致性操作會沒有更新那麼自然。當網絡故障時,會導致一個OSD部分不可達,爲某個PG提供讀服務的OSD可能被標記爲故障,但是可能對那些擁有過期的map的client是可達的。同時,更新後的map可能指定了一個新的OSD。爲了避免新的更新由一個新的OSD處理後,老的OSD還能處理接收到的讀操作,我們需要週期性的心跳報文,在那些負責相同PG的OSD之前,爲了保持這個PG是可讀的。如果一個提供讀的OSD在H秒鐘沒有聽到其他副本的心跳報文,讀操作就會被阻塞。在其他OSD接收一個PG的主要角色前,他必須獲得老的OSD的確認(保證它們都知道自己的角色發生了變化),或延遲一定的時間間隔。目前的實現,我們採用2秒鐘一個相對較小的心跳間隔。這可以抱着故障可以及時的被偵測,並且當主OSD故障是可以保證數據不可用的時間很短。

3.3 故障檢測

RADOS採用一種異步、有序點到點的消息傳遞庫進行通信。當TCP套接字發生故障是會導致有限次的重新連接嘗試,然後才報告monitor集羣。存儲節點會週期性的交換心跳報文與他們的對端(那些與他們共享相同PG數據的OSD),以保證設備故障能夠及時偵測到。當OSD發現他們已經被標記成爲down狀態,會同步硬盤數據,然後將kill自己保證行爲的一致性。

3.4 數據遷移和故障恢復

RADOS數據遷移和故障恢復完全是由Cluster Map的更新和PG到OSD映射改變驅動的。這個改變可能是由於設備故障、恢復,集羣擴展或收縮,已經有一新的CRUSH策略導致所有數據的重新分佈。設備故障只是許多可能引起建立新的集羣數據分佈的一個原因之一。

RADOS沒有對數據連續性做任何假設。在所有情況下,RADOS採用了一個魯棒性peering算法,通過這個算法可以建立一個一致性的PG內容視圖,並且可以恢復適當的數據分佈和複製。這個策略依賴的基本的設計前提是OSD採用積極複製一個PG的日誌,這個記錄了一個PG當前內容是什麼(即:包含的對象的版本),即使當前對象副本可能本地丟失。因此,即使恢復很慢,一些時候對象安全被降級,PG的元數據能夠被保證,從而簡化了恢復算法,並且允許系統檢測到數據丟失。

3.4.1 Peering

當一個SD接收到一個Cluster Map更新,它會遍歷所有先的Map增量,通過最近的檢查和可能的調整PG的狀態值。任何本地存儲的PG的atcive list中的OSD發生改變必須重新re-peer。考慮到所有的map epoch(不只是最近的),確保中間數據分佈要被考慮:如果一個OS從一個PG移除,然後有加入進來,要確認PG的內容可能在中間發生更新,這一點很重要。與複製、對等以及其他後續的更新對系統中的每個PG進行處理。

Peering是由OSD中的Primary OSD驅動的。對於每個PG不是Primary的OSD,通知消息都發送給primary OSD。這個消息包括:本地存儲的PG的基本狀態信息,包括最近的更新,一定範圍的PG日誌信息,已經最近知道的epoch。通知消息保證一個新的primary OSD對於一個PG能夠發現他的新的角色,不用考慮所有可能的PG(可能有幾百萬個)對於每個map改變。一旦知道這些,Primary OSD就可以生成一個prior set,其包含了所有加入到這個PG的OSD,因爲剛剛與這些成功建立peer關係。這個prior set可以被顯式查詢以達到一個穩定狀態,避免對一個OSD沒有政治存儲這個PG的無限等待。

有了PG現有集的元數據,Primary OSD就能夠決定應用於任何副本的最近更新,並且知道從prior OSD請求哪段log片段,以便是PG日誌在active副本上得到更新。如果一個可用的PG日誌是不充足的(例如,一個或多個OSD沒有PG數據),一個完整的PG內容就會生成。對於節點重啓或者其他端的中斷,爲了足夠快的同步副本PG日誌是有足夠的信息。

最好,Primary OSD與其他replica OSD 共享缺失的日誌片段,所有replica知道都知道PG中包含哪些對象(即使他們本地還沒有保存這個對象),這樣就可以在後臺執行恢復進程。

3.4.2 恢復

Declustered replication的一個重要的優勢是能夠進行並行的故障恢復。任何單一故障的設備共享分佈在其他OSD的副本,每個PG可以獨立選擇替換和允許重新複製一樣多的OSD。平均而言,在一個大的系統中,任何一個單一故障恢復的OSD,可以採用Push和Pull的方式複製內容,是故障恢復的速度非常快。Recovery可以被激發通過觀察IO讀是否被限制。雖然每個獨立OSD都擁有所有的PG元數據,可以獨立的獲取每個缺失的對象,但這個策略有兩個限制。一個是多個OSD分佈恢復在相同PG中的對象,這可能在相同時間,不會去下載在相同OSD上的相同對象。這可能導致最大的恢復開銷是Seeking和Reading。另外,如果replica OSD缺失對象被改變,這個副本更新協議可能會變得很複雜。

基於這個原因,RADOS中的PG恢復是由primary OSD協調的。和之前類似,知道主OSD有一個本地拷貝後纔對缺失的對象進行操作。因爲Primary OSD通過peering的過程已經知道了所有replicas缺失哪些對象。它可以將任何對象推送到replica OSD,簡化的複製樓價也保證了這在拷貝的對象只能讀一次。如果主 OSD正在推送一個對象或者它已經剛剛下載了一個對象,它將總是會推送給所有replicas。因此,每個被複制的對象只讀一次。

4 監視器

Monitors是一個小的集羣,通過存儲Cluster Map的主Copy,並週期性的更新配置的改變和OSD狀態,管理整個存儲系統。集羣是建立在Paxos part-time parliament算法的基礎上,這個算法有利與可用性和更新延遲的一致性和持久性。值得注意的是,大部分Monitors必須是可用的,爲了保持Cluster Map的讀取和更新,以及Cluster Map的改變能夠持久性。

4.1 Paxos Service

集羣是基於分佈式狀態機服務,基於Poxos,Cluster Map是當前的集羣狀態,並且每次更新都會到只一個新的Map epoch。實現輕微地簡化了標準的Poxos,通過任何時候只允許一個突變對於當前的Map。將基本的Paxos算法與租約機制相結合,允許任何monitor之前直接請求,確保讀和更新按照一致的順序。集羣初始化選出的一個領導這能夠序列化Map更新和管理一致性。一旦被選出來,Leader就會請求每個monitor上的所有map epoch。Monitor有一個固定的時間探測和家人到quorum。如果大多數monitor是active的,Poaxos算法的第一階段能夠保證每個Monitor有一個最近提交的map epoch,然後分發短的租約到沒有active monitor。每個租約都會授權給active monitor想OSD和Client分發他們請求的Cluster Map拷貝的權利。如果租約T超時,並沒有更新,就認爲leader已經故障,就重新進行選舉。當收到租約時要給leader發送確認消息。如果leader沒有及時收到確認消息,他就假設這個active monitor已經崩潰,重新選舉建立一個新的quorum。當一個monitor第一次啓動,或者過了一定時間間隔後之前的選舉沒有完成,都會激發重新選舉。

當一個active monitor接收到一個更新請求(例如一個錯誤報告),它會先看一下這個請求是否是新的。例如一個OSD已經被標記爲down,這個monitor就會給發送這個消息的OSD相應的Map增量。新的報錯會轉發給Leader,leader會初始化Map更新通過增加Map epoch,並使用Paxos 更新協議將共享分發給其他Monitor,同時撤銷租約。如果更新被大部分Monitor確認,最後會提交新的租約消息。

一個兩個階段的指派和週期性的探測可以保證,是否active monitor集合改變,它保證所有當前的租約,在Map更新時過期。因此,任何順序的Map更新查詢和更新都能夠在一個Map版本上一致的進行。重要的是Map的版本將永遠不會回退,只要大部分monitor是是可用的,無論哪個Monitor消息被髮送到或任何干預Monitor的錯誤。
這裏寫圖片描述

4.2 工作負載和可擴展性

通常情況下,Monitor工作負載是很小的,大部分Map分發是由Storage Node完成的,設備狀態的改變也是不經常發生的。

Monitor集羣採用的租約機制,可以允許任何Monitor可以從OSD或者Client請求Cluster Map拷貝。這種請求很少由OSD發起,因爲搶先的Map共享,Client通常請求Update只發生在OSD操作超時,或者可能發生錯誤的時候。Monitor集羣能夠分發這些複製到更大的集羣。

需要Map update的請求會被轉發給當前的Leader。Lead會彙集多個根系到一個Map更新,Map Update的頻率是可以調節的,而且是與Cluster的大小不相關的。儘管如此,最壞的負載發生在,當大量的OSD在一個短時間內同時發生故障。如果一個OSD存放了u個PG,並且有f個OSD故障,就會有最多uf個錯誤報告產生。當OSD比較大時,這些消息會非常多。爲了防止消息飯量,OSD會在僞隨機的時間間隔發送心跳報文,保證陸續檢測到這些錯誤,壓制和分配的報告錯誤。那些不是Leader的monitor只會針對一個報錯轉發一次,這樣Leader的請求負載就相應的爲fm,其中m爲集羣中monitor的數量。

5 部分評估

通過使用對象存儲層(EBOFS)的性能,結合ceph到每個OSD性能已經被預先測量. 同樣,數據分配性能CRUSH以及它們對總吞吐量集羣效應在別處已經評估過。在這短短的文章中,我們只注重map分配,因爲這會直接影響集羣的擴展能力。我們還沒有實驗評估監控羣集性能,雖然我們信心架構的可擴展性。

5.1 Map 傳播

rados分佈算法在2.4節已經講過了,map更新logn次便可以更新完畢.
當集羣增多,設備故障就好更加頻發,更新次數就好增加.因爲map更新和交互只發生在共享相同PG的OSD間,上限正比於單個OSD接收上的副本個數.

In simulations under near-worst case propagation circumstances with regular map updates, we found that update duplicates approach a steady state even with exponential cluster scaling. In this experiment, the monitors share each map update with a single random OSD, who then shares it with its peers. In Figure 3 we vary the cluster size x and the number of PGs on each OSD (which corresponds to the number of peers it has) and measure the number of duplicate map updates received for every new one (y).Update duplication approaches a constant level—less than 20% of μ—even as the cluster size scales exponentially, implying a fixed map distribution overhead.

We consider a worst case scenario in which the only OSD chatter are pings for failure detection, which means that, generally speaking,OSDs learn about map updates (and the changes known by their peers) as slowly as possible. Limiting map distribution overhead thus relies only on throttling the map update frequency, which the monitor cluster already does as a matter of course.
這裏寫圖片描述

6 未來工作

6.1 Key-value 存儲

6.2 可擴展的FIFO隊列

6.3 對象的粒度快照

6.4 服務質量

6.5 基於奇偶校驗的冗餘

7參考:

[1]Ceph學習–RADOS論文
http://www.yidianzixun.com/news_10373a5b3e9591ba337dc85059854d90
[2]RADOS論文
http://blog.csdn.net/agony000/article/details/22697283
[3]RADOS:一種可擴展高可用的PB級存儲集羣(Ceph)
http://blog.csdn.net/user_friendly/article/details/9768577
[4]Weil S A, Leung A W, Brandt S A, et al. RADOS: a scalable, reliable storage service for petabyte-scale storage clusters[C]// International Workshop on Petascale Data Storage: Held in Conjunction with Supercomputing. ACM, 2007:35-44.

發佈了118 篇原創文章 · 獲贊 16 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章