Django站点静态文件缓存相关问题

高性能网站建设指南》中有一条建议,为网站的页面、文件“添加 Expires 头”。这么做的好处就不多说了,实现方式也比较简单,不过,真的实施这条建议时,还是有许多问题需要考虑。

通常情况下,我们需要将图片、js、css 等不会经常更新的文件缓存起来,一般来说,配置服务器,为它们设置一个较远的未来的 Expires 时间就可以了(比如 1 年后)。不过,在一个经常会更改的网站中,某些 js/css 文件可能并不是一成不变的,虽然它们的更新频率比较低,但还是会不时地更新,我们希望在它们被更新后客户端也能及时更新,而不是依旧使用老的缓存。

解决这个问题的办法有很多,常用的一种是在这些 js/css 后面加上一个版本号或最后修改时间,比如:

<link href="/css/c.css?v=0.1.2" rel="stylesheet" type="text/css" media="screen" />
<a href="/js/c.js?v=3.0.1">/js/c.js?v=3.0.1</a>

如上所示,文件地址后面跟了一个 v 参数,如果文件版本更新了,我们也只需要更改这个参数的值,用户的浏览器就会重新下载新的版本。

不过同时我们又遇到了新的问题:js/css 文件与上面的 HTML 通常是在两个文件中,有时一个 js/css 在很多 HTML 或模板中都有引用,如果一个 js/css 更新了,我们不得不手动更改这些 HTML 模板文件,这是一个很枯燥的工作,而且一不小心就会有遗漏。

好在我们使用的是 Django,我们可以有一些“Djangoly”的解决方法。前不久,我就看到一个很有创意的写法,类似于这样:

&lt;link href=&quot;{{ &quot;/css/c.css&quot;|file_time_stamp }}&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen&quot; /&gt;
<a href=""></a>

熟悉 Django 的朋友应该能立即明白,这儿自定义了一个 filter file_time_stamp ,将 js/css 文件地址作为参数,读取相应文件的最后修改时间,附加到文件地址后面。最终生成的 HTML 形如:

&lt;link href=&quot;/css/c.css?fmts=1289306718.0&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; media=&quot;screen&quot; /&gt;
<a href="/js/c.js?fmts=1287902444.0">/js/c.js?fmts=1287902444.0</a>

这样,当 js/css 文件发生变化时,最后修改时间也会发生变化,相应的参数也会变化

这个 filter 的实现很简单。不过我又想到另一个问题:如果页面访问量比较大,这个 filter 是否会导致硬盘的频繁读操作?如果使用缓存将文件的最后修改时间记住一小段时间会不会更好?于是有了下面的我的实现代码:

# 请将这一段加到你的自定义标签、过滤器文件中
import os
from django.core.cache import cache
# 注意,完整代码还需要 import 更多相关模块
# ...

@register.filter(name = "file_time_stamp")
def file_time_stamp(value):
	u"""
	在 js/css 后面添加最后修改时间的时间戳,如:
	/js/c.js -> /js/c.js?fmts=1289377595.3
	如果没找取对应的文件,则直接返回原value
	"""

	cache_key = "_file_time_stamp__%s" % value
	v = cache.get(cache_key)
	if v: # 如果指定缓存不存在,v 的值将为 None
		return v

	if value.startswith("/"):
		fn = os.path.join(ROOT_DIR, "media", value[1:].replace("/", os.sep))

		if os.path.isfile(fn):
			ts = os.stat(fn).st_mtime
			sp = "?" if "?" not in value else "&"
			value = "%s%sfmts=%.1f" % (value, sp, ts)

	cache.set(cache_key, value, 300) # 300 秒后缓存到期

	return value

你可以在 settings.py 中指定使用哪种缓存,我使用的是内存缓存(CACHE_BACKEND = “locmem:///”)。

我也对使用缓存和直接用 os 模块读取文件最后修改时间两种方式的效率进行了简单的测试。不过,使用缓存并没有带来我原来预期的性能上的提高,相反,似乎比直接用 os 模块读取文件最后修改时间的性能还有略低一点。我将读取缓存与读取文件最后修改时间的操作各执行了 10 万次,在我的本本上(Ubuntu 10.04 系统),前者花费的时间约为 2.9 秒,后者约为 2.5 秒,不知道在使用 os 模块读取文件最后修改时间时,这个值是不是会在系统级别上缓存起来。

上面是在 Django 中使用的一个处理可能不定期修改的文件与缓存之间问题的方法,事实上目前本博客正在实践、体验,暂时没发现大的问题,如果你发现问题或知道更好的方法,欢迎告诉我。:-)

5 Replies to “Django站点静态文件缓存相关问题”

  1. 交给内核处理~ 赫赫关于客户端cache文件,简单的办法 我们通过更新时间戳来做到,但时间戳有个问题是 在资源.js/.css加参数,一些cache服务器比如squid之类会将请求认为是动态请求而放过,也就是说这样的请求会穿透cache..因此还需要根据情况进行额外配置..最优的方法是每次更新修改文件名..但明显成本也是最高的最难实现的..

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s