redis結(jié)構(gòu)
(以下僅僅是我的個(gè)人經(jīng)驗(yàn)兄猩,如果有更好的想法建議或者對(duì)內(nèi)容有疑問的,可以留言)铜秆。
附上 GITHUB地址
假定場(chǎng)景 老師讶迁,班級(jí)巍糯,學(xué)生祟峦,數(shù)學(xué)成績(jī),語文成績(jī)
老師:A老師针姿,B老師
班級(jí):1班距淫,2班
學(xué)生:a學(xué)生溉愁,b學(xué)生饲趋,c學(xué)生奕塑。
成績(jī):英語龄砰,語文,數(shù)學(xué)...
在mysql中式镐,理清楚這樣的關(guān)系是非常容易的娘汞,那么你弦,如果是在redis內(nèi),應(yīng)該怎么做呢尸昧?
首先烹俗,我們需要有幾個(gè)基本單元:
老師衷蜓,班級(jí)尘喝,學(xué)生朽褪。
他們分別在redis可以這樣子保存:
老師A => teacher:A
班級(jí)1 => class:1
學(xué)生a => student:a
那么如果實(shí)際情況里缔赠,老師A執(zhí)教于班級(jí)1嗤堰,班級(jí)2,老師B執(zhí)教于班級(jí)2告匠,學(xué)生a就讀于班級(jí)1后专,學(xué)生b,c就讀于班級(jí)2戚哎。那么參考mysql的關(guān)系表達(dá)式嫂用,在redis里可以同樣使用關(guān)聯(lián)表嘱函。
- 老師和班級(jí)的關(guān)系实夹,由上可得是多對(duì)多:
那么就需要兩個(gè)表(列表形式存儲(chǔ)):(如下可以很快的互相獲取數(shù)據(jù))
"teacher:A:class" = ["class:1","class:2"](通過老師推導(dǎo)班級(jí))
"class:1:teacher" = ["teacher:A"](通過班級(jí)推導(dǎo)老師)
- 學(xué)生和班級(jí)的關(guān)系:由上可知是一對(duì)多:
假定學(xué)生是一個(gè)字典(hash)存儲(chǔ)的對(duì)象
"student:a" = {
...
"class":"class:1",
},(直接通過鍵值獲取亮航,就可以得到對(duì)應(yīng)的班級(jí))
"class:1:teacher" = ["student:a",...](因?yàn)橐粋€(gè)班級(jí)有多個(gè)學(xué)生缴淋,所以只能通過第三張表來保存他們之間的關(guān)系)
- 學(xué)生和成績(jī)的關(guān)系:由上可知是一對(duì)多,可以這么制作模型:
同樣假定學(xué)生是一個(gè)字典(hash)存儲(chǔ)的對(duì)象
"student:a" = {
...
"chinese":100,
"english":100,
"math":50,
},(直接通過鍵值獲取露氮,就可以得到對(duì)應(yīng)的成績(jī))
反之若是也無需求需要把成績(jī)優(yōu)秀的學(xué)生做成熱門值畔规,那么就需要做如下存儲(chǔ):
performance:good = ["student:a",....]
由上可知叁扫,在redis內(nèi)莫绣,數(shù)據(jù)存儲(chǔ)都是由 title:id:subtitle:id 的模式進(jìn)行存儲(chǔ)的对室,基于這個(gè)原理咖祭,衍生出來三種類型:
-1.admin對(duì)象:在上面就有3個(gè)admin對(duì)象心肪,學(xué)生硬鞍,老師固该,班級(jí)伐坏。他們?nèi)齻€(gè)分別有自己的內(nèi)容屬性,例如name,sex,student_count之類的每瞒。
-2.relation對(duì)象:
如同上面的 "class:1:teacher","teacher:A:class"剿骨,就是典型的關(guān)系對(duì)象浓利,用來保存相關(guān)的admin對(duì)象贷掖。
-3.sub對(duì)象:
sub對(duì)象是隸屬于admin的對(duì)象苹威,sub對(duì)象共有兩種:
1. 一種就是上面提到的成績(jī)屠升,可以直接保存于amdin對(duì)象的鍵值中。
2. 那么問題來了汇在,如果你的sub對(duì)象是一個(gè)list(數(shù)組)或者dict(hash)怎么辦脏答?因?yàn)閞edis的hash值只能是字符串糕殉。那么就需要單獨(dú)創(chuàng)建一個(gè)sub對(duì)象來另外存儲(chǔ)。上面的relation對(duì)象其實(shí)也可以當(dāng)做是一種特殊的sub對(duì)象殖告,只是他表達(dá)的是關(guān)系對(duì)象的名稱阿蝶,而不是直接表達(dá)admin對(duì)象的一個(gè)屬性。
""
分析完了大體思路黄绩,接下來就直接上源碼:
MemObject
首先是所有sub對(duì)象的子類,這里封裝了基本用法, 用戶的sub對(duì)象模型可以繼承此類
class MemObject:
'''
內(nèi)存數(shù)據(jù)模型(hash)
'''
_tablename_ = "MemObject"
def __init__(self,pk):
'''
:param pk:主鍵爽丹,一般為數(shù)據(jù)庫(kù)內(nèi)的 id,或者設(shè)備名稱筑煮,編號(hào)等唯一值
'''
self._connection = sync_redis
self._fk= 0
self._admin = ""
self._pk = str(pk)
self._lock = 0
self.sync_count = 10
self._key = None
def __str__(self):
# return "{} 數(shù)據(jù)為:\n {}".format(self.key,dict(self))
return "{}:\n key:{}\n----------------------------".format(self.__class__.__name__,self.key)
def keys(self):
'''
返回一個(gè)tuple,該tuple決定了dict(self)所用到的鍵值
:return:
'''
def setPK(self, pk):
'''
設(shè)置ID
:param pk:
:return:
'''
self._pk = pk
def setFK(self,admin,fk):
'''
設(shè)置外鍵粤蝎,若是關(guān)聯(lián)數(shù)據(jù)真仲,需要設(shè)置管理對(duì)象的id
:param admin: 關(guān)聯(lián)的admin對(duì)象的key
:param fk :關(guān)聯(lián)admin對(duì)象的pk
:return:
'''
self._fk = str(fk)
self._admin = str(admin) + ':'
self.produceKey()
return self
def produceKey(self):
'''
生成redis保存的key
規(guī)則為:外鍵key + : + _tablename_ + : + 主鍵
'''
self._key = ''.join([self._admin, self._tablename_, ':', self._pk])
@property
def key(self):
'''
保證每次需要使用 _key 時(shí),同一個(gè)對(duì)象只會(huì)生成一次初澎,減少cpu消耗
:return:
'''
# Log.debug("使用_key")
if not self._key:
# Log.debug("載入/重載_key")
self.produceKey()
return self._key
def locked(self):
'''
檢測(cè)對(duì)象是否被鎖定
'''
# return self._connection.get(self.key + "_lock")
return False
def lock(self):
'''
鎖定對(duì)象,暫定最長(zhǎng)鎖定時(shí)間為2S(按業(yè)務(wù)需求自己添加鎖)
'''
# return self._connection.set(self.key + "_lock", 1, 2)
def release(self):
'''
釋放鎖(按業(yè)務(wù)需求自己釋放)
'''
# return self._connection.delete(self.key + "_lock")
def is_exist(self):
return self._connection.exists(self.key)
def get(self, key, default=None):
'''
本對(duì)象映射的哈希對(duì)象內(nèi)的某個(gè)key值
@:param key:需要獲取的鍵值
@:param default:若該鍵不存在秸应,則返回 default
@:return str
'''
ret = self._connection.hget(self.key, key)
if ret is not None:
return ret
else:
return default
def get_multi(self, *keys):
'''
一次獲取本對(duì)象映射的哈希對(duì)象內(nèi)的多個(gè)key的值
@param keys: list(str) key的列表
:return: dict
'''
ret = self._connection.hmget2dic(self.key, *keys)
return ret
def get_all(self):
'''
獲取本對(duì)象映射的哈希對(duì)象內(nèi)的所有值(keys里面定義的所有值,而非getall)
:return: self
'''
ret = self._connection.hmget(self.key, self.keys())
return self.get_from_list(ret)
def get_all2dic(self):
'''
獲取本對(duì)象映射的哈希對(duì)象內(nèi)的所有值(keys里面定義的所有值,而非getall)
:return: 字典
'''
ret = self._connection.hmget2dic(self.key, self.keys())
return ret
def get_one(self):
'''
針對(duì)單鍵值
:return:
'''
ret = self._connection.get(self.key)
return ret
def get_pattern(self, pattern):
'''
模糊查詢(keys)謹(jǐn)慎使用
:param pattern:
:return: 字典
'''
ret = self._connection.keys(pattern)
return ret
def dbsize(self):
'''
:return:
'''
ret = self._connection.dbsize()
return ret
def update(self,key,value):
'''
修改對(duì)象的值
'''
locked = self.locked()
# Log.debug("檢查字段是否被鎖定")
if not locked:
# Log.debug("字段檢查通過")
self.lock()
# Log.debug("拼接字段名稱:{}".format(name))
ret = self._connection.hset(self.key, key, value)
# Log.debug("設(shè)置字段值{}:{}".format(key,value))
self.release()
# Log.debug("解鎖字段")
return ret
else:
# Log.err("update:該字段被鎖定")
return 1
def update_multi(self,mapping):
'''
同時(shí)修改name下多個(gè)key的值
'''
locked = self.locked()
# Log.debug("檢查字段是否被鎖定")
if not locked:
# Log.debug("字段檢查通過")
self.lock()
# Log.debug("拼接字段名稱:{}".format(name))
ret = self._connection.hmset(self.key,mapping)
# Log.debug("設(shè)置字段值{}:{}".format(name,mapping))
self.release()
# Log.debug("解鎖字段")
return ret
else:
# Log.err("update_multi:該字段被鎖定")
# defer.returnValue(False)
return None
def update_one(self, value):
'''
修改本單個(gè)鍵值對(duì)象
'''
locked = self.locked()
# Log.debug("檢查字段是否被鎖定")
if not locked:
# Log.debug("字段檢查通過")
self.lock()
# Log.debug("拼接字段名稱:{}".format(name))
ret = self._connection.set(self.key, value)
# Log.debug("設(shè)置字段值{}:{}".format(key,value))
self.release()
# Log.debug("解鎖字段")
return ret
else:
# Log.err("update:該字段被鎖定")
return 1
def delete(self):
'''
刪除指定name的redis數(shù)據(jù)
'''
locked = self.locked()
# Log.debug("檢查字段是否被鎖定")
if not locked:
# Log.debug("字段檢查通過")
self.lock()
# Log.debug("拼接字段名稱:{}".format(self.key))
# print("拼接字段名稱:{}".format(self.key))
ret = self._connection.delete(self.key)
# Log.debug("設(shè)置字段值{}:{}".format(key,value))
self.release()
# Log.debug("解鎖字段")
return ret
else:
# Log.err("mdelete:該字段被鎖定")
return False
def incr(self, key, delta):
'''
自增
'''
locked = self.locked()
# Log.debug("檢查字段是否被鎖定")
if not locked:
# Log.debug("字段檢查通過")
self.lock()
# Log.debug("拼接字段名稱:{}".format(name))
ret = self._connection.hincrby(self.key, key, delta)
# Log.debug("設(shè)置字段值{}:{}".format(key,value))
self.release()
# Log.debug("解鎖字段")
return ret
else:
# Log.err("incr:該字段被鎖定")
return None
def expire(self,ex=0):
self._connection.expire(self.key,ex)
def insert(self):
'''
插入對(duì)象記錄
'''
# return self._insert().addCallback(self.syncDB).addErrback(DefferedErrorHandle)
locked = self.locked()
# Log.debug("檢查字段是否被鎖定")
if not locked:
# Log.debug("字段檢查通過")
self.lock()
nowdict = dict(self)
# Log.debug("拼接字段名稱:{}".format(name))
self._connection.hmset(self.key, nowdict)
# Log.debug("設(shè)置字段值{}:{}".format(key,value))
count = self._connection.hincrby(self.key, "_count", 1)
self.release()
self.syncDB(count)
return True
else:
return False
def insert_without_sync(self):
'''
插入本對(duì)象映射的哈希對(duì)象
'''
# return self._insert().addCallback(self.syncDB).addErrback(DefferedErrorHandle)
locked = self.locked()
# Log.debug("檢查字段是否被鎖定")
if not locked:
# Log.debug("字段檢查通過")
self.lock()
nowdict = dict(self)
# Log.debug("拼接字段名稱:{}".format(name))
self._connection.hmset(self.key, nowdict)
# Log.debug("設(shè)置字段值{}:{}".format(key,value))
self.release()
return True
else:
return False
def syncDB(self,count):
'''
:param count:
:return:
'''
def saveDB(self):
'''
同步數(shù)據(jù)庫(kù)
:return:
'''
def get_from_dict(self, dic):
'''
將字典內(nèi)的值依次賦予自身
:param dic:
:return: self
'''
for (k, v) in dic.items():
if v != None:
setattr(self, k, v)
return self
def get_from_list(self, list):
'''
將列表內(nèi)的值软啼,賦予自身(順序按照keys排列)
:param list:
:return:
'''
for index, key in enumerate(self.keys()):
if list[index] != None:
setattr(self, key, list[index])
return self
def __getitem__(self, item):
return getattr(self, item)
def name2object(self,name):
'''
調(diào)用該函數(shù)可以通過redis中的key值
轉(zhuǎn)換成對(duì)應(yīng)的mem對(duì)象
子類需要重寫才能實(shí)現(xiàn)該功能
:param name:
:return:
'''
name_ = name.split(self._tablename_+":")
self._pk = name_[1]
self._admin = name_[0]
self._key = name
return self
@classmethod
def scan(cls, admin=None,start=0, count=1000):
'''
通過搜索手段返回內(nèi)存內(nèi)所有該對(duì)象
:param start:
:param count:
:return:
'''
admins = []
rets = []
if admin == None:
pattern = cls._tablename_ + "*"
# pattern = cls._tablename_ +"[1,2,3,4,5,6,7,8,9,0]"
while True:
start, t_ = sync_redis.scan(start, pattern, count)
if t_:
admins += t_
if not start:
break
ret = list(set(admins))
for item in ret:
split_item = item.split(":")
if len(split_item) == 2:
name_, id_ = split_item
ret_ = cls(id_)
rets.append(ret_)
return rets
else:
pattern = admin._tablename_ +"*"+ cls._tablename_ + "*"
while True:
start, t_ = sync_redis.scan(start, pattern, count)
if t_:
admins += t_
if not start:
break
return list(set(admins))
MemAdmin
第二個(gè)登場(chǎng)的是admin對(duì)象的子類,他繼承自sub對(duì)象桑谍,擁有sub對(duì)象所有的屬性和方法外,還有admin獨(dú)有的一些屬性方法
class MemAdmin(MemObject):
'''
內(nèi)存數(shù)據(jù)模型(hash),一般用于admin模型
'''
_tablename_ = "MemAdmin"
def __init__(self, pk):
'''
root節(jié)點(diǎn)
:param pk :Mode主鍵焰宣,以此區(qū)分不同對(duì)象
'''
MemObject.__init__(self, pk)
def build_leaf(self, leaf, pk, dict):
'''
創(chuàng)建 子節(jié)點(diǎn) 對(duì)象霉囚,且讀取內(nèi)存數(shù)據(jù)
:param object: 外鍵連接對(duì)象(MemObject 及其子類)
:param pk: MemObject 及其子類 的 主鍵
:param dict: 字典數(shù)據(jù)
:return: MemObject 及其子類
'''
ret = leaf(pk).setFK(self.key, self._pk).get_from_dict(dict)
return ret
def build_empty_leaf(self, leaf, fk=""):
'''
創(chuàng)建 子節(jié)點(diǎn) 對(duì)象,但不讀取內(nèi)存數(shù)據(jù)
:param leaf:
:param fk:
:return:
'''
return leaf(fk).setFK(self.key, self._pk)
def build_empty_relation(self, relation,fk=""):
'''
創(chuàng)建 relation 對(duì)象匕积,但不讀取內(nèi)存數(shù)據(jù)
:return:
'''
return relation(fk).setFK(self.key, self._pk)
def update_leaf(self, leaf, fk, dict):
'''
插入/更新 子節(jié)點(diǎn)對(duì)象,且更新內(nèi)存數(shù)據(jù)
:param leaf: 外鍵連接對(duì)象
:param dict: 字典數(shù)據(jù)
:return:
'''
return leaf(fk).setFK(self.key, self._pk).get_from_dict(dict).insert()
def is_leaf_exits(self, leaf, fk):
'''
判斷子節(jié)點(diǎn)對(duì)象是否存在內(nèi)存中
:return:
'''
return leaf(fk).setFK(self.key, self._pk).is_exist()
def get_leaf(self,leaf,fk="",*keys):
'''
從內(nèi)存中獲取單個(gè)子節(jié)點(diǎn)對(duì)象
:param object: 外鍵連接對(duì)象
:return:
'''
if not keys:
ret = leaf(fk).setFK(self.key,self._pk).get_all()
else:
ret_ = leaf(fk).setFK(self.key,self._pk).get_multi(*keys)
ret = leaf.get_from_dict(ret_)
return ret
def get_leaf2dic(self,leaf,fk="",*keys):
'''
從內(nèi)存中獲取單個(gè)子節(jié)點(diǎn)數(shù)據(jù)(dict)
:param object: 外鍵連接對(duì)象
:return:
'''
if not keys:
return leaf(fk).setFK(self.key, self._pk).get_all2dic()
else:
return leaf(fk).setFK(self.key, self._pk).get_multi(*keys)
def get_leafs_by_relation(self,relation,fk="",start=0,count=1000):
'''
通過 relation 表 取出所有相關(guān)子節(jié)點(diǎn)key名稱
:param relation_name:
:return:
'''
relation = relation(fk).setFK(self.key, self._pk)
leafsname = relation.get_leafs_by_relation(start, count)
ret = relation.names2leafs(leafsname)
return ret
def get_leafnames_by_relation(self,relation,fk="",start=0,count=1000):
'''
通過 relation 表 取出所有相關(guān)子節(jié)點(diǎn)key名稱
:param relation_name:
:return:
'''
relation = relation(fk).setFK(self.key, self._pk)
leafsname = relation.get_leafs_by_relation(start, count)
return leafsname
def add_leafs_on_relation(self,relation,leafs,fk=""):
'''
往 relation 表下 添加子節(jié)點(diǎn)元素
:param relation:
:param fk:
:param leafs:
:return:
'''
relation = relation(fk).setFK(self.key, self._pk)
ret = relation.add_leafs(leafs)
return ret
def scan_leafnames(self,leaf,start=0,count=1000):
'''
搜索所有和該對(duì)象相關(guān)的 leaf 對(duì)象榜跌,并返回key值
:param start:
:param pattern:
:param count:
:return:
'''
keys = []
leaf = leaf("").setFK(self.key,self._pk)
pattern = leaf.key+":"+"*"
while True:
start,t_ = sync_redis.scan(start,pattern,count)
if t_:
keys += t_
if not start:
break
return list(set(keys))
def scan_leafs(self, leaf, start=0, count=1000):
'''
搜索所有和該對(duì)象相關(guān)的 leaf 對(duì)象闪唆,并返回對(duì)象值(并未從內(nèi)存中獲取填充數(shù)據(jù))
:param start:
:param pattern:
:param count:
:return:
'''
leafs = []
keys = self.scan_leafnames(leaf, start, count)
for key in keys:
l_ = leaf().name2object(key)
if l_:
leafs.append(l_)
return leafs
@classmethod
def scan(cls, start=0, count=1000):
'''
通過搜索手段返回內(nèi)存內(nèi)所有該對(duì)象
:param start:
:param count:
:return:
'''
admins = []
rets = []
pattern = cls._tablename_ + "*"
# pattern = cls._tablename_ +"[1,2,3,4,5,6,7,8,9,0]"
while True:
start, t_ = sync_redis.scan(start, pattern, count)
if t_:
admins += t_
if not start:
break
ret = list(set(admins))
for item in ret:
split_item = item.split(":")
if len(split_item) == 2:
name_,id_ = split_item
ret_ = cls(id_)
rets.append(ret_)
return rets
MemRelation
class MemRelation(MemObject):
'''
內(nèi)存模型間的中間關(guān)系表
1 * ADMIN 《-》N * OBJECT
'''
_tablename_ = "MemRelation"
def __init__(self, pk=''):
'''
:param pk :Mode主鍵,以此區(qū)分不同對(duì)象
'''
MemObject.__init__(self, pk)
def produceKey(self):
'''
生成redis保存的key
規(guī)則為:外鍵key + : + _tablename_ + : + 主鍵
'''
self._key = ''.join([self._admin,self._tablename_])
def count(self):
'''
獲取與左外建相關(guān)的所有
:return:
'''
ret = self._connection.llen(self.key)
return ret
def append(self,objects):
'''
往關(guān)系表內(nèi)添加數(shù)據(jù)
:param value:
:return:
'''
if isinstance(objects,list):
if objects and isinstance(objects[0],str):
values = objects
elif objects:
values = [item.key for item in objects]
else:
return False
else:
values = objects.key
locked = self.locked()
# Log.debug("檢查字段是否被鎖定")
if not locked:
# Log.debug("字段檢查通過")
self.lock()
self._connection.lpush(self.key, values)
self.release()
return True
else:
return False
def remove_one(self,leaf,count=0):
'''
移除列表中下標(biāo)從start開始的
第一個(gè)值為value的數(shù)據(jù)
:param count:
count > 0 : 從表頭開始向表尾搜索钓葫,移除與 VALUE 相等的元素悄蕾,數(shù)量為 COUNT 。
count < 0 : 從表尾開始向表頭搜索础浮,移除與 VALUE 相等的元素帆调,數(shù)量為 COUNT 的絕對(duì)值。
count = 0 : 移除表中所有與 VALUE 相等的值豆同。
:param value:
:return:
'''
if isinstance(leaf, str):
value = leaf
else:
value = leaf.key
locked = self.locked()
# Log.debug("檢查字段是否被鎖定")
if not locked:
# Log.debug("字段檢查通過")
self.lock()
# try:
self._connection.lrem(key=self.key,value=value,count = count)
# except Exception as e:
# print(e)
self.release()
return True
else:
return False
def get_by_index(self,index):
ret = self._connection.lindex(self.key,index)
return ret
def add_leafs_on_relation(self, leafs):
'''
同 append 方法番刊,往 relation 表內(nèi)插入 對(duì)象列表
:param leafs:
:return:
'''
if isinstance(leafs, list):
values = [item.key for item in leafs]
else:
values = leafs.key
locked = self.locked()
# Log.debug("檢查字段是否被鎖定")
if not locked:
# Log.debug("字段檢查通過")
self.lock()
self._connection.lpush(self.key, values)
self.release()
return True
else:
return False
def get_leafs_by_relation(self, start, end):
'''
:param start:
:param end:
:return:
'''
ret = self._connection.lrange(self.key, start, end)
return ret
def get_all(self):
'''
:return:
'''
return self.get_leafs_by_relation(start=0,end=1000)
def get_all2dic(self):
'''
:return:
'''
return self.get_leafs_by_relation(start=0,end=1000)
def names2leafs(self, leafnames):
'''
:return:
'''
sensors = []
for leafname in leafnames:
sensor_ = self.name2object(leafname)
if sensor_:
sensors += sensor_.get_data()
return sensors
def name2object(self, name):
'''
調(diào)用該函數(shù)可以通過內(nèi)存中的key值
轉(zhuǎn)換成對(duì)應(yīng)的mem對(duì)象
若是relation對(duì)象,則需要重寫該方法
因?yàn)樵摲椒ㄖ荒芊祷?Object 對(duì)象
:return:
'''
實(shí)例demo:
有空補(bǔ)上