原文:https://www.reactivemanifesto.org/glossaryhtml
牛津词典把“asynchronous(异步的)”定义为“不一样时存在或发生的”。 在本宣言的上下文中, 咱们的意思是: 在来自客户端的请求被发送到了服务端以后, 对于该请求的处理能够发生这以后的任意时间点。 对于发生在服务内部的执行过程, 客户端不能直接对其进行观察, 或者与之同步。 这是同步处理(synchronous processing)的反义词, 同步处理意味着客户端只能在服务已经处理完成该请求以后, 才能恢复它本身的执行。前端
当某个组件正竭力维持响应能力时, 系统做为一个总体就须要以合理的方式做出反应。 对于正遭受压力的组件来讲, 不管是灾难性地失败, 仍是不受控地丢弃消息, 都是不可接受的。 既然它既不能(成功地)应对(压力), 又不能(直接地)失败, 那么它就应该向其上游组件传达其正在遭受压力的事实, 并让它们(该组件的上游组件)下降负载。 这种回压(back-pressure)是一种重要的反馈机制, 使得系统得以优雅地响应负载, 而不是在负载下崩溃。 回压能够一路扩散到(系统的)用户, 在这时即时响应性可能会有所下降, 可是这种机制将确保系统在负载之下具备回弹性 , 并将提供信息,从而容许系统自己经过利用其余资源来帮助分发负载,参见弹性。react
当前计算机为反复执行同一项任务而进行了优化: 在(CPU的)时钟频率保持不变的状况下, 指令缓存和分支预测增长了每秒能够被处理的指令数。 这就意味着,快速连续地将不一样的任务递交给相同的CPU核心,将并不能获益于本有可能获得的彻底(最高利用率的)性能: 若是有可能,咱们应该这样构造应用程序, 它的执行逻辑在不一样的任务之间交替的频率更低。 这就意味着能够成批地处理一组数据元素, 这也可能意味能够在专门的硬件线程(指CPU的逻辑核心)上执行不一样处理步骤。算法
一样的道理也适用于对于须要同步和协调的外部资源的使用。 当从单一线程(即CPU核心)发送指令, 而不是从全部的CPU核心争夺带宽时, 由持久化存储设备所提供的I/O带宽将能够获得显著提升。 使用单一入口的额外的效益,即多个操做能够被从新排序, 从而更好地适应设备的最佳访问模式(当今的存储设备的线性存取性能要优于随机存取的性能)。数据库
此外, 批量处理还提供了分摊昂贵操做(如I/O)或者昂贵计算的成本的机会。 例如, 将多个数据项打包到同一个网络数据包或者磁盘存储块中, 从而提升效能并下降使用率。编程
咱们所描述的是一个模块化的软件架构, 它(实际上)是一个很是古老的概念, 参见Parnas(1972)。 咱们使用“组件(component)”(参见 C.2.8)这个术语, 由于它和“隔间(compartment)”联系紧密, 其意味着每一个组件都是自包含的、封闭的并和其余的组件相隔离。 这个概念首先适用于系统的运行时特征, 可是它一般也会反映在源代码的模块化结构中。 虽然不一样的组件可能会使用相同的软件模块来执行通用的任务, 可是定义了每一个组件的顶层行为的程序代码则是组件自己的一个模块。 组件边界一般与问题域中的有界上下文(BoundedContext)紧密对齐。 这意味着,系统设计倾向于反应问题域, 并所以在保持隔离的同时也更加容易演化。 消息协议为多个有界上下文(组件)之间提供了天然的映射和通讯层。后端
将任务异步地委托给另外一个#组件意味着该任务将会在另外一个组件的上下文中被执行, 举几个可能的状况: 这个被委托的内容甚至可能意味着运行在不一样的错误处理上下文里,属于不一样的线程,来自不一样的进程,甚至在不一样的网络节点上。 委托的目的是将处理某个任务的职责移交给另一个组件, 以便发起委托的组件能够执行其余的处理、 或者有选择性地观察被委托的任务的进度, 以防须要执行额外的操做(如处理失败或者报告进度)。缓存
弹性意味着当资源根据需求按比例地减小或者增长时, 系统的吞吐量将自动地向下或者向上缩放, 从而知足不一样的需求。系统须要具备可伸缩性, 以使得其能够从运行时动态地添加或者删除资源中获益。 所以,弹性是创建在可伸缩性的基础之上的, 并经过添加自动的资源管理概念对其进行了扩充。网络
失败是一种服务内部的意外事件, 会阻止服务继续正常地运行。 失败一般会阻止对于当前的、 并可能全部接下来的客户端请求的响应。 和错误相对照, 错误是意料之中的,而且针各类状况进行了处理( 例如, 在输入验证的过程当中所发现的错误), 将会做为该消息的正常处理过程的一部分返回给客户端。 而失败是意料以外的, 而且在系统可以恢复至(和以前)相同的服务水平以前,须要进行干预。 这并不意味着失败老是致命的(fatal), 虽然在失败发生以后, 系统的某些服务能力可能会被下降。 错误是正常操做流程预期的一部分, 在错误发生以后, 系统将会当即地对其进行处理, 并将继续以相同的服务能力继续运行。架构
失败的例子有: 硬件故障、 因为致命的资源耗尽而引发的进程意外终止,以及致使系统内部状态损坏的程序缺陷。
隔离能够定义为在时间和空间上的解耦。 在时间上解耦意味着发送者和接收者能够拥有独立的生命周期—— 它们不须要同时存在,从而使得相互通讯成为可能。 经过在组件之间添加异步边界, 以及经过消息传递实现了这一点。 在空间上解耦(定义为位置透明性)意味着发送者和接收者没必要运行在同一个进程中。 无论运维部门或者运行时自己决策的部署结构是多么的高效——在应用程序的生命周期以内,这一切均可能会发生改变。
真正的隔离超出了大多数面向对象的编程语言中的常见的封装概念, 并使得咱们能够对下述内容进行划分和遏制:
组件之间的强隔离性是创建在明肯定义的协议的通讯之上的, 并支持解耦, 从而使得系统更加容易被理解、扩展、测试和演化。
弹性系统须要可以自适应, 并不间断地对需求的变化作出反应。 它们须要优雅而高效地扩大或者缩减(部署)规模。 极大地简化这个问题的一个关键洞察是:认识到咱们一直都在处理分布式计算。 不管咱们是在一台单独的(具备多个独立CPU,并经过快速通道互联(QPI)通讯的)节点之上, 仍是在一个(具备多台经过网络进行通讯的独立节点的)机器集群之上运行咱们的系统, 都是如此。 拥抱这一事实意味着, 在多核心之上进行垂直缩放和在集群之上进行水平伸缩并无什么概念上的差别。
若是咱们全部的组件都支持移动性, 而本地通讯只是一项优化。 那么咱们根本不须要预先定义一个静态的系统拓扑和部署结构。 能够将这个决策留给运维人员或者运行时, 让他(它)们其能够根据系统的使用状况来对其进行调整和优化。
这种经过异步的消息传递实现的在空间上的(请参见隔离的定义)解耦, 以及将运行时实例和它们的引用解耦,就是咱们所谓的位置透明性。 位置透明性一般被误认为是“透明的分布式计算”, 然而实际上偏偏相反: 咱们拥抱网络, 以及它全部的约束——如部分失败、 网络分裂、 消息丢失, 以及它的异步性和与生俱来的基于消息的性质,并将它们做为编程模型中的一等公民, 而不是尝试在网络上模拟进程内的方法调用(如RPC、XA等)。 咱们对于位置透明性的观点与Waldo等人著的A Note On Distributed Computing 中的观点彻底一致。
消息是指发送到特定目的地的一组特定数据, 事件是组件在达到了某个给定状态时所发出的信号。 在消息驱动的系统中, 可寻址的接收者等待消息的到来, 并对消息作出反应, 不然只是休眠(即异步非阻塞地等待消息的到来)。 而在事件驱动的系统中, 通知监听器被附加到了事件源, 以便在事件被发出时调用它们(指回调)。 这也就意味着, 事件驱动的系统关注于可寻址的事件源, 而消息驱动的系统则着重于可寻址的接收者。 消息能够包含编码为它的有效载荷的事件。
因为事件消耗链的短暂性, 因此在事件驱动的系统中很难实现回弹性 : 当处理过程已经就绪,监听器已经设置好, 以便于响应结果并对结果进行变换时, 这些监听器一般都将直接地处理成功或者失败, 并向原始的客户端报告执行结果。(这些监听器)响应组件的失败, 以便于恢复它(指失败的组件)的正常功能,而在另一方面, 须要处理的是那些并无与短暂的客户端请求捆绑在一块儿的, 可是影响了整个组件的健康情况的失败。
在并发编程中, 若是争夺资源的线程并无被保护该资源的互斥所无限期地推迟执行, 那么该算法则被认为是非阻塞的。 在实践中, 这一般缩影为一个 API, 当资源可用时, 该API将容许访问该资源, 不然它将会当即地返回, 并通知调用者该资源当前不可用, 或者该操做已经启动了,可是还没有完成。 某个资源的非阻塞 API 使得其调用者能够进行其余操做, 而不是被阻塞以等待该资源变为可用。 此外,还能够经过容许资源的客户端注册, 以便让其在资源可用时,或者操做已经完成时得到通知。
协议定义了在组件之间交换或者传输消息的方法与规范。 协议由会话参与者之间的关系、 协议的累计状态以及容许发送的消息集所构成。 这意味着, 协议描述了会话参与者在什么时候能够发送什么样的消息给另一个会话参与者。 协议能够按照其消息交换的形式进行分类, 一些常见的类型是:请求——响应模式、 重复的请求——响应模式(如 HTTP 中)、 发布——订阅模式、 以及(反应式)流模式(同时包含(动态地)推送和拉取)。
和本地编程接口相比, 协议则更加通用, 由于它能够包含两个以上的参与者, 而且能够预见到消息交换的进展, 而接口仅仅指定了调用者和接收者之间每次一次的交互过程。
须要注意的是, 这里所定义的协议只指定了可能会发送什么样的消息, 而不是它们应该如何被编码、解码(即编解码), 并且对于使用该协议的组件来讲,传输机制是透明的。
在不一样的地方同时地执行一个组件被称为复制。 这可能意味着在不一样的线程或者线程池、 进程、 网络节点或者计算中心中执行。 复制提供了可伸缩性(传入的工做负载将会被分布发到跨组件的多个实例中) 以及回弹性 (传入的工做负载将会被复制到多个并行地处理相同请求的多个实例中)。 这些方式能够结合使用, 例如, 在确保该组件的某个肯定用户的全部相关事务都将由两个实例执行的同时, 实例的总数则又根据传入的负载而变化,(参见 弹性)。
在复制有状态的组件时,必需要当心同步副本之间的状态数据,不然该组件的客户则须要知道同步的模式,而且还违反了封装的目的。一般,同步方案的选择须要在一致性和可用性之间进行权衡,若是容许被复制的副本能够在有限的时间段内不一致(最终一致性),那么将会获得最佳的可用性,同时,完美的一致性则要求全部的复制副本以一种步调一致(lock-step)的方式来推动它们的状态。在这两种“极端”之间存在着一系列的可能解决方案,因此每一个组件都应该选择最适合于其须要的方式。
组件执行其功能所依赖的一切都是资源, 资源必需要根据组件的须要而进行调配。 这包括 CPU 的分配、 内存以及持久化存储以及网络带宽、 内存带宽、 CPU 缓存、 内部插座的 CPU 连接、 可靠的计时器以及任务调度服务、 其余的输入和输出设备、 外部服务(如数据库或者网络文件系统等)等等。 全部的这些资源都必需要考虑到 弹性和回弹性 , 由于缺乏必需的资源将妨碍组件在被须要时发挥正常做用。
一个系统经过利用更多的计算资源来提高其性能的能力, 是经过系统吞吐量的提高比上资源所增长的比值来衡量的。 一个完美的可伸缩性系统的特色是这两个数字是成正比的。 所分配的资源加倍也将使得吞吐量翻倍。 可伸缩性一般受限于系统中所引入的瓶颈或者同步点, 参见Amdahl 定律以及 Gunther 的通用可伸缩模型( Amdahl’s Law and Gunther’s Universal Scalability Model)。
系统为它的用户或者客户端提供服务。 系统可大可小, 它们能够包含许多组件或者只有少数几个组件(参见 C.2.4)。 系统中的全部组件相互协做,从而提供这些服务。 在不少状况下, 位于相同系统中的多个组件之间,具备某种客户端——服务端的对应关系(例如,考虑一下,前端组件依赖于后端组件)。 一个系统中共享着一种通用的回弹性模型, 意即, 某个组件的失败将会在该系统的内部获得处理, 并由一个组件委托给另一个组件。 若是系统中的某系列组件的功能、资源或者失败模型都和系统中的其他部分相互隔离, 那么将这一系列的组件看做是系统的子系统将更有利于系统设计。
咱们使用这个术语来非正式地指代某个服务的任何消费者,能够是人类或者其余服务。