使用Redis來實現LBS的應用

rgks1468 8年前發布 | 16K 次閱讀 Redis NoSQL數據庫

來自: 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存儲的時候,設置有效時間

轉載:微信、陌陌 架構方案分析

 本文由用戶 rgks1468 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!