我们来自字节跳动飞书商业应用研发部(Lark Business Applications),目前我们在北京、深圳、上海、武汉、杭州、成都、广州、三亚都设立了办公区域。我们关注的产品领域主要在企业经验管理软件上,包括飞书 OKR、飞书绩效、飞书招聘、飞书人事等 HCM 领域系统,也包括飞书审批、OA、法务、财务、采购、差旅与报销等系统。欢迎各位加入我们。
本文作者:飞书商业应用研发部 许家强
欢迎大家关注飞书技术,每周定期更新飞书技术团队技术干货内容,想看什么内容,欢迎大家评论区留言~
背景
在 SaaS 平台建设过程中存在一个问题:由于 B 类业务的复杂多变,不同企业的业务流程和管理机制存在很大差异,而市面上的标准化 SaaS 产品,较难满足企业的差异化、定制化需求。
因此需要有一套基于高度可扩展的技术框架,用于支撑 B 类业务中灵活变化、可定制化性强的场景。APaaS 就是用于解决平台扩展性的杀手锏,APaaS 平台不仅提供了 SaaS 产品标准化的产品能力,同时,允许使用方通过零代码、轻量的配置化方式,或是以较低成本在 APaaS 平台进行二次开发,丰富和扩展产品的功能。 本文介绍当前业界用于构建可扩展性模型采用的几种方法。
技术选型
APaaS 平台需要有高度可扩展的模型设计。然而,传统的企业应用的建模方法,通常是基于关系型数据库、面向对象进行建模,该方式预先定义好了模型的结构,如果需要扩展必须通过修改模型( DDL 变更),代价较大;更为致命的是,一个平台上所有租户共享一份数据结构和模型,这套方案显然不适用于 APaaS 平台。
基于元数据驱动的模型
关键概念
- 对象:模型的实例。定义了一个特定的信息论域。如一个具体的订单。
- 模型:一个模型是一个元模型的实例。模型层的主要责任是定义描述信息论域的语言。在建模层上的对象的例子如:订单模型。
- 元模型:一个元模型是一个元元模型的实例。元模型层的主要责任是定义描述模型的语言。一般来说,元模型比定义它的元元模型更加精细,尤其是当它们定义动态语义时。
- 元元模型:构成了元建模( metamodeling )体系结构的基础结构。这一层的主要责任是定义描述元模型的语言。一个元元模型定义了这样一个模型,它比元模型具有更高的抽象级别,而且比它定义的元模型更加简洁。一个元元模型能够定义多个元模型,而每个元模型也可以与多个元元模型相关联。通常所说的相关联的元模型和元元模型共享同一个设计原理和构造,也不是绝对的准则。每一层都需要维持自己设计的完整性。在元元模型层上的元元对象的例子有:元类、元属性和元操作。 salesforce 的 APaaS 平台,就是基于元模型设计的。其主要的模型如下:
多租户隔离
- OrgId:表示租户id,用于多租户隔离。
- Guid:区分该记录的唯一标识。
模型描述
- Object:描述模型的基本信息。如PO、商品、需求单、付款单都是一个Object。
- Field:描述模型的元结构信息,其中FieldNum维护了该字段在Object的顺序编码。
- Data:存储模型对象的数据,定义Value0、Value1...、Value500 共500个固定字段,用于存储在Field定义的字段,value的下标,正好与Field中的FieldNum对应。
- Indexs: 定义了模型的索引字段的信息,用于快速检索。salesforce在写入数据时,将同时写Data和Index表。
- UniqueIndexs:定义了模型的唯一索引字段信息,利用数据库自身的唯一约束能力保证模型的完整性。salesforce在写入数据时,将同时写Data、UniqueIndexs表。
运行机制
基于上述元模型驱动的架构,达到了几个目标:
- 多租户,每个租户的数据完全隔离
- 模型结构是高度可定制化的,其结构不是在系统部署前预先定义好的,是在程序运行时基于用户配置的元模型,动态加载、拼装而成的,在内存中看到的是组装后的真正对象视图
查询的实现:
- 前端在查询数据时,平台需要从Object、Field获取元模型的结构定义,确定了对象类型和对象的结构
- 根据对象的索引,到Index、UniqueIndexs表查询数据的主键,即定位Data的Guid
- 查询Data获取数据
- 根据对象的结构Field,将扁平化的Data,对应value0、value1...value500,组装成对象视图
写入的实现:
- 前端在写入数据时,平台需要从Object、Field获取元模型的结构定义,确定了对象类型和对象的结构
- 根据对象的结构Field,将对象视图的数据,组装到扁平化的Data中,对应value0、value1...value500
- 根据对象的索引设置,按需写入Index、UniqueIndexs表,用于维护索引数据。
方案缺点
- 实现的成本较高,框架逻辑复杂
- 分库分表比较难做,会遇到性能瓶颈
基于key-value的纵表设计
方案描述
所有的数据都基于纵表进行存储。
- Object:按租户id进行隔离。一个Object表示一种业务实体类型,如PO、商品、采购申请单。
- Filed:维护每个对象的结构和数据。每个租户的结构是不同的。例如,一个商品有名称、属性、价格、库存,分成多行记录存储在Filed。
方案缺点
- 数据很容易膨胀,对于B类业务,一个业务实体可能有几十、上百种属性,水平扩展能力差
- 一次写事务要操作上百条数据,大事务的处理会影响系统的吞吐量
- 查询时要涉及表关联查询,且会查大量数据,频繁IO导致系统性能不佳。
- 检索查询支撑能力较差
基于NOSQL的存储设计
方案描述
为了解决上述方案中数据量大、频繁IO查询导致的性能问题,可以采用NOSQL方案以支持水平扩展。
当前NOSQL产品有如下可选:
产品 | 优点 | 缺点 | 存储示例 |
---|---|---|---|
MOGODB | 高性能,支持部分筛选查询功能,海量存储 | 不支持事务操作,数据结构json需要额外转换处理 | PR模型: 以json的key-value格式,维护PR的字段信息,如 { "id":"3444", "prNumber":"3030333", "applyTime":"2020-01-01 00:00:00", "applyUser":"小王", "description":"买一个15英寸苹果电脑", "money":"20000" } |
HBASE | 高性能,支持海量存储,列动态扩展,支持行级事务 | 查询功能有限,仅支持基于get和scan,rowkey的设计有很多限制 | PR模型,查询主要需求为根据pr单号或创建用户查询单据。 rowkey为 :tenantId+PR单号+申请用户id+创建时间戳 col为:创建时间、采购类型、状态、金额、需求说明、立项名称等。 |
动态DDL方案
每个租户的数据模型在物理上是隔离的,当遇到个性化需求时,需要动态创建DDL,该方式较重,实现成本较大。 采用此方案的,有微软的power platform、一些轻量的表单类应用产品。
总结
本文分别介绍了几种提升可扩展性的几种设计方法:大宽表(元数据驱动)、纵表、NOSQL、动态建表,这些方案各有利弊,其复杂度、扩展能力、面向的场景都不一样。
方案 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
元数据模型 | 模型通用度高,灵活性强 | 实现成本较高、性能一般 | 金蝶、salesforce |
纵表 | 灵活性高 | 数据量膨胀,条件检索需求较难支持 | 数据量不大、对检索需求不强的业务场景 |
NOSQL | 可以支持较大的数据量,性能较好 | 条件检索需求较难支持 | 大数据量、对性能有较高需求的场景 |
动态DDL | 具备一定的扩展性 | 因为安全因素,不一定具备落地条件 | 微软云平台 |
在实际业务建设过程中,要根据实际情况(团队技术现状、建设&维护成本、业务对扩展性灵活度的要求、性能)进行权衡,综合判断,选择适合的方案。
加入我们
扫码发现职位&投递简历