典型場景 | PolarDB-X 如何支撐 SaaS 多租戶

SaaS多租戶背景

很多平臺類應用或系統(如電商CRM平臺、倉庫訂單平臺等等),它們的服務模型是圍繞用戶維度(這裏的用戶維度可以是一個賣家或品牌,可以是一個倉庫,等等)展開的。因此,這類型的平臺業務,爲了支持業務系統的水平擴展性,業務的數據庫通常是按用戶維度進行水平切分。 可是,當平臺類應用的一些用戶慢慢成長爲大用戶(比如大品牌、大賣家、大倉庫等)後,這些大用戶由於其數據量或流量明顯要比其它用戶多得多,容易導致以下的現象

  • 大用戶所在分片會成爲業務系統的熱點,佔用大量的數據庫資源,其服務質量容易因資源受限導致不穩定;
  • 其它小用戶容易受到大用戶資源消耗的影響,服務質量也受到影響。

最後就整個平臺的業務系統的熱點頻現,數據庫訪問不穩定,業務服務受影響。 SaaS多租戶模型作爲一種應用的架構,常用來解決業務的上述問題。在SaaS多租戶模型中,業務系統會需要服務多個用戶,每個用戶(或每批用戶)可以被視爲一個租戶。 這些不同的租戶在業務系統內會使用共同的基礎設施及其平臺進行運行,但來自不同租戶的數據仍將被獨立隔離,因此,通常租戶擁有自己物理資源來單獨存儲與管理數據。所以,SaaS多租戶解決業務系統穩定性問題以及租戶資源彈性定製的核心思路,就是租戶間的資源隔離及數據隔離。 在實際的不同應用場景下,常見的 SaaS 多租戶方案有兩種:

Schema 級 SaaS 多租戶

  • Schema 級 SaaS 多租戶,是指一個租戶對應一個包含多個Table定義的Schema(或一個Database,在MySQL, Schema概念等同Database), 不同租戶的Schema會分佈在不同的機器上(如下圖1所示),實現資源隔離,該方案適用於不同租戶需要使用獨立Schema運行的場景;

Partition 級 SaaS 多租戶

  • Partition 級 SaaS 多租戶,是指一個租戶會對應一個Table的一個或多個的分區(或是一個Table的一部分 rows),不同租戶的Parittion會分佈在不同的機器上(如上圖2所示),以實現資源隔離,該方案比較適用於不同租戶需要使用統一Schema運行的場景。

從隔離程度來看, Schema 級 SaaS 多租戶比 Partition 級 SaaS 多租戶要隔離得更徹底,但前者因爲要維護衆多的Schema,會比後者會帶來更高的運維成本及查詢分析成本。 不過,Partition 級 SaaS 多租戶通常要依賴中間件分庫分表或分佈式數據庫分區功能(不然單機數據庫無法做到資源隔離)才能運作,而Schema 級 SaaS 多租戶則不需要,用戶自己搭建幾個單機MySQL也可以運作起來,准入門檻更低。

業務的問題

業務多租戶場景

只說應用架構可能有些抽象,爲了方便讀者更容易地理解 SaaS 多租戶是如何幫助業務解決問題, 本文將以一個真實的案例來進行闡述。 正馬軟件的班牛平臺是國內領先的提供電商全週期客戶服務的賣家訂單管理平臺(以下簡稱B公司)。它的業務系統需要維護多個不同品牌的衆多賣家。通常一個品牌會有多個賣家(比如,一個品牌可能會開通多個線上店鋪),所以,品牌與賣家是一對多的關係。 目前,B公司的訂單管理平臺管理着超過50T的訂單數據,日QPS 近 3W+,不同品牌的訂單量差異會比較大(大品牌的訂單可能是小品牌的訂單量的近百倍或更高)。 一些大品牌除了訂單量比其它品牌的大很多之外,還會使用更高級的付費VIP服務:比如,要求訂單數據獨佔資源與數據隔離、允許獨立地統計分析自己品牌的訂單數據等。 B公司爲了解決不同品牌的數據的資源使用及其服務差異,就會對它的賣家按品牌劃分(相當於一個品牌是一個租戶):

  • 大品牌訴求:
    • 訂單量大(比如,訂單數據存儲的大小超過 1T 或 2T ),數據存儲量大
    • 獨佔一組的存儲資源、有獨立訪問分析數據的需求
    • 該品牌的所有商家都必須同一組的存儲資源
    • 大品牌的大賣家150+,後邊還會陸續增加
  • 小品牌訴求:
    • 訂單表小,商家數目大(6W+賣家)
    • 共用一組存儲資源
    • 要求所有賣家數據在存儲上均衡分佈

現在的核心問題是,B公司的訂單管理平臺的數據庫應該如何設計,才能滿足上述衆多不同品牌及其大賣家對於不同資源使用與數據隔離的訴求?

普通中間件方案及其問題

對於上述的業務場景,B公司若不使用分佈式數據庫,而是簡單通過單機MySQL及一些開源的分庫分表中間件,自己搭建一套SaaS多租戶方案(比如,將品牌及其賣家切分爲租戶),進行租戶的資源隔離。表面上,這貌似可行;但實際上,業務會因此要面臨更多更爲棘手的問題。

首先是跨機分佈式事務問題。 絕大多數的分庫分表中間件無法提供強一致分佈式事務能力,或者只能提供基於最終一致性的事務補償方案,這意味着業務需要做很多額外的應用改造成本,才能儘量來避免跨機事務導致業務出現報錯。

然後是 Schema 一致性問題。 基於中間件分庫分表,無論是採用 Schema 級多租戶及是 Partition 級多租戶,B公司的訂單平臺都要面臨自己維護各個租戶的 Schema 或 Table 的元數據一致性。比如,MySQL的建刪表、加減列、加減索引等常見的DDL操作,中間件的方案無顯然法保證平臺所有租戶的表能同時生效,一旦執行中斷,必須靠人工介入來訂正,人力成本高。

接着是租戶的數據遷移問題。基於 SaaS多租戶方案,B公司若要給一個大品牌分配新的獨立資源,這自然免不了將租戶數據從原來機器到新機器的數據遷移。這個數據遷移操作只能依賴額外的同步工具構建同步鏈路才能完成,這中間切割過程甚至還需要業務停機。這意味,業務執行添加一個新租戶這一基本操作,也會帶來非常高昂的運維成本。

綜合上述的分析,B公司直接基於單機MySQL及一些中間件的 SaaS 多租戶方案,並不是一個成本低廉的方案。

SaaS多租戶PolarDB-X方案

事實上,在分佈式數據庫 PolarDB-X 2.0 中,B公司已經可以通過結合非模板化二級分區 與 Locality 兩項能力,來很好的解決其上述業務所面臨的問題。爲了方便讀者更易理解,以下先簡單介紹下 PolarDB-X 2.0 的非模板化二級分區 與 Locality 兩項的功能。

非模板化二級分區

PolarDB-X 從 5.4.17 開始支持使用二級分區創建分區表。與其它分佈式數據庫所有不同(比如 TiDB/ CockroachDB 等等),PolarDB-X 的二級分區除了語法能完全兼容原生 MySQL 二級分區語法外,還額外擴展很多的二級分區的能力,比如,支持用戶定義非模板化二級分區(原生 MySQL 只支持模板化二級分區)。 所謂的非模板化二級分區,就是各個一級分區之下的二級分的分區數目及其邊界值定義允許不一致,如下所示:

/* 一級分區 LIST COLUMNS + 二級分區HASH分區 的非模板化組合分區 */
CREATE TABLE t_order /* 訂單表 */ (
 id bigint not null auto_increment, 
 sellerId bigint not null, 
 buyerId bigint not null,
 primary key(id)
) 
PARTITION BY LIST(sellerId/*賣家ID*/) /*  */
SUBPARTITION BY HASH(sellerId) 
(
  PARTITION pa VALUES IN (108,109) 
    SUBPARTITIONS 1 /* 一級分區 pa 之下有1個哈希分區, 保存大品牌 a 所有賣家數據 */,
  PARTITION pb VALUES IN (208,209) 
    SUBPARTITIONS 1 /* 一級分區 pb 之下有1個哈希分區, 保存大品牌 b 所有賣家數據 */,
  PARTITION pc VALUES IN (308,309,310)
    SUBPARTITIONS 2 /* 一級分區 pc 之下有2個哈希分區, 保存大品牌 c 所有賣家數據 */,
  PARTITION pDefault VALUES IN (DEFAULT)
    SUBPARTITIONS 64 /* 一級分區 pDefault 之下有64個哈希分區, 衆多小品牌的賣家數據 */
);

基於上述的 LIST+HASH 非模板化二級分區,它能給應用直接帶來的的效果是:

  • 對於大品牌的賣家(相當一個租戶),可以將數據路由到單獨的一組分區;
  • 對於中小品牌,可以將數據按哈希算法自動均衡到多個不同分區,從而避免訪問熱點。

當大品牌與中小品牌的商家數據按LIST分區實現了分區級的隔離後,那實現大品牌與中小品牌的存儲資源的物理隔離也就自然而言的事了。在 PolarDB-X 2.0 中,用戶可以藉助 Locality 的能力,很容易地實現不同分區之間的資源隔離。

LOCALITY 資源綁定

PolarDB-X 支持通過 LOCALITY 關鍵字來指定數據庫分區的實際存儲資源位置(PolarDB-X中存儲資源由多個數據節點(DN節點)組成,可以通過DN的ID進行位置分配),以實現數據隔離或數據的均勻分佈。它的具體語法如下所示:

ALTER TABLE #tableName 
MODIFY (SUB)PARTITION #(sub)partName 
SET LOCALITY='dn=dn1[, dn2,...]'

例如,B公司可以使用以下的SQL命令將 t_order 中的大品牌 pa 的數據全部單獨挪到一個存儲節點 dn4 :

ALTER TABLE t_order MODIFY PARTITION pa SET LOCALITY='dn=dn4'

在實際使用中,用戶可以通過 SHOW STORAGE 查詢 PolarDB-X 的所有DN節點實例ID列表,例如:

mysql> show storage;
+----------------------------+----------------+------------+-----------+----------+-------------+--------+-----------+------------+--------+
| STORAGE_INST_ID            | LEADER_NODE    | IS_HEALTHY | INST_KIND | DB_COUNT | GROUP_COUNT | STATUS | DELETABLE | DELAY      | ACTIVE |
+----------------------------+----------------+------------+-----------+----------+-------------+--------+-----------+------------+--------+
| polardbx-storage-0-master  | 10.0.x.1:3306 | true       | MASTER    | 41       | 66          | 0      | false     | null       | null   |
| polardbx-storage-1-master  | 10.0.x.1:3307 | true       | MASTER    | 41       | 53          | 0      | true      | null       | null   |
| ......                     | ......        | true       | META_DB   | 2        | 2           | 0      | false     | null       | null   |
+----------------------------+----------------+------------+-----------+----------+-------------+--------+-----------+------------+--------+

設計 SaaS 多租戶方案

回到之前B公司的例子,B公司的核心需求是要實現大品牌與中小品牌的賣家數據及其存儲資源的隔離。那麼,B公司可以在上述的二級分區的分區表的基礎上,通過再給每個一級分區增加對應的 LOCALITY 定義,以指定一級分區及其所有二級分區所允許使用的存儲資源,那麼業務就可以在建表階段直接實現SaaS層多租戶(即品牌方)存儲資源的隔離,如下所示:

/* 一級分區:list columns,二級分區:key 的非模板化組合分區 */
CREATE TABLE t_orders /* 訂單表 */ (
 id bigint not null auto_increment, 
 sellerId bigint not null, 
 buyerId bigint not null,
 primary key(id)
) 
PARTITION BY LIST(sellerId /* 賣家ID */ ) 
SUBPARTITION BY HASH(sellerId) 
(
  PARTITION pa VALUES IN (108,109,....) 
    LOCALITY='dn=dn16' /* 大品牌 pa 獨佔一個DN dn4 */
    SUBPARTITIONS 1,
  PARTITION pb VALUES IN (208,209,....) 
    LOCALITY='dn=dn17' /* 大品牌 pb 獨佔一個DN dn5 */
    SUBPARTITIONS 1 ,
  PARTITION pc VALUES IN (308,309,310,...) 
    LOCALITY='dn=dn18,dn19' /* 大品牌 pc 獨佔兩個DN: dn6 與 dn7 */
    SUBPARTITIONS 2,
  PARTITION pDefault VALUES IN (DEFAULT) 
    /* 一級分區 pDefault 佔用 dn0 ~ dn15 共16個DN, 中小品牌共享 */
    LOCALITY='dn=dn0,dn1,...,dn2,dn15' 
    SUBPARTITIONS 64 
);

如上圖所示,通過 Locality 對各個一級分區的DN節點資源的綁定,pa、pb、pc這3個大品牌的租戶被分別配了DN16、DN17 與 DN18~DN19 3組的節點資源,而中小賣家池的 pDefault 分區則被綁定了16個DN節點。

SaaS多租戶運維管理

當二級分區及Locality能力解決了B公司對於不同品牌的多租戶資源隔離後, 那馬上需要面臨的問題自然是:用戶將如何有效便捷地管理這些多租戶?答案是 PolarDB-X 2.0 的分區管理能力。 PolarDB-X 2.0 對於分區表提供了一系列完備的靈活強大的分區管理命令(如下圖所示),讓用戶能夠僅僅通過簡單SQL命令,就可以實現在多租戶場景下的不同運維變更的訴求。

下邊我們還是通過B公司的例子,來單獨介紹基於分區管理支持SaaS多租戶場景下的常見的運維變更。

場景一:基於修改LIST分區實現給租戶添加新的賣家

以B公司爲例,B公司的一個租戶對應的是一個品牌方,一個品牌在B公司的平臺通常會有多個賣家。因此,當品牌方開了新的商鋪時,就需要將新的賣家ID加入到這個品牌方對應的租戶資源之下。 藉助 PolarDB-X 的 MODIFY PARTITION ADD/DROP VALUES 的功能,可以方便地給 LIST 分區添加新的賣家ID, 如下所示:

/* 給品牌 pb 增加新的賣家 205 */
ALTER TABLE t_orders MODIFY PARTITION pb ADD VALUES (205);

在這個DDL的執行中,PolarDB-X 會自動地從 LIST 的 DEFAULT 分區 (如果有顯式定義 DEFAULT 分區的話)抽取 sellerId=205 的所有數據,並遷移到 pb 分區中,DDL 全過程 Online ,業務應用幾乎無感知。

場景二:基於增加LIST分區實現給添加新租戶並分配新的存儲資源

諸如B公司這類訂單管理平臺,平臺上的各品牌的賣家通常會經歷從無到有,從小賣家發展成大賣家的過程。因此,當一個品牌的小賣家發展成一個大賣家時,該品牌就可能會讓 B公司將它的賣家從中小品牌的賣家池(比如,DEFAULT 分區)中抽取了出來,使之成爲獨立租戶的VIP,併爲之分配單獨的存儲資源。

藉助 PolarDB-X 的 ADD/DROP PARTITION 及其 Locality 的功能,B公司可以很便捷地在線地完成上述場景的變更操作。例如,B公司想將新的大品牌 pe 的大賣家 301 從 DEFAULT 分區中抽取出來,並使之獨佔新的存儲資源 new_dn ,如下所示:

/* 1.B公司在管控購買新的 CN/DN 的節點資源... */
/* 2.增加新的大賣家,創建新分區並放置到特定的DN節點 */
ALTER TABLE t_orders ADD PARTITION (
  /* pDefault 分區裏再抽取出新的大賣家 301 , 並命名爲 pe, 並將其數據放置新節點 new_dn */
    PARTITION pe VALUES IN (301) LOCALITY='dn=new_dn' SUBPARTITIONS 1,
);

與 MODIFY PARTITION 類似,這些 ADD/DROP PARTITION 的變更操作也屬於 Online DDL, 這中間的數據遷移操作對業務應用近乎透明。

場景三:基於分區級Locality支持租戶內二級分區數據的重均衡

PolarDB-X 的 LIST + KEY 非模板化二級分區,在多租戶場景下,能給用戶提供一個重要的特性,就是它允許不同的租戶的二級哈希分區數目不一樣。這樣意味着,不同的租戶允許通過定義不同的二級分區數目,可以使用不同數量的存儲資源。 例如,B公司的 t_orders 表的 LIST 分區定義中,大品牌 pc 的一級 LIST 分區之下的二級分區數目是2,並同時獨佔了2個DN節點來存儲訂單數據(即 pc 分區的每個DN節點都分配一個二級分區)。此外,還有它的中小品牌的賣家所共享的 DEFAULT 分區之下有64個二級分區,並且還獨佔 dn0 ~ dn15 共16個DN節點(如下所示):

PARTITION pDefault VALUES IN (DEFAULT) 
    /* 一級分區 pDefault 佔用 dn0 ~ dn3 共16個DN, 中小品牌共享 */
    LOCALITY='dn=dn0,dn1,...,dn2,dn15' 
    SUBPARTITIONS 64

可是,DEFAULT分區裏的衆多中小賣家也可能存在一些熱點(比如,20%的頭部賣家可能佔訂單數量80%),這些熱點賣家如果分佈不合理,也可能會導致DEFAULT內部的16個DN節點間負載不均衡。 因此,B公司需要面臨的問題是:該如何管理這64個二級分區的衆多中小賣家的訂單數據,才能相對均衡地分佈到這16個DN節點,並保證系統整體的負載均衡呢? 這就是需要使用 PolarDB-X 的分區級 Rebalance 能力 。 PolarDB-X 的 分區級 Rebalance 功能允許用戶對一個一級分區內部的多個二級分區,按一級分區的 Locality 進行自動的物理分片調度,使這些二級分區在 Locality 所定義的DN節點上保持均衡分佈。用戶只需要執行一條SQL命令(如下所示), 即可完成上述的均衡變更,:

REBLANCE TABLE t_orders PARTITIONS=pDefault;

場景四:基於分區選擇及視圖功能支持租戶的數據查詢及數據安全

PolarDB-X 的分區表及 Locality 的 SaaS 級多租戶能力,對於諸如B公司這類訂單管理平臺,除了能滿足其對品方的數據隔離與資源隔離的訴求外,還可以爲業務提供更多的數據查詢的能力。 比如,B公司平臺上的大品牌,偶爾還需要使用諸如獨立查詢及分析自己的訂單數據等的VIP服務。這些品牌方會通過B公司所提供一些Web SQL工具來直接查詢分析自己的訂單數據(比如,查詢重要客戶的訂單數目等)。 可是,B公司作爲平臺性的系統,它需要保證不同租戶間的數據安全及其隔離:即租戶查詢訂單數據只能看到自己的數據,無法看到其它租戶的任何數據。 那麼,基於PolarDB-X 的分區表,B公司是如何解決不同租戶的數據隔離的問題呢? 答案是藉助分區選擇與視圖定義。 比如,B公司如果想授權它的租戶 pb 單獨查詢及分析它自己的訂單數據,它的Web SQL工具將會自動化地使用類似以下的 SQL 命令提前在 PolarDB-X 上爲該租戶 pb 創建出對應的只讀視圖 t_order_pb_view :

CREATE VIEW t_order_pb_view AS 
SELECT * 
FROM t_orders PARTITION(pb) /*  t_orders 表的數據只會返回 pb分區以及下所有二級分區 */ ;

然後,平臺再通過對租戶 pb 賬號信息進行自動化的相關授權操作後,租戶 pb 在其所提供的 Web SQL工具裏登錄後將只允許看到 t_order_pb_view 這個只讀視圖。 那麼,假如租戶要執行諸如下邊所示的這類的統計訂單總數的視圖查詢:

/* 大租戶 pb  查詢訂單數據的SQL:統計訂單數目 */
SELECT COUNT(1) FROM t_order_pb_view;

PolarDB-X 將自動地把視圖 t_order_pb_view 替換爲對應的子查詢:

/* 大租戶 pb  查詢訂單數據的SQL:統計訂單數目 */
SELECT COUNT(1) FROM 
(
   SELECT * 
     FROM 
   t_orders PARTITION(pb)
) as t_order_pb_view;

如此一來,基於分區選擇語法的限定,視圖 t_order_pb_view 將只允許返回 pb 分區的數據。這樣租戶 pb 無法查詢到其它租戶的賣家訂單數據,從而達到數據隔離的效果。

實踐總結

PolarDB-X 分區表及其配套的靈活的管理語法,在不同的業務場景下,可以包裝出各種業務模型。比如,本文所介紹的基於非模板化二級分區 + Locality 能力的所構建的SaaS多租戶就是其中的經典用法之一。 事實上,本文所提及的真實案例的B公司的商家訂單管理系統已經基於上述的 PolarDB-X 2.0 的 SaaS 多租戶方案成功上線(其應用架構如下圖所示),目前它所提供的平臺負責管理着超過50T的訂單數據。

但是,B公司的案例顯然是一個能夠複製並推而廣之的案例。比如,它的租戶維度--品牌,可以很容易聯想到其它的業務維度並可以構建類似的實踐,比如,各大倉庫物流單管理、直播平臺各直播室的觀衆送禮物的數據管理、各大城市交通監控數據管理、各大省份氣象監控數據收集,等等。 簡單總結一下最佳實踐,若業務場景存在

  • 需要對數據按某個維度(比如,地域、倉庫、商家或品牌等)進行水平切分,劃分多個業務單元;
  • 還需要爲切分後的業務單元進行不同的資源配置及數據的物理隔離;

以上幾點的,用戶都可以參考使用本文 SaaS 多租戶方案進行數據庫設計。

作者:城璧

點擊立即免費試用雲產品 開啓雲上實踐之旅!

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

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