如果我们正在考虑生产级 REST API,我们是否应该尽可能使用非阻塞,例如
def insertDbAsync(rows: RowList): Future[Unit] = ...
...
val route =
path("database" / "insertRowList") {
post {
entity(as[RowList]) { rows =>
log.info(s"${rows.length} rows received")
val async = insertDbAsync(rows)
onComplete(async) {
case Success(response) =>
complete("success")
case Failure(t) =>
complete("error")
}
}
}
}
我认为答案很可能是“是”,但是在决定什么应该和不应该是阻止代码时有哪些指导原则,为什么?
Spray 使用 Akka 作为底层平台,因此建议与 actor 相同(阻塞需要仔细管理 http://doc.akka.io/docs/akka/snapshot/general/actor-systems.html)。阻塞代码可能需要太多线程,这可能:
杀死actor的轻量性:默认情况下,数百万个actor可能在一个线程上运行。例如,假设一个非阻塞参与者需要 0.001 个线程。一个被阻塞的 Actor(阻塞时间比平时多 100 倍)将平均占用 1 个线程(并不总是相同的线程)。首先,拥有的线程越多,释放的内存就越多,每个被阻塞的线程都保存在阻塞之前分配的完整调用堆栈,包括堆栈中的引用(因此 GC 无法删除它们)。其次,如果你有超过number_of_processors
线程 - 你会失去性能。第三,如果您使用一些动态池 - 添加新线程可能会花费大量时间。
导致线程饥饿 - 您可能有一个充满线程的池,这些线程什么也不做 - 因此在阻塞操作完成之前无法处理新任务(0%CPU负载,但有100500条消息等待处理)。甚至可能会导致死锁。然而,Akka 默认使用 Fork-Join-Pool,所以如果你的阻塞代码被管理(用scala.concurrent.blocking
- Await.result
内部有这样的环境) - 它将通过创建新线程而不是阻塞线程的成本来防止饥饿,但它不会弥补其他问题。
传统上会导致死锁,因此不利于设计
如果代码被外部阻塞,你可以用 future 包围它:
import scala.concurrent._
val f = Future {
someNonBlockingCode()
blocking { //mark this thread as "blocked" so fork-join-pool may create another one to compensate
someBlocking()
}
}
里面单独的演员:
f pipeTo sender //will send the result to `sender` actor
内部喷涂路线:
onComplete(f) { .. }
最好在单独的池/调度程序(基于 fork-join-pool)内执行此类 future。
附:作为 futures 的替代方案(从设计角度来看它们可能不太方便),您可以考虑Akka I/O http://doc.akka.io/docs/akka/2.0/scala/io.html, 继续/协程 http://jim-mcbeath.blogspot.com/2010/09/scala-coroutines.html, 演员的pools http://doc.akka.io/docs/akka/snapshot/scala/routing.html(也在单独的调度程序内)、Disruptor 等。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)