Actor Model & AKKA
- Actor Model
- Actor Model in AKKA
- AKKA在豌豆荚应用
Actor Model
Actor Model是一种不同的并发进程建模方式。
Actor是一种能够接收消息、处理消息以及发送响应的自由运行的活动(Activity)
主要被设计用来支持异步化且高效的消息传递机制
1.OOP:是可变性驱动,每样失误都可变,那么我们就需要妥善处理可见性和竞争条件
2.函数式编程:强调不可变性
3.基于角色的模型进行编程时,往往会采取与普通的面向对象编程不同的设计方法:
(1)将问题拆分城若干个可计算的异步任务,并将他们赋予不同的角色。
(2)每个角色只负责执行分配给自己的那部分任务。这样我们就可以将任意可变状态限定在之多一个角色中。
Actor Model特性:
Actor本身单线程
不同Actor之间通过传递不可变消息通信
1.Actor单线程:不会有任何可见性和竞争条件相关的问题,oop中对象状态都封装在对象中,只有实例方法才能操作对象状态,同时不同线程会可以同时调用,导致并发问题。
只允许一个Actor操作对象状态,即对可变状态进行隔离
2.Actor和线程解耦:
(1)收到一个待处理消息或者待运行的任务时,角色就可以分配到一个可用的线程来执行
(2)一个actor只有一个线程是活动的
(3)这样既保证多个actor之间的并发性,又消除了单个角色之间的竞争
Actor Model生命周期
创建:一个Actor在被创建出来之后既可以被启动也可以被终止
启动:一旦启动,Actor即准备就绪,随时可以接受消息
活动:当Actor处于活跃状态中,则不是在处理消息就是在等待新消息到达
停止:一旦停止之后,角色就不会在接受任何消息了
整个生命周期而言,用于等待和处理消息的耗时比取决于他们所处的应用程序de 动态特性
Actor Model in AKKA
-
Akka特性:更简单的可扩展性,高容错性,提供并发控制及使用Actor实现远程控制
Akka是一个并发处理框架,使用Actor Model和STM(Software Transactional Memory)以提升抽闲界别,提供更好的并发处理平台及高可扩展性。
Akka具体实现
创建Actor: Akka以一种受控的方式创建Actor
import akka.actor.UntypedActor;
/**
* Created by DELL on 2015/5/11.
*/
//继承UntypedActor,并实现onReceive方法,即可接收消息
public class HollywoodActor extends UntypedActor {
private final String name;
//Actor可以接收一些参数
public HollywoodActor(final String theName){name=theName;}
//用来接收消息
public void onReceive(final Object role){
if(role instanceof String)
System.out.println(String.format("%s playing %s",name,role));
else
System.out.println(name + " plays no "+role);
}
}
Send and Receive Messages
可以发送任何类型的消息,发送的消息必须不可变
- sendOneWay:非阻塞
- sendRequestReply(双向消息交互模式):调用线程将被阻塞,直至收到对方相应或达到超时时间为止
多Actors协作
- SizeController:通过接收消息来记录需要被扫描的目录、保存目录大小的当前值以及给FileProcessor提供需要进行扫描的目录
- FileProcessor:用于遍历给定目录下得文件/目录。负责遍历给定目录并返回该目录下所有文件的大小以及旗下所有子目录的名称。
import akka.actor.Actor;
import akka.actor.ActorRef;
import akka.actor.UntypedActor;
import akka.actor.UntypedActorFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class ConcurrentFileSizeWAkka {
//FileProcessor通过RequestAFile消息将自己加入到SIzeCollector的邮箱中
//SizeCollector将消息作为RequestAFile作为请求的应答返回给RequestAFile
class RequestAFile {}
//文件大小信息
class FileSize {
public final long size;
public FileSize(final long fileSize) { size = fileSize; }
}
//FileToProcess消息持有需要进行便利的子目录名称
// 由SizeCollector将作为对RequestAFile请求的应答返回给RequestAFile
class FileToProcess {
public final String fileName;
public FileToProcess(final String name) { fileName = name; }
}
class SizeCollector extends UntypedActor {
//待访问目录
private final List<String> toProcessFileNames = new ArrayList<String>();
//空闲的FileProcessor
private final List<ActorRef> idleFileProcessors =
new ArrayList<ActorRef>();
private long pendingNumberOfFilesToVisit = 0L;
private long totalSize = 0L;
private long start = System.nanoTime();
public void sendAFileToProcess() {
if(!toProcessFileNames.isEmpty() && !idleFileProcessors.isEmpty())
idleFileProcessors.remove(0).sendOneWay(
new FileToProcess(toProcessFileNames.remove(0)));
}
public void onReceive(final Object message) {
//表示有等待
if (message instanceof RequestAFile) {
idleFileProcessors.add(getContext().getSender().get());
sendAFileToProcess();
}
//既收且发的消息
//当需要挑选空闲Processor执行统计任务时,Collector会发送一条消息给空闲的FileProcessor
//当在遍历过程发现有子目录,Processor会用这类消息把发现通知给Collector
if (message instanceof FileToProcess) {
toProcessFileNames.add(((FileToProcess)(message)).fileName);
pendingNumberOfFilesToVisit += 1;
sendAFileToProcess();
}
if (message instanceof FileSize) {
totalSize += ((FileSize)(message)).size;
pendingNumberOfFilesToVisit -= 1;
if(pendingNumberOfFilesToVisit == 0) {
long end = System.nanoTime();
System.out.println("Total size is " + totalSize);
System.out.println("Time taken is " + (end - start)/1.0e9);
Actors.registry().shutdownAll();
}
}
}
}
//
class FileProcessor extends UntypedActor{
//直线sizeCollector的引用
private final ActorRef sizeCollector;
public FileProcessor(final ActorRef theSizeController){ sizeCollector = theSizeController;}
//每次在actor启动时调用
//事先想SizeCollector注册
@Override
public void preStart(){ registerToGetFile();}
//发送RequestAFile来表示自己处于等待
public void registerToGetFile(){
sizeCollector.sendOneWay(new RequestAFile(),getContext());
}
//接收消息,并处理
public void onReceive(final Object message){
FileToProcess fileToProcess = (FileToProcess) message;
final File file = new File(fileToProcess.fileName);
long size = 0L;
if(file.isFile()){
size = file.length();
}else {
File[] children = file.listFiles();
if (children != null)
for(File child : children)
if (child.isFile())
size += child.length();
else
sizeCollector.sendOneWay(new FileToProcess(child.getPath()));//发现是目录
}
//发消息
sizeCollector.sendOneWay(new FileSize(size));
//处理结束后,表示自己在等待状态
registerToGetFile();
}
}
public static void main(final String[] args) {
final ActorRef sizeCollector =
Actors.actorOf(SizeCollector.class).start();
sizeCollector.sendOneWay(new FileToProcess(args[0]));
for(int i = 0; i < 100; i++)
Actors.actorOf(new UntypedActorFactory() {
public UntypedActor create() {
return new FileProcessor(sizeCollector);
}
}).start();
}
}
豌豆荚实时响应平台
http://www.csdn.net/article/2014-11-21/2822770-wandoujia-Typesafe-Akka
豌豆荚实时响应平台
- 数千万移动设备链接系统,通过实时响应式平台随时随地更好地服务
- 先创建了一个持久的连接通道,将每台设备与我们的后端相连接。
- 后端持续不断地接收来自这些设备的大量事件,并做出即时的响应甚至主动性的“变形”。
- 使用AKKA
- 整体:每个 Actor 对接收到的事件或变化做出相应反应,改变状态,然后传递出新的事件或变化,而并行行为则是大量 Actors 的个体行为的整体表现。
- 1.连接层保持着每台设备的持久连接。
- 2.状态层:每台设备的状态由状态层的 Actor 对应以保持每个连接设备的状态。
- 3.Distributed Region 和 Distributed Mediator 则是业务逻辑的接口
- (1)Region 根据 ID 来访问每个状态 actor
- (2)Mediator:所有事件和消息则发布到 Mediator 并最终装换成到 RxScala 的实时事件流
- 4.业务层则只需订阅这一事件流。通过上述架构,不同设备可共享和查询状态,双向推送事件和消息,获得实时通知,并且实际上实现了所有设备之间的连接。
- 优势分析:
- 1.稳定一致的集群:因为 Actor 模式是同时适用于分布式和并行计算的最佳粒度,所以很容易从向单一服务器转换为向一个集群来部署同样的功能,Akka 本身也即是一个非常棒的具水平扩展、在线均衡调整和在线升级能力的集群平台。
- 2.性能:对于我们的应用场景来说,迄今为止的性能足够好了。我们正在运行的集群现在用 4 个节点维持100万以上的持久连接,每个节点约25万。集群里另外3个状态节点则处理每秒5万次以上的设备心跳,以及另外每秒2万次的状态请求。根据我们所做的性能测试和估算,集群的能力大约为:每个连接节点可轻易处理100万以上长连接,每个状态节点则可处理1.5万次的状态请求。我们预计,随着更多节点逐渐增加到集群中,在不久的将来我们可以达到千万级甚至数亿的长连接,每秒处理的消息数则可达数十万条。