[譯] 用 Redis 和 Python 構建一個共享單車的 app

Starrier發表於2018-04-22

用 Redis 和 Python 構建一個共享單車的 app

瞭解如何使用 Redis 和 Python 構建位置感知應用程式。

google bikes on campus
google bikes on campus

圖片來源: Travis Wise. CC BY-SA 2.0

雖然我經常出差,但我不太喜歡開車,所以當我有空的時候,我更喜歡在城市裡散步或騎自行車。我出差去過的許多城市都有自行車租賃系統,可以讓你租幾個小時的自行車。這些系統中的大多數都有一個應用程式來幫助使用者定位和租賃他們的自行車,但對於像我這樣的使用者來說,有一個單獨的地方來獲取城市中所有可供租賃的自行車的資訊會更有幫助。

為了解決這個問題並開源向 Web 應用程式新增位置感知特性的能力,我結合了公開可用的共享單車資料、Python 程式語言和開源 Redis 記憶體資料結構伺服器來索引並查詢地理空間資料。

由此產生的共享單車應用程式整合了來自許多不同共享系統的資料,包括在紐約市的 Citi Bike 共享單車。它利用了 Citi Bike 系統提供的通用共享單車資料流,並使用其資料演示了可以使用 Redis 建立的一些功能來索引地理空間資料。Citi Bike 資料是根據 Citi Bike 資料許可協議提供的。

通用共享單車資料流規範

通用共享單車資料流規範(GBFS)是由北美共享單車協會開發的開源資料規範,目的是讓地圖和交通類應用程式更輕易地將共享單車系統新增到它們的平臺中。目前世界上有 60 多個不同的共享系統在使用該規範。

資料流由包含有關係統狀態資訊的幾個簡單 JSON 資料檔案組成。資料流從引用子資料流資料的 URL 的頂級 JSON 檔案開始:

{
    "data": {
        "en": {
            "feeds": [
                {
                    "name": "system_information",
                    "url": "https://gbfs.citibikenyc.com/gbfs/en/system_information.json"
                },
                {
                    "name": "station_information",
                    "url": "https://gbfs.citibikenyc.com/gbfs/en/station_information.json"
                },
                . . .
            ]
        }
    },
    "last_updated": 1506370010,
    "ttl": 10
}
複製程式碼

第一步是將 system_information 和 station_information 中,資料流有關共享單車站點的資訊的資料載入到 Redis 裡。

system_information 資料流系統 ID,這是一個可用於 Redis 金鑰建立名稱空間的短程式碼。GBFS 規範沒有指定系統 ID 的格式,但保證它是全域性唯一的。對於系統 ID,許多共享單車資料流都是使用簡短的名稱,如 coast_bike_share、boise_greenbike 或者 topeka_metro_bikes。另一些使用熟悉的地理縮寫,如 NYC 或 BA,其中一個使用通用唯一識別符號(UUID)。共享單車應用程式使用識別符號作為字首來構造給定系統的唯一金鑰。

Station_Information 資料流提供了組成系統的關於共享站點的靜態資訊。站點由帶有多個欄位的 JSON 物件表示。在站點物件中有幾個必填欄位,它們提供真實站點的 ID、名稱和位置。還有幾個可選欄位提供的有用資訊,如最近的十字路口或所接受的付款方式。這是共享單車應用程式這部分的主要資訊來源。

建立資料庫

我寫了一個示例應用程式 —— load_station_data.py,它模擬了從外部源載入資料的後端過程中可能發生的情況。

查詢共享單車站點

Github 的 GBFS 倉庫systems.csv 檔案載入共享單車資料。

倉庫的 systems.csv 檔案為註冊的共享單車系統提供了一個帶有可用的 GBFS 資料流發現 URL。發現 URL 是處理共享單車資訊的起點。

load_station_data 應用程式獲取系統檔案中發現的每個 URL,並使用它查詢兩個資料流 URL:系統資訊和站點資訊。系統資訊資料流提供了一條關鍵資訊:系統的唯一 ID。(注意:systems.csv 檔案也提供了系統 ID,但是該檔案中的一些識別符號與提要中的識別符號不匹配,因此我總是從反饋中獲取識別符號。)系統的詳細資訊,比如共享單車 URL、電話號碼和電子郵件。可以新增到應用程式的未來版本中。 因此使用鍵 ${system_id}:system_info 將資料儲存在 Redis 雜湊中。

載入站點資料

站點資訊提供系統中每個站點的資料,包括系統的位置。load_station_data 應用程式迭代站點反饋中的每個站點,使用形如 ${system_id}:station:${station_id} 的鍵將每個站點的資料存到 Redis 雜湊中。使用 GEOADD 命令將每個站點的位置新增到共享單車的地理空間索引中。

更新資料

在隨後的執行中,我不希望程式碼從 Redis 中刪除所有資料流資料再將其重新載入到一個空的 Redis 資料庫中,因此我仔細考慮瞭如何處理資料的本地更新。

程式碼首先載入已經被系統載入到記憶體中的所有共享單車站點資訊的資料集。當為站點載入資訊時,從站點的記憶體集中刪除站點(按鍵)。一旦載入了所有站點資料,我們就會得到一個包含了該系統必須刪除的所有站點的資料集合。

應用程式迭代這組站點並建立一個事務來刪除站點資訊,從地理空間索引中刪除站點鍵,並從系統的站點列表中刪除站點。

程式碼註釋

示例程式碼中有一些有趣的事情需要注意。首先,使用 GEOADD 命令將詞條新增到地理空間索引中,但是用 ZREM 命令刪除。由於地理空間型別底層實現使用排序集,因此使用 ZREM 刪除詞條。請注意,為了簡潔,示例程式碼演示瞭如何使用單個 Redis 節點;如果在叢集環境中執行,需要對事務模組進行重構。

如果您使用的是 Redis 4.0 (或者更高版本),則在程式碼中有一些 DELETEHMSET 命令的替代方法。Redis 4.0 提供UNLINK 命令作為 DELETE 命令的非同步替代。UNLINK 將從金鑰空間中刪除金鑰,但它在單獨的執行緒中回收記憶體。HMSET 命令在 Redis 4.0 中被棄用,HSET 命令現在是可變的(也就是說,它接受數目不定的引數)。

通知客戶端

在流程結束時,將根據我們的資料向客戶端傳送通知。使用 Redis pub/sub 機制,通知通過 geobike:Station_Changed 通道傳送,並帶有系統的 ID。

資料模型

在用 Redis 構造資料時,要考慮的最重要的事情是如何查詢資訊,共享單車應用程式需要支援的兩個主要查詢是:

  • 找到附近站點
  • 顯示站點相關資訊

Redis 提供兩種用於儲存資料的主要資料型別:雜湊和排序集。雜湊型別 很好地對映到表示站點的 JSON 物件;由於 Redis 雜湊不強制執行模式,因此可以使用它們儲存可變站點資訊。

當然,在地理上尋找站點需要一個地理空間索引來搜尋相對於某些座標的地點。Redis 提供一些命令來使用排序集資料結構構建地理控制元件索引。

我們使用 ${System_id}:Station:${Station_id} 格式的雜湊構造金鑰,其中包含站點和金鑰的資訊,使用的 ${System_id}:Station:Location 格式來查詢站點的地理空間索引。

獲取使用者位置

構建應用程式的下一步是確定使用者的當前位置。大多數應用程式通過作業系統提供的內建服務來實現這一點。該作業系統可以為應用程式提供基於內建於裝置中的 GPS 硬體或近似於裝置的可用 WiFi 網路的位置。

查詢位置

在找到使用者的位置後,下一步是定位附近的共享單車站點。Redis 的地理空間功能可以在使用者當前座標的給定距離內返回站點的資訊。下面是一個使用 Redis 命令列介面的示例。

Apple 美國紐約店地址
Apple 紐約店店址地圖

想象我在紐約市第五大道上的蘋果店,我想去西區 37 街的 Mood,和我的好友 Swatch 聊天。我可以乘計程車或地鐵,但我寧願騎自行車。附近有共享站點麼?我可以在那裡租有一輛車去麼?

Apple 專賣店位於 40.76384, -73.97297。根據地圖,兩個自行車站點 —— Grand Army Plaza 和 Central Park South 和 East 58th St. & Madison —— 位於 500 英尺的範圍內(在以上地圖是藍色的)。

我可以使用 Redis GEORADIUS 命令查詢紐約系統索引,查詢半徑為 500 英尺的站點:

127.0.0.1:6379> GEORADIUS NYC:stations:location -73.97297 40.76384 500 ft
1) "NYC:station:3457"
2) "NYC:station:281"
複製程式碼

Redis 使用地理空間索引中的元素作為特定站點的後設資料的鍵,返回在該半徑內找到的兩個自行車共享位置。下一步是查詢這兩個站點的名稱:

127.0.0.1:6379> hget NYC:station:281 name
"Grand Army Plaza & Central Park S"
 
127.0.0.1:6379> hget NYC:station:3457 name
"E 58 St & Madison Ave"
複製程式碼

這些鍵對應以上地圖確定的站臺。如果願意,我可以在 GEORADIUS 命令中新增更多的標誌,以獲取元素的列表、它們的座標以及它們與當前站點的距離:

127.0.0.1:6379> GEORADIUS NYC:stations:location -73.97297 40.76384 500 ft WITHDIST WITHCOORD ASC 
1) 1) "NYC:station:281"
   2) "289.1995"
   3) 1) "-73.97371262311935425"
      2) "40.76439830559216659"
2) 1) "NYC:station:3457"
   2) "383.1782"
   3) 1) "-73.97209256887435913"
      2) "40.76302702144496237"
複製程式碼

查詢與這些鍵相關聯的名稱會生成一個有序的站點列表,我可以從中進行選擇。Redis 不提供方向或路由功能,因此我使用裝置作業系統的路由功能來繪製從當前位置到所選自行車站點的路線。

GEORADIUS 函式可以輕易在您喜歡的開發框架的 API 中實現,以便將位置功能新增到 app 中。

其他查詢命令

除了 GEORADIUS 命令以外,Redis 還提供了三個用於從索引中查詢資料的命令 GEOPOSGEODIST 和 GEORADIUSBYMEMBER

GEOPOS 命令可以從地理雜湊中提供給定元素的座標。例如,如果我知道在 West 38th and 8th 有共享單車站點,而且它的 ID 是 523,那麼該站點的元素名稱是 NYC:station:523。使用 Redis,我可以找到站點的經度和維度:

127.0.0.1:6379> geopos NYC:stations:location NYC:station:523
1) 1) "-73.99138301610946655"
   2) "40.75466497634030105"
複製程式碼

GEODIST 命令提供兩個元素之間的距離索引。如果我想要找出 Grand Army Plaza 和 Central Park South 和在 East 58th St. & Madison 站點之間的距離, 我會發出以下命令:

127.0.0.1:6379> GEODIST NYC:stations:location NYC:station:281 NYC:station:3457 ft
"671.4900"
複製程式碼

最後,GEORADIUSBYMEMBER 命令類似於 GEORADIUS 命令,但該命令沒有接受一組座標,而是取索引的另一個成員的名稱,並返回以該成員為中心所給定半徑內的所有成員。要找到 Grand Army Plaza 和 Central Park 南 1000 英尺範圍內的所有車站,請輸入以下內容:

127.0.0.1:6379> GEORADIUSBYMEMBER NYC:stations:location NYC:station:281 1000 ft WITHDIST
1) 1) "NYC:station:281"
   2) "0.0000"
2) 1) "NYC:station:3132"
   2) "793.4223"
3) 1) "NYC:station:2006"
   2) "911.9752"
4) 1) "NYC:station:3136"
   2) "940.3399"
5) 1) "NYC:station:3457"
   2) "671.4900"
複製程式碼

雖然這個示例側重於使用 Python 和 Redis 來解析資料並構建自行車共享系統位置的索引,但它可以很容易地推廣到定位餐館、公共交通或任何其他型別的地方,以幫助使用者查詢。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章