[英]What are the requirements to read and write from an http stream (in boost::beast)?
[英]HTTP Delay from AWS Instance to Bitmex with Boost Beast and Ubuntu 18
我在 eu-west-1c 中有一個 AWS ec2 實例,它與目標服務器位於同一個數據中心 - www.bitmex.com 。 如果我從實例運行$ping bitmex.com
,則平均往返時間約為 0.4 毫秒。 $ping bitmex.com/api/v1
也是如此(專門測試 API 端點)。 但是,當我從一些 c++ 代碼運行 http 對bitmex.com/api/v1/order/bulk
的請求時,往返時間永遠不會超過 ~10 毫秒,但這是一種罕見的情況,大多數請求所花費的時間在數百毫秒或更長時間。 這使得這些調用和 ping 時間之間的差異會慢 1000 倍。
這些消息專門來自和來自訂單引擎(由於 /order/bulk 端點),所以我知道一些延遲可能與另一端的服務器有一些處理要做的事實相關聯。 但是,我以前從未見過它這么慢 - 之前在對此進行分析時,我觀察到對同一端點的請求與 ping 時間在同一區域的時間,如果有時不是更快的話。 無論市場活動如何,因此也會出現這種延遲,因此 bitmex 端點有多忙。
這讓我想知道問題是否出在我的代碼、正在使用的 http 庫、AWS 實例的設置或其他方面。 A key point here I think is that I tested the same program in a python version using pycurl as the http library and it actually outperformed the c++ version slightly on round-trip time, I know this has nothing to do with python vs. c++ as現在是消息在網絡上傳播並再次返回的時候了(忽略 kernel 和 http 庫在幕后所做的事情),目的只是為了測試不同的語言,Z80791B3AE7002FAAF8 庫是否存在問題。 python 代碼設法在 1 毫秒或接近 1 毫秒內恢復了一些消息,因此它的最佳情況下的性能比 c++ 代碼的最佳情況下快大約 10 倍。 它還具有 1-200 毫秒范圍內的較慢消息,但從未像 c++ 版本那樣慢。
我在 c++ 中嘗試的下一件事是先讀取 header,然后再讀取正文,以防野獸(我正在使用的 http 庫我正在使用的正文)正在等待結束時。 分析表明,延遲完全是在發送請求和實際再次接收任何字節之間。 我通過觀察 header 讀取所花費的時間在 100 毫秒內發現這一點,然后正文讀取最多需要幾微秒。 因此,當調用讀取時,主體的數據必須已經在機器上並准備好 go,但是等待消息的標頭/初始字節。
因此,我想知道是什么原因造成的 - 是否有其他人對 bitmex API 的這個端點有同樣的延遲? 任何人都可以就如何調試或可能導致延遲的原因提供任何建議嗎?
設置:
我嘗試和測試過的其他事情:
這是我的應用程序的精簡版本,它應該使用第 1 行中包含的用於編譯它的命令進行編譯和運行 - 為了讓它在交易所成功打開訂單,您需要添加一個 API 密鑰和秘密聲明為 class 的成員變量。 此代碼連接到 bitmex.com,保持連接有效並向 bitmex.com/api/v1/bulk 發送 5 個相同的訂單,並將響應時間打印到控制台:
// g++ -std=c++17 -pthread -o http_test.out http_test.cpp -lssl -lcrypto && ./http_test.out
//Boost & Beast headers
#include <boost/bind.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/version.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/websocket/ssl.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/optional.hpp>
//REST headers
#include <sstream>
#include <openssl/evp.h>
#include <openssl/hmac.h>
//Misc. headers
#include <iomanip>
#include <iostream>
#include <string>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
using namespace std;
class BitMEX_MM : public std::enable_shared_from_this<BitMEX_MM> {
int n_tests = 1;
//REST
tcp::resolver rest_resolver;
beast::ssl_stream<beast::tcp_stream> rest_stream;
beast::flat_buffer rest_buffer;
http::request<http::string_body> post_req;
http::response<http::string_body> post_res;
string limit_order_msg = "{\"orders\":[";
// Timing
struct timespec start, end;
//MEMBER VARIABLES
string apiKey = ""; //FILL IN API KEY
string apiSecret = ""; //FILL IN API SEC
int apiKeyLen = apiKey.length();
const char* apiKeyCStr = apiKey.c_str();
int apiSecLen = apiSecret.length();
const char* apiSecCStr = apiSecret.c_str();
int expiry_t = 5;
//REST FUNCTIONS
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
((string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}
string HMAC_SHA256_hex_POST(string valid_till)
{
string data = "POST/api/v1/order/bulk" + valid_till + limit_order_msg;
stringstream ss;
unsigned int len;
unsigned char out[EVP_MAX_MD_SIZE];
HMAC_CTX *ctx = HMAC_CTX_new();
HMAC_Init_ex(ctx, apiSecCStr, apiSecLen, EVP_sha256(), NULL);
HMAC_Update(ctx, (unsigned char*)data.c_str(), data.length());
HMAC_Final(ctx, out, &len);
HMAC_CTX_free(ctx);
for (int i = 0; i < len; ++i)
{
ss << std::setw(2) << std::setfill('0') << hex << (unsigned int)out[i];
}
return ss.str();
}
void
REST_on_resolve(
beast::error_code ec,
tcp::resolver::results_type results)
{
// Make the connection on the IP address we get from a lookup
beast::get_lowest_layer(rest_stream).async_connect(
results,
beast::bind_front_handler(
&BitMEX_MM::REST_on_connect,
shared_from_this()));
}
void
REST_on_connect(beast::error_code ec,
tcp::resolver::results_type::endpoint_type)
{
// Perform the SSL handshake
rest_stream.async_handshake(
ssl::stream_base::client,
beast::bind_front_handler(
&BitMEX_MM::REST_on_handshake,
shared_from_this()));
}
void
REST_on_handshake(beast::error_code ec)
{
limit_order_msg += "{\"symbol\":\"XBTUSD\",\"ordType\":\"Limit\",\"execInst\":\"ParticipateDoNotInitiate\",\"clOrdID\":\"" + to_string(n_tests) \
+ "\",\"side\":\"Buy\",\"price\":10.0" \
+ ",\"orderQty\":2}]}";
REST_write_limit_order_bulk();
}
void REST_write_limit_order_bulk()
{
int valid_till = time(0) + 5;
string valid_till_str = to_string(valid_till);
post_req.set("api-expires", valid_till_str);
post_req.set("api-signature", HMAC_SHA256_hex_POST(valid_till_str));
post_req.set("Content-Length", to_string(limit_order_msg.length()));
post_req.body() = limit_order_msg;
clock_gettime(CLOCK_MONOTONIC, &start);
http::write(rest_stream, post_req);
http::read(rest_stream, rest_buffer, post_res);
beast::error_code _ec;
std::size_t _bt;
process_limit_order_bulk_res(_ec, _bt);
}
void process_limit_order_bulk_res(beast::error_code ec,
std::size_t bytes_transferred)
{
clock_gettime(CLOCK_MONOTONIC, &end);
double time_taken;
time_taken = (end.tv_sec - start.tv_sec) + ((end.tv_nsec - start.tv_nsec) * 1e-9);
cout << "response time: " << time_taken << endl;
++n_tests;
if (n_tests <= 5)
{
limit_order_msg = "{\"orders\":[";
limit_order_msg += "{\"symbol\":\"XBTUSD\",\"ordType\":\"Limit\",\"execInst\":\"ParticipateDoNotInitiate\",\"clOrdID\":\"" + to_string(n_tests) \
+ "\",\"side\":\"Buy\",\"price\":10.0" \
+ ",\"orderQty\":2}]}";
REST_write_limit_order_bulk();
}
}
public:
explicit
BitMEX_MM(net::io_context& rest_ioc, ssl::context& rest_ctx)
: rest_resolver(net::make_strand(rest_ioc)),
rest_stream(net::make_strand(rest_ioc), rest_ctx)
{ }
void
run_REST_service()
{
// Set SNI Hostname (many hosts need this to handshake successfully)
if(! SSL_set_tlsext_host_name(rest_stream.native_handle(), "www.bitmex.com"))
{
beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
std::cerr << "ssl err " << ec.message() << "\n";
return;
}
// Set up an HTTP GET request message
post_req.version(11);
post_req.method(http::verb::post);
post_req.target("/api/v1/order/bulk");
post_req.set(http::field::host, "www.bitmex.com");
post_req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
post_req.set(http::field::accept, "*/*");
post_req.set(http::field::content_type, "application/json");
post_req.set(http::field::connection, "Keep-Alive");
post_req.set("api-key", "");
post_req.insert("Content-Length", "");
post_req.insert("api-expires", "");
post_req.insert("api-signature", "");
// Look up the domain name
rest_resolver.async_resolve(
"www.bitmex.com",
"443",
beast::bind_front_handler(
&BitMEX_MM::REST_on_resolve,
shared_from_this()));
}
};
int main(int argc, char** argv)
{
net::io_context rest_ioc;
ssl::context rest_ctx{ssl::context::tlsv12_client};
auto algo = make_shared<BitMEX_MM>(rest_ioc, rest_ctx);
cout << "Running http test." << endl;
algo->run_REST_service();
rest_ioc.run();
return 0;
}
來自並置服務器的結果:
Running http test.
response time: 0.0110592
response time: 0.00464095
response time: 0.00503278
response time: 0.00440869
response time: 0.00362543
似乎如您所料,由於與服務器建立連接,第一個響應需要更長的時間。 在那之后,響應時間要快得多,但仍然比 ping 時間長約 10 倍。 然而,它確實是可變的,另一次運行它會給出:
Running http test.
response time: 0.288384
response time: 0.188272
response time: 0.133779
response time: 0.0540737
response time: 0.0179791
慢得多。
根據我對 bitmex 引擎的了解,訂單執行的延遲約為 10 毫秒是你能得到的最好的,而且在高波動期間會更糟。 檢查https://bonfida.com/latency-monitor以了解延遲。 在加密世界中,延遲遠高於傳統的 hft
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.