简介
在web开发中,缓存是经常用来提高服务器的响应速度以及减少数据库压力的用力手段。在处理缓存时,有三个重要的步骤生成缓存键,存入缓存和获取缓存数据。对于不同的缓存软件(Redis,Memcached等)操作基本相同,只是在具体的存储获取环节存在差异,所以将常用的缓存处理过程封装成一个装饰器类(CacheDecorator下文给出实现代码)。通过装饰器类可以派生出基于不同缓存软件的具体缓存装饰器类如RedisCacheDecorator,MemcacheCacheDecorator等。
缓存装饰器类的基类
缓存装饰器基类的主要工作是生成缓存键和处理缓存调用,而具体的增删改查则由具体的子类去完成。
缓存装饰器基类(python2.7.6):
# coding=utf-8
import inspect
import re
from functools import wraps
class CacheDecorator(object):
"""
缓存装饰器类基类
"""
MC_DEFAULT_EXPIRE_IN = 0 # 不过期
# __formaters = {}
percent_pattern = re.compile(r'%\w')
brace_pattern = re.compile(r'\{[\w\d\.\[\]_]+\}')
# Copy from
# https://github.com/douban/douban-utils/blob/master/douban/utils/format.py#L17
@classmethod
def _formater(cls, text):
"""
>>> _format('%s %s', 3, 2, 7, a=7, id=8)
'3 2'
>>> _format('%(a)d %(id)s', 3, 2, 7, a=7, id=8)
'7 8'
>>> _format('{1} {id}', 3, 2, a=7, id=8)
'2 8'
>>> class Obj: id = 3
>>> _format('{obj.id} {0.id}', Obj(), obj=Obj())
'3 3'
>>> class Obj: id = 3
>>> _format('{obj.id.__class__} {obj.id.__class__.__class__} {0.id} {1}', \
>>> Obj(), 6, obj=Obj())
"<type 'int'> <type 'type'> 3 6"
"""
percent = cls.percent_pattern.findall(text)
brace = cls.brace_pattern.search(text)
if percent and brace:
raise Exception('mixed format is not allowed')
if percent:
n = len(percent)
return lambda *a, **kw: text % tuple(a[:n])
elif '%(' in text:
return lambda *a, **kw: text % kw
else:
return text.format
@classmethod
def _format(cls, text, *a, **kw):
# f = cls.__formaters.get(text)
# if f is None:
# f = cls.formater(text)
# cls.__formaters[text] = f
f = cls._formater(text)
return f(*a, **kw)
@classmethod
def _gen_key_factory(cls, key_pattern, arg_names, defaults):
args = dict(zip(arg_names[-len(defaults):], defaults)) if defaults else {}
if callable(key_pattern):
names = inspect.getargspec(key_pattern)[0]
def gen_key(*a, **kw):
aa = args.copy()
aa.update(zip(arg_names, a))
aa.update(kw)
if callable(key_pattern):
key = key_pattern(*[aa[n] for n in names])
else:
key = cls._format(key_pattern, *[aa[n] for n in arg_names], **aa)
return key and key.replace(' ', '_'), aa
return gen_key
def set(self, key, value, expire=0):
"""
用于添加或者更新缓存
:param key:
:param value:
:param expire: 过期时间
:return:
"""
raise NotImplementedError()
def get(self, key):
"""
获取缓存数据
:param key:
:return: 缓存中的数据
"""
raise NotImplementedError()
def delete(self, key):
"""
删除key对应的缓存
:param key:
:return:
"""
raise NotImplementedError()
@staticmethod
def new_instance():
"""
子类集中处理创建逻辑
:return: 装饰器类子类对象
"""
raise NotImplementedError()
def __call__(self, key_pattern, expire=MC_DEFAULT_EXPIRE_IN):
def deco(f):
arg_names, varargs, varkw, defaults = inspect.getargspec(f)
if varargs or varkw:
raise Exception("do not support varargs")
gen_key = self._gen_key_factory(key_pattern, arg_names, defaults)
@wraps(f)
def _(*a, **kw):
key, args = gen_key(*a, **kw)
if not key:
return f(*a, **kw)
force = kw.pop('force', False)
# 先从缓存中获取
r = self.get(key) if not force else None
# 调用原函数获取原数据
if r is None:
r = f(*a, **kw)
if r is not None:
self.set(key, r, expire)
return r
_.original_function = f
return _
return deco
缓存键的生成方式来源于豆瓣缓存键生成方式。其中get,set,delete方法分别使用来实现缓存操作的抽象方法,new_instance用来统一初始化缓存装饰器类对象的地方。
重点看下__call__
方法,该方法是一个魔法方法可以将类对象像方法一样调用,所以具体的缓存装饰器对象就可以像装饰器一样调用。参数介绍如下:
key_pattern
:__call__
首先会获取被装饰禅参数列表然后将参数传递个_format
处理生成真实的缓存键,_format
支持的模式如下。
>>> _format('%s %s', 3, 2, 7, a=7, id=8)
'3 2'
>>> _format('%(a)d %(id)s', 3, 2, 7, a=7, id=8)
'7 8'
>>> _format('{1} {id}', 3, 2, a=7, id=8)
'2 8'
>>> class Obj: id = 3
>>> _format('{obj.id} {0.id}', Obj(), obj=Obj())
'3 3'
>>> class Obj: id = 3
>>> _format('{obj.id.__class__} {obj.id.__class__.__class__} {0.id} {1}', \
>>> Obj(), 6, obj=Obj())
"<type 'int'> <type 'type'> 3 6"
expire
:缓存过期时间,默认值为MC_DEFAULT_EXPIRE_IN=0
不过期。__call__
方法会将该参数传递给set
方法。
Redis缓存装饰器类
首先我们基于Redis来实现一个缓存装饰器类,并介绍使用方法。
import redis
import time
class RedisCacheDecorator(CacheDecorator):
@staticmethod
def new_instance():
pool = redis.ConnectionPool(host='localhost', port=6379)
redis_conn = redis.Redis(connection_pool=pool)
return RedisCacheDecorator(redis_conn)
def __init__(self, redis_obj):
self.redis = redis_obj
def get(self, key):
value = self.redis.get(key)
try:
value = json.loads(value)
except:
pass
print "redis get:", key, value
return value
def set(self, key, value, expire=0):
if isinstance(value, (dict, set, list)):
result = json.dumps(value)
else:
result = str(value)
if expire:
self.redis.set(key, result, int(time.time() + expire))
else:
self.redis.set(key, result)
print "redis set:", key, result, expire
def delete(self, key):
if self.redis.exists(key):
self.redis.delete(key)
RedisCacheDecorator使用redis客户端来操作Redis服务端。为了方便观察存储过程通过打印函数值来观察函数是否成功调用。使用方式如下:
if __name__ == '__main__':
# 创建缓存装饰器类对象
redis_cache = RedisCacheDecorator.new_instance()
# 设置缓存键模式
KEY = "redis_cache:%s"
# {value}表示取被装饰函数中value的值
@redis_cache(KEY % "{value}")
def return_int(value):
print "call return_int"
return 1
# 执行
return_int(4)
执行结果如下:
第一次调用
redis get: redis_cache:4 None
call return_int
redis set: redis_cache:4 1 0
第二次调用
redis get: redis_cache:4 1
在终端查看:
通过继承CacheDecorator可以实现基于各种缓存软件的缓存装饰器,方便快捷。
更多栗子
DictCacheDecorator是一个非常简单基于python内置数据结构dict实现的缓存装饰器类,MemcacheCacheDecorator是Memcached装饰器类。
class DictCacheDecorator(CacheDecorator):
@staticmethod
def new_instance():
return DictCacheDecorator(dict())
def __init__(self, dict_obj):
self.dict_obj = dict_obj
def get(self, key):
value = self.dict_obj.get(key)
print "dict get:", key, value
return value
def set(self, key, value, expire=0):
self.dict_obj[key] = value
print "dict set:", key, value, expire
def delete(self, key):
if key in self.dict_obj:
del self.dict_obj[key]
class MemcacheCacheDecorator(CacheDecorator):
@staticmethod
def new_instance():
from libmc import (
Client, MC_HASH_MD5, MC_POLL_TIMEOUT, MC_CONNECT_TIMEOUT, MC_RETRY_TIMEOUT
)
mc = Client(
[
'localhost',
'localhost:11212',
'localhost:11213 mc_213'
],
do_split=True,
comp_threshold=0,
noreply=False,
prefix=None,
hash_fn=MC_HASH_MD5,
failover=False
)
mc.config(MC_POLL_TIMEOUT, 100) # 100 ms
mc.config(MC_CONNECT_TIMEOUT, 300) # 300 ms
mc.config(MC_RETRY_TIMEOUT, 5) # 5 s
return MemcacheCacheDecorator(mc)
def __init__(self, mc_obj):
self.mc_obj = mc_obj
def get(self, key):
value = self.mc_obj.get(key)
try:
value = json.loads(value)
except:
pass
print "mc get:", key, value
return value
def set(self, key, value, expire=0):
if isinstance(value, (dict, set, list)):
result = json.dumps(value)
else:
result = str(value)
if expire:
self.mc_obj.set(key, result, int(time.time() + expire))
else:
self.mc_obj.set(key, result)
print "mc set:", key, result, expire
def delete(self, key):
if self.mc_obj.exists(key):
self.mc_obj.delete(key)
参考书籍:《Python Web 开发实战》