[关闭]
@virusdefender 2016-01-09T03:27:23.000000Z 字数 6529 阅读 11984

注意这已经是老版的文档了,新版的在 http://qingdaou.github.io/OnlineJudge/

青岛大学在线评测平台 Document

https://github.com/QingdaoU/OnlineJudge
qduoj 可以提供多种语言代码的运行和评测,举办各种类型的比赛,分析比赛结果等,可以用于日常教学练习和考试,目前青大 ACM 队内部使用。

系统架构

此处输入图片的描述

如图,系统主要分为 Web serverMySQL数据库判题服务器Redis 四部分,它们可以任意的合并或者拆分到一台服务器上。

系统使用两个 MySQL 数据库,一个存储用户信息、题目信息、比赛信息等,另一个只存储用户提交的代码和运行结果。因为用户提交的代码和运行结果通常比较长,数据量和体积比较大,所以将其拆分出来。

两个 Redis 实例也是分别承担不同的任务,一个是 web 服务器的缓存,另一个是消息队列和任务队列,承担 web 服务器和判题服务器的通信任务。

判题服务器使用 dockerlrun 实现,每次收到一个新的判题任务就会创建一个新的 docker 容器,启动相关的判题程序,结束后自动删除。然后会向 submission 数据库写入本次的判题结果,同时向消息队列中写入,这样 web 服务器在不轮询数据库的情况下就能获取最新的判题结果。

安装方法

web 服务器和数据库

以下安装步骤均为 Ubuntu 系统下的演示,其他的系统可能命令稍有不同。

系统使用 docker 创建运行环境。源代码dockerfiles/oj_web_serverdockerfiles/judger中有两个 Dockerfile,是 Django web 服务器的环境和判题的运行环境。同时还依赖于 MySQLredis 的 docker 镜像。

首先安装 Docker,进入上面oj_web_server目录,命令是 docker build -t oj_web_server .,其中-t参数oj_web_server就是创建的镜像的名字,注意不要漏了最后的那个.,代表是当前目录。接下来创建判题的运行环境,进入 judger 目录,运行docker build -t judger .即可。MySQL 和 redis 直接使用官方镜像,命令分别是docker pull mysql/mysql-serverdocker pull redis

国内的网络下载 docker 和 build docker 镜像不太方便,可以使用 daocloud 的镜像 https://dashboard.daocloud.io/mirror。安装后运行dao pull ubuntu:14.04dao pull python:2.7之后再 build,应该就会快很多了。如果不介意的话,可以用下我的邀请链接

我正在使用 DaoCloud 提供的一站式容器云平台 ,你也快来加入吧! 自动化持续集成,超高速 Docker
镜像构建,还支持一键部署的容器运行集群哦!点此注册:
https://account.daocloud.io/signup?invite_code=95q5q8dw0n1xv22zc69y
,还有机会获得全球首本《Docker源码分析》和树莓派!

这个时候运行docker images就可以看到刚才创建的镜像了。

  1. REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
  2. oj_web_server latest 0d5fd23cb4aa 5 minutes ago 739.1 MB
  3. judger latest 3d15638ddab8 About an hour ago 951.9 MB
  4. redis latest 2f2578ff984f 4 days ago 109.2 MB
  5. python 2.7 7a7d87336a33 4 days ago 675.4 MB
  6. mysql/mysql-server latest ba04a84ec299 5 days ago 285.8 MB
  7. ubuntu 14.04 91e54dfb1179 3 weeks ago 188.4 MB

oj目录中有几个配置文件,是通过环境变量oj_env来控制加载哪一个的,如果oj_env=local或者没有这个环境变量就使用local_settings.py,否则使用server_settings.py。通用设置在settings.py中。

两个设置项目的不同点主要包括

还有两个设置项需要注意,就是 TEST_CASE_DIRLOG_PATHIMAGE_UPLOAD_DIR,暂时没有区分配置文件,都在源代码根目录下。但是在 docker 中运行的时候,我们需要在命令中对这三个文件加进行映射,让实际的文件写入主机磁盘,否则 docker 容器删除后这些文件也丢失了。我们创建/root/log/root/test_case/root/upload三个目录。因为 MySQL 也是运行在 docker 中的,也需要对数据库的数据存储目录进行映射, 还需要创建/root/data

启动 MySQL,命令是docker run --name mysql -v /root/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root -d mysql/mysql-server:latest。然后 docker ps -a就能看到这个容器了。以后手动的去操作 MySQL 都可以使用这个命令。

  1. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  2. 360073468c74 mysql/mysql-server:latest "/entrypoint.sh mysql" 3 seconds ago Up 3 seconds 3306/tcp mysql

要注意的是
- 如果你的 MySQL 备份路径不是/root/data 的话,注意替换命令中的路径。
- --name 参数是 mysql,这个只能存在一个,如果docker ps -a中还有一个同名的,会造成启动失败,需要先删除就容器docker rm CONTAINER_ID
- MYSQL_ROOT_PASSWORD=root说明 root 密码是 root,这个可以自己设置,运行在容器中的代码可以通过环境变量获取到这个密码。
- MySQL 5.6及以上版本会默认打开performance_schema=OFF,导致 MySQL 的 docker 容器占用大量内存,512M 内存的基本不能运行,问题分析见这里。如果确实内存不足,可以在本地新建一个 my.cnf,复制以下内容,

  1. [mysqld]
  2. skip-host-cache
  3. skip-name-resolve
  4. datadir=/var/lib/mysql
  5. socket=/var/lib/mysql/mysql.sock
  6. secure-file-priv=/var/lib/mysql-files
  7. user=mysql
  8. assorted security risks
  9. symbolic-links=0
  10. log-error=/var/log/mysqld.log
  11. pid-file=/var/run/mysqld/mysqld.pid
  12. performance_schema=OFF

然后启动 MySQL 的命令再加上-v /root/data/my.cnf:/etc/my.cnf,使用自定义的配置文件,和官方的相比,就添加了最后一行。

接下来我们就能连接到 docker 中的数据库了,命令是docker run -it --link mysql:mysql --rm mysql/mysql-server:latest sh -c 'exec mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -p"$MYSQL_ENV_MYSQL_ROOT_PASSWORD"'
然后运行下面两个 sql 语句。
create database oj default character set utf8;create database oj_submission default character set utf8;。这个时候,在 /root/data 目录就能看到同名的目录了。

然后我们创建数据库表,首先需要进入到oj_web_server中,命令是docker run -i -t -e oj_env=server -v /root/qduoj:/code -v /root/test_case:/code/test_case -v /root/log:/code/log -v /root/upload:/code/upload --link mysql --rm oj_web_server /bin/bash,然后使用命令python manage.py migratepython manage.py migrate --database=submission,如果没有错误,就可以了。然后运行python manage.py shell创建超级管理员用户。

  1. root@93cfb0df88e5:/code# python manage.py shell
  2. Python 2.7.10 (default, Sep 9 2015, 20:21:51)
  3. [GCC 4.9.2] on linux2
  4. Type "help", "copyright", "credits" or "license" for more information.
  5. (InteractiveConsole)
  6. >>> from account.models import User
  7. >>> u = User.objects.create(username="root", admin_type=2)
  8. >>> u.set_password("root")
  9. >>> u.save()

安装完成,exit退出就行了。

接下来我们要在服务器上进行 js 的打包和模板中静态文件加 md5 戳的操作,可以将多个 js 合并成一个,同时避免服务器文件修改后浏览器没及时更新的问题,以后每次更新代码也要进行相同的操作。这个操作需要安装 node.js,自行安装就行。然后在 manage.py 所在的目录运行python tools/release_static.py就可以了。

启动 redis

  1. docker run --name redis -v:/root/data/:/data -d redis redis-server --appendonly yes

接下来我们就可以用 gunicorn 真正的去运行 web 程序了,命令是

  1. docker run --name oj_web_server -e oj_env=server -v /root/qduoj:/code -v /root/test_case:/code/test_case -v /root/log:/code/log -v /root/upload:/code/upload -v /root/qduoj/dockerfiles/oj_web_server/supervisord.conf:/etc/supervisord.conf -v /root/qduoj/dockerfiles/oj_web_server/gunicorn.conf:/etc/gunicorn.conf -v /root/qduoj/dockerfiles/oj_web_server/mq.conf:/etc/mq.conf -d -p 127.0.0.1:8080:8080 --link mysql --link=redis oj_web_server

安装 nginx,进行一下反向代理和静态文件的处理就好了。

  1. server {
  2. listen 80;
  3. location /static/upload {
  4. alias /root/upload;
  5. }
  6. location /static {
  7. alias /root/qduoj/static/release;
  8. }
  9. location / {
  10. proxy_pass http://127.0.0.1:8080;
  11. proxy_set_header Host $host;
  12. proxy_set_header X-Real-IP $remote_addr;
  13. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  14. }
  15. }

这个时候尝试访问你的 ip 或者域名应该就可以看到网页界面了。

判题

判题队列使用的 celery 不是运行在 docker 里面的,所有它运行需要的数据库配置等都需要手动的获取。主要包括 submission 数据库地址,redis 地址,主机上测试用例目录和源代码目录。因为数据库和 redis 都是在 docker 中运行的,我们首先要获取这两个地址。

  1. docker inspect redis
  2. docker inspect mysql

在这两个输出中寻找 IPAddress,记住,然后打开/etc/profile,在文件最后增加

  1. export REDIS_PORT_6379_TCP_ADDR=192.168.xx.xx
  2. export submission_db_host=192.168.xx.xx

自己按照实际情况替换即可。

主机上我们也是使用的 supervisor 监控 celery 的,创建/etc/supervisord.conf,

  1. [supervisord]
  2. logfile=/root/log/supervisord.log ; supervisord log file
  3. logfile_maxbytes=50MB ; maximum size of logfile before rotation
  4. logfile_backups=10 ; number of backed up logfiles
  5. loglevel=info ; info, debug, warn, trace
  6. pidfile=/root/log/supervisord.pid ; pidfile location
  7. nodaemon=false ; run supervisord as a daemon
  8. minfds=1024 ; number of startup file descriptors
  9. minprocs=200 ; number of process descriptors
  10. user=root ; default user
  11. childlogdir=/root/log/ ; where child log files will live
  12. [rpcinterface:supervisor]
  13. supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
  14. [supervisorctl]
  15. serverurl=unix:///tmp/supervisor.sock ; use unix:// schem for a unix sockets.
  16. [include]
  17. files=celeryd.conf

创建/etc/celeryd.conf

  1. [program:celery]
  2. command=celery worker -A judge.judger_controller --loglevel=INFO
  3. directory=/root/qduoj/
  4. user=root
  5. numprocs=1
  6. stdout_logfile=/root/log/worker.log
  7. stderr_logfile=/root/log/worker.log
  8. autostart=true
  9. autorestart=true
  10. startsecs=1
  11. stopwaitsecs = 6
  12. killasgroup=true

运行supervisord,这时候就会启动 celery 了。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注