feat: mysql connection pool..
in progress
This commit is contained in:
parent
31b4763bc3
commit
5011fb6fb4
3 changed files with 151 additions and 92 deletions
|
|
@ -41,7 +41,7 @@ cmake --build build
|
||||||
- Session: 개별 클라이언트 비동기 수신/송신 처리
|
- Session: 개별 클라이언트 비동기 수신/송신 처리
|
||||||
- PacketHandler: 패킷 ID별 로직 디스패칭
|
- PacketHandler: 패킷 ID별 로직 디스패칭
|
||||||
- SessionManager: 글로벌 세션 관리 및 브로드캐스팅
|
- SessionManager: 글로벌 세션 관리 및 브로드캐스팅
|
||||||
- DatabaseManager: 비동기 데이터베이스 작업 및 락 제어
|
- DatabaseManager: 커넥션 풀을 통한 동기/비동기 데이터베이스 작업 관리
|
||||||
- Packet: Protobuf 기반 직렬화 프로토콜 (proto/Protocol.proto)
|
- Packet: Protobuf 기반 직렬화 프로토콜 (proto/Protocol.proto)
|
||||||
|
|
||||||
## Python GUI 클라이언트
|
## Python GUI 클라이언트
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,13 @@
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <boost/asio/awaitable.hpp>
|
#include <boost/asio/awaitable.hpp>
|
||||||
|
#include <boost/asio/ssl.hpp>
|
||||||
#include <boost/mysql.hpp>
|
#include <boost/mysql.hpp>
|
||||||
|
#include <deque>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class DatabaseManager {
|
class DatabaseManager {
|
||||||
public:
|
public:
|
||||||
|
|
@ -33,24 +36,60 @@ public:
|
||||||
uint64_t myValue;
|
uint64_t myValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ConnectionProxy {
|
||||||
|
public:
|
||||||
|
ConnectionProxy(std::unique_ptr<boost::mysql::tcp_ssl_connection> conn,
|
||||||
|
DatabaseManager &owner)
|
||||||
|
: conn_(std::move(conn)), owner_(owner) {}
|
||||||
|
~ConnectionProxy();
|
||||||
|
|
||||||
|
// Move only
|
||||||
|
ConnectionProxy(ConnectionProxy &&other) noexcept
|
||||||
|
: conn_(std::move(other.conn_)), owner_(other.owner_) {}
|
||||||
|
ConnectionProxy &operator=(ConnectionProxy &&other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
conn_ = std::move(other.conn_);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ConnectionProxy(const ConnectionProxy &) = delete;
|
||||||
|
ConnectionProxy &operator=(const ConnectionProxy &) = delete;
|
||||||
|
|
||||||
|
boost::mysql::tcp_ssl_connection *operator->() { return conn_.get(); }
|
||||||
|
boost::mysql::tcp_ssl_connection &operator*() { return *conn_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<boost::mysql::tcp_ssl_connection> conn_;
|
||||||
|
DatabaseManager &owner_;
|
||||||
|
};
|
||||||
|
|
||||||
boost::asio::awaitable<UserData> LoadUser(std::string nickname);
|
boost::asio::awaitable<UserData> LoadUser(std::string nickname);
|
||||||
boost::asio::awaitable<void> SaveUser(std::string nickname, uint64_t gold,
|
boost::asio::awaitable<void> SaveUser(std::string nickname, uint64_t gold,
|
||||||
uint32_t swordLevel);
|
uint32_t swordLevel);
|
||||||
boost::asio::awaitable<RankingResult> GetRanking(int type,
|
boost::asio::awaitable<RankingResult> GetRanking(int type,
|
||||||
std::string nickname);
|
std::string nickname);
|
||||||
|
|
||||||
|
boost::asio::awaitable<ConnectionProxy> GetConnection();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DatabaseManager() = default;
|
DatabaseManager() = default;
|
||||||
|
|
||||||
// 비동기 Mutex 역할을 할 함수들
|
void ReturnConnection(std::unique_ptr<boost::mysql::tcp_ssl_connection> conn);
|
||||||
boost::asio::awaitable<void> Lock();
|
boost::asio::awaitable<std::unique_ptr<boost::mysql::tcp_ssl_connection>>
|
||||||
void Unlock();
|
CreateNewConnection();
|
||||||
|
|
||||||
std::unique_ptr<boost::mysql::tcp_ssl_connection> conn_;
|
std::deque<std::unique_ptr<boost::mysql::tcp_ssl_connection>> pool_;
|
||||||
|
std::queue<std::shared_ptr<boost::asio::steady_timer>> waiting_queue_;
|
||||||
|
|
||||||
|
boost::asio::io_context *io_context_ = nullptr;
|
||||||
std::unique_ptr<boost::asio::strand<boost::asio::io_context::executor_type>>
|
std::unique_ptr<boost::asio::strand<boost::asio::io_context::executor_type>>
|
||||||
strand_;
|
strand_;
|
||||||
|
|
||||||
// 비동기 락 시스템을 위한 상태 변수
|
boost::asio::ssl::context ssl_ctx_{boost::asio::ssl::context::tlsv12_client};
|
||||||
bool is_locked_ = false;
|
|
||||||
std::queue<std::shared_ptr<boost::asio::steady_timer>> waiting_queue_;
|
std::string host_, user_, password_, db_;
|
||||||
|
uint16_t port_;
|
||||||
|
|
||||||
|
uint32_t max_connections_ = 10;
|
||||||
|
uint32_t current_total_connections_ = 0;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,101 +9,138 @@ DatabaseManager &DatabaseManager::GetInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DatabaseManager::ConnectionProxy::~ConnectionProxy() {
|
||||||
|
if (conn_) {
|
||||||
|
owner_.ReturnConnection(std::move(conn_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool DatabaseManager::Init(boost::asio::io_context &io_context,
|
bool DatabaseManager::Init(boost::asio::io_context &io_context,
|
||||||
const std::string &host, uint16_t port,
|
const std::string &host, uint16_t port,
|
||||||
const std::string &user, const std::string &password,
|
const std::string &user, const std::string &password,
|
||||||
const std::string &db) {
|
const std::string &db) {
|
||||||
try {
|
try {
|
||||||
|
io_context_ = &io_context;
|
||||||
strand_ = std::make_unique<
|
strand_ = std::make_unique<
|
||||||
boost::asio::strand<boost::asio::io_context::executor_type>>(
|
boost::asio::strand<boost::asio::io_context::executor_type>>(
|
||||||
io_context.get_executor());
|
io_context.get_executor());
|
||||||
|
|
||||||
boost::asio::ip::tcp::resolver resolver(io_context.get_executor());
|
host_ = host;
|
||||||
auto endpoints = resolver.resolve(host, std::to_string(port));
|
port_ = port;
|
||||||
|
user_ = user;
|
||||||
|
password_ = password;
|
||||||
|
db_ = db;
|
||||||
|
|
||||||
boost::mysql::handshake_params params(user, password, db);
|
current_total_connections_ = 0;
|
||||||
static boost::asio::ssl::context ssl_ctx(
|
pool_.clear();
|
||||||
boost::asio::ssl::context::tlsv12_client);
|
|
||||||
|
|
||||||
conn_ = std::make_unique<boost::mysql::tcp_ssl_connection>(
|
Logger::Log("Database Dynamic Pool 초기화 완료 (Max: ", max_connections_,
|
||||||
io_context.get_executor(), ssl_ctx);
|
"): ", host, ":", port);
|
||||||
conn_->connect(*endpoints.begin(), params);
|
|
||||||
|
|
||||||
Logger::Log("Database 연결 성공 (AsyncLock 적용): ", host, ":", port);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
Logger::Log("Database 연결 실패: ", e.what());
|
Logger::Log("Database Pool 초기화 실패: ", e.what());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 비동기 락 획득 (이미 락이 걸려있으면 대기열에 들어감)
|
boost::asio::awaitable<std::unique_ptr<boost::mysql::tcp_ssl_connection>>
|
||||||
boost::asio::awaitable<void> DatabaseManager::Lock() {
|
DatabaseManager::CreateNewConnection() {
|
||||||
auto executor = co_await boost::asio::this_coro::executor;
|
auto conn = std::make_unique<boost::mysql::tcp_ssl_connection>(
|
||||||
|
io_context_->get_executor(), ssl_ctx_);
|
||||||
|
|
||||||
co_await boost::asio::post(
|
boost::asio::ip::tcp::resolver resolver(io_context_->get_executor());
|
||||||
boost::asio::bind_executor(*strand_, boost::asio::use_awaitable));
|
auto endpoints = co_await resolver.async_resolve(host_, std::to_string(port_),
|
||||||
|
boost::asio::use_awaitable);
|
||||||
|
|
||||||
if (!is_locked_) {
|
boost::mysql::handshake_params params(user_, password_, db_);
|
||||||
is_locked_ = true;
|
co_await conn->async_connect(*endpoints.begin(), params,
|
||||||
co_return;
|
boost::asio::use_awaitable);
|
||||||
}
|
|
||||||
|
|
||||||
// 이미 잠겨있으면 타이머를 만들어서 대기열에 추가 (비동기 대기)
|
co_return conn;
|
||||||
auto timer = std::make_shared<boost::asio::steady_timer>(
|
|
||||||
executor, std::chrono::steady_clock::time_point::max());
|
|
||||||
waiting_queue_.push(timer);
|
|
||||||
|
|
||||||
try {
|
|
||||||
co_await timer->async_wait(boost::asio::use_awaitable);
|
|
||||||
} catch (...) {
|
|
||||||
// 타이머 취소(Unlock에서 호출함)되면 성공적으로 락을 획득한 것으로 간주
|
|
||||||
}
|
|
||||||
co_return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 비동기 락 해제 (대기열에 다음 사람이 있으면 깨워줌)
|
boost::asio::awaitable<DatabaseManager::ConnectionProxy>
|
||||||
void DatabaseManager::Unlock() {
|
DatabaseManager::GetConnection() {
|
||||||
boost::asio::dispatch(*strand_, [this]() {
|
while (true) {
|
||||||
if (waiting_queue_.empty()) {
|
co_await boost::asio::post(
|
||||||
is_locked_ = false;
|
boost::asio::bind_executor(*strand_, boost::asio::use_awaitable));
|
||||||
} else {
|
|
||||||
auto next = waiting_queue_.front();
|
if (!pool_.empty()) {
|
||||||
|
auto conn = std::move(pool_.front());
|
||||||
|
pool_.pop_front();
|
||||||
|
co_return ConnectionProxy(std::move(conn), *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_total_connections_ < max_connections_) {
|
||||||
|
current_total_connections_++;
|
||||||
|
try {
|
||||||
|
Logger::Log("새로운 DB 커넥션 생성 중... (현재: ",
|
||||||
|
current_total_connections_, ")");
|
||||||
|
auto conn = co_await CreateNewConnection();
|
||||||
|
Logger::Log("새로운 DB 커넥션 생성 완료");
|
||||||
|
co_return ConnectionProxy(std::move(conn), *this);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
Logger::Log("DB 커넥션 생성 실패: ", e.what());
|
||||||
|
current_total_connections_--;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto executor = co_await boost::asio::this_coro::executor;
|
||||||
|
auto timer = std::make_shared<boost::asio::steady_timer>(
|
||||||
|
executor, std::chrono::steady_clock::time_point::max());
|
||||||
|
waiting_queue_.push(timer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
co_await timer->async_wait(boost::asio::use_awaitable);
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseManager::ReturnConnection(
|
||||||
|
std::unique_ptr<boost::mysql::tcp_ssl_connection> conn) {
|
||||||
|
if (!conn) {
|
||||||
|
boost::asio::dispatch(*strand_, [this]() {
|
||||||
|
if (current_total_connections_ > 0) {
|
||||||
|
current_total_connections_--;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::asio::dispatch(*strand_, [this, conn = std::move(conn)]() mutable {
|
||||||
|
pool_.push_back(std::move(conn));
|
||||||
|
if (!waiting_queue_.empty()) {
|
||||||
|
auto timer = waiting_queue_.front();
|
||||||
waiting_queue_.pop();
|
waiting_queue_.pop();
|
||||||
next->cancel();
|
timer->cancel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::asio::awaitable<DatabaseManager::UserData>
|
boost::asio::awaitable<DatabaseManager::UserData>
|
||||||
DatabaseManager::LoadUser(std::string nickname) {
|
DatabaseManager::LoadUser(std::string nickname) {
|
||||||
co_await Lock();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!conn_) {
|
auto conn = co_await GetConnection();
|
||||||
Unlock();
|
|
||||||
co_return UserData{nickname, 10000, 0};
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::mysql::results result;
|
boost::mysql::results result;
|
||||||
boost::mysql::statement stmt = co_await conn_->async_prepare_statement(
|
boost::mysql::statement stmt = co_await conn->async_prepare_statement(
|
||||||
"SELECT username, gold, sword_level, max_sword_level FROM users WHERE "
|
"SELECT username, gold, sword_level, max_sword_level FROM users WHERE "
|
||||||
"username = ?",
|
"username = ?",
|
||||||
boost::asio::use_awaitable);
|
boost::asio::use_awaitable);
|
||||||
co_await conn_->async_execute(stmt.bind(nickname), result,
|
co_await conn->async_execute(stmt.bind(nickname), result,
|
||||||
boost::asio::use_awaitable);
|
boost::asio::use_awaitable);
|
||||||
|
|
||||||
if (result.rows().empty()) {
|
if (result.rows().empty()) {
|
||||||
boost::mysql::statement ins_stmt =
|
boost::mysql::statement ins_stmt = co_await conn->async_prepare_statement(
|
||||||
co_await conn_->async_prepare_statement(
|
"INSERT INTO users (username, gold, sword_level) VALUES (?, "
|
||||||
"INSERT INTO users (username, gold, sword_level) VALUES (?, "
|
"10000, 0)",
|
||||||
"10000, 0)",
|
boost::asio::use_awaitable);
|
||||||
boost::asio::use_awaitable);
|
co_await conn->async_execute(ins_stmt.bind(nickname), result,
|
||||||
co_await conn_->async_execute(ins_stmt.bind(nickname), result,
|
boost::asio::use_awaitable);
|
||||||
boost::asio::use_awaitable);
|
|
||||||
Logger::Log("새로운 유저 탄생 (DB): ", nickname);
|
Logger::Log("새로운 유저 탄생 (DB): ", nickname);
|
||||||
Unlock();
|
co_return UserData{nickname, 10000, 0, 0};
|
||||||
co_return UserData{nickname, 10000, 0};
|
|
||||||
} else {
|
} else {
|
||||||
auto row = result.rows().at(0);
|
auto row = result.rows().at(0);
|
||||||
UserData data;
|
UserData data;
|
||||||
|
|
@ -111,56 +148,42 @@ DatabaseManager::LoadUser(std::string nickname) {
|
||||||
data.gold = row.at(1).as_uint64();
|
data.gold = row.at(1).as_uint64();
|
||||||
data.swordLevel = (uint32_t)row.at(2).as_int64();
|
data.swordLevel = (uint32_t)row.at(2).as_int64();
|
||||||
data.maxSwordLevel = (uint32_t)row.at(3).as_int64();
|
data.maxSwordLevel = (uint32_t)row.at(3).as_int64();
|
||||||
Unlock();
|
|
||||||
co_return data;
|
co_return data;
|
||||||
}
|
}
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
Logger::Log("LoadUser 에러: ", e.what());
|
Logger::Log("LoadUser 에러: ", e.what());
|
||||||
Unlock();
|
co_return UserData{nickname, 10000, 0, 0};
|
||||||
co_return UserData{nickname, 10000, 0};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::asio::awaitable<void> DatabaseManager::SaveUser(std::string nickname,
|
boost::asio::awaitable<void> DatabaseManager::SaveUser(std::string nickname,
|
||||||
uint64_t gold,
|
uint64_t gold,
|
||||||
uint32_t swordLevel) {
|
uint32_t swordLevel) {
|
||||||
co_await Lock();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!conn_) {
|
auto conn = co_await GetConnection();
|
||||||
Unlock();
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::mysql::results result;
|
boost::mysql::results result;
|
||||||
boost::mysql::statement stmt = co_await conn_->async_prepare_statement(
|
boost::mysql::statement stmt = co_await conn->async_prepare_statement(
|
||||||
"UPDATE users SET gold = ?, sword_level = ?, max_sword_level = "
|
"UPDATE users SET gold = ?, sword_level = ?, max_sword_level = "
|
||||||
"GREATEST(max_sword_level, ?) WHERE username = ?",
|
"GREATEST(max_sword_level, ?) WHERE username = ?",
|
||||||
boost::asio::use_awaitable);
|
boost::asio::use_awaitable);
|
||||||
co_await conn_->async_execute(
|
co_await conn->async_execute(
|
||||||
stmt.bind(gold, (int64_t)swordLevel, (int64_t)swordLevel, nickname),
|
stmt.bind(gold, (int64_t)swordLevel, (int64_t)swordLevel, nickname),
|
||||||
result, boost::asio::use_awaitable);
|
result, boost::asio::use_awaitable);
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
Logger::Log("SaveUser 에러: ", e.what());
|
Logger::Log("SaveUser 에러: ", e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
Unlock();
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::asio::awaitable<DatabaseManager::RankingResult>
|
boost::asio::awaitable<DatabaseManager::RankingResult>
|
||||||
DatabaseManager::GetRanking(int type, std::string nickname) {
|
DatabaseManager::GetRanking(int type, std::string nickname) {
|
||||||
co_await Lock();
|
|
||||||
|
|
||||||
RankingResult res;
|
RankingResult res;
|
||||||
res.myRank = -1;
|
res.myRank = -1;
|
||||||
res.myValue = 0;
|
res.myValue = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!conn_) {
|
auto conn = co_await GetConnection();
|
||||||
Unlock();
|
|
||||||
co_return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string columnName = (type == 0) ? "max_sword_level" : "gold";
|
std::string columnName = (type == 0) ? "max_sword_level" : "gold";
|
||||||
|
|
||||||
|
|
@ -168,15 +191,14 @@ DatabaseManager::GetRanking(int type, std::string nickname) {
|
||||||
std::string topQuery = "SELECT username, " + columnName +
|
std::string topQuery = "SELECT username, " + columnName +
|
||||||
" FROM users ORDER BY " + columnName +
|
" FROM users ORDER BY " + columnName +
|
||||||
" DESC LIMIT 10";
|
" DESC LIMIT 10";
|
||||||
co_await conn_->async_execute(topQuery, topResult,
|
co_await conn->async_execute(topQuery, topResult,
|
||||||
boost::asio::use_awaitable);
|
boost::asio::use_awaitable);
|
||||||
|
|
||||||
uint32_t rank = 1;
|
uint32_t rank = 1;
|
||||||
for (auto row : topResult.rows()) {
|
for (auto row : topResult.rows()) {
|
||||||
RankingResult::Entry entry;
|
RankingResult::Entry entry;
|
||||||
entry.rank = rank++;
|
entry.rank = rank++;
|
||||||
entry.nickname = row.at(0).as_string();
|
entry.nickname = row.at(0).as_string();
|
||||||
// DB 타입이 달라 분기처리..
|
|
||||||
if (type == 0) {
|
if (type == 0) {
|
||||||
entry.value = (uint64_t)row.at(1).as_int64();
|
entry.value = (uint64_t)row.at(1).as_int64();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -190,10 +212,10 @@ DatabaseManager::GetRanking(int type, std::string nickname) {
|
||||||
"SELECT " + columnName + ", (SELECT COUNT(*) + 1 FROM users WHERE " +
|
"SELECT " + columnName + ", (SELECT COUNT(*) + 1 FROM users WHERE " +
|
||||||
columnName + " > u." + columnName + ") FROM users u WHERE username = ?";
|
columnName + " > u." + columnName + ") FROM users u WHERE username = ?";
|
||||||
|
|
||||||
boost::mysql::statement stmt = co_await conn_->async_prepare_statement(
|
boost::mysql::statement stmt = co_await conn->async_prepare_statement(
|
||||||
myQuery, boost::asio::use_awaitable);
|
myQuery, boost::asio::use_awaitable);
|
||||||
co_await conn_->async_execute(stmt.bind(nickname), myResult,
|
co_await conn->async_execute(stmt.bind(nickname), myResult,
|
||||||
boost::asio::use_awaitable);
|
boost::asio::use_awaitable);
|
||||||
|
|
||||||
if (!myResult.rows().empty()) {
|
if (!myResult.rows().empty()) {
|
||||||
auto row = myResult.rows().at(0);
|
auto row = myResult.rows().at(0);
|
||||||
|
|
@ -207,7 +229,5 @@ DatabaseManager::GetRanking(int type, std::string nickname) {
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
Logger::Log("GetRanking 에러: ", e.what());
|
Logger::Log("GetRanking 에러: ", e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
Unlock();
|
|
||||||
co_return res;
|
co_return res;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue