无响应的actor系统:ThreadPoolExecutor调度程序只创建核心线程池,显然忽略了最大线程池大小

时间:2015-07-08 22:00:02

标签: java multithreading scala akka blocking

更新:如果我将ThreadPoolExecutor's核心池大小设置为与最大池大小(29个线程)相同,我发现我的程序仍然保持响应。但是,如果我将核心池大小设置为11并将最大池大小设置为29,那么actor系统只会创建11个线程。如何配置ActorSystem / ThreadPoolExecutor以继续创建线程以超过核心线程数并保持在最大线程数内?我宁愿不将核心线程数设置为最大线程数,因为我只需要额外的线程来取消作业(这应该是一个罕见的事件)。

我有一个针对Oracle数据库运行的批处理程序,使用Java / Akka类型的actor与以下actor一起实现:

  1. BatchManager负责与REST控制器通信。它管理Queue未初始化的批处理作业;当从队列中轮询未初始化的批处理作业时,它将变为JobManager actor并执行。
  2. JobManager维护一个存储过程队列和一个Workers池;它使用存储过程初始化每个Worker,当Worker完成时,它将过程的结果发送到JobManagerJobManager发送另一个存储过程到Worker。当作业队列为空且所有Workers都空闲时,批处理终止,此时JobManager将其结果报告给BatchManager,关闭其工作人员(通过TypedActor.context().stop() ),然后关闭自己。 JobManagerPromise<Status> completion在作业成功完成时完成,或者当作业因取消或致命异常而终止时完成。
  3. Worker执行存储过程。它创建用于执行存储过程的OracleConnectionCallableStatement,并向onFailure注册JobManager.completion回调到abort连接和cancel } 该声明。此回调不使用actor系统的执行上下文,而是使用从BatchManager中创建的高速缓存执行程序服务创建的执行上下文。
  4. 我的配置是

    {"akka" : { "actor" : { "default-dispatcher" : {
        "type" : "Dispatcher",
        "executor" : "default-executor",
        "throughput" : "1",
        "default-executor" : { "fallback" : "thread-pool-executor" }
        "thread-pool-executor" : {
            "keep-alive-time" : "60s",
            "core-pool-size-min" : coreActorCount,
            "core-pool-size-max" : coreActorCount,
            "max-pool-size-min" : maxActorCount,
            "max-pool-size-max" : maxActorCount,
            "task-queue-size" : "-1",
            "task-queue-type" : "linked",
            "allow-core-timeout" : "on"
    }}}}}
    

    工作人员的数量在其他地方配置,目前为workerCount = 8; coreActorCountworkerCount + 3,而maxActorCountworkerCount * 3 + 5。我在具有两个内核和8GB内存的Macbook Pro 10上进行测试;生产服务器要大得多。我正在谈论的数据库背后是一个非常慢的VPN。我使用Oracle的JavaSE 1.8 JVM运行所有这些。本地服务器是Tomcat 7. Oracle JDBC驱动程序是10.2版(我可能会说服使用更新版本的权力)。所有方法都返回voidFuture<>,并且应该是非阻塞的。

    当一个批次成功终止时,则没有问题 - 下一个批次立即启动,并提供完整的工作人员。但是,如果我通过JobManager#completion.tryFailure(new CancellationException("Batch cancelled"))终止当前批次,则onFailure注册的Workers回调会关闭,然后系统就会无法响应。调试printlns表明新批处理从八个正常运行的工作程序中的三个开始,BatchManager变得完全没有响应(我添加了Future<String> ping命令只返回Futures.successful("ping"),这也超时了)。 onFailure回调正在一个单独的线程池上执行,即使它们在actor系统的线程池中,我应该有足够高的max-pool-size来容纳原始JobManagerWorkersonFailure回调,第二个JobManagerWorkers。相反,我似乎正在容纳原始的JobManager及其Workers,新的JobManager以及不到Workers的一半,并且BatchManager.没有遗留任何内容。我运行它的计算机缺乏资源,但它似乎应该可以运行十几个线程。

    这是配置问题吗?这是由于JVM强加的限制和/或Tomcat强加的限制吗?这是由于我如何处理阻止IO的问题?可能还有其他一些我可能做错的事情,这些只是我想到的。

    Gist of CancellableStatement CallableStatementOracleConnection被取消

    Gist of Immutable其中CancellableStatements已创建

    Gist of JobManager's cleanup code

    Config dump通过System.out.println(mergedConfig.toString());

    获得

    编辑:我相信我已经将问题缩小到了演员系统(无论是配置还是与阻止数据库调用的交互)。我删除了Worker个参与者,并将他们的工作量移到Runnables,该ThreadPoolExecutor执行固定大小JobManager,其中每个ThreadPoolExecutor创建自己的shutDown并关闭它批处理完成时关闭(正常终止时shutDownNow,特殊终止时BatchManager。取消在ThreadPoolExecutor中实例化的缓存线程池上运行。演员系统的调度程序仍然是JobManager,但只分配了六个线程。使用此备用设置,取消按预期执行 - 工作程序在数据库连接中止时终止,新CallableStatement.close()立即执行完整的工作线程。这向我表明这不是硬件/ JVM / Tomcat问题。

    更新:我使用Eclipse's Memory Analyzer进行了线程转储。我发现取消线程挂在OracleConnection.abort()上,因此我重新排序取消,以便CallableStatement.cancel()Worker之前,这解决了问题 - 取消所有(显然)正确执行。尽管如此,PerformanceAsync-akka.actor.default-dispatcher-19 at java.net.SocketInputStream.socketRead0(Ljava/io/FileDescriptor;[BIII)I (Native Method) at java.net.SocketInputStream.read([BIII)I (SocketInputStream.java:150) at java.net.SocketInputStream.read([BII)I (SocketInputStream.java:121) at oracle.net.ns.Packet.receive()V (Unknown Source) at oracle.net.ns.DataPacket.receive()V (Unknown Source) at oracle.net.ns.NetInputStream.getNextPacket()V (Unknown Source) at oracle.net.ns.NetInputStream.read([BII)I (Unknown Source) at oracle.net.ns.NetInputStream.read([B)I (Unknown Source) at oracle.net.ns.NetInputStream.read()I (Unknown Source) at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1()S (T4CMAREngine.java:1109) at oracle.jdbc.driver.T4CMAREngine.unmarshalSB1()B (T4CMAREngine.java:1080) at oracle.jdbc.driver.T4C8Oall.receive()V (T4C8Oall.java:485) at oracle.jdbc.driver.T4CCallableStatement.doOall8(ZZZZ)V (T4CCallableStatement.java:218) at oracle.jdbc.driver.T4CCallableStatement.executeForRows(Z)V (T4CCallableStatement.java:971) at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout()V (OracleStatement.java:1192) at oracle.jdbc.driver.OraclePreparedStatement.executeInternal()I (OraclePreparedStatement.java:3415) at oracle.jdbc.driver.OraclePreparedStatement.execute()Z (OraclePreparedStatement.java:3521) at oracle.jdbc.driver.OracleCallableStatement.execute()Z (OracleCallableStatement.java:4612) at com.util.CPProcExecutor.execute(Loracle/jdbc/OracleConnection;Ljava/sql/CallableStatement;Lcom/controller/BaseJobRequest;)V (CPProcExecutor.java:57) 线程继续依赖于他们的陈述 - 我怀疑我的VPN可能部分或完全归咎于此。

    {{1}}

    然而,即使在确定取消订单后,我仍然遇到演员系统没有创建足够线程的问题:我仍然只有新工人中的八个工人中的三个,新工人被取消的工作人员的网络连接超时。总共我有11个线程 - 我的核心池大小,29个线程 - 我的最大池大小。显然,actor系统忽略了我的max pool size参数,或者我没有正确配置max pool size。

2 个答案:

答案 0 :(得分:5)

(免责声明:我不认识Akka)

通过你的queue-size = -1的下面配置,我猜,任务队列是无界限的。

  "task-queue-size": "-1",
  "task-queue-type": "linked"
除非工作队列已满并且无法排队,否则

ThreadPoolExecutor不会产生核心池大小。只有当任务队列已满时,它才会开始产生最多的线程。

  

如果正在运行少于corePoolSize的线程,则始终执行Executor   喜欢添加新线程而不是排队。如果是corePoolSize或   正在运行更多线程,Executor总是更喜欢排队   请求而不是添加新线程。如果请求不能   排队,创建一个新线程,除非这会超过   maximumPoolSize,在这种情况下,任务将被拒绝。

请检查您是否可以修复有限的队列大小,并查看线程是否增加到最大线程数。感谢。

答案 1 :(得分:0)

没有足够的代码来提供解决方案,但是当系统无响应时,您可以检查系统资源利用率(cpu,ram)是否未更改,请检查Oracle数据库。

如果你杀死一组连接,那么另一个工作立即启动:我想在oracle级别有一些阻塞会话(一个未提交的写入事务阻止了对同一资源的另一个写入事务)。

当处于无响应状态时,请检查阻止会话:

SELECT s1.username || '@' || s1.machine
    || ' ( SID=' || s1.sid || ' )  is blocking '
    || s2.username || '@' || s2.machine || ' ( SID=' || s2.sid || ' ) ' AS blocking_status
    FROM v$lock l1, v$session s1, v$lock l2, v$session s2
    WHERE s1.sid=l1.sid AND s2.sid=l2.sid
    AND l1.BLOCK=1 AND l2.request > 0
    AND l1.id1 = l2.id1
    AND l1.id2 = l2.id2