咱們最近有個項目需求,實現面對面匹配好友功能。後端
預想的場景是兩個用戶面對面的時候能夠很方便地經過搖一搖或者其它互動方式快速找到對方來達成好友。網絡
關鍵點數據結構
後端服務基於GPS位置快速匹配附近的人。併發
第一時間想到的方式是直接計算用戶的距離,若用戶距離在要求的範圍以內,則這兩個用戶達成匹配。測試
問題.net
這個方案須要遍歷全量用戶,計算量很大,匹配的效率很低。3d
geohash的思想是將二維的經緯度轉換成一維的字符串,geohash有如下三個特色:cdn
geohash精度信息以下,字符串越長,表示的範圍越精確。blog
Redis在3.2版本以後提供了geo地理位置的支持。咱們能夠很方便地經過Redis維護地理位置,快速查詢某個座標附近特定距離內的地理位置。排序
問題
在通常場景裏面商店的地理位置基本不變,信息也是長期有效。因此經過Redis維護全部的地理位置也比較合適。但是在咱們的需求場景,好友匹配是個瞬時場景。用戶的匹配請求自己是有時效性的,若直接使用Redis的GeoHash來維護地理位置還須要考慮信息的刪除等狀況,反而變得複雜。
咱們能夠直接使用Redis的Hash結構來維護全部待匹配的用戶。field爲用戶ID,value爲用戶匹配相關的信息,其中包含用戶座標對應的GeoHash。而後經過定時任務獲取全部待匹配的用戶信息,根據geohash字段排序,再計算相鄰用戶是否在要求的距離以內,若條件符合則能夠達成匹配。
關鍵點
兩個用戶在彼此附近,那他們的geohash字符串會很類似,在按geohash排序時,這兩個用戶信息下標也會相鄰。
R樹是用來作空間數據存儲的樹狀數據結構。在外賣場景不少時候會使用R樹來維護商家的配送範圍,爲用戶篩選出在其附近的商家。在本次需求R樹不適用。
最終咱們採用Redis Hash+GeoHash的方案,在實施過程當中也有其它問題須要考慮。
對用戶全量集合進行拆分,先按用戶城市來作一次分桶。大機率上只有同一個城市的用戶纔可能出如今附近。另外用戶匹配請求有效時間一般比較短,因此通過分桶以後全量集合大小應該可控。
在咱們進行匹配任務的同時用戶可能會取消匹配。在進行匹配操做時,咱們會獲取全量的用戶集,並刪除對應的全量集數據(經過pipeline方式實現原子性)。因此此時用戶取消匹配會失敗,簡化邏輯。若最後用戶沒有匹配上,再將相應的信息批量回寫到用戶集合便可。
在測試過程當中發現Android端和iOS端會出現座標系不統一的狀況,這種狀況下因出現位置偏移致使沒法匹配。能夠經過座標系轉換的方式統一座標系。
以下圖所示,圖片來自網絡。邊緣附近的點,黃色的點要比黑色的點更加靠近紅點,可是因爲黑點跟紅點的GeoHash前綴匹配數目更多,所以獲得黑點更加靠近紅點的結果 。通常的處理方式是經過篩選周圍8個區域內的全部點,而後計算距離獲得知足條件結果。
在咱們的匹配定時任務裏面,GeoHash只是做爲距離遠近的參考依據,理論上相鄰區域的用戶也會在相鄰下標位置,而後再經過計算距離判斷條件是否符合。
咱們經過GeoHash來實現面對面匹配功能。GeoHash將二維的經緯度轉換成一維的字符串,咱們再對一維的字符串進行排序操做,這樣能夠快速找到相鄰的用戶信息。