当前位置:   article > 正文

python使用redis做缓存_python实现类redis缓存

redis保存历史问题python

越来越觉得的缓存是计算机科学里最NB的发明(没有之一), 现在项目用的是redis做的缓存, 它的两个特性用的蛮顺手的:

键值查找功能

缓存可设置过期时间

突突然的,觉得用python也可以简单的模拟一下,做一个本地的轻量级缓存.(不过, 注意一点:redis的缓存可以用于分布式, python模拟的则不行, 但是如果把本地缓存的过期时间设的短一点,比如10s, 在大并发下还是有不错表现的)

对于键值查找功能, python原生的字典dict完美胜任. 对于缓存自动过期, 简单的想法后台起个服务, 定期检测, 但是这样代码会相对复杂,而且还要抢占宝贵的cpu资源,失去了轻量级的初衷.

那么思路是这样的:

只要将每个对应的缓存键值,加一个expire字段,代表过期时间点,

第一次获取, expire=当前时间点+过期时间

非第一次获取时, 通过expire判断是否已过期, 如果过期则可认为数据没找到,反之返回正常的缓存

简单的代码如下:

def get(self, key):

value = self._cache.get(key, self.notFound)

if(value is not self.notFound):

expire = value[r'expire']

if( self.nowTime() > expire):

return self.notFound

else:

return value

else:

return self.notFound

通过第1步, 基本功能已实现,现在收拾"烂摊子". 步骤1明显的确点就是内存泄漏了(搞python的应该不怎么听的到"内存泄漏"),原因很明显,通过字典模拟redis缓存, 所有数据都保存在缓存字典中(即使它过期了), 因为没人去删除它们.

解决的方法是,采用弱引用

python 文档有有句:

A primary use for weak references is to implement caches or mappings holding large objects, where it’s desired that a large object not be kept alive solely because it appears in a cache or mapping.

就是说,弱引用主要的用途也是为了实现缓存.

对于我们的应用,WeakValueDictionary这货就很符合需求,那么代码就如下这样了:

class LocalCache():

notFound = object()

class Dict(dict):

def __del__(self):

pass

def __init__(self, maxlen=2):

self.weak = weakref.WeakValueDictionary()

@staticmethod

def nowTime():

return int(time.time())

def get(self, key):

value = self.weak.get(key, self.notFound)

if(value is not self.notFound):

expire = value[r'expire']

if( self.nowTime() > expire):

return self.notFound

else:

return value

else:

return self.notFound

def set(self, key, value):

self.weak[key] = LocalCache.Dict(value)

几点说明下:

创建内部类Dict的原因

python文档的说法

Several built-in types such as list and dict do not directly support weak references but can add support through subclassing:

classDict(dict):

pass

obj=Dict(red=1,green=2,blue=3)

就是说内建的list和dict不能直接支持弱引用,但是继承他们的子类就支持弱引用了. so..

weakref.WeakValueDictionary说明

现在就是用WeakValueDictionary来代替之前的dict了,弱引用的优势就是:

WeakValueDictionary随时都可能被回收(听上去很不靠谱,不过下面有解决方法), 所以不用担心之前的内存泄漏问题.

可能有人担心,WeakValueDictionary只是value值是弱引用,也就说value可以被回收,但是key值回一直存在,导致泄漏,don't wrong, 有文档为证:

Entries in the dictionary will be discarded when no strong reference to the value exists any more.

即如果value值没有强引用了,那么对应的记录就会被丢弃(这句话有彩蛋). 所以也不用担心key导致的泄漏了.

当初写完这个后,感觉一切都比较的perfect. 但是还是too young too simple.

最后较成熟的方案

重新关注下第二步, 有彩蛋的那句话, 这句话再深入理解下就是,如果self.weak[key] = LocalCache.Dict(value)这样,LocalCache.Dict(value)没有其他强引用, 那么对不起,下一瞬间这个记录就没了(WTF).

所以之前的写法会导致--没有任何缓存作用(如果你耐着性子看到这,估计要骂娘了。。。),不过既然都写了这么多,方案还是有的(我在 http://stackoverflow.com 找到类似的方案,稍微改进了下),既然要强引用,那就给他强引用了,代码如下:

import weakref, collections

import time

class LocalCache():

notFound = object()

class Dict(dict):

def __del__(self):

pass

def __init__(self, maxlen=10):

self.weak = weakref.WeakValueDictionary()

self.strong = collections.deque(maxlen=maxlen)

@staticmethod

def nowTime():

return int(time.time())

def get(self, key):

value = self.weak.get(key, self.notFound)

if(value is not self.notFound):

expire = value[r'expire']

if( self.nowTime() > expire):

return self.notFound

else:

return value

else:

return self.notFound

def set(self, key, value):

self.weak[key] = strongRef = LocalCache.Dict(value)

self.strong.append(strongRef)

代码跟之前的差不多,就是多了self.strong这个队列来保存强引用, 并利用collections.deque的一个特性:

the deque is bounded to the specified maximum length. Once a bounded length deque is full, when new items are added, a corresponding number of items are discarded from the opposite end.

意思是如果给deque设置了大小(通过maxlen,不传或设为None则没有限制), 那么deque满的时候,新添加的对象会将之前的'老家伙挤出去'.

整个过程是这样的,刚开始往缓存加数据时, 添加的每一个值都有一个弱引用和强引用,不停的加,直到deque的队列满了(地主家也没余粮啊), 这时后面每加一个,都将导致deque中最早加入的强引用被deque废弃,而被废弃的强引用对应的值只有弱引用了,于是与之相关的WeakValueDictionary记录也被回收了(不知道有没人闪过虚拟内存中内存不够用时,数据在硬盘和内存捣鼓的画面)

到这基本已经写完。当然这个LocalCache类还有很多可完善的地方,这里只是讲解下它的形成过程。

附加一个应用这个LocalCache类的函数调用缓存装饰器

from functools import wraps

def funcCache(expire=0):

caches = LocalCache()

def _wrappend(func):

@wraps(func)

def __wrapped(*args , **kwargs):

#计算出缓存的key值

key = str(func) + str(args) + str(kwargs)

result = caches.get(key)

if(result is LocalCache.notFound):

result = func(*args, **kwargs)

caches.set(key, {r'result':result, r'expire':expire + caches.nowTime() })

result = caches.get(key)

return result

return __wrapped

return _wrappend

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/670176
推荐阅读
相关标签
  

闽ICP备14008679号