Akka中cluster集群模式的使用

akka基本使用已经在前面的文章介绍了,Akka基本使用总结 ,对于小规模使用这种方式已经够用了,大规模应用时一般都是使用集群模式,Akka是支持集群模式的,下面介绍一下Akka使用集群模式:

使用集群模式时,首先需要在项目中引入如下的相关依赖:

<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>

注意,这里引入的akka版本较低,如果引入高版本的依赖,相关的api可能不同。

下面的这个示例在同一个机器上模拟一个服务集群:由两个leader节点和多个worker节点组成,worker节点设置不同的角色,这里指定的两个leader节点对外提供服务的端口号分别是6660和6661,leader节点的代码如下:

import akka.actor.ActorSystem;
import akka.actor.Address;
import akka.actor.AddressFromURIString;
import akka.actor.Props;
import akka.cluster.Cluster;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import scala.collection.JavaConverters;
import scala.collection.Seq;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class LeaderNode {

    public static void main(String[] args) {
        Map<String, Object> propMap = new HashMap<>();
        propMap.put("akka.actor.provider", "akka.cluster.ClusterActorRefProvider");
        propMap.put("akka.cluster.roles", Stream.of("LeaderNode").collect(Collectors.toList()));
        propMap.put("akka.remote.netty.tcp.hostname", "127.0.0.1");
        propMap.put("akka.remote.netty.tcp.port", 6660);

        Config config = ConfigFactory.parseMap(propMap);
        ActorSystem system = ActorSystem.create("cluster-server", config);

        // 如果有多个leader节点时使用下面的方式加入集群
        Address address = AddressFromURIString.parse("akka.tcp://cluster-server@127.0.0.1:6660");
        Address address1 = AddressFromURIString.parse("akka.tcp://cluster-server@127.0.0.1:6661");
        List<Address> list = Stream.of(address, address1).collect(Collectors.toList());
        Seq<Address> seedNodes = JavaConverters.asScalaIteratorConverter(list.iterator()).asScala().toSeq();
        Cluster.get(system).joinSeedNodes((scala.collection.immutable.Seq<Address>) seedNodes);
        
        // 如果只有一个leader节点时使用下面的方式加入集群
        // Address address = AddressFromURIString.parse("akka.tcp://cluster-server@127.0.0.1:6660");
        // Cluster.get(system).join(address);

        system.actorOf(Props.create(ClusterNodeListener.class), "cluster-listener");
    }
}

在节点加入集群时,同时注册一个用于监听节点状态的Actor,这个Actor不同于其他的Actor,它是用来监听节点状态的,当节点状态发生变动时,可以通过这个Actor感知到

import akka.actor.UntypedActor;
import akka.cluster.Cluster;
import akka.cluster.ClusterEvent;
import akka.cluster.Member;
import scala.collection.JavaConversions;
import scala.collection.immutable.Set;

import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;

public class ClusterNodeListener extends UntypedActor {

    /**
     * 集群对象
     */
    private Cluster cluster = null;
    /**
     * 集群中的节点对象:集合的key是角色,集合的值是节点列表集合
     */
    private ConcurrentHashMap<String, HashSet<String>> nodeMap = null;

    @Override
    public void preStart() throws Exception {
        cluster = Cluster.get(getContext().system());
        cluster.subscribe(getSelf(), ClusterEvent.initialStateAsEvents(),
                ClusterEvent.MemberUp.class, ClusterEvent.MemberExited.class,
                ClusterEvent.MemberRemoved.class, ClusterEvent.MemberEvent.class,
                ClusterEvent.LeaderChanged.class, ClusterEvent.RoleLeaderChanged.class,
                ClusterEvent.UnreachableMember.class);

        nodeMap = new ConcurrentHashMap<>();
    }

    @Override
    public void postStop() throws Exception {
        cluster.unsubscribe(getSelf());
    }

    @Override
    public void onReceive(Object message) throws Exception {
        if (message instanceof ClusterEvent.MemberUp) {
            ClusterEvent.MemberUp node = (ClusterEvent.MemberUp) message;
            Member member = node.member();
            this.addNode(member);

            System.out.println("新节点加入: " + member);
        } else if (message instanceof ClusterEvent.UnreachableMember) {
            ClusterEvent.UnreachableMember node = (ClusterEvent.UnreachableMember) message;
            Member member = node.member();
            this.removeNode(member);

            System.out.println("节点不可用: " + member);
        } else if (message instanceof ClusterEvent.MemberRemoved) {
            ClusterEvent.MemberRemoved node = (ClusterEvent.MemberRemoved) message;
            Member member = node.member();
            this.removeNode(member);

            System.out.println("节点删除: " + member);
        } else if(message instanceof ClusterEvent.MemberEvent) {

        } else if (message instanceof ClusterEvent.LeaderChanged) {
            ClusterEvent.LeaderChanged member = (ClusterEvent.LeaderChanged) message;
            System.out.println("Leader节点变动: " + member.leader());
        } else {
            unhandled(message);
        }
    }

    /**
     * 维护节点状态:将上线节点加入到缓存集合中
     * @param member
     */
    private void addNode(Member member) {
        Set<String> roles = member.roles();
        String address = member.address().toString();
        if(roles != null && !roles.isEmpty()) {
            synchronized (nodeMap) {
                JavaConversions.asJavaCollection(roles).stream().forEach(key -> {
                    if(!nodeMap.contains(key)) {
                        nodeMap.put(key, new HashSet<>());
                    }
                    nodeMap.get(key).add(address);
                });
            }
        }
    }

    /**
     * 维护节点状态:将下线节点从缓存集合中移除
     * @param member
     */
    private void removeNode(Member member) {
        Set<String> roles = member.roles();
        String address = member.address().toString();
        if(roles != null && !roles.isEmpty()) {
            synchronized (nodeMap) {
                JavaConversions.asJavaCollection(roles).stream().forEach(key -> {
                    if(nodeMap.contains(key)) {
                        nodeMap.get(key).remove(address);
                    }
                });
            }
        }
    }
}

集群中的所有节点都需要创建这个监听的Actor,当集群中的其他节点发生变动时,就可以感知到节点的状态,这时就可以进行相应的更新操作了。

这里有一个nodeMap集合,这个集合记录了不同角色对应的节点列表,它的作用就是用于查找Actor,比如目前有一个节点的角色是CalculateRole,它有一个Actor是负责进行一些计算任务的,那么有需要用到这个Actor的节点就可以通过nodeMap查找到节点的信息,进而就可以找到这个Actor执行相关的计算工作了。

下面在写两个节点,其中一个节点的角色是CalculateNode,它负责执行计算任务;另外一个节点是ResultNode,他负责执行结果的输出,

下面是CalculateNode和它对应的Actor代码:

import akka.actor.*;
import akka.cluster.Cluster;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import scala.collection.JavaConverters;
import scala.collection.Seq;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CalculateNode {

    public static void main(String[] args) {
        Map<String, Object> propMap = new HashMap<>();
        propMap.put("akka.actor.provider", "akka.cluster.ClusterActorRefProvider");
        propMap.put("akka.cluster.roles", Stream.of("CalculateNode").collect(Collectors.toList()));
        propMap.put("akka.remote.netty.tcp.hostname", "127.0.0.1");
        propMap.put("akka.remote.netty.tcp.port", 6662);

        Config config = ConfigFactory.parseMap(propMap);
        ActorSystem system = ActorSystem.create("cluster-server", config);

        // 如果有多个leader节点时使用下面的方式加入集群
        Address address = AddressFromURIString.parse("akka.tcp://cluster-server@127.0.0.1:6660");
        Address address1 = AddressFromURIString.parse("akka.tcp://cluster-server@127.0.0.1:6661");
        List<Address> list = Stream.of(address, address1).collect(Collectors.toList());
        Seq<Address> seedNodes = JavaConverters.asScalaIteratorConverter(list.iterator()).asScala().toSeq();
        Cluster.get(system).joinSeedNodes((scala.collection.immutable.Seq<Address>) seedNodes);

        system.actorOf(Props.create(ClusterNodeListener.class), "cluster-listener");
        system.actorOf(Props.create(CalculateActor.class), "calc-actor");
    }
}
import akka.actor.ActorRef;
import akka.actor.UntypedActor;

public class CalculateActor extends UntypedActor {

    @Override
    public void onReceive(Object o) throws Exception {
        if(o instanceof String) {
            String str = (String) o;

            String[] arr = str.split("\\+");
            int num1 = Integer.parseInt(arr[0]);
            int num2 = Integer.parseInt(arr[1]);
            int result = num1 + num2;
            getSender().tell(num1 + "+" + num2 + "=" + result, ActorRef.noSender());
        }
    }
}

下面是ResultNode和它对应的Actor代码:

import akka.actor.*;
import akka.cluster.Cluster;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import scala.collection.JavaConverters;
import scala.collection.Seq;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class ResultNode {

    public static void main(String[] args) {
        Map<String, Object> propMap = new HashMap<>();
        propMap.put("akka.actor.provider", "akka.cluster.ClusterActorRefProvider");
        propMap.put("akka.cluster.roles", Stream.of("ResultNode").collect(Collectors.toList()));
        propMap.put("akka.remote.netty.tcp.hostname", "127.0.0.1");
        propMap.put("akka.remote.netty.tcp.port", 6667);

        Config config = ConfigFactory.parseMap(propMap);
        ActorSystem system = ActorSystem.create("cluster-server", config);

        // 如果有多个leader节点时使用下面的方式加入集群
        Address address = AddressFromURIString.parse("akka.tcp://cluster-server@127.0.0.1:6660");
        Address address1 = AddressFromURIString.parse("akka.tcp://cluster-server@127.0.0.1:6661");
        List<Address> list = Stream.of(address, address1).collect(Collectors.toList());
        Seq<Address> seedNodes = JavaConverters.asScalaIteratorConverter(list.iterator()).asScala().toSeq();
        Cluster.get(system).joinSeedNodes((scala.collection.immutable.Seq<Address>) seedNodes);

        system.actorOf(Props.create(ClusterNodeListener.class), "cluster-listener");
        ActorRef res = system.actorOf(Props.create(ResultActor.class), "result-actor");
        
        // 循环判断是否能够获取到CalculateNode,如果获取到了节点上线就构建一个Actor
        while (ClusterNodeListener.nodeMap.get("CalculateNode") == null
                || ClusterNodeListener.nodeMap.get("CalculateNode").isEmpty()) {
            try {
                TimeUnit.MICROSECONDS.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        String nodeAddress = ClusterNodeListener.nodeMap.get("CalculateNode").iterator().next();
        ActorSelection worker = system.actorSelection(nodeAddress + "/user/calc-actor");
        // 发送消息
        worker.tell("2+3", res);
    }
}
import akka.actor.UntypedActor;

/**
 * @Author wangxixin
 * @Date 2023/10/10
 * @Description: TODO
 * @Version 1.9
 */
public class ResultActor extends UntypedActor {
    @Override
    public void onReceive(Object o) throws Exception {
        System.out.println(o);
    }
}

以上就实现了一个集群模式的Akka的使用,这里的所有节点都可以启动多个,当节点发生状态变化时,通过负责监听的Actor能够感知到状态改变,并且通过角色的不同实现了不同功能Actor的分配,再配合负载均衡算法就能实现灵活调度。

对于集群的这种使用方式是我的理解,如有不同的想法大家可以一起交流沟通。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值