之前的篇章,介绍了redis中的大多数用法,今天使用redis来设计与实现一个伪微博项目。
具体使用什么客户端语言(java、python等),这里就不详细写了,主要是讲解该项目怎么使用redis这个key-value数据库来设计。
我一直觉得,一个程序猿,思想很重要,语言只是一种工具!!!
微博项目的key设计
全局key的设计
-----------------------------------------------------------
表名 global
列名 操作 备注
global:userid incr 产生全局的userid
global:postid incr 产生全局的postid
-----------------------------------------------------------
说明:
在以前,我们使用mysql等关系型数据库时,一般会使用数据库自带的自增主键,
在redis中,我们可以设置一个全局的key,然后通过incr指令来实现mysql中的自增主键。
userid:代表用户的id
postid:代表微博的id
用户相关的key的设计
用户相关的表设计(mysql中)
------------------------------------------------------------------------
表名 user
userid username password authsecret
1 test1 11111111 #Ujkdjf#o4&
2 test2 22222222 zzuWNM#$jkf
··· ··· ··· ···
------------------------------------------------------------------------
在redis中,变成以下几个key
-----------------------------------------------------------------------------------------------------
key的前缀 user
user:userid:* user:userid:*:username user:userid:*:password user:userid:*:authsecret
user:userid:1 user:userid:1:username user:userid:1:password user:userid:1:authsecret
-----------------------------------------------------------------------------------------------------
get global:userid ====> 返回一个userid
假设返回:1
set user:userid:1:username test1
set user:userid:1:password 11111111
set user:userid:1:authsecret #Ujkdjf#o4&
说明:authsecret是为了防止,客户端修改cookie中的userid和username来切换用户的身份,为了安全性,所以设置了authsecret。
每次用户登入,都会随机设置新的authsecret值。
微博相关的key设计
微博相关的表设计
-------------------------------------------------------------
表名 post
postid userid time content
4 2 13792238923 测试内容
-------------------------------------------------------------
微博在redis中, 与表设计对应的key设计
--------------------------------------------------------------------------------------------------------------------
key的前缀 post
post:postid:* post:postid:*:userid post:postid:*:username post:postid:*:time post:postid:*:content
post:postid:4 post:postid:4:userid post:postid:4:username post:postid:4:time post:postid:4:content
--------------------------------------------------------------------------------------------------------------------
注意:在这里,可以看到,我们在redis中,多设置了username的属性,这是为啥?username不是存在user:userid:*:username里面嘛,这样
不是数据冗余了嘛,有时候,为了提高查询的速度,适当的增加一些冗余数据是可以的,我们使用redis,不就是因为它的执行速度快嘛。
这里,使用的是string结构来存储的,但是可以改成hash更棒哦,大家可以自己设计一个hash结构。
关注表和粉丝表的设计
在伪微博的项目中,一定会有关注者,被关注者的功能,即:你的粉丝,和你关注的人。
关注表:
我们使用redis中的集合结构来存储:
following:$userid —> set
即:sadd following:1 2 3 4
表示:用户1,关注了用户id为2、3、4的用户。
粉丝表:
我们使用redis中的集合结构来存储:
follower:$userid —–> set
即:sadd follower:2 6 7 8
表示:用户id为2的粉丝有:6、7、8。
微博推送的设计
在该项目中,一个用户发表了一条微博,那么他的粉丝应该也会收到微博的推送信息。即:在用户的首页,可以看到自己发的微博和你们关注的博主的微博。
这个功能是该项目的难点,也是最重要的部分。
方法一:采用推送的方式
推送表: revicepost
使用list结构:
recivepost:$userid —-> list(3,4,7)
即当一个用户发布一条微博的时候,他会自己主动的向他的粉丝发送该条微博。
即:lpush recivepost:$userid —-> list(3,4,7)
表示的是,当前博主发送了多条微博的话,会向redis数据库中,recivepost列表中加入发布的微博的id。
使用一个for循环,向自己的粉丝,调用:lpush recivepost:$userid $postid。$userid:粉丝的id,postid:自己发布的微博的id。
然后,在其中一个粉丝登入之后,点击首页的时候,获取到recivepost中的所有微博id,再通过post来获取微博的信息。
方法二:采用拉取的方式
推送的方式,有一个致命的缺点,那就是当一个粉丝有可能会很久不登入微博来了,那么那些博主自己主动推的意义也不大,会浪费内存,因为该粉丝短时间不登入微博。某一天,粉丝突然登入微博的话,会接受到大量的博主的推送信息,会造成大量的数据发送,而且该粉丝只想看到最近的博主发送的微博,这个方式会发送所有的微博,意义不大。
采用拉取方式,是粉丝登入后,自己主动拉取博主的微博信息,不是被动的接受信息,而且,我们可以设置只拉取关注的每个博主的最近的20条数据,不需要很久之前的微博信息。
该怎么实现呢?这是一个难点。
拉取表
pull:$userid: —–> list(3,4,7)
问: 上次我拉取了 A -> 5,6,7, 三条微博, 下次刷新首页 , 需要从postid大于7的微博开始拉取。
解决: 拉取时, 设定一个lastpull时间点, 下次拉取时, 取>lastpull的微博
问: 有很多关注人,如何取?
解决: 循环自己的关注列表 , 逐个取他们的新微博
问: 取出来之后放在哪儿?
答: pull:$userid的链接里面
问: 如果个人中心 , 只有前1000条
答: ltrim,只取前1000条
问: 如果我关注 A,B两人, 从2人中,各取3条最新信息, 这3+3条信息, 从时间上,是交错的, 如何按时间排序?
答: 我们发布时, 是发布的hash结构 , 不能按时间来排序.
解决: 同步时, 取微博后, 记录本次取的微博的最大id,下次同步时, 只取比最大id更大的微博。
思路:
(1)当博主发送微博后,会pull:$userid $postid。 $userid:博主的userid,博主发送的postid。因为postid的自增的,所以postid越大表示该微博是最近发布的,这样就可以根据postid的大小来决定获取最近的1000条数据了。
(2)当粉丝登入到首页后,使用for循环来获取到博主的userid,然后通过userid来获取到每个博主发送的postid,该postid > lastpull (lastpull初始化为0,后来就赋值为上次访问后的最大的postid)
(3)然后将第二步获取到的所有postid,取其中的最近的1000条数据。
将redis中的数据写入到mysql中
前面的操作,都是在redis中操作的,现在,我们需要将每个用户的不经常访问的微博放到mysql中,redis中只保存最新的1000条数据即可。
思路:
每个用户发微博的时候,将自己的微博构新建成一个链表,然后还有添加一个全局的链表
即:mypost:userid:* —–> postid 和 global:storge —–> postid
当mypost:userid中的postid的数量,大于1000后,就将mypost链表最后的一条postid,插入到global:storge的最左端。
当global:storge中的postid的数量大于1000后,一致性将global:storge中的最右端的1000条数据,批量插入到mysql中。
示意图如下: