使用Django.core.cache操作Memcached導致性能不穩定的分析過程

使用Django.core.cache操作Memcached導致性能不穩定的分析過程

    最近測試一項目,用到了Nginx緩存服務,那可真是快啊!2Gb帶寬都輕易耗盡。

    不過Api接口無法簡單使用Nginx緩存,使用Memcached作二級緩存。但發現性能非常之不穩定,最終發現問題出在Memcached上。大壓力時Memcached無法連接,即使使用Telnet也連接超時/連接被拒絕。

    與開發溝通後發現用的django.core.cache操作Memcached,於是要求使用其它庫取代,選中python-memcached庫(memcache.Client)。

    問題迎刃而解。爲了進一步確定問題的根源,專門針對這兩個庫進行了對比測試。在測試過程監控Memcached的各指標,然後進行對比。

    對比Memcached指標“get_hits/second”和“total_connections/second”直揭問題根源:


    上圖說明的問題是:

     1、使用django.core.cache庫操作Memcached時,創建的連接數和查詢數相當(基本是完全相等的,爲了不兩曲線重疊,給查詢數 + 50)。

    2、 使用memcache.Client則創建連接的曲線是平穩的,即創建連接的速度是穩定的。

    那麼:爲什麼會導致Memcached無法連接呢?因爲Memcached啓動時可設定最大連接數,大壓力下創建連接數超過限制自然無法連接。

    雖然調大Memcached可接受的連接數可以緩解問題,但這種解決辦法明顯不靠譜!

    所以,拋棄Django吧!Django確實提高開發速度,但易用性背後損失了性能。

    在這裏把驗證過程也貼出來,有興趣的朋友可以自己搶建測試環境進行驗證:

/usr/local/python/bin/easy_install django
/usr/local/python/bin/easy_install python-memcached
cd /data/apps/
/usr/local/python/bin/django-admin.py startproject higkoo

# nginx.conf
location / {
    fastcgi_pass        127.0.0.1:8950;
    fastcgi_param       CONTENT_TYPE    $content_type;
    fastcgi_param       CONTENT_LENGTH $content_length;
    fastcgi_param       PATH_INFO       $fastcgi_script_name;
    fastcgi_param       REQUEST_METHOD $request_method;
    fastcgi_param       QUERY_STRING    $query_string;
    fastcgi_param       REMOTE_ADDR     $remote_addr;
    fastcgi_param       REMOTE_PORT     $remote_port;
    fastcgi_param       SERVER_PROTOCOL $server_protocol;
    fastcgi_param       SERVER_ADDR     $server_addr;
    fastcgi_param       SERVER_PORT     $server_port;
    fastcgi_param       SERVER_NAME     $server_name;
    fastcgi_param       SCRIPT_FILENAME $fastcgi_script_name;
    fastcgi_param      GATEWAY_INTERFACE CGI/1.1;
    fastcgi_param       SERVER_SOFTWARE nginx/$nginx_version;
} # higkoo/settings.py 加入Memcached的配置
CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
# 新增文件 higkoo/views.py
from django.http import HttpResponse
from django.core.cache import cache
import memcache
mc = memcache.Client(['127.0.0.1:11211'], debug=0)

def cache1(request):
    data = cache.get('test1KeyName')
    if data is not None:
        return HttpResponse(data)
    data = 'You request test1 .'
    cache.set('key1', data)
    return HttpResponse(data)

def cache2(request):
    data = mc.get('test2KeyName')
    if data is not None:
        return HttpResponse(data)
    data = 'You request test2 .'
    mc.set('key2', data)
    return HttpResponse(data)

# 在 higkoo/urls.py 添加程序入口
    (r'^cache1/', 'views.cache1'),
    (r'^cache2/', 'views.cache2'),

# 啓動 Python fastcgi
/usr/local/python/bin/python2.5 /data/apps/higkoo/manage.py runfcgi host=127.0.0.1 port=8950 pidfile=/tmp/higkoo.pid
# 啓動 Memcached
/usr/local/memcached/bin/memcached -d -m 1024 -u root -p 11211 -c 1024 -P /tmp/memcached.pid
# 啓動 Nginx
/usr/local/nginx/sbin/nginx -c /data/apps/higkoo/nginx.conf
# 訪問方式:
http://192.168.10.31/cache1
http://192.168.10.31/cache2

OK:cache1接口是Django的,cache2接口是Python-Memcached的。




摘自:http://www.9say.com/2009/01/django-cache-framework/

翻譯:django 緩存框架(1)(django cache framework)

January 8, 2009 by admin · Leave a Comment
Filed under: django 

譯者注:1、無用的,吹噓的說辭不翻譯;2、意譯,很多地方不準確。

動態網站最爲重要的一點就是好,網頁是動態的。每一次用戶請求頁面,網站就要進行各種計算——從數據庫查詢,到render模板,到各種邏輯運算——生成頁面所需的。這個過程是異常消耗資源的,遠遠比從硬盤讀取一個文件然後顯示出來的代價高昂。

對於大多數中小網站來說,這也許不是問題,因爲他們的訪問量不大,而對於大型網站而言,必須儘量減少不必要的服務器資源開支。

因此,有了緩存技術。

緩存就是把一些需要消耗很多資源的計算結果保存下來,當下次需要時,不需要再次進行計算,直接從緩存中取,從而節省了資源。緩存的僞碼錶達如下:

給定的url,在緩存中查找是否存在;

如果存在,返回cached page;

否則:

     生成該頁面;

     把該頁面保存在緩存中

     return 頁面

django的緩存系統非常健壯…..(一些自誇的話,譯者注)。簡單的說,django提供不同粒度的緩存:特定view的緩存,緩存某個耗時的程序片段的結果,或者緩存整個網站。

django也能夠和一些“上游的”緩存技術協同工作,例如squid ,瀏覽器的緩存等。這些緩存類型是你不需要直接控制,但是可以通過HTTP head 協議來告訴別人,網站的那些部分需要被緩存,如何緩存。

建立緩存

緩存系統(cache system)需要一些設置。你需要告訴django,你的緩存數據保存在什麼地方:文件,數據庫,或者內存中。不同的緩存方式的性能是不同的。

在settings.py文件中,設置CACHE_BACKEND, 下面是一些可用的CACHE_BACKEND值:

Memcached

目前最快的,效率最高的cache。

Memcached的官方網站:http://danga.com/memcached/  Memcached的所有數據都存在內存中,因此,不會有數據庫消耗和讀取文件消耗。

安裝Memcached後,你需要安裝Memcached python bindings。目前有兩種版本可用,分別爲:

速度最快的選擇是cmemcache,下載地址爲 http://gijsbert.org/cmemcache/

django 1.0之前版本不可用。

如果你無法安裝 cmemcache, 你可以選擇安裝 python-memcached, 下載地址:ftp://ftp.tummy.com/pub/python-memcached/ . 如果這個網址已經失效,, 到memcached官方網站 (http://www.danga.com/memcached/) ,到“Client APIs”下載 Python bindings.

使用Memcached,只需把CACHE_BACKEND設置爲memcached://ip:port/的格式,ip地址是memcached服務運行的機器ip地址,端口是memcached服務監聽的端口號。

示例如下,如果memcached服務運行於本機,端口爲11211,則配置語句爲:

CACHE_BACKEND=’memcached://127.0.0.1:11211/’

memcached一個非常好的特性是可以多機共享cache,只需把所有提供緩存的機器的ip地址和端口號全部列入即可,之間用逗號分開。示例如下:

CACHE_BACKEND= ‘memcached://172.19.26.240:11211;172.19.26.242:11211/’

memcache也許有一個缺點,那就是由於memcached是基於內存的,當你的服務器crash時,所有的cache數據都將丟失。但由於cache本身就是臨時的,所以,丟失cache數據沒什麼大問題。

 

 

翻譯:django 緩存框架(2)(django cache framework)[轉載]

January 17, 2009 by admin · Leave a Comment
Filed under: django 

在網絡上搜索了一下,這篇文章已經有人翻譯過了。

原文在這裏:http://www.woodpecker.org.cn/obp/django/django-faq/cache.html

文件系統緩存

 

要在文件系統中保存緩存數據, 在 CACHE_BACKEND 中使用 “file://” 緩存類型. 下面這個例子將緩存數據保存在 /var/tmp/django_cache 中, 設置如下:

 

CACHE_BACKEND = ‘file:///var/tmp/django_cache’

注意 file: 之後是三個斜線而不是兩個. 前兩個是 file:// 協議, 第三個是路徑 /var/tmp/django_cache 的第一個字符.

 

這個目錄路徑必須是絕對路徑 — 也就是說,它應該從你的文件系統的根開始算起.至於路徑的最後是否加一個斜線, Django 並不在意.

要確保這個設定的路徑存在並且 Web 服務器可以讀寫.繼續上面的例子,如果你的服務器以 apache 用戶身份運行, 確保目錄 /var/tmp/django_cache 存在並且可以被用戶 apache 讀寫.

 

本地內存緩存

 

如果你想要內存緩存的高性能卻沒有條件運行 Memcached, 可以考虛使用本地內存緩存後端. 這個緩存後端是多進程的並且線程安全. 要使用它,設置 CACHE_BACKEND 爲 “locmem:///”. 舉例來說:

 

CACHE_BACKEND = ‘locmem:///’

簡單緩存(用於開發)

 

“simple:///” 是一個簡單的,單進程的內存緩存類型. 它僅僅在進程中保存緩存數據, 這意味着它僅適用於開發或測試環境. 例子:

 

CACHE_BACKEND = ’simple:///’

虛擬緩存 (用於開發)

 

最後, Django還支持一種 “dummy” 緩存(事實上並未緩存) — 僅僅實現了緩存接口但未實際做任何事.

 

CACHE_BACKEND 參數

 

所有緩存類型均接受參數. 提供參數的方式類似查詢字符串風格. 下面列出了所有合法的參數:

 

timeout

默認的緩存有效時間,以秒計. 默認值是 300 秒(五分鐘).

max_entries

用於 簡單緩存 及 數據庫緩存 後端, 緩存的最大條目數(超出該數舊的緩存會被清除,默認值是 300).

cull_percentage

當達到緩存的最大條目數時要保留的精選條目比率. 實際被保存的是 1/cull_percentage, 因此設置 cull_percentage=3 就會保存精選的 1/3 條目上,其餘的條目則被刪除.

 

如果將 cull_percentage 設置爲 0 則意味着當達到緩存的最大條目數時整個緩存都被清除.當緩存命中率很低時這會 極大的 提高精選緩存條目的效率(根本不精選).

這個例子裏, timeout 被設置爲 60:

 

CACHE_BACKEND = “memcached://127.0.0.1:11211/?timeout=60″

這個例子, timeout 設置爲 30 而 max_entries 設置爲 400:

 

CACHE_BACKEND = “memcached://127.0.0.1:11211/?timeout=30&max_entries=400″

非法的參數會被忽略.

 

緩存整個站點

 

設置了緩存類型之後, 最簡單使用緩存的方式就是緩存整個站點. 在“MIDDLEWARE_CLASSES“ 設置中添加 django.middleware.cache.CacheMiddleware , 就象下面的例子一樣:

 

MIDDLEWARE_CLASSES = (

    ”django.middleware.cache.CacheMiddleware”,

    ”django.middleware.common.CommonMiddleware”,

)

( MIDDLEWARE_CLASSES 順序相關. 參閱下文中的 “Order of MIDDLEWARE_CLASSES”)

 

然後,在 Django 設置文件中添加以下設置:

 

CACHE_MIDDLEWARE_SECONDS — 每個頁面被緩存的時間.

CACHE_MIDDLEWARE_KEY_PREFIX — 如果緩存被同一個 Django 安裝的多個站點共享, 在這裏設置站點名字, 或者某些其它唯一的字符串, 以避免 key 衝突.如果你不介意,也可以使用空串.

緩存中間件緩存沒有 GET/POST 參數的每個頁面.另外, CacheMiddleware 自動在每個 HttpResponse 中設置一些 headers:

 

當請求一個未緩存頁面時,設置 Last-Modified header 到當前的日期時間.

設置 Expires header 到當前日期時間加上定義的 CACHE_MIDDLEWARE_SECONDS.

設置 Cache-Control header 爲該頁面的生存期 — 該生存期也來自 CACHE_MIDDLEWARE_SECONDS 設置.

參閱 middleware documentation 瞭解中間件的更多信息.

 

緩存單個 view

 

Django 能夠只緩存特定的頁面. django.views.decorators.cache 定義了一個 cache_page 修飾符, 它能自動緩存該 view 的響應. 該修飾符的使用極爲簡單:

 

from django.views.decorators.cache import cache_page

 

def slashdot_this(request):

    …

 

slashdot_this = cache_page(slashdot_this, 60 * 15)

或者, 使用Python 2.4 的修飾符語法:

 

@cache_page(60 * 15)

def slashdot_this(request):

    …

cache_page 僅接受一個參數: 緩存有效期,以秒計. 在上面的例子裏,  slashdot_this() view 將被緩存 15 分鐘.

 

底層緩存 API

 

某些時候, 緩存一個完整的頁面不符合你的要求. 比如你認爲僅有某些高強度的查詢纔有必要緩存其結果.要達到這種目的,你能使用底層緩存 API 來在任意層次保存對象到緩存系統.

 

緩存 API 是簡單的. 從緩存模塊 django.core.cache 導出一個由 CACHE_BACKEND 設置自動生成的 cache 對象:

 

>>> 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:

 

>>> cache.get(’some_other_key’)

None

 

# Wait 30 seconds for ‘my_key’ to expire…

 

>>> cache.get(’my_key’)

None

get() 可以接受一個 default 參數:

 

>>> cache.get(’my_key’, ‘has expired’)

‘has expired’

當然還有一個 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}

最後, 你可以使用  delete() 顯式的刪除鍵. 這是一個在緩存中清除特定對象的簡便的方式:

 

>>> cache.delete(’a')

就是這樣. 緩存機制限制非常少: 你可以安全的緩存能被 pickled 的任意對象(key必須是字符串).

 

Upstream caches

 

到現在爲止, 我們的目光僅僅聚焦在 你自己的 數據上. 在 WEB 開發中還有另外一種類型的緩存: 

 

“upstream” 緩存. 這是用戶請求還未抵達你的站點時由瀏覽器實施的緩存.

 

下面是 upstream 緩存的幾個例子:

 

你的 ISP 會緩存特定頁面, 當你請求 somedomain.com 的一個頁面時, 你的 ISP 會直接發送給你一個緩存頁.(不訪問 somedomain.com ).

你的 Django Web 站點可能建立在一個 Squid (http://www.squid-cache.org/) Web 代理服務器之後, 它會緩存頁面以提高性能. 這種情況下,每個請求會先經 Squid 處理, 僅在需要時它會將請求傳遞給你的應用程序.

你的瀏覽器也會緩存一些頁面. 如果一個 WEB 頁發送了正確的 headers, 瀏覽器會用本地(緩存的)拷貝來回應後發的同一頁面的請求.

Upstream 緩存是一個非常有效的推進, 不過它也有相當不足之處: 很多 WEB 頁基於授權及一堆變量, 而這個緩存系統盲目的單純依賴 URL 緩存頁面, 這可能會對不適當的用戶泄露敏感信息.

 

舉例來說,假設你使用一個 Web e-mail 系統, “inbox” 頁的內容顯然依賴當前登錄用戶. 如果一個 ISP 盲目的緩存了你的站點, 後來的用戶就會看到前一用戶的收件箱, 這可不是一件有趣的事.

 

幸運的是, HTTP 提供了一個該問題的解決方案: 用一系列 HTTP headers 來構建緩存機制以區分緩存內容, 這樣緩存系統就不會緩存某些特定頁.

 

使用 Vary headers

 

其中一個 header 就是 Vary. 它定義了緩存機制在創建緩存 key 時的請求 headers. 舉例來說, 如果一個網頁的內容依賴一個戶的語言設置, 則該網頁被告知 “vary on language.”

 

默認情況, Django 的緩存系統使用請求路徑創建 緩存 key — 比如, ”/stories/2005/jun/23/bank_robbed/”. 這意味着該 URL 的每個請求使用相同的緩存版本, 不考慮用戶代理的不同.(cookies 及語言特性).

 

因此我們需要 Vary .

 

如果你的基於 Django 的頁面根據不同的請求 headers 輸出不同的內容 — 比如一個 cookie, 或語言, 或用戶代理 — 你會需要使用 Vary header 來告訴緩存系統這個頁面輸出依賴這些東西.

 

要在 Django 中做到這一步, 使用  vary_on_headers view 修飾符,就象下面這樣:

 

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 header 相比:使用類似 response['Vary'] = ‘user-agent’)修飾符會添加到 Vary header (可能已存在) 而不是覆蓋掉它.

 

你也可以傳遞多個 header 給 vary_on_headers():

 

@vary_on_headers(’User-Agent’, ‘Cookie’)

def my_view(request):

    …

由於多個 cookie 的情況相當常見, 這裏有一個 vary_on_cookie 修飾符. 下面兩個 views 是等價的:

 

@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:

 

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 實例作爲它的第一個參數及一個 header 名字的列表或tuple作爲第二個參數.

 

要了解 Vary headers 的更多信息, 參閱 official Vary spec.

控制緩存: 使用其它 headers緩存的另一個問題是數據的私密性及瀑布緩存模式下數據保存到哪裏.

 

用戶經常要面對的有兩種緩存: 他自己的瀏覽器緩存(私人緩存) 及站點提供的緩存(公開緩存).一個公開緩存用於多用戶情況, 其內容由另外的人控制. 這造成了數據的私密性問題: 你當然不想你的銀行帳號保存在公共緩存裏. 因此應用程序需要一種方式告訴緩存系統哪些東西是私密的,哪些則是公開的.

 

解決方案就是聲明某個頁面的緩存是 “私密的”. 在 Django 中, 使用 cache_control view 修飾符. 例子:

 

from django.views.decorators.cache import cache_control

@cache_control(private=True)

def my_view(request):

    …

這個修飾符會在幕後謹慎的發送適當的 HTTP header 發避免上面的問題.

 

還有一些其它的方式控制緩存參數. 舉例來說,HTTP 允許應用程序做以下事:

 

定義一個頁面被緩存的最大時間.指定緩存是否需要總是檢查新版本, 如果沒有變化則僅傳送緩存版本. (某些緩存即使服務器端頁面變化也僅傳遞緩存版本–僅僅因爲緩存拷貝尚未到期).

在 Django 中, 使用 cache_control view 修飾符指定緩存參數.在這個例子裏 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 HTTP 指令在 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

要了解 Cache-Control HTTP 指令的細節,參閱 Cache-Control spec.

 

(注意緩存中間件已經通過設置中的 CACHE_MIDDLEWARE_SETTINGS 的值設定了緩存 header 的 max-age. 如果你在 cache_control 修飾符中使用了自定義的 max_age , 修飾符中的設置將被優先使用, header 值會被正確的合併)

 

其它優化

 

Django 自帶了一些中間件以幫助你提高站點性能:

 

django.middleware.http.ConditionalGetMiddleware 添加了有條件GET的支持. 它利用了 ETag 及 Last-Modified headers.

django.middleware.gzip.GZipMiddleware 爲支持 Gzip 的瀏覽器對發送內容進行壓縮(所有流行瀏覽器均支持).

MIDDLEWARE_CLASSES 順序

 

如果你使用了 CacheMiddleware, 在 MIDDLEWARE_CLASSES 設置中使用正確順序非常重要. 由於緩存中間件需要知道哪些 headers 由哪些緩存存儲.中間件總是在可能的情況下添加某些東西到 Vary 響應 header.

 

將 CacheMiddleware 放到其它可能添加某些東西到 Vary Header的中間件之後,下面的中間件會添加東西到 Vary header:

 

SessionMiddleware 添加了 Cookie

GZipMiddleware 添加了 Accept-Encoding


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章