00:00
都可以,OK, 好。我们现在开始啊,嗯,今天我主要介绍的就是spring batch的一个架构分析,以及我们这个no batch, 比如下一代的这个P出引擎的一个设计,因为一般的架构分析啊,或者是你在网上找到的资料呢,一般他只是在告诉你这个东西是什么,比如说SP怎么用啊,它的有哪些功能啊,它会,但是对于它为什么要这么设计,它这么设计有什么好处或者是缺点,实际上是没有一个明确的一个分析过程的。那么今天呢,我觉得,呃,相当于比较深入的分析一下spring BA它设设计上存在什么样的问题,以及我们如何通过设计来改进它这个问题。嗯,首先呢,Spring batch可能有些人不是特别熟悉啊,他因为他在那个Java生态圈呢,应该说是一个最常用的一个批处理的框架,像我们在银行业务中间呢,就经常使用这个框架来做这个日中的结算和报表的输出啊,其实现在在国内银行应该用的是还是比较多的,我接触的国外的银行用的也比较多,就是在spring这个方面。
01:11
它的起源呢,是2006年艾森哲他将自己的一个私有的批处理框架开源,然后他和spring这个公司啊,就合作开发了这个spring BA1.0啊,最早的时候呢,是相当于是从埃森哲他们做银行的那个业务来的一些需求吧,然后后期呢,应该说也是经过了多次的重构,嗯,但是从今天看来啊,因为它这个框架的历史还是比较久远的,从今天看来呢,它实际上是存在比较严重的一个设计问题的啊,对于性能规划,代码复用都是极为不友好的。那么首先来看一下spring BA器它的一个介绍啊,当初7年AI里面它有一个介绍,比如说它是一个批量处理的轻量级全面的一个框架啊,可以做数据导导出啊,迁移啊等等这样的功能,它的关键性的特性呢,是包括可重用性啊,可扩展性啊,它的因为它支持事务处理啊,然后包括一些从事啊等等这样机制,所以它是有建造性的,因为它是有所谓的生命式开发,因为他自己就提供了一个类似于DSL的一个形式吧,可以做那种声明式的配置啊,也只是病行处理啊等等。
02:24
它的核心接口呢,包括这些内容,嗯,通过这个job repository去保存它的这个批处理的任务,然后通过这个job launch呢,然后发起这个job,就相当于把它直运行起来,那每一个job呢,它是有多个step构成啊,有多个step,那么在每一个step里面处理的时候呢,它又细分为reader processor writer啊,Reader负责读取数据,然后是。Process呢,是负责处理这个数据哈,卡卡,呃,老哥喂,你这边的我可能有点不是很好,呃,网络比较卡是吧。
03:07
1。啊,稍等一下,稍等一下啊。啊,不是很好。对,你的网络好像。繁体。呃,网络不行是吧。啊对,你是在哪个地方吗?可能离WiFi有点远,或者是嗯,不是我是在家里,但是它的信号可能现在好了吗。现在好一些,大家其他人呢,其他人可以开麦说一下这个现在的情况。我现在感觉好一点。对,刚刚有点有点卡。啊,现在可以,现在好了吗?对对对,现在可以了,哎,好。呃,那么它的内容就是包含这个read process writer的。这样一个比较涉涉及到批处理方面的比较特殊的一个处理,因为我们刚才说到所谓的ETL嘛,是吧,就是你相当于读取处理,然后。
04:08
包括写入这样的一个过程,那么spring Bach的使用场景呢,包括数据同步啊,包括财务和报表的生成啊等等这样的东西,那么它的这个三个核心的接口就是相当于是批处理领域最核心的就是属于这个领域特有的概念里面,那么它抽象出来呢,就是三个接口,首先呢,一个item reader, 然后它是有相当于是它就是读,你每调它一次,它就返回一条记录,那么PRO上呢是你。传入一条记录,然后处理完之后呢,它会产生一个输出,当这个输出你可以返回now,就是这么一个process,是这样的接口,那么write呢,它是一个比较特殊,你送入一个所谓的chug chug就是一批记录,让后他给你写出。这个里面呢,它主要是相当于spring by器为了控制整个批处理过程中间的一个资源消耗,它引入了一个特定的概念,就是CH的概念,CHNK呢,就是一次性处理的数据量可以通过这个commit interval来控制,也就是说呢,比如说你要处理100万的数据,那么你的chug呢,可能配置成100或者200是吧,就100条数据,它读取、处理、写入,这样完成一个呛格,然后再继续处理下面的100条记录,这样的话呢,你的内存的消耗,你的资源的消耗等等都控制在一定的范围之内。
05:30
那么在配置上面呢,这个spring器呢,它就提供了一个所谓的类似于,这是在spring的这个xman文件里面可以配它类似于一种DSL吧,相当于是用了spring的2.0的一个配置方式,就是说你。在这里配一个job,然后底下配step,然后它底下有一个task里啊,Task里其实就是step的一种实现方式了,然后这个CHK就是这个task里里面它是用一个CHK的task里,呃,也相当于CHNK是task里的一种实现方式,那么这个task里呢,它是它底下就分为配置process和这个process呢,你可以不配,如果你不配的话,其实是走。
06:14
然后commit interval, 也就是说每100条记录它会呃提交一次,大概是这么一个概念,那么整个这个CH的处理逻辑如果是用伪代码表示的话呢,它大概是这样,就是reader,它去读取一条记录,然后读取完之后呢,紧接着就调processor去处理,处理完之后呢,它把它收集到一个outputs的集合里面,然后循环处理,这个循环的过程中呢,就是一个Chun的size啊,然后整个chunnk全部读取处理完毕之后呢,才调这个writer去写出嘛,啊,就写到这个呃,Writer里面去。这里面呢,比较关键的一个设计,其实就是这个批处理里面,它这个写出的时候是一组数据一次性写出的,因为你最终写出的时候呢,这样比较容易实现一个优化,比如说你想像数据库里插入的话,你你可能是想用JDBC的一个半系的一个机制,那么这个机制呢,它就是需要说是一批记录,一次性执行,如果你一条一条执行的话,你就没有办法利用这个JBC这个BY系的优化了,所以它这个写入的时候呢,是一次性写入了,所以你看到他这个设计比较有意思啊,它是一条,每一条数据就单独的读取,然后同时单独的处理,但是把它的那个呃输出呢,缓存起来,最后一次性的写出,当这个spring霸器的设计,从今天看来呢,它是存在很多问题的,比如说它这三个接口的设计都存在问题,那么第一个问题就是说这个load的这个reader的接口啊,它设计不合适,因为它设计是一次性。
07:51
返回一条嘛,嗯。这样的设计呢,它导致它比较难以进行批量读取的优化,所以它内部呢,是的,它所提供的reader的内部实现,往往实际上内部也是有一个小批的去处理的,按内部有个批次,但是呢,他会呃,通过记录临时变量的方式,然后相当于是他每次去读取一批数据回来放在内存里面,然后你调税的时候,他先检查内存里面有没有多余的数据,如果有的话,它返回,否则的话,他。
08:27
嗯,如果没有多余的数据,他再去真正的到底层的,呃,数据集里面再去加载数据,比如说它这个JDBC,这个配ing it reader, 它这个实现,你看它是需要一些成员变量的,就像这个current啊等等这些东西都是相当于是它的成员变量,它是需要记录在这里面的。但它就要记在自己的这个成员变量里面,这种方式呢,它使得这个read的的设计就必须持有临时状态变量,而且它使得批量优化也比较难在外面进行,嗯,比如说你现在想读出一笔一组数据之后,你再想进行一些额外的一次性做一些额外处理的话是不行的,因为你你在外面每次都是读到一条数据嘛,你就不能说我读到一批数据之后,我整个的对这批数据做一个什么处理,比如说我想对它进行一些,呃,有一些延迟加载的属性啊,我想怎么样批量的加载一下或者等等就变得很不方便,而后现在我们在not batch这个PP的框架里面,它的第一个接口的变化,就是说这个load的,它用load的去加载的时候呢,是指定Bach size加载,然后一次性返回,这个集合就是它不是一条一条的返回,而是一次性的返回,这样的话呢,相当于你。
09:51
通过这个beside的参数去告诉加载器当前你需要多少数据,它比较便于底层具根据具体的情况来判断该怎么处理。
10:03
然后呢,在原来的它的这个spring batch的这个接口里面,它是没有上下文这个参数的,没有上下文的参数呢,它是让你通过一些其他的方案去获取这个参数,这显得不是很直观啊,而你你去实现的时候呢,假如说你是通过像个listen的机制去去做的话呢,你还要单独额外去实现一个函数,就相当于你在那个read reader的那个类上面要单独实现一个ch listener的这个函函数,然后是覆盖它的这个before枪的这个方法,然后才拿到拿到枪context的,拿到context之后呢,你还要存在认识指针上面,所以这个是比较繁琐的一个过程。那么如果是基于这个列表数就load的返回列表数据的话,那你看到我们就可以用很自然很简单的方式批量加载相关的数据,比如load的,它直接调用之后返回一组数据,那么有一些相关的数据,我我就可以在load的读取到数据之后啊,我再去做一些额外的处理,因为你是一次性加载到所有的数数据嘛,所以你就很容易的做一些批量的处理的优化。
11:11
嗯,还还有一些比如说需要做批量处理的东西,比如说这个你需要加锁的时候,比如说你把这组数据取出来之后了,那你想可能加一些额外的锁的处理,那么锁的处理的时候呢,Item reader是一条一条读取的,你获取锁的时候就不太容易做这个批量优化,而且你获取锁的顺序也比较难以控制,但是如果是你是通过load的机子一次加载到一据数据,加载完之后呢,你是可以对这个记录进行某种排序的,排完序之后你再去一次性获取锁,就可以避免死锁的这种风险。当这个设计呢,从某种意义上说呢,它实际上是spring batch1.0设计的一个残余,因为1.0的时候,Bach的设计它叫做all item rented的就是它整个处理都是关注的重点,它整个设计重心是关注item的,那么它在2.0之后呢,实际上它逐步的修改为这个基于呛的概念,呛的概念实际上并不是在1.0引入的,那么呛概念是2.0引入的,走你从1.0的这个writeer的接口也可以看出来,所以它这个writeer的接口里面,因为瑞ER是一条一条读取的,它writeer这边实际上也是一条一条写出的,但是2.0的时候呢,它调整为chuner oriened了,它调整完chouted的时候,那个rier的接口并没有改,这个不知道什么原因,他可能是因为当时他已经有一些东西已经出来了,或者他觉得这样也是可以的,但从极端角度上看呢,他这个设计是不正确的,就是应该他已经调整了一。
12:44
枪UN个ien的,应该从reader就是唱个oriented的,而不是说只是writeer,是枪个oriented。那么第二个设计上的问题呢,是processor这个接口,它的设计存在问题,因为processor呢。他现在的这种处理逻辑啊,类似于函数是编程中的map函数,也就是说data.map,然后给一个函数啊,就是你给它一个输入,它产生一个输出嘛,Map部函数就是这样的,但是呢,因为我们现在说一次处理你是只产生一个输出吗?你就不能一次性产生多个输出吗?如果说你允许它产生多个输出的话,那么整个这个这个语义呢,实际上就更接近于现代的流处理框架的语义,比如像flink这样的流处理框架啊,流处理框架它的核心处理函数啊,都相当于是这个flap mapb的处理,就是你给他一个输入,它还有可能产生多条输出,当这个实际上有三种情况,就是说它没有产生输出,就相当于把这条记录忽略掉了,第二呢,它产生一个输出,然后第三种情况是产生多个输出,那么产生多个输出,实际上在原来的接口的模式下面,它它是很容易做的,就是说你稍微调。
13:59
调整一下。
14:00
像那BA里面呢,它的整体设计呢,更接近于流处理的框架,所以它的这个process函数是这么设计的,就是入口是item参数,但是输出呢有很多条,那么但是他没有选择说让它的返回类型变成一个list啊,因为这样的话,你就需要去创建这个列表,就是说有可能你实际上有很多情况,也可能你只是想返回单一的一条数据,那你创建这个列表,你不是还有一个内存消耗嘛,而且如果说是。你有多条输出的话,那么你如果是通过列表来返回的话,那你必须等所有的输出都完成了之后,你才能走下一步嘛,是吧?那么但是刘处理说的是什么呢?他是说你给我一个,呃,我现在在处理过程中,如果发现已经产生结果了,我就可以直接的通知外部,所以它这边呢,就传入了一个consumer参数,就是说你拿到这个item之后,你中间可以处理,处理产生输出了之后,你就立刻调这个consumer,那么consumer里面呢,它就可以在后面就是进行后面的后续处理了,它不用等你这个process全部处理完毕,它就可以做这样的处理了,而且这个过程呢,你看到就是说他整个这个处理过程呢,甚至可以是异步处理了,就是说你调这个process的过程中间,你可能在里面写了一个异步的线程,在什么时候你就会在另外一个线程上去调这个consumer,这都是有可能的,而且呢,如果你是这样做的,就是。
15:30
就是通过processor,然后它调process,并且传入这个consumer的,这种情况下呢,它是可以实现一个组合的,就两个processor卡,它可以组合成一个,合并成一个processor,从对外部看,它还是一个process,但是它内部呢,相当于是流处理的两步,你第一个process处理完之后,就立刻的交给第二个processor去处理,你这个前面的这个process就可以干自己的工作了,那你里面如果有一些异步处理的线程啊,或者这样的,你就可以在这个级别进行协调。
16:05
这个机制呢,其实这个这种组合呢,其实类似于模纳的的机制,就是函数是编程的里面的一些概念啊,就是说整个这个spring batch的设计,它因为它是这个相当于流处理的语义嘛,那基本上跟函数式编程的概念还是比较接近的,但是呢,Spring Bach自己的实现,它的几个核心的接口呢,不符合一般的函数是编程设计的这种,这种就是比较大家呃,现在认为是比较好的一个方案,它的设计方案呢,实际上是不是太适合做组合的。那么第三个接口spring BA里面第三个建筑writer的这个接口了,那么这个writer的接口呢,它设计的其实也是不太合适的,那么首先呢,这个writer的命名就不太合适,因为它从命名上看应该是消费数据的,但是呢,呃,很多情况下,其实我们并不一定要往外写出结果,我们仅仅是要消费数据是吧,并不一定要write,我们可能只是consume,所以now match里面它引入的接口呢,它的名字就叫consum,它只是去消费这个数据,它里边怎么处理呢?它不一定是对外写出,它有可能调别的API是吧,也可能就是说只是最终记录一些日志啊,或者一些什么样的东西。
17:19
那么这个consumer呢,它的入口参数呢,也比较简单,你想它核心的就是一个collection,这个入口的这个参数,就是不是像spring BA呢,它是一个呛NK结构,那个呛呢,结构呢还是比较复杂的。我看一下它那个枪的设计,这个是s spring BA写的这个CH的设计,你看它里面是有一个例子的,就是一组items,然后还有一些skips errors这些变量,你都不知道它什么时候设置的,其实你去看这个什么是霸写的接口设计的时候,你就会,如果你只看他这几个核心接口的话,你可能就比较疑惑,这几个接口是怎么配合起来的呢?当它处理完之后,它是怎么去创建呛格,然后怎么去做,做下一步的话,实际上都是很不明确的。但是你看我们现在把这个load的接口改造为返回list了,那么consumer接口也改造成直接返回接受collection了,那么这两个接口的这个配合关系实际上是很明确的,就load出来之后,直接就可以送到consumerum里面去处理了,是吧?所以你可以感觉到这个整个处理过程是非常直观的,通过接口上就可以看出来它是比较直观的。那么PRO3摄是怎么处理呢?Processor相当于是一种可选的这种consumer的一种机制,就是说如果你配置了processor,那么它就相当于对这个记录。
18:36
或每一条都处理一下,同时去收集一下这个outp,然后再送到下一个consumer中处理是吧,相当于consumer也是可以复合的,Processor只是一种特殊的consumer,然后它处理完之后交给下一个consumer进行处理,而且当这里面呢,就是说是呃,在no best实现里面,这个process是允许异步执行的,这边这个做法呢,它实际上是一个简化,呃,真正去执行的时候,如果是异步执行的,这边是相当于一个并发的一个结构,然后它并发了,处理完完之后再送到下一个层报中去处理的。
19:15
所以我们看到在这个now be的这个呃接口里面呢,我们看一下它三个接口。他们之间的这种配合关系啊,是比较明确的,比较直观的,Load的取出来的类型可以直接匹配consumer的入餐类型,而且他们三个很明确,就是说三个之间如何去共享信息呢?因为他们实际上是在一个check个里面要执行这三个动作嘛,那么他们就通过一个共同的上下文来共享信息是吧?但如果你去看spring霸TCH的那个接口呢,你可能就很疑惑,你不知道他们之间是怎么样共享信息的是吧,他们怎么样传递参数呢?所以这个接口,它的那个接口的设计呢,就是不是很合适,那么我们说呢,一个良好的架构设计应该是可以通过它的函数签名,实际上也就是它的类型定义来看出它的内在的组织方式,这个类型的东西相互匹配啊,那这个就能送给那个就是下面的东西下,他们就可以进行这个组合式的一种调用,所以在这个基本的接口的这种定义规范下面,嗯,是需要对spring Bach的设计做一定的改造的。
20:22
当时办它的设计呢,还有很多其他的,你真正去用它的时候,你会觉得它,嗯,实际上很多东西是不方便的,比如说它的事务处理,它的事务处理呢,就是一个强制限定的一个情况,它就是限定了整个check是在一个事务里执行的,就是read process right, 但是我们在那平台里面呢,因为业务实体一般情况下它都是有乐观锁字段的,所以呢,我们可以只在right阶段的时候打开事故,就是你一。读取处理的过程中间,你可以不打开事物的,你这样的话可以缩小事物的范围,减少数据库连接时的占用时间,但是在spring办里面这就做不了了,嗯,在这个像我们现在在RA阶段才打开事务,说还有什么好处呢,因为process呢,它在事务之外执行呢,那么process是process,一般实际上是业务处理嘛,如果业务处理失败的时候,在我们整个框架下面是没有任何数据库层面的回滚的,就是实际上在process阶段你并没有写库,你也不会涉及到数数数据库的roll back, 那么在整个这方面呢,实际上也是减少了数据库的压力,也减少数据库层面的一个所叫竞争啊。
21:33
在那半里面呢,我们是引入一个全蓝性scope的一个配置,就是说你可以配置它是在整个这个check处理这个阶段都打开事物,还是说从process到consum阶段打开事物,还是说只有consum阶段打开事物,就是实际上是分不同的阶段了,在不同你可以通过自己的方式呃,自己的需要进行配置,不像spring,它是一个写死的一个情况。另外在失败重试方面呢,Spring Bach呢,他的领那个强调了他自己的一个失败重试的逻辑,但是他的失败重试呢,是在processor级别,大部分情况是在processor级别进行处理的,但是processor级别真正在做业务处理的时候,并没有完成所有的业务逻辑,比如说我们可能说插库的时候,你实际去写到就是在write特里面写数据库的时候,最终发现,哦,数据库可能因为有其他的线程,或者是比如说你正在是一个跑批的任务,那你前端可能还有在线的任务呢,你在线的任务跟跑批的任务两个同时进行,可能会有一些竞争关系,导致一些冲突,那么这个时候呢,你重试应该是说你整个过程啊重试,而不是仅仅单单的process重试,那你写入的时候失败了,难道你就不能重试吗?实际上也能是可以重试的,那么now里面呢,我们提供的这个重试这个机制呢,是针对整个呛格的,整个呛失败了之后呢,会自动重试整个呛格。
22:58
而且重试的时候是这样的,它有一个比较灵活的选择,你可以选择逐条重试,比如说一个check,它为了批处理嘛,它是100条处理,然后100条提交,但是因为你100条提交嘛,你有你的跟前台,比如说你前端的在线交易那些任务之间啊,就冲突可能性就会比较大,或者说某一条交易的某一条记录,它可能对应的有些数据就是有问题,你再去重试,它也是有问题的,但我们重试的时候呢,让他一条一条去重试,就是不是说整个恰骨头上是每一条去重试,那么这个时候如果只是某一条有冲突的话,那么这一条就会可以被隔离出来,那么这样的话,你比如说你100条里面可能只有两条是有问题的,那你98条还是可以提交成功的,所以他在重事的时候呢,就跟spring BA是不一样,他从事的时候力度是可以,你可以选择性的让他进行那个单条记录的这种重试的。
23:55
那么具体的处理逻辑呢,也是很简单,就是一开始你去你是批量可嘛,如果你批量可能那么失败了,那么有一些你可能中间已经知道你确定成功了,你可以删了,剩下的你可以re RET try conse RET try cons的时候,如果你选择了one by one cons的话,那么它就可以把这个里面的这种记录就是一条一条的进行这种处理。
24:26
然后重试的时候呢,当然你也可以配置这个重试的这种等待的时间啊,从事reard delay.这都是通过策略可以进行处理的。呃,上面呢,基本上是在spring BA现在已有的架构下,我们说调整一下它的一些接口的一些基本的方式,就是在它的接口上,呃,在它的接口层面啊,做一些签名上的调整,这可以做到,但是下面呢讲介绍呢,就是涉及到一些更深刻的一些架构层面的调整啊,这个就相当于是跟spring Bach的架构有比较本质性的区别了,因为spring BA里面呢,它有一个情况就是说它是怎么样去监听,它里面有一叫事件监听的机制,用来实现在这个整个任务处理的生命周期这个过程中间去获取一些回调的时机,比如说我们这个MY啊,他你要写一个processor是吧,你这个processing呢,可能是说我在整个这个step,整个这个任务的这个步骤开始的时候,我要做一些初始化的工作,那么是步骤结束的时候,做一些那个收尾的工作,那么这个时候呢,他的这种推荐的做法呢,相当于是。
25:39
在你这个process上面去实现一个这个execution listen的接口,那么你实际上这个接口,那么它就会在这个时刻去调用你这个接口。当这个回调函数。那么这个地方它就会产生一个问题,首先呢,因为整个spring白起它的病都是用spring容器来管理的,那么一种容器来管理这些问这个病的话,如果考虑到并发执行的话,你想一下这个病就不能是一个single的了,因为每一个步骤,这个步骤有可能有同时有好几个这个实例在执行,那么每一个步骤这你去。
26:18
调用这个,呃,就比方step之后有一些处理的临时变量,你可能想保存到这个Z帧啊,那么这个C并呢,它就需要设定设置这个scope=step,相当于在那个steppe级别,它是相当于有一个这样的这个实例,就是说它不是全局的SCO的实例了,但是呢,Spring bus这个step scope的这个实现呢,它是利用spring内置的一个scope的机制,它实际上呢,非常的tricky,导致你要要开启全局的开关,实际上这个也是一个bug,这个bug实际上在那个spring BA那边提了很久,际上最终一直没有解决,这个问题可能源自于spring容器自己,就LG容器自身设计上的一些缺陷,就是当你想让这个processor让它在成为一个stas gogo的病的时候,必须要允许这个病的重写,实际上这个开关它缺审认为现在认为是缺省,应该是false的,它不推荐你这样,但是用到spring半血的时候,你还要打开,就导致。
27:18
实际上是概念什么样,实际上是一个自相矛盾的一个做法。另外呢,如果你对你现在通过这种就是实现listen森的接口的方案,它还有一个什么不好的地方呢?如果你对它进行了分装,那么这些listen呢,它的上面就没有办法自动的被spring BA框架发现了,比如说我现在要写出两个writer,就是我写出的时候,实际上是写到两个write里面,写到两个write里面,它的做法呢,就提供了一个叫comp还什个write的东西,你会把两个write包装一下嘛,包装成一一个writer,但是你包装成一个writer之后,这两个writer上面的listen呢,就没有办法被spring框架,就是BY起这个框架发现了,因为你这边注册的writer,这个com的writer上面是没有这些listen的。
28:08
所以他们不知道这些事情了,那怎么办呢?他说你还要单独的去想办法去告诉spring啊,Spring啊,你这些东西是要支持额外的接口的,所以这其实也是某种相当于是不太合适的一种方案是吧,就是说你本来已经写好了,说我需要有一些事件监听,但你稍微一照包装,那么你这个自发现的机制就被就被那个破坏了,那你还要额外的去告诉他。当然这个地方呢,我们如果跟前端的框架做一个对比的话,会发现一件非常有趣的事情,就是spring batch的这个做法跟前端的这种class component的做法是如出一辙的,比如说你在前端写一个组件叫my componentant, 那你可能是从一个,比如说你用是voe组件的话,你从voe这个对象集成,如果你是react组件的话,你从react component中集成了,然后你在这个组件上面写一些函数,叫mounted updated这样的函数,这些函数就是相当于生命周期函数嘛,所谓的生命周期函数,也就是说在框架在一定的时候,它会调用你这些函数是吧?那你想象一下跟你这个。
29:19
这个这个before step after step其实是一模一样的吧,整个整体情况实际上是一模一样的,而且你的你看这个组件。前端组件里面真正核心的就是这个random函数嘛,那你的processor这个机制里面,你真正重要的就是process那个函数嘛,是吧,剩下的before step up step跟这个生命周期也是一样的,它只是一个回调函数。所以整体处理方案其实都是类似的,就是早期在面向对象的整个架构下面,这实际上也是一个比较传统的一个,或者说大家都在使用的一个方案吧。但是前端领域很有意思,后来他出现一个革命性的进展,就是引入了所谓的hos机制,抛弃了class based的这种组建方案,然后当然在我的一篇文章中,实际上也有介绍,就是red hook return, 本质啊,在hooks的方案下面,这个前端组件,它不再是一个class,或者不,不再说是一个这样的一个对象了,它变成了一个响应式的run的函数。
30:18
嗯,在那个vuee的情况下,我们是跟vuee对比一下,Vuee它在它所提供的vuee3.0的设计里面,它是这样的,它它的组件就相当于是一个setup函数,那么在这个函数里面呢,你可以调这些hook,就是UN mounted这样的hook说我要监听到a mounted的事件上面,然后最后你return一个函数,也就是塞他们呢,相当于一个工厂啊,这个工厂里面呢,进来的时候呢,这些东西是调用一次的,它只处理一次,然后呢,它会返回一个这个random的函数,这个random的函数呢,它是就是响应式更新了,就当它所依赖的数据发生变化的时候,这个random的就被不停的调用,相当于是setup,它被调用一次,访问一个rener,这个rener呢,有可能会被调用很多次。
31:07
当这里面这个方案呢,相当相比原来的类结构,它是有很多的好处呢,第一个是第一个好处是什么地方呢?就是这个事件间定函数,你看它现在是独立于类结构并被定义的,所以它很容易实现二次分装,你看比如说上面,你看上面就啊,比如说我需要在UN mounted的和on update dated的这两个函数里面都要做一定的事情,那我现在就可以写一个函数,把它分装起来,也写到别的地方去了啊原先呢,你看这个函数啊,就是如果这两个函数你发现经常复用的话,你完全可以写个函数把它封装起来,然后就是可以下次用的时候直接引用一个函数,调用一下,相当于里面就调了a money和AA,但是如果是你基于class based的class based了,那你怎么去复用这个类的这一个片段呢?就你这个函数是依附于这个类的是吧,那你怎么说我从这个类上截取一个小部分去拿出去复用呢,当前端。
32:08
看起前的复用方式有一个mixing的方式,相当于你把一些东西拿出来再进行mixing合并,但是mixing合并的时候呢,它实际上一般情况下都是直接的覆盖关系,那你就算是把mounted和updated拿走了,你mixing进来,你有可能会覆盖掉已有的mounted和updated是吧,这会产生冲突的。所以Mixing呢,它不是一个,就是真正比较组合性很强的一个方案,而这个如果你是采用HX机制的时候,那你这个事件年定函数就可以独立的拿去采用传统的这种函数复用的方案实现组合了,所以这个是很不一样的地方。第二件事情呢,就是这个事件监听函数呢,它是可以通过B包来传递信息的,它不需要像类组件一样把这个东西先放到Z指针上,然后我再从指身上读取,而是说你在这个地方,比如说我这个地方写了一个变量。
33:03
是吧是吧,比如说X=3是吧,那么你在这里面就可以用,然后这个on oned里面也可以用是吧,Before, 呃,这个on before command里面也可以用,就是说他们是可以通过这种函数B包很自然的就看到相关的数据,是吧,那它,而且它可以利用函数的这种scope,就是变量scope,你可以控制它啊,每一个函数能看见的哪些数据怎么怎么样啊,传统的这种类的方式呢,你也不太容易控制这个可见性的范围,呃,而且你还要通过这个指针迂回迂回。第三点呢,就是说它这边传入这个参数之后呢,它可以动态的决定是不是注册这种组件的监听器啊,你它可以说if到某个条件的时候啊,某个PRO参数的时候我才去注册,否则我就不注册,实际上你想到这样,实际上也是减少了这个运行时的一些呃,这种压力嘛,就是说他的资源的消耗都有可能减少是吧。
34:01
所以这里面的一个关键性的架构变化是什么呢?它就是说不再把事件监听函数啊,从属于这个组件了,这个这个虽然说你要监听一些东西,但是这个监听呢,你是和全局的这个上园文环境发生那个相互作用,在全局的动态事件注射机制上发生作用,而不是说是一个。就是说是属于从属于某一个组件的这样的方式,所以这是一个很关键性的一个架构的变化,那么在前端的整体架构呢,已经发生了这种变化,但在后端整个这个社区里面,面向对象的社区里面并没有意识到这样的一种方式,他会更加的就是处理问题会更加的方便,所以现在还没有看到很多类似的一种设计,但是呢,嗯,你如果去仔细去想一下的话,这个方案在Java里面实际上也是很容易实现的,而且它也更加自然,所以我们现在在那办洗的这种处理里面呢,它的核心抽象是这样的,就是不是。
35:05
Bych load的这种运行组件,它是这种运行式组件,而是一种Bach load的provide这样做工厂组件,那么工厂组件呢,它提供一个setup函数,我起名字叫setup呢,就是为了和这个voe的这个东西啊,保持概念上的一致,让你看的比较更加清楚一些,就是说。你。定义一个这个load provider, 它是一个single,就是全局的可以用spring容器去配置的东西,那么他发起这个批处理任务的时候,它会调用这个setup,那么通过setup返回一个load来后,进行后面这个load,你看这个provide是一个single的东西,Setup函数只被调用一次,就一次任务它只调用一次,返回一个load的,然后load的呢,就会被调用很多次,就类似于VOOE组件的三量被调用一次,返回一个renderra,那么renderer就会被调用多次,所以这个整体适用的设计的逻辑啊是就是非常1,就是与这个主店的这个HX机制啊是非常接近的。
36:08
而且你看到他这边去处理的时候呢。我们现在的做法呢,是通过这个context就传入这个context的嘛,这个context上面就提供了一些方法,就比如说on,什么after complete呀,什么这些就是对这样的一些,就是函数这个事件监听的这种注射函数啊,是写在这个上下文上的,就是写就基于这个上下文,你拿着这个上下文环境就可以注册一样的事件,它就不是说是写在组件上,然后期待这个框架去发现,那整体这个处理逻辑就不愿就不一样了,是你主动的向框架注册,而不是框架去发现啊。那你注册的时候就很灵活了是吧,你可以决定,你可以动态的决定很多事情。而且上面的做,我们上面的做法呢,相当于是你要是拿到这个contact的去处理嘛,是吧,你拿到这个contact对象,然后调它上面的方法,那么如果我们想做进一步的分装,让它和这个hooks机制看的更接近的,你是可以通过一个spreadre local把你这个task contact存起来啊,它就是和这个上下文变得就是它成为一个全局的一个东西了,类似于全局的东西,然后你通过这个provide,然后把你的上下文放到这个线程上下文上,然后你就可以通过这个一个静态的函数,然后on task and这样的函数去负责去监听了,所以这样其实这个实现原理和前端的这个组件机制也是非常接近的,因为前端呢,它那个hooks,它所谓的比如说是一个on mountained的这个hook,它具体执行的时候也是去找上下文里面的一个上下文对象,然后在里面去注册的,只不过那上下文一样的,它是一个全球,像你没有去明确的从编程角度去看见嘛,那么现在呢,你是通过。
37:56
拍的logo去去处理了,实际上也是相当于一个全局的嘛,那么通过这种方式,如果你做的上面的这个分装的话,那么整个的做法就会和这个hox机制就是一模一样是吧,你看到你是调用一个全局的函数,然后通过它去注册你的事件监听,而且呢,你也可以把这些函数封装起来,对吧,封装到一个别的函数里面去,然后在很多地方就调用去复用啊,所以它这个做法是非常接近的。
38:24
当然这里面就顺便提一句啊,虽然hooks是react的发明啊,但是vouee呢,它要选择是将这个逻辑啊分解为setup和这个run的两个阶段,这个设计呢成实际上是一个更加自然的一个选择,因为否则的话,你想象一下嘛,那你实际上是要区分你是setup的时候,还是说已经set up过的时候,在react的组件里面,它随时都在做这种区分,实际上是在性能上是有一些问题的,而且在很多比较微妙的地方,它是存在的一些,呃,就是设计上的容易出现混乱的一些地方啊,所以实际上是有一些问题的,就是比较理想的情况下呢,还是应该分裂成两个函数,一个setup,然后返回一个东西,然后再去处理它,这个两阶段是比较自然的一个选择。
39:12
那么另外一个我们在nowt里面做了一个架构的一个重大的变化呢,就是说使用通用的task flow来组织逻辑流,因为这个spring BA呢,它是一种。它提供了一个逻辑流模型,因为包括嗯,他的XM里面,比如说它是可以做这种步骤的编排的,像下面这个事例,它就是说我要做一个并行处理,那么这个步骤和这个步骤是并行的,那么这个步骤里面呢,它又是一个串行的,相当于他每个去执行,执行完走下一步啊等等,它是可以对这个步骤进行一定的组织的,而且它这个组织呢,是依赖一个核心的接口,就要task里的接口,那么我们前面介绍的这个CHNK的处理呢,实际上是taskli的一个特殊的实现,就枪框re taskli了。
40:02
这个task里的接口呢,是很有意思的,因为它是在2.0的时候引入的,1.0的时候是并没有这个抽象,那么实际上也就反映出spring bit它整体设计的时候呢,是它的抽象程度整体是先天不足的,因为它这个例子它为什么要抽象出来呢?就是说抽象出来之后,你的步骤就可以变得相对通用化了,就原先你的步骤就只能处理呛课是吧,那如果你不是呛课的逻辑,你你在你的这个框架里面怎么描,怎么表达呢?它不是很好表达的,那么现在单独提出一个task里的接口来,相当于也就是一个步骤的执行是吧,那么这个步骤里面就会调用到这个task里接口,那你在步骤里面想做什么都可以了,那实际上SP spring, 它如果只使用task里的接口的话,它也可以看作是一个呃,相当于是逻辑编排的引一个引擎啊,只不过这个这个引擎的可重用性和可扩展性都是很差的啊,它。只适用于它自己的,就spring bit的这个场景,就你如果想把它当一个通用的逻辑的引擎来用的话,你会发现都很不方便,它所有的接口呢,在很多程度上都是和这个嵌的处理啊,或者这个整个主人霸写的处理进行绑定的,你想把它的某一个部分拿走,拿到外面去用,这是不可能的。实际上之前的反映出就是很多人他做这个抽象的时候呢,他的抽象不是分层次的,就是说他要去做一个场景的东西啊,他的设计都是跟这个场景绑定的,比如说现在有很多人他去做流辑周编排,嗯,他是做这个API级别的逻辑周编排,那么他的脑子里面所有想的,所有的设计都是跟API有关系的,就是说他编排的是远程的的服务或者什么服务,你想让他编排一个别的东西,那实际上他的可能在性能上有问题,也可能在其他的设计方面不方便是吧,你想让它变成一个编排普通的加va函数,我什么都不用变,我就根号函数你就可以,是不是可以编排,但是他实际上实现不了,做不到是。
41:59
是吧,它中间就会有很多很多的问题,就是说它是针对特定的场景,然后把很多这个特定场景的假定都引入到它的上业环境对象里面,引入它的核心接口里面,然后绑定在某种情况下,但实际上有很多的设计,我们应该是分层次设计的,是吧?逻辑流编排逻辑流逻辑就是整个步骤级别的逻辑吧,就是第一步,第二步,第三步是吧?这种逻辑跟你步骤里面说哦,我要先读取,然后再处理,这其实是两个逻辑,这两个逻辑并不是说非要混杂在一起的,你是可以把它们分开的啊。
42:33
所以这个我们在这里面呢,就是提供了一个实际上是复用,我上次介绍的这个nob task flow的这个设计,就是nob task flow, 它本身可以作为一个通用,作为一个通用的逻辑引擎里面来用,就是任何一个地方,你原来是写函数实验的,假如任何一个。地方实际像它如果是一个函数是吧,你你的抽象的最小单位就是函数嘛,那么这个函数都可以被替换为一个逻辑编排的任务啊,都是可以的,那么整个这个no note task flu呢,它就相当于是对函数的一种结构化啊,对函数的进一步的抽象,对它的一种编排设计,就任何你调函数的地方都可以用它来做,那么嗯,这个呢,我们现在里面呢,也是提供了一些比较,那个就相当于是方便的一些配置方式,比如说你要并行处理,并行处理它就单独有一个步骤叫做parallel,这个并行处理,那么并行处理里面它有多个step嘛,那么这个部分就是相对于并行的一个部分,然后这个是并行的第二个部分,然后这个水宽项呢,它就是顺序执行,就是这里面的东西是顺序执行的。
43:42
那么这边呢,Next on error, 就是当你执行失败的时候,你跳到下面一步啊,什么什么这些它里面都提供好了,而且呢。我们不仅仅是说你对这个步骤进行编排了,因为上次也就专门说过了,你只是说对函数进行抽象的话,这个抽象其实你真正在做编排的时候是不是很方便的,因为它有很多通用的功能啊,你会反复的需要配置,那你不如说让他们变成一种类似于Java注解的东西,就是个瑞ator,但你每一个步骤都可以具有这些知识能力,比如说你的这个步骤是不是要有,有没有超时限制啊,那么是有有问题的时候,你是不是要重试啊,比如说我们这个重试,你在步骤级别的重试,就可以通过这个nobop flow里面的这个re try这个配置来做了,比如说你现在要在这个事物环境里面执行,比如说你看你现在有两个步骤,这两个步骤要在一个事物里面,那么在spring Bach里面,他想做这件事情,实际上是做不到的,他没有办法说这两个步骤是在一个受理执行的,他没有这种机制,有很多的逻辑流编排引擎,实际上他也做不到这一点,就是说嗯,我不是整体上怎么怎么样啊。
44:51
然后我其中有某一个部分啊,某一个局部,我需要把它们放在一个事物里,然后怎么怎么样,这个都是现在的很多设计都做不到的,因为它整体设计里面呢,没有对于就是他已经假定了某种情况了,那么这个假定呢,它不是一个通用的假定,就是直接阻碍了他去做很多其他的一些设计给他插进来,而且他也没有预留,比如说像这种机制啊,包括他的嵌套的逻辑啊,都没有这种预留的空间。
45:22
嗯,然后呢,我们这边这个not task里面呢,它的步骤之间的关系呢,相当于是个堆栈,就简单的说呢,跟函数的嵌套调用是非常接近的,而且中间呢,还提供了一些非常强大的这样插模板引擎的这种调用方式,比如说你这个地方直接插距插入一段这个插P模板脚本对吧,这里面是一个类似于个XM的一个脚本语言了,呃,就是插这个相当于是一个模板语言吧,当然你也可以直接写Java,就是如果说你需要写Java,就这上script节点上面写line,等于Java这里面呢,它就用JA的那个框架,它做的种Java的这种语言,你可以直接嵌入里面,当然你也可以扩展使用什么啊,Python啊什么的都可以,你相当于你有一个script节点,这里面可以执行script。
46:11
那么touch flow呢,它的核心的抽象呢,就是一个所谓的I task step, 那么这里面呢,它就包含影input这个outut,就是输入输出的一个变量,那么执行的时候呢,它会呃支持取消,像一般的你像很多的这种逻辑编排引擎,它实际上没有内置取消机制的,就是它只是去执行是吧,那你能不能说执行到一半的时候我取消呢?而且我取消的时候,如果我是嵌套的,就是我这个步骤执行下一个步骤我又调用其他的东西了,那其他的东西能不能取消呢?就是间接的把这个取消逻辑,就是触发到它的子步骤里边的一些取消呢?这个都是没有保证的,而整个闹平台里面,它所有的设计都是会呃支持这个取消逻辑的这种嵌套,比如说你整个task flow取消了它,然后它会把这个取消逻辑送到那个task step里边,比如说你step里面正在跑一个半棋任务啊,你半棋正在处理,你正在处理100万条记录呢,是吧,然后这个时候一个取消的信号到到你这儿来了,那你这个中间就可以暂停,就取消出来了,所以它。
47:11
它中间的逻辑啊,就是呃,比较完整的做一个逻辑流的设计,中间是有很多要考虑的内容的,一般的逻辑流实际上是没有这么丰富的功能。那么我们这个步骤里面呢,实际上它跟这个就是整个这个程序员是非常接近的,你比如说这个影铺的,你看到这个影铺呢,相当于如果我这我这个地方定义了一个sta嘛,然后定义了它的输入变量是A和B,那么相当于它有两个参数是AB,然后它的输出呢,是一个result的那类型的,那我们就这样,然后对应的代码相当于是A+B嘛,所以它整体STEP1的定义就相当于是这个,然后这个地方呢,你写了之后呢,不仅仅相当于定义啊,它还是代表了执行,那么执行的时候相当于是什么呢?第这个影input的里面,这个A的参数是X+1嘛,所以相当于是给这个参数A送X+1,然后给它送这个YY这个B的参数是Y+2是吧,这然后最后返回这个道的这个变量是吧,这是这么这么一个情况,那么这个整体处理啊,是和这个Java程序员非常接近的,呃,相当于这个task任何一个task的,就是我们整个task flow里面的step呢,它的入口。
48:23
补参数就相当于是个map是吧,每个编码都有名字,是返回的结果呢,也是一个map,每个编码都有名字,这样你比较容易进行编排,如果它没有名字,实际上你要去处理的时候是比较麻烦的。那么在整个那Bach里面呢,它也是做了这个分区并行处理的一个特殊的设计,就是spring BA呢,它是支持分区的,呃,但它这个分区呢,是有很大的限制,比如说在spring BA里面它可以做。就是允许多个线程同时去处理,呃,就是并行处理一件事情,而且每个线程只处理这个数据的一部分,他怎么做呢?它首先是通过一个master step里面配一个part提性的,通过帕提性呢,先把是那个数据分成几份,比如说你是100份数据是吧,那你你呃,就是总共你的数据总量是100是吧,有100个数据,那么你现在有5个线程去处理,那么它就是每个线程处理20分啊,就处理20个数据是吧?那么去处理的时候呢,它就是相当于是通过帕西线的把它分成5份,分成5份之后呢,他把这个就是就是你在pass新呢里面就会设置一些分区的参数,然后调这个slave step的时候呢,你的这个reader就可以读取到这个step的参数,那你就知道啊,你这个ER就只读取其中的1/5的数据,然后再去调process,再调right嘛,那么这种并行处理呢。
49:50
相当于从reader开始就进行分区的读取了,然后每一个slave step呢,都使用专属于他自己的reader去读取数据,然后再做处理,那么如果一个数据一个分区的数据特别的多,那么其他的县城就是说如果事情都已经干完了,就有一个县城没有干完的话,其他的县城是没有办法去帮他的,因为他这个力度就是一开始就分好了,就已经分成5份了,后面就不可能再做工作的这种交接啊,这些办就是互相的这种转换啊,就就做不了了。
50:23
但是我们实际的业务中间呢,我们往往存在了很多更细力度的分区的可能性,比如说我们做银行业务的话,我们是对每个账户去做处理,每一个账户的数据呢,要求是顺序处理的,就是它必须分到一个分区里面,但是不同的账户呢,是可以并行处理的,那么讲,那实际上因为你的账户总数是很多的嘛,所以你理论上的并行性是很高的,闹平台呢,它就是这个no be呢,它就提供了一个呃,特殊的设计,那我们可以去让多一个线程,嗯,去处理的时候呢,可以共享这个其中的这种任务队列工作,首先呢,我们是设计了一个concuent的参数,就是说呢,你的这个枪contact里面,它知道当前有多少个线程在执行,就是你的concuent,就是当前有多少个并行的处理的线程,这段时间w index告诉你,你是这个线程里面的第几个,比如说5个线程里面你是第三个,这样的话呢,你。
51:23
去送到RA里面的时候。这个read的他就知道你当前是在哪个线程上去访问它了,那么有可能它就会优先返某个线程的数据给你,但是因为我们整理处理的时候与这个spring BA不同,Spring BA它是分区之后,这个reader process和writer是专门针对那个分区的now BA里面呢,它在架构上做了调整,那么这个架构调整之后呢,这个read的process和writer都是共享的,就是说它会起5个线程去处理,这5个线程会调用同样的reader和processor和writer,它并不是说调用5个不同的reader processor writer, 它是同一个,所以你在这个reader process writer里面,你如果需要共享数据或者做某种任务共享的话,你是可以自己想办法去做的,它与这个spring这种严格的隔离是不一样的,而且我们内部,呃,就是内次的一个叫partition dispa load provider的一个机制,那么这个机制是怎么做的?
52:26
它是这样的,就是你读取到数据之后,那么我这边呢,就把它派发到一个队列里面去,就是根据你的帕提新index,你可以告诉他一个帕提新index,然后他把它派发到每一个帕提新index,就相当于是一个小的队列啊,就变成一个微队列,就派发到一个微队列里面去,当这个part inexx不要求在原始的数据上就有,你可以把这个数据读出来之后,然后在一个afterlow afterload的这个阶段,动态的计算出一个帕提新inex,然后再派发到这个这个,然后通过dispat啊派发到这个微队列里面,我们现在的做法实际上这样,就是那你帕应dex要求,你计算出来是一个整数嘛,然后他把它会把它放,呃,就是划归到0~3535,就是3535这个这个区间范围内,相当于是个效的一个一个范围之内,然后每一个part index就给你放到一个小。
53:26
小的队列里面这一个part index的数据呢,必须顺序执行,但是呢,你的reader去加载的时候呢,它会从所有的这个这个这个小的队列里面去取,比如说先从第一个队列取,取了如果没有数据的话,它就从第二个队列取,然后取到比如说BA size100条数据,然后它就返回了,那么第二个进来再去到这个小的微队列里面去啊,确保每一个队列当前只有一个线程在处理,但是你因为微店类的数量是很多的是吧,因为它是好几万个,那么。
54:01
你的线程数呢,也一般有几十到几百个嘛,所以它之间它的并行性就相当于相比这个县城级别的并行要提升很多。当然如果你确实需要spring Bach的这种步骤级别的运行处理的话呢,你可以直接使用no task flow的这个fog这种配置,就是说它有一个专门的一个一个步骤就叫fog,你通过producer可以返回说你要fog,呃,你的有几份数据是吧,然后你。这里面的你你比如说这个就变这这个fok,就相当于把自己就变成两个数据了,每一个里面呢,都会有一个这样的变量是吧,就就它叫A点什么DAT啊,什么这个名字,变量名就叫file name, 那么在这个你,然后你在这个执行这个fo的这个子步骤的时候呢,那么它读取到的这个方name就不一样,比如说在第一个实例里面,它读到就a.daatt是吧,第二个实例里面就是b.daatt了,而且呢,当你所有的FOX步骤执行完毕之后呢,你还可以执行一个可选的汇总动作啊,把你放的结果啊,怎统一,怎么再去加总一下,然后再反馈啊等等这样的一个情况。
55:14
另外还有一种情况,如果是使用这个fog的这种机制的话,我们也可以直接在这个这个reader上面配这个part提ing index field, 你配了part提in index field, 然后你整个起你by task by task contact的时候,你它有一个参数叫part提in range, 你直接设上去,你直接上去之后,这边这个reader去读取的时候,它就从它context里面取到这个数据了,然后它就自己知道要取其中的一部分,所以它生成的scle语句呢,就相当于自动的会追加这个part index的这个呃相当于过滤条件是,所以这个用起来也是很方便的,就比你使用这个spring的这个grade,就做它这个party性处理啊,一个是更加灵活,而且你呃可以做工作共享等等。
56:03
当下面我介绍的部分呢,也是这个闹be里面非常非常特殊,也整个闹平台里面非常特殊的一个部分,就是所谓DSL森林的概念,呃,因为整个那平台它所做的设计就叫做下一代的这种低代码的这种平台,那么它里面的这个任务引擎呢,叫下一代的任务,呃,逻辑编板引擎,它的P速理引擎呢,也是叫下一代的P处理引擎是吧?那么它这个所谓的下一代呢,就在于说是它整体的设计是呃。基于可能计算理论跟传统的这种框架完全不一样的,这里面一个非常明显的一种表征,就是说它是使用DL来做了,而且呢,它的DSL呢是构成所谓的DL森的概念,这个DL森呢,就是说首先它有很多的D是吧,比如我们这个编,还有那批注里那不要写,那么批注里面如果要读写文件,我们有nob record, 那么如果你要读取数据库,有NOBOM,那么你是有很多的DSL,同时呢,这些DSL之间呢,还是无缝集成在一起的,你可以把它们看作是一个整体啊,所以这个跟传统的东西是非常非常不一样的,那么下面就介绍这个内容,比如说我们看spring BA, 虽然他号称自己是声明式开发啊,但是他的这个声明式呢,是靠这个。
57:22
病的组装描述就是在spring的XM文件里面进行描述的,那么这个描述呢,首先是不充分的,因为它那个描述啊,它受spring by写的,呃,Spring这个IC的一些限制,你很多东西不是很好在里面表达,而且你表达了之后呢,你很多东西它现在实际上是写在加入代码里面呢,就是说他的DSL不是一个完整的一个逻辑的表达,就所有关于这个,比如说批处理的东西,是不是一眼看过去看这个DSL都明白呢?不是的,你可能还要在很多地方去找Java代码才去看,才知道是怎么回事儿,是吧,它并不是一个完整的能够实现细力度的声明,是开发的一个批处理的模型,它只是其中的一小部分,而且呢,Spring Bach它。
58:07
移出的这个模型呢,它也很难保证它的可扩展性,就是说如果你你真的假如说spring白不是现现在的设计啊,就是现在的设计是说把有一些逻辑相当于交给那个并去代理了,交给你写的JAVA0,就是如果说你spring办真的说我提出一些抽象,呃,我一般情况下不写Java代码了,我完全通过我电上来配置,那么感觉上好像又不能穷尽所有的需求是吧,因为呢,你做了一个DL,有可能就限制死了你未来的这种发展空间了,你今天做这个DSL,未来你想困难,你怎么困难呢?就感觉上好像基于spring BA, 如果你想做一个DSL,呃,实际上也是很难的,你难以保证它的扩展性是吧,那你是不是相当于像现在这种半调子的DSL是最佳的选择呢?那平台呢,他的回答就不是,他是提出了一种,呃,通过原编程的方案来做的这种DS方案啊。他可以说你。
59:04
既提供非常细致的这种抽象,同时呢又不限制你未来的扩展性啊,这中间是可以同时达到这两个设计目标的,那么它的具体的做法呢,是跟这个spring的做法是非常非常不一样的。比如说我们看一个具体的例子,比如在文件解析方面,文件解析方面呢,Spring BA是没有提供DSL,就是严格的DL支持的,那么它可以在这个就是spring LC这个容器里面去配,但配的时候呢,是使用这个spring的配置语法去配置说这个文件的,比如说前四个字节是干什么的,后四个字节是干什么的,然后它对应的字段是什么,你看这个从某种意义上看起来呢,好像也是一种类似于这种描述吧,但是这个描述呢,肯定是非常非常的。
60:00
就是不优化了是吧,你看到这个这个东西你也能勉强的说它是一种声明式的东西,但是这种声明式是非常臃肿的,非常不容易去理解,而且你你呃,就是你去去分析它或者什么都是不方便的,而且你是配的是不是对,也不是,也不是很简单可以看出来嘛,那么在那平台里面,我们说一个真正的DSL,它如果说是想描述说我这个文件怎么解析,那么它就是有一个特意特意定制的一个DSL是吧,比如说你看我们在这个任务文件里面,我们定义了一个段叫record冒号file model, 然后他说这个格式就是这是一个文件格式嘛,这个文件格式的名字我们就定义为这个simple file, 然后他说他就有几个字段是吧,要name字段,它的类型是什么,它占了几个长度,然后它的编解码器是什么,他用什么编解码器进行读写的,像这样是一个DSL,你这个DS你会看到它的表述是非常精炼的,就所有的概念没有像额外的东西,就像你看上面这个表达,它是。
61:00
非常勇于的是吧,像这个什么property,什么construct,这个和这个文件解析有任何关系吗?没有任何关系是吧?这是一些额外的一些信息,但是你在这里表达了,然后这个真正的DSL去表达的时候,都只跟你这个文件的结构有关系,与你这个,呃,其他的东西是没有任何关系的,是不是我们是在。当你会看到这个地方就比较有意思的地方在什么呢?就是我们这个nob task个flow,它是一个逻辑编码引擎,它设计的时候并没有考虑到说我当前要去做跑批的任务是吧?是所以他设计的时候是没有任何批处理相关的概念的,就是说我并不是说我设计了一个D,这个DL里面就定义了一个节点,叫做record冒号发model,然后用来定义这个批处理文件的格式的,我不是这么定义的是吧?整个Task的这这个原模型定义里面是没有这么一段的,但是我们所有的DSL都是支持扩展的,也就是说在no平台所定义的DS号语言里面,所有带名字空间的子节点也好,属性也好,你都是可以随时添加的,你自己可以添加这些东西,这个东西相当于是扩展,是在整个你做这个task模型的时候不需要考虑的,但你后期可以进可以加进来的东西。所以你对于你的任何。
62:23
它的DSL先天的它都是有一种扩展能力的,可以针对你特定的需求做扩展,而且你看这个这个本身是一个DSL嘛,是吧,然后呢,我们这两个DSL是可以无缝嵌套在一起的,就逻辑编排这个DSL和我们这个文件模型的DL是无缝嵌套在一起的,那么它具体怎么用呢?我们看到这是另外一个扩展,就是说在整个这个逻辑编码引擎里面呢,我们特殊定义了一个customer这种步骤,Customer步骤里面呢,它有个customer属性叫在这个地方。
63:01
这个customer属性呢,它就是一个标签函数名,就是这个在对应一个标签库里面的一个标签,就相当于是这是一个插票模板语言里面定义的一个标签嘛,然后它的代码是写的这个库里面的,你引入这个库,然后写这个customer,这个typeb等于这个标签名,那么它就会相当于把整个这个节点,它就识别出这个节点是一个特殊定制的一个扩展的节点,它会做一个结构变化,把这个里面呢,因为因为你看到这里面的属性,它这里面就是允许。我下面看一下它具体的这种配置逻辑啊,那这个地方要说一下好的,你看它这个逻辑是一个,它相当于做一个结构变换,这个结构变换是什么呢?你写有你有一个customer节点,然后你有一个customer typeb, 然后里面是名字空间,是标签名,然后这是这个标签所在的这个标签库的名字是吧?然后你在这个节点上可以加名字空间属性名,然后属性参数,然后名字空间,然后这个slo的名字,那么这样的一个custom的节点,它在编译期会被自动的转换为下面的结构,就是说你你如果不不是在上面,那么写你直接写一个插票节点啊里你要写一个sources段,然后里面这个地方写半细冒号xecu叉P冒号6等于半起点X Li啊等等是一样的,但这个时候呢,你你可以看到你的结构呢,会稍微的呃复杂一点,就会多一些东西,而现在通过上面这种方式呢,你会感觉到好像说是这个BY起节点就是天生就存在。
64:38
在这在这里的就是说这个节点下面直接就是代起相关的东西了,它好像是你专门针对办起定制的一个步骤,而所以他这种扩展逻辑和一般的这种逻辑版本引擎的这种扩展逻辑是不一样的,就是逻辑版本引擎它一般的这种扩展的方式结构都是引擎来定的,就是说这个引擎逻辑边边引擎定义了啊,它有哪些属性来源节点,它就是固定是这样的,但是呢,我们现在说我引入一个这样的。
65:08
Customer step是不是,那么它customer引入一个custom type, 然后这里面所有的结构就相当于不归你定了,由这个customer type来定,那么它具体怎么样去去做处理或者等等,它其实就是一个简单的一个结构变换,就在编译期把它的把它从这个这样的一个XL给它变成了下面一个X ma是吧?所以它是非常简单一个直接的一个做法,而且这种变换的规则是你自己可以随便定的,你自己并不是说我事先给你定好,你就只能用我这些规则,而是你想怎么定都可以,你自己可以引入自己的这种逻辑来做。那么具体的做法是什么呢?就通过上面这个X冒号extend,通过这个extend引入一个公共的一个A一个模型,那么这个模型里面就可以在它的原编程段里面去引入一些变换规则,所以你只要引入这个基础模型,那么通过它的这个POS以上的原编程就可以进行做你,所以你可以按需引入这个编译器。
66:08
这结构变换规则就是不是我定的,也不是说就只有这些,而是你可以根据自己的需求来自引入自己的结构变换规则的。所以你看它的扩展方式跟一般的这种扩展方式是不一样的,一般的如果说你是这种这个逻辑编排引擎吧,它都是说你要首先在这个引擎内部内置一个困难接口是吧,然后你可能有一个什么registerg注册机制啊,说我要允许你注册几种步骤的类型,然后你注册,所以你要知道这个接口是什么,知道这个注射器就进入registry在什么地方,然后你才能去做这个扩展啊整个那task flow呢,它是面向DDL的,而且是基于DSL森林概念的,所以你是不需要知道这些概念的,你只需要去查看一下这个task的它的这个原模型,去查看一下它的原模型,它的原模型里就会说它里边有哪些step,大概是什么样的情况,是不是啊,然后你就可以引入一个,引入一个自己的原编程阶段的一个处理器,然后把你这这个业务,就把你这个程序的结构从一个结构变。
67:19
看到就是说task的原模型允许的结构上就可以了,那么它变换之前这个结构实际上是随意的,你你自己可以随便编都都完全不受影响的,而且你看到我们这边这个模型,这个record这个模型在哪个地方用了,这是我们在这个read的这个地方。啊,这这个地方我看一下是在哪个地方用的,是在这个writer这个地方写的,你看这个writer,这个writer里面我们写了write的冒号model=simple far, 那么这个具体它怎么处理呢?首先这个里面是有一个原编程的阶段的,我在这个原编程阶段呢,先把这个模型解析了,然后让它成为一个变量,叫simple file, 然后把它放在编译期,就在译的这种状态空间里面,然后我编译到这个file writer的时候,从编译期取到这个变量,所以你看这个file writer这边呢,它也是一个结构变化,这个编辑结构变化是什么呢?就是你写了这个far record, 然后这个record发model的情况下呢,它实际上在编译期会变成这个变,变成这个结构去调一个标签,然后呢,通过这个方model等于这个symbol file这种方式来引用你在编译器的变量,这里面呢,这个井号大括号呢,就是一个编译期表达式。通过。
68:40
从这个编译期表达式呢,可以从编译的,就是从编译期的状态空间里面取到值,然后执行这种计算啊,当然这里面呢,还有一个,你看上面这个process这边呢,我们也做了一个这样的相当于的一个转换,这个第转换是什么呢?就是说你写了task冒号task model pass的情况下,它会给你转换成对,就是相当于对上段的一个实现,那当然你你看就是因为你这个这几个属性是经常用的嘛,所以你每次用可能都要用到这个,你觉得是一个比较重复的东西,那么你的DSL就可以精简就精简A,你只需要说我定义一下task的名字就行了,所以你看到经过这个编译器的转换之后,这一边的这个整个这个DSL,它的概念体系就得到了丰富,首先你看他就知道说我有这个。
69:33
呃,Record模型,我知道这个文件怎么读写是吧,这个文件的结构是什么样的,它里面怎么我我是可以知道这件事情,第二呢,我这个步骤里面呢,是可以直接写我这个批处理的这个DSL的是吧,这个批批处理的这个相当于这个步骤就是专门用于这个批处理任务执行的,而且呢,这里面如果说我到某一个步骤的时候。我到这个process的时候,就是每一个条目去处理的时候,我这个条目也可以去调,另外一个就是逻辑编排的,因为这个process本身就是一个函数嘛,是吧,我刚才说了,任何一个函数都可以被替换成一个逻辑编排的任务,那它就可以直接的指定这个逻辑编排的这个任务,它的一个模型文件在哪里,然后直接就去调这个模型,对吧?所以。
70:23
这边呢,你看到这边也是通过这个编排的话,它可以直接取到的,相当于你去做这个设计的时候,它本身原先并没有。说这几个DSL之间设计的时候有任何的关系,但是通过这种原编程的这种转换,他们之间是可以无缝集成在一起的,就好像他在第一天设计的时候就是这么设计的是吧,就是任何你如果你想象一下,就是这个信息,它对于整个信息的这种压缩,在很短的一个单一的这个一个雕塑L文件里面包含多种这种结构描述,同时让它无缝集中在一起,啊这个他的这种表达能力还是非常非常不一样的。
71:06
包括你看我们这个跟OM的集成啊等等这样的东西,就是结合这几个模型,所以我们在一般做业务开发的时候,可以通过完全声明式的方式实现批处理的任务,你是不需要写Java代码的,呃。你想象一下,如果你不使用那平台,你能够实现这一点嘛,是不是你定义DL,你你像我们这里面,它写的就是你可以义DSL,而且你可以把DSL就是连接在一起,这种能力在那平台里面是一种通用能力,它不是针对这个DSL设计的,它是所有的DSL天生都有这个能力,就是你随时可以把多个DS连成一个DSL就看起来像是一个,而且你想一下,如果你连成一个dscel之后,你是可以跟GPD交互变得更简洁的,是吧?你跟GPT讲话的时候,你就不用说我分成多个文件跟他讲话了,我可以用一个大的文件跟他讲话,然后讲完了之后我还可以,就我其实直接就可以运行,我都不需要跟他再做额外的什么东西了,是吧?那么这种抽样能力它是不影响运行时性能的,因为它在编译期做结构转换实现的,它就相当于就是从一个SMR变成另外一个SMR,是吧?而且你不需要对运行时。
72:23
引擎有任何的知识,就是说你去连接这个三个模型的,或者这四五个模型的时候,你不需要知道说no touch PRO到底是怎么执行的,它运营是有什么接口啊,有说你都不需要知道这个,你只要看它的原模型啊,只知道它里面能放什么数据,然后它有什么格式,直接给他转到这个格式就行了,是吧,你不需要知道运行时的任何的状态空间的一些信息,你是不需要知道这件事情,你只知道你有一个目标结构,你有一个当前的结构,这两个结构之间怎么对应是吧,而且你这个几个DSL放在一起,你你能不能做断点调试啊,你报错的时候是不是能够精准的定位到这个DSL的源码。
73:03
就是说比如说我这个地方是。呃,报错了,在半器里面报错了,那你报错的时候是不是说对应于你这个DL里面源码的这个文件,因为你变换之后,你的行数啊,或者什么都是变化了呢?那你是不是能定位为你变换之前呢这个代码行的位置呢?这些都是在no平台里面它解决的问题,因为它整体解析完之后是一个X no的,X no的记录了每一个属性和它节点的这个源码位置,当它做结构变换的时候,它是保存了这些位置做这个信息,做那个结构变换的,所以它是可以精确定位到这件事情的。而且还有一个有趣的问题,就是这样的一个D绍,你怎么去给他开发一个可视化设计器嘛,那么NOP平台里面,它的一个基本的设计思想是说你有一个第二套之后,他就会自动的得到一个设计器,当这个工作我现在还没有,呃,刚开始做还没有完全做完,等我最后做完之后,就相当于是我可以快速的开发DSL,然后我可以快速把多个DS混合成一个DL,而且我还可以给这个混合的DL。
74:06
立刻让他得到一个设计器,它可以综合性的设计里面的东西,那么这个才是这个问题的关键,那么在整个这个前面说了,因为我们整体这个东西呢,它是可以完全不写Java代码就可以执行的,因为它整体已经是一个完整的一个配置了,那么这个情况下呢,就是你通过这个闹平台的这个no CI工具就可以执行,就是你不需要说是单独再去打个包啊或者什么,它是可以独立于闹平台之外的,就是其独立道平台之外就可以去执行,比如说你执行这个给他传入这个APP点压嘛,有些配置文件你传给他是吧。然后呢,你这边就加va杠架,然后执行那个NOBCL这个价va包,然后通过这个run task个指令,Run task个指令,然后你告诉他一个模型文件的位置,就是逻辑编排引擎的这个逻辑模型文件的位置,然后通过杠矮给他传这个参数啊,相当于给他传一个接缝的参数,那它里面就可以运行了,而且它里面运营的时候可能是一个,呃,就多线程啊,并行怎么处理的这样的一个东西,所以你如果想集成使用这个孬办系的话呢,实际上是很方便的,因为现在一般情况下,我们跑批的时候,也是本身就是把它当做一个独立任务来执行的,也不是说在一个什么系统里面去执行是吧,因为跑批他可能运营完之后就停了嘛,所以而且他可能只是晚上跑一会儿,他也不用常年常年起的,所以我们现在就是有一个这样的工具,你只要通过某种方法调用到这个架包是吧,然后把它去执行就行了,它可以在那平台外使用啊,当然这里面。
75:46
前呢,还有一些测试用例啊,就是test的间什么DEMO啊什么的,可以调试这个。像你看这个这边就是相当于是,呃,你可以通过这个里面进行调试啊,就发现去掌握这里面的一些具体的使用了,那这里面最后在讲的呢,就是我们说的这个DSL的一个情况,就是和一般的这种DSL有个非常大的区别,就是在now平台里面,它不仅仅是有一个DLL的问题,它是强调这个dcel有多重表象的问题,就是你可以用XL来做这个DL载体,你也可以用Excel文件来做这个dcel的载体,这这个所谓的不同的载体都是同一个逻辑信息的表达,只是表达形式不一样,你的可视化编辑器也只是这个DSL的一种表达形式,是吧,它本身并没有任何的特殊性,所以你看到我刚才演示的这个整个这个批处理的情况呢,它我们目前很多东西啊,都是在这个Excel里面定义的。
76:56
看一下这个颜色啊。首先呢,他跑的时候呢,整个这个架包运营的时候呢,它会加载这个像像VS这个目录下的这种模型文件。
77:07
嗯,我们首先定义这个OM模型,你看这个OM的模型,就是因为我是通过这个批处理任务,我是可以用这个OM read的了,做要用这个OM read的的时候呢,我是用这个OM模型,但是我并不需要对它生成代码,就我们这个OM模型是可以不生成代码,你只要有这个啊,不是b.om.XL文件,有这个文件就可以了,然后这上面设false等于案例的等于处,那么它就不会说依赖于强类名的那个,就对象就是Java实实现类的类名,它就用dynamic和entity来代表所有的实体,但你就可以利用它里面的关联查询呀,那个延迟,那个批处理加,呃,延迟加载啊,批量更新啊,这种机制就是OM引擎的这种优化就都可以用了,然后你看我们这个地方很有意思的地方就是我并不是在这里面直接写这个exll文件,我并没有写这个XL文件是吧,你看到我是说我用这个Excel去生成,而且这个Excel我还分成了2。
78:07
两个M模型,你看我们就是说。这里就是我的这个Y模型,实际上是在这个Excel里面定义的啊,这是我并不是在这个SL直接写我在这个Excel里定义的这个第一个这个Excel就是一个基础的模型,然后这个呢,是一个data尔塔模型,因为我说我要在已有的Excel的基础上,我要给这个表加一个字段,我怎么加呢?就是我引入一个单的这个Excel,我这里面只写这个额外的字段,然后当组件你要写一下,就是通过这两个方式,然后它。在这个建议个参里面,你就这么写一下,然后它就会自动的加载第一个模型,生成一个X no节点,然后又加载第二个达到的模型,生成一个X no的节点,然后根据这个no平台的基本的运算规则,这两个模型就自动的会做达尔塔合并,然后会合并在一起,然后成为这样,所以你看到如果说你你如果不用到平台,你去写一个这样的可视化的一个机制,说我用某种可视化的工具去做设计,而且让这个可视化的工具支持德尔塔化。
79:18
实际上你是没有那么方便的是吧,你中间都要单独的去写代码,但是现在这个方式是不需要写代码的,而且这个模型不一定说只有Excel模型,你可以想就是如果是power designer的模型,嗯,Power designer是一个设计工具啊,或pder的这个模型它。不需要这个PDM呢里面做任何的处理,它就是自动支持这种达到模型的,你看到它就是自动的合并的,就这是任何的一种模型,你你只要给他写个解析器就可以了,而且我们整个闹平台里面已经内置了PDM的这个模型解析和这个powerdm的这个PDM模型的这个模型解析啊,这里面当然也有一个很关键的概念,就是在那平台里面,它强调差量和全量是就是它的形式是完全一样的,因为A=0+A是吧,那么任何一个全量都是一个差量的特例,所以你做一个设计器的时候,你没有必要单独为一个差量去做一个设计器,你只要用全量的设计器去设计这个差量就行了,最多是在上面加一些标注,所以差量设计器跟全量设计器是一个设计器,可以用同一个设计器设计,所以这也是这两个模型为什么能够合并在一起的一个观念,就是。
80:39
它本质上是一回事,格式是一模一样的啊,所以这个这个是就非常非常不一样的一个东西了。那么这里面呢,你看到我们这个ori模型,就是当我们要存库的时候,我们可以用这个ori模型去描述,那么我们跑P的时候呢。这里面呢,这有一个要要读写这个文件,你比如说我们现在这个地方是要读写这一个定长文件嘛,我们这里面就会定义这个文件的结构啊,它的文件头是什么,文件体是什么,文件尾是什么,文件尾呢?你看是这样的,它到写完所有的数据之后呢,它会写出这个文件呢,总共里面包含了多少行,那么它的有多少条记录,那么这个行多少条记录是需要在写的过程中进行汇总的嘛,所以这个地方呢,它就有一个汇总计算的一个配置,就是说你会你可以在这里配说有哪些变量是需要汇总的,汇总计算得到的变量名是什么,然后在这边就可以直接用了,当然因为整个这个写出的行数,它内置有一个变量叫汇总状态,这上面就有RA的看,所以你就不需要再去定义那个汇总逻辑了,你就直接用就行了,所以你这个地方有个导数表达式,但这里面可以扩展更复杂的东西,因为借助原编程的话,有很多很多的东西你都可以抽象的进行描述啊,所以。
81:56
这个是文件结构,包括我们去做那个TC pip的消息的解析啊等等,都是通过这种这个以上L模型的方式去定义的,而且我们跟客户去沟通需求的时候也是这样的,那这里面呢,你看我我现在提供了两个就是测试用的这种批处理任务,那么这个批处理任务呢。
82:16
第一个这个批处理任务呢,是一个这个测试数据生成,因为我们要做银行的这种呃压测嘛,它是要准备很多数据的,而且他准备数据的时候呢,是有比较复杂的这种要求的,他会说你要准备多少条多少张卡,然后每种卡有分别它的种类,呃就是说他要呃是什么样类型的卡,然后它要底下要做多少条交易啊等等这样的,比如所以这边呢,我们有一个专门用来生成数据的测试数据的一个这种模型,这个模型呢,它它前面首先首先有个数据模板,说我要按这个数据模板去生成,然后它有一个权重,权重说你要生成30%的这个A类账户,70%的B类账户,30%这个A类账户里面呢,又分成两种情况,叫A1和AA2是吧,那么AA1呢,它是一个这个地方有一个叫是否串行的标识,它是什么情况呢?它这个底下就是不就是不是说同时生成了,或者说不是按比例生成了,它是按顺序生成,它顺序生。
83:17
那时候呢,就先开一张主卡,那么先开主卡,它执行执行完之后呢,它会返回一个结果,返回一个结果呢,你把这个结果缓存下来,就返回主卡对象,然后你再开附属卡的时候呢,它就用上这个主卡对象的这个就个一些一些信息,比如说他的卡号啊或者什么的这样的一些信息,就相当于是,呃,这两个测试用例之间是有相互关联的,就是先生成这个,然后再生,这是要给他传参的啊,这个是这样的,如果是。如果是普通情况下,你直接写数据模板就行了,这个模板呢是一个负,就是说是一个合并关系,就是如果你写了继承副模板,就它会和它的这种副模板里的东西会合在一起,然后会墨叽到一起,然后作为去代码的这种传给你,这个相当于是生成器里面会得到的返回的结果,里面就合并好的结果,你比如说像这个credit card, 它通过这个gen,你告诉他这样的一个模型是吧,告诉他这样的一个模型,然后告诉他总体生成多少条,嗯,然后它里面就会按比例生成,然后生成的时候,我们这边就会调一个生成任务,就是说我们要调一个就是建卡的一个流程,然后去做处理,在我们实际去做业务的时候呢,是很有意思的,我们实际上我们在做银行的时候呢,是把这个批处理和这个在线的东西是共用代码了,比如说我们开卡的这个服务,我们批处理的时候,他会调他后面的这种批处理的任务嘛,那么我们在线的API就。
84:47
就开卡,它在线开卡的时候也是调同样的任务,所以批处理它只是相当于是把你的这种在线API的输入方式变一下啊,它其实是可以跟在线API共用,整个这个逻辑编排的,所以你看到我们这里面就是比如说建创建客户啊,就提供了一个标签叫saveity啊,有ity的data,就是一个上的数据是吧,然后create,然后它是一个saveity,而且你看到这个流程里面,因为它里面这个都是这个XL嘛,所以它是可以。
85:20
有一个编辑器的啊,你仔细想一下啊,其实它跟组件的那种配置,只要把那个组件的那个可视化配置器,那个配置器稍微改造一下,它就可以用来配置这每一个节点下面执行的东西是就怎么弄,你就拖一个节点叫save安ity的节点过来,然后给他指定属性就行了嘛,是吧,包括指定这样的映射规则就行了,所以它是你想象一下就是它是可以全部通过就无代码的方式,通过编排来做的,通过配置来做的,那么这是第一个任务是建的东西啊,当然这里面呢,你还还可以用来生成报表,因为我们有一个呃报表引擎嘛,那b report的这个报表引擎,所以当你需要在某一个步骤里面要生成报表的时候,你可以指定这个报表的模板,这个Excel的报表的模板,然后告诉他输出的文件东西在哪,然后执行的这个报表要传的参数是什么,那么在这个步骤里面,它就可以执行这个相当于生成这个Excel报表的这个工作了。
86:21
那么这边呢,是另外一个演示就是。这个批处理的演示,那么这个演示呢,它是。首先呢,它是从文件读取,文件读取完了之后呢,他把它写到库里去啊,写到库里去,而且写到库里的时候是判重的,就是这几个字段,如果重复了,那你是不是要插入,你还是要更音是吧,你是呃你你是是不是你你起几个线程去做处理是吧?然后这边呢,是。处理完之后呢,这也是一个并行的一个一个消费,这个这个消费的时候呢,它是你看它这个地方是多输出的,因为它分两种情况,就是说我去处理的时候呢,我是从那个。
87:09
呃,我是先,呃,我是从这个这个数据库里读取,你看这个地方是从这个数据库里读取,读取完我处理的时候呢,是需要把这个数据库里的记录给它删掉的,就是我处理一条,我给它删一条,处理一条删一条,同时呢,我还会产生一个输出,那我可以cons两次,就是输出两个,输出两个之后呢,我在这边通过filter来判断,说我到底是哪一个的消费,就是说你像你很自然的就有多个消费者是吧,你可以消费说我是这个是用来处理这个删除的,就我拿到这个,然后我就把它删了,然后这个呢是保存的啊,所以这个是这样的,而且你看到这个地方呢,呃。这个是。啊,报表引擎呢啊,就是你要生成报表的时候啊,通过Excel模板来配置,就是如果你有很多的这种可视化的东西都已经做好了的话,那你在你的这个DSL里面就是很容易的集成的,因为你看到我们在这个DL里面,当我需要使用某一个模型的时候,比如我引入一个报表模型啊,或者引入一个。
88:12
啊,什么record的模型啊,这边都是。已经做好处理了,就是你这边只要指定你的这个。模模型的这个路径就行了,因为我们系统里面所有的这种模型,你只要注册了以后,它是用一个通用的加载器去加载了,就是你给到这个路径名,它加载出来就是对应的模型,中间它会自动的根据你配置的一些一些那个原模型啊等等去解析,所以它最终就会得到这样的结果。嗯,好吧,我今天的介绍就到这里啊,就大概是这样的一个情况,就是现在有什么问题吗。哎,我问一个问题,就是如果这样的话,相当于所有的。
89:04
呃,这个逻辑全都写在这个插ML里面了,对吧,但但是插M里面又可以去写Java的一些代码。嗯,对,就是理论层面啊,理论层面你所有的逻辑都可以写在里面,因为这个里面呢,你首先它这里面呢,是可以写这个脚本啊是吧,它有脚本就C,呃就是呃插标就s script的脚本啊,或者插票那个模板语言,你也可以引入脚本库是吧?模板语言库你也可以嵌入你已经有的其他的DSL,所以它本身呢,这个能力它肯定是一个完备的能力,你都可以写,当然你也可以在外面写Java,你在外面写Java,它里面可以引泡进来,去就直接去呃调用Java上面的方法了啊。你这个地方都可以引泡的,你你实际上这个地方是跟引炮的加个类的,你也可以直接用你的spring容器里配的东西,你再引J进来,它就能用了。
90:06
那所以如果我刚刚看不明白,就是那我这里如果我呃像reader和呃和loader我都定义一个Java的B,然后不想在这个插表里面写,这样可以定义吗?可以啊,这个地方这个logo上面就有一个,并直接写就行了。哦,也可以用这种方式,嗯。OK, 那确实挺好的,我我确实就是spring办,其实就没有这种DSL的去描述这个编排文件的这样能力,就基本上我们是要做了讲Excel,但是它没有办法做的那么细,因为如果做细了,实际上它你想这个东西是这样的,你做的越细,你的抽样越多,你的应用范围越窄,它就变成这个问题了,那么闹平台你看它怎么做的呢?它首先它底下是一个完备的设计,但是这个完备的设计它本身的扩展性呢,是通过原编程实间扩展的,那就是相当于是你不用做的特别的细,你只要把你比如说你做拜起,你只需要处理拜起的问题,你他not,他只需要处理逻辑编排的问题,那么它两个是可以无缝集中在一起的,你不用设计一个超完备的一个DSLL,把你所有的情况都描述了,你只用关心你局部的东西怎么去做描描述是吧,它是这么一个问题,而且呢,这中间呢,它实际上。
91:26
你看一般的设计是怎么设计啊,一般的设计比如说他会做一个扩展,这个扩展是摊大饼式的扩展,比如说是。你去做它那个逻辑变换引擎嘛,你假如说你它的扩展一般是步骤级别的是吧,你假如说我在现在系统里面定义了10个步骤,那你要扩展,你可以把它扩展成11个步骤,12个步骤,13个步骤是吧,你的扩展逻辑都是这么叫扩展,它是摊大饼式的这么扩展,那么那平台里面它的扩展呢,它不一样,它是说它实际上是一种怎么说呢,封闭式的扩展,就是说假如说我现在这个task sta, 你看我这个task是怎么扩展。
92:07
我的扩展方式不一样,我这个step里面,你看我现在已经定义了这些步骤是吧,我定义了这些步骤,我可以说我是我未来就永远只有这些步骤了,我没有更多的步骤了,我也不需要扩展新的步骤,然后他。这个所有的这些步骤里面有一个特殊的扩展点,那么这个扩展点使得你其他的扩展可以挂在这个扩展点下面,成为一个二级扩展,它是这么一个方,就相当于你的系统里面所有东西都定好了之后,但你有一个0啊,有一个特殊的,呃,那个值就是零是吧,那一切的东西都可以从这个0在,因为customer在整个设计里面它是没内容的,知道吧,你直接写的customer,它相当于是它没有任何代码值指引,它实际上是一个空步骤,但是你的世界是可以从零里面产生出来的,它可以被零这个概念被再解释,产生新的东西,但是整个体系里面没有新的东西了,所以123456789,然后再加上一个0,我所有东西就这么多,我没有新的横向的东西,然后你再有东西是从零里面出来的,那么这个customer的东西呢,当然你如果是一般的扩展的话,它如果customer扩展的话,它会显。
93:27
平市场就会和原有的东西差别比较大,像这种二级扩展,它就没有办法像做的像一级扩展,那么自然就是说你的这种XL配置啊,或者说如果你是二级扩展的话,那有有可能是吧,你就不能做校验或者什么什么东西的,它是不一样,但是在闹平台里不一样,你的二级扩展一样是可以和一级一级的东西在,就是说它的形式长得一模一样的,而且呢,你可以在这个地方,就是说你在这个我看一下。你可以在这个地方。
94:05
你可以在这个地方直接定义一个你自己的task减号exx.X大,然后你的那个X大从我的X大继承,然后把你所有的这种扩展节点都通过xtime原模型约束起来,就相当于你不仅仅是因为在闹平台,你是这样的,你只要有这个xto原模型,你的idea插件里面就会自动的给你提示语法,而且自动给你做校验嘛,那么虽然说它允许任意的扩展,就是假如说你现在这个引入了xtime是这个是吧,我系统内的,那你这个地方写一个什么record什么的,他是不给你做校验的,因为他知道你是扩展机的呀,他不校验你这个事情是吧,他也不给你做提示,他他就变成就会丢失一部分这样的内容,但是如果说你还想有提示,那你只要引入一个自己的XF,把你这个东西引进来就行了,但是它实际的执行引擎呢,它还是用原来的task的执行引擎就行了,所以你看到在这个层面上,就是你的形式和你的这个运行呢,就实际的能力之间,它实际上是。
95:06
很松的,就是没有必要说我为了做这个形式,我要写一个引擎,这是没有必要的,你只要有一个通用的圆的引擎,你后面的上面的形式是你可以随意选,你可以随意适配的,而且它们的之间的适配就是一个字符串,你可以就认为就是个字符串转换的事情,或者说X no的转换的事情啊,不涉及到任何运行时的问题,就是个形式的问题,就是相当于一个数学的一个什么定理一样的东西啊,有几个规则啊,遇到这个东西这的报告发个model,我就怎么有一个什么处理是吧,就给他转换成某个结构,所以他这个你仔细想一下就知道这个。这个中间的这种定制逻辑,扩展逻辑和原来的东西是完完全全不一样的,它差别非常非常大,特别是跟以前的电造,因为SolidWorks公司以前可能推过这个Ruby的DL,但是实际上我感觉SolidWorks最后的总结应该说Ruby的DL是呃有存在比较严重的问题,那么Ruby的DL它有什么样的问题呢?首先我觉得有一个比较明显的问题是什么?他那个DL是指给人看的,就是你想用一个。
96:10
不用rub比语言的东西去解析这个DSLL,单独的去做分析或者做格视化,或者什么什么东西是不可能的,而且你不按照他那个DS号写也是可以的,就是他那个disc号没有约束性,它不是一个稳定存在,概念层面上独立存在的一个东西,只是说为了方便你看,让你看上去像这个东西,但是实际上它是一个不存在的虚幻的东西啊,所以我觉得它那个就是没有独立的一个价值,但是现在闹平台这个不一样,首先你看它出来就是XL,你是可以用这个XL解析器处理了是吧,假如说你就想全局的做一些什么处理的话,你不需要经过我这个东西是吧,你自己用别的工具就可以写,而且如果你它利用它内置的这种原模型的方式去处理的话,它在相当于在进入这个引擎之前,因为rub的介绍它是先解析成class结构,实际上已经进入运行时了,而那平台的这个呢,它不是在运行时的,它首先是解析成一个通用的X no.
97:09
那么在X no的层面是它相当于是个通用的充感语法数是吧,然后只是有标签名属性,然后它是没有任何执行语义的,而且没有任何的形式约束,他在这个层面上可以做其他的任何的处理,处理完了之后再进入模型层,然后再去做交给运营使引擎去解析的,所以整个它的这种概念体系的差距差别啊,对于它这个D造的这种组合性,特别它的原能力在什么时候发挥作用,这个设计都是不一样的。嗯,不好意思,我我最后再问一个问题,就是呃,就是我我觉得如果这样去写这个产品肯定能实现这个,呃,这个需求啊,就是比如说我有个需求是要去。
98:04
类似于stream里面把。多个数据,比如多多个items,然后合并汇总成可能。呃,少数的几个,比如说我有10条数据,可能经过一些处理变成就5条,原来在在那个spring办里面,它的那个就是reader process和writer这种模式,要想去改变这个数量的这种模式下,其实他会有一些问题,其实也比较痛苦,所以从你的这边角度来讲,这种处理。呃,有什么好的这种事件吗?不,你那个相当于是怎么处理啊,你那个相当于是你看你那个情况是这样的啊,你那个情况就和这个dispat器dispat这个机制就点类似了,就是你底层是有好几个reader是吧,你好几个reader可能都在读取,然后你在读取的时候呢,你是给它合并到一个东西,然后给它放到一个队列里去了,放到一个队列里,然后你另外的东西再从这个队列里面去抓,然后抓到数据送送到外面的。
99:07
你肯定也不一定是,也不一定是要多个reader,比如说呃呃,就比如说我有5张表,但是5张表可能处理,处理完之后可能就做join易之后就展开了,比如说可能有三张表,然后做了一个地摊地卡二期,然后乘出来之后就变变多了嘛,所以我我在那个四主任班级里面可以去处理,但是这样处理起来总是觉得不是很优雅的一种方式。你说表就是合并是吗?啊对对,就比如合并是应该正常讲的话,你应该是这样,你还是以某个表,就就比如说在一个,如果你现有一个情况,你是说你先你比如说你先去读取,从某个表得到一批记录,得到一批记录里面呢,你可以在after load里面从另外一个表抓数据,然后就把它合在一起,然后往回返是不是,但这个时候呢,你的问题是说有可能说是。
100:00
有一些不在这个表里怎么办,是不是啊?啊对,就比如说它的superma的这个编程模型里面,它其实它的reader和writer和process,它它最好的最好的模式,其实它的那个数据数量,它的行数实际上是不变,最好处理的,但是往往在现实当中它不一致,这里面不存在这个问题啊。这个函数没有关系啊,你看这个PRO,你你你看我这边我读到一个,我cons出去两个呀,我可能我往后发了两个呀,而且我这边PRO我还可以再对这个,如果你写多个process的话,这第二个就会看到两个,他会看到前面发给他的东西,对我的意思是说这种实践比较好的方式是哪种,就比如说写写两个,呃,Writer是比如写两个processor还是呃,就是在一个里面去去处理多个数据目标。这种比较好的实践是什么?我觉得都可以吧,要是候我我可能会写两个,嗯,OK, 呃,这这个就只是给我一个问题,就是我我困惑的地方就是我不知道业界大家是怎么玩儿的,就是当当我我用了其他类似的这种啊E条L工具也是有这种困惑,就是当我我说是spring办些它的设计,首先是它就是就是你说的它的item就是啊aemiented,而且它的设计思想是一对一啊吧,是的,就这个一对一,这是spring霸器里面最头疼,也就是为什么很多场景下我们没法采用四轮霸,其实是因为你绕过,如果绕过了它这个一对一的这个模型呢,你你发现就没啥用它的东西了。
101:34
对,所以你看看我,我们现在就是说,首先你在抽象上面这个processor它可能调多次consume,它是一对多,就flag map, 我刚刚说了,就是它的模型是map,就是A到B是吧,然后最多是B是now是吧,那么我们现在这个,嗯,这个批处理模型实际上是一个flagp f flag map, 就是A到一个数组,那么数组里面有0条,呃,一条或者多条啊都是有可能,你可以去可以consum出多了个来,那可出多个来之后,你后面可以过滤掉,然后你你可以说只我只接受某一个,另外这边还有个tiger,你也可以说,呃,出来之后呢,直接给它加标记,就是说你加标记的时候,你可以选择说它是走后面的哪几个,就是相当于类似于flink的那个机制,就flink,它只际要它处理的时候呢,它是写往后写的时候,它是它是写到哪一个流里面啊,它right的时候相当于有个tag,然后他会说我写到哪个流里面,然后后面有那个监听。
102:34
因为它那个后续的流是哪一个,他他是知道的啊。对,多谢,就是呃就如果按照你这个思的话,相当于呃就是按照你这个思,当然是可以很容易实现,但是因为就是spring发它是以那个item的模式,就不太好去去处理。他的模型就有问题,但是它那个模型有一个优势,就是他如果是以animal种思维出去,他去重试,他去。
103:02
做的时候其实更加容易一点,不是它那个不容易啊,你你你相相当于你看我现在从事更简单知道吧,因为为什么我reader已经把数据读出来了,我重试的时候我就重试这批数据就行了。我不需要再去读了呀,他的重试,如果他要从他已经读了100条之后,他要再重试的话,他是需要在他实际上也是要内部去缓存起来的,另外你说的那种处理逻辑,比如说他说我已经记到哪一条了,是吧,我读到哪一条了,是不是假如说我读到100条了,或者读到1000条了,我后面重试的时候,但是因为重试的时候,我们不是以读为准的,我们是以处理为准的,就是说我处理到100条,比如说100条全部成立成功了,我第二次重试的时候,我可以把前面100条跳掉,但并不是说我读到100条了,我前前我再重试的时候,我就能把100条跳掉了,所以他这个他的那个方,如果他那个方案模型啊,他去从事的时候啊,你就简单的说,你要是多线程的话,它是不对的。
104:00
他他他没有办法保证多线能的情况下,它的那个文件读取处理也是可以跳安全的跳过了,我们现在做法呢,是在那个后面,就相当于是你看他这个地方。我们现在是在这边去监听了一个。监听了一个这个应该是consume的一个事件,就是说是啊监听了这个check and的事件,整个check结束的时候,然后我怎么怎么样,然后这个时候我才标记我这个东西是结束了,我当年那个条目这个complete index, 我我给他设上了,否则我没没给他设知道吧,就是说他这个逻辑是不一样,就是我整个处理完了,我才说这个东西是可以被跳过了,然后我再重新跑的时候呢,就整个这个从事逻辑,他的东西是非常不灵观,因为你有时候不,而且你刚我刚才在介绍这里面的时候也说了,你从事的时候有可能不是说你光从事process就行了,你因为你有可能整个你写的时候失败了,就是你写数据库的时候,我们现在经常遇到的情况,就是说跑批的时候,前面还在正在那个在线还在交易呢,那你跑批有可能一个枪口会失败啊,那你失败的时候你要重试啊,或者怎么样的,那你啊,或者你有一些数据不对,就这个人的数据他。
105:28
他状态已经被锁了或者什么的,他没有办法做这个业务操作了,是不是,那你怎么办啊,那我们就是从事的时候是一个一个从事的,就是one慢万的从事的,然后且因为spring他的那个事务处理是一个是一个事物,他就没法做这种精细控制。是我最后最后再问一个问题,就是因为时间也比较晚了,就是它能够做到像那种呃,像那种stream里面那种像比如说reduce这种类似的操作吗。Reduce, 这这个地方有有个例子吗?呃,这里面没有例子啊,有有有例子有例子啊。
106:07
如果能做到这些,像这么各种这种操作,Map reduce, 那就非常的灵活。嗯嗯,不是那个,我们现在是在当不你那个,你如果你是说留了问题的话,那是另外一个问题啊,就是说可能就是说是,呃,首先是你自己在这个process的时候,就是如果你想去做这个东西的话,这个process因为它是共享一个task context的,就是你整个这个你processor会看见task context的嘛,是吧,你看到task context中间呢,是可以去定义一些,比如说我在这个地方给你加一个段叫aggregator是吧,Aggregator啊,你可以对你的定义几个,就跟我,你看我刚才那个文,那个文件是可以做处理的,你比如说你看这个文件啊,这个文件当我写出的时候,这个就是一个reduce的一个操作吗?看到吧,这个文件是reduce的操作,而且我这个里面实际上还有一个分页reduce,就是说你比如说因为我们有些客户关是说他说100条,你要给我reduce一下啊,怎么怎么样,或者因为某种条件你要reduce。
107:14
都是一样,然后往里插一条横向记录什么的,然后这个这个是在写出的时候,我可以定义一个汇总计算是不是,那你到在这边去做的时候呢。当然呢,也可以再说定义一个aggregator去做这种处理也是可以的啊,也不是说可以,但我目前没有这个需求啊,知道吧,就是我可以加,当你也可以把它抽象出来,比如说我刚才说的东西,你完全可以有一个aggregator的模型,知道吧,你processor,你实际上是加一部processor在里面,然后你你叫aggregate,然后这个aggregate,呃,假model啊,叫什么东西是吧,那你可以你可以加,引入一个,引入一个现有的模型,或者说你是这个process,然后你底下有一个aggregate的。
108:06
或者白起吧,二期。给他。你这地方还有这你这个有field的,嗯,什么内部你你可以你可以扩展一个这样的模型是不是,然后就这个模型就写在这,然后实际上呢,实际的做法呢,是把这个I的模型在编译期给它解析了,然后把它变换成一个标签函数,然后那个函数里面呢,他就负责从上面去取东么东西啊什么就就可以去做了啊。那这个agg这个这个里面操作,它相当于还是从从这个上下文里面去这办齐下100条这个数据的吧。对啊,但是它的存的结果,它可以存到task contact了,就整个整个task进来,有一个全局的contact对象,然后每一个CH有一个ch context对象。OK, 我我大概理解这个逻辑了。
109:02
其他朋友还有什么问题吗?OK, 如果没有其他的问题的话,我们是不是今天就到这里。行,那我停止录制了啊。OK.有什么问题,我们在群里面再再聊吧。
我来说两句