akka使用示例

785 阅读9分钟

Akka 简介

欢迎使用 Akka,这是一组用于设计可扩展、弹性系统的库,这些系统涵盖处理器核心和网络。Akka 让您可以专注于满足业务需求,而不是编写低级代码来提供可靠的行为、容错能力和高性能。

许多常见的做法和公认的编程模型并未解决为现代计算机架构设计系统时固有的重要挑战。要取得成功,分布式系统必须应对组件崩溃而无响应、消息丢失而无踪迹以及网络延迟波动的环境。这些问题经常发生在精心管理的数据中心内环境中 - 在虚拟化架构中更是如此。

为了帮助您处理这些现实问题,Akka 提供:

  • 多线程行为无需使用原子或锁等低级并发构造 - 让您甚至无需考虑内存可见性问题。
  • 系统及其组件之间的透明远程通信——使您免于编写和维护困难的网络代码。
  • 集群式、高可用性架构具有弹性,可根据需要缩小或缩小规模,使您能够提供真正反应灵敏的系统。

Akka 使用 Actor 模型提供了一定程度的抽象,使编写正确的并发、并行和分布式系统变得更加容易。Actor 模型涵盖了整套 Akka 库,为您提供了一致的方式来理解和使用它们。因此,Akka 提供了深度集成,这是您无法通过挑选库来解决单个问题并尝试将它们拼凑在一起来实现的。

什么是 Actor 模型?

Actor 模型是一种并发计算模型,它把 actor 作为并发计算的基本单元。actor 可以异步地接收消息、发送消息、创建新的 actor。Actor 模型提供了一种优雅的方式来处理并发和并行问题,特别适用于构建高并发、分布式系统。

Actor 模型的核心概念

  • Actor:

    • 是并发计算的最小单元。
    • 拥有自己的邮箱,用来接收消息。
    • 可以创建新的 actor。
    • 可以发送消息给其他 actor。
    • 有自己的状态,状态只能通过发送消息来改变。
  • 消息:

    • Actor 之间通信的载体。
    • 消息是异步发送的。
    • 一个 actor 可以同时处理多个消息。
  • 邮箱:

    • 每个 actor 都有一个邮箱,用于存放收到的消息。
    • 邮箱按照一定的顺序处理消息。
  • Actor 系统:

    • 管理所有 actor 的生命周期。
    • 提供 actor 之间的通信机制。

image.png

核心类库

  • akka-actor: 这是 Akka 的核心模块,提供了 Actor 模型的实现,包括 Actor 系统、Actor 创建、消息传递等基础功能。
  • akka-cluster: 用于构建分布式 Actor 系统,提供集群成员发现、故障检测、消息路由等功能。
  • akka-http: 一个用于构建高性能、异步的 HTTP 服务器和客户端的库,可以轻松地构建 RESTful API。
  • akka-stream: 提供了一个基于背压的流处理模型,用于处理大量异步数据。
  • akka-persistence: 提供了持久化 Actor 状态的能力,确保数据在故障发生时不会丢失。
  • akka-testkit: 提供了一套测试工具,用于测试 Actor 系统。

其他常用类库

  • akka-remote: 支持远程 Actor,使得 Actor 可以分布在不同的节点上。
  • akka-slf4j: 集成了 SLF4j 日志框架,方便进行日志记录。
  • akka-persistence-query: 提供了查询持久化事件的 API。
  • akka-http-spray-json: 将 Spray-JSON 集成到 Akka HTTP 中,方便 JSON 的序列化和反序列化。

各类库功能详解

  • akka-actor:

    • Actor System :Actor 的容器,负责管理 Actor 的生命周期。
    • Actor:并发执行单元,通过消息传递进行通信。
    • 消息:Actor 之间通信的数据载体。
    • ActorRef:Actor 的代理引用,用于发送消息。
  • akka-cluster:

    • 集群成员发现:自动发现集群中的其他节点。
    • 故障检测:检测集群中的节点故障。
    • 消息路由:根据路由规则将消息路由到相应的节点。
  • akka-http:

    • 路由:定义 HTTP 请求和响应的路由规则。
    • 指令:处理 HTTP 请求的指令。
    • 响应:构建 HTTP 响应。
  • akka-stream:

    • Source:数据源,可以是文件、数据库、Actor 等。
    • Sink:数据接收端,可以是文件、数据库、Actor 等。
    • Flow:对数据进行转换的处理阶段。
  • akka-persistence:

    • Journal:持久化事件的存储。
    • Snapshot:Actor 状态的快照。
  • akka-testkit:

    • TestProbe:用于发送消息给 Actor 并接收响应的测试工具。
    • ExpectMsg:断言 Actor 收到特定消息。

akka-actor 核心api

1. ActorSystem 类

ActorSystem 是 Akka 中管理和协调 Actor 的容器。每个 Actor 系统都包含一组 Actor,并负责管理其生命周期、资源等。

常用函数:
  • ActorSystem.create(String name)
    创建一个新的 ActorSystem 实例。

    ActorSystem system = ActorSystem.create("myActorSystem");
    
  • actorOf(Props props, String name)
    创建一个新的 Actor 实例。返回一个 ActorRef,通过它可以向 Actor 发送消息。

    ActorRef actor = system.actorOf(Props.create(MyActor.class), "myActor");
    
  • terminate()
    优雅地关闭 ActorSystem。该方法返回一个 CompletionStage,表示系统关闭的结果。

    system.terminate();
    
  • log()
    获取 ActorSystem 内部的日志记录器,用于日志记录。

    system.log().info("Actor system started");
    

2. ActorRef 类

ActorRef 是对 Actor 的引用,允许发送消息给该 Actor。

常用函数:
  • tell(Object message, ActorRef sender)
    发送消息给 Actor。message 是要发送的消息,sender 是消息的发送者(通常可以使用 ActorRef.noSender() 表示没有发送者)。

    actorRef.tell("Hello, Akka!", ActorRef.noSender());
    
  • path()
    获取 ActorRef 的路径,表示该 Actor 在 ActorSystem 中的位置。

    String actorPath = actorRef.path().toString();
    
  • isTerminated()
    检查 Actor 是否已经被终止。

    boolean terminated = actorRef.isTerminated();
    

3. Props 类

Props 是用来配置和创建 Actor 实例的类。它可以传递构造函数的参数,用于创建 Actor 的实例。

常用函数:
  • create(Class<? extends Actor> actorClass)
    创建 Props 对象,用于创建 Actor 实例。该方法返回一个 Props 实例,用于指定 Actor 的类型。

    Props props = Props.create(MyActor.class);
    
  • create(ActorFactory factory)
    使用自定义的工厂方法来创建 Props 对象。这通常用于需要传递构造函数参数的情况。

    Props props = Props.create(() -> new MyActor("some argument"));
    
  • withDispatcher(String dispatcherId)
    设置 Actor 的调度器。可以指定自定义的调度器 ID,来决定 Actor 执行消息的线程池。

    Props props = Props.create(MyActor.class).withDispatcher("my-dispatcher");
    

4. AbstractActor 类

AbstractActor 是 Akka 提供的一个抽象类,方便开发者定义 Actor。开发者需要重写 createReceive() 方法来定义消息的处理逻辑。

常用函数:
  • createReceive()
    定义该 Actor 如何响应不同类型的消息。它返回一个 Receive 对象,表示该 Actor 能处理的消息类型及其处理逻辑。

    @Override
    public Receive createReceive() {
        return receiveBuilder()
            .match(String.class, msg -> {
                System.out.println("Received: " + msg);
            })
            .build();
    }
    
  • getSelf()
    获取当前 Actor 的引用。通常在 Actor 内部使用,以便能够向自己发送消息。

    getSelf().tell("Hello, Actor!", getSelf());
    
  • getContext()
    获取当前 Actor 的上下文(ActorContext),用于获取更多 Actor 信息,发送消息等。

    getContext().actorOf(Props.create(MyActor.class), "anotherActor");
    
  • sender()
    获取当前消息的发送者的 ActorRef。通常用于在响应消息时知道谁是发送者。

    ActorRef sender = sender();
    

使用示例

定义主类


public class RuleEngine {
    private static ActorSystem system;

    private static  Config config = ConfigFactory.parseString("akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-min = 8 \n" +
                    "akka.actor.default-dispatcher.thread-pool-executor.core-pool-size-max = 16 \n" +
                    "akka.actor.default-dispatcher.thread-pool-executor.max-pool-size = 64 \n" +
                    "akka.actor.default-dispatcher.thread-pool-executor.task-queue-size = 100 \n");

    public static void main(String[] args) throws IOException {

        // 创建 ActorSystem,所有 Actor 都必须在 ActorSystem 中运行
        system = ActorSystem.create("engine-system",config);
//        System.out.println(system.settings());

        // 创建一个二维列表数据,模拟输入的数据结构
        EngineParam messageReq = new EngineParam("login", Arrays.asList(
                Arrays.asList("item 11", "item 22", "item 33"), // 第一个子列表
                Arrays.asList("item Aa", "item Bb", "item Cc"),  // 第二个子列表
                Arrays.asList("item 33", "item 44", "item 55"),  // 第二个子列表
                Arrays.asList("item Dd", "item Ee", "item Gg")  // 第二个子列表
        ));
        messageReq.setNotifySync(false);
        exec(messageReq);





        // 创建一个二维列表数据,模拟输入的数据结构
        messageReq =  new EngineParam("register",Arrays.asList(
                Arrays.asList("item A", "item B", "item C"),  // 第二个子列表
                Arrays.asList("item 1", "item 2", "item 3"), // 第一个子列表
                Arrays.asList("item D", "item E", "item G"),  // 第二个子列表
                Arrays.asList("item 3", "item 4", "item 5"))  // 第二个子列表
        );
        messageReq.setNotifySync(true);
        exec(messageReq);

        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        system.terminate();

    }


    public static void exec(EngineParam param){
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("创建actor");
        System.out.println("请求线程是 in thread =" + Thread.currentThread().getName());
        ActorRef rootActor = system.actorOf(Props.create(BatchActor.class));
        stopWatch.stop();
        stopWatch.start("执行任务");
        Future<Object> ask = Patterns.ask(rootActor, param, TimeUnit.SECONDS.toSeconds(param.getTimeoutSeconds()));
        stopWatch.stop();
        stopWatch.start("等待结果");
        if (param.isNotifySync()){
            //同步等待
            try {
                Await.result(ask, Duration.create(param.getTimeoutSeconds(), TimeUnit.SECONDS));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }else{
            //异步通知
            ask.onComplete(result -> {
                System.out.println("进行异步结果通知 比如发送kafka in thread =" + Thread.currentThread().getName());
                return null;
            }, system.dispatcher());
        }
        stopWatch.stop();
        System.out.println(stopWatch.prettyPrint());
        System.out.println(stopWatch.getTotalTimeMillis());
    }
}

处理一维actor

public class BatchActor extends AbstractActor {

    // 构造函数:创建子 Actor 的实例
    public BatchActor() {
        // 创建一个单例的子 Actor 实例
        getContext().actorOf(Props.create(ExecActor.class), "execActor");
    }

    @Override
    public void preStart() throws Exception {
        super.preStart();
        //TODO 可以根据二维数组中最大的值预先创建子actor 以便复用 今后性能优化点
    }

    /**
     * 创建接收消息的行为
      */
    @Override
    public Receive createReceive() {
        return receiveBuilder()
                // 匹配 List 类型的消息,表示父 Actor 将处理二维列表数据
                .match(EngineParam.class, this::onProcessList)
                .matchAny(message -> {
                    System.out.println("Unhandled message: " + message + " in thread " + Thread.currentThread().getName());
                    unhandled(message);
                })
                .build();
    }

    // 处理传入的二维列表数据
    private void onProcessList(EngineParam messageReq) {
        // 遍历二维列表的每个子列表
        for (List<String> sublist : messageReq.getRuleList()) {
            // 为每个子列表中的每个元素启动一个子 Actor 进行处理
            List<Future<Object>> futures = new ArrayList<>();
            System.out.println("All items processed start"  + " in thread " + Thread.currentThread().getName());
            // 遍历每个子列表的元素,发送给子 Actor 处理
            for (String item : sublist) {
                //此处多actor的用意是为了并发执行 向子 Actor 发送消息(单个子项),并返回一个 Future 对象 TODO 这里可优化 ,一开始就创建好 二维列表的最大值的actor数量,后续直接复用即可
                ActorRef execActor = getContext().actorOf(Props.create(ExecActor.class));
                futures.add(Patterns.ask(execActor, item, Timeout.create(java.time.Duration.ofSeconds(5))));
            }
            System.out.println("All items processed end"  + " in thread " + Thread.currentThread().getName());
            // 使用 Akka 的 Futures.sequence 方法合并多个 Future,以便等待所有子任务完成
            Future<?> aggregatedFuture = Futures.sequence(futures, getContext().dispatcher());

            // 等待所有子任务完成后再继续
            try {
                // 设置等待的最长时间为 10 秒
                Await.result(aggregatedFuture, Duration.create(10, TimeUnit.SECONDS));
                aggregatedFuture.onComplete(result -> {
                    System.out.println("All items processed callback" + result.get() + " in thread " + Thread.currentThread().getName());
                    return null;
                }, getContext().dispatcher());
            } catch (Exception e) {
                e.printStackTrace();  // 如果有异常,打印异常信息
            }
        }
        getSender().tell("executionIsComplete", getSelf());
        // 停止自己
        getContext().stop(getSelf());
    }
}

处理二维actor

public class ExecActor extends AbstractActor {
    // 创建接收消息的行为
    @Override
    public Receive createReceive() {
        return receiveBuilder()
                // 匹配 String 类型的消息(每个子项)
                .match(String.class, this::onProcessItem)
                .matchAny(message -> {
                    System.out.println("Unhandled message: " + message + " in thread " + Thread.currentThread().getName());
                    unhandled(message);
                })
                .build();
    }

    // 处理单个子项的逻辑
    private void onProcessItem(String item) {
        // 打印当前正在处理的项
        System.out.println("Processing item: " + item + " in thread " + Thread.currentThread().getName());

        // 处理完当前项后,向发送者返回消息,表示该项已处理完成
        getSender().tell("Done processing " + item, getSelf());

        // 如果需要复用该actor 不应该 停止自己
        getContext().stop(getSelf());
    }
}

消息体

@Data
@AllArgsConstructor
@NoArgsConstructor
public class EngineParam {

    private String bizType;

    private long timeoutSeconds = 10;

    private boolean notifySync = true;

    private List<List<String>> ruleList;

    private ConcurrentHashMap<String, Object> s;

    private ConcurrentHashMap<String, Object> e;

    private ConcurrentHashMap<String, Object> o;

    public EngineParam(String bizType, List<List<String>> ruleList) {
        this.bizType = bizType;
        this.ruleList = ruleList;
    }
}