系统设计:在线评测系统 UniJudge

在线评测系统(Online Judge System)想必很多人都有所耳闻,比如上程序设计课程的时候需要在网上做题交代码……更不必说那些做高难度试题的信息竞赛选手了。

通常来说,一个在线评测系统的核心是其积累的题库及测试数据。注册用户在系统里查看问题并提交答案交由评测系统评测,最后反馈给用户,这是一个基本的业务逻辑。

我并不想做一个寻常的Online Judge,而是一个概化可复用的在线评测系统,用函数来描述其内涵即为:

UniJudge:SubmissionSentence

我给这个系统取名为UniJudge,We Judge All !

架构

核心业务逻辑图:

Created with Raphaël 2.1.0 User User Interface Interface Database Database Judger Judger Submit Save Submission Dispatch Submission Judge... Return Sentence Save Sentence Query Query Return Return

UniJudge包含Interface、Database,至于前端(Front-End)与Judger是不属于UniJudge开发范畴内的。

我们不对前端做任何假设,因此是前后端解耦的,前端工程师可以参考我们的API文档自行开发适合特定情境的在线评测系统。

另外为什么也不做Judger的部分呢?原因如下:

  • Judger 可能有潜在的安全问题

    对于需要在服务器上运行用户提交的程序的时候,安全性是首要需要考虑的事物。

    Judger在本系统中的地位与User相似,是不被完全信任的,Judger没有直接访问数据库的权限

    与User一样需要通过Interface来访问UniJudge

  • Judger 的逻辑是更加丰富可定制的

    Judger的逻辑是不固定的。以经典的程序设计评测为例,可以严格匹配空白字符,不允许多余的任何一个字符或者使用Special Judge,判题逻辑是不确定的。

    因此将其实现分离到UniJudge之外可以保持UniJudge的洁净。

    当然了,UniJudge官方也会开发几个Judger样例,如果不想定制也可以直接部署这些样例。

  • Judger 不一定是机器,也有可能是人类

    谁说在线评测系统一定要用机器自动判定?UniJudge还支持接入第三方人工评测系统!比如作文批改系统、申请审核系统、作业互评系统等。

  • 性能问题

    由于机器自动评测往往要经过大量运算,大大增加了系统的负载。Judger完全可以部署在网络的另外一台设备上,UniJudge通过基于TCP的某协议来与之通信。这样简单的一个分布式设计有力地增强了整个系统的可用性。

建模

一个完整的在线评测系统中至少有实体集:

  • 问题(Problem)
  • 用户(User)
  • 裁判(Judger)

以及其衍生联系集:

  • 提交(Submission)
  • 题单(ProblemList)

问题集

问题是UniJudge中最重要的部分。

基本要素

问题至少包括一个标题和一个描述,其中标题是不可重复的,描述是一个基本不限制长度的字符串。

有了基本的两个要素,这个问题已经具备了完备的表达能力。出题者已经可以组合出各种各样的问题了。

举个例子:

标题:我为什么这么智慧?
描述:请举例分析。

这样就是一个合法的问题(当然,可能会收到一些奇奇怪怪的答案)。

问题包含标题与描述的设计范式,我们称为基础范式(FNF, foundation normal form)。

拓展性

要说仅仅满足FNF的问题想使用不具备AI的机器Judger来评判那基本是一项不可能完成的任务。

当问题仅满足FNF的时候,UniJudge不会将它分发给一个不支持FNF评测的Judger。

从FNF的定义看,实现一个支持FNF的机器Judger是相当困难的。

UniJudge是以鸭子类型(duck-type)的思想来看待问题的,只要该问题满足了某个范式,该问题就能被宣称支持该范式的Judger评测,并且Judger也要返回问题范式规定的评测范式,用户可以自行选择评测范式。

因此,只要定义各种各样的范式,即可产生各种各样的拓展性。

比如当问题的 TestCase 域非空的时候,这个问题就满足了可简单测试范式(STNF, Singly Testable Normal Form),而实现一个支持STNF的机器Judger是相当容易的。

问题来源

让广大用户参与到出题的过程中来,可以更快地凝聚众人的智慧。

但如今并不是一个缺乏信息的时代,而是一个信息爆炸的时代。

将出题的门限放得过宽会导致问题泛滥,使得真正有益的问题被埋没。

有如下解决方案:

  • 提高出题的门槛,例如只有排名靠前的或者等级高的人可以出题。
  • 出题需要审核,有一个Pull Request的机制。
  • 直面大数据,做好聚类分析,给特定的用户推送特定的问题。

用户集

UniJudge的用户建模与其他的系统并没有太大的区别:对使用UniJudge的人建模。

就不在此废话了。

裁判集

裁判本质上并不属于UniJudge,UniJudge仅仅是保存裁判的联系方式。

Created with Raphaël 2.1.0 User User UniJudge UniJudge Judger A Judger A Submit I have a submission to judge. OK, submission received. Your submission is pending for judging. How is it going? It's still pending. Wrong Answer! OK, the sentence has been updated. How is it going? Wrong Answer!

UniJudge仅需要保存以下信息即可掌握对裁判的各种操作所需要的信息:

  • 裁判名称,不可重复。
  • 裁判地址,一个URL,UniJudge将会按照协议将提交数据发送到这个URL。
  • 开发维护者,这个人是UniJudge的用户之一,他可以对Judger做各种操作,所以这里是一个引用(外键)。
  • 支持的问题范式列表,一个枚举列表。

提交集

提交集是一个与User、Problem、Judger的联系集,每一条提交都至少有这三类对象实例的引用(外键)。

除此之外,提交最核心的是内容,包含两个部分:

  • 内容符合的范式
  • base64编码的数据体

之所以要使用base64编码是因为它可以将二进制文件编码通过HTTP(s)协议异步传输,这使得我们不仅可以提交一个文本框的代码,甚至可以提交一个压缩包、图片等,UniJudge的适用面大大拓宽。

题单集

题单是为了将海量的问题分类的一个弱实体集,用于将问题聚合并控制访问权限。

注意:问题是不直接对普通用户开放的,用户能直接访问的是各式各样的题单(尽管这个题单可以包含所有问题或者仅包含一个问题甚至不含任何问题)。如果一个问题不在任何的题单里面,那么普通用户将无法看到、回答该问题。

一个题单至少需要如下要素:

  • 题单名字,不可重复。
  • 问题列表,一个问题引用的列表。

权限控制

以上,只要问题出现在某个题单内,用户就可以访问到这个问题,这种设计本质上是完成了题单的聚类功能。很多的情境下,我们需要控制用户访问的条件,如:

  • 比赛,仅规定时间范围内可以访问。
  • 前提依赖,某些题集需要阶段性地访问,比如在通过某一些题集之后才开放。
  • 不公开的题集,仅有部分用户可以访问。

接下来要设计权限控制系统。

权限控制系统是一个比较重要的子系统,必须内嵌于UniJudge,但又应该保持一个较低的耦合度。
这可以导入一个中间件来实现。

在用户访问特定题单时,先检查权限,如果权限不够,直接返回错误提示,否则就将请求传递给下一个函数。

对于每一种不同的权限控制策略,只需要且必须要一个函数来判断。

你可能感兴趣的:(产品)