一.目的
负责负载均衡,获取网站首页,通过网络罗调用编译并运行并提供结果给用户。根据用户的操作提供不同的功能。采用mvc结构。使用 ctemplate文字模板做数据渲染
m在这里指的是和数据的交互,可以用文件或者sql交互。在使用文件的情况下,就必须要有对应的文件目录存放每一道题。提供题目描述和题头还有测试用例。
二.实现model
负责将文件题库抽象成数据结构,并提供接口给ojcontrol调用。ojcontrol通过model模块获取全部的题目信息和测试用例,用来交给后端服务继续运行
题的数据结构
typedef struct Question // 每一道题对应的基本信息{string number;// 题目编号string title; // 题目名称string diffculty; // 题目难度int cpu_limit;// 运行时间限制int mem_limit;// 内存时间限制string desc;// 题目描述string header;// 题目提供给用户的首部代码string tailer;// 题目的测试用例,需要和header拼接,形成完整代码}Question;
获取题信息的数据结构,因为是从文件中读取,所以需要一个字符串格分割函数。
加载所有题库信息到数据结构。
bool LoadQuestionList(const string question_path) //根据文件题目列表获取到数据结构内{ifstream in(questionlist_path); // 打开题目列表的文件流if (!in.is_open()) // 打开文件流失败{LOG(FATAL) << "加载题库失败,请检查是否存在题库文件" << "\n";return false;}string line;while (getline(in, line)){vector tokens; // 题的五个信息StringUtil::SplitString(line, &tokens, " "); // 根据空格分隔出不同元素if (tokens.size() != 5) // 每道题有五种基本信息(编号,题目,,难度,mem,cpu){LOG(WARNING) << "加载部分题目失败, 请检查文件格式" << "\n";continue; // 获取当前题目信息失败直接跳过}Question q; // 创建题目对象填充信息q.number = tokens[0];q.title = tokens[1];q.diffculty = tokens[2];q.cpu_limit = atoi(tokens[3].c_str());q.mem_limit = atoi(tokens[4].c_str());// 获取题目详细信息string path = question_path; // 需要拼出对应的题目路径path += q.number;path += "/";// 从文件内读出内容并填充进题目结构体里FileUtil::ReadFile(path + "desc.txt", &(q.desc), true);FileUtil::ReadFile(path + "header.cpp", &(q.header), true);FileUtil::ReadFile(path + "tail.cpp", &(q.tailer), true);// 形成哈希映射questions.insert({q.number, q});}LOG(INFO) << "题库加载成功"<< "\n";in.close();return true;}
获取一道题给客户
bool GetOneQuestion(const string &number, Question *ret) // 通过题号获取对应题{const auto &iter = questions.find(number);if (iter == questions.end()){LOG(ERROR) << "用户获取题目失败, 题目编号: " << number <second; // 输出行参数return true;}
获取所有题目给客户
bool GetAllQuestion(vector *out){if (questions.size() == 0){LOG(ERROR) << "用户获取题库失败" <push_back(q.second); // 遍历哈希映射把题目信息依次录入vector}return true;}
三.control模块
主要实现逻辑控制,从网页上拿来各种信息,提取有用信息后,结合本地数据向后端提交,如判题功能,或者根据要求获取对应本地信息,并通过前段模块返回给用户
// 核心业务逻辑class Control // 控制ojserver的基础功能,包括修改题库和前端界面交互,整合数据控制和前端交互{private:ns_model::Model _model; // 提供后台数据View _view; // 提供html渲染功能LoadBalance _load;// 提供负载均衡模块public:void RecoveryMachine(){_load.OnlineMachine();}// 获取所有题目并生成html文件bool AllQuestions(string *html) // 输出型参数{vector all;if (_model.GetAllQuestion(&all)) // 获取所有题目到vector内{// 获取信息成功构建htmlsort(all.begin(),all.end(),[](const Question& q1,const Question& q2){ return atoi(q1.number.c_str())_ip, m->_port);m->IncLoad();LOG(INFO) << " 选择主机成功, 主机id: " << id << " 详情: " <_ip << ":" <_port << " 当前主机的负载是: " <Load() <status == 200) // 200表示成功运行{// 将结果返回给用户*outj_son = res->body;m->DecLoad();LOG(INFO) << "主机执行任务成功"<DecLoad();}else{// 请求失败(当前主机不存在)LOG(ERROR) << " 当前请求的主机id: " << id << " 详情: " <_ip << ":" <_port << " 可能已经离线" << "\n";_load.OfflineMachine(id);_load.ShowMachines(); // 仅仅是为了用来调试}}}
四.view模块
在拿到model的题目信息后,结合本地html进行渲染,给用户提供前端展示页面。采用ctemplate渲染。
通过互取到的所有题目信息,使用ctemplate渲染展示给用户。
// 获取所有题目并生成html文件bool AllQuestions(string *html) // 输出型参数{vector all;if (_model.GetAllQuestion(&all)) // 获取所有题目到vector内{// 获取信息成功构建htmlsort(all.begin(),all.end(),[](const Question& q1,const Question& q2){ return atoi(q1.number.c_str())
一道题
bool OneQuestion(const string &number, string *html){Question q;if (_model.GetOneQuestion(number, &q)){_view.OneExepandHtml(q, html);return true;}else{*html = "指定题目: " + number + " 不存在!";return false;}}
五.负载均衡
我们需要以一个文件存放所有可用的主机和端口号。作为主机的配置文件。同时主机需要保存自身的负载情况,所以需要加锁。
class Machine // 标识提供服务的主机{public:std::string _ip; // 本机的ip端口号和负载程度int _port;uint64_t _load;std::mutex *_mtx; // 重点必须用指针不能用实例,容器内有拷贝,c++里的mutex是禁止拷贝的,只能用指针取地址绕过Machine(): _ip(""),_port(0),_load(0),_mtx(nullptr){}// 提升负载(有可能多个主机同时运行有竞争问题)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;}void ResetLoad(){if (_mtx){_mtx->lock();}_load = 0;if (_mtx){_mtx->unlock();}}};
负载均衡模块负责所有主机的情况,包括是否上线。并提供负载最小的主机,也需要加锁,因为涉及到对所有主机属性的更改。算临界区
class LoadBalance // 负载均衡模块{// 可以给我们提供编译服务的所有的主机// 每一台主机都有自己的下标,充当当前主机的idvector machines;// 所有在线的主机idstd::vector online;// 所有离线的主机idstd::vector offline;std::mutex mtx; //需要保证负载均衡的数据安全public:LoadBalance(){assert(LoadConf(service_machine));LOG(INFO) << "加载 " << service_machine << " 成功"<< "\n";}bool LoadConf(const string &machine_conf) // 从conf文件里读取上来所有主机信息{ifstream in(machine_conf); // 打开文件流if (!in.is_open()){LOG(FATAL) << " 加载: " << machine_conf << " 失败" << "\n";return false;}// 读取数据到linestring line;while (getline(in, line)){vector tokens; // 每一行切分到这里,只有两个元素,ip和端口号StringUtil::SplitString(line, &tokens, ":");if (tokens.size() != 2){LOG(WARNING) << " 切分 " << line << " 失败" << "\n";continue;}// 读取数据完毕构建对象Machine m;m._ip = tokens[0]; // 填充ip和端口号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;}bool SmartChoice(Machine **m, int *id) // 两个输出出行参数,返回选择的主机,或者看情况离线主机{// 因为传参的时候不想通过下标访问,所以通过地址修改// 1. 选择主机(更新该主机的负载)// 2. 我们需要可能离线该主机// 选择主机有安全问题mtx.lock();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 < online_num; i++) // 寻找最小负载{uint64_t cur_load = machines[online[i]].Load();if (cur_load < min_load){min_load = cur_load;*id = online[i];*m = &machines[online[i]];}}mtx.unlock();return true;}void OnlineMachine() // 上线服务器(一次直接全部上线){mtx.lock();online.insert(online.end(), offline.begin(), offline.end());offline.erase(offline.begin(), offline.end());LOG(INFO) << "重新登陆主机成功"<< "\n";mtx.unlock();}void ShowMachines() // 查看所有服务器状态{mtx.lock();std::cout << "当前在线主机列表: "<< "\n";for (auto &id : online){std::cout << id << " ";}std::cout << std::endl;std::cout << "当前离线主机列表: "<< "\n";for (auto &id : offline){std::cout << id << " ";}mtx.unlock();}void OfflineMachine(const int &mid) // 下线服务器{mtx.lock();for (auto iter = online.begin(); iter != online.end(); iter++) // 用迭代器遍历好一些,利于删除{if (*iter == mid){machines[mid].ResetLoad(); // 下线前清空负载online.erase(iter);offline.push_back(mid);break; // 因为break所以不用考虑迭代器失效,此时循环已经终止了}}mtx.unlock();}};