閱讀104 返回首頁    go 阿裏雲


表操作篇__最佳實踐_表格存儲-阿裏雲

以下將會提供一些關於使用表格存儲的表的建議。

設計良好的主鍵

表格存儲會根據表的分片鍵將表的數據自動切分成多個分片,每個分片調度到一台服務節點上。分區鍵是最小的分區單位,一個分區鍵下的數據無法在做切分。為了防止某一個分區鍵的數據成為訪問熱點達到單機服務能力上限,應用程序需要讓數據的分布和訪問量的分布盡可能均勻。

表格存儲會對表中的行按主鍵進行排序,合理設計主鍵可以讓數據在分片上的分布更加均勻,從而能夠充分的利用表格存儲水平擴展的特點。

選取分片鍵時,建議遵循以下幾個原則:

  • 單個分片鍵中的數據不宜過大(建議不超過 1 GB,1GB限製為了避免訪問熱點,而不是數據存儲的限製)。

  • 同一張表不同分片鍵中的數據在邏輯上獨立。

  • 訪問壓力不要集中在小範圍連續的分片鍵中。

使用示例:

例如,有一張表,裏麵存儲的是某大學內所有學生使用學生卡消費的記錄。主鍵列有學生卡 ID(CardID),商家ID(SellerID),消費終端ID(DeviceID),訂單號(OrderNumber)。同時我們有如下約定:

  • 每一張學生卡對應一個 CardID,每一個商家對應一個 SellerID。

  • 每一個消費終端對應 DeviceID,DeviceID 在全局是唯一的。

  • 在每一個消費終端上產生的每一筆消費記錄一個 OrderNumber。一個消費終端產生的 OrderNumber 是唯一的,但是在全局範圍內 OrderNumber 不唯一。例如,不同的消費終端有可能產生兩條完全不同的消費記錄,但是它們的 OrderNumber 相同。

  • 同一個消費終端產生的 OrderNumber 按時間排序,新的消費記錄比老的消費記錄擁有更大的 OrderNumber。

  • 每筆消費記錄均會被實時寫入這張表中。

為高效利用表格存儲,在設計表格存儲的表的主鍵時,需考慮表的分片鍵:

  • 使用 CardID 作為表的分片鍵

    使用 CardID 作為表的分片鍵是一個比較好的選擇。每天每張卡產生的消費記錄數從總體上來講是均勻的,每一個分片鍵中的訪問壓力也應該是均勻的。以 CardID 作為表的分片鍵可以較好地利用預留讀/寫吞吐量資源。

  • 使用 SellerID 作為表的分片鍵

    使用 SellerID 作為表的分片鍵不是一個好的選擇。因為學校內的商鋪數量相對較少,同時一些商鋪可能產生大量的消費記錄成為熱點,不利於訪問壓力的均勻分配。

  • 使用 DeviceID 作為表的分片鍵

    使用 DeviceID 作為表的分片鍵是一個比較好的選擇。盡管每家商鋪的消費記錄數可能相差較大,但是每天每台消費終端上產生的消費記錄數是可預期的。消費終端每天產生消費記錄的條數取決於收銀員操作的速度,這就決定了一台消費終端產生的消費記錄數是受限的。因此,使用 DeviceID 作為表的分片鍵也可以保證訪問壓力的相對均勻。

  • 使用 OrderNumber 作為表的分片鍵

    使用 OrderNumber 作為表的分片鍵不是一個好的選擇。因為 OrderNumber 是順序增長的,因此在同一段時間內產生的消費訂單的 OrderNumber 的值會集中在一個較小的範圍內,這些消費訂單記錄會集中寫入到個別的分片,以致預留讀/寫吞吐量沒能得到高效利用。如果必須使用 OrderNumber 作為分片鍵,建議在 OrderNumber 上進行哈希散列,將哈希值作為 OrderNumber 的前綴,保證數據和訪問壓力的均勻。

綜上,可以根據需求將 CardID 和 DeviceID 作為表的分片鍵,而不應該使用 SellerID 和 OrderNumber,之後再根據應用的實際需求來設計剩餘的主鍵列。

通過拚接方式使用分片鍵

建議表格存儲表的單個分片下的數據量大小不超過 1 GB。如果您的表中單個分片鍵的所有行的總數據量大小可能超過 1 GB,在設計表時可以將原來的多個主鍵列拚接成分片鍵。

使用示例:

例如,上一小節中提到的學生卡消費記錄表,假設主鍵為 [DeviceID, SellerID, CardID, OrderNumber]。DeviceID 是該表的分片鍵,單個 DeviceID 中所有行的數據量總大小可能超過 1 GB,可以將 DeviceID、SellerID 和 CardID 拚接作為表的第一個主鍵列(也就是分片鍵)。

原來的表如下:

DeviceID SellerID CardID OrderNumber attrs
16 ‘a100’ 66661 200001
54 ‘a100’ 6777 200003
54 ‘a1001’ 6777 200004
167 ‘a101’ 283408 200002

將 DeviceID、SellerID 和 CardID 拚接成分片鍵後的表如下:

CombineDeviceIDSellerIDCardID OrderNumber attrs
‘16:a100:66661’ 200001
‘167:a101:283408’ 200002
‘54:a1001:6777’ 200004
‘54:a100:6777’ 200003

在原來的表中,Device=54 的兩行是屬於同一個分片鍵為 54 下的兩條消費記錄。在新的表中,這兩條消費記錄擁有不同的分片鍵。通過拚接多列主鍵列形成分片鍵的表減少了單個分片鍵下的總數據量大小。

選擇將 DeviceID、SellerID 和 CardID 拚接成分片鍵,而不選擇將 DeviceID 和 SellerID 進行拚接的原因是,前一節提到的消費記錄表約定所有 DeviceID 相同的消費記錄其 SellerID 也相同,因此僅僅拚接 DeviceID 和 SellerID 並不能解決單個分片鍵的數據量過大的問題。

但是,拚接主鍵列形成表有一些小瑕疵。DeviceID 是一個 Integer 類型的主鍵列,在原來的表中,DeviceID=54 的消費記錄在 DeviceID=167 的前麵。將前三列主鍵列拚接成 String 類型的主鍵列後,DeviceID=54 的消費記錄在 DeviceID=167 的後麵。假如應用程序需要範圍讀取 DeviceID 在 [15, 100) 之間所有的消費記錄,上麵的表無法滿足需求。

為了應對這種狀況,可以在 DeviceID 高位補 0。補 0 的個數取決於 DeviceID 最大位數。假設 DeviceID 的取值範圍是 [0, 999999],可以將 DeviceID 高位補 0 至 6 位後再進行拚接,得到的表如下:

CombineDeviceiDSellerIDCardID OrderNumber attrs
‘000016:a100:66661’ 200001
‘000054:a1001:6777’ 200004
‘000054:a100:6777’ 200003
‘000167:a101:283408’ 200002

經過高位補 0 後的表依然有一些問題。在原來的表中,DeviceID=54 的兩行,SellerID=’a1001’ 的行應該在 SellerID=’a100’ 的後麵。產生這種現象的原因是,’000054:a1001’的字典序小於’000054:a100:’,但是’a1001’的字典序大於’a100’,連接符”:”影響了字典序。在選取連接符時,應該選取比所有可用字符的 ASCII 碼都小的字符作為連接符。在該表中,SellerID 的取值為數字、大小寫英文字母。我們可以使用”,”作為連接符,因為”,”比所有 SellerID 可用字符的 ASCII 碼小。

使用”,”拚接後的表如下:

CombineDeviceiDSellerIDCardID OrderNumber attrs
‘000016,a100,66661’ 200001
‘000054,a100,6777’ 200003
‘000054,a1001,6777’ 200004
‘000167,a101,283408’ 200002

上麵經過拚接形成的分片鍵的表的記錄順序就和原來的表保持一致了。

綜上,當表中單個分片鍵的所有行的數據量總大小可能超過 1 GB時,可以使用將多個主鍵列拚接成分片鍵的方法,以避免單分片鍵的數據量大小限製。在拚接分片鍵時需要注意以下事項:

  • 選取需要拚接的多個主鍵列必須能有效地將原來表中相同的分片鍵的記錄形成擁有不同分片鍵的記錄。

  • 拚接 Integer 類型主鍵列時可以在高位補 0,保持記錄的順序一致。

  • 選取連接符時需要考慮連接符對新的分片鍵的字典序的影響,選取比所有可用字符都小的連接符是一個比較安全的選擇。

在分片鍵中加入哈希前綴

使用示例:

在設計良好的主鍵一節中已經提到,盡量不要使用 OrderNumber 作為表的分片鍵。因為 OrderNumber 是順序增長的,消費記錄總是被寫入最新的 OrderNumber 範圍之內,舊的 OrderNumber 不再有寫入壓力,造成訪問壓力不均勻的現象,以致預留讀/寫吞吐量得不到高效利用。如果必須使用順序增長的鍵值作為分片鍵,我們可以對分片鍵拚接哈希前綴,讓相連的 OrderNumber 在表中隨機分布,使訪問壓力分布均勻。

以 OrderNumber 為分片鍵的消費記錄表如下:

OrderNumber DeviceID SellerID CardID attrs
200001 16 ‘a100’ 66661
200002 167 ‘a101’ 283408
200003 54 ‘a100’ 6777
200004 54 ‘a1001’ 6777
200005 66 ‘b304’ 178994

對 OrderNumber 使用 md5 算法計算前綴(您也可以采取其他哈希散列算法),拚接成 HashOrderNumber。因為 md5 算法計算得到的哈希字符串可能過長,我們隻需要取前幾位就能達到讓 OrderNumber 相連的記錄在表中隨機分布的目的。在這個例子中我們取前 4 位:

HashOrderNumber DeviceID SellerID CardID attrs
‘2e38200004’ 54 ‘a1001’ 6777
‘a5a9200003’ 54 ‘a100’ 6777
‘c335200005’ 66 ‘b304’ 178994
‘db6e200002 167 ‘a101’ 283408
‘ddba200001’ 16 ‘a100’ 66661

在後續訪問消費記錄時,使用相同的算法對 OrderNumber 計算哈希前綴,即可得到對應消費記錄的 HashOrderNumber。在分片鍵中加入哈希前綴的弊端是,原來連續的記錄會被打散,無法再使用 GetRange 操作讀取一段範圍內在邏輯上連續的記錄。

並行寫入數據

表格存儲的表會被切分成多個分片,這些分片被分散在多個表格存儲的服務器上。如果有一批數據要上傳到表格存儲中,同時這批數據是按主鍵排好順序的,若按順序寫入數據,可能會導致寫入壓力集中在某個分片中,而其他的分片處於空閑狀態,無法有效利用預留讀/寫吞吐量,影響數據導入速度。

可以采取以下任一措施來提升導入數據的速率:

  • 將原始數據順序打亂後再進行導入,以保證寫入數據均勻地分配在各個分片鍵上。

  • 使用多個工作線程並行導入數據。把大的數據集合切分成很多個小集合,工作線程隨機選取小集合進行數據導入。

區分冷數據和熱數據

數據往往具有時效性。例如,在設計良好的主鍵一節中提到的存儲消費記錄表,近期產生的消費記錄被訪問的可能性較大,因為應用程序需要及時地對消費記錄進行處理和統計,或者查詢最近的消費記錄。但是年代久遠的消費記錄被查詢的可能性不大,這些數據漸漸成為冷數據,但仍然占用存儲空間。

其次,表中存在大量冷數據會導致數據訪問壓力不均勻,從而導致表上配置的預留讀/寫吞吐量無法被充分利用。例如,已經畢業的學生的卡片,不會再產生消費記錄。假如 CardID 是隨著卡片申請時間遞增的,以 CardID 作為分片鍵,會導致已經畢業的學生的 CardID 沒有訪問壓力卻被分配到預留讀/寫吞吐量,造成浪費。

為了解決這種問題,可以用不同的表來區分冷熱數據,並設置不同的預留讀/寫吞吐量。例如,將消費記錄按月份分表,每一個新的自然月就換一張新的表。當月的消費記錄表需要不停寫入新的消費記錄,同時有查詢操作。當月的消費記錄表可以設置一個較大的預留讀/寫吞吐量配置來滿足訪問需求。前幾個月的表由於不再寫入新數據或者寫入的新數據量較少,查詢的請求較多,因此前幾個月的消費記錄表可以設置較小的預留寫吞吐量,較大的預留讀吞吐量。而曆史超過一年的消費記錄表,由於再被使用的可能性不大,可以設置較小的預留讀/寫吞吐量配置。已經超出維護年限的消費記錄表可以將數據導出,存入 OSS(Object Storage Service)歸檔,或直接刪除。

最後更新:2016-11-23 16:03:56

  上一篇:go 計量計費__購買指導_表格存儲-阿裏雲
  下一篇:go 數據操作篇__最佳實踐_表格存儲-阿裏雲