21、聊聊akka(一)使用及集群调用(负载)

问到:akka的线程模型
为actor模型,那actor模型具体是怎么样的呢?
一种类似于协程的事件(消息)驱动?


AKKA提供一种Actor并发模型,其粒度比线程小很多,这意味着你可以在项目中使用大量的Actor。
Akka提供了一套容错机制,允许在Actor出错时进行一些恢复或者重置操作
AKKA不仅可以在单击上构建高并发程序,也可以在网络中构建分布式程序,并提供位置透明的Actor定位服务

21、聊聊akka(一)使用及集群调用(负载)_第1张图片
Akka是一个运行时与编程模型一致的系统,为以下目标设计:

垂直扩展(并发)
水平扩展(远程调用)
高容错

可惜在我设计的项目里只用做了异步机制,是远远不够的,杀鸡用了牛刀。

一个简单的启动

public static void main(String[] args) {
    ActorSystem system = ActorSystem.create("helloWorld");
    ActorRef helloWorld = system.actorOf(Props.create(HelloWorld.class), "helloWorld");
    helloWorld.tell(HelloWorld.Msg.Starting, ActorRef.noSender());

调用ActorSystem的actorOf方法创建一个Actor实例,返回一个引用ActorRef,它包括一个UID和一个Path,标识了一个Actor,可以通过该引用向该Actor实例发送消息。
Actor:

public interface Actor {
    ActorContext context();
    ActorRef self();
    ActorRef sender();
    PartialFunction<Object, BoxedUnit> receive();
    void aroundReceive(PartialFunction<Object, BoxedUnit> var1, Object var2);
    void aroundPreStart();
    void aroundPostStop();
    void aroundPreRestart(Throwable var1, Option<Object> var2);
    void aroundPostRestart(Throwable var1);
    SupervisorStrategy supervisorStrategy();
    void preStart() throws Exception;
    void postStop() throws Exception;
    void preRestart(Throwable var1, Option<Object> var2) throws Exception;
    void postRestart(Throwable var1) throws Exception;
    void unhandled(Object var1);
    ...

Akka程序是由多个Actor组成的。它的工作原理是把一项大运算分割成许多小任务然后把这些任务托付给多个Actor去运算。Actor不单可以在当前JVM中运行,也可以跨JVM在任何机器上运行,这基本上就是Akka程序实现分布式运算的关键了。当然,这也有赖于Akka提供的包括监管、监视各种Actor角色,各式运算管理策略和方式包括容错机制、内置线程管理、远程运行管理(remoting)等,以及一套分布式的消息系统来协调、控制整体运算的安全进行。

Supervisor

在一个ActorSystem是具有分层结构(Hierarchical Structure)的:一个Actor能够管理(Oversee)某个特定的函数,他可能希望将一个task分解为更小的多个子task,这样它就需要创建多个子Actor(Child Actors),并监督这些子Actor处理任务的进度等详细情况,实际上这个Actor为一个Supervisor来监督管理子Actor执行拆分后的多个子task,如果一个子Actor执行子task失败,那么就要向Supervisor发送一个消息说明处理子task失败。需要知道的是,一个Actor能且仅能有一个Supervisor,就是创建它的那个Actor。基于被监控任务的性质和失败的性质,一个Supervisor可以选择执行如下操作选择:

重新开始(Resume)一个子Actor,保持它内部的状态 ,该Actor的所有子Actor都继续工作
重启一个子Actor,清除它内部的状态,该Actor的所有子Actor都被重新启动
终止一个子Actor,该Actor的所有子Actor都被终止(默认)
扩大失败的影响,从而使这个子Actor失败

一个ActorSystem在创建过程中,至少启动3个Actor,如下图所示:
21、聊聊akka(一)使用及集群调用(负载)_第2张图片
上图是一个类似树状层次结构,ActorSystem的Top-Level层次结构,与Actor关联起来,称为Actor路径(Actor Path),不同的路径代表了不同的监督范围(Supervision Scope)。下面说明ActorSystem的监督范围:

“/”路径:通过根路径可以搜索到所有的Actor
“/user”路径:用户创建的Top-Level Actor在该路径下面,通过调用ActorSystem.actorOf来实现Actor的创建
“/system”路径:系统创建的Top-Level Actor在该路径下面
“/deadLetters”路径:消息被发送到已经终止,或者不存在的Actor,这些Actor都在该路径下面
“/temp”路径:被系统临时创建的Actor在该路径下面
“/remote”路径:改路径下存在的Actor,它们的Supervisor都是远程Actor的引用

System.out.println(helloWorld.path());
//akka://helloWorld/user/helloWorld

Gossip协议

Akka基于Gossip实现集群服务,而且支持服务自动失败检测。
在consul里面是否对Gossip有了一些了解:

Gossip协议是点对点(Computer-to-Computer)通信协议的一种,它受社交网络中的流言传播的特点所启发。现在分布式系统常常使用Gossip协议来解决其他方式所无法解决的问题,或者是由于底层网络的超大特殊结构,或者是因为Gossip方案是解决这类问题最有效的一种方式。

一个Akka集群由一组成员节点组成,每个成员节点通过hostname:port:uid来唯一标识,并且每个成员节点之间是解耦合的(Decoupled)。一个Akka应用程序是一个分布式应用程序,它具有一个Actor的集合S,而每个节点上可以启动这个Akka应用S的集合的的一部分Actor,而不必是全集S。如果一个新的成员节点需要加入到Akka集群,只需要在集群中任意一个成员节点上执行Join命令即可。

Akka集群中各个成员节点之间的状态关系,如下图所示:
21、聊聊akka(一)使用及集群调用(负载)_第3张图片
Akka集群中任何一个成员节点都有可能成为集群的Leader,这是基于Gossip收敛(Convergence)过程得到的确定性结果,没有经过选举的过程。Leader只是一种角色,在各轮Gossip收敛过程中Leader是不断变化的。Leader的职责是使成员节点进入/离开集群。
一个成员节点开始于joining状态,一旦所有其节点都看到了该新加入Akka集群的节点,则Leader会设置这个节点的状态为up。
如果一个节点安全离开Akka集群,可预期地它的状态会变为leaving状态,当Leader看到该节点为leaving状态,会将其状态修改为exiting,然后当所有节点看到该节点状态为exiting,则Leader将该节点移除,状态修改为removed状态。
如果一个节点处于unreachable状态,基于Gossip协议Leader是无法执行任何操作收敛(Convergence)到该节点的,所以unreachable状态的节点的状态是必须被改变的,它必须变成reachable状态或者down状态。如果该节点想再次加入到Akka集群,它必须需要重新启动,并且重新加入集群(经由joining状态)。

利用Inbox收件箱给Actor发送消息

actor是消息驱动的。

修改HelloWorld:

 }else if(msg == Msg.CLOSE){
    logger.info(" stoping");
     getSender().tell(new Terminated(getSelf(),false,false), getSelf());
     //getSender().tell("STOP", getSelf());
     getContext().stop(getSelf());
 }else {
ActorSystem system = ActorSystem.create("helloWorld");
ActorRef helloWorld = system.actorOf(Props.create(HelloWorld.class), "helloWorld");

Inbox inbox = Inbox.create(system);
inbox.watch(helloWorld);//监听一个actor
inbox.send(helloWorld ,HelloWorld.Msg.Starting);
inbox.send(helloWorld ,HelloWorld.Msg.CLOSE);
Object receive = null;
for (; ; ) {
    try {
        receive = inbox.receive(Duration.create(1, TimeUnit.SECONDS));
        System.out.println(receive);
        if(receive instanceof  Terminated) {//中断 ,和线程一个概念
            System.out.println("================");
            system.shutdown();
            break;
        }
    } catch (TimeoutException e) {
        e.printStackTrace();
    }
}

负载均衡(路由策略)

通常在分布式任务调度系统中会有这样的需求:一组actor提供相同的服务,我们在调用任务的时候只需要选择其中一个actor进行处理即可。

public class RouterTest extends UntypedActor {
    public Router router;
    {
        ArrayList routees = new ArrayList<>();
        for(int i = 0; i < 5; i ++) {
            //借用上面的 inboxActor
            ActorRef worker = getContext().actorOf(Props.create(HelloWorld.class), "worker_" + i);
            getContext().watch(worker);//监听
            routees.add(new ActorRefRoutee(worker));
        }
        /**
         * RoundRobinRoutingLogic: 轮询
         * BroadcastRoutingLogic: 广播
         * RandomRoutingLogic: 随机
         * SmallestMailboxRoutingLogic: 空闲
         */
        router = new Router(new RoundRobinRoutingLogic(), routees);
    }

    @Override
    public void onReceive(Object o) throws Throwable {
        if(o instanceof HelloWorld.Msg){
            router.route(o, getSender());//进行路由转发
        }else if(o instanceof Terminated){
            router = router.removeRoutee(((Terminated)o).actor());//发生中断,将该actor删除。当然这里可以参考之前的actor重启策略,进行优化,为了简单,这里仅进行删除处理
            System.out.println(((Terminated)o).actor().path() + " 该actor已经删除。router.size=" + router.routees().size());

            if(router.routees().size() == 0){//没有可用actor了
                System.out.print("没有可用actor了,系统关闭。");
                flag.compareAndSet(true, false);
                getContext().system().shutdown();
            }
        }else {
            unhandled(o);
        }

    }

    public  static AtomicBoolean flag = new AtomicBoolean(true);
    public static void main(String[] args) throws InterruptedException {
        ActorSystem system = ActorSystem.create("route");
        ActorRef routerTest = system.actorOf(Props.create(RouterTest.class), "RouterTest");

        int i = 1;
        while(flag.get()){
            routerTest.tell(HelloWorld.Msg.Starting, ActorRef.noSender());
            if(i % 10 == 0) routerTest.tell(HelloWorld.Msg.CLOSE, ActorRef.noSender());
            Thread.sleep(500);
            i ++;
        }
    }
}

日志:

[INFO] [04/15/2018 22:25:06.340] [route-akka.actor.default-dispatcher-6] [akka://route/user/RouterTest/worker_1] HelloWorld starting.
[INFO] [04/15/2018 22:25:06.340] [route-akka.actor.default-dispatcher-3] [akka://route/user/RouterTest/worker_2] HelloWorld starting.
[INFO] [04/15/2018 22:25:06.340] [route-akka.actor.default-dispatcher-3] [akka://route/user/RouterTest/worker_3] HelloWorld starting.
[INFO] [04/15/2018 22:25:06.340] [route-akka.actor.default-dispatcher-3] [akka://route/user/RouterTest/worker_4] HelloWorld starting.
[INFO] [04/15/2018 22:25:06.340] [route-akka.actor.default-dispatcher-2] [akka://route/user/RouterTest/worker_0] HelloWorld starting.
...
[INFO] [04/15/2018 22:25:10.839] [route-akka.actor.default-dispatcher-7] [akka://route/user/RouterTest/worker_0]  stoping
akka://route/user/RouterTest/worker_0 该actor已经删除。router.size=4
HelloWorld received msg :Starting
....
akka://route/user/RouterTest/worker_2 该actor已经删除。router.size=3
[INFO] [04/15/2018 22:25:15.841] [route-akka.actor.default-dispatcher-4] [akka://route/user/RouterTest/worker_2]  stoping
...
akka://route/user/RouterTest/worker_4 该actor已经删除。router.size=2
[INFO] [04/15/2018 22:25:20.842] [route-akka.actor.default-dispatcher-4] [akka://route/user/RouterTest/worker_4]  stoping
...
[INFO] [04/15/2018 22:25:25.843] [route-akka.actor.default-dispatcher-7] [akka://route/user/RouterTest/worker_3]  stoping
akka://route/user/RouterTest/worker_3 该actor已经删除。router.size=1
...
[INFO] [04/15/2018 22:25:30.845] [route-akka.actor.default-dispatcher-4] [akka://route/user/RouterTest/worker_1]  stoping
akka://route/user/RouterTest/worker_1 该actor已经删除。router.size=0
没有可用actor了,系统关闭。 

actor内置状态转换Procedure

public class ProcedureTest extends UntypedActor {
    public static enum Msg{
        PLAY, SLEEP;
    }
    private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
    Procedure happy = new Procedure() {
        @Override
        public void apply(Object o) throws Exception {
            log.info("i am happy! " + o);
            if (o == Msg.PLAY) {
                getSender().tell("i am alrady happy!!", getSelf());
                log.info("i am alrady happy!!");
            } else if (o == Msg.SLEEP) {
                log.info("i do not like sleep!");
                getContext().become(angray);
            } else {
                unhandled(o);
            }
        }
    };
    Procedure angray = new Procedure() {
        @Override
        public void apply(Object o) throws Exception {
            log.info("i am angray! "+o);
            if(o ==Msg.SLEEP){
                getSender().tell("i am alrady angray!!", getSelf());
                log.info("i am alrady angray!!");
            } else if(o ==Msg.PLAY) {
                log.info("i like play.");
                getContext().become(happy);
            } else {
                unhandled(o);
            }
        }
    };

    @Override
    public void onReceive(Object o) throws Throwable {
        log.info("onReceive msg: " + o);
        if(o == Msg.SLEEP){
            getContext().become(angray);
        }else if(o == Msg.PLAY){
            getContext().become(happy);
        }else {
            unhandled(o);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ActorSystem system = ActorSystem.create("strategy", ConfigFactory.load("akka.config"));
        ActorRef procedureTest = system.actorOf(Props.create(ProcedureTest.class), "ProcedureTest");

        procedureTest.tell(Msg.PLAY, ActorRef.noSender());
        procedureTest.tell(Msg.SLEEP, ActorRef.noSender());
        procedureTest.tell(Msg.PLAY, ActorRef.noSender());
        procedureTest.tell(Msg.PLAY, ActorRef.noSender());

        procedureTest.tell(PoisonPill.getInstance(), ActorRef.noSender());
    }
} 
  

日志:

[INFO].. [akka://strategy/user/ProcedureTest] onReceive msg: PLAY
[INFO].. [akka://strategy/user/ProcedureTest] i am happy! SLEEP
[INFO].. [akka://strategy/user/ProcedureTest] i do not like sleep!
[INFO].. [akka://strategy/user/ProcedureTest] i am angray! PLAY
[INFO].. [akka://strategy/user/ProcedureTest] i like play.
[INFO].. [akka://strategy/user/ProcedureTest] i am happy! PLAY
[INFO].. [akka://strategy/user/ProcedureTest] i am alrady happy!!

注意:

akka.ProcedureTest#onReceive这个方法只被调用一次,只有的切换均在procedure中处理,所以在实际开发过程中要注意状态切换的准确性。

Actor-Future

和java中的future挺像的,可以将一个actor的返回结果重定向到另一个actor中进行处理,主actor或者进程无需等待actor的返回结果。

阻塞方式

public class FutureBlockDemo {
    private static void block(){
        Callable callable = new Callable() {
            public Integer call() throws Exception {
                return new Random().nextInt(100);
            }
        };
        FutureTask future = new FutureTask(callable);
        new Thread(future).start();

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
    private static void block2(){
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        Future future = threadPool.submit(new Callable() {
            public Integer call() throws Exception {
                return new Random().nextInt(100);
            }
        });
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        block();
        block2();
    }
}

非阻塞方式(回调方式)

通过onComplete,onSuccess,onFailure三个回调函数来异步执行Future任务,而后两者仅仅是第一项的特例。

public class WorkerActor extends UntypedActor {
    private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
    @Override
    public void onReceive(Object o) throws Throwable {
        log.info("akka.future.WorkerActor.onReceive:" + o);

        if (o instanceof Integer) {
            Thread.sleep(1000);
            int i = Integer.parseInt(o.toString());
            getSender().tell(i, getSelf());
        }else  if (o instanceof String) {
            Thread.sleep(1000);
            int i = Integer.parseInt(o.toString());
            getSender().tell(i, getSelf());
        } else {
            unhandled(o);
        }
    }
}

public class FutureActor extends UntypedActor {
    private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
    @Override
    public void onReceive(Object o) throws Throwable {
        log.info("akka.future.FutureActor.onReceive:" + o);
        if (o instanceof Integer) {
            log.info("print:" + o);
        } else if (o instanceof String) {
            log.info("print:" + o);
        } else {
            unhandled(o);
        }
    }
}


public class FutureMain {
    public static void main(String[] args) throws Exception {
        ActorSystem system = ActorSystem.create("FutureMain" );
        ActorRef futureActor = system.actorOf(Props.create(FutureActor.class), "FutureActor");
        ActorRef workerActor = system.actorOf(Props.create(WorkerActor.class), "WorkerActor");

        //等等future返回
        Future future = Patterns.ask(workerActor, "1", 1000);
        int result = (int) Await.result(future, Duration.create(3, TimeUnit.SECONDS));
        System.out.println("result:" + result);

        //不等待返回值,直接重定向到其他actor,有返回值来的时候将会重定向到printActor
        Future future1 = Patterns.ask(workerActor, 3, 1000);
        Patterns.pipe(future1, system.dispatcher()).to(futureActor);
        workerActor.tell(PoisonPill.getInstance(), ActorRef.noSender()); //stop
    }
} 
  

共享变量

akka集群

<dependency>
  <groupId>com.typesafe.akkagroupId>
  <artifactId>akka-cluster_2.11artifactId>
  <version>2.4.16version>
dependency>

日志:

[INFO]..[akka.tcp://akkaClusterTest@127.0.0.1:2551] - Node [akka.tcp://akkaClusterTest@127.0.0.1:2551] is JOINING, roles []
[INFO]..[akka.tcp://akkaClusterTest@127.0.0.1:2551] - Leader is moving node [akka.tcp://akkaClusterTest@127.0.0.1:2551] to [Up]
[INFO] ..[akka://akkaClusterTest/user/simpleClusterListener] Member is Up: Member(address = akka.tcp://akkaClusterTest@127.0.0.1:2551, status = Up)
[INFO]..[akka.tcp://akkaClusterTest@127.0.0.1:2551] - Node [akka.tcp://akkaClusterTest@127.0.0.1:2552] is JOINING, roles []
[INFO].. [akka.tcp://akkaClusterTest@127.0.0.1:2551] - Leader is moving node [akka.tcp://akkaClusterTest@127.0.0.1:2552] to [Up]
[INFO].. [akka://akkaClusterTest/user/simpleClusterListener] Member is Up: Member(address = akka.tcp://akkaClusterTest@127.0.0.1:2552, status = Up)
[INFO].. [akka.tcp://akkaClusterTest@127.0.0.1:2551] - Node [akka.tcp://akkaClusterTest@127.0.0.1:2553] is JOINING, roles []
[INFO]..[akka.tcp://akkaClusterTest@127.0.0.1:2551] - Leader is moving node [akka.tcp://akkaClusterTest@127.0.0.1:2553] to [Up]
[INFO]..[akka://akkaClusterTest/user/simpleClusterListener] Member is Up: Member(address = akka.tcp://akkaClusterTest@127.0.0.1:2553, status = Up)

上面日志中可以看到Akka集群中各个节点的状态迁移信息,第一个种子节点正在加入自身创建的集群时的状态时JOINING,由于第一个种子节点将自己率先选举为Leader,因此它还将自己的状态改变为Up。后面它还将第二个种子节点和第三个节点从JOINING转换到Up状态。

现在把2553服务停止,看看日志输出:

[INFO] [04/15/2018 23:27:47.637] [akkaClusterTest-akka.actor.default-dispatcher-2] [akka://akkaClusterTest/user/simpleClusterListener] Member detected as unreachable: Member(address = akka.tcp://akkaClusterTest@127.0.0.1:2553, status = Up)
[WARN] [04/15/2018 23:27:47.869] [akkaClusterTest-akka.actor.default-dispatcher-40] [akka.tcp://akkaClusterTest@127.0.0.1:2551/system/cluster/core/daemon] Cluster Node [akka.tcp://akkaClusterTest@127.0.0.1:2551] - Marking node(s) as UNREACHABLE [Member(address = akka.tcp://akkaClusterTest@127.0.0.1:2553, status = Up)]. Node roles []
[WARN] [04/15/2018 23:27:48.892] [akkaClusterTest-akka.remote.default-remote-dispatcher-37] [akka.tcp://akkaClusterTest@127.0.0.1:2551/system/endpointManager/reliableEndpointWriter-akka.tcp%3A%2F%2FakkaClusterTest%40127.0.0.1%3A2553-4] Association with remote system [akka.tcp://akkaClusterTest@127.0.0.1:2553] has failed, address is now gated for [5000] ms. Reason: [Association failed with [akka.tcp://akkaClusterTest@127.0.0.1:2553]] Caused by: [拒绝连接: /127.0.0.1:2553]
[WARN] [04/15/2018 23:27:54.891] [akkaClusterTest-akka.remote.default-remote-dispatcher-37] [akka.tcp://akkaClusterTest@127.0.0.1:2551/system/endpointManager/reliableEndpointWriter-akka.tcp%3A%2F%2FakkaClusterTest%40127.0.0.1%3A2553-5] Association with remote system [akka.tcp://akkaClusterTest@127.0.0.1:2553] has failed, address is now gated for [5000] ms. Reason: [Association failed with [akka.tcp://akkaClusterTest@127.0.0.1:2553]] Caused by: [拒绝连接: /127.0.0.1:2553]
[INFO] [04/15/2018 23:27:57.649] [akkaClusterTest-akka.actor.default-dispatcher-40] [akka.cluster.Cluster(akka://akkaClusterTest)] Cluster Node [akka.tcp://akkaClusterTest@127.0.0.1:2551] - Leader is auto-downing unreachable node [akka.tcp://akkaClusterTest@127.0.0.1:2553]. Don't use auto-down feature of Akka Cluster in production. See 'Auto-downing (DO NOT USE)' section of Akka Cluster documentation.
[INFO] [04/15/2018 23:27:57.649] [akkaClusterTest-akka.actor.default-dispatcher-40] [akka.cluster.Cluster(akka://akkaClusterTest)] Cluster Node [akka.tcp://[email protected]:2551] - Marking unreachable node [akka.tcp://[email protected]:2553] as [Down]
[INFO] [04/15/2018 23:27:58.870] [akkaClusterTest-akka.actor.default-dispatcher-2] [akka.cluster.Cluster(akka://akkaClusterTest)] Cluster Node [akka.tcp://[email protected]:2551] - Leader is removing unreachable node [akka.tcp://[email protected]:2553]
[INFO] [04/15/2018 23:27:58.871] [akkaClusterTest-akka.actor.default-dispatcher-20] [akka://akkaClusterTest/user/simpleClusterListener] Member is Removed: Member(address = akka.tcp://[email protected]:2553, status = Removed)

看到2553 状态为Removed

分布式实战

现在假如服务的生产者与消费者两个角色,模拟真实的服务调用:
消费者三个服务,端口为2552,2553,2551;生产者有两个:2554,2555
git:https://github.com/nick8sky/akka_mycat_nio/tree/master/akka

参考:https://blog.csdn.net/liubenlong007/article/details/53782966
https://akka.io/
http://shiyanjun.cn/archives/1168.html
https://blog.csdn.net/jasonding1354/article/details/50555383

你可能感兴趣的:(中间件分析)