像查詢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截圖



如想了解Redis 主從/哨兵配置,可閱讀我的文章:http://blog.csdn.net/w13528476101/article/details/70143766

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章