本文共 5247 字,大约阅读时间需要 17 分钟。
需要注意的是——本文不是真正的讨论如何做煎饼果子。
相信南方人都吃过煎饼果子——一种裹着生菜和火腿肠的鸡蛋煎饼。虽然好吃,其制作过程却也不比汉堡包简单,让我们一起来拆解下吧。
图 1 ——煎饼果子拆解
从上图(这个?没有卷起来)我们可以看到,一个煎饼果子分为下面的几个部分:
最后把它卷起来,套上食品塑料袋,就是煎饼果子了,如下图:
看到如此美味的煎饼果子,是不是很想自己做来吃吃,甚至是去开个店,专门卖煎饼果子呢?当上CEO,迎娶白富美,这都不是梦啊~~。那么,下面就让我们一起看看如果做一个煎饼果子吧。
煎饼果子需要下面几种食材:
准备好了原材料,让我们来做煎饼果子吧,
现在送脆饼
从上面的图解中我们可以看到,流程是这样的:
我们对上面的步骤进行化简:
我们对上面的步骤继续化简
面粉 -> 煎饼 -> 鸡蛋 -> 鸡蛋煎饼 -> 生菜 -> 火腿肠 -> 煎饼果子
下面让我们使用代码先来模拟下,验证怎么样才可以高效的做出煎饼果子吧。
面粉 -> 煎饼 -> 鸡蛋 -> 鸡蛋煎饼 -> 生菜 -> 火腿肠 -> 煎饼果子
实现代码:
让我们运行上面的代码,其输出结果如下:
开始计时好吃的煎饼果子勒耗时:9 秒
如果我们偶尔做一次,其实还好啦,满足,不过作为有梦想的程序员,我们要快点,这样才有竞争力啊,对,快,更快。
这就是我们牛逼的异步的方式了,即基于EventLoop/CSP的方式,事情都是一个人做,这个好了接着做下一个,在做煎饼的时候,不会等待煎饼烤熟。而是回去洗菜、准备火腿肠等。
这个时候,我们就会想,是不是多招聘几个人呢?掐指一算,对,招聘4个人吧:
这里我们,招聘了4个人,如下所示:
private static ExecutorService executorService = Executors.newFixedThreadPool(4);
人手多了,让我们来看看效果吧:
开始计时好吃的煎饼果子勒耗时:5 秒
是的,我们变快了~~~,只需要 5秒,我们投入了这么大的人力成本不就是要,客户第一,让顾客更加快速地买到煎饼果子么?
上面的方式稍显老套,作为弄潮儿,最新的技术,搞搞搞,瞧,我们的透明后厨,简洁着呢:
通过上面的方式,真的好简洁,一套一套的,让我们来看看效果吧:
开始计时好吃的煎饼果子勒耗时:5 秒
额,竟然还是这样?
感觉这个人有点多,生意也不够好,还是换方式搞吧,某某某,你帮帮他吧,某某某,你也别闲着,把XXX也搞了。
不行,这样不行,一定要从根本上解决问题,对,我们来梳理一下。
开店成功需要几个要素?!
所以,对我们的例子使用Actor模型来进行建模,然后应用CQRS来减少语义。当然,这里我们的事件并没有持久化,算是部分实现。
Actor模型 + Facade门面模式:
其中,使用 Ask 模式,我们接驳了传统的门店服务,以及基于Actor 模型的店员。而对于我们的店员,我们又使用了Actor模型以及监管机制,同时使用 CQRS 来对命令和事件进行分离。
其中我们有命令:
在我们的门店服务内部,将会对这些命令进行处理,并且产生相应的事件。
再和门店的接驳处,使用了Ask模式:
而对于老板/店长来说,他肯定是自己不处理的,所以他将任务,派发给了煎饼果子大侠
,即:
注意,上面我们又一次使用了Ask模式,而非FSM。
这项任务就到达了我们的煎饼果子大侠了,他的任务可重了,因为他需要等待的东西有:
所以:我们的煎饼果子大侠会分别和,鸡蛋煎饼太郎、生菜小二哥以及火腿肠大叔形成依赖关系,并且会依赖于他们的结果,才可以做出一个完整的煎饼果子:
当收到老板的命令的时候,他将任务进行了拆解,分发给了和他合作的其他店员:
这里,我们搭配使用了Ask模式和Aggregator模式,将从各个部分收集到的结果,进行汇总,并产生了最终的美味的煎饼果子。
需要注意的是,我们在这里,并没有看到煎饼是怎么来的,而是直接看到的是鸡蛋煎饼,这就是DDD中的领域分层和依赖了:
我们的鸡蛋煎饼太郎和煎饼西施之间是一个强依赖关系:
这里,我们看到了一个奇怪的地方,即不对称的超时设置。因为太郎对西施特别好,所以顶住压力,不管怎么催他,他都会给西施说,别着急,慢慢来。
这就引发一个问题了,不正确的超时设置,可能让消费者非常不耐烦,本来消费者已经等了30s了,结果你对他说,哎呀,我们的鸡蛋煎饼太郎太忙了,然后顾客灰溜溜的走了,丢失了大量的潜在客户。
即——不合适的超时配置,会造成服务质量的下降。
让我们来把店开起来,并且提供服务吧
现在店开起来了,让我们来看一看运行结果吧。
开始计时老板,煎饼果子来一个大侠,做个煎饼果子太郎,做鸡蛋煎饼小二哥,切生菜大叔,撕火腿肠西施,做煎饼太郎:我在做鸡蛋煎饼大侠:我在开始做煎饼果子了老板:大侠已经做好了啊?!好吃的煎饼果子勒耗时:5 秒
喔,完美的组织
架构和模式应用!当然,我们这里没有对Event进行持久化,这一点儿是不利于回溯的,同时也没有应用断路器模式,以及还有多处不合理的超时配置。
上面的这些方式,都让我们不难想,如何让客户更少的等待,如何提供更好的服务,如果我们的心更加大,如果我们要把店面做大,甚至要开连锁店,或者开煎饼果子工厂呢?
技术,不是给业务以限制,而是助力其想象。
如果,我们想要将这个模式,复制到更多的场景,甚至开一条生产,N调生产线,如果我们要这些生产线能实现智能的调控,达到最小的资源占用,来达到最大的效力,那么我们应该怎么做呢?
对于这个问题,在2013年到2014年,业界也在思考,后来几经波折,想到了一种基于流的拓扑描述的方式。下面就让我们使用反应式流的方式,来实现上面的业务吧。
首先来一个不太清晰的例子,这个例子中,我们使用了Akka-Stream——一个ReactiveStream的实现。
在上图中,我们对抽象进行了下面几点改进:
即——类似于下面的结构:
我们将生产好的结果,直接递交给了消费者。
即——可以做到图中的任意一个方框内的结构拓扑,都是可以单独地进行复制,和调控的,甚至是智能地进行调控:)。智慧物流,我们也有智慧服务。
分别使用Akka-Stream、RxJava2以及Flux来实现
有了上面的实现,我们可以使用现在的一些语义化的工具来进行描述,比如:
好了,我们再看一种:
然后,我们再看一种:
然后仔细一下对比:
我们都需要煎饼Flow/Source,而且从煎饼Flow变成了鸡蛋煎饼Flow/Source
我们都还需要生菜Flow/Source,以及火腿肠Flow/Source。
我们从鸡蛋煎饼Flow/Source 、生菜Flow/Source以及火腿肠Flow/Source,构建了一条煎饼果子的Flow/Source。如果我们把Flow/Source看做生产线,喔喔,我们根据三个现有的Flow/Source,构建了一条煎饼果子的Flow/Source。
最后,我们只从里面拿了一个煎饼果子出来:)
上面我们还有一点,就是异步和并发怎么控制?我如何并发动做一些事情呢?
或者
非常简单,如果我们想要同步的呢?去掉红框中的部分就好了。
也就是说,我们描绘了整个服务的编排,然后我们便可以方便地对任意特定的子拓扑进行优化了。开辟新的生产线,或者通过复制某个处理的过程来对某个流程进行并发执行,以提高其生产效率,当然在真的扩不了的情况下,保护我们的系统,保障我们SLA。
那么,我们如何不断地获取我们的美味煎饼果子呢?早上喜欢吃煎饼果子的人太多,都要疯掉了,好,请看:
或者
剩下的留作练习:)
只要我们的请求不断,我们的
就会持续的产生结果。多么简单直接,而且非常的优雅。复用这样的模式,开连锁店,开工厂,智能化的工业生产,人生巅峰不是梦。
拓展思考?
基于这个例子,我们可以看到,如果我们的服务代码,通过上面的方式来编写,那么势必更加地简洁优雅,而且我们也具备了更细腻的控制力,并且也有了更高屋建瓴的拓扑、思维建模以及全局优化的可能。同时,我们的服务,都是由反应式流中流转的信号量进行驱动的,从而实现了动态的推拉结合——对,没错,控制论中的知识:)
我相信,通过面向流的编程,从数据first,切换服务编排,请求/响应拓扑first的思维,将会大大地提高我们对链路的理解能力。同时,有了这些工具,我们常见的反应式设计模式,都可以非常方便地应用,并完全可以结合FP以及DDD的一些思路,打造更加清晰、明了、性能优异的系统。而且,我们仅仅描述了服务的编排,从而产生拓扑,而剩下的事情,只需要动动手指,也许手指都不需要动:),这一切,都将会有下一代的架构来智能地保证。
小结
从上面,大家已经看到了,我们的编程模式,是如何一步一步地从传统的方式,变成我们的Reactive 化的方式。通过Reactive 的方式,我们可以方便的实现服务编排,有了服务编排之后,我们可以做更多的事情,服务将会是更加智能化的,而非一成不变,提升了客户体验、资源利用率,并降低了资源的浪费。
原文发布时间为:2018-02-07
本文作者:虎鸣
本文来自云栖社区合作伙伴“”,了解相关信息可以关注“”微信公众号
转载地址:http://kcwno.baihongyu.com/