使用Scotty的网络I/O吞吐量低得出乎意料



我尝试对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"

我编写了另一个服务器,它调用这个服务器并返回响应。

{-# 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 -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

虽然这明显高于其他web服务器(如Phoenix),但我意识到这并不意味着什么,因为大多数响应是由于文件描述符耗尽而发生的500个错误。

我检查了限值,它非常低。

ulimit -n
256

我将这些限制增加到

ulimit -n 10240

我再次运行work,这一次很明显,吞吐量已经大大降低了。

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。

我不知道原因。如果能有个解释就好了

相关内容

  • 没有找到相关文章

最新更新