๐๐ป ๋ก๋ ๋ฐธ๋ฐ์๋ ์ ํ๋ฆฌ์ผ์ด์
(์น ์๋น์ค ๋ฑ)์ ์์ฒญ์ ์ฌ๋ฌ ์๋ฒ๋ก ๋ถ์ฐ(๋ก๋ ๋ฐธ๋ฐ์ฑ)ํ์ฌ, ํน์ ์๋ฒ์ ๋ถํ๊ฐ ์ง์ค๋์ง ์๋๋ก ํ๊ณ ๊ฐ์ฉ์ฑ๊ณผ ์์ ์ฑ์ ๋์ด๋ ์ฅ์น ๋๋ ์ํํธ์จ์ด์
๋๋ค.
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.


