从算法中的记忆化到实际开发中的缓存(python语言)

本文介绍了一种使用缓存来优化Web应用性能的方法,通过将重复的查询结果存储在内存中,显著减少了处理时间,从2的n次方降低至n平方。文章详细解释了如何在Python中实现这一机制,包括使用装饰器简化缓存逻辑和实现LRU缓存字典。

刷算法竞赛题目时候有一种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开发中接口非常多的,不可能在每个方法都加上同一个缓存查找逻辑,所以为了把代码逻辑抽离出来可以写成装饰器。
先写一个假的访问idarticle_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在缓存中过期了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值