簡體   English   中英

何時使用相同 Akka 演員的更多實例?

[英]When to use more instances of same Akka actor?

我一直在使用 Akka 一段時間,但現在正在深入探索它的演員系統。 我知道有線程輪詢執行器和分叉連接執行器和 afinity 執行器。 我知道調度程序的工作原理以及所有 rest 詳細信息。 順便說一句,這個鏈接給出了很好的解釋

https://scalac.io/improving-akka-dispatchers

然而,當我嘗試一個簡單的調用actor並切換執行上下文時,我總是得到大致相同的性能。 我同時運行 60 個請求,平均執行時間約為 800 毫秒,只需將簡單的字符串返回給調用者。

我在具有 8 核(英特爾 i7 處理器)的 MAC 上運行。

所以,這里是我嘗試的執行上下文:

thread-poll {
  type = Dispatcher
  executor = "thread-pool-executor"
  thread-pool-executor {
    fixed-pool-size = 32
  }
  throughput = 10
}

fork-join {
  type = Dispatcher
  executor = "fork-join-executor"
  fork-join-executor {
    parallelism-min = 2
    parallelism-factor = 1
    parallelism-max = 8
  }
  throughput = 100
}

pinned {
  type = Dispatcher
  executor = "affinity-pool-executor"
}

所以,問題是:

  1. 在這個例子中是否有機會獲得更好的表現?
  2. 演員實例的全部內容是什么? 這有多重要,如果我們知道調度程序正在調度線程(使用執行上下文)以在來自 Actor 郵箱的下一條消息上執行該線程內的 Actor 的接收方法。 演員接收方法不只是像回調嗎? 演員實例的數量何時開始發揮作用?
  3. 我有一些正在執行 Future 的代碼,如果我直接從主文件運行該代碼,它的執行速度比我將其放入 actor 並從 actor 執行 Future 將其結果通過管道傳送給發送者時快 100-150 毫秒。 是什么讓它變慢了?

如果你有一些現實世界的例子來解釋這一點,那就太受歡迎了。 我讀了一些文章,但都是理論上的。 如果我在一個簡單的例子上嘗試一些東西,我會在性能方面得到一些意想不到的結果。

這是一個代碼

object RedisService {
  case class Get(key: String)
  case class GetSC(key: String)
}

class RedisService extends Actor {
  private val host = config.getString("redis.host")
  private val port = config.getInt("redis.port")

  var currentConnection = 0

  val redis = Redis()

  implicit val ec = context.system.dispatchers.lookup("redis.dispatchers.fork-join")

  override def receive: Receive = {
    case GetSC(key) => {
      val sen = sender()

      sen ! ""
    }
  }
}

呼叫者:

    val as = ActorSystem("test")
    implicit val ec = as.dispatchers.lookup("redis.dispatchers.fork-join")

    val service = as.actorOf(Props(new RedisService()), "redis_service")

    var sumTime = 0L
    val futures: Seq[Future[Any]] = (0 until 4).flatMap { index =>
      terminalIds.map { terminalId =>
        val future = getRedisSymbolsAsyncSCActor(terminalId)

        val s = System.currentTimeMillis()
        future.onComplete {
          case Success(r) => {
            val duration = System.currentTimeMillis() - s
            logger.info(s"got redis symbols async in ${duration} ms: ${r}")
            sumTime = sumTime + duration
          }
          case Failure(ex) => logger.error(s"Failure on getting Redis symbols: ${ex.getMessage}", ex)
        }

        future
      }
    }

    val f = Future.sequence(futures)


    f.onComplete {
      case Success(r) => logger.info(s"Mean time: ${sumTime / (4 * terminalIds.size)}")
      case Failure(ex) => logger.error(s"error: ${ex.getMessage}")
    }

該代碼非常基本,只是為了測試它的行為方式。

我有點不清楚你具體要問什么,但我會試一試。

如果您的調度程序(並且,如果參與者正在做的是 CPU/內存與 IO 綁定,實際可用內核數(請注意,虛擬化越多,這會變得越模糊(謝謝,超額訂閱主機 CPU ... )和容器化(感謝基於共享和配額的 cgroup 限制)發揮作用))允許m個參與者同時處理,並且您很少/永遠不會有超過n 個參與者處理消息( m > n ),試圖通過調度程序設置增加並行度不會為您帶來任何好處。 (請注意,在前面,調度器上調度的任何任務,例如Future回調,實際上與參與者相同)。

上一段中的n顯然最多是應用程序/調度程序中的參與者數量(取決於我們想要查看的 scope :我會注意到每個調度程序超過兩個(一個用於不阻塞的參與者和期貨)對於那些這樣做的人來說)更強烈的氣味(如果在 Akka 2.5 上,圍繞默認調度程序設置調整一些 2.6 更改並在他們自己的調度程序中運行諸如遠程處理/集群之類的東西可能是一個不錯的主意,這樣他們就不會挨餓出;另請注意,Alpakka Kafka 默認使用自己的調度程序:我不會將它們與這兩者相提並論),因此通常更多的參與者意味着更多的並行性意味着更多的核心利用率。相對於線程而言,參與者相對便宜,因此大量他們不是一個值得關注的大問題。

Singleton 參與者(無論是在節點還是集群(甚至在非常極端的情況下,實體)級別)可以做很多事情來限制整體並行度和吞吐量:一次一條消息的限制可能是一個非常有效的限制(有時這就是你想要的,但通常不是)。 所以不要害怕創建做一件高級事情的短命演員(他們肯定可以處理不止一條消息)然后停止(請注意,許多簡單的情況可以通過稍微更輕量級的方式完成期貨)。 如果他們正在與某些外部服務進行交互,讓他們成為路由器參與者的孩子,如果現有的孩子都忙(等等),則可能值得這樣做:這個路由器是 singleton,但只要它不不要花很多時間處理任何消息,它限制系統的機會很低。 您的RedisService可能是此類事情的理想人選。

另請注意,性能和可伸縮性並不總是相同的,提高一個會削弱另一個。 Akka 通常願意以較小的性能來換取較大的性能下降。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM