
随着移动设备的普及,越来越多的业务具备了时空属性,例如快递,试试跟踪包裹、快递员位置。例如实体,具备了空间属性。
例如餐饮配送,送货员位置属性。例如车辆,实时位置。等等。
其中两大需求包括:
1、对象位置实时跟踪,例如实时查询某个位点附近、或某个多边形区域内的送货员。
2、对象位置轨迹记录和分析。结合地图,分析轨迹,结合路由算法,预测、生成最佳路径等。
以快递配送为例,GPS设备实时上报快递员轨迹,写入位置跟踪系统,同时将轨迹记录永久保存到轨迹分析系统。
由于快递员可能在配送过程中停留时间较长(比如在某个小区配送时),上报的多条位置可能变化并不大,同时考虑到数据库更新消耗,以及位置的时效性,可以避免一些点的更新(打个比方,上一次位置和当前位置变化量在50米时,不更新)。
动态更新可以减少数据库的更新量,提高整体吞吐能力。

1、建表
create table t_pos (
uid int primary key, -- 传感器、快递员、车辆、。。。对象ID
pos point, -- 位置
mod_time timestamp -- 最后修改时间
);
create index idx_t_pos_1 on t_pos using gist (pos);
真实环境中,我们可以使用PostGIS空间数据库插件,使用geometry数据类型来存储经纬度点。
create extension postgis;
create table t_pos (
uid int primary key, -- 传感器、快递员、车辆、。。。对象ID
pos geometry, -- 位置
mod_time timestamp -- 最后修改时间
);
create index idx_t_pos_1 on t_pos using gist (pos);
2、上报位置,自动根据移动范围,更新位置。
例如,移动距离50米以内,不更新。
insert into t_pos values (?, st_setsrid(st_makepoint($lat, $lon), 4326), now())
on conflict (uid)
do update set pos=excluded.pos, mod_time=excluded.mod_time
where st_distancespheroid(t_pos.pos, excluded.pos, 'SPHEROID["WGS84",6378137,298.257223563]') > ?; -- 超过多少米不更新
通常终端会批量上报数据,例如每隔10秒上报10秒内采集的点,一次上报的数据可能包含多个点,在PostgreSQL中可以以数组存储。
create table t_pos_hist (
uid int, -- 传感器、快递员、车辆、。。。对象ID
pos point[], -- 批量上报的位置
crt_time timestamp[] -- 批量上报的时间点
);
create index idx_t_pos_hist_uid on t_pos_hist (uid); -- 对象ID
create index idx_t_pos_hist_crt_time on t_pos_hist ((crt_time[1])); -- 对每批数据的起始时间创建索引
有必要的话,可以多存一个时间字段,用于分区。
写入并合并,同时判断当距离大于50时,才更新,否则不更新。
(测试)如果使用point类型,则使用如下SQL
insert into t_pos values (1, point(1,1), now())
on conflict (uid)
do update set pos=excluded.pos, mod_time=excluded.mod_time
where t_pos.pos <-> excluded.pos > 50;
(实际生产)如果使用PostGIS的geometry类型,则使用如下SQL
insert into t_pos values (1, st_setsrid(st_makepoint(120, 71), 4326), now())
on conflict (uid)
do update set pos=excluded.pos, mod_time=excluded.mod_time
where st_distancespheroid(t_pos.pos, excluded.pos, 'SPHEROID["WGS84",6378137,298.257223563]') > 50;
首先生成1亿随机空间对象数据。
postgres=# insert into t_pos select generate_series(1,100000000), point(random()*10000, random()*10000), now();
INSERT 0 100000000
Time: 250039.193 ms (04:10.039)
压测脚本如下,1亿空间对象,测试动态更新性能(距离50以内,不更新)。
vi test.sql
\set uid random(1,100000000)
insert into t_pos
select uid, point(pos[0]+random()*100-50, pos[1]+random()*100-50), now() from t_pos where uid=:uid
on conflict (uid)
do update set pos=excluded.pos, mod_time=excluded.mod_time
where t_pos.pos <-> excluded.pos > 50;
压测结果,动态更新 21.6万点/s,187亿点/天。
pgbench -M prepared -n -r -P 1 -f ./test.sql -c 64 -j 64 -T 120
number of transactions actually processed: 26014936
latency average = 0.295 ms
latency stddev = 0.163 ms
tps = 216767.645838 (including connections establishing)
tps = 216786.403543 (excluding connections establishing)
每个UID,每批写入50条:写入速度约 467.5万点/s,4039亿点/天。
压测时,写多表,压测使用动态SQL。
do language plpgsql $$
declare
begin
for i in 0..127 loop
execute 'create table t_pos_hist'||i||' (like t_pos_hist including all)';
end loop;
end;
$$;
create or replace function import_test(int) returns void as $$
declare
begin
execute format('insert into t_pos_hist%s values (%s, %L, %L)', mod($1, 128), $1,
array[point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1), point(1,1)] ,
array['2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10', '2018-01-01 10:10:10']);
end;
$$ language plpgsql strict;
vi test1.sql
\set uid random(1,100000000)
select import_test(:uid);
pgbench -M prepared -n -r -P 1 -f ./test1.sql -c 56 -j 56 -T 120
number of transactions actually processed: 11220725
latency average = 0.599 ms
latency stddev = 5.452 ms
tps = 93504.532256 (including connections establishing)
tps = 93512.274135 (excluding connections establishing)
1、块级索引(BRIN),在时序属性字段上,建立块级索引,既能达到高效检索目的,又能节约索引空间,还能加速写入。
《PostgreSQL BRIN索引的pages_per_range选项优化与内核代码优化思考》
《万亿级电商广告 - brin黑科技带你(最低成本)玩转毫秒级圈人(视觉挖掘姊妹篇) - 阿里云RDS PostgreSQL, HybridDB for PostgreSQL最佳实践》
《PostGIS空间索引(GiST、BRIN、R-Tree)选择、优化 - 阿里云RDS PostgreSQL最佳实践》
《自动选择正确索引访问接口(btree,hash,gin,gist,sp-gist,brin,bitmap...)的方法》
《PostgreSQL 并行写入堆表,如何保证时序线性存储 - BRIN索引优化》
2、阿里云HDB PG特性:sort key , metascan
与BRIN类似,适合线性数据,自动建立块级元数据(取值范围、平均值、CNT、SUM等)进行过滤。
3、空间索引
GiST, SP-GiST空间索引,适合空间数据、以及其他异构数据。
4、动态合并写,根据位置变化量,自动判断是否需要合并更新。
insert on conflict语法,在do update里面,可以进行条件过滤,当位置变化超过N米时,才进行更新。
5、数组、JSON、KV等多值类型。
特别适合多值属性,例如批量上传的轨迹,通常GPS终端上报位置并不是实时的,可能存在一定的 延迟(例如批量上报)。使用数组、JSON都可以存储。
如果使用数组存储,将来分析轨迹时,依旧可以unnest解开,绘制轨迹。
1、动态位置变更:1亿被跟踪对象,TPS:21.6万,动态更新21.6万点/s,187亿点/天。
2、轨迹写入:tps约10万,写入467.5万点/s,4039亿点/天。
《PostgreSQL + PostGIS + SFCGAL 优雅的处理3D数据》
《PostGIS 距离计算建议 - 投影 与 球 坐标系, geometry 与 geography 类型》
《PostgreSQL 10 + PostGIS + Sharding(pg_pathman) + MySQL(fdw外部表) on ECS 部署指南(适合新用户)》
《PostGIS 空间索引(GiST、BRIN、R-Tree)选择、优化 - 阿里云RDS PostgreSQL最佳实践》
《PostGIS 坐标转换(SRID)的边界问题引发的专业知识 - ST_Transform》
《无人驾驶背后的技术 - PostGIS点云(pointcloud)应用 - 2》
《无人驾驶背后的技术 - PostGIS点云(pointcloud)应用 - 1》
《视觉挖掘与PostGIS空间数据库的完美邂逅 - 广告营销\圈人》
《开放地图OpenStreetMap与PostGIS的三生三世十里桃花》
《PostGIS 地理信息、栅格数据 多核并行处理(st_memunion, st_union)》
《蜂巢的艺术与技术价值 - PostgreSQL PostGIS's hex-grid》
《如何建立GIS测试环境 - 将openstreetmap的样本数据导入PostgreSQL PostGIS库》
《GIS附近查找性能优化 - PostGIS long lat geometry distance search tuning using gist knn function》
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵
电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。 准备工作: 1、U盘一个(尽量使用8G以上的U盘)。 2、一台正常联网可使用的电脑。 3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。 4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。 U盘启动盘制作步骤: 注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc
我需要一个非常简单的字符串验证器来显示第一个符号与所需格式不对应的位置。我想使用正则表达式,但在这种情况下,我必须找到与表达式相对应的字符串停止的位置,但我找不到可以做到这一点的方法。(这一定是一种相当简单的方法……也许没有?)例如,如果我有正则表达式:/^Q+E+R+$/带字符串:"QQQQEEE2ER"期望的结果应该是7 最佳答案 一个想法:你可以做的是标记你的模式并用可选的嵌套捕获组编写它:^(Q+(E+(R+($)?)?)?)?然后你只需要计算你获得的捕获组的数量就可以知道正则表达式引擎在模式中停止的位置,你可以确定匹配结束
我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来
我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion
因为我现在正在做一些时间测量,我想知道是否可以在不使用Benchmark类或命令行实用程序time的情况下测量用户时间或系统时间。使用Time类只显示挂钟时间,而不显示系统和用户时间,但是我正在寻找具有相同灵active的解决方案,例如time=TimeUtility.now#somecodeuser,system,real=TimeUtility.now-time原因是我有点不喜欢Benchmark,因为它不能只返回数字(编辑:我错了-它可以。请参阅下面的答案。)。当然,我可以解析输出,但感觉不对。*NIX系统的time实用程序也应该可以解决我的问题,但我想知道是否已经在Ruby中实
我已经找到了几个使用datamapper的示例,并且能够让它们正常工作。不过,所有这些示例都是针对sqlite数据库的。我正在尝试将数据映射器与postgresql一起使用。我将datamapper中的调用从sqlite3更改为postgres,并且我已经安装了dm-postgres-adapter。但它仍然不起作用。我还需要做什么? 最佳答案 与SQLite不同,PostgreSQL不将数据库存储在单个文件中。在你拥有createdyourdatabase之后,尝试这样的事情:DataMapper.setup:default,{: