LPOJ网址:www.lpoj.cn
LPOJ文档:docs.lpoj.cn

以下摘自我的毕业论文
一个OJ的架构多种多样,这里仅介绍我使用的。

什么是OJ

背景及研究意义

随着信息技术的发展,人们越来越注重基础的算法知识教学。现代企业中,人工智能无处不在,而人工智能又离不开算法,对于未来人才的培养,基础算法显得尤为重要。因此各大高校也越来越重视算法人才的培养。现在许多高校都有一套成熟的培养系统,比如杭州电子科技大学,北京大学,清华大学都有自己的判题系统。所谓判题系统,就是一个能对学生提交的程序进行自动判断的系统,这样不仅能省下老师许多时间,学生还能够自行的进行更多的训练,并从中学到更多的算法。一个好的判题系统不仅能进行判题,还能让学生随时随地的学习,即让判题程序以网页的形式呈现,只需要一个浏览器即可访问。同时还配套有一系列功能,如学生能迅速定位到自己的错误,并且能动态的看到自己的学习与锻炼情况等等。

上述判题系统我们称之为OJ,即Online Judge,中文名叫在线评测系统,目前在国内比较出名的在线评测系统有学校运营着的杭州电子科技大学的HDOJ,有北大的POJ,有浙江大学的ZOJ等等,同时也有企业运营着的PTA系统,计蒜客的判题系统,和企业面试常用的牛客网。这些均是国内出名的判题系统。在国外也有在全球出名的LeetCode网站,许多程序员在面试前都会上这个网站进行学习,同时还有由俄罗斯高校运营着的一个算法竞赛的做题网站Codeforces,还有日本的AtCoder等等。

虽然市面上有许许多多的OJ,但是OJ之间的题目数据并不共享,这就使得这些学习资源被垄断,因此一个属于学校本身的OJ就显得尤为重要。虽然在Github上有许多开源的OJ,最出名的如华中科技大学主持开源的HustOJ,有青岛大学主持的QDUOJ,但是要理解且修改当中的源码或者定制自己的功能显得尤为困难。所以学校要有自己的一个判题系统去培养算法人才。

算法竞赛介绍与OJ介绍

现代科技企业为了招到算法人才,会举办各种各样的算法竞赛去吸引人才。如百度每年都会举办百度之星,美团也会举办算法竞赛,同时在国际上,有由美国计算机协会(现在由JetBrain公司)支持举办的国际大学生程序设计竞赛,每年都会举办各国区域赛选拔顶尖算法人才去参加全球总决赛。在国内,也有中国大学生程序设计赛,也有各省的省赛。这些竞赛的目的都是为了培养各种各样的算法人才。

算法竞赛的形式非常简单,通常是在比赛的时候,每人或每只队伍使用一台电脑,然后需要在有限的5个小时内使用自己熟悉的编程语言写程序解决七个以上的问题。程序提交之后会给裁判或者在线的测评系统进行编译,然后运行,运行的结果会判定为正确或错误两种并及时通知各个参赛队。比赛形式非常的简单,但是却很非常紧张且具有挑战性。这些题目涵盖的领域非常广,包括数据结构,动态规划,图论,数论,运筹学,博弈论,概率论等等,而且通常需要学生编写非常严谨的程序,不容得一丝错误。

在实际工作当中,一个小小的漏洞都会给企业带来非常大的损失,所以一个长期参加算法竞赛的学生在工作中会展现出自身的优势,因此各大高校和企业也都非常重视算法竞赛的发展。但是要判断学生写的程序是否正确就需要一个能自动对学生程序进行判题的系统。这个系统会在数据库中存放各种题目数据,然后通过前端展示给学生,通常题目由:题目描述、输入描述、输出描述组成。学生需要编写一个程序去接收系统提供的输入数据,然后按照题目要求,对数据进行处理后,输出一个正确的答案。系统会将这个答案与后台的答案进行比较,如果相同,则代表学生的程序是正确的,错误则代表学生的程序有漏洞。通常系统会限制学生的程序要在一定时间内运行完毕,即程序的时间复杂度要在合理的范围内,同时空间复杂度也要在限定的范围内。因为在实际工作中效率非常重要。

同时程序在运行过程中不容得出现致命的系统错误,比如数组越界,除零等等。还有在比赛过程当中,由于集中提交,会有高并发的业务,因此一个系统要对上述所有情况作出判断且稳定的运行是非常困难的,这也是一个要攻克的难点。在本文后面会有提到如何解决。

系统说明

本系统由六部分组成,分别是:前端系统、后台系统、数据库、判题服务器、判题系统和爬虫系统组成。其中前端负责把数据展示给用户,采用最新的前端框架Vue.js开发。后端负责处理处理数据,并将数据发送给前端,将采用Python中的Django REST 框架开发。数据库将采用MySQL开发。同时会有一个判题服务器不断的从数据库中拉取学生的代码,并将代码发送给判题系统进行判题。这两个系统都将采用Python开发。同时判题系统要支持多个部署,实现同时判题,这样才能均衡负载。最后会有一个爬虫系统,负责收集统计和分析学生行为,并给出指导意见。其中本系统提供的主要功能有:①用户管理 ②题目查看 ③比赛系统 ④排名系统 ⑤数据查看 ⑥后台管理 ⑦自动判题 ⑧均衡负载。

Vue.js 介绍

Vue.js是一个构建 Web 界面的成熟的渐进式框架。它的目标是通过尽可能简单的接口来实现响应式的数据绑定和组合的视图组件,然后再将这些组件组合起来。它不仅上手容易,而且还便于与第三方库或既有项目整合。本系统将采用它进行开发,而且学习起来非常简单,拓展性非常好,用户可以使用自己的组件,非常简单且高效的自定义自己的页面。 同时结合Element的UI库进行精致的页面设计,给用户呈现一个较好的网页。同时使用Js中的Axios库,可以异步的向后台请求数据,然后使用Js解析后台返回的Json数据,通过Vue的数据绑定功能,可以非常简单且便利的将数据呈现给用户。

Django REST framework 介绍

现在越来越多的网站采用前后端分离技术。在前后端分离的应用模式中,后端仅返回前端所需要的数据,不再渲染HTML页面,不再控制前端的效果。前端用户想要看到什么效果,从后端请求的数据如何加载到前端中,都由前端浏览器自己决定。在前后端分离的应用模式中,前端与后端的耦合度相对较低,我们通常将后端开发的每一视图都成为一个接口,或者API,前端通过访问接口来对数据进行增删改查。因此我们采用它进行我们的后端开发,同时他配套有各种各样的服务,如权限管理,路由管理,限流等等,这些在我们的判题系统中尤为重要。

MySql 介绍

MySQL是一个关系型数据库管理系统。这个数据库有许多的优点。首先,它使用起来非常的简单,在网上有许多的教程,一个稍微学习过数据库理论的人都能很快的上手。其次,他是开源的。开源使得人们可以随意的使用它,只要遵循它的协议即可,这就带来了免费,给我们开发者带来了许多的便利。

数据库功能介绍

在本OJ中,数据库除了充当一个存储数据的角色,还要负责队列这样的一个角色。得益于MySQL中的事务管理功能,使得有高并发的查询时,仍然能得心应手的应对,所以OJ中的判题机器将会不断地从数据库中拉取未判题列表,而用户又不断地向数据库中插入未判题记录。两者分别是消费者和生产者的角色,而数据库充当一个中间件的角色。

数据库作用示意图

前端设计

Vue.js 是使用 MVVC模式开发,所谓MVVC即 Model,View, Viewmodel。其中Model层用于存储数据,ViewModel层用于网页元素的变化和实现数据之间的双向绑定。View层用于显示数据。相比于其他框架,我们可以花费更多的代码时间在View和Model层的编写上,从而不必关心中间的消息是如何传递的。因为Vue在底层通过观察者模式已经很好地帮我们实现了。因此本系统主要通过如下方式实现数据绑定:
对于每一个页面都以组件的形式开发,然后组件与组件之间互不干扰。页面之间的跳转通过路由实现。组件内的数据在Created函数中通过Axois库进行后台的API访问,获取后绑定到data中,然后再由vue的model数据绑定,自动的呈现给用户,在之前我们只需要定义好页面即可。前端的页面采用Element库开发。

后端设计

后端的开发比前端的开发要简单很多,因为开发者只需专注于数据的呈现即可,不必关心显示的逻辑。在众多后端框架中,我选择了开发和学习成本较低的Python语言中的Django框架,同时Python语言与我们的判题程序又相辅相成,因此是一个很好的选择。Django是一个开源的Web框架,整体采用MVC的设计模式。但是在本系统中,我们并不需要构建自己的前端页面,我们只关心有哪些数据要交付给前端,因此本系统采用Django中的REST框架来快速构建自己的数据API。其中REST是RESTful的简称。RESTful是一种软件架构风格,它采用http协议,非常简单,任意客户端都能运行。因此我们只需要关心有哪些数据要交付给前端即可。其中REST框架主要分为三个部分,分别是Model,Serializer和View。Model即数据层,定义了数据在数据库中的形式。Serializer即序列化器,其中定义了各种数据库的操作,相当于一个中间层,最后View层决定了哪些数据可以呈现给用户,怎么呈现给用户等等。所以当开发者编写API时,只要着重于实现这三层即可。因为有了RESTful框架,这一切都变得非常简单便利。

RESTful 示意图

开源的评测技术

由于系统追求稳定性,自己开发测评程序难免会有各种各样的问题,因此一个稳定的测评技术非常重要。在开源社区上有一款开源的测评模块,由青岛大学开发,该测评技术在青岛大学得到了运用,且非常的稳定,经过测试和改进后,可以运用到本系统当中。该测评技术只有一个简单的功能,即运行程序,并得到输出。函数结构如下:

Function (最大CPU时间,最大运行时间,最大允许内存,最大输出大小,最大栈大小,执行程序的路径,输入文件的路径,输出文件的路径,错误输出额路径)

调用该函数并传入对应参数后,该函数会生成一个沙盒,然后在沙盒内运行该程序,运行后程序会返回一个结构体,该结构体保存了程序的运行结果,具体如下:

{'cpu_time', 'signal':, 'memory', 'exit_code', 'result', 'error', 'real_time'}

分别对应着,程序运行使用CPU得时间,程序发出的信号,程序占用的内存,程序退出时的返回值,程序运行的结果,程序运行的时间(包括了系统调度的时间)其中result有如下6个枚举值

  • SUCCESS = 0 (此结果仅代表程序成功运行完毕并正确退出)
  • CPU_TIME_LIMIT_EXCEEDED = 1
  • REAL_TIME_LIMIT_EXCEEDED = 2
  • MEMORY_LIMIT_EXCEEDED = 3
  • RUNTIME_ERROR = 4
  • SYSTEM_ERROR = 5

如果程序正常退出的话,我们就可以将程序输出的内容,与正确的内容做比较,得出是答案错误还是通过。

测评服务器与测评机

测评模块仅提供了安全稳定的程序运行稳定,但是并不能判断程序是否通过,因此还要自己完成许多的逻辑工作。本系统的测评模块分为两部分,一部分是测评服务器,负责分发测评任务,另一部分是测评机,负责运行程序和提交测评结果。程序在运行过程中难免会消耗系统资源,如果只有一个判题程序在判题,如果判题时间较长,会导致后面的题目无法得到及时的反馈,但是如果太多判题程序同时运行,会导致系统资源消耗过大,导致前端和后台系统可能无法正确运行,所以要设计一个能够在多个机器上运行的判题程序,实现均衡负载功能,因此本OJ测评模块示意图如下:

测评模块示意图

利用了数据库的事务管理,可使得高并发得以实现。用户相当于生产者,不断地向数据库提交待测评列表,测评服务器相当于一个消费者,不断的从数据库中获取未判提交列表,然后将这些题目分发到准备就绪的判题机,判题机收到判题任务后会进入忙状态,此时判题服务器不再向该判题机发送判题任务。当判题机判题完毕,会告诉服务器,可以继续判题。服务器再将该判题机纳入空闲列表。这样我们就能实现多个判题机同时运行,且这些判题机可以在任意的机器上运行,只需要通过TCP协议,链接到服务器上即可。同时服务器被设计成多线程的形式,在通过资源锁去控制并发的资源访问,使得多个判题程序能同时判题。
测评服务器,测评服务器使用Python开发,运行时会循环监听9906端口,一旦有测评机连上服务器,会新建一个线程,专门处理该测评机的消息。该线程首先会向测评机发送getstatus信息,测评机收到消息后会向服务器发送ok信号,代表已准备好判题。此时服务器会将该测评机纳入空闲列表。然后不断地循环发送getstatus信号,如果是not ok,代表非空闲状态,会将该测评机纳入非空闲列表。同时会有一个线程专门负责向数据库获取未判题列表,然后将未判题发放给空闲的判题机进行判题。
测评机,测评机的功能就是进行判题,在收到判题服务器发送过来的判题消息后,会对该提交在沙盒中进行评测。具体过程如下图所示:

判题过程示意图

测评机向数据库查询代码,将代码生成文件,然后编译。如果编译通过会进行程序运行。程序运行成功后,会将输出的文件和正确的输出文件进行比较,如果完全一致,则返回代码通过,否则不通过。同时将测试数据也一并截取保存到数据库中

全系列文章

全系列文章在此(点我)

Logo

开源、云原生的融合云平台

更多推荐