數據偏移、分區陷阱……咱們這樣避開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 相關架構設計與開發。喜歡研究代碼,注重系統高擴展設計,略有代碼潔癖。
相關文章
相關標籤/搜索