刷算法竞赛题目时候有一种DFS算法的题目,它因为会有时间限制所以我那会时加个记忆化数组,把前面算过的结果存下来,有时候因此时间复杂度从2的n次方下降到n平方。偶然在看《Django企业开发》一书的缓存章节,颇有意思,这不就是记忆化吗!
此文算是从0到1,讲解如何在web开发中实现缓存机制。
最简单的查询结果缓存
先写一个假的查询接口
def query(pk):
time.sleep(2) # 故意增加的延迟效果
result = pk * 100
return result
尝试调用这个接口两次,并且用同一个参数:
start = time.time()
result = query(21)
print(time.time() - start) # 2s
print("------------------------")
start = time.time()
result = query(21)
print(time.time() - start) # 2s
两次都是参数21都花去了2秒的时间。下面我们开始优化代码。
import time
CACHE = dict() # 缓存查询结果的字典
def query(pk):
result = CACHE.get(pk) # 如果要查询的结果在字典中,那么直接返回
if not result: # 否则查询参数并保存在字典中
time.sleep(2)
result = CACHE[pk] = pk * 100
return result
start = time.time()
result = query(21)
print(time.time() - start) # 2s
print("------------------------")
start = time.time()
result = query(21)
print(time.time() - start) # 0.01s
如果你运行了优化后的代码,你会发现第二次查询时间开销几乎是0,这就是缓存的功劳啦。那么这其实就是缓存的最基本逻辑。
用装饰器改造
一般的web开发中接口非常多的,不可能在每个方法都加上同一个缓存查找逻辑,所以为了把代码逻辑抽离出来可以写成装饰器。
先写一个假的访问id为article_id的接口代码:
def blog_show_one_article(request, article_id):
"""
查看article_id的文章内容
"""
blog = db.get(id=article_id) # 从数据库中拿出id为article_id的文章
# 故意增加的延迟效果
import time
time.sleep(5)
# 结果渲染到前端页面
return render(request=request, context={'blog': blog)
上面的代码只是演示一个逻辑,并不能运行。接下来把缓存逻辑写成装饰器。
_CACHE = dict() # 缓存字典
def cache_it(func):
def inner(*args, **kwargs):
result = _CACHE.get(kwargs['article_id']) # article_id:需要查看的文章id
if not result:
result = _CACHE[kwargs['article_id']] = func(*args, **kwargs)
return result
return inner
最后我们在blog_show_one_article方法头上写一个@cache_it即可。实际效果大致是:第一次访问www.myblog.com/blog/2开销5秒显示出页面,再刷新一次页面立即显示出页面。
缓存字典
这段代码参考胡阳的《django企业开发实战》一书
import functools
from collections import OrderedDict
import time
class LRUCacheDict:
"""
缓存字典
"""
def __init__(self, max_size=1024, expiration=60):
"""
:param max_size: 缓存的容量
:param expiration: 过期时间
"""
self.max_size = max_size
self.expiration = expiration
self._cache = {} # 缓存
self.__access_records = OrderedDict() # 访问记录时间
self.__expire_recodes = OrderedDict() # 过期记录时间
def __setitem__(self, key, value):
"""
存入新的数据
当新的数据存进来时候,过期记录时间需要更新,旧的数据要删除
:param key:
:param value:
"""
now = int(time.time())
self.__delete__(key)
self._cache[key] = value
self.__expire_recodes[key] = now + self.expiration
self.__access_records[key] = now
self.cleanup() # 清除旧的缓存
def __getitem__(self, key):
"""
获取缓存
:param key:
:return:
"""
now = int(time.time())
del self.__access_records[key]
self.__access_records[key] = now
self.cleanup() # 清除旧的缓存
return self._cache[key]
def __contains__(self, key):
self.cleanup()
return key in self._cache
def __delete__(self, key):
if key in self._cache:
del self._cache[key]
del self.__access_records[key]
del self.__expire_recodes[key]
def cleanup(self):
if self.expiration is None:
return None
pending_delete_keys = [] # 准备删除的key
now = int(time.time())
for k, v in self.__expire_recodes.items():
if v < now:
pending_delete_keys.append(k)
for del_k in pending_delete_keys:
self.__delete__(del_k)
# 这里其实就是LRU的思想
while len(self._cache) > self.max_size:
for k in self.__access_records:
self.__delete__(k)
break
cache_dict = LRUCacheDict(max_size=2, expiration=10)
cache_dict['name'] = "Sundy"
cache_dict['age'] = 12
cache_dict['addr'] = "www/bb/com"
assert ('name' in cache_dict) == False, "LRU 部分出错"
assert ('age' in cache_dict) == True, "age 不在字典中"
print('name' in cache_dict)
print('age' in cache_dict)
time.sleep(11)
print('age' in cache_dict) # 应当是false 因为age在缓存中过期了
本文介绍了一种使用缓存来优化Web应用性能的方法,通过将重复的查询结果存储在内存中,显著减少了处理时间,从2的n次方降低至n平方。文章详细解释了如何在Python中实现这一机制,包括使用装饰器简化缓存逻辑和实现LRU缓存字典。

1020

被折叠的 条评论
为什么被折叠?



