Celery使用過程中遇到的一些問題
本文轉載自微信公眾號「新鈦云服」,作者黃平安 。轉載本文請聯(lián)系新鈦云服公眾號。
做項目時,其中用了Celery這種工具。前段時間,遇到過一些問題,解決后沒有進行總結,今天就抽個空把它記錄下來。
用過Celery的程序員,都知道它是一種異步執(zhí)行程序的工具。里面有Worker、Task等概念,這里我就不贅述了。
1、功能需求
在使用Celery的過程中,我需要知道Task的狀態(tài)。Task就是異步任務,用戶沒執(zhí)行一次異步任務,就會新創(chuàng)建一個Task,代表此異步任務。
該Task對象中,包含著許多信息,其中也包括狀態(tài)。我的項目中,需要根據(jù)Task的狀態(tài)來判斷,這次異步任務是否還在執(zhí)行中。
2、 出現(xiàn)問題
既然,我需要Task的狀態(tài),那我就需要查看Task的狀態(tài)怎么獲取啊!我查看了一下Celery的源碼!發(fā)現(xiàn)在Celery的AsyncResult對象中,有個state字段。如下圖所示:
根據(jù),該源碼中的注釋說明,該字段有好幾個值。分別是:PENDING、STARTED、RETRY、FAILURE、SUCCESS。
然后,我就趕緊寫個demo驗證一下,看看這個state字段是不是我想要的。demo如下:
我執(zhí)行項目中的Celery異步任務,根據(jù)我之前查出來的task id。執(zhí)行demo查詢該Task的狀態(tài)。
這時候,問題就出來了,根據(jù)demo返回的Task狀態(tài)為PENDING。表示Task還在等待中,尚未執(zhí)行。
這就不對了,此時的狀態(tài)應該是STARTED,因為我的Task已經(jīng)執(zhí)行好一段時間了,它返回的結果不準確。
3、 解決問題
難道我用的字段不正確,然后我就谷歌搜索。發(fā)現(xiàn)Celery官網(wǎng)和網(wǎng)上的大多數(shù)反饋也是表示Task的狀態(tài)字段就是state。
那我為什么測出的結果和理論的不同呢?然后,我詳細查看Celery的配置,發(fā)現(xiàn)了一個參數(shù):CELERY_TRACK_STARTED。
該參數(shù)默認是關閉的,表示只要Celery開始執(zhí)行Task就會追蹤該Task。所以,開啟該參數(shù)后,Task的狀態(tài)是時刻記在BACKEND中的。
好,我在Celery的配置文件處,加了該參數(shù)。
然后再執(zhí)行Celery的異步任務,得到的結果是我想要的。
4、 引申思考以及問題
我的問題是解決了,但是這引起了我對Celery的一些興趣。
當時,我就考慮到,如果我把正在運行中的Task任務,直接kill掉。那么此時我再去看Task的狀態(tài),它會是啥呢?
STARTED,正在執(zhí)行中的狀態(tài)。
而此時,Task已經(jīng)關掉,它不應該是這種狀態(tài)。為此,我猜測這應該是,Task意外結束,沒有改變Task的狀態(tài)導致的。
但是這樣就不太好了,因為只要是程序,那它就一定有意外退出的可能。假設,我的項目需要查看Task的狀態(tài),當Task被意外kill掉時,項目中查看Task的狀態(tài)就不準確了。
5、 引申問題解決思路一
當時我想:既然Task被kill掉之后,還能顯示運行中,說明此Task的狀態(tài)一定是保存在某個地方,我把該Task的數(shù)據(jù)清空了不就完了。
而Celery的數(shù)據(jù)存儲,只有可能存在三個地方:使用RabbitMQ的消息代理(BROKER),使用Redis的任務結果保存處(BACKEND),以及文件保存(當然這點基本上沒可能,Celery沒這樣用過,我主要是死馬當活馬醫(yī))。
這三個地方,其實只有Redis可能存放Task的狀態(tài),按照Celery的機制,也只有它最有可能存放。
但是呢?為了弄清Celery的存儲機制,我想試試Celery會把數(shù)據(jù)存到RabbitMQ中嗎?然后,我執(zhí)行了,以下命令,清空RabbitMQ隊列。
此時,RabbitMQ隊列的數(shù)據(jù)已經(jīng)為空。然后我查看Task的狀態(tài),依然還為STARTED。說明不是它存儲Task的狀態(tài)。
然后,我進入redis中。使用keys *命令,發(fā)現(xiàn)許多帶有celery-task-meta前綴的記錄。
后經(jīng)查明,這些記錄的后綴就是Celery中Task的id。
我根據(jù)我的Task的id,查出如下內(nèi)容:
我們能清晰的看出,內(nèi)容中Task的狀態(tài)為STARTED。這說明的確是存放在Redis中的。然后,我把這條記錄刪除,再執(zhí)行demo,Task的狀態(tài)不再是STARTED,項目中顯示的狀態(tài)就正確了。
但是,這又引出了一個問題,怎么刪除這條記錄,或者什么時候刪除這條記錄。當然,我們刪除很容易,編程語言的redis模塊或者Celery自己提供的代碼都能刪除。Celery中根據(jù)task id刪除backend中的數(shù)據(jù)。
那么,什么時候刪除這條記錄呢?Celery默認的是保留此數(shù)據(jù)24小時。我左思右想,還是不刪這條記錄了。換種思路解決這個問題吧!
6、 引申問題解決思路二
要知道,Celery的Task是運行在Worker上的。只要判斷此時的Worker程序是否還正常運行,不就可以判斷Task的狀態(tài)是否還在運行中了嗎?
說干就干,我們通過ps命令,可以查看Celery運行的程序。
然后,我們執(zhí)行task時,把它本身運行的程序進程pid記錄下來,發(fā)現(xiàn)正好就是Worker的進程pid。
這樣就簡單了,我們只需要結合Celery提供查看Task狀態(tài)的接口,以及Python提供的Psutil查看進程的模塊。就能最終判斷Task是不是真在運行中。只有Task的狀態(tài)為STARTED,并且Task所在的Worker進程在運行中,Task才是真正在運行狀態(tài)。
Psutil查看進程是否運行代碼如下:
7、 總結
今天只是把我前段時間遇到的問題以及解決思路記錄下來,也沒寫Celery的內(nèi)部機制等等,這些東西網(wǎng)上一大把,我也不是很有寫它們的必要。
做過幾年的程序員,感觸最多的就是解決問題的思路。一旦遇到某個問題了,一種思路解決不了,可以換種思路解決,另一種思路可能也不一定能完美解決,但可以加深對問題的理解。而怎么想到另一種思路,就需要平時的多積累和提高自己的認知范圍了,這還是比較難的。