dal(Data Access Layer)的含义是数据访问层。
背景
在团队的开发中,我们主要是使用mongodb作为持久化db,redis作为缓存服务。两者组合交替使用,当访问缓存的数据不存在时,会访问db。然后把db的数据加载进缓存中。
在不断堆积的业务代码中,我发现了一个代码重复的问题。每次都是需要写那么一段判断缓存数据是否存在,不存在就从数据库加载的逻辑。基于这种情况,我封装了一套redis+mongodb的控制操作。由dal自己去判断缓存数据,并自动从数据库加载。持久层的代码量大大缩减。而且,操作行为都收敛到公开的接口,便于做统一的升级和各种统计。
结构
redis_proxy:用于代理redis的各种操作
redis_pool:python版redis的连接池
mongodb_pool:pymongodb的连接池
redis_list:支持配置多个redis
代码分析
nfind
@ctime(NAME)
"""
table:mongodb的表名
prefix:缓存前缀
query:查询条件,dict类型
cache:是否使用缓存
cache_time:缓存时间
sort:排序条件
criteria:查询的字段
limit:结果数量
cache_kw:关联的缓存key
pack:缓存value是否使用msgpack
"""
def nfind(self, table, prefix="", query={}, cache=True, cache_time=43200, sort=None, criteria=None, limit=None, cache_kw=None, pack=True):
result = None
try:
"""查询缓存"""
if cache:
result = self.redis_proxy.read_by_cache_type(CACHETYPE.string ,table, prefix, query, cache_time, sort=sort, limit=limit, criteria=criteria, pack=pack)
if result is not None:
return result
"""从mongodb获取查询的cursor"""
if criteria:
cursor = self.get_mongodb()[table].find(query,criteria)
else:
cursor = self.get_mongodb()[table].find(query)
"""如果有排序条件,进行排序操作"""
if sort:
if isinstance(sort, tuple):
cursor = cursor.sort(*sort)
else:
cursor = cursor.sort(sort)
"""限定结果集数量"""
if limit and limit > 0:
cursor = cursor.limit(limit)
"""把查询结果转化为list类型"""
result = list(cursor)
"""把主键_id转为str类型"""
for r in result:
if "_id" in r:
r["_id"] = str(r.get("_id"))
"""操作缓存"""
if cache:
if self.debug:
self.logger.debug("[Dal.nfind]write_by_cache_type table=%s, prefix=%s, query=%s" %(table, prefix, query))
self.redis_proxy.write_by_cache_type(CACHETYPE.string, table, prefix=prefix, value=result, query=query,criteria=criteria,cache_time=cache_time,sort=sort,limit=limit,cache_kw=cache_kw, pack=pack)
return result
except Exception, e:
self.logger.error("[Dal.nfind]error: %s, bt: %s" %(e, traceback.format_exc()))
return None
finally:
if self.debug:
self.logger.debug("[Dal.find]finally table=%s, prefix=%s, query=%s, cache=%s, cache_time=%s, criteria=%s" %(table, prefix, query, cache, cache_time, criteria))
RedisProxy.get
代理缓存的get操作
@ctime(REDIS_STAT_NAME)
def get(self, table, prefix="", query={}, cache_time=0, sort=None, limit=None, criteria=None, pack=True):
"""生成通用的key"""
key = self.generateKey(table, prefix, query, sort=sort, limit=limit, criteria=criteria, pack=pack)
status = None
result = None
try:
status = self.dal.get_redis().exists(key)
if status:
result = self.dal.get_redis().get(key)
if pack:
"""unpackb二进制数据"""
return msgpack.unpackb(result, use_list = True)
elif result:
return json.loads(result, "UTF-8")
return None
except Exception:
self.dal.logger.error("[RedisProxy.get]error, result=%s, %s" %(result, traceback.format_exc()))
return None
finally:
if self.dal.debug:
self.dal.logger.debug("[RedisProxy.get]key=%s, status=%s" %(key, status))
RedisProxy.generateKey
生成redis的key
通过key是以tablecache_table开头,table是mongodb的集合表名
prefix:缓存前缀
query(查询条件),sort(排序条件),criteria(查询字段)
limit(限定值)
pack(使用msgpack处理)
def generateKey(self, table, prefix="", query={}, sort=None, limit=None, name="tablecache", criteria=None, pack=True):
key = "%s_%s" %(name,table)
if prefix:
key = "%s_%s" %(key, prefix)
if query:
key = key + "_" + "_".join(sorted(["%s_%s" %(key,value) for key,value in query.iteritems()], key=lambda a:a))
if sort and isinstance(sort, tuple):
key = key + "_$sort_" + "_".join([ "%s" %val for val in sort])
if criteria:
key = key + "_$criteria_" + "_".join(sorted(["%s_%s" %(key,value) for key,value in criteria.iteritems()], key=lambda a:a))
if limit:
key = key + "_$limit_%s" %(limit)
if pack:
key = key +"_$pack_1"
else:
key = key +"_$pack_0"
return key