导航引擎结构分析之二

数据管理模块的设计是导航结构设计的关键之一。

因为它涉及到产品的性能、依赖关系、可变性(某些结构是不变的,但一些业务数据是无法预测其变化的)、数据访问的安全性。它又是导航中大部分模块的交互中心。


常用的数据管理、通信方式

1. 一个线程内,不同函数模块间数据通信: 栈(局部变量),堆(内存)变量实现数据的保存和通信,无需加锁、同步、信号量机制

2. 两个线程间:信号量、消息队列、事件event。一个进程内的多线程共享数据空间,所有的静态变量都可以作为线程共享的内存空间。

3. 进程间通信:管道、有名管道、消息队列、信号量、共享内存、信号、socket套接字。需要通过操作系统内核对数据进行传递。

4. 分布式、网络通信:socket套接字、D-bus、

品质需求

导航的数据管理模块一般又称数据访问层,或者Data Access,其目的用于各模块间的数据交换。

这里对其品质需求做进一步的分析:

1. 性能对通信机制的限制。

独立模块间的通信机制可以通过socket或D-bus通信,这样模块的灵活性更高。模块之间彼此不需知道对方的存在,只须采用“订阅-通知”或“请求-反馈”的方式进行数据通信。

但上述方式不适合实时性要求高的场景,如任务切换(消息队列处理),复杂的状态处理算法都会造成延迟,异步处理的Socket通信方式对于DataAccess不太合适。

2. 数据载入

当前位置的附件的地图数据,规划的导航线路的周边地图数据,是很多模块共同使用的数据。

这些数据都是从数据库中载入,文件的读取开销大,理想的设计是载入一次,共同使用。

3. 数据传递

除了静态数据库的地图数据,还有一些动态数据,如计算的路径、交通信息,也是在各模块间共同使用。若每个模块都复制一份这样的数据,那么数据复制会增加CPU的载荷,并且需要更多的内存。

所以,以上场景要求数据的共享或交换通过“共享内存”来实现,而不是socket或D-bus通信。共享内存的机制能跨进程和地址空间工作。


需求分析

1. 哪些模块使用DataAccess?
1)NDS 或数据库的provider: 读取地图数据,并转换为DataAccess中的格式
2)DestinationInput : 一些POI数据的存放
3)Positioning:定位模块,获取自身位置的周边数据,将自身位置与地图匹配
4)Routing:路径规划,地图数据是必不可少的
5)Guidance:导航,获取地图,并用于必要导航标志渲染或语音提示
6)RouteInfo: 计算导航过程的开销,需要详细的地图属性,和交通信息
7)TrafficInfo:包括静态交通信息,动态交通信息数据,将数据存放在DA中,供其他模块使用
8)Mapviewer: 渲染模块需要地图数据渲染数据
....

2. 各个模块的数据需求如何分类?
1)原始NDS地图数据:包括DB中的NODE,LINK,POI,ROUTE,ADAS等信息
2)Route信息:由Routing模块产生的path,结合对应的tile数据,供其他模块使用
3)Maneuver信息:由route导航产生的实时计算的信息,如cost,tiles,进一步供routing和guidanceviewer使用
4)Traffic实时交通信息:仍分为cost和tiles类信息
5)Weather类信息:cost与tile
6)style:风格配置,这块数据相对独立
7)其他:如记录历史路径规划信息等
...

3. 如何设计DA的缓存?
1)DA是使用共享内存的方式来作为缓存cache,之所以叫缓存,是因为整个地图数据太大,无法一下子放入共享内存,所以,根据需要,将所需的地图数据放入共享内存,这些数据会一直存在直到没有模块再使用它们,缓存再清理这些数据。这些数据可以通过指针引用的方式直接被使用。引用计数的方式,可以防止数据在使用中被清理。但若过多的数据不被释放,不合理的使用缓存,容易造成内存溢出。
2)还有一类数据,当一个模块将其数据放入DA缓存中,就会产生一个引用,防止其被删除,直到指定的客户端来读取,这样的使用方式其实是将缓存区一个共享堆,用于数据传递。
3)缓存内的每一个数据对象可以看做一个容器(与stl中的容器概念不同),由于不同的进程有不同的数据空间,共享内存中不能使用指针或者容器直接使用指针引用,或者是虚函数(其内部实现机制仍是指针),只能使用地址偏移的方式来实现容器内指针或引用的功能。
4)缓存内会有很多容器对象,如何有效的组织和查询一个对象?
每个对象可以有一个key,DA可以利用这些key散列,或顺序组织这些容器对象。但会产生另外一个问题:不同的数据直接出现地址冲突?应该怎么办?
为解决冲突问题,需要更多的信息:对每种数据建立一个目录表,这个目录表也可以成为容器对象key的一部分。需要有专门的结构和代码来管理这个目录,其他模块需要知道这个目录,并通过这个目录来分配存储、读取数据。
5)数据共享的同步处理方式。为提高DA的性能,对于容器对象的数据可以不使用同步机制,这样每个数据只能写一次(在建立对象的时候),不能修改,若想修改数据,需要建立新的数据容器,并且新数据容器可以与key建立联系,便于查找。而对于旧对象,仍在缓存中,直到没有其他模块使用。

同步、异步、阻塞、非阻塞的概念,下面两篇文章写的比较透彻:
http://blog.csdn.net/historyasamirror/article/details/5778378
http://blog.csdn.net/ixidof/article/details/7108771

4. 如何设计DA的对外接口?
1)设计数据访问接口accessor:一般情况下,其他模块访问DA中数据,需要通过专门的接口,而不是直接访问缓存的数据。所以需要设计一个accesor对象,包括对所需的数据结构和对数据的读写操作,accessor中的代码是在其他模块client端执行的。
2)同步函数调用:accessor函数一般设计成同步函数调用的形式(为了实时性能),但为了防止client端被阻塞,卡死,client一般会起专门的worker thread来使用accessor。
3)接口的抽象:若使用C++,可以尽量抽象一些接口,使近似的数据采用同样的接口,方便client端使用,而且client端代码可以被复用
4)每种accessor对应一种数据源:accessor的实例化,与client的关系,与目录表的关系,根据实际情况进行设计。

同步的方式:
mutex(c++)或者sychronized(java)
前提概念:线程 同步函数调用,异步函数调用,回调 的区别?

5. 如何使用accessor接口?
每个导航引擎模块都可以使用accessor接口,DA的内容最好仅限于引擎内部使用,而不开放给其他层面使用,如controller。
每个导航引擎模块既可以作为client端读取数据,又可以作为provider端写数据。
client端与provider可以是不同的模块,不同的模块可以不直接交互,而是交给DA的一个专门的协调消息的模块来处理。这样,client端与provider端可以相互独立,不必耦合在一起。

6. 其他需要考虑的技术细节
1)目录管理如何设计?
2)容器对象如何被清理?何时进行清理?
3)数据key的散列算法如何选择?
4)如何慎重选择数据同步的方式,提高性能?
....

DataAccess中有很多的技术需要根据实际情况来使用,也有很多需要改进的地方。
一些东西后面再补充吧。

导航引擎结构分析之二

上一篇:下拉刷新控件


下一篇:一款分布式系统监控及网络监控的企业级神器zabbix