Skip to content

Commit bc6944c

Browse files
Rustamchukru.nazarov
andauthored
tcp balancer logic (#38)
* tcp balancer logic * tcp balancer logic --------- Co-authored-by: ru.nazarov <ru.nazarov@vkteam.ru>
1 parent 2278e75 commit bc6944c

1 file changed

Lines changed: 69 additions & 31 deletions

File tree

statshouse.hpp

Lines changed: 69 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ class TransportUDPBase {
180180
MAX_DATAGRAM_SIZE = 65507, // https://stackoverflow.com/questions/42609561/udp-maximum-packet-size/42610200
181181
MAX_KEYS = 47,
182182
MAX_FULL_KEY_SIZE = 1024, // roughly metric plus all tags
183+
MAX_HOST_TAG_LEN = 128,
183184
};
184185

185186
// Metric name builder. Use by calling transport.metric("metric").tag("tag1").tag("tag2").write_count(1);
@@ -197,9 +198,6 @@ class TransportUDPBase {
197198
}
198199
// Assigns arbitrary tag value by key name
199200
MetricBuilder & tag(string_view key, string_view str) {
200-
if (key.size() == 2 && key.data()[0] == '_' && key.data()[1] == 'h') {
201-
host_set = true;
202-
}
203201
auto begin = buffer + buffer_pos;
204202
auto end = buffer + MAX_FULL_KEY_SIZE;
205203
if (STATSHOUSE_UNLIKELY(!(begin = pack_string(begin, end, key)))) { did_not_fit = true; return *this; }
@@ -262,7 +260,6 @@ class TransportUDPBase {
262260
}
263261
TransportUDPBase *transport;
264262
bool env_set = false; // so current default_env will be added in pack_header even if set later.
265-
bool host_set = false; // if explicit _h is set, do not add default hostname tag.
266263
bool did_not_fit = false; // we do not want any exceptions in writing metrics
267264
size_t next_tag = 1;
268265
size_t tags_count = 0;
@@ -276,16 +273,10 @@ class TransportUDPBase {
276273
if (::gethostname(host_buf, sizeof(host_buf) - 1) == 0) {
277274
host_buf[sizeof(host_buf) - 1] = '\0';
278275
host_tag.assign(host_buf);
276+
if (host_tag.size() > MAX_HOST_TAG_LEN) {
277+
host_tag.resize(MAX_HOST_TAG_LEN);
278+
}
279279
}
280-
if (host_tag.empty()) {
281-
return;
282-
}
283-
host_tag_tl_pair.resize(4 + host_tag.size() + 4); // key name, host, padding
284-
auto begin = &host_tag_tl_pair[0];
285-
auto end = begin + host_tag_tl_pair.size();
286-
begin = pack32(begin, end, 2 | (uint32_t('_') << 8) | (uint32_t('h') << 16));
287-
begin = pack_string(begin, end, host_tag);
288-
host_tag_tl_pair.resize(begin - &host_tag_tl_pair[0]);
289280
}
290281
virtual ~TransportUDPBase() = default;
291282

@@ -401,6 +392,7 @@ class TransportUDPBase {
401392
protected:
402393
Stats stats; // allow implementations to update stats directly for simplicity
403394
mutable mutex mu;
395+
std::string host_tag;
404396
bool write_count_locked(const MetricBuilder &metric, double count, uint32_t tsUnixSec = 0) {
405397
return write_count_impl(metric, count, tsUnixSec);
406398
}
@@ -430,8 +422,6 @@ class TransportUDPBase {
430422
std::string default_env;
431423
std::string default_env_tl_pair; // "0", default_env in TL format to optimized writing
432424
std::string app_name;
433-
std::string host_tag;
434-
std::string host_tag_tl_pair; // "_h", hostname in TL format;
435425

436426
size_t max_payload_size = DEFAULT_DATAGRAM_SIZE;
437427
bool immediate_flush = false;
@@ -616,19 +606,13 @@ class TransportUDPBase {
616606
if (STATSHOUSE_UNLIKELY(!enoughSpace(begin, end, metric.buffer_pos))) { return nullptr; }
617607
std::memcpy(begin, metric.buffer, metric.buffer_pos);
618608
const bool add_env = !metric.env_set && !default_env_tl_pair.empty();
619-
const bool add_host = !metric.host_set && !host_tag_tl_pair.empty();
620-
put32(begin + metric.tags_count_pos(), metric.tags_count + int(add_env) + int(add_host));
609+
put32(begin + metric.tags_count_pos(), metric.tags_count + int(add_env));
621610
begin += metric.buffer_pos;
622611
if (add_env) {
623612
if (STATSHOUSE_UNLIKELY(!enoughSpace(begin, end, default_env_tl_pair.size()))) { return nullptr; }
624613
std::memcpy(begin, default_env_tl_pair.data(), default_env_tl_pair.size());
625614
begin += default_env_tl_pair.size();
626615
}
627-
if (add_host) {
628-
if (STATSHOUSE_UNLIKELY(!enoughSpace(begin, end, host_tag_tl_pair.size()))) { return nullptr; }
629-
std::memcpy(begin, host_tag_tl_pair.data(), host_tag_tl_pair.size());
630-
begin += host_tag_tl_pair.size();
631-
}
632616
if (fields_mask & TL_STATSHOUSE_METRIC_COUNTER_FIELDS_MASK) {
633617
if (STATSHOUSE_UNLIKELY(!(begin = pack64(begin, end, doubleBits(counter))))) { return nullptr;}
634618
}
@@ -901,7 +885,8 @@ class TransportTCP : public TransportUDPBase {
901885
TransportTCP(const std::string &host, int port, const std::string &resolve_targets = "")
902886
: host_{host}
903887
, resolve_targets_{resolve_targets}
904-
, port_{port} {
888+
, port_{port}
889+
, handshake_(build_tcp_handshake_v2(host_tag)) {
905890
(void)refresh_pools();
906891
primary_worker_.th = std::thread(&TransportTCP::run_worker, this, &primary_worker_);
907892
secondary_worker_.th = std::thread(&TransportTCP::run_worker, this, &secondary_worker_);
@@ -938,7 +923,10 @@ class TransportTCP : public TransportUDPBase {
938923
}
939924

940925
private:
941-
enum { tcpConnBucketCount = 512 };
926+
enum {
927+
tcpConnBucketCount = 512,
928+
stuckReconDelaySec = 30,
929+
};
942930
struct resolved_addr {
943931
::sockaddr_storage storage{};
944932
socklen_t len = 0;
@@ -952,6 +940,8 @@ class TransportTCP : public TransportUDPBase {
952940
int sock = -1;
953941
std::thread th;
954942
std::atomic<size_t> would_block_bytes{0};
943+
std::atomic_bool force_reconnect{false};
944+
std::chrono::steady_clock::time_point last_stuck_recon{std::chrono::steady_clock::now()};
955945
};
956946
struct send_result {
957947
bool ok = false;
@@ -962,6 +952,7 @@ class TransportTCP : public TransportUDPBase {
962952
std::string host_;
963953
std::string resolve_targets_;
964954
int port_ = 0;
955+
std::string handshake_; // precomputed reconnect key (statshousev2 + host)
965956
worker primary_worker_;
966957
worker secondary_worker_;
967958
std::mutex route_mu_;
@@ -996,6 +987,11 @@ class TransportTCP : public TransportUDPBase {
996987
return flush_packet_result::ok;
997988
}
998989
if (enqueue(*secondary, payload)) {
990+
primary->force_reconnect.store(true, std::memory_order_relaxed);
991+
{
992+
std::lock_guard<std::mutex> lock{primary->mu};
993+
primary->cv.notify_one();
994+
}
999995
std::lock_guard<std::mutex> lock{route_mu_};
1000996
std::swap(primary_route_, secondary_route_);
1001997
return flush_packet_result::ok;
@@ -1017,21 +1013,47 @@ class TransportTCP : public TransportUDPBase {
10171013
void run_worker(worker *w) {
10181014
for (;;) {
10191015
std::vector<char> payload;
1016+
bool have_payload = false;
1017+
bool need_recon = false;
10201018
{
10211019
std::unique_lock<std::mutex> lock{w->mu};
1022-
w->cv.wait(lock, [this, w]() { return stop_.load() || !w->queue.empty(); });
1023-
if (w->queue.empty() && stop_.load()) {
1020+
w->cv.wait(lock, [this, w]() {
1021+
return stop_.load() || !w->queue.empty() || w->force_reconnect.load(std::memory_order_relaxed);
1022+
});
1023+
if (w->force_reconnect.exchange(false, std::memory_order_relaxed)) {
1024+
need_recon = true;
1025+
}
1026+
if (!w->queue.empty()) {
1027+
payload = std::move(w->queue.front());
1028+
w->queue.pop_front();
1029+
have_payload = true;
1030+
} else if (stop_.load()) {
10241031
break;
10251032
}
1026-
payload = std::move(w->queue.front());
1027-
w->queue.pop_front();
10281033
}
1029-
send_payload_with_reconnect(*w, payload);
1034+
if (need_recon) {
1035+
maybe_stuck_reconnect(*w);
1036+
}
1037+
if (have_payload) {
1038+
send_payload_with_reconnect(*w, payload);
1039+
}
10301040
}
10311041
}
10321042

1043+
void maybe_stuck_reconnect(worker &w) {
1044+
const auto now = std::chrono::steady_clock::now();
1045+
if (now < w.last_stuck_recon + std::chrono::seconds(stuckReconDelaySec)) {
1046+
return;
1047+
}
1048+
close_socket(w.sock);
1049+
w.last_stuck_recon = now;
1050+
}
1051+
10331052
void send_payload_with_reconnect(worker &w, const std::vector<char> &payload) {
10341053
for (;;) {
1054+
if (w.force_reconnect.exchange(false, std::memory_order_relaxed)) {
1055+
maybe_stuck_reconnect(w);
1056+
}
10351057
if (w.sock < 0 && !reconnect(w)) {
10361058
if (stop_.load()) {
10371059
return;
@@ -1107,8 +1129,7 @@ class TransportTCP : public TransportUDPBase {
11071129
set_errno_error(err, "statshouse::TransportTCP connect() failed");
11081130
return false;
11091131
}
1110-
static const char kHeader[] = "statshousev1";
1111-
const send_result header_write = send_all(sock, kHeader, sizeof(kHeader) - 1);
1132+
const send_result header_write = send_all(sock, handshake_.data(), handshake_.size());
11121133
if (!header_write.ok) {
11131134
int err = header_write.err;
11141135
::close(sock);
@@ -1241,10 +1262,27 @@ class TransportTCP : public TransportUDPBase {
12411262
kb.tag("1", "5"); // lang: cpp
12421263
kb.tag("2", "1"); // kind: would block
12431264
kb.tag("3", app_name_locked());
1265+
if (!host_tag.empty()) {
1266+
kb.tag("_h", host_tag);
1267+
}
12441268
const double lost = static_cast<double>(n);
12451269
(void)write_values_locked(kb, &lost, 1, 0, 0);
12461270
}
12471271

1272+
static std::string build_tcp_handshake_v2(const std::string &host_tag) {
1273+
std::string buf;
1274+
buf.reserve(11 + 1 + 4 + host_tag.size());
1275+
buf.append("statshousev");
1276+
buf.push_back('2');
1277+
const uint32_t n = static_cast<uint32_t>(host_tag.size());
1278+
buf.push_back(static_cast<char>(n));
1279+
buf.push_back(static_cast<char>(n >> 8));
1280+
buf.push_back(static_cast<char>(n >> 16));
1281+
buf.push_back(static_cast<char>(n >> 24));
1282+
buf.append(host_tag);
1283+
return buf;
1284+
}
1285+
12481286
void set_errno_error(int err, const char *msg) {
12491287
stats.last_error.clear();
12501288
stats.last_error.append(msg);

0 commit comments

Comments
 (0)