2008年10月27日星期一

Mashups

非常浅显易懂而且有趣的video
Restful Web Service, SOA, Mashups确实是大势所趋

2008年10月14日星期二

SOAP中 RPC/encoded, RPC/literal, document/literal 之间区别

最近看了一个IBM的文章讲SOAP四种消息方式的差别

RPC是面向调用的,所以要求在payload中包含operation名字
而document方式是不包含operation名字的,payload里直接就是part

encoded包含part的类型信息,比如xsi:type="xsd:int"
literal是不包含part的类型信息但是通过引用schema里的元素也可以容易的通过schema验证

RPC/encoded
WSDL:
<message name="myMethodRequest">
<part name="x" type="xsd:int"/>
<part name="y" type="xsd:float"/>
</message>
<message name="empty"/>

<portType name="PT">
<operation name="myMethod">
<input message="myMethodRequest"/>
<output message="empty"/>
</operation>
</portType>

Payload on wire:
<myMethod>
<x xsi:type="xsd:int">5</x>
<y xsi:type="xsd:float">5.0</y>
</myMethod>
优点:
服务端通过payload的顶层元素operation-name就可以分发请求到底层实现类,不需要根据myMethod内的几个part的类型来找到到底调用的哪一个operation
缺点:
xsi:type="xsd:int"这种太长,也没必要,性能下降
验证麻烦,x, y是单独通过schema type验证,而myMethod不属于schema,属于WSDL定义。
不兼容WS-I,所以互操作性有问题


RPC/Literal
WSDL: 和RPC/encoded一样

Payload on wire:
<myMethod>
<x>5</x>
<y>5.0</y>
</myMethod>
优点:
服务端通过payload的顶层元素operation-name就可以分发请求到底层实现类,和RPC/encoded一样
type encoding 没有了,性能能上升
RPC/literal is WS-I compliant.
缺点:
不容易验证message因为不包含x和y的类型信息,myMethod也不是schema定义的。


Document/encoded
Nobody follows this style. It is not WS-I compliant. So let's move on.


Document/Literal
WSDL:多了包含schema的types元素
<types>
<schema>
<element name="xElement" type="xsd:int"/>
<element name="yElement" type="xsd:float"/>
</schema>
</types>

<message name="myMethodRequest">
<part name="x" element="xElement"/>
<part name="y" element="yElement"/>
</message>
<message name="empty"/>

<portType name="PT">
<operation name="myMethod">
<input message="myMethodRequest"/>
<output message="empty"/>
</operation>
</portType>

Payload on wire:
<xElement>5</xElement>
<yElement>5.0</yElement>

优点:
没有type信息,消息简短,传输性能好
所有payload内的内容都可以通过schema校验(xElement是引用types里的schema)
WS-I部分兼容(只有一个元素的时候才兼容)
缺点:
WSDL复杂,包含了太多types
operation name没有了,分发比较复杂(后面解释soapAction)
WS-I要求soap:body内只有一个元素,而这里有了两个,所以有时候不兼容WS-I

其中在HTTP头里加入soapAction可以解决分发问题,SOAP 1.1规范里说:The SOAPAction HTTP request header field can be used to indicate the intent of the SOAP HTTP request. The value is a URI identifying the intent
那么intent是什么呢?你需要在WSDL定义operation的时候给operation指定soapAction,这相当于一个operation的key或者id
当document/literal的时候,如果有两个operation用了同样的参数(类似于方法重载), 那么服务端无法区分是调用了哪个operation
这种情况下,必须要包含soapActiont头来指明到底哪个operation被调用。当然soapAction不要太长,否则还不如包含type的效率高
注意:在SOAP 1.2协议中,soapActionb变成了action。


Document/literal wrapped pattern
从这看出来,就算是document/literal虽然可以校验message但是没有了operation,RPC/literal相反,有operation但是校验message比较麻烦
如果两点都能满足的话多好?恩,现在事实上从微软开始,很多人逐渐采用了document/literal wrapped pattern来解决这个问题.

WSDL:
<types>
<schema>
<element name="myMethodRequest">
<complexType>
<sequence>
<element name="x" type="xsd:int"/>
<element name="y" type="xsd:float"/>
</sequence>
</complexType>
</element>
<element name="myMethodResponse">
<complexType/>
</element>
</schema>
</types>
<message name="myMethodRequest">
<part name="parameters" element="myMethodRequest"/>
</message>
<message name="myMethodResponse">
<part name="parameters" element="myMethodResponse"/>
</message>

<portType name="PT">
<operation name="myMethod">
<input message="myMethodRequest"/>
<output message="myMethodResponse"/>
</operation>
</portType>

Payload on wire
<myMethod>
<x>5</x>
<y>5.0</y>
</myMethod>
这么乍一看,payload和RPC/literal一样的,没有什么变化,其实不然。在RPC/literal中myMethod就是operation。但是在docment/literal wrapped中,myMethod是wrapper的名字,这个可以通过payload中唯一的input message得到operation,这样operation也可以得到了。

对于document/literal wrapped方式,有几个特点:
对于每一个operation, input message只有一个
part不是primitive类型,是一个complex element
wrapper和operation名字一样
wrapper没有任何atrribute

优点:
没有type encoding,消息短小,效率高
payload所有元素都可以有对应的schema校验(从根元素myMethod开始)
soap body里的wrapper就是operation,所以容易分发
wrapper增加的约束使得document/literal方式的payload只有一个元素(myMethod),满足了WS-I
缺点:
WSDL太长
本身只是一个style,不是标准或者规范

2008年10月10日星期五

一点点关于Restful Web Service的设计的思考

Restful Web Service 的五条原则:
* 为所有“事物”定义ID
* 将所有事物链接在一起
* 使用标准方法
* 资源多重表述
* 无状态通信

如果你要实现这样的Restful WS那么你可能会遇到和我遇到的一样的一些问题:

问题1:面向资源和面向消息动作的服务

Restful Web Service是面向资源的服务,不同于SOAP是面向消息和动作的服务,Rest WS应该用URI来表示资源.
这就有一个问题,对于资源来讲是没有业务语义的,比如一个缺陷跟踪软件如果需要暴露这样两个服务:
1) update一个bug,对于SOAP来讲,在WSDL里暴露一个update的operation,服务端代码更新数据库就好了
2) reopen一个bug,对于SOAP来讲,你需要WSDL暴露一个operation是reopen,然后服务端代码除了会操作数据库更改bug的状态之外,还会发送email通知owner,并启动一个处理bug的工作流。
这两个操作本质上都会update bug的字段,但是由于两个操作有业务语义,所以他们是不同的,后者会启动一个业务工作流,而前者不会。

对于Restful Web Service来讲,更改bug在数据库的状态很简单,但是如何区分语义呢?我们知道这个HTTP PUT(或POST)操作在Restful WS中本身只知道我要更新资源,它本身是没有业务语义的,因为Rest WS是基于资源的服务,没有任何业务逻辑。
这个问题确实比较头疼,在现实世界中,大多数复杂的应用是粗颗粒基于消息和动作的,比如RPC.如果你需要对资源操作,那么客户端就要负责业务逻辑和事务性等,这对客户端是一个很大的麻烦。比如一个SOAP里deletePersons( names[])对应到RestWS就要客户端循环发送DELETE /persons/name这个请求,对于更复杂的运算客户端不得不更多的了解服务端的内部数据关系,严重破坏了封装性。另外,这种情况下客户端也很难实现事务性。
但是反过来说,现实世界中暴露的服务,可能80%的操作还是最基本的CRUD(增读改删),这个比较适合Rest,清晰简单,可能20%的操作还是有很强的业务语义的操作,更适合SOAP,这个比例每个项目会不太一样,但是肯定是CRUD比较多。那么如果你要使用Restful WS如何平衡呢?我的想法是,做一些违反Restful规则的服务。比如deletePersons(names[])这个操作,你可以发一个请求 POST /persons?deletePersons,然后请求体包含一段XML或者JSON包含所有的name.事实上很多互联网网站提供的Restful API也是这种面向资源和动作的服务的混合体,正是那句话,没有银弹。

问题2,客户端说,该死,Restful不是规范所以更不会有WSDL,我怎么知道怎么调用?
另外Restful WS没有WSDL那样的规范,即便你用JSON或者ATOM协议,具体内容(比如atom的content tag)仍然是没有具体规定的,你可以纯文本或者POX(Plain Old XML)或者base64的内容放到atom的content tag里,没有限制,如果资源是一个树状结构的复杂数据实体,那么客户端怎么才能知道如何产生请求报文和解析响应呢?
我的想法:提供MDS(meta data service),暴露一个全局的服务,告诉客户端每个服务支持哪些格式,比如如果你的内容是POX那么这个MDS应该会是很多schema,如果内容也可以是JSON格式,因为JSON没有schema,但是可以用BNF范式,不过BNF读起来实在不是很方便,你可以用JSON-XML转换的规则来套用XML的Schema (http://www.ibm.com/developerworks/library/x-atom2json.html, 我更喜欢这个 http://www.xml.com/pub/a/2006/05/31/converting-between-xml-and-json.html) 。还有一种方式,就是把复杂的资源拆分为多个小的资源,保证所有资源背后的数据都是flat的key/value pair,这样你就可以通过POST/PUT做表单提交一样的更新数据。但是这个工作有些时候对调用者不太方便,事务性也很难保证。

Restful WS没有任何WS-Enumeration, WS-Policy, WS-Security, WS-Transaction这样的协议集合,所以你需要自己来实现,这是没有标准的。这么看来Restful WS确实比较单薄,但是或许正是因为Restful 比较简单所以才会使用越来越广泛。这也不是一个大问题,你可以尽量利用现有技术或者实现自己的方案,比如安全性你可以使用证书和HTTPS的保证验证和传输安全。

问题3,我该用PUT 还是 POST?
根据HTTP规范,PUT适用于做安全的幂等性操作(idempotent),换句话说,两次操作是安全的,不会导致不同的结果。
比如一条SQL:update gender='male' where name='daniel'执行两次不会造成不同的结果
类似的还有:createOrUpdate(user1)调用两次也没有问题
非幂等性的操作就不能保证这一点,比如,一条SQL:update counter=counter+1 where name='x'执行两次就会产生不同的结果,再比如create(user1)调用两次会创建两个一样的用户,这也会产生不同的结果。

这意味着PUT其实可以看作是createOrUpdate操作,比如
PUT /persons/daniel第一次会创建daniel这个人,第二次请求会更新daniel这个人,如果两个请求内容一样,那么更新操作其实不会对第一次创建的daniel做任何改动。

和PUT一样GET,HEAD,PUT,DELETE,OPTIONS和TRACE都有这种性质.
DELETE可以用于删除,GET可以用于读取。

比较特殊的是POST,他蕴含的意思是,嘿,客户端,调用我的话,我可不保证幂等性,我可能会启动一个工作流,也可能会插入一个消息到另一个系统,我什么都能做,但我就是不能保证你调用我两次同样的请求会出现什么后果。
上面我们提到过的Restful WS是没有面向消息和动作的服务的,如果我们要提供的话,其实POST是最好的选择,他可以做任何事情。而PUT更适合做面向资源的服务,用来创建或者更新一个数据。

参考HTTP规范http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html:
The fundamental difference between the POST and PUT requests is reflected in the different meaning of the Request-URI. The URI in a POST request identifies the resource that will handle the enclosed entity. That resource might be a data-accepting process, a gateway to some other protocol, or a separate entity that accepts annotations. In contrast, the URI in a PUT request identifies the entity enclosed with the request -- the user agent knows what URI is intended and the server MUST NOT attempt to apply the request to some other resource.

但是Atom Publishing Protocol这种经常被用作Restful WS的协议去不是这样的,她就是用PUT做更新,POST做创建。为什么呢?我觉得现实世界中,大多数情况下当你创建一个entry你是不知道他的主键的,这个主键90%的情况下都是服务器端生成的自增主键。如果你用PUT去创建一个entry,比如PUT /rest/person/daniel, 这隐含着daniel就是person的主键值,这里不能有两个人都叫daniel。而事实上当你创建一个新的person你通常是不指定主键值,而是服务器端返回一个社会福利号码或者身份证编号这样的主键,所以如果你还用PUT那么请求看起来应该是这样PUT /rest/person, 然后创建出来的person的URI /rest/person/12434320456。乍一看没问题,其实问题很严重,PUT URI之后,资源的URI不应该变化,这里你的URI从没有后面的数字ID变成了有数字ID, 这种对一个URL提交然后在服务端处理后在另外一个URL暴露你提交的数据,这不是POST在HTTP规范里定义的行为么?所以,呵呵,APP认为大多数情况下ID是服务端生成的,所以用POST更合适。

那到底我们应该用什么呢?PUT还是POST创建记录?
我觉得,如果你知道新纪录的主键,比如客户端可以生成GUID你就可以用PUT,90%的情况下都是自增主键,那你还是用POST。

2008年10月5日星期日

Concurrency Cheat Sheet

It's the mutable state, stupid.
All concurrency issues boil down to coordinating access to mutable state. The less mutable state, the easier it is to ensure thread safety.

Make fields final unless they need to be mutable.

Immutable objects are automatically thread-safe.
Immutable objects simplify concurrent programming tremendously. They are simpler and safer, and can be shared freely without locking or defensive copying.

Encapsulation makes it practical to manage the complexity.

Guard each mutable variable with a lock.

Guard all variables in an invariant with the same lock.

Hold locks for the duration of compound actions.

A program that accesses a mutable variable from multiple threads without synchronization is a broken program.

Don't rely on clever reasoning about why you don't need to synchronize.

Include thread safety in the design processor explicitly document that your class is not thread-safe.

Document your synchronization policy.

2008年10月4日星期六

一个随便写写的测试Java reordering的程序

程序很简单,就是有一个类Obj不停的被实例化
class Obj {
int x;
public Obj(int x) {
this.x = x;
}
}
这个类总是被一个线程传入参数100不停的构造,另外一个线程读取,理论上另一个线程可能只会看到x=100对么?其实不是,如果java -server来执行这个程序你会看到很多x=0的情况出现。为什么呢?就是因为reordering的优化
如果你给x加上volatile或者final关键字你就不会看到这个问题了,或者给obj加上volatile也可以。
因为这两个关键字都禁止reordering,在JDK1.4里或许不行,但是JDK5重新规定了volatile的行为,其中包括了不仅和volatile变量之间禁止reordering,也和non-volatile变量之间禁止reordering了。
final这里可能更好一些,因为Obj这样就是一个immutable对象了,而我们Obj的构造函数并没有escape this到外部线程,所以可以获得JDK5的所谓Initialization Safety的特性,保证了final字段x一定是不需要同步就可以被外部线程看到,这蕴含着禁止reordering.


package test.jmm;

public class TestReordering {
public Obj obj=null;

public static void main(String[] args) {
TestReordering test = new TestReordering();
new WriterThread(test).start();
new ReaderThread(test).start();
}
}

class Obj {
int x;
public Obj(int x) {
this.x = x;
}
}

class WriterThread extends Thread {
private TestReordering test;
public WriterThread(TestReordering test) {
this.test = test;
}
public void run() {
while(true) {
test.obj = new Obj(100);
}
}
}
class ReaderThread extends Thread {
private TestReordering test;
public ReaderThread(TestReordering test) {
this.test = test;
}
public void run() {
while(true) {
if (test.obj != null)
if(test.obj.x == 0) {
System.out.println("found");
} else {
//System.out.println(test.obj.x);
}
}
}
}

2008年10月2日星期四

Strictfp到底有多实用?

如果是为了满足IEEE-754 floating-point specification,那么是否意味着数学浮点协处理器单元就不能发挥最大的作用了?

如果满足这个IEEE754是否表示我的程序可以移植?我觉得如果你的程序移植性依赖于浮点运算的结果本身就是个错误,所以这个问题似乎没有太大意义。

如果我真的需要非常严格的数值,我可能会用BigDecimal

所以,有多大用处呢?貌似很多5年Java经验的人也不太清楚strictfp。

浅谈JMM的reordering导致的一些问题

1997年开始JVM的内存问题开始逐渐暴露出来,不同于脚本语言的简单内存模型或者C的不完整内存模型,由于Java是历史上第一个提供完整的复杂的内存模型的语言和运行环境,有没有考虑周全的问题是必然的。这后来导致了一代宗师Doug Lea的JSR133。这引发了很多VM和语言本身的变化,比如volatile关键字的含义的变化。我觉得reordering是其中比较tricky的一个问题,直到JDK5才得以解决。

什么是reordering问题?
表示对代码指令的执行顺序的改变。比如先写a再写b,如果b和a不是互相依赖,编译器可以改成先写a和另外一个变量,凑成一个字长写回,这种优化不仅可能发生在编译期间,甚至发生在运行期。这些看起来是很聪明的优化,但是这些优化大多数只是对单线程的执行优化,在多线程的时候会导致问题。过去C是依赖于线程类库来保证内存操作的正确性,Java是通过JMM实现的。

recording可以发生在几种情况如下:

*编译期间,编译器可以对某些指令重新排序甚至改变来提高性能,比如inline
*运行期间,处理器在某些情况下可能会改变指令的运算顺序来提高性能,比如64位子长机器为了一次写两个32位的值
*运行期间,缓存(寄存器)有可能不是按照指令的顺序写回更新

比如Doug Lea的一个例子:
Class Reordering {
int x = 0, y = 0;
public void writer() {
x = 1;
y = 2;
}

public void reader() {
int r1 = y;
int r2 = x;
}
}
一个线程调用writer的时候如果处理器发生了reordering,可能会先执行y=2然后再执行x=1。如果这时候另外一个线程调用reader,y被读取的时候可以看到2但是不一定能看到x=1,很可能看到x=0,因为writer执行的顺序发生了变化.

另外,对于比较短小的函数,编译器会做inline,和C++里inline关键字含义很类似,这样可以减少方法调用栈的频繁压栈弹栈来提升性能。比如这样一个构造函数
public Foo {
public int x = 0;
public Foo(int v) {
x=v;
}
}
当编译Foo f = new Foo()的时候,编译器会inline成两行普通代码:
Foo f = 分配内存;
f.x=0;
f.x=v;
这样导致一个问题就是如果另外一个线程看到x的时候可能是0,稍后又变成v.这会导致另外一个线程看到了一个不完全构造的Foo对象!即便你在构造函数内没有写类似于global.ref=this,即便你没有escape this reference到外部线程,由于这种inline+reordering的优化,外部线程仍然可以看到半成品的对象。这对于一般的情况我们可能会考虑把x作为私有的不让外部线程访问,或者其他线程访问的时候同步,因为同步会除了mutex和flush还会设立reordering的上下边界,也就是说在同步边界上,另外一个线程不会先获得s2的引用,必须退出同步块,s2完全构造结束后另一个线程才能得到s2的引用。但是对于我们通常理解final应该不受这个约束的,事实上老的JMM不是这样处理的,这对于final的字段是一场灾难。

final问题:
过去final没有被特殊处理,这导致final的东西不final,会发生很困惑不可预料的问题。
比如String这个类,包含了三个final成员变量,分别是长度,偏移量和一个字符数组。
下面的两行代码如果被一个线程执行到第二行的时候,
String s1 = "/usr/tmp";
String s2 = s1.substring(4);
JVM为s1.substring()所将要产生的String调用parent constructor Object(),这时候heap分配Object所需要的内存并把地址赋值给s2,然后所有final的成员变量会被初始化为0,然后String的构造函数再把offset和length设置为期望的值。由于在老的JMM里在内存地址先复制给s2然后再调用String的构造函数就会导致另外一个线程如果这时候使用了s2就会看到offset=0,也就是说s2="/usr/tmp",稍后再一次使用s2又会看到s2="/tmp"
这样感觉就是final的字段也会变化,immutable的String也会变化,这个问题会非常难以发现。JSR133新的内存模型改变了这种行为,叫做initialization safety. 意思是说当一个对象构造过程中没有把自己通过引用暴露给其他线程,构造函数结束后,这个对象才算安全构造成功,之后这个对象的内部所有的final字段都可以不需要同步就可以让外部的线程看到。这也隐含着新的JMM对s2的赋值会发生在构造函数完全结束,如果有final字段的话。

volatile问题
一般来说普通变量会被保存到线程级别local copy,通常是寄存器,用的时候直接从速度最快的寄存器读取,避免多次频繁的寻址读取主存,这就意味着在某些情况下必须在主存和local copy之间flush.这种flush保证了同步,保证了happens before 原则。
而volatile他会总是访问主存而不是local copy,换句话说运算的时候,每次都是重新读取内存然后写入寄存器再参与计算,L1/L2 cache也不会缓存。
这样他总是可以看到其他线程对主存的最新的操作结果。但是这不能保证独享,因为没有lock
比如两个线程都进行a++,那么理路上可能两个线程都看到了初始值0,然后都累加到1,都在某个flush的时候写回主存,结果是最终结果不是2,而是1。所以这个不可以用于计数器或者信号量。

但是他更快更简单,不需要锁,但是如果你有大量的volatile变量参与运算,那么大量的main memory操作也可能会比较慢,这时候你也可以考虑用lock批量的操作之后flush回main memory。

老的JMM中volatile是仅仅不可以和volatile变量之间reordering,但和普通变量之间是可以发生reordering的,这导致实用性大大下降。Doug Lea给了一个例子:
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}

public void reader() {
if (v == true) {
//uses x - guaranteed to see 42.
}
}
}
如果在老的JMM中,x和v可以reorder,处理器可能先写了v=true,这个时候还没有写x=42,恰巧这个时候另外一个线程执行reader(),那么这个线程可以看到v=true,然后认为x一定已经是42了,其实不然,这时候x是0!只有在新的JMM中,volatile和non-volatile彼此不允许在一个方法调用栈中recorder的时候才能保证数据操作的顺序性。注意:volatile还保证原子性。对于long和double由于是64位,其他primitives类型是32位,所以在32位字长的系统上long和double不能保证原子性,这时候需要volatile.对于64为系统是否需要volatile我不敢说,但是我会安全起见也加上volatile,毕竟Java会运行在不同的硬件平台上。

一个相关的有趣的问题可以在这里找到,volatile禁止reordering最终解决了DCL问题。
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

这里我只是简单介绍一下:
下面这段代码想避免每次都要同步的开销同时保证只创建一次helper.

class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other functions and members...
}
其实这里有个问题,helper的构造可能会被Inline+reorder,导致赋值和构造顺序的变化,所以你是没有办法保证得到一个完全构造好的helper的,除非helper所有的字段都是final的,而且运行在JDK5下。有了volatile可以禁止这种优化,下面的代码就可以正常运行:

class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null)
helper = new Helper();
}
}
return helper;
}
}

当然这里注意的是对于32位的primitive,不加volatile的DCL也是可以的,因为32位字长的机器写32位的int/float本身就是硬件提供了原子操作的能力的,但是32位子长的机器写64位的long和double的时候,也一定要加volatile,因为第一个进入的线程写高32位和低32位之间,如果另外一个线程进入同步快会看到helper不等于0(其实是写了一半的数值),第二个线程就会面临一场灾难了.

volatile还有一些技巧,比如当作cheap lock用:
public class CheesyCounter {
private volatile int value;
public int getValue() { return value; }
public synchronized int increment() {
return value++;
}
}

小结,JMM其实更为复杂的多,写出伸缩性好的程序一定要对JMM有基本的理解,因为JMM其实主要是和线程相关的,而大规模的应用几乎没有不使用线程的,而且可能是非常intensive的使用。