728x90
유저의 DB 처리 순서 보장을 위한 클래스. (Poco 라이브러리를 사용) 앞에 Game이 붙은 클래스는 실 사용하는 예제를 보이기 위한 클래스
싱글 스레드 서버라고 해도 DB 처리를 위한 스레드는 멀티스레드로 준비하는 경우가 대부분이기 때문에 유저의 DB 작업 순서 보장은 매우 중요하다. 그 중 한가지 방법으로 LoadBalancer를 제작해보았다.
먼저 필요한 클래스는 3가지 이다.
1. DBService
DB스레드 1개당 DBService클래스 객체가 생성되고 DB연결 및 DB작업예약과 DBHandler를 연결 시켜주는 부분을 담당한다.
struct DBQueueData
{
uint16 ProtocolId = 0;
std::shared_ptr<DBData> data = nullptr;
DBHandler& handler;
};
class DBService
{
public:
/// DB 연결 직접호출 하지 않고 로드밸런서를 통해 호출
bool Connect(const std::string& connectionString);
/// DB 작업 Push 직접호출 하지 않고 로드밸런서를 통해 호출
bool Push(const uint16& protocolId, std::shared_ptr<DBData> data, DBHandler& handler);
/// 해당 스레드에 배정된 DB 작업을 처리
void Execute();
Poco::Data::Session* GetDBSession() { return _session; }
private:
std::string _connectionString = "";
Poco::Data::Session* _session = nullptr;
std::atomic_int32_t _queueCount = 0;
// Lock을 사용한 큐 멀티스레드 환경 고려
LockQueue<std::shared_ptr<DBQueueData>> _dbQueue; // DB 작업 큐
};
2. DBLoadBalancer
DBService를 관리하는 클래스로 일련의 규칙에 따라 DBService로 DB작업을 전달한다. 기본적으로 상속 받아 사용해야 하며 DB 스키마 1개당 LoadBalancer 1개이다.
constexpr uint64 DBRECONNECTTIME = UINT64_C(5000); // DB 재연결 시간
class DBLoadBalancer
{
public:
/// DB 연결
/// <param name="serviceCount">DB 스레드 개수</param>
bool Init(const std::string& connectionString, const int32& serviceCount);
/// 로드밸런싱할 DB 작업 Push
/// <param name="workId">밸런싱할 ID</param>
/// <param name="protocolId">프로토콜</param>
/// <param name="data">DB스레드에 넘길 데이터, DBData를 상속 받은 데이터를 사용</param>
/// <param name="handler">작업을 수행할 핸들러</param>
bool Push(const int32& workId, const uint16& protocolId, std::shared_ptr<DBData> data, DBHandler& handler);
bool Push(std::shared_ptr<DBData> data, DBHandler& handler);
void Launch();
private:
std::string _connectionString = "";
DBService* _serviceList = nullptr; // 배열로 사용
int32 _serviceCount = 0; // DB 스레드 개수
};
3. DBHandler
DBLoadBalancer를 통해 DBService로 전달된 DB작업을 수행하기위한 핸들러로 DBLoadBalancer와 같이 상속받아 사용하고, 스키마1개당 1개로 즉, DB스키마 1개 당 LoadBalancer와 Handler가 하나씩 생성된다.
여기서 DBData 클래스는 DB작업을 위한 데이터로 해당 클래스를 상속받게하여 밸런싱에 필요한 정보를 필수로 갖고 있게 하였다.
로드밸런서라고 해서 스레드마다 작업량에 따라 작업을 분배하는 기능을 갖는건 아니지만 WorkID가 같으면 항상 같은 스레드를 타게하여 순서보장을 할 수 있게 된다.
class DBData
{
public:
DBData(uint16 protocolId, int32 workId) : ProtocolID(protocolId), WorkID(workId) {}
int32 WorkID = 0; // 밸런싱 할 ID
uint16 ProtocolID = 0; // 서버 내 프로토콜
};
class DBService;
using DBHandlerFunc = std::function<bool(std::shared_ptr<DBData>, DBService*)>;
bool Handle_INVALID(std::shared_ptr<DBData> data, DBService* service);
class DBHandler
{
public:
virtual void Init();
/// DB 작업 수행
/// <param name="protocolId">서버 내 DB 프로토콜 ID</param>
/// <param name="data">DB call에 필요한 데이터</param>
/// <param name="service">작업을 수행할 DB 스레드</param>
bool HandleData(uint16 protocolId, std::shared_ptr<DBData> data, DBService* service);
/// 핸들러 등록
/// <param name="protocol">서버 내 프로토콜</param>
/// <param name="fn">프토토콜 ID에 해당하는 핸들러 함수 포인터</param>
bool RegisterHandler(const uint16& protocol, DBHandlerFunc fn);
template<class HandlerType, typename = typename std::enable_if<std::is_base_of<DBHandler, HandlerType>::value>::type>
bool RegisterHandler(const uint16& protocol, bool (HandlerType::* handler)(std::shared_ptr<DBData>, DBService*))
{
return RegisterHandler(protocol, [=](std::shared_ptr<DBData> data, DBService* service)
{
return (static_cast<HandlerType*>(this)->*handler)(data, service);
});
}
private:
DBHandlerFunc _dbHandler[UINT16_MAX]; // 핸들러 리스트
std::unordered_set<uint16> _useProtocol; // 중복 체크용
};
728x90
'C++ > 서버코어 라이브러리' 카테고리의 다른 글
RIONotify (0) | 2025.03.14 |
---|---|
RIO (0) | 2025.03.12 |