点击次数超过总请求数

Hits more than total requests

所以我开始学习Scala和Akka actor,Akka-Http。我尝试使用 Akka Http 实现一个简单的点击计数器,它计算本地主机页面上的每次点击。我使用 wrk 工具 运行 10 个线程和 100 个连接,之后计数和请求总数不匹配(在 wrk 上看到)。

这是我的代码:


object WebServer3 {

  var number: Int = 0


  final case class Inc()
  class ActorClass extends Actor with ActorLogging {

    def receive = {
      case Inc => number = number + 1
    }
  }


  def main(args: Array[String]) {
    implicit val system = ActorSystem("my-system")
    implicit val materializer = ActorMaterializer()
    implicit val executionContext = system.dispatcher

    val actor1 = system.actorOf(Props[ActorClass], "SimpleActor")
    val route =
      path("Counter") {

        get {
          actor1 ! Inc
         complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, s"<h1>You visited $number times</h1>"))
        }
      }

    val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
    println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
    StdIn.readLine() // let it run until user presses return
    bindingFuture
      .flatMap(_.unbind()) // trigger unbinding from the port
      .onComplete(_ => system.terminate()) // and shutdown when done
  }
}

请原谅我的 immature/amateurish 编码技巧。我仍在学习,我知道这与并发性有关。但我还找不到解决办法。请帮忙。

编辑#1:我也试过 AtomicInteger。那没有帮助。 编辑#2:我也尝试了使用 ask 和 await 的完整 akka-http 方式。这也没有帮助。

您的代码几乎没有问题。

您正在定义一个案例 class final case class Inc() 但您正在发送一个伴随对象 actor1 ! Inc。但是,您仍然匹配伴随对象 case Inc => 并且您的代码有效。但不应该这样做。

其他问题,我们正在 actor 边界之外存储、修改和检索 var number: Int = 0。我认为这就是你计算错误的原因。 Actor 必须只改变内部状态。

我通过引入 ask pattern 修改了您的代码,因此可以从 actor 中检索值。

import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.pattern.ask
import akka.stream.ActorMaterializer
import akka.util.Timeout

import scala.concurrent.duration._
import scala.io.StdIn

object WebServer3 {


  final case object IncAndGet //not a case class anymore

  class ActorClass extends Actor with ActorLogging {
    private var number: Int = 0 //inner state must be private and not accessible from outside of an actor

    def receive = {
      case IncAndGet =>
        number += 1
        context.sender() ! number // send current value back to sender
    }
  }

  def main(args: Array[String]) {
    implicit val system = ActorSystem("my-system")
    implicit val materializer = ActorMaterializer()
    implicit val executionContext = system.dispatcher
    implicit val timeout: Timeout = 2.seconds

    val actor1 = system.actorOf(Props[ActorClass], "SimpleActor")
    val route =
      path("counter") {
        get {
          onComplete((actor1 ? IncAndGet).mapTo[Int]) { number =>
            complete(
              HttpEntity(ContentTypes.`text/html(UTF-8)`,
                         s"<h1>You visited $number times</h1>"))
          }
        }
      }

    val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
    println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
    StdIn.readLine() // let it run until user presses return
    val _ = bindingFuture
      .flatMap(_.unbind()) // trigger unbinding from the port
  }
}