Posts Tagged ‘java’

RPC, Serialization and Schema

06月 12, 2010

The post is brought to you by lekhonee v0.7

糖果项目的后端用Java编写,我负责service gateway的开发(暂且叫sergent),服务以Java接口+Annotation的形式声明,与Spring集成使用,Java对象被序列化为JSON和XML(通过jackson和castor)与外部系统交互。专门的JSON Schema和XML Schema是可选的,系统交互通过简明的文档和人工确认。

RPC框架是跨进程、跨系统交互的重要工具,RPC框架中又包括远程调用、网络传输和序列化反序列化等等部分。流行的工具包括Facebook的thrift,Google的Protobuf和原先Hadoop项目下的avro。其中thrift包含远程调用、反序列化、网络等等全部的功能。Protobuf本身是一个序列化反序列化库,另有很多第三方RPC实现,avro目前除了序列化和反序列化的功能,也包含了ipc的HTTP Server和SocketServer等实现。在序列化的格式方面,Thrift支持JSON和二进制协议,Protobuf本身仅有二进制支持,但已经存在第三方的其他格式实现。 avro原生支持二进制和JSON格式。

从效率上来说,二进制方式的序列化要比文本方式的快。Google Code上(最近迁往了github)有一个tpc项目(thrift-protobuf-compare),根据这个项目的最新的比较结果(与原先不同):

protobuf成为了三者中耗时最少的框架,之后是thrift和avro,这次avro的耗时甚至超过了文本方式的jackson(主要在反序列化上)。

但是二进制协议通常都需要定义Schema,thrift / protobuf / avro三者各自定义了Schema的格式,没有类似XSD和Json Schema的统一标准,也就是说,当你需要传输一个对象,就要为它编写一个Schema文件。按照通常的习惯,都是先编写Schema,然后通过命令行工具或者自动构建工具来生成Java source。对于新系统还好说,对旧系统这个改造就比较麻烦了。另外,二进制协议不便于调试,所以各个thrift/protobuf/avro先后也都有JSON的实现,在文本的序列化格式上,JSON对XML的优势是全方位的。

所以综合起来,很难说有一种完美的解决方案。二进制协议的效率高,但是改造、编写Schema的代价并不小,还要面对核心Model被绑架到具体框架的风险。文本协议开发简便,不需要Schema,直接POJO就可以序列化和反序列化,但是在时间和空间上都不如二进制的方式。

补充
从tpc项目的结果上看,kryo在时间、空间上都击败了所有对手,而且,kryo的API非常简洁,不需要Schema文件就可以序列化POJO,听起来太完美了,看来以后sergent要借鉴一下的。

补充 2010-06-14
发现avro现在也有ReflectDatumReader和ReflectDatumWriter,可以通过反射内部自动映射生成Schema,可以尝试一下。

ibatis infinite loop when getFirstResultSet

06月 8, 2010

前几天上线后老大发现几台负载非常高,dump线程状态后发现多个线程死循环在同一处,于是发现了ibatis的这个bug:

https://issues.apache.org/jira/browse/IBATIS-384

https://issues.apache.org/jira/browse/IBATIS-587

在mysql数据库上,没有结果集时,stmt.getUpdateCount()会返回0,而非-1.
ibatis 2.4.3
[cc lang="java"]
private ResultSet getFirstResultSet(StatementScope scope, Statement stmt) throws SQLException {
ResultSet rs = null;
boolean hasMoreResults = true;
while (hasMoreResults) {
rs = stmt.getResultSet();
if (rs != null) {
break;
}
hasMoreResults = moveToNextResultsIfPresent(scope, stmt);
}
return rs;
}

private boolean moveToNextResultsIfPresent(StatementScope scope, Statement stmt) throws SQLException {
boolean moreResults;
// This is the messed up JDBC approach for determining if there are more results
moreResults = !(((moveToNextResultsSafely(scope, stmt) == false) && (stmt.getUpdateCount() == -1)));
return moreResults;
}

private boolean moveToNextResultsSafely(StatementScope scope, Statement stmt) throws SQLException {
if (forceMultipleResultSetSupport(scope) || stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
return stmt.getMoreResults();
}
return false;
}

[/cc]
moreResults恒为真,程序出现死循环。

在mybatis 2.5的代码里,这部分已经修改为:
[cc lang="java"]
private boolean moveToNextResultsIfPresent(StatementScope scope, Statement stmt) throws SQLException {
boolean moreResults;
// This is the messed up JDBC approach for determining if there are more results
boolean movedToNextResultsSafely = moveToNextResultsSafely(scope, stmt);
int updateCount = stmt.getUpdateCount();

moreResults = !(!movedToNextResultsSafely && (updateCount == -1));

//ibatis-384: workaround for mysql not returning -1 for stmt.getUpdateCount()
if (moreResults == true){
moreResults = !(!movedToNextResultsSafely && !isMultipleResultSetSupportPresent(scope, stmt));
}

return moreResults;
}

private boolean moveToNextResultsSafely(StatementScope scope, Statement stmt) throws SQLException {
if (isMultipleResultSetSupportPresent(scope, stmt)) {
return stmt.getMoreResults();
}
return false;
}

/**
* checks whether multiple result set support is present – either by direct support of the database driver or by forcing it
*/
private boolean isMultipleResultSetSupportPresent(StatementScope scope,
Statement stmt) throws SQLException {
return forceMultipleResultSetSupport(scope) || stmt.getConnection().getMetaData().supportsMultipleResultSets();
}
[/cc]
这部分条件判断实在很极致了。

新的实现当getUpdateResult是0时,moreResults恒为真,这时再进行一个判断,如果是由于isMultipleResultSetSupportPresent为false导致了moveToNextResultsSafely为false,那么实际moreResults应是false

MyBatis 2.5还有一个issue没有解决,离发布还有一些时间,这个问题只好签出新版本代码自己build了

Maven recipe #1: Test Java code with groovy

05月 19, 2010

问题: 受够了,不想写Java了,写个含有数据的map还要new出来一个一个put进去,想用groovy解决单元测试
解决: gmaven+groovy eclipse插件可以解决这个需求
在pom.xml中添加gmaven的依赖,注意,仅用来测试。老大不让生产代码里有不可靠的东西。
[cc lang="xml"]

org.codehaus.groovy.maven.runtime
gmaven-runtime-default
1.0-rc-3
test

org.codehaus.groovy.maven
gmaven-plugin
1.0-rc-3

generateStubs
compile
generateTestStubs
testCompile

[/cc]

在eclipse中新建source folder, src/test/groovy,在其中创建groovy test case即可

Maven recipe #0

05月 16, 2010

问题: 多个root pom的dependencyManagement有重复的内容,希望统一管理。
解决:
新建一个空pom.xml,在dependencyManagement中指定这些依赖,如
[cc lang="xml"]

4.0.0
info.sunng
root
pom
0.0.1-SNAPSHOT

info.sunng
X
0.0.1-SNAPSHOT

[/cc]

在其他root pom中添加这样的dependencyManagement
[cc lang="xml"]
….

info.sunng
root
0.0.1-SNAPSHOT
pom
import

….
[/cc]

My First Hello World Web App Using Compojure

04月 24, 2010

Compojure是一个用Clojure写成的类似Sinatra的Web框架。Leiningen是一个新的Clojure构建工具,它用Maven来处理依赖管理,而通过封装Lancet(基于Ant)来实现build-in的task。

以上是背景介绍。以下是HelloWorld。

创建一个目录作为工程目录
[cc lang="bash"]
mkdir compojure-app
cd compojure-app
[/cc]

像创建build.xml和pom.xml一样创建project.clj
[cc lang="lisp"]
(defproject info.sunng/compojure-app “0.0.1″
:description “A demo app running on compojure framework”
:dependencies [[org.clojure/clojure "1.1.0"]
[org.clojure/clojure-contrib "1.1.0"]
[compojure "0.3.2"]]
:dev-dependencies [[leiningen/lein-swank "1.1.0"]]
:main info.sunng.compojureapp.helloworld)
[/cc]
首行定义了项目的groupId, artifactId和version,其后的是maven风格的依赖定义,最后我们还定义了程序的主类。

这个helloworld只有一个文件,被放在src/info/sunng/compojureapp/目录下
[cc lang="lisp"]
(ns info.sunng.compojureapp.helloworld (:gen-class) (:use compojure))

(defroutes example-routes
(GET “/” “Hello W0rld”)
(ANY “*” [404 "Page Not Found"]))

(defn -main []
(run-server {:port 8080} “/*” (servlet example-routes)))
[/cc]
中间就是Sinatra风格的URL映射定义,最后在主类中通过一个run-server方法以嵌入式的方式运行一个jetty

回到工程目录,执行构建
[cc lang="bash"]
lein deps
lein compile
lein uberjar
[/cc]

通过uberjar可以将依赖通通打入一个jar包中,接着就可以通过
java -jar compojure-app-standalone.jar
启动你的Web程序了,很酷吧

除了这种方式,还可以通过
lein swank
启动一个swank server(project.clj中定义了dev-dependency),再用Emacs的SLIME交互式地运行程序。

参考

We are using Hudson for continuous integration

04月 6, 2010

经历了持续两周的人肉集成,今天上午抢得一台Linux机器,终于尝试用hudson来替代人肉构建。

Hudson的安装和配置远比想象的简单,只要下载发布的war包,在相应的目录执行
nohup java -jar hudson.war > hudson.log 2>&1 &
即可启动到后台

hudson的web图形界面可以胜任几乎全部工作。我们主要使用maven来构建项目,hudson提供了非常强大的功能:邮件提醒(通过插件支持twitter/jabber/irc提醒);自动构建,除了基本的定时构建以外,hudson还会自动解析其管理的项目之间的依赖关系,从而实现级联的构建,这个功能非常震撼。

刚刚上手以后我还安装了两个插件。一个是build-timeout插件,可以之间一次构建的超时时间:我们的项目中有老大写的交互式的maven配置插件,一旦这个插件在自动构建时运行会阻塞构建的流程。另一个是scp发布插件,可以自动scp一个文件到远程服务器上,我用这个插件来把构建版本发布到运行环境中,只要在适当的时候重启一下运行环境的服务器就可以实现部署了。不过scp插件由于上游依赖的问题貌似不支持putty生成的privatekey,这是暂时的美中不足。

配置了整整一天,终于有了CI工具,我就彻底解放出来可以做其他事了。

Update

04月 2, 2010

太忙了,真的太忙了,每天重复无数遍 mvn clean install -Dmaven.test.skip, 一不小心再遇上”cidaemon.exe”锁死文件无法删除,或者SVN报点Tree conflict,无法见证奇迹就只剩下悲剧了。都是重复的工作,但是在Windows下想做automation还是一筹莫展,比如要构建多个maven项目,也不知道怎么自动化地判断maven构建成功还是失败;再者在Windows里也不知道怎么自动化的scp文件到服务器上去,还要ssh执行重启服务的命令;手足无措啊。

最严重的问题是每天忙的要命,但是并没有感觉有应有的收获或者长进什么的,折腾了半天做的事情还是自己熟悉的小方面,只是做得相对变态而已。每天新的情况让你根本来不及重构,而且严重的问题是,重构好了又有什么用呢,谁能想到后面又有什么变化把你自己引以为豪的设计通通推翻。程序员要考虑的事情太多了,常常要揣摩一些需求方的心思,偷偷摸摸地提前做一些准备。

我现在已经有了一个annotation driven的自动暴露Java接口的框架。之前在CRL实习时,在Jazz上开发也是用一个类似的机制,当时是使用接口的命名规范来确定暴露,比如说有以get和post开头的接口都暴露为HTTP服务,就有很多类似postUpdate、postAdd之类的名字,昨天同事说这个名字太诡异。后来参考了淘宝Open平台的实现,通通的XML描述接口,服务名、方法名、参数名、约束通通写XML配置,我懒,也放弃这个方案了。最后选择用annotation,通过反射解析Java接口,实现的效果和前两者差不多,自认为相对名称的约定要灵活一些,相对无数XML描述符要简洁一些。

后来还为方法的每一步调用都加上监听器,每一个环节都可以注册外部自定义的监听器,所有的核心功能之外的需求都放到自定义的监听器里处理,和框架核心剥离。

再者,框架原本直接序列化接口返回的对象,后来需求有变,要加各种各样的包装。OK,弄一个WrapperFacotry的接口出去,想怎么搞也放到框架核心之外了。Spring的@Authwire(required=false)真是解决了许多问题,在实现InitializingBean初始化一个默认实现就可以了,这样在applicationContext里可以轻松控制外部功能。

还有的发现,序列化的框架分别用的是castor和jackson,结果做profile的时候一比吓一跳,jackson序列话的时间占整个调用的20%,castor的就占到50%了,太可怕了,恐怕要试试其他实现了。

细节扯多了,其实框架的核心都是一天写成的,后面加各种各样的功能花了很大精力。

最近升级到UBuntu10.04已经可以见证奇迹了,眼看着Bug越来越少,之前启动打在tty1上的ureadahead错误日志现在也隐藏了。一切趋于完美,新indicator菜单很好用。

清明回家,下午的火车。因为回家周一不能加班了,少了3倍的工资,悲凉啊。

New features in JTS 1.11

03月 3, 2010

JTS最近发布了1.11版本,新增了:

计算Delaunay三角网和Voronoi多边形:
[cc lang="java"]
import java.util.ArrayList;
import java.util.Collection;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.triangulate.DelaunayTriangulationBuilder;
import com.vividsolutions.jts.triangulate.VoronoiDiagramBuilder;

/**
*
* @author Sun Ning/SNDA
* @since 2010-3-3
*/
public class DelaunayAndVoronoiApp {

/**
* create some predefined sites
* @return
*/
public static Collection getPredefinedSites(){
double[][] coords = {{100,27},{28, 50},{29, 40},{32, 90}, {12, 26}};
ArrayList coordinates = new ArrayList(coords.length);

for(int i=0; i<coords.length; i++){
coordinates.add(new Coordinate(coords[i][0], coords[i][1]));
}

return coordinates;
}

/**
*
* @param coords
* @return a geometry collection of triangulations
*/
public static Geometry buildDelaunayTriangulation(Collection coords){
DelaunayTriangulationBuilder builder = new DelaunayTriangulationBuilder();
builder.setSites(coords);
return builder.getTriangles(new GeometryFactory());
}
/**
*
* @param coords
* @return a collection of polygons
*/
public static Geometry buildVoronoiDiagram(Collection coords){
VoronoiDiagramBuilder builder = new VoronoiDiagramBuilder();
builder.setSites(coords);
return builder.getDiagram(new GeometryFactory());
}

/**
*
* @param args
*/
public static void main(String[] args){
Collection coordinates = getPredefinedSites();

/**
* Delauny
*/
GeometryCollection triangulations
= (GeometryCollection)buildDelaunayTriangulation(coordinates);

int total = triangulations.getNumGeometries();
System.out.printf(“Total triangulations: %d\n”, total);
for(int i=0; i<total; i++){
Geometry g = triangulations.getGeometryN(i);
Coordinate[] coords = g.getCoordinates();
System.out.printf("Triangulation %d: ", i);
for(Coordinate c : coords){
System.out.printf("(%.3f, %.3f) ", c.x, c.y);
}
System.out.println();
}

/**
* Voronoi
*/
GeometryCollection diagram = (GeometryCollection)
buildVoronoiDiagram(coordinates);
int totalDia = diagram.getNumGeometries();
for(int i=0; i<totalDia; i++){
Geometry g = diagram.getGeometryN(i);
Coordinate[] coords = g.getCoordinates();
System.out.printf("Diagram %d: ", i);
for(Coordinate c : coords){
System.out.printf("(%.3f, %.3f) ", c.x, c.y);
}
System.out.println();
}
}

}

[/cc]
输出:
[cc lang="text"]
Total triangulations: 5
Triangulation 0: (32.000, 90.000) (12.000, 26.000) (28.000, 50.000) (32.000, 90.000)
Triangulation 1: (32.000, 90.000) (28.000, 50.000) (100.000, 27.000) (32.000, 90.000)
Triangulation 2: (100.000, 27.000) (28.000, 50.000) (29.000, 40.000) (100.000, 27.000)
Triangulation 3: (100.000, 27.000) (29.000, 40.000) (12.000, 26.000) (100.000, 27.000)
Triangulation 4: (12.000, 26.000) (29.000, 40.000) (28.000, 50.000) (12.000, 26.000)
Diagram 0: (-76.000, 88.625) (-76.000, 178.000) (176.713, 178.000) (72.699, 65.730) (-38.235, 76.824) (-76.000, 88.625)
Diagram 1: (-76.000, -62.000) (-76.000, 88.625) (-38.235, 76.824) (11.978, 43.348) (56.422, -10.619) (57.006, -62.000) (-76.000, -62.000)
Diagram 2: (11.978, 43.348) (-38.235, 76.824) (72.699, 65.730) (67.316, 48.882) (11.978, 43.348)
Diagram 3: (176.713, 178.000) (188.000, 178.000) (188.000, -62.000) (57.006, -62.000) (56.422, -10.619) (67.316, 48.882) (72.699, 65.730) (176.713, 178.000)
Diagram 4: (11.978, 43.348) (67.316, 48.882) (56.422, -10.619) (11.978, 43.348)
[/cc]

将Geometry对象转换成Shape对象,绘制在JPanel上:
[cc lang="java"]
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.util.Collection;
import javax.swing.JFrame;
import javax.swing.JPanel;

import com.vividsolutions.jts.awt.ShapeWriter;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import java.util.Random;

/**
*
* @author Sun Ning/SNDA
* @since 2010-3-3
*/
public class JTS2Awt {

public static void showUI(final Shape… shape){
JFrame jframe = new JFrame("JTS Geometry to AWT Shape");

JPanel jp = new JPanel(){
@Override
public void paint(Graphics g){
super.paint(g);
Graphics2D g2d = (Graphics2D)g;
if(shape != null){
for(Shape s: shape){
g2d.setColor(new Color(Color.HSBtoRGB(new Random().nextFloat(), 1f, 0.6f)));
g2d.draw(s);
}
}
}
};
jp.setPreferredSize(new Dimension(150, 150));

jframe.getContentPane().add(jp);
jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jframe.pack();
jframe.setVisible(true);
}

public static Shape toShape(Geometry geom){
ShapeWriter writer = new ShapeWriter();
return writer.toShape(geom);
}

public static void main(String[] args){
Collection coords = DelaunayAndVoronoiApp.getPredefinedSites();
Geometry geomT = DelaunayAndVoronoiApp.buildDelaunayTriangulation(coords);
Geometry geomD = DelaunayAndVoronoiApp.buildVoronoiDiagram(coords);

showUI(toShape(geomT), toShape(geomD));
}

}

[/cc]
jts

Suppressing simple-xml's class attribute

02月 8, 2010

Simple-xml is an object-xml serialization and de-serialization framework. It’s featured by annotation-driven, light-weight and self-contained.

In simple-xml, pojos that will be serialized are annotated with @Root, @Element, @Attribute or @Text. The problem is, when handling with inheritance, if the actual type is different from declared type, simple-xml will add a “class” attribute for de-serialization consideration. That is, the parser will use this attribute to determine the schema of the xml element. However, this is of no useful when we do not parse xml by simple-xml. And there is also risk that the class attribute exposes our program’s internal structure.

Take following code as example:

@Root
public class Response{
    @Element
    private Object entry;

    // getter and setter ...
}

public class OrderItem{
    @Element
    private String name;

    // getter and setter ...
}

When you generate xml from classes above, you will have the entry element of response with an attribute “class”, which value is the qualified class name, like “package.OrderItem”.

This is done by simple-xml’s Strategy interface. By default, the Persister uses TreeStrategy which has an implementation of setElement like:

/**
 * This is used to attach a attribute to the provided element
 * that is used to identify the class. The attribute name is
 * "class" and has the value of the fully qualified class
 * name for the object provided. This will only be invoked
 * if the object class is different from the field class.
 *
 * @param type this is the declared class for the field used
 * @param value this is the instance variable being serialized
 * @param node this is the element used to represent the value
 * @param map this is used to maintain contextual information
 *
 * @return this returns true if serialization is complete
 */
public boolean setElement(Type type, Object value, NodeMap node, Map map){
    Class actual = value.getClass();
    Class expect = type.getType();
    Class real = actual;
    if(actual.isArray()) {
        real = setArray(expect, value, node);
    }
    if(actual != expect) {
        node.put(label, real.getName());
    }
    return false;
}

This is where class attributed. So to suppress the attribute, we simply override this method by inherit TreeStrategy. I take a inline class for convenience here.

Serializer s = new Persister(new TreeStrategy(){
    @Override
    public boolean setElement(Type type, Object value, NodeMap node, Map map){
        return false;
    }
});

Now it works, however, simple-xml won’t be able to deserialize xml generated by this modified strategy.

New composite based captcha image

01月 27, 2010

recaptcha的验证码新增了alpha composite的新机制取代干扰线,今天用了一些时间在YAN上也实现了这种绘图机制。

30a2512d899641a8ab79a7c86946ff71
f03ec596b91b4ce985c5b5af4a79e961

使用Java2D的AlphaComposite实现,选用的Rule为alpha 1.0的SrcOut,即通过公式

Ar = As * (1 – Ad )
Cr = Cs * (1 – Ad )
用语言描述就是叠加区域的透明度为0. 使用这种机制必须采用BufferedImage.TYPE_INT_ARGB的图像,并且输出支持alpha通道的格式。


加关注

Get every new post delivered to your Inbox.