akka学习教程(十四) akka分布式实战
来源:互联网 发布:淘宝客服建议 编辑:程序博客网 时间:2024/04/28 17:54
akka系列文章目录
- akka学习教程(十四) akka分布式实战
- akka学习教程(十三) akka分布式
- akka学习教程(十二) Spring与Akka的集成
- akka学习教程(十一) akka持久化
- akka学习教程(十) agent
- akka学习教程(九) STM软件事务内存
- akka学习教程(八) Actor中的Future-询问模式
- akka学习教程(七) 内置状态转换Procedure
- akka学习教程(六) 路由器Router
- akka学习教程(五) inbox消息收件箱
- akka学习教程(四) actor生命周期
- akka学习教程(三) 不可变对象
- akka学习教程(二)HelloWord
- akka学习教程(一)简介
上一篇文章介绍了akka集群的搭建,现在假如服务的生产者与消费者两个角色,模拟真实的服务调用。
本篇文章主要参考 使用Akka构建集群(二)
整体架构
服务端三个服务,端口为2552,2553,2551;客户端有两个:2554,2555
服务端角色为[server];客户端角色为[client]
服务端
集群角色
首先配置服务端集群角色为[server]:
akka { loglevel = "INFO" actor { provider = "akka.cluster.ClusterActorRefProvider" } remote { log-remote-lifecycle-events = off netty.tcp { hostname = "127.0.0.1" port = 2551 } } cluster { seed-nodes = [ "akka.tcp://akkaClusterTest@127.0.0.1:2551", "akka.tcp://akkaClusterTest@127.0.0.1:2552"] #//#snippet # excluded from snippet auto-down-unreachable-after = 10s #//#snippet # auto downing is NOT safe for production deployments. # you may want to use it during development, read more about it in the docs. # auto-down-unreachable-after = 10s roles = [server] # Disable legacy metrics in akka-cluster. metrics.enabled=off }}# 持久化相关akka.persistence.journal.plugin = "akka.persistence.journal.inmem"# Absolute path to the default snapshot store plugin configuration entry.akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local"
定义通讯的数据结构
实际开发过程中,可以将该数据结构作为单独的接口供服务端和客户端引用。
package akka.myCluster;import java.io.Serializable;public class TransformationMessages { /** * 传递的数据(参数) */ public static class TransformationJob implements Serializable { private final String text; public TransformationJob(String text) { this.text = text; } public String getText() { return text; } } /** * 返回结果 */ public static class TransformationResult implements Serializable { private final String text; public TransformationResult(String text) { this.text = text; } public String getText() { return text; } @Override public String toString() { return "TransformationResult(" + text + ")"; } } /** * 异常处理 */ public static class JobFailed implements Serializable { private final String reason; private final TransformationJob job; public JobFailed(String reason, TransformationJob job) { this.reason = reason; this.job = job; } public String getReason() { return reason; } public TransformationJob getJob() { return job; } @Override public String toString() { return "JobFailed(" + reason + ")"; } } /** * 用于服务端向客户端注册 */ public static final int BACKEND_REGISTRATION = 1;}
真正服务端业务逻辑处理代码:简单讲收到的字符串转为大写返回
package akka.myCluster;import akka.actor.ActorSystem;import akka.actor.Props;import akka.actor.UntypedActor;import akka.cluster.Cluster;import akka.cluster.ClusterEvent;import akka.cluster.Member;import akka.cluster.MemberStatus;import akka.event.Logging;import akka.event.LoggingAdapter;import com.typesafe.config.ConfigFactory;import static akka.myCluster.TransformationMessages.BACKEND_REGISTRATION;public class MyAkkaClusterServer extends UntypedActor { LoggingAdapter logger = Logging.getLogger(getContext().system(), this); Cluster cluster = Cluster.get(getContext().system()); // subscribe to cluster changes @Override public void preStart() { // #subscribe cluster.subscribe(getSelf(), ClusterEvent.MemberUp.class); // #subscribe } // re-subscribe when restart @Override public void postStop() { cluster.unsubscribe(getSelf()); } @Override public void onReceive(Object message) { if (message instanceof TransformationMessages.TransformationJob) { TransformationMessages.TransformationJob job = (TransformationMessages.TransformationJob) message; logger.info(job.getText()); getSender().tell(new TransformationMessages.TransformationResult(job.getText().toUpperCase()), getSelf()); } else if (message instanceof ClusterEvent.CurrentClusterState) { /** * 当前节点在刚刚加入集群时,会收到CurrentClusterState消息,从中可以解析出集群中的所有前端节点(即roles为frontend的),并向其发送BACKEND_REGISTRATION消息,用于注册自己 */ ClusterEvent.CurrentClusterState state = (ClusterEvent.CurrentClusterState) message; for (Member member : state.getMembers()) { if (member.status().equals(MemberStatus.up())) { register(member); } } } else if (message instanceof ClusterEvent.MemberUp) { /** * 有新的节点加入 */ ClusterEvent.MemberUp mUp = (ClusterEvent.MemberUp) message; register(mUp.member()); } else { unhandled(message); } } /** * 如果是客户端角色,则像客户端注册自己的信息。客户端收到消息以后会讲这个服务端存到本机服务列表中 * @param member */ void register(Member member) { if (member.hasRole("client")) getContext().actorSelection(member.address() + "/user/myAkkaClusterClient").tell(BACKEND_REGISTRATION, getSelf()); } public static void main(String [] args){ System.out.println("Start MyAkkaClusterServer"); ActorSystem system = ActorSystem.create("akkaClusterTest", ConfigFactory.load("reference.conf")); system.actorOf(Props.create(MyAkkaClusterServer.class), "myAkkaClusterServer"); System.out.println("Started MyAkkaClusterServer"); }}
如何保证服务发现与维护
上面代码已经有注释了,有2点:
- 有新节点加入时,如果是客户端角色,则像客户端注册自己的信息。客户端收到消息以后会讲这个服务端存到本机服务列表中
- 服务端当前节点在刚刚加入集群时,会收到CurrentClusterState消息,从中可以解析出集群中的所有前端节点(即roles为frontend的),并向其发送BACKEND_REGISTRATION消息,用于注册自己
客户端
修改客户端roles为client
akka { loglevel = "INFO" actor { provider = "akka.cluster.ClusterActorRefProvider" } remote { log-remote-lifecycle-events = off netty.tcp { hostname = "127.0.0.1" port = 2554 } } cluster { seed-nodes = [ "akka.tcp://akkaClusterTest@127.0.0.1:2551", "akka.tcp://akkaClusterTest@127.0.0.1:2552"] #//#snippet # excluded from snippet auto-down-unreachable-after = 10s #//#snippet # auto downing is NOT safe for production deployments. # you may want to use it during development, read more about it in the docs. # auto-down-unreachable-after = 10s roles = [client] # Disable legacy metrics in akka-cluster. metrics.enabled=off }}# 持久化相关akka.persistence.journal.plugin = "akka.persistence.journal.inmem"# Absolute path to the default snapshot store plugin configuration entry.akka.persistence.snapshot-store.plugin = "akka.persistence.snapshot-store.local"
客户端代码
package akka.myCluster;import akka.actor.*;import akka.dispatch.OnSuccess;import akka.util.Timeout;import com.typesafe.config.ConfigFactory;import scala.concurrent.ExecutionContext;import scala.concurrent.duration.Duration;import scala.concurrent.duration.FiniteDuration;import java.util.ArrayList;import java.util.List;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;import static akka.myCluster.TransformationMessages.BACKEND_REGISTRATION;import static akka.pattern.Patterns.ask;public class MyAkkaClusterClient extends UntypedActor { List<ActorRef> backends = new ArrayList<ActorRef>(); int jobCounter = 0; @Override public void onReceive(Object message) { if ((message instanceof TransformationMessages.TransformationJob) && backends.isEmpty()) {//无服务提供者 TransformationMessages.TransformationJob job = (TransformationMessages.TransformationJob) message; getSender().tell( new TransformationMessages.JobFailed("Service unavailable, try again later", job), getSender()); } else if (message instanceof TransformationMessages.TransformationJob) { TransformationMessages.TransformationJob job = (TransformationMessages.TransformationJob) message; /** * 这里在客户端业务代码里进行负载均衡操作。实际业务中可以提供多种负载均衡策略,并且也可以做分流限流等各种控制。 */ jobCounter++; backends.get(jobCounter % backends.size()) .forward(job, getContext()); } else if (message == BACKEND_REGISTRATION) { /** * 注册服务提供者 */ getContext().watch(getSender());//这里对服务提供者进行watch backends.add(getSender()); } else if (message instanceof Terminated) { /** * 移除服务提供者 */ Terminated terminated = (Terminated) message; backends.remove(terminated.getActor()); } else { unhandled(message); } } public static void main(String [] args){ System.out.println("Start myAkkaClusterClient"); ActorSystem actorSystem = ActorSystem.create("akkaClusterTest", ConfigFactory.load("reference.conf")); final ActorRef myAkkaClusterClient = actorSystem.actorOf(Props.create(MyAkkaClusterClient.class), "myAkkaClusterClient"); System.out.println("Started myAkkaClusterClient"); final FiniteDuration interval = Duration.create(2, TimeUnit.SECONDS); final Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS)); final ExecutionContext ec = actorSystem.dispatcher(); final AtomicInteger counter = new AtomicInteger(); actorSystem.scheduler().schedule(interval, interval, new Runnable() { public void run() { ask(myAkkaClusterClient, new TransformationMessages.TransformationJob("hello-" + counter.incrementAndGet()), timeout) .onSuccess(new OnSuccess<Object>() { public void onSuccess(Object result) { System.out.println(result.toString()); } }, ec); } }, ec); }}
可以看到TransformationFrontend处理的消息分为以下三种:
- BACKEND_REGISTRATION:收到此消息说明有服务端通知客户端,TransformationFrontend首先将服务端的ActorRef加入backends列表,然后对服务端的ActorRef添加监管;
- Terminated:由于TransformationFrontend对服务端的ActorRef添加了监管,所以当服务端进程奔溃或者重启时,将收到Terminated消息,此时TransformationFrontend将此服务端的ActorRef从backends列表中移除;
- TransformationJob:此消息说明有新的转换任务需要TransformationFrontend处理,处理分两种情况:
backends
列表为空,则向发送此任务的发送者返回JobFailed消息,并告知“目前没有服务端可用,请稍后再试”;backends
列表不为空,则通过取模运算选出一个服务端,将TransformationJob转发给服务端进一步处理;
运行结果
启动3个服务端,2个客户端
服务端2551输出:
[INFO] [01/18/2017 17:01:57.167] [akkaClusterTest-akka.actor.default-dispatcher-20] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-4[INFO] [01/18/2017 17:02:02.438] [akkaClusterTest-akka.actor.default-dispatcher-20] [akka://akkaClusterTest/user/myAkkaClusterServer] hello.2555-4[INFO] [01/18/2017 17:02:03.124] [akkaClusterTest-akka.actor.default-dispatcher-3] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-7[INFO] [01/18/2017 17:02:08.416] [akkaClusterTest-akka.actor.default-dispatcher-20] [akka://akkaClusterTest/user/myAkkaClusterServer] hello.2555-7[INFO] [01/18/2017 17:02:09.137] [akkaClusterTest-akka.actor.default-dispatcher-18] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-10[INFO] [01/18/2017 17:02:14.414] [akkaClusterTest-akka.actor.default-dispatcher-17] [akka://akkaClusterTest/user/myAkkaClusterServer] hello.2555-10[WARN] [01/18/2017 17:02:15.104] [akkaClusterTest-akka.remote.default-remote-dispatcher-6] [INFO] [01/18/2017 17:02:15.204] [akkaClusterTest-akka.actor.default-dispatcher-17] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-13
服务端2552输出:
[INFO] [01/18/2017 17:01:53.178] [akkaClusterTest-akka.actor.default-dispatcher-5] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-2[INFO] [01/18/2017 17:01:59.125] [akkaClusterTest-akka.actor.default-dispatcher-22] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-5[INFO] [01/18/2017 17:02:00.433] [akkaClusterTest-akka.actor.default-dispatcher-17] [akka://akkaClusterTest/user/myAkkaClusterServer] hello.2555-3[INFO] [01/18/2017 17:02:05.126] [akkaClusterTest-akka.actor.default-dispatcher-6] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-8[INFO] [01/18/2017 17:02:06.427] [akkaClusterTest-akka.actor.default-dispatcher-17] [akka://akkaClusterTest/user/myAkkaClusterServer] hello.2555-6[INFO] [01/18/2017 17:02:11.130] [akkaClusterTest-akka.actor.default-dispatcher-5] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-11[INFO] [01/18/2017 17:02:12.420] [akkaClusterTest-akka.actor.default-dispatcher-17] [akka://akkaClusterTest/user/myAkkaClusterServer] hello.2555-9[WARN] [01/18/2017 17:02:15.053] [akkaClusterTest-akka.remote.default-remote-dispatcher-7] [INFO] [01/18/2017 17:02:17.123] [akkaClusterTest-akka.actor.default-dispatcher-16] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-14
服务端2553输出:
[INFO] [01/18/2017 17:01:55.144] [akkaClusterTest-akka.actor.default-dispatcher-20] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-3[INFO] [01/18/2017 17:01:58.428] [akkaClusterTest-akka.actor.default-dispatcher-4] [akka://akkaClusterTest/user/myAkkaClusterServer] hello.2555-2[INFO] [01/18/2017 17:02:01.130] [akkaClusterTest-akka.actor.default-dispatcher-16] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-6[INFO] [01/18/2017 17:02:04.413] [akkaClusterTest-akka.actor.default-dispatcher-16] [akka://akkaClusterTest/user/myAkkaClusterServer] hello.2555-5[INFO] [01/18/2017 17:02:07.141] [akkaClusterTest-akka.actor.default-dispatcher-5] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-9[INFO] [01/18/2017 17:02:10.413] [akkaClusterTest-akka.actor.default-dispatcher-18] [akka://akkaClusterTest/user/myAkkaClusterServer] hello.2555-8[INFO] [01/18/2017 17:02:13.128] [akkaClusterTest-akka.actor.default-dispatcher-18] [akka://akkaClusterTest/user/myAkkaClusterServer] hello-12
客户端2554输出
JobFailed(Service unavailable, try again later)TransformationResult(HELLO-2)TransformationResult(HELLO-3)TransformationResult(HELLO-4)TransformationResult(HELLO-5)TransformationResult(HELLO-6)TransformationResult(HELLO-7)TransformationResult(HELLO-8)TransformationResult(HELLO-9)TransformationResult(HELLO-10)TransformationResult(HELLO-11)TransformationResult(HELLO-12)
客户端2555输出
JobFailed(Service unavailable, try again later)TransformationResult(HELLO.2555-2)TransformationResult(HELLO.2555-3)TransformationResult(HELLO.2555-4)TransformationResult(HELLO.2555-5)TransformationResult(HELLO.2555-6)TransformationResult(HELLO.2555-7)TransformationResult(HELLO.2555-8)TransformationResult(HELLO.2555-9)TransformationResult(HELLO.2555-10)
可以发现,客户端发送的消息被服务端正确消费,并且进行了负载均衡。不过上面第一条消息由于客户端节点刚开始处理消息时,backends列表里还没有缓存好任何backend的ActorRef,所以报错JobFailed(Service unavailable, try again later)
总结
与thrift一样,使用akka需要自己进行服务的发现治理工作。但是著名的spark都完全依赖akka,所以我们在工作中是可以使用akka的。当然akka的一些概念比较困难,学习路线比较长,所以想要学会也需要一些时日,必须经过深入学习实战才可。
参考资料
- 书籍《java高并发程序设计》
- AKKA官方文档
- akka学习教程(十四) akka分布式实战
- akka学习教程(十三) akka分布式
- akka学习教程(十一) akka持久化
- akka学习教程(一)简介
- akka学习教程(二)HelloWord
- akka学习教程(十) agent
- akka学习教程(十二) Spring与Akka的集成
- AKKA学习
- Akka
- Akka
- Akka
- Akka
- Cassandra+Akka+Spark分布式机器学习架构
- akka学习教程(三) 不可变对象
- akka学习教程(四) actor生命周期
- akka学习教程(五) inbox消息收件箱
- akka学习教程(六) 路由器Router
- AAAakka学习教程(十一) akka持久化
- 国内优秀Android学习资源
- VMWare虚拟机的Bridge(桥接)、NAT(网络地址转换)和Host-only(主机模式)
- javaBean为什么要实现Serializable接口?
- linux下定时备份数据库
- linux(redhat)服务器配置,第一章:mysql安装
- akka学习教程(十四) akka分布式实战
- iOS开发-轻松实现十六进制颜色(#开头)转RGB颜色_Swift/OC
- 1 Python 将word文件转换为PDF文件
- 如何使用PL/SQL导入CSV文件到Oracle
- Echarts入门(零基础小白教程)
- luogu1527补丁VS错误、luogu2716软件补丁问题
- 如何在SMOD中写的比较规整
- MQTT相关知识点整理
- iOS学习笔记-029.UITableView的编辑模式