Actor监控与容错机制

       在如今的IT行业中,如果一个系统不具备高可用以及稳定性,那么它迟早会被淘汰的。也许有时出现故障并不是代码本身的问题,网络(连接超时、读超时等)、服务器故障、用户操作有误等原因,也不能影响用户的体验。程序发生故障时,我们要尽可能保证用户体验不受影响,这就需要我们的系统具有良好的容错机制,能保证服务在不可用时自我修复。

     监督策略

       Actor作为Akka中最基本的执行单元,是所有业务的核心,如若发生故障,可能产生不可估量的后果,这就需要拥有一套容错机制。前面我们已经提到,Actor系统具有监控机制,父级对子级进行管理,子级如果出现异常情况,父级可以通过处理逻辑来确定子级是重启、恢复、还是停止。

       Akka提供了两种监督策略,分别是One-For-One Strategy和All-For-One Strategy。关于这两种策略的区别如下:

策略

描述

One-For-One Strategy

子Actor出现异常,只针对该Actor处理。

All-For-One Strategy

子Actor出现异常,对所有子级Actor进行处理。

       在程序中,当我们没有为Actor指定管理策略,Akka提供了一套默认的策略:

  • 抛出ActorInitializationException、ActorKilledException 、DeathPactException 时,停止子Actor运行。
  • 抛出Exception时,重启子Actor。
  • 其它类型的Throwable异常时,会上溯到父级。

       实际项目中,我们可能自定义了许多异常,以此来区分不同的情况。针对这些不同的异常,我们处理Actor方式或许不一样,这就需要我们自定义监督策略。SupervisorStrategy是自定义监督策略核心类,构造函数如下:

public OneForOneStrategy(int maxNrOfRetries, java.time.Duration withinTimeRange, PartialFunction<Throwable, Directive> decider)

      我们可以看到它有三个参数,分别是maxNrOfRetries、withinTimeRange、decider。其中maxNrOfRetries和withinTimeRange表明在一段时间的重启次数,超过次数则stop。第三个参数是一个Function对象,我们可以在里面定义监督指令,使用DeciderBuilder构建工厂可以快速编写,监督指令有:

指令

描述

API

重新启动

会在旧实例上调用preRestart方法,停掉所有子级actor并调用postStop方法,然后在新实例上调用postRestart方法(默认会调用preStart方法),不会保留旧实例的状态,保留邮箱(消息)。

SupervisorStrategy.restart()

恢复运行

出现异常,不会抛出,保留之前的状态。

SupervisorStrategy.resume()

停止运行

销毁actor相关资源,后续消息不会接受。

SupervisorStrategy.stop()

上溯父级

父级无法处理异常消息,并把异常上报上一级父级。

SupervisorStrategy.escalate()

       下面,我们写一个示例来观察这几个指令,示例如下:

         监控者SupervisorActor类:

public class SupervisorActor extends AbstractActor {
    /**
     * 自定义OneForOneStrategy监督策略
     */
    private static SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.ofMinutes(1),
            DeciderBuilder.match(IOException.class, e -> SupervisorStrategy.resume())
                    .match(NullPointerException.class, e -> SupervisorStrategy.restart())
                    .match(IllegalArgumentException.class, e -> SupervisorStrategy.stop())
                    .matchAny(o -> SupervisorStrategy.escalate())
                    .build());
    private final LoggingAdapter logger = Logging.getLogger(getContext().getSystem(), this);


    public static void main(String[] args) throws InterruptedException {
        ActorSystem system = ActorSystem.create("system");
        ActorRef supervisor = system.actorOf(Props.create(SupervisorActor.class), "supervisor");
        supervisor.tell("IOException", ActorRef.noSender());
        //supervisor.tell("NullPointerException", ActorRef.noSender());
        //supervisor.tell("IllegalArgumentException", ActorRef.noSender());
        supervisor.tell("get", ActorRef.noSender());
    }

    @Override
    public SupervisorStrategy supervisorStrategy() {
        return strategy;
    }

    @Override
    public void preStart() throws Exception {
        ActorRef childActor = getContext().actorOf(Props.create(ChildActor.class), "childActor");
        //监控
        getContext().watch(childActor);
    }

    @Override
    public Receive createReceive() {
        return receiveBuilder().match(String.class, s -> {
            Option<ActorRef> childActor = getContext().child("childActor");
            ActorRef child = childActor.get();
            if ("IOException".equals(s)) {
                child.tell(new IOException(), getSelf());
            } else if ("NullPointerException".equals(s)) {
                child.tell(new NullPointerException("空指针异常"), getSelf());
            } else if ("IllegalArgumentException".equals(s)) {
                child.tell(new IllegalArgumentException(), getSelf());
            }else if("get".equals(s)){
                child.tell("get", getSelf());
            }
        }).match(Terminated.class, t -> {
            logger.info("监控到" + t.getActor() + "停止了");
        }).matchAny(other -> {
            logger.info("state= " + other);
        }).build();
    }
}

子类ChildActor ,我们重写preStart、postStop、preRestart、postRestart四个方法,方便我们观察不同指令下的流程:

public class ChildActor extends AbstractActor {
    /**
     * 用来观察不同处理后的结果
     */
    private int state = 1;

    @Override
    public Receive createReceive() {
        return receiveBuilder()
                .match(Exception.class, e -> {
                    this.state++;
                    throw e;
                })
                .matchEquals("get", s -> {
                    this.state++;
                    getSender().tell(state, getSelf());
                })
                .matchAny(o -> {
                    this.state++;
                    unhandled(o);
                })
                .build();
    }

    @Override
    public void preStart() throws Exception {
        super.preStart();
        System.out.println("child preStart");
    }

    @Override
    public void postStop() throws Exception {
        super.postStop();
        System.out.println("child postStop");
    }

    @Override
    public void preRestart(Throwable reason, Optional<Object> message) throws Exception {
        System.out.println("child preRestart start: " + this.state);
        super.preRestart(reason, message);
        System.out.println("child preRestart end: " + this.state);
    }

    @Override
    public void postRestart(Throwable reason) throws Exception {
        System.out.println("child postRestart start: " + this.state);
        super.postRestart(reason);
        System.out.println("child postRestart end: " + this.state);
    }
}

      这里特别提醒一下,ChildActor的createReceive方法中的this.state++必须写在每个匹配项里,如若写在createReceive方法里,只会执行一次。

       运行SupervisorActor 的main方法,测试三种异常带来的不同状态结果(运行结果不贴出来了,大家可以运行一下示例),  结果说明如下:

异常

结果及说明

IOException

匹配到resume指令,childActor恢复继续运行,大家应该发现没有抛出异常,异常自动跳过。state值为3,说明之前的状态保留了下来。

NullPointerException

匹配到restart指令,childActor进行重启。根据运行结果,大家应该发现,会先调用旧实例的preRestart方法,然后调用postStop,之后在新实例上调用postRestart,调用preStart方法。重启后发现state=2,说明重启会清空之前的状态。

IllegalArgumentException

匹配到stop指令,childActor停止运行,后续获取state消息没有接受到,父类supervisor收到Terminated消息。

      正确的使用监控和容错机制,可以在程序出现异常或故障时,保证系统不崩溃,对于系统的高可用和稳定性提供极大的保障。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值