前言:
笔者最近在搞一个项目,需要把python客户端代码(可以理解成绑定不同账号的爬虫吧)部署在20+台windows机器上(以后还会追加新机器)。由于客户机多,而且常常优化、修bug、加功能,使得笔者为了更新代码而不胜其扰!小小的调整居然要一台台搞(windows只能远程连接,你懂的,可没办法linux那样同时给多个ssh连接发相同的命令)。于是,笔者测试并开发了一套“基于windows自动下拉git代码并重启程序模块”(笔者不知道该简称什么...大概就是自动更新代码吧)。
思路:
linux系统就不多说了,只需要git检测到更新后,kill -9 掉即可,可以根据参数识别,例如 python client_main.py 利用ps筛选出来干掉即可。
windows没有这命令,但是可以根据执行文件名杀掉。例如杀掉名叫 python.exe 的所有进程。这是唯一方法,但是你会把系统下所有的python都干掉,包括基于python的调试工具。
于是,我想到的方法是用最土的方法,“用标识位告知程序是否有更新,是否要介绍,是否要重启”。233333虽然又土又不帅气,却很实用。特别像我这种客户端必须等一个任务完成后才能终结的,非常合适。一个守护进程检测更新,一个文件标识状态,子进程自己检测标识状态去停止。简简单单的完成了自动化需求。
代码:
测试git仓库 : https://gitee.com/kid0/tx 【有需要测试,代码请上传到自己的git上方便测试。当然也可以在自己的git项目中直接引用auto.py下的函数】
模块代码(我命名为auto.py)
#-*-coding:utf-8-*-
import os
import time
import codecs
from configparser import ConfigParser
from multiprocessing import Process
local_path = os.path.join(os.getcwd(),'local.txt' )
code_check_time = 10
def set_code_check_time(t=10):
"""
设置git检测频率,单位秒
"""
global code_check_time
code_check_time = t
def auto_manager_main(fun,*args,**kwargs):
"""
委托进程
"""
print('主进程PID:',os.getpid())
global code_check_time
_code_check_time = code_check_time
if 1:
if not os.path.isfile(local_path):
f = open(local_path,'w+')
txt = """[local]
need_stop = 0
need_restart = 0"""
f.write(txt)
f.close()
con = ConfigParser()
con.read_file(codecs.open(local_path,'r+',"utf-8-sig"))
con.set('local','need_stop','0')
con.set('local','need_restart','0')
con.write(open(local_path,'w+'))
_f = True
while True:
con.read_file(codecs.open(local_path,'r+',"utf-8-sig"))
if not con.get('local','need_stop') == '1':
_res = os.popen('git pull').read()
if 'Fast-forward' in _res:
con.set('local','need_stop','1')
else:
con.set('local','need_stop','0')
con.write(open(local_path,'w+'))
if con.get('local','need_restart') == '1' or _f:
_f = False
p = Process(target=fun,args=args,kwargs=kwargs)
p.start()
con.set('local', 'need_restart','0')
con.write(open(local_path,'w+'))
time.sleep(10)
def check_stop_flag():
"""
检测是否应该停止,在被托管的进程里使用
"""
con = ConfigParser()
con.read_file(codecs.open(local_path,'r+','utf-8-sig'))
if con.get('local','need_stop') == "1":
print('code was updata,now stop running!!')
con.set('local','need_stop',"0")
con.set('local','need_restart',"1")
con.write(open(local_path,"w+"))
exit(0)
测试main.py文件
#-*-coding:utf-8-*-
from auto import check_stop_flag,auto_manager_main,set_code_check_time
import time
def run(x=0,y=0):
#开始子进程
print('run!!!!')
for i in range(x,500):
#设置在这里检测是否要退出进程
check_stop_flag()
#....做不应该突然中断任务....
time.sleep(1)
print('i={} y={}'.format(i,y))
#....做完了...
def tx(y=1):#(稍后测试我把1改成10)
#main函数【可变参数可以传这里,如果参数值改变,程序重启后会跟随改变】
run(x=1,y=y)#(稍后测试我把1改成10)
if __name__ == '__main__':
#设置检测频率(似乎并不会生效,待验证优化)
set_code_check_time(1)
#把进程委托自动管理【固定参数可以这里传入,重启程序参数也不会跟随改变的】
auto_manager_main(tx,y=1)#(稍后测试我把1改成10)
我先运行main.py文件,顺利的执行后,我在git上把参数x和y的值由1改成10。结果如下。
主进程PID: 6004
run!!!!
i=1 y=1
i=2 y=1
i=3 y=1
i=4 y=1
i=5 y=1
i=6 y=1
i=7 y=1
i=8 y=1
i=9 y=1
i=10 y=1
i=11 y=1
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
i=12 y=1g objects: 33% (1/3)
Unpacking objects: 100% (3/3), done.
From gitee.com:kid0/tx
3ca10d8..9f08a5d master -> origin/master
i=13 y=1
code was updata,now stop running!!
run!!!!
i=10 y=1
i=11 y=1
i=12 y=1
i=13 y=1
i=14 y=1
在我git改了代码后,main正常的自己退出,并且重启了。参数也是用最新的去运行。
实验完成!!!投入使用!
注意:
1、.gitignore文件必须加入忽略local.txt文件!!!否则会导致冲突,无法下拉代码!!!
2、可能会被修改的参数绝对不能由auto_manager_main传!应当用另一个函数简单包起main函数,像我测试的那样。
解疑:
1、为什么用Process(进程)而不是threading(线程)?
python虽然是解释性语言,但它需要把py编译成pyc文件去运行。进程启动时会重新编译,但线程不会。大家可以试试。虽然会更新代码并重启,但是运行结果不会改变。
2、是否适用于所有python项目?
适用于“一套代码放在多个客户机,并且经常更新git”的情况。对于server服务我并不推荐。因为server服务应该是累积一定的更新后再更新的线上。
3、直接杀掉进程用脚本启动会不会更简单?
条条大路通罗马。方法很多,看你喜欢。像我的项目,任务是从队列拿的。如果突然干掉了进程,任务就会丢失,而且我这每个任务都十分关键!条条任务都是钱!(我有双重保护,拿到任务会写到本地文件内,完成再删除,防一手机器宕机的可能)如果是普通爬虫之类的倒无所谓,丢失1-2条数据没关系的。
4、git pull为什么不是检测“Already up to date”?
一开始我也是这样的考虑的。但是,网络异常、冲突、sshkeygen失效等问题都会导致无限的重启程序。因此只能检测pull成功下拉的标识。
————————————————————————————————————————————————————————
以上就是我开发的模块和理解。欢迎大家使用和改进。
转载请注明出处。