设计目的:希望查询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截图
如想了解Redis 主从/哨兵配置,可阅读我的文章:http://blog.csdn.net/w13528476101/article/details/70143766