作為業務系統技術開發同學,面向當下:首先應該是快速搭建業務通路,讓線上業務跑起來,快速試錯,解決生存問題;第二步是在鏈路通了,業務基本跑起來的基礎上如何支撐業務跑更快,解決快速增長問題;第三步,在完成支撐業務快速增長的基礎上,要進行精細化提升,通過在支撐業務快跑間隙擠時間打磨系統功能和體驗,踏踏實實花時間,抽象能力,沉淀產品,提升效能。
同時,我們也必須面向未來,如何在抽象能力以及沉淀了產品的基礎上,如何把所承載和沉淀的業務能力快速輸出,貢獻給整個行業,抑或為整個社會商業生態提供基座支撐。那么面向未來,將平台產品進行SAAS化升級真正將能力進行有價值開放輸出是我們提前要布局的核心方向。
那么將平台產品進行SAAS輸出,需要解決那些問題呢?這里嘗試把核心問題列舉一下:
1. 如何根據不同用戶需求進行計算能力按需調度分配?(IAAS/PAAS)
2. 如何滿足用戶數據安全性要求,嚴格隔離不同用戶的數據,使用戶只能看到自己的數據?(PAAS)
3. 如何支持不同用戶在標准的數據對象/數據模型上按需添加定義自定義的數據對象/擴展模型?(PAAS & SAAS)
4. 如何按照不同用戶進行按需功能搭配組合,滿足不同用戶從基礎到專業級不同業務場景需求?(SAAS)
5. 如何統一對平台產品進行升級而不影響用戶已有數據及功能?(IAAS、PAAS、SAAS)
通過以上問題,我們可以看出產品SAAS化輸出的關鍵是如何對不同的用戶通過標准+擴展能力按需進行算力、數據、安全、功能有效定制,支持多用戶共性和個性的問題,也暨多租戶的問題,同時也涉及到計費和服務水平等相關問題。我們下面來聊下上述問題的解題關鍵和解題思路
1. 第1個算力的問題核心是調度問題,彈性計算提供在IAAS層的統一算力調度能力,而Serverless則可以在PAAS層提供更高層次的算力調度能力。
2. 第4個問題的核心是業務流程的抽象和業務功能的拆分,領域驅動的設計以及服務化(微服務)在平台功能抽象拆分提供了相對成熟的思路,催化了以縱向業務功能細分作為域划分的依據的服務化方案以及組織結構,主要訴求是在細分的業務功能服務基礎上,能按需快速靈活的組合支撐不同的業務模式,提供業務敏捷性,支撐業務創新求變。
當然放過來,由於縱向功能細分,業務功能域增多,整個業務鏈條上的咬合點越來越多,隨之產生越來越多的數據來源冗余重復或者缺失,功能或者重合且各自發散,或者缺失,最終給整體業務帶來較多數據和功能的不一致性風險,這樣一來不僅橫向端到端的業務串聯成本高,而且關鍵路徑的風險收斂成本比較高,矛盾沖突點集中在各縱向域功能和數據咬合處,具體表現為:
數據上:1.無主數據,有數據需求無owner,2.大量重復且不一致數據,
功能上:3.部分業務功能缺失,4.域之間存在業務功能重復且行為不一致。
到底是縱向切分域還是橫向分業務模式拉平來做,這個問題沒有標准答案,更沒有最佳答案,只有根據不同的業務發展階段及時動態調整試錯,是一個不斷尋找相對最優解的動態過程
3. 彈性計算和Serverless解決了算力的問題,領域驅動服務化設計解決了功能的拆分和按需搭配組合的問題,那么剩下核心問題就是數據了:如何以一套統一的數據架構即能支撐多租戶的數據安全性需求以及通用的數據存儲,也能支撐用戶擴展的自定義數據對象定義和模型變更,同時也要保證數據定義層面的擴展和變更不會影響自身和其他租戶的業務功能的可用性。我們來分析下可能的方案(暫不考慮按服務邊界進行數據庫拆分):
1. 統一的數據庫,標准數據模型和擴展數據模型直接映射到物理表和索引:很顯然對於不同的租戶自定義的數據對象和數據模型要求是無法支撐的,物理數據模型會相互干擾,相互沖突直到無以為繼。即使是對於所有租戶完全標准的功能和數據存儲,平台自身的標准模型的升級的DDL也會對用戶的可用性造成較大影響,所以顯然是行不通的。
2. 如果為每個租戶創建各自的數據庫呢?各自租戶擁有各自的數據庫,可以滿足用戶數據安全隔離的需求,也可以滿足各租戶自定義的數據需求,看上去像是一種合理的SAAS數據方案,但是仔細分析,發現有兩個明顯的問題:
1. 如果用戶需要修改或者擴展現有物理數據模型而進行的DDL操作,必然會影響線上業務的整體可用性,也可能會影響到標准數據模型,從而影響到線上功能使用。
2. 如果用戶可自定義對物理模型進行擴展和定制,當平台進行模型升級的時候,極容易產生物理模型的沖突,導致新舊功能異常。
3. 由於用戶在各自數據庫存在各自定義的擴展和定制,則平台數據模型和功能升級,需要針對不同的租戶進行分別驗證,存在着極大的升級驗證工作量和風險。
以上兩種方案可行性低,我們從其中發現的問題是平台業務系統的邏輯模型到物理模型的直接映射是造成問題的主要因素。既然物理模型的變更是平台不穩定的動因,那么我們是否通過解耦業務邏輯模型和物理模型的映射關系來嘗試解決這個問題呢?
既然問題已經定義清楚了,如何解決這個問題呢?通常我們解決架構問題的一個“萬能”的方法是:增加一個層次,我們也來套用一次,增加一個層次(元數據層)來解耦邏輯模型到物理模型強映射的問題:
1. 首先,我們需要對業務進行建模,對業務進行抽象,定義出業務邏輯模型,然后對模型進行二次抽象,定義出邏輯模型的定義數據,實現業務模型的數據化,也暨模型的元數據(the metadata of the logic model ),將模型結構存儲為數據,而不是直接對應的物理存儲結構。
2. 其次根據定義出的元數據,也就是對數據對象定義數據,數據對象數據內容數據的存儲結構進行統一抽象,形成元數據邏輯模型
3. 將元數據邏輯模型映射到元數據物理模型,對應實際存儲結構
4. 通過對業務模型的變更變成了對元數據層的數據的變更,而不是物理結構的變更,來實現業務邏輯模型同物理模型的解耦。

很多事情說起來好像挺簡單,實際上是一個非常巨大的系統工程,將其付諸實踐是挑戰非常大的事情,而取得踏踏實實的成功更難,上述的解題思路是Salesforce的解題思路,而且Salesforce 不僅取得了成功,而且接近將其做到的極致,下面我們站在巨人的肩膀上來看看Salesforce如何通過元數據驅動的架構(核心是基礎數據架構)來支撐多租戶的SAAS業務平台的。注意:由於Salesforce並未有對核心實現邏輯進行完全公開和說明,所以本文所整理的部分核心邏輯包含了作者的邏輯推理和解讀,但是確實進行了邏輯驗證和場景驗證,如有紕漏和不盡全的地方,歡迎討論及指正。
元數據驅動的多租戶架構
Salesforce將Force.com定義為PAAS平台,Force.com的基礎就是元數據驅動的軟件架構來支撐多租戶應用。下面將元數據驅動的軟件架構作為核心進行介紹。
一、多租戶意味着什么?
多租戶的含義用一句話來描述就是:一個雲平台,無數多個客戶。
一個雲平台的含義是:一個代碼庫,一個數據庫,一整套共享的可擴展服務包括數據服務、應用服務以及Web服務。
無數多個客戶的含義是:每個客戶都被分配一個唯一的租戶OrgID,所有的數據存儲都是按照租戶OrgID隔離的,所有的數據訪問必須包含OrgID,所有的操作也都是包含租戶OrgID的,也就是所有的客戶數據和行為都是被安全的通過唯一的租戶Org進行嚴格的隔離的。
每個租戶/組織只能看到和定義按照自己租戶OrgID隔離的它自己版本的元數據和數據,而且只能執行自己租戶OrgID所授權的行為,這樣每個租戶就擁有各自版本的SAAS方案。
二、元數據驅動意味着什么
元數據對於平台意味着平台數據的數據,對於租戶意味着是關於租戶數據的數據,
當用戶定義一個新的用戶表的時候,用戶創建的不是數據庫中的物理表,而是在系統態的元數據表中添加了一條記錄,這個記錄描述的是用戶表的邏輯定義,是虛擬的,這個表並不在數據庫中物理存在,而這條記錄代表就是用戶態的數據表。
當用戶定義了用戶表的一個新的字段時,用戶並沒有在物理表中創建物理字段,而是在系統態的元數據表中添加了一個記錄,這個記錄描述的用戶表的字段組成的邏輯結構,是虛擬的,這個字段也不再數據庫中表結構中物理存在,而這條記錄代表的就是用戶態的用戶表字段。
也就是通過存儲在系統態的元數據表的元數據記錄來作為虛擬用戶的數據庫結構。
三、元數據驅動的多租戶整體架構
我們先來大概了解下元數據驅動的多租戶架構的整體架構,整體架構大概分為5個大的邏輯層次:
1. 底層數據架構分為三個層次:
1. 最底層是數據層,存儲了離散的系統和用戶的業務數據,業務日常運營的數據存儲在這里。
2. 公共元數據層,存儲了應用系統標准的對象和標准的字段定義,對底層數據的結構進行定義說明
3. 租戶特定元數據,存儲了租戶自動的對象和自定義的字段的定義,用於對底層的數據的結構進行定義說明。
2. 通用數據字典UDD(Universal Data Dictionary)運行引擎層實現了應用對象到底層數據存儲的映射,包含對象模型操作、SOQL語言解析、查詢優化,全文搜索等功能,我們常說的ORM功能也是其核心功能,但比其復雜的多。
3. 平台服務層,提供PAAS層平台服務,提供應用對象模型的創建,權限模型創建,邏輯和工作流程創建以及用戶界面的創建包括屏幕布局,數據項,報表等
4. 標准應用層,提供端到端的標准的業務應用功能。
5. 租戶虛擬應用層,用戶可以在標准應用層或者平台服務層之上定義自己特有的業務應用功能,來滿足自己特定的業務場景需要。

其中底層數據架構為最為關鍵的平台基石(the corner stone),其核心運行引擎也是基於強大的底層數據架構基礎上構建的。本文則以元數據驅動的多租戶數據架構為核心來一一展開。
四、元數據驅動的多租戶數據架構
下面我們具體來看下系統態的數據模型,基於Salesforce加上個人推理的元數據驅動的多租戶數據模型。注意:由於Salesforce並未有對核心邏輯進行完全公開和說明,所以本文所整理的部分核心模型包含了個人的邏輯推理和解讀,但是確實進行了邏輯驗證和場景驗證,如有紕漏和不盡全的地方,歡迎討論及指正。
Salesforce雲服務平台遵循的是面向對象的設計理念,所有的實體、實體關系以及實體的CRUD均是以對象的視角來進行的,所以其元數據驅動的多租戶數據模型的存儲基本元素也是按照對象的顆粒度進行存儲,源自與OO的對象間引用,同普通關系數據庫主外鍵關系異曲同工,只是細節處理上不盡相同,請大家注意這一點。
1. 元數據驅動的多租戶數據架構概覽
首先,我們先來大概了解下元數據驅動的多租戶模型的核心內容,元數據驅動的多租戶的數據模型主要分為三個部分:元數據表、數據表和功能透視表。
1. 元數據表(Metadata Tables)
元數據表用於存放系統標准對象以及用戶自定義對象和字段的定義的元數據,也就是系統和用戶對象的邏輯結構暨對應於關系數據庫中的虛擬表結構。元數據表主要包括Objects表以及Fields表,是系統標准對象和用戶對象定義數據的倉庫,元數據倉庫。
2. 數據表(Data Tables)
數據表用戶存放系統以及用戶對象和字段的實際數據,實際的用戶業務數據以及應用系統相關數據存放在這里。數據表包括Data表和存放大文本數據的Clob表。數據表存儲了絕大部分用戶的實際數據,是一個巨大的用戶業務數據倉庫。
3. 功能透視表(Specialized Pivot Tables)
功能透視表包含了非常關鍵的關系表、索引表、關系表以及其他特定用途表。例如關系表定義了對象間的關系,索引表解決虛擬結構索引的問題,后續進行詳盡的敘述。

2. 元數據驅動的多租戶數據架構詳解
上一節粗略地描述了元數據驅動的多租戶模型三大部分模型實體和基本作用,大家可能會比較疑惑這么簡單一個實體模型,怎么就起了這么個牛逼的名字,而且支撐了“一個雲平台,無數個客戶”,我們下面就對此模型的核心邏輯進行詳細展開和推理說明,同時詳細闡述以此模型為中心的服務來說明整個元數據層或者說UDD(Universal Data Dictionary)層的設計。
土話說:“沒有對比,就沒有傷害”,道理是想通的,用相似的事物進行對比是對理解客觀事物進行了解比較好的方法,找出其相同點,共性的地方,找出其不同點,異樣的地方,同時識別出是否有不可對比的方面,從各個方面去對比,則能更全面、更深入的了解客觀事物。
下面我按照普通應用設計思路方式來定義一個簡單直觀的多租戶SAAS數據架構方案示例,作為元數據驅動多租戶數據架構方案的對比基准方案,用對比來更好的幫大家了解元數據驅動多租戶數據模型及架構的設計邏輯。
1. 普通多租戶SAAS數據架構方案示例(僅做示例)
1. 多租戶基本思路:每個租戶一個數據庫,提供數據庫級別的租戶數據隔離,平台提供標准應用功能模型,用戶可以在各自數據庫內定義以及修改各自的定義模型,所有模型采用數據庫物理表、索引,主外鍵實現。不同的租戶通過路由到不同的數據庫來實現隔離。
2. 域模型樣例采用大家都熟悉的最小集的訂單模型實現,包含商品、用戶、訂單和訂單詳情表。注意:此簡化模型僅用做示意說明,和意圖無關大多數字段均省略,非嚴謹定義。

3. 示例模型數據
1. 數據庫物理表數據:Customer
Demo Table |
Customer |
||||
---|---|---|---|---|---|
CustomerID |
CustomerNo |
FirstName |
LastName |
NickName |
LoginName |
CI00000000000000000001 |
CI200903091014A0000001 |
Yan |
Cheng |
cy |
chengyan |
CI00000000000000000002 |
CI200903091014A0000002 |
Jun |
Ling |
lj |
lingjun |
CI00000000000000000003 |
CI200903091014A0000003 |
Tommy |
Valdles |
tom |
Tommy |
CI00000000000000000004 |
CI200903091014A0000004 |
Dorothy |
Franklin |
doro |
Dorothy |
2. 數據庫物理表數據:Product
Demo Table |
Product |
|||||
---|---|---|---|---|---|---|
ProductID |
ProductNo |
ProductName |
ProductPrice |
Currency |
ProductStatus |
CreatedTime |
PI00000000000000000001 |
PI201901060930A0000001 |
IPhone8 256G Golden |
6000 |
CNY |
Online |
2018/7/9 19:14 |
PI00000000000000000002 |
PI201901060930A0000002 |
IPhoneX 256G Golden |
10000 |
CNY |
Online |
2018/7/10 19:14 |
PI00000000000000000003 |
PI201901060930A0000003 |
IPhoneXR 256G Golden |
8000 |
CNY |
Online |
2018/7/11 19:14 |
3. 數據庫物理表數據:Order
Demo Table |
Order |
|||
---|---|---|---|---|
OrderID |
OrderNo |
CustomerID |
OrderStatus |
OrderTime |
OI00000000000000000001 |
ON201903091914A0000001 |
CI00000000000000000001 |
Completed |
2019/3/9 19:14 |
OI00000000000000000002 |
ON201903091914A0100003 |
CI00000000000000000002 |
Completed |
2019/3/9 19:14 |
OI00000000000000000003 |
ON201903091914A0200005 |
CI00000000000000000004 |
Canceld |
2019/3/9 19:14 |
4. 數據庫物理表數據:OrderItem
Demo Table |
OrderItem |
|||||
---|---|---|---|---|---|---|
OrderItemID |
OrderID |
ItemID |
ItemPrice |
ItemCurrency |
ItemQuantity |
OrderItemStatus |
OII0000000000009835101 |
OI00000000000000000001 |
PI00000000000000000001 |
5888 |
CNY |
2 |
InProduction |
OII0000000000009835102 |
OI00000000000000000001 |
PI00000000000000000002 |
9888 |
CNY |
3 |
Canceled |
OII0000000000009997401 |
OI00000000000000000002 |
PI00000000000000000003 |
7888 |
CNY |
1 |
Completed |
OII0000000000009998702 |
OI00000000000000000003 |
PI00000000000000000003 |
7888 |
CNY |
1 |
Canceled |
4. 實體表關系
Order表同OrderItem為父子表,通過OrderID進行主外鍵關聯;Customer表同Order表為父子表,通過CustomerID進行主外鍵關聯;Product表同OrderItem表為父子表,通過ProductID進行主外鍵關聯。
5. 用戶自定制
用戶有執行DDL權限,可以在自己租戶數據庫內在進行擴展模型自定義,建立自定義的物理表,索引,關系等。
6. 問題和風險
1. 用戶具有執行DDL權限,可以自定義數據庫物理模型,會帶來各租戶的自定義數據模型大爆炸,會給后續平台模型定義升級沖突,造成模型升級的巨大的障礙
2. 同時由於系統標准模型和用戶模型均為物理模型,未有做系統標准和自定義數據的有效隔離,如何保證平台應用的每一次升級必然會考慮對現有用戶自定義模型的穩定性和可用性的影響,在自定義物理模型的情況下,不僅挑戰巨大,而且包含巨大的回歸驗證的工作量,很難收斂。
3. 當用戶執行DDL時,通常會鎖定數據庫物理資源,當數據庫數量非常巨大時可能會帶來不可控的downtime,對應用系統的可用性造成加大影響。如果數據庫是每個租戶各自獨占的還只會影響到單個租戶,但是如果是多租戶共享數據庫,則可能會影響到其他租戶,影響是災難性的。作為雲平台服務商,不管是用戶操作還是系統行為,我們都不期望我們的設計對用戶系統的可用性造成影響,所以用戶執行DDL的行為是否允許確實有待商榷,但是如果不允許,用戶可擴展性在這種設計環境中必然受到一定程度的限制。
2. 元數據驅動的多租戶數據模型(Metadata Tables)
前面章節描述了元數據驅動的多租戶模型簡單模型圖,本小節詳細解說下每個核心實體表的核心結構,同時已知資料部分較為簡略,無法描述模型全貌和核心細節,為了模型完整性,整體數據模型包含了作者思路推理部分,用以來完整清晰地定義模型。當然由於所有模型都是主觀的(subjective),僅代表個人觀點,歡迎大家的不同的觀點,一起討論改進。
正如前面”一個雲平台“的提到的通過一個統一的數據庫來支撐無數個租戶,所以元數據驅動的多租戶模型是基於一個共享數據庫的前提下。當然多租戶實現設計多種多樣,大家可以不拘泥此種。
1. 元數據表之對象定義表:Objects表

Object系統表存儲了每個租戶為它的擴展應用對象定義的元數據,包含如下核心字段:
1. ObjID:應用對象唯一標識,具有固定長度和格式。
2. OrgID: 應用對象所歸屬的租戶ID,用於統一共享數據庫內的多租戶數據隔離,通常和租戶定義的域名對應。
3. ObjName/Name: 對象名稱,用於系統配置和開發(developer name)。
4. Label: 對象的顯示名稱。
除了用戶自定義對象,系統的標准對象也是采用相同的方式進行定義的。
2. 元數據表之字段與關系定義表:Fields表

Fields系統表存儲了每個租戶為他的擴展應用對象字段定義的元數據,包含了其所歸屬的應用對象的租戶OrgID,字段所屬對象的ObjID,字段定義標識FieldID,字段名稱FieldName,字段存儲位置定義FieldNum,數據類型DataType,數據類型重要補充關聯字段(DigitLeft,Scale,TextLength,RelatedTo,ChildRelationshipName)以及是否必選、唯一、索引標記,還有部分標准字段。Fields表非常關鍵,其不僅定義了普通的應用對象字段定義包括基本信息和數據類型信息,而且通過特殊關系字段對不同應用對象之間的關系進行了定義,詳細說明如下:
1. FieldID: 此對象字段的唯一標識,具有固定長度和格式
2. OrgID: 其所歸屬的應用對象所歸屬的租戶OrgID
3. ObjID:字段所屬對象的ObjID
4. FieldName/Name: 字段名,用於系統配置和開發(developer name)。
5. Label: 字段展示名稱,用以展示給最終用戶。
6. FieldNum:對應到Data數據表的數據存儲字段映射,暨Data表中ValueX字段中的X。
7. DataType:指定此對象字段的數據類型包含普通類型:Number、TEXT、Auto Number、Date/Time、Email、Text Area等,也包含特殊的關系類型如:Look up關系類型、Master-Detail關系類型等。
8. DigitLeft和Scale:用於Number、Currency、Geolocation等數字數據類型的關聯設定,例如定義了一個字段的DateType為Number,則需要指定其整數部分的最大位數DigitLeft和小數部分的最大位數Scale,兩部分長度總和不超過18位。
9. TextLength:當數據類型為TEXT時啟用,用於指定TEXT類型的字符的長度限制。
10. RelatedTo和ChildRelationshipName:這兩個字段當DateType為關系類型(Look up,Master-Detail等)時會啟用,其中RelatedTo保存關聯的應用對象ID,ChildRelationshipName用於保存父子關系中子方的關系名稱,同一個父對象的子方的關系名稱唯一,用於關系的反向查詢。
11. IsRequired:此字段數據保存時,是否校驗值的存在
12. IsUnique: 是否允許重復值
13. IsIndexed:此字段是否需要建索引
14. 其他字段:此處僅列舉了說明模型所需要的字段,其他字段暫不進行列舉,不列舉原因和其重要性並無直接關聯。
3. 數據表(Data Tables)之關系數據表:Data表

MT_Data系統表存儲了MT_Objects和MT_Fields元數據表內定義的數據對象(表)所對應的數據,一一映射到不同的租戶各自定義的表和表中的字段(對象和對象字段)。
1. GUID:數據表的主鍵,用於存放每個應用對象實例的標識ID。
2. ObjID: 其所歸屬的應用對象所歸屬的租戶OrgID
3. Name: 應用對象實例名稱
4. Value0…Value500: 用於存放對象實例字段的數據,其ValueX中X值對應到Fields表中FieldNum定義,ValueX存放的數據,不管原始數據類型,存儲格式均為變長字符串格式。
4. 數據表(Data Tables)之非結構化數據表:CLobs
MT_Clobs用於存儲大字符段的存儲CLOB,同時CLOB的也存儲在數據庫外的索引結構中,用於快速的Full-Text文本檢索。
3. 元數據模型核心實體關系圖
我們在應用系統開發中,通常我們定義的數據結構包括數據表、表字段,索引通常都會直接定義在物理數據庫中,創建物理的表和字段以及索引等。
但是在元數據驅動平台數據模型中,我們定義的用戶表包括系統表都是邏輯表,其結構是虛擬的,用戶表的定義存儲在Objects表,對應的字段定義存儲在Fields表中,實際用戶數據存儲在Data表中,特別注意的是,對象的引用關系定義也定義在Fields表中,以特殊數據類型方式來定義。(另:Relationships表后面章節進行描述)。
從每個租戶視角來看,每個租戶都在一個共享數據庫內擁有一個基於租戶標識OrgID來隔離的虛擬的租戶數據庫。
元數據實體包括Objects和Fileds實體以及實際數據Data實體都包含租戶OrgID,這樣就可以通過租戶OrgID來天然隔離各租戶的數據,當然不止這些實體,包括索引相關等透視表實體也使如此。

4. 標准對象與標准字段
前面整體架構層次里提到了公共元數據層和標准應用層,公共元數據層提供了標准對象和標准字段的定義。
其中標准對象為每個租戶提供公共端到端的應用的標准應用功能。
Standard Objects |
---|
Account |
Contact |
Lead |
Opportunity |
Case |
… |
同時用戶可以在標准的對象基礎上擴展自定義的應用對象,滿足自己的特定業務場景。__c后綴代表自定義,后續詳解。
Custom Objects |
---|
Product__c |
Customer__c |
Order__c |
OrderItem__c |
… |
而標准字段則提供給每個對象包括自定義對象的共同的字段,包含部分業務字段和非業務字段。
Standard Fields |
---|
ID |
Name |
CreatedBy |
CreatedDate |
LastModifiedBy |
LastModifiedDate |
OwnerID |
IsDeleted |
… |
用戶也可以在標准對象和自定義對象內自定義不同的字段,以滿足業務需要。__c后綴代表自定義,后續詳解。
Custom Fields |
---|
First_Name__c |
Last_Name__c |
Nick_Name__c |
Login_Name__c |
Custome__Status__c |
Product_Status__c |
Product_No__c |
Order_No__c |
ItemID__c |
Item_Quantity__c |
Order_Time__c |
5. 對象關系類型
應用對象關系類型主要分為Look up 和 Master-Detail兩種關系類型,其中Look up為弱的父子關系類型,Master-Detail為強的父子關系類型,其特性對比如下。
特性 |
Look up |
Master-Detail |
---|---|---|
1.非空性 |
可選 |
不可以 |
2.刪除行為 |
方式可選:Clear/Block/Cascade |
Cascade級聯 |
3.記錄關系 |
相互獨立父子關系 |
不可分父子關系 |
6. 元數據驅動的多租戶數據架構示例
同樣采用普通多租戶SAAS數據架構方案中相同的域模型和示例數據作為參照進行說明,只不過在這里域模型不再對應到數據庫的物理模型,而是對應到元數據所定義的虛擬數據庫的邏輯模型。請前后對比兩種模型對用戶業務模型承載的差異和聯系,以便深入了解元數據驅動的多租戶數據架構。

對於Tenant租戶 A00001,需要支撐相同的業務邏輯,需要定義相同的域模型,和普通的方案不同的是,這里采用元數據驅動的多租戶數據模型來定義訂單域模型和對應示例數據,其中域模型定義在元數據表(Metadata Tables)中,數據存儲在Data Tables表中。
1. 用戶自定義對象Product的定義
Product對象的基本信息定義在Objects表,作為Objects表的一條記錄,通過OrgID進行不同租戶數據隔離。Object中的每一條記錄都代表一個不同的對象。Objects表的定義非常清晰,這里不做過多的解釋,請參考Objects表介紹。
Objects |
||||
---|---|---|---|---|
ObjID |
OrgID |
ObjName |
ObjLabel |
Description |
01I2v000002zTEU |
A00001 |
Product |
Product |
Product |
Product對象的字段結構定義在Fields表,同時通過ObjID同Order對象定義進行關聯,通過OrgID進行多租戶數據隔離。
FieldID格式為字段定義的標識ID,用於區分每個字段定義,對於標准字段,則采用標准字段ID,如Name,則直接采用Name作為字段標識ID,對於自定義字段,則元數據引擎自動生成15位的標准格式的FieldID。其他字段定義請參考前面的Fields元數據表詳細介紹。
下面詳細描述一下Product對象中每個字段定義:
1. 產品名稱Name 字段 為標准字段,數據格式為TEXT,長度為80
2. 產品編號ProductNo 為自定義字段,數據格式為TEXT,長度為22,FieldNum為1對應Data表存儲字段Value1,存儲格式為變長字符串。
3. 產品價格ProductPrice為自定義字段,數據格式為Currentcy(此格式類似Number,不同是帶幣種),整數最大長度DigitLeft:16位,小數位最大精度Scale:2位,FieldNum為2對應Data表存儲字段Value3,存儲格式為變長字符串。。
4. 狀態ProductStatus為自定義字段,數據格式為TEXT,長度為20,FieldNum為3對應Data表存儲字段Value3,存儲格式為變長字符串。
Fields |
|||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
FieldID |
OrgID |
ObjID |
FieldName |
FieldLabel |
FieldNum |
DataType |
DigitLeft |
Scale |
TextLength |
RelatedTo |
ChildRelationshipName |
IsRequired |
IsUnique |
IsIndexed |
… |
Name |
A00001 |
01I2v000002zTEU |
Name |
Product Name |
(標准字段) |
TEXT |
80 |
Y |
|||||||
00N2v00000OHwTL |
A00001 |
01I2v000002zTEU |
ProductNo |
Product No |
1 |
TEXT |
22 |
Y |
Y |
Yes |
|||||
00N2v00000OHwTQ |
A00001 |
01I2v000002zTEU |
ProductPrice |
Product Price |
2 |
Currency |
16 |
2 |
Y |
||||||
00N2v00000OHwTV |
A00001 |
01I2v000002zTEU |
ProductStatus |
Product Status |
3 |
TEXT |
20 |
Y |
|||||||
00N2v00000PDTIn |
A00001 |
01I2v000002zTEU |
Category |
Category |
4 |
TEXT |
20 |
||||||||
00N2v00000PDTIx |
A00001 |
01I2v000002zTEU |
Brand |
Brand |
5 |
TEXT |
20 |
2. 用戶自定義對象Customer的定義
Customer對象的基本信息定義在Objects表,作為Objects表的一條記錄,通過OrgID進行不同租戶數據隔離。Object中的每一條記錄都代表一個不同的對象。Objects表的定義非常清晰,這里不做過多的解釋,請參考Objects表介紹。
Objects |
||||
---|---|---|---|---|
ObjID |
OrgID |
ObjName |
ObjLabel |
Description |
01I2v000002zTEZ |
A00001 |
Customer |
Customer |
Customer |
Customer對象的字段結構定義在Fields表,同時通過ObjID同Order對象定義進行關聯,通過OrgID進行多租戶數據隔離。
下面詳細描述一下Customer對象中每個字段定義:
1. 用戶名稱Name,必選標准字段,不過多解釋
2. 用戶編號CustomerNo為自定義字段,數據類型為TEXT,長度為22,FieldNum為1對應Data表存儲字段Value1,存儲格式為變長字符串。
3. FirstName和LastName為自定義字段,數據類型為TEXT,長度均為20,FieldNum為2,3對應Data表存儲字段Value2和Value3,存儲格式為變長字符串。
4. 用戶昵稱Nick Name為自定義字段,數據類型為TEXT,長度均為20,FieldNum為4對應Data表存儲字段Value4,存儲格式為變長字符串。
5. 用戶登錄名LoginName為自定義字段,數據類型為TEXT,長度均為20,FieldNum為5對應Data表存儲字段Value5,存儲格式為變長字符串。
6. 用戶狀態CustomerStatus為自定義字段,數據類型為TEXT或者PickList,長度為20,FieldNum為6對應Data表存儲字段Value6。為簡化起見,狀態字段暫定義為TEXT,對應Data表存儲字段Value4,存儲格式為變長字符串。
Fields |
|||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
FieldID |
OrgID |
ObjID |
FieldName |
FieldLabel |
FieldNum |
DataType |
DigitLeft |
Scale |
TextLength |
RelatedTo |
ChildRelationshipName |
IsRequired |
IsUnique |
IsIndexed |
… |
Name |
A00001 |
01I2v000002zTEZ |
Name |
Customer Name |
(標准字段此項無) |
TEXT |
80 |
Y |
|||||||
00N2v00000OHwmn |
A00001 |
01I2v000002zTEZ |
CustomerNo |
Customer No |
1 |
TEXT |
22 |
Y |
Y |
Yes |
|||||
00N2v00000OHwms |
A00001 |
01I2v000002zTEZ |
FirstName |
First Name |
2 |
TEXT |
20 |
Y |
|||||||
00N2v00000OHwmx |
A00001 |
01I2v000002zTEZ |
LastName |
Last Name |
3 |
TEXT |
20 |
Y |
|||||||
00N2v00000OHwoF |
A00001 |
01I2v000003zTEZ |
NickName |
Nick Name |
4 |
TEXT |
20 |
Y |
|||||||
00N2v00000OHwoK |
A00001 |
01I2v000004zTEZ |
LoginName |
Login Name |
5 |
TEXT |
20 |
Y |
|||||||
00N2v00000OHwoP |
A00001 |
01I2v000005zTEZ |
CustomerStatus |
Customer Status |
6 |
TEXT |
20 |
Y |
3. 用戶訂單Order邏輯表的定義
Order對象的基本信息定義在Objects表,作為Objects表的一條記錄,通過OrgID進行多租戶數據隔離。Objects表中的每一條記錄都代表一個不同的對象。
Objects |
||||
---|---|---|---|---|
ObjID |
OrgID |
ObjName |
ObjLabel |
Description |
01I2v000002zTEj |
A00001 |
Order |
Order |
Order |
Order對象的字段結構定義在Fields表,同時通過ObjID同Order對象定義進行關聯,通過OrgID進行多租戶數據隔離。
下面詳細描述一下Order對象中每個字段定義:
1. 訂單編號OrderNo為自定義字段,DataType數據格式為TEXT,長度為22,FieldNum為1,對應Data表存儲字段Value1,存儲格式為變長字符串。
2. 關系字段Customer為自定義關系字段,DataType類型為弱類型Look up 關系,關聯到父對象Customer,則RelatedTo列存儲Customer的ObjID:01I2v000002zTEZ,對應的FieldNum為2,則Customer對象實例GUID存儲在Data表的Value2列。ChildRelationshipName列存儲對象父子關系中子關系名稱:orders,用於對象關系中從父對象實例數據反查子對象實例數據。
3. 訂單狀態OrderStatus為自定義字段,DataType類型為TEXT,長度為20,FieldNum為3,則狀態存儲在Data表的Value3列。為簡化起見,狀態字段暫定義為TEXT。
4. 下單時間OrderTime為自定義字段,DataType類型為Date/Time,FieldNum為4,則下單時間存儲在Data數據表的Value4列。
Fields |
|||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
FieldID |
OrgID |
ObjID |
FieldName |
FieldLabel |
FieldNum |
DataType |
DigitLeft |
Scale |
TextLength |
RelatedTo |
ChildRelationshipName |
IsRequired |
IsUnique |
IsIndexed |
… |
00N2v00000OHwsb |
A00001 |
01I2v000002zTEj |
OrderNo |
Order No |
1 |
TEXT |
22 |
Y |
Y |
Yes |
|||||
00N2v00000OHwsl |
A00001 |
01I2v000002zTEj |
Customer |
Customer |
2 |
Lookup |
01I2v000002zTEZ |
orders |
Y |
Yes |
|||||
00N2v00000OHwt0 |
A00001 |
01I2v000002zTEj |
OrderStatus |
Order Status |
3 |
TEXT |
20 |
Y |
|||||||
00N2v00000OHwtt |
A00001 |
01I2v000002zTEj |
OrderTime |
Order Time |
4 |
Date/Time |
Y |
4. 用戶訂單行OrderItem邏輯表定義 同樣的,OrderItem對象的基本信息也以一條記錄的信息定義在Objects表,通過OrgID進行多租戶數據隔離。Objects表中的每一條記錄都代表一個不同的對象。
Objects |
||||
---|---|---|---|---|
ObjID |
OrgID |
ObjName |
ObjLabel |
Description |
01I2v000002zTEo |
A00001 |
OrderItem |
Order Item |
Order Item |
OrderItem的字段結構也定義在Fields表,通過ObjID同OrderItem對象關聯,通過OrgID進行多租戶數據隔離。
下面詳細描述一下Order對象中每個字段定義:
1. 關系字段Order為自定義關系字段,DataType類型為強類型的Master-Detail關系,關聯到父對象Order,則RelatedTo列存儲Order對象的ObjID:01I2v000002zTEj,對應的FieldNum為1,則Order對象實例GUID存儲在Data表的Value1列。ChildRelationshipName列存儲對象父子關系中子關系名稱:OrderItem(s),用於對象關系中從父對象Order實例數據反查子對象實例數據。
2. 關系字段Product為自定義關系字段,DataType類型為弱類型的Look up關系,關聯到父對象Product,則RelatedTo列存儲Product對象的ObjID:01I2v000002zTEU,對應的FieldNum為2,則Product對象實例GUID存儲在Data表的Value2列。ChildRelationshipName列存儲對象父子關系中子關系名稱:OrderItem(s),用於對象關系中從父對象Product實例數據反查子對象實例數據。
3. 商品實際售價ItemPrice為自定義字段,DateType類型為Currentcy(此格式類似Number,不同是帶幣種),整數最大長度DigitLeft:16位,小數位最大精度Scale:2位,FieldNum為2對應Data表存儲列Value3,存儲格式為變長字符串。
4. 商品購買數量Item Quantity為自定義字段,DataType類型為Number,整形長度為18位,無小數位數,FieldNum為4,對應Data數據表存儲列Value4。
5. 訂單明細狀態OrderItemStatus為自定義字段,Datetype類型為TEXT,長度為20,對應FieldNum為5,對應Data數據表存儲列Value5。為簡化起見,狀態字段暫定義為TEXT。
Fields |
|||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
FieldID |
OrgID |
ObjID |
FieldName |
FieldLabel |
FieldNum |
DataType |
DigitLeft |
Scale |
TextLength |
RelatedTo |
ChildRelationshipName |
IsRequired |
IsUnique |
IsIndexed |
… |
00N2v00000OHwtF |
A00001 |
01I2v000002zTEo |
Order |
Order |
1 |
Master-Detail |
01I2v000002zTEj |
OrderItem |
Y |
||||||
00N2v00000OHwtZ |
A00001 |
01I2v000002zTEo |
Product |
Product |
2 |
Lookup |
01I2v000002zTEU |
OrderItem |
Y |
||||||
00N2v00000OHwte |
A00001 |
01I2v000002zTEo |
ItemPrice |
ItemPrice |
3 |
Currency |
16 |
2 |
Y |
||||||
00N2v00000OHwtj |
A00001 |
01I2v000002zTEo |
ItemQuantity |
Item Quantity |
4 |
Number |
18 |
0 |
Y |
||||||
00N2v00000OHwto |
A00001 |
01I2v000002zTEo |
OrderItemStatus |
Order Item Status |
5 |
TEXT |
20 |
Y |
5. 對象Schema
定義好的用戶應用對象Schema如下圖

6. 數據表Data表用戶數據存儲
前面提到了用戶自定義的應用對象以虛擬結構的方式存儲在Objects和Fields表中,那么用戶定義的應用對象Product、Customer、Order和OrderItem里的數據存儲在哪里呢?答案是Data表,用戶定義的對象的數據均會存儲在Data表中,每個用戶定義對象實例(或者近似稱為用戶表記錄)數據以Data表中一條記錄的形式存在。Product、Customer、Order表的數據記錄均存儲在Data表,OrderItem也亦是如此。
其中,GUID作為每條數據記錄暨是每個對象實例的全局唯一標識,OrgID進行多租戶數據隔離,ObjID同Objects表關聯代表具體哪個對象定義。 這里重點提一下,Fields中定義的對象字段在Data表中的存儲,其中Fields表中FieldNum非常關鍵,它對應了對象實例字段在Data表中的具體存儲位置,FieldNum對應數字決定着數據存儲在Data表中的哪個ValueX列。前面每個對象結構定義都對FieldNum對應Data的進行了說明,對象字段FieldNum可以不按照順序來,只要FieldNum沒有占用,可以任意對應,當然按照順序是比較好的實踐。
再舉例來說:
1. Order對象的Customer關系字段定義在Fields表中,其FieldNum為1,則其在Data表中存儲的位置,就是是Order對象實例在Data對應的記錄中Value1這個字段所存儲的值,存儲的值為Customer對象實例GUID,也就是:a062v00001YXEKuAAP、a062v00001YXEKzAAP等。
2. OrderItem對象的Product、ItemQuantity字段定義在Fields表中,其對應的FieldNum分別為2、4,則其在Data表中存儲的位置,就是OrderItem對象在Data對應的記錄中Value2、以及Value4所存儲的數據,也就是:a052v00000jbgEQAAY、2以及a052v00000jbgMqAAI、3等記錄。
Data |
|||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
GUID |
OrgID |
ObjID |
Name |
Value1 |
Value2 |
Value3 |
Value4 |
Value5 |
Value6 |
… |
Value500 |
CreatedBy |
CreatedDate |
… |
… |
a052v00000jbgEQAAY |
A00001 |
01I2v000002zTEU |
IPhone8 256G Golden |
PI201901060930A0000001 |
6000 |
Online |
Mobile Phone |
Apple |
|||||||
a052v00000jbgMqAAI |
A00001 |
01I2v000002zTEU |
IPhoneX 256G Golden |
PI201901060930A0000002 |
10000 |
Online |
Mobile Phone |
Apple |
|||||||
a052v00000jbgMvAAI |
A00001 |
01I2v000002zTEU |
IPhoneXR 256G Golden |
PI201901060930A0000003 |
8000 |
Online |
Mobile Phone |
Apple |
|||||||
a052v00000jc4ViAAI |
A00001 |
01I2v000002zTEU |
HUAWEI P30 256G |
PI201901060930A0000004 |
5000 |
Online |
Mobile Phone |
HUAWEI |
|||||||
a062v00001YXEKuAAP |
A00001 |
01I2v000002zTEZ |
Cheng Yan |
CI200903091014A0000001 |
Yan |
Cheng |
cy |
chengyan |
Valid |
||||||
a062v00001YXEKzAAP |
A00001 |
01I2v000003zTEZ |
Ling Jun |
CI200903091014A0000002 |
Jun |
Ling |
lj |
lingjun |
Valid |
||||||
a062v00001YXEL0AAP |
A00001 |
01I2v000004zTEZ |
Tommy Valdels |
CI200903091014A0000003 |
Tommy |
Valdels |
tom |
Tommy |
Valid |
||||||
a062v00001YXEL1AAP |
A00001 |
01I2v000005zTEZ |
Dorothy Franklin |
CI200903091014A0000004 |
Dorothy |
Franklin |
doro |
Dorothy |
Valid |
||||||
a072v000016DxY0AAK |
A00001 |
01I2v000002zTEj |
Order |
ON201903091914A0000001 |
a062v00001YXEKuAAP |
Completed |
2019-08-01T04:00:00.000+0000 |
||||||||
a072v000016DxYPAA0 |
A00001 |
01I2v000002zTEj |
Order |
ON201903091914A0100003 |
a062v00001YXEKzAAP |
Completed |
2019-03-09T11:00:00.000+0000 |
||||||||
a072v000016DxYFAA0 |
A00001 |
01I2v000002zTEj |
Order |
ON201903091914A0200005 |
a062v00001YXEL1AAP |
Completed |
2019-03-08T23:30:00.000+0000 |
||||||||
a082v00002mD8EoAAK |
A00001 |
01I2v000002zTEo |
Order Item |
a072v000016DxY0AAK |
a052v00000jbgEQAAY |
5888 |
2 |
InProduction |
|||||||
a082v00002mD8EpAAK |
A00001 |
01I2v000002zTEo |
Order Item |
a072v000016DxY0AAK |
a052v00000jbgMqAAI |
9888 |
3 |
Canceled |
|||||||
a082v00002mD8EtAAK |
A00001 |
01I2v000002zTEo |
Order Item |
a072v000016DxYPAA0 |
a052v00000jbgMvAAI |
7888 |
1 |
InTransfer |
|||||||
a082v00002mD8EuAAK |
A00001 |
01I2v000002zTEo |
Order Item |
a072v000016DxYFAA0 |
a052v00000jbgMvAAI |
7888 |
1 |
InTransfer |
|||||||
… |
7. 通用的存儲,按需轉換—Data表數據類型與存儲
我們看了元數據驅動的多租戶模型的核心關系,明白了用戶自定義表(包括應用系統表)以及表結構是在Objects和Fields進行虛擬定義的,也清楚的知道了系統以及用戶表的數據是作為一條條記錄存儲在Data表中的,那么我們下面來看下不同的數據類型如何在Data中進行存儲的呢?
在Fields表中,可以采用任何一種標准的結構化的數據類型,如text,number,date,以及date/time對用戶表字段進行定義,也可以采用特殊結構的數據類型對字段類型進行定義,如下拉框picklist,系統自增字段autonumber,公式列(只讀的公式推導列),布爾多選框,email,URL以及其他的類型,當然也可以通過系統應用來對Fields中的自定義字段進行強制約束包括是否必須非空以及掐校驗規則(如符合特定格式,符合特定值范圍等)。
上述的各種不同字段格式數據都是存儲在Data表中的ValueX列中的,Data表中包含500個數據列,稱為彈性列,用來存儲用戶數據和系統數據,也就是對應到Objects表和Fields表對應的虛擬表結構所要承載的數據。
特別的,所有彈性列都用了一個可變長度的字符串類型,以便於他們可以存儲任何結構化類型的應用和用戶數據(字符串,數字,日期等)。
正是因為彈性列把所有不同的數據類型拉平來存儲,所以任一彈性列可以對存儲任何對象的任何類型的屬性來存儲,用戶可以指定不同的對象的不同屬性對應的不同的存儲彈性列,當然同屬於相同對象的實例的屬性對應的彈性列是一致的。一個彈性列可以存儲來不同的格式的數據,前提條件是這些數據屬於不同的對象的不同屬性。例如:上一節示例中,Data表的Value2列可以存儲Order表的日期格式的OrderTime數據,也可以存儲OrderItem表的格式為字符串的OrderID數據。

如上所述,彈性列用通用數據類型暨可變長字符串來存儲所有類型的數據,這樣就可以在不同的用戶表字段間共享相同彈性列,即便它們的數據類型各異。
既然所有的數據全部用通用的可變長字符串來存儲,那么應用邏輯處理需要不同的數據格式時候怎么辦呢?具體做法如下:
當應用系統需要從彈性列讀取和寫入數據時候,UDD(Universal Data Dictionary)層暨元數據運行引擎會用底層數據庫系統數據轉換函數(如Oracle數據庫的TO_NUMBER,TO_DATE,TO_CHAR函數)按需對數據格式進行轉換,將字符串格式轉換成對應的數據格式(數字,日期等)。
如果存儲非結構化的大文本塊數據怎么辦呢?模型支持對Clob大字段的定義,對於在Data表中具有CLob數據的每一行數據,系統將其存儲在Clobs透視表中,並按照需要同Data表的對應數據對象實例記錄進行關聯。
8. 多租戶索引透視表(Pivot Tables)
1. Indexes透視表
大多數結構化的數據存儲在Data表內,如前面提到的,所有這些不同類型數據都是以可變字符串的形式存在ValueX列里面如各種數字以及日期等全部都是以可變字符存儲的,這樣雖然對於對象實例各種字段的存儲確實非常靈活,不同的列可以存儲不同類型的數據,即使同一ValueX列不同的對象也可以存儲類型的數據,但是這樣帶來一個巨大的問題,由於不同的數據類型以可變字符串的方式存儲在同一列內,你沒辦法利用底層數據庫索引的能力對其進行排序,ValueX列的數據都是一種按照離散的順序來存儲的。傳統的數據庫依賴原生的數據庫索引來快速在數據表內定位到符合查詢條件的記錄。而按照Data表ValueX列的數據存儲情況,在Data表建立ValueX列的索引來支撐數據快速查詢是不現實的。
所以解決辦法就是建立另外的透視表叫做Indexes索引表,並把數據拷貝出數據表並轉換成原始的的數據類型,並存儲到Indexes索引表列內,如原來是整形的數據以可變字符串的格式存儲在ValueX列中,拷貝到Indexes表之前通過函數將其轉換為原始的數據類型,在存儲到Indexes對應的NumValue列內,以方便建立索引,Indexes表包含強類型的索引類,像StringValue,NumValue,DataValue,用來定位對應數據類型的字段數據。

Indexes透視表的字段說明如下:
1. OrgID:其所歸屬的應用對象所歸屬的租戶OrgID
2. ObjID:字段所屬應用對象唯一標識
3. FieldNum:對象字段存儲位置
4. ObjInstanceGUID:對象實例唯一標識
5. StringValue:強類型的字符串列
6. NumValue:強類型的數字列
7. DateValue:強類型的日期列
下面的Indexes表示例包含對字符、數字和日期性數據的索引需求支持,數據來源於前面的Data表數據。
Indexes |
||||||
---|---|---|---|---|---|---|
OrgID |
ObjID |
FieldNum |
GUID |
StringValue |
NumValue |
DateValue |
A00001 |
01I2v000002zTEU |
1 |
a052v00000jbgEQAAY |
PI201901060930A0000001 |
||
A00001 |
01I2v000002zTEU |
1 |
a052v00000jbgMqAAI |
PI201901060930A0000002 |
||
A00001 |
01I2v000002zTEU |
1 |
a052v00000jbgMvAAI |
PI201901060930A0000003 |
||
A00001 |
01I2v000002zTEU |
1 |
a052v00000jc4ViAAI |
PI201901060930A0000004 |
||
A00001 |
01I2v000002zTEU |
4 |
a052v00000jbgEQAAY |
Mobile Phone |
||
A00001 |
01I2v000002zTEU |
4 |
a052v00000jbgMqAAI |
Mobile Phone |
||
A00001 |
01I2v000002zTEU |
4 |
a052v00000jbgMvAAI |
Mobile Phone |
||
A00001 |
01I2v000002zTEU |
4 |
a052v00000jc4ViAAI |
Mobile Phone |
||
A00001 |
01I2v000002zTEU |
5 |
a052v00000jbgEQAAY |
Apple |
||
A00001 |
01I2v000002zTEU |
5 |
a052v00000jbgMqAAI |
Apple |
||
A00001 |
01I2v000002zTEU |
5 |
a052v00000jbgMvAAI |
Apple |
||
A00001 |
01I2v000002zTEU |
5 |
a052v00000jc4ViAAI |
HUAWEI |
||
A00001 |
01I2v000002zTEZ |
1 |
a062v00001YXEKuAAP |
CI200903091014A0000001 |
||
A00001 |
01I2v000002zTEZ |
1 |
a062v00001YXEKzAAP |
CI200903091014A0000002 |
||
A00001 |
01I2v000002zTEZ |
1 |
a062v00001YXEL0AAP |
CI200903091014A0000003 |
||
A00001 |
01I2v000002zTEZ |
1 |
a062v00001YXEL1AAP |
CI200903091014A0000004 |
||
A00001 |
01I2v000002zTEj |
1 |
a072v000016DxY0AAK |
ON201903091914A0000001 |
||
A00001 |
01I2v000002zTEj |
1 |
a072v000016DxYPAA0 |
ON201903091914A0100003 |
||
A00001 |
01I2v000002zTEj |
1 |
a072v000016DxYFAA0 |
ON201903091914A0200005 |
||
A00001 |
01I2v000002zTEj |
4 |
a072v000016DxY0AAK |
2019-08-01T04:00:00.000+0000 |
||
A00001 |
01I2v000002zTEj |
4 |
a072v000016DxYPAA0 |
2019-03-09T11:00:00.000+0000 |
||
A00001 |
01I2v000002zTEj |
4 |
a072v000016DxYFAA0 |
2019-03-08T23:30:00.000+0000 |
||
A00001 |
01I2v000002zTEU |
2 |
a052v00000jbgEQAAY |
6000 |
||
A00001 |
01I2v000002zTEU |
2 |
a052v00000jbgMqAAI |
10000 |
||
A00001 |
01I2v000002zTEU |
2 |
a052v00000jbgMvAAI |
8000 |
||
A00001 |
… |
|||||
A00001 |
… |
Indexes表的底層索引是標准的,采用非唯一性的數據庫索引。當做對象檢索查詢的時候,實際上不是在Data數據表上做查詢,而是在Indexes索引表上做的查詢,獲取到OrgID,ObjectID以及GUID,然后再返回數據表獲取數據。也就是當系統查詢條件包含對象實例的結構化的字段時,系統查詢優化器采用MT_Indexes來幫助優化相關的數據訪問操作。
2. Unique Indexes透視表
由於Data數據表的多數據類型的無差別存儲,無法在Data數據表建唯一性的索引供用戶來使用對對象字段值進行唯一性校驗。為了支持用戶對象自定義字段的唯一性校驗,解決辦法是采用了Unique_Indexes 透視表;這個表非常類似於Indexes表,不過Unique_indexes采用底層原生的數據庫索引來強制唯一性校驗。當一個用戶嘗試來插入一個重復的值到具有唯一性約束的對象字段時,或者當用戶嘗試去在一個現存的包含唯一性的字段進行強制唯一性時,系統會給出唯一性校驗失敗的提示,阻止用戶的下一步操作。

Unique Indexes透視表的核心字段說明如下:
1. UniqueStringValue:唯一的字符串列
2. UniqueNumValue:唯一的數字列
3. UniqueDateValue:唯一的日期列
4. 其他字段定義請參考Indexes透視表
Relationships索引透視表
在元數據驅動的多租戶模型中,提到了在Objects表以及Fields表中保存了用戶對象結構和對象關系的定義,對象關系的定義是通過元數據模型Fields表字段數據類型提供了一個特殊的數據類型:“關系”(Relationship), 來給用戶用於聲明不同的用戶應用對象之間的關系,也就是我們通常說引用完整性。
對象之間的引用關系定義以及對象實例間的引用關系存儲在元數據表Objects、Fields中和Data表中,關聯查詢關系復雜,為了提升對象之間查詢的效率,特別是通過對象相互引用關系對對象實例數據進行檢索,系統提供關系索引透視表Relationship來優化對象引用關聯查詢。

Relationships索引透視表的字段說明如下:
1. OrgID:其所歸屬的應用對象所歸屬的租戶OrgID
2. ObjID:子對象的對象標識
3. GUID:子對象實例的唯一標識
4. RelationID:子對象內關系字段定義的標識
5. TargetObjInstanceID:父對象實例的唯一標識
關系透視表Relationship定義了兩個底層數據庫復合索引:
1. 第一個索引字段:OrgID + GUID,用於從子對象到父對象的關聯查詢
2. 第二個索引字段:OrgID + ObjID + RelationID + TargetObjInstanceID,用於父對象到子對象的關聯查詢
Relationships索引透視表會在后面SOQL章節進行進一步描述驗證。
4. 其他索引透視表
其他索引透視表的邏輯類似,都是為了滿足特定檢索和查詢需要,將數據同步到索引表,供應用系統使用。此處不再贅述,如確實有需要再補充。
五、SOQL與關系Relationships
SOQL是Salesforce Object Query Language的簡稱,具有SQL類似的語法結構,就像前面提到的一樣,Salesforce是以應用對象(Salesforce Object,簡稱SObject)的視角管理業務數據和功能,SOQL類似對用於對應有對象數據進行查詢的API。
1. 從SQL到SOQL
SOQL也是采用類似表查詢的結構,同SQL非常相似,也通過底層數據庫索引來提供查詢優化支撐。不同點如下:
1. 沒有 select *
2. 沒有視圖概念
3. SOQL是只讀的
4. 由於底層元數據驅動的多租戶數據模型的限制,索引是受限制的,沒有原生數據庫物理結構豐富的索引支持。
5. 對象到關系的映射(Object-Relational Mapping)是自動完成的.
6. SObjects 在多租戶環境中並不是對應實際的物理數據表
7. SObjects包括SObjects之間的關系都是以元數據的方式存儲在多租戶環境中的。
2. SOQL示例&語法
下面我用示例來說明一下SOQL的用法,同時引出SOQL的特殊語法說明,SOQL大小寫不敏感。
1. 單個對象的查詢及語法說明
select id,productno__c,name,productprice__c,productstatus__c from product__c

前面提到過系統提供了標准應用對象和標准字段定義,更大的優勢在於支持用戶自行自定義對象和字段。這里__c
代表的使用戶自定義的含義, product__c
代表的用戶自定義對象Product,而非系統標准對象和字段,系統標准對象和字段在SOQL無需__c
后綴,如ID,Name,CreatedBy等字段則為系統提供給每個對象的標准字段,而字段ProductNo為用戶自定義字段,則SOQL中的語法表示為productno__c
。這樣的好處是講標准和用戶自定義對象和字段很容易區分開,系統可以定義標准Product對象,以product表示,用戶也可以同樣定義一個Product對象,不過SOQL用product__c
表示用於區分。
2. 子對象關聯父對象(Child to Parent)查詢及語法說明
select id,name,orderno__c, customer__c, customer__r.customerno__c,customer__r.name, orderstatus__c,ordertime__c from order__c order by orderno__c

select id,name,orderno__c, customer__c, customer__r.customerno__c,customer__r.name, orderstatus__c,ordertime__c from order__c where customer__r.name='Cheng Yan' order by orderno__c

這里是從子對象Order關聯到父對象Customer進行查詢,其中:
1. from后面的對象order__c
表示Order為用於自定義對象
2. Id,name為Order對象內系統定義的標准字段,
3. Orderno__c,customer__c,orderstatus__c,ordertime__c
為用戶自定義字段,這里需要說明的是customer__c
自定義字段存儲的是父對象實例ID
4. customer__r
就特別有意思,其中 __r
部分代表父對象關系引用,customer
部分對應關系字段名,customer__r
代表從Order對象到Customer對象的一個應用關系,並通過customer__r.customerno__c,customer__r.name
獲取到Customer對象的字段值。
3. 父對象關聯子對象(Parent to Child)查詢及語法說明
select id,orderno__c,customer__r.name,ordertime__c,orderstatus__c, ( select id, product__r.productno__c,product__r.name,product__r.productprice__c from orderitem__r ) from order__c order by orderno__c

這個語句稍微有些復雜,從Order對象關聯到OrderItem對象,又從OrderItem關聯到Product,同時還包含了Order對象到Customer對象的關聯。
這里着重說一下從父對象到子對象的關聯,父到子的關聯是在父對象的主查詢語句中在查詢字段中用()來封裝到子對象的關聯,其中
1. 子句中from orderitem__r
的 orderitem__r
代表的是對子對象OrderItem的引用,orderitem
對應的為前文關系字段中提到的ChildRelationshipName,並且同一個父對象的子方的關系名稱唯一(父對象Name+ChildRelationshipName必須唯一),用作父對象到子對象的查詢關聯。
2. 子句中id,product__r.productno__c,product__r.name,product__r.productprice__c
的上下文為orderitem__r
代表的子對象
3. Relationships索引透視表
Relationships是為了SOQL的快速對象關聯查詢所定義的,子對象關聯父對象(Child to Parent)查詢,復合索引(OrgID+GUID)在Join中起到較大作用,而需要從父對象關聯子對象(Parent to Child)查詢,則復合索引(OrgID + ObjID + RelationID + TargetObjInstanceID)在Join中起到較大作用。
六、如何支撐多租戶巨大數據量
前面我們提到Salesforce一個共享數據庫的概念,那一個共享數據庫怎么來支撐如此巨大的多租戶數據庫呢,同時不僅需要支持巨量數據,並且還可以支撐租戶間的數據物理隔離,保證各租戶的數據穩定性、可用性和數據安全?
Salesforce的做法是:分區。所有的Force.com的數據,元數據,透視表結構,包含底層數據庫索引,都是通過對OrgID進行物理分區的,采用的是原生的數據庫分區機制。所有的數據以及元數據通過你的OrgID(16digits)進行分片Hash。
數據分區是數據庫系統提供的被驗證過的技術,用以物理划分較大的邏輯數據結構到較小的可以管理的區塊中。分區也可以幫助提升性能和擴展性,貼別是在多租戶環境下一個巨大的數據系統的擴展性。根據定義,每一個SOQL的查詢對應一個特別的租戶信息,因此查詢優化器,僅僅需要考慮訪問包含對應租戶的數據分區訪問,而不是整個表或者索引。
七、無感的對象結構變更(No DDL)
當一個應用系統或者服務組件需要對其數據模型進行升級的時候,通常會通過數據庫DDL語言對數據庫物理結構進行操作,如果涉及的數據量較大,則可能會造成較長時間的數據庫變更時效,造成對應時間內的系統不可用,如果是多租戶系統還會可能其他租戶的可用性造成影響,抑或造成諸多的底層模型不一致產生。
在元數據驅動的數據架構中,所有的DDL語言操作對應的使元數據層的元數據的記錄的更新,不涉及
數據庫物理結構的更新,不會造成變更期間的數據庫物理結構耗時調整造成的不可用,同時系統平台提供了一個高效的機制來減少對平台多租戶應用總體性能影響。
當用戶修改了一個表字段列的數據結構,從一種數據類型改成另外一種不同存儲格式的數據類型時候,系統會重新分派一個新的彈性列給到這個字段列的數據,將數據從原來的存儲彈性列批量拷貝到新的彈性列,然后才會更新此字段列的元數據,暨在Fields表中更新這個字段列的元數據,將數據類型更改為新的數據類型,並將FieldNum更新為新的ValueX列對應的X值。
同時,在如上對用戶邏輯表結構調整生效過程中,原來的數據結構和對應的數據訪問正常進行,直到邏輯表結構變更生效,對應用系統可用性不會造成影響,用戶對此無感知。
八、多租戶架構對於研發人員意味着什么
對於研發人員來說,多租戶結構最多意味着兩個版本:當前版本,以及下一個版本。沒有遺留版本需要維護。
所有人不用操心舊的技術,舊的版本,所有只有最新的版本,只需要關心最新的版本。
這樣就給敏捷開發帶來極大的好處,每年做個位的發布,每次發布幾百個新的特性新的版本也不會改變用戶的體驗,新的特性可以根據用戶需要開啟,通過特性管理來開關。
新版本發布前,提供沙箱環境來允許用戶提前試用新版本的系統。
如果做bug修復,則是在所有租戶層面上進行統一修復的。
對於用戶應用的發布進行嚴格管理,防止對其他租戶產生影響,通過提供沙箱環境來讓用戶驗證新應用發布,並通過成千上萬的自動化測試保證用戶的正常功能。
在運行期間,不作任何底層DDL操作,不會做表的創建,也不會做表的變更,只可能在極少數的更新周期時候進行。