一般来说,电商数据架构是比较复杂的,做过电商的同学都有深刻的体会。
具体复杂在什么地方呢,举几个具体的例子:
- 1.每种商品都有自己的独特属性。比如鞋子和电脑的属性是完全不一样的。如何对他们构造商品数据架构?
- 2.如何维护它们。不至于因为业务的发展,使得数据结构变得异常复杂而无法拓展和维护?
- 3.未来可能会卖更多种类的商品、我们人类也会发明出来更多新的商品。如何适应变化?
- 4.每秒钟的并发量十分巨大,数据架构在这方面如何考量?
- 5.商品数据配置错误,如果快速就地回滚,为线上快速止血?
- 6.如何为整个开发周期提供支持?
- 7.其它原因 。
在电商这样的场景下,某些问题就变得异常复杂了!
如果我们还是按照传统方式来设计数据架构,比如说和领域模型强绑定的ER方式来设计数据架构,必然会导致数据架构随着业务的发展而变得越来越复杂!
所以需要一种新型的数据架构来解决我们系统的基础性问题。
这里提供一种新的思路,也在实际生产环境中验证过了。各位看官和我一起看过来:
- 1.建立统一的、可管理的数据平台,防止数字孤岛的产生,提高数据共享程度并兼顾性能的监管和提升。
- 2.我们为数据架构提供机制而并不提供策略。将策略放到App端来决定,由App来决定其领域的上层数据架构,不管什么样的电商APP都可以从数据平台“长出来”,模式自由化,做到Code First!
- 3.提供数据沙箱和多版本回溯的能力,能够快速建立数据clone和止血。
- 4.提供电商基础数据架构的抽象。
基于上面的考虑,我们有这样的具体设计和实现:
不同流程、不同节点所绑定的数据模式是不一样的。
比如不同类型客户(普通客户、客户经理等等)对于平台而言对业务的处理流程不同,会形成不同类型的订单,不同类型的订单会产生不同的结算数据模式。
所以模式动态化是一个非常重要的设计轨迹,不然数据模式必然非常复杂,难以拓展和维护,表的数量越多,也会带来性能问题。
互联网系统无非是: App = 功能编排 + 流程编排 + 业务规则 + 数据编排
所以基本设计思路是:动静分离
静的部分
是系统元数据部分,它们提供了流程编排、功能编排、业务规则以及动态表元数据管理等功能。
动的部分
动的部分,就是动态表的部分,表示最终的业务数据,并且业务数据可以整体加密了(因为他们是JSON),数据访问的方式都采用数据中间件的方式(比如Mycat)。实现方式是:
- 1.表空间就是一个MySQL 5.7 + 物理表。表的字段统一都是:
- Id | header | payload | creator | createTime
- 2.我们定义个管理表空间的物理表(元数据表,属于静态部分),一旦有人在此表注册一个表空间,那么就自动根据1中的模式生成一个物理表
- 3.我们定义一个管理动态表模式的物理表(元数据表,属于静态部分),它使用JSON字符串保存描述动态表结构的json-schema。假设如此:
并根据此模式动态生成虚拟列(这是mysql 5.7+本身的能力,具体的请查询官网资料MysqL Json functions)
- 4.我们在写入数据到新建的表空间的时候,根据某个json-schema描述的结构来写入header和data字段,并且使用jsonschema-validator验证格式是否正确。形式如下:
header: { “table”:”a”, ”sandbox”:”test”, ”version”:”时间戳”, snapshot=”标记名字”, ”schemaname”:”aschema” } payload:{ “id”:”1”, ”name”:”leo”, ”price”:78.89 } //业务数据
- 5.
此时上层查询类似于:dataContext.select().from(“a”).where(“id=1”);(推荐采用Jinq和JooQ的方
式)虚拟表名在datacontext中可以根据元数据反向找到对应的所有表空间,然后动态形成 union all
SQL并提交MySQL服务器执行。
- 6.底层解释为:select id, name, price from 名空间名称 where table=”a” and id=1,union all 其他表空间 where table=”a”…….
我们约定:
1) 一个虚拟表A可以保存在N个表空间中
2) 一个表空间可以保存N个虚拟表
3) 底层DataContext为上层提供透明性,只需要提供虚拟表名、需要提取的列名以及条件即可获取数据,并不会感觉到差异。
虚拟表和表空间的关系由静态元数据表来维护。
这样做的目的,是一个虚拟表可以分布在不同表空间甚至是不同库的表空间中,将数据散列,一遍形成分布式表,达到较好的性能要求。
另外上层程序代码通过数据中间件访问数据源,自动分表,进一步提高了性能指标。
7.实际上形成了一个虚拟表体制。
8.我们称payload中json的Id为虚拟表Id,这一点不要和表空间(物理表)的Id向混淆。
9.我们在header中记录版本,如果有N个id=1的虚拟表记录的话,那么我们在上层DataAPI将会看到的数据是版本最大的那一个(这个过程叫做版本塌陷)。
10.如果我们需要回滚数据,只需要指定一个版本并query这个版本的数据并插入虚拟表即可,所有数据形成的历史将会保存起来。
11.在一个表空间(物理表)中,可以存在很多不同schema的虚拟表。
这样我们就具备了一个解放模式的动态表架构,不过要注意:
1) 由于Mycat的存在,表空间的数据量不会对性能产生不好的影响。
2) DataAPI应该可以构建并提交静态表和动态虚拟表的联合查询(Mysql支持的)
3) 迫使我们必须将底层数据架构DataAPI化,这样就做到上层代码会认为静态表和动态表并无差别,做到底层数据架构透明化。
4) 提供管理API,可以维护元数据的方式动态构成动态表。
12如何实现动态表记录的修改、删除:
1). 修改:对物理表data字段进行修改,
使用:添加一条新的包含修改好的数据记录,并在header中写入op=modify。来实现更新业务数据。
2). 删除:按虚拟Id添加一个header中op为del值的空白记录
如:
header: { “table”:”a”,
”changeset”:”test”,
”version”:”时间戳”,
snapshot=”标记名字”,
”schemaname”:”aschema”,
”id”:”1” }
payload:NULL // 业务数据。
NULL是个特殊的值,表示已删除。
此时查询时判断payload =NULL and id=1,这条记录就没有了,查询结果为null。
13.sandbox:修改集是一种沙箱概念。
我们约定如果sandbox =test,那么这里的数据就是给beta测试用的。
如果是release就是给正式环境用的。
于是我们预设这样的sandbox名字:
A:dev 开发用数据
B:test 测试用数据
C:release 正式用数据
我们可以这样认为对数据进行了虚拟分区。
一个重要的副产品就是,如果我们需要作一个将会用于正式环境的test数据集,测试之后,我们只需要需要changset为release,那么数据集立即生效,正式环境就可以用了。
静态表部分,还设计了工作流部分、业务元数据部分、标签系统部分、权限管理的数据模型,这些模型是稳定的,并且是属于元数据的,动态部分是不同app所形成的模型。
至此,我们用有限的底层数据模型手段就可以对应千变万化的业务数据架构了。
- 应用管理静态表: App为前缀的表, 负责应用产品通道的管理
- 工作流相关静态表: Workflow为前缀的表,负责定义和管理工作流实例
- 工作流Form静态表: WorkflowForm为前缀的表,定义工作流节点相关的表单以及规则绑定
- 非工作流Form静态表: NormalForm为前缀的表,定义非工作流绑定的,普通的表单
- 业务数据元数据静态表: 为业务提供度量衡、城市、地铁等等元数据支持
- 表空间管理静态表: 用来注册表空间,并由DataAPI服务器创建具体的物理表
- 动态表模式管理静态表: 动态表注册到表空间,schema注册,并由DataAPI创建动态表
- 权限管理相关静态表: 应用权限的定义和分配
- 标签静态表: 用于管理定义标点和打标的表
预设动态表说明:预设的动态表对应的json-schema不可以被删除和修改,作为root业务基础存在!
之后后续的版本都是基于这个root演变出来的。
每个表空间中的表,全部是根据业务形态定义的,定义权利交给了App,也就是通过App代码定义动态表,做到Code First。
并且一个动态表可以有N个注册在表空间中的多个模式,模式也具备版本。
也就是说虚拟表的列定义是可以动态拓展的,并且同时只有一个最新的会生效,可以有回滚模式。
我们约定:动态表的定义就等同于其JSON-schema的定义,也就是说一个动态表对应一个固定的JSON-schema.
- 交易主体表空间
- 计算规则定义表空间
- 购物车表空间
- 订单表空间
- 财务表空间
- 商品表空间
总之,灵活的底层数据架构,决定了基础数据API服务变成了一种基础设施,使得其他业务APP可以在上面方便的拓展和生长。
中生代技术群微信公众号
本文作者 高磊
中生代技术微信号公众号:freshmanTechnology