對於網站來說,給用戶一個較好的體驗是很重要的事情,其中最重要的指標就是網站的瀏覽速度。因此服務端要從各個方面對網站性能進行優化,比如可采用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異步執行任務的程序寫完了。除此之外,還可以寫很多的異步任務,發郵件就是非常典型的一種。