面向数据编程:ECS设计模式在数仓中应用的思考

简介: 前言 作为一个从Java转去做大数据的开发,尤其是基于Hiv采用SQL的开发来说,抛弃了使用了很久的OOP,面向对象编程的设计思想后,总觉得有点不习惯。传统的web项目中,对SQL的使用更多还是在数据的增删改查上,而在大数据领域,更多复杂的数据分析,数据交并差的处理,导致SQL代码量急速增加,可维护性大幅降低。而SQL本身就是一个面向过程描述的语言,Java中常见的MVC,MVVP等设计模式也不

前言

作为一个从Java转去做大数据的开发,尤其是基于Hiv采用SQL的开发来说,抛弃了使用了很久的OOP,面向对象编程的设计思想后,总觉得有点不习惯。传统的web项目中,对SQL的使用更多还是在数据的增删改查上,而在大数据领域,更多复杂的数据分析,数据交并差的处理,导致SQL代码量急速增加,可维护性大幅降低。而SQL本身就是一个面向过程描述的语言,Java中常见的MVC,MVVP等设计模式也不适合套用在SQL身上。那么,是不是应该存在一种设计模式,适用于面向过程的编程设计呢?

带着这样的疑问,我开始关注面向数据编程。面向数据编程,核心在于数据。我希望数据可以变得更加灵活,方便开发者对它进行加工。同时,加工过程可以做到高内聚,低耦合。带着这样的需求,查阅了很多资料。直到有一次,无意中看到游戏引擎Unity3D采用的ECS设计模式,突发奇想,意识到这是不是可以满足我的需求呢?

关于游戏开发中的ECS简单介绍

ECS是Entity-Component-System三个单词的缩写。最早是在2002年的Game Dungeon Siege上被提出来,是为了解决游戏设计中,物体直接数据交互和性能的问题。

它在游戏开发中的演变逻辑可以参考这篇文章:https://zhuanlan.zhihu.com/p/32787878

简单的说,Entity、Component、System分别代表了三类模型。

实体(Entity):实体是一个普通的对象。通常,它只包含了一个独一无二的ID值,用来标记它是一个独立的对象。通常使用整型数字作为它的实现。

组件(Component):对象一个方面的数据,以及对象如何和世界进行交互。用来标记实体是否需要进行这一方面的处理,通常使用结构体,类或关联数组实现。

系统(System):每个系统不间断地运行(就像每个系统运行在自己的私有线程上),处理标记使用了该系统处理的组件的每个实体。

 

它跟传统OOP编程有什么不一样呢?

最核心差异点在于:传统OOP编程里,我们会先对编程对象进行虚拟化抽象,将共同的一类数据归到父类或者接口中,子类继承或实现对应的接口。在游戏开发中,父类往往是被锁死的,而一旦需要对逻辑作出修改,要么重写实现,要么继承基类进行覆盖。但游戏策划的创意是天天都可能会变化的。从而造成大量子类重复出现,大幅降低。此外,在对于C++语言中,使用对象池优化时就会造成灾难性的后果——一种类型一个池。

其次,从计算机底层数据传输上来说,传统OOP在传递数据时都是采用对象进行封装。但通常需要用到的数据只是对象中一两个属性。对于大部分web应用上来说,多读取的对象数据影响不大,但对数据密集型计算(例如游戏图像领域),则对性能会产生影响。

 

而ECS就是可以解决以上问题。ECS全写即“实例-组件-系统”的设计模式。简言之,实例就是一个游戏对象实体,一个实体拥有众多的组件,而游戏系统则负责依据组件对实例做出更新。

举个例子,如果对象A需要实现碰撞和渲染,那么我们就给它加一个碰撞组件和一个渲染组件;如果对象B只需要渲染不需要碰撞,那么我们就给它加一个渲染组件即可。而在游戏循环中,每一个系统都会遍历一次对象,当渲染系统发现对象持有一个渲染组件时,就会根据渲染组件的数据来执行相应的渲染过程。同样的碰撞系统也是如此。

也就是说游戏对象需要什么就会给自己加一个组件。而系统会依据游戏对象增加了哪些组件来做出行为。换言之实例只需要持有必要的数据,由系统负责逻辑就行了。由于只需要持有必要数据,因此对于缓存是非常友好的。这也就是ECS模式能和数据驱动很好结合的一个原因。

 

对于ECS在数仓建设应用中的一些思考

对于数仓建设,也是一个面向数据驱动的开发。因此我将ECS和数仓的代码联系起来,思考如何将ECS的设计模式在数仓中应用。我给出了以下的一些想法:

一个基本假设:

在数仓中,如果可以抛弃pk依赖后,一张表就是一群Schema的合集。

这是我对数仓中数据构成的根本假设。如果一张表里的其他Schema被PK约束,自然会导致Schema直接产生逻辑关系。如果没有PK,那么各个Schema互相之间是平等的,Schema之间可以互相组合。表只是由一个个的Schema填充而成的。这样听起来是不是很像Entity和Component之间的关系呢?

所以我大胆的列出一个映射关系。

与ECS的关系映射:

Entity对应于数仓中的Table,Component对应Schema,System对应数仓中SQL逻辑。

image.png

 

对于一张表来说,又若干个Schema构成。对于SQL代码来说,它关心的只是要用到的Schema,而不是表的业务逻辑。一张表可以由多个不同的SQL共同产出。所以依赖关系可以是这样的:

image.png

SQL只需关心它加工逻辑中需要用到什么Schema,产出什么Schema;Table只需要关心,它的业务逻辑是由哪几个Schema组成;而Schema自己只需要关心,自己代表什么原子含义。

ECS模式下的SQL伪代码简单实现

在SQL语言,我们一般代码会写成这样:

Select A1

From Tbale1

Where Condition1

A1代表我们需要的Schema,Table1是表,Condition1是需要满足的条件。

对于ECS架构来说,这样写违背了System不跟Entity交互的原则。理想的ECS实现是:

Select Table1.A1

Where  Condition1

如果不同表中的Schema都是平等的,那么只需要指出使用的是哪个表里的Schema,和对应的加工条件。无需再将表名列入其中。

当然,有人会说,不就是多个From Table嘛,多写这一句话也不会怎么样。

是的,但大多数数仓开发中,并不是简简单单的一张表的处理。往往我们还会遇到很多表之间交并差的情况。这个时候,我们写的最多的代码是:

select t1.a,t2.b

from (

select *

from table1

where condition1

) as t1 left

join table2 as t2

on condition2

 

对于一个ECS架构,我们的实现是:

select table1.a,table2.b

where condition1 and condition2

这样看起来,代码是不是就简洁明了多了呢?(当然,现阶段SQL语法并不支持这种写法)

 

另外,我们在处理表数据的时候,经常还会遇到这样一种情况:

insert into tmp_table1

select a1,a2,a3……a31,a32,cast(a33 as bigint) as b1

from table

where condition1

 

inset into result_table1

select a1,a2,a3……a31,a32,b1+1 as c

from tmp_table1

 

inset into result_table2

select a1,a2,a3……a31,a32,b1+2 as c

from tmp_table1

从a1到a32 一共32个列名,其实是不需要做任何特殊处理的,只需要根据condition1条件筛选出来。之后我们又要带着a1……a32在两张结果表中进行插入。且不提这样复制粘贴列名操作十分麻烦,容易出错,就是我们是否有必要这么做?

我们的诉求可能只是修改某一张表里的某一列值,但不得不把这张表的其他字段反复提取插入。

根据ECS的设计思想,所有列值都是互相平等的。每张表(Entity)只是由列(Component)填充,Sql(System)只是负责逻辑行为。

那么,实际操作应该是:

insert into tmp_table1

select table.a1,table.a2,table.a3……table.a31,table.a32

where condition1

 

insert into tmp_table2

select ,cast(table.a33 as bigint) as b1

where condition1

 

inset into result_table1

from tmp_table1 add colum tmp_table2.b1+1 as c

 

inset into result_table2

from tmp_table1 add colum tmp_table2.b1+2 as c

 

(以上都是伪代码)

这样写看上去代码行数没变化,但好处是,如果table中结构发生变更,只需修改上层tmp_table1的结构即可,对结果表无感知。这一点上反而有点像OOP中的继承关系。

 

总结

思考将ECS设计模式引入数仓设计,本意是希望开发者可以更加关注于逻辑,关注数据如何处理,也就是S的部分。业务则由从列构建表的时候产生。将表结构和数据处理逻辑进行拆分,从而希望能提升SQL代码的可读性和结构性。

SQL本身是一个非常优秀的描述型语言,给数据处理带来了极大的便利。但在表结构越发复杂的今天,我已经感觉到传统的SQL的局限性。希望通过ECS设计模式的思考,可以大家带来更多的启发,可以让SQL代码像其他工程语言一样,简洁优雅。

 

 

 

 

相关实践学习
阿里云云原生数据仓库AnalyticDB MySQL版 使用教程
云原生数据仓库AnalyticDB MySQL版是一种支持高并发低延时查询的新一代云原生数据仓库,高度兼容MySQL协议以及SQL:92、SQL:99、SQL:2003标准,可以对海量数据进行即时的多维分析透视和业务探索,快速构建企业云上数据仓库。 了解产品 https://www.aliyun.com/product/ApsaraDB/ads
目录
相关文章
|
运维 算法 机器人
阿里云AnalyticDB具身智能方案:破解机器人仿真数据、算力与运维之困
本文将介绍阿里云瑶池旗下的云原生数据仓库AnalyticDB MySQL推出的全托管云上仿真解决方案,方案采用云原生架构,为开发者提供从开发环境、仿真计算到数据管理的全链路支持。
|
网络协议 关系型数据库 应用服务中间件
如何迁移网站数据到新的服务器
迁移网站数据到新服务器是一个系统化的过程,需谨慎操作以避免数据丢失或服务中断。小编为您整理发布如何迁移网站数据到新的服务器,以下是详细步骤和注意事项。
|
设计模式 缓存 安全
【高薪程序员必看】万字长文拆解Java并发编程!(8):设计模式-享元模式设计指南
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的经典对象复用设计模式-享元模式,废话不多说让我们直接开始。
358 0
|
设计模式 缓存 监控
并发设计模式实战系列(14):CAS(无锁编程)
🌟 大家好,我是摘星!🌟今天为大家带来的是并发设计模式实战系列,第十四章,废话不多说直接开始~
311 0
|
设计模式 人工智能 算法
基于多设计模式的状态扭转设计:策略模式与责任链模式的实战应用
接下来,我会结合实战案例,聊聊如何用「策略模式 + 责任链模式」构建灵活可扩展的状态引擎,让抽奖系统的状态管理从「混乱战场」变成「有序流水线」。
|
设计模式 XML 安全
Java枚举(Enum)与设计模式应用
Java枚举不仅是类型安全的常量,还具备面向对象能力,可添加属性与方法,实现接口。通过枚举能优雅实现单例、策略、状态等设计模式,具备线程安全、序列化安全等特性,是编写高效、安全代码的利器。
|
弹性计算 安全
阿里云服务器镜像,快速迁移项目数据
有时候旧服务器快到期了,想把项目、数据、软件挪到新服务器上,如果全部重新搭建的话,那无疑是耗时又费力。有了镜像迁移,就方便了许多。
992 1
|
存储 安全 数据管理
服务器违规资源被删,数据定时备份OSS 云存储才是 “救命稻草”
在数字化时代,数据已成为企业与个人的核心资产。然而,服务器违规、硬件故障等问题频发,导致数据丢失、业务中断,甚至造成不可挽回的损失。为保障数据安全与业务连续性,定时备份至关重要。阿里云国际站OSS提供高效、可靠的云存储解决方案,支持自动定时备份,帮助用户轻松应对数据风险。本文详解OSS备份操作步骤与注意事项,助你为数据穿上“防护甲”,实现安全无忧存储。
|
存储 数据库 Python
使用HTTP POST协议将本地压缩数据发送到服务器
总的来说,使用HTTP POST协议将本地压缩数据发送到服务器是一个涉及多个步骤的过程,包括创建压缩文件,设置HTTP客户端,发送POST请求,以及服务器端的处理。虽然这个过程可能看起来复杂,但一旦你理解了每个步骤,就会变得相对简单。
556 19
|
弹性计算 NoSQL 数据库
阿里云服务器如何备份数据?
阿里云服务器数据备份有多种方法,用户可按需选择。主要方式包括:1)快照备份,创建云盘的时间点拷贝,支持定期备份与数据恢复;2)数据库备份DBS,适用于多种环境的数据库备份,涵盖本地及多云场景;3)云备份Cloud Backup,提供统一灾备平台,支持ECS整机、数据库、文件系统等全方位备份,保障数据安全。

热门文章

最新文章