Document handling blocking ops in ScalaDSL Akka HTTP (#20912)
* +doc Added doc handling blocking operations #20066 (by @ktoso) * Move and rewrite handling blocking ops somewhat
This commit is contained in:
parent
6d2d4d5d25
commit
77513c41d4
7 changed files with 175 additions and 1 deletions
|
|
@ -1,7 +1,7 @@
|
|||
.. _-parameters-java-:
|
||||
|
||||
parameters
|
||||
=========
|
||||
==========
|
||||
Extracts multiple *query* parameter values from the request.
|
||||
|
||||
Description
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.scaladsl.server
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.http.scaladsl.server.{ Directives, Route }
|
||||
import docs.CompileOnlySpec
|
||||
import org.scalatest.WordSpec
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
class BlockingInHttpExamplesSpec extends WordSpec with CompileOnlySpec
|
||||
with Directives {
|
||||
|
||||
compileOnlySpec {
|
||||
val system: ActorSystem = ???
|
||||
|
||||
//#blocking-example-in-default-dispatcher
|
||||
// BAD (due to blocking in Future, on default dispatcher)
|
||||
implicit val defaultDispatcher = system.dispatcher
|
||||
|
||||
val routes: Route = post {
|
||||
complete {
|
||||
Future { // uses defaultDispatcher
|
||||
Thread.sleep(5000) // will block on default dispatcher,
|
||||
System.currentTimeMillis().toString // Starving the routing infrastructure
|
||||
}
|
||||
}
|
||||
}
|
||||
//#
|
||||
}
|
||||
|
||||
compileOnlySpec {
|
||||
val system: ActorSystem = ???
|
||||
|
||||
//#blocking-example-in-dedicated-dispatcher
|
||||
// GOOD (the blocking is now isolated onto a dedicated dispatcher):
|
||||
implicit val blockingDispatcher = system.dispatchers.lookup("my-blocking-dispatcher")
|
||||
|
||||
val routes: Route = post {
|
||||
complete {
|
||||
Future { // uses the good "blocking dispatcher" that we configured,
|
||||
// instead of the default dispatcher- the blocking is isolated.
|
||||
Thread.sleep(5000)
|
||||
System.currentTimeMillis().toString
|
||||
}
|
||||
}
|
||||
}
|
||||
//#
|
||||
}
|
||||
|
||||
}
|
||||
BIN
akka-docs/rst/scala/http/DispatcherBehaviourOnBadCode.png
Normal file
BIN
akka-docs/rst/scala/http/DispatcherBehaviourOnBadCode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 202 KiB |
BIN
akka-docs/rst/scala/http/DispatcherBehaviourOnGoodCode.png
Normal file
BIN
akka-docs/rst/scala/http/DispatcherBehaviourOnGoodCode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 218 KiB |
BIN
akka-docs/rst/scala/http/DispatcherBehaviourProperBlocking.png
Normal file
BIN
akka-docs/rst/scala/http/DispatcherBehaviourProperBlocking.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 258 KiB |
|
|
@ -0,0 +1,120 @@
|
|||
.. _handling-blocking-in-http-routes-scala:
|
||||
|
||||
Handling blocking operations in Akka HTTP
|
||||
=========================================
|
||||
Sometimes it is difficult to avoid performing the blocking operations and there
|
||||
are good chances that the blocking is done inside a Future execute, which may
|
||||
lead to problems. It is important to handle the blocking operations correctly.
|
||||
|
||||
Problem
|
||||
-------
|
||||
Using ``context.dispatcher`` as the dispatcher on which the blocking Future
|
||||
executes, can be a problem. The same dispatcher is used by the routing
|
||||
infrastructure to actually handle the incoming requests.
|
||||
|
||||
If all of the available threads are blocked, the routing infrastructure will end up *starving*.
|
||||
Therefore, routing infrastructure should not be blocked. Instead, a dedicated dispatcher
|
||||
for blocking operations should be used.
|
||||
|
||||
.. note::
|
||||
Blocking APIs should also be avoided if possible. Try to find or build Reactive APIs,
|
||||
such that blocking is minimised, or moved over to dedicated dispatchers.
|
||||
|
||||
Often when integrating with existing libraries or systems it is not possible to
|
||||
avoid blocking APIs, then following solution explains how to handle blocking
|
||||
operations properly.
|
||||
|
||||
Note that the same hints apply to managing blocking operations anywhere in Akka,
|
||||
including in Actors etc.
|
||||
|
||||
In the below thread state diagrams the colours have the following meaning:
|
||||
|
||||
* Turquoise - Sleeping state
|
||||
* Orange - Waiting state
|
||||
* Green - Runnable state
|
||||
|
||||
The thread information was recorded using the YourKit profiler, however any good JVM profiler
|
||||
has this feature (including the free and bundled with the Oracle JDK VisualVM as well as Oracle Flight Recorder).
|
||||
|
||||
Problem example: blocking the default dispatcher
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. includecode2:: ../code/docs/http/scaladsl/server/BlockingInHttpExamplesSpec.scala
|
||||
:snippet: blocking-example-in-default-dispatcher
|
||||
|
||||
Here the app is exposed to load of continous GET requests and large number
|
||||
of akka.actor.default-dispatcher threads are handling requests. The orange
|
||||
portion of the thread shows that they are idle. Idle threads are fine,
|
||||
they're ready to accept new work. However large amounts of Turquoise (sleeping) threads are very bad!
|
||||
|
||||
.. image:: DispatcherBehaviourOnBadCode.png
|
||||
|
||||
After some time, the app is exposed to the load of requesting POST requests,
|
||||
which will block these threads. For example "``default-akka.default-dispatcher2,3,4``"
|
||||
are going into the blocking state, after being idle before. It can be observed
|
||||
that the number of new threads increase, "``default-akka.actor.default-dispatcher 18,19,20,...``"
|
||||
however they go to sleep state immediately, thus wasting the
|
||||
resources.
|
||||
|
||||
The number of such new threads depend on the default dispatcher configuration,
|
||||
but likely will not exceed 50. Since many POST requests are done, the entire
|
||||
thread pool is starved. The blocking operations dominate such that the routing
|
||||
infra has no thread available to handle the other requests.
|
||||
|
||||
In essence, the ``Thread.sleep`` has dominated all threads and caused anything
|
||||
executing on the default dispatcher to starve for resources (including any Actors
|
||||
that you have not configured an explicit dispatcher for (sic!)).
|
||||
|
||||
Solution: Dedicated dispatcher for blocking operations
|
||||
------------------------------------------------------
|
||||
|
||||
In ``application.conf``, the dispatcher dedicated for blocking behaviour should
|
||||
be configured as follows::
|
||||
|
||||
my-blocking-dispatcher {
|
||||
type = Dispatcher
|
||||
executor = "thread-pool-executor"
|
||||
thread-pool-executer {
|
||||
// or in Akka 2.4.2+
|
||||
fixed-pool-size = 16
|
||||
}
|
||||
throughput = 100
|
||||
}
|
||||
|
||||
There are many dispatcher options available which can be found in :ref:`dispatchers-scala`.
|
||||
|
||||
Here ``thread-pool-executer`` is used, which has a hard limit of threads, it can
|
||||
keep available for blocking operations. The size settings depend on the app
|
||||
functionality and the number of cores the server has.
|
||||
|
||||
Whenever blocking has to be done, use the above configured dispatcher
|
||||
instead of the default one:
|
||||
|
||||
.. includecode2:: ../code/docs/http/scaladsl/server/BlockingInHttpExamplesSpec.scala
|
||||
:snippet: blocking-example-in-dedicated-dispatcher
|
||||
|
||||
This forces the app to use the same load, initially normal requests and then
|
||||
the blocking requests. The thread pool behaviour is shown in the figrue.
|
||||
|
||||
.. image:: DispatcherBehaviourOnGoodCode.png
|
||||
|
||||
Initially, the normal requests are easily handled by default dispatcher, the
|
||||
green lines, which represents the actual execution.
|
||||
|
||||
When blocking operations are issued, the ``my-blocking-dispatcher``
|
||||
starts up to the number of configured threads. It handles sleeping. After
|
||||
certain period of nothing happening to the threads, it shuts them down.
|
||||
|
||||
If another bunch of operations have to be done, the pool will start new
|
||||
threads that will take care of putting them into sleep state, but the
|
||||
threads are not wasted.
|
||||
|
||||
In this case, the throughput of the normal GET requests are not impacted
|
||||
they were still served on the default dispatcher.
|
||||
|
||||
This is the recommended way of dealing with any kind of blocking in reactive
|
||||
applications. It is referred as "bulkheading" or "isolating" the bad behaving
|
||||
parts of an app. In this case, bad behaviour of blocking operations.
|
||||
|
||||
There is good documentation availabe in Akka docs section,
|
||||
`Blocking needs careful management <http://doc.akka.io/docs/akka/current/general/actor-systems.html#Blocking_Needs_Careful_Management>`_.
|
||||
|
|
@ -13,6 +13,7 @@ Akka HTTP
|
|||
routing-dsl/index
|
||||
client-side/index
|
||||
server-side-https-support
|
||||
handling-blocking-operations-in-akka-http-routes
|
||||
migration-from-spray
|
||||
migration-from-old-http-javadsl
|
||||
migration-guide-2.4.x-experimental
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue