Akka系列---什么是Actor
本文已.Net语法为主,同时写有Scala及Java实现代码
严肃的说,演员是一个广泛的概念,作为外行人我对Actor 模型的定义:
Actor是一个系统中参与者的虚拟人物,Actor与Actor之间是可以相互沟通,所有的沟通都是通过Message
比如说一个呼叫中心,数以百万计的客户可能会呼叫一个1-800的号码,并与许多可能的客户服务代表进行对话
向这样类型的交互可以使用演员建模
在Actor模型中,一切都是Actor,就想在面向对象编程(OOP)中一切都是"Object"一样.在OOP中,你需要使用类和对象进行建模,在Akka中,你可以使用Actor和Message进行建模
以下是Akka中基本例子
.Net
using System;
using Akka.Actor; namespace ActorsSendingMessages
{
/// <summary>
/// 在Akka.NET中Actor都继承于UntypedActor。/// </summary>
public class BasicActor : UntypedActor
{
protected override void PreStart()
{ } protected override void PreRestart(Exception reason, object message)
{ } protected override void OnReceive(object message)
{
//handle messages here
} protected override void PostStop()
{ } protected override void PostRestart(Exception reason)
{ }
}
}
Scala
class BasicActor extends UntypedActor{ override def preStart(): Unit = super.preStart() override def postStop(): Unit = super.postStop() override def preRestart(reason: Throwable, message: Option[Any]): Unit = super.preRestart(reason, message) override def postRestart(reason: Throwable): Unit = super.postRestart(reason) override def onReceive(message: Any): Unit = { } }
Java
import akka.actor.UntypedActor;
import scala.Option; /**
* Created by Lei on 2017-4-25.
*/
public class BasicActor extends UntypedActor {
@Override
public void onReceive(Object message) throws Throwable { } @Override
public void preStart() throws Exception {
super.preStart();
} @Override
public void postStop() throws Exception {
super.postStop();
} @Override
public void preRestart(Throwable reason, Option<Object> message) throws Exception {
super.preRestart(reason, message);
} @Override
public void postRestart(Throwable reason) throws Exception {
super.postRestart(reason);
} }
什么是Message
可能已经注意到了BasicActor中定义的方法
.Net
protected override void OnReceive(object message)
{
//handle messages here
}
Scala
override def onReceive(message: Any): Unit = { }
Java
@Override
public void onReceive(Object message) throws Throwable { }
该OnReceive方法是Actor接收Message的地方,在Akka中,一个Message就是一个Object,Message可以是任意类型的实例
Actor通常只能处理特定类型的Message,如果Actor收到无法处理的Message,则不会触发任何异常,一般只是将Message标记为"unhandled"
Message是不变的
什么是"不可变"的对象呢?
不可变对象:是一个对象中的状态(即该对象在内存中的内容)一旦被实例化就不能修改
类似于String类型(.net中 string)
不可变的Message本质上是线程安全的.没有线程可以修改不可变Message的内容,所以接收到原始Message的第二个线程不必担心以前的线程有修改Message的可能
因此,在Akka中,所有消息都是不可变的,线程也是安全的,这也是为什么可以让上千万Akka的Actor同时处理消息的原因之一.正式不可变的Message消除了同步机制和其他不必要的代码
Actor的行为
我们大概了解了Actor和Message,那这些如何用?
Actor通过Message进行通信
在OOP中,对象通过函数调用与其他对象进行通信.A类在B类上调用一个函数,并等待该函数返回,然后A类可以继续其余工作.在Akka和Actor模型中,Actor之间通过Message进行通信
那有什么特别的呢?
对于初学者来说,消息传递是异步的,发送Message的Actor可以在接收的Actor处理发件人的Message时继续执行其他工作.所以Actor之间的互动,默认情况下都是异步的
还有另一个变化
由于所有"函数调用"都被Message代替,即对象的不同实例,因此Actor可以存储其函数调用的历史记录,甚至延迟处理一些函数调用
想象一下,如何使用一个Actor来构建像Microsoft Word中的“撤消”按钮这样的功能?默认情况下,你有一个Message代表每个人对文档所做的更改.要撤消其中一个更改,只需要将消息从UndoActor的垃圾邮件中删除,并将该更改推送回管理Word文档的当前状态的另一个Actor。在实践中这是一个非常强大的概念。
Actor发送Message到地址,而不是直接发送给Actor
位置透明
什么是位置透明,位置透明意味着无论在何时向Actor发送Message,都不需要知道他们在系统中的位置,可能这个Message覆盖数百台计算机,所以只需要知道Actor的地址.
就像给别人打电话一样,你只需要知道电话号码,其他的电信供应商就会帮你连通.
Actor的工作方式也是一样,每个Actor都包含了以下部分地址
Protocol(协议):就像网络上使用的Http和Https一样,Akka支持多种传输协议用于进程间的通信.单进程Actor系统默认协议是akka://如果使用的远程或者集群,则通常会使用akka.tcp://或者akka.udp://在节点之间进行通信
ActorSystem(Actor系统):在akka的每个ActorSystem实例必须在启动时赋予一个名称,该名称可以由所有参与分布式的多个进程或者计算机共享ActorSystem.
Address(地址):如果不使用远程处理,则ActorPath可以省略这一部分,这部分是用来传达用于Actor系统之间的远程通信的具体IP地址/域名和端口信息
Path(路径):这是一个特定Actor在地址上的路径,结构就像一个网站的Url,所有用户定义的actor都是在/user/下
因此,要向Actor发送消息:
.Net
//local actor
var actorRef = MyActorSystem.Selection("/user/myActor");
actorRef.Tell("HI!"); //remote actor
var remoteActorRef = MyActorSystem.Selection("akka.tcp://MyActorSystem@localhost:1001/user/myActor");
remoteActorRef.Tell("HI!");
Scala
//local actor//local actor
val actorRef = system.actorSelection ("/user/myActor")
actorRef ! ("Hi!") //remote actor
val remoteActorRef =system.actorSelection ("akka.tcp://MyActorSystem@localhost:1001/user/myActor")
remoteActorRef ! ("Hi!")
Java
//local actor//local actor
ActorSelection actorRef = system.actorSelection ("/user/myActor");
actorRef .tell("Hi!",getSelf()); //remote actor
ActorSelection remoteActorRef =system.actorSelection ("akka.tcp://MyActorSystem@localhost:1001/user/myActor");
remoteActorRef.tell("Hi!",getSelf());
向远程Actor发送Message就像本地Actor一样,这就是位置透明的意思
发送给Actor地址的所有Message都是放置在属于Actor的"邮箱"中
当向Actor发送Message时,该Message不会直接进入Actor的OnReceive方法.
Message被放置在按照FIFO(先进先出)顺序排列的"邮箱"中,就像C#中Queue<T>(java中LinkedList)数据结构一样.邮箱有一个非常简单的工作,接收和挂起邮件,直到Actor准备好处理它们.
当Actor准备处理Message时,邮箱将把Message推送到Actor的OnReceive方法中,并运行Actor的消息处理方法.
Actor只能一次处理一个Message
Akka中保证参与者处理Message时,Actor的上下文和内部状态都是线程安全的
之所以这样的原因是:
因为Message是不可变的,所以每个Message的内容本质上是线程安全的
因为Message是串行处理的,所以更改一个actor的内部状态和上下文都不需要跨多个线程进行同步
因此,一个Actor在它的OnReceive方法退出之前,都无法处理下一个Message.当处理完时,邮箱会将下一个可用的Message推送到OnReceive方法中
Actor可以拥有内部状态
就像任何类一样,Actor可以拥有自己的属性和字段
当一个Actor重启时,actor实例就像我们这个BasicActor类的一个实例一样被销毁并重新创建.
BasicActor创建新的一个实例,通过Props把构造函数的参数传递给新的实例
Actor有一个明确的生命周期
在Actor可以从邮箱中开始处理Message之前,必须由Actor系统进行实例化并运行其生命周期.
Actor被创建并启动,然后会花费大部分时间接收消息,如果不再需要Actor,可以终止或者停止Actor
如果Actor以外崩溃(即抛出未处理的Exception),Actor的父级将从头开始自动重启Actor的生命周期,而不会丢失仍在Actor邮箱中的剩余的Message
结合之前的例子BasicActor中实现这个生命周期:
Actor's constructor(构造函数):BasicActor没有中并没有声明,而是使用了默认的构造函数,当然也可以使用任何带参数的构造函数
PreStart:这是在actor可以开始接收消息之运行,是放置初始化逻辑的好地方,在重启时会被调用
PreRestart:如果Actor意外失败(即抛出未处理的Exception),Actor的父级会重启Actor
PostStop:一旦Actor停止并且不再接收消息,就会被调用,这里可以处理清理引用对象,PostStop在Actor重启时不会调用,只有在人为关闭时才调用
PostRestart:在PreRestart之后,PreStart之前被调用,这是可以处理崩溃错误和诊断报告
每个Actor都有父级,有的有子级
就像人一样,Actor有父母,有的有祖父母,兄弟姐妹和孩子
这意味着每个Actor都必须由其他Actor创建,所以我们代码:
.Net
var actorRef = MyActorSystem.ActorOf(Props.Create<BasicActor>(), "myActor");
actorRef.Tell("HI!");
Scala
val system = ActorSystem("MySystem")
val actorRef = system.actorOf(Props.create(classOf[BasicActor], "myActor"))
actorRef ! ("Hi!")
Java
final ActorSystem system = ActorSystem.create("MySystem");
ActorRef actorRef= system.actorOf(Props.create(BasicActor.class,"myActor"));
actorRef.tell("Hi!",getSelf());
在/user/根Actor下创建一个新的Actor,新的Actor的路径就是/user/myActor
同样,还可以在BasicActor中创建其他Actor
.Net
protected override void OnReceive(object message)
{
var childActor = Context.ActorOf(Props.Create<BasicChildActor>(), "child1");
childActor.Tell("Hi!");
}
Scala
override def onReceive(message: Any): Unit = {
val childActor = context.actorOf(Props.create(classOf[BasicChildActor]), "child1")
childActor ! ("Hi!")
}
Java
@Override
public void onReceive(Object message) throws Throwable {
ActorRef childActor = getContext().actorOf(Props.create(BasicChildActor.class), "child1");
childActor.tell("Hi!",getSelf());
}
这样childActor的路径就是/user/myActor/child1/
父级监督子级
在关于Actor生命周期的部分中,提到了"Actor是由他们的父级进行重启"的概念.这就类似于,每个家长都收到他们孩子发的特别的消息"求助,我要崩溃了".
每个父级都带有默认的SuperviserStrategy对象(可以自定义).该对象决定了如何处理他们的子级Actor的失败.有三种方式:
Restart:重启失败的Actor,父级默认方式,除非子级在60秒内反复重启
Stop:永久停止失败的Actor
Escalate:将决定交给父级的父级处理
当发出一个Restart或者Stop 的Message,受影响的所有的子级包括自己都会重启或者停止.当然,也可以重启失败的Actor的actor家族树整个部分
引荐:https://petabridge.com/blog/akkadotnet-what-is-an-actor/
Akka系列---什么是Actor的更多相关文章
- Akka系列(二):Akka中的Actor系统
前言......... Actor模型作为Akka中最核心的概念,所以Actor在Akka中的组织结构是至关重要,本文主要介绍Akka中Actor系统. 1.Actor系统 Actor作为一种封装状态 ...
- Akka系列(三):监管与容错
前言...... Akka作为一种成熟的生产环境并发解决方案,必须拥有一套完善的错误异常处理机制,本文主要讲讲Akka中的监管和容错. 监管 看过我上篇文章的同学应该对Actor系统的工作流程有了一定 ...
- Akka 编程: 什么是Actor
上一篇我们简介了Actor系统.说明了Actor之间存在着层次关系,它也是构成Actor应用的最主要的单位. 本篇介绍Actor本身的一些基本概念.一个Actor包括了State(状态),Behavi ...
- Akka源码分析-Actor&ActorContext&ActorRef&ActorCell
分析源码的过程中我们发现,Akka出现了Actor.ActorRef.ActorCell.ActorContext等几个相似的概念,它们之间究竟有什么区别和联系呢? /** * Actor base ...
- Akka源码分析-Actor发消息(续)
上一篇博客我们分析道mailbox同时也是一个forkjointask,run方法中,调用了processMailbox处理一定数量的消息,然后最终调用dispatcher的registerForEx ...
- Akka系列(七):Actor持久化之Akka persistence
前言.......... 我们在使用Akka时,会经常遇到一些存储Actor内部状态的场景,在系统正常运行的情况下,我们不需要担心什么,但是当系统出错,比如Actor错误需要重启,或者内存溢出,亦或者 ...
- Akka系列(六):Actor解决了什么问题?
前言..... 文档来源于 : What problems does the actor model solve? Actor解决了什么问题? Akka使用Actor模型来克服传统面向对象编程模型的 ...
- Akka系列(十):Akka集群之Akka Cluster
前言........... 上一篇文章我们讲了Akka Remote,理解了Akka中的远程通信,其实Akka Cluster可以看成Akka Remote的扩展,由原来的两点变成由多点组成的通信网络 ...
- Akka源码分析-Actor创建(续)
在上一遍博客中,我们已经分析了actor创建的大致过程,但只是涉及到了Dipatcher/Mailbox/ActorCell/InternalActorRef等对象的创建,并没有介绍我们自定义的继承A ...
随机推荐
- 20155301 滕树晨linux基础——linux进程间通信(IPC)机制总结
20155301 滕树晨linux基础--linux进程间通信(IPC)机制总结 共享内存 共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,由IPC为进程创建的一个特殊地址范围,它将出现在 ...
- 20145209刘一阳《JAVA程序设计》第六周课堂测试
第六周课堂测试 1.现有以下代码,哪些选项插入到第5行可以通过编译?(BDF) 1.import java.util.*; 2. 3.Class FindStuff { 4.public static ...
- python基础学习1-描述符
#!/usr/bin/env python # -*- coding:utf-8 -*- #描述符就是将某种特殊类型的类的实例指派给另一个类的属性 #特殊类型指 实现了 # __get__(self, ...
- 24-[jQuery]-属性,文档,位置,筛选
1.jquery的属性操作 jquery对象有它自己的属性和方法,我们先研究一下jquery的属性操作.jquery的属性操作模块分为四个部分:html属性操作,dom属性操作,类样式操作和值操作 h ...
- 11-[CSS]-标准文档流,display,浮动,清除浮动,overflow
1.标准文档流 <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...
- let和var定义变量的区别
使用 let 语句声明一个变量,该变量的范围限于声明它的块中. 可以在声明变量时为变量赋值,也可以稍后在脚本中给变量赋值. 使用 let 声明的变量,在声明前无法使用,否则将会导致错误. 如果未在 ...
- cogs1772 [国家集训队2010]小Z的袜子
沉迷于文化的我N年没更blog了...(\(N \in (0,1)\)) 然后回到机房就沉迷于 \(generals.io\) 无法自拔...QAQ 然后想打一遍splay(然后是LCT),然后放弃了 ...
- Kafka系列四 flume-kafka-storm整合
flume-kafka-storm flume读取日志数据,然后发送至kafka. 1.flume配置文件 agent.sources = kafkaSource agent.channels = k ...
- Google 日历短信通知没有了
关于 Google 日历短信通知的重要通知 从 2015 年 6 月 27 日起,Google 日历将不再发送短信通知.短信通知是我们在智能手机问世之前推出的功能.如今,智能手机和通知随处可见,即使处 ...
- 基于Centos7的比特币源码编译
因为一直比较熟悉Windows平台的开发,本来打算在windows下进行编译,但查了一些文章,发现还是在linux下编译成本最低,所以这里就以手头现有的Centos7环境进行代码编译. 一.代码获 ...