使用Redis來實現LBS的應用
來自: http://blog.csdn.net//jiao_fuyou/article/details/36179867
微信、陌陌 架構方案分析
近兩年、手機應用,莫過于微信、陌陌之類最受歡迎;但實現原理,分享文章甚少。
故,提出兩種方案,供分享;不對之處,敬請留言學習。
目標
解決大型應用(微信、陌陌級別)中,用戶經緯度在不斷更新,用戶查找頻繁的問題。(每分鐘1000W級)
方案A:
本方案前,請先閱讀:基于LBS功能應用的Geohash方案,看過該文章便可簡單知道;
1、僅需每分鐘將用戶的經緯度,上報到數據庫;
2、然后每次用戶查找附近好友時,通過 LIKE 'wm3yr3%',即可獲取
缺點:稍有一定數據量,對數據庫的鴨梨可想而知
方案B:使用Redis
策略
假象把中國分成,若干個一平方公里的單元格
1)、用戶位置的變更,理解為一個單元格移動到另外一個單元格(或者不移動)
2)、用戶查找附近,理解為查找,自己所在方塊的的所有人
數據結構
1)、用戶基本信息 緯度、經度、GeoHash值(經緯度,僅用于后期距離計算)
2)、單元格 集合(用戶1,用戶2,…)
存儲工具
1)、redis string(key->value) 結構,存儲用戶基本信息
2)、redis set(集合) 結構,以GeoHash值,前6位作為key(約表示一平方千米),存儲單元格的用戶群
算法流程
1)、更新用戶信息,先刪除用戶原所在集合,再更新當前用戶信息,最后更新當前用戶所在集合
2)、查找附近,直接查找,所在單元格集合所有用戶ID
具體實現
<?php /** * LBS核心類 * @author name <simplephp@163.com> * @site http://www.wubiao.info */ include_once('geohash.class.php'); class LBS { //索引長度 6位 protected $index_len = 6; protected $redis; protected $geohash; public function __construct() { //redis $this->redis = new Redis(); $this->redis->pconnect('127.0.0.1','6379'); //geohash $this->geohash = new Geohash(); } /** * 更新用戶信息 * @param mixed $latitude 緯度 * @param mixed $longitude 經度 */ public function upinfo($user_id,$latitude,$longitude) { //原數據處理 //獲取原Geohash $o_hashdata = $this->redis->hGet($user_id,'geo'); if (!empty($o_hashdata)) { //原索引 $o_index_key = substr($o_hashdata, 0, $this->index_len); //刪除 $this->redis->sRem($o_index_key,$user_id); } //新數據處理 //緯度 $this->redis->hSet($user_id,'la',$latitude); //經度 $this->redis->hSet($user_id,'lo',$longitude); //Geohash $hashdata = $this->geohash->encode($latitude,$longitude); $this->redis->hSet($user_id,'geo',$hashdata); //索引 $index_key = substr($hashdata, 0, $this->index_len); //存入 $this->redis->sAdd($index_key,$user_id); return true; } /** * 獲取附近用戶 * @param mixed $latitude 緯度 * @param mixed $longitude 經度 */ public function serach($latitude,$longitude) { //Geohash $hashdata = $this->geohash->encode($latitude,$longitude); //索引 $index_key = substr($hashdata, 0, $this->index_len); //取得 $user_id_array = $this->redis->sMembers($index_key); return $user_id_array; } } ?>
性能測試
1)模擬數據上報
<?php /** * 模擬數據上報 * @author name <simplephp@163.com> * @site http://www.wubiao.info */ include_once('lbs.class.php'); $b_time = microtime(true); $n = 0; while(1) { //user_id 1~1000000 $user_id = rand(1, 1000000); //latitude 30.59773~30.726786 $rand_latitude = rand(30597730, 30726786); $latitude = $rand_latitude / 1000000; //longitude 103.983192 ~104.16069 $rand_longitude = rand(103983192, 104160690); $longitude = $rand_longitude / 1000000; $lbs = new lbs(); $lbs->upinfo($user_id, $latitude, $longitude); $n++; mylog($n); $e_time = microtime(true); if(($e_time - $b_time) >= 60) { exit; } } function mylog($content) { file_put_contents('upinfo.log', $content . "\r\n", FILE_APPEND); } ?>
2)模擬附近查找
<?php /** * 模擬附近查找 * @author name <simplephp@163.com> * @site http://www.wubiao.info */ include_once('lbs.class.php'); $b_time = microtime(true); $n = 0; while(1) { //latitude 30.59773~30.726786 $rand_latitude = rand(30597730, 30726786); $latitude = $rand_latitude / 1000000; //longitude 103.983192 ~104.16069 $rand_longitude = rand(103983192, 104160690); $longitude = $rand_longitude / 1000000; $lbs = new lbs(); $re = $lbs->serach($latitude,$longitude); $n++; mylog($n); $e_time = microtime(true); if(($e_time-$b_time) >= 60) { exit; } } function mylog($content) { file_put_contents('search.log', $content . "\r\n", FILE_APPEND); } ?>
測試環境
vmWare虛擬機,內存256M,主頻2.93GHz
性能結果
模擬了100W活躍用戶行為,不斷更新,不斷查找附近好友
//60 seconds insert 88544 //60 seconds search 117660 //成都 100W人,數據占用內存 11.97M
總結
從測試結果來看,完全能滿足,微信、陌陌之類的性能要求;
尚可改進之處:
1、Geohash,可寫成PHP C擴展;或者其他Geohash實現方式
2、Redis,內存消耗較大,可考慮redis集群方案
3、本文僅查出本單元格用戶,提高精度,可查出周圍八個單元個,求交集
4、求出結果,如需按照由遠到近排序;讀出Redis經緯度,利用距離公式排序方可。(可參照上一篇文章)
問題
1)假設我現在設定的hash長度為7 ,那一個個hash值對應一個塊,如何得到這個塊的坐標區間呢?
例如,成都永豐立交的Geohash值為:wm3yr31d2524;如取7位,則為,wm3yr31;
根據Geohash的算法,那么區間就會是 wm3yr3100000 ~ wm3yr31zzzzz;
根據如上兩值,通過“Geohash->經緯度”算出經緯度,可大致確定區間。
2)如果用戶上報的位置信息有時效性(比如:15秒內有效)如何處理?
可以在redis存儲的時候,設置有效時間
轉載:微信、陌陌 架構方案分析