使用 Scotty 的网络 I/O 吞吐量异常低
Unexpectedly low throughput for Network I/O using Scotty
我尝试对 Scotty 进行基准测试以测试网络 I/O 效率和整体吞吐量。
为此我设置了两个用Haskell编写的本地服务器。一个什么都不做,只是充当 API.
相同的代码是
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Network.Wai.Middleware.RequestLogger
import Control.Monad
import Data.Text
import Control.Monad.Trans
import Data.ByteString
import Network.HTTP.Types (status302)
import Data.Time.Clock
import Data.Text.Lazy.Encoding (decodeUtf8)
import Control.Concurrent
import Network.HTTP.Conduit
import Network.Connection (TLSSettings (..))
import Network.HTTP.Client
import Network
main = do
scotty 4001 $ do
middleware logStdoutDev
get "/dummy_api" $ do
text $ "dummy response"
我写了另一个调用这个服务器的服务器和 returns 响应。
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Network.Wai.Middleware.RequestLogger
import Control.Monad
import Control.Monad.Trans
import qualified Data.Text.Internal.Lazy as LT
import Data.ByteString
import Network.HTTP.Types (status302)
import Data.Time.Clock
import Data.Text.Lazy.Encoding (decodeUtf8)
import Control.Concurrent
import qualified Data.ByteString.Lazy as LB
import Network.HTTP.Conduit
import Network.Connection (TLSSettings (..))
import Network.HTTP.Client
import Network
main = do
let man = newManager defaultManagerSettings
scotty 3000 $ do
middleware logStdoutDev
get "/filters" $ do
response <- liftIO $! (testGet man)
json $ decodeUtf8 (LB.fromChunks response)
testGet :: IO Manager -> IO [B.ByteString]
testGet manager = do
request <- parseUrl "http://localhost:4001/dummy_api"
man <- manager
let req = request { method = "GET", responseTimeout = Nothing, redirectCount = 0}
a <- withResponse req man $ brConsume . responseBody
return $! a
对于这两个服务器 运行,我执行了 wrk 基准测试并获得了极高的吞吐量。
wrk -t30 -c100 -d60s "http://localhost:3000/filters"
Running 1m test @ http://localhost:3000/filters
30 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 30.86ms 78.40ms 1.14s 95.63%
Req/Sec 174.05 62.29 1.18k 76.20%
287047 requests in 1.00m, 91.61MB read
Socket errors: connect 0, read 0, write 0, timeout 118
Non-2xx or 3xx responses: 284752
Requests/sec: 4776.57
Transfer/sec: 1.52MB
虽然这明显高于 Phoenix 等其他 Web 服务器,但我意识到这没有任何意义,因为大多数响应都是由于文件描述符耗尽而发生的 500 错误。
我检查了非常低的限制。
ulimit -n
256
我将这些限制提高到
ulimit -n 10240
我 运行 再次工作,这一次显然吞吐量已经大幅下降。
wrk -t30 -c100 -d60s "http://localhost:3000/filters"
Running 1m test @ http://localhost:3000/filters
30 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 105.69ms 161.72ms 1.24s 96.27%
Req/Sec 19.88 16.62 120.00 58.12%
8207 requests in 1.00m, 1.42MB read
Socket errors: connect 0, read 0, write 0, timeout 1961
Non-2xx or 3xx responses: 1521
Requests/sec: 136.60
Transfer/sec: 24.24KB
虽然500个错误的数量减少了,但并没有消除。我对 Gin 和 Phoenix 进行了基准测试,它们比 Scotty
好得多,但没有给出任何 500 个响应。
我错过了什么拼图?我怀疑有一个问题我无法调试。
我知道 http-conduit 与这些错误有很大关系,http-client
库在幕后使用它,这与 Scotty
无关。
@Yuras 的比喻是正确的。再次在 运行 服务器上,所有与非 2xx 状态代码相关的问题都消失了。
主块中的第一行是罪魁祸首。
我从
更改了行
main = do
let man = newManager defaultManagerSettings
至
main = do
man <- newManager defaultManagerSettings
瞧,没有任何问题。该程序的高内存使用量也从之前的 1GB 稳定到 21MB。
虽然我不知道原因。如果对此有解释就好了。
我尝试对 Scotty 进行基准测试以测试网络 I/O 效率和整体吞吐量。
为此我设置了两个用Haskell编写的本地服务器。一个什么都不做,只是充当 API.
相同的代码是
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Network.Wai.Middleware.RequestLogger
import Control.Monad
import Data.Text
import Control.Monad.Trans
import Data.ByteString
import Network.HTTP.Types (status302)
import Data.Time.Clock
import Data.Text.Lazy.Encoding (decodeUtf8)
import Control.Concurrent
import Network.HTTP.Conduit
import Network.Connection (TLSSettings (..))
import Network.HTTP.Client
import Network
main = do
scotty 4001 $ do
middleware logStdoutDev
get "/dummy_api" $ do
text $ "dummy response"
我写了另一个调用这个服务器的服务器和 returns 响应。
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Network.Wai.Middleware.RequestLogger
import Control.Monad
import Control.Monad.Trans
import qualified Data.Text.Internal.Lazy as LT
import Data.ByteString
import Network.HTTP.Types (status302)
import Data.Time.Clock
import Data.Text.Lazy.Encoding (decodeUtf8)
import Control.Concurrent
import qualified Data.ByteString.Lazy as LB
import Network.HTTP.Conduit
import Network.Connection (TLSSettings (..))
import Network.HTTP.Client
import Network
main = do
let man = newManager defaultManagerSettings
scotty 3000 $ do
middleware logStdoutDev
get "/filters" $ do
response <- liftIO $! (testGet man)
json $ decodeUtf8 (LB.fromChunks response)
testGet :: IO Manager -> IO [B.ByteString]
testGet manager = do
request <- parseUrl "http://localhost:4001/dummy_api"
man <- manager
let req = request { method = "GET", responseTimeout = Nothing, redirectCount = 0}
a <- withResponse req man $ brConsume . responseBody
return $! a
对于这两个服务器 运行,我执行了 wrk 基准测试并获得了极高的吞吐量。
wrk -t30 -c100 -d60s "http://localhost:3000/filters"
Running 1m test @ http://localhost:3000/filters
30 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 30.86ms 78.40ms 1.14s 95.63%
Req/Sec 174.05 62.29 1.18k 76.20%
287047 requests in 1.00m, 91.61MB read
Socket errors: connect 0, read 0, write 0, timeout 118
Non-2xx or 3xx responses: 284752
Requests/sec: 4776.57
Transfer/sec: 1.52MB
虽然这明显高于 Phoenix 等其他 Web 服务器,但我意识到这没有任何意义,因为大多数响应都是由于文件描述符耗尽而发生的 500 错误。
我检查了非常低的限制。
ulimit -n
256
我将这些限制提高到
ulimit -n 10240
我 运行 再次工作,这一次显然吞吐量已经大幅下降。
wrk -t30 -c100 -d60s "http://localhost:3000/filters"
Running 1m test @ http://localhost:3000/filters
30 threads and 100 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 105.69ms 161.72ms 1.24s 96.27%
Req/Sec 19.88 16.62 120.00 58.12%
8207 requests in 1.00m, 1.42MB read
Socket errors: connect 0, read 0, write 0, timeout 1961
Non-2xx or 3xx responses: 1521
Requests/sec: 136.60
Transfer/sec: 24.24KB
虽然500个错误的数量减少了,但并没有消除。我对 Gin 和 Phoenix 进行了基准测试,它们比 Scotty
好得多,但没有给出任何 500 个响应。
我错过了什么拼图?我怀疑有一个问题我无法调试。
我知道 http-conduit 与这些错误有很大关系,http-client
库在幕后使用它,这与 Scotty
无关。
@Yuras 的比喻是正确的。再次在 运行 服务器上,所有与非 2xx 状态代码相关的问题都消失了。
主块中的第一行是罪魁祸首。 我从
更改了行main = do
let man = newManager defaultManagerSettings
至
main = do
man <- newManager defaultManagerSettings
瞧,没有任何问题。该程序的高内存使用量也从之前的 1GB 稳定到 21MB。
虽然我不知道原因。如果对此有解释就好了。