开篇

Akka框架作为Akka是一个以Actor模型为基础构建的基于事件的并发编程框架,底层使用Scala语言实现,提供Java和Scala两种API。通过Actor能够简化锁以及线程管理,Actor具有以下的特性:

  • 提供了一种高级的抽象,能够封装状态和操作。简化并发应用的开发。
  • 提供异步非阻塞/高性能的事件驱动模型,避免锁的滥用。
  • 超级轻量级的线程事件处理能力。

由于Akka框架底层使用Scala语言实现,另外存在一个“月经”问题(每次RPC请求内部一个Int类型值+1,当服务长时间不重启导致Int越界),因此使用JAVA基于Actor模型去现实了一个RPC。从大致上来说,可以将该RPC认为是一个JAVA版的Akka,不过,在实现的过程中一些处理方式又不尽相同。

ReleaseNote

  • 使用protostuff序列化(.proto文件编写恶心,与Protocol Buffer性能几乎接近)
  • 使用Netty进行通讯(同节点RPC不走网络,直接入收件箱队列);
  • 路由策略:随机路由、指定Key路由、资源Id路由、强制路由
  • 使用ZK进行集群状态管理
  • 使用自定义注解进行服务注册及辅助控制(线程数量、方法名称设置等)

源码地址:

https://github.com/bossfriday/actor-rpc 原型项目(目前已不维护)
https://github.com/bossfriday/bossfriday-nubybear/tree/master/cn.bossfriday.common/src/main/java/cn/bossfriday/common

当前版本已经经过了很多商用项目的检验(支持私有部署的IM系统),并且表现良好,Akka自增Int溢出的月经问题再也没有出现过,并且基于此还写了一个使用示例项目:详见:https://github.com/bossfriday/bossfriday-nubybear。另外为了实现同节点RPC不走网络(原生Akka并不支持),实现了一个AbstractServiceBootstrap,这个Bootstrap可以理解为一个容器,启动时将所有PluginElements中的BaseUntypedActor加载到容器中,同时完成服务注册。大家可以把每一个PluginElement认为是一个微服务(每个微服务可以含有N个Actor,一个系统由N个微服务构成),把这个容器类比为tomcat,由于所有的Actor均运行于该容器内(同一进程下),再配合邮箱机制(A给B发RPC可以类比发邮件的过程:先进A的发现箱,B收到了A发来的邮件进B的收件箱)就可以做到同节点的ActorRPC不走网络。当然你也可以把每个微服务进行独立部署(在service-config.xml去配置即可,有时确实有这种强需求,例如:接入服务需要部署在DMZ区,其他服务部署在内网区。不过这种问题完全可以通过在DMZ部署一个代理去解决),不过我建议的方式还是在每台服务均部署全量服务,这样可以去尽量的吃同节点RPC不走网络的红利,另外这种方式也能最大化的利用服务器的硬件资源,因为很多时候,业务上就导致了各类的请求比率差异很大,这样横向扩容的时候,单独增加机器即可,而不用考虑,业务A扩几台,然后依赖的业务B需要对等扩充几台。

1. Actor模型简介背景

随着业务的发展,现代分布式系统对于垂直扩展、水平扩展、容错性的要求越来越高。常见的一些编程模式已经不能很好的解决这些问题。

解决并发问题核心是并发线程中的数据通讯问题,一般有两种策略:

  • 共享数据
  • 消息传递
共享数据

基于 JVM 内存模型的设计,需要通过加锁等同步机制保证共享数据的一致性。但其实使用锁对于高并发系统并不是一个很好的解决方案:

运行低效,代价昂贵,非常限制并发。
调用线程会被阻塞,以致于它不能去做其他有意义的任务。
很难实现,比较容易出现死锁等各种问题。

消息传递

与共享数据方式相比,消息传递机制的最大优点就是不会产生竞争。实现消息传递的两种常见形式:

  • 基于 Channel 的消息传递
  • 基于 Actor 模型的消息传递

2. Actor 模型

Actor 的基础就是消息传递,一个 Actor 可以认为是一个基本的计算单元,它能接收消息并基于其执行运算,它也可以发送消息给其他 Actor。Actors 之间相互隔离,它们之间并不共享内存。

Actor 本身封装了状态和行为,在进行并发编程时,Actor 只需要关注消息和它本身。而消息是一个不可变对象,所以 Actor 不需要去关注锁和内存原子性等一系列多线程常见的问题。

所以 Actor 是由状态(State)、行为(Behavior)和邮箱(MailBox,可以认为是一个消息队列)三部分组成:

  • 状态:Actor 中的状态指 Actor 对象的变量信息,状态由 Actor 自己管理,避免了并发环境下的锁和内存原子性等问题。
  • 行为:Actor 中的计算逻辑,通过 Actor 接收到的消息来改变 Actor 的状态。
  • 邮箱:邮箱是 Actor 和 Actor 之间的通信桥梁,邮箱内部通过 FIFO(先入先出)消息队列来存储发送方 Actor 消息,接受方 Actor 从邮箱队列中获取消息。

2.1 模型概念

java rpc 数据量 java rpa_开发语言


可以看出按消息的流向,可以将 Actor 分为发送方和接收方,一个 Actor 既可以是发送方也可以是接受方。

另外我们可以了解到 Actor 是串行处理消息的,另外 Actor 中消息不可变。

Actor 模型特点
  • 对并发模型进行了更高的抽象。
  • 使用了异步、非阻塞、高性能的事件驱动编程模型。
  • 轻量级事件处理(1 GB 内存可容纳百万级别 Actor)。
  • 简单了解了 Actor 模型,我们来看一个基于其实现的框架。
Akka Actor

Akka 是一个构建在 JVM 上,基于 Actor 模型的的并发框架,为构建伸缩性强,有弹性的响应式并发应用提高更好的平台。

ActorSystem

ActorSystem 可以看做是 Actor 的系统工厂或管理者。主要有以下功能:

  • 管理调度服务
  • 配置相关参数
ActorRef

ActorRef 可以看做是 Actor 的引用,是一个 Actor 的不可变,可序列化的句柄(handle),它可能不在本地或同一个 ActorSystem 中,它是实现网络空间位置透明性的关键设计。

ActorRef 最重要功能是支持向它所代表的 Actor 发送消息:

Dispatcher 和 MailBox(queue)

ActorRef 将消息处理能力委派给 Dispatcher,实际上,当我们创建 ActorSystem 和 ActorRef 时,Dispatcher 和 MailBox 就已经被创建了。

Dispatcher 从 ActorRef 中获取消息并传递给 MailBox,Dispatcher 封装了一个线程池,之后在线程池中执行 MailBox。

流程

通过了解上面的一些概念,我们可以 Akka Actor 的处理流程归纳如下:

  • 创建 ActorSystem
  • 通过 ActorSystem 创建 ActorRef,并将消息发送到 ActorRef
  • ActorRef 将消息传递到 Dispatcher中
  • Dispatcher 依次的将消息发送到 Actor 邮箱中
  • Dispatcher 将邮箱推送至一个线程中
  • 邮箱取出一条消息并委派给 Actor 的 receive 方法

3. 总结

在Actor模型中,一切都可以抽象为Actor。
而Actor是封装了状态和行为的对象,他们的唯一通讯方式就是交换消息,交换的消息放在接收方的邮箱(Inbox)里。也就是说Actor之间并不直接通信,而是通过消息来相互沟通,每一个Actor都把它要做的事情都封装在了它的内部。

每一个Actor是可以有状态也可以是无状态的,理论上来讲,每一个Actor都拥有属于自己的轻量级线程,保护它不会被系统中的其他部分影响。因此,我们在编写Actor时,就不用担心并发的问题。

通过Actor能够简化锁以及线程管理,Actor具有以下的特性:

  • 提供了一种高级的抽象,能够封装状态和操作。简化并发应用的开发。
  • 提供异步非阻塞/高性能的事件驱动模型,避免锁的滥用。
  • 超级轻量级的线程事件处理能力。