简体   繁体   English

何时使用相同 Akka 演员的更多实例?

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

I've been working with Akka for some time, but now am exploring its actor system in depth.我一直在使用 Akka 一段时间,但现在正在深入探索它的演员系统。 I know there is thread poll executor and fork join executor and afinity executor.我知道有线程轮询执行器和分叉连接执行器和 afinity 执行器。 I know how dispatcher works and all the rest details.我知道调度程序的工作原理以及所有 rest 详细信息。 BTW, this link gives a great explanation顺便说一句,这个链接给出了很好的解释

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

However, when I experimented with a simple call actor and switched execution contexts, I always got roughly the same performance.然而,当我尝试一个简单的调用actor并切换执行上下文时,我总是得到大致相同的性能。 I run 60 requests simultaneously and average execution time is around 800 ms to just return simple string to a caller.我同时运行 60 个请求,平均执行时间约为 800 毫秒,只需将简单的字符串返回给调用者。

I'm running on a MAC which has 8 core (Intel i7 processor).我在具有 8 核(英特尔 i7 处理器)的 MAC 上运行。

So, here are the execution contexts I tried:所以,这里是我尝试的执行上下文:

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"
}

So, questions are:所以,问题是:

  1. Is there any chance to get a better performance in this example?在这个例子中是否有机会获得更好的表现?
  2. What's all about the actor instances?演员实例的全部内容是什么? How that matters, if we know that dispatcher is scheduling thread (using execution context) to execute actor's receive method inside that thread on the next message from actor's mailbox.这有多重要,如果我们知道调度程序正在调度线程(使用执行上下文)以在来自 Actor 邮箱的下一条消息上执行该线程内的 Actor 的接收方法。 Isn't than actor receive method only like a callback?演员接收方法不只是像回调吗? When do number of actors instance get into play?演员实例的数量何时开始发挥作用?
  3. I have some code which is executing Future and if I run that code directly from main file, it executed around 100-150 ms faster than when I put it in actors and execute Future from actor, piping its result to a sender.我有一些正在执行 Future 的代码,如果我直接从主文件运行该代码,它的执行速度比我将其放入 actor 并从 actor 执行 Future 将其结果通过管道传送给发送者时快 100-150 毫秒。 What is making it slower?是什么让它变慢了?

If you have some real world example with this explained, it is more than welcome.如果你有一些现实世界的例子来解释这一点,那就太受欢迎了。 I read some articles, but all in theory.我读了一些文章,但都是理论上的。 If I try something on a simple example, I get some unexpected results, in terms of performance.如果我在一个简单的例子上尝试一些东西,我会在性能方面得到一些意想不到的结果。

Here is a code这是一个代码

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 ! ""
    }
  }
}

Caller:呼叫者:

    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}")
    }

The code is pretty basic, just to test how it behaves.该代码非常基本,只是为了测试它的行为方式。

It's a little unclear to me what you're specifically asking, but I'll take a stab.我有点不清楚你具体要问什么,但我会试一试。

If your dispatcher(s) (and, if what the actor is doing is CPU/memory- vs. IO-bound, actual number of cores available (note that this gets hazier the more virtualization (thank you, oversubscribed host CPU...) and containerization (thank you share- and quota-based cgroup limits) comes into play)) allows m actors to be processing simultaneously and you rarely/never have more than n actors with a message to handle ( m > n ), trying to increase parallelism via dispatcher settings won't gain you anything.如果您的调度程序(并且,如果参与者正在做的是 CPU/内存与 IO 绑定,实际可用内核数(请注意,虚拟化越多,这会变得越模糊(谢谢,超额订阅主机 CPU ... )和容器化(感谢基于共享和配额的 cgroup 限制)发挥作用))允许m个参与者同时处理,并且您很少/永远不会有超过n 个参与者处理消息( m > n ),试图通过调度程序设置增加并行度不会为您带来任何好处。 (Note that in the foregoing, any task scheduled on the dispatcher(s), eg a Future callback, is effectively the same thing as an actor). (请注意,在前面,调度器上调度的任何任务,例如Future回调,实际上与参与者相同)。

n in the previous paragraph is obviously at most the number of actors in the application/dispatcher (depending on what scope we want to look at things: I'll note that every dispatcher over two (one for actors and futures that don't block and one for those that do) is stronger smell (if on Akka 2.5, it's probably a decent idea to adapt some of the 2.6 changes around default dispatcher settings and running things like remoting/cluster in their own dispatcher so they don't get starved out; note also that Alpakka Kafka uses its own dispatcher by default: I wouldn't count those against the two), so in general more actors implies more parallelism implies more core utilization. Actors are comparatively cheap, relative to threads so a profusion of them isn't a huge matter for concern.上一段中的n显然最多是应用程序/调度程序中的参与者数量(取决于我们想要查看的 scope :我会注意到每个调度程序超过两个(一个用于不阻塞的参与者和期货)对于那些这样做的人来说)更强烈的气味(如果在 Akka 2.5 上,围绕默认调度程序设置调整一些 2.6 更改并在他们自己的调度程序中运行诸如远程处理/集群之类的东西可能是一个不错的主意,这样他们就不会挨饿出;另请注意,Alpakka Kafka 默认使用自己的调度程序:我不会将它们与这两者相提并论),因此通常更多的参与者意味着更多的并行性意味着更多的核心利用率。相对于线程而言,参与者相对便宜,因此大量他们不是一个值得关注的大问题。

Singleton actors (whether at node or cluster (or even, in really extreme cases, entity) level) can do a lot to limit overall parallelism and throughput: the one-message-at-a-time restriction can be a very effective throttle (sometimes that's what you want, often it's not). Singleton 参与者(无论是在节点还是集群(甚至在非常极端的情况下,实体)级别)可以做很多事情来限制整体并行度和吞吐量:一次一条消息的限制可能是一个非常有效的限制(有时这就是你想要的,但通常不是)。 So don't be afraid to create short-lived actors that do one high-level thing (they can definitely process more than one message) and then stop (note that many simple cases of this can be done in a slightly more lightweight way via futures).所以不要害怕创建做一件高级事情的短命演员(他们肯定可以处理不止一条消息)然后停止(请注意,许多简单的情况可以通过稍微更轻量级的方式完成期货)。 If they're interacting with some external service, having them be children of a router actor which spawns new children if the existing ones are all busy (etc.) is probably worth doing: this router is a singleton, but as long as it doesn't spend a lot of time processing any message, the chances of it throttling the system are low.如果他们正在与某些外部服务进行交互,让他们成为路由器参与者的孩子,如果现有的孩子都忙(等等),则可能值得这样做:这个路由器是 singleton,但只要它不不要花很多时间处理任何消息,它限制系统的机会很低。 Your RedisService might be a good candidate for this sort of thing.您的RedisService可能是此类事情的理想人选。

Note also that performance and scalability aren't always one and the same and improving one diminishes the other.另请注意,性能和可伸缩性并不总是相同的,提高一个会削弱另一个。 Akka is often somewhat willing to trade performance in the small for reduced degradation in the large. Akka 通常愿意以较小的性能来换取较大的性能下降。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM