Akka 是 JVM 平台上构建高并发、分布式和容错应用的工具包和运行时环境。Akka用Scala 语言编写,同时提供了 Scala 、JAVA 的开发接口。
Akka 处理并发的方法基于 Actor 模型。在基于 Actor的系统里,所有的事物都是 Actor。这就好像在面向对象设计里面所有的事物都是对象一样。但是有一个重要区别,那就是Actor模型是作为一个并发模型设计和架构的,而面向对象模式则不是。Actor 与Actor之间只能通过消息通信。
为什么 Actor 模型是一种处理并发问题的解决方案呢?
处理并发问题就是如何保证共享数据的一致性和正确性,为什么会有保持共享数据正确性这个问题呢?
答:无非是我们的程序是多线程的,多个线程对同一个数据进行修改,若不加同步条件,势必会造成数据污染。那么我们是不是可以转换一下思维,用单线程去处理相应的请求,但是又有人会问了,若是用单线程处理,那系统的性能又如何保证。Actor模型的出现解决了这个问题,简化并发编程,提升程序性能。
从图中可以看到,Actor 与 Actor 之前只能用消息进行通信,当某一个 Actor 给另外一个 Actor发消息,消息是有顺序的,只需要将消息投寄到相应的邮箱,至于对方 Actor 怎么处理你的消息你并不知道,当然你也可等待它的回复。
Actor 是 ActorSystem 创建的,ActorSystem 的职责是负责创建并管理其创建的 Actor,ActorSystem 的单例的,一个 JVM 进程中有一个即可,而 Acotr 是多例的。
如果Actor A需要给Actor B发送消息,必须获得ActorRef B,然后调用ActorRef B的recive方法,在该方法内有相关的API(sender方法)可以获得是由Actor A作为发送方发送给B的消息。
导入pom 依赖:
4.0.0
cn.itcats.akka
itcats-akka
1.0-SNAPSHOT
UTF-8
2.11.8
2.11
2.4.17
org.scala-lang
scala-library
${scala.version}
com.typesafe.akka
akka-actor_${scala.compat.version}
${akka.version}
com.typesafe.akka
akka-remote_${scala.compat.version}
${akka.version}
src/main/scala
src/test/scala
net.alchim31.maven
scala-maven-plugin
3.2.2
compile
testCompile
-dependencyfile
${project.build.directory}/.scala_dependencies
org.apache.maven.plugins
maven-shade-plugin
2.4.3
package
shade
*:*
META-INF/*.SF
META-INF/*.DSA
META-INF/*.RSA
reference.conf
cn.sheep.robot.ClientActor
创建一个最简单的Actor模型,实现自己给自己发消息的环回实验
//继承akka.actor.Actor
class HelloActor extends Actor{
//用于接收Receive
//type Receive = PartialFunction[Any, Unit]
override def receive: Receive = {
//接收消息并处理
case "itcats" => println("This is itcats")
case "itcats_cn" => println("This is itcats_cn")
case "stop" => {
context.stop(self) //self: ActorRef引用自己,将Actor关闭
context.system.terminate() //关闭ActorSystem
}
case _ => println("What is is?")
}
}
object HelloActor{
//1.获取ActorSystem工厂对象,并命名
private val helloActorFactory = ActorSystem("HelloActorFactory")
//2.从ActorSystem获取HelloActor的引用对象ActorRef,并命名
private val helloActorRef: ActorRef = helloActorFactory.actorOf(Props[HelloActor],"helloActor")
def main(args: Array[String]): Unit = {
//给自己(HelloActor)发送消息 方法名称就是!
helloActorRef ! "itcats" //给自己发送了消息itcats
helloActorRef ! "itcats_cn" //给自己发送了消息itcats_cn
helloActorRef ! "stop" //给自己发送消息stop
}
}
打印结果:
This is itcats
This is itcats_cn
打印完运行结果后,发现Actor工程一直在运行。实际上说明了Dispatcher Message内部是一个线程池,receive()方法实际上是从自己的Mail Box中取出消息,内部类似于调用Runnable的run方法。
import akka.actor.{Actor, ActorRef}
class AActor(val bActor: ActorRef) extends Actor{
override def receive: Receive = {
case "a is ready" => {
println("a is ready")
bActor ! "啪"
}
case "啪啪" => {
println("接到了BActor的球")
Thread.sleep(1000)
bActor ! "啪"
}
}
}
import akka.actor.Actor
class BActor extends Actor {
override def receive: Receive = {
case "b is ready" => println("b is ready")
case "啪" => {
println("接到了AActor的球")
Thread.sleep(1000)
sender() ! "啪啪"
}
}
}
执行类:
import akka.actor.{ActorRef, ActorSystem, Props}
// A --> B
// B --> A
object PingPongMain {
def main(args: Array[String]): Unit = {
//创建ActorSystem
val pingpongActorSystem = ActorSystem("pingpongActorSystem")
//创建bActor的引用对象
val bActor: ActorRef = pingpongActorSystem.actorOf(Props[BActor],"bActor")
//创建aActor的引用对象
val aActor: ActorRef = pingpongActorSystem.actorOf(Props(new AActor(bActor)),"aActor")
bActor ! "b is ready"
aActor ! "a is ready"
}
}
假设智能聊天客服能回答如下问题
1、在吗
2、你叫什么名字
3、你是男的还是女的
4、你在哪
对于其他问题直接返回:我听不懂你的问题!
客户端程序:
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
class ChatServer extends Actor {
override def receive: Receive = {
case "start" => println("服务器已就绪")
case ClientToServerMessage(msg) => {
println(s"收到客户端消息: $msg")
msg match {
case "在吗" => sender() ! ServerToClientMessage("我在")
case "你叫什么名字" => sender() ! ServerToClientMessage("我是中国电信语音客服")
case "你是男的还是女的" => sender() ! ServerToClientMessage("我是小姐姐哟~")
case "你在哪" => sender() ! ServerToClientMessage("我在广东省广州市")
case _ => sender() ! ServerToClientMessage("我听不懂你亲的意思,客服还在不断学习中哦~")
}
}
}
}
//为了服务端能够独立运行,定义一个伴生对象
object ChatServer extends App {
val host: String = "127.0.0.1"
val port: Int = 8878
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$host
|akka.remote.netty.tcp.port=$port
""".stripMargin)
//指定服务器的IP和端口号
private val server = ActorSystem("Server", config)
private val serverActorRef: ActorRef = server.actorOf(Props[ChatServer], "chat")
serverActorRef ! "start"
}
客户端程序:
import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.io.StdIn
class ClientActor extends Actor{
var serverActorRef: ActorSelection = _
//根据服务端的协议地址
override def preStart(): Unit = {
//获得服务端的引用对象
serverActorRef = context.actorSelection("akka.tcp://[email protected]:8878/user/chat")
}
override def receive = {
case "start" => println("聊天客户端已就绪")
case msg: String => {
serverActorRef ! ClientToServerMessage(msg)
}
case ServerToClientMessage(msg) => {
println(s"收到服务端消息: $msg")
}
}
}
//创建伴生类对象,可执行
object ClientActor extends App {
val host: String = "127.0.0.1"
//再启动只需修改端口号即可
val port: Int = 8879
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$host
|akka.remote.netty.tcp.port=$port
""".stripMargin)
//指定服务器的IP和端口号
private val clientSystem = ActorSystem("client", config)
private val actorRef: ActorRef = clientSystem.actorOf(Props[ClientActor], "chatClient1")
//再启动一个客户端修改名称和端口号即可
//private val actorRef: ActorRef = clientSystem.actorOf(Props[ClientActor], "chatClient2")
actorRef ! "start"
//接收客户端的参数
while(true){
//获取用户输入的内容
val question = StdIn.readLine()
actorRef ! question
}
}
协议格式样例类:
//服务端发送给客户端的消息格式
case class ServerToClientMessage(msg: String)
//客户端发送给服务端的消息格式
case class ClientToServerMessage(msg: String)