基于Tablestore实现海量运动轨迹数据存储

本文涉及的产品
对象存储 OSS,20GB 3个月
文件存储 NAS,50GB 3个月
云备份 Cloud Backup,100GB 3个月
简介: 前言 现在越来越多的人都开始关心自己的运动数据,比如每日的计步、跑步里程、骑行里程等。运动APP与运动类的穿戴设备借助传感器、地图、GPS定位等技术,收集好运动数据以后,通过与互联网社交功能结合,产生了一种新的运动模式。

前言

现在越来越多的人都开始关心自己的运动数据,比如每日的计步、跑步里程、骑行里程等。运动APP与运动类的穿戴设备借助传感器、地图、GPS定位等技术,收集好运动数据以后,通过与互联网社交功能结合,产生了一种新的运动模式。用户不仅可以查看与分析自己的运动数据,还可以分享跑步路线、骑行路线给附近的运动好友,还可以组团跑步、推荐运动设备等,吸引了各年龄段的用户。

核心需求

现在比较流行的一些运动APP和穿戴设备都提供了较为丰富的功能,甚至可以购物和社交。但运动轨迹管理、运动数据分析、附近的跑步路线/骑行路线、附近的运动团这些始终是核心功能点。
运动轨迹数据可以是穿戴设备生成的也可以是手机APP生成的,先在APP端存储,最后由手机APP批量上传到服务端。服务端和数据库都需要支持高并发的读写、数据库要支持海量的存储。
附近的运动好友、附近的运动路线、附近的运动团数据量相对会少一些,但需要支持地理位置检索。


cadb00b9aa5fd878aafbf8f2a2be2cf6dba5c4a6

数据模型

如下方左边的图所示,一次跑步或者骑行就会产生一条轨迹信息。我们可以把轨迹数据分成两个部分:跑步记录和轨迹点信息。
附近的人、附近的跑步路线这类数据,我们可以把它们都看成一个点。例如:我们认为跑步路线的起始点就是它的位置,于是就有了右下方的图:中间点是我,以我为中心,去查找附近N公里范围内的数据。

301ad248cb04eb4b85cd02da92b457d2e62c26c5 6cfb281d656fdd82f628a6cc2a2db5f94716c1de

技术选型

我们主要分析下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中有两类信息,如果当前主体是轨迹订单,属性列对应的是运动类型、起至时间,如果主体是设备,属性列对应的是对象类型,位置点和时间等。


7616519ada80c6a5c08d3e145bc439652239baa5

实现方案

基于上面的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<TrackPosition> 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<TrackPosition> 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的基础上添加属性字段。

示例代码开源

示例代码开源地址:SportTrack

欢迎加入

表格存储(Tablestore)推出了很多贴近用户场景的文章与示例代码,欢迎大家加入我们的钉钉公开交流群一起讨论,群号:11789671。


b264d4e3ad46a7caad16efab251a39df277bd23e
相关实践学习
消息队列+Serverless+Tablestore:实现高弹性的电商订单系统
基于消息队列以及函数计算,快速部署一个高弹性的商品订单系统,能够应对抢购场景下的高并发情况。
阿里云表格存储使用教程
表格存储(Table Store)是构建在阿里云飞天分布式系统之上的分布式NoSQL数据存储服务,根据99.99%的高可用以及11个9的数据可靠性的标准设计。表格存储通过数据分片和负载均衡技术,实现数据规模与访问并发上的无缝扩展,提供海量结构化数据的存储和实时访问。 产品详情:https://www.aliyun.com/product/ots
目录
相关文章
|
存储 传感器 运维
基于 Tablestore 时序模型构建车联网数据存储
背景最近几年,物联网得到了飞速的发展。在车联网、设备监控、网络监控、快递跟踪等物联网典型场景下,海量监控数据、轨迹数据、传感器数据被生产数来。这些数据产生频率高、数据量大、严重依赖采集时间,是典型的时序数据。传统的数据库是无法应对这种高写入的海量实时数据的,需要使用能够支持时序模型的时序数据库对这些数据进行储存和分析。表格存储时序模型是专门针对时序数据特点,为物联网、车联网等场景设计的。本文基于车
475 0
基于 Tablestore 时序模型构建车联网数据存储
|
存储 SQL 传感器
基于 Tablestore 时序存储的物联网数据存储方案
背景物联网时序场景是目前最火热的方向之一。海量的时序数据如汽车轨迹数据、汽车状态监控数据、传感器实时监控数据需要存放进入数据库。一般这类场景下存在如下需求数据高写入,低读取需要对写入数据进行基础的图表展示对写入数据进行聚合分析传统的关系型数据库并不适合此类场景,时序数据库脱颖而出。表格存储时序实例支持时序数据的存储,其具有如下特点:Serverless,分布式,低成本高写入支持优秀的索引能力对数据
1496 0
基于 Tablestore 时序存储的物联网数据存储方案
|
存储 SQL NoSQL
海量结构化数据存储技术揭秘:Tablestore存储和索引引擎详解
海量结构化数据存储技术揭秘:Tablestore存储和索引引擎详解
392 0
海量结构化数据存储技术揭秘:Tablestore存储和索引引擎详解
|
NoSQL 数据库 索引
海量结构化数据存储技术揭秘:Tablestore表设计最佳实践
前言 表格存储Tablestore是阿里云自研的面向海量结构化数据存储的Serverless NoSQL多模型数据库。在处理海量数据时,方案设计非常重要,合理的设计才能够发挥出数据库的性能水平。本文主要介绍Tablestore在表设计方面的一些实践经验,供大家参考。
10059 1
|
索引 存储 NoSQL
海量结构化数据存储技术揭秘:Tablestore存储和索引引擎详解
前言 表格存储Tablestore是阿里云自研的面向海量结构化数据存储的Serverless NoSQL多模型数据库。Tablestore在阿里云官网上有各种文档介绍,也发布了很多场景案例文章,这些文章收录在这个合集中《表格存储Tablestore权威指南》。
24859 0
|
存储 监控 NoSQL
基于Tablestore管理海量快递轨迹数据架构实现
对于一个快递公司,在全国范围内有着大量的快递点、快递员、运输车辆以及仓储中心。而快递自产生后,就会在这些地点、人物之间流转。因而,一套完善的快递管理追踪系统是快递公司的重要管理工具; 用户通过平台客户端下单后,产生唯一的快递单号作为唯一身份标识。
11979 1
|
存储 NoSQL Java
海量结构化数据存储技术揭秘:Tablestore存储和索引引擎详解 | 7月24号云栖夜读
今天的首篇文章,讲述了:表格存储Tablestore是阿里云自研的面向海量结构化数据存储的Serverless NoSQL多模型数据库。Tablestore在阿里云官网上有各种文档介绍,也发布了很多场景案例文章,这些文章收录在这个合集中《表格存储Tablestore权威指南》。
4872 0
|
存储 监控 NoSQL
Tablestore Timestream:为海量时序数据存储设计的全新数据模型
引言 随着近几年物联网的发展,时序数据迎来了一个不小的爆发。为了存储这些时序数据,各大企业纷纷推出自己的时序数据库。Tablestore作为阿里云自研的NoSQL多模型数据库,能够提供海量结构化数据存储以及快速的查询和分析服务,其在存储模型、数据规模以及写入和查询能力上,都能很好的满足时序数据的场景,另外已经支持很多时序类业务,例如监控类的云监控,事件类的阿里健康药品追踪以及快递包裹轨迹等。
6095 0
|
11月前
|
存储 索引
表格存储根据多元索引查询条件直接更新数据
表格存储是否可以根据多元索引查询条件直接更新数据?
93 3
|
2月前
|
DataWorks NoSQL 关系型数据库
DataWorks产品使用合集之如何从Tablestore同步数据到MySQL
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。