對於網站來說,給使用者一個較好的體驗是很重要的事情,其中最重要的指標就是網站的瀏覽速度。因此服務端要從各個方面對網站效能進行優化,比如可採用CDN載入一些公共靜態檔案,如js和css;合併css或者js從而減少靜態檔案的請求等等…..還有一種方法是將一些不需要立即返回給使用者,可以非同步執行的任務交給後臺處理,以防網路阻塞,減小響應時間。看了the5fire的部落格之後我受到了啟發,決定從這方面進行改進。
我採用celery實現後臺非同步執行的需求。對於celery,先看一下網上給的celery的定義和用途:
1 2 3 4 5 |
Celery is a simple, flexible, and reliable distributed system to process vast amounts of messages, while providing operations with the tools required to maintain such a system. It’s a task queue with focus on real-time processing, while also supporting task scheduling. Celery has a large and diverse community of users and contributors, you should come join us on IRC or our mailing-list. |
上面的英文還是比較好理解的,簡而言之,就是一個專注於實時處理和任務排程的分散式佇列。
我買了一本《Python Web開發實戰》,那裡面也介紹了celery。說了使用celery的常見場景:
- Web應用。當使用者觸發一個動作需要較長時間來執行完成時,可以把它作為任務交給celery非同步執行,執行完再返回給使用者。這點和你在前端使用ajax實現非同步載入有異曲同工之妙。
- 定時任務。假設有多臺伺服器,多個任務,定時任務的管理是很困難的,你要在不同電腦上寫不同的crontab,而且還不好管理。Celery可以幫助我們快速在不同的機器設定不同任務。
- 其他可以非同步執行的任務。比如傳送簡訊,郵件,推送訊息,清理/設定快取等。這點還是比較有用的。
綜上所述,第1點和第3點的用途是我考慮celery的原因。目前,考慮在Django中實現兩個功能:
- 文章閱讀量的統計
- 傳送郵件
關於文章閱讀量的統計,我之前的做法就是在使用者每一次訪問文章的時候,都會同步執行一遍+1的函式,現在打算用非同步執行的方式。
下面介紹在Django中的使用方法:
1、環境準備
安裝celery,rabbitmq,django-celery.
2、啟動訊息中介軟體rabbitmq。
用它的原因是celery官方推薦的就是它,也可以用Redis等,但Redis會因為斷電的原因造成資料全部丟失等問題。
讓其在後臺執行:
1 |
sudo rabbitmq-server -detached |
3、在Django中配置(原始碼)
專案程式碼結構
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
dailyblog ├── blog │ ├── models.py │ ├── serializer.py │ ├── tasks.py │ ├── urls.py │ ├── views.py ├── config.yaml ├── dailyblog │ ├── celery.py │ ├── __init__.py │ ├── __init__.pyc │ ├── settings.py │ ├── urls.py │ ├── wsgi.py |
對於celery的配置,需要編寫幾個檔案:
1 2 3 4 5 6 7 |
1、dailyblog/celery.py 2、dailyblog/settings.py 3、blog/tasks.py 4、dailyblog/__init__.py |
1、dailyblog/celery.py
本模組主要是建立了celery應用,配置來自django的settings檔案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from __future__ import absolute_import,unicode_literals #目的是拒絕隱士引入,celery.py和celery衝突。 import os from celery import Celery from django.conf import settings os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dailyblog.settings") #建立celery應用 app = Celery('dailyblog') #You can pass the object directly here, but using a string is better since then the worker doesn’t have to serialize the object. app.config_from_object('django.conf:settings') #如果在工程的應用中建立了tasks.py模組,那麼Celery應用就會自動去檢索建立的任務。比如你新增了一個任務,在django中會實時地檢索出來。 app.autodiscover_tasks(lambda :settings.INSTALLED_APPS) |
關於config_from_object,我對於如何載入配置檔案還是比較感興趣的,於是研究了一下原始碼,具體可以見:“celery載入配置檔案”。
2、settings.py
配置celery,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import djcelery djcelery.setup_loader() #末尾新增 CELERYBEAT_SCHEDULER = ‘djcelery.schedulers.DatabaseScheduler‘ # 這是使用了django-celery預設的資料庫排程模型,任務執行週期都被存在你指定的orm資料庫中 #INstalled_apps INSTALLED_APPS = ( ‘django.contrib.admin‘, ‘django.contrib.auth‘, ‘django.contrib.contenttypes‘, ‘django.contrib.sessions‘, ‘django.contrib.messages‘, ‘django.contrib.staticfiles‘, ‘djcelery‘, #### 這裡增加了djcelery 也就是為了在django admin裡面可一直接配置和檢視celery ‘blog‘, ### ) |
setup_loader目的是設定celery的載入器,原始碼:
1 2 3 4 |
def setup_loader(): # noqa os.environ.setdefault( b'CELERY_LOADER', b'djcelery.loaders.DjangoLoader', ) |
3、dailyblog/init.py
1 2 3 4 5 |
from __future__ import absolute_import # This will make sure the app is always imported when # Django starts so that shared_task will use this app. from .celery import app as celery_app |
4、blog/tasks.py
1 2 3 4 5 6 7 8 9 |
from django.db.models import F from .models import Article from dailyblog import celery_app @celery_app.task def incr_readtimes(article_id): return Article.objects.filter(id=article_id).update(read_times=F('read_times') + 1) |
這裡面新增了一個任務。任務可以通過delay方法執行,也可以週期性地執行。
這裡還需要注意,如果把上面任務的返回值賦值給一個變數,那麼程式也會被阻塞,需要等待非同步任務返回的結果。因此,實際應用不需要賦值。
上面的程式碼寫好後,要執行資料庫更新:
1 2 |
python manage.py makemigrations python manage.py migrate. |
Django會建立了幾個資料庫,分別為:
Crontabs Intervals Periodic tasks Tasks Workers
在views.py新增非同步任務:
1 2 3 4 5 6 7 |
from .tasks import incr_readtimes class ArticleDetailView(BaseMixin,DetailView): def get(self, request, *args, **kwargs): ....... incr_readtimes.delay(self.object.id) |
這裡不需要賦值。
下面要啟動celery,我採用supervisor程式管理器來管理celery:
1 2 3 4 5 6 7 8 9 10 |
[program:celery] command= celery -A dailyblog worker --loglevel=INFO directory=/srv/dailyblog/www/ numprocess=1 startsecs=0 stopwaitsecs=0 autostart=true autorestart=true stdout_logfile=/tmp/celery.log stderr_logfile=/tmp/celery.err |
重新載入supervisor.conf檔案,然後啟動celery:
1 |
supervisorctl start celery |
至此,通過celery非同步執行任務的程式寫完了。除此之外,還可以寫很多的非同步任務,發郵件就是非常典型的一種。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式