@virusdefender
2016-01-09T03:27:23.000000Z
字数 6529
阅读 12260
https://github.com/QingdaoU/OnlineJudge
qduoj 可以提供多种语言代码的运行和评测,举办各种类型的比赛,分析比赛结果等,可以用于日常教学练习和考试,目前青大 ACM 队内部使用。

如图,系统主要分为 Web server、MySQL数据库、判题服务器和 Redis 四部分,它们可以任意的合并或者拆分到一台服务器上。
系统使用两个 MySQL 数据库,一个存储用户信息、题目信息、比赛信息等,另一个只存储用户提交的代码和运行结果。因为用户提交的代码和运行结果通常比较长,数据量和体积比较大,所以将其拆分出来。
两个 Redis 实例也是分别承担不同的任务,一个是 web 服务器的缓存,另一个是消息队列和任务队列,承担 web 服务器和判题服务器的通信任务。
判题服务器使用 docker 和 lrun 实现,每次收到一个新的判题任务就会创建一个新的 docker 容器,启动相关的判题程序,结束后自动删除。然后会向 submission 数据库写入本次的判题结果,同时向消息队列中写入,这样 web 服务器在不轮询数据库的情况下就能获取最新的判题结果。
以下安装步骤均为 Ubuntu 系统下的演示,其他的系统可能命令稍有不同。
系统使用 docker 创建运行环境。源代码dockerfiles/oj_web_server和dockerfiles/judger中有两个 Dockerfile,是 Django web 服务器的环境和判题的运行环境。同时还依赖于 MySQL 和 redis 的 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-server和docker pull redis。
国内的网络下载 docker 和 build docker 镜像不太方便,可以使用 daocloud 的镜像 https://dashboard.daocloud.io/mirror。安装后运行dao pull ubuntu:14.04和dao pull python:2.7之后再 build,应该就会快很多了。如果不介意的话,可以用下我的邀请链接
我正在使用 DaoCloud 提供的一站式容器云平台 ,你也快来加入吧! 自动化持续集成,超高速 Docker
镜像构建,还支持一键部署的容器运行集群哦!点此注册:
https://account.daocloud.io/signup?invite_code=95q5q8dw0n1xv22zc69y
,还有机会获得全球首本《Docker源码分析》和树莓派!
这个时候运行docker images就可以看到刚才创建的镜像了。
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZEoj_web_server latest 0d5fd23cb4aa 5 minutes ago 739.1 MBjudger latest 3d15638ddab8 About an hour ago 951.9 MBredis latest 2f2578ff984f 4 days ago 109.2 MBpython 2.7 7a7d87336a33 4 days ago 675.4 MBmysql/mysql-server latest ba04a84ec299 5 days ago 285.8 MBubuntu 14.04 91e54dfb1179 3 weeks ago 188.4 MB
在oj目录中有几个配置文件,是通过环境变量oj_env来控制加载哪一个的,如果oj_env=local或者没有这个环境变量就使用local_settings.py,否则使用server_settings.py。通用设置在settings.py中。
两个设置项目的不同点主要包括
BASEBASE 数据库地址,default是除了用户提交之外的所有的数据,submission 是用户提交数据库。REDIS_CACHE redis 网页缓存服务器STATICFILES_DIRS 静态文件目录,只有在 DEBUG=True 的时候 runserver 才会去处理。css、js 等都在 static 目录中,用户上传的图片在根目录下的 upload 目录中。TEMPLATE_DIRS 网页模板目录。因为静态文件需要加 md5来处理缓存问题,所以需要修改模板文件,处理后的模板放在 release 里面,源代码在 src 里面。还有两个设置项需要注意,就是 TEST_CASE_DIR、LOG_PATH 和 IMAGE_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 都可以使用这个命令。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES360073468c74 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,复制以下内容,
[mysqld]skip-host-cacheskip-name-resolvedatadir=/var/lib/mysqlsocket=/var/lib/mysql/mysql.socksecure-file-priv=/var/lib/mysql-filesuser=mysqlassorted security riskssymbolic-links=0log-error=/var/log/mysqld.logpid-file=/var/run/mysqld/mysqld.pidperformance_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 migrate和python manage.py migrate --database=submission,如果没有错误,就可以了。然后运行python manage.py shell创建超级管理员用户。
root@93cfb0df88e5:/code# python manage.py shellPython 2.7.10 (default, Sep 9 2015, 20:21:51)[GCC 4.9.2] on linux2Type "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from account.models import User>>> u = User.objects.create(username="root", admin_type=2)>>> u.set_password("root")>>> u.save()
安装完成,exit退出就行了。
接下来我们要在服务器上进行 js 的打包和模板中静态文件加 md5 戳的操作,可以将多个 js 合并成一个,同时避免服务器文件修改后浏览器没及时更新的问题,以后每次更新代码也要进行相同的操作。这个操作需要安装 node.js,自行安装就行。然后在 manage.py 所在的目录运行python tools/release_static.py就可以了。
启动 redis
docker run --name redis -v:/root/data/:/data -d redis redis-server --appendonly yes
接下来我们就可以用 gunicorn 真正的去运行 web 程序了,命令是
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,进行一下反向代理和静态文件的处理就好了。
server {listen 80;location /static/upload {alias /root/upload;}location /static {alias /root/qduoj/static/release;}location / {proxy_pass http://127.0.0.1:8080;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}}
这个时候尝试访问你的 ip 或者域名应该就可以看到网页界面了。
判题队列使用的 celery 不是运行在 docker 里面的,所有它运行需要的数据库配置等都需要手动的获取。主要包括 submission 数据库地址,redis 地址,主机上测试用例目录和源代码目录。因为数据库和 redis 都是在 docker 中运行的,我们首先要获取这两个地址。
docker inspect redisdocker inspect mysql
在这两个输出中寻找 IPAddress,记住,然后打开/etc/profile,在文件最后增加
export REDIS_PORT_6379_TCP_ADDR=192.168.xx.xxexport submission_db_host=192.168.xx.xx
自己按照实际情况替换即可。
主机上我们也是使用的 supervisor 监控 celery 的,创建/etc/supervisord.conf,
[supervisord]logfile=/root/log/supervisord.log ; supervisord log filelogfile_maxbytes=50MB ; maximum size of logfile before rotationlogfile_backups=10 ; number of backed up logfilesloglevel=info ; info, debug, warn, tracepidfile=/root/log/supervisord.pid ; pidfile locationnodaemon=false ; run supervisord as a daemonminfds=1024 ; number of startup file descriptorsminprocs=200 ; number of process descriptorsuser=root ; default userchildlogdir=/root/log/ ; where child log files will live[rpcinterface:supervisor]supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface[supervisorctl]serverurl=unix:///tmp/supervisor.sock ; use unix:// schem for a unix sockets.[include]files=celeryd.conf
创建/etc/celeryd.conf
[program:celery]command=celery worker -A judge.judger_controller --loglevel=INFOdirectory=/root/qduoj/user=rootnumprocs=1stdout_logfile=/root/log/worker.logstderr_logfile=/root/log/worker.logautostart=trueautorestart=truestartsecs=1stopwaitsecs = 6killasgroup=true
运行supervisord,这时候就会启动 celery 了。