Archive

Author Archive

[译]Redis的事务

December 15th, 2010 西坪 No comments

Redis相比于她的一些竞争对手如Memcached, Tokyo Tyrant等,有一个实在是太吸引人的特性,就是事务。尽管它的事务支持不那么完美,但还是很难得。打算介绍一点使用心得,本想先写个简单介绍,不过觉得官方的文档写得相当不错,我索性做个翻译好了。

声明:本文翻译自Redis官方的MULTI/EXEC命令文档。翻译本文旨在传播更多信息,原著版权为Redis所有。转载请包含此声明。

 

WATCH key1 key2 ... keyN (Redis >= 2.1.0)
UNWATCH
MULTI
COMMAND_1 ...
COMMAND_2 ...
COMMAND_N ...
EXEC or DISCARD

MULTI, EXEC, DISCARD 和 WATCH 命令是Redis事务的基石。一个Redis事务允许一组Redis命令单步执行,并提供下面两个重要保证:

  1. 一个事务中的所有命令串行执行。在事务的执行过程中不会为另一个客户端发起的请求提供服务。这保证了事务中的命令以原子操作的方式被执行。
  2. 要么全部命令要么没有任何命令被处理。Exec命令触发事务中的所有命令的执行,所以如果一个在事务上下文中的客户端在调用MULTI(译注:貌似应该是EXEC,原文为MULTI)之前丢失了到服务器的链接,没有任何操作会被执行,而如果EXEC命令被调用了,所有的操作都会执行。一个例外是当AOF开启的时候:每个属于某个Redis事务的命令只要操作完成了的话就会记录在AOF中,所以如果Redis服务器崩溃或者被系统管理员以一种粗暴的方式杀死,可能只有部分操作被登记在AOF中。

从Redis 2.1.0开始,以类似于CAS(比较并设置)操作的乐观地锁定一组key的形式,在上面两种保证之外提供更进一步的保证也是可行的。这将在后文中详细阐述。

用法

通过MULTI命令进入一个事务。MULTI总是返回OK。然后用户可以发起多个命令。Redis将这些命令排队,而不是执行它们。一旦EXEC被调用,所有的命令都被执行。

调用DISCARD则将清空队列并退出事务。

下面是一个使用Ruby客户端的例子:

?> r.multi
=> "OK"
>> r.incr "foo"
=> "QUEUED"
>> r.incr "bar"
=> "QUEUED"
>> r.incr "bar"
=> "QUEUED"
>> r.exec
=> [1, 1, 2]

正如从上面的会话能看出来的那样,MULTI返回一个回复”数组”,每个元素都是事务中的某一个命令的回复,以命令排队时的顺序排列。

当一个Redis连接处于MULTI请求的上下文中时,所有的命令将用一个简单的字符串“QUEUED”回复,如果它们的语法和参数个数都正确的话。某些命令甚至被允许可在执行期间失败。

在协议层面上这更易于理解;在下面的例子中一个命令将在执行时失败,尽管它的语法是正确的:

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
MULTI
+OK
SET a 3 
abc
+QUEUED
LPOP a
+QUEUED
EXEC
*2
+OK
-ERR Operation against a key holding the wrong kind of value

MULTI 返回一个两个元素的组合回复(bulk reply),其中一个是 +OK码,另一个是 -ERR回复。这中情况下将由客户端库来选择一种合理的方式将错误提供给用户。

重要提示:即使在一个命令将会抛出错误时,队列中的所有其他的命令仍将被处理。在发现错误时,Redis不会停止命令的处理。

这是另一个例子,仍旧使用telnet的写协议,展示了语法错误会被尽可能快地报告:

MULTI
+OK
INCR a b c
-ERR wrong number of arguments for 'incr' command

这次由于语法错误,有问题的INCR命令根本不会被排队。

DISCARD

DISCARD用于放弃一个事务。没有命令将被执行,客户端的状态重新变回正常,在事务之外。这是个使用Ruby客户端的例子:

?> r.set("foo",1)
=> true
>> r.multi
=> "OK"
>> r.incr("foo")
=> "QUEUED"
>> r.discard
=> "OK"
>> r.get("foo")
=> "1"

基于WATCH的比较并设置(CAS)事务

WATCH(观察)用于给Redis事务提供CAS(比较并操作)行为。

调用了WATCH命令(WATCHed)的键将被监视以侦测针对键的修改。如果至少有一个监视的键在EXEC调用之前被修改,整个事务将被放弃,EXEC调用将会返回一个空对象(nil object)(一个Null Multi Bulk回复)来通知客户端事务失败了。

例如想象我们需要给一个键的值原子增一(我知道有INCR,让我们假设我们没有它)。

首先我们可能会像这样尝试:

val = GET mykey
val = val + 1
SET mykey $val

这只在一个给定时间内只有一个客户端做这个操作时可靠地工作。如果多个客户端将在大约同一时间内递增这个键,将会出现竞争条件(race condition)。例如客户端A和B将读到旧值,例如10。这个值将被这两个客户端递增到11,并最终设置这个键的值。于是最后的值将是11,而不是12。

多亏了WATCH,我们能够很好地解决这个问题:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

使用上面的代码,如果存在竞争条件并且另一个客户端在我们调用WATCH和EXEC之间修改了val的结果,事务将会失败。

我们只需要重复这个操作,寄希望于这次没有新的竞争。这种锁定形式叫做乐观锁定,并且在面对许多有多个客户端同时访问数目庞大的键的问题时,是一种十分强大的锁定形式,这种情况下冲突的可能性是很小的:通常操作不需要执行很多次。

WATCH的解释

那WATCH到底是什么呢?它是一个将使得EXEC受到约束的命令:我们要求Redis只在没有别的客户端修改任何一个被监视的键时执行事务。否则,根本就不会开始事务。(注意如果你监视(WATCH)一个瞬时的键,然后Redis在你监视了这个键后,将这个键过期(expire)了,EXEC仍将继续生效。更多信息

WATCH可以被调用很多次。很简单地,所有的WATCH调用都将从调用时开始监视修改,直到EXEC被调用那一刻。

当EXEC被调用时,不管它是成功还是失败,所有的键都不再被监视。同样当一个客户连接被关闭时,所有的key不再被监视。

也可使用UNWATCH命令(没有参数)来清除被监视的键。有时这是有用的:因为可能我们需要执行一个事务来修改一些键,我们乐观锁定了这些键,但是在读取了这些键的当前内容后我们不想继续修改这些键了。这是我们只需要调用UNWATCH,于是这个连接就能够自由地用于新事务了。

WATCH用于实现ZPOP

一个很好的展示WATCH可被用于创建Redis不直接支持的新原子操作的例子是实现ZPOP,这是一个从有序集合(sorted set)中以原子方式弹出(pop)分数权值(score)较低的元素的命令。下面这是最简单的实现:

WATCH zset
ele = ZRANGE zset 0 0
MULTI
ZREM zset ele
EXEC

如果EXEC失败了(返回一个nil值),我们只需要重复上面的操作。

返回值

多组合(Multi bulk)回复, 特别地:

一个MULTI/EXEC命令的返回值是一个多组合回复(multi bulk reply),其中的每个元素是原子事务中的每个命令的返回值。

如果一个MULTI/EXEC事务因为监视命令(WATCH)检测到有键被修改了,返回一个空多组合回复(Null Multi Bulk reply)。

Categories: @Translation Tags: ,

Java中的Volatile关键字

December 13th, 2010 西坪 2 comments

前两天,并行实验室发表了一篇有关volatile关键字的文章[1],而该文参考文献中的Sayonara volatile[2]则更直接地要与volatile再见。两篇文章不谋而合,从c/c++到Java/.net,将这几门语言中的volatile过了一遍。列举出各种乱象,看上去volatile是如此不堪。

确实,volatile的语义不是那么常见。不是锁,但是它又拥有锁的部分特性(可见性,Java 5以后还有顺序保证)。如果不能正确理解,确实是个容易出错的地方。但是如果理解了用对了,仅就Java 5以及以后的版本而言,在目前阶段,volatile对性能上的提升还是有帮助的。

Volatile在Java中的语义以及历史

维基百科上,对Java中的Volatile给了一个准确的定义[6]

  • (In all versions of Java) There is a global ordering on the reads and writes to a volatile variable. This implies that every thread accessing a volatile field will read its current value before continuing, instead of (potentially) using a cached value. (However, there is no guarantee about the relative ordering of volatile reads and writes with regular reads and writes, meaning that it’s generally not a useful threading construct.)
  • (In Java 5 or later) Volatile reads and writes establish a happens-before relationship, much like acquiring and releasing a mutex.

翻译过来就是:

  • (在所有的Java版本中)对volatile变量上的读和写有一个全局排序。这暗示着每个线程访问一个volatile域时在往下运行之前将读到当前的值,而不是(有可能)使用一个缓存值。(然而,在volatie变量的读写与常规的读写之间的排序没有保证,意味着volatile通常不是一个有用的线程化的构造)
  • (在Java5及之后版本中) Volatile读和写建立了一种happens-before关系,就像获取和释放一个互斥体。

在Java中,要正确地并发,必须在原子性、可见性和顺序性三个方面做出保证。在Java 5以前的版本中,volatile就已经实现了可见性,但是因为不能保证与普通变量读写之间的顺序,故而经常是没有用的。在Java 5以后的版本中,则额外保证了其与普通变量读写的顺序性。这里不是特别好懂,我们将javamx上的例子[3] [4]改造一下:

public class MyBrokenFactory {
  private static volatile MyFactory instance;
  private int field1, field2 ...
 
  public static MyBrokenFactory getFactory() {
    // This is incorrect: don't do it at home, kids!
    if (instance == null) {
      synchronized (MyBrokenFactory.class) {
        if (instance == null)
          instance = new MyBrokenFactory();
      }
    }
    return instance;
  }
 
  private MyBrokenFactory() {
    field1 = ...
    field2 = ...
  }
}

上面的代码,在Java5以前,这个例子是不能正确工作的,因为volatile不保证对field1,field2的初始化(常规读写)一定在对volatile变量instance的写之前完成。但双检查锁本身并非volatile导致的,反而是Java 1.5的volatile让双检查锁能正确工作了。并行实验室将双检查锁不正确算在volatile的头上,有点冤了。

正是因为volatile在Java 5以前没有得到正确实现,更添加了不少复杂性。在Java 5之前,volatile基本没用处。

Volatile的实现机制分析

在Hotspot JVM中,
a) 在JVM层次,对volatile变量在线程本地工作区中不做缓存,对volatile的读写总是指向堆中的引用。可以视作在一个assign指令后总是跟着一个store指令[5]
b) 在机器码执行层次,通过内存屏障指令等迫使CPU不重排序,清除缓存,详细请参考《Memory Barriers and JVM Concurrency》的分析。

这与synchronized是有明显不同的,synchronized通常需要原子锁定,在SMP上要通过锁定总线等方式来实现,其代价在大多数平台上通常要比volatile高得多。

Performance

图一运行100万次
引入Volatile的主要目的,就是为了性能。我分下面几个方面来谈谈volatile在目前的Java平台中的作用。

直接性能比较

简单写了一个测试来比较一下volatile以及它的两种替代品 — 原子类型(atomic)和锁(此处指synchronized)。例子的代码改编自庄周梦蝶slides《Java NIO trick and trap》中的SystemTimer(部分代码省略,完整源码在Github上)。这个测试只是一个例子,但其实在网络库里都要做定时,原理也大抵如此。

第一个版本如下:

package me.xiping.volitileverify;
public class SystemTimerV1 {
	private final static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
	private static final long tickUnit = Long.parseLong(System.getProperty(
			"notify.systimer.tick", "50"));
	private static volatile long time = System.currentTimeMillis();
 
	private static class TimerTicker implements Runnable {
		public void run() {
			time = System.currentTimeMillis();
		}
	}
 
	public static long currentTimeMillis() {
		return time;
	}
 
	static {
		executor.scheduleAtFixedRate(new TimerTicker(), tickUnit, tickUnit,
				TimeUnit.MILLISECONDS);
		Runtime.getRuntime().addShutdownHook(new Thread() {
			@Override
			public void run() {
				executor.shutdown();
			}
		});
	}
}

代码的重点在于volatile修饰的time域。time是一个只有一个线程写,其他线程都是读的域,没有并发修改,这里要解决的就是可见性。这正是volatile最适合的场合。

第二个版本使用synchronize关键字,与版本V1的不同如下:

package me.xiping.volitileverify;
public class SystemTimerV2 {
	....
	private static long time = System.currentTimeMillis();
 
	private static class TimerTicker implements Runnable {
		public void run() {
			synchronized (SystemTimerV2.class) {
				time = System.currentTimeMillis();
			}
		}
	}
 
	public static synchronized long currentTimeMillis() {
		return time;
	}
	....
}

第二个版本使用Java的内部锁来同步,既保证了原子性,也保证了可见性。第三个版本则是使用AtomicLong来实现的,如下:

package me.xiping.volitileverify;
 
public class SystemTimerV3 {
	......
	private static AtomicLong time = new AtomicLong(System.currentTimeMillis());
 
	private static class TimerTicker implements Runnable {
		public void run() {
			time.set(System.currentTimeMillis());
		}
	}
 
	public static long currentTimeMillis() {
		return time.get();
	}
	.....
}

在我的本上(cpu: 2core; OS: win7/cygwin; java:1.6.0_13)运行100万次,其性能如上面右图一所示(单位:ms)。
图二运行1千万次
运行1000万次性能如图二所示(注意纵坐标的值与图一不一样)。

结论:性能上的优越性是显而易见的。volatile比AtomicLong明细要快,相比于synchronized则有几倍几十倍的提高了。
(欢迎在不同平台下测试并在评论中给一个反馈,反馈时请包括处理器信息(平台,core数),OS和Java版本。源代码在这里。编译和运行需要Ant,具体指南请参考这里。)

[Update, Dec15th: 图片是用Google Chart API 生成的,在源代码中包含着两个自动执行测试并生成图片URL的脚本。脚本需运行在Bash环境下,Windows上需在Cygwin下运行。]

Mina/netty对volatile的利用

写博文时顺便统计了下volatile在一些性能比较重要的程序中的情况,我顺手统计了手边有的mina和netty两个项目的核心代码,数据如下:

Project sources path synchronized occurrence volatile occurrence
netty src/main/java 150 177
mina src/main/java 216 55

其中,mina是2009-3-31日Subversion的trunk,如下:

$svn info
Path: .
URL: http://svn.apache.org/repos/asf/mina/trunk
Repository Root: http://svn.apache.org/repos/asf
Repository UUID: 13f79535-47bb-0310-9956-ffa450edef68
Revision: 761900
Node Kind: directory
Schedule: normal
Last Changed Author: jvermillard
Last Changed Rev: 760493
Last Changed Date: 2009-03-31 23:49:15 +0800 (Tue, 31 Mar 2009)

netty的版本信息是:

$git log
commit 1ffb1aea75c36def56b709bd0892b19df78d9249
Author: Trustin Lee <trustin@gmail.com>
Date:   Fri Nov 12 10:20:03 2010 +0900

从侧面证明了volatile其实是很重要的,在性能很重要的场合,应用还是比较多的。

结论

总而言之,相比与Atomic和synchronized,volatile在性能上还是有显著的优势。

不过,正如文章中开头讨论的那样,volatile的缺点也显而易见,需要开发者对volatile的应用需要对内存模型的理解,否则容易误解而造成错误。在并发程序中,其仅可用于JVM支持的几个基本类型(int,short,long,double,reference, etc.),而这几个基本类型皆有对应的完整并发特性的包装原子类型(java.util.concurrent.atomic.*),虽然其性能与volatile有些差距,但大部分情形下也可代替volatile(但用synchronized来代替volatile是很不划算的)。

  1. 为什么在多核多线程程序中要慎用volatile关键字?
  2. Sayonara volatile
  3. Double-checked Locking (DCL) and how to fix it
  4. Double-checked Locking (DCL) and how to fix it (ctd)
  5. JVM specification#Threads and Locks
  6. Volatile variable#Java
  7. http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
  8. Memory Barriers and JVM Concurrency
Categories: $Performance Tags: , ,

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: , , , ,

Google Apache modpagespeed 测试

November 9th, 2010 西坪 No comments

昨日尝新测试Google Pagespeed Mod。今天重新测试了一下,服务器为OpenVZ vps/amd64bit/512M。Web服务器为 Apache 2.2.11。机器的性能是比较差的,所有数据仅供参考。

从客户端看速度有不错提升

未启用之前在Google Chrome中打开 本页,页面各部分内容的大小如下图:

a2b

请求速度如下图:

a1b

开启mod_pagespeed以后,页面各部分内容的大小如下图:
a2

css减少约1K,js减少1.5K,图片没有太大变化。总体减少约2K。

请求速度如下图:

a1

页面加载时间变化明显,document加载减少了480ms,css减少了6ms(提升比为25%),图片减少了24ms(提升比为18%),js脚本减少了6ms(提升比为25%),整体加载时间减少了460ms(提升比为21%)。

从服务端看

用ab在本机测试服务器的性能,可能是因为机器资源有限,ab本身要消耗一些资源,而本机测试时传输阶段的性能提升可以不计,故整体提升作用不明显。另外,ab不会去获取外部连接的文件也会错过mod_pagespeed在css/js/jpg方面的许多优化工作。尽管如此,处理速度还是有一些提升。开启前,50%的请求需要609ms,开启后降到599ms;66%的请求需要641ms,开启后降到621ms。所有请求的处理时间的中位数从609ms降到599ms。

启用前数据如下:

blog@feihoo:~$ ab -n 1000 -c 3 blog.feihoo.com/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
 
Document Path:          /
Document Length:        114025 bytes
 
Concurrency Level:      3
Time taken for tests:   209.315 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      114394000 bytes
HTML transferred:       114025000 bytes
Requests per second:    4.78 [#/sec] (mean)
Time per request:       627.946 [ms] (mean)
Time per request:       209.315 [ms] (mean, across all concurrent requests)
Transfer rate:          533.71 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:   484  628 108.2    609    1763
Waiting:      224  288  57.5    284    1035
Total:        485  628 108.2    609    1763
 
Percentage of the requests served within a certain time (ms)
  50%    609
  66%    641
  75%    657
  80%    670
  90%    735
  95%    825
  98%    895
  99%    988
 100%   1763 (longest request)

启用后测试结果如下:

blog@feihoo:~$ ab -n 1000 -c 3 blog.feihoo.com/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
 
Server Software:        Apache/2.2.11
Document Path:          /
Document Length:        114356 bytes
 
Concurrency Level:      3
Time taken for tests:   209.686 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      114755000 bytes
HTML transferred:       114356000 bytes
Requests per second:    4.77 [#/sec] (mean)
Time per request:       629.057 [ms] (mean)
Time per request:       209.686 [ms] (mean, across all concurrent requests)
Transfer rate:          534.44 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       6
Processing:   511  628 216.0    599    3660
Waiting:      233  294 157.4    277    2984
Total:        511  628 216.0    599    3660
 
Percentage of the requests served within a certain time (ms)
  50%    599
  66%    621
  75%    635
  80%    643
  90%    664
  95%    694
  98%   1075
  99%   1641
 100%   3660 (longest request)
Categories: server & system Tags: ,

位图存储优化

October 2nd, 2010 西坪 No comments

位图作为一种简高效使用内存的数据结构,在很多场合都能够用最省的内存表达大量的数据。我对位图最早的印象来自于《编程珠玑》中用位图结构来存储电话号码。感叹其简单、方便。本质上,位图是一个存储单个位的数组,每一个位表示一个数组元素。例如如果我们需要标记100万用户的在线状态,则可以将每个用户对应到一个位,只需要100万个位(约0.125M内存)的位图就可以表示了。如下图所示,在线的用户标记为绿色。其存储则可表示为 {01010011 0011…}。

位图

但是对于某些比较稀疏的位图,其存储效率也会有一些问题。如果100万用户平均只有100个人在线,那另外的999,900个位就浪费掉了。例如在第1位有一个用户,第1,000,000位有个用户,需要1M个位来保存。既浪费了存储空间,也带来大量无用的运算。在前不久我们的应用中就有这种问题。未经压缩的位图保存到数据库中,结果大量的IO操作对数据库的性能造成较大影响。

因为稀疏的位图比较多,比较直接地就想去掉中间的这些空白位,于是就想这样表示:存储第一个位的值(0或1),接着将接下来的所有值相同的位的数目保存为一个整数,再保存接下来另的一类连续位的数目,依此类推。如:{0000 0000 1000 0000 0000 1100 0001 0000 …… } 保存为 0,7,1,11,2,5…。 如果是一百万个位,只有第一个和最后一个是1{10000 …. …. 1},则保存为{1,999998,1}只需要三个整数就可以了!(注:实际上,早有人完整地将这种方法实现了,参考D-Gap Compression

于是对比较稀疏的位图应用这种方法,由于大部分位图都是比较稀疏,90%以上的位图的体积最终缩小了10倍。在测试过程中,发现最终存储的是大量的小整数(小于1000)。这些小整数其实可以用更短的类型来表示,于是应用变长编码来处理。其原理是每一个字节的高位做标识,其余7位存储值。1标识从这个字节开始是一个新的整数,0表示是上一个字节的延续。则区间[1, 2^7 )中的整数占用一个字节,[2^7, 2^14)中的整数占用两个字节,依次类推。

最终,90%以上的位图体积压缩了30倍以上。

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: ,

15%技术,75%思维和性格?

July 13th, 2010 西坪 2 comments

有感于下面这句话(来自AgileChina论坛有关程序员职业规划的讨论(Jacky Huang))。

一个人的成功,15%来自他的专业技术,75%来自于他的思维和性格,10%来自于机遇。

虽然下面Jeff Xiong做了一些批驳,但是某些情况下我大致同意上面这句话。一方面上面所指的成功因素的比例跟时间分配上的比例完全是不可比的,另一方面,专业技术的发展与性格和思维根本就是分不开的。但其实另外那75%里的思维和性格,同样决定了技术上是否能有所成绩。每个人的目标和志向不同,性格和习惯也不同,不是一句话能概括得了的。(Updated at Nov 30th)

Categories: @生活 Tags:

[译] Orx教程:1. object

July 5th, 2010 西坪 No comments

本文译自 orx tutorials 对象(object)phpxer 译,九天雁翎 (博客)修订。最新版本见Orx官方中文Wiki

Object (对象)教程

总结

由于orx是数据驱动的,我们只需要两行代码创建一个viewport(视口)和一个object。它们的所有属性都定义在配置文件(01_Object.ini)中。

Viewport关联到一个按照配置文件中的信息隐含创建的camera(摄像头)。在配置文件里,你还可以设置它们的大小,坐标,对象的颜色,缩放,旋转,动画,物理属性等等。你甚至无需增加一行代码就可以让任何的配置获得随机值。

在后面的一个示例中我们将看到如何使用一行代码生成复杂的object体系甚至整个 scene(场景)(所有的背景对象和普通对象)。

现在,你可以尝试取消01_Object.ini中某些行的注释,自己尝试一下,然后再继续学习这个教程。完整的选项列表请查看CreationTemplate.ini。

详细说明

创建一个object是相当简单的。不过,我们首先需确保已经加载了定义了所有object(对象)的属性的配置文件。我们还要通过viewport/camera组合显示创建好的object(对象)。

不要慌张!所有这些都很容易。

在这篇教程中,我们将加载一个位于父目录中的配置文件。正如你可能想到的,在所有的可执行程序都根据其构建类别(mingw, msvs2005, msvs2008, 等)位于各自的子目录的情况下,我们不打算在每个地方重复同样的配置文件。1)

在我们的例子中,加载配置文件使用类似下面这行代码的方式实现:
orxConfig_Load(”../01_Object.ini”);

然后我们创建viewport(视口)。注意 camera的创建是按照为这个viewport预置的配置信息自动完成的。
orxViewport_CreateFromConfig(”Viewport”);

我们差不多完成了。现在我们只需要创建 object!
orxObject_CreateFromConfig(”Object”);

就这样了!object(对象)已经创建,并且由于在camera的视觉平截体(frustum)内,将会被显示出来。
现在,因为我们使用Orx默认的启动器,我们需要申明我们的插件入口点(这里是我们的Init函数)。这可以使用一个宏很容易地实现。

orxSTATUS Init(){...}
orxPLUGIN_DECLARE_ENTRY_POINT(Init);

因为orx是数据驱动的,我们不需要手动加载任何数据,例如一个sprite(精灵)。一切都由数据管理器为我们搞定,它会确保sprites不在内存中重复并在其不再使用时自动释放的
如果你查看配置文件,在[Object]这一节,你将看到你可以设定所有的对象属性,例如 graphic (sprite),锚点,颜色,透明度,物理属性,坐标,旋转,缩放,tiling(平铺)(重复),动画,视觉特效,等等。
不要担心,这一切都将在后面的教程中讲到。

现在我们拥有了一个object(对象),我们需要学习如何与之交互。这将我们带入第二个教程:clock.

资源

  • 源代码: 01_Object.c
  • 配置文件: 01_Object.ini

1) 不过,如果你的配置文件名字与可执行文件匹配并且在同一个文件夹下,它将被自动加载。

Categories: @Translation Tags:

[译] Orx教程:0.Basic

July 5th, 2010 西坪 No comments

本文译自 orx tutorials 首页(main)phpxer 译,九天雁翎 (博客)修订。最新版本见Orx官方Wiki中文教程

教程

本教程主要包含Orx的基础和高级教程。Orx 是一个开源、跨平台、轻量级、数据驱动的2D游戏引擎。

安装

这些教程演示了如何设置不同的编程环境(IDE)来运行orx1)

基础

本节将要介绍orx的基础知识。

你可以从这里下载Windows(mingw, msvs2005 & msvs2008). Linux 和 MacOS X下的可执行文件(包括项目文件,数据和源码)。

前九个基础教程(#1 – #9)使用默认的orx启动程序为基础(underlying layer),这样易于快速测试/制作原型2

它们被编译成运行时加载(在命令行3) 上 或配置文件中指定它们的名字)的动态连接库。

此外,下面的内容 4) 解释了哪些行为是由默认的orx.exe/orx 启动程序提供的。

这是一个基础的C教程。
由于我们在本教程中使用默认的可执行文件,下面的代码将以插件的方式加载和执行。

另外,一些基础设施主执行文件为我们处理。
首先,它会加载所有可用的插件和模块。如果你只 需要其中的一些,最好编写你自己的可执行文件而不是插件。这部分包含在后面的教程中。

主执行文件还处理下面这些键盘输入

* F11 是纵向对齐切换
* Escape 退出
* F12 截屏
* 退格键(Backspace) 重新载入全部配置文件

如果有 orxSYSTEM_EVENT_CLOSE事件发生,程序也会退出。

不过,如果使用 orx作为传统库构建你自己的可执行文件当然也是可以的(也很容易做到)。在教程 #10(使用C++编写) 和 教程 #11 (使用 C编写)。教程 #10 还演示了如何使用orx编写C++代码5)。 同样地,你可以用任何可与C连接的语言编写程序。

在将来的发布中将会为某些常见语言提供封装。如果你想编写这种封装库,为orx做贡献,请通过论坛联系我们。

当前提供的基础教程列表:

  1. [C] object
  2. [C] clock
  3. [C] frame
  4. [C] animation
  5. [C] viewport & camera
  6. [C] sound & music
  7. [C] fx
  8. [C] physics
  9. [C] scrolling
  10. [C++] stand alone & localization
  11. [C] spawner & shader

社区

下面这些教程由社区创建,它们是了解如何使用 orx的不错的资源,可以在这里寻找到对一些简单问题的解答。


1) 所有的IDE都是免费可以从英特网上下载的。
2) one line for the whole initialization, no main function to write, no loop to handle
用一行代码完成初始化,不需要写main函数, 没有循环要处理
3) 另提供了 .bat/.sh 脚本方便启动所有示例
4) 你将在在所有教程的源文件的开始处看到这些内容
5) Orx本身使用C编写

Categories: @Translation Tags: