天天看点

做煎饼果子的N种方式——From Sequential to Reactive

需要注意的是 本文不是真正的讨论如何做煎饼果子。

相信南方人都吃过煎饼果子——一种裹着生菜和火腿肠的鸡蛋煎饼。虽然好吃,其制作过程却也不比汉堡包简单,让我们一起来拆解下吧。

做煎饼果子的N种方式——From Sequential to Reactive

图 1 ——煎饼果子拆解

从上图(这个?没有卷起来)我们可以看到,一个煎饼果子分为下面的几个部分:

最下面的是鸡蛋煎饼——鸡蛋是打在煎饼上的

中间是一层生菜——若干

在上面是一根火腿肠——只有一根

最后把它卷起来,套上食品塑料袋,就是煎饼果子了,如下图:

做煎饼果子的N种方式——From Sequential to Reactive

看到如此美味的煎饼果子,是不是很想自己做来吃吃,甚至是去开个店,专门卖煎饼果子呢?当上CEO,迎娶白富美,这都不是梦啊~~。那么,下面就让我们一起看看如果做一个煎饼果子吧。

煎饼果子需要下面几种食材:

面粉——我们需要做煎饼,

鸡蛋N枚——取决于你要做的?的数量,一般一个煎饼果子,使用一枚?

生菜若干——平均一个煎饼果子,使用1到2片生菜,

火腿肠N根——一般,都会加上火腿肠,可选,一般一个煎饼果子,一根火腿肠。

做煎饼果子的N种方式——From Sequential to Reactive

准备好了原材料,让我们来做煎饼果子吧,

做煎饼果子的N种方式——From Sequential to Reactive
做煎饼果子的N种方式——From Sequential to Reactive
做煎饼果子的N种方式——From Sequential to Reactive

现在送脆饼

做煎饼果子的N种方式——From Sequential to Reactive
做煎饼果子的N种方式——From Sequential to Reactive
做煎饼果子的N种方式——From Sequential to Reactive

从上面的图解中我们可以看到,流程是这样的:

面粉 + 水 -> 面浆;面浆 + 烘焙 -> 煎饼

煎饼 + 鸡蛋 + 烘焙 -> 鸡蛋煎饼

鸡蛋煎饼 + 生菜 -> 带有生菜的鸡蛋煎饼

带有生菜的鸡蛋煎饼 + 火腿肠 -> 带有生菜和火腿肠的鸡蛋煎饼

带有生菜和火腿肠的鸡蛋煎饼 + 卷曲 + 切断 -> 煎饼果子

我们对上面的步骤进行化简:

面粉 -> 煎饼

煎饼 + 鸡蛋 -> 鸡蛋煎饼

鸡蛋煎饼 + 生菜 + 火腿肠 -> 煎饼果子

我们对上面的步骤继续化简

面粉 -> 煎饼 -> 鸡蛋 -> 鸡蛋煎饼 -> 生菜 -> 火腿肠 -> 煎饼果子

下面让我们使用代码先来模拟下,验证怎么样才可以高效的做出煎饼果子吧。

实现代码:

做煎饼果子的N种方式——From Sequential to Reactive

让我们运行上面的代码,其输出结果如下:

如果我们偶尔做一次,其实还好啦,满足,不过作为有梦想的程序员,我们要快点,这样才有竞争力啊,对,快,更快。

这就是我们牛逼的异步的方式了,即基于EventLoop/CSP的方式,事情都是一个人做,这个好了接着做下一个,在做煎饼的时候,不会等待煎饼烤熟。而是回去洗菜、准备火腿肠等。

这个时候,我们就会想,是不是多招聘几个人呢?掐指一算,对,招聘4个人吧:

做煎饼果子的N种方式——From Sequential to Reactive

这里我们,招聘了4个人,如下所示:

人手多了,让我们来看看效果吧:

是的,我们变快了~~~,只需要 5秒,我们投入了这么大的人力成本不就是要,客户第一,让顾客更加快速地买到煎饼果子么?

上面的方式稍显老套,作为弄潮儿,最新的技术,搞搞搞,瞧,我们的透明后厨,简洁着呢:

做煎饼果子的N种方式——From Sequential to Reactive

通过上面的方式,真的好简洁,一套一套的,让我们来看看效果吧:

额,竟然还是这样?

感觉这个人有点多,生意也不够好,还是换方式搞吧,某某某,你帮帮他吧,某某某,你也别闲着,把XXX也搞了。

不行,这样不行,一定要从根本上解决问题,对,我们来梳理一下。

开店成功需要几个要素?!

成本节约,在等待的时候,可以帮忙做点别的事情。

结构优化,效率提升,建立完整的上下行监管机制,消息顺达。

精简语义,使用专用词沟通,减少自然语言表意不明。

客户第一,提高服务质量,尽快返回结果,不要超出购买者的能够忍受的最长等待时间。

所以,对我们的例子使用Actor模型来进行建模,然后应用CQRS来减少语义。当然,这里我们的事件并没有持久化,算是部分实现。

Actor模型 + Facade门面模式:

做煎饼果子的N种方式——From Sequential to Reactive

其中,使用 Ask 模式,我们接驳了传统的门店服务,以及基于Actor 模型的店员。而对于我们的店员,我们又使用了Actor模型以及监管机制,同时使用 CQRS 来对命令和事件进行分离。

其中我们有命令:

做煎饼果子的N种方式——From Sequential to Reactive

在我们的门店服务内部,将会对这些命令进行处理,并且产生相应的事件。

做煎饼果子的N种方式——From Sequential to Reactive

再和门店的接驳处,使用了Ask模式:

做煎饼果子的N种方式——From Sequential to Reactive

而对于老板/店长来说,他肯定是自己不处理的,所以他将任务,派发给了<code>煎饼果子大侠</code>,即:

做煎饼果子的N种方式——From Sequential to Reactive

注意,上面我们又一次使用了Ask模式,而非FSM。

这项任务就到达了我们的煎饼果子大侠了,他的任务可重了,因为他需要等待的东西有:

鸡蛋煎饼

洗好的生菜

撕开好的火腿肠

所以:我们的煎饼果子大侠会分别和,鸡蛋煎饼太郎、生菜小二哥以及火腿肠大叔形成依赖关系,并且会依赖于他们的结果,才可以做出一个完整的煎饼果子:

做煎饼果子的N种方式——From Sequential to Reactive

当收到老板的命令的时候,他将任务进行了拆解,分发给了和他合作的其他店员:

做煎饼果子的N种方式——From Sequential to Reactive

这里,我们搭配使用了Ask模式和Aggregator模式,将从各个部分收集到的结果,进行汇总,并产生了最终的美味的煎饼果子。

需要注意的是,我们在这里,并没有看到煎饼是怎么来的,而是直接看到的是鸡蛋煎饼,这就是DDD中的领域分层和依赖了:

我们的鸡蛋煎饼太郎和煎饼西施之间是一个强依赖关系:

做煎饼果子的N种方式——From Sequential to Reactive

这里,我们看到了一个奇怪的地方,即不对称的超时设置。因为太郎对西施特别好,所以顶住压力,不管怎么催他,他都会给西施说,别着急,慢慢来。

这就引发一个问题了,不正确的超时设置,可能让消费者非常不耐烦,本来消费者已经等了30s了,结果你对他说,哎呀,我们的鸡蛋煎饼太郎太忙了,然后顾客灰溜溜的走了,丢失了大量的潜在客户。

即,不合适的超时配置,会造成服务质量的下降。

让我们来把店开起来,并且提供服务吧

做煎饼果子的N种方式——From Sequential to Reactive

现在店开起来了,让我们来看一看运行结果吧。

喔,完美的组织

架构和模式应用!当然,我们这里没有对Event进行持久化,这一点儿是不利于回溯的,同时也没有应用断路器模式,以及还有多处不合理的超时配置。

上面的这些方式,都让我们不难想,如何让客户更少的等待,如何提供更好的服务,如果我们的心更加大,如果我们要把店面做大,甚至要开连锁店,或者开煎饼果子工厂呢?

技术,不是给业务以限制,而是助力其想象。

如果,我们想要将这个模式,复制到更多的场景,甚至开一条生产,N调生产线,如果我们要这些生产线能实现智能的调控,达到最小的资源占用,来达到最大的效力,那么我们应该怎么做呢?

对于这个问题,在2013年到2014年,业界也在思考,后来几经波折,想到了一种基于流的拓扑描述的方式。下面就让我们使用反应式流的方式,来实现上面的业务吧。

首先来一个不太清晰的例子,这个例子中,我们使用了Akka-Stream——一个ReactiveStream的实现。

做煎饼果子的N种方式——From Sequential to Reactive

在上图中,我们对抽象进行了下面几点改进:

我们的消费请求,被抽象为了一个流,这个流,类似于我们做的供给侧改革,使用消费请求,来指导我们的生产。

我们的店员,不再是基本的店员了,把他们想成持续提供煎饼,鸡蛋煎饼,生菜,和撕开的火腿肠,以及煎饼果子的流,即生产线。

我们可以动态地控制生产速度,如果消费者多,就在能力满足的情况下,尽量地多生产,在某项能力不能满足目前需求的情况下,就进行复制这些服务,对其进行复制,以提高更搞的生产力。比如,做煎饼是个比较缓慢的动作,那么我们可以再加一条生产线,这个生产线专门生产煎饼。

有了这个流,我们发现,煎饼的生产和鸡蛋煎饼的生产,总是强依赖的,那么我们可以将他们部署到临近的生产线,减少成本。

同上,我们发现鸡蛋煎饼和生菜以及火腿肠的产生,也是最终要进行合并使用,那么我们也可把这三种生产线,排布在一起,这样降低了将这三种材料,运输到煎饼果子大哥的时间。

如果我们发现煎饼果子大哥这个只做煎饼的过程很慢了,这个时候我们可以只在这个流程节点上,进行复制,从而加快煎饼果子的卷曲和打包过程。

我们如果一个发货窗口发不过来,我们可以多开两个门店/物流发货窗口。

我们可以使用类似于由仓库直接发货的方式,将生产好的热腾腾的煎饼果子,直接交给购买者,而不用通过我们的店长或者店面,在消息模式中,这叫做Forward模式。

即,我们描述了依赖,而再有了这样的依赖之后,基于我们的需求和供应信息。我们可以处处都进行复制流程,复制处理节点,从而做到最优化地让热腾腾的煎饼果子到达用户的手里。

当然,如果我们实在是,实在是不能再扩展生产线了,那么我们就会进行回压,回压的时候,我们在最前面,就会让客户等待,或者在满足SLA的情况下,进行一些策略,但是,我们整体的服务质量,依然是那么得好,RS,就是双向的流,控制流,数据流。

即,类似于下面的结构:

做煎饼果子的N种方式——From Sequential to Reactive

我们将生产好的结果,直接递交给了消费者。

即,可以做到图中的任意一个方框内的结构拓扑,都是可以单独地进行复制,和调控的,甚至是智能地进行调控:)。智慧物流,我们也有智慧服务。

有了上面的实现,我们可以使用现在的一些语义化的工具来进行描述,比如:

做煎饼果子的N种方式——From Sequential to Reactive

好了,我们再看一种:

做煎饼果子的N种方式——From Sequential to Reactive

然后,我们再看一种:

做煎饼果子的N种方式——From Sequential to Reactive

然后仔细一下对比:

做煎饼果子的N种方式——From Sequential to Reactive

我们都需要煎饼Flow/Source,而且从煎饼Flow变成了鸡蛋煎饼Flow/Source

做煎饼果子的N种方式——From Sequential to Reactive

我们都还需要生菜Flow/Source,以及火腿肠Flow/Source。

做煎饼果子的N种方式——From Sequential to Reactive

我们从鸡蛋煎饼Flow/Source 、生菜Flow/Source以及火腿肠Flow/Source,构建了一条煎饼果子的Flow/Source。如果我们把Flow/Source看做生产线,喔喔,我们根据三个现有的Flow/Source,构建了一条煎饼果子的Flow/Source。

最后,我们只从里面拿了一个煎饼果子出来:)

做煎饼果子的N种方式——From Sequential to Reactive

上面我们还有一点,就是异步和并发怎么控制?我如何并发动做一些事情呢?

做煎饼果子的N种方式——From Sequential to Reactive

或者

做煎饼果子的N种方式——From Sequential to Reactive

非常简单,如果我们想要同步的呢?去掉红框中的部分就好了。

也就是说,我们描绘了整个服务的编排,然后我们便可以方便地对任意特定的子拓扑进行优化了。开辟新的生产线,或者通过复制某个处理的过程来对某个流程进行并发执行,以提高其生产效率,当然在真的扩不了的情况下,保护我们的系统,保障我们SLA。

那么,我们如何不断地获取我们的美味煎饼果子呢?早上喜欢吃煎饼果子的人太多,都要疯掉了,好,请看:

做煎饼果子的N种方式——From Sequential to Reactive
剩下的留作练习:)

只要我们的请求不断,我们的

做煎饼果子的N种方式——From Sequential to Reactive

就会持续的产生结果。多么简单直接,而且非常的优雅。复用这样的模式,开连锁店,开工厂,智能化的工业生产,人生巅峰不是梦。

拓展思考?

基于这个例子,我们可以看到,如果我们的服务代码,通过上面的方式来编写,那么势必更加地简洁优雅,而且我们也具备了更细腻的控制力,并且也有了更高屋建瓴的拓扑、思维建模以及全局优化的可能。同时,我们的服务,都是由反应式流中流转的信号量进行驱动的,从而实现了动态的推拉结合——对,没错,控制论中的知识:)

我相信,通过面向流的编程,从数据first,切换服务编排,请求/响应拓扑first的思维,将会大大地提高我们对链路的理解能力。同时,有了这些工具,我们常见的反应式设计模式,都可以非常方便地应用,并完全可以结合FP以及DDD的一些思路,打造更加清晰、明了、性能优异的系统。而且,我们仅仅描述了服务的编排,从而产生拓扑,而剩下的事情,只需要动动手指,也许手指都不需要动:),这一切,都将会有下一代的架构来智能地保证。

从上面,大家已经看到了,我们的编程模式,是如何一步一步地从传统的方式,变成我们的Reactive 化的方式。通过Reactive 的方式,我们可以方便的实现服务编排,有了服务编排之后,我们可以做更多的事情,服务将会是更加智能化的,而非一成不变,提升了客户体验、资源利用率,并降低了资源的浪费。

继续阅读