放翁(文初)的一亩三分地

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  210 随笔 :: 1 文章 :: 320 评论 :: 0 Trackbacks

#

这是我最后一部分在InfoQ上连载的文章,测试部分其实很有启发
http://www.infoq.com/cn/articles/hadoop-process-develop
posted @ 2008-08-13 23:06 岑文初 阅读(757) | 评论 (0)编辑 收藏

 

最近关注Hadoop,因此也顺便关注了一下Hadoop相关的项目。HBASE就是基于Hadoop的一个开源项目,也是对GoogleBigTable的一种实现。

       BigTable是什么?GooglePaper对其作了充分的说明。字面上看就是一张大表,其实和我们想象的传统数据库的表还是有些差别的。松散数据可以说是介于Map Entrykey & value)和DB Row之间的一种数据。在我使用Memcache的时候,有时候的需求是需要存储的不仅仅是简单的一个key对应一个value,可能我需要类似于数据库表结构中多属性的存储,但是又不会有传统数据库表结构中那么多关联关系的需求,其实这类数据就是所谓的松散数据。BigTable最浅显来看就是一张很大的表,表的属性可以根据需求去动态增加,但是又没有表与表之间关联查询的需求。

       互联网应用有一个最大的特点,就是速度,功能再强大,速度慢,还是会被舍弃。因此在大访问量的网站都采取前后的缓存来提升性能和响应时间。对于Map Entry类型的数据,集中式分布式Cache都有很多选择,对于传统的关系型数据,从MySQLOracle都给了很好的支持,唯有松散数据这类数据,采用前后两种解决方案都不能最大化它的处理能力。因此BigTable才有了它用武之地。

       HBASE作为Apache的开源项目,也是出于起步阶段,因为其实它所依赖的Hadoop也不能说已经到了成熟阶段,所以都有很大的发展空间,这也为我们这些开源爱好者提供了更多空间去贡献。这里主要会谈到HBASE的框架设计方面的知识和它的一些特点,不论是否采用HBASE去解决工作中的问题,一种好的流程设计总会给开发者和架构设计者带来一些思想上的火花。

HBASE设计介绍

数据模型

       HBASE中的每一张表,就是所谓的BigTableBigTable会存储一系列的行记录,行记录有三个基本类型的定义:Row Key,Time Stamp,ColumnRow Key是行在BigTable中的唯一标识,Time Stamp是每次数据操作对应关联的时间戳,可以看作类似于SVN的版本,Column定义为:<family>:<label>,通过这两部分可以唯一的指定一个数据的存储列,family的定义和修改需要对HBASE作类似于DBDDL操作,而对于label的使用,则不需要定义直接可以使用,这也为动态定制列提供了一种手段。family另一个作用其实在于物理存储优化读写操作,同family的数据物理上保存的会比较临近,因此在业务设计的过程中可以利用这个特性。

看一下逻辑数据模型:

Row Key

Time Stamp

Column "contents:"

Column "anchor:"

Column "mime:"

"com.cnn.www"

t9

"anchor:cnnsi.com"

"CNN"

t8

"anchor:my.look.ca"

"CNN.com"

t6

"<html>..."

"text/html"

t5

"<html>..."

t3

"<html>..."

上表中有一列,列的唯一标识为com.cnn.www,每一次逻辑修改都有一个timestamp关联对应,一共有四个列定义:<contents:>,<anchor:cnnsi.com>,<anchor:my.look.ca>,<mime:>。如果用传统的概念来将BigTable作解释,那么BigTable可以看作一个DB Schema,每一个Row就是一个表,Row key就是表名,这个表根据列的不同可以划分为多个版本,同时每个版本的操作都会有时间戳关联到操作的行。

再看一下HBASE的物理数据模型:

Row Key

Time Stamp

Column "contents:"

"com.cnn.www"

t6

"<html>..."

t5

"<html>..."

t3

"<html>..."

Row Key

Time Stamp

Column "anchor:"

"com.cnn.www"

t9

"anchor:cnnsi.com"

"CNN"

t8

"anchor:my.look.ca"

"CNN.com"

Row Key

Time Stamp

Column "mime:"

"com.cnn.www"

t6

"text/html"

物理数据模型其实就是将逻辑模型中的一个Row分割成为根据Column family存储的物理模型。

对于BigTable的数据模型操作的时候,会锁定Row,并保证Row的原子操作。

框架结构及流程


1 框架结构图

       HBASE依托于HadoopHDFS作为存储基础,因此结构也很类似于HadoopMaster-Slave模式,Hbase Master Server 负责管理所有的HRegion Server,但Hbase Master Server本身并不存储HBASE中的任何数据。HBASE逻辑上的Table被定义成为一个Region存储在某一台HRegion Server上,HRegion Server Region的对应关系是一对多的关系。每一个HRegion在物理上会被分为三个部分:HmemcacheHlogHStore,分别代表了缓存,日志,持久层。通过一次更新流程来看一下这三部分的作用:


2 提交更新以及刷新Cache流程

       由流程可以看出,提交更新操作将会写入到两部分实体中,HMemcacheHlog中,HMemcache就是为了提高效率在内存中建立缓存,保证了部分最近操作过的数据能够快速的被读取和修改,Hlog是作为同步HmemcacheHstore的事务日志,在HRegion Server周期性的发起Flush Cache命令的时候,就会将Hmemcache中的数据持久化到Hstore中,同时会清空Hmemecache中的数据,这里采用的是比较简单的策略来做数据缓存和同步,复杂一些其实可以参照java的垃圾收集机制来做。

       在读取Region信息的时候,优先读取HMemcache中的内容,如果未取到再去读取Hstore中的数据。

几个细节:

1.              由于每一次Flash Cache,就会产生一个Hstore File,在Hstore中存储的文件会越来越多,对性能也会产生一定影响,因此达到设置文件数量阀值的时候就会Merge这些文件为一个大文件。

2.              Cache大小的设置以及flush的时间间隔设置需要考虑内存消耗以及对性能的影响。

3.              HRegion Server每次重新启动的时候会将Hlog中没有被FlushHstore中的数据再次载入到Hmemcache,因此Hmemcache过大对于启动的速度也有直接影响。

4.              Hstore File中存储数据采用B-tree的算法,因此也支持了前面提到对于ColumnFamily数据操作的快速定位获取。

5.              HRegion可以Merge也可以被Split,根据HRegion的大小决定。不过在做这些操作的时候HRegion都会被锁定不可使用。

6.              Hbase Master Server通过Meta-info Table来获取HRegion Server的信息以及Region的信息,Meta最顶部的一个Region是虚拟的一个叫做Root Region,通过Root Region可以找到下面各个实际的Region

7.              客户端通过Hbase Master Server获得了Region所在的Region Server,然后就直接和Region Server进行交互,而对于Region Server相互之间不通信,只和Hbase Master Server交互,受到Master Server的监控和管理。

后话

       HBase还没有怎么使用,仅仅只是看了wiki去了解了一下结构和作用,暂时还没有需要使用的场景,不过对于各种开源项目的设计有所了解,对自己的框架结构设计也会有很多帮助,因此分享一下。

posted @ 2008-08-08 11:37 岑文初 阅读(5973) | 评论 (1)编辑 收藏

这部分内容是分布式计算开源框架Hadoop入门实践的第二部分,讲述了关于实际使用配置的内容.第三部分是对于集群配置的测试结果分析的部分,下周三应该会在InfoQ刊登.

http://www.infoq.com/cn/articles/hadoop-config-tip

posted @ 2008-08-08 08:47 岑文初 阅读(1412) | 评论 (1)编辑 收藏

分布式计算开源框架Hadoop入门实践(一)
第一部分已经在InfoQ.cn上刊登了第一部分

http://www.infoq.com/cn/articles/hadoop-intro

posted @ 2008-08-04 17:52 岑文初 阅读(1872) | 评论 (1)编辑 收藏

 

SIP的第四期结束了,因为控制策略的丰富,早先的的压力测试结果已经无法反映在高并发和高压力下SIP的运行状况,因此需要重新作压力测试。跟在测试人员后面做了快一周的压力测试,压力测试的报告也正式出炉,本来也就算是告一段落,但第二天测试人员说要修改报告,由于这次作压力测试的同学是第一次作,有一个指标没有注意,因此需要修改几个测试结果。那个没有注意的指标就是load average,他和我一样开始只是注意了CPU,内存的使用状况,而没有太注意这个指标,这个指标与他们通常的限制(10左右)有差别。重新测试的结果由于这个指标被要求压低,最后的报告显然不如原来的好看。自己也没有深入过压力测试,但是觉得不搞明白对将来机器配置和扩容都会有影响,因此去问了DBASA,得到的结果相差很大,看来不得不自己去找找问题的根本所在了。

       通过下面的几个部分的了解,可以一步一步的找出Load Average在压力测试中真正的作用。

CPU时间片

       为了提高程序执行效率,大家在很多应用中都采用了多线程模式,这样可以将原来的序列化执行变为并行执行,任务的分解以及并行执行能够极大地提高程序的运行效率。但这都是代码级别的表现,而硬件是如何支持的呢?那就要靠CPU的时间片模式来说明这一切。程序的任何指令的执行往往都会要竞争CPU这个最宝贵的资源,不论你的程序分成了多少个线程去执行不同的任务,他们都必须排队等待获取这个资源来计算和处理命令。先看看单CPU的情况。下面两图描述了时间片模式和非时间片模式下的线程执行的情况:


1 非时间片线程执行情况


2 非时间片线程执行情况

       在图一中可以看到,任何线程如果都排队等待CPU资源的获取,那么所谓的多线程就没有任何实际意义。图二中的CPU Manager只是我虚拟的一个角色,由它来分配和管理CPU的使用状况,此时多线程将会在运行过程中都有机会得到CPU资源,也真正实现了在单CPU的情况下实现多线程并行处理。

       CPU的情况只是单CPU的扩展,当所有的CPU都满负荷运作的时候,就会对每一个CPU采用时间片的方式来提高效率。

       Linux的内核处理过程中,每一个进程默认会有一个固定的时间片来执行命令(默认为1/100秒),这段时间内进程被分配到CPU,然后独占使用。如果使用完,同时未到时间片的规定时间,那么就主动放弃CPU的占用,如果到时间片尚未完成工作,那么CPU的使用权也会被收回,进程将会被中断挂起等待下一个时间片。

CPU利用率和Load Average的区别

       压力测试不仅需要对业务场景的并发用户等压力参数作模拟,同时也需要在压力测试过程中随时关注机器的性能情况,来确保压力测试的有效性。当服务器长期处于一种超负荷的情况下运行,所能接收的压力并不是我们所认为的可接受的压力。就好比项目经理在给一个人估工作量的时候,每天都让这个人工作12个小时,那么所制定的项目计划就不是一个合理的计划,那个人迟早会垮掉,而影响整体的项目进度。

CPU利用率在过去常常被我们这些外行认为是判断机器是否已经到了满负荷的一个标准,看到50%-60%的使用率就认为机器就已经压到了临界了。CPU利用率,顾名思义就是对于CPU的使用状况,这是对一个时间段内CPU使用状况的统计,通过这个指标可以看出在某一个时间段内CPU被占用的情况,如果被占用时间很高,那么就需要考虑CPU是否已经处于超负荷运作,长期超负荷运作对于机器本身来说是一种损害,因此必须将CPU的利用率控制在一定的比例下,以保证机器的正常运作。

Load AverageCPULoad,它所包含的信息不是CPU的使用率状况,而是在一段时间内CPU正在处理以及等待CPU处理的进程数之和的统计信息,也就是CPU使用队列的长度的统计信息。为什么要统计这个信息,这个信息的对于压力测试的影响究竟是怎么样的,那就通过一个类比来解释CPU利用率和Load Average的区别以及对于压力测试的指导意义。

我们将CPU就类比为电话亭,每一个进程都是一个需要打电话的人。现在一共有4个电话亭(就好比我们的机器有4核),有10个人需要打电话。现在使用电话的规则是管理员会按照顺序给每一个人轮流分配1分钟的使用电话时间,如果使用者在1分钟内使用完毕,那么可以立刻将电话使用权返还给管理员,如果到了1分钟电话使用者还没有使用完毕,那么需要重新排队,等待再次分配使用。


3 电话使用场景

       上图中对于使用电话的用户又作了一次分类,1min的代表这些使用者占用电话时间小于等于1min2min表示使用者占用电话时间小于等于2min,以此类推。根据电话使用规则,1min的用户只需要得到一次分配即可完成通话,而其他两类用户需要排队两次到三次。

       电话的利用率 = sum (active use cpu time)/period

每一个分配到电话的使用者使用电话时间的总和去除以统计的时间段。这里需要注意的是是使用电话的时间总和(sum(active use cpu time)),这与占用时间的总和(sum(occupy cpu time))是有区别的。(例如一个用户得到了一分钟的使用权,在10秒钟内打了电话,然后去查询号码本花了20秒钟,再用剩下的30秒打了另一个电话,那么占用了电话1分钟,实际只是使用了40秒)

电话的Average Load体现的是在某一统计时间段内,所有使用电话的人加上等待电话分配的人一个平均统计。

电话利用率的统计能够反映的是电话被使用的情况,当电话长期处于被使用而没有的到足够的时间休息间歇,那么对于电话硬件来说是一种超负荷的运作,需要调整使用频度。而电话Average Load却从另一个角度来展现对于电话使用状态的描述,Average Load越高说明对于电话资源的竞争越激烈,电话资源比较短缺。对于资源的申请和维护其实也是需要很大的成本,所以在这种高Average Load的情况下电话资源的长期“热竞争”也是对于硬件的一种损害。

低利用率的情况下是否会有高Load Average的情况产生呢?理解占有时间和使用时间就可以知道,当分配时间片以后,是否使用完全取决于使用者,因此完全可能出现低利用率高Load Average的情况。由此来看,仅仅从CPU的使用率来判断CPU是否处于一种超负荷的工作状态还是不够的,必须结合Load Average来全局的看CPU的使用情况和申请情况。

所以回过头来再看测试部对于Load Average的要求,在我们机器为8CPU的情况下,控制在10 Load左右,也就是每一个CPU正在处理一个请求,同时还有2个在等待处理。看了看网上很多人的介绍一般来说Load简单的计算就是2* CPU个数减去1-2左右(这个只是网上看来的,未必是一个标准)。

补充几点:

1.对于CPU利用率和CPU Load Average的结果来判断性能问题。首先低CPU利用率不表明CPU不是瓶颈,竞争CPU的队列长期保持较长也是CPU超负荷的一种表现。对于应用来说可能会去花时间在I/O,Socket等方面,那么可以考虑是否后这些硬件的速度影响了整体的效率。

这里最好的样板范例就是我在测试中发现的一个现象:SIP当前在处理过程中,为了提高处理效率,将控制策略以及计数信息都放置在Memcached Cache里面,当我将Memcached Cache配置扩容一倍以后,CPU的利用率以及Load都有所下降,其实也就是在处理任务的过程中,等待Socket的返回对于CPU的竞争也产生了影响。

2.未来多CPU编程的重要性。现在服务器的CPU都是多CPU了,我们的服务器处理能力已经不再按照摩尔定律来发展。就我上面提到的电话亭场景来看,对于三种不同时间需求的用户来说,采用不同的分配顺序,我们可看到的Load Average就会有不同。假设我们统计Load的时间段为2分钟,如果将电话分配的顺序按照:1min的用户,2min的用户,3min的用户来分配,那么我们的Load Average将会最低,采用其他顺序将会有不同的结果。所以未来的多CPU编程可以更好的提高CPU的利用率,让程序跑的更快。

以上所提到的内容未必都是很准确或者正确,如果有任何的偏差也请大家指出,可以纠正一些不清楚的概念。

posted @ 2008-06-30 17:35 岑文初 阅读(37438) | 评论 (17)编辑 收藏

     摘要:          我对于Memcached的接触,还是在去年看了CSDN的一系列国外大型网站架构设计而开始的。最初的时候只是简单的封装了Memcached Java版的客户端,主要是对于配置的简化以及Memcached多点备份作了一些工作,然后就作为ASF的组件一部分提供给其他Team使用。其实看过Memcached Jav...  阅读全文
posted @ 2008-06-04 23:10 岑文初 阅读(8961) | 评论 (2)编辑 收藏

 在《Java 载入Jar内资源问题的探究》这个文档贴出来以后,有朋友给了我反馈,最终知道了问题就出现在JarOutputstream输出的时候,虽然支持直接写入目录中的文件来同时产生目录和文件,但是这样在jar中目录就不是一个有效的entry,因此在资源定位的时候就无法得到,因此必须也把目录作为entry写入,这样才会正常定位资源。这个问题作了测试以后反馈到我们的工具开发人员那边,做了修改以后一切都恢复正常,细节决定成败,那么一点细微的差异,会让各种框架都无法正常运作。

代码修改如下:

JarOutputStream jos;

       try

       {

           jos = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(file)));

           String f = "spring/sip-analyzer-dataSource.xml";

           String dir = "spring/";
           JarEntry je1 = new JarEntry(dir);
           jos.putNextEntry(je1);

   
     
    
           JarEntry je =
new JarEntry(f);

           jos.putNextEntry(je);

           BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:/work/sip3/analyzer/src/conf.test/spring/sip-analyzer-dataSource.xml"));

           int i = 0;

           while ((i=bis.read())!=-1)

           {

              jos.write(i);

           }

           bis.close();

           jos.closeEntry();

           jos.close();

} catch  ...

posted @ 2008-06-03 14:24 岑文初 阅读(2330) | 评论 (3)编辑 收藏

 

       工作忙,有些许时间没有更新Blog了,这次在开发监控模块的时候遇到了这个问题,整个问题定位过程真是走了不少路,所以觉得有必要记录下来分享一下。在我看来很多时候结果也许就很简单一个原因,但是开发人员却要探究很久,也许在找到了其他可实现业务逻辑方法的情况下,就会放弃寻找原因,这期间我也是一样。

问题初现:

       在服务集成平台中需要新增一块写入数据库的逻辑,因此考虑最简便就是弄个SpringBeanFactory来搞定这一切,谁知道,问题就这么出现了。很简单,通过SpringClassPathXmlApplicationContext来构建BeanFactory,下面的语句大家应该很熟悉:

ctx = new ClassPathXmlApplicationContext("/spring/sip-*.xml");

       通过通配符来载入ClassPath下的所有的符合规则的spring配置文件。然后在Eclipse中作完单元测试和集成测试,一切正常。然后用我们内部的打包部署,而这些配置文件都被打在Jar中作为lib库依赖。结果启动以后,在分析完日志需要写入到数据库的时候出现异常:

Could not resolve bean definition resource pattern [/spring/sip-*.xml]; nested exception is java.io.FileNotFoundException: class path resource [spring/] cannot be resolved to URL because it does not exist

就提示来说,就是没有找到spring这个目录,也就是在ClassPath下面就没有找到资源。

第一次试图解决问题:

以前调整过Jboss关于ClassLoader的配置,即自上而下搜索还是自下而上搜索,以及是否采用Web容器的ClassLoader,开始怀疑是否是这种修改造成的问题。修改了没有问题,然后就设置断点跟踪SpringClassPathXmlApplicationContext的构造过程,发现Spring在分析此类通配类型的过程中,首先将前面的文件目录和后面的具体通配文件分开,先定位文件目录资源,也就是在定位文件目录资源的过程中,找不到spring目录,而出现了那个异常。看了代码中也有对Jar的处理,但是在处理之前就出现了问题。

自己做了尝试,将spring目录和其内容解压到WebClasses目录下运行正常,或者解压到war下面也是正常的,这些地方其实都是ClassPath可以找到的,但是lib目录下的jar也应该是可以找到的。在仔细跟踪了代码中最后载入这些资源的ClassLoader内的数据,所有的Jar都是包含在内的。

由于工作太多,因此将原有的打包模式作了修改,每次打包将这部分配置解压到war下面,这样就找到了可解决方案了,因此细致的缘由也就没有再去追究。(如果不是后面再次遇到,这个问题就会在此了结)

问题再现:

       监控模块中需要新增一块写入数据库的逻辑,在单元测试和集成测试通过的情况下出现了问题,由于此次是普通的J2SE的应用,所有的配置和依赖都打入在了Jar中,所以问题和前次一样。

       这次决定花一些时间好好找到问题所在,首先觉的Spring的资源载入应该不会不支持从Jar中载入,这是最基本的功能,因此再次打开了Spring的源码。

问题二次定位:

先看看ClassPathXmlApplicationContext的类图结构:




    关键方法就是getResource方法,ClassPathXmlApplicationContext的资源定位就是采用了DefaultResourceLoadergetResource方法。内部也没有做太多的工作,其实就是如下的代码:

try

{

                      // Try to parse the location as a URL...

                      URL url = new URL(location);

                      returnnew UrlResource(url);

           }

           catch (MalformedURLException ex)

{

                 // No URL -> resolve as resource path.

                 return getResourceByPath(location);

      }

上面的代码都是标准的j2se的代码.作为URL通过字符串来构造,通常需要能够首先获得URL的资源全路径,而在当前情况下发现到获取资源的时候location还是保持了spring/的状态,而没有被替换成为所在jar的资源全路径,那么就先作以下测试:

    新建简单的项目,然后在项目中加入包含spring配置的jar,然后作单元测试,测试代码如下:

URL url = Thread.currentThread().getClass().getResource("/spring/");

    未获取到URL,出现异常。

URL url = Thread.currentThread().getClass().getResource("/spring/sip-analyzer-dataSource.xml");

       正常获取到了URL

由此看来应该是在获取Jar中的目录资源路径的时候出现问题导致后续载入出现问题,尝试直接传入具体的文件名:

ctx = new ClassPathXmlApplicationContext("/spring/sip-analyzer-dataSource.xml");

发现还是出现问题,在new URL的时候传入的是没有翻译过的文件名,考虑在传入的过程中就直接替换成为资源路径,因此写了一个简单的方法:

publicstatic String[] getRealClassPath(String[] locationfile)

      {

           String[] result = locationfile;

                 for(int i = 0 ; i < locationfile.length; i++)

                 {

                      try

                      {

                            URL url = Thread.currentThread().getClass().getResource(locationfile[i]);

                            String file = url.getFile();

                            if (file.indexOf(".jar!") > 0)

                                  result[i] = new StringBuffer("jar:").append(file.substring(0,file.indexOf(".jar!")+".jar!".length()))

                                             .append(locationfile[i]).toString();

                      }

                      catch(Exception ex)

                      {}

                 }

          

           returnresult;

}

在将构造工厂类修改为:

ctx = new ClassPathXmlApplicationContext(BaseUtil.getRealClassPath(new String[]{"/spring/sip-analyzer-dataSource.xml"}));

运行测试,正常启动,这也就是又变成最原始的文件罗列的模式。问题虽然找到了解决方案,但是始终觉得很别扭,同时对于无法在Jar中载入配置资源的情况我一直都还是觉得应该不是Spring的问题。

峰回路转:

晚上到家还是有点不死心,就直接建了个项目作单元测试,然后将一个自己建立的Jar加入到Classpath下面,作单元测试,结果大吃一惊。

URL url = Thread.currentThread().getClass().getResource("/test/");

URL url = Thread.currentThread().getClass().getResource("/test/test.txt");

都正常获取到了资源,这完全推翻了我早先认为在Jar中无法获得目录作为资源的问题。然后把公司里面的项目重新打包然后加入到ClassPath下,验证spring的目录,出错,目录无法获取,此时我确定看来应该不是应用的问题,而是环境问题。检查了两个Jar,看似没有什么区别,将公司项目的Jar中的spring目录拷贝到测试的jar中,然后作测试,可以找到目录。那么问题完全定位到了Jar本身。通过RAR的压缩工具看了一下两个Jar的信息,除了显示所谓的压缩平台不同(一个是DOS,一个是Unix)其他没有任何区别。然后自己用RAR打了一个Jar以及在linux下打了一个Jar做了测试,两个Jar内的目录都是正常可以被获取。

无意中我换了一下需要获取的目录名称,也就是说在公司项目中有多个目录在jar中,这次换成为ibatis目录,正常获取,看来不是Jar的格式。回想了一下,公司的打包工具是自己人写的,其中提供了一个特性,如果一个项目内部的一些配置信息是需要让调用它的第三方在编译期配置,那么可以通过在第三方项目构建的过程中,动态的生成配置文件然后植入到被依赖的jar中。而spring这个目录中由于那些数据库的配置都是需要动态配置的,因此spring的那个目录是后期被写入的,而ibatis是早先就固化在项目中的。

由于我们的JarMETA-INF中都有INDEX.LIST文件,过去遇到过在JAR中自己手工放入一些文件由于没有修改INDEX.LIST而导致虽然文件已经存在但是不会被发现,于是打开公司项目中的Jar,果然INDEX.LIST中只有ibatis,而没有spring,看来是我的同事在写入的时候没有将INDEX.LIST更新。立刻将INDEX.LIST作了更新,测试spring目录,结果依然出错。看来这还不是问题的根本。

立刻问了我们开发打包工具的同事,向他们要写入Jar的代码,对方的回答是就是采用简单的JarOutputStream来写入,没有什么特殊的。那我就开始怀疑是否是因为采用这种方式写入到Jar中的目录在被资源定位的时候会出现问题。于是写了下面的代码:

JarOutputStream jos;

           try

           {

                 jos = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(file)));

                 String f = "spring/sip-analyzer-dataSource.xml";

                 File source = new File(f);

                 JarEntry je = new JarEntry(f);

                 jos.putNextEntry(je);

                 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:/work/sip3/analyzer/src/conf.test/spring/sip-analyzer-dataSource.xml"));

                 int i = 0;

                 while ((i=bis.read())!=-1)

                 {

                      jos.write(i);

                 }

                 bis.close();

                 jos.closeEntry();

                 jos.close();

} catch  ...

结果创建出来的Jar中的spring目录无法被资源定位,同样在这个Jar中直接拖入一个目录test,然后刷新测试,test目录可以被定位。

后续

       就到了这个阶段来看如果以上面这种方式写入,对于目录资源定位的却存在问题。这个问题还没有最终的肯定的结论,在我现在所有的试验来看,不论是否有INDEX.LIST,或者INDEX.LIST,如果用程序写入到Jar中,目录作为资源定位都会出现问题(起码是上面那种普通写入方式)。

       这种情况可能是由于这种写法还有一些其他需要配置的,例如写入到META-INF/INDEX.LIST中,或者就是J2SE现在API存在的一个问题。不过不管是什么问题,起码值得引起重视,特别是现在类似于Spring框架载入Jar目录下的配置。

posted @ 2008-05-28 16:42 岑文初 阅读(3843) | 评论 (2)编辑 收藏

 

       在完成ASF集成REST以后,接到的任务就是要完成一个日志分析应用。需求没有很明确,只是要有这么一个东西能够满足分析收集后的日志,将分析后的原始数据入库,作为后期分析和统计使用。

       在动手做之前,我还是给这个应用作了最基本的需求定义:灵活配置(输入源,输出目标,分析器的实现等),高效(并行任务分解)。就这两点能够做到,那么将来需求如何变化都可以适应。TigerConcurrent包是满足后面那项最好的实现,因此打算好好的实践一把,也就这部分Tiger的特性还没有充分使用过,里面的线程池,异步服务调用,并发控制都能够极好的完成并行任务分解的工作。也就是在这个过程中,看到了IBM开发者论坛上的一片文章,讲关于《应用fork-join框架》,谈到了在J2SE 7 Concurrent包中将会增加fork-join风格的并行分解库,其实这个是更细粒度的任务分解,同时能够在当前多CPU的情况下提高执行效率,充分利用CPU的一种实现。无关的话不多说了,就写一下整个设计和实现的过程以及中间的一些细节知识。

 

背景:

       由于服务路由应用访问量十分大,即时的将访问记录入库对于路由应用本身以及数据库来说无疑都会产生很大的压力和影响。因此考虑首先将访问信息通过log4j记录在本地(当然自己需要定制一下Log4jAppenderFilter),然后通过服务器的定时任务脚本来将日志集中到日志分析应用所在的机器上(这里通过配置可以决定日志是根据什么时间间隔来产生新文件)。日志分析应用就比较单纯的读取日志,分析日志,输出分析结果(包括写入数据库或者是将即时统计信息存入到集中式缓存Memcached中)。网络结构图如下:

1 网络结构图

 

Concurrent概述:

       Tiger出来也有些年头了,但是每一个新的特性是否都在实际的工作中使用过,起码我自己是没有作到的,包括对于Concurrent包也只是看过,写了几个Test case玩一下,但具体使用到实际开发中还是比较少的。在这个工作之前,如果考虑要使用对象池或者线程池,那么一定会去采用apachecommon pool,不过在现在jdk日益“强大”的基础下,能够通过jdk自己搞定的,就尽量不再引入第三方包了。看Java Doc很容易就理解了Concurrent,这里我只是大致的说一下几个自己在应用中使用的接口:

 

BlockingQueue<E>:看看名字就知道了,阻塞式队列,可以设置大小。适合于生产者和消费者模式,生产者在队列满时阻塞,消费者在队列空时阻塞。在日志分析应用开发中被用于分析任务(生产者)和输出任务(消费者)之间的分析结果存储通道。

 

Callable<V>:任何需要执行的任务都可以定义成Callable,类似于线程的Runnable接口,可以被Service Executor指派给内部的线程异步执行,并且返回对象或者抛出异常。在日志分析应用开发中,非定时性的任务都定义成为此类型。

 

ConcurrentMap<K,V>:这个以前常常使用,因为效率要远远高于Collections.synchronizedCollectionsynchronized。后面还会提到实践中的几个实用的技巧来防止在高并发的情况下出现问题。在日志分析应用中,此类型的Map作为保存日志文件分析状态的缓存(日志文件分为两种状态:分析中,分析结束。如果不存在于Map中就认为尚未分析,那么将其纳入Map然后启动分析处理线程工作,如果存在于Map中标示为分析中,那么将不会再分析此文件,如果分析结束并且被输出,将会标示此文件分析结束,异步清理线程将会定时根据策略删除或移动文件)。

 

ExecutorService:内置线程池,异步执行指派任务,并可以根据返回的Future来跟踪执行情况。在日志分析应用开发中,被用于非定时性任务执行。

 

ScheduledExecutorService:内置线程池,定时异步执行指派任务,并可以根据返回的Future来跟踪执行情况。在日志分析应用开发中,被用于定时性任务执行。

 

以上就是被使用到的接口,具体实现策略配置就不在此赘述了。

 

整体结构设计:

       整体设计还是基于开始设定的两个原则:灵活配置,高效性(任务分解,并行流水线执行)。说到任务分解又会想起读书时候的离散数学中关键路径等等。任务分解还是要根据具体情况来分析和设计,不然并行不但不会提高效率,反而还降低了处理效率。

就日志分析来看,主要的处理过程可以分成这么几个任务:

1.             检查日志来源目录,锁定需要分析的文件。(执行需要时间很短,可通过定时间隔执行)。

2.             分析已经被锁定的日志文件,产生分析结果。(执行需要时间根据日志文件大小来决定,因此需要线程异步执行,结果根据设定拆分成细粒度包,降低输出线程等待时间)。

3.             检查分析结果队列。(执行需要时间很短,当前是配置了SingleThreadExecutor来执行检查阻塞队列的工作,同时获取到分析结果包以后立刻创建线程来执行输出任务)

4.             输出分析结果,如果输出成功,将分析过的日志文件在日志文件状态缓存中的状态更新为已分析。(执行时间根据输出情况来定,当前实现的是批量输出到数据库中,根据配置来批量提交入库,后续还会考虑实时统计到集中式Cache作为监控使用)。

5.             清理分析日志文件。(执行时间较短,设定了定时线程池执行清理任务,根据策略配置来执行清理和移动文件任务,并且清除在日志文件状态缓存中的信息)

 

根据上面的分解可以看到,其实在单线程工作的过程中,容易造成阻塞而影响性能的主要是读取,分析和写出这三个过程的协调,一个一个读取分析和写出,性能一定低于读取和分析并行工作,而分析完毕才写出,性能一定低于分析部分,写出部分。

同时由于细分各个任务,因此任务与任务之间的耦合度降低,可以运行期获取具体的任务实现配置,达到灵活配置的目的。

下面就具体的看看整个流程,以及其中的一些细节的说明,这里根据下图中的序号来逐一描述:

1.              配置了Schedule Executor来检查日志所属目录中的日志文件,Executor的线程池大小以及检查时间间隔都根据配置来设定。

Tip:定时任务可以设置delay时间,那么可以根据你的任务数量以及时间间隔来设定每一个任务的delay时间,均匀的将这些任务分布,提高效率。

 

2.              Read Schedule被执行时,将会去检查Analysis Log File State Concurrent Cache(也就是上面提到的ConcurrentMap)中是否存在此文件,如果不存在证明尚未分析,需要将其置入Cache,如果已经存在就去查询其他文件。Tip:这里用了一点小技巧,通常我们对于此类操作应该做两部分工作,get然后再put,但是这样可能就会在高并发的情况下出现问题,因为这两个操作不是一个原子操作。ConcurrentMap提供了putIfAbsent操作,这个操作意思就是说如果需要putkey没有存在于Map中,那么将会把key,value存入,并且返回null,如果已经存在了key那么就返回keymap已经对应的值。通过if (resources.putIfAbsent(filename, Constants.FILE_STATUS_ANALYSISING) == null)就可以把两个操作合并成为一个操作。

3.              日志读取的工作线程完成锁定文件以后,就将后续的工作交给Log Analysis Service Executor来创建分析任务异步执行分析操作,日志读取工作线程任务就此完成。

4.              Log Analysis Schedule是运行期装载具体的接口实现类(采用的就是类似于JAXP等框架使用的META-INF/services来读取工厂类,载入接口实现)。Analysis Schedule执行的主要任务就是分析文件,并且根据配置将分析结果拆分并串行的置入到Block Queue中,提供给输出线程使用。

5.              Receiver主要工作就是守候着Block Queue,当有数据结果产生就创建Write Schedule来异步执行输出。

6.              Log Writer Service Executor根据配置来决定内置线程池大小,同时在Receiver获取到数据包时产生Write Schedule来异步执行输出工作。

7.              Write ScheduleAnalysis Schedule一样可以运行期装载接口实现类,这样提供了灵活的输出策略配置。

Tips:在数据库输出的时候需要配置批量提交记录最大数,分批提交提高性能,也防止过大结果集批量提交问题。

8.              写出完成以后需要更新锁定文件的状态,标示成为已经分析成功。这里还遗留一点问题,在一个日志文件分包的过程中每一个包都回记录隶属于哪一个分析文件,文件的最后一个数据包将会被标示。在输出成功以后会去检查哪些包是文件最后数据包,更新此文件为已分析成功,如果出现异常,那么将会把这些文件状态清除,接受下一次的重新分析。这里一个文件部分包提交暂时没有做到事务一致,如果出现部分成功可能会重复分析和记录。

9.              最后就是Clean Schedule被定时执行,根据策略来删除或者移动已经被分析过的文件。

 

Tips:

ScheduledExecutorService内部可以配置线程池,当执行定时任务比较耗时,线程池中的线程都被占用的情况下,定时任务将不会准确的按时执行,因此设计过程中需要注意的是,定时任务一般是简短的工作任务,如果比较耗时,那么应该结合ScheduledExecutorServiceExecutorService,定时任务完成必要工作以后将耗时工作转交给ExecutorService创建的即时执行异步线程去处理,保证Schedule Executor正常工作。

2 流程结构设计

 

类图:

 

 

3 类图1

 

       上面的类图中主要描述的就是日志分析应用的三个主类:类似于控制台的LogAnalyzer,具体内部资源管理类,配置类。(T表示采用泛型)

 

4 类图2

       类图2主要就是描述了在整个应用中所有的被分解可并行的任务定义。ClearSchedule是用来在控制台输入stop停止日志分析的时候,做后续资源回收工作的任务。CleanSchedule是用来清除被分析后的日志文件任务。ConsumerSchedule是阻塞队列消费者任务。

       其他还有一些辅助工具类以及工厂类和定义类就不画了。

 

后话:

       做这个设计和开发的过程中又好好的实践了一些编程细节方面的内容,作为架构设计来说,需要多一些全局观和业务观,作为一个良好的开发者来说需要多实践,多了解一些细节,在不断学习和掌握各种大方向技术框架的同时,适当的了解一些细节也是一种很好的补充,同时也可以衍生思考。

       REST风格的服务结合云计算的思想,会被使用的更为广泛,而云计算其实就是一个问题分解和组合处理的过程,可以说是一种宏观的问题解决策略。高效解决问题,提供服务,通过组合体现业务最大价值,就是互联服务的最重要目的。

更多文章请访问:http://blog.csdn.net/cenwenchu79/

posted @ 2008-04-23 08:41 岑文初 阅读(2440) | 评论 (6)编辑 收藏

  
       假期结束,开始收心回来继续工作。晚上有一个项目要发布,公司的同事突然打手机给我,说ASF的文件解析又出了上次的问题,希望尽快解决。
 
问题描述:
 
上一次问题:
多台机器运行同一个分支的应用,但是有些机器正常,有一台机器始终在启动的时候报文件解析错误,从提示看来,主要是因为解析配置文件的时候校验dtd失效,这台机器无法连接外网。最后降低了我们内部的核心解析包,问题解决(或者让这台机器连接到外网)。(当时由于自己手头工作比较多,也没有在意,既然解决了就随之过去了)
 
此次问题:
       问题的提示和上次的类似,不过这次的机器时连接外网的。
 
问题查找:
       解析出错的文件是ASF(SCA的服务框架)的组件配置文件(composite文件),格式为xml的格式,解析方式是通过StAX标准来实现的。
       按照上一次的解决方法,我将内部的tuscany0.998降级到tuscany0.997,解析正常。看了一下我对于这两个版本升级作的修改,主要是支持了SCA框架中的Spring配置文件能够使用import的标签,内签多个标准的spring文件。
       跟踪代码内部发现,果然是在解析某几个spring的配置文件时出现了问题,比较了一下ASF的Spring(正常解析)和标准的Spring配置文件,差别主要是在关于Xml的校验申明的区别。ASF的Spring配置文件是由ASF Spring插件来自己解析的(采用Schema申明(固定的Target namingspace),因此早先所有的ASF的Spring我都要求大家采用Schema的校验申明),而对于原来不是ASF的spring都是采用dtd的校验方式申明(互相拷贝导致都是这样)。下面就是两种申明:
 
Schema:
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:sca="http://www.springframework.org/schema/sca"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/sca http://www.springframework.org/schema/sca/spring-sca.xsd">
 
Dtd:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
 
       早先由于在0.997版本中没有支持import,因此也就不会去解析那些不是ASF的Spring文件,而现在因为需求支持了import所以需要解析那些原来不属于ASF的Spring的配置文件。因此降低版本不是解决问题的办法。
       进一步跟进问题,发现是在解析Dtd的申明时候出现问题,抛出异常说连接超时。通过IE访问了一下dtd的地址,的却也是有问题,无法连接。看来是Spring的dtd的服务器出现了问题,导致了我们解析文件时候校验无法正常,最终无法正常启动。
 
问题解决:
       这里先说一下最后解决的几个方案,后面会有一些详细的解释和说明。
 
1. 升级ASF的Spring插件包,去除对于Xml的格式校验。
XMLInputFactory xmlFactory = XMLInputFactory.newInstance();          
//add by wenchu.cenwc cancel support dtd check
xmlFactory.setProperty(XMLInputFactory.SUPPORT_DTD, "false");
 
2. 将Dtd的校验申明修改成为Schema的校验申明。
3. 建立公司的Xml校验服务器,控制管理dtd或者schema,将所有的xml Schema或者dtd申明指向该服务器。
 
问题延伸开来的思考:
就问题的解决方案来看这个问题的一些值得注意和思考的地方。
 
方案1:
当前对于XML解析来说,各种框架都已经统一的实现了StAX的标准,同时在jdk6得rt.jar中都已经将StAX API作为基础框架API纳入其内。而通常情况下,如果不配置是否校验Xml,那么都将默认会主动校验Xml,此时就会出现上面我所遇到的问题,如果当你依赖的Xml DTD 或者 Schema服务器出现问题,就会导致你本地应用可能受到影响。在Dtd的申明说明中,<!DOCTYPE rootElement PUBLIC "PublicIdentifier" "URIreference">红色部分说可以在网络出现问题或者网络速度很慢的时候被部分xml解析器替代URIreference使用,不过我这边没有成功过。我通过自己屏蔽网络连接来模拟环境的情况,都是无法通过的。(Schema比较奇怪,就算无法连接网络还是可以正常的,这个后续需要继续研究看看,或者有朋友对这个问题有了解请告知一下)
 
方案2:
       其实早在2002年就已经有将dtd替换成为schema的趋势了,两者的区别和优劣网上的文章介绍了很多了,这儿不再罗列,其实最更本一点就是schema就是用xml来校验xml,而dtd却采用了另一套规则来校验xml,其本身也是xml,扩展性,可读性,学习曲线等等都次与schema。除了遗留系统,我个人看不出还有什么必要去使用老的dtd来校验xml的格式。Spring的dtd服务的出现问题,也说明了其实对于dtd这种方式的校验,spring也已经不会保证几个9的稳定性。
       Xml常常会被作为数据承载中介,使双方能够在跨平台跨语言的情况下松耦合的交互信息,也是现在的SOA的实施基础。那么双方势必需要有协议和数据格式规范来约束,schema作为dtd的新一代替代者已经广为使用。另一方面,xsd也早已独立于wsdl作为数据描述和可重用的数据描述说明被采用到各种互联网应用。
       看看国外的Facebook,亚马逊,ebay等公司的REST风格的API,就可以清楚地了解到xsd十分适合作为轻量级的数据交互协议。在后续ASF中融入REST配置的实现中,也需要采用XSD这种Schema描述来实现数据交互解析。
       因此替换掉Dtd的配置是迟早要做的一件事情,所以迟作不如早作,更避免拷贝引起的问题放大效果(不过这个问题由于要考虑QA和业务组的项目经理的顾虑,因此我只能做到的是建议)。
 
方案3:
       看看Maven这些年这么火,其实在我们自己公司内部的antx同样都是在做一件事情,就是对于第三方的依赖包的版本控制。对于开源项目依赖的管理其实很重要,作的好项目能够很好的利用已有的成果,管理的不好就会被一些不太稳定的开源项目搞得头破血流。
       记得在上次三亚的聚会上谈到了对于Tuscany的依赖,其实对于这个项目来说,如果要作为成熟的产品来说,那么势必要获取一个版本然后就作为稳定的依赖,而不是一味的升级更新,由于我们产品的特殊性以及早期的Tuscany的不成熟,因此我们仅仅只是使用了Tuscany的最核心解析文件框架部分,其他的插件都采取自己设计或者在原有设计上优化和更新的做法。当然这不是说对于所有的第三方依赖都是采取这样的策略,其实如果不是基础框架设计,仅仅只是应用级别的使用,只需要拿来主义就完全可以了。
       回过头来看,大家现在对于类库的管理已经都很重视了,但是对于配置性的或者数据格式类的文件还没有引起足够的重视,不过看到很多朋友已经在本地作了这样的工作,不过就我来看,如果能够对dtd,schema作版本控制和服务器搭建,在长远来看还是有一定的好处的,只是说根据各自的需求来做这样的工作。
 
 
后续
       问题出现的当天,我其实晚上就一直比较担心,因为如果问题不解决,那么将会影响到很多项目组(框架被使用的越广泛,自己所要承担的责任越重大)。其实也是由于自己上次对于这个问题的不上心,导致了问题的再次出现。刨根问底是件好事,做我们这行的还是需要多问一些为什么,这样就会少不少危急时刻的怎么办了。
       很多时候为什么程序员自己喜欢什么都自己做,因为掌握在自己手中的事情总是能够解决,但是现在项目中对于第三方的依赖越来越多,在选择和控制上必须慎之又慎,有时候依赖也是双刃剑。
 
posted @ 2008-04-07 08:22 岑文初 阅读(1498) | 评论 (0)编辑 收藏

仅列出标题
共12页: First 上一页 4 5 6 7 8 9 10 11 12 下一页