【负载均衡oj】(七)ojserver

news/2024/7/10 2:45:48 标签: 负载均衡

一.目的

负责负载均衡,获取网站首页,通过网络罗调用编译并运行并提供结果给用户。根据用户的操作提供不同的功能。采用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<string> 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 << "\n";
                return false;
            }
            *ret = iter->second; // 输出行参数
            return true;
        }

获取所有题目给客户

bool GetAllQuestion(vector<Question> *out)
        {
            if (questions.size() == 0)
            {
                LOG(ERROR) << "用户获取题库失败"
                           << "\n";
                return false;
            }
            for (auto &q : questions)
            {
                out->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<Question> all;
            if (_model.GetAllQuestion(&all)) // 获取所有题目到vector内
            {
                // 获取信息成功构建html
                sort(all.begin(),all.end(),[](const Question& q1,const Question& q2){ 
                    return atoi(q1.number.c_str())<atoi(q2.number.c_str());
                });
                _view.AllExpandHtml(all, html);
            }
            else
            {
                *html = "获取题目失败, 形成题目列表失败";
                return false;
            }
            return true;
        }

        // 获取一道题
        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;
            }
        }

        // 判断用户代码是否正确
        void Judge(const string &in_json, string *outj_son, const string &number)
        {
            // 需要读取上来用户的代码并和测试用例拼接成一份完整代码最后
            // 0. 根据题目编号,直接拿到对应的题目细节
            Question q;
            _model.GetOneQuestion(number, &q);

            // 1. in_json进行反序列化,得到题目的id,得到用户提交源代码,input
            Json::Reader reader;
            Json::Value usr_val;
            reader.parse(in_json, usr_val);
            string code = usr_val["code"].asString(); // 用户写的代码

            // 2. 重新拼接用户代码+测试用例代码,形成新的代码
            Json::Value compile_val;
            compile_val["code"] = code + '\n' + q.tailer;
            compile_val["input"] = usr_val["input"].asString();
            compile_val["cpu_limit"] = q.cpu_limit;
            compile_val["mem_limit"] = q.mem_limit;

            // 序列化准备传输
            Json::FastWriter writer;
            std::string compile_string = writer.write(compile_val);
            // 3. 选择负载最低的主机(差错处理)
            // 规则: 一直选择,直到主机可用,否则,就是全部挂掉
            while (true)
            {
                int id;
                Machine *m;
                // 跟据负载选择服务器
                if (!_load.SmartChoice(&m, &id))
                {
                    break; // 选择主机失败
                }
                // 4. 选择完主机后发起http请求链接到主机
                Client cli(m->_ip, m->_port);
                m->IncLoad();
                LOG(INFO) << " 选择主机成功, 主机id: " << id << " 详情: " << m->_ip << ":" << m->_port << " 当前主机的负载是: " << m->Load() << "\n";

                // 5.将数据提交给服务主机并进行编译运行
                if (auto res = cli.Post("/Compile_and_run", compile_string, "application/json;charset=utf-8")) // 这个请求完成编译运行并返回结果
                {
                    // 需要判断返回结果,有返回结果不一定成功运行
                    if (res->status == 200) // 200表示成功运行
                    {
                        // 将结果返回给用户
                        *outj_son = res->body;
                        m->DecLoad();
                        LOG(INFO) << "主机执行任务成功"
                                  << "\n";
                        break;
                    }
                    // 若提交失败也需要将负载复原并重新执行该流程
                    m->DecLoad();
                }
                else
                {
                    // 请求失败(当前主机不存在)
                    LOG(ERROR) << " 当前请求的主机id: " << id << " 详情: " << m->_ip << ":" << m->_port << " 可能已经离线"
                               << "\n";
                    _load.OfflineMachine(id);

                    _load.ShowMachines(); // 仅仅是为了用来调试
                }
            }
        }

四.view模块

在拿到model的题目信息后,结合本地html进行渲染,给用户提供前端展示页面。采用ctemplate渲染。

通过互取到的所有题目信息,使用ctemplate渲染展示给用户。

// 获取所有题目并生成html文件
        bool AllQuestions(string *html) // 输出型参数
        {
            vector<Question> all;
            if (_model.GetAllQuestion(&all)) // 获取所有题目到vector内
            {
                // 获取信息成功构建html
                sort(all.begin(),all.end(),[](const Question& q1,const Question& q2){ 
                    return atoi(q1.number.c_str())<atoi(q2.number.c_str());
                });
                _view.AllExpandHtml(all, html);
            }
            else
            {
                *html = "获取题目失败, 形成题目列表失败";
                return false;
            }
            return true;
        }

一道题

 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 // 负载均衡模块
    {
        // 可以给我们提供编译服务的所有的主机
        // 每一台主机都有自己的下标,充当当前主机的id
        vector<Machine> machines;
        // 所有在线的主机id
        std::vector<int> online;
        // 所有离线的主机id
        std::vector<int> 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;
            }
            // 读取数据到line
            string line;
            while (getline(in, line))
            {
                vector<string> 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();
        }
    };


http://www.niftyadmin.cn/n/5306404.html

相关文章

Java后端开发——Ajax、jQuery和JSON

Java后端开发——Ajax、jQuery和JSON 概述 Ajax全称是Asynchronous Javascript and XML&#xff0c;即异步的JavaScript和 XML。Ajax是一种Web应用技术&#xff0c;该技术是在JavaScript、DOM、服务器配合下&#xff0c;实现浏览器向服务器发送异步请求。 Ajax异步请求方式不…

洛谷——P1347 排序(图论-拓扑排序)

文章目录 一、题目排序题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 样例 #3样例输入 #3样例输出 #3 提示 二、题解基本思路&#xff1a;代码 一、题目 排序 题目描述 一个不同的值的升序排序数列指的是一个从左到右元素依次增大的…

机器学习的三个方面

1 机器学习的三个方面 1.1 数据 包括数据采集、增强和质量管理&#xff0c;相当于给人工智能模型学习什么样的知识 第一、什么专业的知识&#xff1b; 第二、知识是否有体系&#xff0c;也就是说样本之间是否存在某种关联、差异等&#xff0c;这个涉及到样本选择等问题&#x…

【论文阅读笔记】医学多模态新数据集-Large-scale Long-tailed Disease Diagnosis on Radiology Images

这是复旦大学2023.12.28开放出来的数据集和论文&#xff0c;感觉很宝藏&#xff0c;稍微将阅读过程记录一下。 Zheng Q, Zhao W, Wu C, et al. Large-scale Long-tailed Disease Diagnosis on Radiology Images[J]. arXiv preprint arXiv:2312.16151, 2023. 项目主页&#xf…

手机上连网络转接app,电脑连接手机,共用网络转接app的办法

方法一&#xff0c;&#xff08;不推荐&#xff09; 因为太简单了所以写一下 电脑安装MuMu模拟器&#xff0c;之后安装网络转接app&#xff0c;这个模拟器设置了从电脑上安装app和&#xff0c;安卓与电脑同步文件夹功能&#xff0c;实现文件共享。所以直接用就可以了。 方法二…

[运维|gitlab] docker Gitlab 命令行后台修改密码

参考文献 docker Gitlab 初始化账号以及密码是什么呢 修改密码 进入容器 docker exec -it gitlab bash进入bin目录 进入gitlab的bin目录 cd /opt/gitlab/bin执行命令 执行gitlab-rails console gitlab-rails console执行命令 找到root的用户&#xff0c;输入uUser.where…

云原生战专题 | 深入浅出分析云原生微服务的技术结构和架构设计

深入浅出分析云原生微服务的技术结构和架构设计 云原生容器技术背景容器编排Kubernetes控制平面的四大组件Kubernetes在容器编排中的设计要点 云原生微服务典型架构第一代微服务架构第二代微服务架构第三代微服务架构第四代微服务架构 未来的云原生架构 — Serverless 云原生容…

【AI视野·今日NLP 自然语言处理论文速览 第六十八期】Tue, 2 Jan 2024

AI视野今日CS.NLP 自然语言处理论文速览 Tue, 2 Jan 2024 Totally 48 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers A Computational Framework for Behavioral Assessment of LLM Therapists Authors Yu Ying Chiu, Ashish Shar…