在上一部分,我们已经介绍了actor的基本用法,知道如何创建一个actor。我相信大家肯定不想局限于如此,一定想把actor紧紧的握在手中,掌握它的生死。come on ,来吧!
Actor生命周期
线程在运行历程中,会经历创建、准备、等待、阻塞等阶段,这一系列我们称之为生命周期。当然,actor也有它自己的生命历程,比如创建、运行、重启和销毁等。在理想的情况下,Actor会任劳任怨的不停工作,但在实际情况下,网络超时或者程序异常,我们希望能够及时感知并处理,例如actor重启或停止执行。关于Actor的生命周期,我们先来看一张图(来自Akka官网):
从图中,我们看出Actor的生命周期主要包含创建并启动(Start)、恢复(Resume)、重启(Restart)、停止(Stop)这几个阶段,针对自身状态变化,各阶段主要有如下行为:
阶段 |
行为 |
创建并启动 (Start) |
actorOf()创建并启动Actor时,指定对象Path和UID(对象唯一标识,通过getSelf().path().uid()获取),默认执行preStart()方法,我们可以在该方法中进行资源初始化。 |
恢复 (Resume) |
actor出现异常时,在容错机制下,可以让actor恢复并继续执行,此时actor会继续使用之前的对象实例,状态也回保留。 |
重启 (Restart) |
重启会经历两个过程: 1.调用旧实例的preRestart()方法,该方法会默认停掉所有子级actor并调用postStop()方法。 2.创建新实例,在新实例上调用postRestart()方法, 该方法默认会调用preStart()方法。 重启之后,path和UID不变,ActorRef不变,但是自身状态已改变。 |
停止 (Stop) |
停止Actor,会调用postStop()方法,同时会发送一条Terminated信息给自己的监控者。 |
创建并启动
上一章节,我们已经知道如何创建Actor对象,现在不做过多介绍,上一章节的ActorDemo类上,我们重写preStart:
@Override
public void preStart() throws Exception {
System.out.println("actorDemo开始启动");
}
大家执行可以发现,当actor在启动的时候,默认就会执行该方法,针对这个特性,我们可以在该方法做一些资源初始化或创建子类Actor。
停止
在我们的程序里,出现某种异常或当我们不需要actor时,我们可以停止actor的执行。在Akka中,停止一个actor相当的简单,有三种方式供我们选择,如下:
第一种 |
调用ActorSystem或者ActorContext的stop方法 |
第二种 |
给Actor发送PoisonPill(毒药丸) |
第三种 |
发送一条kill消息给Actor,此时会抛出ActorKilledException异常,另外会通知父级做相应处理。 |
创建一个Actor,我们演示这三种停止方式:
public class StopActor extends AbstractActor {
private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
@Override
public Receive createReceive() {
return receiveBuilder().match(Object.class,s->{
log.info(s.toString());
}).build();
}
@Override
public void postStop() throws Exception {
log.info("actor stop");
}
public static void main(String[] args) {
ActorSystem system = ActorSystem.create("system");
ActorRef stopActor = system.actorOf(Props.create(StopActor.class), "stopActor");
//第一种:ActorSystem停止actor
//system.stop(stopActor);
//第二种
stopActor.tell(PoisonPill.getInstance(),ActorRef.noSender());
//第三种
//stopActor.tell(Kill.getInstance(),ActorRef.noSender());
}
}
使用第一种或第二种方式,我们会收到如下结果:
[INFO] [10/03/2018 20:57:38.517] [system-akka.actor.default-dispatcher-3] [akka://system/user/stopActor] actor stop
当使用第三种方式时,我们会多收到一条ERROR级别的消息:
[ERROR] [10/03/2018 20:56:06.261] [system-akka.actor.default-dispatcher-3] [akka://system/user/stopActor] Kill (akka.actor.ActorKilledException: Kill)
使用kill停止actor,会抛出ActorKilledException异常,上报到父级,父级默认处理就是停止Actor。
我们发现,使用Akka提供的API,我们可以很简单的停止actor,但是大家想过没有,在实际项目中,我们的业务是复杂多样的,我们需要考虑停止actor是否会带来不可预知的后果。其实大家也不必太过担心,Actor在停止时已经有一套相当可靠的流程:
1.停止actor时,会先将正常的消息处理完毕后,再完全停止,后续消息将不再处理,邮箱(保留Actor消息)将被挂起。
2.给子级发送停止消息,需等待子级全部停止,再停掉自己,停止后会调用postStop方法,释放资源。
3.向生命周期监控者(DeathWatch)发送Terminated消息,以便监控者做相应处理。
下面我们写一个监控actor,来显示这个流程:
public class WatchActor extends AbstractActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().getSystem(), this);
private ActorRef stopActor;
@Override
public Receive createReceive() {
return receiveBuilder().match(Object.class, o -> {
logger.info(o.toString());
}).build();
}
@Override
public void preStart() throws Exception {
//初始化子类stopActor
stopActor=getContext().actorOf(Props.create(StopActor.class),"stopActor");
}
@Override
public void postStop() throws Exception {
logger.info("watchActor stop");
}
public static void main(String[] args) {
ActorSystem system = ActorSystem.create("system");
ActorRef watchActor = system.actorOf(Props.create(WatchActor.class), "watchActor");
system.stop(watchActor);
}
}
此时,当我们执行main方法时,会输出如下内容:
[INFO] [10/04/2018 08:52:24.549] [system-akka.actor.default-dispatcher-4] [akka://system/user/watchActor/stopActor] actor stop
[INFO] [10/04/2018 08:52:24.551] [system-akka.actor.default-dispatcher-2] [akka://system/user/watchActor] watchActor stop
结果表明,子级actor停止后,才会停止父类actor。
有时,我们需要监控子级actor,针对子级actor的生命周期,做出不同的处理。监控actor可以使用getContext().watch方法(相反,getContext().unwatch取消监控),对WatchActor稍作修改,如下:
public static void main(String[] args) {
ActorSystem system = ActorSystem.create("system");
ActorRef watchActor = system.actorOf(Props.create(WatchActor.class), "watchActor");
watchActor.tell("stopActor",ActorRef.noSender());
// system.stop(watchActor);
}
@Override
public Receive createReceive() {
return receiveBuilder().match(String.class, s -> {
if ("stopActor".equals(s)) {
getContext().stop(stopActor);
}
}).match(Terminated.class, t -> {
logger.info("监控到" + t.getActor() + "停止了");
}).build();
}
@Override
public void preStart() throws Exception {
//初始化子类stopActor
stopActor = getContext().actorOf(Props.create(StopActor.class), "stopActor");
//用于监控actor
getContext().watch(stopActor);
}
此时,执行main方法,我们会得到如下结果:
[INFO] [10/04/2018 09:00:20.805] [system-akka.actor.default-dispatcher-4] [akka://system/user/watchActor/stopActor] actor stop
[INFO] [10/04/2018 09:00:20.809] [system-akka.actor.default-dispatcher-3] [akka://system/user/watchActor] 监控到Actor[akka://system/user/watchActor/stopActor#-213431265]停止了
结果表明,子级actor停止,会给父类发送一条Terminated消息。
在实际项目中,由于各种原因,例如网络、代码、服务器等都可能出现问题,为了程序的高可用,就涉及到监督和容错处理。这一块内容就会涉及到我们的actor在出现异常时,是使用重启、恢复、还是停止,限于篇幅,我们下一节继续。