search
Evernote從自有數據中心遷移到Google雲平台的經驗之談

Evernote從自有數據中心遷移到Google雲平台的經驗之談

2016年底,Evernote對他們的服務進行了一次大規模的遷移。他們將Evernote服務從自有數據中心遷移到了Google Cloud Platform上。他們在短短的70天內完成了整個遷移過程,包括Evernote服務、存儲數據和其他配套服務。參與這次遷移的工程師們在Evernote官方博客上分享了這次遷移的過程。文章包含了5個部分。以下譯文已獲得翻譯授權。

Evernote服務和遷移到Google雲平台的可選方案

Evernote服務在2008年開始投入使用,我們一直維護著自己的伺服器和網路。我們有很大的自由度來構建服務,但同時也面臨著一些挑戰:伸縮困難、升級緩慢、高昂的維護成本。雖然我們的基礎設施在過去為Evernote提供了很好的支持,但在未來,我們需要更快的速度和更大的靈活性,而這些正是目前的基礎設施所缺乏的。

遷移到公有雲這一決定讓Evernote的每一位員工都興奮不已。在首次發布遷移聲明之後,我們便在幕後啟動了從物理數據中心到Google雲平台(GCP)的遷移工作。經過70天日以繼夜的奮戰,我們完成了遷移。

可能有些人想了解我們的遷移過程,又或者想進行類似的遷移,所以我們決定把遷移過程分享出來。我們會告訴大家在遷移過程中我們都做了哪些事情,以及如何能夠快速地完成遷移。這不是一個完整的遷移手冊,不過它涵蓋了我們做出的所有關鍵決策,以及我們是如何實現它們的。

先讓我們來了解一下Evernote服務的概況。

Evernote服務

我們的服務由以下幾個組件組成。

分片(NoteStore)

分片是Evernote服務的核心單元,用於存儲用戶的筆記。每個分片最多可以支撐30萬個Evernote用戶,並包含了如下幾個組件。

  • 基於Tomcat的前端Web服務層:Evernote客戶端會連接到這個層。

  • 數據存儲層:用於存儲用戶筆記的MySQL資料庫。

  • 搜索索引:基於Lucene的服務端搜索索引,用於搜索用戶的筆記內容。

我們總共有762個分片,存儲著2億個用戶賬號和大約50億個用戶筆記。

UserStore

UserStore使用MySQL資料庫來存儲用戶信息,包括他們的認證信息。因為資料庫管理著所有用戶的狀態和認證信息,所以它是最為關鍵也是最為複雜的一個組件,在遷移過程中我們需要格外小心。

用戶附件存儲(資源)

我們有一個單獨的文件存儲層,用於存儲50億個用戶附件(我們把這些附件稱為資源)。這個層由206個自包含的WebDav伺服器組成。每個用戶附件有三個副本,其中兩個分別被保存到本地的兩個不同的WebDev伺服器上,另外一個被發送到遠程災備數據中心的WebDav伺服器上。

前端負載均衡

我們的高可用負載均衡器集群負責將用戶請求路由到特定的分片上。

配套服務

我們還有200多台Linux伺服器用於緩存和執行批處理任務,比如筆跡識別和文本識別。

我們的遷移可選方案

Evernote服務規模龐大,向雲端遷移是一項複雜的工程,我們需要作出很多與依賴項有關的決策。我們希望能夠儘快完成遷移,所以我們為關鍵性決策制定了一個草案。

我們要先理清楚需要做出哪些變更。我們知道,有些組件無法直接被簡單地遷移到雲端,所以我們把組件分為兩類。

  • 轉移:有些組件在CGP上能夠找到幾乎一樣的替代品,分片、UserStore以及大部分配套服務都屬於這一類。在我們的物理數據中心,這些組件都是基於Linux的,所以我們將會直接把它們遷移到雲端的Linux虛擬機上。

  • 轉換:在遷移過程中需要對用戶附件存儲、負載均衡層和識別服務(Reco)做一些重大的轉換。對於這些組件來說,要麼在雲端找不到相應的替代方案,要麼雲端已經存在更好的替代方案。

遷移方法

接下來我們要開始計劃如何進行遷移,我們有兩種選擇。

  • 一次性遷移:遷移項目里存在著一個分界點,跨過這個分界點就等於完成了100%的切換。對於自有數據中心和採用了單體架構的組織來說,這種方式會更適合(這或許也是唯一可行的方案)。

  • 階段性遷移:這是一種分而治之的方法,根據類型或用戶將服務分組,進行階段性的遷移。採用這種方式可以在提交階段性工作之前對其進行測試和驗證。

從我們的情況來看,一次性遷移的方案並不適合我們。即使做了詳盡的計劃,一次性遷移完所有的組件仍然存在很大的風險。況且,我們的應用並非為多站點運行而設計的,儘管我們的環境在很早之前就已經支持多站點運行。

所以,我們需要在上述的兩種極端之間找到一個折衷的方案,我們決定採取「增速階段性切換」的方案。整個遷移過程要在短短的20天內完成,它包含了多個階段,每個階段的風險需要被降到最低。如果某些階段的工作無法達到預期的效果,可以進行回滾。

保護GCP上的用戶數據

我們的安全團隊負責保護用戶數據的安全。在向雲端遷移的過程中,我們要考慮如何保證用戶數據的安全。我們面臨兩個主要的問題。

  • 我們即將把數據交由Google來看管,他們是否擁有足夠成熟的安控措施,能夠避免給我們的服務帶來新的風險?

  • GCP是否提供了等價的或者更好的安控措施來保證數據的安全?

廠商信任

我們內部有一個廠商審查流程,由我們的法律團隊和安全團隊共同執行這個流程。我們在與新的服務提供商展開合作之前,會對相關的隱私和安全問題進行審查。

在廠商審查過程中,我們會檢查60多項與隱私和安全有關的問題。這些內容與我們內部程序的結構保持一致,通過這些審查,我們就可以知道廠商是否符合我們的預期。審查內容可以被分為幾類。

  • 組織層面的控制措施

  • 基礎設施的安全

  • 產品安全

  • 物理設施安全

  • 與隱私有關的數據使用

我們和Google一起對他們的審計報告進行了評審,發現他們在各個方面都能夠滿足我們的預期。

接下來,我們將注意力放在他們是否提供了用於保護用戶數據安全的安控措施上。

雲安全控制措施

首先,我們對現有的所有用於保護用戶數據的安全措施進行了評審。這些安控措施包括使用了雙因子認證的遠程訪問VPN和提供了流量過濾功能的防火牆。還有其他一些物理安控措施,比如物理外圍、生物技術認證、探頭,以及用於防止物理數據被盜的警報系統。我們在安全主頁上有很多關於基礎設施安全防護的討論。

我們列出了一個表格,記錄了遷移過程中需要注意的安全問題,下面是一些主要內容。

  • 外圍網路安全(向內流量和向外流量過濾)

  • 內部隔離

  • 多因子認證

  • 傳輸加密

  • 缺陷管理

  • 身份信息和訪問控制

  • 診斷日誌

  • 入侵檢測

  • 變更監控

我們還需要考慮多租戶雲環境可能會出現新的威脅模型。內存和存儲的使用方式與過去也不一樣。我們還要考慮來自其他用戶的威脅,這些用戶與我們處在同一虛擬層上。好在Google已經考慮到了這些問題,他們在博客上就如何解決這些問題進行過討論。

我們在GCP上為我們的大部分安控措施找到了等價的替代方案。我們還為靜態數據加密找到了一種更好的替代方案。而對於其他一些安控措施,比如IP白名單,我們只能通過改造我們的架構,讓它不再依賴傳統的網路安控。我們使用包含了Google託管密鑰的CGP服務賬號來實現這一改造。

如何安全地使用GCP服務賬號

遷移到雲平台之後,原先的靜態CIDR塊被靜態公共IP和動態公共IP的混合體所替代。維護IP白名單的成本非常高,而且在CGP的其他服務里並不存在這樣的功能。

在我們的安控架構里,網路和用戶數據之間包含至少兩個安全層。內部服務全部被部署在一個安全的網路外圍里,服務之間通過API密鑰進行交互。我們以一種安全的方式來存儲和分發這些密鑰,不過仍然存在泄露或被盜的風險。如果真的發生泄露或被盜,我們還有第二個安全層。在我們的生產環境之外,秘鑰失去了它們的作用,而要想進入生產環境使用這些秘鑰,必須先通過雙因子認證。

每一個GCP服務都是互聯網服務,它們並沒有提供面向用戶的白名單安控機制用於訪問Google計算引擎(GCE)項目里的主機。我們需要在密鑰和用戶數據之間添加另一個安全層。

我們使用GCP服務賬號解決了這個問題。每個GCE項目都有一個默認的服務賬號,在GCE中啟動的實例可以通過這個賬號來訪問其他服務。Google提供了一組公鑰/私鑰對,密鑰對每24小時自動翻轉一次。對於自定義的服務賬號,也會執行相同的操作。我們可以為每個主機角色創建一個自定義服務賬號,並通過配置虛擬實例來使用這些賬號。現在,那些運行在這些虛擬實例上的應用就可以使用內置的Google託管密鑰。

我們的運維工程師沒有必要訪問這些密鑰,因為Google每天都會自動翻轉這些密鑰。要想訪問到這些密鑰只能通過滲透我們的基礎設施,不過我們目前已經具備了相應的防護能力。

總的來說,我們對雲平台的安全充滿了信心。我們還會繼續加強平台的安全,把不斷變化的安全威脅甩在身後。

GCP上的Evernote架構和我們的技術轉型我們的系統架構

下一個重大決定是如何在即將實施的系統架構方案上達成一致。下面是一些重要的考慮點。

  • Evernote服務可以運行在多個數據中心上(不包括地區的服務)。

  • 將用戶數據的兩個副本保存在不同的地理區域。

  • 最小化加州北部數據中心和GCP之間的網路延遲,為遷移工作帶來更多的靈活性。而且如果有可能,在短期內還能支持多站點運行。

基於這些考慮,我們達成了以下方案。

  • 主要位置(處理生產流量):US-West1

  • 次要位置(災備站點):US-Central1

後續我們還會把US-West1的服務拆分成兩個區域,進一步提升故障防護能力。

正如Google所說的,「Google把區域設計成相互獨立的:每個區域有自己的供電系統、冷卻系統、網路和控制面板,大部分的故障只會影響到單個區域」。

基於上述的架構,我們可以處理US-West1之外的流量,並在US-Central1保存用戶數據的第二個副本。如果US-West1發生故障,我們可以使用US-Central1的數據進行恢復。

我們以後還要想辦法對我們的應用進行重構,提升它們的彈性。還要考慮如何同時處理多個區域的流量,以便進一步減少從故障中恢復的時間。我們也會考慮如何更好地利用GCP的全球基礎設施來改善用戶的延遲體驗。

到現在為止,我們定義了清晰的需求,並做出了很多決策。接下來開始進入具體的實現階段。

技術轉型

最大化從數據中心到GCP的網路連接

我們在一開始就意識到,從數據中心到GCP的網路連接是決定這次遷移成功與否的關鍵因素,同時也是主要的約束所在。為了能夠靈活地對數據和服務進行遷移,網路互連計劃需要實現如下幾個目標。

  • 對從數據中心流向CGP的流量進行加密。

  • 將兩個區域中的任意一個作為用戶流量的「前門」,如果有必要,可以基於這兩個「前門」來分割流量。

  • 對每個區域的服務進行分割,讓它們部分運行在物理數據中心裡,部分運行在GCP上。

  • 最大化站點間的帶寬,以支持大塊數據的拷貝。

在GCP上運行Evernote後端服務,並向CGP拷貝3PB的數據。在這種情況下,為了讓100%的「前門」流量流經數據中心和物理負載均衡器系統,我們需要進一步提升靈活性。

我們已有的外部網路連接可以處理峰值負載,而且留有餘量。不過,它的容量仍然無法滿足及時清退數據的要求。

另外,我們的內部網路結構並不支持將如此大規模的請求導出到外部設備(比如Google雲存儲)上。基於目前的情況,要將所有的數據上傳到雲端,可能需要超過一年的時間,而且會影響用戶體驗。所以我們還有很多工作要做。

首先,我們需要建立私有網路互連(PNI),或者直接在Evernote的網路和GCP之間建立連接。這樣可以將可用帶寬增加一倍,而且這些連接獨立於用戶流量。這就在Evernote和GCP之間建立起了一個快速的私有通道。

其次,我們需要確定數據中心的哪些地方需要輸出數據。我們的數據有多個副本,所以需要確定該使用哪個副本。我們需要開闢出一條網路,在不影響系統正常運行的情況下,將成千上百台伺服器的數據通過私有通道移動到GCP。我們需要小心地協調數據拷貝作業,讓這些請求正確地流經一系列負載均衡器和代理伺服器。

在項目啟動后的頭一個月,我們的網路工程團隊爭取在數據拷貝啟動之前完成準備工作。因為如果他們的工作無法按時交付,整個遷移工程都會受到影響。

我們可以多站點運行嗎?

到目前為止,我們的應用只在單個數據中心裡運行。在單個數據中心裡,節點間的延遲通常是亞毫秒級的。如果我們能夠成功地在數據中心和GCP上運行應用,我們需要知道該如何將節點間的延遲保持在20毫秒到50毫秒之間。延遲的增加有兩方面的原因,一方面是受光纜的速度限制,另一方面是受數據中心和GCP之間距離的影響。

很顯然,我們不希望在遷移過程中發生此類問題。為了避免給用戶帶來延遲,我們需要先進行自測。在項目計劃期間,我們決定使用一種服務端工具(tc)引入人為的網路延遲,並模擬出因地域和光纜速度限制所帶來的延遲。我們逐漸將NoteStore的延遲增加到50毫秒,並保持4天不變。在此期間,我們對應用的KPI進行監控,並將它們與我們的基準數據進行比較。我們發現,大部分API調用有輕微的減慢,不過都在可接受的範圍之內,而且沒有對用戶體驗造成影響。

這是一個里程碑式的階段性勝利:我們確信我們的應用可以跨站點運行,也就是說,我們的「增速階段性切換」方案是可行的。

我們的數據中心運行著一個高可用的負載均衡器集群。在遷移到雲端之後,就無法使用物理的負載均衡器,所以我們開始調研虛擬的負載均衡解決方案。

理想情況下,我們可以在GCP上部署單個負載均衡層。不過從現實來看,這個是不可行的。我們根據cookie、消息頭和URL將請求消息路由到特定的分片上,但我們無法在GCP負載均衡平台上做這些解析工作。最後,我們基於Google網路均衡器和一個基於Linux的HAProxy集群構建了新方案。

Google網路均衡器將成為用戶流量的入口點,並將流量均衡地分發到HAProxy集群上,然後HAProxy集群將流量路由到特定的分片上。

在通過實驗測試之後,我們想使用真實的流量來對新方案進行測試。我們採取階段性測試,在前一個測試通過之後才會增加更多的流量。到目前為止,後端的Evernote服務仍然在物理數據中心裡運行,而跨站點的負載流量會通過私有VPN連接路由到那裡。我們打算這樣做:

  • 將Evernote員工流量重定向到新的「前門」。我們更新了公司的DNS,從而將Evernote員工流量引導到新的前門。

  • 使用Dyn Traffic Manager逐步將用戶流量導入到新前門。

我們通過上述的兩個方法對我們的新負載均衡平台進行測試。與跨站點測試一樣,我們慶幸我們能夠單獨完成組件的測試,並確信這個方案是可行的。

Reco服務(從UDP到發布訂閱)

當用戶將附件或資源上傳至Evernote,有個後端服務會嘗試提取圖像或PDF里的文本信息,這個服務被稱為Reco(「recognition」的縮寫)。

因為之前的架構存在種種限制,Reco伺服器只能通過輪詢的方式拉取資源。可以想象得到,多台Reco伺服器定期向NoteStore伺服器發起輪詢,會給NoteStore伺服器和資源存儲伺服器帶來很大的壓力。隨著用戶附件的增加,需要添加更多的Reco伺服器,從而讓情況愈加惡化。

為了減少因Reco伺服器的增加而帶來的開銷和延遲,我們對Reco伺服器進行了重新設計,通過使用多路廣播,當NoteStore增加新的資源時,Reco伺服器就會收到通知。

不過GCP的網路並不支持多路廣播,所以我們使用新的通信模型對應用進行了重新設計。

我們移除了輪詢機制和多路廣播機制,取而代之的是基於發布訂閱模型的隊列機制,這種機制不僅可靠而且可伸縮。NoteStore向隊列里發布任務,Reco伺服器訂閱隊列,並獲取任務,在處理完任務後進行確認。我們創建了多個優先順序隊列,Reco服務根據隊列的優先順序來處理任務。

我們使用基於雲的隊列機制極大簡化了原先的架構,現在能夠影響Reco服務的因素只有兩個:隊列里是否有需要處理的任務以及通知的速度。除此之外,我們正進一步改造Reco服務,讓它支持自動伸縮,這樣我們就可以把注意力放在如何管理和維護附件資源上。

用戶附件存儲(從多WebDev到Google雲存儲)

我們需要將120億個用戶附件和元數據文件從原先的WebDav伺服器拷貝到Google雲存儲上。

因為數據量巨大,拷貝文件是整個遷移項目的關鍵一環。在進行拷貝的過程中,我們的服務仍然會對WebDav伺服器讀寫數據。

我們碰到的第一個障礙是我們的網路,我們無法每天從幾千個節點拷貝幾百TB的數據。所以,我們不得不花一些時間建立了多條到GCP的網路通道。在進行數據拷貝的同時,我們要確保不會對自己造成DDoS攻擊,還要保護好用戶服務。

資源遷移器

我們開發了一個Java應用程序,它可以直接運行在WebDav伺服器上。WebDav伺服器根據它們的物理RAID情況被拆分成目錄樹。資源遷移器遍歷目錄樹,並將每個資源文件上傳到Google雲存儲(GCS)。

為了確保文件能夠上傳成功,我們在本地為每個文件生成了一個散列值,這個散列值連同文件一起發送給GCS。GCS有獨立計算散列值的功能,它將自己計算的散列值與收到的散列值進行比較。如果散列值不匹配,GCS會返回一個HTTP 400 BAD REQUEST錯誤碼,資源遷移器會進行重試。如果連續幾次重試失敗,錯誤會被記錄到日誌里,用於後續的修復之用,而資源遷移器會繼續上傳其他文件。

通過性能測試,我們發現拷貝過程主要受RAID陣列的IOPS(每秒輸入輸出操作)和WebDav伺服器CPU的影響。為了避免對用戶體驗造成影響,我們使用了兩個并行的資源遷移器實例(每個RAID陣列使用一個遷移器),每個實例使用40個并行線程。這樣我們就可以同時上傳80個資源文件而不會對用戶造成負面影響。

有了資源遷移器,接下來需要創建一個控制層來管理這些遷移器。

遷移編排器

資源遷移器是一個小型的應用,WebDav集群上的每個目錄樹都需要一個遷移器實例。因為有幾百個目錄樹需要遷移,所以需要一個控制層來管理這些遷移器。

我們在shell腳本里集成了現有的目錄管理工具,實現了對資源遷移器實例的啟動、停止和追蹤。

因為受每個WebDav伺服器最多只能運行兩個實例和每個物理伺服器機櫃最多只能運行20個實例(受網路限制)的限制,遷移編排器必須能夠智能地管理好資源遷移器實例,盡量減少人工的干預。

從高層面看,遷移編排器需要滿足如下要求。

  • 提供一個集中式的控制台用於管理所有的資源遷移器

  • 維護任務清單,並識別出可遷移的任務(正在寫入的目錄是不能進行遷移的)

  • 了解數據中心和主機的情況,避免出現資源過載或影響到生產流量

  • 提供穩定的24/7吞吐和併發任務

如果速度全開,我們可以同時運行100到120個遷移器實例,這些實例完全由遷移編排器來控制。

讓應用與GCS發生交互

接下來,我們要考慮如何更新我們的應用程序,以便從GCS讀寫數據。我們決定加入一些開關,用於切換GCS的讀寫功能。有了這些開關,我們可以先在部分分片上打開這個功能,讓新功能的發布更安全、更可控。

服務切換

將服務遷移到新的存儲後端是一個敏感的操作。我們在開發環境和測試環境進行過大量的測試,在進入生產環境之前我們能做的也只有這些了。

為了做到乾淨安全的切換,並最小化對用戶的影響,我們把遷移分成幾個獨立的步驟。為了方便理解,我們先來了解下最初的資源上傳過程。

  • 用戶向Evernote服務發送保存資源的請求。

  • 服務收到用戶的資源,並啟動兩個進程:

    2.1 確保資源的兩個副本被保存到WebDav伺服器上

    2.2 將資源添加到非同步任務隊列

  • 在完成了上述兩個步驟之後,向用戶返回保存成功的消息。

  • 後台處理非同步任務,資源的第三個副本被拷貝到遠程的WebDav伺服器上。

第一階段

GCS非同步寫入

第一步,我們往非同步隊列里寫入需要上傳到GCS的資源。這樣做有如下幾個好處。

  • 新的資源將會被自動上傳到GCS,為我們節省了使用資源遷移器的時間。

  • 另外,我們可以將生產環境的寫入流量導向GCS API。這對於測試我們的代碼是否能夠處理大量數據來說非常關鍵。我們也因此對一些故障場景和GCS的評估有了更深入的了解。

  • 更重要的是,以上過程全部發生在用戶的視野之外。這裡所發生的故障對於用戶來說都是100%透明的,所以我們可以自由地進行迭代,不斷改進我們的代碼,而不會對用戶體驗造成負面影響。

GCS伺機讀取

到現在為止,我們對寫入性能充滿了信心,並對我們的代碼和GCS有了正確的評估。下一步是如何讓讀操作的性能也能達到相同的等級。

我們伺機從GCS讀取資源數據,如果有些資源還沒有被遷移過來,或者出現了故障,我們的服務會立即轉向主WebDav伺服器讀取數據。

因為下載資源處在用戶的關鍵路徑上,在讀取資源時發生轉向有可能會造成一些延遲。不過這些延遲一般在毫秒級別,用戶感知不到。

初步結論

頭兩輪的測試讓我們對新的存儲後端和代碼有了更多了解。我們發現了一些罕見的故障(1:1,000,000的比率),而且發現GCS Java SDK內置的重試機制無法滿足我們的要求。我們通過快速迭代改進了我們的代碼,解決了這些故障,讓服務更加健壯。

第二階段

在經過幾輪迭代之後,我們準備進行全面的提交。下一步要移除對WebDav伺服器的依賴。不過,我們對資源的遷移流程還在進行中,所以伺機讀取資源的機制還要保留,不過對於新的資源來說,就沒必要再使用WebDev了。

關鍵路徑上的GCS寫入

首先,我們要改變資源上傳流程。之前,資源會被上傳到兩個主WebDav伺服器上,而現在它們直接被上傳到GCS,而且我們將WebDav伺服器的位置數量減至一個。

  • 如果GCS發生重大故障,所有的重試都會失敗,用戶最終會收到「上傳失敗」的錯誤信息(不過我們的客戶端會再次重試,所以不用太擔心)。這也就是我們能得到的最糟糕的結果。

  • 雖然我們減少了WebDav的寫入,但並不會降低數據的持久性。GCS提供了更好的數據冗餘,所以這是一個很大的進步!

  • 減少WebDav數量有助於降低延遲(兩次寫入與三次寫入的對比),同時,仍然保留單個主WebDav拷貝讓我們感覺到很安全。

全面禁用WebDav寫入

我們對系統的性能和健康情況進行了深切的監控,在運行了一段時間之後,我們準備全面停止向WebDav伺服器寫入數據。

  • 禁用離岸災備中心的非同步備份作業(GCS不僅為每個資源維護多個副本,還會將它們存儲在不同的地理區域,為我們提供了很好的彈性,以便應對災難性故障和自然災害)。

  • 禁用主WebDav寫入。

現在的上傳流程更簡單了。

  • 用戶像Evernote服務發送保存資源的請求。

  • 服務接收資源並將其寫入GCS。

  • 服務向用戶返回成功信息。

在後台,GCS負責保存資源的多個副本,並保存在多個地理位置。

最後的驗證

在撰寫本文時,我們已經完成99.2%的資源遷移,這一過程是整個遷移項目非常重要的一部分。可以說,我們即將看到勝利的曙光!

GCS伺機讀取機制還會繼續存在,直到我們進行一次最終的完整性檢查,確保所有的資源都已成功遷移。

為了完成最終的完整性檢查,我們遍歷了資源資料庫,然後向GCS查詢這些資源是否存在,並再次驗證每個文件的散列值。與資源遷移過程相比,完整性檢查完成得會快很多。我們按周而不是按月來檢查資源,確保120億個資源都安全地進行了遷移。

在雲端生產環境測試分片

進行雲端分片測試也需要經過一些步驟。在遷移項目啟動之初,我們就定義了一些迭代流程用於測試運行在GCP上的Evernote服務。在進行全面遷移之前,我們需要確保在用戶負載全開的情況下,單個分片能夠成功地運行在GCP上。我們要找出之前的測試沒能覆蓋到的邊界情況。

在感恩節之前,我們成功地將兩個用戶分片遷移到GCP上,並讓它們運行了幾個小時,最後再回退到物理數據中心。我們完成了最後的預遷移檢查,為後續的全面遷移亮起了綠燈。

全面遷移的計劃和實施

在計劃Evernote服務遷移的過程中,我們希望盡量最小化宕機時間,不過我們也知道,我們現有的架構不支持零宕機遷移。在制定遷移方法和維護時間窗口的計劃時,我們提出了一些要求。

  • 我們要最小化在「多站點」模式下運行的時間。

  • 從遷移單個分片開始,隨後逐漸增加到同時遷移8個分片。

  • 在少量分片遷移到GCP之後,我們需要大概48小時的時間用於觀察它們的運行狀態。

  • 最多同時支持遷移32個分片,再多就會有風險。如果出現問題,涉及的範圍越大就越難以控制。

  • 選擇在周末時間遷移UserStore,並把遷移時間保持在一個小時以下。因為UserStore的遷移會造成全面宕機,所以我們要最小化對用戶造成的影響。

  • 每個分片的遷移造成的宕機時間不能超過30分鐘。宕機從來都不是一件好事,這也是我們一直要避免的。可惜我們的應用不支持透明的分片失效轉移。不過好在用戶客戶端內容的同步間隔一般在15分鐘以內,所以用戶可能不會注意到宕機。

  • 我們希望有足夠的靈活性,以便在遷移過程中應對一些未知的狀況。

  • 一旦全局服務正式運行,就不再允許出現任何「停業整頓」之類的維護時間了。

基於上述幾點考慮,以及我們對分片遷移速度的預期,我們最終做出了如下計劃。我們在論壇和社交媒體上與用戶就此事進行了溝通。

分片的遷移時間為每天的太平洋夏令時時間,從早上9點到晚上9點。

  • 12月8號到9號進行第一次分片遷移

  • 12月11號進行UserStore遷移

  • 12月14號到16號進行第二次分片遷移

從12月16號到31號,我們耐心觀察等待。所有的運維工程師24/7待命,以便快速對問題和故障做出響應。

從12月1號開始,我們也一直在遷移那些不會影響用戶體驗的服務,比如圖像識別服務和前端流量。

遷移的技術細節

NoteStore

在計劃遷移NoteStore時,我們利用到了之前的「分片遷移」經驗。之前,為了在數據中心的不同硬體間移動分片,我們開發了一套流程和腳本。雖然這套流程不能完全適用於這次遷移,但仍然成為這次遷移的核心基礎。

首先,我們要在GCP端創建新的分片,然後開始同步數據。

創建空的分片

我們使用Google的部署管理器和我們已有的Puppet腳本創建新的NoteStore。這些新分片的名字和配置與生產環境中的分片是一樣的,只是沒有用戶數據,而且還沒有被配置到負載均衡平台上。

同步數據

每個分片有兩種用戶數據需要同步:

  • 存儲用戶筆記的MySQL資料庫

    複製生產環境MySQL最新的備份

    處理備份

    啟動資料庫實例

    在源實例和目標實例上配置用於複製的用戶角色

    從備份點開始複製

    • 解壓

    • 解密

  • Lucene索引。Lucene與MySQL不一樣,它並沒有提供原生的數據複製支持,所以我們需要使用自定義工具來移動數據。我們基於ubiquitous rsync utility創建了一個工具,並通過cron在每台源主機上運行,向我們的Graphite/Splunk數據收集管道發送度量指標和日誌。

我們在切換分片服務之前的5到10天啟動數據複製流程。

在這個過程中,我們不斷迭代更新我們的方法。

  • 最初的MySQL配置使用日常備份和二進位日誌進行數據複製。不過後來發現,我們的備份伺服器無法滿足多個進程并行運行的要求。

  • 為了滿足我們的并行要求,我們使用新的GCS bucket作為備份目標。我們通過重寫ansible playbook進行基於流的innobackupex加密和壓縮備份,並將其直接寫入GCS bucket。

  • 在每個分片的備份可用之後,我們使用新的playbook在複製目標上掛載更多的PD存儲,用於下載、解壓備份數據。

  • 另一個playbook會運行我們在之前的遷移中開發的工具「genesis」,這個工具會驗證MySQL的配置、恢復備份、更改密碼、啟動和關閉檢查、升級資料庫,並最終啟動MySQL複製進程。

  • 我們的Lucene複製工具很穩定,不過很快我們就發現我們的交換機鏈路和數據中心到GCP之間的VPN連接逐漸趨於飽和。因為一些技術原因,我們不得不調整最初的Lucene複製進程數量(最初的同步複製嚴重消耗網路帶寬,經過調整之後,後續的同步只複製變更的部分)。

  • 我們對網路通道映射也做了一些調整,讓ansible按需進行批次複製,先是觀察初始的複製情況,再通過cron運行新的複製。

服務切換

服務切換的目標是儘快安全地完成切換,以便最小化宕機時間。

  • 停止生產環境的分片,並將其移出負載均衡器,確保用戶不會對其做任何更新。

  • 運行最後階段的增量數據同步。

  • 啟動新的分片(包含了同步過的數據)。

  • 使用一套工具(我們把它們叫作「confidence」)檢查數據是否複製成功,是否是最新的,校驗和是否匹配,等等。

  • 在新分片上運行QA腳本,驗證分片的功能是否正常。

  • 移除新分片的備份。

  • 新分片向用戶開放。

  • 關閉日誌、事務和告警監控。

為了加快速度,我們不能依靠手動工作。在確保這個流程可行之後,我們使用ansible對流程進行自動化。

我們的腳本可以在30分鐘內自動遷移一個分片(平均包含了30萬個用戶的數據)。我們逐漸增加并行數量,直到可以并行遷移32個分片。這樣我們就可以在30分鐘內遷移大約1千萬用戶的數據。

我們的團隊

在遷移過程中,來自運營團隊的不同角色的人員一起協作,確保一切進展順利。

項目經理:項目經理協調整個遷移項目,並作出決策,包括批處理的大小、何時開始批處理以及如何與外部溝通進度。

首席工程師:首席工程師對遷移有完全的控制權,他在遷移之前堅定大家的信心,管理遷移工具,並作出排查決策。

監控工程師:監控工程師查看我們的監控系統,找出異常情況,並在每個遷移完成之後通知大家。他們還會運行QA工具,最終確認遷移是否成功。

修復團隊:我們有2到4個工程師排查和修復由首先工程師和監控工程師發現的問題。

UserStore

UserStore的遷移步驟與MySQL的遷移基本步驟是一樣的,不一樣的地方在於需要將原先指向NoteStore的JDBC URL改成指向新的主機。

因為UserStore處在整個Evernote服務的中間,所以我們在一個周日早上特意安排了一個小時的維護時間。因為UserStore承擔了整個服務的大部分事務,為了監控遷移過程,我們讓一大組運營人員和工程師在辦公室待命。還有一個來自Google的團隊,他們也做好準備,以便應對可能出現的問題(他們還為整個團隊帶來了早點)。

這也許算得上整個遷移過程最為緊張的一小時,整個服務出於停機狀態,時間在一分一秒地流逝。

其他服務

其他服務的遷移會簡單一些,我們可以在遷移的同時創建新的服務,並逐步完成切換。我們可以重用一部分已有的Puppet代碼,並藉機對它們進行清理和簡化。

那麼我們是如何執行最初的計劃的?

總得來說,我們基本是按照最初的計劃進行的,儘管曾經有兩次偏離了計劃。

  • 為了更新配置參數,我們在12月15號重啟了UserStore,確保它們在新的環境中能夠正常運行。

  • 我們使用了額外的一天時間(12月19號)用於遷移最後的96個分片。我們本來可以很快地遷移這些分片,不過最後還是決定採取更安全的方式,為此多花了一天時間。

關於這些變更,我們都有在論壇和社交媒體上與用戶進行過溝通。

分片遷移過程中的數據

下圖的氣泡代表遷移中的用戶批次,氣泡越大,用戶批次就越大。Y軸表示每分鐘遷移的用戶數量,X軸表示批次。不同顏色代表不同的遷移工作日。

此處輸入圖片的描述

  • 11月23號,我們進行嘗試性的用戶分片遷移,用於確定最終的流程和工具。

  • 12月8號,沒有遷移分片,因為我們發現了一些代碼缺陷需要修復。

  • 12月9號,開始小規模的遷移。

  • 12月14號,我們開始大踏步,在一天內遷移了7千7百萬個用戶。

批次統計

  • 最大的一個批次是33號,在24分鐘內遷移了9,463,546個用戶(每分鐘394,314個用戶)。

  • 最快的一個批次是37號,在14分鐘內遷移了9,244,151個用戶(每分鐘660,297個用戶)。

  • 耗費時間最短的一個批次是43號,在11分鐘內遷移了4,042,851個用戶(每分鐘367,532個用戶)。

  • 耗費時間最長的一個批次是51號,在30分鐘內遷移了147,765個用戶(每分鐘4,926個用戶)。

  • 所有用戶的服務不可用時間都控制在30分鐘以內。

結論和展望

極度聚焦

為了達成我們的目標,我們盡量排除外界的干擾,避免浪費時間。我們不得不拒絕那些不重要的業務任務,而且要讓整個公司都知道我們這樣做的理由。我們通過分享會議讓其他人了解我們的項目,同時給他們機會提問。

給力的夥伴

在項目開始的時候,我們討論如何快速地完成遷移,我們希望避免平庸,所以設定了非常高的目標。與我們一起合作的Google團隊對於我們提出的挑戰也毫不畏懼。每次在我們發現可能導致偏離目標的問題時,Google團隊總能站出來一起解決。

為了完成共同的目標,來自兩個不同公司的團隊在一起合作,這是一件非常有意思的事情。

讓事情落地,並儘快完成

傳統的思維告訴我們,對於這類遷移,應該從小處著手。某種程度上說,這是對的,不過我們不能完全依照這種說法去做。我們認為我們應該快速完成遷移,並把遷移看作整個環境的組成部分,因為這是讓所有潛在問題浮出水面的唯一方法。

從小處著手,或者從非關鍵路徑開始遷移存在一些根本性的問題。

  • 規模太小的東西一般不會暴露出什麼問題,即使我們儘可能地進行基準測試或負載測試,也無法發現可能會在生產環境發生的問題。它們只會降低我們對安全的防範意識。

  • 我們一般不太關注非關鍵的東西(不太重要的),所以即使出現問題也能接受。這些問題一般得不到修復,一旦進入生產環境就會給我們帶來危害。

不過,從小處著手可以幫助我們熟悉新的平台,並做出一些基本的測試。這一步是必須去完成的,不過這並不代表在進入生產環境之後一切能夠正常運行。

在遷移過程中,我們儘可能快速地對生產環境服務進行測試。如果我們的系統或應用能夠被拆分成組件進行遷移,對於測試來說就非常有利。基於微服務的架構在這方面極具優勢,不過對於古老的單體應用來說就非常困難。

簡而言之,處在用戶關鍵路徑之外的高流量組件是最好的著手點。

自動化

我們儘可能藉助自動化加快遷移過程。很多時候,我們使用已有的工具。不過基於一些原因,我們也會自己構建一些工具。

  • 在GCP里創建實例。我們基於Google雲部署管理器創建自定義工具,用於創建配置模板,並利用現有的Puppet基礎設施進行配置管理。

  • 我們創建了遷移腳本和整體控制框架,實現了24/7的高速文件拷貝。我們有大約50億個用戶文件需要拷貝,而每個用戶文件又有相關的元數據文件(如縮略圖),所以總的文件數量達到120億個。

  • 我們創建了基於Ansible的工具,用於初始化數據的遷移流程,並實現了分片遷移的完全自動化。

如果不注重自動化,根本不可能在如此短的時間內完成遷移。

打破約束

在制定遷移計劃時,我們意識到數據中心和GCP之間的網路帶寬會對我們造成約束。它不允許我們拷貝大塊的數據,也限制了多站點運行能力。

我們知道網路約束會給我們的工作帶來多個負面影響。於是在項目早期階段,我們花了額外的時間和精力最大化站點間的帶寬。我們對比了幾個雲供應商,並最終做出了選擇,為我們節省了寶貴的時間。

我們從中學到了什麼?

總得來說,我們對遷移過程感到很滿意。我們也從中學到了很多。

應用程序可以運行在雲端,但是……

雲服務的成熟度已經達到一定高度。在大多數情況下,我們的應用可以正常運行在雲端。不過,我們仍然需要「調節」我們的應用,因為在雲端環境可能會受到一些約束。所以我們要提前做好準備,在遷移之後需要留出「調節」時間。

例如,為了解決磁碟吞吐量受限問題,我們不得不調節我們的運行環境。在我們的數據中心,我們可以儘快地運行備份,因為我們的環境或存儲設備沒有被共享,所以可以盡情地消耗所有可用的磁碟IO。但在GCP上,我們的自動化備份會讓虛擬機的IO達到頂峰,從而拖慢了上游應用。後來我們通過限定備份的速率來解決這個問題。

我們還通過調節環境來實現實時遷移。這是Google後端的一個特性,允許在主機間遷移虛擬機,而且在大多數情況下對用戶是不可見的。UserStore和部分比較活躍的分片因為IO很高,在進行實時遷移時不太順暢,即使是一段很短時間的不可用也會造成破壞性的中斷。我們通過調節MySQL和應用來解決這個問題。儘管還不完美,但我們仍然會繼續在Google的協助下不斷完善它。

和用戶的溝通是關鍵

我們需要與用戶溝通有關遷移維護的事情。我們不希望給用戶發送大量的郵件,因為他們很可能不會查看郵件。我們希望通過Evernote社區論壇和社交媒體就維護問題與用戶進行溝通。這種方式確實奏效了,不過還是有一小部分用戶因為不知道這些事情而受到了影響。我們希望消除這個問題,所以在未來,我們會考慮使用應用內置的方式與用戶進行溝通。

總結

在新的環境里,我們總不可避免地會遇到各種小問題。有些問題還比較棘手,不過通過調整已有的配置,並與Google團隊展開通力合作,這些問題最終都得到了解決。

現在的狀態

我們完成了整個遷移,現在可以展望下未來。我們之所以要遷移到雲端,是為了讓我們的工程流程更加順暢,讓我們的工程師可以使用更多的構建工具。我們知道,隨著時間推移,我們將會逐漸遠離虛擬機,並逐步擁抱容器和無伺服器技術,從而把精力聚焦在重要的事情上。

初見成效

Evernote Hack Week

Evernote每年都會有一個Hack Week(一般在1月份)。對於公司里的每一個人來說,這是一個展示他們創造性想法的好機會。人們爆棚的創造力給我們帶來驚喜,團隊和想法在自由的環境里自主成形。

在過去,我們的運營團隊會花時間來支持這個活動,他們需要為活動提供伺服器和其他東西。不過今年很不一樣,我們給了工程師單獨的GCP項目訪問許可權,並讓他們自由發揮。他們可以輕鬆地為自己的項目配置基礎服務,儘可能少給運營同事增加負擔,這樣運營同事們就可以專註於遷移工作。

微服務平台

我們在去年還決定了要把我們的單體架構遷移到微服務架構,而使用容器是完成這個目標的第一步。我們做了很多調研,最終選擇Kubernetes作為編排框架。運行生產級別的Kubernetes集群是一個艱巨的任務,所幸的是,Google Container Engine(GKE)簡化了整個流程。

現在,我們在生產環境運行了一個GKE集群,為我們的下一代搜索基礎設施提供支持。我們會在另一篇文章中詳細介紹這方面的內容。

無伺服器的未來

儘管我們對當前的架構進行了改進,為了能夠運行Evernote的代碼,仍然有上千個操作系統需要安裝。理想情況下,我們應該只負責部署代碼,然後讓Google負責運行這些代碼。

也就是說,我們只要負責使用計算能力來處理用戶請求。不過我們還有很長的路要走,我們正在嘗試使用Google Cloud Functions作為我們的核心構建塊,這樣我們就可以專註在用戶代碼和特性的開發上。

如何為雲項目命名?

每個偉大的項目都需要一個名字。經過深思熟慮之後,我們從兩個備選方案中選出一個作為項目的名稱。我們的軟體工程團隊提議把這個項目叫作「Cloudy Meatballs」,其靈感源於Judi Barrett的著作《天降美食》(Cloudy with a Chance of Meatballs)。我們的運營團隊則提議「Bob Ross」,源於著名的藝術家和電視名人Bob Ross,他能夠畫出蓬鬆的雲彩。最後我們通過整個公司的投票來決定,「Bob Ross」勝出。

查看英文原文:Cloud Migration

今日薦文

2017谷歌雲大會,一口氣發布100+消息

細說雲計算

「細說雲計算」是InfoQ旗下關注云計算技術的垂直社群,投稿請發郵件到editors@cn.infoq.com,註明「細說雲計算投稿」即可。

熱門推薦

本文由 一點資訊 提供 原文連結

一點資訊
寫了5860316篇文章,獲得23261次喜歡
留言回覆
回覆
精彩推薦