新聞中心
HBase原理 – 分布式系統(tǒng)中Snapshot是怎么玩的?
作者:佚名 2017-09-19 14:13:53
運(yùn)維
數(shù)據(jù)庫(kù)運(yùn)維
分布式 一個(gè)snapshot是一個(gè)全部文件系統(tǒng)、或者某個(gè)目錄在某一時(shí)刻的鏡像。實(shí)現(xiàn)數(shù)據(jù)文件鏡像最簡(jiǎn)單粗暴的方式是加鎖拷貝,拷貝的這段時(shí)間不允許對(duì)原數(shù)據(jù)進(jìn)行任何形式的更新刪除,僅提供只讀操作,拷貝完成之后再釋放鎖。這種方式涉及數(shù)據(jù)的實(shí)際拷貝,數(shù)據(jù)量大的情況下必然會(huì)花費(fèi)大量時(shí)間,長(zhǎng)時(shí)間的加鎖拷貝必然導(dǎo)致客戶端長(zhǎng)時(shí)間不能更新刪除。

snapshot(快照)基礎(chǔ)原理
snapshot是很多存儲(chǔ)系統(tǒng)和數(shù)據(jù)庫(kù)系統(tǒng)都支持的功能。一個(gè)snapshot是一個(gè)全部文件系統(tǒng)、或者某個(gè)目錄在某一時(shí)刻的鏡像。實(shí)現(xiàn)數(shù)據(jù)文件鏡像最簡(jiǎn)單粗暴的方式是加鎖拷貝(之所以需要加鎖,是因?yàn)殓R像得到的數(shù)據(jù)必須是某一時(shí)刻完全一致的數(shù)據(jù)),拷貝的這段時(shí)間不允許對(duì)原數(shù)據(jù)進(jìn)行任何形式的更新刪除,僅提供只讀操作,拷貝完成之后再釋放鎖。這種方式涉及數(shù)據(jù)的實(shí)際拷貝,數(shù)據(jù)量大的情況下必然會(huì)花費(fèi)大量時(shí)間,長(zhǎng)時(shí)間的加鎖拷貝必然導(dǎo)致客戶端長(zhǎng)時(shí)間不能更新刪除,這是生產(chǎn)線上不能容忍的。
snapshot機(jī)制并不會(huì)拷貝數(shù)據(jù),可以理解為它是原數(shù)據(jù)的一份指針。在HBase這種LSM類型系統(tǒng)結(jié)構(gòu)下是比較容易理解的,我們知道HBase數(shù)據(jù)文件一旦落到磁盤之后就不再允許更新刪除等原地修改操作,如果想更新刪除的話可以追加寫入新文件(HBase中根本沒(méi)有更新接口,刪除命令也是追加寫入)。這種機(jī)制下實(shí)現(xiàn)某個(gè)表的snapshot只需要給當(dāng)前表的所有文件分別新建一個(gè)引用(指針),其他新寫入的數(shù)據(jù)重新創(chuàng)建一個(gè)新文件寫入即可。如下圖所示:
snapshot流程主要涉及3個(gè)步驟:
- 加一把全局鎖,此時(shí)不允許任何的數(shù)據(jù)寫入更新以及刪除
- 將Memstore中的緩存數(shù)據(jù)flush到文件中(可選)
- 為所有HFile文件分別新建引用指針,這些指針元數(shù)據(jù)就是snapshot
擴(kuò)展思考:LSM類系統(tǒng)確實(shí)比較容易理解,那其他非LSM系統(tǒng)原地更新的存儲(chǔ)系統(tǒng)如何實(shí)現(xiàn)snapshot呢?
snapshot能實(shí)現(xiàn)什么功能?
snapshot是HBase非常核心的一個(gè)功能,使用snapshot的不同用法可以實(shí)現(xiàn)很多功能,比如:
全量/增量備份:任何數(shù)據(jù)庫(kù)都需要有備份的功能來(lái)實(shí)現(xiàn)數(shù)據(jù)的高可靠性,snapshot可以非常方便的實(shí)現(xiàn)表的在線備份功能,并且對(duì)在線業(yè)務(wù)請(qǐng)求影響非常小。使用備份數(shù)據(jù),用戶可以在異常發(fā)生的情況下快速回滾到指定快照點(diǎn)。增量備份會(huì)在全量備份的基礎(chǔ)上使用binlog進(jìn)行周期性的增量備份。
使用場(chǎng)景一:通常情況下,對(duì)重要的業(yè)務(wù)數(shù)據(jù),建議至少每天執(zhí)行一次snapshot來(lái)保存數(shù)據(jù)的快照記錄,并且定期清理過(guò)期快照,這樣如果業(yè)務(wù)發(fā)生重要錯(cuò)誤需要回滾的話是可以回滾到之前的一個(gè)快照點(diǎn)的。
使用場(chǎng)景二:如果要對(duì)集群做重大的升級(jí)的話,建議升級(jí)前對(duì)重要的表執(zhí)行一次snapshot,一旦升級(jí)有任何異??梢钥焖倩貪L到升級(jí)前。
2. 數(shù)據(jù)遷移:可以使用ExportSnapshot功能將快照導(dǎo)出到另一個(gè)集群,實(shí)現(xiàn)數(shù)據(jù)的遷移
使用場(chǎng)景一:機(jī)房在線遷移,通常情況是數(shù)據(jù)在A機(jī)房,因?yàn)锳機(jī)房機(jī)位不夠或者機(jī)架不夠需要將整個(gè)集群遷移到另一個(gè)容量更大的B集群,而且在遷移過(guò)程中不能停服?;具w移思路是先使用snapshot在B集群恢復(fù)出一個(gè)全量數(shù)據(jù),再使用replication技術(shù)增量復(fù)制A集群的更新數(shù)據(jù),等待兩個(gè)集群數(shù)據(jù)一致之后將客戶端請(qǐng)求重定向到B機(jī)房。具體步驟可以參考: https://www.cloudera.com/documentation/enterprise/5-5-x/topics/cdh_bdr_hbase_replication.html#topic_20_11_7
使用場(chǎng)景二:使用snapshot將表數(shù)據(jù)導(dǎo)出到HDFS,再使用Hive\Spark等進(jìn)行離線OLAP分析,比如審計(jì)報(bào)表、月度報(bào)表等
hbase snapshot用法大全
snapshot最常用的命令有snapshot、restore_snapshot、clone_snapshot以及ExportSnapshot這個(gè)工具,具體使用方法如下:
為表’sourceTable’打一個(gè)快照’snapshotName’,快照并不涉及數(shù)據(jù)移動(dòng),可以在線完成。
- hbase> snapshot 'sourceTable', ‘snapshotName'
恢復(fù)指定快照,恢復(fù)過(guò)程會(huì)替代原有數(shù)據(jù),將表還原到快照點(diǎn),快照點(diǎn)之后的所有更新將會(huì)丟失。需要注意的是原表需要先disable掉,才能執(zhí)行restore_snapshot操作。
- hbase> restore_snapshot ‘snapshotName'
根據(jù)快照恢復(fù)出一個(gè)新表,恢復(fù)過(guò)程不涉及數(shù)據(jù)移動(dòng),可以在秒級(jí)完成。很好奇是怎么做的吧,且聽下文分解。
- hbase> clone_snapshot 'snapshotName', ‘tableName'
使用ExportSnapshot命令可以將A集群的快照數(shù)據(jù)遷移到B集群,ExportSnapshot是HDFS層面的操作,會(huì)使用MR進(jìn)行數(shù)據(jù)的并行遷移,因此需要在開啟MR的機(jī)器上進(jìn)行遷移。HMaster和HRegionServer并不參與這個(gè)過(guò)程,因此不會(huì)帶來(lái)額外的內(nèi)存開銷以及GC開銷。唯一的影響是DN在拷貝數(shù)據(jù)的時(shí)候需要額外的帶寬以及IO負(fù)載,ExportSnapshot也針對(duì)這個(gè)問(wèn)題設(shè)置了參數(shù)-bandwidth來(lái)限制帶寬的使用。
- hbase org.apache.hadoop.hbase.snapshot.ExportSnapshot \
- -snapshot MySnapshot -copy-from hdfs://srv2:8082/hbase \
- -copy-to hdfs://srv1:50070/hbase -mappers 16 -bandwidth 1024\
hbase snapshot分布式架構(gòu)-兩階段提交
hbase為指定表執(zhí)行snapshot操作,實(shí)際上真正執(zhí)行snapshot的是對(duì)應(yīng)表的所有region。這些region因?yàn)榉植荚诙鄠€(gè)RegionServer上,所以需要一種機(jī)制來(lái)保證所有參與執(zhí)行snapshot的region要么全部完成,要么都沒(méi)有開始做,不能出現(xiàn)中間狀態(tài),比如某些region完成了,某些region未完成。
HBase使用兩階段提交協(xié)議(2PC)來(lái)保證snapshot的分布式原子性。2PC一般由一個(gè)協(xié)調(diào)者和多個(gè)參與者組成,整個(gè)事務(wù)提交分為兩個(gè)階段:prepare階段和commit階段。其中prepare階段協(xié)調(diào)者會(huì)向所有參與者發(fā)送prepare命令,所有參與者開始獲取相應(yīng)資源(比如鎖資源)并執(zhí)行prepare操作確認(rèn)可以執(zhí)行成功,通常核心工作都是在prepare操作中完成的。并返回給協(xié)調(diào)者prepared應(yīng)答。協(xié)調(diào)者接收到所有參與者返回的prepared應(yīng)答之后(表明所有參與者都已經(jīng)準(zhǔn)備好提交),在本地持久化commit狀態(tài),進(jìn)入commit階段,協(xié)調(diào)者會(huì)向所有參與者發(fā)送commit命令,參與者接收到commit命令之后會(huì)執(zhí)行commit操作并釋放資源,通常commit操作都非常簡(jiǎn)單。
接下來(lái)就看看hbase是如何使用2PC協(xié)議來(lái)構(gòu)建snapshot架構(gòu)的,基本步驟如下:
1. prepare階段:HMaster在zookeeper創(chuàng)建一個(gè)’/acquired-snapshotname’節(jié)點(diǎn),并在此節(jié)點(diǎn)上寫入snapshot相關(guān)信息(snapshot表信息)。所有regionserver監(jiān)測(cè)到這個(gè)節(jié)點(diǎn)之后,根據(jù)/acquired-snapshotname節(jié)點(diǎn)攜帶的snapshot表信息查看當(dāng)前regionserver上是否存在目標(biāo)表,如果不存在,就忽略該命令。如果存在,遍歷目標(biāo)表中的所有region,分別針對(duì)每個(gè)region執(zhí)行snapshot操作,注意此處snapshot操作的結(jié)果并沒(méi)有寫入最終文件夾,而是寫入臨時(shí)文件夾。regionserver執(zhí)行完成之后會(huì)在/acquired-snapshotname節(jié)點(diǎn)下新建一個(gè)子節(jié)點(diǎn)/acquired-snapshotname/nodex,表示nodex節(jié)點(diǎn)完成了該regionserver上所有相關(guān)region的snapshot準(zhǔn)備工作。
2. commit階段:一旦所有regionserver都完成了snapshot的prepared工作,即都在/acquired-snapshotname節(jié)點(diǎn)下新建了對(duì)應(yīng)子節(jié)點(diǎn),hmaster就認(rèn)為snapshot的準(zhǔn)備工作完全完成。master會(huì)新建一個(gè)新的節(jié)點(diǎn)/reached-snapshotname,表示發(fā)送一個(gè)commit命令給參與的regionserver。所有regionserver監(jiān)測(cè)到/reached-snapshotname節(jié)點(diǎn)之后,執(zhí)行snapshot commit操作,commit操作非常簡(jiǎn)單,只需要將prepare階段生成的結(jié)果從臨時(shí)文件夾移動(dòng)到最終文件夾即可。執(zhí)行完成之后在/reached-snapshotname節(jié)點(diǎn)下新建子節(jié)點(diǎn)/reached-snapshotname/nodex,表示節(jié)點(diǎn)nodex完成snapshot工作。
3. abort階段:如果在一定時(shí)間內(nèi)/acquired-snapshotname節(jié)點(diǎn)個(gè)數(shù)沒(méi)有滿足條件(還有regionserver的準(zhǔn)備工作沒(méi)有完成),hmaster認(rèn)為snapshot的準(zhǔn)備工作超時(shí)。hmaster會(huì)新建另一種新的節(jié)點(diǎn)/abort-snapshotname,所有regionserver監(jiān)聽到這個(gè)命令之后會(huì)清理snapshot在臨時(shí)文件夾中生成的結(jié)果。
可以看到,在這個(gè)系統(tǒng)中HMaster充當(dāng)了協(xié)調(diào)者的角色,RegionServer充當(dāng)了參與者的角色。HMaster和RegionServer之間的通信通過(guò)Zookeeper來(lái)完成,同時(shí),事務(wù)狀態(tài)也是記錄在Zookeeper上的節(jié)點(diǎn)上。HMaster高可用情況下主HMaster宕機(jī)了,從HMaster切成主后根據(jù)Zookeeper上的狀態(tài)可以決定事務(wù)十分繼續(xù)提交或者abort。
snapshot核心實(shí)現(xiàn)
上節(jié)從架構(gòu)層面介紹了snapshot如何在分布式體系中完成原子性操作。那每個(gè)region是如何真正實(shí)現(xiàn)snapshot呢?hmaster又是如何匯總所有region snapshot結(jié)果?
region如何實(shí)現(xiàn)snapshot?
在基本原理一節(jié)我們提到過(guò)snapshot不會(huì)真正拷貝數(shù)據(jù),而是使用指針引用的方式創(chuàng)建一系列元數(shù)據(jù)。那元數(shù)據(jù)具體是什么樣的元數(shù)據(jù)呢?實(shí)際上snapshot的整個(gè)流程基本如下:
分別對(duì)應(yīng)debug日志中如下片段:
- snapshot.FlushSnapshotSubprocedure: Flush Snapshotting region yixin:yunxin,user1359,1502949275629.77f4ac61c4db0be9075669726f3b72e6. started...
- snapshot.SnapshotManifest: Storing 'yixin:yunxin,user1359,1502949275629.77f4ac61c4db0be9075669726f3b72e6.' region-info for snapshot.
- snapshot.SnapshotManifest: Creating references for hfiles
- snapshot.SnapshotManifest: Adding snapshot references for [] hfiles
注意:region生成的snapshot文件是臨時(shí)文件,生成目錄在/hbase/.hbase-snapshot/.tmp下,一般因?yàn)閟napshot過(guò)程特別快,所以很難看到單個(gè)region生成的snapshot文件。
hmaster如何匯總所有region snapshot的結(jié)果?
hmaster會(huì)在所有region完成snapshot之后執(zhí)行一個(gè)匯總操作(consolidate),將所有region snapshot manifest匯總成一個(gè)單獨(dú)manifest,匯總后的snapshot文件是可以在HDFS目錄下看到的,路徑為:/hbase/.hbase-snapshot/snapshotname/data.manifest。注意,snapshot目錄下有3個(gè)文件,如下圖所示:
其中.snapshotinfo為snapshot基本信息,包含待snapshot的表名稱以及snapshot名;data.manifest為snapshot執(zhí)行后生成的元數(shù)據(jù)信息,即snapshot結(jié)果信息??梢允褂胔adoop dfs -cat /hbase/.hbase-snapshot/snapshotname/data.manifest 查看:
clone_snapshot如何實(shí)現(xiàn)呢?
前文提到snapshot可以用來(lái)搞很多大事情,比如restore_snapshot、clone_snapshot以及export snapshot等等,這節(jié)就來(lái)看看clone_snapshot這個(gè)功能具體是如何實(shí)現(xiàn)的。直接進(jìn)入正題,整個(gè)步驟可以概括為如下:
- 預(yù)檢查:確認(rèn)目標(biāo)表沒(méi)有進(jìn)行snapshot操作以及restore操作,否則直接返回錯(cuò)誤
- 在tmp文件夾下新建表目錄并在表目錄下新建.tabledesc文件,在該文件中寫入表schema信息
- 新建region目錄:這個(gè)步驟是clone_snapshot和create table最大的不同,新建的region目錄是依據(jù)snapshot manifest中信息確定的,region中有哪些列族?列族中有哪些HFile文件?都來(lái)源于此。
此處有一個(gè)很有意思的事情是clone_snapshot克隆表的過(guò)程中并不涉及數(shù)據(jù)的移動(dòng),那不禁要問(wèn)克隆出的表中文件是什么文件?與原表中數(shù)據(jù)文件之間的對(duì)應(yīng)關(guān)系如何建立?這個(gè)問(wèn)題的解決和split過(guò)程中reference文件的解決思路基本一致,不過(guò)在clone_snapshot中并不稱作reference文件,而叫做linkfile,和reference文件不一樣的是linkfile文件沒(méi)有任何內(nèi)容,只是在文件名上做了文章,比如原文件名是abc,生成的linkfile就為:table=region-abc,通過(guò)這種方式就可以很容易定位到原表中原始文件的具體路徑:xxx/table/region/hfile,因此就可以不需要移動(dòng)數(shù)據(jù)了。
上圖中LinkFile文件名為music=5e54d8620eae123761e5290e618d556b-f928e045bb1e41ecbef6fc28ec2d5712,根據(jù)定義我們知道m(xù)usic為原始文件的表名,5e54d8620eae123761e5290e618d556b為引用文件所在的region,f928e045bb1e41ecbef6fc28ec2d5712為引用文件,如下圖所示:
我們可以依據(jù)規(guī)則可以直接根據(jù)LinkFile的文件名定位到引用文件所在位置:***/music/5e54d8620eae123761e5290e618d556b/cf/f928e045bb1e41ecbef6fc28ec2d5712,如下圖所示:
4. 將表目錄從tmp文件夾下移動(dòng)到hbase root location
5. 修改meta表,將克隆表的region信息添加到meta表中,注意克隆表的region名稱和原數(shù)據(jù)表的region名稱并不相同(region名稱與table名稱相關(guān),table名不同,region名稱就肯定不會(huì)相同)
6. 將這些region通過(guò)round-robin方式立刻均勻分配到整個(gè)集群中,并在zk上將克隆表的狀態(tài)設(shè)置為enabled,正式對(duì)外提供服務(wù)
其他需要注意的
不知道大家有沒(méi)有關(guān)注另一個(gè)問(wèn)題,按照上文的說(shuō)法我們知道snapshot實(shí)際上是一系列原始表的元數(shù)據(jù),主要包括表schema信息、原始表所有region的region info信息,region包含的列族信息以及region下所有的hfile文件名以及文件大小等。那如果原始表發(fā)生了compaction導(dǎo)致hfile文件名發(fā)生了變化或者region發(fā)生了分裂,甚至刪除了原始表,之前所做的snapshot是否就失效了?
從功能實(shí)現(xiàn)的角度來(lái)講肯定不會(huì)讓用戶任何時(shí)間點(diǎn)所作的snapshot失效,那如何避免上述所列的各種情況下snapshot失效呢?HBase的實(shí)現(xiàn)也比較簡(jiǎn)單,在原始表發(fā)生compact的操作前會(huì)將原始表復(fù)制到archive目錄下再執(zhí)行compact(對(duì)于表刪除操作,正常情況也會(huì)將刪除表數(shù)據(jù)移動(dòng)到archive目錄下),這樣snapshot對(duì)應(yīng)的元數(shù)據(jù)就不會(huì)失去意義,只不過(guò)原始數(shù)據(jù)不再存在于數(shù)據(jù)目錄下,而是移動(dòng)到了archive目錄下。
大家可以做一下這樣一個(gè)實(shí)驗(yàn)看看:
- 使用snapshot給一張表做快照,比如snapshot ’test’,’test_snapshot’
- 查看archive目錄,確認(rèn)不存在目錄:/hbase-root-dir/archive/data/default/test
- 對(duì)表test執(zhí)行major_compact操作:major_compact ’test’
- 再次查看archive目錄,就會(huì)發(fā)現(xiàn)test原始表移動(dòng)到了該目錄,/hbase-root-dir/archive/data/default/test就會(huì)存在
同理,如果對(duì)原始表執(zhí)行delete操作,比如delete ’test’,也會(huì)在archive目錄下找到該目錄。和普通表刪除的情況不同的是,普通表一旦刪除,剛開始是可以在archive中看到刪除表的數(shù)據(jù)文件,但是等待一段時(shí)間后archive中的數(shù)據(jù)就會(huì)被徹底刪除,再也無(wú)法找回。這是因?yàn)閙aster上會(huì)啟動(dòng)一個(gè)定期清理archive中垃圾文件的線程(HFileCleaner),定期會(huì)對(duì)這些被刪除的垃圾文件進(jìn)行清理。但是snapshot原始表被刪除之后進(jìn)入archive,并不可以被定期清理掉,上文說(shuō)過(guò)clone出來(lái)的新表并沒(méi)有clone真正的文件,而是生成的指向原始文件的連接,這類文件稱之為L(zhǎng)inkFile,很顯然,只要LinkFile還指向這些原始文件,它們就不可以被刪除。好了,這里有兩個(gè)問(wèn)題:
1. 什么時(shí)候LinkFile會(huì)變成真實(shí)的數(shù)據(jù)文件?
如果看過(guò)筆者上篇文章《HBase原理 – 所有Region切分的細(xì)節(jié)都在這里了》的同學(xué),肯定看著這個(gè)問(wèn)題有種似曾相識(shí)的趕腳。不錯(cuò),HBase中一個(gè)region分裂成兩個(gè)子region后,子region的文件也是引用文件,這些引用文件是在執(zhí)行compact的時(shí)候才真正將父region中的文件遷移到自己的文件目錄下。LinkFile也一樣,在clone出的新表執(zhí)行compact的時(shí)候才將合并后的文件寫到新目錄并將相關(guān)的LinkFile刪除,理論上也是借著compact順便做了這件事。
2. 系統(tǒng)在刪除archive中原始表文件的時(shí)候怎么知道這些文件還被一些LinkFile引用著?
HBase Split后系統(tǒng)要?jiǎng)h除父region的數(shù)據(jù)文件,是首先要確認(rèn)兩個(gè)子region已經(jīng)沒(méi)有引用文件指向它了,系統(tǒng)怎么確認(rèn)這點(diǎn)的呢?上節(jié)我們分析過(guò),meta表中會(huì)存儲(chǔ)父region對(duì)應(yīng)的兩個(gè)子region,再掃描兩個(gè)子region的所有文件確認(rèn)是否還有引用文件,如果已經(jīng)沒(méi)有引用文件了,就可以放心地將父region的數(shù)據(jù)文件刪掉了,當(dāng)然,如果還有引用文件存在就只能作罷。
那刪除clone后的原始表文件,是不是也是一樣的套路?答案并不是,HBase用了另一種方式來(lái)根據(jù)原始表文件找到引用文件,這就是back-reference機(jī)制。HBase系統(tǒng)在archive目錄下新建了一種新的back-reference文件,來(lái)幫助原始表文件找到引用文件。來(lái)看看back-reference文件是一種什么樣的文件,它是如何根據(jù)原始文件定位到LinkFile的:
- (1)原始文件:/hbase/data/table-x/region-x/cf/file-x
- (2)clone生成的LinkFile:/hbase/data/table-cloned/region-y/cf/{table-x}-{region-x}-{file-x},因此可以很容易根據(jù)LinkFile定位到原始文件
- (3)back-reference文件:/hbase/.archive/data/table-x/region-x/cf/.links-file-x/{region-y}.{table-cloned},可以看到,back-reference文件路徑中包含所有原始文件和LinkFile的信息,因此可以有效的根據(jù)原始文件/table-x/region-x/cf/file-x定位到LinkFile:/table-cloned/region-y/cf/{table-x}-{region-x}-{file-x}
到這里,有興趣的童鞋可以將這塊知識(shí)點(diǎn)串起來(lái)做個(gè)簡(jiǎn)單的小實(shí)驗(yàn):
(1)使用snapshot給一張表做快照,比如snapshot ’table-x’,’table-x-snapshot’
(2)使用clone_snapshot克隆出一張新表,比如clone_snapshot ’table-x-snapshot’,’table-x-cloned’。并查看新表test_clone的HDFS文件目錄,確認(rèn)會(huì)存在LinkFile
(3)刪除原表table-x(刪表之前先確認(rèn)archive下沒(méi)有原表文件),查看確認(rèn)原表文件進(jìn)入archive,并在archive中存在back-reference文件。注意瞅瞅back-reference文件格式哈。
(4)對(duì)表’table-x-clone’執(zhí)行major_compact,命令為major_compact ’test_clone’。執(zhí)行命令前確認(rèn)table-x-clone文件目錄下LinkFile存在。
(5)major_compact執(zhí)行完成之后查看table-x-clone的HDFS文件目錄,確認(rèn)所有LinkFile已經(jīng)不再存在,全部變成了真實(shí)數(shù)據(jù)文件。
名稱欄目:HBase原理–分布式系統(tǒng)中Snapshot是怎么玩的?
網(wǎng)站網(wǎng)址:http://m.fisionsoft.com.cn/article/cocegco.html


咨詢
建站咨詢
