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