akka custom fork-join-executor dispatcher在OSX和RHEL上的行为有所不同

时间:2015-01-02 20:09:29

标签: scala playframework akka

当我部署Play框架应用程序时,使用Akka框架到生产机器,它的行为与我的开发工作站不同。

这是一个接收一批设备IP地址的系统,它在每个设备上执行一些处理,并在处理完批次中的所有设备后聚合结果。这种处理不是CPU密集型的。

我基本上有两种类型的actor,一个BatchActor和一个DeviceActor。对于设备,我创建了一个由RoundRobinPool路由器和自定义调度程序支持的创建的actor。我试图一次处理~500个设备(并行)。

这个问题是,当我在我的OSX机器上运行此代码时,它会像我一样运行。

例如,如果我提交了一批200个设备的IP地址,则应用程序在我的工作站上并行运行所有设备。

但是,当我将此应用程序复制到生产计算机Red Hat Enterprise Linux(RHEL)并运行它提交相同的设备列表时,它一次只处理1到2个设备

我需要做些什么来解决这个问题?

相关代码如下:

object Application extends Controller {
    ...

    val numberOfWorkers = 500
    val workers = Akka.system.actorOf(Props[DeviceActor]
          .withRouter(RoundRobinPool(nrOfInstances = numberOfWorkers))
          .withDispatcher("my-dispatcher")
    )

    def batchActor(config:BatchConfig) 
        = Akka.system.actorOf(BatchActor.props(workers, config), s"batch-${config.batchId}")

    ...

    def batch = Action(parse.json) { request =>
        request.body.validate[BatchConfig] match {

            case config:BatchConfig => {
                ...

                val batch = batchActor(config)
                batch ! BatchActorProtocol.Start

                Ok(Json.toJson(status))
            }

            ...

        }
    }

application.conf配置部分如下所示:

my-dispatcher {
  # Dispatcher is the name of the event-based dispatcher
  type = Dispatcher
  # What kind of ExecutionService to use
  executor = "fork-join-executor"
  # Configuration for the fork join pool
  fork-join-executor {
    # Min number of threads to cap factor-based parallelism number to
    parallelism-min = 1000
    # Parallelism (threads) ... ceil(available processors * factor)
    parallelism-factor = 100.0
    # Max number of threads to cap factor-based parallelism number to
    parallelism-max = 5000
  }
  # Throughput defines the maximum number of messages to be
  # processed per actor before the thread jumps to the next actor.
  # Set to 1 for as fair as possible.
  throughput = 500
}

在BatchActor中,我只是解析设备列表并将其提供给

class BatchActor(val workers:ActorRef, val config:BatchConfig) extends Actor
  ...

  def receive = {
      case Start => start
      ...
  }

  private def start = {
      ...

      devices.map { devices =>
        results(devices.host) = None
        workers ! DeviceWork(self, config, devices, steps) 
      }

      ...
  }

之后,WorkerActor将结果对象提交回BatchActer。

我的工作站:OS X - v10.9.3

java -version
java version "1.7.0_67"
Java(TM) SE Runtime Environment (build 1.7.0_67-b01)
Java HotSpot(TM) 64-Bit Server VM (build 24.65-b04, mixed mode) 

生产机器:Red Hat Enterprise Linux Server 6.5版(圣地亚哥)

java -version
java version "1.7.0_65"
OpenJDK Runtime Environment (rhel-2.5.1.2.el6_5-x86_64 u65-b17)
OpenJDK 64-Bit Server VM (build 24.65-b04, mixed mode)

软件:

Scala: v2.11.2
SBT: v0.13.6
Play: v2.3.5
Akka: v2.3.4

我使用typesafe activator / sbt来启动应用程序。命令如下:

cd <project dir>
./activator run -Dhttp.port=6600

任何帮助表示赞赏。我已经在这个问题上坚持了几天。

2 个答案:

答案 0 :(得分:0)

我相信您的代码中有太多 parallelism ,即您在调度程序中创建了太多线程。您的Redhat框上有多少个核心?我从来没有见过这么高的价值。 FJ池中的许多线程可能导致大量上下文切换。尝试使用默认调度程序,看看是否能解决您的问题。您还可以将最小和最大并行度的值更改为您拥有的核心数的2或3倍。

fork-join-executor {
    # Min number of threads to cap factor-based parallelism number to
    parallelism-min = 1000
    # Parallelism (threads) ... ceil(available processors * factor)
    parallelism-factor = 100.0
    # Max number of threads to cap factor-based parallelism number to
    parallelism-max = 5000
  }

要尝试的另一件事是使用(sbt-assembly)创建一个超级jar,然后部署它而不是使用激活器来部署它。

最后,您可以使用VisualJVM或Yourkit等内容查看JVM。

答案 1 :(得分:0)

花了几个小时尝试不同的事情,包括:

  • 研究linux上的不同线程实现 - pthreads vs NPTL
  • 阅读有关线程的所有VM文档
  • ulimits
  • 尝试Play和Akka框架配置中的各种更改
  • 最后使用scala期货等完全重写了线程管理。

似乎没什么用。然后我进行了详细的比较,唯一不同的是我在笔记本电脑上使用了Oracle Hotspot实现,在生产机器上使用了OpenJDK实现。

所以我在生产机器上安装了Oracle VM,这似乎解决了这个问题。虽然我无法确定最终解决方案是什么,但似乎RHEL上的OpenJDK默认安装的编译或配置不同,不允许一次产生~500个线程。

我确定我错过了一些东西,但经过约3天的搜索后我找不到它。