django 缓存

2016/6/7 posted in  python

django 缓存框架

动态网站的基本原因就是每次请求都是动态的,有人在后台添加数据也会同时有人在前台读取数据,web的工作就是每次将数据层查询的数据渲染到前台给客户查看。

其实这里的缓存和CPU系统级别的缓存比较相似,对计算机架构比较清楚的会了解,CPU在读取内存的速度是大于读取磁盘的,而同时CPU进行内存交换的速度同样也有一定的延时,所以CPU引入了CPU缓存机制,针对20-80原则,这个缓存不需要很大,但是缓存的速度基本等同于CPU速度,所以可以提升整体性能。

网站缓存同样也是起到这个作用,数据库当前是存在磁盘上的,所以每次web去取值需要去到磁盘上取,这个性能由数据库性能和磁盘IO来决定。同样的根据80-20原则,大多数的请求实际上是相同的,所以我们可以将内容缓存到内存中,如果某请求在内存中已存在,就不需要重复的再去数据库查询。对于某些更新频率不高的网站,可能memcache可以解决基本所有的查询了。

django 中的伪代码描述

    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也能很好的配合那些“下游”缓存, 比如 Squid 和基于浏览器的缓存。

设置缓存位置

缓存同样可以存在不同的地方,可以存在数据库中,起到减少数据连接的效用。可以存在文件系统中,减少数据库的负担。也可以存在内存中,提高整体性能。不同的位置对于性能的区别是完全不同的。

Memchache

最高效的缓存类型, Memcached 是一个全部基于内存的缓存服务,起初是为了解决LiveJournal.com负载来开发的,后来是由Danga开源出来的。 它被类似Facebook 和 维基百科这种网站使用,用来减少数据库访问,显著的提高了网站的性能。

Memcached 是个守护进程,它被分配了单独的内存块。 它做的所有工作就是为缓存提供一个快速的添加,检索,删除的接口。 所有的数据直接存储在内存中,所以它不能取代数据库或者文件系统的使用。

需要在Django中使用Memcached时:

将 BACKEND 设置为django.core.cache.backends.memcached.MemcachedCache 或者 django.core.cache.backends.memcached.PyLibMCCache (取决于你所选绑定memcached的方式)
将 LOCATION 设置为 `ip:port 值,ip 是 Memcached 守护进程的ip地址, port 是Memcached 运行的端口。或者设置为 unix:path 值,path 是 Memcached Unix socket file的路径.

memcache的几种绑定模式,本地端口绑定:

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': '127.0.0.1:11211',
        }
    }

socket绑定:

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

多服务缓存共享:

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

Database caching 数据库级缓存

将缓存保存在数据库中。

数据库缓存配置

为了把数据表用来当做你的缓存后台:

把BACKEND设置为django.core.cache.backends.db.DatabaseCache
把 LOCATION 设置为 tablename, 数据表的名称。这个名字可以是任何你想要的名字,只要它是一个合法的表名并且在你的数据库中没有被使用过。

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
            'LOCATION': 'my_cache_table',
        }
    }   
创建缓存表

在使用之前需要使用django来创建缓存表:

    python manage.py createcachetable

表名会从LOCATION中获取

多数据库缓存

如果你在多数据库的情况下使用数据库缓存,你还必须为你的数据库缓存表设置路由说明。

    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 replica"
            if model._meta.app_label == 'django_cache':
                return 'cache_replica'
            return None
        def db_for_write(self, model, **hints):
            "All cache write operations go to primary"
            if model._meta.app_label == 'django_cache':
                return 'cache_primary'
            return None
        def allow_migrate(self, db, app_label, model_name=None, **hints):
            "Only install the cache model on primary"
            if app_label == 'django_cache':
                return db == 'cache_primary'
            return None

如果你使用多数据库缓存, createcachetable会在每个缓存中创建一个表。

如果你使用多数据库,createcachetable会遵循你的数据库路由中的allow_migrate()方法

Filesystem caching 文件级缓存

为了使用文件缓存,需要配置两个参数:

把BACKEND配置成"django.core.cache.backends.filebased.FileBasedCache"
把LOCATION配置成相应的文件目录

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

如果是windown的话,需要加上盘符

    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
            'LOCATION': 'c:/foo/bar',
        }
    }

Local-memory caching

如果没有设置其他的缓存方式,这种方式将会是setting.py中默认的缓存方式。这种缓存是每进程的,并且是线程安全的。

如果需要使用需要将BACKEND设置成 "django.core.cache.backends.locmem.LocMemCache"

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

如果有多个缓存系统的话,需要将LOCATION的名称区分出来。

Dummy caching

django 的 dummy caching实际上不是一个真实的缓存,他主要实现缓存的interface,但是不进行任何实际操作。

他的用处在于如果生产环境有一个叫重量的缓存,但是本地开发环境不希望缓存,但是你又不希望为这种情况修改代码,就可以使用dummy caching

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

缓存配置参数

TIMEOUT: 缓存过期时间,以秒为准,默认为300秒

OPTIONS: 由第三方库所支持的缓存将会把这些选项直接配置到底层缓存库

  • MAX_ENTRIES:高速缓存允许的最大条目数,超出这个数则旧值将被删除. 这个参数默认是300.
  • CULL_FREQUENCY:当达到MAX_ENTRIES 的时候,被删除的条目比率。 实际比率是 1 / CULL_FREQUENCY, 所以设置CULL_FREQUENCY 为2会删去一半的缓存MAX_ENTRIES 达到时。这个参数应该是整数,默认为 3. 把 CULL_FREQUENCY的值设置为 0 意味着当达到MAX_ENTRIES时,缓存将被清空。某些缓存后端 (database尤其)这将以很多缓存丢失为代价,大大much 提高接受访问的速度。

KEY_PREFIX:将会自动保存到django服务器的所有缓存key中

VERSION:默认成本的而cache key版本。

KEY_FUNCTION:

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

使用站点级缓存

一旦高速缓存设置,最简单的方法是使用缓存缓存整个网站。

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

'update'中间件,必须放在列表的开始位置,而fectch中间件,必须放在最后。

然后,添加下面这些需要的参数到settings文件里:

CACHE_MIDDLEWARE_ALIAS – 用于存储的缓存的别名

CACHE_MIDDLEWARE_SECONDS –每个page需要被缓存多少秒.

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

FetchFromCacheMiddleware 缓存GET和HEAD状态为200的回应,用不同的参数请求相同的url被视为独立的页面,缓存是分开的。

单个view缓存

更加轻巧的缓存方式,可以精确到每个view中。django为每个view定义了相关的装饰器

django.views.decorators.cache 定义了一个自动缓存视图响应的 cache_page装饰器

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

60*15代表缓存的秒数。

和站点缓存一样,视图缓存与 URL 无关。如果多个 URL 指向同一视图,每个URL将会分别缓存。

    urlpatterns = [
        url(r'^foo/([0-9]{1,2})/$', my_view),
    ]

此案例中发送到_foo/1和 /foo/23会被分别缓存。但是一旦一个明确的 URL (e.g., /foo/23_) 已经被请求过了, 之后再度发出的指向该 URL 的请求将使用缓存

一些额外的参数

cache, 指示修饰符去具体使用缓存 (from your CACHES setting) 当要缓存页面结果时。

    @cache_page(60 * 15, cache="special_cache")
    def my_view(request):
        ...

key_prefix

    @cache_page(60 * 15, key_prefix="site1")
    def my_view(request):
        ...

在URL中使用cache

前面可以看到我们在view中添加了相关的装饰器来使用缓存,这样会导致view层和缓存强耦合。会导致在某些无缓存的站点中重用该视图的时候报错。

解决方法是将cache配置到url中:

from django.views.decorators.cache import cache_page

urlpatterns = [
url(r'foo_([0-9]{1,2})_$', cache_page(60 * 15)(my_view)),
]

模板片段缓存

可以使用cache模板标签来缓存模板的一个片段。使用 {% load cache %} 来指定缓存模板标签

标签{% cache %}将按给定的时间缓存包含块中的内容。

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

缓存API

有时候,我们并不希望总是缓存完整的页面,这样实际上有时候效率比较低下

如果网页包含一个view,这个view依赖于几个比较复杂的查询,查询结果在不同的情况下都会变化。在这种情况的时候,使用整页caching结果肯定是不理想的。

在这种时候,django提供了更底层的缓存API,用这样的API能够更好并且更安全的cache数据。

可以参考:

    >>> from django.core.cache import caches
    >>> cache1 = caches['myalias']
    >>> cache2 = caches['myalias']
    >>> cache1 is cache2
    True
    >>> from django.core.cache import get_cache
    >>> get_cache('default')
    >>> get_cache('django.core.cache.backends.memcached.MemcachedCache', LOCATION='127.0.0.2')
    >>> get_cache('default', TIMEOUT=300)

基本的使用方法:

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

Cache key prefixing

如果你在多台服务器之间,或者在生产和开发环境之间共享cache实例,有可能出现一个数据在一台服务器上缓存,但是需要被另外一台使用。如果缓存数据在两台服务器上是不同的,将导致很多的严重问题。

为了解决这些问题,Django可以提供将可以给不同的服务器添加一个cache前缀,当出现这种情况的时候,django将自动为这些cache添加前缀。

Cache versioning

当运行时的缓存值状态改变的时候,我们需要清除并更新已经存在的cache值。最简单的方法当然是更新整个的缓存区,但是这肯定会导致很多依然可用的缓存丢失。

django为单独的缓存值提供了一个更好的方法,django缓存框架有一个系统记得版本控制器,使用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!'