h¶
缓存(cache)¶
应用
缓存用于保存需要大量计算的操作结果,或者需要快速访问的数据。
比如一个数量庞大的排序,一个耗时的搜索,网站主页内容,等等。
定义
最简单的缓存实现一般只有两个操作,一个是set
,另一个是get
。
更新缓存有两种常用策略:
- 手动更新
- 自动过期
通常我们可以在数据更新的时候手动更新缓存,比如每当有新数据插入的时候,我们就执行一次set
,让新缓存覆盖旧缓存。
另一方面,我们可以用expire
操作给缓存设定一个过期时间,当缓存过期时它被自动删除,然后在每次查找数据的时候查看缓存是否存在,如果缓存不存在则重建缓存,并为缓存设定过期时间,然后返回结果;如果缓存存在,则直接取出缓存里的结果并返回。
缓存的删除操作delete
通常是可有可无的,其一是因为你可以直接用set
覆盖旧缓存,其二是可以让缓存自动过期,所以通常不需要删除缓存,只是偶尔在调试的时候会用上。
实现
缓存可以用Redis的string_struct或hash_struct来实现。
字符串实现的优点是可以为每个缓存分别设置过期时间,缺点是比哈希表实现占用更多的空间。
而在哈希表实现中,每个哈希表只能共享同一个过期时间(也即是,放在同一个哈希表中的所有缓存会同时过期)。但是你可以利用这一特点为缓存分类,比如你可以将所有排序操作的缓存放到名为sort_cache
的哈希表中,而将所有搜索操作的缓存放到名为search_cache
的哈希表中,然后分别为sort_cache
和search_cache
设置不同的过期时间。
并且哈希表实现比字符串实现更节省空间。
See also
我们通常不对缓存的数量进行限制,如果你需要限制缓存的数量(比如只允许最多100个缓存),请参考日志(log)。
如果你需要实现一些复杂的缓存算法,比如Most Recently Used(MRU)或Least Recently Used(LRU)请使用sorted_set_struct。
关于哈希表比字符串更节约空间的讨论,请参考Redis官方的Memory optimization文档。
字符串实现¶
# file: ./h/cache/string_implement.py
from redis import Redis
def set(name, value, ttl=None, client=Redis()):
if ttl:
client.setex(name, value, ttl)
else:
client.set(name, value)
def get(name, client=Redis()):
return client.get(name)
def delete(name, client=Redis()):
client.delete(name)
# test:
if __name__ == "__main__":
from time import sleep
key = 'phone'
value = '10086'
expire_time = 3
set(key, value)
assert get(key) == value
delete(key)
assert get(key) == None
set(key, value, expire_time)
assert get(key) == value
sleep(expire_time * 2)
assert get(key) == None
哈希表实现¶
哈希表实现比字符串实现提供更多功能,因此也相对复杂一些。
我们用category
参数给缓存分类,并增加expire
操作来设置整个哈希表的过期时间,ttl
函数返回哈希表的剩余生存时间,size
则返回给定类型的分类缓存的数量。
# file: ./h/cache/hash_implement.py
from redis import Redis
def set(category, name, value, client=Redis()):
client.hset(category, name, value)
def get(category, name, client=Redis()):
return client.hget(category, name)
def delete(category, name, client=Redis()):
client.hdel(category, name)
def expire(category, ttl, client=Redis()):
client.expire(category, ttl)
def ttl(category, client=Redis()):
return client.ttl(category)
def size(category, client=Redis()):
return client.hlen(category)
# test:
if __name__ == "__main__":
from time import sleep
category = 'greet'
key = 'morning'
value = 'good morning!'
expire_time = 3
set(category, key, value)
assert get(category, key) == value
assert size(category) == 1
delete(category, key)
assert get(category, key) == None
assert size(category) == 0
set(category, key,value)
expire(category, expire_time)
assert ttl(category) != None
sleep(expire_time * 2)
assert get(category, key) == None
实例:用Python装饰器为函数加上缓存¶
Python中有一个方便好用的特性,就是它的装饰器(decorator)机制,可以无缝地为特定的函数加上新的功能。
我们可以将装饰器、函数和我们的缓存实现三者集合起来,为指定的函数提供方便且通用的缓存机制。
比如现在有一个函数search
,这个搜索非常耗时,所以我们想给它加上个缓存,我们利用装饰器cache
,为search
加上缓存机制。
@cache
def search(key):
pass
这样,search
函数就会在每次执行时查找缓存,如果缓存不命中,就执行一次搜索,将结果保存到缓存并返回。如果搜索命中,则直接返回缓存作为结果。
以下就是cache
装饰器的实现方法:
# file: ./h/cache/example.py
from functools import wraps
from string_implement import set, get
def make_unique_id(function, args, kwargs):
return function.__name__ + repr(args) + repr(kwargs)
def cache(function):
@wraps(function)
def _(*args, **kwargs):
id = make_unique_id(function, *args, **kwargs)
cache = get(id)
if cache:
return cache
else:
result = function(*args, **kwargs)
set(result)
return result
return _