在开发初期,业务单一、系统简单,一台机器或许就能支撑,这个时候,单机上的任务大多直接调用API的方式就能完成。然而当业务需求日益增多,功能逐渐复杂化,我们就需要考虑把项目拆分成分布式环境,这时,多系统通信就需要通过网络实现。JDK内置的并发包在单机上游刃有余,在分布式环境中,就不是那么完美了。Akka通过Actor很好的解决了远程通信问题。
Actor初识
Akka中,Actor使用消息传递进行通信,完成任务。Actor编程,不同于之前通过对象调用完成某个功能,它采用“问答”式API进行通信,这种方式更加贴近日常生活,易于理解。另外,使用Actor,不是操作Actor对象,而是操作它的引用对象(ActorRef),在Akka中,ActorRef对Actor做了很好的封装,防止外界对Actor的状态进行修改。
下面,来一个小示例认识一下actor:
首先,我们创建一个maven project,并在pom.xml中引入Akka依赖:
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.11</artifactId>
<version>2.5.16</version>
</dependency>
定义Actor:
public class ActorDemo extends AbstractActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().getSystem(), this);
@Override
public Receive createReceive() {
return receiveBuilder().match(String.class, s -> {
logger.info("小明:" + s);
}).matchAny(other -> {
logger.info("其它未知消息:" + other);
}).build();
}
public static void main(String[] args) {
ActorSystem system = ActorSystem.create("system");
ActorRef demo = system.actorOf(Props.create(ActorDemo.class), "actorDemo");
demo.tell("hello world",ActorRef.noSender());
final Integer other=20;
demo.tell(other,ActorRef.noSender());
}
}
执行结果:
[INFO] [09/22/2018 10:35:54.295] [system-akka.actor.default-dispatcher-4] [akka://system/user/actorDemo] 小明:hello world
[INFO] [09/22/2018 10:35:54.296] [system-akka.actor.default-dispatcher-4] [akka://system/user/actorDemo] 其它未知消息:20
我们通过extends AbstractActor 类并且重写createReceive方法创建Actor。重写createReceive(),我们可以决定自己的actor可以接受何种类型的消息,以及如何处理这些消息。receiveBuilder工厂定义了许多API,可以方便我们处理。上述代码,actorDemo被允许接受String类型消息,当不属于String类型的消息,我们当做未知消息进行处理。
Actor创建方式
在Akka中,我们一般会使用ActorSystem或ActorContext来创建Actor。在上述示例中,我们使用的是ActorSystem来创建,它是一个比较重量级的对象,通常一个应用程序我们只会创建一个该对象。在创建Actor时,最好给出相应名字,用于业务识别。特别注意,在同一个ActorSystem中,actor不能重名。在实际项目中,我们更希望创建具有层级的actor,让父级对子级进行管理和监控,这时,我们就会使用ActorContext来创建。例如:
ActorRef actorDemo = getContext().actorOf(Props.create(ActorDemo.class), "actorDemo");
Actor构建工具Props
通过上述介绍发现,无论是ActorSystem,还是ActorContext,它们都是通过接受一个Props对象来创建Actor。Props是一个配置类,Akka为我们提供三种方式,如下:
Props props1 = Props.create(ActorDemo.class);
Props props2 = Props.create(ActorDemo.class,() -> new ActorWithArgs("arg"));
Props props3 = Props.create(ActorDemo.class, "arg");
消息
在actor网络中,actor与actor进行通信都是通过消息传递。这就好比现实中的写信,A和B两个人只知道各自的地址,没有其它联系方式,这时他们通过信件来进行沟通,A给B发完信件之后,又可以去做其它的事情,B也是一样。于此同时,A和B也可以接受其他小伙伴发送的信件。这里的信件就类似actor之间的消息,通过异步的方式发送。
消息的发送分为两种:
类别 |
特点 |
tell |
异步发送消息并立即返回。 |
ask |
异步发送消息,返回Future表示可能的结果,可以设置超时时间,超时未得到结果,返回超时异常。 |
tell方法
当我们仅仅只需要异步发送消息的时候,tell方法是一个不错的选择。
demo.tell("hello world",ActorRef.noSender());
tell方法有两个参数,第一个参数表示消息,它可以接受任何类型的数据,第二个参数表示发送者,也就是另外一个actor的引用对象。上述使用的ActorRef.noSender(),表示没有发送者(其实是一个叫做deadLetters的Actor)。在项目中,如果我们想得到发送者,可以调用getSender()方法。
ask方法
这个方法类似于java中的Future模式,也是异步的得到一个返回对象,典型的“请求-响应”模式。通常我们采用异步回调的方式获取Future对象。
ask方法不能通过actorRef对象调用,而是 通过akka.pattern.Patterns包下的Patterns.ask方式来操作,ask方法有三个参数,第一个参数表示接受消息的ActorRef,第二个参数表示发送的消息,第三个参数表示超时时间。ask方法会返回一个Future对象,它提供了三个回调方法,供我们处理返回数据和异常—onSuccess、onFailure、onComplete。例如:
Future<Object> future = Patterns.ask(demo, "hello world", new Timeout(Duration.create(1, TimeUnit.SECONDS)));
future.onSuccess(new OnSuccess<Object>() {
@Override
public void onSuccess(Object o) throws Throwable {
System.out.println("返回结果:" + o);
}
}, system.dispatcher());
通过forward转发消息,发送给TargetActor消息的sender仍然是ForwardActor的发送者,而不是ForwardActor。
消息转发
在Actor通信中,我们除了可以给某个Actor直接发送消息之外,还可以通过forward转发的方式。forward表明Akka会保留消息原始发件人的地址及引用,当我们在实现消息路由或者负载均衡时,非常有用。示例:
public class ForwardActor extends AbstractActor {
private ActorRef actorRef = getContext().actorOf(Props.create(TargetActor.class), "targetActor");
public static void main(String[] args) {
ActorSystem actorSystem = ActorSystem.create();
ActorRef forwardActor = actorSystem.actorOf(Props.create(ForwardActor.class), "forwardActor");
forwardActor.tell("hello world", ActorRef.noSender());
}
@Override
public Receive createReceive() {
return receiveBuilder().match(String.class, s -> {
actorRef.forward(s, getContext());
}).build();
}
}
class TargetActor extends AbstractActor {
@Override
public Receive createReceive() {
return receiveBuilder().match(String.class, s -> {
System.out.println("forward message:" + s);
}).build();
}
}
查找
在介绍actor查找之前,我们需要先了解一下actor路径问题。
之前我们已经谈到,创建actor可以使用ActorSystem或ActorContext,使用ActorSystem创建的actor是我们所能创建的最高级别actor,也就是在/user/目录下。在上述第一个示例中,输出日志中,有这样一段akka://system/user/actorDemo,这就代表actorDemo的路径,其中system是我们定义的ActorSystem名称,user是我们创建actor所在的父目录(我们创建的actor都在它之下),actorDemo是我们创建的actor名称,它们共同组成一个actor路径。假如我们在ActorDemo中,使用ActorContext创建一个Actor—otherActor,那么otherActor的路径就是akka://system/user/actorDemo/otherActor,以此类推。
在实际项目中,当我们需要调用某一个Actor时,我们也可以使用路径去定位到它,相关API:
ActorSelection=[ActorSystem/ActorContext].actorSelection([path]);
其中path,我们可以使用相对或者是绝对路径,例如:
/user/actorDemo
../actorDemo
akka.tcp://[email protected]:8080/user/parentActor/childActor (远程actor路径)
调用actorSelection会返回一个ActorSelection对象,当我们想得到目标ActorRef时,可以向ActorSelection发送一个Identify对象消息,然后对方会自动返回一个ActorIdentity对象,该对象包含了目标ActorRef信息,示例:
class TargetActor extends AbstractActor {
@Override
public Receive createReceive() {
return receiveBuilder().match(Object.class, o -> {
System.out.println("receive message:" + o);
}).build();
}
}
class LookupActor extends AbstractActor {
private ActorRef actorRef = getContext().actorOf(Props.create(TargetActor.class), "targetActor");
public static void main(String[] args) {
ActorSystem actorSystem = ActorSystem.create("system");
ActorRef actor = actorSystem.actorOf(Props.create(LookupActor.class), "lookupActor");
actor.tell("find", ActorRef.noSender());
}
@Override
public Receive createReceive() {
return receiveBuilder().match(String.class, s -> {
if ("find".equals(s)) {
ActorSelection targetActor = getContext().actorSelection("targetActor");
targetActor.tell(new Identify("001"), getSelf());
}
}).match(ActorIdentity.class, i -> {
if (i.correlationId().equals("001")) {
Optional<ActorRef> ref = i.getActorRef();
if (ref != null) {
ActorRef actorRef = ref.get();
System.out.println("id:" + i.correlationId() + " " + actorRef);
actorRef.tell("hello targetActor", getSelf());
}
}
}).build();
}
}
执行结果:
id:001 Actor[akka://system/user/lookupActor/targetActor#1717439239]
receive message:hello targetActor
上述代码中,由于TargetActor是LookupActor的子集,所以我们可以指定为“targetActor”。由于我们指定actor,有可能找不到,所以在获取目标ActorRef时,需要判断是否为空,以免出现异常。
Actor查找,不仅可以使用相对或绝对路径,而且还可以使用通配符进行匹配,例如:
../* 查找所有同级的actor
/user/parentActor/* 查找parentActor下面的所有子actor
/user/parentActor/child* 查找parentActor下面所有以child开头的子actor
另外,我们还可以使用ActorSelection的resolveOne得到ActorRef,该方法会返回一个Future对象,和ask返回Future对象相同,也具有onSuccess和onFailure回调方法。我们可以使用onSuccess回调方法获取ActorRef对象,使用onFailure方法处理异常信息。这里不做过多描述,大家有兴趣可以自行研究研究。
在这里对Actor的基本用法做了大致讲解,下一节,对Actor的生命周期做详细描述,由于本人才疏学浅,有不对的地方,望大家批评指正,共同进步,