像查询DB一样查询redis

设计目的:希望查询redis缓存像查询数据库一样,支持多条件组合查询、模糊查询、区间查询、多字段排序查询、分页查询。

其实,在redis中,就只有key-value这种存储结构,如何利用这种存储结构完成复杂的查询呢?让我们一起往下看

例如有以下表结构:
CREATE TABLE student (
id bigint(18) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
name varchar(30) NOT NULL COMMENT '姓名',
birth date DEFAULT NULL COMMENT '出生日期',
age int(2) DEFAULT 0 COMMENT '年龄',
clazz varchar(30) DEFAULT NULL COMMENT '班级',
create_tm datetime not null DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='学生表';

表数据:

INSERT INTO student (id, name, birth, age, clazz) VALUES ('1', '张三', '2000-07-07', '17', '高二1班');
INSERT INTO student (id, name, birth, age, clazz) VALUES ('2', '王丽', '2001-02-14', '16', '高二1班');
INSERT INTO student (id, name, birth, age, clazz) VALUES ('3', '张库', '2000-04-16', '17', '高二2班');
INSERT INTO student (id, name, birth, age, clazz) VALUES ('4', '李四', '2002-04-12', '15', '高一2班');
INSERT INTO student (id, name, birth, age, clazz) VALUES ('5', '何声', '2000-11-23', '17', '高二1班');

以下都是使用redis的基本命令实现,不分编程语言,如果对redis命令不是很熟的朋友可以阅读一下 Redis 命令参考http://doc.redisfans.com/

一、构建数据存储(用途:存储DB数据)
key值构建:data:[表名 (如果表名比较长建议使用缩写,保证唯一即可,目的是省内存)]:[主键] ,例:data:student:1
value值构建:json字符串,例:{"id":1,"name":"张三","birth":"2000-07-07","age":17,"clazz":"高二1班","createTm":1504856483000"}

有时候,为了方便我们查看缓存是那台机器什么时候写进redis的,可在value值中加入额外的字段,例如:{"id":1,"name":"张三","birth":"2000-07-07","age":17,"clazz":"高二1班","createTm":1504856483000","hostIp":"10.118.62.53","hostTm":"2017-03-22 10:51:22"}

以下为我们写入redis的数据(key -> value)
data:student:1 -> {"id":1,"name":"张三","birth":"2000-07-07","age":17,"clazz":"高二1班","createTm":1504856483000}
data:student:2 -> {"id":2,"name":"王丽","birth":"2001-02-14","age":16,"clazz":"高二1班","createTm":1504856486000}
data:student:3 -> {"id":3,"name":"张库","birth":"2000-04-16","age":17,"clazz":"高二2班","createTm":1504856484000}
data:student:4 -> {"id":4,"name":"李四","birth":"2002-04-12","age":15,"clazz":"高一2班","createTm":1504856480000}
data:student:5 -> {"id":5,"name":"何声","birth":"2000-11-23","age":17,"clazz":"高二1班","createTm":1504856483000}

到这步,我们可以在redis中实现了select * from student where id = ?的查询
查询命令:get data:student:1

二、构建索引存储(用途:筛选数据,存储DB数据的主键,所有的索引都是为data:开头的数据的查询服务的)
1.全表查询
构建全数据索引(如可不带任何条件查询,需构建所有数据的主键索引)
key值构建:idx:[表名],例:idx:student
value值构建:主键set集合,例:[1,2,3,4,5]

    以下为我们写入redis的数据(key -> value)
    idx:student -> [1,2,3,4,5]

到这步,我们可以在redis中实现了select * from student的查询
查询命令:sort idx:student get data:student:*

在redis分片集群中,如果data:student:x[1,2,3,4,5]与idx:student不完全在同一个集群,则不支持sort get 命令组合。查询方式可以修改为先查询idx:student的内容,遍历idx:student得到多个数据key data:student:x,再通过pipeline的方式批量获取多个key的值(pipeline可以批量执行redis命令,减少网络延迟时间)

扩展(分页查询):
1)select * from student limit 0,3;
查询命令:sort idx:student get data:student:* limit 0 3

2.条件查询(field = ?) 设计宗旨:必填条件尽量组合起来,这样我们才能快速定位redis的key值,从而取到对应的value值,例如查询时班级是必填字段,那么就不需要idx:student这个索引了
    key值构建:idx:[表名]:[字段名]:[字段值],例:idx:student:clazz:高二1班
    value值构建:主键set集合,例:[1,2,3]

    以下为我们写入redis的数据(key -> value)
    idx:student:clazz:高二1班 -> [1,2,5]
    idx:student:clazz:高二2班 -> [3]
    idx:student:clazz:高一2班 -> [4]

到这步,我们可以在redis中实现了select * from student where clazz = '高二1班'的查询
查询命令:sort idx:student:clazz:高二1班 get data:student:*

扩展:
1)select * from student where clazz in('高二1班','高二2班');
查询命令:
1.sunionstore temp:student:64d6bf1ff8194573a65b6f26e7dc1452 idx:student:clazz:高二1班 idx:student:clazz:高二2班 将它们的并集存储起来,例如 temp:student:64d6bf1ff8194573a65b6f26e7dc1452 [1,2,3,5]
2.sort temp:student:64d6bf1ff8194573a65b6f26e7dc1452 get data:student:*
3.del temp:student:64d6bf1ff8194573a65b6f26e7dc1452 临时key使用完后记得删除

2)select * from student where clazz = '高二1班' and age = 17;
查询命令:sinterstore temp:student:64d6bf1ff8194573a65b6f26e7dc1452 idx:student:clazz:高二1班 idx:student:age:17 将它们的交集存储起来,例如 temp:student:64d6bf1ff8194573a65b6f26e7dc1452 [1,5]

    查询条件支持模糊搜索(field like ? ) 注:以下所构建的索引key无法在redis删除数据的时候移除
        key值构建:idx:[表名]:[字段名],例:idx:student:clazz
        value值构建:字段值hash集合,hash[key:[字段值],value:[0]],例:{{key:"高二1班",value:0},{key:"高二2班",value:0}}

        以下为我们写入redis的数据(key -> value)
        idx:student:clazz -> [{key:"高二1班",value:0},{key:"高二2班",value:0},{key:"高一2班",value:0}]

到这步,我们可以在redis中实现了select * from student where clazz like '%高二%'的查询
查询命令:
1.hscan idx:student:clazz 0 MATCH 高二 取得值[高二1班,高二2班]
2.sunionstore temp:student:64d6bf1ff8194573a65b6f26e7dc1452 idx:student:clazz:高二1班 idx:student:clazz:高二2班 将它们的并集存储起来,例如 temp:student:64d6bf1ff8194573a65b6f26e7dc1452 [1,2,3,5]
3.sort temp:student:64d6bf1ff8194573a65b6f26e7dc1452 get data:student:*
4.del temp:student:64d6bf1ff8194573a65b6f26e7dc1452 临时key使用完后记得删除

3.构建区间条件索引(field >= ? and field < ?)
    key值构建:idx:[表名]:[字段名],例:idx:student:age
    value值构建:主键zset集合([value:[主键],score:[字段值]]),例:[{value:2,score:17},{value:1,score:17} ,{value:3,score:17}]

    以下为我们写入redis的数据(key -> value)
    idx:student:age -> [{value:4,score:15},{value:2,score:16},{value:1,score:17},{value:3,score:17},{value:5,score:17}]

到这步,我们可以在redis中实现了select * from student where age >= 15 and age < 17的查询
查询命令:
1.zrangebyscore idx:student:age 15 (17 取得值[4,2]
2.sadd temp:student:64d6bf1ff8194573a65b6f26e7dc1452 4 2 将值存储到临时key
3.sort temp:student:64d6bf1ff8194573a65b6f26e7dc1452 get data:student:*
4.del temp:student:64d6bf1ff8194573a65b6f26e7dc1452 临时key使用完后记得删除

数据查询准则:
1.or查询,in查询(field = ? or field = ?,filed in(?,?)),使用sunionstore求set集合的并集
2.and查询(field1 = ? and field2 = ?),使用sinterstore求set集合的交集
3.区间查询(field >= ? and field < ?),使用zrangebyscore获取区间内的主键

三、构建分值(用途:排序)
排序查询(order by field1 [desc],field2 [desc],fieldn [desc])注:多个字段排序的情况下,字段值需做定长处理,如果无法做到,则不适合使用该方法进行排序
key值构建:score:[表名]:[字段名1]:...:[字段名n]:[主键],例:score:student:createTm:1
value值构建:分值字符串,例:1504856483000

以下为我们写入redis的数据(key -> value)
score:student:createTm:1 -> 1504856483000
score:student:createTm:2 -> 1504856486000
score:student:createTm:3 -> 1504856484000
score:student:createTm:4 -> 1504856480000
score:student:createTm:5 -> 1504856483000

到这步,我们可以在redis中实现了select * from student order by create_tm [desc]的查询
查询命令:sort idx:student by score:student:createTm:* [desc] get data:student:*

扩展:
1)select * from student where clazz = '高二1班' order by create_tm desc;
查询命令:sort idx:student:clazz:高二1班 by score:student:createTm:* desc get data:student:*

2)select * from student order by age,create_tm;
以下为我们写入redis的数据(key -> value)
score:student:age:createTm:1 -> 171504856483000
score:student:age:createTm:2 -> 161504856486000
score:student:age:createTm:3 -> 171504856484000
score:student:age:createTm:4 -> 151504856480000
score:student:age:createTm:5 -> 171504856483000
查询命令:sort idx:student by score:student:age:createTm:* get data:student:*

在redis中,我们不仅要考虑如何将数据存储进去,还要考虑如何将数据删除。

四、构建索引记录(用途:通过主键找到该主键被记录到哪个索引集合里)
key值构建:record:[表名]:[主键],例:score:student:1
value值构建:索引set集合,例:[idx:student,idx:student:age,idx:student:clazz:高二1班]
当我们要把score:student:1这条数据删除的时候,需要把对应的索引的1移除
执行命令:
1.srem idx:student 1
2.zrem idx:student:age 1
3.srem idx:student:clazz:高二1班 1

这样,我们的redis中才不会有垃圾数据

五、key过期策略
1.使用zset把主键,过期参照字段存储起来,例如:expire:student:createTm [{value:4,score:1504856480000},{value:1,score:1504856483000},{value:5,score:1504856483000},{value:3,score:1504856484000},{value:2,score:1504856486000}]
假如保留30天的数据,使用定时任务定期把距离当前时间30前的数据从expire:student:createTm中取出来,然后构建对应的key进行删除。

2.使用redis 键空间通知(keyspace notification),不了解的朋友可阅读http://doc.redisfans.com/topic/notification.html
键空间通知主要是使用redis 发布与订阅(pub/sub),因为我们存储在redis中的所有数据都是为data:开头的数据的查询服务的,所以我们可以把
过期时间设在data:开头的key上,例如:当data:student:1过期后,我们可以通过键空间通知回调获得data:student:1已被redis删除,从而我们

可以把主键为1相关的数据清除掉。

最后献上redis存储key截图

image

转载

https://blog.csdn.net/w13528476101/article/details/70146064?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356