|
18 | 18 |
|
19 | 19 | #include <algorithm> // std::set_union |
20 | 20 | #include <array> |
| 21 | +#include <cmath> // std::ceil |
| 22 | +#include <limits> // numeric_limits |
21 | 23 | #include <gflags/gflags.h> |
22 | 24 | #include <openssl/md5.h> |
23 | 25 | #include "butil/containers/flat_map.h" |
24 | 26 | #include "butil/errno.h" |
25 | 27 | #include "butil/strings/string_number_conversions.h" |
26 | 28 | #include "brpc/socket.h" |
| 29 | +#include "brpc/reloadable_flags.h" |
27 | 30 | #include "brpc/policy/consistent_hashing_load_balancer.h" |
28 | 31 | #include "brpc/policy/hasher.h" |
29 | 32 |
|
30 | 33 | namespace brpc { |
31 | 34 | namespace policy { |
32 | 35 |
|
33 | | -// TODO: or 160? |
34 | | -DEFINE_int32(chash_num_replicas, 100, |
35 | | - "default number of replicas per server in chash"); |
36 | | -DEFINE_bool(consistent_hashing_enable_server_tag, false, |
| 36 | +DEFINE_int32(chash_num_replicas, 100, |
| 37 | + "default number of replicas per server in chash, " |
| 38 | + "overridable per channel with the `replicas' parameter"); |
| 39 | +DEFINE_bool(consistent_hashing_enable_server_tag, false, |
37 | 40 | "if consistent hashing enable server with tag"); |
| 41 | +DEFINE_double(chash_bounded_load_factor, 1.25, |
| 42 | + "default capacity factor of bounded-load consistent hashing" |
| 43 | + "(c_*_bl): a server takes at most ceil(factor * average " |
| 44 | + "in-flight) requests before overflowing to its ring successor, " |
| 45 | + "overridable per channel with the `load_factor' parameter"); |
| 46 | + |
| 47 | +static bool ValidateLoadFactor(const char*, double factor) { |
| 48 | + return factor > 1.0; |
| 49 | +} |
| 50 | +BRPC_VALIDATE_GFLAG(chash_bounded_load_factor, ValidateLoadFactor); |
38 | 51 |
|
39 | 52 | // Defined in hasher.cpp. |
40 | 53 | const char* GetHashName(HashFunc hasher); |
@@ -395,16 +408,223 @@ bool ConsistentHashingLoadBalancer::SetParameters(const butil::StringPiece& para |
395 | 408 | LOG(ERROR) << "Empty value for " << sp.key() << " in lb parameter"; |
396 | 409 | return false; |
397 | 410 | } |
398 | | - if (sp.key() == "replicas") { |
399 | | - if (!butil::StringToSizeT(sp.value(), &_num_replicas)) { |
400 | | - return false; |
| 411 | + if (!SetParameter(sp.key(), sp.value())) { |
| 412 | + return false; |
| 413 | + } |
| 414 | + } |
| 415 | + return true; |
| 416 | +} |
| 417 | + |
| 418 | +bool ConsistentHashingLoadBalancer::SetParameter( |
| 419 | + const butil::StringPiece& key, const butil::StringPiece& value) { |
| 420 | + if (key == "replicas") { |
| 421 | + return butil::StringToSizeT(value, &_num_replicas); |
| 422 | + } |
| 423 | + LOG(ERROR) << "Failed to set this unknown parameters " << key << '=' << value; |
| 424 | + return true; |
| 425 | +} |
| 426 | + |
| 427 | +ConsistentHashingBoundedLoadBalancer::ConsistentHashingBoundedLoadBalancer( |
| 428 | + ConsistentHashingLoadBalancerType type) |
| 429 | + : ConsistentHashingLoadBalancer(type) |
| 430 | + , _load_factor(FLAGS_chash_bounded_load_factor) |
| 431 | + , _total_inflight(0) {} |
| 432 | + |
| 433 | +size_t ConsistentHashingBoundedLoadBalancer::ResetLoads( |
| 434 | + LoadMap& bg, const LoadMap& fg, const std::vector<SocketId>& ids) { |
| 435 | + bg.clear(); |
| 436 | + for (size_t i = 0; i < ids.size(); ++i) { |
| 437 | + const std::shared_ptr<ServerLoad>* fg_load = fg.seek(ids[i]); |
| 438 | + bg[ids[i]] = (fg_load != nullptr) |
| 439 | + ? *fg_load : std::make_shared<ServerLoad>(); |
| 440 | + } |
| 441 | + // Non-zero so that both buffers are always rebuilt. |
| 442 | + return 1; |
| 443 | +} |
| 444 | + |
| 445 | +void ConsistentHashingBoundedLoadBalancer::SyncLoadMap() { |
| 446 | + std::vector<SocketId> ids; |
| 447 | + { |
| 448 | + butil::DoublyBufferedData<std::vector<Node> >::ScopedPtr s; |
| 449 | + if (_db_hash_ring.Read(&s) != 0) { |
| 450 | + return; |
| 451 | + } |
| 452 | + butil::FlatSet<SocketId> id_set; |
| 453 | + ids.reserve(s->size() / std::max(_num_replicas, (size_t)1)); |
| 454 | + for (size_t i = 0; i < s->size(); ++i) { |
| 455 | + const SocketId id = (*s)[i].server_sock.id; |
| 456 | + if (id_set.seek(id) == nullptr && id_set.insert(id) != nullptr) { |
| 457 | + ids.push_back(id); |
401 | 458 | } |
402 | | - continue; |
403 | 459 | } |
404 | | - LOG(ERROR) << "Failed to set this unknown parameters " << sp.key_and_value(); |
405 | 460 | } |
| 461 | + _db_load_map.ModifyWithForeground(ResetLoads, ids); |
| 462 | +} |
| 463 | + |
| 464 | +bool ConsistentHashingBoundedLoadBalancer::AddServer(const ServerId& server) { |
| 465 | + if (!ConsistentHashingLoadBalancer::AddServer(server)) { |
| 466 | + return false; |
| 467 | + } |
| 468 | + SyncLoadMap(); |
406 | 469 | return true; |
407 | 470 | } |
408 | 471 |
|
| 472 | +bool ConsistentHashingBoundedLoadBalancer::RemoveServer(const ServerId& server) { |
| 473 | + if (!ConsistentHashingLoadBalancer::RemoveServer(server)) { |
| 474 | + return false; |
| 475 | + } |
| 476 | + SyncLoadMap(); |
| 477 | + return true; |
| 478 | +} |
| 479 | + |
| 480 | +size_t ConsistentHashingBoundedLoadBalancer::AddServersInBatch( |
| 481 | + const std::vector<ServerId>& servers) { |
| 482 | + const size_t n = ConsistentHashingLoadBalancer::AddServersInBatch(servers); |
| 483 | + if (n != 0) { |
| 484 | + SyncLoadMap(); |
| 485 | + } |
| 486 | + return n; |
| 487 | +} |
| 488 | + |
| 489 | +size_t ConsistentHashingBoundedLoadBalancer::RemoveServersInBatch( |
| 490 | + const std::vector<ServerId>& servers) { |
| 491 | + const size_t n = ConsistentHashingLoadBalancer::RemoveServersInBatch(servers); |
| 492 | + if (n != 0) { |
| 493 | + SyncLoadMap(); |
| 494 | + } |
| 495 | + return n; |
| 496 | +} |
| 497 | + |
| 498 | +LoadBalancer* ConsistentHashingBoundedLoadBalancer::New( |
| 499 | + const butil::StringPiece& params) const { |
| 500 | + ConsistentHashingBoundedLoadBalancer* lb = |
| 501 | + new (std::nothrow) ConsistentHashingBoundedLoadBalancer(_type); |
| 502 | + if (lb && !lb->SetParameters(params)) { |
| 503 | + delete lb; |
| 504 | + lb = nullptr; |
| 505 | + } |
| 506 | + return lb; |
| 507 | +} |
| 508 | + |
| 509 | +bool ConsistentHashingBoundedLoadBalancer::SetParameter( |
| 510 | + const butil::StringPiece& key, const butil::StringPiece& value) { |
| 511 | + if (key == "load_factor") { |
| 512 | + double factor = 0.0; |
| 513 | + if (!butil::StringToDouble(value.as_string(), &factor) || |
| 514 | + factor <= 1.0) { |
| 515 | + LOG(ERROR) << "Invalid load_factor=`" << value |
| 516 | + << "', must be a number > 1"; |
| 517 | + return false; |
| 518 | + } |
| 519 | + _load_factor = factor; |
| 520 | + return true; |
| 521 | + } |
| 522 | + return ConsistentHashingLoadBalancer::SetParameter(key, value); |
| 523 | +} |
| 524 | + |
| 525 | +int ConsistentHashingBoundedLoadBalancer::SelectServer( |
| 526 | + const SelectIn& in, SelectOut* out) { |
| 527 | + if (!in.has_request_code) { |
| 528 | + LOG(ERROR) << "Controller.set_request_code() is required"; |
| 529 | + return EINVAL; |
| 530 | + } |
| 531 | + if (in.request_code > UINT_MAX) { |
| 532 | + LOG(ERROR) << "request_code must be 32-bit currently"; |
| 533 | + return EINVAL; |
| 534 | + } |
| 535 | + butil::DoublyBufferedData<std::vector<Node> >::ScopedPtr s; |
| 536 | + if (_db_hash_ring.Read(&s) != 0) { |
| 537 | + return ENOMEM; |
| 538 | + } |
| 539 | + if (s->empty()) { |
| 540 | + return ENODATA; |
| 541 | + } |
| 542 | + butil::DoublyBufferedData<LoadMap>::ScopedPtr lm; |
| 543 | + if (_db_load_map.Read(&lm) != 0) { |
| 544 | + return ENOMEM; |
| 545 | + } |
| 546 | + int64_t capacity = std::numeric_limits<int64_t>::max(); |
| 547 | + if (!lm->empty()) { |
| 548 | + const int64_t total = _total_inflight.load(butil::memory_order_relaxed); |
| 549 | + capacity = (int64_t)std::ceil( |
| 550 | + _load_factor * (double)(total + 1) / (double)lm->size()); |
| 551 | + } |
| 552 | + std::vector<Node>::const_iterator choice = |
| 553 | + std::lower_bound(s->begin(), s->end(), (uint32_t)in.request_code); |
| 554 | + if (choice == s->end()) { |
| 555 | + choice = s->begin(); |
| 556 | + } |
| 557 | + // Walk clockwise from the hashed-to node and take the first server under |
| 558 | + // capacity. With load_factor > 1 at least one server is below the average |
| 559 | + // whenever counters are consistent, so the walk finds one; the first |
| 560 | + // acceptable server is kept as a fallback to guard against transient |
| 561 | + // inconsistency of the relaxed counters. |
| 562 | + SocketUniquePtr fallback_ptr; |
| 563 | + ServerLoad* fallback_load = nullptr; |
| 564 | + ServerLoad* selected_load = nullptr; |
| 565 | + for (size_t i = 0; i < s->size(); ++i) { |
| 566 | + SocketUniquePtr ptr; |
| 567 | + if (((i + 1) == s->size() // always take last chance |
| 568 | + || !ExcludedServers::IsExcluded(in.excluded, choice->server_sock.id)) |
| 569 | + && IsServerAvailable(choice->server_sock.id, &ptr)) { |
| 570 | + const std::shared_ptr<ServerLoad>* pload = |
| 571 | + lm->seek(choice->server_sock.id); |
| 572 | + ServerLoad* load = (pload != nullptr) ? pload->get() : nullptr; |
| 573 | + const int32_t inflight = (load != nullptr) |
| 574 | + ? load->inflight.load(butil::memory_order_relaxed) : 0; |
| 575 | + if (inflight < capacity) { |
| 576 | + selected_load = load; |
| 577 | + out->ptr->swap(ptr); |
| 578 | + break; |
| 579 | + } |
| 580 | + if (fallback_ptr.get() == nullptr) { |
| 581 | + fallback_load = load; |
| 582 | + fallback_ptr.swap(ptr); |
| 583 | + } |
| 584 | + } |
| 585 | + if (++choice == s->end()) { |
| 586 | + choice = s->begin(); |
| 587 | + } |
| 588 | + } |
| 589 | + if (out->ptr->get() == nullptr) { |
| 590 | + if (fallback_ptr.get() == nullptr) { |
| 591 | + return EHOSTDOWN; |
| 592 | + } |
| 593 | + selected_load = fallback_load; |
| 594 | + out->ptr->swap(fallback_ptr); |
| 595 | + } |
| 596 | + if (in.changable_weights && selected_load != nullptr) { |
| 597 | + selected_load->inflight.fetch_add(1, butil::memory_order_relaxed); |
| 598 | + _total_inflight.fetch_add(1, butil::memory_order_relaxed); |
| 599 | + out->need_feedback = true; |
| 600 | + } |
| 601 | + return 0; |
| 602 | +} |
| 603 | + |
| 604 | +void ConsistentHashingBoundedLoadBalancer::Feedback(const CallInfo& info) { |
| 605 | + _total_inflight.fetch_sub(1, butil::memory_order_relaxed); |
| 606 | + butil::DoublyBufferedData<LoadMap>::ScopedPtr lm; |
| 607 | + if (_db_load_map.Read(&lm) != 0) { |
| 608 | + return; |
| 609 | + } |
| 610 | + const std::shared_ptr<ServerLoad>* pload = lm->seek(info.server_id); |
| 611 | + if (pload != nullptr) { |
| 612 | + // If the server was removed after selection, its counter is already |
| 613 | + // gone and only the total needs restoring. |
| 614 | + (*pload)->inflight.fetch_sub(1, butil::memory_order_relaxed); |
| 615 | + } |
| 616 | +} |
| 617 | + |
| 618 | +void ConsistentHashingBoundedLoadBalancer::Describe( |
| 619 | + std::ostream& os, const DescribeOptions& options) { |
| 620 | + if (!options.verbose) { |
| 621 | + os << "c_hash_bl"; |
| 622 | + return; |
| 623 | + } |
| 624 | + os << "BoundedLoad{load_factor=" << _load_factor << " total_inflight=" |
| 625 | + << _total_inflight.load(butil::memory_order_relaxed) << "}\n"; |
| 626 | + ConsistentHashingLoadBalancer::Describe(os, options); |
| 627 | +} |
| 628 | + |
409 | 629 | } // namespace policy |
410 | 630 | } // namespace brpc |
0 commit comments