交友系統設計:哪種地理空間鄰近算法更快?

小熊學Java:https://javaxiaobear.cn

交友與婚戀是人們最基本的需求之一。隨著互聯網時代的不斷發展,移動社交軟件已經成為了人們生活中必不可少的一部分。然而,熟人社交并不能完全滿足年輕人的社交與情感需求,于是陌生人交友平臺悄然興起。

我們決定開發一款基于地理位置服務(LBS)的應用,為用戶匹配鄰近的、互相感興趣的好友,應用名稱為“Liao”。

Liao 面臨的技術挑戰包括:面對海量的用戶,如何為其快速找到鄰近的人,可以選擇的地理空間鄰近算法有哪些?Liao 如何在這些算法中選擇出最合適的那個?

1、需求分析

Liao 的客戶端是一個移動 App,用戶打開 App 后,上傳、編輯自己的基本信息,然后系統(推薦算法)根據其地理位置和個人信息,為其推薦位置鄰近的用戶。用戶在手機上查看對方的照片和資料,如果感興趣,希望進一步聯系,就向右滑動照片;如果不感興趣,就向左滑動照片。

如果兩個人都向右滑動了對方,就表示他們互相感興趣。系統就通知他們配對成功,并為他們開啟聊天功能,可以更進一步了解對方,決定是否建立更深入的關系。Liao 的功能用例圖如下:

image-20231218221315843

用戶規模分析

Liao 的目標用戶是全球范圍內的中青年單身男女,預估目標用戶超過 10 億,系統按 10 億用戶進行設計。

2、概要設計

Liao 的系統架構采用典型的微服務架構設計方案,用戶通過網關服務器訪問具體的微服務,如下圖:

image-20231219231710903

可知,首先,用戶所有請求都通過統一的網關服務器處理。網關服務器負責限流、防攻擊、用戶身份識別及權限驗證、微服務調用及數據聚合封裝等,而真正的業務邏輯則通過訪問微服務來完成。Liao 的關鍵微服務有:用戶微服務、圖片微服務、配對微服務、聊天微服務、推薦微服務、鄰近算法微服務等。Liao 的網關預計將承擔每天百億次規模的訪問壓力。

用戶微服務管理用戶的個人信息、興趣愛好以及交友偏好等,此外也負責用戶登錄服務,只有登錄用戶才能訪問系統。因為需要存儲十億條用戶數據,所以用戶數據庫采用分片的MySQL 數據庫。

圖片微服務用于管理用戶照片,提供用戶照片存儲及展示的功能。Liao 需要存儲的圖片數大約幾百億張。我們使用 Nginx 作為圖片服務器,圖片服務器可以線性擴容,每寫滿一臺服務器(及其 Slave 服務器),就繼續寫入下一臺服務器。服務器 IP、圖片路徑則記錄在用戶數據庫中。同時,購買 CDN 服務,緩存熱門的用戶照片。

配對微服務負責將互相喜歡的用戶配對,通知用戶,并加入彼此的通訊錄中。用戶每次右劃操作都調用該微服務。系統設置一個用戶每天可以喜歡(右劃)的人是有上限的,但是,對于活躍用戶而言,長期積累下來,喜歡的人的數量還是非常大的,因此配對微服務會將數據發送給一個流式大數據引擎進行計算。

推薦微服務負責向用戶展示其可能感興趣的、鄰近的用戶。因此,一方面,推薦微服務需要根據用戶操作、個人興趣、交友偏好調用協同過濾等推薦算法進行推薦,另一方面必須保證推薦的用戶在當前用戶的附近。

3、詳細設計

詳細設計主要關注鄰近位置算法,也就是,如何根據用戶的地理位置尋找距其一定范圍內的其他用戶。

我們可以通過 Liao App 獲取用戶當前經、緯度坐標,然后根據經、緯度,計算兩個用戶之間的距離,距離計算公式采用半正矢公式:

image-20231219231858953

其中 r 代表地球半徑, 表示緯度, 表示經度。

但是,當我們有 10 億用戶的時候,如果每次進行當前用戶匹配都要和其他所有用戶做一次距離計算,然后再進行排序,那么需要的計算量至少也是千億級別,這樣的計算量是我們不能承受的。通常的空間鄰近算法有以下 4 種,我們一一進行分析,最終選擇出最合適的方案。

1、SQL 鄰近算法

我們可以將用戶經、緯度直接記錄到數據庫中,緯度記錄在 latitude 字段,經度記錄在longitude 字段,用戶當前的緯度和經度為 X,Y,如果我們想要查找和當前用戶經、緯度距離 D 之內的其他用戶,可以通過如下 SQL 實現。

select * from users where latitude between X-D and X+D and longtitude between

這樣的 SQL 實現起來比較簡單,但是如果有十億用戶,數據分片在幾百臺服務器上,SQL執行效率就會很低。而且我們用經、緯度距離進行近似計算,在高緯度地區,這種近似計算的偏差還是非常大的。

同時“between X-D and X+D”以及“between Y-D and Y+D”也會產生大量中間計算數據,這兩個 betwen 會先返回經度和緯度各自區間內的所有用戶,再進行交集 and 處理,如下圖。

image-20231219232028935

我們的用戶量非常大,而計算鄰近好友又是一個非常高頻的訪問,同時,分片數據庫進行集合計算需要在中間代理服務器或應用程序服務器完成計算,因此,這樣的交集計算帶來計算負載壓力是我們的系統完全不能承受的。所以這個方案可以被放棄。

2、地理網格鄰近算法

為了減少上述交集計算使用的中間數據量,我們將整個地球用網格進行劃分,如下圖:

image-20231219232111614

事實上,我們劃分的網格遠比圖中示意的要密集得多,赤道附近,經、緯度方向每 10 公里一個網格。

這樣每個用戶必然會落入到一個網格中,我們在用戶表中記錄用戶所在的網格ID(gridID),然后借助這個字段進行輔助查找,將查找范圍限制在用戶所在的網格(gridIDx0)及其周圍 8 個網格(gridIDx1 ~ gridIDx8)中,可以極大降低中間數據量,SQL 如下:

select * from users where latitude between X-D and X+D and longtitude between

這條 SQL 要比上面 SQL 的計算負載壓力小得多,但是對于高頻訪問的分片數據庫而言,用這樣的 SQL 進行鄰近好友查詢依然是不能承受的,同樣距離精度也不滿足要求。

但是基于這種網格設計思想,我們發現,我們可以不通過數據庫就能實現鄰近好友查詢:我們可以將所有的網格及其包含的用戶都記錄在內存中。當我們進行鄰近查詢時,只需要在內存中計算用戶及其鄰近的 8 個網格內的所有用戶的距離即可。

我們可以估算下所有用戶經、緯度都加載到內存中需要的內存量:1G × 3 × 4B = 12GB(用戶 ID、經度、緯度,都采用 4 個字節編碼,總用戶數 1G)。這個內存量是完全可以接受的。

實際上,通過恰當地選擇網格的大小,我們不停訪問當前用戶位置周邊的網格就可以由近及遠不斷得到鄰近的其他用戶,而不需要再通過 SQL 來得到。那么如何選擇網格大小?如何根據用戶位置得到其所在的網格?又如何得到當前用戶位置周邊的其他網格呢?我們看
下實踐中更常用的動態網格和 GeoHash 算法。

3、動態網格算法

事實上,不管如何選擇網格大小,可能都不合適。因為在陸家嘴即使很小的網格可能就包含近百萬的用戶,而在可可西里,非常大的網格也包含不了幾個用戶。

因此,我們希望能夠動態設定網格的大小,如果一個網格內用戶太多,就把它分裂成幾個小網格,小網格內如果用戶還是太多,繼續分裂更小的網格,如下圖:

image-20231219232347384

這是一個四叉樹網格結構,開始的時候整個地球只有一個網格,當用戶增加,超過閾值(500 個用戶)的時候,就分裂出 4 個子樹,4 個子樹對應父節點網格的 4 個地理子網格。同時,將用戶根據位置信息重新分配到 4 個子樹中。同樣,如圖中所示,如果某個子樹中的用戶增加,超過了閾值,該子樹繼續分裂成 4 個子樹。

因此,我們可以將全球用戶分配在這樣一個 4 叉樹網格結構中,所有的用戶都必然在這個4 叉樹的葉子節點中,而且每個節點內包含的用戶數不超過 500 個。那么,陸家嘴的網格可能就會很小,而可可西里的網格就會很大,太平洋對應的網格可能有幾千公里。

當給定當前用戶的經、緯度,查詢鄰近用戶的時候,首先從根節點開始查找,如果根節點就是葉子節點,那么直接遍歷根節點中的所有用戶,計算距離即可。如果根節點不是葉子節點,那么根據給定的經、緯度判斷其在網格中的位置,左上、右上、右下、左下,4 個位置,順序對應 4 個子樹,根據網格位置訪問對應的子樹。如果子樹是葉子節點,那么在葉子節點中查找,如果不是葉子節點,繼續上面的過程,直到葉子節點。

上面的過程只能找到當前用戶所在網格的好友,如何查找鄰近網格的其他用戶呢?事實上,我們只需要將 4 叉樹所有的葉子節點順序組成一個雙向鏈表,每個節點在鏈表上的若干個前驅和后繼節點正好就是其地理位置鄰近的節點。

動態網格也叫 4 叉樹網格,在空間鄰近算法中較為常用,也能滿足 Liao 的需求。但是編程實現稍稍有點麻煩,而且如果網格大小設計不合適,導致樹的高度太高,每次查找需要遍歷的路徑太長,性能結果也比較差。我們再看下性能和靈活性更好的 GeoHash 算法。

4、GeoHash 算法

除了動態網格算法,GeoHash 事實上是另外一種變形了的網格算法,同時也是 Redis 中Geo 函數使用的算法。GeoHash 是將網格進行編碼,然后根據編碼進行 Hash 存儲的一種算法。

經、緯度數字的不同精度,意味著經、緯度的誤差范圍,比如保留經、緯度到小數點后第1 位,那么誤差范圍最大可能會達到 11 公里(在赤道附近)。也就是說,小數點后 1 位精度的經、緯度,其覆蓋范圍是一個 11km * 11km 的網格。

那么,我們用小數點后 1 位精度的經、緯度做 key,網格內的用戶集合做 value,就可以構建一個 Hash 表的 <key, value> 對。通過查找這個 KV 對及其周圍 8 個網格的 KV 對,計算這些 value 內所有用戶和當前用戶的距離,就可以找到鄰近 11 公里內的所有用戶。

實踐中,redis 的 GeoHash 并不會直接用經、緯度做 key,而是采用一種基于 Z 階曲線的編碼方式,將二維的經、緯度,轉化為一維的二進制數字,再進行 base32 編碼,具體過程如下:

  1. 首先,分別針對經度和緯度,求取當前區間(對于緯度而言,開始的區間就是[-90, 90], 對于經度而言,開始區間就是[-180, 180])的平均值,將當前區間分為兩個區間。然后用用戶的經、緯度和區間平均值進行比較,用戶經、緯度必然落在兩個區間中的一個,如果大于平均值,那么取 1,如果小于平均值,那么取 0。

  2. 繼續求取當前區間的平均值,進一步將當前區間分為兩個區間。如此不斷重復,可以在經度和緯度方向上,得到兩個二進制數。這個二進制數越長,其所在的區間越小,精度越高。

    下圖表示經、緯度 <43.60411, -5.59041> 的二進制編碼過程,最終得到緯度 12 位編碼,經度 13 位編碼。

    image-20231219232619732 image-20231219232637085

    得到兩個二進制數后,再將它們合并成一個二進制數。合并規則是,從第一位開始,奇數位為經度,偶數位為緯度,上面例子合并后的結果為 01101 11111 11000 00100 00010,共 25 位二進制數。

  3. 將 25 位二進制數劃分成 5 組,每組 5 個二進制數,對應的 10 進制數是 0-31,采用Base32 編碼,可以得到一個 5 位字符串,Base32 編碼表如下:

    image-20231219232717325

    編碼計算過程如下:

    image-20231219232736133

  4. 最后得到一個字符串“ezs42”,作為 Hash 表的 key。25 位二進制的 GeoHash 編碼,其誤差范圍大概 2.4 公里,即對應一個2.4km * 2.4km 的網格。網格內的用戶都作為value 放入到 Hash 表中。

一般說來,通過選擇 GeoHash 的編碼長度,實現不同大小的網格,就可以滿足我們鄰近交友的應用場景了。但是在 Redis 中,需要面對更通用的地理位置計算場景,所以 Redis中的 GeoHash 并沒有用 Hash 表存儲,而是用跳表存儲。

Redis 使用 52 位二進制的 GeoHash 編碼,誤差范圍 0.6 米。Redis 將編碼后的二進制數按照 Z 階曲線的布局,進行一維化展開。即將二維的經、緯度上的點,用一條 Z 型曲線連接起來,Z 階曲線布局示例如下圖。

image-20231219232819378

事實上,所謂的 Z 階曲線布局,本質其實就是基于 GeoHash 的二進制排序。將這些經過編碼的 2 進制數據用跳表存儲。查找用戶的時候,可以快速找到該用戶,沿著跳表前后檢索,得到的就是鄰近的用戶。

5、Liao 的最終算法選擇

Liao 的鄰近算法最終選擇使用 Hash 表存儲的 GeoHash 算法,經度采用 13bit 編碼,緯度采用 12bit 編碼,即最后的 GeoHash 編碼 5 個字符,每個網格 2.4km * 2.4km = 5km,將整個地球分為 個網格,去掉海洋和幾乎無人生存的荒漠極地,需要存儲的 Hash 鍵不到 500 萬個,采用 Hash 表存儲。Hash 表的 key 是 GeoHash 編碼,value 是一個 List,其中包含了所有相同 GeoHash 編碼的用戶 ID。

查找鄰近好友的時候,Liao 將先計算用戶當前位置的 GeoHash 值(5 個字符),然后從Hash 表中讀取該 Hash 值對應的所有用戶,即在同一個網格內的用戶,進行匹配,將滿足匹配條件的對象返回給用戶。如果一個網格內匹配的對象數量不足,計算周圍 8 個網格的GeoHash 值,讀取這些 Hash 值對應的用戶列表,繼續匹配。

4、總結

算法是軟件編程中最有技術挑戰性,也最能考驗一個人編程能力的領域。所以很多企業面試的時候特別喜歡問算法類的問題,即使這些算法和將來的工作內容關系不大,面試官也可以憑借這些問題對候選人的專業能力和智力水平進行評判,而且越是大廠的面試越是如此。

架構和算法通常是一個復雜系統的一體兩面,架構是關于整體系統是如何組織起來的,而算法則是關于核心功能如何處理的。我們專欄大多數案例也都體現了這種一體兩面,很多案例設計都有一兩個核心算法,比如短 URL 生成與預加載算法、縮略圖生成與推薦算法、

本篇的空間鄰近算法以及下一篇要講的倒排索引與 PageRank 算法,都展現了這一點。一個合格的架構師除了要掌握系統的整體架構,也要能把握住這些關鍵的算法,才能在系統的設計和開發中做到心中有數、控制自如。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/373069.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/373069.shtml
英文地址,請注明出處:http://en.pswp.cn/news/373069.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux ntp 'ntp_request.c'遠程拒絕服務漏洞,NTP 'ntp_request.c'遠程拒絕服務漏洞

NTP ntp_request.c遠程拒絕服務漏洞發布日期&#xff1a;2013-12-30更新日期&#xff1a;2014-01-09受影響系統&#xff1a;NTP NTP 描述&#xff1a;--------------------------------------------------------------------------------BUGTRAQ ID: 64692CVE(CAN) ID: CVE-20…

指針的內容 ; 指針的地址 指針所指向的內容 指針的類型 指針所指向的類型...

這幾個個東東很具有迷惑性。 int a10; //假設a的地址是 0x0000004C int *p; //假設p的地址是 0x0035FA94 p&a; 指針的內容&#xff1a;指針里面存放的是地址。 指針p里面存放的是a的地址(&a)。即指針p里面存放的內容是0x0000004C。 指針的地址&#xff…

Apache Camel教程– EIP,路由,組件,測試和其他概念的簡介

公司之間的數據交換增加了很多。 必須集成的應用程序數量也增加了。 這些接口使用不同的技術&#xff0c;協議和數據格式。 但是&#xff0c;這些應用程序的集成應以標準化的方式建模&#xff0c;有效實現并由自動測試支持。 企業集成模式&#xff08;EIP&#xff09;[1]中存在…

iOS開發UI篇—UITableview控件簡單介紹

一、基本介紹 在眾多移動應?用中,能看到各式各樣的表格數據 。 在iOS中,要實現表格數據展示,最常用的做法就是使用UITableView&#xff0c;UITableView繼承自UIScrollView,因此支持垂直滾動,?且性能極佳 。 UITableview有分組和不分組兩種樣式&#xff0c;可以在storyboard或…

PL/SQL 08 異常 exception

--PL/SQL錯誤 編譯時 運行時--運行時的出錯處理 EXCEPTION --異常處理塊DECLARE …BEGIN …EXCEPTION WHEN OTHERS THEN handler_error(…);END; --用戶自定義的異常DECLARE e_TooManyStudents EXCEPTION; …BEGIN … RAISE e_TooManyStudents; …EXCEPTION WHEN e_TooMany…

html鼠標事件沒反應,鼠標有時候點擊沒反應怎么解決

關于鼠標有時候點擊沒反應的問題&#xff0c;一些網友顯得一頭霧水&#xff0c;那這該怎么解決呢?下面就由小編來給你們說說鼠標有時候點擊沒反應的原因及解決方法吧&#xff0c;希望可以幫到你們哦!鼠標有時候點擊沒反應的解決方法一&#xff1a;一&#xff0c;系統繁忙&…

動態ADF火車:以編程方式添加火車停靠站

我將展示如何以編程方式“即時”將火車停靠站添加到ADF火車中。 在我的用例中&#xff0c;我有一些票務預訂應用程序。 它具有訓練模型的有限任務流。 在火車的第一站&#xff0c;用戶輸入乘客的數量&#xff0c;在隨后的站點&#xff0c;他們輸入一些乘客的信息。 帶有乘客信息…

修改sqlserver的數據庫排序規則語句

alter database SOETMS collate Chinese_PRC_CI_AS 轉載于:https://www.cnblogs.com/lxboy2009/p/5481977.html

關于存儲過程權限

關于ORACLE賬號的權限問題&#xff0c;一般分為兩種權限&#xff1a; 系統權限: 允許用戶執行特定的數據庫動作&#xff0c;如創建表、創建索引、創建存儲過程等 對象權限: 允許用戶操縱一些特定的對象&#xff0c;如讀取視圖&#xff0c;可更新某些列、執行存儲過程等 像這種查…

寧波鎮海2021年高考成績查詢,最新!2021年,寧波鎮海區的這14所中小學“爆了...

寧波鎮海區教育局發布了2021年公辦學校小學一年級、初中一年級招生第一次預警&#xff0c;這也是寧波首個發布2021年公辦學校招生預警的縣、市、區。根據最新數據摸排&#xff0c;寧波鎮海區有8所小學紅色預警、2所初中紅色預警&#xff0c;1所小學黃色預警、3所初中黃色預警。…

用Java解決生產者-消費者問題

當我們嘗試多線程編程時&#xff0c;生產者-消費者問題是最常見的問題之一。 盡管不像多線程編程中的其他一些問題那樣具有挑戰性&#xff0c;但是錯誤地實現此問題可能會造成應用程序混亂。 生產的物品將不使用&#xff0c;開始的物品將被跳過&#xff0c;消耗量取決于生產是在…

哪位科學家奠定了計算機結構理論,計算機等級考試一級理論知識選擇題題庫(1-50)...

領域中的問題為主的數值計算稱為科學計算B)計算機應用可分為數值應用和非數值應用兩類C)計算機各部件之間有兩股信息流&#xff0c;即數據流和控制流D)對信息(即各種形式的數據)進行收集、儲存、加工與傳輸等一系列活動的總稱為實時控制答案&#xff1a;D32. 金卡工程是我國正在…

axios 參數為payload的解決方法

1. 添加頭部headers headers: {Content-Type: application/x-www-form-urlencoded,}, axios.post(url, {a: 1, b:2}, {headers: {Content-Type: application/x-www-form-urlencoded,}, }).then(response > response.data).then(err > {console.log(err);}); 2. 在Browser…

超出了GC開銷限制– Java堆分析

這篇文章是我們原來的GC超出限制的問題模式帖子的延續。 正確的Java堆分析對于消除O??utOfMemoryError&#xff1a;GC開銷問題至關重要。 如果您不熟悉此Java HotSpot 1.6錯誤&#xff0c;建議您首先閱讀有關此主題的第一篇文章 。 本文將為您提供一個示例程序和一個教程&…

開燈問題

開燈問題 時間限制&#xff1a;3000 ms | 內存限制&#xff1a;65535 KB難度&#xff1a;1描述有n盞燈&#xff0c;編號為1~n&#xff0c;第1個人把所有燈打開&#xff0c;第2個人按下所有編號為2 的倍數的開關&#xff08;這些燈將被關掉&#xff09;&#xff0c;第3 個人按…

計算機科學基本理論,計算機科學的基礎知識.ppt

計算機科學的基礎知識第二章 計算機科學的基礎知識 本章學習目標&#xff1a; 數據的理解、分類與表示 計算機的基本結構與工作原理 程序設計基礎 算法基礎 2.1 數據類型 2.2 計算機內部的數據 2.3 表示數據 2.4 十進制表示法 2.5 二進制表示法 2.6 十六進制表示法 2.7 八進制表…

損壞注冊表的原因

軟件: &#xff08;1&#xff09;應用程序錯誤 &#xff08;2&#xff09;驅動程序不兼容或使用了錯誤的應用程序 &#xff08;3&#xff09;應用程序在注冊表中添加了錯誤的內容 &#xff08;4&#xff09;應用程序添加了錯誤的數據文件和應用程序之間的聯系 硬件: &#xff0…

cdockpane限制調整大小_影視后期制作小伙伴必看:使用AU對聲音質量進行調整的三大技巧...

一、增幅一般人進入AU的音頻調整界面&#xff0c;會使用圖中的旋鈕進行音量調整&#xff0c;這種操作是錯誤的&#xff0c;因為通過拖拽并不能確定調整音量的大小幅度&#xff0c;精準度極低&#xff0c;反復操作才能試出最佳音量&#xff0c;效率極低。最優方案是使用左側效果…

html5css3js文件作業,HTML5 CSS3 JavaScriptWeb前端開發自測試卷2.docx

自測試卷2一、選擇題1&#xff0e;使用標簽在網頁中成功地添加一張圖片&#xff0c;必不可少的屬性是( )。A&#xff0e;alt B&#xff0e;title C&#xff0e;src D&#xff0e;width2&#xff0e;使用CSS設置鼠標放置在鏈接上時的樣式應使用以下哪個選擇器( )。A&#xff0e;…

線程故事:Web應用程序中的ThreadLocal

本周&#xff0c;我花了一些合理的時間來消除Web應用程序中的所有ThreadLocal變量。 原因是他們造成了類加載器泄漏&#xff0c;我們不能再適當地取消部署我們的應用程序。 取消部署應用程序后&#xff0c;當GC根目錄繼續引用應用程序對象時&#xff0c;將發生類加載器泄漏。 如…