Profilo di 家恒和你在一起BlogElenchiGuestbook Strumenti Guida

Blog


27 giugno

年复一年

    每年到了这个时候,学校各年级就开始对着女生楼唱歌了,歌声到了,也就预示着一批人又要毕业了。一到这种时候,就不能不想起两年前的我们。时间过的真快啊!

 两年,也就是眨眨眼睛。车峰今年五一回国结婚了,可惜的是时间太短了,也没机会聚聚,唉,这一走又不知道什么时候再回来了。不过好在网络通信还比较方便,离得挺远,感觉还是挺近的;牛磊这个bt,考研又考回来了,呵呵,真不知道怎么想的,还携家带口的,不过无所谓了,年轻嘛,哈,这下回来给我们当师弟了;小样儿葛立峰去年买房了,奢人就是奢人,花钱绝对走在第一位,哈哈;傻猪现在过得挺潇洒的,有老婆照顾的日子就是不一样啊;BT人嘛,一样bt着,有了gf还藏了半年,呵呵;甲鱼还是老样子,体型保持的还不错,没有继续变胖了;最杳无音信就是小胖了,傻猪前段时间去了趟广州,回来说是过得挺惨淡的,好好保重啊,呵呵,现在啥都不缺,就确个女人了,加油啦~

 眼瞅着就要毕业了,动荡的一年啊,不知道明年的这个时候我会在哪里呢?

25 giugno

[项目总结 之七]一路走来-问题与思考

    这块就写点比较琐碎的东西了,想到啥写啥了~


--多线程相关
    HashMap, Hashtable, ConcurrentHashMap
    把这三个拿出来,是因为项目里面Map用到的地方很多,碰到一些问题,需要对比一下。HashMap不是同步,所以不能用于多线程环境下;Hashtable是同步的了,可以用于多线程下,但是,需要注意一个问题。Map有两个个方法 keySet()values()用于得到Map中的键或值的一个集合,我们经常会做一些遍历的操作。多线程环境下,在数据量小的时候这样的遍历可能没有问题,但是在数据量大的情况下遍历就可能出现这个异常——java.util.ConcurrentModificationException。原因是你在遍历的同时有对Map进行了添加或删除Map元素的操作(注意:这里的添加是增加一个全新的元素,键必须是新的,如果你原键的基础上更新值,是不会有异常的)。数据量小的时候,由于遍历速度很快所以没有问题,在大数据量下,遍历时间延长,所以出现问题的可能性加大了。个人认为这点对应Hashtable很致命的,因为为了解决这样的问题你不得不在已经是同步的 Hashtable上再进行添加和遍历的同步,这点很不好。
   JDK5.0里引入了ConcurrentHashMap ,它本身是同步的,更好的是它避免了上面Hashtable遍历时会引发的问题。原理比较简单,就是在遍历时,对当前Map建立一个副本,遍历是针对副本进行的,遍历时对Map的任何操作都不在副本上生效,所以遍历可以成功进行。这样的结果是,遍历的时候,与真实Map可能会存在一些不同步的时候,不过,这点损失应该还是可以理解的。


    随手可得的线程池
JDK5.0提供了对线程池的支持——Executor。简单点说就是new一个最大可以有多少个线程的池,然后把实现了Runnalbe接口的类扔进去就完了。该执行的执行、该等待的等待,不用操心了,呵呵。还是比较爽的。线程的东西,自己的实现也可以,但是总是对可靠性有些担忧,现在JDK提供支持了,放心使用吧~~


--持久层的Bug
   持久层这块我们需求并不大,十来张表而已,使用的是Torque 。但是,就是这个Troque带来了不小的麻烦。Torque的原理是这样的,定义好数据库描述的schema(数据库换了但是schema是相同的,这点还是比较好的),它会帮你在对应数据库建表并生成一系列用于持久化的对象。这里想说的就是这些对象了,不像Hibernate生成的那些对象,是POJO。这里的对象有一套完成的体系,包含了所有持久化的功能。所以,如果把这些对象用于业务逻辑部分,那么业务逻辑就间接也Torque耦合了,但是如果不这样呢,又需要自己再做一次数据转换进行数据传递,有增加复杂性。
   在使用的过程中Torque并非想像的那么易用,好几次想换掉torque,但是又发现了其他问题。用Torque与Hibernate进行了 insert操作的比较(我们应用有大量的insert),Torque几乎比Hibernate快了一倍,这样的性能又不能轻易放弃。
   现在的Torque感觉就像鸡肋...

 

--异步网关的设想
    目前的网关是同步网关,即:SIPC一侧消息的发送直接受制于WV消息的发送。流程如下:WV请求->SIPC请求->SIPC反馈->WV反馈。这样的话,网关仅仅只起到了代理的作用。但是,网关其实还可以做更多的东西。
   IM应用有个很大的特点,就是获取类操作(获取列表、获取好友)比较多,而更新类操作(添加/删除列表、添加/删除好友)比较少,因此,网关完全可以在用户登录的时候(或者其他时候),发送SIPC请求,获取用户相关信息,然后缓存在网关。这样的话,流程就变成这样了:WV请求->缓存->WV反馈。对于更新类操作,可以划分为两类——与业务有关的更新(如:添加联系人等)和与业务无关的更新(如:用户设置自己的资料等)。对于前一类必须是同步消息,但是对于后一类,网关只需进行字段有效性验证,即可回复用户,而不必登到服务器反馈以后才回复用户。
   WV本身是用于无线网络的,响应时间比较慢,因此,缩减消息量换来相应时间的提高应该是有意义的。


--用好CVS(SVN)
    用的越多越能体会他的重要性,特别是当多个人进行并行开发的时候,没有这么一个东西,真不知道怎么弄。呵呵。不过,用的好的人并不多,我们开发的时候,针对CVS总会碰到这样那样的问题,说实话,应该搞个培训。

21 giugno

[项目总结 之六]水中望月-性能调优

测试测的这么烂,调优起来自然还是有点力不从心。本来测试跟调优是放一块写的,不过后来看了一下,感觉两个东西的侧重点还是有所不同,又拆开了。调了这么久,感觉调优还是有章可循的,总结起来大概就这么一些吧(个人意见)。


可怕的IO
   很多的性能问题都跟IO有关,所以一个系统拿来即便什么都没测也可以先把IO调一下。我碰到的主要有以下几个:

  • 日志。刚开始测的时候很多调试日志都没关,测试数据可想而知了。所以上来记着先把文件日志(Log4j)、控制台输出都屏蔽掉(关闭掉)
  • 数据库。数据库主要有几个地方可以调一下,一个是连接池大小,这个在测试前就应该配一下。具体配多大呢,应该根据测试情况看,不过有一点可以掌握——可以配的跟Tomcat(5.0)线程池一样大,原因想想就知道了;一个是建立索引,这个应该不用说了;再一个就是SQL语句优化,这个是因为在测试的时候发现数据库连接有死锁的情况,后来定位到语句问题的。这里做的基本上都是一些常见的数据库优化,至于数据库本身的一些优化,就没有涉及了。
  • 连接模型。这个主要涉及传统IO跟NIO的使用,可以参考前面的一篇NIO文章。
  • Tomcat。目前使用的是5.0.28版本的,主要做的就是调了一下线程池大小。目前最新的Tomcat6.0支持NIO方式了,性能应该会有不少提高。


监控工具
    所谓“工欲善其事,必先利其器”,好的工具确能起到事半工倍的作用。我用到的最多的就两个JConsoleJProfiler 。JConsole监控系统内存变化情况,如果有内存溢出的话,垃圾回收将会呈现锯齿状。发现问题以后,使用JProfiler,在小压力(或无压力)的情况下监控对象变化,定位内存溢出原因。


JVM调优
   其实不能说调优了,只是一些参数的调整。不过调好了对性能影响还是很大的。对我们的系统大概有10~20%的提高。

  • JVM启动模式。JVM有两种模式,-client和-server。配置成server模式可以得到比较好的性能。但是我尝试配置这两种方式,启动Tomcat后再用JConsole观察,感觉没有什么变化,不知道问题在哪里。
  • 内存大小配置。三个比较重要的参数 -Xms:初始堆大小  -Xmx:最大堆大小  -Xmn:新生代堆大小。Xms和Xmx一般配置为一样大小,这样可以避免每次GC后跳转内存空间。关键的参数就在这个-Xmn。 从测试的情况来看,这个对性能影响比较大。因为Sun的JDK是基于分代垃圾回收的,新生代简单一点说就是存储那些用一次(或几次)就仍掉的对象,当这个空间满了以后JVM就会把当前还存活的对象,移动到年老代的空间中。所以,之前测试的时候,这个空间是按默认值配置的,在16~32m之间,由于太小,一些本该扔掉的无用对象被移动到年老代,造成年老代空间持续快速增长,因而导致频繁的年老代垃圾回收。后来当调整到128m(堆总共为512m)时,性能提升很明显,年老代的垃圾回收次数明显下降。
  • 垃圾回收器的选择。回收器的选择主要是为了解决垃圾回收时的停顿问题,不同的应用对停顿时间有不同的要求因而对应不同的回收器。这块目前没有要求,但是迟早要碰到,所以了解了一些。目前的回收器主要有一下几种:串行收集器,并行收集器,并发收集器。具体差异目前我理解也有限,就不好在瞎说了。

参考资料

[项目总结 之五]雾里看花-性能测试

    到现在为止,性能测试陆陆续续进行半年多,得出来的结果就一个字——晕。一来自己没有这方面的经验,二来由于问题的局限性也没有人能做一些指导性的东西,所以基本上是自己摸索着在做。整个测试过程比较粗糙,没有走专门的一些性能测试的流程。从目前看,这部分做的比较失败。这里说点测试的经验还有困惑吧。


模型的选择
    简单说就是怎么测?可以测一条消息,也可以测几条消息组成的一个业务,或者是几个业务的组合。这个应该是测试初期就要想好的,个人认为由简到繁。不用一上来就考虑很多,否则怎么死的都不知道。比如说我。。。


简单之美
    性能测试本身是检验系统在有压力下的表现的,所以在选择测试的业务逻辑时,应该选择尽可能简单的测试用例。这个简单包括两方面的意思一是业务逻辑应该简单,但是要足以代表一般情况;二是,选取的业务逻辑要尽可能不包含其他业务,也是所谓的正交性。只有足够简单了,在出现问题时才能好定位问题。同时,这样“简单”的测试数据才能够真实反应你系统在这种情况下的性能。测试初期,我就柔了很多逻辑进去,后来不得不一个一个删除,从最简单的部分测起。


有状态 VS 无状态
    状态问题是在整个测试过程中一直困扰我的问题。因为应用是跟具体用户行为相关,如果要测试某个流程,就必须先要满足一些状态。举例说,登录是必须的,用户只有登录上系统以后才能进行其他操作。在未登录的情况下发送消息,系统不会认的。基于类似这样的问题,所以整个测试都是建立在“模拟”的情况下,性能测试的模拟器也是在真实的模拟器上改造的而成的。这样的结果是,一来,增加了客户端的复杂性,同时也增加了客户端的压力,因为每一个客户端就是多个单独的线程维持,结果是一台机器也就能支持几百个用户,服务器压力还没到客户端先不行了;二来,增加了测试的复杂性,因为测试前需要预先满足一些条件,往往在满足这些条件时,系统就先出现一些问题了,使得后面的测试数据不准确;三来,本来想使用一些开源的测试工具的,但是发现由于状态问题,使得加入这些测试工具变得困难
     无状态的优势是比较明显的,无需线程,一个循环控制发送间隔即可;与一些开源测试工具结合也会比较容易,直接就是一些HTTP请求。现在想想,对于我们这个系统,要构造无状态难度还是有点大的。但是应该可以实现。还是一个测试的设计问题,初期如果考虑好的话,后面应该可以省些事情,不过后面木已成舟了,想改有回不了头了。。。


接口之痛
    服务器设计初期没有考虑到一些性能统计数据的问题,所以服务器设计上考虑不周到。造成后面测试的时候,不得不在服务器上添加一些与业务无关的统计代码才能收集到数据。虽然改动不大吧,但是这样相当不好。这些统计跟业务无关,但是对性能会有一定影响,所以正式版本是包含这些东西的。测试的时候需要加上,正式版本又要去掉,谁能保证不会相互影响呢?这让我想起AOP的优雅,虽然没有用过AOP,但是这种时候应该是AOP最能展示其强大的地方。
   接口再次展示了其好处,之前服务器设计的时候,如果恰当的地方加入接口,AOP的加入应该会很方便。


分清楚测什么
    性能测试大概分一下,应该分成负载测试压力测试(个人意见)。负载测试测在机器正常允许的情况下的测试,而压力测试为超过系统性能的情况下的测试。这里还是有必要区分一下这个的,因为不同条件下,两者衡量的标准是不一样的,不能混为一谈。最直接的表现是,在负载测试时,机器应该尽可能处理所有请求,但是在压力测试时,机器应该有选择的抛弃一些请求,以保证机器的安全。为什么说这两个,因为两者测试时的界限很模糊的,但是衡量标准截然不同。关键还是测试方面的知识不是很多,这个应该是个常识问题。


数据收集的痛苦
    性能测试中,需要很多统计数据,各方各面的(包括一些硬件数据);还有,性能测试需要反复的、多次的测试,有时候只是调节一个参数就需要整体测试一趟。所以,这两方面决定了性能测试需要一个好的工具并且需要较长的测试周期。我们的应用更是头疼,没有好的工具用,所以数据收集基本上都是写程序获取的。重复测试的问题,后来没办法,因为要人工测的话效率实在太低下了,还是只有自己写了一个自动化工具,每天晚上自己跑,最后收集过来相应的数据。
    自己实现确实可以,但是效率还是太低,而且bug很多、可靠性很差。这个问题现在我也没找到太好的解决方法,不知道大家有没有什么建议?


MISC

  • 日志对性能影响很大的,所以测试前记着把该关的都关了
  •   系统可能需要预热。主要就是先小压力跑一下要测的流程,主要目的个人认为就是JVM把该加载的一些东西加载一下,免得影响性能。
  •   异常流程的处理。在异常情况下要注意是否已经释放了所有资源,否则可能存在内存溢出

相关文章
20 giugno

[项目总结 之四]刀尖上跳舞-NIO篇

    项目中引入NIO纯属万不得已,因为这个问题在除NIO外似乎没有太好的解决方式了。


O(n)的问题
    我们的网关,在服务器看来其实就是一个一个客户端,服务器根本不知道网关的存在。而客户端与服务器连接的模型,是TCP的长连接,即客户端连接上服务器以后,这个连接在客户端断线以前都是存在的。因此,网关也必须帮客户端保持这个连接,结果是,如果使用传统阻塞IO的方式的话,必须有至少新建一个线程用于维护读的操作。那么,试想一下,如果有1000个用户登录上来的话,就必须新建1000个线程用于守护,这样的结果是不能忍受的。而且还会引出更大的问题。


OutOfMemory
建立在上面的传统IO基础上,我们进行了一些测试,结果是不能令人满意的,在1000多用户的时候,出现了内存溢出:java.lang.OutOfMemoryError: unable to create new native thread.这里需要说一下,内存溢出不单只是在JVM耗尽了分配给他的内存时出现,换个角度想,线程是属于系统级的东西,所以,如果操作系统没有足够的内存来产生这个线程,也同样会造成内存溢出。上面这个内存溢出就属于系统的内存溢出。
   之前查资料的情况是,Windows和Linux一个进程内的线程数是有限制的,具体跟系统不用有所不同。我们在Windows下测试的情况是在5000~7000左右。同时,这么大的线程数,应用单调度的效率都会大大降低。这里还想说一个JVM的配置,-Xss。这个参数是配置JVM分配给一个线程的栈大小的,JDK5.0时,一个线程默认大小应该为1M,所以根据应用情况,把这个值改小的话,同样的内存将能生成更多的线程,同时减少JVM内存溢出的可能性。


O(1)的解决
上面的问题中,最大的问题就是需要一个进程维护被阻塞的“读”操作(PS:写操作是没有关系),因此只要能把“读”部分的阻赛线程化解掉即可。NIO用在这里,再合适不过了。通过NIO,这里的连接模型发生变化了。以前是:N个用户-N个线程-N个IO-N个处理逻辑(数据读取过来以后需要进行业务处理)  现在是 N个用户-k个线程(1个或多个NIO的selector)-N个IO-1个处理线程池。后者的连接部分、处理部分都要用户多少无关。线程池配置多大、使用多少个selector都可根据实际测试情况进行配置了,大大减少了线程数量。


NIO的困惑

  1. NIO是个好东东,不过要很好的驾驭还是一件很困难的事情。NIO最麻烦的地方就在传输消息的不完整性(个人的说法),主要就是在消息量大的情况下NIO一次传输的消息可能是多次发送积累的结果,所以内容可能是多条消息,可能是某条消息的中间部分。我们的应用因为一个用户一个Socket连接,所以消息量比较小,目前还没有发生这种情况,但是这里一直是一个隐患。解决方式也没有太好的办法,只能通过缓冲收到的消息,然后再通过关键字符或者内容长度的标准(如:HTTP中的Cotent-Length)进行内容划分
  2. 线程数是上去了,但是连接数还受到限制。这还是一个操作系统的问题,Windows系统(XP)对TCP连接,默认情况下只能使用5000以下的端口。如果要使用其他端口,需要修改一下注册表。其他操作系统不是很清楚,不过从查到的资料来看应该都有一些限制。Windows XP系统修改方法如下:查找注册表项HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\ Parameters,在其中加入新值 MaxUserPort 类型为DWORD,此键标识最大允许的端口号,自己写一个就行,小于65535,重启电脑
  3. 连接未释放的问题。这个可以参考以前我写过的一篇:网络连接无法释放—— CLOSE_WAIT
  4. NIO方式常用于服务器端,因为服务器总会面临这种大压力的情况。但是对于我们网关,是客户端去连服务器的,NIO方式也同样适用于客户端的程序。连接方式与服务器端相同,SocketChannel连接上以后注册到相应的Selector即可。
  5. 两个小问题,费了我不少时间。ByteBuffer这个东西,不管读还是写,完了记着调用一下flip()方法,不然不是没读出来就是没写进去。只是这个方法可读性也太次了,老忘,ft。还有一个,使用NIO方式进行网络连接的时候(客户端连接服务器),除了调用channel的connect外,等连接上了(触发了Connect事件),记着还要调用一下SocketChannel的finishConnect()方法,以完成整个连接过程,否则会出现一些不知名的问题。。。


站在巨人肩上
    虽然自己写的东西目前用着还可以,但是说实话,写的太简陋了,所以指不定哪天就出现一些不知名的问题了,呵呵。所以下来又找了一些支持NIO的框架。


   --Grizzly :一个开源的NIO框架。GlassFish的HTTP服务器底层实现。同时Grizzly已经实现好了一个基于NIO的HTTP底层,所以如果是 HTTP协议的话,拿来就可以用了。个人还是比较看好这个的,毕竟Sun在支持。由于Grizzly的初衷是搭建一个基于NIO支持不用协议的框架,所以在NIO之上它还提供了一些接口,用户自己实现这些接口,即可分割不同协议。HTTP只是其中的一种协议实现而已。
   --MINA :Apache的项目。MINA旨在提供一个高性能的网络传输框架。所以它对NIO的支持,主要在Socket一层进行封装,使得用户使用起了会比较方便。个人用了一下,确实很简单。不过,MINA也就到此为止了,再往上就需要用户自己实现消息的分割,协议解析一些东西了。
   --QuickServer :这个东东主要是提供服务器端的支持,使用起来也是相当简单的。只是我们的应用是客户端的,所以用不上,写在这里,大家有兴趣可以研究一下。

19 giugno

[项目总结 之三]想说爱你不容易-XML篇

   说XML这块的东西倒不是因为研究的比较深入,只是因为项目里面使用的XML太频繁了,不过只是相当简单的使用,所以在这里只能算是一个对XML技术的相关资料的总结、备忘。


XML解析标准
    这里说可能不太熟悉,但是如果换几个词就应该清楚。DOM、SAX。比较熟悉吧。这里之所以说他们是标准,只因为这两个东西定义的只是XML解析的方式,而此方式的具体实现跟这两个东西是没有关系。对比一下写程序,这里就是“接口”,定义了规约,想怎么实现都是可以的。

    --DOM(Document Object Model):文档对象模型。原理就是把XML解析为内存中的可以树。由于XML保存在内存中了,所以,可以对其进行相应的增、删、改、查,操作。这里就需要说一下XPath了,XPath 为在一颗树中查找XML内容的方式定义。也是一个标准的东西,你满足了其相应的语法,即可在DOM树中查找到相应的内容。使用起了还是相当方便的。

     --SAX(Simple API for XML) : 以事件驱动的解析方式,所谓的“推”方式的XML解析。原理就是在解析XML时,碰到不同的节点,则上报不同的事件,对不同的事件定义不同的回调函数,完成相应的解析逻辑。SAX对XML文档应该就是一次解析,并且不保留解析状态,所以SAX比DOM方式速度会快点、内存使用少点,但是功能上就要弱很多了。
     --StAX(Streaming API for XML):这个东西可能有点陌生。这是所谓的“拉”方式的XML。原理就是平时常见的遍历,即扫描XML文档,当发现我感兴趣的部分时,才开始进行解析。所以,说速度的话,这是三种中最快的一个,而且编写代码也相对简单。它可以多次对同一个文档进行解析。但是相对的,它的缺点也是很明显的,就是无法对XML进行合法性验证,所以即便是不太完整的XML文档,也可以用它完成解析。StAX在JDK6.0中被加入。这里 有篇好文介绍StaX的


    JDK中的位置: 以上标准,JDK中都已经包括相应的接口定义。DOM -- org.w3c.dom; SAX -- org.xml.sax; StAX -- org.xml.stream(JDK6.0)
    更多细节的东西可以参考sun的教程

XML解析器
解析器即为XML解析标准的具体实现。不同的解析器,对应的实现接口都是统一的(DOM的、SAX的、StAX的)。所以不要看见JDOM就认为它只能以DOM方式解析,其实它也支持SAX的。常见的解析器有以下几种:Xerces JavaCrimsonJDOMdom4j。各解析器的具体使用可以参考具体doc即可。
   StAX是一个比较特殊的。JDK6.0刚开始提供支持,所以不是很清楚它的解析器。而基于这种XML Pull方式的解析器,因为速度快、消耗内存小,很早就被用于手机上的XML解析了,比较著名的就是kxml 。有兴趣的大家可以查下看看。


XML解析(绑定)工具
   有了标准、有了实现还不够吗?这个工具干什么使的?标准有了、解析器有了,但是实用性还是差一点,所以,诞生了很多XML解析的工具。它的功能就是,避免你面对解析过程,而是直接面对XML里对你有用的东西。常见的有如下一些:XMLBean ,XStreamCastorJBindQuickZeus


项目中的XML
    项目中我们用到了两处XML解析,分别使用了两个不用的解析器。一个地方使用的是Kxml的解析器,用于把XML压缩过后的文档转换成一般的文本的文档。另一个地方用户的是Zeus,主要是把XML文本文档解析为Java对象。这里主要说下Zeus。把DTD给Zeus,它可以生产能解析对应DTD文档的Java对象;反之,如果你使用这些Java对象把相应的树型XML文档定义构造好,又可用这些Java对象生成相应的XML文档。Zeus使用SAX方式解析的,效率还是相当高的,测评可以参考下面给出的文章。
   目前感觉使用Zeus很麻烦的一点就是“有效信息的获取”。Zeus产生的Java对象是按XML的树型结构组织的,所以如果要获取层次比较深的内容时,会相当麻烦,因为你必须一层一层打开Java对象,而且有些节点定义还可能时List型的,这样又需要遍历List中的所有元素,同时,每次打开一个Java对象以后,还必须判断此Java对象是否为NULL,别看这一点判断,因为对象多了以后,会很麻烦。我们应用中嵌套深一点的可能在7、8层这样子,已经是相当麻烦了。在这种时候你就不得不考虑一下XPath的便捷了。所以,这里是否应该使用Zeus还是值得斟酌一下的。有效信息的获取上是一个相当麻烦的问题。
   木已成舟,该做的都做的差不多,想用XPath也用不了。偏偏在这种时候又让我看到一个工具——JXpth 。这是一个在Java对象树中使用XPath语法进行查找的工具。可惜是相见恨晚啊...这个东西只是大概看了一下,没有具体使用。所以了解不多,是否真正能够放到应用中还是一个问题。
   XML这块还有一个比较大的问题,就是Zeus生成的对象已经被使用到业务部分,造成现在即便想替换掉Zeus也是一个比较大的问题。这块以前也考虑过,但是还是没有想到太好的数据传递的方式,现在暂时也没有什么太大的问题。只是不知道以后会碰到什么,有点担心。


几篇好文
  下面列几篇文章吧,相当的精彩。
  Java 中的 XML: Java 文档模型的用法
  Java 中的 XML: 文档模型,第一部分:性能
  Java 中的 XML: 数据绑定,第 1 部分:代码生成方法 — JAXB 及其它
  Java 中的 XML: 数据绑定,第 2 部分:性能

[项目总结 之二]摸着石头过河-设计篇

    项目初期的原型系统是比较简单的结构,几个类就完成了基本所有功能。结果是其中最复杂的一个类已经有3k多行代码。所以,在项目进行到三分之一的时候,对整个代码进行了一次重构。由于之前我用过Struts,所以重构的过程也就借鉴了MVC的思想,把相应的代码层次整理出来。
    项目是基于HTTP的,HTTP消息体中携带XML格式定义的具体协议,不同的请求的XML协议内容不同,不同的请求需进行不同的处理器,所以从Servlet接收到Request到返回Response,消息处理流程如下:

1.获取到XML格式定义的消息体 -> 2.把XML文档解析为Java对象 -> 3.从Java对象中获取到当前请求类型 -> 4.把请求分发到具体处理器 -> 5.在处理器中获取Java对象中的有用信息 -> 6.调用具体的业务逻辑处理部分 -> 7.执行本地业务逻辑,发送相应的SIPC消息 -> 8.根据服务器返回内容生产相应的XML格式定义的返回 -> 9.通过HTTP Response将处理结果反馈给客户端


具体结构图如下所示:

                                                                        图1


    基本上对应了MVC的几个大部分。这里只是借鉴了MVC的思想,所以与严格的MVC定义应该还是有些不同的。IBM面试的时候,面试官就问到Struts中的ActionForm应该位于MVC中的哪一个?我当时回答的是V,面试官说的是M。解释是ActionForm是承载数据的,反应数据变化。个人认为说它是M还是有点牵强,ActionForm本身仅仅是用于承载数据的,说白了就是放数据的,没有任何“行为”在里面,而M的定义为处理业务逻辑,所以ActionForm属于哪一层面还是需要深入理解一下。查了一下,基本上对于Struts的文章 ActionForm都是属于M 的。还有一点,上面文章中Action也是属于M的?这点还是有点疑问,因为Action本身也在控制页面跳转,还是承担了部分C的作用的。个人认为问题应该在看Action中是否有业务逻辑,如果只是单纯调用,那么它应该输入C,但是如果业务在这里实现的,那么应该属于C也属于M。


    基本上总体架构就是这样的。其中说一下涉及到到的几个设计模式。

  • 工厂:这个没什么好说的,从请求类型获取到具体请求的处理器这块,涉及到几十个不同的请求,所以需要一个工厂做统一处理。
  • 门面:位于7处。上面流程是WV的一侧,所以另外的SIPC一侧提供了相应的发送、接受消息的API给WV一侧使用,这里使用Facade提供统一的调用接口。
  • DTO:这个主要用于两个协议间信息传递。WV和SIPC在一些数据结构的定义上是不同的,所以考虑时DTO进行相应数据结构的封装、转换。


    以下一些问题是设计时候的一些心得或是疑惑:


控制器(分发器)的实现
    控制器的实现还是比较傻瓜的,就是用了一个工厂,给定消息类型返回对应的处理器。要说这里稍微有点技术含量的恐怕就是在处理器上,这里提取了一个接口,所有处理器均实现这个处理器接口。接口定义如下:      处理结果  Process(请求,会话);
    请求封装了所有请求相关信息。会话对应为一个当前在系统中存在的用户。整个输入的含义就是:某个用户的某个请求。处理结果返回此请求的处理情况,可能正确也可能错误。
   由于这个接口的引入,所有的处理均有一个统一入口和统一出口,所以大大清晰业务处理流程。同时,如果想实现AOP的一些功能,直接处理接口即可。在项目我们只是简单使用了代理,用于消息日志处理。


为什么使用了Facade?
    最初业务逻辑部分是直接调用SIPC提供的接口的。但是等功能增加了以后,发现业务部分调用SIPC的接口非常混乱,典型的一种交错结构。一个业务调用几个SIPC接口,一个SIPC接口同时又被不同的业务调用。于是想到了Facade,从目前情况来看,引入这个还是比较好的,至少统一了接口,看上去非常直观。再一个,这里也给单元测试提供了方便。


为什么把处理器和业务逻辑分开?
    之前的代码中处理器和业务逻辑是一起的,即:一个处理器代表了一个业务(一个处理器处理一个业务)。一开始这样做是没有问题。因为一个处理器与一个请求对应,一个请求相应的就是一个业务。但是,随着项目进展,随着一些变态需求的出现,发现做这样存在很大的问题。举个业务场景:添加一个联系人。一开始没有问题,在处理器中执行添加联系人的业务就行了。但是,如果让你在添加联系人的时候,需要给被添加人发送一条消息,说某某某添加你为好友了,怎么办?如果还是处理器的方式,那么就不得不在处理器中再加入发送消息的业务逻辑。结果是造成代码的重复。这样显然是不行的,所以只能把具体的业务抽取处理作为独立的功能块,所以产生了上面的分离。
    分离的结果是,如果想要任何功能的组合,只要进行相应的接口调用即可。这里其实很类似J2EE里的会话bean。


版本问题
    项目开始的时候WV协议有两个版本1.1和1.2,所以网关设计的时候就必须考虑需要同时兼容1.1和1.2.这点上感觉是目前问题比较大的一个。1.1和1.2在协议定义上有所区别,所以DTD定义上有所区别。问题在这儿了,当网关接收到HTTP的消息体时,还未进行XML解析,因此也就不知道该用那个版本的DTD进行解析(PS:因为我们使用的XML解析工具与DTD进行了绑定,关于XML的问题后面会说到),那如何解析呢?后来想到的解决方式是:把两个版本的DTD合为一个,这样做就不必考虑版本问题了。但是带来的麻烦就是需要合并DTD,如果出现重复字段(相同名字,但是定义、位置不同的字段)则需要特殊处理,同时如果还有新版本出现,那么需要重新生产DTD,这样的改动可能会涉及到以前版本。这点感觉很不好,严重影响系统的扩展性。目前能想到的解决方式就是更换XML解析工具,但是这样代价又太高了。
   版本带来的另外一个问题是,不同版本的处理流程可能不同。目前的解决方式是,不同的版本的同一个消息,对应到不同的处理器上(即:在处理器上进行版本区别),进行相应处理。但是这样的话业务逻辑部分可能也会涉及到相应的版本划分,感觉相当不好。
   目前版本问题感觉挺难弄的,还好一般情况下版本不会随便升级的,但是从这点系统结构上还是存在不合理的地方,目前还没有太好的想法。。。


结构清晰了多少?复杂度增加了多少?
    从上面结构定义上看,各层的逻辑应该划分的还是比较清晰了。如果要往里面增加新功能,主要有几步:1.XML解析部分需要加入对新消息的解析;2.业务逻辑部分需要加入新的逻辑;3.SIPC部分加入新接口;4.工厂加入新消息;5.可能涉及数据库、或者DTO一类的增加或更改。所以加入一个东西还是比较麻烦的,这样划分下来结构清晰一点了,但是复杂度也相应的提高了。但是具体的复杂了多少、清晰了多少,我也不清楚。稀里糊涂就这么一路做下来了。呵呵。大家提点建议。


单元测试与架构
    这里还需要提一下单元测试。之前我们基本上是没有单元测试的,除了时间紧这个原因外还有一个更大的原因就是系统结构划分不合理。所以,要测试只能进行集成测试。最惨的时候是,连调试都要把服务器启起来,这样的效率时不能容忍的。随着后来慢慢把层次理清以后,发现单元测试也水到渠成的加入进去了,呵呵,算是意外收获吧。因此,从这里也可以看出,很多时候一个项目没有单元测试,很大程度也时因为系统结构不合理。

    现在回头看看,一个系统的形成是一个逐步的过程。在整个过程中重构起了相当大的作用。当然我们做的还很不够,这里做的主要是一些比较大的重构,其实还有很多小的重构是可以做的。这里也还想说一点,就是一个系统不是设计出来的,而是重构出来的。这样说可能有点极端,不过确实是有点体会。因为很多时候,在项目初期,你根本不会知道项目会出现哪些变数,你可以预料到一些,但总有一些还是无法预料到的。现在看,我们这个系统跟当初比,早已面目全非了,不过现在如果还有什么变态需求出现的话,我想整个系统还是有一定适应性的。深刻体会了一把敏捷的思想啊,呵呵。


    总的看下来,项目中,心得比较多的恐怕就是设计这块了。

[项目总结 之一]-序

    IBM面试回来就觉得有必要总结一下。所谓“年年岁岁花相似,岁岁年年人不同”,眼瞅着就快两年了,呵呵,有点不可思议啊~有走的人,有来的人,有些东西,再不写写,恐怕就要忘了...时间这个东西啊,真是不可思议,过着过着就过来了,哈~


背景:项目是一个即时消息协议网关。什么是即时消息?想想QQ、MSN就知道了。跟那一样,发消息、看联系人的。网关干什么用的?打个比方就是通过网关可以实现QQ跟MSN发消息,或者用QQ登录到MSN的服务器上。我们基本上就是做这么一个东西,只不过把QQ和MSN换成其他的即时通信软件而已。

技术:既然是网关,就要涉及到“两头一中间”。两头就是分别网关两端的两种协议,所以网关的需要两个协议栈。一中间就是指主要的业务逻辑,实现协议转换、协议映射。两头中一头是基于HTTP的,一头是基于类似SIP的协议-SIPC。我主要负责HTTP这头,所以主要站在这边的角度上看问题。HTTP这部分使用的是WV协议,此协议用XML进行描述,使用HTTP承载具体消息体,与网关进行交互。我们使用的Java技术,所以项目基于Servlet。

10 giugno

学而时习之——小记IBM面试

    早晨6:50起来,7:10离开宿舍。到公司打印了一下简历,8:00出发去坐城铁。速度还是比较快的,10多分钟就到西二旗了。第一次来这边,没想到这么荒凉。。。8:30到的“得实大厦”。晕的是这么一个“得实大厦”居然外面连个牌子的都没有,虽然离城铁挺近的,不过要没打车也得找的够呛。进去一看,原来这大厦叫“descom”(大概是这样)——无语中。签到,进去后在一个会议室等着,我到的时候晓斌已经在哪儿了,呵呵,不错,聊吧。

    挺准时的,9点开始,面我的是个gg,挺严肃的那种,搞得还挺紧张。一上来先自我介绍吧,blabla。。。然后开始说项目,边说边问,gg问得又细又深,不过还好了,自己做的项目,总有点说头的。我面的时候,基本上都是技术的东西,没有涉及其他的,可能跟组有关。项目说的差不多了,gg用英文问了几个问题,唉,听见英语就头疼,磕磕绊绊算是挺过来了。末了,gg发话 了:“二面的机会还是给你,给你提点建议,英语还要多练练,有些技术研究的还不够深入”。这里赞一个,就冲gg提意见这点就得说句谢谢~~ 有时候觉得自己还不错,呵呵,看跟谁比了,所谓天外有天,人外有人,要学的东西还很多。

    一面出来觉得挺累,可惜没功夫休息了,直接上四层开始二面。这次也是个gg,不过是那种面带笑容、和蔼可亲的那种,感觉好多了。上来看完简历就问:“你是东信北邮的啊?”,ms这位gg知道东信北邮,呵呵,不知道是好印象还是坏印象。面试流程差不多,先自我介绍,然后说项目,不过gg的问法有点不用,问的是“找一个你觉得项目最有闪光点的地方说说?”。项目做的地方东西挺多的,但是以前还真没想过提炼一点“闪光点”,blabla...说了一堆,也不知道有没有体现出“闪光”来。这个gg技术细节问的不是很多,大体上说说基本就过了。技术问完还来了一道逻辑题,一道算法题,呵呵,逻辑题比较简单,算法也不难,不过数学忘的还是太多了,写出了递归式没有推出公式来。接下了又是英文,问了几个问题,也不怎么地。。。英语啊。。。 最后让问问题,我问了一下IBM的SOA组的情况,gg很有耐心,讲了很多IBM的SOA的推广计划、部门划分一类的,收获不少~~最后问了一下什么时候有结果,gg说等HR通知,呵呵,Bless自己吧。

自我总结一下:

  1. 口语好烂啊。。。啥都甭说了,抽空快点练吧。
  2. 项目怎么说?这次感觉项目给人家讲的不好!非常的不好!!!搞得我想表达的,人家不清楚;人家按人家理解的方向提问题,我又说不清楚。主要的问题二面的gg提醒我了,就是那个“闪光点”,往大了不好说,往小了大家就容易沟通了。下来要总结总结,把项目的精华提炼一下。
  3. IBM的面试还是很具有这种大企业的风范的,问技术的够深入,不问技术的也够发散。
  4. 面试确实还是个好东东,每一次下来都有不同的体会,有机会还要多去去。

    IBM算是告一段落,今天的表现不怎么样啊,英语就不说了,技术的东西其实可以说的更好的,感觉有一肚子东西,但是没法往外倒啊。。。所以本篇blog取了“学而时习之”,总结、提炼是个人提高的不可缺少的部分。呵呵,话说回来,不管怎么说吧,对自己还是有信心的,应该会有一个满意的结果。  

06 giugno

发现Word的翻译功能

    不知道算不算是后知后觉。最近用word 2007,发现他的翻译功能相当好用。对准单词,Alt+鼠标左键 即可看到相关单词的解释。解释还是比较详细的,意思、常用法、同义词都有。如果对准的中文单词,则给出相关英文翻译。也有类似金山词霸的屏幕取词,不过给出解释相对多一些。

04 giugno

未来的家(肯定有图!)

 呵呵,不知道能不能住上,到时候再说了,贴个图吧。有些比例可能不对,不过大体上就这个样子了。