Scala中的Akka和第三方Java库的最佳实践

时间:2014-08-02 11:35:44

标签: java scala asynchronous concurrency akka

我需要在我的Scala / Akka代码中使用memcached Java API。此API为您提供同步和异步方法。异步的返回java.util.concurrent.Future。这里有一个关于在Scala中处理Java Futures的问题How do I wrap a java.util.concurrent.Future in an Akka Future?。但在我的情况下,我有两个选择:

  1. 将来使用同步API并包装阻止代码并标记阻止:

    Future {
      blocking {
        cache.get(key) //synchronous blocking call
      } 
    }
    
  2. 使用异步Java API并在Java Future上每隔n ms轮询一次以检查未来是否已完成(如上面链接问题中上述答案之一所述)。

  3. 哪一个更好?我倾向于第一种选择,因为轮询可以极大地影响响应时间。不应该blocking { }阻止阻止整个池吗?

2 个答案:

答案 0 :(得分:10)

我总是选择第一个选项。但我这样做的方式略有不同。我不使用blocking功能。 (实际上我还没有考虑过它。)相反,我正在为Future提供一个包装同步阻塞调用的自定义执行上下文。所以看起来基本上是这样的:

val ecForBlockingMemcachedStuff = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(100)) // whatever number you think is appropriate
// i create a separate ec for each blocking client/resource/api i use

Future {
    cache.get(key) //synchronous blocking call
}(ecForBlockingMemcachedStuff) // or mark the execution context implicit. I like to mention it explicitly.

因此所有阻塞调用都将使用专用的执行上下文(= Threadpool)。因此它与主要执行上下文分开,负责非阻塞内容。

这种方法也在Typesafe提供的online training video for Play/Akka中进行了解释。第4课中有一个关于如何处理阻塞呼叫的视频。它由 Nilanjan Raychaudhuri (希望我拼写正确)解释,他是Scala书籍的着名作者。

更新:我有一个discussion with Nilanjan on twitter。他解释了使用blocking和自定义ExecutionContext的方法之间的区别。 blocking功能只会创建一个特殊的ExecutionContext。它提供了一个简单的方法来解决您需要多少线程的问题。每当池中所有其他现有线程都忙时,它就会生成一个新线程。所以它实际上是一个不受控制的 ExecutionContext。它可能会创建大量线程并导致问题,例如内存不足错误。因此,具有自定义执行上下文的解决方案实际上更好,因为它使这个问题变得明显。 Nilanjan还补充说,在这个池被请求超载的情况下,你需要考虑断路。

TLDR:是的,阻止来电很糟糕。 使用自定义/专用ExecutionContext来阻止呼叫。还要考虑断路。

答案 1 :(得分:0)

Akka documentation提供了一些有关如何处理阻止呼叫的建议:

  

在某些情况下,进行阻止操作是不可避免的   一个线程在不确定的时间内睡觉,等待外部   事件发生。示例是旧版RDBMS驱动程序或消息传递API,   而根本原因通常是(网络)I / O发生在   封面。面对这个时,你可能会想要包裹它   在Future中阻止调用并使用它,但是这样   策略太简单了:你很可能会发现瓶颈或者   当应用程序运行增加时,内存或线程耗尽   负荷。

     

“封锁”的适当解决方案的非详尽清单   问题“包括以下建议:

     
      
  • 在actor(或由路由器管理的一组actor)中执行阻塞调用,确保配置一个线程池,该线程池要么是   专门用于此目的或足够大小。

  •   
  • 在Future中执行阻塞调用,确保在任何时间点对此类调用的数量设置上限(提交无限制)   这种性质的任务将耗尽你的记忆或线程   限值)。

  •   
  • 在Future中执行阻塞调用,提供一个线程池,其线程数上限适合于   应用程序运行的硬件。

  •   
  • 专用一个线程来管理一组阻塞资源(例如驱动多个渠道的NIO选择器)并调度事件   作为演员消息发生。

  •   
     

第一种可能性特别适用于资源   本质上是单线程的,就像数据库句柄一样   传统上一次只能执行一个未完成的查询并使用   内部同步以确保这一点。一种常见的模式是创造   一个N个actor的路由器,每个都包含一个DB连接   处理发送到路由器的查询。然后必须调整数字N.   最大吞吐量,具体取决于DBMS   部署在什么硬件上。