侧边栏在每一页都会查分类、最新文章、热门文章、最新评论和归档。这些数据不要求毫秒级新鲜,很适合缓存。我把侧边栏结果缓存一分钟;本地开发使用 LocMemCache,配置 REDIS_URL 后自动切到 Redis。

视图先查缓存;命中就直接渲染;未命中执行数据库查询,写回缓存。
缓存后端可切换
Django 自带 Redis 缓存后端,不需要再引入一层封装。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| import os
REDIS_URL = os.environ.get("REDIS_URL")
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
"LOCATION": "blog-default",
}
}
if REDIS_URL:
CACHES["default"] = {
"BACKEND": "django.core.cache.backends.redis.RedisCache",
"LOCATION": REDIS_URL,
}
|
本地没有 Redis 时测试仍然能跑。树莓派上用 Docker Compose 启动 Redis 后,导出一个地址即可。
1
| export REDIS_URL=redis://192.168.1.97:6379/0
|
安装驱动。
本阶段锁定 redis==8.1.0。
只缓存已经计算好的数据
原来的 sidebar_context() 返回字典,里面很多值是 QuerySet。QuerySet 是惰性对象,放进缓存后再遍历仍会查数据库。所以先让它真正执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| from django.core.cache import cache
from django.db.models import Count
def sidebar_context():
archives = list(
Post.published
.filter(published_at__isnull=False)
.values("published_at__year", "published_at__month")
.annotate(total=Count("id"))
.order_by("-published_at__year", "-published_at__month")
)
return {
"latest_posts": list(Post.published.all()[:5]),
"popular_posts": list(Post.published.order_by("-views")[:5]),
"recent_comments": list(Comment.objects.select_related("post")[:5]),
"sidebar_categories": list(
Category.objects.annotate(total=Count("posts")).filter(total__gt=0)
),
"archives": archives,
}
def cached_sidebar_context():
return cache.get_or_set(
"blog:sidebar:v1",
sidebar_context,
timeout=60,
)
|
列表、详情、分类、标签和归档都调用 cached_sidebar_context()。缓存键里带 v1。以后字段结构变化,把版本改成 v2,旧缓存自然失效。
缓存粒度放在侧边栏
我没有缓存整页。列表页有搜索词、分页码和消息提示;详情页还会更新浏览量。整页缓存要把这些变量都纳入键,复杂度立刻上来。
侧边栏不同。它只服务导航,数据变化可以容忍几十秒延迟。缓存这一小块,收益高,风险低。
一分钟是教程阶段的保守值。真实站点可以按内容更新频率调整。如果管理员发布文章后必须马上看到侧边栏变化,就在文章保存和删除时删除这个键。
1
2
3
| from django.core.cache import cache
cache.delete("blog:sidebar:v1")
|
更彻底的做法是用信号监听 Post、Category、Tag 和 Comment 的保存删除。当前先用短过期时间,避免缓存失效逻辑先于业务逻辑变复杂。
用查询数证明缓存生效
Django 提供捕获查询的工具。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| from django.core.cache import cache
from django.db import connection
from django.test import TestCase
from django.test.utils import CaptureQueriesContext
from blog.views import cached_sidebar_context
class SidebarCacheTests(TestCase):
def test_cached_sidebar_reuses_result(self):
cache.clear()
with CaptureQueriesContext(connection) as first:
cached_sidebar_context()
with CaptureQueriesContext(connection) as second:
cached_sidebar_context()
self.assertLess(len(second.captured_queries), len(first.captured_queries))
|
第一次调用会执行多条查询;第二次命中缓存,查询数应该明显减少。这个测试不关心缓存实现是 LocMem 还是 Redis,只关心 Django 缓存 API 的行为。
运行测试。
本阶段共 27 条测试。
1
2
3
| Ran 27 tests in 5.167s
OK
|
缓存不是万能优化。未命中时它不减少查询,过期时还会重建数据。但它能把公共导航的开销从每页一次降到过期周期内一次。对博客来说,这一步已经值得。
源码
GitHub 地址:https://github.com/yidao620c/simpleblog