1. 程式人生 > >那些年,我們遷移過的大資料叢集

那些年,我們遷移過的大資料叢集

大資料叢集遷移這件事,不知道有多少同學做過。我說的不是把一個叢集的資料備份到另一個叢集上。我指的是整個資料平臺與大資料相關的所有叢集及業務的遷移工作,從一個機房到另一個機房。

具體範圍可能包括:從離線計算叢集到實時計算叢集;從儲存,計算元件,到作業排程,開發平臺服務;從底層資料同步到上層業務遷移。要求:不能影響業務,幾乎沒有停服預算。。。

這事,這要求,但凡玩過大資料平臺的同學,應該都不難想象,一定是個吃力不討好,還要背故障的苦差事。不幸的是,這樣的苦差事,出於各種各樣的原因,最近兩年來,我們一共幹了三次。。。


所以,這篇文章,我打算和大家一起討論分享一下那些不堪回首的歲月裡,我們遷移過的叢集。以備哪一天,萬一,你也攤上事了,也好有所準備,看看有那些工作需要做的,儘量不要死得太過悲慘。

都有哪些麻煩事

要說這事有多苦,那得從解放前的那個晚上。。。哦,不,底層的環境開始向上說起。


叢集和機房外部環境問題

歷數三次搬遷工作,一次是同城機房搬遷,另外兩次是異地機房搬遷。其中一次同城和一次異地,機房間有10-20Gb不等的專線頻寬可供使用;另外一次異地搬遷,機房間只有大概1Gb的公網頻寬。。。

需要搬遷的叢集的整體規模,大概在200到400個節點之間,所涉及到的叢集型別,包括,HDFS/HBase等儲存叢集,以及Yarn/Storm/Spark等計算叢集

資料量呢,具備專線環境的這兩次搬遷,基本上叢集上的歷史全量資料按單拷貝來算大概2-3個PB,佔用HDFS叢集容量6-7個P左右,而叢集每日的資料增量規模大概在十幾到幾十個TB的樣子。

可以粗略估算一下,如果全部歷史資料走網路傳輸,純粹的拷貝傳輸動作,平均跑滿10Gb頻寬的話,大概要花費二十幾天的時間才能拷貝完成2P的資料,如果這期間資料拷貝出了問題。。。而做一次單日的增量拷貝動作,根據資料量變化大小的不同,大概也要花費3-8個小時的時間。

那麼如何在可接受的時間和空間資源內,及時,正確的完成資料的同步工作呢?

平臺自身元件和服務依賴問題

講完叢集外部環境問題,接下來看看開發平臺自身的元件和服務依賴。

我司的大資料開發平臺,自身元件眾多,和外部系統也有著千絲萬縷的關聯。

以離線批處理業務流程為例:

  • 首先,會有資料採集系統負責從各種外部資料來源比如,日誌,DB,訊息佇列中以全量或增量的方式將資料採集到叢集中來,

  • 其次會有排程系統將各種不同型別的作業分發到不同的Worker和叢集上去執行,而作業的來源,包括週期性排程的作業,也包括從開發平臺上發起的臨時作業,還包括通過我們對外提供的服務介面,由外部業務系統通過程式自動觸發的週期或一次性作業

  • 然後,會有資料交換系統將資料匯出到其它各個目標資料來源中,比如報表DB,各種業務DB,HBase叢集,ES叢集等等。

  • 此外,平臺的報警監控服務,訊息通知服務,元資料管理服務,許可權管控服務,資料視覺化服務,資料質量監控服務等等,往往也是互相依賴的。

實時業務流程和離線業務流程相比,整體鏈路長度可能會更短,但是和外部系統的關聯倒並不見得更簡單,而服務的容錯性和對網路頻寬延遲等方面的要求往往也會更高一些。

所以,如何保證遷移期間,服務的穩定可靠,儘可能減少服務下線或不可用的時間,如何保證各種依賴服務的平滑過渡銜接呢?

業務模式和流程配合問題

說完服務,接下來說說業務方使用我們服務的模式,又會給遷移工作帶來哪些麻煩。

如果所有的業務都由平臺完全掌控,事情會好辦一些,但作為資料平臺基礎架構團隊,我們的定位是提供平臺服務,所以,絕大多數的業務都是由業務方透過我們的服務來自主執行和管理的。那就會存在服務用多用少,用好用壞的問題

比如,可能某個業務方的一個完整業務鏈路,一半的流程是和他們自身的業務系統緊耦合的,在它們自己的平臺和機器上執行,另一半的流程則零散的透過我們的服務來執行,甚至由於各種各樣的原因跳過我們的平臺排程體系和服務,直接使用底層的應用介面和叢集進行互動(比如,具體的業務邏輯和資料處理邏輯強關聯,程式碼邏輯無法拆分,不能模組化或著不想重構改造成通過我們的服務介面來處理資料,直接讀寫HDFS,直接提交MR任務等等)

如果整個流程鏈路是自封閉的,自產自銷也就罷了,壞就壞在這些業務保不定還有些上下游依賴,需要和其它業務方的作業相串聯。更糟糕的是,有時候,這些上下游依賴方並沒有意識到對方的存在,再加上網際網路公司難免出現的業務變更,組織架構調整,工作交接,遇上這種情況,事情就更加嚴重了。

這種情況,往往很多業務就需要具體的業務方配合梳理或者改造才能順利完成遷移,可是業務方對這類工作一般都是拒絕的,很容易理解,大家都忙,都希望麻煩越少越好不是,你們遷移,別拖我們下水啊 ;)好吧,這怎麼辦?也只能動之以情,曉之以公司大義了唄 ;)


總之,這種情況下,如何降低風險,確保業務在遷移過程中不會出現大的差錯,的確是個大問題。

業務邏輯和資料正確性問題

最後,是業務邏輯和由此帶來的資料正確性問題。遷移,不光遷,遷完以後,你得保證業務結果的正確性吧?

在海量資料的情況下,如何驗證資料,這本身已經是個很棘手的問題了,更糟糕的是,如果業務方自己都搞不清楚資料是否正確怎麼辦? 甚至同一個作業,在同樣的資料集上跑兩次,結果都不一樣,重跑作業也不冪等又怎麼辦?再糟糕一點,連資料集自身的狀態,可能也和時間相關,隨時可能變化怎麼辦?

所以,你如何驗證遷移的結果是正確的,或者有哪些業務是正確的?有多大的概率是正確的?誰能替結果負責? 萬一某個業務真的有問題,能不能發現,怎麼發現,具體又會是資料,指令碼,叢集哪個環節的問題?

那該怎麼辦

總之,大資料平臺搬遷工作並不是光“叢集” 搬遷 這麼簡單,它是一個你享受過一次以後,就絕對不想再來一次的苦差事。然而,說這麼多,並沒有什麼用。日子再苦,也得過不是。接下來,讓我來針對上述具體問題,和大家一起探討一下應對的方式和幾次搬遷過程中的我們的經驗教訓。


總體目標和原則

日子怎麼過,上述問題如何應對,取決於你的目標是什麼。一旦決定了目標,為了順利達到,也就會有一些原則是不能輕易打破,要時時刻刻留心遵守的。

所以,我們的目標是:

  • 整體遷移工作,一到兩個月的週期內完成

  • 遷移期間,大資料平臺的各種服務不能長時間下線(最多小時級別),不能對公司業務造成影響。

  • 必須確保遷移完成後,核心業務的正確性,不能靠運氣,要有足夠可靠的驗證手段和資料

  • 對於和外部系統重度耦合的業務,需要給業務方足夠的時間,正確的環境和過渡手段分批逐步遷移

  • 遷移過程,儘可能做到對多數業務方透明,減少需要業務方需要配合的工作

那麼,原則有哪些呢:

  • 一切遷移工作和步驟,不以難易為標準,以不對線上業務造成影響為標準

  • 凡是可能出錯,不能一步做到位的環節,都必須要有事前驗證測試的手段

  • 只要能夠雙跑的環節那就雙跑,寧可花費更多的精力準備並行方案,也不能寄希望於一切順利

  • 具體的雙跑方案,要確保與最終完成遷移,停止雙跑後的流程最大限度的保持一致,減少切換帶來的變數。

  • 不做一錘子買賣,直到完成叢集切換,資料和業務正確性驗證完畢,正式開始對使用者提供服務之前,都要給自己留下後路,堅決不做任何不可逆的操作

  • 過程和步驟,能自動化的自動化,不能自動化的,也要明確的文件化和標準化,不能依靠臨場的隨機應變。

好吧,你可能會說,這些,不是廢話麼,必需做到呀。。。是的,如果只是光站著說說的話,那的確如此。

但是當你真正面對這項棘手的工作的時候,你就隨時都有可能就把這些目標原則拋在腦後。畢竟,沒有人想要主動給自己找麻煩,所以,這個時候,你只有時刻告誡自己,如果不這麼做,一旦出了問題,只會更加的麻煩 ;)

大致流程方案

要在預期的時間範圍內,風險和代價可控的完成遷移的工作,光靠跨機房網路這點頻寬進行同步肯定是不現實的。所以,我們的整體遷移流程大致如下

  • 分離歷史資料,在源機房內部搭建中轉叢集,先做一次歷史大全量資料的拷貝工作,受資料量規模限制,只同步那些確定不經常變更的資料,然後下線中轉叢集,物理搬遷到目標機房,再次上線同步到目標叢集中

  • 在歷史資料同步過程中,在目標機房搭建資料平臺的全套叢集和服務,逐個驗證各個服務功能的正確性

  • 完成初始的大全量資料拷貝工作後,開始通過網路實施若干輪階段性小全量資料拷貝工作,目標是將資料同步時間逐步縮短到當天能同步完成截止前一天為止的資料(因為第一輪全量拷貝的同步週期會比較長,期間叢集新增的資料無法在一天內透過網路完成拷貝)

  • 使用實際的歷史資料,驗證叢集服務和效能

  • 開始叢集每日增量資料同步工作,同時,同步各種資料平臺服務自身的元資料資訊和作業指令碼資訊,開啟作業雙跑流程

  • 每日核心作業雙跑完畢後,對比兩邊平臺的產出結果,排查問題,存在問題,修復,並繼續下一輪雙跑工作,如此迴圈,直到結果驗證滿意為止。

  • 正式切換各種對外服務的域名,介面,資料庫等到新機房,完成主要鏈路的遷移工作。

  • 切換完畢後,保留原平臺整體業務按既有邏輯運轉一段時間,給部分因為各種原因無法雙跑或立刻切換的業務留下分批遷移的時間視窗。

上述方案,主要描述的是偏離線批處理業務的遷移流程,實時類業務,由於從業務邏輯的角度來看,往往無法在統一的時間點上整體切換,所以更加強調分批雙跑的流程。具體的遷移工作,也往往需要業務方根據自己的業務情況參與配合,因此流程上有些環節需要具體業務具體討論,這裡就不再詳細闡述。

一些具體問題的分析和實踐

如何保證正確性?

你要問遷移工作中,哪部分工作最難? 我可以很負責任的告訴你,不是海量資料的同步,也不是服務的搭建,甚至也不是與各種關聯業務方無止境的溝通工作。

實際上,上述工作,儘管工作量很大,但只要花時間,總是能做好的。而最難,也最容易被輕視的,是你如何確保遷移完畢後,作業執行結果的正確性?

你可能會想,這還不簡單,前面不都說了兩邊機房同步進行作業的雙跑麼?那第二天比較作業執行的結果資料就好了啊,不對,查問題,查到對為止。。。然而,事情並沒有那麼美好,暫且放下怎麼比較結果不提,先讓我們來看看結果真的可以用來比較麼?

具體難在哪裡


首先,雙跑結果可以比較驗證的前提,是資料來源是一致的,但資料來源往往做不到一致。。。

先來看看資料平臺是如何採集外部資料的?資料平臺的上游資料來源有很多,但主要的來源是DB和日誌,這兩種資料來源根據業務場景不同,會有不同的採集方式

比如日誌可能通過客戶端Agent採集後,寫入Kafka訊息佇列,然後再消費解析寫入Hive。

而DB一方面可以通過Binlog採集進入訊息佇列,走日誌類似的流程消費,另一方面也可以直接連線DB,定時按一定的業務邏輯掃描源表後寫入資料平臺。採用哪種方式取決於資料量的大小,業務的更新模式等等。

那麼問題來了,這些資料來源是會隨著時間變化的,什麼時候執行這些資料採集任務的作業邏輯呢?任務執行的時間不同,採集到的結果就不一樣啊,而兩邊叢集具體某個任務的實際執行時間,受叢集資源,前序任務執行時間等隨機因素的干擾,是無法精確控制的。

你會說,判斷資料來源裡資訊的時間不就好了? 這裡面有三個問題:

  • 有些資料來源的採集邏輯裡沒有可以用來做精確更新判斷時間的依據資訊(不要問我為什麼,DB設計,業務邏輯,歷史遺留問題等等都有可能)

  • 自定義的清洗指令碼邏輯,或者自認為對具體時間資訊不敏感,或者沒有意識到會有問題,沒有做時間篩選。

  • 流程中做了資料的時間篩選判斷,但客戶端會有晚到的資料,會有時間錯誤的資料,前置鏈路會有延遲的情況等等。

其次,同樣的資料來源,雙跑的結果一致,還有一個要求是,作業執行邏輯是冪等的。 所謂冪等,這裡包含了兩層意思:一是隻要輸入源一致,作業每次執行的結果都因該是一樣的,二是重跑等情況對結果沒有影響。

但實際情況也不是這樣,不少作業邏輯並非冪等,執行兩次,結果並不能保證一致,一個叢集上跑如此,在兩邊叢集分別執行,那就更加無法保證了。


為什麼會出現非冪等作業的情況?比如有些作業邏輯對資料進行排序,然後Limit取部分值,而排序用的欄位組合,並非唯一標識一行資料的,也就是說在分散式計算的場景下,排序的結果順序可能是隨機的。再有,比如指令碼執行的邏輯是把上游的增量資料寫入下游的全量表中,如果因為各種原因執行了兩次,那就會寫入兩份資料等等。

雖然實際上這些差異可能對作業的業務邏輯結果的正確性不一定有很大影響(否則早就被發現並解決了),比如客戶端晚到的PV資料算昨天的還是今天的?每天都有部分移位的資料的話,量級上還補償上了呢。。。

但是這對雙跑結果的驗證,卻帶來了不小的麻煩,我們平臺上每天跑上萬個任務,產出幾千張結果表,雖然對應存在冪等或隨機問題的作業比例不會很高,但是經過作業依賴傳導以後,可能會對下游的大批作業都造成影響。那麼如果幾千張結果表的資料全都不一樣(雖然差異有大有小),你又如何判斷平臺遷移結果的正確性? 你顯然不可能去挨個人工分析每張表資料不一致的原因。

這時候,你可以對自己有信仰,相信只要叢集服務是正確的,就OK了,管它結果如何呢,一定都是上述原因造成的,無傷大雅。


說實話,這的確是一種解決方案。前提是,你的業務方認可,領導也認可。問題在於你如何說服他們相信叢集服務是正確的呢?請拿資料說話啊,可是資料都不一樣啊!你說差異是正常的,是由業務邏輯造成的,可是有差異的部分,業務方會替你背書麼?我看懸,而你自己,說實話,也未必真的心裡踏實。。。

可以採取的措施

所以,要降低風險,就必須儘可能的減少這兩者的干擾。實際上,我們前期做的大量準備工作都是圍繞著這個目標來的,具體方案上在幾次遷移過程中,也採用過不同的措施。詳細的細節不說,大致包括:

  • 資料來源部分不雙跑,單邊跑完,同步,再開始雙跑(然而,一來違反了雙跑和正式切換流程儘可能一致的原則,二來雙跑流程會變得冗長,影響正常業務和實施驗證的時間點,最近一次遷移,放棄了這種方案)

  • 提前梳理非冪等指令碼,邏輯上能夠修復的進行修復

  • 日誌採集鏈路,採用雙跑但是由源端單邊判斷和控制offset進度,確保兩邊資料的讀取範圍完全一致

  • DB鏈路上,適當調整執行時間,儘量規避由於鏈路延遲,業務更新等造成的資料晚到/變更的情況

通過這些手段,減少源頭資料的差異和計算過程的隨機性,最終我們能夠做到主要鏈路三千五百多張表格,90%左右的表,驗證結果我們認為完全一致,無需人工判斷。99%的表,差異比例在0.1%以下,只需要重點人工檢查極少量差異較大的表的結果邏輯,和部分核心關鍵表格的具體數值,加快了結果驗證的效率和可靠性。


驗證比較方式

最後再來說一下如何比較結果資料。

首先,結果資料分為兩類,一類是在叢集上的資料,主要以hive表為主。另一類是匯出到外部資料來源的資料,以DB為主,也有ES/HBASE等。

理論上你會說,那就一條一條的對比啊。但問題是,你如何確定用哪一條資料和哪一條資料進行對比?排序麼?怎麼排序?一方面,你不可能為每張表單獨構建比較邏輯,另一方面,海量的資料你是否有足夠的計算和儲存資源進行比較?你是否可能承擔相應的代價?

要確定哪條資料對比哪條資料,針對DB中的資料,我們的做法是拼合所有我們認為可能區別一行資料的欄位,對兩邊的表進行Join,然後根據Join後的結果進行值的比對。這種做法並不完全精確,因為無法保證拼合用的欄位就一定構成Unqiue key,能夠唯一標識每一行資料,還是可能造成錯位比較資料,將實際結果正確的表格誤判為結果不匹配。

而對於叢集上的海量hive資料來說,這種操作,無論計算和儲存代價都是無法接受的。所以,叢集上的hive表,我們只統計資料的條數和尺寸大小,你說這樣做會不會風險太大?其實還好,理論上,只要比較最終匯出到DB的業務資料就OK了,畢竟下游資料如果一致,上游資料也應該是一致的,所以Count Hive表中的資料量大小,主要是為了方便溯源查詢問題,同時快速判斷整體的差異情況。

最後,這些工作要執行的順利,還需要儘可能的自動化,還要讓比較結果便於人工解讀,所以在實際操作中,我們還會自動將比較的結果格式化的匯入到視覺化平臺的報表中,這樣,業務方可以通過各種條件,過濾和篩選比較結果,便於快速定位問題。 總之,一切都是為了提高驗證效率,加快驗證速度。給問題修復,雙跑迭代和正式切換工作留出更充裕的時間。

叢集資料同步拷貝

平臺搬遷,需要同步的資料來源頭很多,包括 HDFS/HBase/DB 這裡面有業務資料,也有各種服務和系統自身需要的配置,任務,元資料,歷史記錄等資訊。

這裡主要討論一下HDFS叢集資料的拷貝同步,畢竟這是同步工作中,佔比最大,也最麻煩的部分。


如何在兩個叢集間同步HDFS叢集資料,顯然,你不會去做硬碟拷貝的動作。因為叢集上的資料是在持續變化的,而且,還有元資料對映關係要處理呢。

玩過一點HDFS叢集的同學應該都瞭解,HDFS叢集自身提供了一個distcp工具來做叢集間的資料拷貝工作。但是,真正用這個工具實踐過整個叢集規模的資料拷貝工作的同學,估計就是鳳毛麟角了。為什麼這麼說,因為這個工具有很大的侷限性。無論谷歌,stackoverflow,還是郵件列表,你幾乎看不到大規模搬遷的實際案例。

所以,distcp工具最大的問題是什麼? 它最大的問題是慢! 不是拷貝檔案速度慢,而是拷貝任務的啟動速度和收尾速度慢!

至於為什麼慢,就要來看看distcp的工作原理和流程了。

Distcp在執行拷貝工作前,會先根據指定的目錄路徑比較兩邊叢集的檔案狀態,生成需要新增/修改/刪除的檔案列表內容,這個過程包括遍歷目錄樹,比較檔案元資料資訊(比如時間戳,尺寸,CRC校驗值等等)。生成的結果提交執行MR任務執行,然後,當資料全部拷貝完成以後,還要執行結果校驗,元資料資訊同步之類的工作。同步哪些元資料資訊取決於你的執行distcp時指定的引數,比如檔案owner,許可權,時間戳,拷貝數等等。通常情況下,做叢集遷移工作,這些資訊都是要同步的。

在Distcp的執行流程中,開始和收尾的很多步驟,都是單機執行的。。。所以當叢集的規模大到一定程度的時候(比如我們叢集PB級別的容量,億級別的檔案物件),這兩步動作就會變得異常緩慢。在我們的叢集中,往往需要2-3個小時的啟動和收尾時間。(這還是在一些步驟社群已經打過多執行緒補丁,配置過引數以後,否則會需要4-8小時的啟動時間)

所以,用Distcp來做歷史全量資料的同步,問題不大,但是要在資料增量同步階段,進行快速同步迭代,就比較困難了。而我們的目標是做到最大限度不影響線上業務,那麼同步流程就會希望做到儘可能快速的迭代,最後一輪增量同步動作加上各種DB元資料同步和準備工作,必須在一個小時內完成。

為了達到這個目標,我們參考distcp的程式碼,自己開發了資料同步拷貝的工具,主要針對distcp的問題,將一些單機執行的流程進行了調整,分散到Map任務中並行的執行,同時調整和簡化了同步過程中的一些工作步驟(比如拷貝完成後的CRC校驗,出錯的概率非常非常低,萬一拷貝出錯了,下一輪同步的時候覆蓋掉或者再同步一次就好了),這樣準備和收尾時間可以做到只需要半小時就能完成。整體一輪增量同步所需時間,在最後一輪增量資料很少的情況下,可以滿足一小時內完成的目標。

實際的同步工作,我們前期通過distcp完成了歷史資料的同步,後續叢集範圍的增量資料同步通過自己開發的這個工具定時自動迴圈執行來完成。而如果有臨時的小範圍的資料拷貝動作,則還是通過distcp工具來完成(因為我們的同步工具,業務邏輯設計得比較固定)

當然,除了上面說的迭代速度的問題,資料同步工作中還有很多其它的問題要考慮,比如:

  • 有些資料是不需要/不能同步的,那麼需要過濾掉

  • 資料拷貝過程中源頭資料發生了變化怎麼辦?(實際上Distcp後期的版本還提供了基於叢集Snapshot來拷貝和驗證的機制,我們其中一次遷移使用過這個機制,也有很多具體問題需要解決)

  • 出於各種原因,在一些場景下,我們需要獲取叢集檔案比較的差異資訊(彙總和明細),來做同步任務的決策。

總結來說,資料同步工作的難點在於及時,準確和檔案狀態的可控可比較。這三點做得好不好,對整體遷移流程的順利進行和結果的驗證,影響還是很大的。

各種無法雙跑的業務場景梳理

要想處理無法雙跑的業務,首先,你得找到哪些業務不能雙跑不是。問業務方是不行的,因為業務方自己可能也未必清楚,多數情況下,要靠你的經驗判斷以及反覆的溝通去推動梳理


舉幾個在我們的場景下無法/不宜 雙跑的例子:

  • 大量不受我們自己管轄的資料來源,具體管轄的業務方沒有時間精力或資源搭建雙跑用資料來源的(比如,沒那麼多機器,還有其它資料來源寫入,相關業務需要修改程式碼等等)

  • 一些服務或作業雙跑會干擾線上業務的。比如監控報警相關業務,如果按流程跑,雙跑過程中無效的報警或雙份的報警都不是業務方希望看到的。可以hack一些流程,但是代價就比較高了。

  • 雙跑會對一些系統會造成壓力的,比如網路頻寬,服務負載

  • 業務整體流程,只有部分鏈路在我們的系統中,雙跑這部分業務鏈路會造成整體業務邏輯錯誤的

此外還有一些業務場景可能需要修改以後才能支援雙跑的,比如從訊息佇列讀取資料,如果不修改消費組ID資訊,雙跑的業務就會在同一份資料中各自讀取部分資料,造成兩邊結果都是錯誤的。

類似會出問題的地方可能還有很多,都是坑啊。。。

至於無法雙跑的業務場景,如果發現了,那麼具體如何處理,反倒可能沒有那麼難,總能找到臨時解決方案,最最不濟,在目標叢集禁掉部分業務,不要雙跑,依靠其它手段提前驗證確保流程的正確性,叢集切換完再把這部分業務單獨切換過去就好了。

小結

小結一下:大資料平臺/叢集的搬遷工作,絕對不是叢集資料拷貝這麼簡單(實際上,資料拷貝也不簡單),作為一個開放的服務平臺,擁有海量的資料,系統元件眾多,上下游依賴關係錯綜複雜,業務邏輯不完全受你控制,外部系統的方案和決策也往往不受你左右,而你的業務環境又是在持續變化中,可能出錯的環節太多太多了。

在這種情況下,請務必堅守文中我們所提到的原則:堅決不做任何不可逆轉的操作;凡事另可麻煩一些,也要給自己留下退路;儘可能讓所有的步驟,流程自動化,標準化;讓系統狀態透明化;同時做好犯錯的準備,提前想好補救的手段。


生活總是如此艱辛,還是隻有搬遷時才這樣?總是如此!

祝好運,搬遷順利!

常按掃描下面的二維碼,關注“大資料務虛雜談”,務虛,我是認真的 ;)