• 负载均衡的在线OJ


    1.所用技术与开发环境

    所用技术 :
    C++ STL 标准库
    Boost 准标准库 ( 字符串切割 )
    cpp - httplib 第三方开源网络库
    ctemplate 第三方开源前端网页渲染库
    jsoncpp 第三方开源序列化、反序列化库
    负载均衡设计
    多进程、多线程
    MySQL C connect
    Ace 前端在线编辑器
    html/css/js/jquery/ajax 

    2.开发环境

    Centos 7 云服务器
    vscode
    Mysql Workbench

    3. 项目宏观结构

    我们的项目核心是三个模块
    1. comm : 公共模块
    2. compile_server : 编译与运行模块
    3. oj_server : 获取题目列表,查看题目编写题目界面,负载均衡,其他功能

    I. leetcode 结构

    只实现类似 leetcode 的题目列表 + 在线编程功能

    II. 我们的项目宏观结构

    III. 编写思路

    1. 先编写 compile_server
    2. oj_server
    3. version1 基于文件版的在线 OJ
    4. 前端的页面设计
    5. version2 基于 MySQL 版的在线 OJ

    4. compiler 服务设计

    提供的服务:编译并运行代码,得到格式化的相关的结果

     第一个功能 compiler :编译功能

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include "../comm/util.hpp"
    9. #include "../comm/log.hpp"
    10. //只负责代码的编译
    11. namespace ns_compiler
    12. {
    13. //引入路径拼接功能
    14. using namespace ns_util;
    15. using namespace ns_log;
    16. class Compiler
    17. {
    18. public:
    19. Compiler() = default;
    20. ~Compiler() = default;
    21. //返回值:编译成功:True 编译失败:False
    22. //输入参数:编译文件名
    23. // file_name: 123
    24. // 123 ->./temp/123.cpp
    25. // 123 ->./temp/123.exe
    26. // 123 ->./temp/123.stderr
    27. static bool Compile(const std::string &file_name)
    28. {
    29. pid_t pid = fork();
    30. if (pid < 0)
    31. {
    32. LOG(ERROR)<<"内部错误,创建子进程失败"<<"\n";
    33. return false;
    34. }
    35. else if (pid == 0)
    36. {
    37. umask(0);
    38. int _stderr = open(PathUtil::Error(file_name).c_str(), O_CREAT | O_WRONLY, 0644);
    39. if (_stderr < 0)
    40. {
    41. LOG(WARNING)<<"没有形成stderr文件"<<"\n";
    42. exit(1);
    43. }
    44. dup2(_stderr, 2); //将错误信息重定向到文件中
    45. //子进程使用程序替换完成代码的编译功能
    46. execlp("g++","g++", "-o", PathUtil::Exe(file_name).c_str(),PathUtil::Src(file_name).c_str(), "-std=c++11", nullptr /*不要忘记*/);
    47. LOG(ERROR) << "启动编译器g++失败,可能是参数传入有误"<<"\n";
    48. exit(2);
    49. }
    50. else
    51. {
    52. waitpid(pid, nullptr, 0);
    53. //编译是否成功,就看有没有形成同名的可执行文件
    54. if (FileUtil::IsFileExists(PathUtil::Exe(file_name)))
    55. {
    56. LOG(INFO)<Src(file_name)<<"编译成功"<<"\n";
    57. return true;
    58. }
    59. }
    60. LOG(ERROR)<<"编译失败,没有形成可执行文件"<<"\n";
    61. return false;
    62. }
    63. };
    64. }
    Log 功能
    1. #pragma once
    2. #include
    3. #include
    4. #include "util.hpp"
    5. namespace ns_log
    6. {
    7. //日志等级
    8. enum
    9. {
    10. INFO,
    11. DEBUG,
    12. WARNING,
    13. ERROR,
    14. FATAL
    15. };
    16. //LOG()<<"message"
    17. inline std::ostream &Log(const std::string &level,const std::string &file_name,const int line)
    18. {
    19. //添加日志等级
    20. std::string message = "[";
    21. message += level;
    22. message += "]";
    23. //添加报错文件名称
    24. message += "[";
    25. message += file_name;
    26. message += "]";
    27. //添加报错行
    28. message += "[";
    29. message += std::to_string(line);
    30. message += "]";
    31. //日志时间戳
    32. message += "[";
    33. message += ns_util::TimeUtil::GetTimeStamp();
    34. message += "]";
    35. std::cout << message;
    36. return std::cout;
    37. }
    38. //开放日志
    39. #define LOG(level) Log(#level,__FILE__,__LINE__)
    40. }
    第二个功能 runner :运行功能
    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include "../comm/util.hpp"
    12. #include "../comm/log.hpp"
    13. namespace ns_runner
    14. {
    15. class Runner
    16. {
    17. public:
    18. Runner() = default;
    19. ~Runner() = default;
    20. public:
    21. static void SetProcLimit(int cpu_limit,int mem_limit)
    22. {
    23. //设置CPU时长
    24. struct rlimit _cpu_limit;
    25. _cpu_limit.rlim_max = RLIM_INFINITY;
    26. _cpu_limit.rlim_cur = cpu_limit;
    27. setrlimit(RLIMIT_CPU,&_cpu_limit);
    28. //设置内存大小
    29. struct rlimit _mem_limit;
    30. _mem_limit.rlim_max = RLIM_INFINITY;
    31. _mem_limit.rlim_cur = mem_limit*1024; //转化成KB
    32. setrlimit(RLIMIT_AS,&_mem_limit);
    33. }
    34. //返回值 > 0 ,程序异常,收到信号,返回值就是信号编号
    35. //返回值 == 0 ,正常运行完毕,结果保存到对应的临时文件
    36. //返回值 < 0 ,内部错误
    37. //cpu_limit:该程序运行的时候,可以使用的最大CPU资源上限
    38. //mem_limit:该程序运行的时候,可以使用最大内存
    39. static int Run(const std::string &file_name,int cpu_limit,int mem_limit)
    40. {
    41. //只考虑是否正确运行,不考虑结果是否正确
    42. /*
    43. 一个程序在默认启动的时候
    44. 标准输入:不处理
    45. 标准输出:结果
    46. 标准错误:运行时错误信息
    47. */
    48. std::string _exectue = ns_util::PathUtil::Exe(file_name);
    49. std::string _stdin = ns_util::PathUtil::Stdin(file_name);
    50. std::string _stdout = ns_util::PathUtil::Stdout(file_name);
    51. std::string _stderr = ns_util::PathUtil::Stderr(file_name);
    52. //打开临时文件
    53. umask(0);
    54. int _stdin_fd = open(_stdin.c_str(),O_CREAT|O_RDONLY,0644);
    55. int _stdout_fd = open(_stdout.c_str(),O_CREAT|O_WRONLY,0644);
    56. int _stderr_fd = open(_stderr.c_str(),O_CREAT|O_WRONLY,0644);
    57. if(_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0)
    58. {
    59. ns_log::LOG(ERROR)<<"运行时打开文件失败"<<"\n";
    60. return -1; //文件打开失败
    61. }
    62. pid_t pid = fork();
    63. if (pid < 0)
    64. {
    65. ns_log::LOG(ERROR)<<"运行时创建子进程失败"<<"\n";
    66. close(_stdin_fd);
    67. close(_stdout_fd);
    68. close(_stderr_fd);
    69. }
    70. else if (pid == 0)
    71. {
    72. dup2(_stdin_fd,0);
    73. dup2(_stdout_fd,1);
    74. dup2(_stderr_fd,2);
    75. SetProcLimit(cpu_limit,mem_limit);
    76. execl(_exectue.c_str(),_exectue.c_str(),nullptr);
    77. exit(1);
    78. }
    79. else
    80. {
    81. // std::cout<<"关闭文件描述符"<
    82. close(_stdin_fd);
    83. close(_stdout_fd);
    84. close(_stderr_fd);
    85. int status = 0;
    86. //进程异常收到信号
    87. waitpid(pid, &status,0);
    88. ns_log::LOG(INFO)<<"运行完毕,info:"<< (status & 0x7F) << "\n";
    89. return status & 0x7F;
    90. }
    91. return 0;
    92. }
    93. };
    94. }
    测试资源限制:
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. void handler(int signo)
    7. {
    8. std::cout << "signo : " << signo << std::endl; exit(1);
    9. }
    10. int main()
    11. {
    12. //资源不足,导致OS终止进程,是通过信号终止的 for(int i =1; i <= 31; i++)
    13. { signal(i, handler);
    14. // struct rlimit r;
    15. // r.rlim_cur = 1;
    16. // r.rlim_max = RLIM_INFINITY;
    17. // setrlimit(RLIMIT_CPU, &r);
    18. //while(1); struct rlimit r;
    19. r.rlim_cur = 1024 * 1024 * 40;
    20. //20M r.rlim_max = RLIM_INFINITY;
    21. setrlimit(RLIMIT_AS, &r);
    22. int count = 0; while(true)
    23. {
    24. int *p = new int[1024*1024];
    25. count++; std::cout << "size: " << count << std::endl;
    26. sleep(1);
    27. }
    28. return 0;
    29. }
    30. }// 限
    // 内存申请失败
    terminate called after throwing an instance of 'std::bad_alloc'
    what (): std::bad_alloc
    signo : 6
    [ whb@bite - alicloud OnlineJudge ] $ kill - l
    1 ) SIGHUP 2 ) SIGINT 3 ) SIGQUIT 4 ) SIGILL 5 ) SIGTRAP
    6 ) SIGABRT 7 ) SIGBUS 8 ) SIGFPE 9 ) SIGKILL 10 ) SIGUSR1
    11 ) SIGSEGV 12 ) SIGUSR2 13 ) SIGPIPE 14 ) SIGALRM 15 ) SIGTERM
    16 ) SIGSTKFLT 17 ) SIGCHLD 18 ) SIGCONT 19 ) SIGSTOP 20 ) SIGTSTP
            
    //CPU 使用超时
    [ whb@bite - alicloud OnlineJudge ] $ . / a . out
    signo : 24
    [ whb@bite - alicloud OnlineJudge ] $ kill - l
    1 ) SIGHUP 2 ) SIGINT 3 ) SIGQUIT 4 ) SIGILL 5 ) SIGTRAP
    6 ) SIGABRT 7 ) SIGBUS 8 ) SIGFPE 9 ) SIGKILL 10 ) SIGUSR1
    11 ) SIGSEGV 12 ) SIGUSR2 13 ) SIGPIPE 14 ) SIGALRM 15 ) SIGTERM
    16 ) SIGSTKFLT 17 ) SIGCHLD 18 ) SIGCONT 19 ) SIGSTOP 20 ) SIGTSTP
    21 ) SIGTTIN 22 ) SIGTTOU 23 ) SIGURG 24 ) SIGXCPU 25 ) SIGXFSZ
    26 ) SIGVTALRM 27 ) SIGPROF 28 ) SIGWINCH 29 ) SIGIO 30 ) SIGPWR
    第三个功能 compile_run :编译并运行功能
    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include "compiler.hpp"
    7. #include "complie_run.hpp"
    8. #include "../comm/log.hpp"
    9. #include "../comm/util.hpp"
    10. #include "runner.hpp"
    11. namespace ns_compile_and_run
    12. {
    13. class ComplieAndRun
    14. {
    15. public:
    16. static std::string CodeToDesc(int code, const std::string &file_name)
    17. {
    18. std::string desc;
    19. switch (code)
    20. {
    21. case 0:
    22. desc = "编译运行成功";
    23. break;
    24. case -1:
    25. desc = "用户提交的代码为空";
    26. break;
    27. case -2:
    28. desc = "未知错误";
    29. break;
    30. case -3:
    31. // desc = "代码编译是出现错误";
    32. ns_util::FileUtil::ReadFile(ns_util::PathUtil::Error(file_name), &desc, true);
    33. break;
    34. case SIGABRT: // 6
    35. desc = "内存超过范围";
    36. break;
    37. case SIGXCPU: // 24
    38. desc = "CPU使用超时";
    39. break;
    40. case SIGFPE: // 8
    41. desc = "浮点数溢出"; //除0
    42. break;
    43. default:
    44. desc = "未知错误" + std::to_string(code);
    45. break;
    46. }
    47. return desc;
    48. }
    49. public:
    50. static void RemoveTempFile(const std::string &file_name)
    51. {
    52. std::vector AllTempFile{ns_util::PathUtil::Src(file_name),
    53. ns_util::PathUtil::Error(file_name),
    54. ns_util::PathUtil::Exe(file_name),
    55. ns_util::PathUtil::Stderr(file_name),
    56. ns_util::PathUtil::Stdin(file_name),
    57. ns_util::PathUtil::Stdout(file_name)};
    58. for(const auto &e :AllTempFile)
    59. {
    60. if(ns_util::FileUtil::IsFileExists(e));
    61. unlink(e.c_str());
    62. }
    63. }
    64. /*
    65. 输入:
    66. code:用户给自己提交的代码
    67. input:用户给自己的代码对应的输入,不作处理(后期可以扩展)
    68. cpu_limit:时间复杂度
    69. mem_limit:时间复杂度
    70. 输出:
    71. status:状态码(必填)
    72. reason:请求结果(必填)
    73. stdout:程序运行结果(选填)
    74. stderr:程序运行完的错误结果(选填)
    75. */
    76. //参数
    77. // in_json:{"code":"";"input":"";"cpu_limit":"";"mem_limit":"";}
    78. // out_json:{"status":"0";"reason":"";"stdout":"";"stderr":""}
    79. static void Start(const std::string &in_json, std::string *out_json)
    80. {
    81. Json::Value in_vaule;
    82. Json::Reader reader;
    83. reader.parse(in_json, in_vaule); //最后在差错处理
    84. std::string code = in_vaule["code"].asString();
    85. std::string input = in_vaule["input"].asString();
    86. int cpu_limit = in_vaule["cpu_limit"].asInt();
    87. int mem_limit = in_vaule["mem_limit"].asInt();
    88. Json::Value out_vaule;
    89. int status_code = 0;
    90. std::string file_name;
    91. int run_result = 0;
    92. if (code.size() == 0)
    93. {
    94. // //最后差错处理
    95. // out_vaule["status"] = -1; //代码为空
    96. // out_vaule["reason"] = "用户提交的代码是空的";
    97. // //序列化
    98. // return;
    99. status_code = -1;
    100. goto END;
    101. }
    102. //形成唯一文件名 毫秒级时间戳 + 原子性递增唯一值
    103. file_name = ns_util::FileUtil::UniqFileName();
    104. if (!ns_util::FileUtil::WiterFile(ns_util::PathUtil::Src(file_name), code)) //形成临时源src文件
    105. {
    106. // out_vaule["status"] = -2; //未知错误
    107. // out_vaule["reason"] = "提交的代码发生了未知错误";
    108. // //序列化
    109. // return;
    110. status_code = -2;
    111. goto END;
    112. }
    113. if (!ns_compiler::Compiler::Compile(file_name)) //编译失败
    114. {
    115. // out_vaule["status"] = -3;
    116. // //编译失败的内容保存到了.error文件中,读取序列化
    117. // out_vaule["reason"] = us_util::FileUtil::ReadFile(us_util::PathUtil::Error(file_name));
    118. // //序列化
    119. // return;
    120. status_code = -3;
    121. goto END;
    122. }
    123. run_result = ns_runner::Runner::Run(file_name, cpu_limit, mem_limit); //需要知道时间复杂度和空间复杂度
    124. if (run_result < 0)
    125. {
    126. // out_vaule["status"] = -2; //未知错误
    127. // out_vaule["reason"] = "发生了未知错误";
    128. // //序列化
    129. // return;
    130. status_code = -2;
    131. goto END;
    132. }
    133. else if (run_result > 0)
    134. {
    135. // out_vaule["status"] = -4; //运行时报错,收到信号
    136. // out_vaule["reason"] = SignoToDesc(); //将信号转化成报错原因;
    137. // //序列化
    138. // return;
    139. status_code = run_result;
    140. goto END;
    141. }
    142. else
    143. {
    144. // //运行成功
    145. // out_vaule["status"] = 0;
    146. // out_vaule["reason"] = "运行成功";
    147. status_code = 0;
    148. }
    149. END:
    150. out_vaule["status"] = status_code;
    151. out_vaule["reason"] = CodeToDesc(status_code,file_name);
    152. if (status_code == 0)
    153. {
    154. //全部成功
    155. std::string _stdout;
    156. ns_util::FileUtil::ReadFile(ns_util::PathUtil::Stdout(file_name), &_stdout, true);
    157. out_vaule["stdout"] = _stdout;
    158. // std::cout<<"标准输出:"<<_stdout<
    159. std::string _stderr;
    160. ns_util::FileUtil::ReadFile(ns_util::PathUtil::Stderr(file_name), &_stderr, true);
    161. out_vaule["stderr"] = _stderr;
    162. }
    163. //序列化
    164. Json::StyledWriter writer;
    165. *out_json = writer.write(out_vaule);
    166. RemoveTempFile(file_name);
    167. }
    168. };
    169. }

    5. 基于MVC 结构的oj 服务设计

    本质:建立一个小型网站
    1. 获取首页,用题目列表充当
    2. 编辑区域页面
    3. 提交判题功能 ( 编译并运行 )
    M : Model , 通常是和数据交互的模块,比如,对题库进行增删改查(文件版, MySQL
    V : view , 通常是拿到数据之后,要进行构建网页,渲染网页内容,展示给用户的 ( 浏览器 )
    C : control , 控制器,就是我们的核心业务逻辑
    第一个功能:用户请求的服务路由功能
    1. #include
    2. #include "../comm/httplib.h"
    3. #include "oj_control.hpp"
    4. using namespace httplib;
    5. int main()
    6. {
    7. //用户请求的的路由功能
    8. Server svr;
    9. ns_control::Control ctrl;
    10. //获取所有题目列表
    11. svr.Get("/all_questions",[&ctrl](const Request& req,Response &resq){
    12. //返回一张带有所有题目的html网页
    13. std::string html;
    14. ctrl.ALlQuestions(&html);
    15. resq.set_content(html,"text/html;charset=utf-8");
    16. });
    17. //获取要根据题目编号,获取题目的内容
    18. svr.Get(R"(/question/(\d+))",[&ctrl](const Request& req,Response &resq){
    19. std::string num = req.matches[1];
    20. std::string html;
    21. ctrl.Question(num,&html);
    22. resq.set_content(html,"text/html;charset=utf-8");
    23. });
    24. //用户提交代码,使用我们的判题功能()
    25. svr.Post(R"(/judge/(\d+))",[&ctrl](const Request& req,Response &resq){
    26. std::string number = req.matches[1];
    27. std::string result_json;
    28. ctrl.Judge(number,req.body,&result_json);
    29. resq.set_content(result_json,"application/json;charset=utf-8");
    30. // resq.set_content("指明题目的判题:"+num,"text/plain;charset=utf-8");
    31. });
    32. svr.set_base_dir("./wwwroot");
    33. svr.listen("0.0.0.0",8080);
    34. return 0;
    35. }
    第二个功能:model功能,提供对数据的操作
    1. #pragma once
    2. //文件版本
    3. #include "../comm/util.hpp"
    4. #include "../comm/log.hpp"
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. // 根据题目list文件,加载所有的题目信息到内存中
    13. // model: 主要用来和数据进行交互,对外提供访问数据的接口
    14. namespace ns_model
    15. {
    16. using namespace std;
    17. using namespace ns_log;
    18. using namespace ns_util;
    19. struct Question
    20. {
    21. std::string number; //题目编号,唯一
    22. std::string title; //题目的标题
    23. std::string star; //难度: 简单 中等 困难
    24. int cpu_limit; //题目的时间要求(S)
    25. int mem_limit; //题目的空间要去(KB)
    26. std::string desc; //题目的描述
    27. std::string header; //题目预设给用户在线编辑器的代码
    28. std::string tail; //题目的测试用例,需要和header拼接,形成完整代码
    29. };
    30. const std::string questins_list = "./questions/questions.list";
    31. const std::string questins_path = "./questions/";
    32. class Model
    33. {
    34. private:
    35. //题号 : 题目细节
    36. unordered_map questions;
    37. public:
    38. Model()
    39. {
    40. assert(LoadQuestionList(questins_list));
    41. }
    42. bool LoadQuestionList(const string &question_list)
    43. {
    44. //加载配置文件: questions/questions.list + 题目编号文件
    45. ifstream in(question_list);
    46. if (!in.is_open())
    47. {
    48. LOG(FATAL) << " 加载题库失败,请检查是否存在题库文件"<< "\n";
    49. return false;
    50. }
    51. string line;
    52. while (getline(in, line))
    53. {
    54. vector tokens;
    55. StringUtil::SplitString(line, &tokens, " ");
    56. // 1 判断回文数 简单 1 30000
    57. if (tokens.size() != 5)
    58. {
    59. LOG(WARNING) << "加载部分题目失败, 请检查文件格式" << "\n";
    60. continue;
    61. }
    62. Question q;
    63. q.number = tokens[0];
    64. q.title = tokens[1];
    65. //std::cout<
    66. q.star = tokens[2];
    67. q.cpu_limit = atoi(tokens[3].c_str());
    68. q.mem_limit = atoi(tokens[4].c_str());
    69. string path = questins_path;
    70. path += q.number;
    71. path += "/";
    72. FileUtil::ReadFile(path + "desc.txt", &(q.desc), true);
    73. FileUtil::ReadFile(path + "header.cpp", &(q.header), true);
    74. FileUtil::ReadFile(path + "tail.cpp", &(q.tail), true);
    75. questions.insert({q.number, q});
    76. }
    77. LOG(INFO) << "加载题库...成功!"
    78. << "\n";
    79. in.close();
    80. return true;
    81. }
    82. bool GetAllQuestions(vector *out)
    83. {
    84. if (questions.size() == 0)
    85. {
    86. LOG(ERROR) << "用户获取题库失败"<< "\n";
    87. return false;
    88. }
    89. for (const auto &q : questions)
    90. {
    91. out->push_back(q.second); // first: key, second: value
    92. }
    93. return true;
    94. }
    95. bool GetOneQuestion(const std::string &number, Question *q)
    96. {
    97. const auto &iter = questions.find(number);
    98. if (iter == questions.end())
    99. {
    100. LOG(ERROR) << "用户获取题目失败, 题目编号: " << number << "\n";
    101. return false;
    102. }
    103. (*q) = iter->second;
    104. return true;
    105. }
    106. ~Model()
    107. {
    108. }
    109. };
    110. } // namespace ns_model
    第三个功能:control,逻辑控制模块
    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include "oj_model.hpp"
    9. #include "../comm/log.hpp"
    10. #include "../comm/util.hpp"
    11. #include "oj_view.hpp"
    12. #include "../comm/httplib.h"
    13. namespace ns_control
    14. {
    15. const std::string service_machine = "./conf/service_machine.conf";
    16. //提供服务的主机
    17. class Machine
    18. {
    19. public:
    20. std::string ip; //编译服务的ip
    21. int port; //编译服务的端口
    22. uint64_t load; //编译服务的负载
    23. std::mutex *mtx; //mutex禁止拷贝的,使用指针来完成
    24. public:
    25. Machine()
    26. :ip("")
    27. ,port(0)
    28. ,load(0)
    29. ,mtx(nullptr)
    30. {
    31. }
    32. ~Machine()
    33. {
    34. }
    35. public:
    36. void IncLoad() //提升负载
    37. {
    38. if(mtx) mtx->lock();
    39. ++load;
    40. if(mtx) mtx->unlock();
    41. }
    42. void DecLoad() //减少负载
    43. {
    44. if(mtx) mtx->lock();
    45. --load;
    46. if(mtx) mtx->unlock();
    47. }
    48. uint64_t Load() //获取负载
    49. {
    50. uint64_t _load = 0;
    51. if(mtx) mtx->lock();
    52. _load = load;
    53. if(mtx) mtx->unlock();
    54. return _load;
    55. }
    56. };
    57. //负载均衡模块
    58. class LoadBlance
    59. {
    60. private:
    61. std::vector machines; //可以提供编译服务所有主机
    62. std::vector<int> online; //所有在线的主机
    63. std::vector<int> offline; //所有离线主机
    64. std::mutex mtx; //保证LoadBlance数据安全
    65. public:
    66. LoadBlance()
    67. {
    68. assert(LoadConf(service_machine));
    69. ns_log::LOG(ns_log::INFO)<<" 加载 "<" 成功 "<<"\n";
    70. }
    71. ~LoadBlance()
    72. {
    73. }
    74. public:
    75. bool LoadConf(const std::string &machine_conf)
    76. {
    77. std::ifstream in(machine_conf);
    78. if(!in.is_open())
    79. {
    80. ns_log::LOG(ns_log::FATAL)<<"加载配置:"<"文件失败"<<"\n";
    81. return false;
    82. }
    83. std::string line;
    84. while(getline(in,line))
    85. {
    86. std::vector tokens;
    87. ns_util::StringUtil::SplitString(line,&tokens,":");
    88. if(tokens.size() != 2)
    89. {
    90. ns_log::LOG(ns_log::WARNING) <<" 切分 "<" 失败 "<<"\n";
    91. continue;
    92. }
    93. Machine m;
    94. m.ip = tokens[0];
    95. m.port = atoi(tokens[1].c_str());
    96. m.load = 0;
    97. m.mtx = new std::mutex();
    98. online.push_back(machines.size());
    99. machines.push_back(m);
    100. }
    101. in.close();
    102. return true;
    103. }
    104. //id:输出型参数
    105. //m :输出型参数
    106. bool SmartChoice(int* id,Machine **m)
    107. {
    108. //1.使用选择好的主机(更新该主机的负载)
    109. //2.我们需要可能离线该主机
    110. mtx.lock();
    111. //负载均衡的算法
    112. //1.随机数 + hash
    113. //2.轮询 + hash
    114. int online_num = online.size();
    115. if(online_num == 0)
    116. {
    117. mtx.unlock();
    118. ns_log::LOG(ns_log::FATAL) << "后端编译服务全部挂掉了,请运维的老铁尽快查看"<<"\n";
    119. return false;
    120. }
    121. //通过编译找到负载最小的机器
    122. *id = online[0];
    123. *m = &machines[online[0]];
    124. uint64_t min_load = machines[online[0]].Load();
    125. for(int i = 0; i < online_num; ++i)
    126. {
    127. min_load = min_load < machines[online[i]].Load() ? machines[online[i]].Load() : min_load;
    128. *id = online[i];
    129. *m = &machines[online[i]];
    130. }
    131. mtx.unlock();
    132. return true;
    133. }
    134. void OfflineMachine(int which)
    135. {
    136. mtx.lock();
    137. for(auto iter = online.begin(); iter != online.end(); ++iter)
    138. {
    139. if(*iter == which)
    140. {
    141. online.erase(iter);
    142. offline.push_back(which);
    143. break;
    144. }
    145. }
    146. mtx.unlock();
    147. }
    148. void OnlineMachine()
    149. {
    150. }
    151. void ShowMachines()
    152. {
    153. mtx.lock();
    154. std::cout<<"当前在线主机列表:";
    155. for(auto &id : online)
    156. {
    157. std::cout << id <<" ";
    158. }
    159. std::cout<
    160. for(auto &id : offline)
    161. {
    162. std::cout<<"当前离线主机列表:";
    163. std::cout << id << " ";
    164. }
    165. std::cout<
    166. mtx.unlock();
    167. }
    168. };
    169. class Control
    170. {
    171. private:
    172. ns_model::Model _model; //提供后台服务
    173. ns_view::View _view; //提供html渲染功能
    174. LoadBlance _load_blance; //提供负载均衡器
    175. public:
    176. Control()
    177. {
    178. }
    179. //根据题目数据构建网页
    180. bool ALlQuestions(std::string *html)
    181. {
    182. std::vector all;
    183. if(_model.GetAllQuestions(&all))
    184. {
    185. //获取题目信息成功,将所有的题目数据构建成网页
    186. _view.AllExpandHtml(all,html);
    187. }
    188. else
    189. {
    190. *html = "获取题目失败,形成题目列表失败";
    191. return false;
    192. }
    193. return true;
    194. }
    195. bool Question(const std::string number,std::string *html)
    196. {
    197. ns_model::Question q;
    198. if(_model.GetOneQuestion(number,&q))
    199. {
    200. //获取指定题目成功,将题目数据构建成网页
    201. _view.OneExpandHtml(q,html);
    202. }
    203. else
    204. {
    205. *html = "指定题目" + number + "不存在";
    206. return false;
    207. }
    208. return true;
    209. }
    210. void Judge(const std::string& number, const std::string in_json,std::string *out_json)
    211. {
    212. //0.根据题号,直接拿到题目细节
    213. ns_model::Question q;
    214. _model.GetOneQuestion(number,&q);
    215. //1.in_json进行反序列化,得到题目的id,得到用户提交的源代码,input
    216. Json::Reader reader;
    217. Json::Value in_value;
    218. reader.parse(in_json,in_value);
    219. //2.重新拼接用户代码 + 测试用例代码,形成新代码
    220. std::string code = in_value["code"].asString();
    221. Json::Value compile_value;
    222. compile_value["input"] = in_value["input"].asString();
    223. compile_value["code"] = code + q.tail;
    224. compile_value["cpu_limit"] = q.cpu_limit;
    225. compile_value["mem_limit"] = q.mem_limit;
    226. Json::FastWriter writer;
    227. std::string complie_string = writer.write(compile_value);
    228. //3.选择负载最低的主机(差错处理)
    229. for( ; ;)
    230. {
    231. int id = 0;
    232. Machine *m = nullptr;
    233. if(!_load_blance.SmartChoice(&id,&m))
    234. {
    235. break;
    236. }
    237. ns_log::LOG(ns_log::INFO) <<"选择主机成功"<"详情"<ip<<":"<port<<"\n";
    238. //4.然后发起http请求,得到结果
    239. httplib::Client cli(m->ip,m->port);
    240. m->IncLoad();
    241. if(auto res = cli.Post("/compile_and_run",complie_string, "application/json;charset=utf-8"))
    242. {
    243. //5.将结果赋值给out_json
    244. if(res->status == 200)
    245. {
    246. *out_json = res->body;
    247. m->DecLoad();
    248. break;
    249. }
    250. m->DecLoad();
    251. }
    252. else
    253. {
    254. //请求失败
    255. ns_log::LOG(ns_log::ERROR)<<"详情:" << id <<":"<< m->ip << ":"<port<<"可能已经离线"<<"\n";
    256. _load_blance.OfflineMachine(id);
    257. _load_blance.ShowMachines();
    258. }
    259. }
    260. }
    261. ~Control()
    262. {
    263. }
    264. };
    265. }
    附加功能:需要有数据渲染
    // 如果后续引入了 ctemplate ,一旦对网页结构进行修改,尽量的每次想看到结果,将 server 重启一下。 ctemplate 有 自己的优化加速策略,可能在内存中存在缓存网页数据(old)

     

    当我们完成全部功能之后,需要注意:
    要给编译模块添加—D条件编译掉测试用例中的头文件incldue

    6. version1 文件版题目设计

    1. 题目的编号
    2. 题目的标题
    3. 题目的难度
    4. 题目的描述,题面
    5. 时间要求 ( 内部处理 )
    6. 空间要求 ( 内部处理 )
    两批文件构成
    1. 第一个: questions . list : 题目列表(不需要题目的内容)
    2. 第二个:题目的描述,题目的预设置代码 ( header . cpp ), 测试用例代码 ( tail . cpp )
    这两个内容是通过题目的编号,产生关联的
    1. 当用户提交自己的代码的时候: header . cpp
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. using namespace std;
    7. class Solution{
    8. public:
    9. bool isPalindrome(int x)
    10. {
    11. //将你的代码写在下面
    12. return true;
    13. }
    14. };
    该题号对应的测试用例 : tail . cpp
    1. #ifndef COMPILER_ONLINE
    2. #include "header.cpp"
    3. #endif
    4. void Test1()
    5. {
    6. // 通过定义临时对象,来完成方法的调用
    7. bool ret = Solution().isPalindrome(121);
    8. if(ret){
    9. std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;
    10. }
    11. else{
    12. std::cout << "没有通过用例1, 测试的值是: 121" << std::endl;
    13. }
    14. }
    15. void Test2()
    16. {
    17. // 通过定义临时对象,来完成方法的调用
    18. bool ret = Solution().isPalindrome(-10);
    19. if(!ret){
    20. std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;
    21. }
    22. else{
    23. std::cout << "没有通过用例2, 测试的值是: -10" << std::endl;
    24. }
    25. }
    26. int main()
    27. {
    28. Test1();
    29. Test2();
    30. return 0;
    31. }

    后端全部写完使用Postman 来测试

     

     

     

    7. 前端页面设计

    8. version2 MySQL版题目设计

    9.综合测试

    10.项目扩展思路

  • 相关阅读:
    【Java】-【使用smtp协议发邮件】
    Windows cmd窗口常用命令
    python中怎样把一个文件夹中所有的*.ts文件都读进来形成一个视频文件?
    Window环境下不安装DM数据库,编译dmPython
    如何快速解决d3dcompiler_43.dll缺失问题?五种方法快速解决
    Jmeter压测实战:Jmeter二次开发之自定义函数
    单细胞聚类,究竟聚类聚成多少类比较合适?全代码分享
    Spring Security 实现动态权限菜单方案(附源码)
    SpringBoot-单元测试
    苹果起诉以色列安全公司NSO,间谍软件是侵犯隐私还是打击犯罪?
  • 原文地址:https://blog.csdn.net/qq_57283958/article/details/126439223