我是靠谱客的博主 神勇哈密瓜,最近开发中收集的这篇文章主要介绍物联网应用中实时定位与轨迹回放的解决方案 – Redis的典型运用(转载),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

物联网应用中实时定位与轨迹回放的解决方案 – Redis的典型运用(转载)
2015年11月14日| by: nbboy| Category: 系统设计, 缓存设计, 高性能系统
摘要 我们解决某个问题,很多时候并不在于你掌握了某个工具或某项技术,而在于你对该场景下该问题的本质理解。 这则博文,针对物联网应用中实时定位与轨迹回放的问题,阐述最为简要的解决之道。



目录[-]

1. 系统需求
2. 应用场景
3. 数据量估算
4. 项目难点及解决
4.1如何存储千亿级的坐标记录且便于检索?
4.1.1 关系型数据库的不足
4.1.2 NoSql产品选型
4.1.3 数据存储结构设计
4.1.3 高性能、高可靠性的部署
4.1.4 数据定期维护设计
4.2 如何实现标签定位的实时性与可靠性?
5. 最终方案示意
6. 补充(2015-11-13)–注意啦!

1. 系统需求

实时监控:系统能实时接收2000个超rfid标签的坐标信息, 然后在3D模型界面展示各标签的实时位置。
轨迹回放:系统可查询任一标签在最近1个月内的坐标信息, 然后在3D模型界面回放该标签在这个时间段内的运动轨迹。

2. 应用场景

监狱监管:给犯人配戴腕表或工牌,在腕表或工牌中嵌入rfid标签,实现对犯人行踪的实时监控、事先预警、事后回查等。如图:

输入图片说明

3. 数据量估算

1个超rfid标签在1秒内会产生10条80byte的坐标信息,即1个标签1秒的数据量是约0.8kb(坐标信息的结构为:标签id, x坐标,y坐标,z坐标,时间),以此推算:

1个标签 1分钟会产生记录数是 600条, 约54 KB
1个标签 1小时会产生记录数是3.6万条, 约3.2 MB
1个标签 1天会产生记录数是86.4万条, 约76.8 MB
1个标签 1个月会产生记录数是2.6亿条, 约2.3 GB

2000个标签 1天产生的记录数是172亿条, 约150 GB
2000个标签 1个月产生的记录数是5200亿条, 约4.4 TB

4. 项目难点及解决

4.1如何存储千亿级的坐标记录且便于检索?

如何存储海量的坐标信息,且能快速地从其中检索出指定犯人(也即标签)在某个时间范围内的坐标?

4.1.1 关系型数据库的不足

把千亿级的记录存储在关系型数据库,肯定要分库分表。如果按标签、按天分表存放,每个表存放一个标签的一天的坐标信息,那么每个表只有84.6万条记录,但有多达6万张表(=2000 X 30)。 由于轨迹回放时,是选定标签后才进行的,也即必然会根据标签检索,而从一张84.6万条记录的表中找出某个时间范围内的坐标是不成问题的。

但这方案实现起来很繁锁,因为每当新的一天来到,系统就要自动为所有标签创建新的表、并删除掉30天前创建的表;每当有新的标签加入时,也要立即为这个标签创建表。 而且,每当系统自动创建一个表后,还要为这个表创建索引,使得根据时间范围检索时能达到相应的性能要求。

以上方案,系统做了太多的DDL操作,显然不合适; 另外,由于对坐标信息的写入并没有事务方面的要求,多写或少写一条对系统几乎没有影响。 所以我们考虑用NoSql产品。

4.1.2 NoSql产品选型

NoSql产品有很多,典型的列式存储有Hbase,Key-Value型存储有Redis,文档型存储有MongoDB。

Hbase,其部署、维护较为复杂,是基于分布式文件系统的数据库。它通常不会用于仅存储某类或几类的数据,而会存储大量的各类数据,以用作分布式计算的基础。
Redis,其部署、维护都很简单,适合于对结构简单的数据提供高速缓存和持久存储。
MongoDB,其部署、维护比Redis稍复杂,其优点在于对结构不固定、不规则的数据提供存储。

由于Redis的数据结构很适合对本系统的坐标信息进行高效读写,最终选定了Redis。

4.1.3 数据存储结构设计

Redis的数据结构有以下几种:

String, 类似于java的 Map<key, String>。采用jedis的客户端代码示例:

jedis.set("key001", "value01"));

List, 类似于java的 Map<key, List<String>>。采用jedis的客户端代码示例:

jedis.lpush("key001", "定远");
jedis.lpush("key001", "致远");

List warshipList= jedis.lrange("key001", 0, -1);//从"key001" 取得所有数据项

Set, 类似于java的 Map<key, Set<String>>。采用jedis的客户端代码示例:

jedis.sadd("key001", "定远");
jedis.sadd("key001", "致远");

Set warshipSet = jedis.smembers("key001");//从"key001" 取得所有数据项

SortedSet, 与Set类似, 但提供一列属性score,用于排序。采用jedis的客户端代码示例:

//将北洋舰队的舰只名称、吨位写入"key001"
jedis.sadd("key001", 7335, "定远");
jedis.sadd("key001", 2300, "致远");

//从"key001"取得吨位在3000~8000之间的舰只名称
Set warshipSet = jedis.zrangeByScore("key001", 3000, 8000);

Hash, 类似于java的 Map<key, Map<String,String>>。采用jedis的客户端代码示例:

//将北洋舰队各舰只的基本信息写入"key001"
jedis.hset("key001", "定远", "铁甲舰,吨位7335,马力6200,1882,德国")
jedis.hset("key001", "致远", "巡洋舰,吨位2300,马力7500,1886,英国")

//从"key001" 取得所有舰只名称
List warshipList = jedis.hkeys("key001");

//从"key001" 取得"定远"的描述
String warshipDesc= jedis.hget("key001", "定远");

以上6种数据结构,第1种用来存储单个字符串,后面5种用于存储List,Set对象,但只有SortedSet提供了根据数据值的范围进行查找的功能。很显然,我们应该采用SortedSet来存储坐标信息。

如果每个key只存放一个标签的一天的坐标信息,那么就会有多达6万(=2000 X 30)个key,而Redis最多只支持 10万个key。考虑到以后可能需要支持更多标签,让每个key存放一个标签的连续三天的坐标信息,那么就只需要2万个key,于是key命名可设计为: lableId + 起始日yyyyMMdd + 终止日yyyyMMdd。

比如,可将编号为“rfid1002023”的标签的11月1日~11月3月的坐标信息写入到key: “rfid1002023-20151101-20151103″,并且把坐标时间(dd hh:mi:ss.SSS)作为score:

//标签“rfid1002023” 在11月1日17时20分21秒内,其坐标从位置(20.3, 34.0, 0.05)移动到(21.7, 34.5, 0.05)..
//则系统会以如下方式往redis写入:
jedis.sadd("rfid1002023-20151101-20151103", "01 17:20:21.008", "20.3, 34.0, 0.05");
jedis.sadd("rfid1002023-20151101-20151103", "01 17:20:21.395", "21.7, 34.5, 0.05");

//如果要查询标签“rfid1002023” 在11月1日17:15分~17:25分的记录,以便回放其在这段时间内的轨迹,则调用如下代码:
Set xyzSet = jedis.zrangeByScore("rfid1002023-20151101-20151103", "01 17:15:00", "01 17:25:00");

4.1.3 高性能、高可靠性的部署

现采用4台计算机部署两组主从模式的redis server:

A组:A-Master, A-Slave; 每台机配置1块3T的硬盘、8G以上的内存;
B组:B-Master, B-Slave; 每台机配置1块3T的硬盘、8G以上的内存;

另外采用一台计算机做监控,安装redis sentinel,以达到主从切换的目的;再将A-Master和B-Master设置为集群,就实现了读写的负载均衡。

Redis有两持久化的方式:RDB,AOF。由于RDB能更灵活地控制写入的频率,我们选择RDB方式,并将4台redis server按如下方式设置:

save 10 1 #当有1条Key的数据被改变时,10秒持久化一次
save 5 2 #当有2条Key的数据被改变时,5秒持久化一次
save 3 10 #当有10条Key的数据被改变时,3秒持久化一次

4.1.4 数据定期维护设计

Redis提供为每个key设定生命周期的功能,一旦key的存活时间超过生命周期,则这个key以及相应的数据都会都清除。所以不需要编写程序定时删除数据,让我们省了不少事。

回到本项目,我们在创建key: “rfid1002023-20151101-20151103″或者说首次写入这个key后,通过以下代码指定保存时间为1个月: jedis.expire("rfid1002023-20151101-20151103", 3600*24*31) 。 就这么简单!

4.2 如何实现标签定位的实时性与可靠性?

原始坐标信息来自基站,经路由器等网络设施传给WEB服务器,由WEB服务器对原始坐标进行相应转换,传给3D模块展现。那么,使这些坐标信息尽可能实时地、可靠地传给3D模块,是做好实时定位的关键。

前面的存储设计已经采用了Redis, 现在可以再次利用Redis的内存数据库特性。 我们再部署一组主从模式的redis server, 关闭持久化,配置主从切换。

我们应该采用Hash结构来存每个标签的最新的坐标信息,只需要设计一个key,可命名可为:“the-newest-xyz”。假如当前标签“rfid1002023”的坐标为“20.3, 34.0, 0.05”,那么以下代码就把这个坐标写入到了redis内存:

jedis.hset("the-newest-xyz", "rfid1002023", "20.3, 34.0, 0.05");
jedis.expire("the-newest-xyz",10)//将生命周期设为10s, 用于标签损坏或越防区后报警

//其它标签
jedis.hset("the-newest-xyz", "rfid1069551", "80.3, 36.0, 10.2");

这样,3D模块从这组redis server取得所有标签的坐标即可,代码如下:

List labelList = jedis.hkeys("the-newest-xyz");//取所有标签
String xyz= jedis.hget("the-newest-xyz", "rfid1069551");//取标签“rfid1069551”的坐标

5. 最终方案示意

输入图片说明

本文着重描述了系统中最为关键的坐标存储与实时读取的方案。但整个系统的实现,还需要许多基础模块的辅助,如警员信息管理、犯人信息管理、标签管理、标签绑定、楼层管理、基站管理、监区管理、坐标转换、菜单及权限管理等, 上图中的mysql就是用于存放警员信息、犯人信息、标签信息、楼层信息、权限及菜单等数据的。

6. 补充(2015-11-13)–注意啦!

感谢网友”Frank_mc”和”JavaGG”! 经他俩指出,在这里采用redis做持久存储是不可行的,因为redis启动时会加载整个数据文件到内存,这里的数据文件相对内存来说是远不够用的。

本文上述的持久化存储方案,是我预想的以为更理想的方案,也是当时(2013年)我做的备选方案。而实际项目中的存储方案是采用分目录写文件的,但由于这个方案很传统而没兴趣介绍。所以关于坐标存储这块,我没有按实际方案介绍,而是阐述我以为的理想方案。

下面,我介绍”坐标存储” 这块实际所采用的方案:

1 分目录存放文件:

- 1级目录名:年月(yyyyMM)
- 2级目录名:天(dd)
- 3级目录名:标签id

这样1级目录就1~2个,每个1级目录下有30个2级目录,每个2级目录下有2000个3级目录,每个3级目录下有24个文件。每个文件是只存放1个标签的1个小时的数据量,约3.2MB。

2 提供查询接口:设计相应算法定位到要查找的文件,载入文件内容,再进行相应的汇总、过滤。

3 定时删除文件:只保留1个月内创建的文件。

4 读写可靠性:磁盘做raid。

最后

以上就是神勇哈密瓜为你收集整理的物联网应用中实时定位与轨迹回放的解决方案 – Redis的典型运用(转载)的全部内容,希望文章能够帮你解决物联网应用中实时定位与轨迹回放的解决方案 – Redis的典型运用(转载)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(53)

评论列表共有 0 条评论

立即
投稿
返回
顶部