akka
如前 一篇 文章所述,我们的系统基于Akka 。 在详细介绍我们的解决方案之前,我想先解释一下Akka的含义以及为何如此出色。
Akka是一个工具包,而不是一个框架,您可以简单地使用服务所需的位。 在本系列中,我们将重点介绍核心功能,并且不会涉及Akka Streams , Akka-Http或Akka Cluster (因为它们不在范围内,不是因为它们并不出色)。
Akka提供了不同的抽象来处理并发,并行运算和容错。 这种抽象称为演员模型。 如果您有使用旧Java生态系统的经验,您将知道编写安全且正确的多线程代码有多么困难。 提供像Actors这样的人性化抽象是可与引入时的Java内存管理相比的一场革命。 互联网规模和云计算带来的新挑战需要一种React性的编程方法。 Akka遵循这一理念,使您能够实现事件驱动的架构 。
一路演员
线程是一种昂贵的资源,因此我们需要谨慎使用它们。 在我们等待某些I / O操作响应时阻塞线程确实效率很低。 Actor以不同的方式使用线程,因此它们非常轻量( 每GB堆内存数百万个actor )
Actor很好地融合了面向对象和功能编程原则。 OOP基本上是关于邮件作为艾伦凯,OOP的先驱之一, 指出
很抱歉,我很久以前为该主题创造了“对象”一词,因为它使许多人专注于较小的想法。 最大的想法是“消息传递”。
参与者交换不可变的消息并保持其自身的封装状态:每次交互都需要通过消息来完成。
容错能力
Java中没有清晰,共享的错误处理模型,您可以在几个项目中工作后才能意识到这一点。 它的起源是一个令人困惑的抽象,称为Exception及其子类型Checked和Unchecked Exceptions。 唯一真正的区别是,已检查异常会迫使我们在直接调用方中处理问题,只是它们没有暗示我们应采用的策略。
我强烈推荐这篇文章。 帖子中如此强大的想法之一是,错误不是可恢复的错误。 此报价包含在该帖子的原始来源中 :
我参与了用C ++编写的库的开发。 一位开发人员告诉我,开发人员分为喜欢异常的开发人员和喜欢返回代码的其他开发人员。 在我看来,返回码的朋友赢得了。 但是,我得到的印象是,他们在错误的观点上进行了辩论:异常和返回代码具有同等的表现力,但是不应将其用于描述错误。 实际上,返回码包含诸如
ARRAY_INDEX_OUT_OF_RANGE
定义。 但我想知道:当函数从子例程获取此返回代码时,我的函数将如何React? 它将发送邮件给其程序员吗? 它可以将此代码依次返回给其调用者,但它也不知道如何处理。 更糟糕的是,由于我无法对函数的实现进行假设,因此我必须期望每个子例程都有ARRAY_INDEX_OUT_OF_RANGE
。 我的结论是,ARRAY_INDEX_OUT_OF_RANGE
是一个(编程)错误。 它不能在运行时处理或修复,只能由其开发人员修复。 因此,不应有相应的返回码,而应有断言。
Akka基于以下原则提供了一种处理故障/错误的好方法:
- 单一责任原则:将故障管理委托给主管,从而创建专注的业务参与者。 我们前段时间同意将生命周期管理(例如对象创建)移至工厂,因此对象不负责创建自身。 在发生某些故障之后,恢复或重新启动对象是生命周期管理的一部分,而Akka会强制您将职责移交给主管。 生成的代码将松散耦合且具有高度内聚性。
- 默认恢复策略:在系统出现某些故障后,了解我们的选择非常重要。 数据库暂时关闭了吗? 是否将某些输入与现有数据结合起来创建了不良状态? 那是个错误吗? 在应用某些补丁之前,我们是否需要放弃该特定请求或将系统的那部分弄乱? 这些问题将决定我们对该事件的响应,而Akka提供了一些内置策略 。
override val supervisorStrategy =
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
case _: ArithmeticException => Resume
case _: NullPointerException => Restart
case _: IllegalArgumentException => Stop
case _: Exception => Escalate
}
- 作为头等公民的失败:一些图书馆或生态系统通过晦涩的API隐藏失败。 Akka通常部署在分布式环境中,因此使用诸如网络之类的不可靠资源。 这迫使我们把失败摆在最前面。 即使在“更安全”的环境中,故障也无处不在,因此使用Akka之类的工具包对于创建可靠而强大的软件至关重要。
并发与并行
使用Akka,我们不会直接处理线程,它们被隐藏在抽象层下。 Akka应用程序的主干是Actor系统 :
角色系统是一组角色的层次结构组,它们共享通用配置,例如调度程序,部署,远程功能和地址。 它也是创建或查找参与者的入口。
分派器也是执行上下文,因此最终它是线程池所在的位置。 假设您的应用使用分配了4个线程的单个调度程序。 如您所见,线程是一种稀缺资源,如果我们确实阻止了某些参与者中的I / O或繁重的CPU工作,我们将仅使用其中一个线程,并且您可以看到您的服务将很快用完线程。
解决方案是使用Scala Futures之类的结构包装这些操作,然后为这些任务提供不同的执行上下文。
摘要
Akka是一个很棒的工具包,其中包含针对现代问题的非常适合的抽象。 在下一篇文章中,我们将借助一些代码来了解如何在示例应用程序的约束下协调和监督参与者。
akka