关于消息投递的一些说明
整个Akka应用是由消息驱动的,消息是除Actor之外最重要的核心组件。作为并发程序中的核心组件,在Actor之间传递的消息应该满足不可变性,也就是不变模式。因此可变的消息无法高效的在并发环境中使用。理论上Akka中的消息可以使用任何对象实例,但在实际使用中,强烈推荐使用不可变的对象。
典型的不可变对象的实现
public final class ImmutableMessage {
private final int sequenceNumber;
private final List<String> values;
public ImmutableMessage(int sequenceNumber, List<String> values) {
this.sequenceNumber = sequenceNumber;
this.values = Collections.unmodifiableList(new ArrayList<String>(values));
}
public int getSequenceNumber() {
return sequenceNumber;
}
public List<String> getValues() {
return values;
}
}
消息投递的三种策略
- 至多一次投递
在这种策略中,每一条消息最多会被投递一次。在这种情况下,可能偶尔会出现消息投递失败,而导致消息丢失。这种策略性能最高,成本最低。
- 至少一次投递
在这种策略中,每一条消息至少会被投递一次,直到成功为止。因此在一些偶然的场合,接受者可能会收到重复的消息,但不会发生消息丢失。这种策略需要保存消息投递的状态并不断充实。
- 精确的消息投递
也就是所有的消息保证被精确地投递并成功接受一次。既不会有丢失,也不会有重复接受。这种策略则是成本最高且最不容易实现的。
实际上,我们没有必要在Akka层保证消息的可靠性。这样做成本太高了,也是没有必要的。消息的可靠性更应该从应用的业务层去维护,因此也许在有些时候,丢失一些消息完全是符合应用要求的。
Akka的顺序性
对于消息投递Akka可以在一定程度上保证顺序性。比如,Actor A1向Actor A2顺序发送M1,M2和M3三条消息,Actor A3向 A2顺序发送M4,M5和M6三条消息,那么系统可以保证;
- 如果M1没有丢失,那它一定先于M2和M3被Actor A2收到。
- 如果M2没有丢失,那它一定先于M3被Actor A2收到。
- 如果M4没有丢失,那它一定先于M5和M6被A2收到。
- 如果M5没有丢失,那它一定先于M6被A2收到。
- 对于A2来说,来自A1和A3的消息可能交织在一起,没有顺序保证
这种消息投递规则不具备可传递性,比如A向C发送了M1,A向B发送了M2,B再将M2转发给C,那么C收到M1和M2的先后顺序是没有保证的。
Actor的生命周期
一个Actor再actorof()函数被调用后开始建立,实例创建后会回调preStart()方法。在这个方法里,我们可以进行一些资源的初始化工作。在Actor的工作过程中,可能会出现一些异常,这种情况下Actor需要重启,当Actor被重启时,会回调preRestart()方法,接着创建一个新的Actor对象实例(表示同一个Actor)。之后会回调postRestart()方法,表示启动完成,同时新的实例将会代替旧的实例。可以通过stop()方法或者发送一个PosionPill(毒药丸)停止Actor,停止时会调用postStop()方法,同时这个Actor的监视则会收到一个Terminated消息。
public class MyWorker extends UntypedAbstractActor {
private final LoggingAdapter loggingAdapter= Logging.getLogger(getContext().system(),this);
public static enum Msg{
WORKONG,DONE,CLOSE;
}
@Override
public void preStart() throws Exception {
System.out.println("MyWorker is starting");
}
@Override
public void postStop() throws Exception {
System.out.println("MyWorker is stopping");
}
@Override
public void onReceive(Object message) throws Throwable {
if(message==Msg.WORKONG){
System.out.println("I am working");
} else if(message==Msg.DONE){
System.out.println("Stop working");
}else if(message==Msg.CLOSE){
System.out.println("I will shutdown");
getSender().tell(Msg.CLOSE,getSelf());
getContext().stop(getSelf());
}else{
unhandled(message);
}
}
}
监听者
public class WatchActor extends UntypedAbstractActor {
private final LoggingAdapter loggingAdapter= Logging.getLogger(getContext().system(),this);
public WatchActor(ActorRef ref){
getContext().watch(ref); //监视一个Actor
}
@Override
public void onReceive(Object message) throws Throwable {
if(message instanceof Terminated){
System.out.println(String.format("%s has terminated,shutting down system",((Terminated) message).getActor().path()));
return;
}else {
unhandled(message);
}
}
public static void main(String[] args) {
ActorSystem system=ActorSystem.create("deadwatch");
ActorRef worker=system.actorOf(Props.create(MyWorker.class), "worker");
system.actorOf(Props.create(WatchActor.class,worker),"watcher");
worker.tell(MyWorker.Msg.WORKONG,ActorRef.noSender());
worker.tell(MyWorker.Msg.DONE,ActorRef.noSender());
worker.tell(PoisonPill.getInstance(),ActorRef.noSender());
}
}