关于angular:元数据驱动的-SaaS-架构与背后的技术思考

118次阅读

共计 19779 个字符,预计需要花费 50 分钟才能阅读完成。

简介:在形象能力以及积淀了产品的根底上,把所承载和积淀的业务能力疾速输入,奉献给整个行业。

道冲而用之或不盈,渊兮似万物之宗。

—老子

引言

作为业务零碎技术开发同学,面向当下:

  • 首先应该是疾速搭建业务通路,让线上业务跑起来,疾速试错,解决生存问题;
  • 第二步是在链路畅通、业务根本跑起来的根底上,如何撑持业务跑得更快,就须要解决快速增长问题;
  • 第三步,在实现撑持业务快速增长的根底上,要进行精细化晋升,通过在撑持业务快跑间隙挤时间打磨零碎性能和体验,踏踏实实花工夫去形象能力,积淀产品,晋升效力;

同时咱们也必须面向未来,如何在形象能力以及积淀了产品的根底上,把所承载和积淀的业务能力疾速输入,奉献给整个行业,或为整个社会商业生态提供基座撑持。面向未来,将平台产品进行 SaaS 化降级,真正将能力进行有价值凋谢输入是咱们提前要布局的外围方向。

将平台产品进行 SaaS 输入,须要解决那些问题呢?这里尝试把外围问题列举一下:

  1. 如何依据不同用户需要进行计算能力按需调度调配?(IaaS/PaaS)
  2. 如何满足用户数据安全性要求,严格隔离不同用户的数据,使用户只能看到本人的数据?(PaaS)
  3. 如何反对不同用户在规范的数据对象 / 数据模型上按需增加自定义的数据对象 / 扩大模型?(PaaS & SaaS)
  4. 如何依照不同用户进行按需性能搭配组合,满足不同用户从根底到专业级不同业务场景需要?(SaaS)
  5. 如何对立对平台产品进行降级而不影响用户已有数据及性能?(IaaS、PaaS、SaaS)

通过以上问题,咱们能够看出,产品 SaaS 化输入的要害是如何对不同的用户通过规范 + 扩大能力按需进行算力、数据、平安、性能无效定制,反对多用户共性和共性的问题,即多租户的问题,同时也波及到计费和服务水平等相干问题。咱们上面来聊下上述问题的解题要害和解题思路:

  • 第 1 个算力问题的外围是调度问题,弹性计算提供在 IaaS 层的对立算力调度能力,而 Serverless 则能够在 PaaS 层提供更高层次的算力调度能力。
  • 第 4 个问题的外围是业务流程的形象和业务性能的拆分。畛域驱动设计以及服务化 (微服务) 在平台性能形象拆分上提供了绝对成熟的思路,催化了以纵向业务性能细分作为域划分的根据的服务化计划以及组织构造,次要诉求是在细分的业务性能服务根底上,能按需疾速灵便的组合,从而撑持不同的业务模式,提供业务敏捷性,撑持业务翻新求变。

当然反过来,因为纵向性能细分,业务功能域增多,整个业务链条上的咬合点越来越多,随之产生越来越多的数据起源冗余反复或者缺失,性能或者重合且各自发散,或者缺失,最终给整体业务带来较多数据和性能的不一致性危险。这样一来,不仅横向端到端的业务串联老本高,而且要害门路的危险收敛老本比拟高,矛盾冲突点集中在各纵向域性能和数据咬合处,具体表现为:

数据上:

  • 无主数据,有数据需要无 owner;
  • 大量反复且不统一数据;

性能上:

  • 局部业务性能缺失;
  • 域之间存在业务性能反复且行为不统一。

到底是纵向切分域还是横向分业务模式拉平来做,这个问题没有标准答案,更没有最佳答案。只有依据不同的业务倒退阶段及时动静调整试错,换言之,这是一个一直寻找绝对最优解的动静过程。

弹性计算和 Serverless 解决了算力的问题,畛域驱动服务化设计解决了性能的拆分和按需搭配组合的问题,那么剩下的外围问题就是数据了:如何以一套对立的数据架构,既能撑持多租户的数据安全性需要以及通用的数据存储,也能撑持用户扩大的自定义数据对象定义和模型变更,同时也要保证数据定义层面的扩大和变更不会影响本身和其余租户业务性能的可用性。咱们来剖析下可能的计划(暂不思考按服务边界进行数据库拆分):

  1. 对立的数据库,规范数据模型和扩大数据模型间接映射到物理表和索引:很显然,对于不同租户自定义的数据对象和数据模型要求是无奈撑持的,物理数据模型会互相烦扰、互相抵触直到无以为继。即便是对于所有租户齐全规范的性能和数据存储,平台本身的规范模型降级的 DDL 也会对用户的可用性造成较大影响,所以显然是行不通的。
  2. 如果为每个租户创立各自的数据库呢?各自租户领有各自的数据库,能够满足用户数据安全隔离的需要,也能够满足各租户自定义的数据需要,看上去像是一种正当的 SaaS 数据计划。然而仔细分析,会发现有两个显著的问题:
  • 如果用户须要批改或者扩大现有物理数据模型而进行的 DDL 操作,必然会影响线上业务的整体可用性,也可能会影响到规范数据模型,从而影响到线上性能应用。
  • 如果用户可自定义对物理模型进行扩大和定制,当平台进行模型降级的时候,极容易产生物理模型的抵触,导致新旧性能异样。
  • 因为用户在各自数据库存在各自定义的扩大和定制,则平台数据模型和性能降级须要针对不同的租户进行别离验证,存在极大的降级验证工作量和危险。

以上两种计划可行性低,咱们从其中发现的问题是:平台业务零碎的逻辑模型到物理模型的间接映射是造成问题的次要因素。既然物理模型的变更是平台不稳固的动因,那么咱们是否能通过解耦业务逻辑模型和物理模型的映射关系来尝试解决这个问题呢?

既然问题曾经定义分明了,如何解决这个问题呢?通常咱们解决架构问题的一个“万能”的办法是:减少一个档次,咱们也来套用一次,减少一个档次(元数据层)来解耦逻辑模型到物理模型强映射的问题。

首先,咱们须要对业务进行建模,对业务进行形象,定义出业务逻辑模型,而后对模型进行二次形象,定义出逻辑模型的定义数据,实现业务模型的数据化,即模型的元数据(The Metadata of the Logic Model),将模型构造存储为数据,而不是间接对应的物理存储构造。

其次依据定义出的元数据进行对立形象,造成元数据逻辑模型。

将元数据逻辑模型映射到元数据物理模型,对应理论存储构造。

通过对业务模型的变更,造成对元数据层的数据变更,而不是物理构造的变更,从而实现业务逻辑模型同物理模型的解耦。

很多事件说起来如同挺简略,实际上是一个十分微小的系统工程,将其付诸实践是挑战十分大的事件,而获得踏踏实实的胜利则更难。上述问题的解题思路是 Salesforce 的解题思路,而且 Salesforce 不仅获得了胜利,也靠近将其做到了极致,上面咱们站在伟人的肩膀上来看看 Salesforce 如何通过元数据驱动的架构(外围是根底数据架构)来撑持多租户的 SaaS 业务平台。
留神:因为 Salesforce 并未有对外围实现逻辑进行齐全公开和阐明,所以本文所整顿的局部外围逻辑蕴含了作者的逻辑推理和解读,然而的确进行了逻辑验证和场景验证,如有纰漏和不全面的中央,欢送探讨及斧正。

元数据驱动的多租户架构

Salesforce 将 Force.com 定义为 PaaS 平台,Force.com 的根底就是元数据驱动的软件架构来撑持多租户利用。首先我来解释下什么是以元数据驱动的软件架构为外围。

一、多租户意味着什么

多租户的含意用一句话来形容就是:一个云平台,有数多个客户。

一个云平台的含意是:一个代码库,一个数据库,一整套共享的可扩大服务,包含数据服务、应用服务以及 Web 服务。

有数多个客户的含意是:每个客户都被调配一个惟一的租户 OrgID,所有的数据存储都是依照租户 OrgID 隔离的,所有的数据拜访必须蕴含 OrgID,所有的操作也都是蕴含租户 OrgID 的,也就是所有的客户数据和行为都是被平安的通过惟一的租户 Org 进行严格隔离的。

每个租户 / 组织只能看到和定义依照本人租户 OrgID 隔离的本人版本的元数据和数据,而且只能执行本人租户 OrgID 所受权的行为,这样每个租户就领有各自版本的 SaaS 计划。

二、元数据驱动意味着什么

元数据对于平台意味着平台数据的数据,对于租户意味着是对于租户数据的数据。

当用户定义一个新的用户表的时候,用户创立的不是数据库中的物理表,而是在零碎态的元数据表中增加了一条记录,这个记录形容的是用户表的逻辑定义,是虚构的,这个表并不在数据库中物理存在,而这条记录代表就是用户态的数据表。

当用户定义了用户表的一个新的字段时,用户并没有在物理表中创立物理字段,而是在零碎态的元数据表中增加了一个记录,这个记录形容的用户表的字段组成的逻辑构造,是虚构的,这个字段也不在数据库表构造中物理存在,而这条记录代表的就是用户态的用户表字段。

也就是通过存储在零碎态的元数据表中的元数据记录作为虚构用户的数据库构造。

三、元数据驱动的多租户整体架构

咱们先来大略理解下元数据驱动的多租户的整体架构,整体架构大略分为 5 个逻辑档次:

  1. 底层数据架构分为三个档次:
  • 最底层是数据层,存储了离散的零碎和用户的业务数据,业务日常经营的数据存储在这里。
  • 公共元数据层,存储了利用零碎规范的对象和规范的字段定义,对底层数据的构造进行定义阐明。
  • 租户特定元数据,存储了租户主动的对象和自定义的字段定义,用于对底层的数据结构进行定义阐明。
  1. 通用数据字典 UDD(Universal Data Dictionary) 运行引擎层实现了利用对象到底层数据存储的映射,蕴含对象模型操作、SOQL 语言解析、查问优化,全文搜寻等性能,咱们常说的 ORM 性能也是其外围性能,但比其简单的多。
  2. 平台服务层提供 PaaS 层平台服务,提供利用对象模型的创立,权限模型创立,逻辑和工作流程创立以及用户界面的创立,包含屏幕布局、数据项、报表等。
  3. 规范应用层提供端到端的规范的业务利用性能。
  4. 租户虚构应用层,用户能够在规范应用层或者平台服务层之上定义本人特有的业务利用性能,满足本人特定的业务场景须要。

其中,底层数据架构是最为要害的平台基石(The Corner Stone),其外围运行引擎也是基于弱小的底层数据架构根底上构建的。本文则以元数据驱动的多租户数据架构为外围来一一开展。

四、元数据驱动的多租户数据架构

上面咱们具体来看下零碎态的数据模型,基于 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) 以及是否必选、惟一、索引标记,还有局部规范字段。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 别离为 2、4,则其在 Data 表中存储的地位,就是 OrderItem 对象在 Data 对应的记录中 Value2、以及 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)其余索引透视表

其余索引透视表的逻辑相似,都是为了满足特定检索和查问须要,将数据同步到索引表,供给用零碎应用。此处不再赘述,如的确有须要再补充。

五、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 中起到较大作用。

六、如何撑持多租户微小数据量

后面咱们提到 Salesforce 一个共享数据库的概念,那一个共享数据库怎么来撑持如此微小的多租户数据库呢,同时不仅须要反对巨量数据,并且还能够撑持租户间的数据物理隔离,保障各租户的数据稳定性、可用性和数据安全?

Salesforce 的做法是:分区。所有的 Force.com 的数据,元数据,透视表构造,蕴含底层数据库索引,都是通过对 OrgID 进行物理分区的,采纳的是原生的数据库分区机制。所有的数据以及元数据通过你的 OrgID(16digits)进行分片 Hash。

数据分区是数据库系统提供的被验证过的技术,用以物理划分较大的逻辑数据结构到较小的能够治理的区块中。分区也能够帮忙晋升性能和扩展性,贴别是在多租户环境下一个微小的数据系统的扩展性。依据定义,每一个 SOQL 的查问对应一个特地的租户信息,因而查问优化器,仅仅须要思考拜访蕴含对应租户的数据分区拜访,而不是整个表或者索引。

七、无感的对象构造变更(No DDL)

当一个利用零碎或者服务组件须要对其数据模型进行降级的时候,通常会通过数据库 DDL 语言对数据库物理构造进行操作,如果波及的数据量较大,则可能会造成较长时间的数据库变更时效,造成对应工夫内的零碎不可用,如果是多租户零碎还会可能其余租户的可用性造成影响,抑或造成诸多的底层模型不统一产生。

在元数据驱动的数据架构中,所有的 DDL 语言操作对应的使元数据层的元数据的记录的更新,不波及数据库物理构造的更新,不会造成变更期间的数据库物理构造耗时调整造成的不可用,同时零碎平台提供了一个高效的机制来缩小对平台多租户利用总体性能影响。

当用户批改了一个表字段列的数据结构,从一种数据类型改成另外一种不同存储格局的数据类型时候,零碎会从新分派一个新的弹性列给到这个字段列的数据,将数据从原来的存储弹性列批量拷贝到新的弹性列,而后才会更新此字段列的元数据,暨在 Fields 表中更新这个字段列的元数据,将数据类型更改为新的数据类型,并将 FieldNum 更新为新的 ValueX 列对应的 X 值。

同时,在如上对用户逻辑表结构调整失效过程中,原来的数据结构和对应的数据拜访失常进行,直到逻辑表构造变更失效,对利用零碎可用性不会造成影响,用户对此无感知。

八、多租户架构对于研发人员意味着什么

对于研发人员来说,多租户构造最多意味着两个版本:以后版本,以及下一个版本。没有遗留版本须要保护。所有人不必操心旧的技术,旧的版本,所有只有最新的版本,只须要关怀最新的版本。

这样就给麻利开发带来极大的益处,每年做个位的公布,每次公布几百个新的个性新的版本也不会扭转用户的体验,新的个性能够依据用户须要开启,通过个性治理来开关。

新版本公布前,提供沙箱环境来容许用户提前试用新版本的零碎。如果做 bug 修复,则是在所有租户层面上进行对立修复的。

对于用户利用的公布进行严格管理,避免对其余租户产生影响,通过提供沙箱环境来让用户验证新利用公布,并通过成千上万的自动化测试保障用户的失常性能。

在运行期间,不作任何底层 DDL 操作,不会做表的创立,也不会做表的变更,只可能在极少数的更新周期时候进行。

作者:程彦,曾就任于阿里数字供应链事业部负责多年供应链打算域研发,目前在阿里数据中台负责相干商业化产品开发。
原文链接
本文为阿里云原创内容,未经容许不得转载

正文完
 0