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:
Konrad Malawski 2016-07-08 14:05:49 +02:00 committed by GitHub
parent 6d2d4d5d25
commit 77513c41d4
7 changed files with 175 additions and 1 deletions

View file

@ -1,7 +1,7 @@
.. _-parameters-java-: .. _-parameters-java-:
parameters parameters
========= ==========
Extracts multiple *query* parameter values from the request. Extracts multiple *query* parameter values from the request.
Description Description

View file

@ -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
}
}
}
//#
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

View file

@ -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>`_.

View file

@ -13,6 +13,7 @@ Akka HTTP
routing-dsl/index routing-dsl/index
client-side/index client-side/index
server-side-https-support server-side-https-support
handling-blocking-operations-in-akka-http-routes
migration-from-spray migration-from-spray
migration-from-old-http-javadsl migration-from-old-http-javadsl
migration-guide-2.4.x-experimental migration-guide-2.4.x-experimental