Akka基本使用总结

1、什么是akka

akka是一个构建高并发、分布式、可容错、事件驱动的开发库,它可以使构建高并发分布式应用更加容易。

akka的特性:

  1. 易于构建并行和分布式应用: akka的核心组件是actor,actor被设计成独立的,每个actor之间互相不影响,没有了线程间资源共享的问题。
  2. 可靠性: akka中的actor之间有层级关系,actor有专门的进程监护。
  3. 高性能: actor占用内存非常小,1GB内存中可以保存上百万的actor,相对于传统的线程模型有很高的性能提升,java21已经推出了虚拟线程也可以支持海量任务处理了。

2、什么是actor

actor可以理解为做事情的,当系统收到一个需要处理的任务时,就会交给actor进行处理,actor具有层级关系,是一个树形结构,父节点负责子actor的创建、销毁和状态管理,在akka中,所有actor的根是ActorSystem,在系统中,ActorSystem需要被设计成单例模式。

3、使用案例

在使用时,要注意akka的版本,高版本中的一些api与低版本的api并不通用(大版本号好像是2.6),这里需要注意。目前很多软件都是使用低版本的api,这里就总结一些低版本api的使用:

需求如下:计算两个数的加减乘除,master端提供两个数和运算符,将任务提交到worker端进行计算,worker端按照要求将计算结果返回给master端。

  1. 引入依赖:
<!-- 基础actor依赖 -->
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-actor_2.11</artifactId>
    <version>2.3.15</version>
</dependency>
<!-- 集群依赖 -->
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-cluster_2.11</artifactId>
    <version>2.3.15</version>
</dependency>
<!-- 远程调用依赖 -->
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-remote_2.11</artifactId>
    <version>2.3.15</version>
</dependency>
  1. 构建一个公用类:存储两个数、运算符、运算结果,类结构如下:
import java.io.Serializable;

public class MathCalculate implements Serializable {

    /**
     * 数字1
     */
    private int num1;
    /**
     * 数字2
     */
    private int num2;
    /**
     * 计算结果
     */
    private int result;
    /**
     * 操作类型
     */
    private OperateEnum type;
    /**
     * 计算结果对应的运算符
     */
    private String op;

    public MathCalculate() {
    }

    public MathCalculate(int result, OperateEnum type) {
        this.result = result;
        this.type = type;
    }

    public MathCalculate(int num1, int num2, OperateEnum type) {
        this.num1 = num1;
        this.num2 = num2;
        this.type = type;
    }

    public MathCalculate(int num1, int num2, int result, OperateEnum type) {
        this.num1 = num1;
        this.num2 = num2;
        this.result = result;
        this.type = type;
    }

    public int getNum1() {
        return num1;
    }

    public void setNum1(int num1) {
        this.num1 = num1;
    }

    public int getNum2() {
        return num2;
    }

    public void setNum2(int num2) {
        this.num2 = num2;
    }

    public int getResult() {
        return result;
    }

    public void setResult(int result) {
        this.result = result;
    }

    public OperateEnum getType() {
        return type;
    }

    public void setType(OperateEnum type) {
        this.type = type;
    }

    public String getOp() {
        return op;
    }

    public void setOp(String op) {
        this.op = op;
    }

    public static enum OperateEnum {
        ADD("+"),
        SUBTRACT("-"),
        MULTIPLY("*"),
        DIVIDE("/"),
        RESULT("="),
        ;

        private String val;

        OperateEnum(String val) {
            this.val = val;
        }

        public String getVal() {
            return val;
        }
    }
}
  1. master端:master端创建一个akka环境,指定服务地址和端口号,并继承Actor用于处理结果信息:

master端的actor内容:

import akka.actor.UntypedActor;
import com.alibaba.fastjson.JSONObject;

public class MasterResultActor extends UntypedActor {

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

    @Override
    public void onReceive(Object message) {
        MathCalculate math = null;
        if(message instanceof String) {
            String json = (String) message;
            math = JSONObject.parseObject(json, MathCalculate.class);
        } else if(message instanceof MathCalculate) {
            math = (MathCalculate) message;
        }

        System.out.println("calculate result: " + math.getNum1() + " " + math.getOp() + " " + math.getNum2() + " = " + math.getResult());
    }
}

master端的主类内容:

import akka.actor.ActorRef;
import akka.actor.ActorSelection;
import akka.actor.ActorSystem;
import akka.actor.Props;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;

import java.util.HashMap;
import java.util.Map;

public class MasterCalculateTest {

    public static void main(String[] args) {
        Map<String, Object> propMap = new HashMap<>();
        propMap.put("akka.actor.provider", "akka.remote.RemoteActorRefProvider");
        propMap.put("akka.remote.netty.tcp.hostname", "127.0.0.1");
        propMap.put("akka.remote.netty.tcp.port", 8989);

        Config config = ConfigFactory.parseMap(propMap);
        ActorSystem system = ActorSystem.create("master-server", config);
        ActorRef masterActor = system.actorOf(Props.create(MasterResultActor.class), "master-actor");

        ActorSelection actorSelection = system.actorSelection("akka.tcp://math-worker@127.0.0.1:8899/user/worker-actor");
        for(int i =0; i < 5; i++) {
            final int num = i;
            new Thread(() -> actorSelection.tell(new MathCalculate(num, num, MathCalculate.OperateEnum.ADD), masterActor)).start();
        }
    }
}
  1. worker端:worker端也需要创建一个akka环境,用于接收master端的任务,并且创建一个actor用于计算任务结果:

worker端的actor内容:

import akka.actor.UntypedActor;
import com.alibaba.fastjson.JSONObject;

public class WorkerCalculateActor extends UntypedActor {

    @Override
    public void onReceive(Object message) {
        // System.out.println("sender info : " + getSender().path().toString());

        MathCalculate math = null;
        if(message instanceof String) {
            String json = (String) message;
            math = JSONObject.parseObject(json, MathCalculate.class);
        } else if(message instanceof MathCalculate) {
            math = (MathCalculate) message;
        }

        if(math != null) {
            int result = 0;
            switch (math.getType()) {
                case ADD:
                    result = math.getNum1() + math.getNum2();
                    break;
                case SUBTRACT:
                    result = math.getNum1() - math.getNum2();
                    break;
                case DIVIDE:
                    result = math.getNum1() / math.getNum2();
                    break;
                case MULTIPLY:
                    result = math.getNum1() * math.getNum2();
                    break;
                case RESULT:
                    result = math.getResult();
                    break;
            }

            if(math.getType() != MathCalculate.OperateEnum.RESULT) {
                MathCalculate rs = new MathCalculate(math.getNum1(), math.getNum2(), result, MathCalculate.OperateEnum.RESULT);
                rs.setOp(math.getType().getVal());
                getSender().tell(rs, this.getSelf());

                System.out.println("worker calculator: " + math.getNum1() + " " + math.getType().getVal() + " " + math.getNum2() + " = " + result);
            }
        }
    }
}

worker端的主类:

import akka.actor.ActorSystem;
import akka.actor.Props;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;

import java.util.HashMap;
import java.util.Map;

public class MathWorkerTest {

    public static void main(String[] args) {
        Map<String, Object> propMap = new HashMap<>();
        propMap.put("akka.actor.provider", "akka.remote.RemoteActorRefProvider");
        propMap.put("akka.remote.netty.tcp.hostname", "127.0.0.1");
        propMap.put("akka.remote.netty.tcp.port", 8899);
        Config config = ConfigFactory.parseMap(propMap);

        ActorSystem system = ActorSystem.create("math-worker", config);
        system.actorOf(Props.create(WorkerCalculateActor.class), "worker-actor");
    }
}

上面就是一个akka使用示例,运行示例代码首先启动worker端,等待master端下发任务进行计算,当worker端收到任务并生成计算结果了,在将计算结果返回给master,在这个过程中,都是通过 tell 方法进行交互的。

4、相关api介绍

4.1 actor的获取

在使用akka框架时,最重要的就是actor的创建和调用,下面总结一下都可以通过哪些api可以获取到actor:

1、通过 ActorSystem 的 actorOf() 方法获取到actor,这个api是将一个actor实例化并将它注入到系统中,这样他就可以被本地或远程调用了:

ActorSystem system = ActorSystem.create("master-server", config);
ActorRef masterActor = system.actorOf(Props.create(MasterResultActor.class), "master-actor");

2、通过 ActorSystem 的 actorSelection() 方法获取actor,这种方法可以获取到一个已经注入到系统中的actor或一个远程的actor,通过该actor就可以发送任务:

ActorSystem system = ActorSystem.create("master-server", config);
ActorSelection actorSelection = system.actorSelection("akka.tcp://math-worker@127.0.0.1:8899/user/worker-actor");

3、在actor实例内部,可以通过 this.getContext() 获取到应用的上下文,通过上下文环境像 ActorSystem 一样也可以创建actor:

ActorRef actor1 = this.getContext().actorOf(Props.create(MasterResultActor.class), "actor-name1");
ActorSelection actor2 = this.getContext().actorSelection("akka.tcp://math-worker@127.0.0.1:8899/user/worker-actor");

4、在actor实现类内部,可以获取到自身actor或消息发送者actor:

ActorRef self = this.getSelf();
ActorRef sender = this.getSender();

4.2 tell()方法

在akka中进行服务间通信,需要调用actor的tell方法:

remoteActor.tell("Hello,world!", localActor);

这里的remoteActor就是要调用的远程actor,localActor是本地actor,当远端执行完成后,就可以在里面调用this.getSender()获取到它,将结果通知回来。

4.3 远程url结构

在akka中调用远程actor时,就需要构建远程actor的链接,它的格式大概如下:

"akka.tcp://math-worker@127.0.0.1:8899/user/worker-actor"

akka.tcp : 这个格式是固定的协议头部。

math-worker : 这个是不确定的,它其实就是ActorSystem创建时指定的名称,如果名称不同在构建url时也要进行相应的修改。

127.0.0.1:8899 : 这个是服务的IP和端口号,在构建ActorSystem时指定。

/user/ : 固定格式。

worker-actor : 注册ActorRef时指定的actor名称,actor就是根据这个名称查找。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值