前言

现在越来越多的人都开始关心自己的运动数据,比如每日的计步、跑步里程、骑行里程等。运动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实现海量运动轨迹数据存储相关推荐

  1. mysql 轨迹数据存储_基于Tablestore实现海量运动轨迹数据存储-阿里云开发者社区...

    前言 现在越来越多的人都开始关心自己的运动数据,比如每日的计步.跑步里程.骑行里程等.运动APP与运动类的穿戴设备借助传感器.地图.GPS定位等技术,收集好运动数据以后,通过与互联网社交功能结合,产生 ...

  2. hdfs 数据迁移_基于JindoFS+OSS构建高效数据湖

    作者:孙大鹏,花名诚历,阿里巴巴计算平台事业部 EMR 技术专家,Apache Sentry PMC,Apache Commons Committer,目前从事开源大数据存储和优化方面的工作. 为什么 ...

  3. hdfs 数据迁移_基于 JindoFS+OSS 构建高效数据湖

    为什么要构建数据湖 大数据时代早期,Apache HDFS 是构建具有海量存储能力数据仓库的首选方案.随着云计算.大数据.AI 等技术的发展,所有云厂商都在不断完善自家的对象存储,来更好地适配 Apa ...

  4. 千万数据去重_基于 Flink 的百亿数据去重实践

    在工作中经常会遇到去重的场景,例如基于 App 的用户行为日志分析系统,用户的行为日志从手机客户端上报到 Nginx 服务端,通过 Logstash.Flume 或其他工具将日志从 Nginx 写入到 ...

  5. python大数据平台_基于腾讯位置大数据平台的全球移动定位数据Python爬取与清洗...

    前不久投稿了一篇论文是以腾讯位置大数据为基础进行人口空间化研究的,但是还未见刊,见刊后会给大家分享下具体的研究方法. 首先打开腾讯位置大数据星云图链接:https://xingyun.map.qq.c ...

  6. python收集数据程序_基于Python语言的互联网数据收集软件的设计

    软件建立所需的工具及其版本 编写环境与 IDE Python3.5.2 Windows10 PyCharm 2016.3 Sublime Text3 第三方库与版本号 Requests 2.12.1 ...

  7. 数据科学家数据分析师_站出来! 分析人员,数据科学家和其他所有人的领导和沟通技巧...

    数据科学家数据分析师 这一切如何发生? (How did this All Happen?) As I reflect on my life over the past few years, even ...

  8. 基于Tablestore管理海量快递轨迹数据架构实现

    快递轨迹管理 对于一个快递公司,在全国范围内有着大量的快递点.快递员.运输车辆以及仓储中心.而快递自产生后,就会在这些地点.人物之间流转.因而,一套完善的快递管理追踪系统是快递公司的重要管理工具: 用 ...

  9. mysql修改工资字段_基于Linux的MySQL操作实例(修改表结构,MySQL索引,MySQL数据引擎)...

    基于Linux的MySQL操作实例(修改表结构,MySQL索引,MySQL数据引擎) 前言 本篇是基于Linux下针对MySQL表结构的修改,MySQL索引的操作以及MySQL数据引擎的配置和说明. ...

最新文章

  1. esc pos java打印图片_android 调用蓝牙打印机(ESC/POS 热敏打印机)打印小票和图片...
  2. (六十三)第四章复习题
  3. linux下镜像播放视频,linux下挂载iso镜像的方法
  4. PWA(Progressive Web App)入门系列:Cache Storage Cache
  5. Panoptic Segmentation论文笔记
  6. iOS学习笔记-自己动手写RESideMenu
  7. Python机器学习笔记:异常点检测算法——Isolation Forest
  8. LabelSmooth
  9. 2021最新对比学习(Contrastive Learning)在各大顶会上的经典必读论文解读
  10. 渗透测试工具——漏洞扫描工具
  11. MATLAB2017B 下载
  12. 微信小程序保存图片以及分享给朋友
  13. WordPress 5文章编辑真难用 换回老版经典编辑器教程
  14. 论文译文——BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
  15. 第一章:第1章 CRM核心业务介绍--概述,crm架构,公司组织结构,软件开发的生命周期,crm项目的核心业务介绍。...
  16. Matlab中的元胞数组
  17. 三小时,阿里云使用docker部署redis(阅文无数)
  18. 【历史上的今天】3 月 24 日:苹果推出 Mac OS X;微软前任 CEO 出生;Spring 1.0 正式发布
  19. 论这两年不断突破心理底线的互联网薪水by OfferCome从猎头角度推测,搜狗的买卖对于互联网格局和薪水的影响烧钱薪水反思
  20. 从整车控制器VCU模型入门simulink(4)

热门文章

  1. yarn : 无法加载文件 ...Roaming\npm\yarn.ps1,因为在此系统上禁止运行脚本
  2. 驭势科技无人驾驶技术走向海外,AI国潮获赞满满
  3. Linux系统的grub.cfg文件损坏修复
  4. 牛客 小米校招 最大新整数 单调栈
  5. android屏幕分辨率适配总结
  6. 恐怖系列丨互联网幕后攻防:咳血的独角兽之血腥丛林中的无底线战争
  7. 高斯勒让德(Gauss-legendre)求解多重积分(python,数值积分)
  8. 【万字长文】2022年最全的搭建Web自动化测试框架教程
  9. 2019年下半年系统架构设计师上午真题及答案解析
  10. HTML 5 em strong dfn code samp kbd var cite 标签