《PHP教程:php中Redis的應用--消息傳遞》要點:
本文介紹了PHP教程:php中Redis的應用--消息傳遞,希望對您有用。如果有疑問,可以聯系我們。
閱讀目錄PHP學習
1、摘要PHP學習
2、實現方法PHP學習
3、一對一消息傳遞PHP學習
4、多對多消息傳遞PHP學習
1、摘要PHP學習
消息傳遞這一應用廣泛存在于各個網站中,這個功能也是一個網站必不可少的.常見的消息傳遞應用有,新浪微博中的@我呀、給你評論然后的提示呀、贊贊贊提示、私信呀、甚至是發微博分享的新鮮事;知乎中的私信呀、live發送過來的消息、知乎團隊消息呀等等.PHP學習
2、實現方法PHP學習
消息傳遞即兩個或者多個客戶端在相互發送和接收消息.PHP學習
通常有兩種方法實現:PHP學習
第一種為消息推送.Redis內置有這種機制,publish往頻道推送消息、subscribe訂閱頻道.這種方法有一個缺點就是必須保證接收者時刻在線(即是此時程序不能停下來,一直保持監控狀態,假若斷線后就會出現客戶端丟失信息)PHP學習
第二種為消息拉取.所謂消息拉取,就是客戶端自主去獲取存儲在服務器中的數據.Redis內部沒有實現消息拉取這種機制.因此我們需要自己手動編寫代碼去實現這個功能.PHP學習
在這里我們,我們進一步將消息傳遞再細分為一對一的消息傳遞,多對多的消息傳遞(群組消息傳遞).PHP學習
【注:兩個類的代碼相對較多,因此將其折疊起來了】PHP學習
3、一對一消息傳遞PHP學習
例子1:一對一消息發送與獲取PHP學習
模塊要求:PHP學習
1、提示有多少個聯系人發來新消息PHP學習
2、信息包含發送人、時間、信息內容PHP學習
3、能夠獲取之前的舊消息PHP學習
4、并且消息能夠保持7天,過期將會被動觸發刪除PHP學習
Redis實現思路:PHP學習
1、新消息與舊消息分別采用兩個鏈表來存儲PHP學習
2、原始消息的結構采用數組的形式存放,并且含有發送人、時間戳、信息內容PHP學習
3、在推入redis的鏈表前,需要將數據轉換為json類型然后再進行存儲PHP學習
4、在取出新信息時應該使用rpoplpush來實現,將已讀的新消息推入舊消息鏈表中PHP學習
5、取出舊消息時,應該用舊消息的時間與現在的時間進行對比,若超時,則直接刪除后面的全部數據(因為數據是按時間一個一個壓進鏈表中的,所以對于時間是有序排列的)PHP學習
數據存儲結構圖:PHP學習
PHP學習
PHP的實現代碼:PHP學習
#SinglePullMessage.class.phpPHP學習
<?php #單接接收者接收消息 class SinglePullMessage { private $redis=''; #存儲redis對象 /** * @desc 構造函數 * * @param $host string | redis主機 * @param $port int | 端口 */ public function __construct($host,$port=6379) { $this->redis=new Redis(); $this->redis->connect($host,$port); } /** * @desc 發送消息(一個人) * * @param $toUser string | 接收人 * @param $messageArr array | 發送的消息數組,包含sender、message、time * * @return bool */ public function sendSingle($toUser,$messageArr) { $json_message=json_encode($messageArr); #編碼成json數據 return $this->redis->lpush($toUser,$json_message); #將數據推入鏈表 } /** * @desc 用戶獲取新消息 * * @param $user string | 用戶名 * * @return array 返回數組,包含多少個用戶發來新消息,以及具體消息 */ public function getNewMessage($user) { #接收新信息數據,并且將數據推入舊信息數據鏈表中,并且在原鏈表中刪除 $messageArr=array(); while($json_message=$this->redis->rpoplpush($user, 'preMessage_'.$user)) { $temp=json_decode($json_message); #將json數據變成對象 $messageArr[$temp->sender][]=$temp; #轉換成數組信息 } if($messageArr) { $arr['count']=count($messageArr); #統計有多少個用戶發來信息 $arr['messageArr']=$messageArr; return $arr; } return false; } public function getPreMessage($user) { ##取出舊消息 $messageArr=array(); $json_pre=$this->redis->lrange('preMessage_'.$user, 0, -1); #一次性將全部舊消息取出來 foreach ($json_pre as $k => $v) { $temp=json_decode($v); #json反編碼 $timeout=$temp->time+60*60*24*7; #數據過期時間 七天過期 if($timeout<time()) #判斷數據是否過期 { if($k==0) #若是最遲插入的數據都過期了,則將所有數據刪除 { $this->redis->del('preMessage_'.$user); break; } $this->redis->ltrim('preMessage_'.$user, 0, $k); #若檢測出有過期的,則將比它之前插入的所有數據刪除 break; } $messageArr[$temp->sender][]=$temp; } return $messageArr; } /** * @desc 消息處理,沒什么特別的作用.在這里這是用來處理數組信息,然后將其輸出. * * @param $arr array | 需要處理的信息數組 * * @return 返回打印輸出 */ public function dealArr($arr) { foreach ($arr as $k => $v) { foreach ($v as $k1 => $v2) { echo '發送人:'.$v2->sender.' 發送時間:'.date('Y-m-d h:i:s',$v2->time).'<br/>'; echo '消息內容:'.$v2->message.'<br/>'; } echo "<hr/>"; } } }
測試:PHP學習
1、發送消息PHP學習
#建立test1.phpPHP學習
include './SinglePullMessage.class.php'; $object=new SinglePullMessage('192.168.95.11'); #發送消息 $sender='boss'; #發送者 $to='jane'; #接收者 $message='How are you'; #信息 $time=time(); $arr=array('sender'=>$sender,'message'=>$message,'time'=>$time); echo $object->sendSingle($to,$arr);
2、獲取新消息PHP學習
#建立test2.phpPHP學習
include './SinglePullMessage.class.php'; $object=new SinglePullMessage('192.168.95.11'); #獲取新消息 $arr=$object->getNewMessage('jane'); if($arr) { echo $arr['count']."個聯系人發來新消息<br/><hr/>"; $object->dealArr($arr['messageArr']); } else echo "無新消息";
訪問結果:PHP學習
PHP學習
3、獲取舊消息PHP學習
#建立test3.phpPHP學習
include './SinglePullMessage.class.php'; $object=new SinglePullMessage('192.168.95.11'); #獲取舊消息 $arr=$object->getPreMessage('jane'); if($arr) { $object->dealArr($arr); } else echo "無舊數據";
4、多對多消息傳遞PHP學習
例子2:多對多消息發送與獲取(即是群組)PHP學習
模塊要求:PHP學習
1、用戶能夠自行創建群組,并成為群主PHP學習
2、群主可以拉人進來作為群組成員、并且可以踢人PHP學習
3、用戶可以直接退出群組PHP學習
4、可以發送消息,每一位成員都可以拉取消息PHP學習
5、群組的消息最大容納量為5000條PHP學習
6、成員可以拉取新消息,并提示有多少新消息PHP學習
7、成員可以分頁獲取之前已讀的舊消息PHP學習
.....功能就寫這幾個吧,有需要或者想練習的同學們可以增加其他功能,例如禁言、匿名消息發送、文件發送等等.PHP學習
Redis實現思路:PHP學習
1、群組的消息以及群組的成員組成采用有序集合進行存儲.群組消息有序集合的member存儲用戶發送的json數據消息,score存儲唯一值,將采用原子操作incr獲取string中的自增長值進行存儲;群組成員有序集合的member存儲user,score存儲非零數字(在這里這個score意義不大,我的例子代碼中使用數字1為群主的score,其他的存儲為2.當然這使用這個數據還可以擴展別的功能,例如群組中成員等級)可參考下面數據存儲結構簡圖.PHP學習
2、用戶所加入的群組也是采用有序集合進行存儲.其中,member存儲群組ID,score存儲用戶已經獲取該群組的最大消息分值(對應群組消息的score值)PHP學習
3、用戶創建群組的時候,通過原子操作incr從而獲取一個唯一IDPHP學習
4、用戶在群中發送消息時,也是通過原子操作incr獲取一個唯一自增長有序IDPHP學習
5、在執行incr時,為防止并發導致競爭關系,因此需要進行加鎖操作【redis詳細鎖的講解可以參考:Redis構建分布式鎖/article/109704.htm】PHP學習
6、創建群組方法簡要思路,任何一個用戶都可以創建群組聊天,在創建的同時,可以選擇時是否添加群組成員(參數通過數組的形式).創建過程將會為這個群組建立一個群組成員有序集合(群組信息有序集合暫時不創建),接著將群主添加進去,再將群ID添加用戶所參加的群組有序集合中.PHP學習
數據存儲結構圖:PHP學習
PHP學習
PHP學習
PHP的代碼實現:PHP學習
#ManyPullMessage.class.phpPHP學習
<?php class ManyPullMessage { private $redis=''; #存儲redis對象 /** * @desc 構造函數 * * @param $host string | redis主機 * @param $port int | 端口 */ public function __construct($host,$port=6379) { $this->redis=new Redis(); $this->redis->connect($host,$port); } /** * @desc 用于創建群組的方法,在創建的同時還可以拉人進群組 * * @param $user string | 用戶名,創建群組的主人 * @param $addUser array | 其他用戶構成的數組 * * @param $lockName string | 鎖的名字,用于獲取群組ID的時候用 * @return int 返回群組ID */ public function createGroupChat($user, $addUser=array(), $lockName='chatIdLock') { $identifier=$this->getLock($lockName); #獲取鎖 if($identifier) { $id=$this->redis->incr('groupChatID'); #獲取群組ID $this->releaseLock($lockName,$identifier); #釋放鎖 } else return false; $messageCount=$this->redis->set('countMessage_'.$id, 0); #初始化這個群組消息計數器 #開啟非事務型流水線,一次性將所有redis命令傳給redis,減少與redis的連接 $pipe=$this->redis->pipeline(); $this->redis->zadd('groupChat_'.$id, 1, $user); #創建群組成員有序集合,并添加群主 #將這個群組添加到user所參加的群組有序集合中 $this->redis->zadd('hasGroupChat_'.$user, 0, $id); foreach ($addUser as $v) #創建群組的同時需要添加的用戶成員 { $this->redis->zadd('groupChat_'.$id, 2, $v); $this->redis->zadd('hasGroupChat_'.$v, 0, $id); } $pipe->exec(); return $id; #返回群組ID } /** * @desc 群主主動拉人進群 * * @param $user string | 群主名 * @param $groupChatID int | 群組ID * @param $addMembers array | 需要拉進群的用戶 * * @return bool */ public function addMembers($user, $groupChatID, $addMembers=array()) { $groupMasterScore=$this->redis->zscore('groupChat_'.$groupChatID, $user); #將groupChatName的群主取出來 if($groupMasterScore==1) #判斷user是否是群主 { $pipe=$this->redis->pipeline(); #開啟非事務流水線 foreach ($addMembers as $v) { $this->redis->zadd('groupChat_'.$groupChatID, 2, $v); #添加進群 $this->redis->zadd('hasGroupChat_'.$v, 0, $groupChatID); #添加群名到用戶的有序集合中 } $pipe->exec(); return true; } return false; } /** * @desc 群主刪除成員 * * @param $user string | 群主名 * @param $groupChatID int | 群組ID * @param $delMembers array | 需要刪除的成員名字 * * @return bool */ public function delMembers($user, $groupChatID, $delMembers=array()) { $groupMasterScore=$this->redis->zscore('groupChat_'.$groupChatID, $user); if($groupMasterScore==1) #判斷user是否是群主 { $pipe=$this->redis->pipeline(); #開啟非事務流水線 foreach ($delMembers as $v) { $this->redis->zrem('groupChat_'.$groupChatID, $v); $this->redis->zrem('hasGroupChat_'.$v, $groupChatID); } $pipe->exec(); return true; } return false; } /** * @desc 退出群組 * * @param $user string | 用戶名 * @param $groupChatID int | 群組名 */ public function quitGroupChat($user, $groupChatID) { $this->redis->zrem('groupChat_'.$groupChatID, $user); $this->redis->zrem('hasGroupChat_'.$user, $groupChatID); return true; } /** * @desc 發送消息 * * @param $user string | 用戶名 * @param $groupChatID int | 群組ID * @param $messageArr array | 包含發送消息的數組 * @param $preLockName string | 群消息鎖前綴,群消息鎖全名為countLock_群ID * * @return bool */ public function sendMessage($user, $groupChatID, $messageArr, $preLockName='countLock_') { $memberScore=$this->redis->zscore('groupChat_'.$groupChatID, $user); #成員score if($memberScore) { $identifier=$this->getLock($preLockName.$groupChatID); #獲取鎖 if($identifier) #判斷獲取鎖是否成功 { $messageCount=$this->redis->incr('countMessage_'.$groupChatID); $this->releaseLock($preLockName.$groupChatID,$identifier); #釋放鎖 } else return false; $json_message=json_encode($messageArr); $this->redis->zadd('groupChatMessage_'.$groupChatID, $messageCount, $json_message); $count=$this->redis->zcard('groupChatMessage_'.$groupChatID); #查看信息量大小 if($count>5000) #判斷數據量有沒有達到5000條 { #數據量超5000,則需要清除舊數據 $start=5000-$count; $this->redis->zremrangebyrank('groupChatMessage_'.$groupChatID, $start, $count); } return true; } return false; } /** * @desc 獲取新信息 * * @param $user string | 用戶名 * * @return 成功則放回json數據數組,無新信息返回false */ public function getNewMessage($user) { $arrID=$this->redis->zrange('hasGroupChat_'.$user, 0, -1, 'withscores'); #獲取用戶擁有的群組ID $json_message=array(); #初始化 foreach ($arrID as $k => $v) #遍歷循環所有群組,查看是否有新消息 { $messageCount=$this->redis->get('countMessage_'.$k); #群組最大信息分值數 if($messageCount>$v) #判斷用戶是否存在未讀新消息 { $json_message[$k]['message']=$this->redis->zrangebyscore('groupChatMessage_'.$k, $v+1, $messageCount); $json_message[$k]['count']=count($json_message[$k]['message']); #統計新消息數量 $this->redis->zadd('hasGroupChat_'.$user, $messageCount, $k); #更新已獲取消息 } } if($json_message) return $json_message; return false; } /** * @desc 分頁獲取群組信息 * * @param $user string | 用戶名 * @param $groupChatID int | 群組ID * @param $page int | 第幾頁 * @param $size int | 每頁多少條數據 * * @return 成功返回json數據,失敗返回false */ public function getPartMessage($user, $groupChatID, $page=1, $size=10) { $start=$page*$size-$size; #開始截取數據位置 $stop=$page*$size-1; #結束截取數據位置 $json_message=$this->redis->zrevrange('groupChatMessage_'.$groupChatID, $start, $stop); if($json_message) return $json_message; return false; } /** * @desc 加鎖方法 * * @param $lockName string | 鎖的名字 * @param $timeout int | 鎖的過期時間 * * @return 成功返回identifier/失敗返回false */ public function getLock($lockName, $timeout=2) { $identifier=uniqid(); #獲取唯一標識符 $timeout=ceil($timeout); #確保是整數 $end=time()+$timeout; while(time()<$end) #循環獲取鎖 { /* #這里的set操作可以等同于下面那個if操作,并且可以減少一次與redis通訊 if($this->redis->set($lockName, $identifier array('nx', 'ex'=>$timeout))) return $identifier; */ if($this->redis->setnx($lockName, $identifier)) #查看$lockName是否被上鎖 { $this->redis->expire($lockName, $timeout); #為$lockName設置過期時間 return $identifier; #返回一維標識符 } elseif ($this->redis->ttl($lockName)===-1) { $this->redis->expire($lockName, $timeout); #檢測是否有設置過期時間,沒有則加上 } usleep(0.001); #停止0.001ms } return false; } /** * @desc 釋放鎖 * * @param $lockName string | 鎖名 * @param $identifier string | 鎖的唯一值 * * @param bool */ public function releaseLock($lockName,$identifier) { if($this->redis->get($lockName)==$identifier) #判斷是鎖有沒有被其他客戶端修改 { $this->redis->multi(); $this->redis->del($lockName); #釋放鎖 $this->redis->exec(); return true; } else { return false; #其他客戶端修改了鎖,不能刪除別人的鎖 } } } ?>
測試:PHP學習
1、建立createGroupChat.php(測試創建群組功能)PHP學習
執行代碼并創建568、569群組(群主為jack)PHP學習
include './ManyPullMessage.class.php'; $object=new ManyPullMessage('192.168.95.11'); #創建群組 $user='jack'; $arr=array('jane1','jane2'); $a=$object->createGroupChat($user,$arr); echo "<pre>"; print_r($a); echo "</pre>";die;
PHP學習
PHP學習
2、建立addMembers.php(測試添加成員功能)PHP學習
執行代碼并添加新成員PHP學習
include './ManyPullMessage.class.php'; $object=new ManyPullMessage('192.168.95.11'); $b=$object->addMembers('jack','568',array('jane1','jane2','jane3','jane4')); echo "<pre>"; print_r($b); echo "</pre>";die;
PHP學習
3、建立delete.php(測試群主刪除成員功能)PHP學習
include './ManyPullMessage.class.php'; $object=new ManyPullMessage('192.168.95.11'); #群主刪除成員 $c=$object->delMembers('jack', '568', array('jane1','jane4')); echo "<pre>"; print_r($c); echo "</pre>";die;
PHP學習
4、建立sendMessage.php(測試發送消息功能)PHP學習
多執行幾遍,568、569都發幾條PHP學習
include './ManyPullMessage.class.php'; $object=new ManyPullMessage('192.168.95.11'); #發送消息 $user='jane2'; $message='go go go'; $groupChatID=568; $arr=array('sender'=>$user, 'message'=>$message, 'time'=>time()); $d=$object->sendMessage($user,$groupChatID,$arr); echo "<pre>"; print_r($d); echo "</pre>";die;
PHP學習
PHP學習
5、建立getNewMessage.php(測試用戶獲取新消息功能)PHP學習
include './ManyPullMessage.class.php'; $object=new ManyPullMessage('192.168.95.11'); #用戶獲取新消息 $e=$object->getNewMessage('jane2'); echo "<pre>"; print_r($e); echo "</pre>";die;
PHP學習
6、建立getPartMessage.php(測試用戶獲取某個群組部分消息)PHP學習
(多發送幾條消息,用于測試.568中共18條數據)PHP學習
include './ManyPullMessage.class.php'; $object=new ManyPullMessage('192.168.95.11'); #用戶獲取某個群組部分消息 $f=$object->getPartMessage('jane2', 568, 1, 10); echo "<pre>"; print_r($f); echo "</pre>";die;
page=1,size=10PHP學習
PHP學習
page=2,size=10PHP學習
PHP學習
測試完畢,還需要別的功能可以自己進行修改添加測試.PHP學習
這次整理這篇文章相對比較趕,心里已經想著快點整理完趕緊學習其他的技術啦,哈哈.各位大神請留步,懇請各位給點學習redis的指導意見,本人職業方向是PHPPHP學習
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持維易PHP!PHP學習
轉載請注明本頁網址:
http://www.snjht.com/jiaocheng/1315.html