一. celery概要
1. 概念
分佈式任務隊列,django中所有需要用到異步操作的,或者定時操作的,都可以使用celery
2. 組件
Celery Beat : 任務調度器. Beat 進程會讀取配置文件的內容, 週期性的將配置中到期需要執行的任務發送給任務隊列.
Celery Worker : 執行任務的消費者, 通常會在多臺服務器運行多個消費者, 提高運行效率.
Broker : 消息代理, 隊列本身. 也稱爲消息中間件.
Producer : 任務生產者. 調用 Celery API , 函數或者裝飾器, 而產生任務並交給任務隊列處理的都是任務生產者.
Result Backend : 任務處理完成之後保存狀態信息和結果, 以供查詢.
3. 工作流程
producer發佈任務,傳遞給broker,borker 傳遞給 beat,beat 將任務調度給worker,worker執行任務並將結果存入數據庫(redis等)
二. celery搭建
1. 安裝redis
官網下載對應版本的redis tar包
tar -xf redis-3.2.12.tar.gz
cd redis-3.2.12
make
make install
./utils/install_server.sh
2. 安裝celery
pip3.6 install celery
3. 使用celery簡單測試
1). 編寫tasks.py
import time
from celery import Celery
# 使用redis作爲broker(消息隊列)
celery = Celery('tasks', broker='redis://localhost:6379/0')
# 創建任務函數
@celery.task
def sendmail(mail):
print("sending mail to %s..."%(mail['to']))
time.sleep(2.0)
print('mail sent.')
2). 進入tasks.py目錄啓動 celery 處理任務
celery -A tasks worker --loglevel=info
說明:celery對tasks任務啓動一個worker線程,日誌級別爲info
3). 另開命令行進入tasks.py目錄編寫測試腳本 test.py
from tasks import sendmail
import time
while 1:
print(sendmail.delay(dict(to='[email protected]')))
time.sleep(1)
調用delay函數,是將任務函數加入到隊列中
可以看到worker線程有對應的處理輸出,測試celery完成,目前celery可以使用了,但是還不能放入後臺運行,可以使用supervisor進行後臺運行管理
4. supervisor 安裝使用
1). 使用pip安裝supervisor
pip3.6 install supervisor
2). 生成supervisor配置文件
echo_supervisord_conf > /etc/supervisord.conf
3). 修改supervisor配置文件
vim /etc/supervisord.conf
# supervisor子配置目錄
[include]
files = /etc/supervisor/*.conf
# supervisor web管理界面
[inet_http_server]
port=192.168.89.133:9001
username=user
password=123
4). 創建子配置文件目錄和子配置文件
mkdir /etc/supervisor
vim /etc/supervisor/test.conf
[program:testpy]
command=python /opt/celery/test1.py ; 要執行的程序
;process_name=%(program_name)s
;numprocs=1
directory=/opt/celery/ ; 要進入的項目目錄(先進入,再執行)
;umask=022
priority=999 ; 任務要執行的優先級
startsecs = 5 ; 啓動 5 秒後沒有異常退出,就當作已經正常啓動了
autorestart = true ; 程序異常退出後自動重啓
startretries = 3 ; 啓動失敗自動重試次數,默認是 3
stdout_logfile = /opt/celery/supervisor.log
5). 編寫測試腳本/opt/celery/test1.py
import time
while 1:
with open('/tmp/bb.txt','a') as fobj:
fobj.write('111\n')
time.sleep(1)
3). 通過配置文件啓動supervisor
supervisord -c /etc/supervisord.conf
4). 查看運行狀態
supervisorctl status
testpy RUNNING pid 61435, uptime 0:02:32
注意報錯:gave up: testpy entered FATAL state, too many start retries too quickly
原因:
1. 腳本啓動比較慢,將配置文件中startsecs調大一些
2. 腳本運行退出,非死循環狀態,也會報這個錯 , 這種情況也不算報錯,執行完成自然退出,配置文件中設置了重啓3次,重啓執行完後自然又會退出
supervisorctl stop testpy 停止任務
supervisorctl start testpy 啓動任務
5. 使用 supervisor 控制 celery 半個小時更新一次
1). 將第4步的test1.py腳本更換成第3步的test.py腳本,celery就被supervisor控制
2). 定義時間,半個小時更新一次腳本
a. 創建supervisor子配置文件[調度器] /etc/supervisor/get_version_beat.conf
[program:get_version_beat]
# 執行命令
directory=/opt/gits/orange
command = /root/venv/bin/celery --workdir=/opt/gits/orange -A webhook beat -l info
# 日誌配置
loglevel = info
stdout_logfile = /tmp/get_version_beat.log
stderr_logfile = /tmp/get_version_beat.log
stdout_logfile_maxbytes = 50MB
stdout_logfile_backups = 1
# 給每個進程命名,便於管理
process_name = get_version_beat%(process_num)s
# 啓動的進程數,設置成雲服務器的vCPU數
numprocs_start = 1
numprocs = 1
# 設置自啓和重啓
autostart = true
autorestart = true
redirect_stderr = True
b. 創建supervisor子配置文件[消費者] /etc/supervisor/get_version_worker.conf
[program:get_version_worker]
# 執行用戶
# user = root
# 執行的命令
directory=/opt/gits/orange
command = /root/venv/bin/celery --workdir=/opt/gits/orange -A webhook worker -l info
# 日誌文件配置
loglevel = info
stdout_logfile = /tmp/get_version_worker.log
stderr_logfile = /tmp/get_version_worker.log
stdout_logfile_maxbytes = 50MB
stdout_logfile_backups = 1
# 給每個進程命名,便於管理
process_name = get_version_worker%(process_num)s
# 啓動的進程數,設置成雲服務器的vCPU數
numprocs_start = 1
numprocs = 1
# 設置自啓和重啓
autostart = true
autorestart = true
redirect_stderr = True
c. 到項目根目錄配置celery.py文件
... ...
app.conf.update(
CELERYBEAT_SCHEDULE = {
... ...
'get_version':{
'task': 'celery_app.tasks.get_version',
'schedule': crontab(minute='1', hour='1', day_of_week='*', day_of_month='*', month_of_year='*'),
}
}
}
d. 進入到celery_app目錄,進行功能編寫
vim get_version.py
import paramiko
import requests
import datetime
import redis
import json
import os
import subprocess
from threading import Timer
import logging
logger = logging.getLogger('t3logger')
class GetVersion():
def __init__(self):
pass
def par_ver(self, host0, app_name):
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname=host0, port=2222, username='dc')
stdin, stdout, stderr = client.exec_command("awk '/jfrog/' /data/scripts/deploy_%s.sh | tail -1 | awk '{print $4}' | awk -F/ '{print $4}'" % (app_name))
out = stdout.read().decode('utf-8')
err = stderr.read().decode('utf-8')
if out == '':
out = '0'
client.close()
out = out.strip()
return out
def chaoshi(self, args, timeout):
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
timer = Timer(timeout, lambda process: process.kill(), [p])
try:
timer.start()
stdout, stderr = p.communicate()
return_code = p.returncode
if stdout != b'':
return True
else:
return False
finally:
timer.cancel()
def main(self):
os.system('rm -f /tmp/hosts_questions.txt')
# 此處爲數據接口,此功能不贅述
all_keys = requests.get("http://10.0.0.1:10080/assets/inventory/--list/None/")
all_objs = all_keys.json()
i = 1
objs = []
for item in all_objs:
if item == 'all' or item == '_meta':
continue
if 't3_data_apps' in item:
env = item.split('_ktz_data_apps_')[0]
center = 'ktz_data_apps'
app_name = item.split('_ktz_data_apps_')[-1]
elif 'ktz_m' in item:
env = item.split('_ktz_m_')[0]
center = 'ktz_m'
app_name = item.split('_ktz_m_')[-1]
else:
env = item.split('_')[0]
center = item.split('_')[1]
app_name = item.split('_')[-1]
hosts = all_objs[item]['hosts']
str = ''
for host in hosts:
str += host + ','
hosts_str = str
host0 = all_objs[item]['hosts'][0]
result = self.chaoshi(['telnet', host0, '53742'], 2)
if result == False:
os.system('echo %s >> /tmp/hosts_questions.txt' % (host0))
continue
ver = self.par_ver(host0, app_name)
now = datetime.datetime.now().strftime('%Y-%m-%d')
objs.append([i, center, app_name, ver, hosts_str, now, env])
i += 1
red = redis.Redis(host='localhost', port=6379, db=1)
objs_json = json.dumps(objs)
red.set('versions', objs_json)
if __name__ == '__main__':
gv = GetVersion()
gv.main()
e. 進入到celery_app目錄的tasks.py文件,引入get_version功能
from celery_app.get_version import GetVersion
@shared_task
def get_version():
"""
週期性收集服務器上對應的服務版本信息
:return:
"""
info = '週期性收集服務器上對應的服務版本信息'
logger.info(info)
gv = GetVersion()
gv.main()
f. 啓動django服務
g. 重啓supervisord服務
killall -9 supervisord
supervisord -c /etc/supervisord.conf
6. 問題排錯
問題1:查看日誌,發現beat啓動錯誤
tail -10f /tmp/get_version_beat.log
celery beat v4.3.0 (rhubarb) is starting.
ERROR: Pidfile (celerybeat.pid) already exists.
排錯思路:不用創建beat調度器,因爲已經存在了
1. 去掉get_version_beat.conf
rm -f /etc/supervisor/get_version_beat.conf
2. 啓動supervisor查看是否還有錯誤
tail -10f /tmp/get_version_worker.log
[2019-12-16 17:38:34,897: INFO/MainProcess] Connected to redis://127.0.0.1:6379/8
[2019-12-16 17:38:34,903: INFO/MainProcess] mingle: searching for neighbors
[2019-12-16 17:38:35,945: WARNING/MainProcess] /root/venv/lib/python3.6/site-packages/celery/app/control.py:54: DuplicateNodenameWarning: Received multiple replies from node name: celery@k8snode01.
Please make sure you give each node a unique nodename using
the celery worker `-n` option.
pluralize(len(dupes), 'name'), ', '.join(sorted(dupes)),
[2019-12-16 17:38:35,946: INFO/MainProcess] mingle: all alone
[2019-12-16 17:38:35,954: WARNING/MainProcess] /root/venv/lib/python3.6/site-packages/celery/fixups/django.py:202: UserWarning: Using settings.DEBUG leads to a memory leak, never use this setting in production environments!
warnings.warn('Using settings.DEBUG leads to a memory leak, never '
[2019-12-16 17:38:35,954: INFO/MainProcess] celery@k8snode01 ready.
supervisorctl status
get_version_worker:get_version_worker1 RUNNING pid 7598, uptime 0:01:55
啓動正常,並無錯誤,等待運行結果結束
雖然沒有大問題,但是看get_version_worker.log日誌,查看報錯 UserWarning: Using settings.DEBUG leads to a memory leak,主項目settings配置文件debug改爲false即可
問題2:結果並沒有實現,沒有創建/tmp/abc.txt測試文件,沒有將數據導入到redis庫中,不知道原因是啥?
排錯思路:
1. 直接運行 get_version.py 沒有問題,數據也可以導入到redis庫中,說明 get_version.py 腳本沒有問題
2. 檢查 celery_app 目錄下 tasks.py 腳本, 引入GetVersion類並實例化調用方法,沒有問題
3. 'get_version':{ 冒號後面沒有空格,重新運行
tail -10f /tmp/supervisord.log
'get_version_worker1' with pid 103842 顯示這個任務已經啓動
tail -10f /tmp/get_version_worker.log
日誌都沒啥變化,不是這個原因引起的。
4. 第二天早上查看,日誌神奇的出現了,而且redis也有了對應的鍵值對,猜測是 celery schedule 的單位問題
於是將schedule後面寫上3600(秒)
重啓django服務,更新 supervisorctl 配置
supervisorctl update
supervisorctl reload (重啓supervisord)
supervisorctl status
get_version_worker:get_version_worker1 RUNNING pid 32703, uptime 0:02:08
啓動時間:9:30;測試時間:10:30-40之間
結果:10:50後還沒有數據。
分析:收集信息需要時間,1100個服務,每個花費5s收集,總共需要1100*5/60=91分鐘,9:30+91=11:00左右結束,還沒有寫入,11:20查看一次,
建議:先測試,收集少量數據,就不會花費太多時間
結果:到下午 13:30 redis 裏都沒有數據
先排除日誌報錯:debug=false和名稱相同的錯,配置了啓動位置加了 -n get_version_worker
5. 查看 supervisorctl 是否可以控制celery
使用 gv.main()函數(將字符串傳入reids中) 腳本測試
6. 第三天早上查看,redis裏又存入了versions鍵值對,在/tmp下也創建了相應的測試文件abc.txt,時間是今天的9:00
思考:supervisorctl 裏的任務壓根沒有運行,但是數據還是創建了,可能是celery自己創建的,並不受supervisor控制
排錯:
ss -anptu | grep celery
發現有很多celery進程在運行,殺死celery和supervisord進程,刪除redis中的鍵值
killall -9 celery
killall -9 supervisord
redis-cli
> select 1
> keys *
1) "versions"
> del versions
重新啓動django和supervisord
supervisord -c /etc/supervisord.conf
ss anptu | grep celery ; ss anptu | grep supervisord 查看進程已經存在
查看日誌也沒有什麼報錯
進入redis並沒有看到數據存入庫中,淚奔
/etc/supervisor/下沒有配置beat,添加配置,重啓supervisord和celery,發現版本信息可以存入到redis庫中,踩了好幾天的坑,問題終於解決了。
問題排錯總結:
看來第5步/etc/supervisor/get_version_beat.conf這個文件還不能刪,要回顧一下celery定時任務原理,如果沒有beat,就相當於沒有了調度器,沒有任務調度器,配置了也不會執行。
三. 總結
使用任何一項新的技術,一定要將原理搞清楚,弄明白,然後再對照着原理搭建自己的任務,每個環境都不漏下,就很大可能會走通,不會出現什麼問題。
雲計算之celery+django+supervisor實現定時任務
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.