关于Akka

关于Akka

马草原 442 2021-08-13

关于Akka

Akka是一个用于设计在多核和不同网络之间,具有扩展性的,弹性系统的开源库。Akka可以让你集中精力在业务需求上而不是写低级的代码,并且提供提供可靠性、故障冗余、高性能的特点。

在分布式系统中,必须要能处理组件故障不响应问题,消息丢失、网络抖动、网络延迟问题,这些问题通常会时不时的出现。

面对这些现实状况,Akka提供了:

  • 多线程方式,但不使用低级的并发设计如原子性、锁——减轻你去思考内存可见性的问题
  • 在不同系统和组件之间透明的远程通信——较少你编写和维护网络代码
  • 集群高可用架构、可以按需弹性扩容和缩容——让你实现真正的“响应式”系统

Akka使用actor模型提供了一个抽象层让你更容易的编写正确的并发和并行的分布式系统,在所有的Akka库中都有使用统一的actor模型使用方法,通过学习Akka和使用Actor模型,你将会获得一个非常棒的解决分布式/并发系统问题的工具集。

当前系统的问题

在单线程的环境中,对象一般都能正常运行,但是在多线程状态下,多个线程进入对象的同一个方法中,对象就不能保证数据的不可变性。通常我们的做法是加锁,但是:

  • 锁限制了并发性,锁会很消耗CPU的资源,要求CPU挂起然后恢复线程。
  • 调用线程会被阻塞,这时候调用线程就不能处理其它工作。
  • 锁引入了一个新的威胁:死锁
  • 本地锁使用起来不会出太大问题,但是分布式锁,一方面严重影响性能,另外限制了系统的扩展性。

在JVM系统中,有些数据被多线程访问的情况,我们可能会加上volatile关键字或者使用atomic封装一下。其它的则是加锁,这些操作的目的是为了让CPU在操作数据的时候能直接的将数据写入内存或者直接从内存中读取,跳过线程的本地变量。

想象一个多核的系统,这种CPU核与核之间的通信与分布式系统之间的通信是不是有点像?如果我们在写Java程序的时候,所有的数据都标记为volatile,结果就是系统的性能会降低很多。

JVM的内存模型不是一个很好的解决方式。

现在我们去调用方法都会觉得理所当然,但是在多线程的环境下,异步调用就会出现问题。

  • 代理线程将任务放到队列中
  • 工作线程从队列中取出任务然后开始执行

当任务失败的时候,发生了异常,异常只能被工作线程感知到,而最初的任务发起者,真正调用执行任务的并不知道。

akka1

异常可以分为这两类:

  • 可以被工作线程捕获到的,捕获之后发送通知给调用者
  • 工作线程未捕获到的或者无法捕获的异常发生,导致工作线程直接不可用。那么调用者就不会知道到底发生了什么

Akka的解决方案

使用协作的实体对象,响应信号,改变状态,互相发送信号驱动整个应用向前

Akka使用在actor之间相互发送消息,而不是方法调用。在发送消息的时候actor并不会被阻塞,因此可以实现很高的吞吐量。

发送消息与方法调用的区别是,方法调用的时候需要等待另外一个方法返回结果,而发送消息,在工作actor完成任务后,回复一个消息给调用者,整个过程不会被阻塞。

发送消息和处理有序的,一次只响应一个消息,一次只发送一个消息,actor可以理解为一个Java对象,响应消息如果调用一个方法,不同的actor之间可以同时的处理消息。

image-1692775694054

  1. Actor添加消息到队列的末尾
  2. 如果Actor当前没有执行的任务,它就被标记为准备被调度
  3. 调度器获得actor然后开始执行它
  4. Actor从队列的头部获取消息
  5. Actor修改内部的状态,发送消息给其它的Actor
  6. Actor进入未调度状态,没有执行的任务

想要做到如上步骤,需要提供:

  • 邮箱:mailbox ,存放消息的地方
  • 行为: behavior 。 actor的状态,内部的变量
  • 消息: message。 代表信号,类似于方法调用的参数
  • 执行环境:execution environment
  • 地址: 分布式需要

处理错误

  • 第一种,actor自己捕获到了当前的异常,然后发送给调用者具体的消息。这与正常的捕获异常进行处理美业太大的区别
  • 第二周,actor遇到了未知的错误,然后错误actor将错误传递给自己的父actor。一直向上找到能处理当前错误的actor为止

在父actor捕获到错误之后,它可以控制是否要重启出现错误的actor,还是忽略错误继续执行,或者其它的策略。

Quickstart For Java

https://developer.lightbend.com/guides/akka-quickstart-java/