首頁  >  文章  >  後端開發  >  Django快取機制

Django快取機制

黄舟
黄舟原創
2017-01-17 14:08:42991瀏覽

靜態的網站的內容都是一些簡單的靜態網頁直接儲存在伺服器上,可以非常容易地達到非常驚人的訪問量。但是動態網站因為是動態的,也就是說每次使用者造訪一個頁面,伺服器要執行資料庫查詢,啟動模板,執行業務邏輯到最終產生一個你說看到的網頁,這一切都是動態即時產生的。從處理器資源的角度來看,這是比較昂貴的。


對於大多數網路應用來說,過載並不是大問題。因為大多數網頁應用程式並不是washingtopost.com或Slashdot;它們通常是很小很簡單,或是中等規模的站點,只有很少的流量。但是對於中等至大規模流量的站點來說,盡可能解決過載問題是非常必要的。這就需要用到快取了。


快取的目的是為了避免重複計算,特別是對一些比較耗時間、資源的計算。下面是的程式碼示範如何對動態頁面的結果進行快取。

given a URL, try finding that page in the cache
if the page is in the cache:
return the cached page
else:
generate the page
save the generated page in the cache (for next time)
return the generated page

為此,Django提供了一個穩定的快取系統讓你快取動態頁面的結果,這樣在接下來有相同的請求就可以直接使用快取中的數據,避免不必要的重複計算。另外Django也提供了不同粒度資料的緩存,例如:你可以快取整個頁面,也可以快取某個部分,甚至快取整個網站。


Django也和」上游」快取工作的很好,例如Squid(http://www.squid-cache.org)和基於瀏覽器的緩存,這些類型的快取你不直接控制,但是你可以提供關於你的網站哪部分應該被快取和怎樣快取的線索(透過HTTP頭部)給它們


繼續閱讀來研究如何使用Django的快取系統。當你的網站變成像Slashdot的時候,你會很高興理解了這部分材料


設定緩存


緩存系統需要一些少量的設定工作,即你必需告訴它你的快取資料在哪裡—在資料庫,檔案系統或直接在記憶體中,這是影響你的快取效能的重要決定,是的,有些快取類型要比其它的快,記憶體快取通常比檔案系統或資料庫快取快,因為前者沒有存取檔案系統或資料庫的過度連線


你的快取選擇在你的settings檔案的CACHE_BACKEND設定中,如果你使用快取但沒有指定CACHE_BACKEND,Django會預設使用simple:///,以下將解釋CACHE_BACKEND的所有可得到的值


內存緩衝


目前為止Django可得到的最快的最高效的緩存類型是基於內存的框架Meached,目前為止開發來開始LiveJournal .com處理高負荷並隨後被Danga
Interactive(http://www.danga.com)開源,它被Slashdot和Wikipedia等站點使用來減少數據庫訪問和戲劇般的增加站點性能


Memcached可以在http://danga.com/memcached/免費得到,它作為後台進程運行並分配一個指定數量的RAM.它能為你提供在緩存中*如閃電般快速的*添加,獲取和刪除任意數據,所有的資料直接儲存在記憶體中,所以沒有資料庫和檔案系統使用的過度使用


在安裝了Memcached本身之後,你將需要安裝MemcachedPython綁定,它沒有直接和Django綁定,這些綁定在一個單獨的Python模組中,'memcache.py',可以在http://www.djangoproject.com/thirdparty/python-memcached得到


設定CACHEached://ip:port/來讓Django使用Memcached,這裡的ip是Memcached後台進程的IP位址,port則是Memcached運行所在的連接埠


在這個例子中,Memcached運行在本地主機(127.0.0.1)上,連接埠為11211:

CACHE_BACKEND = 'memcached://127.0.0.1:11211/'

Memcached的一個極好的特性是它在多個伺服器共享快取的能力,這意味著你可以在多台機器上運行Memcached進程,程式將會把這組機器當作一個*單獨的*緩存,而不需要在每台機器上複製緩存值,為了讓Django利用此特性,需要在CACHE_BACKEND裡包含所有的伺服器位址並用分號分隔


這個例子中,緩存在運行在172.19.26.240和172.19.26.242的IP位址和11211埠的Memcached實例間分享:

CACHE_BACKEND = 'meached://172.1912121. / '

這個例子中,快取在運行在172.19.26.240(連接埠11211),172.19.26.242(埠11212),172.19.26.244(CHE11213)的實例連接埠共享.26.240:11211;172.19.26.242:11212;172.19.26.244:11213/'

最後關於Memcached的是基於記憶體的快取有一個重大的缺點,因為快取資料只儲存在記憶體中,則如果伺服器死機的話資料會遺失,顯然記憶體不是為持久資料儲存準備的,Django沒有一個快取後端是用來做持久存儲的,它們都是緩存方案,而不是存儲.但是我們在這裡指出是因為基於內存的緩存特別的短暫
.


數據庫緩存


數據庫緩存


數據庫緩存


數據庫表作為快取後端,需要在資料庫中建立一個快取表並將Django的快取系統指向該表

首先,使用如下語句建立一個快取用資料表:

python manage.py createcachetable [cache_table_name]
這裡的[cache_table_name]是要創建的資料庫表名,名字可以是任何你想要的,只要它是合法的在你的資料庫中沒有被使用這個命令在你的資料庫創建一個遵循Django的資料庫快取系統期望形式的單獨的表.


一旦你創建了數據庫表,設置你的CACHE_BACKEND設置為”db://tablename”,這裡的tablename是數據庫表的名字,在這個例子中,緩存表名為my_cache_table:

CACHE_BACKEND = 'db://my_cache_table'

資料庫快取後端使用你的settings檔案指定的相同資料庫,你不能為你的快取表使用不同的資料庫後端.

。系統快取


使用」file://「快取類型作為CACHE_BACKEND並指定儲存快取資料的檔案系統目錄來在檔案系統儲存快取資料.


例如,使用下方的設定來在/var /tmp/django_cache儲存快取資料:

CACHE_BACKEND = 'file:///var/tmp/django_cache'


注意範例中開頭有三個前斜線,前兩個是file://,第三個是目錄路徑的第一個字符,/var/tmp/django_cache,如果你使用Windows系統,把盤符字母放在file://後面,像這樣:'file://c:/foo/bar'.


目錄路徑應該是*絕對*路徑,即應該以你的檔案系統的根開始,你在設定的結尾放置斜線與否無關緊要.


確認該設定指向的目錄存在並且你的Web伺服器運行的系統的用戶可以讀寫該目錄,繼續上面的例子,如果你的伺服器以用戶apache運行,確認/var/tmp/django_cache存在並且用戶apache可以讀寫/var/tmp/django_cache目錄


每個快取值將被儲存為單獨的文件,其內容是Python的pickle模組以序列化(“pickled”)形式保存的快取數據,每個文件的文件名稱是快取鍵,為安全的檔案系統使用釋放


本地記憶體快取

如果你想要記憶體快取的速度優勢但沒有能力運行Memcached,可以考慮使用本地記憶體快取後端,該快取是多執行緒和安全的執行緒執行緒的,但由於其簡單的鎖和記憶體分配策略它沒有Memcached高效


設定CACHE_BACKEND為locmem:///來使用它,例如::

設定CACHE_BACKEND為locmem:///來使用它,例如::00 '

簡易緩存(用於開發階段)


可以透過配置'simple:///'來使用一個簡單的單進程記憶體緩存,例如:


CACHE_BACKEND = 'simple:///'

這個快取只是將資料保存在進程內,因此它應該只在開發環境或測試環境中使用.

仿快取(供開發時使用)




,Django提供一個假緩存的設定:它僅僅實現了緩存的接口而不做任何實際的事情


這是個有用的特性,如果你的線上站點使用了很多比較重的緩存,而在開發環境中卻不想使用緩存,那麼你只要修改配置文件,將CACHE_BACKEND設置為'dummy:///'就可以了,例如:

CACHE_BACKEND = 'dummy:///'

這樣的結果就是你的開發環境沒有使用緩存,而線上環境依然在使用緩存.

CACHE_BACKEND參數

字符串

每個緩存後端都可能使用參數,它們在CACHE_BACKEND設定中以查詢形式給出,合法的參數為:

🎜timeout:用於快取的過期時間,以秒為單位。這個參數預設被設定為300秒(五分鐘)🎜🎜🎜🎜🎜max_entries:對於simple, local-memory與database類型的快取,這個參數是指定快取中存放的最大條目數,大於這個數時,舊的條目將會被刪除。這個參數預設是300.🎜🎜🎜🎜🎜cull_frequency:當達到max_entries的時候,被接受的訪問的比率。實際的比率是1/cull_frequency,所以設定cull_frequency=2就是在達到max_entries的時候去除一半數量的快取🎜🎜🎜🎜🎜把cull_frequency的值設定為0意味著當達到max_entries,,快取將被清空。這將以許多快取遺失為代價,大大提高接受存取的速度。這個值預設是3🎜🎜🎜🎜🎜在這個範例中,timeout被設成60🎜

CACHE_BACKEND = "locmem:///?timeout=60"



而在这个例子中,timeout设为30而max_entries为400:

CACHE_BACKEND = "locmem:///?timeout=30&max_entries=400"

其中,非法的参数与非法的参数值都将被忽略。


站点级 Cache


一旦你指定了”CACHE_BACKEND”,使用缓存的最简单的方法就是缓存你的整个网站。这意味着所有不包含GET或POST参数的页面在第一次被请求之后将被缓存指定好的一段时间。


要激活每个站点的cache,只要将``’django.middleware.cache.CacheMiddleware’``添加到MIDDLEWARE_CLASSES的设置里,就像下面这样:

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

注意


关于MIDDLEWARE_CLASSES顺序的一些事情。请看本章节后面的MIDDLEWARE_CLASSES顺序部分。


然后,在你的Django settings文件里加入下面所需的设置:


CACHE_MIDDLEWARE_SECONDS:每个页面应该被缓存的秒数


§ “CACHE_MIDDLEWARE_KEY_PREFIX”:如果缓存被多个使用相同Django安装的网站所共享,那么把这个值设成当前网站名,或其他能代表这个Django实例的唯一字符串,以避免key发生冲突。如果你不在意的话可以设成空字符串。


缓存中间件缓存每个没有GET或者POST参数的页面,即如果用户请求页面并在查询字符串里传递GET参数或者POST参数,中间件将不会尝试得到缓存版本的页面,如果你打算使用整站缓存,设计你的程序时牢记这点,例如,不要使用拥有查询字符串的URLs,除非那些页面可以不缓存


缓存中间件( cache middleware)支持另外一种设置选项,CACHE_MIDDLEWARE_ANONYMOUS_ONLY。如果你把它设置为“True”,那么缓存中间件就只会对匿名请求进行缓存,匿名请求是指那些没有登录的用户发起的请求。如果想取消用户相关页面(user-specific
pages)的缓存,例如Djangos的管理界面,这是一种既简单又有效的方法。另外,如果你要使用CACHE_MIDDLEWARE_ANONYMOUS_ONLY选项,你必须先激活AuthenticationMiddleware才行,也就是在你的配置文件MIDDLEWARE_CLASSES的地方,AuthenticationMiddleware必须出现在CacheMiddleware前面。


最后,再提醒一下:CacheMiddleware在每个HttpResponse中都会自动设置一些头部信息(headers)


§ 当一个新(没缓存的)版本的页面被请求时设置Last-Modified头部为当前日期/时间


§ 设置Expires头部为当前日期/时间加上定义的CACHE_MIDDLEWARE_SECONDS


§ 设置Cache-Control头部来给页面一个最大的时间—再一次,根据CACHE_MIDDLEWARE_SECONDS设置


视图级缓存


更加颗粒级的缓存框架使用方法是对单个视图的输出进行缓存。这和整站级缓存有一样的效果(包括忽略对有 GET和 POST
参数的请求的缓存)。它应用于你所指定的视图,而不是整个站点。


完成这项工作的方式是使用修饰器,其作用是包裹视图函数,将其行为转换为使用缓存。视图缓存修饰器称为cache_page,位于django.views.decorators.cache模块中,例如:

from django.views.decorators.cache import cache_page
def my_view(request, param):
# ...
my_view = cache_page(my_view, 60 * 15)

如果使用 Python 2.4或更高版本,
你也可以使用 decorator语法。这个例子和前面的那个是等同的:

from django.views.decorators.cache import cache_page
@cache_page(60 * 15)
def my_view(request, param):
# ...

cache_page只接受一个参数:以秒计的缓存超时。在前例中, “my_view()”视图的结果将被缓存 15
分钟。(注意:为了提高可读性,该参数被书写为60 * 15。60 * 15将被计算为900,也就是说15分钟乘以每分钟
60 秒。)


和站点缓存一样,视图缓存与 URL无关。如果多个 URL
指向同一视图,每个视图将会分别缓存。继续my_view范例,如果 URLconf如下所示:

urlpatterns = ('',
(r'^foo/(/d{1,2})/$', my_view),
)

那么正如你所期待的那样,发送到/foo/1/和/foo/23/的请求将会分别缓存。但一旦发出了特定的请求(如:/foo/23/),之后再度发出的指向该
URL 的请求将使用缓存。


在 URLconf中指定视图缓存


前一节中的范例将视图硬编码为使用缓存,因为cache_page在适当的位置对my_view函数进行了转换。该方法将视图与缓存系统进行了耦合,从几个方面来说并不理想。例如,你可能想在某个无缓存的站点中重用该视图函数,或者你可能想将该视图发布给那些不想通过缓存使用它们的人。解决这些问题的方法是在
URLconf 中指定视图缓存,而不是紧挨着这些视图函数本身来指定。


完成这项工作非常简单:在 URLconf中用到这些视图函数的时候简单地包裹一个cache_page。以下是刚才用到过的

URLconf :
urlpatterns = ('',
(r'^foo/(/d{1,2})/$', my_view),
)
以下是同一个 URLconf,不过用cache_page包裹了my_view:
from django.views.decorators.cache import cache_page
urlpatterns = ('',
(r'^foo/(/d{1,2})/$', cache_page(my_view, 60 * 15)),
)

如果采取这种方法,不要忘记在 URLconf
中导入cache_page.


低层次缓存API


有些时候,对整个经解析的页面进行缓存并不会给你带来太多,事实上可能会过犹不及。


比如说,也许你的站点所包含的一个视图依赖几个费时的查询,每隔一段时间结果就会发生变化。在这种情况下,使用站点级缓存或者视图级缓存策略所提供的整页缓存并不是最理想的,因为你可能不会想对整个结果进行缓存(因为一些数据经常变化),但你仍然会想对很少变化的部分进行缓存。


在像这样的情形下, Django展示了一种位于django.core.cache模块中的简单、低层次的缓存
API。你可以使用这种低层次的缓存 API在缓存中以任何级别粒度进行对象储存。你可以对所有能够安全进行 pickle处理的
Python 对象进行缓存:字符串、字典和模型对象列表等等;查阅 Python文档可以了解到更多关于 pickling的信息。)


下面是如何导入这个 API :

>>> from django.core.cache import cache

基本的接口是set(key, value, timeout_seconds)和get(key):

>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'

timeout_seconds参数是可选的,并且默认为前面讲过的CACHE_BACKEND设置中的timeout参数.


如果对象在缓存中不存在,或者缓存后端是不可达的,cache.get()返回None:

# Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key')
None
>>> cache.get('some_unset_key')
None

我们不建议在缓存中保存None常量,因为你将无法区分所保存的None变量及由返回值None所标识的缓存未中。


cache.get()接受一个缺省参数。其指定了当缓存中不存在该对象时所返回的值:

>>> cache.get('my_key', 'has expired')


'has expired'

要想一次获取多个缓存值,可以使用cache.get_many()。如果可能的话,对于给定的缓存后端,get_many()将只访问缓存一次,而不是对每个缓存键值都进行一次访问。get_many()所返回的字典包括了你所请求的存在于缓存中且未超时的所有键值。

>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

如果某个缓存关键字不存在或者已超时,它将不会被包含在字典中。下面是范例的延续:

>>> cache.get_many(['a', 'b', 'c', 'd'])


{'a': 1, 'b': 2, 'c': 3}

最后,你可以用cache.delete()显式地删除关键字。这是在缓存中清除特定对象的简单途径。

>>> cache.delete('a')

cache.delete()没有返回值,不管给定的缓存关键字对应的值存在与否,它都将以同样方式工作。


上游缓存


目前为止,本章的焦点一直是对你自己的数据进行缓存。但还有一种与 Web开发相关的缓存:由上游高速缓存执行的缓冲。有一些系统甚至在请求到达站点之前就为用户进行页面缓存。


下面是上游缓存的几个例子:


§ 你的 ISP (互联网服务商)可能会对特定的页面进行缓存,因此如果你向http://www.infocool.net/请求一个页面,你的
ISP 可能无需直接访问 www.infocool.net就能将页面发送给你。而 www.infocool.net的维护者们却无从得知这种缓存,ISP位于
www.infocool.net和你的网页浏览器之间,透明底处理所有的缓存。


§ 你的 Django网站可能位于某个代理缓存之后,例如
Squid网页代理缓存 (http://www.squid-cache.org/),该缓存为提高性能而对页面进行缓存。在此情况下,每个请求将首先由代理服务器进行处理,然后仅在需要的情况下才被传递至你的应用程序。


§ 你的网页浏览器也对页面进行缓存。如果某网页送出了相应的头部,你的浏览器将在为对该网页的后续的访问请求使用本地缓存的拷贝,甚至不会再次联系该网页查看是否发生了变化。


上游缓存将会产生非常明显的效率提升,但也存在一定风险。许多网页的内容依据身份验证以及许多其他变量的情况发生变化,缓存系统仅盲目地根据 URL保存页面,可能会向这些页面的后续访问者暴露不正确或者敏感的数据。


举个例子,假定你在使用网页电邮系统,显然收件箱页面的内容取决于登录的是哪个用户。如果 ISP盲目地缓存了该站点,那么第一个用户通过该 ISP登录之后,他(或她)的用户收件箱页面将会缓存给后续的访问者。这一点也不好玩。


幸运的是, HTTP提供了解决该问题的方案。已有一些 HTTP头标用于指引上游缓存根据指定变量来区分缓存内容,并通知缓存机制不对特定页面进行缓存。我们将在本节后续部分将对这些头标进行阐述。


使用 Vary头标


Vary头标定义了缓存机制在构建其缓存键值时应当将哪个请求头标考虑在内。例如,如果网页的内容取决于用户的语言偏好,该页面被称为根据语言而不同。


缺省情况下,Django的缓存系统使用所请求的路径(比如:"/stories/2005/jun/23/bank_robbed/")来创建其缓存键。这意味着对该
URL的每个请求都将使用同一个已缓存版本,而不考虑 cookies或语言偏好之类的 user-agent差别。然而,如果该页面基于请求头标的区别(例如
cookies、语言或者 user-agent)产生不同内容,你就不得不使用


Vary头标来通知缓存机制:该页面的输出取决与这些东西。


要在 Django完成这项工作,可使用便利的vary_on_headers视图修饰器,如下所示:

from django.views.decorators.vary import vary_on_headers
# Python 2.3 syntax.
def my_view(request):
# ...
my_view = vary_on_headers(my_view, 'User-Agent')
# Python 2.4+ decorator syntax.
@vary_on_headers('User-Agent')
def my_view(request):
# ...

在这种情况下,缓存装置(如 Django自己的缓存中间件)将会为每一个单独的用户浏览器缓存一个独立的页面版本。


使用vary_on_headers修饰器而不是手动设置Vary头标(使用像response['Vary']
= 'user-agent'之类的代码)的好处是修饰器在(可能已经存在的)Vary之上进行添加,而不是从零开始设置,且可能覆盖该处已经存在的设置。


你可以向vary_on_headers()传入多个头标:

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

该段代码通知上游缓存对两者都进行不同操作,也就是说 user-agent和 cookie
的每种组合都应获取自己的缓存值。举例来说,使用Mozilla作为 user-agent而foo=bar作为
cookie值的请求应该和使用Mozilla作为 user-agent而foo=ham的请求应该被视为不同请求。


由于根据 cookie而区分对待是很常见的情况,因此有vary_on_cookie修饰器。以下两个视图是等效的:

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

传入vary_on_headers头标是大小写不敏感的;"User-Agent"与"user-agent"完全相同。


你也可以直接使用帮助函数:django.utils.cache.patch_vary_headers。该函数设置或增加Vary header,例如:

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

patch_vary_headers以一个HttpResponse实例为第一个参数,以一个大小写不敏感的头标名称列表或元组为第二个参数。


其它缓存头标


关于缓存剩下的问题是数据的私隐性以及关于在级联缓存中数据应该在何处储存的问题。


通常用户将会面对两种缓存:他或她自己的浏览器缓存(私有缓存)以及他或她的提供者缓存(公共缓存)。公共缓存由多个用户使用,而受其他某人的控制。这就产生了你不想遇到的敏感数据的问题,比如说你的银行账号被存储在公众缓存中。因此,Web应用程序需要以某种方式告诉缓存那些数据是私有的,哪些是公共的。


解决方案是标示出某个页面缓存应当是私有的。要在 Django中完成此项工作,可使用cache_control视图修饰器:

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

该修饰器负责在后台发送相应的 HTTP头标。


还有一些其他方法可以控制缓存参数。例如, HTTP允许应用程序执行如下操作:


§ 定义页面可以被缓存的最大次数。


§ 指定某个缓存是否总是检查较新版本,仅当无更新时才传递所缓存内容。(一些缓存即便在服务器页面发生变化的情况下都可能还会传送所缓存的内容,只因为缓存拷贝没有过期。)


在 Django中,可使用cache_control视图修饰器指定这些缓存参数。在本例中,cache_control告诉缓存对每次访问都重新验证缓存并在最长
3600 秒内保存所缓存版本:

from django.views.decorators.cache import cache_control
@cache_control(must_revalidate=True, max_age=3600)
def my_view(request):
...

在cache_control()中,任何有效Cache-ControlHTTP指令都是有效的。以下是一个完整的清单:

§ public=True
§ private=True
§ no_cache=True
§ no_transform=True
§ must_revalidate=True
§ proxy_revalidate=True
§ max_age=num_seconds
§ s_maxage=num_seconds


小提示


要了解有关Cache-ControlHTTP指令的相关解释,
可以查阅http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9的规范文档。


注意


缓存中间件已经使用CACHE_MIDDLEWARE_SETTINGS设置设定了缓存头标max-age。如果你在cache_control修饰器中使用了自定义的max_age,该修饰器将会取得优先权,该头标的值将被正确地被合并。)


其他优化


Django 带有一些其它中间件可帮助您优化应用程序的性能:


§ django.middleware.http.ConditionalGetMiddleware為現代瀏覽器增加了有條件地 GET基於ETag和Last-Modified頭標的回應的相關支援。


§ django.middleware.gzip.GZipMiddleware為所有現代瀏覽器壓縮回應內容,以節省頻寬和傳送時間。


MIDDLEWARE_CLASSES 的順序


如果使用快取中間件,一定要將其放置在 MIDDLEWARE_CLASSES設定的正確位置,因為快取中間件需要知道用於產生不同快取的快取中間件的標頭的標頭。


將CacheMiddleware放置在所有可能向Vary頭標添加內容的中間件之後,包括下列中間件:


§Cookie的SessionMiddlecom

以上就是Django快取機制的內容,更多相關內容請關注PHP中文網(www.php.cn)!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn