redis结构
(以下仅仅是我的个人经验,如果有更好的想法建议或者对内容有疑问的,可以留言)。
附上 GITHUB地址
假定场景 老师,班级,学生,数学成绩,语文成绩
老师:A老师,B老师
班级:1班,2班
学生:a学生,b学生,c学生。
成绩:英语,语文,数学...
在mysql中,理清楚这样的关系是非常容易的,那么,如果是在redis内,应该怎么做呢?
首先,我们需要有几个基本单元:
老师,班级,学生。
他们分别在redis可以这样子保存:
老师A => teacher:A
班级1 => class:1
学生a => student:a
那么如果实际情况里,老师A执教于班级1,班级2,老师B执教于班级2,学生a就读于班级1,学生b,c就读于班级2。那么参考mysql的关系表达式,在redis里可以同样使用关联表。
- 老师和班级的关系,由上可得是多对多:
那么就需要两个表(列表形式存储):(如下可以很快的互相获取数据)
"teacher:A:class" = ["class:1","class:2"](通过老师推导班级)
"class:1:teacher" = ["teacher:A"](通过班级推导老师)
- 学生和班级的关系:由上可知是一对多:
假定学生是一个字典(hash)存储的对象
"student:a" = {
...
"class":"class:1",
},(直接通过键值获取,就可以得到对应的班级)
"class:1:teacher" = ["student:a",...](因为一个班级有多个学生,所以只能通过第三张表来保存他们之间的关系)
- 学生和成绩的关系:由上可知是一对多,可以这么制作模型:
同样假定学生是一个字典(hash)存储的对象
"student:a" = {
...
"chinese":100,
"english":100,
"math":50,
},(直接通过键值获取,就可以得到对应的成绩)
反之若是也无需求需要把成绩优秀的学生做成热门值,那么就需要做如下存储:
performance:good = ["student:a",....]
由上可知,在redis内,数据存储都是由 title:id:subtitle:id 的模式进行存储的,基于这个原理,衍生出来三种类型:
-1.admin对象:在上面就有3个admin对象,学生,老师,班级。他们三个分别有自己的内容属性,例如name,sex,student_count之类的。
-2.relation对象:
如同上面的 "class:1:teacher","teacher:A:class",就是典型的关系对象,用来保存相关的admin对象。
-3.sub对象:
sub对象是隶属于admin的对象,sub对象共有两种:
1. 一种就是上面提到的成绩,可以直接保存于amdin对象的键值中。
2. 那么问题来了,如果你的sub对象是一个list(数组)或者dict(hash)怎么办?因为redis的hash值只能是字符串。那么就需要单独创建一个sub对象来另外存储。上面的relation对象其实也可以当做是一种特殊的sub对象,只是他表达的是关系对象的名称,而不是直接表达admin对象的一个属性。
""
分析完了大体思路,接下来就直接上源码:
MemObject
首先是所有sub对象的子类,这里封装了基本用法, 用户的sub对象模型可以继承此类
class MemObject:
'''
内存数据模型(hash)
'''
_tablename_ = "MemObject"
def __init__(self,pk):
'''
:param pk:主键,一般为数据库内的 id,或者设备名称,编号等唯一值
'''
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 "{} 数据为:\n {}".format(self.key,dict(self))
return "{}:\n key:{}\n----------------------------".format(self.__class__.__name__,self.key)
def keys(self):
'''
返回一个tuple,该tuple决定了dict(self)所用到的键值
:return:
'''
def setPK(self, pk):
'''
设置ID
:param pk:
:return:
'''
self._pk = pk
def setFK(self,admin,fk):
'''
设置外键,若是关联数据,需要设置管理对象的id
:param admin: 关联的admin对象的key
:param fk :关联admin对象的pk
:return:
'''
self._fk = str(fk)
self._admin = str(admin) + ':'
self.produceKey()
return self
def produceKey(self):
'''
生成redis保存的key
规则为:外键key + : + _tablename_ + : + 主键
'''
self._key = ''.join([self._admin, self._tablename_, ':', self._pk])
@property
def key(self):
'''
保证每次需要使用 _key 时,同一个对象只会生成一次,减少cpu消耗
:return:
'''
# Log.debug("使用_key")
if not self._key:
# Log.debug("载入/重载_key")
self.produceKey()
return self._key
def locked(self):
'''
检测对象是否被锁定
'''
# return self._connection.get(self.key + "_lock")
return False
def lock(self):
'''
锁定对象,暂定最长锁定时间为2S(按业务需求自己添加锁)
'''
# return self._connection.set(self.key + "_lock", 1, 2)
def release(self):
'''
释放锁(按业务需求自己释放)
'''
# return self._connection.delete(self.key + "_lock")
def is_exist(self):
return self._connection.exists(self.key)
def get(self, key, default=None):
'''
本对象映射的哈希对象内的某个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):
'''
一次获取本对象映射的哈希对象内的多个key的值
@param keys: list(str) key的列表
:return: dict
'''
ret = self._connection.hmget2dic(self.key, *keys)
return ret
def get_all(self):
'''
获取本对象映射的哈希对象内的所有值(keys里面定义的所有值,而非getall)
:return: self
'''
ret = self._connection.hmget(self.key, self.keys())
return self.get_from_list(ret)
def get_all2dic(self):
'''
获取本对象映射的哈希对象内的所有值(keys里面定义的所有值,而非getall)
:return: 字典
'''
ret = self._connection.hmget2dic(self.key, self.keys())
return ret
def get_one(self):
'''
针对单键值
:return:
'''
ret = self._connection.get(self.key)
return ret
def get_pattern(self, pattern):
'''
模糊查询(keys)谨慎使用
: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):
'''
修改对象的值
'''
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("设置字段值{}:{}".format(key,value))
self.release()
# Log.debug("解锁字段")
return ret
else:
# Log.err("update:该字段被锁定")
return 1
def update_multi(self,mapping):
'''
同时修改name下多个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("设置字段值{}:{}".format(name,mapping))
self.release()
# Log.debug("解锁字段")
return ret
else:
# Log.err("update_multi:该字段被锁定")
# defer.returnValue(False)
return None
def update_one(self, value):
'''
修改本单个键值对象
'''
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("设置字段值{}:{}".format(key,value))
self.release()
# Log.debug("解锁字段")
return ret
else:
# Log.err("update:该字段被锁定")
return 1
def delete(self):
'''
删除指定name的redis数据
'''
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("设置字段值{}:{}".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("设置字段值{}:{}".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):
'''
插入对象记录
'''
# 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("设置字段值{}:{}".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):
'''
插入本对象映射的哈希对象
'''
# 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("设置字段值{}:{}".format(key,value))
self.release()
return True
else:
return False
def syncDB(self,count):
'''
:param count:
:return:
'''
def saveDB(self):
'''
同步数据库
:return:
'''
def get_from_dict(self, dic):
'''
将字典内的值依次赋予自身
: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):
'''
将列表内的值,赋予自身(顺序按照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):
'''
调用该函数可以通过redis中的key值
转换成对应的mem对象
子类需要重写才能实现该功能
: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):
'''
通过搜索手段返回内存内所有该对象
: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
第二个登场的是admin对象的子类,他继承自sub对象,拥有sub对象所有的属性和方法外,还有admin独有的一些属性方法
class MemAdmin(MemObject):
'''
内存数据模型(hash),一般用于admin模型
'''
_tablename_ = "MemAdmin"
def __init__(self, pk):
'''
root节点
:param pk :Mode主键,以此区分不同对象
'''
MemObject.__init__(self, pk)
def build_leaf(self, leaf, pk, dict):
'''
创建 子节点 对象,且读取内存数据
:param object: 外键连接对象(MemObject 及其子类)
:param pk: MemObject 及其子类 的 主键
:param dict: 字典数据
:return: MemObject 及其子类
'''
ret = leaf(pk).setFK(self.key, self._pk).get_from_dict(dict)
return ret
def build_empty_leaf(self, leaf, fk=""):
'''
创建 子节点 对象,但不读取内存数据
:param leaf:
:param fk:
:return:
'''
return leaf(fk).setFK(self.key, self._pk)
def build_empty_relation(self, relation,fk=""):
'''
创建 relation 对象,但不读取内存数据
:return:
'''
return relation(fk).setFK(self.key, self._pk)
def update_leaf(self, leaf, fk, dict):
'''
插入/更新 子节点对象,且更新内存数据
:param leaf: 外键连接对象
:param dict: 字典数据
:return:
'''
return leaf(fk).setFK(self.key, self._pk).get_from_dict(dict).insert()
def is_leaf_exits(self, leaf, fk):
'''
判断子节点对象是否存在内存中
:return:
'''
return leaf(fk).setFK(self.key, self._pk).is_exist()
def get_leaf(self,leaf,fk="",*keys):
'''
从内存中获取单个子节点对象
:param object: 外键连接对象
: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):
'''
从内存中获取单个子节点数据(dict)
:param object: 外键连接对象
: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 表 取出所有相关子节点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 表 取出所有相关子节点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 表下 添加子节点元素
: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):
'''
搜索所有和该对象相关的 leaf 对象,并返回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):
'''
搜索所有和该对象相关的 leaf 对象,并返回对象值(并未从内存中获取填充数据)
: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):
'''
通过搜索手段返回内存内所有该对象
: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):
'''
内存模型间的中间关系表
1 * ADMIN 《-》N * OBJECT
'''
_tablename_ = "MemRelation"
def __init__(self, pk=''):
'''
:param pk :Mode主键,以此区分不同对象
'''
MemObject.__init__(self, pk)
def produceKey(self):
'''
生成redis保存的key
规则为:外键key + : + _tablename_ + : + 主键
'''
self._key = ''.join([self._admin,self._tablename_])
def count(self):
'''
获取与左外建相关的所有
:return:
'''
ret = self._connection.llen(self.key)
return ret
def append(self,objects):
'''
往关系表内添加数据
: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):
'''
移除列表中下标从start开始的
第一个值为value的数据
:param count:
count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
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 表内插入 对象列表
: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):
'''
调用该函数可以通过内存中的key值
转换成对应的mem对象
若是relation对象,则需要重写该方法
因为该方法只能返回 Object 对象
:return:
'''
实例demo:
有空补上