15:django 緩存架構

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

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

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

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

設置緩存

緩存系統須要少許的配置才能使用,你必須告訴系統你的緩存數據存放在哪裏-數據庫仍是文件系統亦或是直接存在緩存中-這是一個重要的決定,直接影響到你的緩存性能;固然,一些緩存類型原本就是比其餘的快,這咱們沒法迴避。django

在項目的配置文件裏面能夠經過CACHES配置緩存,下面咱們一一講解django中可用的緩存系統windows

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

class CacheRouter(object):
    """A router to control all database cache operations"""

    def db_for_read(self, model, **hints):
        "All cache read operations go to the slave"
        if model._meta.app_label in ('django_cache',):
            return 'cache_slave'
        return None

    def db_for_write(self, model, **hints):
        "All cache write operations go to master"
        if model._meta.app_label in ('django_cache',):
            return 'cache_master'
        return None

    def allow_syncdb(self, db, model):
        "Only synchronize the cache model on master"
        if model._meta.app_label in ('django_cache',):
            return db == 'cache_master'
        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所謂的參數,直接去看他們的源碼就知道能夠接受那些參數了,哈哈)

class BaseCache(object):
    def __init__(self, params):
        timeout = params.get('timeout', params.get('TIMEOUT', 300))
        try:
            timeout = int(timeout)
        except (ValueError, TypeError):
            timeout = 300
        self.default_timeout = timeout

        options = params.get('OPTIONS', {})
        max_entries = params.get('max_entries', options.get('MAX_ENTRIES', 300))
        try:
            self._max_entries = int(max_entries)
        except (ValueError, TypeError):
            self._max_entries = 300

        cull_frequency = params.get('cull_frequency', options.get('CULL_FREQUENCY', 3))
        try:
            self._cull_frequency = int(cull_frequency)
        except (ValueError, TypeError):
            self._cull_frequency = 3

        self.key_prefix = smart_str(params.get('KEY_PREFIX', ''))
        self.version = params.get('VERSION', 1)
        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的值
@cache_page(60 * 15, key_prefix="site1")
def my_view(request):

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

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

from django.views.decorators.cache import cache_page

urlpatterns = ('',
    (r'^foo/(\d{1,2})/$', cache_page(60 * 15)(my_view)),
)

模板片斷緩存

若是你想更精細的使用緩存,你能夠是用cache這個模板標籤。爲了使用這個標籤,記得使用{% load cache %}標籤,本人以爲如今不必用到這麼細的緩存控制,之後用到的時候再說吧

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

底層的緩存API

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

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

>>> from django.core.cache import cache
>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
# Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key')
None
>>> cache.get('my_key', 'has expired')
'has expired'
>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'
>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

緩存關鍵字前綴

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

緩存版本

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

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

利用版本這個特性,咱們能夠經過設置CACHE的VERSION控制緩存的版本,當咱們須要保留一些數據摒除另一些的時候,咱們能夠經過修改須要保留數據的版本,和設置新的VERSION來達到目的

# Increment the version of 'my_key'
>>> cache.incr_version('my_key')
# The default version still isn't available
>>> cache.get('my_key')
None
# Version 2 isn't available, either
>>> cache.get('my_key', version=2)
None
# But version 3 *is* available
>>> cache.get('my_key', version=3)
'hello world!'

 緩存關鍵字轉換

由上面的關鍵字前綴和關鍵字版本,咱們能夠知道,其實關鍵字是有由關鍵字,前綴和版本號組成的,默認他們之間是有冒號來鏈接的

def make_key(key, key_prefix, version):
    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方法,下面以重寫本地緩存後端爲例子,請在模塊中加入下面代碼

from django.core.cache.backends.locmem import LocMemCache

class CustomLocMemCache(LocMemCache):
    def validate_key(self, key):
        """Custom validation, raising exceptions or warnings as needed."""
        # ...

「上流」緩存

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

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

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

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

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

使用Vary頭部信息

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

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

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

from django.views.decorators.vary import vary_on_headers

@vary_on_headers('User-Agent')
def my_view(request):
    # ...

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

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

@vary_on_cookie
def my_view(request):
    # ...

@vary_on_headers('Cookie')
def my_view(request):
    # ...

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

from django.utils.cache import patch_vary_headers

def my_view(request):
    # ...
    response = render_to_response('template_name', context)
    patch_vary_headers(response, ['Cookie'])
    return response

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

其餘的一些關於緩存的問題是:數據的私有性和數據應該被存儲在級聯的緩存的問題。一個用戶面對兩種類型的緩存:本身的瀏覽器緩存(私有的)和提供者的緩存(共有的);一個共有的緩存被多個用戶使用同時被某我的控制,這將涉及到一些敏感的問題,好比你的銀行帳單。所以,咱們的web應用須要一個方法告訴緩存那些數據是私有的,那些是能夠共享的。

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

from django.views.decorators.cache import cache_control

@cache_control(private=True)
def my_view(request):
    # ...

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

from django.views.decorators.cache import patch_cache_control
from django.views.decorators.vary import vary_on_cookie

@vary_on_cookie
def list_blog_entries_view(request):
    if request.user.is_anonymous():
        response = render_only_public_entries()
        patch_cache_control(response, public=True)
    else:
        response = render_private_and_public_entries(request.user)
        patch_cache_control(response, private=True)

    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
相關文章
相關標籤/搜索