そのため、サーバーを介してサードパーティの URL から要求元のクライアントに巨大なファイルをストリーミングしたい状況を実験しています。
これまでのところ、次のように、"eachable" レスポンス ボディの標準的な Rack の慣例に従って、Curb または Net::HTTP でこれを実装しようとしました。
class StreamBody
...
def each
some_http_library.on_body do | body_chunk |
yield(body_chunk)
end
end
end
ただし、このシステムの CPU 使用率を 40% 未満にすることはできません (私の MacBook Air では)。Goliath で同じことをしようとすると、em-synchrony を使用して (Goliath ページでアドバイスされているように)、CPU 使用率を約 25% の CPU に下げることができますが、ヘッダーをフラッシュすることはできません。要求しているクライアントでストリーミング ダウンロードが「ハング」し、指定したヘッダーに関係なく、応答全体がクライアントに送信されるとヘッダーが表示されます。
これは、Ruby が驚くほどうまくいかず、代わりに世界の go と nodejs に目を向けなければならないケースの 1 つであると考えるのは正しいでしょうか?
比較すると、現在、CURL から PHP 出力ストリームへの PHP ストリーミングを使用しており、CPU オーバーヘッドはほとんどありません。
または、自分のものを処理するよう依頼できるアップストリーム プロキシ ソリューションはありますか? 問題は - 本体全体がソケットに送信されたら Ruby 関数を確実に呼び出したいのですが、nginx プロキシなどではそれができません。
更新: HTTP クライアントの簡単なベンチマークを実行しようとしましたが、ほとんどの CPU 使用は HTTP クライアント ライブラリのようです。Ruby HTTP クライアントのベンチマークがありますが、それらは応答受信時間に基づいていますが、CPU 使用率については言及されていません。私のテストでは、結果を に書き込む HTTP ストリーミング ダウンロードを実行し、/dev/null
一貫して 30 ~ 40% の CPU 使用率を得ました。これは、Rack ハンドラを介してストリーミングしたときの CPU 使用率とほぼ一致します。
更新:ほとんどの Rack ハンドラー (Unicorn など) は、応答本文で write() ループを使用することが判明しました。これは、応答を十分に高速に書き込むことができない場合に (CPU 負荷が高い) ビジー状態になる可能性があります。これは、を使用して出力ソケットに書き込みを行うことで、ある程度軽減できます(rack.hijack
サーバーが自分でそれを行わないことに驚きました)。write_nonblock
IO.select
lambda do |socket|
begin
rack_response_body.each do | chunk |
begin
bytes_written = socket.write_nonblock(chunk)
# If we could write only partially, make sure we do a retry on the next
# iteration with the remaining part
if bytes_written < chunk.bytesize
chunk = chunk[bytes_written..-1]
raise Errno::EINTR
end
rescue IO::WaitWritable, Errno::EINTR # The output socket is saturated.
IO.select(nil, [socket]) # Then let's wait on the socket to be writable again
retry # and off we go...
rescue Errno::EPIPE # Happens when the client aborts the connection
return
end
end
ensure
socket.close rescue IOError
rack_response_body.close if rack_response_body.respond_to?(:close)
end
end