本文由公众号EAWorld翻译发表,转载需注明出处。
作者:Simone Pezzano
译者:白小白
原文:http://t.cn/Ea0Ej0x
原题:A digital symphony — The architecture of API Fortress
依据我对软件工程领域的兴趣来看,外人会认为我的个性十分无趣。
我最喜欢设计和构建的东西,就是作业编排。我乐于设想软件的每个组成部分是如何构成一幅宏大的图景,系统在高负载或者系统失败等各种不同的场景下如何产生反馈。
我时常在脑海里畅想着庞大、复杂的系统是如何处理海量的数据,分发任务并且恢复系统的失败。
我并不十分确定这是否使我有点神经质,但确实有点不正常。
同多数的软件一样,API Fortress第一个迭代版本,是一个原型,对这个版本的乱七八糟我实在一言难尽。但在2016年左右发布的第二个主要迭代版本,让我完全放飞了自我,并受此启发写下这篇文章。
在本文中,我主要是想分享API Fortress平台的计算架构设计。或许在稍后的文章中写一点关于数据架构或者前端的内容。
不能说这一设计是完美的,之所以想拿出来分享,是想抛砖引玉,在读者的团队中引发有益的思考,或许在你的软件里就会有更富灵感的想法诞生。
一、关于微服务架构
微服务在一段时间里都炙手可热。作为旁观者,在过去几年中,我观察到了各种向微服务架构的迁移,有些是必要的,有些则毫无意义。
对我们来说,显然属于前者。
之所以需要采用微服务架构,是因为我们需要将一些具有独特角色的无状态的组件从集群中分离出来。
所有的微服务(橙色的部分)都可以接收从Dashboard/Core -Server产生的任务,Scheduler将把相关任务引入到集群中适合执行该任务的微服务去执行。
相关组件完全解耦,易于监控,支持热更新,并且是无状态的。
从Dashboard/Core-Server仍旧是一大坨软件的集合,而且显然需要保存登陆用户的状态数据。
那么,服务间是如何进行通信的呢?
微服务通信方法1:书信体小说的模式
书信体小说,用书信的形式写成的小说。著名的书信体小说有德国歌德的《少年维特之烦恼》、卢梭《新爱洛绮丝》
可以选择RabbitMQ作为消息队列来处理绝大多数的服务间异步通信。类似如下:
RabbitMQ扮演了4个至关重要的角色:
RabbitMQ将相关微服务从集群中解耦。不同的实现模式,1对1或者1对多的服务消费模式等变得透明。
RabbitMQ实现了一个基于订阅的模型,每个服务可以基于需要来订阅某个消息队列。基于这样的模型,消息成为对话的主体。
由RabbitMQ决定每个消息的目的地,并确保消息可以传递到正确的接收者。如果接收者当前不可用,消息可以等待接收者恢复,或者传递到相似的接收者。同时,RabbitMQ确保由消息所衍生的相关流程成功执行,并从服务消费者处回收通信确认。
在等待通信确认之前,由RabbitMQ决定服务消费者可被分发的消息数量,通过这种方式,编排了并行计算的强度。
微服务通信方法2:更直接的方式
对于异步任务,RabbitMQ工作出色,但在有一些场合,微服务需要对发出请求的代理给予及时的反馈。
因此,在这样一些特定场景下,HTTP API也有相关应用。
二、参与者模型闪亮登场
目前为止,我们所提到的微服务都需要做大量的工作来完成使命。
比如邮件服务,这是一个相当简单的服务,需要对消息做反序列化,确定邮件是否确实有必要发送,接收回执,匹配模板然后再实际发送。不仅如此,邮件服务支持同时发送大量的邮件。对于一个袖珍的微服务来说,相当不错。
再说Core-Server。Core-Server用于实际处理测试过程。包括接收测试代码,加载上下文,初始化测试,启动测试,存储测试结果,传递效果,触发通知等等。
软件从来难言简单,这是因为:
即使一开始是简单的,但很快就会变得一团糟;
静下心来想一下,所谓“简单”的任务需要在背后承担多少工作,把这些工作分成最小的组件,评估一下会出现哪些问题。你将很快意识到,没什么事可以说是简单的。
这就是为什么我引入了参与者模型(Actor Model)。此处需要感谢Lightbend发布的Akka,提供了很棒的实现。
关于参与者模型的内容,可以讲上几天几夜,所以本文中很难深入细节,只是介绍一些特定场景的内容。
简而言之,参与者模型的目标就是把一个大的处理过程切分成多个步骤的流程,流程中的每个操作,就是一个“参与者”。参与者是一个独立的进程,仅通过接收和发送消息来与其他参与者通信,一次只执行一个任务,在问题出现时负责隔绝影响,并由一个监督者(Supervisor)来决定恰当的对策。
一个基本的Core-Server
如前所述,每个参与者一次只做一件事,所以可以支持同种类型的多任务同时执行,你只需要建立更多同样类型的参与者,基于特定的路由逻辑,消息路由会决定哪个参与者的实例应该接收消息。这一机制可以确保十分漂亮的调优能力,并且不止于此。
失败处置同样十分重要。前面提到了监督机制,这里介绍两种实现。
监督机制实现的两个例子
(左图的)I/O类的参与者因为具有与数据库连接的特性,可以共享一个监督者。在本例中,如果在参与者发生数据库连接异常,很大程度上意味着对数据库的连接完蛋了,因此监督者决定重启参与者,希望重新初始化数据库连接可以解决相关问题。如果持续的收到连接失败的反馈,在经过一定数量的尝试之后,监督者将认为参与者已经无法完成其工作,并将其关闭。这是一个很现实的应用场景,即数据库服务失败,而参与者服务仍旧存活。系统接下来将会尝试进行自我修复,采取恰当的对策,而不是持续的失败报错。
(右图的)Execute参与者则恰恰相反,当触发了一个测试代码的语法错误时,参与者将执行恢复操作。但代码是通过消息传递的,并不与参与者的状态相关,因此重启参与者无济于事。
三、参与者级联
最后一步。尽管没有毒品和疯狂的音乐,这仍旧是最可怕,最深不可测而具有哲学色彩的步骤。
通过架构图,读者应该可以推测出,Dashboard和Core-Server都是可以水平扩展的,只要添加实例,就可以增加计算能力。但是增加的比率如何?
基于对架构目前的理解,如果在原来一个Dashboard/Core-Server实例的基础上添加一个实例,并不能实际获得两倍的系统计算能力,因为有诸多的因素需要考量。比如,运气不好的情况下,大部分的核心用户都登陆到了实例一,并抽风一样手动的运行测试。又或者,实例一需要向数据库写入大量的记录,堵塞了参与者的消息收件箱。
更糟糕的情况是,实例二由于某种原因无法与数据库通信,而实例一可以。
长期来看,在原有一个实例的基础上添加新的实例确实会增加一倍的计算能力,但这只是一个近似值。这并不意味着在每天的工作中我们都能够享受到所购买服务的完整体验。
大图警告。下面是一幅巨复杂的架构图,可能没必要,但我还是想借此展示一下我的绘图技巧[手动狗头]。
完整的分布式消息架构
图示中有两个核心。我用蓝色的线代表参与者的消息路由能力(图上没画,但实际上是有的。),你会看到这种能力是如何决定参与者是否实时的执行一个任务,还是埋头工作。
鉴于两个服务基于其状态来交换信息,他们可以知晓另一服务的副本是否状态良好,如果是,就将工作分发过去。
忽略通信开销的话,你可以通过平均分布工作负载来完整的利用当前设置的全部计算能力。
四、结语
声明:在开发API Fortress的过程中,没有任何演员(Actor)或者兔子(Rabbit)受到伤害。
我呢,更年长,也更加睿智了。仍旧畅想着下一个小目标。
关于EAWorld:微服务,DevOps,数据治理,移动架构原创技术分享,长按二维码关注