原文地址:https://www.lightbend.com/blog/the-case-for-message-driven
本文转载采用谷歌翻译,存在不准确情况
了解异步,非阻塞,并发,并行等等
我一直在努力寻找一种有意义的方式来描述构建高效的响应式(Reactive)应用程序的核心概念 - 异步和非阻塞,同时通过增强并行性来最小化并发性并支持线性可伸缩性。即使是最有经验的开发人员也难以理解这是真正深奥的术语。但理解它们对于构建真正的反应式应用程序至关重要。
在过去的几个月里,我想我可能已经找到了更清楚地表达这些概念的方法。它还突出了一些其他有趣的概念,比如通过日常生活来隐喻流水线(pipelining),批处理(batching),fork/join和Amdahl定律【1】。这种现实世界的比喻总是能帮助我理解概念。当我与客户一起完成这一过程时,他们发现对其也有帮助。
说真的我喜欢洗碗。我知道虽然你可能会想“这很奇怪”。任何见过我的人都会告诉你,干的不错。不仅如此,我还是那些特别怪异的人之一,不仅手洗碗碟,而且会在我完成时将它们放入洗碗机进一步清洁。我有一个大学室友吃了芝士意面,碗碟能放在水槽里好几天。在使用这个比喻时,我发现了更多的人有类似的洁癖,我对于自己强迫性的洗碗行为一直很满意。但这个故事并不是关于我是否需要专业的治疗帮助。
每次晚餐后,都有一堆盘子躺着水槽里等待清洁。这个过程很像您在处理流数据时看到的内容。我使用了一个流水线的转换过程:冲洗掉每道菜的碎屑,用肥皂水擦洗它,再次冲洗除去肥皂泡并将碟放入洗碗机。这个过程纯粹是同步的(全部由一个处理器/人员处理)和顺序的(不能重排序),因为我一次只能完成一项任务。我可以单独将盘子送入工作管道,或者我可以无需保证顺序,并尝试通过管道中的每次转换来分批清洗盘子,方法是先清洗所有盘子,然后再擦洗所有盘子,再次冲洗所有盘子然后将它们全部放入洗碗机中作为一个组。在这样做的过程中,我可能会通过增加数据到执行地点(水龙头,擦洗海绵,洗碗机等)的位置来提高我的表现,但是我的表现受到以下事实的约束:我仍然只是一个处理所有工作的处理器。
想象一下,我有一个很喜欢洗碗的好朋友。知道他或她也喜欢洗碗,我邀请那个人到我家去帮助一个晚上。我现在创建了一个线程池 - 多个执行线程,这可能会或可能不会对我想要完成的工作有所贡献。当那个人到达时,我向他们展示了一堆盘子,并询问他们是否会开始清洗它们。我的这位朋友前往水槽,并开始通过流水线工艺工作。现在我产生了异步工作,就好像我在线程池上使用了Java Callable或Runnable一样。如果我站在他们后面等待他们完成并且别做任何事情,我就是一个*线程。我已经产生了要完成的工作,并将它委派给某个执行线程,他们将在任意时间执行工作,但在完成其他任务之前我没有做任何事情,就像Java Future实例一样。不用站在旁边,我可以去喝一杯柠檬水,现在我是非阻塞的(比如Scala Future或者Java 8 CompletableFuture),但对于手头的任务来说也没有什么效率。而我的朋友可能对我变得非常恼火。此外,除非他们比我更有效率地清洗碟子(例如更快的处理器),否则工作速度不会更快。
我真正想要的是让我们两个人都做出贡献,让这项工作更有效,更快地完成。在这一点上,我和我的朋友一起完成这项工作。我的朋友负责从一堆盘子中抓取一个盘子,冲洗并擦洗它。我从那里拿起碟子,再冲洗一遍,放入洗碗机。我现在对这项任务不具有阻塞性和生产力,但通过这种方式进行工作,我们共享了影响我们以最佳方式完成工作的能力的资源。作为负责处理我的朋友委派的工作的执行线程,我必须等待每个盘子的前继,这可能需要花费无限的时间来清理,这取决于它的肮脏程度(CPU密集型工作的本质)。更糟糕的是,我们都必须使用龙头来冲洗碗碟,所以我们对通过通信进行仲裁的状态(水龙头)有一个互斥的操作,就像内核对争用的互斥(互斥)锁的仲裁一样。一台电脑。这是并发性的本质,通常是通过共享可变状态。如果状态(水龙头)是无人参与的(在需要的时候没有被我们两个人使用),我们可以快速完成我们的任务。但如果存在争议(当其他人需要时,我们中的一个人正在使用水龙头),我们一直在等待,直到其他人完成进度。
改善并发性和争用的方法是增加占用空间。 如果我的房子足够大以至于有另一个水槽,我可以拿一堆盘子,独立于我的朋友去做我的工作,那样会更有效率。 可以认为这与使用ForkJoinPool的Java 8或Scala中的并行集合类似。 我通过抓取一堆盘子来分担工作,我和我的朋友正在执行这项工作作为可用的执行核心,当我们需要将碟子重新组装到洗碗机中时,我们将加入。 然而,就像平行收集一样,分叉和连接阶段仍然是并发的 - 我们必须将数据和盘子分开处理,然后我们必须将转换后的数据(洗碗机)中的数据(盘子)。
1390/5000
这种fork操作可以便宜或昂贵,这取决于工作如何分配。如果我只是简单地抓住板的上半部分,这是一个简单的操作。但是,如果我想以某种有序的方式将它们分发给我的朋友和我之间呢?这将会更加昂贵。根据订单,连接点也可能具有类似的成本。这带来了Amdahl法则的成本 - 即使我们对工作进行了并行处理,但如果分叉和加入工作的时间过高,它仍然可能需要比顺序执行时间更长的时间。
我们需要工作尽可能平行以最大化我们的处理效率。为此,我们需要更多地增加我们的足迹。如果我们不知道谁在做什么,或者我们是从一个共同的队列中偷走工作,那么我们就可以把这些盘子交给我的朋友和我来做。如果我有两台洗碗机,我不会在单个连接点上争用。这种占地面积的增加确实意味着额外的成本,但并发性的降低和并行性的增加意味着我的可扩展性已经成为线性的 - 当我添加更多的处理器/接收器/洗碗机时,我增加了可以处理的菜肴数量(以及我可以转换)相同的因素。这种增加可扩展性和效率的价值可能很好地证明商业硬件对我业务的成本增加。
最后,我的最终目标是创建异步,非阻塞和并行执行的应用并同时具有最少并发点。 通过代理这项工作,我将每个盘子(或一批盘子)视为一条消息,我不关心哪条管道进行转换(洗碗)。 这是位置透明性的本质,它允许您启动额外的节点来处理日益增加的工作负载,或在工作减少时关闭冗余节点,从而提高Reactive系统的弹性。 位置透明度也支持韧性,因为您不关心管线失败,并且没有成功转换的消息可以重新处理。 面对不断变化的负载以及系统中各种故障的恢复能力,我们可以确保响应式用户体验。 通过这种方式,消息驱动体系结构是反应式应用程序的精髓。