实战项目: 负载均衡
0. 前言
这个项目使用了前后端,实现一个丐版的LeetCode刷题网站,并根据每台主机的实际情况,选择对应的主机,负载均衡的调度
0.1 所用技术与开发环境
所用技术:
- C++ STL 标准库
- Boost 准标准库 ( 字符串切割 )
- cpp- httplib 第三方开源网络库
- ctemplate 第三方开源前端网页渲染库
- jsoncpp 第三方开源序列化、反序列化库
- 负载均衡设计
- 多进程、多线程
- MySQL C connect
- Ace前端在线编辑器 ( 部分 )
- html/css/js/jquery/ajax (部分 )
0.2 建立目录及文件
0.3 项目宏观结构
- 具体的功能类似 leetcode 的题目列表+在线编程功能
1. compile 服务设计
- 由于compiler这个模块管理的是编译与运行,则可以先直接就创建所需要的文件
1.0 书写makefile文件
- 随着后续代码的跟进,并不断引入第三方库,这里还会新增编译选项
1.1 compiler_server
1.1.0 编译功能(compiler.hpp)
- 在编译的时候,无非存在2种情况,a)要么通过,b)要么不通过
- 要确定编译通过:
只需要确定是否生成对应的.exe文件 - 要当编译出错的时候(stderr):
需要将出错信息,重定向到一个临时文件中,保存编译出错的结果
还需要调用fork();子进程完成编译工作
父进程继续执行
- 由于需要频繁的文件名转换,所以在comm模块中,新建util.hpp文件并将文件名转换的函数放在一起
- 还有后面判断编译成功生成的可执行程序,虽然可以直接暴力的打开文件判断是否存在,但这里使用stat函数会好一些
- stat结构体会记录文件的各种信息
- 注意: 程序替换是不会影响进程的文件符描述符表的
1.1.1 日志模块(log.hpp)
由于一般日志都会带上时间, 这里还需要实现一个得到当前时间的函数,则我又在util.hpp把得到时间函数的类封装成了一个类
由于会频繁的调用日志进行打印信息,也为了更简便的调用,我进行了以下处理
- 如果在宏定义中使用#,那么这个宏就被称为带有字符串化操作的宏。这种宏可以将其参数转换成字符串常量,并在预处理阶段进行替换。
由于引入了日志,则就可以把之前所有的输出信息,换成日志输出
1.1.2 测试编译模块
- Compile的参数是文件名,它内部会自动拼接
- 我们还需要再./temp中创建一个code.cpp文件
- 上面我的代码有一个错误,在编译成功的时候,并没有return,导致LOG日志打印有问题
- 要是我们的源文件有问题,错误信息就会重定向到 文件.compile_error中
- 在测试的时候,还需要把 文件.exe 文件.compile_error文件删除,就是上次生成的文件
1.1.3 运行功能(runner.hpp)
程序运行: 1)代码跑完,结果正确 2)代码跑完,结果不正确, 3)代码没跑完,异常了
程序结果是否正确,是由oj_server中的测试用例决定的,则run模块只考虑是否正确运行完毕
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>// fork接口需要#include "../comm/log.hpp"
#include "../comm/util.hpp"
namespace ns_runner
{using namespace ns_util;using namespace ns_log;class Runner{public:Runner(){}~Runner(){}static int Run(const std::string &file_name){std::string _execute = PathUtil::Exe(file_name);// 可执行std::string _stdin = PathUtil::Stdin(file_name);// 输入std::string _stdout = PathUtil::Stdout(file_name);// 输出std::string _stderr = PathUtil::Stderr(file_name);// 错误umask(0);int _stdin_fd = open(_stdin.c_str(),O_CREAT | O_RDONLY,0644);int _stdout_fd = open(_stdout.c_str(),O_CREAT | O_WRONLY,0644);int _stderr_fd = open(_stderr.c_str(),O_CREAT | O_WRONLY,0644);if(_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0){LOG(ERROR) << "运行时打开标准文件失败" << "\n";return -1;// 代表打开文件失败}pid_t pid = fork();if(pid < 0){LOG(ERROR) << "运行创建子进程失败" << "\n";close(_stdin_fd);close(_stdout_fd);close(_stderr_fd);return -2;// 代表创建自己失败}else if(pid == 0){// 子进程dup2(_stdin_fd,0);dup2(_stdout_fd,1);dup2(_stderr_fd,2);LOG(INFO) << "123";// 是不是有问题啊// 这个程序替换等价于 ./tmp/code.exe ./tmp/code.exeexecl(_execute.c_str(),_execute.c_str(),nullptr);exit(1);}else{close(_stdin_fd);close(_stdout_fd);close(_stderr_fd);int status = 0;// 表示输出型参数waitpid(pid,&status,0);// 阻塞式等待// 程序运行异常,一定是因为收到信号LOG(INFO) << "运行完毕,infor: " << (status & 0x7f) << "\n";return status & 0x7f;}}};
}
-
返回值 > 0: 程序异常了,退出时收到了信号,返回值就是对应的信号编号
返回值 == 0: 正常运行完毕的,结果保存到了对应的临时文件中
返回值 < 0: 内部错误 - run.hpp也是一样的,把自己的各种输出信息,输出到一个临时文件中
- 要判断一个程序是否异常,只需要看它是否收到了异常信号
解释waitpid第2个输出型参数
- status并不是按照整数来整体使用的,而是按照比特位的方式,将32个比特位进行划分,只需要学习低16位
- 这也是上面为什么会写成status & 0x7F的原因
那6个程序替换的系统接口,具体使用那个看实际情况
- 没有p就需要带路径
- 有l,就是列表式传命令
- 有v就是数组式传命令
- 有e就需要传自己设置的环境变量
1.1.4 测试运行模块
- 虽然运行模块已经能正常运行了,但是万一code.cpp是恶意程序了,比如死循环,不停消耗CPU资源 , 所以还需要进一步的资源约束
1.1.5 添加资源限制(setrlimit)
- 资源不足,导致OS终止进程,是通过信号终止的
- 为了方便上层调用,我直接在Run函数中增加了cpu_limit和mem_limit形参
这个项目走到这里就需要编写compile_run.hpp,将编译和运行的逻辑连接在一起,且code.cpp需要被处理的源文件,不应该是我们自己添加的,而是需要再客户端中导入的
1.1.6 编译 && 运行功能 (compile_run.hpp)
这个模块要做的是:a)适配用户请求,引入json定制通信协议字段b)形成唯一文件名c)正确调用compile and run
在centos中安装: sudo yum install json-c-devel
头文件 #include <jsoncpp/json/json.h>
- 注意: 在编译引入了json的文件,需要加上-ljsoncpp
- 虽然这个code就是文件名了,但client可能会提交大量的代码,所以内部就会需要形成唯一的文件名(待完善)
- 还有很多个出错问题怎么解决(待完善)
complie_run.hpp
#pragma once#include "compiler.hpp"
#include "runner.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include <jsoncpp/json/json.h>namespace ns_compile_and_run
{using namespace ns_log;using namespace ns_util;using namespace ns_compiler;using namespace ns_runner;// in_json: {"code": "#include...", "input": "","cpu_limit":1, "mem_limit":10240}// out_json: {"status":"0", "reason":"","stdout":"","stderr":"",}static void Start(const std::string &in_json,std::string *out_json){// step1: 反序列化过程Json::Value in_value;Json::Reader reader;// 把in_json中的数据写到in_value中reader.parse(in_json,in_value);// 最后再处理差错问题std::string code = in_value["code"].asString();std::string input = in_value["input"].asString();int cpu_limit = in_value["cpu_limit"].asInt();int mem_limit = in_value["mem_limit"].asInt();Json::Value out_value;int status_code = 0;int run_result = 0;std::string file_name;// 唯一文件名if(code.size() == 0){status_code = -1;// 代码为空goto END;}// 形成的文件名只居有唯一性,没有目录没有后缀// 使用: 毫秒级时间戳 + 原子性递增唯一值 : 来保证唯一性file_name = FileUtil::UniqFileName();// 形成临时的src文件if(!FileUtil::WriteFile(PathUtil::Src(file_name),code)){status_code = -2;// 未知错误goto END;}if(!Compiler::Compile(file_name)){status_code = -3;// 编译错误goto END;}run_result = Runner::Run(file_name,cpu_limit,mem_limit);if(run_result < 0){// runnem模块内部错误status_code = -2;// 未知错误}else if(run_result > 0){// 程序运行崩溃status_code = run_result;// 这里的run_result是信号}else{// 运行成功status_code = 0;}END:out_value["status"] = status_code;out_value["reason"] = CodeToDest(status_code,file_name);// 得到错误信息字符串if(status_code == 0){// 整个过程全部成功std::string _stdout;FileUtil::ReadFile(PathUtil::Stdout(file_name),&_stdout,true);out_value["stdout"] = _stdout;std::string _stderr;FileUtil::ReadFile(PathUtil::Stdout(file_name),&_stderr,true);out_value["stdout"] = _stdout;}// step2: 序列化Json::StyledWriter writer;*out_json = writer.write(out_value);}}
1.1.7 基于compile_run.hpp对util.hpp的补充
- 注意引入流时需要引入头文件: #include <fstream>
-
getline:不保存行分割符,有些时候需要保留\n,getline内部重载了强制类型转化
1.1.8 测试编译运行模块
#include "compile_run.hpp"
using namespace ns_compile_and_run;int main()
{std::string in_json;Json::Value in_value;// R"()", raw stringin_value["code"] = R"(#include<iostream>int main(){std::cout << "你可以看见我了" << std::endl;return 0;})";in_value["input"] = "";in_value["cpu_limit"] = 1;in_value["mem_limit"] = 10240 * 3;Json::FastWriter writer;in_json = writer.write(in_value);// std::cout << in_json << std::endl;// 这个是将来给客户端返回的json串std::string out_json;CompileAndRun::Start(in_json, &out_json);std::cout << out_json << std::endl;return 0;
}
- 实际上这里的代码应该是client自动提交给我们的,我们直接使用第三方库就行了
- 待优化: 可以把临时生成的这些文件都清理掉,
1.1.9 清理临时文件
- 这个函数直接放在compile_server.cc中的start函数的最后,清理临时文件
1.1.10 引入cpp-httplib 网络库
下载地址: cpp-httplib: C++ http 网络库 - Gitee.com
- 这个就是别人写好的网络库,我们直接使用就行了
1.1.11 更新gcc
安装scl : sudo yum install centos-release-scl scl-utils-build
安装新版本gcc: sudo yum install - y devtoolset - 9 - gcc devtoolset - 9 - gcc - c ++
- 把 scl enable devtoolset-9 bash 放在 ~/.bash_profile中
- 想每次登陆的时候,都是较新的gcc
如果不更新在使用cpp-httplib时可能会报错, 用老的编译器,要么编译不通过,要么直接运行报错
1.1.12 测试cpp-httplib网络库
- 可能会出现服务器的公网ip无法访问的问题,可以试试把防火墙关闭,并打开端口
1.1.13 将compiler_server打包成网络服务
compiler_server.cc
#include "compile_run.hpp"
#include "../comm/httplib.h"// 引入using namespace ns_compile_and_run;
using namespace httplib;// 引入void Usage(std::string proc){std::cerr << "Usage: " << "\n\t" << proc << " port" << std::endl;
}//./copile_server port
int main(int argc,char *argv[])
{if(argc != 2){Usage(argv[0]);return 1;}Server svr;svr.Post("/compile_and_run",[](const Request&req,Response & resp){// 用户请求的服务正文是我们想要的json stringstd::string in_json = req.body;std::string out_json;if(!in_json.empty()){CompileAndRun::Start(in_json,&out_json);resp.set_content(out_json,"application/json;charset=uft-8");}});svr.listen("0.0.0.0",atoi(argv[1]));return 0;
}
- 由于我这里没有写客户端代码,则这里暂时不好测试,不过可以借助第三方工具进行测试
2. oj_server服务设计
1. 获取首页,用题目列表充当2. 编辑区域页面3. 提交判题功能(编译并运行)
2.1 书写makefile文件
- 随着后续代码的跟进,并不断引入第三方库,这里还会新增编译选项
2.2 服务路由功能(oj_server.cc)
- 为用户实现的路由功能就3个 a. 获取所有的题目列表 b.根据题目编号,获取题目内容 c.判断用户提交的代码
2.3 MVC 结构的oj 服务设计(M)
Model , 通常是和数据交互的模块 ,比如,对题库进行增删改查(文件版, MySQL )
2.3.1 安装boost库 && 字符切分功能
sudo yum install -y boost-devel //是boost 开发库
- 第一个参数为缓冲区,第二个参数为被分割的字符串
- 第三个参数为分割符,第四个参数为是否压缩
- 要压缩: 当sep = "空格"时,sepsepsep -> 空格
- 不压缩: 当sep = "空格"时,sepsepsep -> 空格空格空格
2.3.2 数据结构
header.cpp
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>using namespace std;class Solution{public:bool isPalindrome(int x){//将你的代码写在下面return true;}
};
tail.cpp
#ifndef COMPILER_ONLINE
#include "header.cpp"
#endif// 这里先把测试用例 暴露出来
void Test1()
{// 通过定义临时对象,来完成方法的调用bool ret = Solution().isPalindrome(121);if(ret){std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;}else{std::cout << "没有通过用例1, 测试的值是: 121" << std::endl;}
}void Test2()
{// 通过定义临时对象,来完成方法的调用bool ret = Solution().isPalindrome(-10);if(!ret){std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;}else{std::cout << "没有通过用例2, 测试的值是: -10" << std::endl;}
}int main()
{Test1();Test2();return 0;
}
- des.txt表示题目信息
- header.cpp表示预设代码
- tail.cpp表示测试用例
- 真正代码 = 用户在head.cpp中的代码 + header.cpp + tail.cpp 并去到COMPILER_ONLINE
- 这个条件编译只是为了编写tail.cpp时不报错
2.3.3 model功能(oj_model.cpp)
数据交互 && 提供接口
#pragma once
// 文件版本
#include "../comm/util.hpp"
#include "../comm/log.hpp"
#include <unordered_map>
#include <cassert>
#include <vector>namespace ns_model
{using namespace std;using namespace ns_log;using namespace ns_util;struct Question{string number;// 题目编号,唯一string tile;// 题目标题string star;// 难度: 简单 中等 困难int cpu_limit;// 题目的时间复杂度(S)int mem_limit;// 题目的空间复杂度(KB)string desc;// 题目描述string header; // 题目预设给用户在线编辑器的代码string tail;// 题目测试用例,需要和header拼接}; const string questions_list = "./question/quetions.list";const string questions_path = "./question";class Model{public:Model(){// 加载所有题目:底层是用hash表映射的assert(LoadQuestionList(questions_list));}~Model(){;}// 获取所有题目,这里的out是输出型参数bool GetAllQuestions(vector<Question>*out){if(questions.size() == 0){LOG(ERROR) << "用户获取题库失败" << "\n";return false;}for(const auto&q: questions){out->push_back(q.second);}return true;}// 获取指定题目,这里的q是输出型参数bool GetOneQuestion(const string& number,Question* q){const auto& iter = questions.find(number);if(iter == questions.end()){LOG(ERROR) << "用户获取题目失败,题目编号: " << number << "\n";return false;}(*q) = iter->second;return true;}// 加载配置文件: questions/questions.list + 题目编号文件bool LoadQuestionList(const string&question_list){// 加载配置文件: questions/questions.list +题目编号文件ifstream in(question_list);if(!in.is_open()){LOG(FATAL) << "加载题库失败,请检查是否存在题库文件" << "\n";return false;}string line;while(getline(in,line)){vector<string>tokens;StringUtil::SplitString(line,&tokens," ");// 被分割的字符串 缓冲区 分割符// eg: 1 判断回文数 简单 1 30000if(tokens.size()!=5){LOG(WARNING) << "加载部分题目失败,请检查文件格式" << "\n";continue;}Question q;q.number = tokens[0];q.tile = tokens[1];q.star = tokens[2];q.cpu_limit = atoi(tokens[3].c_str());q.mem_limit = atoi(tokens[4].c_str());string path = questions_list;path += q.number;path += "/";// 第三个参数代表 是否加上 \nFileUtil::ReadFile(path+"desc.txt",&(q.desc),true);FileUtil::ReadFile(path+"header.cpp",&(q.header),true);FileUtil::ReadFile(path+"tail.txt",&(q.tail),true);questions.insert({q.number,q});// 录题成功}LOG(INFO) << "加载题库...成功" << "\n";in.close();}private:// 题号 : 题目细节unordered_map<string,Question> questions;};
}
2.4 MVC 结构的oj 服务设计(C)
2.4.1 负载均衡模块
namespace ns_control
{using namespace std;using namespace ns_log;using namespace ns_util;using namespace ns_model;using namespace ns_view;using namespace httplib;// 提供服务的主机class Machine{public:std::string ip; //编译服务的ipint port; //编译服务的portuint64_t load; //编译服务的负载std::mutex *mtx; // mutex禁止拷贝的,使用指针public:Machine() : ip(""), port(0), load(0), mtx(nullptr){}~Machine(){}public:// 提升主机负载void IncLoad(){if (mtx) mtx->lock();++load;if (mtx) mtx->unlock();}// 减少主机负载void DecLoad(){if (mtx) mtx->lock();--load;if (mtx) mtx->unlock();}void ResetLoad(){if(mtx) mtx->lock();load = 0;if(mtx) mtx->unlock();}// 获取主机负载,没有太大的意义,只是为了统一接口uint64_t Load(){uint64_t _load = 0;if (mtx) mtx->lock();_load = load;if (mtx) mtx->unlock();return _load;}};const std::string service_machine = "./conf/service_machine.conf";class LoadBlance{private:// 可以给我们提供编译服务的所有的主机// 每一台主机都有自己的下标,充当当前主机的idstd::vector<Machine> machines;// 所有在线的主机idstd::vector<int> online;// 所有离线的主机idstd::vector<int> offline;// 保证LoadBlance它的数据安全std::mutex mtx;public:LoadBlance(){assert(LoadConf(service_machine));LOG(INFO) << "加载 " << service_machine << " 成功"<< "\n";}~LoadBlance(){}public: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<std::string> 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;}// id: 输出型参数// m : 输出型参数bool SmartChoice(int *id, Machine **m){// 1. 使用选择好的主机(更新该主机的负载)// 2. 我们需要可能离线该主机mtx.lock();// 负载均衡的算法// 1. 随机数+hash// 2. 轮询+hashint 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 = 1; i < online_num; i++){uint64_t curr_load = machines[online[i]].Load();if (min_load > curr_load){min_load = curr_load;*id = online[i];*m = &machines[online[i]];}}mtx.unlock();return true;}void OfflineMachine(int which){mtx.lock();for(auto iter = online.begin(); iter != online.end(); iter++){if(*iter == which){machines[which].ResetLoad();//要离线的主机已经找到啦online.erase(iter);offline.push_back(which);break; //因为break的存在,所有我们暂时不考虑迭代器失效的问题}}mtx.unlock();}void OnlineMachine(){//我们统一上线,后面统一解决mtx.lock();online.insert(online.end(), offline.begin(), offline.end());offline.erase(offline.begin(), offline.end());mtx.unlock();LOG(INFO) << "所有的主机有上线啦!" << "\n";}//for testvoid 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();}};
}
2.4.1 control功能(oj_control.hpp)
逻辑控制模块
// 这是我们的核心业务逻辑的控制器class Control{private:Model model_; //提供后台数据View view_; //提供html渲染功能LoadBlance load_blance_; //核心负载均衡器public:Control(){}~Control(){}public:void RecoveryMachine(){load_blance_.OnlineMachine();}//根据题目数据构建网页// html: 输出型参数bool AllQuestions(string *html){bool ret = true;vector<struct Question> all;if (model_.GetAllQuestions(&all)){sort(all.begin(), all.end(), [](const struct Question &q1, const struct Question &q2){return atoi(q1.number.c_str()) < atoi(q2.number.c_str());});// 获取题目信息成功,将所有的题目数据构建成网页// ...}else{*html = "获取题目失败, 形成题目列表失败";ret = false;}return ret;}bool Question(const string &number, string *html){bool ret = true;struct Question q;if (model_.GetOneQuestion(number, &q)){// 获取指定题目信息成功,将所有的题目数据构建成网页// ....}else{*html = "指定题目: " + number + " 不存在!";ret = false;}return ret;}// code: #include...// input: ""void Judge(const std::string &number, const std::string in_json, std::string *out_json){}};
- control模块中的判题功能,我打算最后设计
2.5 MVC 结构的oj 服务设计(V)
2.4 安装与测试 ctemplate(网页渲染)
渲染本质就是key-value之间的替换
- 安装镜像源: git clone https://gitee.com/mirrors_OlafvdSpek/ctemplate.git
- . / autogen . sh
- . / configure
- make // 编译 如果报错请 更新gcc
- make install
test.cpp
#include <iostream>
#include <string>
#include <ctemplate/template.h>
int main()
{std::string html = "./test.html";std::string html_info = "测试ctemplate渲染";// 建立ctemplate参数目录结构ctemplate::TemplateDictionary root("test"); // unordered_map<string,string> test;// 向结构中添加你要替换的数据,kv的root.SetValue("info", html_info); // test.insert({key, value});// 获取被渲染对象// DO_NOT_STRIP:保持html网页原貌ctemplate::Template *tpl = ctemplate::Template::GetTemplate(html,ctemplate::DO_NOT_STRIP); // 开始渲染,返回新的网页结果到out_htmlstd::string out_html;tpl->Expand(&out_html, &root);std::cout << "渲染的带参html是:" << std::endl;std::cout << out_html << std::endl;return 0;
}
test.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><body><p>{{info}}</p><p>{{info}}</p><p>{{info}}</p><p>{{info}}</p>
</body></html>
错误原因: error while loading shared libraries: libmpc.so.3: cannot open shared object file
export LD\_LIBRARY\_PATH=$LD\_LIBRARY\_PATH:/usr/local/lib
- 在命令行上输入 上面这段命令,注:只在当前会话中有效
# cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
# echo "/usr/local/lib" >> /etc/ld.so.conf
# ldconfig
2.5.2 渲染功能(oj_view.hpp)
#pragma once
#include <iostream>
#include <ctemplate/template.h>
#include "./oj_model.hpp"namespace ns_view
{using namespace ns_model;const std::string template_path = "./template_html/";class View{public:View(){}~View(){}// 渲染所有题目void ALLExpandHtml(const vector<struct Question>&question,std::string *html){// 题目编号 题目标题 题难度// 推荐表格实现// 1.形成路径string src_html = template_path + "all_quetions.html";// 2.形成数字典ctemplate::TemplateDictionary root("all_question");for(const auto& q: question){ctemplate::TemplateDictionary *sub = root.AddSectionDictionary("question_list");sub->SetValue("number",q.number);sub->SetValue("title",q.title);sub->SetValue("star",q.star);}// 3. 获取被渲染的htmlctemplate::Template*tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);// 4.开始完成渲染功能tpl->Expand(html,&root);}// 渲染一道题目void OneExpandHtml(const struct Question &q,string *html){// 1.形成路径std::string src_html = template_path + "one_question.html";// 2. 形成数字典ctemplate::TemplateDictionary root("one_question");root.SetValue("number",q.number);root.SetValue("title",q.title);root.SetValue("star",q.star);root.SetValue("desc",q.desc);root.SetValue("header",q.header);// 3.获取被渲染的htmlctemplate::Template*tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);// 4.开始完成渲染功能tpl->Expand(html,&root);}};
}
2.6 联动MVC模块并测试
oj_server.cc
#include <iostream>
#include "../comm/httplib.h"// 引入
#include "oj_control.hpp"using namespace httplib;// 引入
using namespace ns_control;int main()
{// 用户请求的服务器路由功能Server svr;Control ctrl;// 获取所有的题目列表svr.Get("/all_questions",[&ctrl](const Request&req,Response &resp){// 返回一张包含所有题目的html网页std::string html;// 待处理ctrl.AllQuestions(&html);resp.set_content(html,"text/html;charset=utf-8");});// 根据题目编号,获取题目内容// \d+ 是正则表达式的特殊符合svr.Get(R"(/question/(\d+))",[&ctrl](const Request&req,Response &resp){std::string number = req.matches[1];std::string html;ctrl.Question(number,&html);resp.set_content(html,"text/html;charset=utf-8");});// 判断用户提交的代码(1.每道题c测试用例,2.compile_and_run)svr.Post(R"(/judge/(\d+))",[&ctrl](const Request&req,Response &resp){std::string number = req.matches[1];std::string result_json;ctrl.Judge(number,req.body,&result_json);resp.set_content(result_json,"application/json;charset=utf-8");});svr.set_base_dir("./wwwroot");svr.listen("0.0.0.0",8080);return 0;
}
- 这里的前端都是提前做好了的,我们可以不关心前端;
- control功能还有个判题功能没有实现
2.7 完善oj_control.hpp中的判题功能
void Judge(const std::string &number, const std::string in_json, std::string *out_json){// LOG(DEBUG) << in_json << " \nnumber:" << number << "\n";// 0. 根据题目编号,直接拿到对应的题目细节struct Question q;model_.GetOneQuestion(number, &q);// 1. in_json进行反序列化,得到题目的id,得到用户提交源代码,inputJson::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 + "\n" + 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;}// 4. 然后发起http请求,得到结果Client cli(m->ip, m->port);m->IncLoad();LOG(INFO) << " 选择主机成功, 主机id: " << id << " 详情: " << m->ip << ":" << m->port << " 当前主机的负载是: " << m->Load() << "\n";if(auto res = cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8")){// 5. 将结果赋值给out_jsonif(res->status == 200){*out_json = res->body;m->DecLoad();LOG(INFO) << "请求编译和运行服务成功..." << "\n";break;}m->DecLoad();}else{//请求失败LOG(ERROR) << " 当前请求的主机id: " << id << " 详情: " << m->ip << ":" << m->port << " 可能已经离线"<< "\n";load_blance_.OfflineMachine(id);load_blance_.ShowMachines(); //仅仅是为了用来调试}}
2.8 测试oj_server服务
- 在编译时需要加上-D COMPILER_ONLINE条件编译,
- 设计到前端网页,下面会有提及
2.9 一个BUG
- 把tail.txt改成tail.cpp,不然后面无法进行代码拼接
3. 前端页面设计(了解)
3.1 index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>这是我的个人OJ系统</title><style>/* 起手式, 100%保证我们的样式设置可以不受默认影响 */* {/* 消除网页的默认外边距 */margin: 0px;/* 消除网页的默认内边距 */padding: 0px;}html,body {width: 100%;height: 100%;}.container .navbar {width: 100%;height: 50px;background-color: black;/* 给父级标签设置overflow,取消后续float带来的影响 */overflow: hidden;}.container .navbar a {/* 设置a标签是行内块元素,允许你设置宽度 */display: inline-block;/* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */width: 80px;/* 设置字体颜色 */color: white;/* 设置字体的大小 */font-size: large;/* 设置文字的高度和导航栏一样的高度 */line-height: 50px;/* 去掉a标签的下划线 */text-decoration: none;/* 设置a标签中的文字居中 */text-align: center;}/* 设置鼠标事件 */.container .navbar a:hover {background-color: green;}.container .navbar .login {float: right;}.container .content {/* 设置标签的宽度 */width: 800px;/* 用来调试 *//* background-color: #ccc; *//* 整体居中 */margin: 0px auto;/* 设置文字居中 */text-align: center;/* 设置上外边距 */margin-top: 200px;}.container .content .font_ {/* 设置标签为块级元素,独占一行,可以设置高度宽度等属性 */display: block;/* 设置每个文字的上外边距 */margin-top: 20px;/* 去掉a标签的下划线 */text-decoration: none;/* 设置字体大小font-size: larger; */}</style>
</head><body><div class="container"><!-- 导航栏, 功能不实现--><div class="navbar"><a href="/">首页</a><a href="/all_questions">题库</a><a href="#">竞赛</a><a href="#">讨论</a><a href="#">求职</a><a class="login" href="#">登录</a></div><!-- 网页的内容 --><div class="content"><h1 class="font_">欢迎来到我的OnlineJudge平台</h1><p class="font_">这个我个人独立开发的一个在线OJ平台</p><a class="font_" href="/all_questions">点击我开始编程啦!</a></div></div>
</body></html>
3.2 all_questions.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>在线OJ-题目列表</title><style>/* 起手式, 100%保证我们的样式设置可以不受默认影响 */* {/* 消除网页的默认外边距 */margin: 0px;/* 消除网页的默认内边距 */padding: 0px;}html,body {width: 100%;height: 100%;}.container .navbar {width: 100%;height: 50px;background-color: black;/* 给父级标签设置overflow,取消后续float带来的影响 */overflow: hidden;}.container .navbar a {/* 设置a标签是行内块元素,允许你设置宽度 */display: inline-block;/* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */width: 80px;/* 设置字体颜色 */color: white;/* 设置字体的大小 */font-size: large;/* 设置文字的高度和导航栏一样的高度 */line-height: 50px;/* 去掉a标签的下划线 */text-decoration: none;/* 设置a标签中的文字居中 */text-align: center;}/* 设置鼠标事件 */.container .navbar a:hover {background-color: green;}.container .navbar .login {float: right;}.container .question_list {padding-top: 50px;width: 800px;height: 100%;margin: 0px auto;/* background-color: #ccc; */text-align: center;}.container .question_list table {width: 100%;font-size: large;font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;margin-top: 50px;background-color: rgb(243, 248, 246);}.container .question_list h1 {color: green;}.container .question_list table .item {width: 100px;height: 40px;font-size: large;font-family: 'Times New Roman', Times, serif;}.container .question_list table .item a {text-decoration: none;color: black;}.container .question_list table .item a:hover {color: blue;text-decoration: underline;}.container .footer {width: 100%;height: 50px;text-align: center;line-height: 50px;color: #ccc;margin-top: 15px;}</style>
</head><body><div class="container"><!-- 导航栏, 功能不实现--><div class="navbar"><a href="/">首页</a><a href="/all_questions">题库</a><a href="#">竞赛</a><a href="#">讨论</a><a href="#">求职</a><a class="login" href="#">登录</a></div><div class="question_list"><h1>OnlineJuge题目列表</h1><table><tr><th class="item">编号</th><th class="item">标题</th><th class="item">难度</th></tr>{{#question_list}}<tr><td class="item">{{number}}</td><td class="item"><a href="/question/{{number}}">{{title}}</a></td><td class="item">{{star}}</td></tr>{{/question_list}}</table></div><div class="footer"><!-- <hr> --><h4>@lyc</h4></div></div></body></html>
3.3 one_questions.html(ACE插件&&JQuery&&ajax)
<!-- 引入ACE插件 --><!-- 引入ACE CDN --><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/ext-language_tools.js" type="text/javascript"charset="utf-8"></script><!-- 引入jquery CDN --><script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
- ACE插件是一个 编写代码的编译框
- 收集当前页面的有关数据 , a. 题号 a. 代码 , 我们采用 JQuery 来进行获取 html 中的内容
- 构建json,并通过 ajax向后台 发起基于http的json请求
全部代码
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{{number}}.{{title}}</title><!-- 引入ACE插件 --><!-- 引入ACE CDN --><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/ext-language_tools.js" type="text/javascript"charset="utf-8"></script><!-- 引入jquery CDN --><script src="http://code.jquery.com/jquery-2.1.1.min.js"></script><style>* {margin: 0;padding: 0;}html,body {width: 100%;height: 100%;}.container .navbar {width: 100%;height: 50px;background-color: black;/* 给父级标签设置overflow,取消后续float带来的影响 */overflow: hidden;}.container .navbar a {/* 设置a标签是行内块元素,允许你设置宽度 */display: inline-block;/* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */width: 80px;/* 设置字体颜色 */color: white;/* 设置字体的大小 */font-size: large;/* 设置文字的高度和导航栏一样的高度 */line-height: 50px;/* 去掉a标签的下划线 */text-decoration: none;/* 设置a标签中的文字居中 */text-align: center;}/* 设置鼠标事件 */.container .navbar a:hover {background-color: green;}.container .navbar .login {float: right;}.container .part1 {width: 100%;height: 600px;overflow: hidden;}.container .part1 .left_desc {width: 50%;height: 600px;float: left;overflow: scroll;}.container .part1 .left_desc h3 {padding-top: 10px;padding-left: 10px;}.container .part1 .left_desc pre {padding-top: 10px;padding-left: 10px;font-size: medium;font-family:'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;}.container .part1 .right_code {width: 50%;float: right;}.container .part1 .right_code .ace_editor {height: 600px;}.container .part2 {width: 100%;overflow: hidden;}.container .part2 .result {width: 300px;float: left;}.container .part2 .btn-submit {width: 120px;height: 50px;font-size: large;float: right;background-color: #26bb9c;color: #FFF;/* 给按钮带上圆角 *//* border-radius: 1ch; */border: 0px;margin-top: 10px;margin-right: 10px;}.container .part2 button:hover {color:green;}.container .part2 .result {margin-top: 15px;margin-left: 15px;}.container .part2 .result pre {font-size: large;}</style>
</head><body><div class="container"><!-- 导航栏, 功能不实现--><div class="navbar"><a href="/">首页</a><a href="/all_questions">题库</a><a href="#">竞赛</a><a href="#">讨论</a><a href="#">求职</a><a class="login" href="#">登录</a></div><!-- 左右呈现,题目描述和预设代码 --><div class="part1"><div class="left_desc"><h3><span id="number">{{number}}</span>.{{title}}_{{star}}</h3><pre>{{desc}}</pre></div><div class="right_code"><pre id="code" class="ace_editor"><textarea class="ace_text-input">{{pre_code}}</textarea></pre></div></div><!-- 提交并且得到结果,并显示 --><div class="part2"><div class="result"></div><button class="btn-submit" onclick="submit()">提交代码</button></div></div><script>//初始化对象editor = ace.edit("code");//设置风格和语言(更多风格和语言,请到github上相应目录查看)// 主题大全:http://www.manongjc.com/detail/25-cfpdrwkkivkikmk.htmleditor.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});function submit(){// alert("嘿嘿!");// 1. 收集当前页面的有关数据, 1. 题号 2.代码var code = editor.getSession().getValue();// console.log(code);var number = $(".container .part1 .left_desc h3 #number").text();// console.log(number);var judge_url = "/judge/" + number;// console.log(judge_url);// 2. 构建json,并通过ajax向后台发起基于http的json请求$.ajax({method: 'Post', // 向后端发起请求的方式url: judge_url, // 向后端指定的url发起请求dataType: 'json', // 告知server,我需要什么格式contentType: 'application/json;charset=utf-8', // 告知server,我给你的是什么格式data: JSON.stringify({'code':code,'input': ''}),success: function(data){//成功得到结果// console.log(data);show_result(data);}});// 3. 得到结果,解析并显示到 result中function show_result(data){// console.log(data.status);// console.log(data.reason);// 拿到result结果标签var result_div = $(".container .part2 .result");// 清空上一次的运行结果result_div.empty();// 首先拿到结果的状态码和原因结果var _status = data.status;var _reason = data.reason;var reason_lable = $( "<p>",{text: _reason});reason_lable.appendTo(result_div);if(status == 0){// 请求是成功的,编译运行过程没出问题,但是结果是否通过看测试用例的结果var _stdout = data.stdout;var _stderr = data.stderr;var stdout_lable = $("<pre>", {text: _stdout});var stderr_lable = $("<pre>", {text: _stderr})stdout_lable.appendTo(result_div);stderr_lable.appendTo(result_div);}else{// 编译运行出错,do nothing}}}</script>
</body></html>
3.4 相关测试
4. MySQL版题目设计
4.1 注册用户 && 赋予权限
- create user 'oj_client'@'localhost' identified by '123456';
- create database oj;
- grant select on oj.* to 'oj_client'@'localhost';
- select user,Host from user;
4.2 下载第三方工具-workbench
- 下载下来之后,就不断的下一步,下一步就行了
4.3 录题到mysql中
use oj;
drop table if exists oj_table;create table if not exists oj_table(_number varchar(200) comment '题目编号',_titie varchar(200) comment '题目标题',_start varchar(200) comment '题目简单中等困难',_desc varchar(2000) comment '题目描述',_header varchar(2000) comment '题目预设',_tail varchar(2000) comment '题目测试用例',_cpu_limit int comment '时间要求',_mem_limt int comment '空间要求'
);insert into oj_table values(1,'判断回文数','简单','判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。示例 1:输入: 121输出: true示例 2:输入: -121输出: false解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。示例 3:输入: 10输出: false解释: 从右向左读, 为 01 。因此它不是一个回文数。进阶:你能不将整数转为字符串来解决这个问题吗?','#include <iostream>#include <string>#include <vector>#include <map>#include <algorithm>using namespace std;class Solution{public:bool isPalindrome(int x){//将你的代码写在下面return true;}};','#ifndef COMPILER_ONLINE#include "header.cpp"#endifvoid Test1(){// 通过定义临时对象,来完成方法的调用bool ret = Solution().isPalindrome(121);if(ret){std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;}else{std::cout << "没有通过用例1, 测试的值是: 121" << std::endl;}}void Test2(){// 通过定义临时对象,来完成方法的调用bool ret = Solution().isPalindrome(-10);if(!ret){std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;}else{std::cout << "没有通过用例2, 测试的值是: -10" << std::endl;}}int main(){Test1();Test2();return 0;}',1,30000
);select * from oj_table;
- 这里我只录入了一道题为了测试
4.4 下载并引入mysql库文件
MySQL :: Download MySQL Community Server
要使用C/C++连接MySQL,需要使用MySQL官网提供的库
下载完毕后需要将其上传到云服务器,这里将下载的库文件存放在下面的目录:
然后使用tar命令将压缩包解压到当前目录下:
xz -d mysql-8.0.37-linux-glibc2.28-i686.tar.xz
tar xvf mysql-8.0.37-linux-glibc2.28-i686.tar
进入解压后的目录当中,可以看到有一个include子目录和一个lib子目录,其中,include目录下存放的一堆头文件。而lib64目录下存放的就是动静态库。
然后在我们的项目中建立软连接
4.5 一个BUG
-
如果你当时下载myql把mysql-devel也下载了,不需要进行上面步骤
-
这种引入第三方库的操作,可能会因为版本不兼容,而导致出错
skipping incompatible ./lib/libmysqlclient.so when searching for -lmysqlclient - 建议直接安装: yum -y install mysql-devel
4.5 重新设计oj_model
因为oj_model模块是管理数据,提供接口的模块,所以要把这个项目变成mysql就需要重新设计
#pragma once
// 文件版本
#include "../comm/util.hpp"
#include "../comm/log.hpp"
#include <unordered_map>
#include <cassert>
#include <vector>#include "./include/mysql.h"namespace ns_model
{using namespace std;using namespace ns_log;using namespace ns_util;struct Question{string number;// 题目编号,唯一string title;// 题目标题string star;// 难度: 简单 中等 困难int cpu_limit;// 题目的时间复杂度(S)int mem_limit;// 题目的空间复杂度(KB)string desc;// 题目描述string header; // 题目预设给用户在线编辑器的代码string tail;// 题目测试用例,需要和header拼接}; const std::string oj_questions = "oj_table";const std::string host = "127.0.0.1";const std::string user = "oj_client";const std::string passwd = "123456";const std::string db = "oj";const int port = 3306;class Model{public:Model(){}~Model(){;}bool QueryMysql(const std::string &sql,vector<Question>*out){// 这里的out是输出型参数// 创建mysql句柄MYSQL *my = mysql_init(nullptr);// 连接数据库if(nullptr == mysql_real_connect(my,host.c_str(),user.c_str(),passwd.c_str(),db.c_str(),port,nullptr,0)){LOG(FATAL) << "连接数据库失败!" << "\n";return false;}// 一定要设置该链接的编码格式,要不然会出现乱码的问题mysql_set_character_set(my,"utf8");LOG(INFO) << "连接数据库成功!" << "\n";// 执行sql语句if(0 != mysql_query(my,sql.c_str())){LOG(WARNING) << sql << " execute error! " << "\n";return false;}// 提取结果MYSQL_RES *res = mysql_store_result(my);// 本质就是一个2级指针// 分析结果int rows = mysql_num_rows(res);// 获取行的数量int cols = mysql_num_fields(res);// 获取列的数量Question q;for(int i = 0;i < rows;i++){MYSQL_ROW row = mysql_fetch_row(res);q.number = row[0];q.title = row[1];q.star = row[2];q.desc = row[3];q.header = row[4];q.tail = row[5];q.cpu_limit = atoi(row[6]);q.mem_limit = atoi(row[7]);out->push_back(q);}// 释放控件free(res);// 关闭mysql连接mysql_close(my);return true;}// 获取所有题目,这里的out是输出型参数bool GetAllQuestions(vector<Question>*out){std::string sql = "select * from ";sql += oj_questions;return QueryMysql(sql,out);}// 获取指定题目,这里的q是输出型参数bool GetOneQuestion(const string& number,Question* q){bool res = false;std::string sql = "select * from ";sql += oj_questions;sql += " where number=";sql += number;vector<Question> result;if(QueryMysql(sql,&result)){if(result.size() == 1){*q = result[0];res = true;}}return res;}private:// 题号 : 题目细节unordered_map<string,Question> questions;};
}
- mysql_init: 创建mysql句柄
- mysql_real_connect: 创建mysql连接
-
mysql_query: 发起mysql请求
-
mysql_close: 关闭mysql连接
4.6 相关测试
- 编译期间告诉编译器头文件和库文件在哪里 -I指明搜索的头文件,-L指明搜索的lib
- 并加上-lmysqlclient
5. 扩展
- 功能上更完善一下,判断一道题目正确之后,自动下一道题目
- 基于注册和登陆的录题功能.....
6. 完整项目链接
projects/负载均衡/OnlineJudge at main · 1LYC/projects · GitHub
相关文章:
实战项目: 负载均衡
0. 前言 这个项目使用了前后端,实现一个丐版的LeetCode刷题网站,并根据每台主机的实际情况,选择对应的主机,负载均衡的调度 0.1 所用技术与开发环境 所用技术: C STL 标准库 Boost 准标准库 ( 字符串切割 ) cpp- httplib 第三方开源网络库 ctemplate 第三方开源前端网…...
运维监控系统
做监控系统集成,持续更新ing 1.Prometheus k8s安装prometheusdocker部署prometheusthanos实现prometheus高可用部署 2.Grafana docker安装grafanagrafana的admin密码忘记了grafana使用mysql远程存储 3.Alertmanager 4.Consul 5.夜莺系统 6.时序数据库 6.1 …...
第3章 Unity 3D着色器系统
3.1 从一个外观着色器程序谈起 新建名为basic_diffuse.shader的文件,被一个名为basic_diffuse.mat的材质文件所引用,而basic_diffuse.mat文件则被场景中名为Sphere的game object的MeshRenderer组件所使用。 basic_diffuse.shader代码文件的内容如下所示…...
Qt项目天气预报(1) - ui界面搭建
ui中部 效果演示 ui效果 显示效果 控件列表 配合右图查看 居中对齐-label 设置label居中对齐(别傻傻的空格对齐了) 间距配置 widget03 外围的widget对象: 包含label 和 widget0301,如下图 widget0301 内围的widget对象,如下图 样式表 widget03 …...
一、从C语言到C++(一)
一、从C语言到C(一) C介绍C语言和C的联系C介绍 头文件命名空间定义命名空间使用命名空间中的名称使用using声明或指令命名空间与C语言的对比给命名空间起别名注意事项std 标准输入输出std::endl使用std::cout进行输出使用std::cin进行输入格式化输出 C介…...
MySQL(5)
聚合函数 GROUP BY 的使用 需求:查询各个部门的平均工资,最高工资SELECT department_id,AVG(salary),SUM(salary)FROM employeesGROUP BY department_id;需求:查询各个job_id的平均工资SELECT job_id,AVG(salary)FROM employeesGROUP BY jo…...
区块链之快照
定义 区块链快照是区块链技术中一个非常重要的概念,它可以帮助区块链系统提高性能和数据管理效率。 什么是区块链快照 区块链快照是指在某个时间点对整个区块链的状态进行保存和备份的过程。 快照会记录区块链上所有账户的余额、合约状态等信息,并将其序列化存储起来。 这样…...
自学前端第一天
HTML标签 ’HTML‘全程是‘hypertext Markup langage(超文本标记语言) HTML通过一系列的’标签(也称为元素)‘来定义文本、图像、链接。HTML标签是由尖括号包围的关键字。 标签通常成对存在,包括开始标签和结束标签(也称为双标签…...
SQL Server几种琐
SQL Server 中的锁类型主要包括以下几种,它们用于控制并发访问和数据一致性: 1. 共享锁(Shared Lock,S 锁): - 用于读取操作(如 SELECT 语句)。 - 允许多个事务同时读取同一资…...
redis 一些笔记1
redis 一、redis事务二、管道2.1 事务与管道的区别 三、主从复制3.13.2 权限细节3.3 基本操作命令3.4 常用3.4.1 一主几从3.4.2 薪火相传3.4.3 反客为主 3.5 步骤3.6 缺点 一、redis事务 放在一个队列里,依次执行,并不保证一致性。与mysql事务不同。 命…...
【计网复习】应用层总结(不含HTTP和错题重点解析)
应用层总结(不含HTTP和错题重点解析) 应用层简介 应用层的主要功能常见的应用层协议小林对于应用层通常的解释 网络应用模型 客户端-服务器模型(Client-Server Model, C/S) 特点优点缺点应用场景 对等网络模型(Peer-to…...
carbondata连接数优化
一,背景 carbondata的入库采用arbonData Thrift Server方式提供,由于存在异常的入库segments但是显示状态是success,所以每天运行另一个博客中的脚本,出现连接超时,运行不正常,排查是每天连接数太多&#x…...
云和运维(SRE)的半生缘-深读实证02
这个标题不算太夸张,云计算和很多IT岗位都有缘,但是和运维(SRE)岗位的缘分最深。 “深读实证”系列文章都会结合一些外部事件,点明分析《云计算行业进阶指南》书中的内容。本次分享介绍了下列内容: 我以运维…...
java基础操作5——java自定义获取任意年、月、日的起始和结束时间
在实际项目开发过程中,获取任意时间的起始和结束时间是常用操作,尤其对于统计业务来说,更是必要操作,理解了时间自定义的规律,对于开发人员的效率提升是大有裨益的。 一.获取任意年的起始和结束时间 1.获取任意年的起…...
【Java04】引用变量数组初始化的内存机制
引用类型数组指向的元素也是引用。其本质是: 由一个在栈上的引用数组变量指向一块堆内存;这块堆内存里存储的元素是引用,又分别指向其他堆内存。 class Person // Person是一个自定义的类 {public int age;puiblic double height;public vo…...
基于JSP的足球赛会管理系统
你好呀,我是计算机学长猫哥!如果有相关需求,文末可以找到我的联系方式。 开发语言:Java 数据库:MySQL 技术:JSP技术 工具:IDEA/Eclipse、Navicat、Maven 系统展示 首页 个人中心 球队介绍…...
博客摘录「 AXI三种接口及DMA DDR XDMA介绍(应用于vivado中的ip调用)」2024年6月10日
关键要点: 1.AXI Stream经过协议转换可使用AXI_FULL(PS与PL间的接口,如GP、HP和ACP)。 2.传输数据类里就涉及一个握手协议,即在主从双方数据通信前,有一个握手的过程。基本内容:数据的传输源会…...
Bigtable: A Distributed Storage System for Structured Data
2003年USENIX,出自谷歌,开启分布式大数据时代的三篇论文之一,底层依赖 GFS 存储,上层供 MapReduce 查询使用 Abstract 是一种分布式结构化数据存储管理系统,存储量级是PB级别。存储的数据类型和延时要求差异都很大。…...
RAG下的prompt编写探索
针对特定领域的回答,编写抽象的prompt需要在细节和灵活性之间找到平衡。我们需要一个既能涵盖普遍步骤又能适应不同问题的框架。以下是如何在这种情况下编写抽象prompt的方法,以及适用于各种技术领域的通用策略。 一、编写抽象Prompt的通用策略 定义用户问题和背景信息: 明…...
【计算机组成原理】指令系统考研真题详解之拓展操作码!
计算机组成原理:指令系统概述与深入解析 1. 指令系统概述 计算机软硬件界面的概念 在计算机组成原理中,指令系统扮演着至关重要的角色,它是计算机软硬件界面的核心。软件通过指令与硬件进行通信,硬件根据指令执行相应的操作。指…...
北航第六次数据结构与程序设计作业(查找与排序)选填题
一、 顺序查找的平均查找长度ASL(1 2 …… n)/ n (n 1)/ 2 二、 这半查找法的平均查找次数和判定树的深度有关系。若查找一个不存在的元素,说明进行了深度次比较。 注意,判定树不是满二叉树,因此深…...
Optional详解和常用API
目录 一、Optional简介 二、构建Optional对象三种方式 2.1 Optional.of(value) 2.1.1 使用案例 2.2 Optional.ofNullable(value) 2.2.1 使用案例 2.3 Optional.empty() 2.3.1 使用案例 三、Optional常用的api解析和使用案例 3.1 isPresent 3.1.1 使用案例 3.2 ifPrese…...
Unity 3D 物体的Inspector面板
1、Transform:位置、旋转、大小 2、Mesh Filter:物体的形状 3、Mesh Renderer:物体渲染(物体的衣服) 4、Collider:碰撞体...
闪烁与常亮的符号状态判断机制(状态机算法)
背景说明 在视觉项目中,经常要判断目标的状态,例如:符号的不同频率闪烁、常亮等。然而常规的视觉算法例如YOLO,仅仅只能获取当前帧是否存在该符号,而无法对于符号状态进行判断,然而重新写一个基于时序的卷积…...
Hyper-V如何将文件复制到虚拟机?教您3个简单的方法!
需要将文件复制到虚拟机! “大家好,有谁知道Hyper-V怎么将文件复制到虚拟机吗?我有一些文件,想要从主机中复制进虚拟机中,但是我不知道该怎么操作,有谁可以帮帮我吗?谢谢。” Hyper-V虚拟机可…...
Vue主要使用-03
组件通讯 组件通讯也是我们需要了解的,在我们的实际开发中,我们使用的非常多,比如父组件内的数据传入到子组件,子组件的数据传入到父组件,什么是父组件什么是子组件?父组件内包含着我们的子组件,我们的父组件可以有多个子组件,父组件就是我们使用子组件拼接的。 …...
LoadBalance客户端负载均衡
1. 前言Ribbon Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时࿰…...
Burp Suite Professional 2024.5 (macOS, Linux, Windows) - Web 应用安全、测试和扫描
Burp Suite Professional 2024.5 (macOS, Linux, Windows) - Web 应用安全、测试和扫描 Burp Suite Professional, Test, find, and exploit vulnerabilities. 请访问原文链接:Burp Suite Professional 2024.5 (macOS, Linux, Windows) - Web 应用安全、测试和扫描…...
逢3必过报数游戏-第13届蓝桥杯省赛Python真题精选
[导读]:超平老师的Scratch蓝桥杯真题解读系列在推出之后,受到了广大老师和家长的好评,非常感谢各位的认可和厚爱。作为回馈,超平老师计划推出《Python蓝桥杯真题解析100讲》,这是解读系列的第84讲。 逢3必过报数游戏&…...
解决Qt的multimedia库在clion中依赖库补全的问题
解决Qt的multimedia库在clion中使用报错的问题 在clion中,使用Qt的multimedia库时会报如下错误: defaultServiceProvider::requestService(): no service found for - "org.qt-project.qt.mediaplayer" 我猜测出现这个错误的原因很可能是因为…...
百度seo最新算法/深圳seo公司排名
大批量插入数据 如果一次性需要插入大批量数据,使用insert语句插入性能较低 可以使用MySQL数据库提供的load 指令进行数据插入 # 客户端连接服务端时,加上参数 --local-infile mysql --local-infile -uroot -p# 设置全局参数local_infile,…...
济南做网站哪好/郑州网络推广方案
CPU(Central Processing Unit,中央处理器)发展出来三个分枝,一个是DSP(Digital Signal Processing/Processor,数字信号处理),另外两个是MCU(Micro Control Unitÿ…...
聊城定制化网站建设/怎么创建网站链接
文章目录动态规划的三大步骤实例例1:简单的一维DP例2:不同路径例3:最小路径和例4:编辑距离优化例2不同路径数的优化例4编辑距离优化文章出处:告别动态规划,连刷 40 道题,我总结了这些套路&#…...
做网站到底能不能赚钱/销售的技巧与口才
用EditPlus打造你自己的IDE 因为对微软的notepad有点审美疲劳,所以常用EditPlus或UltraEdit32代替之.这两个文本编辑器功能比notepad强太多了.对程序员而言,本文只讲一下他的两个特点,用它来打造我们自己的IDE.希望对大家有用.1.程序高亮显示写程序的人长期处于某种临界状态,用…...
盘锦做网站价格/营销案例分析
概念Java中数组属于引用类型。数组使用场合较多,对于数组的操作具有一定重复性,例如:数组拷贝,转换字符串,转换成数组,排序等等。既然重复的操作与需求多,那么对于数组操作的支持就成了JDK中的一…...
梅州网站建设/seo外包方案
select * from slot_value_temp force index(idx_slot_type_id) WHERE slot_type_id xxxxxx; 不定期更新转载于:https://www.cnblogs.com/zhzhang/p/8127586.html...