一、基本介绍
1. Akka是JAVA虚拟机JVM平台上构建高并发、分布式和容错应用的工具包和运行时,你可以理解成Akka是
编写并发程序的框架;
2. Akka用Scala语言写成,同时提供了Scala和JAVA的开发接口;
3. Akka主要解决的问题是:可以轻松的写出高效稳定的并发程序,程序员不再过多的考虑线程、锁和资源竞
争等细节。
示意图:
二、Actor模型用于解决什么问题
1. 处理并发问题关键是要保证共享数据的一致性和正确性,因为程序是多线程时,多个线程对同一个数据
进行修改,若不加同步条件,势必会造成数据污染。但是当我们对关键代码加入同步条件synchronized 后,
实际上大并发就会阻塞在这段代码,对程序效率有很大影响;
2. 若是用单线程处理,不会有数据一致性的问题,但是系统的性能又不能保证;
3. Actor 模型的出现解决了这个问题,简化并发编程,提升程序性能。 可以这里理解:Actor 模型是一种处理
并发问题的解决方案,很牛!
三、Akka中Actor模型
示意图:
Actor模型及其说明
1. Akka处理并发的方法基于Actor模型(示意图);
2. 在基于Actor的系统里,所有的事物都是Actor,就好像在面向对象设计里面所有的事物都是对象一样;
3. Actor模型是作为一个并发模型设计和架构的。其中Actor与Actor之间只能通过消息通信,如图中的信封;
4. Actor与Actor之间只能用消息进行通信,当一个Actor给另外一个Actor发消息,消息是有顺序的(消息队列),
只需要将消息投寄的相应的邮箱即可;
5. 怎么处理消息是由接收消息的Actor决定的,发送消息Actor可以等待回复,也可以异步处理【ajax】;
6. ActorSystem的职责是负责创建并管理其创建的Actor, ActorSystem是单例的(可以ActorSystem是一个工厂,
专门创建Actor),一个JVM进程中有一个即可,而Acotr是可以有多个的;
7. Actor模型是对并发模型进行了更高的抽象;
8. Actor模型是异步、非阻塞、高性能的事件驱动编程模型。[案例:说明 什么是异步、非阻塞,最经典的案例就是
ajax异步请求处理 ];
9. Actor模型是轻量级事件处理(1GB 内存可容纳百万级别个 Actor),因此处理大并发性能高。
四、Actor模型工作机制说明
示意图:
Actor模型工作机制说明(对照上面示意图)
1. ActorySystem:创建Actor;
2. ActorRef:可以理解成是Actor的代理或者引用。消息是通过ActorRef来发送,而不能通过Actor发送消息,
通过哪个ActorRef 发消息,就表示把该消息发给哪个Actor;
3. 消息发送到Dispatcher Message (消息分发器),它得到消息后,会将消息进行分发到对应的MailBox。(注:
Dispatcher Message可以理解成是一个线程池, MailBox 可以理解成是消息队列,可以缓冲多个消息,遵守
FIFO);
4. Actor可以通过receive方法来获取消息,然后进行处理。
Actor间传递消息机制(对照上面示意图)
1. 每一个消息就是一个Message对象。Message 继承了Runable, 因为Message就是线程类;
2. 从Actor模型工作机制看上去很麻烦,但是程序员编程时只需要编写Actor就可以了,其它的交给Actor模型完
成即可;
3. A Actor要给B Actor 发送消息,那么A Actor 要先拿到(也称为持有) B Actor的代理对象ActorRef才能发送消息。
五、Actor模型快速入门
应用实例需求
1. 编写一个Actor, 比如SayHelloActor;
2. SayHelloActor可以给自己发送消息;
3. 要求使用Maven的方式来构建项目,这样可以很好的解决项目开发包的依赖关系。[scala 和 akka]。
效果如下图
Actor自我通讯机制原理图
示例代码:
package com.lj.akka
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
/**
* @author Administrator
* @create 2020-03-19
* 说明:当我们继承Actor之后,就是一个Actor,核心方法是receive,进行重写
*/
class SayHelloActor extends Actor{
/**
* 说明:
* 1. receive方法会被该Actor的MailBox(实现了Runable接口)调用;
* 2. 当该Actor的MailBox接收到消息,就会调用receive方法;
* 3. type Receive = PartialFunction[Any, Unit](Receive是一个偏函数);
* 4. receive方法循环接收消息。
*/
// 偏函数的简写形式
override def receive: Receive = {
case "Hello" => println("收到Hello,回应Hello too.")
case "Ok" => println("收到OK,回应OK too.")
case "EXIT" => {
println("收到EXIT指令,退出系统!")
context.stop(self) // 停止 actorRef
context.system.terminate() // 退出actor system
}
case _ => println("接收指令有误!")
}
}
object SayHelloActorMain {
// 1. 先创建一个ActorSystem,专门用于创建Actor
private val actorFactory = ActorSystem("actorFactory")
// 2. 创建一个Actor的同时,返回Actor的ActorRef
/**
* 说明:
* 1. Props[SayHelloActor]:创建一个SayHelloActor实例,使用反射创建,
* 也可以使用new创建Props(new SayHelloActor)
* 2. "sayHelloActor"是给Actor取名
* 3. sayHelloActorRef: ActorRef就是Props[SayHelloActor]的ActorRef
* 4. 创建SayHelloActor实例被ActorSystem接管
*/
private val sayHelloActorRef: ActorRef = actorFactory.actorOf(Props[SayHelloActor], "sayHelloActor")
def main(args: Array[String]): Unit = {
// 给sayHelloActorRef发送消息
// !是方法名(看源码)
sayHelloActorRef ! "Hello"
sayHelloActorRef ! "Ok"
sayHelloActorRef ! "Ok~"
sayHelloActorRef ! "EXIT"
}
}
小结和说明
当程序执行 AActorRef = actorFactory.actorOf(Props[AActor], "AActor") ,会完成如下任务 [这是非常重要的方法]:
1. actorFactory是ActorSystem("ActorFactory") 这样创建的;
2. 这里的Props[AActor] 会使用反射机制,创建一个AActor对象,如果是actorFactory.actorOf(Props(new
AActor(BActorRef)), "AActorRef") 形式,就是使用new的方式创建一个AActor对象, 注意Props()是小括号;
3. 会创建一个AActor对象的代理对象 AActorRef , 使用AActorRef才能发送消息;
4. 会在底层创建Dispather Message,是一个线程池,用于分发消息, 消息是发送到对应的Actor的MailBox;
5. 会在底层创建AActor的MailBox对象,该对象是一个队列,可接收Dispatcher Message发送的消息;
6. MailBox 实现了Runnable接口,是一个线程,一直运行并调用Actor的receive方法,因此当Dispather发送
消息到MailBox时,Actor在receive方法就可以得到信息;
7. AActorRef ! "hello", 表示把hello消息发送到AActor的mailbox(通过Dispatcher Message 转发)。
六、Actor模型应用实例-Actor间通讯
应用实例需求
1. 编写2个 Actor , 分别是 AActor 和 BActor;
2. AActor和BActor之间可以相互发送消息。
两个Actor的通讯机制原理图
示例代码:
package com.lj.akka
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import scala.io.StdIn
/**
* @author Administrator
* @create 2020-03-19
*/
class AActor(actorRef: ActorRef) extends Actor{
val bActorRef: ActorRef = actorRef
var a_num: Int = 0
override def receive: Receive = {
case "start" => {
// println("AActor(黄飞鸿)出招了,开打!")
println("开启两个Actor之间的通讯聊天......")
self ! a_num // 发送消息给自己a
}
case num: Int => {
/**
* 说明:
* 1. 此时需要给BActor发送消息,就需要拥有BActorRef
* 所以需要在创建AActor的时候就创建BActorRef
*/
if (num == 3) { // 出招5次,需要终止,不分胜负
bActorRef ! a_num // 给BActor发出消息
} else {
// println("AActor(黄飞鸿):厉害,看我佛山无影脚...")
print("AActor:")
StdIn.readLine()
Thread.sleep(10)
a_num += 1
bActorRef ! a_num
}
}
case "AActor Exit" => {
println("AActor收到EXIT指令,退出系统!")
sender() ! "BActor Exit"
// 停止AActor的ActorRef
context.stop(self)
context.system.terminate() // 退出actor system
}
}
}
class BActor extends Actor {
var b_num: Int = 0
override def receive: Receive = {
case num: Int => {
if (num == 3) { // 出招5次,需要终止,不分胜负
sendMessage
} else {
sendMessage
}
}
case "BActor Exit" => {
println("BActor收到EXIT指令,退出系统!")
context.stop(self) // 停止 actorRef
context.system.terminate() // 退出actor system
}
}
def sendMessage(): Unit = {
// println("BActor(乔峰):挺猛,看我降龙十八掌...")
print("BActor:")
StdIn.readLine()
Thread.sleep(10)
b_num += 1
// 通过sender()可以获取到发送消息的Actor的ActorRef
if (b_num == 3) {
sender() ! "AActor Exit"
} else {
sender() ! b_num // 给AActor发送消息
}
}
}
/**
* 说明:
* 1. 这里可以不写main方法,可以直接继承App(它是一个trait,实现了main方法的功能。)
*/
object ActorGame extends App {
// 1. 创建ActorSystem
private val actorFactory = ActorSystem("actorFactory")
// 2. 首先需要先创建BActor的ActorRef(因为AActor应用到了BActor的ActorRef)
private val bActorRef: ActorRef = actorFactory.actorOf(Props[BActor], "BActor")
// 3. 再创建AActor的ActorRef
private val aActorRef: ActorRef = actorFactory.actorOf(Props(new AActor(bActorRef)), "AActor")
// AActor开始出招!
aActorRef ! "start"
}
==================================运行结果==================================
开启两个Actor之间的通讯聊天......
AActor:在下黄飞鸿,阁下是?
BActor:在下乔峰,想干架还是咋地!
AActor:想扁你,看我迷踪拳!
BActor:小样,还挺迷惑人的吗!吃我降龙十八掌...
AActor:还挺猛,让你尝尝我的佛山无影脚
BActor:我操,这么厉害,溜了...
AActor收到EXIT指令,退出系统!
BActor收到EXIT指令,退出系统!
==================================运行结果==================================
两个Actor的通讯小结和说明
两个Actor通讯机制和Actor自身发消息机制基本一样,只是要注意如下两点:
1. 如果A Actor在需要给B Actor发消息,则需要持有B Actor的ActorRef,可以通过创建时,传入B Actor的
代理对象(ActorRef);
2. 当B Actor在receive方法中接收到消息,需要回复时,可以通过sender() 获取到发送Actor的代理对象。
如何理解Actor的receive方法被调用
1. 每个Actor对应MailBox;
2. MailBox实现了Runnable接口,处于运行的状态;
3. 当有消息到达MailBox,就会去调用Actor的receive方法,将消息推送给receive。
七、Akka网络编程
1、基本介绍
Akka支持面向大并发后端服务程序,网络通信这块是服务端程序重要的一部分。
网络编程有两种:
1. TCP socket编程,是网络编程的主流。之所以叫Tcp socket编程,是因为底层是基于Tcp/ip协议的.
比如: QQ聊天;
2. b/s结构的http编程,我们使用浏览器去访问服务器时,使用的就是http协议,而http底层依旧是用
tcp socket实现的。 比如: 京东商城 【属于 web 开发范畴 】
2、网络编程基础知识
协议(tcp/ip)
TCP/IP(Transmission Control Protocol/Internet Protocol)的简写,中文译名为传输控制协议/因特网互联协议,
又叫网络通讯协议,这个协议是Internet最基本的协议、Internet国际互联网络的基础,简单地说,就是由网络层
的IP协议和传输层的TCP协议组成的。
OSI与Tcp/ip参考模型 (推荐tcp/ip协议3卷)
tracert命令:追踪网络传输通信
ip地址
概述:每个internet上的主机和路由器都有一个ip地址,它包括网络号和主机号,ip地址有ipv4(32位)或者
ipv6(128位). 可以通过ipconfig 来查看
端口(port)-介绍
这里所指的端口不是指物理意义上的端口,而是特指TCP/IP协议中的端口,是逻辑意义上的端口。
如果把IP地址比作一间房子,端口就是出入这间房子的门。真正的房子只有几个门,但是一个IP地址的端口
可以有65535(即:256×256-1)个之多!端口是通过端口号来标记的。(端口号 0:Reserved)
端口(port)-分类
1. 0号是保留端口.
2. 1-1024是固定端口
又叫有名端口,即被某些程序固定使用,一般程序员不使用.
22: SSH远程登录协议 23: telnet使用 21: ftp使用
25: smtp服务使用 80: iis使用 7: echo服务
3. 1025-65535是动态端口
这些端口,程序员可以使用.
端口(port)-使用注意
1. 在计算机(尤其是做服务器)要尽可能的少开端口;
2. 一个端口只能被一个程序监听;
3. 如果使用 netstat –an 可以查看本机有哪些端口在监听;
4. 可以使用 netstat –anb 来查看监听端口的pid,在结合任务管理器关闭不安全的端口。
八、Akka网络编程-客服对话
需求
1. 服务端进行监听(xxxxxx);
2. 客户端可以通过键盘输入,发送咨询问题给客服(服务端);
3. 客服(服务端)回答客户的问题。
程序框架图
程序网络拓扑图
示例代码:
===============================服务端代码===============================
package com.lj.akka.server
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.lj.akka.common.{AppleClientMessage, AppleServerMessage}
import com.typesafe.config.ConfigFactory
import scala.io.StdIn
/**
* @author Administrator
* @create 2020-03-19
*/
class AppleServerActor extends Actor {
override def receive: Receive = {
case "Apple Server Start" => println("Apple Server 已经上线,欢迎咨询 ^_^ ......")
// 如果收到ClientMessage,进行如下匹配模式
case AppleClientMessage(customer_message) => {
customer_message match {
// 设置自动回复消息
case "Apple 11价格?" => sender() ! AppleServerMessage("6000~12000RMB")
case "保修时间?" => sender() ! AppleServerMessage("一年内非人为损坏,换新。")
case "Apple 11有几款颜色?" => sender() ! AppleServerMessage("目前有三种颜色:黑、白、土豪金")
case customer_message: String => {
print("AppleCustomer:" + customer_message + "\nAppleServer:")
// 服务端回复客户端消息
val server_input_mes = StdIn.readLine()
sender() ! AppleServerMessage(server_input_mes)
}
}
}
}
}
object AppleServerActorMain extends App {
val ip_host = "127.0.0.1" // 服务端IP地址
val ip_port = 13666 // 服务端IP端口
/**
* 第一步:创建config对象,指定协议类型,监听IP和端口PORT
* 说明:
* 1. 字符串中的写法是固定的;
* 2. stripMargin:这个方法默认是以“|”分割的,同时还会去空格。
* 3. akka.actor.warn-about-java-serializer-usage=false: 禁止Java序列化警告
*/
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$ip_host
|akka.remote.netty.tcp.port=$ip_port
|akka.actor.warn-about-java-serializer-usage=false
""".stripMargin)
// 第二步:创建ActorSystem
// 名字:AppleServerSystem这个是可以随便起的
private val appleServerSystem = ActorSystem("AppleServerSystem", config)
// 第三步:AppleServerActor的Actor和返回ActorRef
// 名字:AppleServerActorRef这个也是可以随便起的
private val appleServerActorRef: ActorRef = appleServerSystem.actorOf(Props[AppleServerActor], "AppleServerActorRef")
// 第四步:发送消息给appleServerActorRef,启动Server
appleServerActorRef ! "Apple Server Start"
}
===============================服务端代码===============================
===============================客户端代码===============================
package com.lj.akka.customer
import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.lj.akka.common.{AppleClientMessage, AppleServerMessage}
import com.typesafe.config.ConfigFactory
import scala.io.StdIn
/**
* @author Administrator
* @create 2020-03-19
*/
class AppleCustomerActor(serverIPHost: String, serverIPPort: Int) extends Actor {
// 定义一个AppleServerActorRef使用ActorSelection类型,后续可以输入自己需要的配置信息
var appleServerActorRef: ActorSelection = _
// 在Actor中有一个方法“PreStart方法”,它会在Actor运行之前执行
// 在Akka的开发中,通常将初始化的工作,放在PreStart方法中
override def preStart(): Unit = {
// println("执行PreStart方法......") // 查看PreStart方法执行时机
// AppleServerActorRef:这个是服务端的Actor引用名称
appleServerActorRef = context.actorSelection(
s"akka.tcp://AppleServerSystem@${serverIPHost}:${serverIPPort}/user/AppleServerActorRef")
// println("appleServerActorRef:" + appleServerActorRef)
}
override def receive: Receive = {
case "Apple Customer Start" => print("Apple 客户端已经开通,欢迎使用!!!\nAppleCustomer:")
case customer_input_mes: String => {
// 发送消息给Apple Server ActorRef
appleServerActorRef ! AppleClientMessage(customer_input_mes) // 使用AppleClientMessage case class apply 方法
}
// 如果接收到服务器的回复
case AppleServerMessage(server_message) => {
print("AppleServer:" + server_message + "\nAppleCustomer:")
}
}
}
// 程序入口
object AppleCustomerActorMain extends App {
val (customerIPHost, customerIPPort, serverIPHost, serverIPPort) = ("127.0.0.1", 13660, "127.0.0.1", 13666)
/**
* 第一步:创建config对象,指定协议类型,监听IP和端口PORT
*/
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$customerIPHost
|akka.remote.netty.tcp.port=$customerIPPort
|akka.actor.warn-about-java-serializer-usage=false
""".stripMargin)
// 第二步:创建ActorSystem
private val appleCustomerActorSystem = ActorSystem("AppleCustomerActorSystem", config)
// 第三步:创建CustomerActor的实例和引用
private val appleCustomerActorRef: ActorRef = appleCustomerActorSystem
.actorOf(Props(new AppleCustomerActor(serverIPHost, serverIPPort))
, "appleCustomerActorRef")
// 第四步:启动CustomerRef(Actor)
appleCustomerActorRef ! "Apple Customer Start"
// 客户端可以循环发送消息到服务端
while(true) {
val input_mes = StdIn.readLine()
appleCustomerActorRef ! input_mes
}
}
===============================客户端代码===============================
===============================协议端代码===============================
package com.lj.akka.common
/**
* @author Administrator
* @create 2020-03-19
* 说明:使用样例类来构建协议
*/
// 客户端发送消息给服务端协议(序列化对象)
case class AppleClientMessage(customer_message: String)
// 服务端发送消息给客户端协议
case class AppleServerMessage(server_message: String)
===============================协议端代码===============================
示例效果图:
服务端
客户端
对以前的知识回顾,加深基础知识!
学习来自:北京尚硅谷韩顺平老师—尚硅谷大数据技术之Scala
每天进步一点点,也许某一天你也会变得那么渺小!!!