基于Redis的微博系统基本功能设计

2016-11-16 13:19李磊
电脑知识与技术 2016年25期
关键词:微博

李磊

摘要:微博系统对信息实时性和并发性的要求,传统的关系型数据库无法满足性能要求。Key-Value模型的内存数据库Redis,非常适合微博系统对数据快速存取的需要。

关键词:Redis;Key-Value;微博

中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2016)25-0061-03

Abstract:Microblog system requirements for information-real-time performance and concurrency. Traditional relational database does not meet performance requirements. Key-Value model memory database Redis, very suitable for the microblog system with fast access to the data needed.

Key words:Redis; Key-Value; Microblog

1 概述

微博系统类似于一个群聊的庞大聊天室,每时每刻都会有大量的消息产生,而且产生的消息会反馈给需要的用户,这样就要求数据的读写非常快。关系型数据库在数据量超过一定规模时,由于自身逻辑相对复杂,在信息检索上无法满足用户的体验。

Redis数据库本身的数据就放在内存中,而且有合适的数据结构。Twitter、新浪微博都是目前最大的Redis用户。

2 Redis介绍

Redis是一个速度非常快的非关系型数据库。Redis可以存储键(Key)与5种不同类型的值(Value)之间的映射,可以将存储在内存中的键值对持久化到硬盘。Redis还可以使用复制特性来扩展读性能,使用客户端分片来扩展写性能。

其重要特性如下:

① 持久化:Redis定期把数据异步flush到硬盘进行保存。服务器重启,数据不会丢失。

② 主从复制:主要用1台服务器进行数据备份与恢复。

③ Vitual Memory功能:物理内存毕竟是有限的,这技术主要是把很少用的Value保存到硬盘,而Key保留在内存做检索,提高访问性能。

④ 多种数据结构支持:Redis的Key是string类型,Value的类型有:string、set、list、zset(sorted set)、hash。针对每种数据类型,还提供相应的操作命令,比如list类型有LPOP、LPUSH、RPOP、RPUSH等操作,set类型SDIFF(差运算)、SINTER(交运算)、SUNION(并运算)等操作①。

3 PHP和Redis构建微博系统基本功能

利用PHP的Redis扩展,在PHP中实现微博系统新用户的创建、消息发布、关注与粉丝设计、消息推送等基本功能。PHP版本为5.5.12,Redis版本为3.0.501。

3.1 用户信息表示

Redis hash是一个string类型的field和value的映射表.一个key可对应多个field,一个field对应一个value。将一个对象存储为hash类型,较于每个字段都存储成string类型更能节省内存。新建一个hash对象时开始是用zipmap(又称为small hash)来存储的。这个zipmap其实并不是hash table,但是zipmap相比正常的hash实现可以节省不少hash本身需要的一些元数据存储开销。尽管zipmap的添加,删除,查找都是O(n),但是由于一般对象的field数量都不太多。所以使用zipmap也是很快的,也就是说添加删除平均还是O(1)。如果field或者value的大小超出一定限制后,Redis会在内部自动将zipmap替换成正常的hash实现.。

这里我们在数据库中表示用户信息和发布的消息都用Redis的hash结构。用户信息如表1。

创建新用户时,我们用到一个user:id:的计数器,实际就是Redis的一个Key,初始一个值,然后每次添加到user:uid的hash后值要自增1。用用户信息中login和id两个filed的值构造另一个hash表users:,用来建立login和id之间的映射。关键代码如下:

if($redis->hget("users:",$login)){echo "{$login}已经存在,重新输入";}

else{$uidarray=$redis->multi()->incr("user:id:")->exec();

$uid=$uidarray[0];

$userinfo=array(

"login"=>$login,

"id"=>$uid,

"name"=>$name,

"following"=>0,

"fans"=>0,

"posts"=>0,

"signup"=>time());

$redis->multi()->hset("users:",$login,$uid)

->hmset("user:{$uid}",$userinfo)->exec();}

3.2 发布的消息表示

用户发布的消息也用hash表示,结构如表2。

发布消息时候,也用到一个计数器message:mid:,其值传给消息中的mid,然后自增1,保证每个消息都有不同的mid。发布消息时,不仅要添加message:mid一个新的值,还要修改user:uid中的posts域的值。关键代码如下:

$midarray=$redis->multi()->incr("message:mid:")->exec();

$mid=$midarray[0];

$messageinfo=array(

"content"=>$content,

"time"=>time(),

"mid"=>$mid,

"uid"=>$uid,

"login"=>$login);

$redis->multi()->hmset("message:{$mid}",$messageinfo)

->hincrby("user:{$uid}","posts",1)->exec();

3.3 用户主页时间线和个人时间线

Redis提供zset这种有序集合数据结构。通过zadd命令添加的成员,按照score的值排序,默认score的值递增。在微博系统中利用该数据结构的特点,可以很方便的取出最新的消息。

用户主页时间是指,当用户登录后,能看到用户以及用户关注的人所发布的消息列表,这个列表以发布消息的时间排序。在Redis中用户主页时间线结构如表3。

用户个人时间线仅仅只有用户个人发布的消息列表,也是以发布时间排序。用户个人时间线profile:uid结构如下表4。

3.4 关注者列表和粉丝列表

微博系统就是要让用户之间分享各自的构想、想法。当A用户开始关注或取消关注B用户的时候,我们不仅要更新A用户的关注列表following:A和A用户的个人信息中关注数量following的值,还要更新B用户的粉丝列表fans:B和B用户的个人信息中粉丝数量fans的值。最后把B用户发布的消息,profile:B中的消息,更新到A用户的主页时间线home:A。两个列表都用Redis的zset有序集合结构表示。表5为关注者列表结构,表6位粉丝列表结构。

开始关注操作关键代码如下:

define("HOME_TIMELINE_SIZE",1000);

$fkey1="following:{$uid}";

$fkey2="fans:{$other_id}";

$have=$redis->zscore($fkey1,$other_id);

if($have==true){ echo "{$uid}已经关注{$other_id}";}

else{$time=time();

$values=$redis->multi()->zadd($fkey1,$other_id,$time)

->zadd($fkey2,$uid,$time)

->zrevrange("profile:{$other_id}",0,HOME_TIMELINE_SIZE-1,true)

->exec();

$redis->multi()->hincrby("user:{$uid}","following",$values[0])

->hincrby("user:{$other_id}","fans",$values[1])->exec();

if($values[2]){//获取$other_id发布的消息不为空

foreach($values[2] as $key=>$value)

{ $redis->multi()->zadd("home:{$uid}",$value,$key)->exec();}}}

取消操作关键代码如下:

define("HOME_TIMELINE_SIZE",1000);

$fkey1="following:{$uid}";

$fkey2="fans:{$other_id}";

$have=$redis->zrangebyscore($fkey1,$other_id,$other_id,array(true,1));

if($have==false){echo "{$uid}没有关注{$other_id}";}

else{$values=$redis->multi()->zrem($fkey1,$other_id)

->zrem($fkey2,$uid)

->zrevrange("profile:{$other_id}",0,HOME_TIMELINE_SIZE-1,true)

->exec();

$redis->multi()->hincrby("user:{$uid}","following",-$values[0])

->hincrby("user:{$other_id}","fans",-$values[1])->exec();

if($values[2]){//获取$other_id发布的消息不为空

foreach($values[2] as $key=>$value)

{$redis->multi()->zrem("home:{$uid}",$key)->exec();}}}

3.5 消息推送

在3.2节里面说明的是消息发布后,除了进行消息信息添加以外,用户个人信息中发布消息数量posts值得自增。我们还应该接着把发布的消息告诉给个人时间线和主页时间线,也就是在profile:uid(uid为发布消息的用户id)中添加消息编号mid和时间戳time,并且在home:uid(uid为发布消息的用户id)中也添加消息编号和时间戳time,这个时间戳应该是消息发布的时候服务器的时间戳。然后还要在粉丝的主页时间线home:uid(发布消息用户的粉丝id)中添加同样的数据。由于微博系统中有的用户粉丝数量非常大,如果同步更新可能会导致用户长时间等待。所以,在更新的时候,可以先更新fans:uid(uid为发布消息的用户id)集合中前面1000个关注者,对每个关注者的home:uid进行更新。关键代码如下:

$redis->multi()->zadd("profile:{$uid}",$time,$mid)

->zadd("home:{$uid}",$time,$mid)->exec();

$fans=$redis->zrevrange("fans:{$uid}",0,1000,true);

foreach($fans as $key=>$value)

{ $redis->zadd("home:{$value}",$time,$mid);}

如果存在超过1000个用户的情况,可以设计一个延迟功能来进行转发,避免发布消息的用户长时间等待。

4 总结和展望

Redis本身提供了很多的数据结构,灵活应用可以构造适合微博系统的数据库。这里我们搭建了php+redis环境,做一个简单的微博系统,实现基本功能。要开发像Twitter、sina微博等系统,还要考虑更复杂的数据构造实现更多的功能,以及如何扩展服务器来提高服务质量。

参考文献:

[1] 唐诚.Redis数据库在微博系统中的实践[J].厦门城市学院学报,2012,14(3):55-59.

[2] Josianh L Carlson.Redis实战[M].北京:人民邮电出版社,2015.

[3] 王艳,董丽丽.NoSql与关系数据库相结合的设计与实践[J].电脑知识与技术, 2014(9).

猜你喜欢
微博
何以解忧?基于社交媒体大数据的睡眠健康公众叙事研究
微博信息传播存在的主要问题和对策研究
官方微博舆论引导方式探究
打造医院里的“主流媒体”
事实与流言的博弈
重大突发事件中微博之力不微