- AKKA介绍:
- AKKA是java虚拟机JVM平台上构建高并发、分布式和容错应用工具包和运行时。可以理解为Akka是编写并发程序的框架
- AKKA是scala语言写成,同时提供了scala和java的接口
- AKKA主要解决的问题是:可以轻松的写出高效稳定的并发程序,程序员不在 过多的考虑县城,锁,和资源竞争等细节
- 主要解决什么问题
- 处理并发问题保证共享数据的一致性和正确性,因为程序是多线程时,多线程同事对一个数据进行修改,如果不加锁等同步条件,实际上大并发就会阻塞在这段代码中,对程序效率有很大影响
- 若是用单线程处理,不会有数据一致性问题,但是系统的性能又不能保证
- Actor模型的出现解决了这个问题,简化并发问题,提升程序性能,是一种并发问题的解决方案。
- Akka Actor的模型
- AKKA处理并发的方法基于Actor模型
- 在基于actor的系统里,所有的事物是actor,就好像在面向对象设计里面所有的事物都是对象一样
- Actor模型是作为一个并发模型设计和架构的,actor于actor之间只能通过消息通信
- Actor于Actor之间只能用消息通信,消息是有顺序的
- ActorSystem 的职责是负责创建并管理起创建的Actor,在一个JVM进行中有一个即可
- Actor模型是对并发模型进行了高的抽象
- Actor模型是异步,非阻塞,高性能的事件驱动编程模型
- Actor模型是轻量级时间处理,1GB内存可容纳百万级别个Actor。因此处理大并发性能很高
- 模型工作机制说明
Actor模型说明
1.ActorSystem创建Actor
2.ActorRef:可以理解成是Actor的代理或者引用,消息是通过ActoRef来发送的,而不能通过Actor直接发送消息,通过哪个ActorRef发消息就说明把该消息发送给哪个Actor
3.消息发送 到Dispatch Message(消息分发器,可以理解为一个线程池),它得到消息后会将消息进行分发到对应的MailBox(可以理解为一个消息队列,FIFO)
4.Actor可以通过receive方法来获取消息,然后进行处理
Actor间消息传递机制
1.每个消息就是一个message对象,message继承了Runnable,因为Message就是线程类
2.从Actor模型工作机制看上去很麻烦,但是程序员编程时只需要编写Actor就可以了,其他的交给Actor模型完成即可
3.A Actor要给B Actor发送消息,那么 A Actor要先拿到B Actor的代理对象ActorRef才能发送对象
测试用例:
package analyse
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
class YellowClikenServer extends Actor {
override def receive: Receive = {
case "start" => println("start 小黄鸡客服开始工作了...")
// 如果接收到了服务端的发来的消息,即 ClientMessage
case ClientMessage(mes) => {
println("客户咨询的问题是:" + mes)
mes match {
// 使用 match case 匹配(模糊匹配)
case "价格" => sender() ! ServerMessage("20000 RMB")
case "地址" => sender() ! ServerMessage("北京市")
case "技术" => sender() ! ServerMessage("大数据")
case _ => sender() ! ServerMessage("你说的啥子:)")
}
}
}
}
// 主程序入口
object YellowChickenServerApp extends App {
val host = "127.0.0.1" // 服务端ip地址
val port = 9999 // 端口
// 创建 config 对象,指定协议类型、监听的ip和端口
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$host
|akka.remote.netty.tcp.port=$port
""".stripMargin)
// 创建 ActorSystem
val serverActorSystem = ActorSystem("Server", config)
// 创建 YellowChickenServer 的 Actor 和 ActorRef
val yellowChickenServerActorRef: ActorRef = serverActorSystem.actorOf(Props[YellowClikenServer], "YellowChickenServer-01")
// 启动服务端
yellowChickenServerActorRef ! "start"
}
package analyse
import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.io.StdIn
class CustomerActor(serverHost: String, serverPort: Int) extends Actor {
// 定义一个 YellowChickenServerRef
var serverActorRef: ActorSelection = _
// 在 Actor 中有一个方法 preStart 方法,它会在 Actor 运行前执行
// 在 Akka 开发中,通常将初始化的工作,放在 preStart 方法中
override def preStart(): Unit = {
this.serverActorRef = context.actorSelection(s"akka.tcp://Server@${serverHost}:${serverPort}/user/YellowChickenServer-01")
println("this.serverActorRefer=" + this.serverActorRef)
}
override def receive: Receive = {
case "start" => println("start 客户端运行,可以咨询问题")
case mes: String => {
// 发给服务端
// serverActorRef ! mes // 不应该发送字符串,应该包装一把,应该发送一个(样例)对象(即协议)
serverActorRef ! ClientMessage(mes) // 此时发送的是一个对象,该样例类默认实现了序列化 和 apply 方法
}
// 如果接受到了服务器端的消息
case ServerMessage(mes) => {
println(s"收到小黄鸡客服(Server)消息:$mes")
}
}
}
// 主程序入口
object CustomerActorApp extends App {
val (host, port, serverHost, serverPort) = ("127.0.0.1", 9990, "127.0.0.1", 9999)
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$host
|akka.remote.netty.tcp.port=$port
""".stripMargin)
// 创建 ActorSystem
val clientActorSystem = ActorSystem("Client", config)
// 创建 CustomerActor 的 Actor 和 ActorRef
val clientActorRef: ActorRef = clientActorSystem.actorOf(Props(new CustomerActor(serverHost, serverPort)), "CustomerActor-01")
// 启动客户端
clientActorRef ! "start"
// 客户端发送消息
while (true) {
val mes = StdIn.readLine()
clientActorRef ! mes
}
}