Akka是受Erlang启发的平台(框架?),它承诺可轻松开发可伸缩,多线程和安全的应用程序。 虽然在大多数流行语言中,并发是基于多个线程之间共享的内存,并由各种同步方法来保护,但是Akka提供了基于参与者的并发模型。 Actor是一个轻量级的对象,您可以通过向其发送消息来与之交互。 每个参与者一次最多只能处理一条消息,并且显然可以将消息发送给其他参与者。 在一个Java虚拟机中,可以同时存在数百万个参与者,从而建立一个层次化的父级( 主管 )–子级结构,其中父级监视子级的行为。 如果这还不够,我们可以轻松地在集群中的多个节点之间划分参与者,而无需修改任何代码。 每个参与者可以具有内部状态(字段/变量集),但是通信只能通过消息传递进行,而不能通过共享数据结构(计数器,队列)进行。
上面这些功能的组合带来了更安全,更稳定和可扩展的代码-代价是并发编程模型发生了根本的范式转换。 如此众多的流行语和承诺,让我们继续举例。 这不会成为“ Hello,world”的例子,但我们将尝试构建一个小而完整的解决方案。 在接下来的几篇文章中,我们将实现与random.org API的集成。 该Web服务使我们能够基于大气噪声(无论意味着什么)来获取真正的随机数(与伪随机生成器相对)。 API并没有那么复杂,请访问以下网站并刷新几次:
https://www.random.org/integers/?num=20&min=1000&max=10000&col=1&base=10&format=plain
那么困难在哪里? 阅读自动客户指南,我们了解到:
- 客户端应用程序最多应从一个线程调用上面的URL –禁止使用多个HTTP连接同时获取随机数。
- 我们应该分批加载随机数,而不是每次请求都一一加载。 上面的请求在一个呼叫中使用num = 20个数字。
- 警告我们延迟,甚至在一分钟后响应仍可能到达
- 客户端应定期检查随机数配额(每天最多免费提供给定数量的随机位)
所有这些要求使得与random.org
集成random.org
不平凡。 在本系列中,我才刚刚开始,我们将逐步改进我们的应用程序,逐步学习Akka的新功能。 一旦了解了平台的基本概念,我们很快就会意识到,陡峭的学习曲线是有回报的。 所以,让我们编码!
今天,我们将尝试处理前两个要求,即在任何给定时间点不超过一个连接,并分批加载数量。 对于这一步,我们实际上并不需要Akka,简单的同步和一个缓冲区就足够了:
val buffer = new Queue[Int]
def nextRandom(): Int = {
this.synchronized {
if(buffer.isEmpty) {
buffer ++= fetchRandomNumbers(50)
}
buffer.dequeue()
}
}
def fetchRandomNumbers(count: Int) = {
val url = new URL("https://www.random.org/integers/?num=" + count + "&min=0&max=65535&col=1&base=10&format=plain&rnd=new")
val connection = url.openConnection()
val stream = Source.fromInputStream(connection.getInputStream)
val randomNumbers = stream.getLines().map(_.toInt).toList
stream.close()
randomNumbers
}
此代码有效,并且等效synchronized keyword in Java
的synchronized keyword in Java
。 nextRandom()
工作方式应该很明显:如果缓冲区为空,则用从服务器获取的50个随机数填充它。 最后,从缓冲区中获取并返回第一个值。 此代码有很多缺点,首先是从synchronized
块开始。 每次通话的同步费用都很高,这似乎是过大的选择。 而且我们还没有进入集群,在该集群中,每个集群都必须维护一个活动连接,而不仅仅是使用一个JVM!
我们将从执行一个演员开始。 Actor基本上是一个扩展Actor
特性并实现receive
方法的类。 此方法负责接收和处理一条消息。 让我们重申一下我们已经说过的话:每个参与者每次最多只能处理一条消息,因此永远不会并发调用receive方法。 如果参与者已经在处理某些消息,则其余消息将保留在每个参与者专用的队列中。 由于有了这个严格的规则,我们可以避免在actor内部进行任何同步,而这始终是线程安全的。
case object RandomRequest
class RandomOrgBuffer extends Actor {
val buffer = new Queue[Int]
def receive = {
case RandomRequest =>
if(buffer.isEmpty) {
buffer ++= fetchRandomNumbers(50)
}
println(buffer.dequeue())
}
}
fetchRandomNumbers()
方法保持不变。 由于actor一次只能处理一条消息,因此可以免费获得对random.org
的单线程访问。 说到消息,在这种情况下, RandomRequest
是我们的消息–空对象不传递任何信息,但其类型除外。 在Akka中,消息几乎总是使用case类或其他不可变类型实现的。 因此,如果我们想支持获取任意数量的随机数,则必须将其包括在消息中:
case class RandomRequest(howMany: Int)
class RandomOrgBuffer extends Actor with ActorLogging {
val buffer = new Queue[Int]
def receive = {
case RandomRequest(howMany) =>
if(buffer.isEmpty) {
buffer ++= fetchRandomNumbers(50)
}
for(_ <- 1 to (howMany min 50)) {
println(buffer.dequeue())
}
}
现在,我们应该尝试向新演员发送一些信息。 显然,我们不能只调用传递消息作为参数的receive
方法。 首先,我们必须启动Akka平台,并要求演员参考。 以后,此参考首先用于使用有点违反直觉的方式发送消息! 方法,可以追溯到Erlang的日子:
object Bootstrap extends App {
val system = ActorSystem("RandomOrgSystem")
val randomOrgBuffer = system.actorOf(Props[RandomOrgBuffer], "buffer")
randomOrgBuffer ! RandomRequest(10) //sending a message
system.shutdown()
}
运行该程序后,我们应该在控制台上看到10个随机数。 对这个简单的应用程序进行一些实验(完整的源代码可以在GitHub上找到 , request-response
标签 )。 特别要注意的是,发送消息是非阻塞的,并且消息本身是在不同的线程中处理的(与JMS类似)。 尝试发送其他类型的消息并修复receive
方法,以便它可以处理多种类型的消息。
到目前为止,我们的应用程序不是很有用。 我们想以某种方式访问我们的随机数,而不是将它们(异步!)打印到标准输出中。 您可能会猜到,由于只能通过异步消息传递来建立与参与者的通信(参与者无法“返回”结果,也不能将其放置在任何全局共享内存中)。 因此,演员将通过直接发送给我们(发给发送者)的回复消息将结果发送回去。 但这将是下一篇文章的一部分。
这是我最初发表在scala.net.pl上的文章“ Poznajemy Akka:pierwszy komunikat ”的翻译 。
参考: 您的第一条信息–在Java和社区博客上从我们的JCG合作伙伴 Tomasz Nurkiewicz 发现Akka 。
翻译自: https://www.javacodegeeks.com/2012/10/your-first-message-discovering-akka.html