C++項目 – 負載均衡OJ(三)online_judge
一、基于MVC結(jié)構(gòu)的oj服務(wù)設(shè)計
1.結(jié)構(gòu)與功能
該模塊功能:
- 獲取首頁,用題目列表充當
- 編輯區(qū)域頁面
- 提交判題功能(編譯并運行)
MVC結(jié)構(gòu):
- M: Model,通常是和數(shù)據(jù)交互的模塊,比如,對題庫進行增刪改查(文件版,MySQL)
- V: view, 通常是拿到數(shù)據(jù)之后,要進行構(gòu)建網(wǎng)頁,渲染網(wǎng)頁內(nèi)容,展示給用戶的(瀏覽器)
- C: control, 控制器,就是我們的核心業(yè)務(wù)邏輯
二、oj_model.hpp
這是和數(shù)據(jù)交互的模塊,對外提供訪問數(shù)據(jù)的接口;
1.建立文件版題庫
題目的信息包括:
- 題目的編號
- 題目的標題
- 題目的難度
- 題目的描述,題面
- 時間要求(內(nèi)部處理)
- 空間要求(內(nèi)部處理)
兩批文件構(gòu)成
- questions.list : 題目列表(不需要題目的內(nèi)容)
所有的題目都存放在questions路徑下 - 題目的描述(
desc.txt
),題目的預(yù)設(shè)置代碼(header.cpp
), 測試用例代碼(tail.cpp
)
測試用例tail.cpp
為了在實際編譯的時候文件中沒有#include “header.hpp”,需要在編譯服務(wù)調(diào)用g++的時候,后面加上一個編譯選項-D COMPILER_ONLINE
:
這兩個內(nèi)容是通過題目的編號,產(chǎn)生關(guān)聯(lián)的
2.文件版題庫的服務(wù)模塊
文件版model模塊
- 當用戶提交代碼后,OJ是將用戶寫好的
header.cpp
拼接上題號對應(yīng)的測試用例tail.cpp形成新的源代碼,并發(fā)送到compile_and_run模塊運行,運行結(jié)果返回給用戶 - 測試用例中的條件編譯不想讓編譯器編譯的時候,保留它,而是裁剪掉(
g++ -D COMPILER_ONLINE
) - 根據(jù)題目list文件,加載所有的題目信息道內(nèi)存中;
- 題目的所有信息由一個結(jié)構(gòu)體類型存儲;
-
Model
類中,使用哈希表保存題號與題目信息的映射;-
LoadQuestionList
函數(shù)用于加載所有的題目信息道內(nèi)存中,以哈希表的形式; -
GetAllQuestions
用于獲取所有的題目信息; -
GetOneQuestion
用于獲取指定題目信息;
-
3. MySQL版題庫
3.1.創(chuàng)建名為oj_client的用戶,創(chuàng)建數(shù)據(jù)庫oj,并給oj_client賦權(quán)
mysql> use mysql
mysql> create user oj_client@'%' identified by 'password';
mysql> create database oj;
mysql> grant all on oj.* to oj_client@'%';
登錄oj_client用戶,可以看到oj數(shù)據(jù)庫
3.2. 設(shè)計表結(jié)構(gòu)
- 使用MySQLWorkbench來進行圖形化界面建表:
創(chuàng)建與服務(wù)器MySQL的連接:
連接上,在oj數(shù)據(jù)庫創(chuàng)建oj_questions表:
create table if not exists `oj_questions` (
`number` int primary key auto_increment comment '題目的編號',
`title` varchar(128) not null comment '題目的標題',
`star` varchar(8) not null comment '題目的難度',
`desc` text not null comment '題目的描述',
`header` text not null comment '題目預(yù)設(shè)給用戶的代碼',
`tail` text not null comment '題目的測試用例代碼',
`cpu_limit` int default 1 comment '題目的cpu運行時間限制',
`mem_limit` int default 50000 comment '題目的內(nèi)存空間限制'
)engine=InnoDB default charset=utf8;
- 在Workbench中錄題:
如果想只執(zhí)行這一條語句,就選中然后執(zhí)行;
點擊form editer開始錄入:
錄制完成后點apply;
錄制成功:
3.3.引入MySQL鏈接庫
MySQL版model模塊
- 如果系統(tǒng)中本身就有MySQL連接的庫,就不需要再引入了:
如果系統(tǒng)只有動態(tài)庫文件,沒有devel(開發(fā)庫文件),可以嘗試用yum安裝:
yum install -y mysql-community-devel
安裝好devel(開發(fā)庫)后,我們只需要用 #include <mysql/mysql.h>
就可引入mysql庫。
編譯指令為:
g++ -o oj_server oj_server.cc -std=c++11 -L/usr/lib64/mysql/ -lmysqlclient
- 如果系統(tǒng)中沒有,就需要自己安裝:
MySQL官網(wǎng)下載:
導(dǎo)入服務(wù)器并解壓:
重命名:
在oj_server目錄下引入軟鏈接: - 如果在運行時發(fā)現(xiàn)找不到MySQL的庫:
將庫所在的路徑寫入該配置文件中,這樣運行時系統(tǒng)就知道去哪里尋找?guī)炝耍?br>
3.4.在oj_model中訪問連接數(shù)據(jù)庫
oj_server是基于MVC實現(xiàn)的,和數(shù)據(jù)打交道的只有oj_mode模塊,只需要更改該部分代碼即可;
-
QueryMySQL
函數(shù)用于執(zhí)行查詢sql語句,并將查詢結(jié)果封裝成Question插入到out中;- 關(guān)于MySQL Connector C中的接口作用,見博客MySQL Connection C中的API介紹
-
GetAllQuestions
用于向MySQL發(fā)出查詢所有題目的指令; -
GetOneQuestion
用于向MySQL發(fā)出查詢單個題目的指令;
三、oj_view.hpp
這是構(gòu)建網(wǎng)頁的模塊;
1.安裝與測試ctemplate庫
ctemplate庫的github倉庫
這是谷歌開源的cpp網(wǎng)頁渲染庫;
在ctemplate中數(shù)據(jù)是以字典的格式存放的
待渲染的網(wǎng)頁中寫入的是數(shù)據(jù)的key值,渲染之后將key換成對應(yīng)的value;
測試網(wǎng)頁渲染功能:
- html中待替換的key值需要使用
{{key}}
-
TemplateDictionary root
是建立ctemplate參數(shù)目錄結(jié)構(gòu),相當于unordered_map<string, string> test;
-
root.SetValue
向目錄中添加你要替換的數(shù)據(jù),kv的,相當于test.insert({ket, value});
-
GetTemplate
獲取待渲染對象,DO_NOT_STRIP
是指保持html網(wǎng)頁原貌; -
tpl->Expand
開始渲染,替換字典中的kv,返回新的網(wǎng)頁結(jié)果到out_html;
#include <iostream>
#include <string>
#include <ctemplate/template.h>
using namespace std;
int main()
{
//html網(wǎng)頁的地址
string html = "./test.html";
string html_info = "lmx_xdu";
//建立ctemplate參數(shù)目錄結(jié)構(gòu)
//相當于 unordered_map<string, string> test;
ctemplate::TemplateDictionary root("test");
//向目錄中添加你要替換的數(shù)據(jù),kv的
//第一個參數(shù)是key,第二個參數(shù)是value,將html中的key全部替換為value
//相當于test.insert({ket, value});
root.SetValue("info", html_info);
//獲取待渲染對象
//DO_NOT_STRIP是指保持html網(wǎng)頁原貌
ctemplate::Template *tpl = ctemplate::Template::GetTemplate(html, ctemplate::DO_NOT_STRIP);
//開始渲染,替換字典中的kv,返回新的網(wǎng)頁結(jié)果到out_html
string out_html;
tpl->Expand(&out_html, &root);
cout << "渲染的帶參html是:" << endl;
cout << out_html << endl;
return 0;
}
源html網(wǎng)頁:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--渲染參數(shù),會被我們C++代碼中的數(shù)據(jù)替換, info就是上面SetValue("info", html_info)代碼中的
info,會自動被std::string info_html中的內(nèi)容替換-->
<p>{{info}}</p>
<p>{{info}}</p>
<p>{{info}}</p>
<p>{{info}}</p>
</body>
</html>
渲染后的html網(wǎng)頁:
2.view模塊編寫
view模塊代碼
View類用于網(wǎng)頁的渲染;
- 待渲染的網(wǎng)頁模板在
/template_html
路徑下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>在線OJ題目列表</title>
</head>
<body>
<table>
<tr>
<th>編號</th>
<th>標題</th>
<th>難度</th>
</tr>
{{#question_list}}
<tr>
<td>{{number}}</td>
<td><a href="/question/{{number}}">{{title}}</a></td>
<td>{{star}}</td>
</tr>
{{/question_list}}
</table>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{number}}.{{title}}</title>
</head>
<body>
<h4>{{number}}.{{title}}.{{star}}</h4>
<P>{{desc}}</P>
<textarea name="code" cols="100" rows="50">{{pre_code}}</textarea>
</body>
</html>
-
AllExpandHtml
函數(shù)將讀取到的所有題目信息都渲染到網(wǎng)頁上;- 在創(chuàng)建了root根目錄后,再向根目錄中添加子目錄,用于替換question_list中的內(nèi)容;
可以將html中{{#question_list}}
修飾的所有內(nèi)容循環(huán)渲染;
在題目的title處加上鏈接,可以跳轉(zhuǎn)到這道題的做題界面;
- 在創(chuàng)建了root根目錄后,再向根目錄中添加子目錄,用于替換question_list中的內(nèi)容;
-
OneExpandHtml
用于單個題目信息的渲染;
四、oj_control.hpp
這是業(yè)務(wù)的核心邏輯模塊;
1.負載均衡模塊
負載均衡模塊用于幫助Control選取負載最低的編譯服務(wù)器,所有編譯服務(wù)器的配置信息都在以下文件中:Machine
類用于保存每臺編譯服務(wù)器的具體信息,一個編譯服務(wù)對應(yīng)一個Machine
:
- 包括ip、端口、負載以及每臺服務(wù)器的鎖;
- 由于mutex禁止拷貝,因此使用指針;
LoadBalance
類用于實現(xiàn)負載均衡算法:
- 類中保存所有服務(wù)器的類
Machine
,記錄所有上線和下線的主機,并且有一把鎖保證LoadBalance的數(shù)據(jù)安全; -
LoadConf
函數(shù)用于將配置文件中所有的服務(wù)器信息全部讀取并保存; -
SmartChoice
函數(shù)用于根據(jù)所有上線服務(wù)器的負載信息,選擇負載最低的機器; - 負載均衡的算法有:1.隨機數(shù)+hash;2.輪詢+hash;
這里選取輪詢+hash,通過遍歷的方式,找到所有負載最小的機器; -
OfflineMachine
函數(shù)用于將指定的主機離線; -
OnlineMachine
函數(shù)用于上線所有已離線的主機,是將所有_offline
中的主機全部插入到_online
中,并刪除_offline
中的主機;
2.Control模塊
Control模塊代碼
Control類用于根據(jù)Model類中獲取的題目數(shù)據(jù),來調(diào)用View類中的方法構(gòu)建OJ網(wǎng)頁;
-
RecoveryMachine
用于將所有的離線主機恢復(fù)為上線模式; -
AllQuestions
使用Model模塊獲取所有的題目信息,再通過View模塊將題目信息渲染到網(wǎng)頁上; -
Question
根據(jù)指定題目構(gòu)建網(wǎng)頁; -
Judge
實現(xiàn)判題功能,步驟如下:- 根據(jù)題目編號,拿到對應(yīng)的題目細節(jié);
in_json
進行反序列化,得到題目的id,得到用戶提交的源代碼,input輸入?yún)?shù);
重新拼接用戶代碼+測試用例代碼,形成新的代碼;
選擇負載最低的主機;規(guī)則:一直選擇,直到主機可用,否則,就是全部掛掉;
然后發(fā)起http請求,得到結(jié)果;
將結(jié)果賦值給out_json; - Result中定義了bool類型強轉(zhuǎn),因此Result可以直接放在if語句里,如果返回值存在就會返回true;
- Post請求:第一個參數(shù)是請求,第二個參數(shù)是請求的參數(shù),第三個參數(shù)是請求的類型;
cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8")
- Post請求的返回值是Result對象
成員res_是Response的指針,Result重載了->,能夠直接訪問到Response;
Response中的成員有statue狀態(tài)碼,其值等于200才證明這個http請求是成功的;
- 根據(jù)題目編號,拿到對應(yīng)的題目細節(jié);
五、oj_server.cc
1.Makefile
oj_server:oj_server.cc
g++ -o $@ $^ -std=c++11 -L/usr/lib64/mysql/ -lpthread -ljsoncpp -lctemplate -lmysqlclient
.PHONY:clean
clean:
rm -f oj_server
2.設(shè)置用戶請求的服務(wù)路由功能
- \d+是正則表達式,能夠匹配到用戶輸入的所有數(shù)字;
\d代表數(shù)字,+代表一個或多個;
正則匹配到的內(nèi)容會存放在Request類中的matchs里面;
question/100 ->正則匹配 -
R"()"
,原始字符串,保持字符串內(nèi)容的原貌,不用做相關(guān)的轉(zhuǎn)義; - 設(shè)置首頁為wwwroot,在其中添加html網(wǎng)頁(vdcode中!+Tab可以生成網(wǎng)頁骨架)
#include <iostream>
#include "../Comm/httplib.h"
#include "../Comm/util.hpp"
using namespace httplib;
int main()
{
//用戶請求的服務(wù)路由功能
Server svr;
//獲取所有題目列表
svr.Get("/all_questions", [](const Request &req, Response &resp){
resp.set_content("這是所有題目的列表", "text/plain; charset=utf-8");
});
//用戶要根據(jù)題目編號,獲取題目的內(nèi)容
//question/100 ->正則匹配
//R"()",原始字符串,保持字符串內(nèi)容的原貌,不用做相關(guān)的轉(zhuǎn)義
svr.Get(R"(/question/(\d+))", [](const Request &req, Response &resp){
string number = req.matches[1];
resp.set_content("這是指定的一道題: " + number, "text/plain; charset=utf-8");
});
//用戶提交代碼,使用我們的判題功能(1.每道題的測試用例 2.compile_and_run)
svr.Get(R"(/judge/(\d+))", [](const Request &req, Response &resp){
string number = req.matches[1];
resp.set_content("指定題目的判題: " + number, "text/plain; charset=utf-8");
});
svr.set_base_dir("./wwwroot");
svr.listen("0.0.0.0", 8080);
return 0;
}
首頁:
題目列表:
指定題目:
判題:
3.構(gòu)建正式的OJ功能
- 通過Control模塊獲取由所有題目信息構(gòu)建的網(wǎng)頁,形成網(wǎng)頁服務(wù)
-
set_content
中的格式設(shè)置為html;
-
- 通過Control模塊獲取單個題目信息構(gòu)建的網(wǎng)頁,形成網(wǎng)頁服務(wù)
首頁:
題目列表:
做題界面:
4.形成正式的oj_server
oj_server代碼
添加Control對象,實現(xiàn)加載題目和判題功能的請求:
- 通過捕捉信號上線所有主機:
通過捕捉ctrl + \信號,觸發(fā)時調(diào)用Recovery
重新上線所有主機;
使用PostMan進行測試:
- 創(chuàng)建三個compile_server服務(wù),端口號都是基于配置文件
service_machine.conf
中的:
使用PostMan進行Post請求,請求的文本形式為json,代碼內(nèi)容為無法運行的初始代碼,返回的內(nèi)容中有報錯信息:
5.前端界面
wwwroot首頁
template_html界面
index.html
all_questions.html
one_question.html
這部分包含前后端交互:
- 提交給Judge功能判題時,需要的in_json內(nèi)容主要有input和code;
-
submit
函數(shù)用于獲取頁面上的題目信息,形成請求url,并通過ajax向后臺發(fā)起基于http的json請求: -
show_result
函數(shù)用于得到結(jié)果,解析并顯示到 result中
6.測試
負載均衡測試
每次都會選擇負載最低的機器運行;
主機離線上線測試:
所有主機離線后,再次上線,觸發(fā)ctrl + c信號,就可以上線所有主機;文章來源:http://www.zghlxwxcb.cn/news/detail-854081.html
六、編寫頂層makefile
頂層makefile用于項目的編譯、清理和發(fā)布;文章來源地址http://www.zghlxwxcb.cn/news/detail-854081.html
- 語句前面加@是在運行時不顯示這條語句;
- 項目的發(fā)布:將生成的可執(zhí)行程序和需要的庫文件、網(wǎng)頁文件等全部復(fù)制到output路徑下;
.PHONY: all
all :
#編譯
@cd compiler_server;\
make;\
cd -;\
cd online_judge;\
make;\
cd -;
#項目發(fā)布
.PHONY : output
output :
@mkdir -p output/compiler_server;\
mkdir -p output/online_judge;\
cp -rf compiler_server/compile_server output/compiler_server;\
cp -rf compiler_server/temp output/compiler_server;\
cp -rf online_judge/conf output/online_judge;\
cp -rf online_judge/questions output/online_judge;\
cp -rf online_judge/template_html output/online_judge;\
cp -rf online_judge/wwwroot output/online_judge;\
cp -rf online_judge/oj_server output/online_judge;
#項目清理
.PHONY : clean
clean :
@cd compiler_server;\
make clean;\
cd -;\
cd online_judge;\
make clean;\
cd -;\
rm -rf output;
到了這里,關(guān)于C++項目 -- 負載均衡OJ(三)online_judge的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!