架构解密分布式到微服务:聊聊分布式计算,Actor原理与实践

Actor 原理与实践

为了明白Actor是如何实现的,我们先从一个简单的Java Actor 实现入手,它就是IBM开发者网站中给出的μJavaActors,μJavaActors 非常迷你,仅仅有1200 行代码,但很强大,如下所示是其官方网站给出的Actor的原理示意图。

我们从上图可以看到,μuJavaActors 围绕3个核心对象(Message、 Actor、 ActorManager)来构建。

μJavaActors中的第1个重要对象是Message (消息),它是Actor 之间相互发送的数据,Message对象主要有以下3个属性。

  • ssource:是发送方Actor的标识,可以用于回信给发送方。
  • subject:是消息的主题,是一个字符串,通常用来说明该消息的类型(也被称为命令), 可以用来区分不同类型的消息。
  • data: 是消息的主体内容,可以是任意Java对象,比如一个Java Bean、数组、集合等。

默认的Message实现类是DefaultMessage,而其方法subjectMatches可以用正则表达式来检查该消息的subject(主题)是否符合某个特征,即属于某种类型的消息也可以用普通字符串比较的方式来判断。pμJavaActors 从消息队列( messages列表)中查找下一个符合目标特征的

Message的逻辑方法peekNext时,就用到了subjectMatches 方法。目标特征用方法的参数subject

来表示,既可以是普通字符串,也可以是某个正则表达式,从而实现了很灵活的消息匹配功能。

让我们来一睹能体现其作者的巧妙设计和精湛编程功力的代码片段:

public Message peekNext (String subject, boolean isRegExpr) {
long now = new Date() .getTime() ;
Message res = null;
Pattern p = subject != null ? (isRegExpr ? Pattern. compile (subject) : nu1l) :
null;
synchronized (messages) {
for (DefaultMessage m : messages)
if (m.getDelayUntil() <= now) {
boolean match = subject == null |
(isRegExpr ? m. subjectMatches(p) : m. subjectMatches (subject));
if (match)
res = m;
break;
}
}
}
}eturn res;
}

μJavaActors中的第2个重要对象是Actor。Actor 是-一个执行单元,一次处理一 个 Message消息。大部分程序都由许多Actor组成,这些Actor常常具有不同的类型。Actor 可在程序启动时创建或在程序执行时创建(和销毁)。每个Actor都由ActorManager负责创建并由创建它的ActorManager管理。为了相互通信,每个Actor都必须有唯一的 名称,在它所归属的ActorManager的空间里,这个名称必须是唯一的。 此外,我们可以把某些Actor归属为一类(category 属性),这样就可以定向广播消息给某一类Actor。需要注意的是,每个Actor只能属于-一个类别,如果要开发一个具体的Actor,就可以继承AbstractActor这个抽象类。每个Actor都有生命周期方法activate 和deactivate,每次与某个特定ActorManager关联时,都会调用Actor 的生命周期方法。

Actor可以用willReceive 方法来告诉系统它对哪些类型的Message 感兴趣,当一个Actor收到属于它的消息时,μJavaActors 就会自动触发它的receive 方法来完成对消息的处理,因此receive方法是我们要关注的重点。为了保持高效,在receive方法里应该迅速处理消息,而不要进入漫长的等待状态(比如等待人为输入或者无尽地循环)。除此之外,Actor 对象提供了以下有用的方法。

  • peek(): 允许该Actor查看是否存在挂起的(还未处理的) Mesage.
  • remove():允许该Actor删除或取消任何尚未处理的Message。
  • getMessageCount(): 允许该Actor获取挂起(还未处理的)的Message数量。

每个Actor收到的Message都被放在该Actor 对象内部维护的一个List中,下面这段来自AbstractorActor中的代码体现了其内部原理:

public static final int DEFAULT MAX_ MESSAGES = 100;
protected List<DefaultMessage> messages = new LinkedList<DefaultMessage>();
@Override
public int getMessageCount() {
synchronized (messages) {
return messages.size();
}
}
@Override
public int getMaxMessageCount() {
return DEFAULT MAX MESSAGES;
}
public void addMessage (Message message) (
synchronized (messages) {
if (messages.size() < getMaxMessageCount()) {
messages .add (message) ;
}
else {
throw new IllegalstateException("too many messages, cannot add");
}
}
}
@Override
public boolean remove (Message message) {
synchronized (messages) {
return messages. remove (message) ;
}
}

一个 Actor发送Message给另外一个Actor的send方法(来自DefaultActorManager类)就调用了上面的addMessage方法:

public int send (Message message, Actor from, Actor to){
int count= 0;
AbstractActor aa = (AbstractActor) to;
if (aa!= null) {
if (aa .willReceive (message .getSubject())) {
De faul tMessage xmessage = (Defaul tMessage)
( (Defaul tMessage) message) .assignSender (from) ;
aa. addMessage (xmessage) ;
count++ ;
synchronized (actors) {
actors.notifyAll () ;
}
}
}
return count;
}

我们注意到,在上述send方法中有actors.notifyAll()的调用,这会唤醒正在等待消息的Actor。

下面这段代码展示了AbstractActor中的receive 方法逻辑。它在处理完一个 Message后,就告诉ActorManager把自己放入等待队列中,等待下一个Message:

@Override
public boolean receive() 乐{
Message m = testMessage() ;
boolean res = m!= null;
if (res) {
remove (m) ;
try {
loopBody (m);//子类要做的事情,负责完成消息的处理逻辑
} catch (Exception e) {
System. out.printf ("1oop exception: gson", e) ;
}
}
manager . awai tMessage (this) ;
return res;
}

DefaultActorManager中的awaitMessage 方法如下,它把当前Actor 对象放入自己的等待队列中:

public void awai tMessage (AbstractActor a) {
synchronized (actors){
waiters.put (a. getName(),a) ;
}
}

μJavaActors中的第3个重要对象是ActorManager。我们可以认为ActorManager 就代表我们通常所说的Actor System, 它的默认实现是DefaultActorManager。每个DefaultActorManager都拥有一“个线程池资源,用于处理Message收发与Actor调度。在通常情况下,在我们的一个JVM进程中只有一个 DefaultActorManager 实例存在,但如果希望管理多个独立的线程池(或Actor池),则也可以创建多个DefaultActorManager对象。

ActorManager负责创建、启动、停止及管理所有Actor 对象,并且负责分配线程给Actor 来处理消息,也提供了发送Message的方法。以下是ActorManager的关键方法说明。

●createActor(): 创建一 个Actor 并将它与自己(ActorManager) 相关联。

●startActor(): 启动- 一个Actor,使它开始工作。

●detachActor(): 停止-一个 Actor,使它与自己脱离关系。

●send)/broadcast():将一条 Message发送给一个 Actor、一组 Actor、一个类别中的任何Actor或广播给所有Actor。

在DefaultActorManager内部保存了很多状态。

●actors:包含向管理器注册的所有Actor.

●runnables:包含已创建但尚未调用其run方法的Actor。

●waiters: 包含所有等待消息的Actor。

●threads: 包含管理器启动的所有工作线程。

DefaultActorManager很好地利用了Java 线程,可保证Actor一次只处理一个 Message, 在Actor处理一条消息时,一 个 工作线程仅与-一个特定的Actor关联,消息在被处理完成后就归还到线程池中供其他Actor自由使用,这允许一个固定 大小的线程池为无限数量的Actor提供服务。

这种线程池的思路模式很重要,因为之前我们说过,线程是重量级的对象。在线程切换方面,μJavaActors的实现有很大的不同。如果在Message处理完成时恰好有一-条新Message需要处理,则不会发生线程切换,而是重复一个简单循环来继续处理新的Message。因此,如果等待的Message数量至少与线程一样多, 则没有线程是空闲的,因此不需要进行切换;如果存在足够多的CPU处理器(至少-一个线程),则可以有效地将每个线程都分配给一个处理器,而不会发生线程切换;如果缓冲的Message不足,则线程将会休眠。

下面通过一个例子来说明μJavaActors的用法,体会面向Actor编程的思想。这个例子假设实现了Top N的并行排序,具体逻辑为:根Actor启动后会生成10个子Actor,每个子Actor对随机生成的1000 个数据进行排序,在排序完成后,获取Top N的结果并返回根Actor,根Actor在收到所有应答后再进行最后的汇总排序并输出TopN的结果。之所以假设TopN,是因为笔者没有实现最后的Top N汇聚排序算法。

整段代码很简单,不到100行。首先,根Actor的代码如下:

package com. ibm.actor.test;
import java.util.Arrays;
import com. ibm. actor .AbstractActor;
import com. ibm.actor .Actor;
import com. ibm. actor .ActorManager;
import com. ibm. actor . De faultActorManager;
import com. ibm. actor . DefaultMessage;
import com. ibm. actor .Message;
public class ParallSortActor extends AbstractActor
public static ActorManager manager = DefaultActorManager . getDefaultInstance();
int count = 10;
int finishedCount∞0;
@Override
protected void loopBody (Message m) {
if (m.getSubject() .equals("init")) {
System. out. println("begin parall sort ...");
int totalItems = 10000;
for(inti-0;i<count;1++){
Actor sortActor - manager . createAndstartActor (SortActor .class,
SortActor . class. getSimpleName() + i);
int[] data = new int[totalItems / count];
for (int j = 0; j < data. length; j++) {
data[j] = (int) (Math. abs (Math. random()) *totalltems) ;
DefaultMessage rm = new DefaultMessage ("sort", data) ;
manager.send(rm, this, sortActor);
}
}else if (m. getSubject () . equals("result")) {
finishedCount++; 
System. out. printin("received result from "+m.getSource() .getName()+
“result "+Arrays. toString( (int[])m.getData()));
m.getSource() .shutdown() ;
if (finishedCount==10)
{
System. out. printin("all finished ");
this . shutdown() ;
}
}
}
public static void main(String[] args) throws InterruptedException {
Actor myactor =
manager.createAndstartActor (ParallSortActor. class, 
ParallSortActor .class. getSimpleName()) ;
while (!myactor. isShutdown()) {
Thread. sleep(1000) ;
System. out.println("....");
}
}
}

其次,排序的Actor的代码如下:

public class SortActor extends AbstractActor {
@Override
protected void loopBody (Message m) {
if (m.getSubject() .equals ("sort")) {
System.out.printIn("enter SortActor " + this. getName()) ;
int[] data = (int[]) m. getData() ;
Arrays. sort(data) ; 
int[] topData = Arrays . copyOf(data, 10) ;
System.out.println(" SortActor”+ this.getName() +”finished”+
Arrays. toString(topData) ) ;
DefaultMessage rm = new Defaul tMessage ("result", topData) ;
this.getManager() .send(rm, this, m.getSource()) ;
}
}
}

如果亲自写一遍 上面的代码,则你可能会觉得它很像MapReduce算法。那么,MapReduce是否是Google的大牛们吸收了Actor模型的优秀设计而创造的呢?这值得我们思考。

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值