原文:https://doc.akka.io/docs/akka/current/guide/introduction.html?language=scalahtml
Akka是一套开放源码库,用于设计可伸缩的、具备弹性的系统,跨越处理器内核和网络。Akka容许您专一于知足业务需求,而不是编写低级别代码来提供可靠的行为、容错和高性能。编程
许多常见作法和使用的编程模型并不能解决和设计现代计算机体系结构所固有的重要挑战。为了解决那些问题,分布式系统必须应对组件崩溃不能响应,消息丢失时没有轨迹,网络延迟波动。这些问题常常发生在精心管理的数据中心环境中-甚至在虚拟化架构中更是如此。后端
为了帮助你处理这些现实,Akka提供了:缓存
Akka对Actor模型的使用提供了一个抽象级别,使得编写正确的并发、并行和分布式系统变得更容易。角色模型跨越了所有Akka库,为您提供了一种理解和使用它们的一致方式。所以,Akka提供了深度的集成,您没法经过挑选库来解决单个问题并尝试将它们组合在一块儿来实现。安全
经过学习Akka和如何使用Actor模型,您将得到一组普遍而深刻的工具,这些工具能够在统一的编程模型中解决困难的分布式/并行系统问题,在这个模型中,一切都紧密而有效地结合在一块儿。网络
若是这是您第一次体验Akka,咱们建议您从运行一个简单的HelloWorld项目开始。有关下载和运行HelloWorld示例的说明,请参阅QuickStart指南。QuickStart指南向您介绍了如何定义角色系统、角色和消息以及如何使用测试模块和日志记录的示例代码。在30分钟内,您应该可以运行HelloWorld示例并了解它是如何构造的。数据结构
本入门指南提供了下一级别的信息。它涵盖了为何角色模型适合现代分布式系统的须要,并包括一个教程,将帮助您进一步了解Akka。主题包括:多线程
角色模型是几十年前由卡尔·休伊特(CarlHewitt)提出的,做为在高性能网络中处理并行处理的一种方式-这种环境在当时是不可用的。今天,硬件和基础设施能力已经遇上并超过了休伊特的设想。所以,构建具备严格需求的分布式系统的组织遇到了传统的面向对象编程(OOP)模型没法彻底解决的挑战,但它能够从角色模型中受益。架构
今天,角色模型不只被认为是一种很是有效的解决方案-它已经在世界上一些最苛刻的应用程序的生产中获得了证实。为了突出角色模型解决的问题,本主题讨论传统编程与现代多线程多CPU体系结构的现实之间的不匹配:并发
OOP的核心支柱是封装。封装规定对象的内部数据不能直接从外部访问;它只能经过调用一组精心设计的方法来修改。该对象负责公开保护其封装数据的不变性质的安全操做。
例如,对有序二叉树实现的操做必须不容许违反树的顺序不变。调用方但愿顺序是完整的,而且当查询树以获取特定的数据时,他们须要可以依赖于这个约束。
当咱们分析OOP运行时行为时,咱们有时会绘制一个显示方法调用交互的消息序列图。例如:
不幸的是,上面的图表不能准确地表示执行过程当中实例的生命线。实际上,一个线程执行全部这些调用,执行发生在调用方法的在同一个线程上。用执行线程更新关系图,以下所示:
当您尝试对多线程所发生的状况进行建模时,这种澄清的意义就变得清楚了。忽然间,咱们画得很整齐的图表变得不够用了。咱们能够尝试说明访问同一个实例的多个线程:
有一段执行,两个线程进入相同的方法。不幸的是,对象的封装模型并不能保证该部分中发生了什么。这两个调用的指令能够任意的方式交织在一块儿,这就消除了保持变量不变的任何但愿,而无需在两个线程之间进行某种类型的协调。如今,想象一下这个问题因为存在许多线程而变得更加复杂。
解决此问题的常见方法是围绕这些方法添加一个锁。虽然这能够确保在任何给定的时间最多有一个线程进入该方法,但这是一种代价很高的策略:
这些现实致使了一种不成功的局面:
此外,锁只能在本地正常工做。当涉及到跨多台计算机进行协调时,惟一的选择是分布式锁。不幸的是,分布式锁的效率比本地锁低几个数量级,而且一般对扩展施加一个硬限制。分布式锁协议须要在多台计算机上经过网络进行屡次通讯,所以延迟时间太高。
在面向对象语言中,咱们一般不多考虑线程或线性执行路径。咱们常常设想一个系统是一个对象实例网络,它对方法调用做出反应,修改它们的内部状态,而后经过方法调用相互通讯,从而推进整个应用程序状态向前:
然而,在多线程分布式环境中,实际发生的事情是线程经过如下方法调用“遍历”这个对象实例网络。所以,真正驱动线程执行的是:
总的来讲:
80‘-90年代的编程模型认为写入变量意味着直接写入内存位置(这可能模糊了局部变量可能只存在于寄存器中)。对于现代架构,若是咱们简化了一些事情,那么cpu就会编写缓存行而不是直接写内存。大多数缓存都是本地cpu核心,也就是说,一个核心写的都不能被另外一个内核所显示。为了使本地更改可见于另外一个核心,所以对于另外一个线程来讲,缓存行须要被发送到其余核心缓存。
在jvm上,咱们必须显式地表示经过使用volatile标记或原子包装来跨线程共享的内存位置。不然,咱们只能在锁定的部分访问它们。为何咱们不把全部变量标记为volatile?由于传送高速缓存线跨越核心是一个很是昂贵的操做!这样作会隐式地将涉及额外工做所涉及的核心拖放,从而致使缓存一致性协议(协议cpu用于传输主内存和其余cpu之间缓存线)瓶颈。结果是减速幅度。
即便对于了解这种状况的开发人员来讲,要肯定哪些内存位置应该标记为volatile,或者使用哪些原子结构是一种黑暗艺术。
总的来讲:
今天,咱们常常用call stacks来作理所固然的事。可是,它们是在一个并行编程并不重要,多cpu系统并不常见的时代发明的。call stacks不会跨线程,不模拟异步调用链。
当线程打算将任务委托到“后台”时,会出现问题。实际上,这实际上意味着委托另外一个线程。这不能是简单的方法/函数调用,由于调用对线程是严格的本地调用。一般发生的是,调用者将对象放在一个由工做线程共享的内存位置(“被调用者”),反过来,它会在某些事件循环中从新选择它。这样可让调用方线程继续运行并执行其余任务。
第一个问题是,如何才能通知“caller”完成任务?可是当任务与异常失败时会出现更严重的问题。异常传播到哪里?它可能将传播到工做线程的异常处理程序,彻底忽略实际的调用方是谁:
这是一个严重的问题,工做线程如何处理这种状况?它极可能没法解决这个问题,由于它一般忽略了失败任务的目的。须要通知“调用方”线程,可是没有一个调用堆栈以异常解除。故障通知只能经过侧通道来完成,例如,在调用“调用方”线程时,将错误代码放在另外一个位置上,不然会预期结果一旦就绪。若是此通知未到位,“调用方”将永远不会通知失败,任务丢失!这与网络系统工做相似,由于在没有任何通知的状况下,消息/请求能够丢失/失败。
当事情发生错误时,这种坏状况会变得更糟,而一个由线程支持的遇到了一个bug,最终会出现没法恢复的情况。例如,由bug引起的内部异常,将其抛到root线程,并使线程关闭。这当即引起了一个问题:谁应该从新启动线程托管服务的正常操做,以及如何恢复到一个已知的状态?乍一看,这彷佛是能够控制的,可是咱们忽然面临一个新的、意想不到的现象:线程当前正在运行的实际任务再也不位于任务从(一般是队列)中的共享内存位置。事实上,因为异常到达顶部,解除全部调用堆栈,任务状态彻底丢失!尽管这是本地通讯,但没有联网(预计消息损失将被预期),咱们已经失去了一个消息。
总结:
接下来,让咱们看看如何使用角色模型来克服这些挑战。
有什么讨论的内容,能够加我公众号: