UntypedActor就是我们所说的Actor,之所以这里强调是无类型的,那是因为在Akka中,还支持一种有类型的Actor。有类型的Actor可以使用系统中的其他类型构造,可以缓解Java单继承的问题。因为你在继承了UntypedActor后,就不能再继承系统中的其他类了。如果你一定想这么做,那么就只能选择有类型的Actor。否则,UntypedActor应该就是你的首选。
unhandle(msg)
不处理消息getSender()
获取发送者getSelf()
获取自身角色getSender().tell(msg, getSelf())
向发送者回复消息实例
public class HelloWorld extends UntypedActor { ActorRef greeter; // Akka的回调方法,在Actor启动前会被Akka框架调用,完成一些初始化的工作 @Override public void preStart() throws Exception { // 获取当前上下文 UntypedActorContext untypedActorContext = getContext(); // 创建Greenter的实例,有当前上下文创建,所以是HelloWorld的子Actor greeter = untypedActorContext.actorOf(Props.create(Greeter.class), "greeter"); greeter.tell(Greeter.Msg.GREET, getSelf()); } // 消息处理函数 @Override public void onReceive(Object msg) throws Exception { if (msg == Greeter.Msg.DONE) { // 回复欢迎消息,并停止自己 greeter.tell(Greeter.Msg.GREET, getSelf()); // 停止自己 getContext().stop(getSelf()); } else { unhandled(msg); } } }
ActorSystem表示管理和维护Actor的系统。一般来说,一个应用程序只需要有一个ActorSystem就够了。
public static akka.actor.ActorSystem create(java.lang.String s, com.typesafe.config.Config config)
:
第一个参数s
为系统名称,第二个参数config
为配置文件。
ActorSystem system = ActorSystem.create("Hello", ConfigFactory.load("samplehello.conf"));
// 顶级的Actor,由系统创建
ActorRef actorRef = system.actorOf(Props.create(HelloWorld.class), "helloWorld");
System.out.println("HelloWorld Actor Path:" + actorRef.path());
akka{
loglevel = INFO // 日志级别
}
Actor的路径akka://Hello/user/helloWorld
,第一个Hello
表示系统名,user
表示用户Actor,所有的用户Actor都会挂载到user这个用户路径下,helloWorld
就是这个Actor的名字。
Akka将线程的调度进行封装,使我们只需要关注Actor对象就可以了。当系统内存在多个Actor时,Akka会在线程池中选择线程来执行我们的Actor。因此,多个不同的Actor可能被同一个线程执行,也可能被多个线程执行。
需要注意的是
:我们不要在Actor中执行耗时的任务,这可能会影响到其他Actor的执行。
消息投递的说明
Akka的核心组件包括Actor和消息。
消息应该满足不变性,因为可变的消息无法高效的并发环境中使用。理论上Akka的消息可以任何对象,但是推荐使用不变对象,如下:
public final class ImmutableMesssage { // final不可继承,防止被子类破坏
private final int sequenceNumber;
private final List<String> values;
public ImmutableMesssage(int sequenceNumber, List<String> values) {
this.sequenceNumber = sequenceNumber;
this.values = Collections.unmodifiableList(new ArrayList<>(values)); // fianl仅表示引用不可变,构造不可变的list
}
public int getSequenceNumber() {
return sequenceNumber;
}
public List<String> getValues() {
return values;
}
}
投递消息的策略
- 至多一次投递。每条消息最多投递一次。在这种情况下,会偶尔出现消息投递失败的情况,从而导致消息丢失。
最低成本,最高性能,但会丢失消息,因为系统只要把消息投递出去就行了,不关注是否会成功。
- 至少一次投递。每条消息至少会被投递一次,直到成功为止。因此在一下偶然的场合,接受者可能会收到重复的消息,但是消息不会丢失。
需要保证消息的状态并不断重试,会出现重复投递的情况。
- 精确的消息投递。保证消息被精确的投递成功且只接受一次,即不会丢失,也不会重复接受。
成本最高,最不容易实现。
可靠的消息投递有时候是没必要的,成本太高了,我们应该在应用的业务层去保证。
消息投递的顺序
只保证一定程度上的顺序性,来自同一个Actor的消息是有先后顺序的,不同的Acotr的消息则可能交织在一起。不具备传递性。如:Actor A1向A2顺序发送了M1、M2和M3三条消息。Actor A3向A2顺序发送了M4、M5和M6三条消息。那么系统可以保证:
1. 如果M1没有丢失,那它一定先于M2和M3被A2收到。
2. 如果M2没有丢失,那它一定先于M3被A2收到。
3. 如果M4没有丢失,那它一定先于M5和M6被A2收到。
4. 如果M5没有丢失,那它一定先于M6被A2收到。
5. 对A2来说,来自A1和A3的消息可能交织在一起,没有顺序保证。
在这里,值得注意的一点是,这种消息投递规则不具备可传递性,比如:Actor A向C发送了M1,接着,Actor A向B发送了M2,B将M2转发给Actor C。那么在这种情况下,C收到M1和M2的先后顺序是没有保证的。