mysql 轨迹数据存储_基于Tablestore实现海量运动轨迹数据存储-阿里云开发者社区...
前言
現在越來越多的人都開始關心自己的運動數據,比如每日的計步、跑步里程、騎行里程等。運動APP與運動類的穿戴設備借助傳感器、地圖、GPS定位等技術,收集好運動數據以后,通過與互聯網社交功能結合,產生了一種新的運動模式。用戶不僅可以查看與分析自己的運動數據,還可以分享跑步路線、騎行路線給附近的運動好友,還可以組團跑步、推薦運動設備等,吸引了各年齡段的用戶。
核心需求
現在比較流行的一些運動APP和穿戴設備都提供了較為豐富的功能,甚至可以購物和社交。但運動軌跡管理、運動數據分析、附近的跑步路線/騎行路線、附近的運動團這些始終是核心功能點。
運動軌跡數據可以是穿戴設備生成的也可以是手機APP生成的,先在APP端存儲,最后由手機APP批量上傳到服務端。服務端和數據庫都需要支持高并發的讀寫、數據庫要支持海量的存儲。
附近的運動好友、附近的運動路線、附近的運動團數據量相對會少一些,但需要支持地理位置檢索。
數據模型
如下方左邊的圖所示,一次跑步或者騎行就會產生一條軌跡信息。我們可以把軌跡數據分成兩個部分:跑步記錄和軌跡點信息。
附近的人、附近的跑步路線這類數據,我們可以把它們都看成一個點。例如:我們認為跑步路線的起始點就是它的位置,于是就有了右下方的圖:中間點是我,以我為中心,去查找附近N公里范圍內的數據。
技術選型
我們主要分析下MySQL與Tablestore這兩種數據庫在運動場景下的使用。
MySQL
運動軌跡數據不能刪除,存儲量會越來越大,使用MySQL首先要考慮的是它是單機型數據庫,橫向擴展不友好。另外軌跡數據寫多讀少,大部分是冷數據,用MySQL存儲也不經濟。當用戶規模大起來以后,軌跡點上傳對于數據庫的讀寫性能也有很高的要求。總結起來有如下劣勢點:
單機數據庫,不好擴容,存儲容量受限。
存儲大量冷數據,成本高,不經濟。
對于海量高并發運動軌跡數據的讀寫需要做很多優化。
Tablestore
Tablestore(表格存儲)是阿里云自研的面向海量結構化數據存儲的Serverless NoSQL多模型數據庫,提供了面向軌跡類場景的Timestream模型,可提供PB級存儲、千萬TPS以及毫秒級延遲的服務能力。適合運動軌跡的場景 。
跑步、騎行、健走等動動軌跡數據和附近的人、附近的跑步路線,都可以直接使用Timestream模型,官方的JAVA SDK有使用示例。
基于Tablestore Timestream的功能實現
Timestream模型中,數據存儲分成meta和data兩張表。在我們的場景中,meta表存放兩類數據:設備的元數據和軌跡位置、運動記錄的訂單信息,這兩類數據通過Timestream Identifier中的一個tag字段進行區分。data表存放跑步/騎行的軌跡點信息。
Meta數據結構
Timestream的Identifier部分有三個字段,Name用于存放運動主體的名稱,比如 xxx手機、xxx手環等,ObjectType用于區分本條記錄是設備還是運動訂單,objectId是唯一標識,比如設備ID、訂單ID。
Attributes中有兩類信息,如果當前主體是軌跡訂單,屬性列對應的是運動類型、起至時間,如果主體是設備,屬性列對應的是對象類型,位置點和時間等。
實現方案
基于上面的meta數據結構,我們抽象出來三個對象:SportObject、SportTrackMeta、SportTrackOrder。其中,SportObject對應的是meta表中的Identifier,它是一個運動主體的標識;SportTrackMeta對應的是meta表的設備位置信息,SportTrackOrder對應的是meta表的軌跡訂單信息。
數據的讀寫接口的定義如下:
public interface ITrackWriter {
/**
* 寫入位置點meta信息,包括附近的人、附近的跑步路線
* @param sportObject
* @param sportTrackMeta
*/
void writeTrackMeta(SportObject sportObject, SportTrackMeta sportTrackMeta);
/**
* 寫入跑步、騎行等運動記錄信息
* @param sportObject
* @param sportTrackOrder
*/
void writeTrackOrderMeta(SportObject sportObject, SportTrackOrder sportTrackOrder);
/**
* 寫入軌跡點信息
* @param sportObject
* @param sportTrackMeta
* @param positions
*/
void writeTrackPosition(SportObject sportObject, SportTrackMeta sportTrackMeta, List positions);
}
public interface ITrackerReader {
/**
* 獲取所有的跑步記錄
* @param sportObject
*/
void listTrackMeta(SportObject sportObject);
/**
* 獲取一次跑步、騎行的軌跡點
* @param sportObject
*/
void getTrackData(SportObject sportObject);
/**
* 獲取distanceInMeter范圍內,附近的人、附近的跑步路線 信息,根據距離排序
* @param targetType
* @param centerPoint
* @param distanceInMeter
*/
void listTargetNearbyOrderbyDistance(String targetType, Position centerPoint, int distanceInMeter);
}
設備端通過writeTrackMeta接口,定時向服務端上傳位置點信息,服務端會存儲最新的位置點。跑步/騎行記錄,通過writeTrackOrderMeta接口上傳訂單元數據,通過writeTrackPosition接口上傳軌跡點。
查詢接口也是分成兩類,listTrackMeta用于查詢運動記錄訂單,getTrackData用于查詢一次跑步/騎行的軌跡信息。通過SportObject中的ObjectType,可以區分訂單與設備這兩類數據,示例中只提供了這兩類數據的過濾,如果需要更多的條件過濾,可以傳入Attributes信息,Timestream也支持根據這attribute進行過濾。
寫入設備位置
設備位置信息主要包括:設備標識、位置點、時間這三個部分。
public void writeTrackMeta(SportObject sportObject, SportTrackMeta sportTrackMeta) {
TimestreamIdentifier identifier = buildIdentifier(sportObject);
TimestreamMeta meta = new TimestreamMeta(identifier)
.addAttribute("location", sportTrackMeta.getLocation())
.addAttribute("timestamp", sportTrackMeta.getTimestamp())
.addAttribute("targetnearybytype", sportTrackMeta.getTargetNearbyType());
TimestreamMetaTable metaTable = tablestoreTrack.timestreamClient.metaTable();
// write meta
metaTable.put(meta);
}
查詢附近的人
在本方案中,附近的人對應的點,實際上是附近的設備,所以只要檢索設備的位置信息。
public void listTargetNearbyOrderbyDistance(String targetType, Position centerPoint, int distanceInMeter) {
String gePoint = String.format("%f,%f", centerPoint.getCoords().getLatitude(), centerPoint.getCoords().getLongitude());
Filter filter = and(
//查詢條件一:數據類型為 設備
Tag.equal("objectType", SportObjectType.device),
//查詢條件二:距離中心點distanceInMeter距離的點
Attribute.inGeoDistance(Constants.ATTRIBUTE_COL_GEO, gePoint, distanceInMeter)
);
TimestreamMetaIterator iter = tablestoreTrack.timestreamClient.metaTable()
.filter(filter)
.selectAttributes("location")
.fetchAll();
//距離排序部分省略..。
}
返回數據如下:
user: user_center, distance:0 meters
user: user_0, distance:146 meters
user: user_19, distance:242 meters
user: user_5, distance:308 meters
user: user_10, distance:481 meters
.......
第一個點的用戶是中心點用戶,距離0米,實際使用的時候需要排除。其它點按距離排序。基于附近的人、附近的跑步路線還可以實現很多有趣的功能,比如附近有多少人正在跑步、有多少人正在健走,如果用戶授權公開位置,還可以在地圖上進行標記。
寫入跑步/騎行軌跡
public void writeTrackOrderMeta(SportObject sportObject, SportTrackOrder sportTrackOrder) {
TimestreamIdentifier identifier = buildIdentifier(sportObject);
TimestreamMeta meta = new TimestreamMeta(identifier)
.addAttribute("sporttracktype", sportTrackOrder.getSportTrackType())
.addAttribute("distance", sportTrackOrder.getDistance())
.addAttribute("starttime", sportTrackOrder.getStartTime())
.addAttribute("endtime", sportTrackOrder.getEndTime());
TimestreamMetaTable metaTable = tablestoreTrack.timestreamClient.metaTable();
// write meta
metaTable.put(meta);
}
public void writeTrackPosition(SportObject sportObject, SportTrackMeta sportTrackMeta, List positions) {
TimestreamIdentifier identifier = buildIdentifier(sportObject);
TimestreamMeta meta = new TimestreamMeta(identifier)
.addAttribute("location", sportTrackMeta.getLocation());
TimestreamMetaTable metaTable = tablestoreTrack.timestreamClient.metaTable();
// write meta
metaTable.put(meta);
TimestreamDataTable dataTable = tablestoreTrack.timestreamClient.dataTable(tablestoreTrack.config.getTrackDataTableName());
for (int i = 0; i < positions.size(); i++) {
Point point = new Point.Builder(positions.get(i).getTimestamp(), TimeUnit.MILLISECONDS)
.addField("lat", positions.get(i).getCoords().getLatitude())
.addField("lot", positions.get(i).getCoords().getLongitude())
.addField("accuracy", positions.get(i).getAccuracy())
.addField("altitude", positions.get(i).getAltitude())
.addField("altitudeAccuracy", positions.get(i).getAltitudeAccuracy())
.addField("speed", positions.get(i).getSpeed())
.build();
// write data
dataTable.asyncWrite(identifier, point);
}
}
查詢跑步/騎行記錄
public void listTrackMeta(SportObject sportObject) {
Filter filter = and(
Tag.equal("objectID", sportObject.getObjectId()),
Tag.equal("objectType", sportObject.getSportObjectType())
);
TimestreamMetaIterator iter = tablestoreTrack.timestreamClient.metaTable()
.filter(filter)
.selectAttributes("distance", "starttime")
.fetchAll();
System.out.print(iter.getTotalCount());
while (iter.hasNext()) {
TimestreamMeta meta = iter.next();
String title = meta.getIdentifier().getName();
long distance = meta.getAttributeAsLong("distance");
long timestamp = meta.getAttributeAsLong("starttime");
System.out.println(String.format("title: %s, distance:%d meters,timestamp:%d", title, distance, timestamp));
}
}
返回的數據如下:
title: 4月17日晚上騎行, distance:3000 meters,timestamp:1557316433
實際場景中數據會復雜很多,需要在demo的基礎上添加屬性字段。
示例代碼開源
歡迎加入
表格存儲(Tablestore)推出了很多貼近用戶場景的文章與示例代碼,歡迎大家加入我們的釘釘公開交流群一起討論,群號:11789671。
總結
以上是生活随笔為你收集整理的mysql 轨迹数据存储_基于Tablestore实现海量运动轨迹数据存储-阿里云开发者社区...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 早报:联想多款新品发布 英伟达召开202
- 下一篇: 本世纪仅有4次!闰二月为何少见?专家科普