Archive

Archive for the ‘$Programming’ Category

2011, 掌握一门新语言:Erlang

April 17th, 2011 西坪 1 comment

我们已经进入了多核时代。 做了好些年并发编程设计,觉得虽然主流语言如Java、C/C++都不同程度地通过线程的方式对多核提供了支持,但是要真正理解并发,使用自如,绝非易事。对软件开发人员才是瓶颈的软件工业来说,改革底层的编程模型,以简便地支持多核程序设计,是未来的方向。

为并发而生的 Erlang 已经存活了很多年,只是一直不温不火,阳春白雪。而Java平台上的Scala,在这两年迅速进入软件工业界的视野。尽管其Actor模型借鉴自Eralng,运行在JVM之上,不像Erlang那样纯粹、简单, 但得力于Java平台庞大的现有代码、庞大的程序员阵营和各企业组织的大规模现有投资,很多人都认为Scala会更加成功。即使热爱 Erlang 的 Yariv Sadan — 他在 Facebook 用 Erlang 开发 Facebook Chat ,也在其争论 Erlang 与 Scala 的博文 《Erlang vs. Scala》 的结尾写道:“ Regardless of these differences, though, I think that Scala has a good chance of being the more popular language of the two.” 这篇文章很精彩,后面的评论也很精彩,值得一读。 有趣的是,作者说有些人认为 Erlang来自外国 (作者应该是美国人,而Erlang来自瑞典) 是其未能成为主流语言的一个原因。(A few times, I’ve seen the following take on the relative mertis of Scala and Erlang: Erlang is great for concurrent programming and it has a great track record in its niche, but it’s unlikely to become mainstream because it’s foreign and it doesn’t have as many libraries as Java.)

去年跟风了解了 Golang,写过几个小程序。Golang的方式略有有些易于C/Java,但总的来说,还是非常像的,上手不难。尤其是喜欢C的人,要选择一门多核时代的语言去学习,Golang 是最合适的了。不过,我还没有弄透Golang对并发的支持为什么优于传统语言,是的,它的并发基于消息传递的,但到底是怎么实现的?又为何如此实现呢?

Erlang 给我的感受则完全不同,变量竟然是immutable的,= 竟然不是赋值符号,字符串是整形值,一个类型也没有 :) , 刘姥姥进了大观园,完全是一个惊奇的世界。不过除了惊讶,我便很快喜欢它immutable的变量,也对久仰大名的 List Comprehension 感到异常惊喜。List Comprehension 跟 Map/Reduce的区别还是很明显的。 Erlang 真的是太惊艳了,学习它是必须的。毋庸置疑,它肯定会促使我改变思考事情的方式。

对于Scala,扫描了下它的文档。不过我更关心的是 ActorCSP 。Scala 与 Erlang 的并发都源自 Actor模型,有Erlang就可以了解了。对于一个常跟Java打交道的程序员,Scala需要用了将来再现学就行。

我想要理解的是Erlang/Go与Java/C不同的并发模型,它们的内存模型有哪些异同。也许你会说减少了上下文切换,减少了CPU Cache丢失,减少系统内存开销,减少抢断带来的损失等等,但我想探寻的是它们具体是怎么做的呢? 多核的变革已经来临,或许改进应该从计算机体系的顺序模型开始,硬件结构与软件模型一起改进(看看 Comuunications Of ACM《Memory Models: A Case for Rethinking Parallel Languages and Hardware》)。 何况除了并发,Erlang 还有更多值得发掘的东西。

下面这是相关的几篇值得一读的好文:

———————————–
参考:

Categories: $Programming Tags: , , ,

解决Java序列化的版本冲突

March 19th, 2011 西坪 No comments

序列化机制有很多种,Java原生的序列化机制是最方便、简单、直接的序列化方法之一。尽管这种机制有很多缺点,但还是有不少项目使用。我们有一个项目,将部分对象序列化后存储在Memcached中,后来程序出了升级版本。不幸的是,序列化对象对应的类有了一些变化,新版本的程序不能反序列化之前的数据。在协助升级Memcached中的数据的过程中才发现这个问题,这时便需提前将Memcached中的数据针对新版本程序做转换。 Memcached的导出机制我已经在前面的博文中作过介绍。

什么时候Java序列化会发生冲突

Java的序列化,实际上序列化的是对象的内容,也即只包括域(含父类的域)。因此类中方法的变化不会对序列化造成影响,只有类的体系和属性会对序列化造成影响。主要的导致版本冲突的变化包括:

  • 删除域
  • 改变类继承体系,讲一个实例域修改为静态或将非瞬时(transient)域修改为瞬时域
  • 修改域类型
  • 修writeObject/readObject导致不兼容的修改
  • 以及其他更多….

详细请参见官方文档

我们的程序修改了一个属性的类型,导致新版本程序不能反序列化之前的数据。

使用Reflection解决版本冲突

最早我尝试使用动态类加载和反射(Reflection),将旧版本的classes加入到类路径中,使用旧版本的classes将数据反序列化,再通过Reflection将值设置到新版本class的实例上,再将新版本的实例反序列化回Memcached中。伪代码如下:

//Add old classes to classpath and 
import ***.stat.***.service.impl.AAAContext;
 //return: new Instance of AAAContext;
 //param: serialized data of old instance AAAContext
 private byte[] getNewInstance(byte[] serializedData) throws Exception {
 
   //get the old instance by deserializing data 
   AAAContext oldObje = deserialize(serializedData);
 
   //load new classes from external jar files
   URLClassLoader clazzLoader ;
   Class aaaContextClazz;
   URL jarfile = new URL("jar", "","file:E:\\workshop\\new-lib.jar!/");
   URL jarfile1 = new URL("jar", "","file:E:\\workshop\\dependencies.jar!/");
   clazzLoader = new URLClassLoader(new URL[]{ jarfile, jarfile1}, null);
   aaaContextClazz = clazzLoader.loadClass("***.stat.***.service.impl.AAAContext");
   Object oldins = aaaContextClazz.newInstance();
 
   //get setters on the new clazz, MUST find every setter method by hand coding
   Method m[] = aaaContextClazz.getDeclaredMethods();
 
   m[0].invoke(ins, oldObject.getProp1());
   m[1].invoke(ins, oldObject.getProp2());
   .... ....
 
   //return the serialized data of new instance
   return serializeObj(ins);
}

其中比较特殊的是 “clazzLoader = new URLClassLoader(new URL[]{ jarfile, jarfile1}, null);” 这一行代码,因为Java类加载体系的继承方式,必须指定new URLClassLoader()的第二个参数为null,去掉parent加载器。在不指定该参数时,会使用当前类加载器作为parent加载器,于是则会先从当前的类加载器体系中去寻找,只能得到类路径(classpath)下的同名类。

这种方法因为要序列化的字段太多,实现起来不易,且需要老版本的各依赖包,工程人员实施起来麻烦,故转而采取了下面的方法。

使用内置的序列化措施解决冲突

花了点时间研究Java的序列化机制后,发现有更好的方法支持序列化数据的版本变化。其中最简单的就是添加readObject/writeObject方法,自定义实现序列化,完成新旧版本间的转换。主要有下面三步要做。

第一步:自定义要更新的字段

Java序列化机制容许我们指定哪些字段要序列化(官方文档)。将类型变化了的属性的序列化类型指定为旧版本中的类型(Long.class,新类型为String.class),以兼容老版本的序列化信息。定义如下:

  //定义的序列化版本ID必须与新/旧的class一致
  private static final long serialVersionUID = 3495267477129405823L;
  private static final
  ObjectStreamField[] serialPersistentFields = {
	new ObjectStreamField("id", Long.class),
	new ObjectStreamField("aaa_BeginTime", Date.class),
	new ObjectStreamField("flag", Integer.class),
              //略去几十个
 
              //将类型变化了的属性的序列化类型为旧版本中的类型
              new ObjectStreamField("sess_parameter", Long.class)
 };
第二步:定义readObject方法

定义readObject方法,读取老版本的数据,并做新旧类型转换:

private void readObject(ObjectInputStream ois)
    throws ClassNotFoundException, IOException {
  // Read version one types
  ObjectInputStream.GetField fields =  ois.readFields();
 
  this.id = (Long)fields.get("id", null);
  this.aaa_BeginTime = (Date)fields.get("aaa_BeginTime", null);
  this.flag = (Integer)fields.get("flag", null);
  //略去几十个
 
  //按旧版本读取,在转化为新版本的类型
  Long old_param = (Long)fields.get("sess_parameter", null);
 
  this.sess_parameter = String.valueOf(old_param);
}
第三步,定义writeObject方法

定义writeObject方法,以新版本class的序列化格式得到新的序列化数据:

private void writeObject(ObjectOutputStream oos)
    throws IOException {
   PutField fields = oos.putFields();
  fields.put("id", this.id);
	fields.put("aaa_BeginTime", this.aaa_BeginTime);
	fields.put("RoamFlag", this.RoamFlag);
 
  //按新版本方式写入
  fields.put("sess_parameter", this.sess_parameter);
  oos.writeFields();
}

以这种方式,只需要新增一个类就完成了转化工作。 这个类不是新版本程序中的类,实际上只是用于中间转化操作的临时代码,它以旧版本class的序列化方式读取数据,再按新版本class的序列化方式输出数据。

教训

使用默认的Java序列化机制,会使得具体的类成为数据的schema,持久化的数据受限于类的实现细节。这带来了多方面的约束。所以,如果你用Java的原生序列化,起码应该制定一个合适的Schema,并清楚类的实现细节变化可能导致数据需要特别的迁移处理。我建议选择一种别的与类的实现细节无关的序列化方案。

参考资料

  1. Java Serialization Specification (1.5)
  2. Type Changes Affecting Serialization
  3. Java序列化算法透析: 国人写的一篇Java序列化算法的分析,解析很透彻
  4. The Java serialization algorithm revealed : 一篇英文的有关序列化机制的分析文章,与前一篇参考很接近。
  5. Implementing Serializable : 简明扼要的概述了序列化机制
  6. Advanced Serialization
  7. Discover the secrets of the Java Serialization API
  8. Dynamic Class Loading and Reloading in Java

Impdp/Expdp 之并行(Parallel)和压缩(Compression)

December 9th, 2010 西坪 No comments

需要从远程数据库上导数据过来,但数据量不小,传输起来估计要花不少时间。看了下手册,Oracle 11g的expdp有下面几个比较符合这种情况的特性:

  • Query: 使用Query过滤巨大表中要导出的数据。要注意的是指定query的语法,尤其是在shell下特殊字符的转义问题。
  • Compression: 压缩数据。在11g中,有个compression选项,可以选择压缩 {ALL | DATA_ONLY | METADATA_ONLY | NONE}。在Oracle 11g中,不能像exp一样使用pipe给gzip的方式压缩了,要用gzip的话必须手动gzip。
  • Parallel: 并行选项,在启用压缩时感觉效率有所提升。

下面是一个例子,使用 BLOCKS 方法的总估计: 2.159 GB,实际压缩后仅仅剩下 28M左右(因某些数据我通过Query限制了,压缩前实际大小可能在300M左右,且数据比较特殊)。 更具普遍性的压缩效果可参考这篇文章(需翻墙)。该文也比较了使用gzip与使用compression参数的压缩效果。

一个实际的例子,导出:

expdp dbaname/dbapwd DUMPFILE=expdumps%u.dmp DIRECTORY=DPUMP_DIR  TABLES='(T1,T2,T3)' 
\QUERY=T1:\"where mod\(ID,10\)=0\",T2:\"where mod\(ID,5\)=0\" PARALLEL=4 COMPRESSION=ALL

导入:

impdp dbaname/dbapwd DIRECTORY=dpump_dir1 DUMPFILE=expdumps%U.dmp

详细信息参考Oracle手册相关内容

Categories: $Programming Tags: ,

JDBC和Ibatis中的Date,Time,Timestamp处理

November 25th, 2010 西坪 No comments

在此前,遇到过使用Ibatis操作Oracle时时间精度丢失的问题,昨天又遇到JDBC操作MySQL时间字段的问题,从网上看到各种式样的解释这些问题的博文/帖子,但多是雾里看花,不得要领。

  1. 理解JDBC中的时间类型
  2. MySQL与JDBC之间的类型映射
  3. Oracle与JDBC之间的类型映射
  4. Ibatis是怎么处理日期时间类型的
  5. 注释
  6. 参考资料

理解JDBC中的时间类型

java.sql包中包括三个类,Date, Time, 和 Timestamp,分别用来表示日期(无时间信息,eg: YYYY-MM-DD),时间(只处理时间,无日期部分, eg: HH:MM:SS)和时间戳(精确到纳秒级别)。在 它们都继承自java.util.Date。java.sql.Date和java.sql.Time都存有一个时间域,该时间域是精确到秒级别的,但是,他们只处理日期或者时间部分。在MySQL Connector/J(v5.1.13)的实现中可以看到,PrepareStatement#setDate 时会将时间重新format成”yyyy-MM-dd”格式。

因为历史遗留以及各种数据库本身的不同,各种JDBC实现中留下了格式各样的花招, 某些特殊场景下不遵照此情况可能也能工作,但不推荐这样做。

你必须根据你的实际需要来选择不同的类型,通常应该选用精确度与相应的数据库字段类型相比相同或者更高的JDBC类型

除此以外,还可以使用java.uitl.Date类型来处理时间。java.util.Date类型是上面各个类型的父类型,JDBC的API大都可以使用。

除此以外,在JDK1.6之前版本的构造java.sql.{Date|Time|TimeStamp}对象时存在性能问题,尤其是在多线程环境下更严重。这个Bug在这里。其原因是java.util.Date的相关方法调用了TimeZone.getDefaultRef(),而此方法是同步方法注1

MySQL的JDBC类型映射

DATE java.sql.Date
DATETIME java.sql.Timestamp
TIMESTAMP[(M)] java.sql.Timestamp
TIME java.sql.Time
YEAR[(2|4)] If yearIsDateType configuration property is set to false, then
the returned object type is java.sql.Short. If set to true (the
default) then an object of type java.sql.Date (with the date
set to January 1st, at midnight).

MySQL的DATETIME、TIMESTAMP两种字段类型的显著区别在于TIMESTAMP的取值在’1970-01-01 00:00:01′ UTC 和 ‘2038-01-19 03:14:07′ UTC之间。

MySQL在时间处理方面也有一个问题,当Datetimes类型的字段的值为0000-00-00时取值方法会得到下面的异常:

Cannot convert value '0000-00-00 00:00:00' from column xx to TIMESTAMP

这个问题的原因在于,MySQL中默认使用0000-00-00等来表示时间的特殊值(参见文档)。而在Java中,并没有一个合适的方式来表示这个时间(因为Java中时间轴上0是1970-01-01 00:00:00),早于这个时间的用负数表示,这个最小的负数在时间轴上是表示不出来的。Connector/J提供了一个属性zeroDateTimeBehavior来解决此问题。

  • exception (the default), which throws an SQLException with an SQLState of S1009.(默认行为)
  • convertToNull, which returns NULL instead of the date.
  • round, which rounds the date to the nearest closest value which is 0001-01-01.

如下所示的jdbc连接将指定该行为转化为null值。

jdbc:mysql://localhost/myDatabase?zeroDateTimeBehavior=convertToNull

Oracle与JDBC之间的类型映射

DATE java.sql.Date
DATE java.sql.Time
TIMESTAMP java.sql.Timestamp

Oracle数据库字段类型主要有DATE、TIMESTAMP。

在9i以后、11g以前的Oracle JDBC驱动中存在一个会丢失DATE类型字段的时间信息的bug,原因是其JDBC驱动将Oracle的Date类型处理为java.sql.Date类型,这就丢失了时间部分(看来对java.sql包下三种时间类型认识不足的问题还很普遍的)。关于这个问题,这篇帖子给出了不错的解释(墙外),此文中的方法适用于11g JDBC以前的各版本驱动。在11g JDBC驱动中,此问题已经解决了,Oracle 11g JDBC驱动的手册中也给出了解释注2

事实上,如果是使用Ibatis,pojo属性的类型设置为java.util.Date,确保 jdbcType不为 DATE或者TIME,则避免了这个bug。因为此时Ibatis会以java.sql.Timestamp来处理该字段。我专门对此做了验证,点此看测试项目源码

Ibatis是怎么处理日期类型的

注意:本文皆基于ibatis 2.3.4.726源码分析。不过根据我初略观察,Ibatis3也适用,但请遇到问题时有所留意。

在此前工作中碰到Oracle中的Date字段会只剩下日期部分的数据,丢失了,Google发现一些人的解决方法是将JDBCType指定为datetime。有人甚至自己编写一个自定义的TypeHandler来解决这个问题。其实这完全是瞎猫撞上死耗子,那个datetime根本没意义,却歪打正着。一般的错误都是如下的配置(或者是pojo的属性为java.sql.Date类型):

<sqlMap namespace="Info" >
  <resultMap id="Info" class="pojo.Info" >
    <result column="INFO_BEGINTIME" property="begin" jdbcType="DATE" />
    <result column="INFO_ENDTIME" property="end" jdbcType="DATE" />
  </resultMap>

此时不论你pojo.Info中的字段类型(或者的javaType属性)是java.util.Date还是java.sql.Date,最终都会丢失数据。实际上,在pojo.Info中的字段类型(或者javaType属性)为java.util.Date时,如果指定jdbcType为DATE或TIME(区分大小写),则将分别得到DateOnlyTypeHandler或TimeOnlyTypeHandler。如果你不指定jdbcType,或者指定一个错误的值,例如datetime,或者xxxx等,都会得到DateTypeHandler(日期时间都会处理)。

如果pojo.Info中的属性类型(或者配置中的javaType属性)是java.sql.Date,则选择处理handler类时不会使用jdbcType,而是根据属性类型得到SqlDateTypeHandler(只包含日期)。

在Ibatis的手册中,指出来jdbcType一般情况下是不用于判断处理方式的注3。大部分情况下是依据javaType或者pojo属性类型来判断处理方式,在少部分无法根据pojo属性类型判断的情况下才使用。如果你用java.util.Date对应到MySQL,则就是这种少数情况之一。因为MySQL可以将多个数据库字段类型映射到java.util.Date,所以需要指定是DATE还是TIME(必须是大写),如果不指定则默认是完整的日期时间(此时按JDBC的timestamp操作)。

针对ibatis对时间的处理,我写了个测试,点此看测试代码。

对于Ibatis操作Date/Time/DateTime,总结如下:

  • 将pojo的属性类型设置为java.sql.Date(或java.sql.Time, java.sql.Timestamp),此时会严格遵循这三种类型的语义。但此方法因存在前文中提到的性能问题,在JDK1.6以前的JDK版本中能少使用就少使用。
  • 如果你想在pojo中使用java.util.Date, 则要注意:
    • 完整的日期时间,要确保jdbcType为空,或为DATE,TIME以外的值
    • 只需要时间,要指定jdbcType=”TIME”
    • 只需要日期,要指定jdbcType=”DATE”

注释

注释1:Use oracle.sql.DATE or oracle.sql.TIMESTAMP rather than
java.sql.Date or java.sql.Timestamp if you are using JDK 1.5 or earlier versions or require maximum performance. You may also use the oracle.sql data type if you want to read many date values or compute or display only a small percentage. Due to a bug in all versions of Java prior to JDK 1.6, construction of java.lang.Date and java.lang.Timestamp objects is slow, especially in multithreaded environments. This bug is fixed in JDK 1.6.

注释2:Mapping SQL DATE Data type to Java Oracle Database 8i and earlier versions did not support TIMESTAMP data, but Oracle DATE data used to have a time component as an extension to the SQL standard. So, Oracle Database 8i and earlier versions of JDBC drivers mapped oracle.sql.DATE to java.sql.Timestamp to preserve the time component. Starting with Oracle Database 9.0.1, TIMESTAMP support was included and 9i JDBC drivers started mapping oracle.sql.DATE to java.sql.Date. This mapping was incorrect as it truncated the time component of Oracle DATE data. To overcome this problem, Oracle Database 11.1 introduces a new flag mapDateToTimestamp. The default value of this flag is true, which means that by default the drivers will correctly map oracle.sql.DATE to java.sql.Timestamp, retaining the time information. If you still want the incorrect but 10g compatible oracle.sql.DATE to java.sql.Date mapping, then you can get it by setting the value of mapDateToTimestamp flag to false.

注释3:Ibatis 2的手册中给出的jdbcType属性的解释:属性jdbcType用于显式地指定给本属性(property)赋值的数据库字段的数据类型。对于某些特定的操作,如果不指定字段的数据类型,某些JDBC Driver无法识别字段的数据类型。一个很好的例子是PreparedStatement.setNull(int parameterIndex, int sqlType)方法,要求指定数据类型。如果不指定数据类型,某些Driver可能指定为Types.Other或Types.Null。但是,不能保证所有的Driver都表现一致。对于这种情况,SQL Map API允许使用parameterMap子元素parameter的jdbcType属性指定数据类型。
正常情况下,只有当字段可以为NULL时才需要jdbcType属性。另一需要指定jdbcType属性的情况是字段类型为日期时间类型的情况。因为Java只有一个Date类型(java.util.Date),而大多数SQL数据库有多个-通常至少有3种。因此,需要指定字段类型是DATE还是DATETIME。
属性jdbcType可以是JDBC Types类中定义的任意参数的字符串值。虽然如此,还是有某些类型不支持(即BLOB)。本节的稍后部分会说明架构支持的数据类型。
注意!大多数JDBC Driver只有在字段可以为NULL时需要指定jdbcType属性。因此,对于这些Driver,只是在字段可以为NULL时才需要指定type属性。
注意!当使用Oracle Driver时,如果没有给可以为NULL的字段指定jdbcType属性,当试图给这些字段赋值NULL时,会出现“Invalid column type”错误。

参考资料

Categories: $Programming Tags: , , , ,

Java平台上的XML处理梳理

September 30th, 2010 西坪 No comments

做了几年Java了,也经常用XML,但是鉴于Java平台对于XML处理相关的API和实现十分混乱,一直没有完全理清其关系。而又有如此多的名词跟XML沾亲带故,尤其是Java平台里名词满天飞。你应该见过这些名字吧:SAX,DOM,JAXP,JDOM,DOM4J, JAXB , JAXM , JAXR , XLST,XPATH, JAX-RPC, XML-RPC,XForms,你都知道吗?在维基百科XML页面上还有这么一句话:”As of 2009, hundreds of XML-based languages have been developed”(截止到2009年,有数百基于XML的语言被开发出来),可能也不只Java平台上XML处理这么复杂。

基础技术

XML

XML,全称可扩展标记语言(Extensible Markup Language (XML) )是在W3C制定的几个文档中定义的,用于将内容编码为机器可读的格式。

XML我们经常接触或者使用,其支持Unicode编码、严格完整的标记和错误处理等等,大家都知道。XML最早使用DTD来对XML的内容和格式进行定义和校验,后来W3C开发了新的模式语言XML Schema来取代DTD。于是,现在我们经常见到用来规范XML格式和内容的Xsd文档,DTD 已经逐渐淡出了。

XML的编程接口

XML作为一种数据格式,其设计目标中就包括了能够轻易地为各种编程语言提供接口。概括起来主要是下面这四个类型。

  1. 面向流的API,例如 SAX 和 StAX。
  2. 树遍历的API, 例如 DOM。
  3. XML数据绑定,支持在程序语言的对象与XML文档之间自动转换。(JAXB)
  4. 声明式转换语言,例如 XSLT 和 XQuery。

Sun公司给Java语言定义了一个将上面这数种编程接口(DOM/SAX/StAX)统一的API,JAXP(Java API for XML Processing),其目的是在Java平台上统一处理XML的接口。

Java平台中怎么处理XML

DOM和SAX

DOM的全称 Document Object Model(文档对象模型),是将XML在内存中组织为一个树形结构,并按照树形结构遍历和访问。不论是使用Dom4j,或者是Javascript里,经常看到Node/Element/Attribute这些东西,这就是使用DOM操作XML/XHTML等。DOM操作的最大缺点就是要将所有东西装到内存中,方便频繁查询、修改。一般的配置文件都比较小,使用DOM问题都不大。但是数据量很大的情况下,或者要处理的XML很多,那最好不用它。

SAX的全称是 Simple API for XML, 是基于流的方式来访问XML的。CSDN的这篇博文解释道:”SAX 解析器采用了基于事件的模型,它在解析 XML 文档的时候可以触发一系列的事件,当发现给定的tag的时候,它可以激活一个回调方法,告诉该方法制定的标签已经找到。SAX 对内存的要求通常会比较低,因为它让开发人员自己来决定所要处理的tag。特别是当开发人员只需要处理文档中所包含的部分数据时,SAX 这种扩展能力得到了更好的体现。但用 SAX 解析器的时候编码工作会比较困难,而且很难同时访问同一个文档中的多处不同数据。”(该博文有转字样,不知道其出处是哪,没搜到)。

对于SAX/DOM相关的论述, 上面提到的CSDN的博文关于SAX,DOM,JAXP,JDOM,DOM4J的一些理解 有比较精彩的论述。

有关DOM/SAX两种处理接口在处理大量XML数据时的性能,Tinyfool博文提供了一组数据为证。

Java平台这两种API的实现

在Java的类库中,Dom4j最早就是以DOM方式解析XML的。现在dom4j也支持其他方式解析XML,完整实现了DOM, SAX 和 JAXP。如果你使用Dom4j,在JDK5之前的JDK版本中则需要从JAXP的主页上下载相关的API包。

JDOM是另一个基于DOM模型处理XML的类库,但似乎已经不那么流行了。

Apache的xerces项目下有C++/Java/Perl三种语言的XML解析器。Apache Xerces2 Java 是Java语言的XML解析器。使用Apache xerces-J需要两个jar包,第一个是xml-apis.jar,其中包含了sax dom jaxp等接口的定义,另一个jar包是 xercesImpl.jar,包含各个接口的实现。以前的Xercers包中还包括一个xerces.jar,现在已经废弃了(参考这里)。

XML数据绑定

JAXB,全称是Java Architecture for XML Binding,是Java EE平台中处理XML与Java对象之间数据绑定的API。名如 jaxb1-impl.jar, jaxb1-impl-2.0.jar等的包,就是这个接口的相关产物。这个API也是JAXP的一部分,Xecers-j和dom4j都提供了实现。

Apache Xmlbeans 也提供了处理XML与Java对象间数据绑定的功能。

Java平台中的声明式转换语言

XSLT, 可扩展样式转换语言,常用于将信息内容与展现分离。在《企业应用架构模式》中将之作为表现层的一种模式做了介绍。使用XSLT,同一份内容,能够转换成HTML,文本文件,PDF,Postscript等等。处理XML的声明式转换语言还有从XML中查找节点的XPATH,用于从XML集合中查询数据的XQuery等。此前在Flex平台的项目中用过XPATH来处理界面,还是很方便的。

你可能看到过xalan.jar,有的项目里还必须有这个库。其实这是Apache的Xalan-Java的库,是一个XSLT处理库,实现了 JAXP中的javax.xml.transform接口。Xalan可将XML文档转换为HTML,文本文档或者其他XML格式。这个包依赖实现了SAX接口的相关包,所以必须与实现SAX接口的包一起使用。Xalan官方推荐的是Xercers(参考这里)。

另一个可以实现了XML的声明式转换相关接口的类库是Saxon项目的Java子项目。

结语

有关Java中这些XML处理方式,Apache这里有个FAQ可以参考一下,这个FAQ尝试回答为什么Java里有这么多XML处理的实现,接口和jar包。

总结这些东西好累,Java平台上处理XML实在是太累了。这大概也与XML被用得如此宽广有关。XML作为一种严格规范化的描述能力很强的数据格式,确实无处不在。虽然XML有时很臃肿,但基于XML的技术如XFormJava API for XML-Based RPC (JAX-RPC)XML-RPC等,很多很多。

(本文涉及内容较多较杂,我只是尝试做一个整理,如有谬误请不吝赐教)

Categories: $Programming Tags: ,

导出Memcached中的全部数据

April 5th, 2010 西坪 4 comments

Update(Mar 18th, 2011) 最近,我们针对数十个Memcached集群(数据从几G到几十G)进行了数据转换工作,虽是不得已而为之,但是整个过程还算顺利。编写导出脚本需注意的是cachedump得到的很多key其实已经过期了,这部分过期的key要通过get()操作,将之从Memcached中清除,使之不再出现在下次的cachedump中。
================================================================

因为特别的原因,需要从Memcached中导出所有的数据。因为Memcached对数据的遍历是明确不支持的,所以必须想歪门邪道的方法。

现在网络上主要有两种思路,一种是通过Memcached提供的stats cachedump命令导出,例如下面这些网址:

以上文章都利用这个命令导出了部分数据。但事实上,cachedump的输出会限制在 2*1024*1024=2M 字节的长度内(Memecahed source: items.c, func: do_item_cachedump),其导出的格式如下所示:

ITEM key990017 [1 b; 1270394155 s]
ITEM key990016 [1 b; 1270394155 s]
ITEM key990015 [1 b; 1270394155 s]
ITEM key990014 [1 b; 1270394155 s]

即使不计算key的长度,每一条数据至少要占掉近三十个字节,2M最多到几万项数据。对于Item数量很大的情况下,这种方法并不能导出全部数据。

下面的地址佐证了这些观点。

第二种思路来自 Twitter infrastructure team 的 Evan Weaverpeeping into memcached , 利用 linux 的 ptrace 系统调用导出memcached进程中数据,分析这些数据得到memcached的所有Key。

但是这种方式有其限制的,一方面,编译Memcached时必须编译入调试信息 (参见link); 另一方面,在分析时对内存消耗太大。我们对一个只使用了64M内存、含有76万左右数据项的Memcached进程进行分析,在完成23万左右数据项时,分析进程占用内存的达到1040M,导致虚拟机内存用满,关闭了该进程。

因为我们现在运行的memcached是不带编译符号的,所以放弃了第二种方式。 第一种方式,则只能考虑怎么将slabs中的数据分批导出来。 Memcached 支持一个可选的 slabs reassign 命令,可以将一个slab内的数据项移动到其他slab。但这个命令必须在编译 memcached 时加入该特性。

最后,还是利用了第一种方法,每一次导出一批数据以后就从memcached中删除这一批数据,直到全部导出为止。 虽然这种办法很土,但是还是能够应付一次性数据迁移。 当然这是不得以而为之,明智的做法还是在一开始就不要将需要持久化的数据持久化到其他服务中,而不是只存放于memcached中。

DBCP连接池(一):原理与基本配置

January 29th, 2010 西坪 1 comment

大约在半年前,曾经帮同事解决过一个Commons-dbcp连接池的问题。当时遇到的问题比较诡异,但是其实并不是什么特别复杂的问题,了解DBCP的原理,大部分问题就迎刃而解了。本文主要对连接池的基本原理以及dbcp的实现方式做一个分析,对dbcp的配置参数结合原理做一个简单解释。

连接池扼要

JDBC是一套通用的Java语言与多种数据库(文件)通讯的标准API。大部分针对数据库服务器(例如Oracle, MySQL等等)的JDBC实现都是基于TCP/IP连接的客户端-服务器端通讯方式。

当我们需要执行一个数据库操作时,有下面三步:

  1. 客户端与服务器之间建立一个数据库连接
  2. 执行某种数据库操作
  3. 断开连接

如果每次处理都要走上面的三步,则应用程序与数据库服务器都要将大量的时间和资源消耗在数据连接的断开与建立上。对于某些数据库系统,一个数据库连接就是一个进程,而且数据库连接通常要占用不少资源,如排序区/Join区等等。对于并发较大的系统,建立一次连接然后缓存起来连续使用,直到程序结束等情况下再释放连接,就能够将系统资源集中在对数据库操作的处理上,从而大大提高性能。通常情况下将数据连接的建立和断开委托给一种能够数据库连接池的组件或服务进行管理。而DBCP, C3p0, Proxool等都是常用的开源的连接池组件。

就好像A公司在郊外,他们公司附近没有出租车。如果A公司有人要出去办事,他必须打电话给出租公司订车,用完车后他还要付账报销。 这样每个人出去一趟都必须订车、退车和报销。员工的很多时间白白花费在这上面了。于是A公司跟出租车公司定了一个合同,出租车公司给了他们一个车队。要用车随时去楼下找车队就可以了,用完了也不必结帐,A公司统一跟出租车公司订车和结帐。这个车队就好比连接池,由公司(应用程序)来统一向出租车公司(数据库服务器)订车(建立连接)和退车(关闭连接)。

DBCP的配置参数以及背后的原理

Commons-dbcp连接池的配置参数比较多,也比较复杂,主要分为

  • Jdbc连接参数(username, password, url, driverClassName, connectionProperties )
  • 事务处理参数 (defaultAutoCommit, defaultReadOnly, defaultTransactionIsolation, defaultCatalog)
  • 连接池参数(详见下文)
  • 连接池中链接存活性测试参数(详见下文)
  • 预处理查询池化参数(poolPreparedStatements, maxOpenPreparedStatements)
  • 丢弃失效链接相关参数(详见下文)以及一个控制是否可以正常情况下处于访问连接池包装下的底层JDBC链接参数(accessToUnderlyingConnectionAllowed)

其中Jdbc链接参数、事务处理都跟连接池关系不大,另预处理查询池化参数本文不详细叙述。有关commons-dbcp的详细参数配置信息请参考官方文档

连接池的配置

再用车队来比喻,出租车公司每提供一辆车给A公司,A公司肯定要付出一定费用。这时候维持车队的大小就很重要了,在项目少用车少的时候,车队肯定要减少,不然很多空闲车辆也要付出成本;在项目多用车多的时候,肯定要扩大车队,不然车不够用,车队忙不过来。A公司可以简单地设地两个阀值来动态调节车队中空闲的车辆数目以满足动态需求,一个是最小空闲车辆数(最小空空闲连接数(minIdle),当剩余的空车数目小于该数目时,A公司就向出租车公司请求加入新车。 一个是最大空闲车辆数(maxIdle),当剩余的车大于该数目时,就将刚刚用用完的车还给出租车公司从而减少车队数量。

另外,这家出租车公司可能要为多个客户服务,要考虑能够提供给A公司的最大的车数量,不能超过某个数量(maxActive)。所以A公司想出租车申请新车时首先要看下当前正在用的车辆数目是否超过了这个最大数目,如果没有超过那就直接申请新车,否则可以让申请者(应用程序中执行请求的线程)等待 (maxWait<=0, 无限等待; maxWait>0 当等待时间超过 maxWait时,失败)。

在连接池中,这几个参数是十分重要的,官方的说明如下,是我们调节系统性能时需要认真考虑的值。


Parameter Default Description
initialSize 0 The initial number of connections that are created when the pool is started.

Since: 1.2
线程池启动时初始化的连接数
maxActive 8 The maximum number of active connections that can be allocated from this pool at the same time, or non-positive for no limit.
最大活动连接数,如果非正整数,则不做限制。
maxIdle 8 The maximum number of connections that can remain idle in the pool, without extra ones being released, or negative for no limit.
最大空闲连接数。
minIdle 0 The minimum number of connections that can remain idle in the pool, without extra ones being created, or zero to create none.
最小空闲连接数。
maxWait indefinitely The maximum number of milliseconds that the pool will wait (when there are no available connections) for a connection to be returned before throwing an exception, or -1 to wait indefinitely.
(在没有连接可用时)连接池等待一个数据连接可用时的以毫秒计的最大等待时间,超时以后抛出异常, -1 则将无限等待

实际上,Dbcp 依赖于 commons-pool 来存储连接对象。 BasicDataSource默认使用GenericObjectPool来管理连接对象。除了请求的线程会在请求和返回连接过程中影响池中连接实例外,另有一个跑着GenericObjectPool.Evictor类型 (implements Runnable) 的实例的线程,也会影响池中的数据库连接。

请继续阅读下一篇:《DBCP连接池(二):Commons-pool的设计》

文件锁与进程互斥

January 8th, 2010 西坪 3 comments

有的情况下需要在进程之间实现互斥,这对于普通Java程序来说,因为是运行在虚拟机里的原因,手段远不如原生程序方便,但是也有一些简单的方法可以做到。第一个就是使用Socket监听某个端口,第二种方法是使用文件锁定。

使用Socket实现进程互斥

因为Java服务器套接字都是基于系统的socket接口实现的,而socket接口会保证一个套接字端点在整个系统中是唯一的,所以只需要在应用启动时监听一个端口,就可以让系统不同时启动。虽然方法比较土,但是也能用。

try {
	ServerSocket ss = new ServerSocket(3030);
}catch (Exception e) {
	logger.error("Cannot start", e.printStackTrace());
	System.exit();
}

使用文件锁在进程间同步

另一个实现Java进程间互斥的方法,就是文件锁。不过与上面使用套接字不同,使用文件锁还可以实现更高级的互斥/协作。完全可以用文件锁来实现不同Java进程之间的同步,基本思路与在线程之间使用读写锁同步相似。

JDK本身直接支持文件锁,在FileChannel下有几个与文件锁有关的方法,可以打开一个FileLock对象。当调用FileLock#release()方法,或者Channel关闭,或者JVM退出时都会释放该锁。文件锁是JVM全局的,与Java内部的锁/线程等无关,是进程之间的竞争关系。如果你不想发生意外你的程序挂起,那就应该用FileChannel#trylock,而不是直接使用FileChannel#lock(),这样在不能加锁时可以休眠一段时间再等等,或者超时退出。

文件锁像读写锁一样,可以是加共享锁,也可以加独占锁,还可以给锁定限制范围。这是操作系统本身提供的功能,JDK在操作系统层面上做了封装。在Linux下,其实就是调用fcntl来设置相关的状态。如下面的代码,会给文件加独占锁,如果再起新的进程,进程就会打开文件失败退出。

     char* lockfile;
      int lockfd;
      struct flock lock;
      time_t t;   
 
      if ((lockfd = open(lockfile, O_RDWR)) == -1) {
          fprintf(stdout, "open file %s fail: %s \n", lockfile, strerror(errno));
          exit(1);
      }
 
      t = time(NULL);
 
      lock.l_type = F_WRLCK;
      lock.l_start = 0;
      lock.l_len = 0;
      lock.l_whence = SEEK_SET;
      if (fcntl(lockfd, F_SETLKW, &lock) == -1) {
          fprintf(stdout, "lock file %s fail: %s \n", lockfile, strerror(errno));
      }
      fprintf(stdout, "lock file %s success(%d): %ld \n", lockfile, (int)getpid(), time(NULL)-t);
      sleep(30);
Categories: $Programming Tags: , , ,

Arguments on Java & C++

December 23rd, 2009 西坪 No comments

The Hypertable Team claimed they choose C++ to be Hypertable implement language for performance issues. They gave two points about that, Hypertable is memory (malloc) intensive and and CPU intensive. They also say:

(Hadoop)There are some places where Java is sub-optimal. In particular, at scale, there will be considerable memory pressure in the Namenode of the DFS. Java is a poor choice for this type of memory hungry application. Another place where the use of Java is sub-optimal is the post-map sorting in preparation for the reduce phase. This is CPU-intensive and involves the type of CPU work that Java is not good at.

There are a bunch comments on the Wiki Page. Someone gave another views. A guy gave two links to support Java is not so bad.

Some interesting comparative articles.

http://www.ddj.com/cpp/184401976?pgno=1

http://www.idiom.com/~zilla/Computer/javaCbenchmark.html

Categories: $Programming Tags: ,

理解Java虚拟机的加法运算

October 16th, 2009 西坪 No comments

在CSDN博客诗剑书生的专栏,作者提到了short类型的+操作和++操作的区别,竟然没有一个正确答案。其实这个问题没那么复杂,关键是要了解Java计算的最小单位和理解类型的自动转型。
原问题是:

short tmp = 0;
为什么tmp = tmp +1;错误但 tmp ++;却正确.

理解这个问题,先要理解实现计算的基本原理。Java虚拟机是一种进程虚拟机,它负责解释和执行字节码,跟直接运行在硬件上运行汇编指令是有区别的。普通的机器语言可以直接操作寄存器完成计算,寄存器能够表示的最小单位是字节。而Java的计算是基于栈的,并规定其基本操作单位是一个字。字的实际大小是有虚拟机的设计者来决定的,虚拟机规范只是规定一个字长要能够放下一个 byte、short、 int、 char、float等类型。所以byte、short、char的运算都像 int 一样是在以字为单位来计算的,尽管它本身比字还要小。

此时我们不难理解,tmp+1会得到一个int。根据Java的类型语义,可以向上转型,不会自动向下转型。所以 tmp = tmp+1语法错误, 关键在 = 符号这里。那为什么 tmp++就是正确的呢? 因为++ 只有一个操作数,不涉及转型的问题, 不像 = 符号。简单比对下二者生成的字节码。

	short a = 2;
	a++;

生成的字节码如下,其中的第5行指令执行了自动转型,i2s指定是int类型的数据转换为short类型的数据。

  0:   iconst_2
  1:   istore_1
  2:   iload_1
  3:   iconst_1
  4:   iadd
  5:   i2s
  6:   istore_1
  7:   return

我们在看看 int c = a+b 生成的字节码:

	short a = 2;
	short b = 111;
	int c = a + b;

生成字节码如下:

   0:   iconst_2
   1:   istore_1
   2:   bipush  111 #压栈111
   4:   istore_2
   5:   iload_1  #加载a到栈
   6:   iload_2  #加载b到栈
   7:   iadd  
   8:   istore_3  #将计算结果存到局部变量c中
   9:   return

该文中提到的另一个错误:

short c = a + b

其实是一个原理,尽管a,b,c 都是short, 但是 a+b 实际上得到了是int, 于是不能自动转型为 short 给 c。

Categories: $Programming Tags: ,