數據偏移、分區陷阱……我們這樣避開DynamoDB的5個坑

DynamoDB 是 Amazon 基於《 Dynamo: Amazon’s Highly Available Key-value Store 》實現的 NoSQL 數據庫服務。它可以滿足數據庫無縫的擴展,可以保證數據的持久性以及高可用性。開發人員不必費心關注 DynamoDB 的維護、擴展、性能等一系列問題,它由 Amazon 完全託管,開發人員可以將更多的精力放到架構和業務層面上。

本文主要介紹作者所在團隊在具體業務中所遇到的挑戰,基於這些挑戰爲何最終選型使用 Amazon DynamoDB,在實踐中遇到了哪些問題以及又是如何解決的。文中不會詳細討論 Amazon DynamoDB 的技術細節,也不會涵蓋 Amazon DynamoDB 的全部特性。

背景與挑戰

TalkingData 移動廣告效果監測產品(TalkingData Ad Tracking)作爲廣告主與媒體之間的一個廣告投放監測平臺,每天需要接收大量的推廣樣本信息和實際效果信息,並最終將實際的效果歸因到推廣樣本上。

舉個例子,我們通過手機某新聞類 APP 瀏覽信息,會在信息流中看到穿插的廣告,廣告可能是文字形式、圖片形式、視頻形式的,而不管是哪種形式的廣告它們都是可以和用戶交互的。

如果廣告推送比較精準,剛好是用戶感興趣的內容,用戶可能會去點擊這個廣告來了解更多的信息。一旦點擊了廣告,監測平臺會收到這一次用戶觸發的點擊事件,我們將這個點擊事件攜帶的所有信息稱爲樣本信息,其中可能包含點擊的廣告來源、點擊廣告的時間等等。通常,點擊了廣告後會引導用戶進行相關操作,比如下載廣告推薦的 APP,當用戶下載並打開 APP 後,移動廣告監測平臺會收到這個 APP 發來的效果信息。到此爲止,對於廣告投放來說就算做一次成功轉化。
微信圖片_20191015164915.png

DynamoDB實踐:當數據量巨大而不可預知,如何保證高可用與實時性?

移動廣告監測平臺需要接收源源不斷的樣本信息和效果信息,並反覆、不停的實時處理一次又一次轉化。對於監測平臺來講,它的責任重大,不能多記錄,也不能少記錄,如果轉化數據記多了廣告主需要多付廣告費給媒體,記少了媒體會有虧損。這樣就爲平臺帶來了幾大挑戰:

  • 數據量大:有的媒體爲了利益最大化可能會採取非正常手段製造假的樣本,產生“虛假流量”,所以廣告監測平臺除了會收到真實用戶樣本外,也會收到大量造假的樣本,影響正常的監測和歸因。在最“瘋狂”的時候,我們的平臺會在一天內收到 40 億 + 的點擊樣本事件請求。要知道,這些點擊樣本事件是要保留下來作爲後續效果歸因用的,而且樣本有效期大不相同,從最短 12 小時到最長 90 天不等。
  • 數據量不可預知:對於廣告主的大推、應用商店競價排名等一系列的推廣,會導致突發大量樣本數據流入。面對這些流量不可預知的情況,我們仍要保證系統的正確、穩定、實時。
  • 實時處理:廣告主依賴廣告監測平臺實時處理的結果來調整廣告推廣策略。因此廣告監測平臺需要支持數據實時處理,才能爲廣告主更快的優化推廣策略提供有力支撐。與此同時,廣告監測平臺的處理結果也要實時回傳給媒體方以及廣告主。可以看到,準確性和實時性是系統必須要滿足的基本條件。
  • 樣本存儲:我們的業務最核心的功能就是歸因,我們要明確例如用戶下載打開 APP 的這個轉化效果是由哪一個推廣活動樣本帶來的——也就是上圖中的第 7 步,當用戶安裝 APP 後,監測平臺要對應找到第 1 步中樣本所在推廣活動,這個是一個查詢匹配的過程。對於龐大的歸因樣本數據,有效期又各不相同,我們應該怎樣存儲樣本才能讓系統快速歸因,不影響實時結果,這也是一個很大的挑戰。

最初形態

在 2017 年 6 月前我們的業務處理服務部署在機房,使用 Redis Version 2.8 存儲所有樣本數據。Redis 使用多節點分區,每個分區以主從方式部署。在最開始我們 Redis 部署了多個節點,分成多個分區,每個分區一主一從。剛開始這種方式還沒有出現什麼問題,但隨着用戶設置的樣本有效期加長、監測樣本增多,當時的節點數量逐漸已經不夠支撐業務存儲量級了。如果用戶監測推廣量一旦暴增,我們系統存儲將面臨崩潰,業務也會癱瘓。於是我們進行了第一次擴容。

由於之前的部署方式我們只能將 Redis 的多個節點翻倍擴容,這一切都需要人爲手動操作,並且在此期間我們想盡各種辦法來保護用戶的樣本數據。
微信圖片_20191015165202.png
DynamoDB實踐:當數據量巨大而不可預知,如何保證高可用與實時性?

這種部署方式隨着監測量的增長以及用戶設定有效期變長會越來越不堪重負,當出現不可預知的突發量時就會產生嚴重的後果。而且,手動擴容的方式容易出錯,及時性低,成本也是翻倍的增長。在當時由於機器資源有限,不僅 Redis 需要擴容,廣告監測平臺的一系列服務和集羣也需要進行擴容。

化解挑戰

經過討論和評估,我們決定將樣本處理等服務遷移到雲端處理,同時對存儲方式重新選型爲 Amazon DynamoDB,它能夠滿足我們的絕大部分業務需求。經過結構調整後系統大概是下圖的樣子:
微信圖片_20191015165206.png
DynamoDB實踐:當數據量巨大而不可預知,如何保證高可用與實時性?

  • 應對數據量大且不可預知:我們的平臺需要接受推廣監測連接請求,並進行持久化用於後續的數據歸因處理。理論上來說系統進來多少廣告監測數據請求,DynamoDB 就能存多少數據,只需要一張表就可以存儲任意數量級的數據。不用關心 DynamoDB 擴容問題,在系統運行時我們感知不到存儲正在擴容。這也是 Amazon 官方宣稱的完全託管、無縫擴展。
  • 高可用:Amazon DynamoDB 作爲存儲服務提供了極高的可用性,對於寫入 DynamoDB 的全部數據都會存儲到固態硬盤中,並且自動同步到 AWS 多個可用區,以達到數據的高可用。這些工作同樣完全由 Amazon DynamoDB 服務託管,使用者可以將精力放到業務架構及編碼上。
  • 實時處理:Amazon DynamoDB 提供了極高的吞吐性能,並且支持按秒維度配置任意級別的吞吐量。對於寫多讀少的應用可以將每秒寫入數據的數量調整成 1000 甚至更高,將每秒讀取的數量降低到 10 甚至更少。吞吐量支持使用者任意設定,在設定吞吐量時除了可以隨時在 Web 管理後臺調整外,還可以通過 DynamoDB 提供的客戶端動態調整。比如系統在運行時寫入能力不足了,我們可以選擇到 Web 管理後臺手動上調或在代碼中通過調用客戶端 API 的方式實現自動上調。使用客戶端動態調整的方式會讓系統具備較高的收縮能力,同時還可以保證數據的實時處理,系統數據流量變高了就動態調整上去,數據流量變低了再動態調整下來。相比手動調整來看,動態調整的方式更爲靈活。基於以上幾點,我們認爲 Amazon DynamoDB 可以很輕鬆的支撐系統的核心業務能力。對於業務側需要做的就是,整理好業務邏輯把數據寫到 DynamoDB 就可以了,剩下的就交給 DynamoDB 去做。

此外還有:

  • TTL:我們利用了 Amazon DynamoDB 提供的 TTL 特性管理那些有生命週期的數據。TTL 是對錶中要過期的數據設置特定時間戳的一種機制,一旦時間戳過期 DynamoDB 在後臺會刪除過期的數據,類似於 Redis 中的 TTL 概念。藉助 TTL 的能力,我們減少了很多業務上不必要的邏輯判定,同時還降低了因存儲量帶來的成本。
  • 流:在我們的業務中沒有啓用流來捕獲表的動作,但我們認爲 DynamoDB 流是一個非常好的特性,當存儲在 DynamoDB 表中的數據發生變更(新增、修改、刪除)時,通知到相關的服務 / 程序。比如我們修改了一條記錄的某個字段,DynamoDB 可以捕獲到這個字段的變更,並將變更前後的結果編寫成一條流記錄。

實踐出真知

我們在使用一些開源框架或服務時總會遇到一些“坑”,這些“坑”其實也可以理解爲沒有很好的理解和應對它們的一些使用規則。DynamoDB 和所有服務一樣,也有着它自己的使用規則。在這裏主要分享我們在實際使用過程中遇到的問題以及解決辦法。

數據偏移

在 DynamoDB 中創建表時需要指定表的主鍵,這主要爲了數據的唯一性、能夠快速索引、增加並行度。主鍵有兩種類型,「單獨使用分區鍵」作爲主鍵和「使用分區鍵 + 排序鍵」作爲主鍵,後者可以理解爲組合主鍵(索引),它由兩個字段唯一確定 / 檢索一條數據。DynamoDB 底層根據主鍵的值對數據進行分區存儲,這樣可以負載均衡,減輕單獨分區壓力,同時 DynamoDB 也會對主鍵值嘗試做“合理的”分區。

在開始我們沒有對主鍵值做任何處理,因爲 DynamoDB 會將分區鍵值作爲內部散列函數的輸入,其輸出會決定數據存儲到具體的分區。但隨着運行,我們發現數據開始出現寫入偏移了,而且非常嚴重,帶來的後果就是導致 DynamoDB 表的讀寫性能下降,具體原因在後面會做詳細討論。發現這類問題之後,我們考慮了兩種解決辦法:

微信圖片_20191015165206.png

所以我們選擇了第二種方法,調整業務代碼,在寫入時將主鍵值做哈希,查詢時將主鍵條件做哈希進行查詢。

自動擴容潛規則

在解決了數據偏移之後讀 / 寫性能恢復了,但是運行了一段時間之後讀寫性能卻再次下降。查詢了數據寫入並不偏移,當時我們將寫入性能提升到了 6 萬 +/ 秒,但沒起到任何作用,實際寫入速度也就在 2 萬 +/ 秒。最後發現是我們的分區數量太多了,DynamoDB 在後臺自動維護的分區數量已經達到了 200+ 個,嚴重影響了 DynamoDB 表的讀寫性能。

DynamoDB 自動擴容、支持用戶任意設定的吞吐量,這些都是基於它的兩個自動擴容規則:單分區大小限制和讀寫性能限制。

單分區大小限制

DynamoDB 會自動維護數據存儲分區,但每個分區大小上限爲 10GB,一旦超過該限制會導致 DynamoDB 拆分區。這也正是數據偏移帶來的影響,當數據嚴重偏移時,DynamoDB 會默默爲你的偏移分區拆分區。我們可以根據下面的公式計算分區數量:
數據總大小 / 10GB 再向上取整 = 分區總數

比如表裏數據總量爲 15GB,15 / 10 = 1.5,向上取整 = 2,分區數爲 2,如果數據不偏移均勻分配的話兩個分區每個存儲 7.5GB 數據。

讀寫性能限制

DynamoDB 爲什麼要拆分區呢?因爲它要保證用戶預設的讀 / 寫性能。怎麼保證呢?依靠將每個分區數據控制在 10G 以內。另一個條件就是當分區不能滿足預設吞吐量時,DynamoDB 也會將分區進行擴充。DynamoDB 對於每個分區讀寫容量定義如下:

  • 寫入容量單位:寫入容量單位(WCU:write capacity units),以每條數據最大 1KB 計算,最大每秒寫入 1000 條。
  • 讀取容量單位:讀取容量單位(RCU:read capacity units),以每條數據最大 4KB 計算,最大每秒讀取 3000 條。

也就是說,一個分區的最大寫入容量單位和讀取容量單位是固定的,超過了分區最大容量單位就會拆分區。因此我們可以根據下面的公式計算分區數量:

(預設讀容量 /3000)+(預設寫容量 /1000)再向上取整 = 分區總數

比如預設的讀取容量爲 500,寫入容量爲 5000,(500 / 3000) + (5000 / 1000) = 5.1,再向上取整 = 6,分區數爲 6。

需要注意的是,對於單分區超過 10G 拆分後的新分區是共享原分區讀寫容量的,並不是每個表單獨的讀寫容量。
因爲預設的讀寫容量決定了分區數量,但由於單分區數據量達到上限而拆出兩個新的分區。
所以當數據偏移嚴重時,讀寫性能會急劇下降。

冷熱數據

產生上面的問題是由於我們一開始是單表操作。這樣就算數據不偏移,但隨着時間推移數據量越來越多,自然拆出的分區也越來越多。

因此,我們根據業務做了合理的拆表、設定了冷熱數據表。這樣做有兩大好處:

  1. 提升性能:這一點根據上面的規則顯而易見,熱表中數據量不會持續無限增長,因此分區也穩定在一定數量級內,保證了讀寫性能。
  2. 降低成本:無謂的爲單表增加讀寫性能不僅效果不明顯,而且費用也會急劇增高,使用成本的增加對於誰都是無法接受的。DynamoDB 存儲也是需要成本的,所以可以將冷表數據存儲到 S3 或其他持久化服務中,將 DynamoDB 的表刪除,也是降低成本的一種方式。

表限制

表對於數據的大小以及數量並沒有限制,可以無限制的往一張表裏寫入數據。但對於 AWS 的一個賬戶,每個 DynamoDB 使用區域的限制爲 256 張表。對於一個公司來說,如果共用同一個賬號的話可能會存在創建表受限的風險。所以如果啓用了冷熱表策略,除了刪冷表降低成本外,也是對 256 張表限制的一種解決辦法。

屬性名長度

上面提到了寫入單位每條數據最大 1KB、讀取單位每條最大 4KB 的限制。單條數據的大小除了字段值佔用字節外,屬性名也會佔用字節,因此在保證可讀性的前提下應儘量縮減表中的屬性名。

總結

DynamoDB 的使用也是存在成本的,主要體現在寫入和讀取的費用。我們自己研發了一套按照實際流量實時調整讀、寫上限的策略。隨着發展 DynamoDB 也推出了 Auto Scaling 功能,它實現了自定義策略動態調整寫入與讀取上限的能力,對於開發者來說又可以省去了不少研發精力。目前我們也有部分業務使用了 Auto Scaling 功能,但由於該功能的限制,實際使用上動態調整的實時性略顯欠缺。


作者介紹:
史天舒,資深 Java 工程師,碩士畢業於北京郵電大學。任職於 TalkingData,目前從事移動廣告監測產品 Ad Tracking 相關架構設計與開發。喜歡研究代碼,注重系統高擴展設計,略有代碼潔癖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章