使用redis实现关系型数据库表设计

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhangsheng_1992/article/details/53858271

前言

最近有一个需求,设计一款文件系统,而该文件系统会对不同文件进行不同的转码操作,如rmvb转码成mp4 mp3 与m3u8格式,ppt文件转码为swf pdf与h5。

经过调研以后发现,如果以关系型数据库来实现,单表会出现很多冗余字段,如上述两种文件,需要设计6个字段来存储相关转码信息,但对特定的一种文件来说,只利用了三个字段。

而如果使用分表方式可以避免冗余,单表结果变复杂,后去扩容修改都不容易,因此决定选择一款nosql作为db,至于为什么最后选择redis,有多方面原因,在此就不多说了

在每一个栗子中,我都会分别使用redis自带工具的命令与php脚本完成(redis自带工具在为安装目录下的redis-cli,命令不区分大小写)


自增主键设计

我们都知道关系型数据库有主键这个概念,常用的都是实数int类型的,为了避免重复,这个主键往往还是自增的,那么,而redis是使用key=>value存储的格式,如果使用redis来设计,那么,这个自增主键如何来设计呢?


redis提供一个整形自增命令 incr ,每次执行将对应的key增加1 ,并返回对应key增加后的值   格式 incr key


php代码如下:

$redis = new \Redis();
$connect = $redis->connect("127.0.0.1",6379);
$id=$redis->incr('key');

每次运行都将得到一个唯一的id,如此我们就完成了自增主键的设计。



字段设计

关系型数据库中,字段是预先定一好的,也就是说你不能给一个没有预先定义的字段中插入数据,而在redis中则没有这个限制,即用即存就是他的优点

比如 对所有文件来说,filename(文件名称),filepath(存储路径),filetype(文件类型)等字段是共有的,而对ppt文件来说额外的字段为mp4 mp3 与m3u8格式

对视频文件来说,额外的字段为swf pdf与h5.



查询索引设计

对于关系型数据库来说,每个字段,都可以用来作为查询条件,而对于redis来说,是不行的。为什么呢?上面说过,redis的字段是即用即存的,也就是说,两条数据,

它们的字段数可能是不一样的。如此特性决定了,redis的查询字段都需要预先定义,而且需要选择那些所有数据均有的字断做为条件,在这个demo中,我们设计4个查询

字断,分别为 主键id   文件所属公司(org_id)  文件所属人(user_id) 文件类型(type) ,当然你也可以设计更多,这里只是demo,所以只设计4个,方便理解。


主键id我们已经得到了,那么其他几个查询条件的该如何来设计呢 ?举个栗子,

比如我现在有A,B,C三个公司 (假设三个公司的id为  100,200,300) 

张三,李四,王五三个用户(user_id分别111,222,333)

视频,资料其他三种类型文件(type分别为1,2,3)


在这里需要向大家讲解下redis的一种数据类型----集合,准确点来说是无序集合,集合成员是唯一的,这就意味着集合中不能出现重复的数据,

Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为 232 - 1 我们使用多个集合来保存符合每种条件的纪录主键信息


首先假设 A公司的张三新建了一个视频文件     根据自增运算后 这条记录的id=1

由于是第一次插入,并不存在查询条件的集合

我们定义如下 公司集合的key为      file_org_公司id  ,那么对应A公司key就为 file_org_100 ,然后我们向这个key对应的集合中插入这条纪录的id

集合的插入命令为 sadd key  value


php代码

$redis->Sadd('file_org_100',1);//1为这条纪录的主键id


对于用户集合而言  我们定义key为  file_user_用户id  那么对张三而言  key为  file_user_111

对于类型集合而言  我们定义key为  fiel_type_类型id  那么对于视频文件来说  key为 file_type_1

我们分别将三个集合写入完毕,后面将详细说明,如何使用这三个集合来做为查询条件


数据写入

到目前为止,我们已经生成了自增主键,查询索引,该轮到数据写入了,在这里需要再介绍redis的一种数据结构 哈希(hash)

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。

Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)


哈希是字段与值的的映射关系,所以,字段是不能重复的,而我们刚刚得到了要插入纪录的id与信息,那么一个字断与值的映射关系也就得到了

为id   映射   信息                  由于id是唯一的,所以不会存在字断重复的问题 集合上面张三要写入的这条信息

我们已经知道这条信息的id为1     而写入信息也已得知  假设为(filename,type,org_id,user_id,add_time,mp4_url,m3u8_url)等

那么我们就可以向这个集合中写入数据了   使用命令 hset key 字段名  字断值      这个hash的名字我们就叫做file吧(理解成关系型数据库中的表名)


php代码

$redis->hSet('file',1,json_encode(['id'=>$id,'filename'=>$filename,...]));//为了方便后面的使用,可以将id也存进去

到此,我们一条完成的数据写入就成功了 使用 hget key 字段名 可以查看刚刚插入的那条纪录    详细命令为hget file 1

file为表明 而1是$id=1的字段


事务操作

我们知道,关系型数据库中提供一种数据看支持,叫做事务

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。


对上述而言  我们一共有5个操作     1.获取自增主键  2-4.插入查询条件集合  5.写入数据哈希

如何保证我们的这五步操作都执行成功,而不会出现数据写入但查询条件没有的情况?


redis的设计者早已想到了这点,所以,redis也是支持事务的

命令  multi   开启事务    exec  执行事务   discard  放弃事务

与关系型数据不同的是,redis放弃事务并不等同与回滚,我们需要自己收拾烂摊子,也就是说,假设1-4步都执行成功  第五步失败,此时执行discard命令 1-4步的修改已经生

效,所以我们需要手动删除2-4中插入的数据  而自增的这个主键,将永远不会被再次使用了,但是,如果我们能合理规范键名,数据格式,完全可以避免这类问题,这也是redis

事务快速的原因。

php代码

$redis->multi();//开启

$id=$redis->incr('key');//获取自增id

$redis->Sadd('file_org_100',1);//插入查询条件

$redis->exec();//执行命令$redis->discard();放弃命令

注意 php调用exec得到的结果是一个数组     数据元素为每步操作的返回值(0和1) 而某些操作正确的返回值为0 比如修改替换 所以判断执行是否成功需要注意执行顺序


条件查询


主键查询我上边已经告诉过大家了 hget file 1 下面我们来说说说如何来进行条件查询

再这之前,你可以先联系联系插入与事务,尽量多的给数据库中写入数据(ps:就一条数据怎么条件查询啊?)

我们已经定义好了三个集合  高中的数学课程中  我们因该有个概念叫做交集 并集 差集    而redis就能实现关系型数据库实现不了的  求交集 并集 差集

假设我们现在依旧插入了10条数据了  id分别为1-10   其中属于A机构5条 B机构的3条 C机构的2条(至于具体id是那个机构的我就随便写了)



那么我们现在会有三个公司集合   key分别为  file_org_100,file_org_200,file_org_300,集合中元素分别为(1,3,5,6,7),(2,4,8), (9,10)

其中是张三写入的有4条  李四3条 王五三条  同样的key分别为  file_user_111,file_user_222,file_user_333.集合中元素为(1,2,3,4) ,(5,6,7), (8,9,10)

其中有5个视频  2个文件  3个其他   key分别为  file_type_1,file_type_2,file_type_3 元素为 (1,2,3,4,5),(6,7),(8,9,10)



那么  假设现在的查询条件要查询 A公司的纪录

1.首先  我们跟根据A公司的集合  查询到A公司的文件id为 1,3,5,6,7  

2.然后我们去hash里面拿数据   上面介绍的是取单条数据的方法,我在这里介绍下一次取多个的方法

命令  hmget  key   id1 id2 id3 idn    实际就是  hmget file 1 3 5 6 7

对应php代码

$redis->hmget('file',[1,3,5,6,7]);


返回值为一个数组  然后处理数组元素即可


2.那么 现在我们要查询A公司的视频文件有哪些 步骤如下

1.首先  我们跟根据A公司的集合  查询到A公司的文件id为 1,3,5,6,7

2.查询视频文件集合 为 1,2,3,4,5

3.将两个集合求交集  也就是满足即是A公司的还是视频文件

命令 sinter  第一个集合的key  第二个集合的key  第n个集合的key      返回结果为交集的值

 

对应的php代码

$redis->sinter(key1,key2....);


这里我推荐先获取各个集合,再使用php的array_intersect来获取,这样我们就可以再求完交集后控制每次返回的数据数量(分页)

 array_intersect(array1,array2......)



修改与删除


修改与删除相应简单一些  

修改就是再次执行  hset  key  id值  用新数据将老数据覆盖

删除就是将使用   hdel ket id值  将hash中的信息删除掉  然后再将鸡哥查询集合中对应的数据拿出来 剔除掉要删除条的id值后重新写入,

当然  也可以srem key  元素值  直接删除元素  还可以考虑有序集合,这里不多说了



持久化存储配置与备份


关于redis的持久化存储网上说过很多   两种方式分别是rdb与aof

我这里使用的是aof  理解成mysql的binlog    修改redis.conf如下为止

appendonly yes  打开aof持久化

appendfsync everysec   每秒刷新到磁盘上

no-appendfsync-on-rewrite yes   在日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加 造成disk io上的冲突

auto-aof-rewrite-percentage 100   当前aof文件大小是上次日志重写得到的aof文件大小的两倍时,自动启动新的日志重写 数值表示百分比

auto-aof-rewrite-min-size 64mb 当前aof文件启动新的日志重写过程的最小值,避免刚刚启动redis时由于文件尺寸较小 导致频繁的重写


aof是类似日志的格式  你的每一个操作都会纪录下来  恢复时会按照顺序再执行一边 所以较rdb方式安全    但用脑袋想也知道log文件会越来越大的

好在redis提供了一种压缩日志的方式  命令  bgrewriteaof   上面最后连个配置就是配置这个命令的触发条件的  当然你也可以手动执行



示例代码


php代码 地址 https://code.csdn.net/zhangsheng_1992/redisdemo/tree/master


展开阅读全文

没有更多推荐了,返回首页