背景:
本人接受了一个python3 django框架写的系统,有api有页面。
部署方式是在win服务器上使用docker toolbox部署的,这是一种一起docker desktop还没萌芽或者刚刚萌芽时就存在的一种技术,简单说就是让人可以在win系统上使用docker,构建docker镜像,启动docker容器等待。
这次有一个开发需求:
用户A通过http形式调用我方的接口告诉我他出错了,但是他也有可能从错误中恢复过来,也有可能无法恢复。
如果恢复过来,就通过同一个接口使用不同参数告诉我他已经恢复了,我方系统就啥动作都不要做了。
如果一直没有恢复(也就是一直没有再通过这个接口将表示已经恢复的参数传递给我方系统),10秒钟后,我方系统就需要再告知第三方的一个系统B,让系统B知道用户A这家伙出错了,然后系统B会执行某种动作(具体是什么动作我方系统不需要知道)。我方通知系统B也是通过http形式。
以上就是全部背景了,按理说,这么个小需求应该不难实现。事实也是如此,我在本地没多少时间就开发完成了,使用的是`threading.Timer`方法,设定一个定时器,10秒后调用某个方法发起http请求到系统B,如果10秒内再次收到用户A的已恢复调用,则调用`threading.Timer`返回对象的`cancel()`方法取消这个定时器就好了。
本地调试都没有问题,一切正常。
部署过程简单,只是把我新写的python文件放到服务器的对应目录上,然后找到对应的docker容器,restart一下就好了。
部署到服务器上后,发现问题不小!!!
这个定时器指定的方法怎么弄都不执行,除非你再次请求这个接口,而且是在第二次请求这个API接口的同时执行!WTF!!这和我想的不一样啊!我的想法是只要请求了一次这个接口,然后就等着10秒后就可以自动执行指定的通知系统B的方法了,而且我在本地调试时所看到的现象也是如此。但是,服务器上这个现象就不是这样!它偏不!
来来回回回回来来的改代码,验证,怀疑是不是哪里报错了呀?是不是服务器的docker容器的linux环境和本地开发的win环境不一样导致的啊?是不是服务器docker toolbox这个已经废弃的技术有某种隐藏BUG啊?甚至写了一小段代码,模仿这个等待10秒后发起http请求的逻辑,用一个简单的python脚本承载。然后在服务器对应的那个docker容器内部通过python xxx.py方式运行这小段代码,实验结果也是和我的预期一样的,定时器正常工作了,10秒后http请求顺利发起了。
怀疑人生了。
想着是不是`threading.Timer`方法设置的定时器是不是有缺陷,但是网上找了很久也没看到有人说有缺陷的。
行吧,我且换一种实现试试,这次用`apscheduler.schedulers.background`模块的`BackgroundScheduler`,主要代码如下:
#实例化
timer = BackgroundScheduler()
timer.add_job(callSystemB, \'date\',
run_date=datetime.datetime.now() + datetime.timedelta(seconds=10),
args=[data])
#启动
timer.start()
#不需要时(也就是用户A告知已经恢复时)就shutdown这个计划:
timer.shutdown()
写完代码更新到服务器上去,重启docker容器。访问,还是有问题。
但是这次问题清晰多了,很多人碰到过:https://www.cnblogs.com/zhuminghui/p/9252878.html,https://www.jianshu.com/p/78c46b0716b9
到这才知道是uwsgi托管django搞的鬼,uwsgi默认是one thread one processor,没有请求的时候,进程被挂起,子线程也就被挂起了。
在uwsgi.ini配置文件中添加了`enable-threads = true`后,定时器终于如我预期的工作了,问题解决。
然后再回头一想,是不是之前的`threading.Timer`也是由相同的问题造成无法执行的?再次验证后发现果然如此!!
可真坑!!
网上有很多部署教程,说了一大堆,遇到坑还是要自己一个个解决,这里有几个比较重要的坑大家一定要注意。
1、首先要安装python和python-dev环境,如果没有安装python-dev后面安装有些依赖要报错。这是第一个坑。
apt-get install python python-dev pip
2、安装uwsgi要先安装gcc的编译环境。这是第二个坑。如果不安装gcc运行uwsgi也不会报错,后台进程就是不启动。
我被这个坑了很久。
apt-get install gcc
3、找一个目录,上传你Django程序。我习惯打成zip上传,那么服务器上再安装一个unzip
apt-get install unzip
4、在Django项目目录下安装依赖。
pip install -r XXX.txt
5、安装uwsgi
pip install uwsgi
6、在项目目录下新建一个uwsgi.ini文件,写入以下代码。
[uwsgi]
socket=外网ip:端口(使用nginx连接时,使用socket)
http=外网ip:端口(直接做web服务器,使用http)
chdir=项目根目录
wsgi-file=项目中wsgi.py文件的目录,相对于项目根目录
processes=4
threads=2
master=True
pidfile=uwsgi.pid
daemonize=uswgi.log
7、测试时注释掉socket,用http方式能否运行Django成功。如果不能,那么很可能是你程序上有问题,要调试成功后才开始配置nginx。否则到时候报错,你不知道到底是哪个环节出了问题。
启动:uwsgi --ini uwsgi.ini
停止:uwsgi --stop uwsgi.pid
重启:uwsgi --reload uwsgi.pid
8、uwsgi通过http配置成功后,就开始安装nginx。
apt-get install nginx
9、这种方式安装好nginx后,一般情况下进入/etc/nginx/sites-available编辑,修改以下代码为你网站的相关信息。
server_name localhost blog.com; #在这里配置你的域名或是IP访问地址。
location / {
uwsgi_pass 127.0.0.1:8000;
include /etc/nginx/uwsgi_params;
}
location /media {
alias /home/media; #你的项目media静态文件目录。
}
location /static {
alias /home/static; #你的项目static静态文件目录。
}
10、配置后nginx,那么要修改uwsgi.ini文件,协议使用stock方式,注释掉http。
service nginx start
对外暴露访问端口
/sbin/iptables -I INPUT -p tcp --dport 80 -j ACCEPT
11、到这里大功告成!!后续优化:
A. 其实每次重启服务器还要运行nginx和uwsgi的启动命令挺麻烦,我们可以写一个sh脚本。比如start.sh,代码如下:
uwsgi --ini /home/uwsgi.ini #你的配置文件绝对路径。
service nginx start
/sbin/iptables -I INPUT -p tcp --dport 80 -j ACCEPT
B. vim etc/rc.local 文件写入以下代码。
sudo sh /home/start.sh #你的脚本绝对路径。
这样每次重启服务器,网站就自动运行了。呵呵。
本文来自:http://herostore.cn/article/11/