Django源码剖析(03)--缓存的更多思考(CacheAside的Django实现)

CacheAside或许是应用最为广泛的一种缓存失效策略, 当然这里是指应用层面. 而Django并没有提供这样的机制, 所以我们需要自己手动的进行编码.

1. 缓存原理

Alt text

在前面的源码分析中我们已经得知了完整的缓存原理, 这里画一个简图.

2. 缓存失效

翻遍了Django关于cache的文档, 并没有找到哪个函数或者接口能够让缓存失效, 所以我们需要自己写一个.
缓存的几种策略: CacheAside, Read/Write Through, Write Behind Caching, 后两种(准确来讲是3种)通常在业务中不会使用, CacheAside才是最常用的缓存策略.

  • 请求先从cache取数据,没有得到,则执行视图函数,成功后,将response放到缓存中。
  • 再次请求, 命中缓存, 直接返回
  • 当缓存数据发生改动时, 使该数据所对应的缓存失效

缓存生成与命中Django的两个缓存中间件已经完成了, 剩下的就是使缓存失效而已.

2.1 公共缓存失效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import os
import django
os.environ.update({"DJANGO_SETTINGS_MODULE": "Zero.settings.local"})
django.setup()

from django.core.cache import cache
from django.utils.cache import get_cache_key

from django.http import HttpRequest

http_request = HttpRequest()
http_request.META["SERVER_NAME"] = "localhost"
http_request.META["SERVER_PORT"] = "6060"
http_request.path = "/cache"
key = get_cache_key(http_request, key_prefix="test_cache")
# views.decorators.cache.cache_page.test_cache.GET.15e585e58b05970a7be785828893971e.d41d8cd98f00b204e9800998ecf8427e.en-us.UTC 这里将key打印出来
if key:
cache.delete(key)

上段代码于某py文件中进行测试, 故需要进行Django组件的初始化.而后实例化一个HttpRequest, 添加相关属性, 调用get_cache_key方法生成该请求对象所对应的cache_key, 并将其进行删除.
在测试通过之后, 我们就可以使用Django中为所有的Model提供的观察者模式–信号量, 来进行解耦了.

1
2
3
4
5
6
7
8
from django.dispatch import receiver
from django.db.models.signals import post_save


@receiver(post_save, sender=User)
def remove_user_cache(sender, instance, created, **kwargs):
if created:
# 上面的主要代码逻辑...

2.2 私有缓存失效

在常规的web开发中, Cookie仍然是保持浏览器认证状态的最佳选择, 如果我想要使得主动的删除私有缓存, 那么我们就需要能够通过某一个用户来手动的创建该用户的Cookie.首先看一下Session生成的流程
Alt text

再来看一下数据库表结构以及含义:
Alt text

请求返回的Cookie中的sessionid与数据库中的session_key的关系:
Alt text

可以很清晰的看到, 数据库中的session_key字段, 等于Cookie中的sessionid字段.为随机值, 无法进行二次生成.
那么session_key不行, 就只能使用session_data了.
加密过程:

1
2
3
4
5
def encode(self, session_dict):
"Returns the given session dictionary serialized and encoded as a string."
serialized = self.serializer().dumps(session_dict)
hash = self._hash(serialized)
return base64.b64encode(hash.encode() + b":" + serialized).decode('ascii')

经过调试, 可以得到session_dict值为:

1
2
3
4
5
{
'_auth_user_backend': 'django.contrib.auth.backends.ModelBackend',
'_auth_user_hash': 'dc7232b29179abe90010145032f623a555076eba',
'_auth_user_id': '2'
}

诶, 发现这些字段都可以搞到手, _auth_user_hash在前面也提到过, 是SECRET_KEY, user.password以及一个固定的salt这些字段经过一系列操作所生成的HAMC摘要.为已知量.
到这里就可以正式的编写删除私有缓存的代码了:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import django
import os
os.environ.update({"DJANGO_SETTINGS_MODULE": "Zero.settings.local"})
django.setup()

from django.utils.cache import get_cache_key
from django.contrib.auth.models import User
from django.contrib.sessions.backends.db import SessionStore

from django.http import HttpRequest

from django.contrib.sessions.models import Session


def get_user_session_key(user):
session_auth_hash = user.get_session_auth_hash()
SESSION_KEY = user._meta.pk.value_to_string(user)
BACKEND_SESSION_KEY = "django.contrib.auth.backends.ModelBackend"
HASH_SESSION_KEY = session_auth_hash
session_dict = {
"_auth_user_id": SESSION_KEY,
"_auth_user_backend": BACKEND_SESSION_KEY,
"_auth_user_hash": HASH_SESSION_KEY
}

session_store = SessionStore("sessionid")
session_data = session_store.encode(session_dict)
print(session_data)

session = Session.objects.filter(session_data=session_data).last()
print(session.session_key)
return session.session_key


http_request = HttpRequest()

http_request.META["SERVER_NAME"] = "localhost"
http_request.META["SERVER_PORT"] = "6060"
http_request.path = "/cache"

user = User.objects.get(id=2)
session_key = get_user_session_key(user)
http_request.META["HTTP_COOKIE"] = "sessionid={}".format(session_key)
key = get_cache_key(http_request, key_prefix="test_cache")
print(key)

但是这是一个非常不稳定的程序, 不稳定的原因有很多, 例如用户修改密码, session_key生成失败等等, 都会造成cache_key的失败获取.让私有缓存失效session_key这样的方式其实是非常不好的, 其中的不稳定性非常多, 与其让其稳定, 不如另辟蹊径.寻找其它的私有缓存生成方式.只不过这样一来的话就需要定制化的编码, 工作量初步估计并不会特别的少.

3. 总结

对于公共缓存, Django内部还是能够很少的生成以及使其失效的; 但是对于被私有缓存, 虽然我们能够通过一些手段来使其失效, 但是并不推荐这种做法, 其稳定性我认为是非常差的, 不能应用于生产环境.需要我们自己重新的写一套适用于自身业务场景的代码.