我在Akka-http框架(版本:10.0)上执行负载测试,我正在使用wrk工具 . wrk命令:
wrk -t6 -c10000 -d 60s --timeout 10s --latency http://localhost:8080/hello
首次运行没有任何阻塞调用,
object WebServer {
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
def main(args: Array[String]) {
val bindingFuture = Http().bindAndHandle(router.route, "localhost", 8080)
println(
s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
object router {
implicit val executionContext = WebServer.executionContext
val route =
path("hello") {
get {
complete {
"Ok"
}
}
}
}
输出wrk:
Running 1m test @ http://localhost:8080/hello
6 threads and 10000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 4.22ms 16.41ms 2.08s 98.30%
Req/Sec 9.86k 6.31k 25.79k 62.56%
Latency Distribution
50% 3.14ms
75% 3.50ms
90% 4.19ms
99% 31.08ms
3477084 requests in 1.00m, 477.50MB read
Socket errors: connect 9751, read 344, write 0, timeout 0
Requests/sec: 57860.04
Transfer/sec: 7.95MB
现在,如果我在路线中添加未来的呼叫并再次运行测试 .
val route =
path("hello") {
get {
complete {
Future { // Blocking code
Thread.sleep(100)
"OK"
}
}
}
}
输出,wrk:
Running 1m test @ http://localhost:8080/hello
6 threads and 10000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 527.07ms 491.20ms 10.00s 88.19%
Req/Sec 49.75 39.55 257.00 69.77%
Latency Distribution
50% 379.28ms
75% 632.98ms
90% 1.08s
99% 2.07s
13744 requests in 1.00m, 1.89MB read
Socket errors: connect 9751, read 385, write 38, timeout 98
Requests/sec: 228.88
Transfer/sec: 32.19KB
正如您所看到的,将来只能呼叫 13744 requests are being served .
在关注Akka documentation之后,我为路由添加了一个单独的调度程序线程池,它创建了最大的 200 threads .
implicit val executionContext = WebServer.system.dispatchers.lookup("my-blocking-dispatcher")
// config of dispatcher
my-blocking-dispatcher {
type = Dispatcher
executor = "thread-pool-executor"
thread-pool-executor {
// or in Akka 2.4.2+
fixed-pool-size = 200
}
throughput = 1
}
经过上述改动后,性能有所提升
Running 1m test @ http://localhost:8080/hello
6 threads and 10000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 127.03ms 21.10ms 504.28ms 84.30%
Req/Sec 320.89 175.58 646.00 60.01%
Latency Distribution
50% 122.85ms
75% 135.16ms
90% 147.21ms
99% 190.03ms
114378 requests in 1.00m, 15.71MB read
Socket errors: connect 9751, read 284, write 0, timeout 0
Requests/sec: 1903.01
Transfer/sec: 267.61KB
在 my-blocking-dispatcher config 如果我将池大小增加到200以上,性能是相同的 .
现在,我应该使用什么其他参数或配置来提高性能,同时使用将来的call.So该应用程序提供最大吞吐量 .
1 回答
首先是一些免责声明:我以前没有使用
wrk
工具,所以我可能会出错 . 以下是我为这个答案做出的假设:连接数与线程数无关,即如果我指定
-t4 -c10000
则保持10000个连接,而不是4 * 10000 .对于每个连接,行为如下:它发送请求,完全接收响应,并立即发送下一个,等等,直到时间用完为止 .
另外我在与wrk相同的机器上运行服务器,我的机器似乎比你的机器弱(我只有双核CPU),所以我把wrk的线程数减少到2,连接数减少到1000,得到体面的结果 .
我使用的Akka Http版本是
10.0.1
,而wrk版本是4.0.2
.现在回答 . 让我们来看看你拥有的阻止代码:
这意味着,每个请求至少需要100毫秒 . 如果您有200个线程和1000个连接,则时间轴将如下所示:
其中
Msg
是已处理消息的数量,Ms
是经过的时间(以毫秒为单位) .这给了我们每秒处理2000条消息,或者每30秒大约60000条消息,这大多与测试数字一致:
很明显,这个数字(每秒2000条消息)严格受线程数限制 . 例如 . 如果我们有300个线程,我们每100毫秒处理300条消息,所以如果我们的系统可以处理这么多线程,我们每秒会有3000条消息 . 让我们看看如果我们为每个连接提供1个线程,即池中的1000个线程,我们将如何运行:
正如您所看到的,现在一个请求平均需要几乎100毫秒,即我们放入
Thread.sleep
的相同数量 . 看起来我们可以在one thread per request
的标准情况下非常多,这种情况在很长一段时间内运行良好,直到异步IO让服务器扩展得更高 .为了便于比较,这是我的机器上使用默认的fork-join线程池的完全非阻塞测试结果:
总而言之,如果使用阻塞操作,每个请求需要一个线程来实现最佳吞吐量,因此请相应地配置线程池 . 您的系统可以处理多少线程有自然限制,您可能需要调整操作系统以获得最大线程数 . 为获得最佳吞吐量,请避免阻塞操作
也不要将异步操作与非阻塞操作混淆 . 使用
Future
和Thread.sleep
的代码是异步但阻塞操作的完美示例 . 许多流行的软件在这种模式下运行(一些传统的HTTP客户端,Cassandra驱动程序,AWS Java SDK等) . 要完全获得非阻塞HTTP服务器的好处,您需要一直非阻塞,而不仅仅是异步 . 它可能并不总是可能,但它是值得努力的东西 .