道衝而用之或不盈,淵兮似萬物之宗。
—老子
引言
做爲業務系統技術開發同窗,面向當下:git
- 首先應該是快速搭建業務通路,讓線上業務跑起來,快速試錯,解決生存問題;
- 第二步是在鏈路暢通、業務基本跑起來的基礎上,如何支撐業務跑得更快,就須要解決快速增加問題;
- 第三步,在完成支撐業務快速增加的基礎上,要進行精細化提高,經過在支撐業務快跑間隙擠時間打磨系統功能和體驗,踏踏實實花時間去抽象能力,沉澱產品,提高效能;
同時咱們也必須面向將來,如何在抽象能力以及沉澱了產品的基礎上,把所承載和沉澱的業務能力快速輸出,貢獻給整個行業,或爲整個社會商業生態提供基座支撐。面向將來,將平臺產品進行 SaaS 化升級,真正將能力進行有價值開放輸出是咱們提早要佈局的核心方向。數據庫
將平臺產品進行 SaaS 輸出,須要解決那些問題呢?這裏嘗試把核心問題列舉一下:安全
- 如何根據不一樣用戶需求進行計算能力按需調度分配?(IaaS/PaaS)
- 如何知足用戶數據安全性要求,嚴格隔離不一樣用戶的數據,使用戶只能看到本身的數據?(PaaS)
- 如何支持不一樣用戶在標準的數據對象/數據模型上按需添加自定義的數據對象/擴展模型?(PaaS & SaaS)
- 如何按照不一樣用戶進行按需功能搭配組合,知足不一樣用戶從基礎到專業級不一樣業務場景需求?(SaaS)
- 如何統一對平臺產品進行升級而不影響用戶已有數據及功能?(IaaS、PaaS、SaaS)
經過以上問題,咱們能夠看出,產品 SaaS 化輸出的關鍵是如何對不一樣的用戶經過標準+擴展能力按需進行算力、數據、安全、功能有效定製,支持多用戶共性和個性的問題,即多租戶的問題,同時也涉及到計費和服務水平等相關問題。咱們下面來聊下上述問題的解題關鍵和解題思路:數據結構
- 第1個算力問題的核心是調度問題,彈性計算提供在 IaaS 層的統一算力調度能力,而 Serverless 則能夠在 PaaS 層提供更高層次的算力調度能力。
- 第4個問題的核心是業務流程的抽象和業務功能的拆分。領域驅動設計以及服務化(微服務)在平臺功能抽象拆分上提供了相對成熟的思路,催化了以縱向業務功能細分做爲域劃分的依據的服務化方案以及組織結構,主要訴求是在細分的業務功能服務基礎上,能按需快速靈活的組合,從而支撐不一樣的業務模式,提供業務敏捷性,支撐業務創新求變。
固然反過來,因爲縱向功能細分,業務功能域增多,整個業務鏈條上的咬合點愈來愈多,隨之產生愈來愈多的數據來源冗餘重複或者缺失,功能或者重合且各自發散,或者缺失,最終給總體業務帶來較多數據和功能的不一致性風險。這樣一來,不只橫向端到端的業務串聯成本高,並且關鍵路徑的風險收斂成本比較高,矛盾衝突點集中在各縱向域功能和數據咬合處,具體表現爲:架構
數據上:app
- 無主數據,有數據需求無 owner;
- 大量重複且不一致數據;
功能上:less
- 部分業務功能缺失;
- 域之間存在業務功能重複且行爲不一致。
究竟是縱向切分域仍是橫向分業務模式拉平來作,這個問題沒有標準答案,更沒有最佳答案。只有根據不一樣的業務發展階段及時動態調整試錯,換言之,這是一個不斷尋找相對最優解的動態過程。函數
彈性計算和 Serverless 解決了算力的問題,領域驅動服務化設計解決了功能的拆分和按需搭配組合的問題,那麼剩下的核心問題就是數據了:如何以一套統一的數據架構,既能支撐多租戶的數據安全性需求以及通用的數據存儲,也能支撐用戶擴展的自定義數據對象定義和模型變動,同時也要保證數據定義層面的擴展和變動不會影響自身和其餘租戶業務功能的可用性。咱們來分析下可能的方案(暫不考慮按服務邊界進行數據庫拆分):微服務
- 統一的數據庫,標準數據模型和擴展數據模型直接映射到物理表和索引:很顯然,對於不一樣租戶自定義的數據對象和數據模型要求是沒法支撐的,物理數據模型會相互干擾、相互衝突直到無覺得繼。即便是對於全部租戶徹底標準的功能和數據存儲,平臺自身的標準模型升級的 DDL 也會對用戶的可用性形成較大影響,因此顯然是行不通的。
- 若是爲每一個租戶建立各自的數據庫呢?各自租戶擁有各自的數據庫,能夠知足用戶數據安全隔離的需求,也能夠知足各租戶自定義的數據需求,看上去像是一種合理的 SaaS 數據方案。可是仔細分析,會發現有兩個明顯的問題:
- 若是用戶須要修改或者擴展示有物理數據模型而進行的 DDL 操做,必然會影響線上業務的總體可用性,也可能會影響到標準數據模型,從而影響到線上功能使用。
- 若是用戶可自定義對物理模型進行擴展和定製,當平臺進行模型升級的時候,極容易產生物理模型的衝突,致使新舊功能異常。
- 因爲用戶在各自數據庫存在各自定義的擴展和定製,則平臺數據模型和功能升級須要針對不一樣的租戶進行分別驗證,存在極大的升級驗證工做量和風險。
以上兩種方案可行性低,咱們從其中發現的問題是:平臺業務系統的邏輯模型到物理模型的直接映射是形成問題的主要因素。既然物理模型的變動是平臺不穩定的動因,那麼咱們是否能經過解耦業務邏輯模型和物理模型的映射關係來嘗試解決這個問題呢?佈局
既然問題已經定義清楚了,如何解決這個問題呢?一般咱們解決架構問題的一個「萬能」的方法是:增長一個層次,咱們也來套用一次,增長一個層次(元數據層)來解耦邏輯模型到物理模型強映射的問題。
首先,咱們須要對業務進行建模,對業務進行抽象,定義出業務邏輯模型,而後對模型進行二次抽象,定義出邏輯模型的定義數據,實現業務模型的數據化,即模型的元數據(The Metadata of the Logic Model ),將模型結構存儲爲數據,而不是直接對應的物理存儲結構。
其次根據定義出的元數據進行統一抽象,造成元數據邏輯模型。
將元數據邏輯模型映射到元數據物理模型,對應實際存儲結構。
經過對業務模型的變動,造成對元數據層的數據變動,而不是物理結構的變動,從而實現業務邏輯模型同物理模型的解耦。
不少事情提及來好像挺簡單,其實是一個很是巨大的系統工程,將其付諸實踐是挑戰很是大的事情,而取得踏踏實實的成功則更難。上述問題的解題思路是 Salesforce 的解題思路,並且 Salesforce 不只取得了成功,也接近將其作到了極致,下面咱們站在巨人的肩膀上來看看 Salesforce 如何經過元數據驅動的架構(核心是基礎數據架構)來支撐多租戶的 SaaS 業務平臺。
注意:因爲 Salesforce 並未有對核心實現邏輯進行徹底公開和說明,因此本文所整理的部分核心邏輯包含了做者的邏輯推理和解讀,可是確實進行了邏輯驗證和場景驗證,若有紕漏和不全面的地方,歡迎討論及指正。
元數據驅動的多租戶架構
Salesforce 將 http://Force.com 定義爲 PaaS 平臺,http://Force.com 的基礎就是元數據驅動的軟件架構來支撐多租戶應用。首先我來解釋下什麼是以元數據驅動的軟件架構爲核心。
1、多租戶意味着什麼
多租戶的含義用一句話來描述就是:一個雲平臺,無數多個客戶。
一個雲平臺的含義是:一個代碼庫,一個數據庫,一整套共享的可擴展服務,包括數據服務、應用服務以及 Web 服務。
無數多個客戶的含義是:每一個客戶都被分配一個惟一的租戶 OrgID,全部的數據存儲都是按照租戶 OrgID 隔離的,全部的數據訪問必須包含 OrgID,全部的操做也都是包含租戶 OrgID 的,也就是全部的客戶數據和行爲都是被安全的經過惟一的租戶 Org 進行嚴格隔離的。
每一個租戶/組織只能看到和定義按照本身租戶 OrgID 隔離的本身版本的元數據和數據,並且只能執行本身租戶 OrgID 所受權的行爲,這樣每一個租戶就擁有各自版本的 SaaS 方案。
2、元數據驅動意味着什麼
元數據對於平臺意味着平臺數據的數據,對於租戶意味着是關於租戶數據的數據。
當用戶定義一個新的用戶表的時候,用戶建立的不是數據庫中的物理表,而是在系統態的元數據表中添加了一條記錄,這個記錄描述的是用戶表的邏輯定義,是虛擬的,這個表並不在數據庫中物理存在,而這條記錄表明就是用戶態的數據表。
當用戶定義了用戶表的一個新的字段時,用戶並無在物理表中建立物理字段,而是在系統態的元數據表中添加了一個記錄,這個記錄描述的用戶表的字段組成的邏輯結構,是虛擬的,這個字段也不在數據庫表結構中物理存在,而這條記錄表明的就是用戶態的用戶表字段。
也就是經過存儲在系統態的元數據表中的元數據記錄做爲虛擬用戶的數據庫結構。
3、元數據驅動的多租戶總體架構
咱們先來大概瞭解下元數據驅動的多租戶的總體架構,總體架構大概分爲 5 個邏輯層次:
- 底層數據架構分爲三個層次:
- 最底層是數據層,存儲了離散的系統和用戶的業務數據,業務平常運營的數據存儲在這裏。
- 公共元數據層,存儲了應用系統標準的對象和標準的字段定義,對底層數據的結構進行定義說明。
- 租戶特定元數據,存儲了租戶自動的對象和自定義的字段定義,用於對底層的數據結構進行定義說明。
- 通用數據字典 UDD(Universal Data Dictionary) 運行引擎層實現了應用對象到底層數據存儲的映射,包含對象模型操做、SOQL 語言解析、查詢優化,全文搜索等功能,咱們常說的 ORM 功能也是其核心功能,但比其複雜的多。
- 平臺服務層提供 PaaS 層平臺服務,提供應用對象模型的建立,權限模型建立,邏輯和工做流程建立以及用戶界面的建立,包括屏幕布局、數據項、報表等。
- 標準應用層提供端到端的標準的業務應用功能。
- 租戶虛擬應用層,用戶能夠在標準應用層或者平臺服務層之上定義本身特有的業務應用功能,知足本身特定的業務場景須要。
其中,底層數據架構是最爲關鍵的平臺基石(The Corner Stone),其核心運行引擎也是基於強大的底層數據架構基礎上構建的。本文則以元數據驅動的多租戶數據架構爲核心來一一展開。
4、元數據驅動的多租戶數據架構
下面咱們具體來看下系統態的數據模型,基於 Salesforce 加上我的推理的元數據驅動的多租戶數據模型。
注意:因爲 Salesforce 並未有對核心邏輯進行徹底公開和說明,因此本文所整理的部分核心模型包含了我的的邏輯推理和解讀,可是確實進行了邏輯驗證和場景驗證,若有紕漏和不全面的地方,歡迎討論及指正。
Salesforce 雲服務平臺遵循的是面向對象的設計理念,全部的實體、實體關係以及實體的 CRUD 均是以對象的視角進行的,因此其元數據驅動的多租戶數據模型的存儲基本元素也是按照對象的顆粒度進行存儲,源自於 OO 的對象間引用,同普通關係數據庫主外鍵關係殊途同歸,只是細節處理上不盡相同,請你們注意這一點。
1. 元數據驅動的多租戶數據架構概覽
首先,咱們先來大概瞭解下元數據驅動的多租戶模型的核心內容,元數據驅動的多租戶的數據模型主要分爲三個部分:元數據表、數據表和功能透視表。
元數據表(Metadata Tables)
元數據表用於存放系統標準對象以及用戶自定義對象和字段定義的元數據,也就是系統和用戶對象的邏輯結構,即對應於關係數據庫中的虛擬表結構。元數據表主要包括Objects 表以及 Fields 表,是系統標準對象和用戶對象定義數據的倉庫,即元數據倉庫。
數據表(Data Tables)
數據表用戶存放系統以及用戶對象和字段的實際數據,實際的用戶業務數據以及應用系統相關數據存放在這裏。數據表包括 Data 表和存放大文本數據的 Clob 表,數據表存儲了絕大部分用戶的實際數據,是一個巨大的用戶業務數據倉庫。
功能透視表(Specialized Pivot Tables)
功能透視表包含了很是關鍵的關係表、索引表、關係表以及其餘特定用途表。例如關係表定義了對象間的關係,索引表解決虛擬結構索引的問題,這部分後續將進行詳盡的介紹。
2. 元數據驅動的多租戶數據架構詳解
上一節粗略地描述了元數據驅動的多租戶模型三大部分模型實體和基本做用,你們可能會比較疑惑,這麼簡單一個實體模型,怎麼就起了這麼個牛逼的名字,並且支撐了「一個雲平臺,無數個客戶」。咱們下面就對此模型的核心邏輯進行詳細展開和推理說明,同時詳細闡述以此模型爲中心的服務來講明整個元數據層或者說 UDD(Universal Data Dictionary) 層的設計。
土話說:「沒有對比,就沒有傷害」。道理是相通的,用類似的事物進行對比是對理解客觀事物比較好的方法,找出其相同點和共性的地方,找出其不一樣點和異樣的地方,同時識別出是否有不可對比的方面。從各個方面去對比,則能更全面、更深刻的瞭解客觀事物。
下面我按照普通應用設計思路方式來定義一個簡單直觀的多租戶 SaaS 數據架構方案示例,做爲元數據驅動多租戶數據架構方案的對比基準方案,用對比來更好的幫你們瞭解元數據驅動多租戶數據模型及架構的設計邏輯。
普通多租戶 SaaS 數據架構方案示例(僅作示例)
- 多租戶基本思路:每一個租戶一個數據庫,提供數據庫級別的租戶數據隔離,平臺提供標準應用功能模型,用戶能夠在各自數據庫內定義以及修改各自的定義模型,全部模型採用數據庫物理表、索引、主外鍵實現。不一樣的租戶經過路由到不一樣的數據庫來實現隔離。
- 域模型樣例採用你們都熟悉的最小集的訂單模型實現,包含商品、用戶、訂單和訂單詳情表。注意:此簡化模型僅用作示意說明,和意圖無關的大多數字段均省略,非嚴謹定義。
- 示例模型數據
數據庫物理表數據:Customer
數據庫物理表數據:Product
數據庫物理表數據:Order
數據庫物理表數據:OrderItem
- 實體表關係
Order 表同 OrderItem 爲父子表,經過 OrderID 進行主外鍵關聯;Customer 表同 Order 表爲父子表,經過 CustomerID 進行主外鍵關聯;Product 表同 OrderItem 表爲父子表,經過 ProductID 進行主外鍵關聯。 - 用戶自定製
用戶有執行 DDL 權限,能夠在本身租戶數據庫內在進行擴展模型自定義,創建自定義的物理表,索引,關係等。 - 問題和風險
用戶具備執行 DDL 權限,能夠自定義數據庫物理模型,會帶來各租戶的自定義數據模型大爆炸,會給後續平臺模型定義升級衝突,形成模型升級的巨大的障礙
同時,因爲系統標準模型和用戶模型均爲物理模型,未有作系統標準和自定義數據的有效隔離,如何保證平臺應用的每一次升級必然會考慮對現有用戶自定義模型的穩定性和可用性的影響,在自定義物理模型的狀況下,不只挑戰巨大,並且包含巨大的迴歸驗證的工做量,很難收斂。
當用戶執行 DDL 時,一般會鎖定數據庫物理資源,當數據庫數量很是巨大時可能會帶來不可控的 downtime,對應用系統的可用性形成巨大的影響。若是數據庫是每一個租戶各自獨佔,還只會影響到單個租戶;若是是多租戶共享數據庫,則可能會影響到其餘租戶,影響是災難性的。做爲雲平臺服務商,無論是用戶操做仍是系統行爲,咱們都不指望咱們的設計對用戶系統的可用性形成影響,因此用戶執行 DDL 的行爲是否容許確實有待商榷,可是若是不容許,用戶可擴展性在這種設計環境中必然受到必定程度的限制。
元數據驅動的多租戶數據模型(Metadata Tables)
前面章節描述了元數據驅動的多租戶模型簡單模型圖,本小節詳細解說下每一個核心實體表的核心結構,同時已知資料部分較爲簡略,沒法描述模型全貌和核心細節,爲了模型完整性,總體數據模型包含了做者思路推理部分,用以來完整清晰地定義模型。固然因爲全部模型都是主觀的(subjective),僅表明我的觀點,歡迎你們的不一樣的觀點,一塊兒討論改進。
正如前面介紹「一個雲平臺」時提到,經過一個統一的數據庫來支撐無數個租戶,因此元數據驅動的多租戶模型是基於一個共享數據庫的前提。固然多租戶實現設計多種多樣,你們能夠不拘泥此種。
1)元數據表之對象定義表:Objects 表
Object 系統表存儲了每一個租戶爲它的擴展應用對象定義的元數據,包含以下核心字段:
- ObjID:應用對象惟一標識,具備固定長度和格式。
- OrgID:應用對象所歸屬的租戶 ID,用於統一共享數據庫內的多租戶數據隔離,一般和租戶定義的域名對應。
- ObjName/Name:對象名稱,用於系統配置和開發(developer name)。
- Label: 對象的顯示名稱。
除了用戶自定義對象,系統的標準對象也是採用相同的方式進行定義的。
2)元數據表之字段與關係定義表:Fields 表
Fields 系統表存儲了每一個租戶爲他的擴展應用對象字段定義的元數據,包含了其所歸屬的應用對象的租戶 OrgID,字段所屬對象的 ObjID,字段定義標識 FieldID,字段名稱FieldName,字段存儲位置定義 FieldNum,數據類型 DataType。數據類型重要補充關聯字段(DigitLeft,Scale,TextLength,RelatedTo,ChildRelationshipName)以及是否必選、惟1、索引標記,還有部分標準字段。Fields表很是關鍵,其不只定義了普通的應用對象字段,包括基本信息和數據類型信息,並且經過特殊關係字段對不一樣應用對象之間的關係進行定義,詳細說明以下:
- FieldID:此對象字段的惟一標識,具備固定長度和格式
- OrgID:其所歸屬的應用對象所歸屬的租戶 OrgID
- ObjID:字段所屬對象的 ObjID
- FieldName/Name:字段名,用於系統配置和開發(developer name)。
- Label:字段展現名稱,用以展現給最終用戶。
- FieldNum:對應到 Data 數據表的數據存儲字段映射,暨 Data 表中 ValueX 字段中的X。
- DataType:指定此對象字段的數據類型包含普通類型:Number、TEXT、Auto Number、Date/Time、Email、Text Area等,也包含特殊的關係類型如:Look up關係類型、Master-Detail 關係類型等。
- DigitLeft 和 Scale:用於 Number、Currency、Geolocation 等數字數據類型的關聯設定,例如定義了一個字段的 DateType 爲 Number,則須要指定其整數部分的最大位數 DigitLeft 和小數部分的最大位數 Scale,兩部分長度總和不超過 18 位。
- TextLength:當數據類型爲 TEXT 時啓用,用於指定 TEXT 類型的字符的長度限制。
- RelatedTo 和 ChildRelationshipName:這兩個字段當 DateType 爲關係類型(Look up,Master-Detail 等)時會啓用,其中 RelatedTo 保存關聯的應用對象 ID,ChildRelationshipName 用於保存父子關係中子方的關係名稱,同一個父對象的子方的關係名稱惟一,用於關係的反向查詢。
- IsRequired:此字段數據保存時,是否校驗值的存在。
- IsUnique:是否容許重複值。
- IsIndexed:此字段是否須要建索引。
- 其餘字段:此處僅列舉了說明模型所須要的字段,其餘字段暫不進行列舉,不列舉緣由和其重要性並沒有直接關聯。
3)數據表(Data Tables)之關係數據表:Data 表
MTData 系統表存儲了 MTObjects 和 MT_Fields 元數據表內定義的數據對象(表)所對應的數據,一一映射到不一樣的租戶各自定義的表和表中的字段(對象和對象字段)。
- GUID:數據表的主鍵,用於存放每一個應用對象實例的標識 ID。
- ObjID:其所歸屬的應用對象所歸屬的租戶 OrgID。
- Name:應用對象實例名稱。
- 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. 標準對象與標準字段
前面總體架構層次裏提到了公共元數據層和標準應用層,公共元數據層提供了標準對象和標準字段的定義。
其中標準對象爲每一個租戶提供公共端到端的應用的標準應用功能。
同時用戶能夠在標準的對象基礎上擴展自定義的應用對象,知足本身的特定業務場景。__c 後綴表明自定義,後續詳解。
而標準字段則提供給每一個對象包括自定義對象的共同的字段,包含部分業務字段和非業務字段。
用戶也能夠在標準對象和自定義對象內自定義不一樣的字段,以知足業務須要。__c後綴表明自定義,後續詳解。
5. 對象關係類型
應用對象關係類型主要分爲 Look up 和 Master-Detail 兩種關係類型,其中 Look up 爲弱的父子關係類型,Master-Detail 爲強的父子關係類型,其特性對好比下。
6. 元數據驅動的多租戶數據架構示例
一樣採用普通多租戶 SaaS 數據架構方案中相同的域模型和示例數據做爲參照進行說明,只不過在這裏域模型再也不對應到數據庫的物理模型,而是對應到元數據所定義的虛擬數據庫的邏輯模型。請先後對比兩種模型對用戶業務模型承載的差別和聯繫,以便深刻了解元數據驅動的多租戶數據架構。
對於 Tenant 租戶 A00001,須要支撐相同的業務邏輯,須要定義相同的域模型,和普通的方案不一樣的是,這裏採用元數據驅動的多租戶數據模型來定義訂單域模型和對應示例數據,其中域模型定義在元數據表(Metadata Tables)中,數據存儲在 Data Tables 表中。
1)用戶自定義對象 Product 的定義
Product 對象的基本信息定義在 Objects 表,做爲 Objects 表的一條記錄,經過 OrgID 進行不一樣租戶數據隔離。Object 中的每一條記錄都表明一個不一樣的對象。Objects 表的定義很是清晰,這裏不作過多的解釋,請參考 Objects 表介紹。
Product 對象的字段結構定義在 Fields 表,同時經過 ObjID 同 Order 對象定義進行關聯,經過 OrgID 進行多租戶數據隔離。
FieldID 格式爲字段定義的標識 ID,用於區分每一個字段定義,對於標準字段,則採用標準字段 ID,如 Name,則直接採用 Name 做爲字段標識 ID,對於自定義字段,則元數據引擎自動生成 15 位的標準格式的 FieldID。其餘字段定義請參考前面的 Fields 元數據表詳細介紹。
下面詳細描述一下 Product 對象中每一個字段定義:
- 產品名稱 Name 字段 爲標準字段,數據格式爲TEXT,長度爲80。
- 產品編號 ProductNo 爲自定義字段,數據格式爲 TEXT,長度爲 22,FieldNum 爲 1 對應 Data 表存儲字段 Value1,存儲格式爲變長字符串。
- 產品價格 ProductPrice 爲自定義字段,數據格式爲 Currentcy(此格式相似Number,不一樣是帶幣種),整數最大長度 DigitLeft:16 位,小數位最大精度Scale:2 位,FieldNum 爲 2 對應 Data 表存儲字段 Value3,存儲格式爲變長字符串。
- 狀態 ProductStatus 爲自定義字段,數據格式爲 TEXT,長度爲 20,FieldNum 爲 3對應 Data 表存儲字段 Value3,存儲格式爲變長字符串。
2)用戶自定義對象 Customer 的定義
Customer 對象的基本信息定義在 Objects 表,做爲 Objects 表的一條記錄,經過 OrgID 進行不一樣租戶數據隔離。Object 中的每一條記錄都表明一個不一樣的對象。Objects表的定義很是清晰,這裏不作過多的解釋,請參考Objects表介紹。
Customer 對象的字段結構定義在 Fields 表,同時經過 ObjID 同 Order 對象定義進行關聯,經過 OrgID 進行多租戶數據隔離。
下面詳細描述一下 Customer 對象中每一個字段定義:
- 用戶名稱 Name,必選標準字段,不過多解釋。
- 用戶編號 CustomerNo 爲自定義字段,數據類型爲 TEXT,長度爲 22,FieldNum 爲 1 對應 Data 表存儲字段 Value1,存儲格式爲變長字符串。
- FirstName 和 LastName 爲自定義字段,數據類型爲 TEXT,長度均爲 20,FieldNum 爲 2,3 對應 Data 表存儲字段 Value2 和 Value3,存儲格式爲變長字符串。
- 用戶暱稱 Nick Name 爲自定義字段,數據類型爲 TEXT,長度均爲 20,FieldNum 爲 4 對應 Data 表存儲字段 Value4,存儲格式爲變長字符串。
- 用戶登陸名 LoginName 爲自定義字段,數據類型爲 TEXT,長度均爲 20,FieldNum 爲 5 對應 Data 表存儲字段 Value5,存儲格式爲變長字符串。
- 用戶狀態 CustomerStatus 爲自定義字段,數據類型爲 TEXT 或者 PickList,長度爲 20,FieldNum 爲 6 對應 Data表存儲字段 Value6。爲簡化起見,狀態字段暫定義爲 TEXT,對應 Data 表存儲字段 Value4,存儲格式爲變長字符串。
3)用戶訂單 Order 邏輯表的定義
Order 對象的基本信息定義在 Objects 表,做爲 Objects 表的一條記錄,經過 OrgID 進行多租戶數據隔離。Objects 表中的每一條記錄都表明一個不一樣的對象。
Order 對象的字段結構定義在 Fields 表,同時經過 ObjID 同 Order 對象定義進行關聯,經過 OrgID 進行多租戶數據隔離。
下面詳細描述一下 Order 對象中每一個字段定義:
- 訂單編號 OrderNo 爲自定義字段,DataType 數據格式爲 TEXT,長度爲 22,FieldNum 爲 1,對應 Data 表存儲字段 Value1,存儲格式爲變長字符串。
- 關係字段 Customer 爲自定義關係字段,DataType 類型爲弱類型 Look up 關係,關聯到父對象 Customer,則 RelatedTo 列存儲 Customer 的 ObjID:01I2v000002zTEZ,對應的 FieldNum 爲 2,則 Customer 對象實例 GUID 存儲在 Data 表的 Value2 列。ChildRelationshipName 列存儲對象父子關係中子關係名稱:orders,用於對象關係中從父對象實例數據反查子對象實例數據。
- 訂單狀態 OrderStatus 爲自定義字段,DataType 類型爲 TEXT,長度爲 20,FieldNum 爲 3,則狀態存儲在 Data 表的 Value3 列。爲簡化起見,狀態字段暫定義爲 TEXT。
- 下單時間 OrderTime 爲自定義字段,DataType 類型爲 Date/Time,FieldNum 爲4,則下單時間存儲在 Data 數據表的 Value4 列。
4)用戶訂單行 OrderItem 邏輯表定義一樣的,OrderItem 對象的基本信息也以一條記錄的信息定義在 Objects 表,經過 OrgID 進行多租戶數據隔離。Objects 表中的每一條記錄都表明一個不一樣的對象。
OrderItem 的字段結構也定義在 Fields 表,經過 ObjID 同 OrderItem 對象關聯,經過 OrgID 進行多租戶數據隔離。
下面詳細描述一下 Order 對象中每一個字段定義:
- 關係字段 Order 爲自定義關係字段,DataType 類型爲強類型的 Master-Detail 關係,關聯到父對象 Order,則 RelatedTo 列存儲 Order 對象的 ObjID:01I2v000002zTEj,對應的 FieldNum 爲 1,則 Order 對象實例 GUID 存儲在 Data 表的 Value1 列。ChildRelationshipName 列存儲對象父子關係中子關係名稱:OrderItem(s),用於對象關係中從父對象 Order 實例數據反查子對象實例數據。
- 關係字段 Product 爲自定義關係字段,DataType 類型爲弱類型的 Look up 關係,關聯到父對象 Product,則 RelatedTo 列存儲 Product 對象的 ObjID:01I2v000002zTEU,對應的 FieldNum 爲 2,則 Product 對象實例 GUID 存儲在Data 表的 Value2 列。ChildRelationshipName 列存儲對象父子關係中子關係名稱:OrderItem(s),用於對象關係中從父對象 Product 實例數據反查子對象實例數據。
- 商品實際售價 ItemPrice 爲自定義字段,DateType 類型爲 Currentcy(此格式相似 Number,不一樣是帶幣種),整數最大長度 DigitLeft:16 位,小數位最大精度 Scale:2 位,FieldNum 爲 2 對應 Data 表存儲列 Value3,存儲格式爲變長字符串。
- 商品購買數量 Item Quantity 爲自定義字段,DataType 類型爲 Number,整形長度爲 18 位,無小數位數,FieldNum 爲 4,對應 Data 數據表存儲列 Value4。
- 訂單明細狀態 OrderItemStatus 爲自定義字段,Datetype 類型爲 TEXT,長度爲 20,對應 FieldNum 爲 5,對應 Data 數據表存儲列 Value5。爲簡化起見,狀態字段暫定義爲 TEXT。
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 沒有佔用,能夠任意對應,固然按照順序是比較好的實踐。
再舉例來講:
- Order 對象的 Customer 關係字段定義在 Fields 表中,其 FieldNum 爲 1,則其在 Data 表中存儲的位置,就是是 Order 對象實例在 Data 對應的記錄中 Value1 這個字段所存儲的值,存儲的值爲 Customer 對象實例 GUID,也就是:a062v00001YXEKuAAP、a062v00001YXEKzAAP 等。
- OrderItem 對象的 Product、ItemQuantity 字段定義在 Fields 表中,其對應的 FieldNum 分別爲二、4,則其在 Data 表中存儲的位置,就是 OrderItem 對象在 Data 對應的記錄中 Value二、以及 Value4 所存儲的數據,也就是:a052v00000jbgEQAAY、2以及a052v00000jbgMqAAI、3 等記錄。
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 數據庫的TONUMBER,TODATE,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透視表的字段說明以下:
- OrgID:其所歸屬的應用對象所歸屬的租戶OrgID
- ObjID:字段所屬應用對象惟一標識
- FieldNum:對象字段存儲位置
- ObjInstanceGUID:對象實例惟一標識
- StringValue:強類型的字符串列
- NumValue:強類型的數字列
- DateValue:強類型的日期列
下面的 Indexes 表示例包含對字符、數字和日期性數據的索引需求支持,數據來源於前面的 Data 表數據。
Indexes 表的底層索引是標準的,採用非惟一性的數據庫索引。當作對象檢索查詢的時候,實際上不是在Data數據表上作查詢,而是在 Indexes 索引表上作的查詢,獲取到OrgID,ObjectID 以及 GUID,而後再返回數據表獲取數據。也就是當系統查詢條件包含對象實例的結構化的字段時,系統查詢優化器採用 MT_Indexes 來幫助優化相關的數據訪問操做。
2)Unique Indexes透視表
因爲 Data 數據表的多數據類型的無差異存儲,沒法在 Data 數據表建惟一性的索引供用戶來使用對對象字段值進行惟一性校驗。爲了支持用戶對象自定義字段的惟一性校驗,解決辦法是採用了 UniqueIndexes 透視表;這個表很是相似於 Indexes 表,不過 Uniqueindexes 採用底層原生的數據庫索引來強制惟一性校驗。當一個用戶嘗試來插入一個重複的值到具備惟一性約束的對象字段時,或者當用戶嘗試去在一個現存的包含惟一性的字段進行強制惟一性時,系統會給出惟一性校驗失敗的提示,阻止用戶的下一步操做。
- Unique Indexes 透視表的核心字段說明以下:
- UniqueStringValue:惟一的字符串列
- UniqueNumValue:惟一的數字列
- UniqueDateValue:惟一的日期列
- 其餘字段定義請參考 Indexes 透視表
3)Relationships 索引透視表
在元數據驅動的多租戶模型中,提到了在 Objects 表以及 Fields 表中保存了用戶對象結構和對象關係的定義,對象關係的定義是經過元數據模型 Fields 表字段數據類型提供了一個特殊的數據類型:「關係」 (Relationship), 來給用戶用於聲明不一樣的用戶應用對象之間的關係,也就是咱們一般說引用完整性。
對象之間的引用關係定義以及對象實例間的引用關係存儲在元數據表 Objects、Fields 中和 Data 表中,關聯查詢關係複雜,爲了提高對象之間查詢的效率,特別是經過對象相互引用關係對對象實例數據進行檢索,系統提供關係索引透視表 Relationship 來優化對象引用關聯查詢。
Relationships 索引透視表的字段說明以下:
- OrgID:其所歸屬的應用對象所歸屬的租戶 OrgID
- ObjID:子對象的對象標識
- GUID:子對象實例的惟一標識
- RelationID:子對象內關係字段定義的標識
- TargetObjInstanceID:父對象實例的惟一標識
關係透視表 Relationship 定義了兩個底層數據庫複合索引:
- 第一個索引字段:OrgID + GUID,用於從子對象到父對象的關聯查詢。
- 第二個索引字段:OrgID + ObjID + RelationID + TargetObjInstanceID,用於父對象到子對象的關聯查詢。
Relationships 索引透視表會在後面 SOQL 章節進行進一步描述驗證。
4)其餘索引透視表
其餘索引透視表的邏輯相似,都是爲了知足特定檢索和查詢須要,將數據同步到索引表,供應用系統使用。此處再也不贅述,如確實有須要再補充。
5、SOQL 與關係 Relationships
SOQL 是 Salesforce Object Query Language 的簡稱,具備 SQL 相似的語法結構,就像前面提到的同樣,Salesforce 是以應用對象(Salesforce Object,簡稱 SObject)的視角管理業務數據和功能,SOQL 相似對用於對應有對象數據進行查詢的 API。
1. 從SQL 到 SOQL
SOQL 也是採用相似表查詢的結構,同 SQL 很是類似,也經過底層數據庫索引來提供查詢優化支撐。不一樣點以下:
- 沒有 select *
- 沒有視圖概念
- SOQL 是隻讀的
- 因爲底層元數據驅動的多租戶數據模型的限制,索引是受限制的,沒有原生數據庫物理結構豐富的索引支持。
- 對象到關係的映射 (Object-Relational Mapping) 是自動完成的。
- SObjects 在多租戶環境中並非對應實際的物理數據表。
- SObjects 包括 SObjects 之間的關係都是以元數據的方式存儲在多租戶環境中的。
2. SOQL 示例&語法
下面我用示例來講明一下 SOQL 的用法,同時引出SOQL的特殊語法說明,SOQL 大小寫不敏感。
1)單個對象的查詢及語法說明
select id,productno__c,name,productprice__c,productstatus__c from product__c
前面提到過系統提供了標準應用對象和標準字段定義,更大的優點在於支持用戶自行自定義對象和字段。這裏c 表明的使用戶自定義的含義, productc 表明的用戶自定義對象 Product,而非系統標準對象和字段,系統標準對象和字段在 SOQL 無需c 後綴,如ID,Name,CreatedBy 等字段則爲系統提供給每一個對象的標準字段,而字段 ProductNo 爲用戶自定義字段,則 SOQL 中的語法表示爲 productnoc。這樣的好處是講標準和用戶自定義對象和字段很容易區分開,系統能夠定義標準 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 進行查詢,其中:
- from 後面的對象 order__c 表示 Order 爲用於自定義對象
- Id,name 爲 Order 對象內系統定義的標準字段
- Ordernoc,customerc,orderstatusc,ordertimec 爲用戶自定義字段,這裏須要說明的是 customer__c 自定義字段存儲的是父對象實例 ID
- customerr 就特別有意思,其中r 部分表明父對象關係引用,customer 部分對應關係字段名,customerr 表明從 Order 對象到 Customer 對象的一個應用關係,並經過 customerr.customernoc,customerr.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 對象的關聯。
這裏着重說一下從父對象到子對象的關聯,父到子的關聯是在父對象的主查詢語句中在查詢字段中用()來封裝到子對象的關聯,其中
- 子句中 from orderitemr 的 orderitemr 表明的是對子對象 OrderItem 的引用,orderitem 對應的爲前文關係字段中提到的 ChildRelationshipName,而且同一個父對象的子方的關係名稱惟一(父對象 Name+ChildRelationshipName 必須惟一),用做父對象到子對象的查詢關聯。
- 子句中 id,productr.productnoc,productr.name,productr.productpricec 的上下文爲 orderitemr 表明的子對象。
3. Relationships 索引透視表
Relationships 是爲了 SOQL 的快速對象關聯查詢所定義的,子對象關聯父對象( Child to Parent) 查詢,複合索引(OrgID+GUID)在 Join 中起到較大做用,而須要從父對象關聯子對象 (Parent to Child) 查詢,則複合索引 (OrgID + ObjID + RelationID + TargetObjInstanceID) 在 Join 中起到較大做用。
6、如何支撐多租戶巨大數據量
前面咱們提到 Salesforce 一個共享數據庫的概念,那一個共享數據庫怎麼來支撐如此巨大的多租戶數據庫呢,同時不只須要支持巨量數據,而且還能夠支撐租戶間的數據物理隔離,保證各租戶的數據穩定性、可用性和數據安全?
Salesforce 的作法是:分區。全部的 http://Force.com 的數據,元數據,透視表結構,包含底層數據庫索引,都是經過對 OrgID 進行物理分區的,採用的是原生的數據庫分區機制。全部的數據以及元數據經過你的 OrgID(16digits)進行分片 Hash。
數據分區是數據庫系統提供的被驗證過的技術,用以物理劃分較大的邏輯數據結構到較小的能夠管理的區塊中。分區也能夠幫助提高性能和擴展性,貼別是在多租戶環境下一個巨大的數據系統的擴展性。根據定義,每個 SOQL 的查詢對應一個特別的租戶信息,所以查詢優化器,僅僅須要考慮訪問包含對應租戶的數據分區訪問,而不是整個表或者索引。
7、無感的對象結構變動(No DDL)
當一個應用系統或者服務組件須要對其數據模型進行升級的時候,一般會經過數據庫 DDL 語言對數據庫物理結構進行操做,若是涉及的數據量較大,則可能會形成較長時間的數據庫變動時效,形成對應時間內的系統不可用,若是是多租戶系統還會可能其餘租戶的可用性形成影響,抑或形成諸多的底層模型不一致產生。
在元數據驅動的數據架構中,全部的 DDL 語言操做對應的使元數據層的元數據的記錄的更新,不涉及數據庫物理結構的更新,不會形成變動期間的數據庫物理結構耗時調整形成的不可用,同時系統平臺提供了一個高效的機制來減小對平臺多租戶應用整體性能影響。
當用戶修改了一個表字段列的數據結構,從一種數據類型改爲另一種不一樣存儲格式的數據類型時候,系統會從新分派一個新的彈性列給到這個字段列的數據,將數據從原來的存儲彈性列批量拷貝到新的彈性列,而後纔會更新此字段列的元數據,暨在 Fields 表中更新這個字段列的元數據,將數據類型更改成新的數據類型,並將 FieldNum 更新爲新的 ValueX 列對應的X值。
同時,在如上對用戶邏輯表結構調整生效過程當中,原來的數據結構和對應的數據訪問正常進行,直到邏輯表結構變動生效,對應用系統可用性不會形成影響,用戶對此無感知。
8、多租戶架構對於研發人員意味着什麼
對於研發人員來講,多租戶結構最多意味着兩個版本:當前版本,以及下一個版本。沒有遺留版本須要維護。全部人不用操心舊的技術,舊的版本,全部只有最新的版本,只須要關心最新的版本。
這樣就給敏捷開發帶來極大的好處,每一年作個位的發佈,每次發佈幾百個新的特性新的版本也不會改變用戶的體驗,新的特性能夠根據用戶須要開啓,經過特性管理來開關。
新版本發佈前,提供沙箱環境來容許用戶提早試用新版本的系統。若是作 bug 修復,則是在全部租戶層面上進行統一修復的。
對於用戶應用的發佈進行嚴格管理,防止對其餘租戶產生影響,經過提供沙箱環境來讓用戶驗證新應用發佈,並經過成千上萬的自動化測試保證用戶的正常功能。
在運行期間,不做任何底層 DDL 操做,不會作表的建立,也不會作表的變動,只可能在極少數的更新週期時候進行。
做者:程彥,曾就任於阿里數字供應鏈事業部擔任多年供應鏈計劃域研發,目前在阿里數據中臺負責相關商業化產品開發。
本文爲阿里雲原創內容,未經容許不得轉載。