1. 程式人生 > >如何高效儲存海量GPS資料

如何高效儲存海量GPS資料

最近幾年,移動裝置已經非常普及,對GPS的使用也越來越常見,比如快車專車產品中的實時位置和歷史軌跡圖,運動App中的跑步,騎行軌跡等,很多研發人都遇到了如何設計系統架構來高效儲存和查詢GPS資料的問題。

對於一個網際網路產品,要面對大流量,突發大壓力,要保證低延時,高穩定性,還要考慮以後的實時擴充套件性,作為負責人的話,還需要考慮成本。這樣,設計一個滿足這些需求的系統就不怎麼簡單了,比如下面這些應用。

21f63d1be4d082dd615abed82eaea110

在這篇文章中,我們將通過設計一個騎行類App的GPS功能來說明多種使用場景及其區別。

產品功能

我們先明確這款騎行產品需要具備的基礎功能:

  1. 使用者騎行過程中,App記錄GPS軌跡
  2. 使用者在騎行過程中可以在手機App中看到自己當前的位置,歷史騎行軌跡以及最大時速,平均時速,騎行時間等統計值
  3. 使用者在騎行完成後,可以檢視自己歷史的騎行記錄
  4. 給好友、家人或戀人實時共享當前軌跡
  5. 運營需要分析使用者軌跡某個維度的特徵

儲存系統

首先,我們來看一下此類產品的特點:

  1. 使用者數大,比較有名的產品可能會有百萬,甚至更多的使用者。
  2. 有明確的高峰低谷,比如早晨,傍晚是高峰期,凌晨是低峰期。
  3. 儲存的資料量比較大,資料量和場景,使用者量極度相關。
  4. 產品可能會有爆發發展期,系統需要有非常快速的擴充套件能力。
  5. 跟時間相關,需要按時間查詢範圍,或者需要在不知道起始時間和結束時間情況下查詢軌跡點。
  6. 成本低。
  7. 穩定性高,尤其是寫。

總結下就是:

  1. 高併發寫入,尤其是寫:具有每秒處理千萬請求的能力,且效能不能隨著資料增大而下降。
  2. 按量付費
  3. 單表大容量儲存,最好不限表大小:PB級別。
  4. 實時水平擴充套件能力。
  5. 支援範圍查詢。
  6. 低成本
  7. 高穩定性:SLA保障。

表格儲存(Tablestore)是由阿里雲自主研發的基於共享儲存的NoSQL資料庫,是一款專為海量資料高效能存取而設計的儲存系統,10G以下免費。

表格儲存可以完全滿足上述所有要求,另外,還提供多版本,TTL遞增ID,增量通道等功能。
確定了儲存系統後,我們再來看方案。

方案

gps_1_001_jpeg

表結構設計

表格儲存最多支援四個主鍵,每個主鍵都支援三種類型,字串,二進位制和長整型,屬性列是schema free的,可以自由修改。

在當前方案中,表格儲存的表格可以這麼設計:

主鍵順序 名稱 型別 備註
1 partition_key string md5(user_id)前四位 為了負載均衡
2 user_id string/int 使用者id 可以是字串也可以是長整型數字
3 task_id string/int 此次軌跡圖的id 可以使字串也可以是長整型數字
4 timestamp int 時間戳 使用長整型,64位,足夠儲存毫秒級別的時間戳

如何存

設計好了表結構後,我們可以看一下軌跡資料如何存:
比如原始資料是:

2017/5/20 10:10:10的時候小王在杭州西湖斷橋上騎自行車,速度5m/s,當時風速2m/s,溫度20度,已經騎行了8公里。

在表格儲存中儲存的是(10列);

part_key user_id task_id timestamp longitude latitude speed wind_speed temperature distance
01f3 000001 001 1495246210 120.1516525097 30.2583277934 5 2 20 8000

主鍵

  1. part_key:第一個主鍵,分割槽建,主要是為了負載均衡,保證資料可以均勻分佈在所有機器上,提高併發度和效能。如果業務主鍵user_id可以保證均勻分佈,那麼可以不需要這個主鍵。
  2. user_id:第二個主鍵,使用者ID,可以是字串也可以是數字,唯一標識一個使用者。
  3. task_id:第三個主鍵,任務ID,也就是軌跡ID,表示某一次騎行任務的軌跡圖,可以是字串也可以是數字。
  4. timestamp:第四個主鍵,時間戳,表示某一個時刻,單位可以是秒,如果是每隔10秒記錄一個GPS地址,那麼相鄰行的timestamp就相差10。時間戳1495246210等價於2017/5/20 10:10:10。
  5. 至此,上述四個主鍵可以唯一確定某一個使用者的某一次騎行在某一個時間點的資料

屬性列

  1. longitude:經度值。
  2. latitude:緯度值。這個(120.1516525097,30.2583277934)經緯度值表示的就是杭州西湖的斷橋上。
  3. speed:騎行速度,可以是當前的瞬時值,也可以是過去10秒的平均值,也可以都記錄。
  4. wind_speed:當前風速,這個值無法通過手機採集,需要特定的自行車車載模組。
  5. temperature:當前溫度,可以直接取當前城市的溫度值,也可以使用特定的溫度感測器。
  6. distance:騎行距離,由於騎行的速度不會太快,可以用米為單位。
  7. 上面只是舉了幾個簡單的例子,由於表格儲存的屬性列是schema-free的,任何時候都可以增刪屬性列,每一行可以有不同的屬性列。甚至可以非同步處理後,再回寫挖掘出來的一些特徵值。

下面我們來看一下各個場景:

GPS軌跡儲存

gps_004_jpeg

關鍵點:

  • 使用表格的形式儲存使用者騎行過程中的meta資料
  • 表格儲存單表資料量無上限,支援萬億級在容量,效能上沒任何問題。

手機端:

  • 使用者啟動後,手機每隔10秒鐘,傳送一條資料給應用伺服器,這條訊息包括當前GPS資料和meta值(經度,緯度,速度,距離等)

伺服器端

  • 應用伺服器收到客戶端傳送的訊息後,先對其中的GPS資料做糾正,然後將訊息儲存到表格儲存中的GPS軌跡表中,主鍵儲存使用者id,任務id,時間戳,屬性列儲存經度,緯度,耗費時間,距離等。某一個時刻的資料儲存在一行中。
  • 【補充】使用者資料傳送給使用者的應用伺服器後,應用伺服器可以先做資料訂正,訂正好後再寫到表格儲存,但是有時候需要考慮到寫流程是關鍵路徑,清洗訂正是非關鍵路徑,需要寫入和訂正流程是非同步流程,這時候可以直接使用表格儲存Stream功能。資料直接寫表格儲存,然後使用者應用伺服器通過表格儲存stream功能實時讀取到新增資料,訂正、清洗等完成後將結果寫回表格儲存,因為表格儲存會在stream中儲存12小時,所以,就算訂正清洗功能短時間不可用,也不影響使用者的使用。

GPS軌跡查詢

通過表格儲存GetRange介面可以查詢到完整歷史軌跡圖。
gps_005_jpeg

手機端:

  • 當要查詢自己這次騎行的歷史軌跡和當前位置時,只需要通過一個範圍查詢(GetRange)就可以查到此次騎行的位置軌跡圖。也就是查詢這次任務從最早時間到當前時間內的GPS資料,對應於範圍查詢,首次查詢時起始鍵可以是(md5(user_id).sub(0,4), user_id, task_id, MIN),結束鍵是(md5(user_id).sub(0,4), user_id, task_id, NOW),首次查詢完後可以將NOW值儲存下來,下次查詢的時候起始鍵就可以是上一次查詢的結束鍵,一直到此次騎行結束

伺服器端

  • 接收到手機端的查詢請求後,將其轉換成表格儲存的範圍查詢(GetRange),就可以查詢到此次騎行的歷史軌跡圖。
  • 對於之前的歷史軌跡圖,也可以使用範圍查詢獲取到歷史軌跡。

其他人的GPS軌跡查詢

關鍵點:

  • 讀擴散:自己的GPS軌跡資料只儲存在自己的user id下,其他使用者過來讀取。
  • 新表:share_gps_table,記錄分享的user id,task id等資訊。
  • 加密:對於分享過程中涉及的ID等值加密,防止惡意使用者冒充。

手機端:

  • A使用者啟動後,可以分享當前騎行給自己的朋友B,也就是隻需要把A的user_id和此次的task_id分享給朋友B。
  • 朋友B的客戶端獲取到使用者A的user_id和task_id後,就可以使用範圍查詢獲取使用者A在此次騎行中的實時位置和軌跡圖了。

伺服器端:

  • 如果是分享者A通過二維碼,連結等分享給使用者B,這時候使用者B的手機端可以獲取到使用者A的user id和此次task id(可以使用加密,防止被人篡改id),使用者B可以去伺服器端獲取這次的使用者A的騎行實時軌跡圖。通過這種方式的分享記錄可以持久化到表shard_gps_table中,也可以不持久化。
  • 在App內部使用者A分享給使用者B,使用者A將分享記錄傳送給伺服器後,伺服器記錄在shard_gps_table表中,然後給使用者B傳送一個通知,包含一個特殊的ID值(可以是使用者A的user id和task id加密後的一個值,這個值不能允許反向解析出user id和task id)。
  • 使用者B收到通知後,通過這個特殊的ID值到伺服器端來查看錶shard_gps_table中是否有對應的分享記錄,如果有,則將對應的使用者A的user id和task id傳送給使用者B,使用者B再通過這些來讀取使用者A的軌跡圖。

組團GPS軌跡查詢

有時候,使用者會組團去騎行,這時候,團員都希望可以在一張地圖裡面看到大家所有人的實時位置和歷史軌跡圖,團長也希望能通過這些值作為參考,安排大家休息地點和時間。對於這種場景,可以按下面方式實現:

關鍵點:

  • 所有團員都使用同一個任務ID
  • 讀擴散(寫的時候寫到自己的user id下面,讀的時候讀其他成員的同任務id的資料)
  • 使用新表:group_user_table,記錄每個組的當前使用者和歷史使用者(使用表格儲存多版本功能可以儲存使用者的多次加入和離開時間)

手機端:

  • 團長發起隊伍,建立一個task id,其他成員加入,獲取task id
  • 每個成員騎行過程中,將自己的gps軌跡資料記錄在自己的資料中(前兩個主鍵為:user_id, task_id)
  • 如果有成員先退出,後加入,仍然使用同task id,前後兩段gps軌跡仍然有關聯。

伺服器端:

  • 使用新表(group_user_table)記錄每個task id下對應的成員id。這裡可以使用多版本記錄成員的多次退出加入事件。
  • 檢視整個隊伍gps軌跡時,先通過task id檢視所有成員ID,然後使用表格儲存的BatchGetRow或者GetRange獲取所有成員的軌跡資料。

團員異常路線報警

組團騎行或者組織的大型比賽中,有可能某些成員掉隊或者騎錯路線,這時候就需要讓組織者知道,使用表格儲存的stream功能就可以很容易對於異常騎行者做出報警。

_2_003_jpeg

關鍵點:

  • 使用表格儲存的增量通道(Stream)功能【即將在五月份上線】
  • 讀擴散:App中有可能會搞全國運動,這樣同時參加使用者可能數十萬或者百萬,這時候讀擴散的優勢就比較明顯了。

伺服器端:

  • 每個組員的GPS軌跡資料發給表格儲存後,表格儲存會持久化資料後,然後新資料和更新資料會實時進入stream通道,使用者可以實時或者週期性的使用SDK提供的介面讀取到這些新進來的GPS資料,然後在自己的應用伺服器中計算這些GPS資料和安全區域做比較,如果出了安全區域或者掉隊太遠,傳送一條報警簡訊給組織者或者組織機構。這裡可以使用阿里雲端儲存團隊的訊息服務(MNS)
  • 如果不想使用Stream功能,也可以直接通過GetRange介面從表中讀取,由於不知道哪些使用者有更新,需要讀取所有使用者的最近一段時間的資料,當用戶量很大的時候,這個複雜度和開銷都會很大,遠遠沒有stream功能易用和效能好。

手機端:

  • 組織者收到團員掉隊或者走出安全區域的報警後,可以立即檢視這名成員的歷史軌跡和當前位置,然後安排人員聯絡異常團員,防止危險事件發生。

粉絲圍觀大V騎行

有社群後就有可能會有大V,大V可以在App中搞騎行直播,旁邊可以展示GPS軌跡,幾十萬,甚至幾百萬粉絲圍觀,打賞等

關鍵點:

  • 讀擴散:這時候就只能使用讀擴散了。由於前面的功能都使用了讀擴散,那這裡處理就非常簡單。

手機端:

伺服器端:

  • 和前面處理邏輯差不多,這裡就省略了。

運營分析

全量分析:

gps_006_jpeg

  • SQL:使用Max Computer 2.0 (原ODPS)的SQL功能,可以直接讀取表格儲存中的資料,無需匯出,節省了匯出時間,更快地計算出結果。
  • 自有job:使用dataX可以全量高併發的匯出到Max Computer中去做分析處理,處理完後繼續可以用dataX寫會表格儲存。
  • 適用於批處理方式。

增量分析:

gps_007_jpeg

  • 使用表格儲存的stream功能可以獲取最近十二小時的使用者新增,更新,刪除事件涉及的資料,即可以獲取到資料的實時變化情況後做分析處理。
  • 適用於實時處理方式。

直寫方案

上述方案已經可以完全滿足高併發,低延遲的需求了,那麼這個技術架構是否是還可以繼續優化呢?當然是可以。上述方案中,手機App其實可以直接將資料儲存到表格儲存,不需要經過應用伺服器中轉,減少對應用伺服器的壓力。

gps_002_jpeg

上述5個步驟分別是:

  1. 使用者啟動後,手機嚮應用伺服器請求寫表格儲存的寫許可權。
  2. 應用伺服器審批後,如果認為可以給予這個使用者寫許可權,那麼可以繼續向阿里雲的STS服務申請表格儲存的臨時寫許可權和授權時間。
  3. STS服務接收到請求後,生成一個臨時的令牌,包括臨時AccessKeyId,臨時AccessKeySecret和臨時token,將其返回給應用伺服器。
  4. 應用伺服器接收到STS的結果後,將其返回給手機端的APP。
  5. 手機端的App接收到臨時令牌後,可以向表格儲存開始持續寫資料。等持續寫了一定時間後,臨時令牌會失效(失效時間使用者可自定義),如果到時候還需要寫入,可以繼續申請新的令牌。

上述直寫方案相對於原有方案,有如下一些優勢:

  • 可以減少應用伺服器壓力。
  • 手機端直接寫TableStore,不用經過應用伺服器,路徑更短,效能更佳。
  • 將最關鍵的GPS寫流程和應用伺服器解耦合,旁路化,關鍵路徑獨立出來,就算應用伺服器出現故障,不可服務了,但仍然不影響使用者的GPS寫入流程。

總結

前面講了如何用表格儲存(Tablestore)儲存騎行類APP的GPS資料,以及stream功能在各個重要場景中的使用。雖然講的是騎行類APP,但是其他場景也同樣適用,比如跑步類、專車類、車聯網等等。這裡由於篇幅問題,每個部分都還比較簡單,如果大家有興趣,針對上述的每個點後續都可以繼續展開再詳細講講。