負載均衡式在線OJ
目錄
負載均衡式在線OJ
1.項目介紹:
2.comm
2.1 log.hpp
日志等級
開放式日志
?時間戳工具
2.2 util.hpp
TimeUtil類
PathUtil類
FileUtil類
StringUtil類
3.Compile_server
3.1compile_run.hpp
RemoveTempFile
?CodeToDesc
?Start
?3.2compile.hpp
Compiler類
?3.3runner.hpp
Runner類
?4.oj_server
4.1 oj_control.hpp
Machine類
IncLoad
DecLoad
ResetLoad
Load
LoadBlance類
LoadConf
SmartChoice
OfflineMachine
OnlineMachine
Control類
RecoveryMachine
AllQuestions
Question
Judge
?4.2 oj_model
Question結構體
Model類
QueryMysql
GetAllQuestions
GetOneQuestion
依賴的外部模塊
?4.3 oj_view
AllExpandHtml
?OneExpandHtml
1.項目介紹:
??????? 該項目完成了類似力扣,牛客網等網站的在線OJ功能, 通過平衡負載函數,將所有用戶發出的請求平均分配給每一臺主機,做到負載均衡實現高并發、高可用性和高性能。
三個板塊:
comm : 時間戳生成、文件路徑處理、文件讀寫操作以及字符串分割功能。compile_server : 編譯與運?模塊oj_server : 獲取題目列表,查看題目編寫題目界面,負載均衡等功能
開發環境 :?C++、Ubuntu、vim、g++、gdb、git、Makefile
所用技術棧 :?HTML、Json、STL標準庫、Boost準標準庫、cpp-httplib、ctemplate、MySQL
項目源碼 :
2.comm
2.1 log.hpp
log.hpp定義了一個日志系統,可以進行日志等級,日志格式化,日志輸出等功能,命名空間為ns_log。
日志等級
enum
{INFO, // 信息級別日志DEBUG, // 調試級別日志WARNING, // 警告級別日志ERROR, // 錯誤級別日志FATAL // 致命錯誤級別日志
};
開放式日志
使用方法 :?? LOG(INFO) << "This is an info message" << "\n";
inline std::ostream &Log(const std::string &level, const std::string &file_name, int line)#define LOG(level) Log(#level, __FILE__, __LINE__)
?時間戳工具
TimeUtil::GetTimeStamp()
2.2 util.hpp
TimeUtil類
此類中共有兩個接口,分別為GetTimeStamp(),用于獲取當前時間的秒級時間戳,目的是為了給文件形成唯一的文件名,另外一個是GetTimeMs()獲取當前時間的毫秒級時間戳。
static std::string GetTimeStamp()static std::string GetTimeMs()
PathUtil類
該類共有7個接口
AddSuffix:將文件名與后綴拼接,生成完整的文件路徑 ;
Src: 構建源文件的完整路徑,返回.cpp后綴的文件;
Exe:構建可執行文件的完整路徑,添加.exe后綴
CompileError:構建編譯錯誤文件的完整路徑,添加.compile_error后綴
Stdin:構建標準輸入文件的完整路徑,添加.stdin后綴
Stdout:構建標準輸出文件的完整路徑,添加.stdout后綴Stderr:構建標準錯誤文件的完整路徑,添加.stderr后綴
static std::string AddSuffix(const std::string &file_name, const std::string &suffix)
static std::string Src(const std::string &file_name)
static std::string Exe(const std::string &file_name)
static std::string CompilerError(const std::string &file_name)
static std::string Stdin(const std::string &file_name)
static std::string Stdout(const std::string &file_name)
static std::string Stderr(const std::string &file_name)
FileUtil類
IsFileExists:檢查文件是否存在,存在返回true,不存在返回false
static bool IsFileExists(const std::string &path_name)
UniqFileName:使用上述GetTimeMs函數獲得唯一文件名
static std::string UniqFileName()
?WriteFile:將內容寫入指定文件,target為目標文件路徑,content為要寫入的內容,寫入成功為true
static bool WriteFile(const std::string &target, const std::string &content)
ReadFile: 讀取文件的內容,target:目標文件路徑,content:用于存儲讀取內容的字符串指針
static bool ReadFile(const std::string &target, std::string *content, bool keep = false)
StringUtil類
SplitString:將字符串按指定分隔符切分,并存儲到target中,str:要切分的字符串,target:存儲切結果的字符串向量,sep:切割符,該方法是基于Boost庫實現的
?
static void SplitString(const std::string &str, std::vector<std::string> *target, const std::string &sep)
3.Compile_server
3.1compile_run.hpp
編譯用戶代碼: 將用戶提交的代碼編譯為可執行文件。
運行用戶程序: 在限制的 CPU 時間和內存內運行用戶程序。
處理運行結果: 根據運行結果生成狀態碼和描述信息。
清理臨時文件: 在運行結束后清理生成的臨時文件
RemoveTempFile
static void RemoveTempFile(const std::string &file_name)
該方法用于清理有指定文件名相關的臨時文件。可用于清理以下文件:
源文件 (file_name.cpp)
編譯錯誤文件 (file_name.compile_error)
可執行文件 (file_name.exe)
標準輸入文件 (file_name.stdin)
標準輸出文件 (file_name.stdout)
標準錯誤文件 (file_name.stderr)
?CodeToDesc
static std::string CodeToDesc(int code, const std::string &file_name)
該方法的作用是將狀態碼轉換為描述信息
code:狀態碼,file_name:文件名? 以下為狀態碼處理:
0: 編譯運行成功。
-1: 提交的代碼為空。
-2: 未知錯誤。
-3: 編譯錯誤(從 file_name.compile_error 文件中讀取錯誤信息)。
SIGABRT (6): 內存超過范圍。
SIGXCPU (24): CPU 使用超時。
SIGFPE (8): 浮點數溢出。
?Start
static void Start(const std::string &in_json, std::string *out_json)
該方法:編譯并運行用戶提交的代碼,返回運行結果
參數:in_json:輸入的 JSON 字符串,包含用戶代碼、輸入、CPU 時間限制和內存限制
out_json:輸出的 JSON 字符串,包含狀態碼、描述信息、標準輸出和標準錯誤
輸入JSON格式:
{"code": "用戶提交的代碼","input": "用戶輸入","cpu_limit": "CPU 時間限制","mem_limit": "內存限制"
}輸出JSON格式:
{"status": "狀態碼","reason": "描述信息","stdout": "標準輸出","stderr": "標準錯誤"
}
解析輸入 JSON,獲取代碼、輸入、CPU 限制和內存限制。
檢查代碼是否為空,如果為空,設置狀態碼為 -1。
生成唯一的文件名,并將代碼寫入臨時源文件。
調用 Compiler::Compile 編譯代碼:
如果編譯失敗,設置狀態碼為 -3。
調用 Runner::Run 運行編譯后的程序:
如果運行失敗,設置狀態碼為運行結果。
根據狀態碼生成描述信息。
如果運行成功,讀取標準輸出和標準錯誤文件的內容。
將結果寫入輸出 JSON。
(可選)清理臨時文件?
所依賴的外部模塊:
Compiler
: 編譯模塊,負責將用戶代碼編譯為可執行文件。
Runner
: 運行模塊,負責運行編譯后的程序,并限制其 CPU 和內存使用。
ns_log
: 日志模塊,用于記錄日志信息。
ns_util
: 工具模塊,提供文件、路徑、時間等工具函數。
JsonCpp
: 用于解析和生成 JSON 數據。
?3.2compile.hpp
Compiler類
Compile
static bool Compile(const std::string &file_name)
?使用 fork 創建子進程。
在子進程中:
打開編譯錯誤文件(file_name.compile_error),用于存儲編譯錯誤信息。
使用 dup2 將標準錯誤輸出重定向到編譯錯誤文件。
使用 execlp 調用 g++ 編譯器,將源文件(file_name.cpp)編譯為可執行文件(file_name.exe)。
如果 execlp 失敗,記錄錯誤日志并退出。
在父進程中:
使用 waitpid 等待子進程結束。
檢查是否生成了可執行文件(file_name.exe)。
如果生成成功,記錄日志并返回 true;否則返回 false。
所依賴的外部模塊:
ns_util
: 工具模塊,提供路徑拼接和文件操作功能。
PathUtil::Src(file_name)
: 獲取源文件路徑(./temp/file_name.cpp
)。
PathUtil::Exe(file_name)
: 獲取可執行文件路徑(./temp/file_name.exe
)。
PathUtil::CompilerError(file_name)
: 獲取編譯錯誤文件路徑(./temp/file_name.compile_error
)。
FileUtil::IsFileExists(path)
: 檢查文件是否存在。
ns_log
: 日志模塊,用于記錄日志信息。
LOG(INFO)
: 記錄信息日志。
LOG(WARNING)
: 記錄警告日志。
LOG(ERROR)
: 記錄錯誤日志
?3.3runner.hpp
Runner類
SetProcLimit接口
static void SetProcLimit(int _cpu_limit, int _mem_limit)
參數:_cpu_limit:CPU時間限制,_mem_limit:內存資源限制,設置進程的 CPU 和內存資源限制。
setrlimit
系統調用設置資源限制。RLIMIT_CPU
: 限制 CPU 時間。RLIMIT_AS
: 限制虛擬內存大小。
Run接口
運行編譯后的程序,并限制其 CPU 和內存資源使用
static int Run(const std::string &file_name, int cpu_limit, int mem_limit)
> 0
: 程序異常退出,返回值為收到的信號編號。
== 0
: 程序正常運行完畢。
< 0
: 內部錯誤(如文件打開失敗或子進程創建失敗)
獲取可執行文件、標準輸入、標準輸出和標準錯誤的路徑。
打開標準輸入、標準輸出和標準錯誤文件。
使用
fork
創建子進程。在子進程中:
使用
dup2
重定向標準輸入、標準輸出和標準錯誤。調用
SetProcLimit
設置資源限制。使用
execl
運行可執行程序。如果
execl
失敗,記錄錯誤日志并退出。在父進程中:
關閉文件描述符。
使用
waitpid
等待子進程結束。獲取子進程的退出狀態,并返回狀態碼的低 7 位(信號編號)。
?依賴的外部模塊
ns_util
: 工具模塊,提供路徑拼接功能。
PathUtil::Exe(file_name)
: 獲取可執行文件路徑(./temp/file_name.exe
)。
PathUtil::Stdin(file_name)
: 獲取標準輸入文件路徑(./temp/file_name.stdin
)。
PathUtil::Stdout(file_name)
: 獲取標準輸出文件路徑(./temp/file_name.stdout
)。
PathUtil::Stderr(file_name)
: 獲取標準錯誤文件路徑(./temp/file_name.stderr
)。
ns_log
: 日志模塊,用于記錄日志信息。
LOG(INFO)
: 記錄信息日志。
LOG(ERROR)
: 記錄錯誤日志。
?4.oj_server
4.1 oj_control.hpp
主要的功能:管理題目數據,渲染網面,負載均衡,調用編譯和運行服務。
核心類
Machine:表示提供編譯和運行服務的主機
LoadBlance:實現負載均衡
Control:核心業務邏輯處理器
?依賴模塊
ns_model
: 題目數據管理。
ns_view
: HTML 渲染。
ns_log
: 日志記錄。
ns_util
: 工具函數。
httplib
: HTTP 客戶端。
Machine類
IncLoad
void IncLoad()//增加主機負載,使用互斥鎖保護負載變量,確保線程安全。
DecLoad
void DecLoad()//減少主機負載,使用互斥鎖保護負載變量,確保線程安全。
ResetLoad
void ResetLoad()//重置主機的負載為0,使用互斥鎖保護負載變量,確保線程安全
Load
uint64_t Load() //獲取主機的當前負載,使用互斥鎖保護負載變量,確保線程安全
LoadBlance類
LoadConf
bool LoadConf(const std::string &machine_conf)
?從配置文件中加載主機信息,參數:machine_conf:配置文件路徑
讀取配置文件,解析每臺主機的 IP 和端口。初始化主機對象,并將其加入在線主機列表。
SmartChoice
bool SmartChoice(int *id, Machine **m)
參數:id 輸出型參數返回選擇的主機ID, m:輸出參數,返回選擇的主機的對象指針。
遍歷在線主機列表,選擇負載最低的主機
OfflineMachine
void OfflineMachine(int which)//將指定主機離線
which為要離線的主機ID
OnlineMachine
void OnlineMachine()//將所有離線主機全部上線
ShowMachine
void ShowMachines()//打印當前在線和離線主機列表(用于調試)。
Control類
核心業務邏輯控制器,負責管理題目數據、渲染網頁、負載均衡以及調用編譯和運行服務。
RecoveryMachine
void RecoveryMachine()//恢復離線主機為在線狀態
AllQuestions
bool AllQuestions(string *html) //獲取所有題目數據并渲染為網頁。
html:輸出參數,返回渲染后的HTML內容
從Model獲取所有題目數據,使用View渲染題目列表為HTML
Question
bool Question(const string &number, string *html)//獲取指定題目數據并渲染為網頁
number: 題目編號。html: 輸出參數,返回渲染后的 HTML 內容。
Judge
void Judge(const std::string &number, const std::string in_json, std::string *out_json)
參數:number:題目編號,in_json:輸入的JSON數據,out_json:輸出的JSON數據,包含評測數據
從 Model 獲取指定題目的詳細信息。
解析輸入 JSON,拼接用戶代碼和測試用例代碼。
使用負載均衡選擇主機,發起 HTTP 請求調用編譯和運行服務。
將評測結果寫入輸出 JSON。
?所依賴的外部模塊
ns_model
: 提供題目數據管理功能。
GetAllQuestions
: 獲取所有題目數據。
GetOneQuestion
: 獲取指定題目數據。
ns_view
: 提供 HTML 渲染功能。
AllExpandHtml
: 渲染題目列表為 HTML。
OneExpandHtml
: 渲染題目詳情為 HTML。
ns_log
: 提供日志記錄功能。
ns_util
: 提供工具函數(如字符串分割)。
httplib
: 提供 HTTP 客戶端功能,用于調用編譯和運行服務。
?4.2 oj_model
Question結構體
變量名 | 類型 | 描述 |
number | std::string | 題目編號,唯一標識 |
title | std::string | 題目標題 |
star | std::string | 題目難度(簡單、中等、困難) |
desc | std::string | 題目描述 |
header | std::string | 題目預設代碼(用戶編輯器的初始代碼) |
tail | std::string | 題目測試用例(與 header 拼接形成完整代碼) |
cpu_limit | int | 題目時間限制(單位:秒) |
mem_limit | int | 題目空間限制(單位:KB) |
Model類
QueryMysql
bool QueryMySql(const std::string &sql, vector<Question> *out)//執行 SQL 查詢,并將結果存儲到 out 中。
sql:要執行的SQL查詢語句,out:輸出參數,存儲查詢結果的Question向量
GetAllQuestions
bool GetAllQuestions(vector<Question> *out)
構造 SQL 查詢語句:
SELECT * FROM oj_questions
調用
QueryMySql
執行查詢
GetOneQuestion
bool GetOneQuestion(const std::string &number, Question *q)
構造 SQL 查詢語句:SELECT * FROM oj_questions WHERE number=<number>。
調用 QueryMySql 執行查詢。
如果查詢結果中有且僅有一條記錄,將其賦值給 q。
依賴的外部模塊
ns_log
: 提供日志記錄功能。
LOG(INFO)
: 記錄信息日志。
LOG(WARNING)
: 記錄警告日志。
LOG(FATAL)
: 記錄致命錯誤日志。
ns_util
: 提供工具函數。
mysql.h
: MySQL C API 頭文件,用于連接和操作 MySQL 數據庫。
// 以下是硬編碼的常量const std::string oj_questions = "oj_questions"; // 數據庫表名const std::string host = "127.0.0.1"; // MySQL 服務器地址const std::string user = "oj_client"; // MySQL 用戶名const std::string passwd = "123456"; // MySQL 密碼const std::string db = "oj"; // 數據庫名稱const int port = 3306; // MySQL 服務器端口號
?4.3 oj_view
AllExpandHtml
void AllExpandHtml(const vector<struct Question> &questions, std::string *html)
question:題目列表,包含所有題目的詳細信息,html:輸出參數,存儲渲染后的html內容。
構造模板文件路徑:
./template_html/all_questions.html
。創建
ctemplate::TemplateDictionary
對象root
,用于存儲模板數據。遍歷題目列表,將每個題目的編號、標題和難度添加到模板數據中。
加載模板文件。
使用模板數據渲染 HTML 頁面,并將結果存儲到
html
中。
?OneExpandHtml
q:單個題目的詳細信息,html:輸出參數存儲渲染后的html內容
構造模板文件路徑:
./template_html/one_question.html
。創建
ctemplate::TemplateDictionary
對象root
,用于存儲模板數據。將題目的編號、標題、難度、描述和預設代碼添加到模板數據中。
加載模板文件。
使用模板數據渲染 HTML 頁面,并將結果存儲到
html
中。
?所依賴的外部模塊
ctemplate: 用于 HTML 模板渲染。
ctemplate::TemplateDictionary: 存儲模板數據。
ctemplate::Template: 加載和渲染模板文件。
ns_model: 提供題目數據管理功能。
Question: 題目數據結構,包含編號、標題、難度、描述、預設代碼等信息。
?