使用Redis來實現LBS的應用

微信、陌陌 架構方案分析

近兩年、手機應用,莫過於微信、陌陌之類最受歡迎;但實現原理,分享文章甚少。php

故,提出兩種方案,供分享;不對之處,敬請留言學習。html

目標

解決大型應用(微信、陌陌級別)中,用戶經緯度在不斷更新,用戶查找頻繁的問題。(每分鐘1000W級)git

方案A:

本方案前,請先閱讀:基於LBS功能應用的Geohash方案,看過該文章即可簡單知道;redis

一、僅需每分鐘將用戶的經緯度,上報到數據庫;算法

二、而後每次用戶查找附近好友時,經過 LIKE 'wm3yr3%',便可獲取數據庫

缺點:稍有必定數據量,對數據庫的鴨梨可想而知微信

方案B:使用Redis

策略

假象把中國分紅,若干個一平方千米的單元格數據結構

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;
    }
}
?>

  

性能測試

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人,數據佔用內存

  

總結

從測試結果來看,徹底能知足,微信、陌陌之類的性能要求;

尚可改進之處:

一、Geohash,可寫成PHP C擴展;或者其餘Geohash實現方式

二、Redis,內存消耗較大,可考慮redis集羣方案

三、本文僅查出本單元格用戶,提升精度,可查出周圍八個單元個,求交集

四、求出結果,如需按照由遠到近排序;讀出Redis經緯度,利用距離公式排序方可。(可參照上一篇文章

問題

1)假設我如今設定的hash長度爲7 ,那一個個hash值對應一個塊,如何獲得這個塊的座標區間呢?

例如,成都永豐立交的Geohash值爲:wm3yr31d2524;如取7位,則爲,wm3yr31;

根據Geohash的算法,那麼區間就會是 wm3yr3100000 ~ wm3yr31zzzzz;

根據如上兩值,經過「Geohash->經緯度」算出經緯度,可大體肯定區間。

2)若是用戶上報的位置信息有時效性(好比:15秒內有效)如何處理?

能夠在redis存儲的時候,設置有效時間

相關文章
相關標籤/搜索