系统架构

以下摘自我的毕业论文

系统说明

本系统由六部分组成,分别是:前端系统、后台系统、数据库、判题服务器、判题系统和爬虫系统组成。其中前端负责把数据展示给用户,采用最新的前端框架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时间,最大运行时间,最大允许内存,最大输出大小,最大栈大小,执行程序的路径,输入文件的路径,输出文件的路径,错误输出额路径)
1

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

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

分别对应着,程序运行使用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,代表非空闲状态,会将该测评机纳入非空闲列表。同时会有一个线程专门负责向数据库获取未判题列表,然后将未判题发放给空闲的判题机进行判题。 测评机,测评机的功能就是进行判题,在收到判题服务器发送过来的判题消息后,会对该提交在沙盒中进行评测。具体过程如下图所示:

判题过程示意图

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