Django緩存配置詳解

動態網站的一個基本權衡就是他們是動態的,每次一個用戶請求一個頁面,web服務器進行各種各樣的計算-從數據庫查詢到模板渲染到業務邏輯-從而生成站點訪問者看到的頁面。從處理開銷的角度來看,相比標準的從文件系統讀取文件的服務器調度,這是昂貴了不少。儘管對於大多數網站來說,這種開銷不是什麼大問題,因爲大多數web應用不過是想學學院的首頁那樣,都是小到中型的站點,流量也很少。但對於中到大型的站點來說,必須儘可能的減少開銷。這就是緩存的由來。

緩存的意義在於把昂貴的計算結果保存起來一遍下次的訪問,有緩存的站點的流程大概是這樣子的:

  1. 給定一個url,檢查頁面是否在緩存中
  2. 如果在,返回緩存的頁面
  3. 否則,生成該頁面,把生成的頁面保存在緩存中,返回生成的頁面

django自帶一個強大的緩存系統,提供不同層次的緩存粒度:你可以緩存某個視圖函數的輸出,或者只是某個特別難生成的部分或者是整個站點。同時django也有類似“上流”緩存的東西,類似於Squid和基於瀏覽器的緩存,這類緩存是你無法直接控制但是你可以通過一些提示(比如http頭部)指定你網站的那些部分需要和如何緩存。

設置緩存

緩存系統需要少量的配置才能使用,你必須告訴系統你的緩存數據存放在哪裏-數據庫還是文件系統亦或是直接存在緩存中-這是一個重要的決定,直接影響到你的緩存性能;當然,一些緩存類型本來就是比其他的快,這我們無法迴避。

在項目的配置文件裏面可以通過CACHES配置緩存,下面我們一一講解django中可用的緩存系統

memcached

Memcached 是一個高性能的分佈式內存對象緩存系統,用於動態Web應用以減輕數據庫負載從而顯著提供網站性能,也是django中到目前爲止最有效率的可用緩存。

Memcached作爲一個後臺進程運行,並分配一個指定的內存量,它所做的全是提供一個添加,檢索和刪除緩存中任意數據的快速接口,所有的數據都是直接存儲在內存中,所以就沒有了數據庫或者文件系統使用的額外開銷了。

在安裝Memcached之後,你還需要安裝一個綁定Memcached的東西,這裏當做是插件之類的東西,最常用的兩個是python-mencached和pylibmc。

在django中使用Memcached,你需要在配置文件裏的CACHES做如下設置:

  • 根據你選擇的插件,選擇對應的後端(BACKEND),django.core.cache.backens.memcached.MemcachedCache或者django.core.cache.backends.memcached.PyLibMCCache
  • 給LOCATION設置ip:port值,其中ip是Memcached進程的IP地址,port是Memcached運行的端口,或者unix:path值,其中path是Memcached Unix socket文件所在的路徑

下面的這個例子使用的是python-memcached緩存,服務器有三個(只需要在配置文件配置一次就行)

CACHES = {
	'default': {
		'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
		'LOCATION': [
			'172.19.26.240:11211',
			'172.19.26.242:11211',
			'172.19.26.244:11213',
		]
	}
}

這個是使用Unix socket的例子

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': 'unix:/tmp/memcached.sock',
    }

最後的一點是關於Memcached緩存的不足:緩存完全在內存中,一旦服務器崩潰,所有的數據將不復存在,因此,不要把基於內存的緩存作爲你唯一的數據存儲方式,當然,所有的緩存都不是作爲永久存儲的,緩存只是緩存而不是存儲,但基於內存的緩存是特別的僅僅的真的只是“緩存”,稍“縱”即逝(縱這裏是出現錯誤)。下面我們將見到一些不是那麼稍縱即逝的緩存。

數據庫緩存

在django中使用數據庫緩存,首先你需要在數據庫中建立一個用於緩存的數據庫表,可以參考下面的命令,注意cache_table_name不要和數據庫中已經存在的表名衝突:

python manage.py createcachetable [cache_table_name]

一旦設置好了數據庫表,在配置文件中班的BACKEND設置爲django.core.cache.backends.db.DatabaseCache,LOCATION設置爲你的數據庫表名,下面是個例子:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
    }

注意的是數據庫緩存使用的是你配置文件中的數據庫,所以你不可以使用其他的數據庫;如果你使用的是一個快速良好的索引數據庫服務器的話,你的數據庫緩存可以工作的更好

數據庫緩存和多數據庫

如果你真的要使用多個數據庫用作數據庫緩存的話,你需要設置路由說明給你的數據庫緩存表。處於路由的目的,數據庫緩存表會以一個在一個名爲django_cache的應用下名爲CacheEntry的模型出現,這個模型不會出現在模型緩存中,但這個模型的細節可以在路由中使用。

舉個例子,下面的路由會把所有的緩存讀操作導向cache_slave,所有的寫操作導向cache_master,緩存表只會同步到cache_master

  1. class CacheRouter(object):
  2. """A router to control all database cache operations"""
  3. def db_for_read(self, model, **hints):
  4. "All cache read operations go to the slave"
  5. if model._meta.app_label in ('django_cache',):
  6. return 'cache_slave'
  7. return None
  8. def db_for_write(self, model, **hints):
  9. "All cache write operations go to master"
  10. if model._meta.app_label in ('django_cache',):
  11. return 'cache_master'
  12. return None
  13. def allow_syncdb(self, db, model):
  14. "Only synchronize the cache model on master"
  15. if model._meta.app_label in ('django_cache',):
  16. return db == 'cache_master'
  17. return None

如果你不提供路由導向,那麼django將使用默認的數據庫;當然,如果你不使用數據庫緩存,你完全不需要擔心路由導向的問題。哈哈

文件系統緩存

請使用django.core.cache.backends.filebased.FileBasedCache賦值給後端變量BACKEND,LOCATION設置爲緩存所在的位置,注意是絕對位置(從根目錄開始),下面是一個例子:

CACHES = {
	'default': {
		'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
		'LOCATION': '/var/tmp/django_cache',
		#'LOCATION': 'c:/foo/bar',#windows下的示例
	}
}

必須保證服務器對你列出的路徑具有讀寫權限

本地內存緩存

如果你想具有內存緩存的優點但有沒有能力運行Memcached的時候,你可以考慮本地內存緩存,這個緩存是多進程和線程安全的,後端設置爲django.core.cache.backends.lovMemCache

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake'
    }
}

緩存LOCATION用來區分每個內存存儲,如果你只有一個本地內存緩存,你可以忽略這個設置;但如果你有多個的時候,你需要至少給他們中一個賦予名字以區分他們。

注意每個進程都有它們自己的私有緩存實例,所以跨進陳緩存是不可能的,因此,本地內存緩存不是特別有效率的,建議你只是在內部開發測時使用,不建議在生產環境中使用

虛擬緩存

django自帶一個虛擬緩存-不是真實的緩存,只是實現緩存的接口而已。

這在實際應用中時非常有用的,假如你有一個產品用發佈的時候非常需要用到緩存,但在開發和測試的時候你並不想使用緩存,同時你也不想等到產品發佈的時候特別的去修改緩存相關的代碼,那麼你可以是用虛擬緩存,要啓用虛擬緩存,你只需按下面的例子去配置你的後端

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}

使用自定義的緩存後端

要想使用自定義的cache後端,你可以用python的import格式導入你的後端模塊,例如:

CACHES = {
    'default': {
        'BACKEND': 'path.to.backend',
    }
}

一方面你可以在自己的後端文件中導入或者使用django自帶的緩存後端文件;另一方面,並不建議初學者使用自己的後端配置,出於這樣或那樣的原因。

緩存參數

除了選擇後端之外,我們還可以通過配置緩存參數來控制我們緩存的性能(到現在,我們至少知道了django所謂的參數,直接去看他們的源碼就知道可以接受那些參數了,哈哈)

  1. class BaseCache(object):
  2. def __init__(self, params):
  3. timeout = params.get('timeout', params.get('TIMEOUT', 300))
  4. try:
  5. timeout = int(timeout)
  6. except (ValueError, TypeError):
  7. timeout = 300
  8. self.default_timeout = timeout
  9. options = params.get('OPTIONS', {})
  10. max_entries = params.get('max_entries', options.get('MAX_ENTRIES', 300))
  11. try:
  12. self._max_entries = int(max_entries)
  13. except (ValueError, TypeError):
  14. self._max_entries = 300
  15. cull_frequency = params.get('cull_frequency', options.get('CULL_FREQUENCY', 3))
  16. try:
  17. self._cull_frequency = int(cull_frequency)
  18. except (ValueError, TypeError):
  19. self._cull_frequency = 3
  20. self.key_prefix = smart_str(params.get('KEY_PREFIX', ''))
  21. self.version = params.get('VERSION', 1)
  22. self.key_func = get_key_func(params.get('KEY_FUNCTION', None))

這裏有一個配置CACHES的樣例,使用文件系統緩存,timeout是60秒,最大容量是1000個子項,在Unix系統下的/var/temp/django_cache緩存

CACHES = {
	'default': {
		'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
		'LOCATION': '/var/tmp/django_cache',
		'TIMEOUT': 60,
		'OPTIONS': {
			'MAX_ENTRIES': 1000
		}
	}
}

使用緩存

每個站點的緩存

緩存設置好了之後,最簡單的應用便是緩存你的整個站點。

你需要添加兩個中間件到MIDDLEWRAE_CLASSES:django.middleware.UpdateCacheMiddleware和django.middleware.cache.FetchFromCacheMiddleware,注意的是,UpdateCache中間件一定要放在第一位,Fetch中間件必須放最後(因爲中間件的順序決定着運行的順序)見下面示例:

MIDDLEWARE_CLASSES = (
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
)

然後你需要在項目配置文件中加入下面幾個必須的設置:

  • CACHE_MIDDLEWARE_ALIAS:用來存儲的緩存別名
  • CACHE_MIDDLEWARE_SECONDS:每個頁面應該被緩存的秒數
  • CACHE_MIDDLEWARE_KEY_PREFIX:關鍵的前綴,當多個站點使用同一個配置的時候,這個可以設置可以避免發生衝突;如果你不在乎的話, 你可以是用一個空字符串,建議你別這樣做

如果請求或者響應的頭部允許的時候,緩存中間件會緩存那些200的get或者head的相應頁面。同一個url但是不同查詢參數會被認爲是不同的相應從而被分別緩存。

如果你設置了CACHE_MIDDLEWARE_ANONYMOUS_ONLY爲真,那麼只有匿名的請求會被緩存,這是一個禁用緩存非匿名用戶頁面的最簡單的做法,注意確保已經啓用了認證中間件。

緩存中間件希望一個head請求可以被 一個有相同響應頭部的get請求的響應 響應,因爲這樣的話,緩存中間件可以直接用一個get響應返回給一個head請求。

另外,緩存中間件會自動的設置少量的頭部信息給每一個HttpResponse:

  • 當一個新鮮的頁面被請求的時候,會用當前時間打上一個Last_Modified的頭部
  • 會用當前時間加上CACHE_MIDDLEWARE_SECONDS的值設置給Expires頭部
  • 用CACHE_MIDDLEWARE_SECONDS的值給Cache-Control頭部去設置一個頁面的最大年齡(前提是視圖函數沒有設置該項)

每個視圖函數的緩存

一個更加精細的使用緩存框架的方法是緩存單獨一個視圖函數的輸出。django.views.decorators.cache定義了一個cache_page的裝飾器,使用了這個裝飾器的視圖的輸出會被自動緩存。

cache_page(timeout, [cache=cache name], [key_prefix=key prefix])

cache_page只接受一個參數和兩個關鍵字參數,

  • timeout是緩存時間,以秒爲單位
  • cache:指定使用你的CACHES設置中的哪一個緩存後端
  • key_prefix:指定緩存前綴,可以覆蓋在配置文件中CACHE_MIDDLEWARE_KEY_PREFIX的值
  1. @cache_page(60 * 15, key_prefix="site1")
  2. def my_view(request):

在url配置文件中使用緩存裝飾器

和上面的類似,裝飾器的位置發生了變化

  1. from django.views.decorators.cache import cache_page
  2. urlpatterns = ('',
  3. (r'^foo/(\d{1,2})/$', cache_page(60 * 15)(my_view)),
  4. )

模板片段緩存

如果你想更精細的使用緩存,你可以是用cache這個模板標籤。爲了使用這個標籤,記得使用{% load cache %}標籤,本人覺得現在沒必要用到這麼細的緩存控制,以後用到的時候再說吧

{% load cache %}
{% cache 500 sidebar %}
    .. sidebar ..
{% endcache %}

底層的緩存API

有時候你不想緩存一個頁面,甚至不想某個頁面的一部分,只是想緩存某個數據庫檢索的結果,django提供了底層次的API,你可以是用這些API來緩存任何粒度的數據,

如果你想了解所有的API,強烈建議你去看django\core\cache\backends目錄下的cache.py文件,這裏僅僅列舉一些簡單的用法

  1. >>> from django.core.cache import cache
  2. >>> cache.set('my_key', 'hello, world!', 30)
  3. >>> cache.get('my_key')
  4. # Wait 30 seconds for 'my_key' to expire...
  5. >>> cache.get('my_key')
  6. None
  7. >>> cache.get('my_key', 'has expired')
  8. 'has expired'
  9. >>> cache.set('add_key', 'Initial value')
  10. >>> cache.add('add_key', 'New value')
  11. >>> cache.get('add_key')
  12. 'Initial value'
  13. >>> cache.set('a', 1)
  14. >>> cache.set('b', 2)
  15. >>> cache.set('c', 3)
  16. >>> cache.get_many(['a', 'b', 'c'])
  17. {'a': 1, 'b': 2, 'c': 3}

緩存 關鍵字前綴 

前面有提到這個設置,一旦設置了這個,一個緩存關鍵字被保存或者檢索的時候,django會自動加上這個前綴值,這使得緩存數據共享的時候很方便。

緩存版本

緩存被保存的時候是帶上版本號的,比如

  1. # Set version 2 of a cache key
  2. >>> cache.set('my_key', 'hello world!', version=2)
  3. # Get the default version (assuming version=1)
  4. >>> cache.get('my_key')
  5. None
  6. # Get version 2 of the same key
  7. >>> cache.get('my_key', version=2)
  8. 'hello world!'

利用版本這個特性,我們可以通過設置CACHE的VERSION控制緩存的版本,當我們需要保留一些數據摒除另外一些的時候,我們可以通過修改需要保留數據的版本,和設置新的VERSION來達到目的

  1. # Increment the version of 'my_key'
  2. >>> cache.incr_version('my_key')
  3. # The default version still isn't available
  4. >>> cache.get('my_key')
  5. None
  6. # Version 2 isn't available, either
  7. >>> cache.get('my_key', version=2)
  8. None
  9. # But version 3 *is* available
  10. >>> cache.get('my_key', version=3)
  11. 'hello world!'

緩存關鍵字轉換

由上面的關鍵字前綴和關鍵字版本,我們可以知道,其實關鍵字是有由關鍵字,前綴和版本號組成的,默認他們之間是有冒號來連接的

  1. def make_key(key, key_prefix, version):
  2. return ':'.join([key_prefix, str(version), smart_str(key)])

如果你想用其他的字符來連接,請提供KEY_FUNCTION緩存設置

緩存關鍵字警告

Memcached對於關鍵字的要求是不長於250字符,且不能包含空格或者控制字符,否則會拋出一個異常,因此,爲了使代碼更健壯和避免不愉快的意外,其他的內建緩存後端會發出一個警告django.core.cache.backends.base.CacheKeyWarning,如果關鍵字的使用會在Memcached引發一個錯誤

如果你在使用一個接受更大範圍的關鍵字,然後想使用,你可以在你的INSTALLED_APPS中的一個的management模塊中使用下面的代碼  slience CacheKeyWarning

import warnings

from django.core.cache import CacheKeyWarning

warnings.simplefilter("ignore", CacheKeyWarning)

如果你想自定義關鍵字驗證邏輯的話,你可以繼承那個類,然後重寫validate_key方法,下面以重寫本地緩存後端爲例子,請在模塊中加入下面代碼

  1. from django.core.cache.backends.locmem import LocMemCache
  2. class CustomLocMemCache(LocMemCache):
  3. def validate_key(self, key):
  4. """Custom validation, raising exceptions or warnings as needed."""
  5. # ...

“上流”緩存

到目前爲止我們談到的基本都是服務器端的緩存,但其他類型的緩存也是和網站開發密切相關的,比如“上流”緩存 緩存的頁面,這是一些爲用戶緩存頁面的系統,使得請求甚至沒有到達的你的網站後端。

這是一些上流緩存的例子:

  • 你的ISP(因特網提供商)可能會緩存一些頁面,比如你訪問   http://example.com/ 的時候,你的ISP可能沒有訪問到 example.com就把頁面返回給你了 
  • 你的瀏覽器可能也會緩存一些網頁

上游緩存是一個很好的效率提高,但不適合緩存所有的網頁,特別是一些跟認證有關的頁面的緩存可能會給網站帶來一些隱患,那該如何決定是否在上游緩存呢?

很幸運的,我們可以指定Http頭部信息來要求上游緩存根據我們的提示是否緩存我們的頁面,接着往下看

使用Vary頭部信息

當建立一個緩存關鍵字的時候,緩存機制會根據Vary頭部信息決定是否那些 請求的頭部信息 是需要考慮的。比如一個網頁的內容決定於用戶的語言偏好,那麼可以說這個頁面是“語言因人而異的”。

django默認使用請求路徑和查詢作爲緩存關鍵字,比如/stories/2005/?order_by=author,這意味着所有請求該url的請求都是用同一個緩存頁面,而不管user-agent是否一致,比如語言偏好等等。然而,如果這個頁面是根據不同的請求頭部信息而生成不一樣的頁面的時候,你需要使用Vary頭部去告訴緩存機制要根據這些不同的頭部信息輸出不同的內容。

爲了做到這一點,django提供了vary_on_header視圖裝飾器

  1. from django.views.decorators.vary import vary_on_headers
  2. @vary_on_headers('User-Agent')
  3. def my_view(request):
  4. # ...

上面的這個例子,django的緩存機制會爲每一個user-agent緩存一個單獨的頁面。

vary_on_header裝飾器接受多個參數,比如Cookie和User-Agent,多個參數之間是組合關係;另一方面,由於因爲cookie的不同是比較常用的,django直接提供了一個vary_on_cookie的裝飾器,下面的這兩種寫法是等價的

  1. @vary_on_cookie
  2. def my_view(request):
  3. # ...
  4. @vary_on_headers('Cookie')
  5. def my_view(request):
  6. # ...

django還提供了一個有用的函數django.utils.cache.patch_vary_headers,這個函數設置或者添加內容到Vary 頭部裏面

  1. from django.utils.cache import patch_vary_headers
  2. def my_view(request):
  3. # ...
  4. response = render_to_response('template_name', context)
  5. patch_vary_headers(response, ['Cookie'])
  6. return response

控制緩存:使用其他的頭部信息

其他的一些關於緩存的問題是:數據的私有性和數據應該被存儲在級聯的緩存的問題。一個用戶面對兩種類型的緩存:自己的瀏覽器緩存(私有的)和提供者的緩存(共有的);一個共有的緩存被多個用戶使用同時被某個人控制,這將涉及到一些敏感的問題,比如你的銀行賬單。因此,我們的web應用需要一個方法告訴緩存那些數據是私有的,那些是可以共享的。

django提供了django.views.decorators.cache.cache_control視圖裝飾器解決這個問題

  1. from django.views.decorators.cache import cache_control
  2. @cache_control(private=True)
  3. def my_view(request):
  4. # ...

注意的是,共有和私有這兩控制設置是獨立的,一個被設置另一個就會被移除(對於一個響應),對於同一個視圖函數裏面的不同響應,可以使用不同的設置,例如:

  1. from django.views.decorators.cache import patch_cache_control
  2. from django.views.decorators.vary import vary_on_cookie
  3. @vary_on_cookie
  4. def list_blog_entries_view(request):
  5. if request.user.is_anonymous():
  6. response = render_only_public_entries()
  7. patch_cache_control(response, public=True)
  8. else:
  9. response = render_private_and_public_entries(request.user)
  10. patch_cache_control(response, private=True)
  11. return response

最後列舉cache_control接收到額參數:

  • public=True
  • private=True
  • no_cache=True
  • no_transform=True
  • must_revalidate=True
  • proxy_revalidate=True
  • max_age=num_seconds
  • s_maxage=num_seconds
發佈了13 篇原創文章 · 獲贊 25 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章