Redis中key的设计技巧

传统,我们都使用关系型数据库,比如mysql数据库,一般大家都熟悉了关系型数据库的表设计技巧了,这篇来讲解key-value数据库中key的设计技巧。

redis与关系型数据库的适合场景

现在,我们来考虑一个场景需求,比如豆瓣网站中的书,每本书都有标签,比如西游记,标签:文学等。

mysql中的设计

在mysql中,我们设计表结构如下:

书签系统设计:

#book表
create table book (
bookid int,         # book的id
title char(20)      # book的书名
)engine myisam charset utf8;

insert into book values 
(5 , 'PHP圣经'),
(6 , 'ruby实战'),
(7 , 'mysql运维')
(8, 'ruby服务端编程');

#书签表
create table tags (
tid int,          # 书签的id
bookid int,       # book的id
content char(20)  # 书签的内容
)engine myisam charset utf8;

insert into tags values 
(10 , 5 , 'PHP'),
(11 , 5 , 'WEB'),
(12 , 6 , 'WEB'),
(13 , 6 , 'ruby'),
(14 , 7 , 'database'),
(15 , 8 , 'ruby'),
(16 , 8 , 'server');

现在,我想要知道既有”WEB”标签又有”PHP”标签的书名,sql语句该怎么写呢?

select b.bookid , b.title from book as b,tags inner join tags as t on tags.bookid=t.bookid
where tags.content='PHP' and t.content='WEB' and b.bookid = tags.bookid;

大家可以看到,这是多么麻烦的操作,如果再加一个标签,还包括”ruby”标签呢? 则 and c.content = ‘ruby’

如果数据库中的数量很大,这样的内连接查询,会很耗时的。

因此,对于这样的需求,可以使用key-value数据库。

key-value中的设计

book使用string来存储
set book:5:title 'PHP圣经'
set book:6:title 'ruby实战'
set book:7:title 'mysql运难'
set book:8:title ‘ruby server’

tag使用set集合来存储
sadd tag:PHP 5
sadd tag:WEB 5 6
sadd tag:database 7
sadd tag:ruby 6 8
sadd tag:SERVER 8

查: 既有PHP,又有WEB的书。

sinter tag:PHP tag:WEB  #查集合的交集

查: 有PHP或有WEB标签的书.

sunin tag:PHP tag:WEB  #查集合的并集

查: 含有ruby,不含WEB标签的书.

sdiff tag:ruby tag:WEB #求差集

我们能很轻松的实现这样的需求,速度快。


redis中key的设计技巧

通过上面的演示,我们知道了,key-value数据库可以实现一些特殊的需求,怎样设计key呢?

一般,分为以下4步:

1:第1段放置表名,作为为key前缀 ,如:user:
2:第2段放置用于区分区key的字段–对应mysql中的主键的列名,如:userid
3:第3段放置主键值,如 2,3,4...., a , b ,c
4:第4段,写要存储的列名。如 name,passwd,email

完整的key:set user:userid:1:name gakki


mysql中的表:

user表:
----------------------------------------------------------
userid         username           password      email
1              lisi               123456        a@a.com
2              jack               987656        b@b.com
3              rose               454354        c@c.com
···            ···                ···           ···
----------------------------------------------------------

key-value:

set user:userid:1:username lisi
set user:userid:1:password 123456
set user:userid:1:email a@a.com

----------------------------------
set user:userid:2:username jack
set user:userid:2:password 987656
set user:userid:2:email b@b.com

----------------------------------
set user:userid:3:username rose
set user:userid:3:password 454354
set user:userid:3:email c@c.com

这样设计的好处是,通过设计的key的前缀,比如:user:userid:1 通过hash函数,定位到指定的redis服务器,从该服务器中获取所有的id:1的数据。

这样对于分布式存储很有用处,一般id相同的数据都存储在一个数据库中,不可能id:1的name存储在1号redis服务器,id:1的password存储在2号redis服务器中。

注意:

在关系型数据库中, 除主键外, 还有可能其他列也会频繁查询。
如上表中, username 也是极频繁查询的,往往这种列会通过加索引来加快查询的速度。

那么,在redis中,要查询username为lisi,该怎么呢?

不会使用:keys user:userid:*:username lisi

这样查询的话,redis会从userid为1开始往后依次对比username是否为lisi,这样效率当然很低了。

没有什么其他好的办法了,只能使用冗余数据来加快username的查询

set user:username:lisi:userid 1
set user:username:jack:userid 2
set user:username:rose:userid 3

注意,这里,我们额外添加了冗余的数据,使用username来作为前缀了,来保存userid的值,但是只保存userid的值,其他的值,可以通过username获取的id,再通过id来获取其他的值。

比如:我想获取 jack 的 email

首先,get user:username:jack:userid ====> 返回id:2

然后,get user:userid:2:email ====> 返回email:b@b.com


  目录