您的位置:首页 > 运维架构 > 网站架构

一个信息可视化Demo的设计(一):系统架构

2011-09-28 17:12 316 查看
一个信息可视化Demo的设计(一):系统架构

我可是要成为架构师的男人啊~

----写在前面

北京两个月的实习结束,回归武汉校园,期间完成了一个信息可视化的Demo,回顾这一历程有喜悦有收获,现分系统架构、Index & Search、算法设计和前端MVC架构四部分总结。因实习内容属于公司内部机密,本博文只做设计思路上的记录和探讨,并不会涉及任何与公司利益相关的工作内容。

一、前瞻

所谓信息可视化(Information Visualization)旨在研究大规模非数值型信息资源的视觉呈现,以及利用图形图像方面的技术与方法,帮助人们理解和分析数据。说白了,就是有些信息,其结构、类型比较复杂,信息量较大,各种数据之间的关系也很是纠结,无法或者很难直观地去用人工分析和理解信息所表达的含义。因为人对文字、数字类的东东并不敏感,但对图像、图表这些看的见的东东比较敏感,借助于信息可视化的技术,充分适当地对信息进行组织整理,使用表格、图形,图像展现数据、信息和知识,甚至提供足够好的交互,使得用户能够通过所见即所得(WYSIWYG)的方式分析、理解信息,洞察、定位隐含的漏洞或问题,发现形形色色的关系,理解在人工分析情况下不易发觉的事情,从而追根溯源地挖掘信息背景的问题与深层次原因。

信息可视化日益成为不同领域方向的关键要素:如科学技术研究工作,数字图书馆、数据挖掘、财务数据分析和市场研究、生产投靠过程的控制等。反观现在的互联网行业,如果说过去10年是搜索技术大行其道的10年,那么基于互联网应用所产生的海量数据、海量信息进行数据挖掘,以及大规模机器学习,从而分析、理解用户行为、用户喜好,并提供个性化、精确化的服务将成为未来10年最重要的技术和研究热潮之一。以数据和信息作为驱动而发展的时代,数据和信息将取代技术成为新的门槛和瓶颈。

然而,海量的信息是无法直观的使用人工进行甄别或分析,如何更为迅速快捷的定位和发现问题,信息可视化恰恰成为了处理这一尴尬的切入点。如机器学习过程中,训练出的模型即使能在样本集上跑出很好的效果,在真实数据集面前也未必能够达到理想的状态,这里面的原因一方面可能是由于样本集的选取不具备代表性,不能够客观地表征数据信息的分布,使得训练出来的模型泛化能力不足;另一方面可能是由于训练过程出现了过拟合现象,为了得到一致假设而使模型构造的极为精细和复杂,在样本集上进行良好,但切换到真实数据集上会就出现很多的问题。然而一个模型的训练需要依赖的信息量是海量的,同时训练过程大多很复杂。据我的了解,如某公司广告竞价排名训练所需的数据量为100T,训练时长为10小时左右,往往一个很琐碎的细节将导致整个训练过程的失败。即使在训练过程会输出很多的Log也无法很快地分析和发现问题的原因,如果能够提供一种可视化的方式,提供很多的辅助统计分析信息,则比人工直接去观察数据要高效的多。

二、需求

本文中与公司利益相关工作内容全部略去,抽象描述为:现有数据原料若干,类型各异、结构复杂,数据原料为机器学习算法的输入/输出,主要包括:特征描述信息Features File、输入数据信息Input File、人工标注样本信息HRS File和输出数据信息Decision Tree File。

训练过程概述:Features File & Input File & HRS File --> Training --> Model --> Decision Tree File,即我们将训练得到的模型以一种特征的格式,写入到叫做Decision Tree的文件中,Decision Tree所描述是实际上是一种树形的结构。

需求:用信息可视化的技术,实现各种类型辅助信息的图形化展示,如(Features的分布情况、使用率)、Training相关数据统计分析、基于HRS的训练过程、Decision Tree的结构、不符合人工标注的结果等;提供良好的交互方式,如图形化的拖拽、缩放、点击,支持表达式级别的检索。

三、思考

架构师在系统架构设计阶段,不应该去耦合某种具体的技术,架构的宏观设计应以功能性为出发点,从更高的抽象层次上考虑如何把一个系统的骨架结构给搭建起来。

打个比方,比如说现在实现一个类QQ的IM通讯软件,基本需求为:

(1)用户登入、登出;

(2)用户之间互发消息;

(3)用户之间传送文件。

作为架构师看到的应该是C/S模式的架构,Client与Server,Client与Client之间可以互通消息,至于用何种技术实现消息的传递,他在这一阶段的Design并不需要过分关心。

如果是我,我会先考虑抽象,数据都应该是以一种格式进行传递,会有一个抽象的Message Class统一管理数据包,进而派生出多种不同类型的数据包,一种简易的消息通讯数据包格式如下(伪码):

//抽象的数据包基类
Abstract Class Message
{
//XXXX…
}

//登入数据包
Class LoginMessage extends Message
{
// XXXX…
}

//登出数据包
Class LogoutMessage extends Message
{
// XXXX…
}

//用户聊天数据包
Class ChatMessage extends Message
{
// XXXX…
}

//文件传输数据包
Class FileTransMessage extends Message
{
// XXXX…
}

基于Abstract Class Message可以实现对数据包的统一发送管理。有了通讯协议,我会为之设计一个抽象层次上消息传递接口,且只从功能性上考虑,并不涉及任何一种具体的通信技术。一个抽象的传递数据的接口(伪码):

Interface MessageTrans
{
long SendMessage(Message message);
long Receive(Message message);
}

这样做的好处在于:接口统一,由于LoginMessage、LogoutMessage、ChatMessage以及FileTransMessage都是继承至Message,所以我们可以借助于这个接口发送各种不同类型的数据包。同时,你也可以根据业务需求,方便地扩展出更多类型的数据包,比如支持用户之间发送表情、传递图片等,而抽象层的接口统一使用了Message,不需要做任何修改。

当这一层次的Design完成之后,我们就可以考虑要具体实现这个系统,应该借助于哪种技术落地。比如刚才提到的,Client与Server,Client与Client之间的消息通信,其本质你可以把它理解为是两个进程间的通信。进程间通信的方式相信大家都知道吧,我就不多说了,常见的有:管道、共享内存、消息队列、Socket等。那么,如果Server和Client在同一台机器上,我们可以用管道来实现消息通信,如果Server和Client处于不同的机器上,我们可以使用Socket完成通信。很明显的,基于某项具体技术实现的通信Class就诞生了:

//基于管道实现进程间的消息传递
Class PIPOMessageTrans extends MessageTrans
{
long SendMessage(Message message)
{
//XXXX…
}
long Receive(Message message)
{
//XXXX…
}
}

//基于Socket实现进程间的消息传递
Class SocketMessageTrans extends MessageTrans
{
long SendMessage(Message message)
{
//XXXX…
}
long Receive(Message message)
{
//XXXX…
}
}

依据不同的需求,选择与之对应的通信方式,我们把功能接口落地了,但这一切并不会影响和改变抽象层次上的Design。抽象层仍然是具备了足够好的可扩展性,比如说你现在想使用消息队列,继承MessageTrans接口,派生相应的类实现就OK了。

说了这么多,无非表达一个观点:架构应依赖于抽象,而不应依赖于技术

四、架构

项目从设计之初到后期重构,再到Demo Show之前的灵感突发,一步步地改进,我先后给出了三种设计方案,篇幅关系,这里只讨论Version3.0的Design。从一开始讲需求的时候,就知道,我们需要把一些结构复杂的文件,转化成可展示、可交互的图形、图像、图表等信息,同时支持必要的检索功能。一个宏观上的架构图如下:



整个系统由后台(Backstage)和前台(Visualization)两部分组成,中间通信使用WCF完成,有关WCF的内容我会抽时间写篇博文来介绍。

后台主要由若干服务组成,处于最底层的服务为Data Loader & Parser & Writer Service(DLPWS),直接与磁盘文件或DB交互,以完成数据的I/O操作,其职责在于加载解析各种类型的文件,如Feature、Input、HRS文件等,DLPWS采用接口设计,以支持灵活方便的扩展。当前我们的数据存储介质是基于磁盘文件的,一旦替换为DB、甚至更改文件描述格式,则我们只需要扩展实现DLPWS的接口,抽象层的逻辑不需要任何改变。DLPWS存在的好处在于:使得之上的应用层服务Business
Logic Service(BLS)不必要关心底层数据格式到底长什么样,是INI文件或XML文件;也不需要关心这些数据究竟持久化在哪里,是磁盘文件或者DB中,它一点都不Care,它只需要关注其本身的业务逻辑。BLS只需要从DLPWS处接收到抽象的,可以直接被处理使用的内存数据即可。DLPWS对于BLS而言,相当于一个透明层,对BLS屏蔽了复杂的文件格式的细节,属于应用层之下的基础数据I/O层。

应用层服务Business Logic Service(BLS)是直接用于处理业务逻辑的,为用户的请求提供数据分析、处理和计算的功能。在我们这个Demo中主要包含了图形构建服务Shapes Service、数据分析服务Analysis Service和数据统计服务Statistics Service三个。切合需求本身主要是针对决策树Decision Tree的信息做可视化,所以需要先在后台把整个树状结构的图形给构建起来,这是Shapes
Service的职责。Tree的可视化不仅仅考虑节点,还需要节点之间的连线、节点与连线对接的端口等,同时,需要基于一定的渲染算法,以控制每个节点、每条线段具体摆放在哪个位置,这块内容将在后续的Part3算法设计中给予介绍。Analysis Service和Statistics Service则是用于对某些数据或内容进行统计或分析的应用服务,具体内容涉及公司机密,细节不透露。

为了提升后台服务的性能,我们引入了Cache层。为了支持用户的检索行为,我们设计和与具体应用相结合的索引结构,并提供了支持表达式级别的查询服务Query Service,Index & Query的设计思路将在后续博文Part2中给予介绍。

对于前端的Visualization展现,我们使用了Silverlight技术,将后台拿到的数据与具体的Silverlight控件进行绑定,以控制整个图形化界面的渲染。为了提供系统的性能,前端也引入了Cache层。而对于检索的需求,在Demo里面分为了两类Query:基本的Tree/Feature Query检索,查询Decision Tree的基本信息;DSAT Query检索,查询与人工标注样本差异化较大信息的Query。Query在到达服务器端之间会流经Query
Simple Filter做简易的字符串处理,如去掉空格、标点符号等。

五、剖析

上述是系统的宏观轮廓,具体的细节如下所示。

(1)后台



对于后台而言,需要完成对数据的加载、解析、统计、分析、计算等任务,我们将之以Web Services的形式进行封装,并为前端提供必要的服务调用接口。考虑到后台数据量可能会比较大,而Cache是放置在内存中的一块区域,其容量是有限的,因此,我们不能一开始就把所有解析、统计或计算的结果全部存入Cache。于是,对于数据的获取采用的是懒加载的模式(Lazy Fetch),即只有在真正用到这部分数据信息的时候才会去唤醒相应的服务执行。这样做的好处在于:一个系统中真正被频繁使用到的数据只占整个数据的20%,80%的数据几乎很少或不被访问(二/八定律),若根据系统运行时态,动态地去加载用户所需要的数据,比一开始就把所有数据载入,会减少系统运行对资源的需求,并且这些数据可能很久都不会被访问一次。而对于已经获取到的结果数据,我们会将之缓存入Cache。

结合我们的Demo实际的应用需求,对应于服务Shapes Service、Analysis Service和Statistics Service,其输出结果将分别放置于Shapes Cache、Analysis Data Cache和Statistics Data Cache中。然而,Cache容量是有限的,越来越多数据的置入将导致Cache的饱和,我们引入了Cache Manager对Cache进行管理,实现Cache缓存数据的更新与替换。扩展性方面考虑,Cache
Manager为接口设计,并可扩展出最近最少使用(LRU)替换策略、最近未使用(NRU)替换策略等具体实现,采用策略设计模式来完成缓存替换模块,在此只讲思路,具体细节不做介绍。

对于前端用户请求而言,在经过WCF传递到后台后,会被Cache层拦截,如果Cache层中缓存了对应的结果,则Cache命中,直接返回,而不会被传递到下层的应用服务层BLS;否则Cache未命中,Cache层将会被击穿,请求将被传递至BLS层,驱动对应的服务调用DLPWS接口完成数据的I/O,进而实现统计、分析和计算。最终的生成结果会被经由Cache Manager执行缓存替换后载入Cache。

关于Index & Search设计方案,我个人认为是做的比较巧妙的一块,也是整个工程中代码最优雅的地方,哈哈~我会在本系列博文Part2中来介绍这块内容,请大家持续关注本博客~

(2)前台



在我们的前端设计中,同样引入了一个Cache层,且Cache层的结构与后台Cache层几乎一一对应。引入前端Cache的好处有两个:其一,用户之前访问的数据会被存入Cache,用户同样的访问请求会被前端Cache命中后,直接拦截并返回结果,响应速度迅速,减少了用户的等待时间;其二,用户请求被Cache拦截后,Cache层并没有被击穿,用户的请求不被通过WCF传递至后台服务端,减轻了后台服务器端的访问压力。不过由于前端一般是基于浏览器实现的,因此缓存的大小不能太大,结合Demo的实际需求,我们开设了2M的前端Cache,这对一个Demo项目而言是完全足够了。前端同样会存在一个Cache
Manager用于管理Cache,实现缓存内容的替换和更新。

大家可以看到,上图中存在一条黑线,在黑线的左侧,直到整个后台部分,我们通过后台把抽象图形构建好,并把对应的信息统计、分析好,通过WCF传到前端,存入Visualization Cache,而这一切并没有涉及任何与表示层相关的技术,我们始终关注的都只是业务逻辑。接下来,我们需要借助于一种展现交互方式,将图形信息予以可视化的展示。我们选择了Silverlight(原因很明显,因为我在微软实习),并将Visualization Cache层中的图形、图像、图表信息获取到,与一些具体的Silverlight可视化控件绑定,将数据灌入控件中,并完成控件的渲染。这样的话,Silverlight其实只相当于是一个代理(Proxy),一个外壳,所有的数据和行为全部封装在了抽象的图形(Visualization
Shapes)对象里面,Silverlight只需要借助Proxy的方式调用Visualization Shape的数据和行为即可。这样做的好处在于:我们可以非常方便地更换前端的展现方式,虽然现在我们是用Silverlight,如果你愿意,我们还可以很快地将展现技术替换为使用Flex、JavaScript或HTML5,而黑线左侧,乃至整个后台都不会有任何改动,真正意义上做到了视图与模型的完全剥离。

为了改进用户交互体验,我们在将信息可视化的同时,加入了对控件拖拽、缩放、固定的支持,同时提供了特定的动画效果,如当点击Tree的节点时,则其子孙节点会以一种优雅的动画效果渐变式地展现出来,这些行为全部被封装在了Zoom / Drag / Ping / Stretch Actions中。

对于用户的检索词条,前端会先经由一个过滤器(Query Simple Filter)做简易的字符串处理,如去除空格、去除标点符号等等,后经WCF将Query请求发送到后台服务器端。

(3)架构



如果把前台后台的详细流程放到一起,就形成了上图所示的系统整体架构。大概的流程为:用户的请求会被前端Cache拦截,如果Cache命中则直接返回,并交由Silverlight控件渲染展现;否则Cache被击穿,通过WCF传递至后台;一旦请求被传递至后台,首先会被后台Cache拦截,如果Cache命中则将结果通过WCF直接传递至前台,存入前端Cache中,同时将数据灌入Silverlight控件并显示;否则后台Cache被击穿,说明该数据没被访问过或者最近很少被访问;请求将驱动对应的应用服务;应用服务驱动DLPWS完成底层数据的I/O,并统计、分析和计算生成最终结果;最终结果会经由WCF传递至前台Cache以完成图形的渲染,同时副本会被存入后台的Cache中。

其中,有关Cache数据替换或更新的操作均由Cache Manager完成,用户的检索过程会在本系列博文Part2中单独介绍。其他的,图上画的已经很清楚了,我就不多说了。

(4)性能

由于在前端和后台均引入了两级的Cache结构,因此,系统对用户请求的响应是相当迅速,能够保证在小于1S的情况下完成结果视图的渲染;后台的索引由于采用了BitMap做优化,因此总的数据量只有1~2M左右,可以完全载入内存;后台Cache层在加载完所有的图形信息、统计信息以及分析信息之后,大概有30M左右;而前端Cache我们设定的大小为2M。

(5)扩展

简要说说我们在可扩展性方面的考虑:

1. 数据获取(DLPWS)接口设计,保证数据存储的介质可以是磁盘文件、DB或者是Memory。

2. 前端图形的渲染工作托管给Shape Render,采用访问者设计模式,这样做的好处在于你可以方便地添加任何一种新的Shape,并添加与之配套的Render即可插拔式地完成图形的渲染。

3. Cache的替换采用策略设计模式,可以方便地更换为LRU、NRU或FIFO等。

4. 前端整个采用MVC架构,具体的展现技术(如Silverlight)只是一层代理(Proxy),这样可以方便地更换这个“外壳”(如Flex、JavaScript或HTML5),这部分内容将在本系列博文Part4中单独介绍。

六、心得

不得不说的一点是,整个项目进展了大概持续了1个多月的时间,神马C#、WCF、Silverlight完全都是第一次接触,现学现用,特别对于WCF的部署经常出现各种各样无比纠结诡异的问题,足足让我们抓狂了一个多星期。误用过单例,对于事件委托曾出现过一次请求触发了两次服务调用的诡异事件,然而当我们坚持地挺过去后又是一片崭新的天地。这期间还对现有代码做过一次大的重构,几乎推翻了之前整个架构方案,并基于Version2.0的Design完成了全部的编码。然而,就在Demo
Show PPT制作过程中,我开始不断的回顾、总结、思索整个项目的历程,不知不觉间过度到了第三个阶段,Version2.0的各种设计上不合理的弊端完全清晰了然于心,就有了Version3.0的架构Design。虽然Version3.0依然不是完美的,依然有很多可以改进的地方,但我觉得对于一个Demo性质的项目而言,它是足够的,同时,这也不代表我不知道如何更进一步去ReDesign、去优化现有的结构。

比如,从数据容灾容错、备份恢复等角度我已经可以大概给出Version4.0的构架;从应对大规模数据分析处理角度考虑,我可以给出Version5.0的分布式架构;甚至为了进一步提升系统运行时的性能,我想把某些复杂的计算过程从并行化上考虑,从而给出Version6.0的架构;再比如对于架构中所出现可复用的模块,我想把它完全剥离于现有工程的框架,如Cache层管理、Index & Search等,扩展形成一个完全独立、具备一定通用性的组件库,能够被其他工程所使用,从而实现组件的重用。

有人会说你这叫过度设计,是的,你可以把它当作过度设计,但对于一个刚刚起步,梦想成为架构师的孩子而言,这是一个必然经历的过程,只有不断的、反复的实践方能得出成本最适当、经济效益最好的架构方案。于是,对于现在我的而言,过度设计比根本不知道有这种设计要好上太多。

七、总结

我曾问过Li这样一个问题:“为什么我总觉得老外那么牛,写出来的代码那么的优雅健壮,而我却做不到?”

Li是MSRA的Dev,南大应用数学毕业,之前在IBM做过2年的架构开发,一年半前来到MSRA。我当时做这个Demo时,他是带我做开发的Dev Leader,我们用到了他写的Visualization可视化的MVC框架,框架的源码我花了两天看懂了,感觉在设计上貌似有些不甚合理的地方,对于流程调用、跳转繁琐的纠结程度一直都有些耿耿于怀。

他想都没想告诉我:“写的多了,当你不断的重构、重构再重构的时候,你会惊奇地发现,你现在的代码跟你最初给出的设计,早已不可同日而语了”。

这句话引起了我强烈的共鸣,至少整个Demo做下来,这点我还是深有体会的。感谢Li,教会了我很多东西。或许我只是暂时还达不到某个高度,尚不能完全理解Li的那个Visualization MVC框架设计的某些细节,希望经过自身内力的不断提升,下次再读这份源码时能够找到这样设计的理由。

唯有不断的阅读、实践并反复思索、重构,方能交出一份优雅健壮的架构方案!共勉了各位~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐