ππ» λ‘λ λ°Έλ°μλ μ ν리μΌμ΄μ
(μΉ μλΉμ€ λ±)μ μμ²μ μ¬λ¬ μλ²λ‘ λΆμ°(λ‘λ λ°Έλ°μ±)νμ¬, νΉμ μλ²μ λΆνκ° μ§μ€λμ§ μλλ‘ νκ³ κ°μ©μ±κ³Ό μμ μ±μ λμ΄λ μ₯μΉ λλ μννΈμ¨μ΄μ
λλ€.
A load balancer is a device or software that distributes requests from applications (such as web services) across multiple servers (load balancing) to prevent the load from being concentrated on a specific server and to increase availability and stability.
ππ» λ‘λλ°Έλ°μ±μ λ°©λ²μ μ¬λ¬κ°μ§κ° μμ§λ§ μ¬κΈ°μλ μμ²μ μμ°¨μ μΌλ‘ μ¬λ¬ μλ²μ λΆλ°°νλ λΌμ΄λ λ‘λΉλ°©μμ ꡬνν©λλ€.
There are various load balancing methods, but here we implement the Round Robin method, which distributes requests sequentially to multiple servers.
ππ» μ¦ λ‘λλ°Έλ°μμ μ¬λ¬κ°μ μΉμλ²μ λμΌν μΉμ¬μ΄νΈλ₯Ό λ§λ€μ΄μ μ€νν΄λκ³ ν΄λΌμ΄μΈνΈκ° μ μνλ©΄ λ‘λλ°Έλ°μκ° μ΄λ€ μΉμλ²μ μ μν μ§ κ²°μ ν΄μ μ μνλλ‘ λμ μ€λλ€.
In other words, the same website is created and run on multiple web servers along with a load balancer, and when a client connects, the load balancer determines which web server to connect to and assists in the connection.
ππ» μλλ μ μνμΈμ μν΄μ μΉμλ² νλ©΄μ κ° κ΅¬λΆ λ©μΈμ§κ° μΆκ°λμ΄ μμ΅λλ€.
Below, each distinction message has been added to the web server screen to verify the connection.
ππ» ν°λ―Έλμ μ΄ 4κ°μ μλ²λ₯Ό μ€νν©λλ€.
Run a total of 4 servers in the terminal.
βοΈ λ‘λλ°Έλ°μ 1κ°,μΉμλ²μ© 3κ°
1 load balancer, 3 for web servers
ππ»httplib.h(0.42.0) λ€μ΄λ‘λ / download
curl -L https://raw.githubusercontent.com/yhirose/cpp-httplib/master/httplib.h -o httplib.h
ππ» λ‘λλ°Έλ°μ / Load Balancer
βοΈ μ½λ / code
#include "httplib.h"
#include <atomic>
#include <iostream>
#include <string>
#include <vector>
struct Backend {
std::string host;
int port;
};
static void copy_response(const httplib::Result& r, httplib::Response& res) {
if (!r) {
res.status = 502;
res.set_content("Bad Gateway", "text/plain; charset=utf-8");
return;
}
res.status = r->status;
for (const auto& h : r->headers) {
if (h.first == "Connection" || h.first == "Keep-Alive" ||
h.first == "Proxy-Authenticate" || h.first == "Proxy-Authorization" ||
h.first == "TE" || h.first == "Trailers" ||
h.first == "Transfer-Encoding" || h.first == "Upgrade") {
continue;
}
res.set_header(h.first.c_str(), h.second.c_str());
}
res.body = r->body;
}
int main() {
httplib::Server lb;
std::vector<Backend> backends = {
{"127.0.0.1", 3001},
{"127.0.0.1", 3002},
{"127.0.0.1", 3003}
};
std::atomic<size_t> rr{0};
auto next_backend = [&]() -> Backend {
size_t idx = rr.fetch_add(1, std::memory_order_relaxed) % backends.size();
return backends[idx];
};
auto proxy = [&](const httplib::Request& req, httplib::Response& res) {
Backend b = next_backend();
httplib::Client cli(b.host, b.port);
httplib::Headers headers = req.headers;
headers.erase("Host");
std::cout << "[LB] " << req.method << " " << req.path
<< " -> " << b.host << ":" << b.port << "\n";
if (req.method == "GET") {
copy_response(cli.Get(req.path.c_str(), headers), res);
} else if (req.method == "POST") {
copy_response(cli.Post(req.path.c_str(), headers, req.body,
req.get_header_value("Content-Type").c_str()), res);
} else if (req.method == "PUT") {
copy_response(cli.Put(req.path.c_str(), headers, req.body,
req.get_header_value("Content-Type").c_str()), res);
} else if (req.method == "PATCH") {
copy_response(cli.Patch(req.path.c_str(), headers, req.body,
req.get_header_value("Content-Type").c_str()), res);
} else if (req.method == "DELETE") {
copy_response(cli.Delete(req.path.c_str(), headers, req.body,
req.get_header_value("Content-Type").c_str()), res);
} else {
res.status = 405;
res.set_content("Method Not Allowed", "text/plain; charset=utf-8");
}
};
lb.Get(R"(.*)", proxy);
lb.Post(R"(.*)", proxy);
lb.Put(R"(.*)", proxy);
lb.Patch(R"(.*)", proxy);
lb.Delete(R"(.*)", proxy);
std::cout << "Round-robin LB listening on http://0.0.0.0:5080\n";
lb.listen("0.0.0.0", 5080);
}
βοΈ λ‘λλ°Έλ°μ μ»΄νμΌ / Load Balancer Compiling
g++ lb_server.cpp -o lb_server -std=c++17 -pthread
βοΈ μ€ν / Run
./lb_server

ππ» νκ² μΉμλ² / Target Webserver
βοΈ μ½λ / Code
#include "httplib.h"
#include <atomic>
#include <iostream>
#include <string>
int main(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << "usage: ./target_server <server_id> <port>\n";
return 1;
}
const std::string server_id = argv[1];
const int port = std::stoi(argv[2]);
httplib::Server svr;
std::atomic<unsigned long long> hit{0};
svr.Get("/", [&](const httplib::Request&, httplib::Response& res) {
auto n = ++hit;
std::string html =
"<!doctype html><html><head><meta charset='utf-8'><title>Target</title></head><body>"
"<h1>Target Server: " + server_id + "</h1>"
"<p>Port: " + std::to_string(port) + "</p>"
"<p>Hit Count: " + std::to_string(n) + "</p>"
"<p>λΈλΌμ°μ μμ μλ‘κ³ μΉ¨νλ©΄ LBκ° λ€λ₯Έ μλ²λ‘ λ³΄λΌ μ μμ΅λλ€.</p>"
"<p>If you refresh the browser, the LB may send it to a different server.</p>"
"</body></html>";
res.set_content(html, "text/html; charset=utf-8");
});
std::cout << "target " << server_id << " listening on http://0.0.0.0:" << port << "\n";
svr.listen("0.0.0.0", port);
}
βοΈ μΉμλ² μ»΄νμΌ / Webserver Compiling
g++ target_server.cpp -o target_server -std=c++17 -pthread
βοΈ μ€ν / Run
— ν°λ―Έλμ 3κ° μ€νν΄μ κ° μλ²λ₯Ό μ€νν©λλ€.
Run three terminals and run each server.
./target_server A 3001
./target_server B 3002
./target_server C 3003

ππ» λΈλΌμ°μ μ μ / Browser access
βοΈhttp://localhost:5080μΌλ‘μ μν©λλ€.
Connect to http://localhost:5080.
βοΈ λΈλΌμ°μ λ₯Ό μλ‘κ³ μΉ¨νλ©΄ μλμ²λΌ λ€λ₯Έ μλ²λ‘ μ μλλκ²μ νμΈ ν μ μμ΅λλ€.
If you refresh the browser, you can confirm that it connects to a different server as shown below.
βοΈ hit countλ κ·Έ νκ² μλ² νλ‘μΈμ€ νλ κΈ°μ€ μ μ νμμ
λλ€.
The hit count is the number of connections per target server process.


