Java并发57:Akka Actors并发框架浅谈及入门示例
本章对Actors并发框架进行初步的介绍和入门示例的演示,关于其更深层次的内容,以后会系统性的进行学习。
1.Actors并发模型简介
Actors并发模型是计算机科学领域中的一个并行计算模型,它把actors当做通用的并行计算原语。
一个actor对接收到的消息做出响应,进行本地决策,可以创建更多的actor,或者发送更多的消息;同时准备接收下一条消息。
在Actor理论中,一切都被认为是actor,这和面向对象语言里一切都被看成对象很类似。
但包括面向对象语言在内的软件通常是顺序执行的,而Actor模型本质上则是并发的。
Actors模型对并发模型进行了更高的抽象,它是一种异步、非阻塞、高性能的轻量级事件驱动编程模型。所谓轻量级事件指的是这些acotr内存消耗极小,1GB内存可容纳百万级别个Actor。Actors模型适用于可以用于高并发、分布式场景。
JDK本身并没有提供对Actors并发模型的实现,不过其他的开发库中已经实现并且被广泛应用,例如本章的学习对象:Akka。
2.Akka Actors模型简介
Akka Actors官方文档地址:https://doc.akka.io/docs/akka/current/index-actors.html
Akka Actors的模型图如下:
总结:
- Actor与Actor之前只能用消息进行通信。
- 每个Actor都有一个邮箱。
- Actor与Actor之间并不是直接通过消息通信,而是将消息发送至此Actor的邮箱MailBox。
- 消息在邮箱MailBox是有序的。
- 每个Actor在处理单个消息时都是串行的。
- Actor中的消息是不可变的。
- 消息的传递不保证绝对可靠。
Akka Actors模型的优势:
- 轻量级事件比线程的粒度小得多,意味着可以在程序中使用大量的Actor。
- Akka提供了一套容错机制,允许在出错时进行一些恢复或者重置操作。
- Akka依靠其透明的远程Actor定位服务,保证了其分布式并发环境中的可用性。
3.Akka Actors重要概念
下面介绍Akka Actors模型的几个重要概念:
1.Actor
Actor即角色,前面已经说过,Akka Actors模型将actors当做通用的并行计算原语,所以Actor是必不可少的。
Actor总结:
- Akka Actor的组织结构是一种树形结。
- 因为Actor是树形组织,所以Actor的路径类似于文件的路径。
- 每个Actor都有父级,有可能有子级当然也可能没有。
- 父级Actor给其子级Actor分配资源,任务,并管理其的生命状态(监管和监控)。
- 如果我们知道一个远程Actor的具体位置,那么我们就可以向他发送消息。
- 一个本地Actor的路径:akka://search-system/user/master
- 一个远程Actor的路径:akka.tcp://search-system@host.example.com:5678/user/master
2.ActorSystem
ActorSystem即角色系统,为了统一的调度和管理系统中的众多actors,我们需要首先定义一个ActorSystem。
ActorSystem的主要功能有三个:
- 统一管理和调度actors,如:任务的拆分、处理等等。
- 配置Actor系统参数,如:拦截器、优先、路由策略等等。
- 日志功能:为了保证Akka的高容错机制,编程中尽量需要完善的日志记录,以便出错处理。
3.ActorRef
ActorRef即角色引用,每个Actor有唯一的ActorRef,Actor引用可以看成是Actor的代理,与Actor打交道都需要通过Actor引用。
4.Akka Actors入门编程流程
下面简述一下Akka Actors的入门级的编程流程:
- 编写Message类,用于存放消息。
- 编写Actor类,用于定义接收到消息之后所做的操作,注意日志处理。
- 定义一个ActorSystem,用于管理和调度程序中的Actor。
- 定义ActorRef,用于引用Actor类。
- 调用actorRef.tell(message),用于向此Actor发送消息。
- 关闭ActorSystem。
5.编程实例
下面以两个简单的例子来展示Actor并发编程的基本流程:
- Hello World
- 最快搜索:通过多个搜索引擎查询某个条件,返回最快的查询结果。
5.1.Hello World
场景说明:
- 定义一个消息类,用于存放被欢迎的内容。
- 定义一个角色类,用于欢迎接收到的消息。
实例代码:
消息类:
/** * <p>Hello的消息类</p> * @author hanchao 2018/4/16 21:34 **/ public class HelloMessage{ /** 欢迎的对象 */ private String name; //getter setter constructor toString }
角色类:
/** * <p>Actor框架入门示例-HelloWorld的角色</p> * * @author hanchao 2018/4/16 21:07 **/ public class HelloActor extends UntypedAbstractActor { //定义日志,很重要 LoggingAdapter log = Logging.getLogger(getContext().getSystem(),this); /** * <p>重写接收方法</p> * @author hanchao 2018/4/16 21:31 **/ @Override public void onReceive(Object message){ log.info("HelloActor receive message : " + message); //如果消息类型是HelloMessage,则进行处理 if (message instanceof HelloMessage){ log.info("Hello " + ((HelloMessage) message).getName() + "!"); } } }
测试类:
/** * <p>Actor入门示例</p> * @author hanchao 2018/4/16 21:37 **/ public static void main(String[] args) { //创建actor系统 ActorSystem system = ActorSystem.create("hello-system"); //定义Actor引用 ActorRef helloActor = system.actorOf(Props.create(HelloActor.class),"hello-actor"); //向HelloActor发送消息 helloActor.tell(new HelloMessage("World"),null); helloActor.tell(new HelloMessage("Akka Actor"),null); //终止Actor系统 system.terminate(); }
运行结果:
[INFO] [04/18/2018 22:37:55.744] [hello-system-akka.actor.default-dispatcher-2] [akka://hello-system/user/hello-actor] HelloActor receive message : HelloMessage{name='World'} [INFO] [04/18/2018 22:37:55.744] [hello-system-akka.actor.default-dispatcher-2] [akka://hello-system/user/hello-actor] Hello World! [INFO] [04/18/2018 22:37:55.744] [hello-system-akka.actor.default-dispatcher-2] [akka://hello-system/user/hello-actor] HelloActor receive message : HelloMessage{name='Akka Actor'} [INFO] [04/18/2018 22:37:55.744] [hello-system-akka.actor.default-dispatcher-2] [akka://hello-system/user/hello-actor] Hello Akka Actor!
5.2.最快搜索
场景说明:
通过多个搜索引擎查询某个条件,返回最快的查询结果。
- 定义一个消息类,用于存放搜索条件。
- 定义一个消息类,用于存放搜索结果。
- 定义一个角色类,用于接收搜索条件、搜索和返回搜索结果。
- 定义一个角色类,用于派发搜索任务和接收搜索结果。
实例代码:
搜索引擎工具类EngineUtils :模拟搜索引擎列表和搜索过程。
/** * 搜索引擎工具类 * Created by 韩超 on 2018/3/6. */ public class EngineUtils { private final static Logger LOGGER = Logger.getLogger(EngineUtils.class); //搜索引擎列表 private static List<String> engineList; static { engineList = new ArrayList<>(); engineList.add("百度"); engineList.add("Google"); engineList.add("必应"); engineList.add("搜狗"); engineList.add("Redis"); engineList.add("Solr"); } /** * <p>Title: 模拟一个搜索引擎进行一次问题查询</p> * @author 韩超 2018/3/6 11:20 */ public static String searchByEngine(String question,String engine) throws InterruptedException { //获取随机的时间间隔 int interval = RandomUtils.nextInt(1,5000); // LOGGER.info("搜索引擎[" + engine + "]正在查询,预计用时" + interval + "毫秒..."); //当前线程休眠指定时间,模拟搜索引擎用时 Thread.sleep(interval); return "通过搜索引擎[" + engine + "],首先查到关于(" + question + ")问题的结果,用时 = " + interval + "毫秒!"; } public static List<String> getEngineList() { return engineList; } public static void setEngineList(List<String> engineList) { EngineUtils.engineList = engineList; } }
搜索条件消息类QueryTerms ,用于存储搜索条件:
/** * <p>Title: 定义查询条件类,用于传递消息</p> * * @author 韩超 2018/3/6 16:16 */ static class QueryTerms { /** * 问题 */ private String question; /** * 搜索引擎 */ private String engine; //getter setter toString constructor }
搜索结果消息类QueryResult ,用于存放搜索结果:
/** * <p>Title: 定义查询结果类,用于消息传递</p> * * @author 韩超 2018/3/6 16:17 */ static class QueryResult { /** * 查询结果 */ private String result; //getter setter toString constructor }
搜索角色类SearchEngineAcotr ,用于接收搜索条件,调用搜索引擎进行搜索:
/** * <p>Title:搜索引擎Actor </br> * 继承UntypedAbstractActor成为一个Actor</p> * * @author 韩超 2018/3/6 14:42 */ static class SearchEngineAcotr extends UntypedAbstractActor { //定义Actor日志 private LoggingAdapter log = Logging.getLogger(getContext().getSystem(),this); /** * <p>Title: Actor都需要重写消息接收处理方法</p> * * @author 韩超 2018/3/6 14:42 */ @Override public void onReceive(Object message) throws Throwable { //如果消息是指定的类型Message,则进行处理,否则不处理 if (message instanceof QueryTerms) { log.info("接收到搜索条件:" + ((QueryTerms) message).getQuestion()); //通过工具类进行一次搜索引擎查询 String result = EngineUtils.searchByEngine(((QueryTerms) message).getQuestion(), ((QueryTerms) message).getEngine()); //通过getSender().tell(result,actor)将actor的 处理结果[result] 发送消息的发送者[getSender()] //通过getSender获取消息的发送方 //通过getSelf()获取当前Actor getSender().tell(new QueryResult(result), getSelf()); } else { unhandled(message); } } }
主角色类QuestionQuerier ,用于分发搜索任务和接收搜索结果:
/** * <p>Title: 问题查询器Actor</br> * 继承自UntypedAbstractActor</p> * * @author 韩超 2018/3/6 16:31 */ static class QuestionQuerier extends UntypedAbstractActor { //定义Actor日志 private LoggingAdapter log = Logging.getLogger(getContext().getSystem(),this); /** * 搜索引擎列表 */ private List<String> engines; /** * 搜索结果 */ private AtomicReference<String> result; /** * 问题 */ private String question; public QuestionQuerier(String question, List<String> engines, AtomicReference<String> result) { this.question = question; this.engines = engines; this.result = result; } /** * <p>Title: Actor都需要重写消息接收处理方法</p> * * @author 韩超 2018/3/6 16:35 */ @Override public void onReceive(Object message) throws Throwable { //如果收到查询结果,则对查询结果进行处理 if (message instanceof QueryResult) {//如果消息是指定的类型Result,则进行处理,否则不处理 log.info("接收到搜索结果:" + ((QueryResult) message).getResult()); //通过CAS设置原子引用的值 result.compareAndSet(null, ((QueryResult) message).getResult()); //如果已经查询到了结果,则停止Actor //通过getContext()获取ActorSystem的上下文环境 //通过getContext().stop(self())停止当前Actor getContext().stop(self()); } else {//如果没有收到处理结果,则创建搜索引擎Actor进行查询 log.info("开始创建搜索引擎进行查询"); //使用原子变量去测试Actor的创建是否有序 AtomicInteger count = new AtomicInteger(1); //针对每一个搜索引擎,都创建一个Actor for (String engine : engines) { log.info("为" + engine + "创建第" + count + "个搜索引擎Actor...."); count.getAndIncrement(); //通过actorOf(Props,name)创建Actor //通过Props.create(Actor.class)创建Props ActorRef fetcher = this.getContext().actorOf(Props.create(SearchEngineAcotr.class), "fetcher-" + engine.hashCode()); //创建查询条件 QueryTerms msg = new QueryTerms(question, engine); //将查询条件告诉Actor fetcher.tell(msg, self()); } } } }
测试代码:
/** * <p>Title:通过多个搜索引擎查询多个条件,并返回第一条查询结果 </p> * * @author 韩超 2018/3/6 14:15 */ public static void main(String[] args) { //通过工具类获取搜索引擎列表 List<String> engines = EngineUtils.getEngineList(); //通过 Actor 进行并发查询,获取最先查到的答案 String result = new FlavorActorDemo().getFirstResult("今天你吃了吗?", engines); //打印结果 } /** * 通过多个搜索引擎查询,并返回第一条查询结果 * * @param question 查询问题 * @param engines 查询条件数组 * @return 最先查出的结果 * @author 韩超 2018/3/6 16:44 */ @Override public String getFirstResult(String question, List<String> engines) { //创建一个Actor系统 ActorSystem system = ActorSystem.create("search-system"); //创建一个原子引用用于保存查询结果 AtomicReference<String> result = new AtomicReference<>(); //通过静态方法,调用Props的构造器,创建Props对象 Props props = Props.create(QuestionQuerier.class, question, engines, result); //通过system.actorOf(props,name)创建一个 问题查询器Actor final ActorRef querier = system.actorOf(props, "master"); //告诉问题查询器开始查询 querier.tell(new Object(), ActorRef.noSender()); //通过while无限循环 等待actor进行查询,知道产生结果 while (null == result.get()) ; //关闭 Actor系统 system.terminate(); //返回结果 return result.get(); }
运行结果(某一次):
[INFO] [04/18/2018 23:10:11.949] [search-system-akka.actor.default-dispatcher-7] [akka://search-system/user/master] 开始创建搜索引擎进行查询 [INFO] [04/18/2018 23:10:11.959] [search-system-akka.actor.default-dispatcher-7] [akka://search-system/user/master] 为百度创建第1个搜索引擎Actor.... [INFO] [04/18/2018 23:10:11.959] [search-system-akka.actor.default-dispatcher-7] [akka://search-system/user/master] 为Google创建第2个搜索引擎Actor.... [INFO] [04/18/2018 23:10:11.959] [search-system-akka.actor.default-dispatcher-7] [akka://search-system/user/master] 为必应创建第3个搜索引擎Actor.... [INFO] [04/18/2018 23:10:11.959] [search-system-akka.actor.default-dispatcher-6] [akka://search-system/user/master/fetcher-964584] 接收到搜索条件:今天你吃了吗? [INFO] [04/18/2018 23:10:11.959] [search-system-akka.actor.default-dispatcher-7] [akka://search-system/user/master] 为搜狗创建第4个搜索引擎Actor.... [INFO] [04/18/2018 23:10:11.959] [search-system-akka.actor.default-dispatcher-7] [akka://search-system/user/master] 为Redis创建第5个搜索引擎Actor.... [INFO] [04/18/2018 23:10:11.959] [search-system-akka.actor.default-dispatcher-7] [akka://search-system/user/master] 为Solr创建第6个搜索引擎Actor.... [INFO] [04/18/2018 23:10:11.969] [search-system-akka.actor.default-dispatcher-7] [akka://search-system/user/master/fetcher-2138589785] 接收到搜索条件:今天你吃了吗? [INFO] [04/18/2018 23:10:11.969] [search-system-akka.actor.default-dispatcher-2] [akka://search-system/user/master/fetcher-2582786] 接收到搜索条件:今天你吃了吗? [INFO] [04/18/2018 23:10:11.969] [search-system-akka.actor.default-dispatcher-5] [akka://search-system/user/master/fetcher-784239] 接收到搜索条件:今天你吃了吗? [INFO] [04/18/2018 23:10:11.969] [search-system-akka.actor.default-dispatcher-3] [akka://search-system/user/master/fetcher-78837083] 接收到搜索条件:今天你吃了吗? [INFO] [04/18/2018 23:10:11.975] [search-system-akka.actor.default-dispatcher-4] [akka://search-system/user/master/fetcher-823867] 接收到搜索条件:今天你吃了吗? [INFO] [04/18/2018 23:10:13.255] [search-system-akka.actor.default-dispatcher-11] [akka://search-system/user/master] 接收到搜索结果:通过搜索引擎[搜狗],首先查到关于(今天你吃了吗?)问题的结果,用时 = 1279毫秒! [INFO] [04/18/2018 23:10:15.504] [search-system-akka.actor.default-dispatcher-5] [akka://search-system/user/master] Message [pers.hanchao.flavors.FlavorActorDemo$QueryResult] from Actor[akka://search-system/user/master/fetcher-2582786#-286481483] to Actor[akka://search-system/user/master#-1184484885] was not delivered. [1] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'. [INFO] [04/18/2018 23:10:15.504] [search-system-akka.actor.default-dispatcher-5] [akka://search-system/user/master] Message [pers.hanchao.flavors.FlavorActorDemo$QueryResult] from Actor[akka://search-system/user/master/fetcher-964584#166806804] to Actor[akka://search-system/user/master#-1184484885] was not delivered. [2] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'. [INFO] [04/18/2018 23:10:15.504] [search-system-akka.actor.default-dispatcher-5] [akka://search-system/user/master] Message [pers.hanchao.flavors.FlavorActorDemo$QueryResult] from Actor[akka://search-system/user/master/fetcher-2138589785#376820931] to Actor[akka://search-system/user/master#-1184484885] was not delivered. [3] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'. [INFO] [04/18/2018 23:10:15.504] [search-system-akka.actor.default-dispatcher-5] [akka://search-system/user/master] Message [pers.hanchao.flavors.FlavorActorDemo$QueryResult] from Actor[akka://search-system/user/master/fetcher-78837083#-224726121] to Actor[akka://search-system/user/master#-1184484885] was not delivered. [4] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'. [INFO] [04/18/2018 23:10:15.504] [search-system-akka.actor.default-dispatcher-5] [akka://search-system/user/master] Message [pers.hanchao.flavors.FlavorActorDemo$QueryResult] from Actor[akka://search-system/user/master/fetcher-784239#1622650486] to Actor[akka://search-system/user/master#-1184484885] was not delivered. [5] dead letters encountered. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
参考文献
[1] Akka系列(一):Akka简介与Actor模型
[2] 漫谈并发编程:Actor模型
[3] Java并发的四种风味:Thread、Executor、ForkJoin和Actor