近兩年、手機應用,莫過於微信、陌陌之類最受歡迎;但實現原理,分享文章甚少。php
故,提出兩種方案,供分享;不對之處,敬請留言學習。html
解決大型應用(微信、陌陌級別)中,用戶經緯度在不斷更新,用戶查找頻繁的問題。(每分鐘1000W級)git
本方案前,請先閱讀:基於LBS功能應用的Geohash方案,看過該文章即可簡單知道;redis
一、僅需每分鐘將用戶的經緯度,上報到數據庫;算法
二、而後每次用戶查找附近好友時,經過 LIKE 'wm3yr3%',便可獲取數據庫
缺點:稍有必定數據量,對數據庫的鴨梨可想而知微信
假象把中國分紅,若干個一平方千米的單元格數據結構
1)、用戶位置的變動,理解爲一個單元格移動到另一個單元格(或者不移動)架構
2)、用戶查找附近,理解爲查找,本身所在方塊的的全部人app
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; } } ?>
<?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); } ?>
<?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人,數據佔用內存
從測試結果來看,徹底能知足,微信、陌陌之類的性能要求;
一、Geohash,可寫成PHP C擴展;或者其餘Geohash實現方式
二、Redis,內存消耗較大,可考慮redis集羣方案
三、本文僅查出本單元格用戶,提升精度,可查出周圍八個單元個,求交集
四、求出結果,如需按照由遠到近排序;讀出Redis經緯度,利用距離公式排序方可。(可參照上一篇文章)
1)假設我如今設定的hash長度爲7 ,那一個個hash值對應一個塊,如何獲得這個塊的座標區間呢?
例如,成都永豐立交的Geohash值爲:wm3yr31d2524;如取7位,則爲,wm3yr31;
根據Geohash的算法,那麼區間就會是 wm3yr3100000 ~ wm3yr31zzzzz;
根據如上兩值,經過「Geohash->經緯度」算出經緯度,可大體肯定區間。
2)若是用戶上報的位置信息有時效性(好比:15秒內有效)如何處理?
能夠在redis存儲的時候,設置有效時間