详细介绍:负载均衡式的在线OJ项目编写(五)
2025-10-13 14:11 tlnshuju 阅读(6) 评论(0) 收藏 举报一.前期内容回顾
对前面的准备不熟悉的,可以看前面的内容,连接如下:
二.负载均衡器的编写
Judge实现思路如下:
/******************************************************
* in_json:
* id : 100
* code : #include...
* input : ""
******************************************************/
void Judge(const std::string in_json,std::string *out_json)
{
//1. in_json进行反序列化,得到题目id,得到用户提交源代码,input
//2. 重新拼接用户代码和测试用例代码
//3. 选择负载最低的主机(差错处理)
//4. 发起http请求,得到结果
//5. 将结果赋值给out_json
}
我们现在来设计负载均衡器,来选择负载最低的主机
Machine定义的代码:
class Machine
{
public:
std::string ip; //编译服务的ip
int port; //编译服务的port
uint64_t load; //编译服务的负载 -> 负载的增加一定是原子的(引入锁)
std::mutex* mtx;//mutex是禁止拷贝的,所以用指针来完成
public:
Machine():ip(""),port(0),load(0),mtx(nullptr)
{}
~Machine()
{}
//提升主机负载
void IncLoad()
{
if(mtx)
{
mtx->lock();
}
load++;
if(mtx)
{
mtx->unlock();
}
}
//减少主机负载
void DecLoad()
{
if(mtx)
{
mtx->lock();
}
load--;
if(mtx)
{
mtx->unlock();
}
}
//获取主机负载
uint64_t Load()
{
uint64_t _load = 0;
if(mtx)
{
mtx->lock();
}
_load = load;
if(mtx)
{
mtx->unlock();
}
return _load;
}
};
2.1LoadConf()函数的完成
bool LoadConf(const std::string& machine_conf)
{
std::ifstream in(machine_conf);
if(!in.is_open())
{
LOG(FATAL) << " 加载: " << machine_conf << "失败" << "\n";
return false;
}
std::string line;
while(std::getline(in,line))
{
std::vector tokens;
StringUtil::SplitString(line,&tokens,":");
if(tokens.size() != 2)
{
LOG(WARNING) << " 切分 " << line << "失败" << "\n";
continue;
}
Machine m;
m.ip = tokens[0];
m.port = atoi(tokens[1].c_str());
m.load = 0;
m.mtx = new std::mutex();
online.push_back(machines.size());
machines.push_back(m);
}
in.close();
return true;
}
2.2SmartChoice()函数的完成
bool SmartChoice(int* id,Machine** m)
{
//1. 使用选择好的主机(更新该主机的负载)
//2. 我们需要可能离线该主机
mtx.lock();
// 负载均衡的算法
// 1. 随机数法+hash
// 2. 轮询 + hash
int online_num = online.size();
if(online_num == 0)
{
mtx.unlock();
LOG(FATAL) << "所有的后端编译主机已经离线" << "\n";
return false;
}
//遍历的方式找到负载最小的机器
*id = online[0];
*m = &machines[online[0]];
uint64_t min_load = machines[online[0]].Load();
for(int i=0;i curr_load)
{
min_load = curr_load;
*id = online[i];
*m = &machines[online[i]];
}
}
mtx.unlock();
return true;
}
有了 加载模块 和 选择模块 ,我们现在可以开始编写judge模块
2.3Judge()函数编写
/******************************************************
* in_json:
* code : #include...
* input : ""
******************************************************/
void Judge(const std::string& number,const std::string in_json,std::string *out_json)
{
//0.根据题目编号,直接拿到对应的题目细节
Question q;
model_.GetOneQUestion(number,&q);
//1. in_json进行反序列化,得到题目id,得到用户提交源代码,input
Json::Reader reader;
Json::Value in_value;
reader.parse(in_json,in_value);
std::string code = in_value["code"].asString();
//2. 重新拼接用户代码和测试用例代码
Json::Value compile_value;
compile_value["input"] = in_value["input"].asString();
compile_value["code"] = code + q.tail;
compile_value["cpu_limit"] = q.cpu_limit;
compile_value["mem_limit"] = q.mem_limit;
Json::FastWriter writer;
std::string compile_string = writer.write(compile_value);
//3. 选择负载最低的主机(差错处理)
//规则:一直选择,直到主机可用,否则,就是全部挂掉
while(true)
{
int id = 0;
Machine* m = nullptr;
if(!load_blance_.SmartChoice(&id,&m))
{
break;
}
LOG(INFO) << " 选择主机成功,主机id: " << id << "详情: " << m->ip << ":" <port <<"\n";
//4. 发起请求,得到结果
Client cli(m->ip,m->port);
m->IncLoad();
if(auto res = cli.Post("/compile_and_run",compile_string,"application/json;charset=utf-8"))
{
//5. 将结果赋值给out_json
if(res->status == 200)
{
*out_json = res->body;
m->DecLoad();
LOG(INFO) << "请求编译和运行服务成功... " << "\n";
break;
}
m->DecLoad();
}
else
{
//请求失败
LOG(ERROR) << " 当前请求的主机id: " << id << "详情: " << m->ip << ":" <port << "可能已经离线" << "\n";
load_blance_.OfflineMachine(id);
load_blance_.ShowMachines();
}
}
}
2.4OfflineMachine()函数的完成
void OfflineMachine(int id)
{
mtx.lock();
for(auto iter = online.begin();iter != online.end();iter++)
{
if(*iter == id)
{
//要离线的主机
online.erase(iter);
offline.push_back(id);
break;//不考虑迭代器失效问题
}
}
mtx.unlock();
}
为了测试时,能更好的看到我们离线和在线的主机,我们设计了一个ShowMachines()函数
2.5ShowMachines()函数的实现
//for test
void ShowMachines()
{
mtx.lock();
std::cout << "当前在线主机列表:";
for(auto& id:online)
{
std::cout << id << " ";
}
std::cout << std::endl;
std::cout << "当前离线主机列表:";
for(auto& id:offline)
{
std::cout << id << " ";
}
std::cout << std::endl;
mtx.unlock();
}
目前为止 我们的LoadBlance类中,还有一个 OnlineMachine() 函数还没有实现
三.postman测试
首先先进行编译
我们要将oj_server和compiler_server都要编译好
但是由于postman一次只能发送一条数据,还没办法检测我们的服务的抗压能力(后续用前端测试)
四.前端代码的编写
后端开发需要关系前端页面吗?不需要! 后续大家不想写,就直接复制粘贴即可
1.任何项目都要有前后端
2.后端虽然不关心页面,但是需要了解,前后端是如何进行交互的
一般编写页面的时候,需要三剑客:html , css , js
后续笔试面试,后续工作,后端不用管前端
4.1丐版的首页
所谓对样式进行调整,本质上是对html中的标签样式进行调整
对html中的标签样式进行调整
1.选中标签
2.设置样式
*号,表示通配符,下面的全部都会使用到这个
页面效果图:
代码实现:
这是我的个人oj系统
4.2所有题目的列表
ctempalte渲染的网页,不能直接刷新,要重新启动程序,才能进行显示
网页效果
在线OJ-题目列表
OnlineJudge题目列表
{{#question_list}}
{{/question_list}}
编号
标题
难度
{{number}}
{{title}}
{{star}}
4.3指定题目的编写代码的页面 + 代码提交
代码编译器ace插件,直接引入就行
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js"type="text/javascript"charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/extlanguage_tools.js"type="text/javascript"charset="utf-8">
</script>
{{number}}.{{title}}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript"charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/extlanguage_tools.js"type="text/javascript"charset="utf-8"></script>
{{number}}.{{title}}.{{star}}
{{desc}}
<script>
//初始化对象
editor = ace.edit("code");
//设置⻛格和语⾔(更多⻛格和语⾔,请到github上相应⽬录查看)
// 主题⼤全:http://www.manongjc.com/detail/25-cfpdrwkkivkikmk.html
editor.setTheme("ace/theme/monokai");
editor.session.setMode("ace/mode/c_cpp");
// 字体⼤⼩
editor.setFontSize(16);
// 设置默认制表符的⼤⼩:
editor.getSession().setTabSize(4);
// 设置只读(true时只读,⽤于展⽰代码)
editor.setReadOnly(false);
// 启⽤提⽰菜单
ace.require("ace/ext/language_tools");
editor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true
});
</script>
未完待续