李磊
摘要:微博系統(tǒng)對信息實時性和并發(fā)性的要求,傳統(tǒng)的關(guān)系型數(shù)據(jù)庫無法滿足性能要求。Key-Value模型的內(nèi)存數(shù)據(jù)庫Redis,非常適合微博系統(tǒng)對數(shù)據(jù)快速存取的需要。
關(guān)鍵詞:Redis;Key-Value;微博
中圖分類號:TP311 文獻(xiàn)標(biāo)識碼: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 概述
微博系統(tǒng)類似于一個群聊的龐大聊天室,每時每刻都會有大量的消息產(chǎn)生,而且產(chǎn)生的消息會反饋給需要的用戶,這樣就要求數(shù)據(jù)的讀寫非???。關(guān)系型數(shù)據(jù)庫在數(shù)據(jù)量超過一定規(guī)模時,由于自身邏輯相對復(fù)雜,在信息檢索上無法滿足用戶的體驗。
Redis數(shù)據(jù)庫本身的數(shù)據(jù)就放在內(nèi)存中,而且有合適的數(shù)據(jù)結(jié)構(gòu)。Twitter、新浪微博都是目前最大的Redis用戶。
2 Redis介紹
Redis是一個速度非??斓姆顷P(guān)系型數(shù)據(jù)庫。Redis可以存儲鍵(Key)與5種不同類型的值(Value)之間的映射,可以將存儲在內(nèi)存中的鍵值對持久化到硬盤。Redis還可以使用復(fù)制特性來擴(kuò)展讀性能,使用客戶端分片來擴(kuò)展寫性能。
其重要特性如下:
① 持久化:Redis定期把數(shù)據(jù)異步flush到硬盤進(jìn)行保存。服務(wù)器重啟,數(shù)據(jù)不會丟失。
② 主從復(fù)制:主要用1臺服務(wù)器進(jìn)行數(shù)據(jù)備份與恢復(fù)。
③ Vitual Memory功能:物理內(nèi)存畢竟是有限的,這技術(shù)主要是把很少用的Value保存到硬盤,而Key保留在內(nèi)存做檢索,提高訪問性能。
④ 多種數(shù)據(jù)結(jié)構(gòu)支持:Redis的Key是string類型,Value的類型有:string、set、list、zset(sorted set)、hash。針對每種數(shù)據(jù)類型,還提供相應(yīng)的操作命令,比如list類型有LPOP、LPUSH、RPOP、RPUSH等操作,set類型SDIFF(差運算)、SINTER(交運算)、SUNION(并運算)等操作①。
3 PHP和Redis構(gòu)建微博系統(tǒng)基本功能
利用PHP的Redis擴(kuò)展,在PHP中實現(xiàn)微博系統(tǒng)新用戶的創(chuàng)建、消息發(fā)布、關(guān)注與粉絲設(shè)計、消息推送等基本功能。PHP版本為5.5.12,Redis版本為3.0.501。
3.1 用戶信息表示
Redis hash是一個string類型的field和value的映射表.一個key可對應(yīng)多個field,一個field對應(yīng)一個value。將一個對象存儲為hash類型,較于每個字段都存儲成string類型更能節(jié)省內(nèi)存。新建一個hash對象時開始是用zipmap(又稱為small hash)來存儲的。這個zipmap其實并不是hash table,但是zipmap相比正常的hash實現(xiàn)可以節(jié)省不少hash本身需要的一些元數(shù)據(jù)存儲開銷。盡管zipmap的添加,刪除,查找都是O(n),但是由于一般對象的field數(shù)量都不太多。所以使用zipmap也是很快的,也就是說添加刪除平均還是O(1)。如果field或者value的大小超出一定限制后,Redis會在內(nèi)部自動將zipmap替換成正常的hash實現(xiàn).。
這里我們在數(shù)據(jù)庫中表示用戶信息和發(fā)布的消息都用Redis的hash結(jié)構(gòu)。用戶信息如表1。
創(chuàng)建新用戶時,我們用到一個user:id:的計數(shù)器,實際就是Redis的一個Key,初始一個值,然后每次添加到user:uid的hash后值要自增1。用用戶信息中l(wèi)ogin和id兩個filed的值構(gòu)造另一個hash表users:,用來建立login和id之間的映射。關(guān)鍵代碼如下:
if($redis->hget("users:",$login)){echo "{$login}已經(jīng)存在,重新輸入";}
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 發(fā)布的消息表示
用戶發(fā)布的消息也用hash表示,結(jié)構(gòu)如表2。
發(fā)布消息時候,也用到一個計數(shù)器message:mid:,其值傳給消息中的mid,然后自增1,保證每個消息都有不同的mid。發(fā)布消息時,不僅要添加message:mid一個新的值,還要修改user:uid中的posts域的值。關(guān)鍵代碼如下:
$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這種有序集合數(shù)據(jù)結(jié)構(gòu)。通過zadd命令添加的成員,按照score的值排序,默認(rèn)score的值遞增。在微博系統(tǒng)中利用該數(shù)據(jù)結(jié)構(gòu)的特點,可以很方便的取出最新的消息。
用戶主頁時間是指,當(dāng)用戶登錄后,能看到用戶以及用戶關(guān)注的人所發(fā)布的消息列表,這個列表以發(fā)布消息的時間排序。在Redis中用戶主頁時間線結(jié)構(gòu)如表3。
用戶個人時間線僅僅只有用戶個人發(fā)布的消息列表,也是以發(fā)布時間排序。用戶個人時間線profile:uid結(jié)構(gòu)如下表4。
3.4 關(guān)注者列表和粉絲列表
微博系統(tǒng)就是要讓用戶之間分享各自的構(gòu)想、想法。當(dāng)A用戶開始關(guān)注或取消關(guān)注B用戶的時候,我們不僅要更新A用戶的關(guān)注列表following:A和A用戶的個人信息中關(guān)注數(shù)量following的值,還要更新B用戶的粉絲列表fans:B和B用戶的個人信息中粉絲數(shù)量fans的值。最后把B用戶發(fā)布的消息,profile:B中的消息,更新到A用戶的主頁時間線home:A。兩個列表都用Redis的zset有序集合結(jié)構(gòu)表示。表5為關(guān)注者列表結(jié)構(gòu),表6位粉絲列表結(jié)構(gòu)。
開始關(guān)注操作關(guān)鍵代碼如下:
define("HOME_TIMELINE_SIZE",1000);
$fkey1="following:{$uid}";
$fkey2="fans:{$other_id}";
$have=$redis->zscore($fkey1,$other_id);
if($have==true){ echo "{$uid}已經(jīng)關(guān)注{$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發(fā)布的消息不為空
foreach($values[2] as $key=>$value)
{ $redis->multi()->zadd("home:{$uid}",$value,$key)->exec();}}}
取消操作關(guān)鍵代碼如下:
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}沒有關(guān)注{$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發(fā)布的消息不為空
foreach($values[2] as $key=>$value)
{$redis->multi()->zrem("home:{$uid}",$key)->exec();}}}
3.5 消息推送
在3.2節(jié)里面說明的是消息發(fā)布后,除了進(jìn)行消息信息添加以外,用戶個人信息中發(fā)布消息數(shù)量posts值得自增。我們還應(yīng)該接著把發(fā)布的消息告訴給個人時間線和主頁時間線,也就是在profile:uid(uid為發(fā)布消息的用戶id)中添加消息編號mid和時間戳time,并且在home:uid(uid為發(fā)布消息的用戶id)中也添加消息編號和時間戳time,這個時間戳應(yīng)該是消息發(fā)布的時候服務(wù)器的時間戳。然后還要在粉絲的主頁時間線home:uid(發(fā)布消息用戶的粉絲id)中添加同樣的數(shù)據(jù)。由于微博系統(tǒng)中有的用戶粉絲數(shù)量非常大,如果同步更新可能會導(dǎo)致用戶長時間等待。所以,在更新的時候,可以先更新fans:uid(uid為發(fā)布消息的用戶id)集合中前面1000個關(guān)注者,對每個關(guān)注者的home:uid進(jìn)行更新。關(guān)鍵代碼如下:
$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個用戶的情況,可以設(shè)計一個延遲功能來進(jìn)行轉(zhuǎn)發(fā),避免發(fā)布消息的用戶長時間等待。
4 總結(jié)和展望
Redis本身提供了很多的數(shù)據(jù)結(jié)構(gòu),靈活應(yīng)用可以構(gòu)造適合微博系統(tǒng)的數(shù)據(jù)庫。這里我們搭建了php+redis環(huán)境,做一個簡單的微博系統(tǒng),實現(xiàn)基本功能。要開發(fā)像Twitter、sina微博等系統(tǒng),還要考慮更復(fù)雜的數(shù)據(jù)構(gòu)造實現(xiàn)更多的功能,以及如何擴(kuò)展服務(wù)器來提高服務(wù)質(zhì)量。
參考文獻(xiàn):
[1] 唐誠.Redis數(shù)據(jù)庫在微博系統(tǒng)中的實踐[J].廈門城市學(xué)院學(xué)報,2012,14(3):55-59.
[2] Josianh L Carlson.Redis實戰(zhàn)[M].北京:人民郵電出版社,2015.
[3] 王艷,董麗麗.NoSql與關(guān)系數(shù)據(jù)庫相結(jié)合的設(shè)計與實踐[J].電腦知識與技術(shù), 2014(9).