簡體   English   中英

HTTP 延遲從 AWS 實例到 Bitmex 與 Boost Beast 和 Ubuntu 18

[英]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 的這個端點有同樣的延遲? 任何人都可以就如何調試或可能導致延遲的原因提供任何建議嗎?

設置:

  • AWS 實例正在運行 Ubuntu 服務器 18
  • c++17 在 ubuntu 20(我的本地機器)上使用 g++9 編譯,然后 scp 到 AWS 實例並在那里運行
  • http 的 Boost Beast 庫僅使用異步調用
  • 所有電話都是 https

我嘗試和測試過的其他事情:

  • Ubuntu 20 與 18 - 沒有區別
  • Boost asio 處理程序跟蹤器 - 無法使其工作/不知道在哪里或如何從中查看 output
  • cURL c++ http 庫 - 沒有區別

這是我的應用程序的精簡版本,它應該使用第 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM