通常在django中查询数据都是直接使用django提供的orm,例如,

Post.objects.get(id=1)

这个操作固然方便,但当数据量大或者是数据库压力变大时就不得不考虑引入缓存了,特别是对于那些大部分时间并不会被修改的对象来说。

一开始的想法很简单,实现一个独立的cache访问文件,其中提供接口已从cache中读取,

# post_cache.py
def get_post(post_id):
    key = build_cache_key(Post, post_id)
    val = cache.get(key)
    if not val is None:
        return val
    val = Post.objects.get(id=post_id)
    cache.set(key, val, EXPIRATION)
    return val

# category_cache.py
def get_category(category_id):
     ...

对于不同的model对象分别提供不同的函数以支持从缓存读取。除了提供get之外还需要注意缓存中数据的更新,缓存数据更新这一步比较适合的方法是采用django的signal机制,通过响应model的post_save来进行数据更新。

def post_save_update_post(sender, instance, **kwargs):
    post = instance
    key = build_cache_key(Post, post.id)
    cache.set(key, post, EXPIRATION)

def post_save_update_category(sender, instance, **kwargs):
    ...

实现了上述代码后,则可以进行如下调用,

post = post_cache.get_post(post_id=1)
category = category_cache.get_category(category_id=1)

基本的功能需求算是实现了。但仔细观察一下就会发现这种写法太过繁琐,每一个需要缓存的model都要来这么一下定义还是很麻烦的,特别是还要处理post_save。容易遗忘,容易出错。

不过观察上述代码后也不难发现很多地方是相似的,本着dry的编码原则,重复代码是需要整合的,

# model_cache.py
def get(model_class, pk_id):
    key = build_cache_key(model_class, pk_id)
    val = cache.get(key)
    if not val is None:
        return val
    val = model_class.objects.get(pk=pk_id)
    cache.set(key, val, EXPIRATION)
    return val

def model_post_save(sender, instance, **kwargs):
    key = build_cache_key(sender, instance.pk)
    val.set(key, instance, EXPIRATION)

经过简单的抽象合并后,就可以这么进行使用,

post = model_cache.get(Post, 1)
category = model_cache.get(Category, 1)

很明显一致了很多。不过上述实现还是有点太弱,比如只支持用主键查询缓存,想根据别的唯一字段进行查询就不能了,所以需要进一步优化处理,

# model_cache.py
def get(model_class, **kwargs):
    key = build_cache_key(model_class, **kwargs)
    val = cache.get(key)
    if not val is None:
        return val
    val = model_class.objects.get(**kwargs)
    cache.set(key, val, EXPIRATION)
    return val

在使用时需要改用命名参数,

post = model_class.get(Post, id=1)
category = model_class.get(Category, id=1)
category = model_class.get(Category, title='foobar')

上述实现还需要考虑build_cache_key函数生成的key的有效性、唯一性。此外post_save的更新函数也需要进行相应修改,就不再列出了。

现在得到的版本相比最初是改观很大了,不过用着还是有些不方便。参看下django orm提供的接口,

Post.objects.get(id=1)

缓存访问是不是也可以采用类似的形式呢?好吧,进一步修改,

class ModelCacheManager(object):
    def __init__(self, model_class):
        self.model_class = model_class

    def get(self, **kwargs):
        ....

 class Post(models):
     ...

 Post.cache = ModelCacheManager(Post)

在将之前访问缓存的get操作等封装导ModelCacheManager中之后,在外层就可以这么进行调用,

post = Post.cache.get(id=1)
category = Category.cache.get(id=1)

经过这么一系列的改进,算是有一个能用版本了。这个版本依然有不足,比如接收的参数也可以改为django orm的那些参数,然后可以考虑进一步实现cache.filter(...)等操作。一句话,经可能简化缓存的访问调用。不过这些进一步的优化就有待需要使用的时候再行添加了。