標籤:項目 文章 使用者 如何 菊花
flower:
http://docs.celeryproject.org/en/latest/getting-started/index.html
http://flower.readthedocs.org/en/latest/config.html
https://denibertovic.com/posts/celery-best-practices/
http://daimin.github.io/posts/celery-shi-yong.html
http://ju.outofmemory.cn/entry/221884
https://linfan1.gitbooks.io/kubernetes-chinese-docs/content/098-Distributed%20Task%20Queue.html
http://gangtao.is-programmer.com/posts/83922.html
https://linfan1.gitbooks.io/kubernetes-chinese-docs/content/098-Distributed%20Task%20Queue.html
http://www.vimer.cn/2014/07/%E5%88%86%E5%B8%83%E5%BC%8F%E6%B6%88%E6%81%AF%E7%B3%BB%E7%BB%9F%E5%B0%9D%E8%AF%95rabbitmq-celery-redis.html
http://flower-docs-cn.readthedocs.org/zh/latest/config.html
http://dongweiming.github.io/blog/archives/how-to-use-celery/
celery最佳實務
作為一個Celery使用重度使用者,看到Celery Best Practices這篇文章,不由得菊花一緊。乾脆翻譯出來,同時也會加入我們項目中celery的實戰經驗。
通常在使用Django的時候,你可能需要執行一些長時間的背景工作,沒準你可能需要使用一些能排序的任務隊列,那麼Celery將會是一個非常好的選擇。
當把Celery作為一個任務隊列用於很多項目中後,作者積累了一些最佳實務方式,譬如如何用合適的方式使用Celery,以及一些Celery提供的但是還未充分使用的特性。
1,不要使用資料庫作為你的AMQP Broker
資料庫並不是天生設計成能用於AMQP broker的,在生產環境下,它很有可能在某時候當機(PS,當掉這點我覺得任何系統都不能保證不當吧!!!)。
作者猜想為啥很多人使用資料庫作為broker主要是因為他們已經有一個資料庫用來給web app提供資料存放區了,於是乾脆直接拿來使用,設定成Celery的broker是很容易的,並且不需要再安裝其他組件(譬如RabbitMQ)。
假設有如下情境:你有4個後端workers去擷取並處理放入到資料庫裡面的任務,這意味著你有4個進程為了擷取最新任務,需要頻繁地去輪詢資料庫,沒準每個worker同時還有多個自己的並發線程在幹這事情。
某一天,你發現因為太多的任務產生,4個worker不夠用了,處理任務的速度已經大大落後於生產任務的速度,於是你不停去增加worker的數量。突然,你的資料庫因為大量進程輪詢任務而變得響應緩慢,磁碟IO一直處於高峰值狀態,你的web應用也開始受到影響。這一切,都因為workers在不停地對資料庫進行DDOS。
而當你使用一個合適的AMQP(譬如RabbitMQ)的時候,這一切都不會發生,以RabbitMQ為例,首先,它將任務隊列放到記憶體裡面,你不需要去訪問硬碟。其次,consumers(也就是上面的worker)並不需要頻繁地去輪詢因為RabbitMQ能將新的任務推送給consumers。當然,如果RabbitMQ真出現問題了,至少也不會影響到你的web應用。
這也就是作者說的不用資料庫作為broker的原因,而且很多地方都提供了編譯好的RabbitMQ鏡像,你都能直接使用,譬如這些。
對於這點,我是深表贊同的。我們系統大量使用Celery處理非同步任務,大概平均一天幾百萬的非同步任務,以前我們使用的mysql,然後總會出現任務處理延時太嚴重的問題,即使增加了worker也不管用。於是我們使用了redis,效能提升了很多。至於為啥使用mysql很慢,我們沒去深究,沒準也還真出現了DDOS的問題。
2,使用更多的queue(不要只用預設的)
Celery非常容易設定,通常它會使用預設的queue用來存放任務(除非你顯示指定其他queue)。通常寫法如下:
@app.task()def my_taskA(a, b, c): print("doing something here...")@app.task()def my_taskB(x, y): print("doing something here...")
這兩個任務都會在同一個queue裡面執行,這樣寫其實很有吸引力的,因為你只需要使用一個decorator就能實現一個非同步任務。作者關心的是taskA和taskB沒準是完全兩個不同的東西,或者一個可能比另一個更加重要,那麼為什麼要把它們放到一個籃子裡面呢?(雞蛋都不能放到一個籃子裡面,是吧!)沒準taskB其實不怎麼重要,但是量太多,以至於重要的taskA反而不能快速地被worker進行處理。增加workers也解決不了這個問題,因為taskA和taskB仍然在一個queue裡面執行。
3,使用具有優先順序的workers
為瞭解決2裡面出現的問題,我們需要讓taskA在一個隊列Q1,而taskB在另一個隊列Q2執行。同時指定x workers去處理隊列Q1的任務,然後使用其它的workers去處理隊列Q2的任務。使用這種方式,taskB能夠獲得足夠的workers去處理,同時一些優先順序workers也能很好地處理taskA而不需要進行長時間的等待。
首先手動定義queue
CELERY_QUEUES = ( Queue(‘default‘, Exchange(‘default‘), routing_key=‘default‘), Queue(‘for_task_A‘, Exchange(‘for_task_A‘), routing_key=‘for_task_A‘), Queue(‘for_task_B‘, Exchange(‘for_task_B‘), routing_key=‘for_task_B‘),)
然後定義routes用來決定不同的任務去哪一個queue
CELERY_ROUTES = { ‘my_taskA‘: {‘queue‘: ‘for_task_A‘, ‘routing_key‘: ‘for_task_A‘}, ‘my_taskB‘: {‘queue‘: ‘for_task_B‘, ‘routing_key‘: ‘for_task_B‘},}
最後再為每個task啟動不同的workers
celery worker -E -l INFO -n workerA -Q for_task_Acelery worker -E -l INFO -n workerB -Q for_task_B
在我們項目中,會涉及到大量檔案轉換問題,有大量小於1mb的檔案轉換,同時也有少量將近20mb的檔案轉換,小檔案轉換的優先順序是最高的,同時不用佔用很多時間,但大檔案的轉換很耗時。如果將轉換任務放到一個隊列裡面,那麼很有可能因為出現轉換大檔案,導致耗時太嚴重造成小檔案轉換延時的問題。
所以我們按照檔案大小設定了3個優先隊列,並且每個隊列設定了不同的workers,很好地解決了我們檔案轉換的問題。
4,使用Celery的錯誤處理機制
大多數任務並沒有使用錯誤處理,如果任務失敗,那就失敗了。在一些情況下這很不錯,但是作者見到的多數失敗任務都是去調用第三方API然後出現了網路錯誤,或者資源不可用這些錯誤,而對於這些錯誤,最簡單的方式就是重試一下,也許就是第三方API臨時服務或者網路出現問題,沒準馬上就好了,那麼為什麼不試著重試一下呢?
@app.task(bind=True, default_retry_delay=300, max_retries=5)def my_task_A(): try: print("doing stuff here...") except SomeNetworkException as e: print("maybe do some clenup here....") self.retry(e)
作者喜歡給每一個任務定義一個等待多久重試的時間,以及最大的重試次數。當然還有更詳細的參數設定,自己看文檔去。
對於錯誤處理,我們因為使用情境特殊,例如一個檔案轉換失敗,那麼無論多少次重試都會失敗,所以沒有加入重試機制。
5,使用Flower
Flower是一個非常強大的工具,用來監控celery的tasks和works。
這玩意我們也沒怎麼使用,因為多數時候我們都是直接連接redis去查看celery相關情況了。貌似挺傻逼的對不,尤其是celery在redis裡面存放的資料並不能方便的取出來。
6,沒事別太關注任務退出狀態
一個任務狀態就是該任務結束的時候成功還是失敗資訊,沒準在一些統計場合,這很有用。但我們需要知道,任務退出的狀態並不是該任務執行的結果,該任務執行的一些結果因為會對程式有影響,通常會被寫入資料庫(例如更新一個使用者的朋友列表)。
作者見過的多數項目都將任務結束的狀態存放到sqlite或者自己的資料庫,但是存這些真有必要嗎,沒準可能影響到你的web服務的,所以作者通常設定CELERY_IGNORE_RESULT = True去丟棄。
對於我們來說,因為是非同步任務,知道任務執行完成之後的狀態真沒啥用,所以果斷丟棄。
7,不要給任務傳遞 Database/ORM 對象
這個其實就是不要傳遞Database對象(例如一個使用者的執行個體)給任務,因為沒準序列化之後的資料已經是到期的資料了。所以最好還是直接傳遞一個user id,然後在任務執行的時候即時的從資料庫擷取。
對於這個,我們也是如此,給任務只傳遞相關id資料,譬如檔案轉換的時候,我們只會傳遞檔案的id,而其它檔案資訊的擷取我們都是直接通過該id從資料庫裡面取得。
最後
後面就是我們自己的感觸了,上面作者提到的Celery的使用,真的可以算是很好地實踐方式,至少現在我們的Celery沒出過太大的問題,當然小坑還是有的。至於RabbitMQ,這玩意我們是真沒用過,效果怎麼樣不知道,至少比mysql好用吧。
最後,附上作者的一個Celery Talk https://denibertovic.com/talks/celery-best-practices/。
本文出自 “Mr_Computer” 部落格,請務必保留此出處http://caochun.blog.51cto.com/4497308/1747382
Celery+rabbitmq+mysql+flower