【Redis】数据结构的应用——GEO 【搜索“附近的餐馆”、在打车软件上叫车】
搜索“附近的餐館”、在打車軟件上叫車,這些都離不開基于位置信息服務(wù)(Location-Based Service,LBS)的應(yīng)用。LBS 應(yīng)用訪問的數(shù)據(jù)是和人或物關(guān)聯(lián)的一組經(jīng)緯度信息,而且要能查詢相鄰的經(jīng)緯度范圍
- 以叫車服務(wù)為例,來分析下 LBS 應(yīng)用中經(jīng)緯度的存取特點(diǎn)。
可以看到,一輛車(或一個(gè)用戶)對(duì)應(yīng)一組經(jīng)緯度,并且隨著車(或用戶)的位置移動(dòng),相應(yīng)的經(jīng)緯度也會(huì)變化。
使用hash進(jìn)行key:車輛ID,value:車輛經(jīng)緯度
這種數(shù)據(jù)記錄模式屬于一個(gè) key(例如車 ID)對(duì)應(yīng)一個(gè) value(一組經(jīng)緯度)。當(dāng)有很多車輛信息要保存時(shí),就需要有一個(gè)集合來保存一系列的 key 和 value。Hash 集合類型可以快速存取一系列的 key 和 value,正好可以用來記錄一系列車輛 ID 和經(jīng)緯度的對(duì)應(yīng)關(guān)系,所以,我們可以把不同車輛的 ID 和它們對(duì)應(yīng)的經(jīng)緯度信息存在 Hash 集合中,如下圖所示:
Hash 類型的 HSET 操作命令,會(huì)根據(jù) key 來設(shè)置相應(yīng)的 value 值,所以,我們可以用它來快速地更新車輛變化的經(jīng)緯度信息。
問題:
對(duì)于一個(gè) LBS 應(yīng)用來說,除了記錄經(jīng)緯度信息,還需要根據(jù)用戶的經(jīng)緯度信息在車輛的 Hash 集合中進(jìn)行范圍查詢。一旦涉及到范圍查詢,就意味著集合中的元素需要有序,但 Hash 類型的元素是無序的,顯然不能滿足我們的要求。
Sorted Set類型進(jìn)行記錄
GEO 類型的底層數(shù)據(jù)結(jié)構(gòu)就是用 Sorted Set 來實(shí)現(xiàn)的
Sorted Set 類型也支持一個(gè) key 對(duì)應(yīng)一個(gè) value 的記錄模式,其中,key 就是 Sorted Set 中的元素,而 value 則是元素的權(quán)重分?jǐn)?shù)。更重要的是,Sorted Set 可以根據(jù)元素的權(quán)重分?jǐn)?shù)排序,支持范圍查詢。這就能滿足 LBS 服務(wù)中查找相鄰位置的需求了。
用 Sorted Set 來保存車輛的經(jīng)緯度信息時(shí),Sorted Set 的元素是車輛 ID,元素的權(quán)重分?jǐn)?shù)是經(jīng)緯度信息,如下圖所示:
Sorted Set 元素的權(quán)重分?jǐn)?shù)是一個(gè)浮點(diǎn)數(shù)(float 類型),而一組經(jīng)緯度包含的是經(jīng)度和緯度兩個(gè)值,是沒法直接保存為一個(gè)浮點(diǎn)數(shù)的,那具體該怎么進(jìn)行保存呢?
- GEO 類型中的 GeoHash 編碼
Redis 采用了業(yè)界廣泛使用的 GeoHash 編碼方法,這個(gè)方法的基本原理就是“二分區(qū)間,區(qū)間編碼”。
對(duì)于一個(gè)地理位置信息來說,它的經(jīng)度范圍是[-180,180]。GeoHash 編碼會(huì)把一個(gè)經(jīng)度值編碼成一個(gè) N 位的二進(jìn)制值,我們來對(duì)經(jīng)度范圍[-180,180]做 N 次的二分區(qū)操作,其中 N 可以自定義。
在進(jìn)行第一次二分區(qū)時(shí),經(jīng)度范圍[-180,180]會(huì)被分成兩個(gè)子區(qū)間:[-180,0) 和[0,180](我稱之為左、右分區(qū))。此時(shí),我們可以查看一下要編碼的經(jīng)度值落在了左分區(qū)還是右分區(qū)。如果是落在左分區(qū),我們就用 0 表示;如果落在右分區(qū),就用 1 表示。這樣一來,每做完一次二分區(qū),我們就可以得到 1 位編碼值。
然后,我們?cè)賹?duì)經(jīng)度值所屬的分區(qū)再做一次二分區(qū),同時(shí)再次查看經(jīng)度值落在了二分區(qū)后的左分區(qū)還是右分區(qū),按照剛才的規(guī)則再做 1 位編碼。當(dāng)做完 N 次的二分區(qū)后,經(jīng)度值就可以用一個(gè) N bit 的數(shù)來表示了。
舉個(gè)例子,假設(shè)我們要編碼的經(jīng)度值是 116.37,我們用 5 位編碼值(也就是 N=5,做 5 次分區(qū))。
我們先做第一次二分區(qū)操作,把經(jīng)度區(qū)間[-180,180]分成了左分區(qū)[-180,0) 和右分區(qū)[0,180],此時(shí),經(jīng)度值 116.37 是屬于右分區(qū)[0,180],所以,我們用 1 表示第一次二分區(qū)后的編碼值。
接下來,我們做第二次二分區(qū):把經(jīng)度值 116.37 所屬的[0,180]區(qū)間,分成[0,90) 和[90, 180]。此時(shí),經(jīng)度值 116.37 還是屬于右分區(qū)[90,180],所以,第二次分區(qū)后的編碼值仍然為 1。等到第三次對(duì)[90,180]進(jìn)行二分區(qū),經(jīng)度值 116.37 落在了分區(qū)后的左分區(qū)[90, 135) 中,所以,第三次分區(qū)后的編碼值就是 0。
按照這種方法,做完 5 次分區(qū)后,我們把經(jīng)度值 116.37 定位在[112.5, 123.75]這個(gè)區(qū)間,并且得到了經(jīng)度值的 5 位編碼值,即 11010。這個(gè)編碼過程如下表所示:
對(duì)緯度的編碼方式,和對(duì)經(jīng)度的一樣,只是緯度的范圍是[-90,90],下面這張表顯示了對(duì)緯度值 39.86 的編碼過程。
當(dāng)一組經(jīng)緯度值都編完碼后,我們?cè)侔阉鼈兊母髯跃幋a值組合在一起,組合的規(guī)則是:最終編碼值的偶數(shù)位上依次是經(jīng)度的編碼值,奇數(shù)位上依次是緯度的編碼值,其中,偶數(shù)位從 0 開始,奇數(shù)位從 1 開始。
我們剛剛計(jì)算的經(jīng)緯度(116.37,39.86)的各自編碼值是 11010 和 10111,組合之后,第 0 位是經(jīng)度的第 0 位 1,第 1 位是緯度的第 0 位 1,第 2 位是經(jīng)度的第 1 位 1,第 3 位是緯度的第 1 位 0,以此類推,就能得到最終編碼值 1110011101,如下圖所示:
當(dāng)然,使用 GeoHash 編碼后,我們相當(dāng)于把整個(gè)地理空間劃分成了一個(gè)個(gè)方格,每個(gè)方格對(duì)應(yīng)了 GeoHash 中的一個(gè)分區(qū)。
總結(jié)
以上是生活随笔為你收集整理的【Redis】数据结构的应用——GEO 【搜索“附近的餐馆”、在打车软件上叫车】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 腾讯云+CentOS 7.2+pytho
- 下一篇: 如何使用SQL对数据进行分析和可视化