2008年12月27日星期六

Hibernate 的乐观锁@Version和 MySQL 的两个小问题

MySQL的设计者一直没有支持精度到毫秒或者纳秒的Timestamp类型,过去一直只能支持到秒。这虽然不太方便但是也凑合着能用,比如我通过程序代码插入long int也可以。但是Hibernate的实体如果用了@Version在MySQL一个时间字段上做乐观锁,就有问题了。Hibernate比较实体对象是否改变过,是根据@version字段的值,由于MySQL的DATETIME类型不能包含毫秒,纳秒,所以比较的时候会有问题,导致Hibernate认为实体Version不相等,抛HibernateOptimisticLockingFailureException给你。

MySQL 6 才会支持毫秒,所以目前你只能用INT或者BIGINT来保存Version了。
但是就算是你用了INT你可能仍然遇到别的错误
Caused by: java.lang.NullPointerException
at org.hibernate.type.IntegerType.next(IntegerType.java:59)
at org.hibernate.engine.Versioning.increment(Versioning.java:108)
at org.hibernate.event.def.DefaultFlushEntityEventListener.getNextVersion(DefaultFlushEntityEventListener.java:365)
at org.hibernate.event.def.DefaultFlushEntityEventListener.scheduleUpdate(DefaultFlushEntityEventListener.java:257)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:128)

这个错误一看就头大了吧,Hibernate的源代码是这样的
public Object next(Object current, SessionImplementor session) {
return new Integer( ( (Integer) current ).intValue() + 1 );
}
初步分析原因是前面事件通知的时候传入的当前version是空的,具体看这里 http://opensource.atlassian.com/projects/hibernate/browse/HHH-3030

最简单的解决办法是给@version字段在数据库里设置一个默认值,比如0

Opensource意味着你不能发现bug的时候打电话对售后大喊大叫,没办法,要么自己写一个hotfix,要么等官方fix吧。

2008年11月27日星期四

Swing 的 Validate, Invalidate, Revalidate

Validate/Invalidate/Revalidate in Swing

Class Hierarchy

Component.invalidate():
Invalidates this component. This component and all parents above it are marked as needing to be laid out.





Component.validate()
Ensures that this component has a valid layout. This method is primarily intended to operate on instances of Container





Container.validate()
Validates this container and all of its subcomponents.
The validate method is used to cause a container to lay out its subcomponents again. It should be invoked when this container's subcomponents are modified (added to or removed from the container, or layout-related information changed) after the container has been displayed.
Set valid=true and call Container.validateTree().
Container.validateTree() will call all its sub containers’ validateTree() recursively, so all sub components’ Component.validate() method will be called.



Container.validateTree()





Container.invalidate()
Basically do the same thing as super class Component.invalidate except for notifying the layout manager the changes, since the major difference between Component and Container is that Container can contains objects with layout manager.
LayoutManager2.invalidate() will discard the cached size information about the layout, so next time the layout will be re-calculated.



Conclusion on validate/invalidate
Invalidate() causes the component hierarchy to be marked as needing to be laid out again, and the validate() causes that to be done. It may be expensive, but is a way of getting the peers to recalculate size and to do what is needed to bring the display up to date. It has limitations: it doesn't cause an immediate screen update when invoked from an event handler, where paintImmediately() does.

JComponent.revalidate()
Supports deferred automatic layout.
Calls invalidate and then adds this component's validateRoot to a list of components that need to be validated. Validation will occur after all currently pending events have been dispatched. In other words after this method is called, the first validateRoot (if any) found when walking up the containment hierarchy of this component will be validated. By default, JRootPane, JScrollPane, and JTextField return true from isValidateRoot.



Conclusion on revalidate
This method will automatically be called on this component when a property value changes such that size, location, or internal layout of this component has been affected. This automatic updating differs from the AWT because programs generally no longer need to invoke validate to get the contents of the GUI to update. Because RepaintManger.addInvalidComponent() will validate them.

2008年11月24日星期一

OpenSSL转换PEM(Base64 DER)格式到PFX(PKCS12)格式

Convert PEM (Base64 DER) key pair into PFX (PKCS12)

C:\>openssl pkcs12 -export -in ca.cer -inkey ca.pem -out ca.pfx
Loading 'screen' into random state - done
Enter pass phrase for ca.pem:
Enter Export Password:
Verifying - Enter Export Password:

签名试一下
C:\>signtool sign /f ca.pfx /p password test.js
Done Adding Additional Store
Successfully signed: test.js

导入到JKS格式试一下
C:\Temp>keytool -importkeystore -srckeystore ca.pfx -srcstoretype PKCS12 -destkeystore ca.jks
Enter destination keystore password:
Re-enter new password:
Enter source keystore password:
Entry for alias 1 successfully imported.
Import command completed: 1 entries successfully imported, 0 entries failed or cancelled

2008年11月13日星期四

添加JarFinder.com按钮到google bar

如果你的项目没有Maven2那么classNotFound可能经常会遇到,推荐一个网站jarfinder.com

你可以把JarFinder作为一个google bar的search按钮。只要随便添加一个google按钮,然后选择编辑,use the advanced editor (关于google bar 的教程请访问 http://toolbar.google.com/buttons/apis)

<?xml version="1.0" encoding="UTF-8"?>
<custombuttons xmlns="http://toolbar.google.com/custombuttons/">
<button>
<search method="get">http://www.jarfinder.com/index.php/java/search/~{query}~</search>
<site>http://www.jarfinder.com</site>
<title>JarFinder</title>
<icon type="image/x-icon" mode="base64">
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
aUcAAAAASUVORK5CYII=
</icon>
<description>JarFinder</description>
</button>
</custombuttons>

2008年11月11日星期二

免费杀毒软件AVG

最近被公司的Symantec Endpoint Protection折磨的实在不行了,在资源管理器里每打开一个文件夹都要延迟0.2秒左右,而卸载掉之后几乎没有任何延迟。对于普通用户来说这其实无所谓,但是对于我这样的开发人员,或者需要用QTab在资源管理器里经常大量文件操作的人来说,这种延迟实在是无法忍受,就算你禁止了SEP仍然速度非常慢。更气人的是,live Update的时候机器就像中毒一样,CPU两个核心占有率都保持50%以上,百般无奈,决定删之。回想当年Norton时代,Anti-Virus的运行效率多么令人敬佩,现在实在是失望。
找了一下免费的防病毒软件,综合对比了一下,觉得AVG的Basic版本比较适合我,免费而且轻量。
安装的时候我没有选择Link Scanner, E-Mail Scanner, Plugin for office, 因为我从来不轻易打开链接或者附件的,我需要的仅仅是文件系统实时保护。

装上之后感受了一下,资源管理器果然没有了延迟问题!估计查杀病毒的能力和功能肯定是不如SEP,但是对于我来说,够了。

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的使用。

2008年9月25日星期四

escape analysis 和 lock elision

Escape analysis 是检查所有引用的作用域, 如果一个引用只是在一个本地作用域内,没有escape到更大的作用域, JIT编译成本地代码的时候会进行实时优化。
比如最重要的一个就是锁擦除 lock elision.
如果escape analysis发觉这个引用只在一个本地作用域(局部变量),这表示只有在当前作用域的线程才有可能访问这个锁,在这种情况下永远不可能有别的线程进入这个作用域和当前线程竞争,所以这种情况下这个锁可以被消除,这就是lock elision.

比如这段代码:
public String concatBuffer(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}

这种情况下StringBuffer对象的作用域就这这个方法内,所以StringBuffer完全没有必要线程安全加锁(当然更好的做法是用StringBuilder,不要依赖于聪明但是有时候行为不可预料的编译器和JIT/JRE.), 所以escape analysis结果表示可以优化,lock elision这种情况下就可以发生。

以下是测试程序
package test.thread;

public class LockElisionTest {

public static void main(String[] args) {
long t1 = System.currentTimeMillis();
LockElisionTest obj = new LockElisionTest();
for (int i = 0; i < 10000000; i++) {
obj.concatBuffer("1", "2");
}
long t2 = System.currentTimeMillis();
System.out.println(t2 - t1);
}

public String concatBuffer(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
}


注意的是是-XX:+DoEscapeAnalysis默认没有打开,而且你需要用-server模式

结果JRE5没有打开escape analysis是3500毫秒
JRE6打开是2100毫秒,关闭2200毫秒左右(估计因为还有别的优化)

Java Biased Lock 实验

今天神舟七号升天,不知道是不是去成都的飞机都受影响,19:50的飞机改到23:20,在机场无聊,写篇博客吧

什么是biased lock?
Biased Locking is a class of optimizations that improves uncontended synchronization performance by eliminating atomic operations associated with the Java language’s synchronization primitives.

有些时候一个线程多次获得对象锁的操作中,理论上这些操作可以合并在一起,而减少lock/unlock的时间。不过这样会导致对其他线程的不公平,所以叫biased lock.通常是对第一个获得锁的线程偏心。

禁止:-XX:-UseBiasedLocking
使用:JRE6默认使用,或者显式使用-XX:+UseBiasedLocking
以下是我的测试程序,在JRE6上使用biased lock只需要500毫秒左右,而使用JRE5则需要5500毫秒左右,快10倍以上。
奇怪的是JRE6上禁止biased lock之后貌似性能没有什么变化,如果你打印一些调试信息(关闭注释System.out.println("T" + id + " holds lock")那行)你会发现仍然是biased lock,知道一个线程结束才释放锁。

package test.thread;

import java.util.concurrent.CountDownLatch;

public class BiasedLockTest {

public static volatile Long t1 = System.nanoTime();
static {
System.out.println(t1);
}
public static volatile Long t2;

public static void main(String[] args) throws InterruptedException {

Object lock = new Object();

TestThread[] threads = new TestThread[100];
CountDownLatch latch = new CountDownLatch(threads.length);

for (int i = 0; i < 100; i++) {
threads[i] = new TestThread(i, lock, latch);
}
for (int i = 0; i < 100; i++) {
threads[i].start();
}

for (int i = 0; i < 100; i++) {
threads[i].join();
}
System.out.println(t2);
System.out.println( (t2 - t1) / 10e5);
}
}


class TestThread extends Thread {
final private int id;
final private Object lock;
final private CountDownLatch latch;

public TestThread(int id, Object lock, CountDownLatch latch) {
this.id = id;
this.lock = lock;
this.latch = latch;
}
public void run() {
for(int i = 0; i < 10000; i++) {
synchronized (lock) {
int k = 0;
k=k+1;//dummy calculation
}
// System.out.println("T" + id + " holds lock");
}
BiasedLockTest.t2 = System.nanoTime();
latch.countDown();
// System.out.println("T" + id + " dies");
}
}

2008年9月23日星期二

把金山词霸添加到google bar按钮查询

安装了google金山词霸合作版之后,今天查了几个词都没有查到,觉得很差劲。我甚至尝试性搜索了一个最简单的gonna都查不到,一气之下反安装了。突然想起iciba.com还是不错的,我又用google bar,能不能做一个按钮呢?
首先随便添加任何一个google button,然后选项里点edit, advanced editor, 把内容换成这个
<custombuttons xmlns="http://toolbar.google.com/custombuttons/">
<button>
<title>iciba</title>
<description>www.iciba.com/</description>
<icon mode="base64" type="image/x-icon">
iVBORw0KGgoAAAANSUhEUgAAABEAAAAQCAMAAADH72RtAAAAAXNSR0IArs4c6QAAAARnQU1BAACx
jwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAwBQTFRF
AAAAgAAAAIAAgIAAAACAgACAAICAgICAwMDA/wAAAP8A//8AAAD//wD/AP//////AAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAzAABmAACZAADMAAD/ADMAADMzADNmADOZADPMADP/AGYAAGYzAGZmAGaZAGbM
AGb/AJkAAJkzAJlmAJmZAJnMAJn/AMwAAMwzAMxmAMyZAMzMAMz/AP8AAP8zAP9mAP+ZAP/MAP//
MwAAMwAzMwBmMwCZMwDMMwD/MzMAMzMzMzNmMzOZMzPMMzP/M2YAM2YzM2ZmM2aZM2bMM2b/M5kA
M5kzM5lmM5mZM5nMM5n/M8wAM8wzM8xmM8yZM8zMM8z/M/8AM/8zM/9mM/+ZM//MM///ZgAAZgAz
ZgBmZgCZZgDMZgD/ZjMAZjMzZjNmZjOZZjPMZjP/ZmYAZmYzZmZmZmaZZmbMZmb/ZpkAZpkzZplm
ZpmZZpnMZpn/ZswAZswzZsxmZsyZZszMZsz/Zv8AZv8zZv9mZv+ZZv/MZv//mQAAmQAzmQBmmQCZ
mQDMmQD/mTMAmTMzmTNmmTOZmTPMmTP/mWYAmWYzmWZmmWaZmWbMmWb/mZkAmZkzmZlmmZmZmZnM
mZn/mcwAmcwzmcxmmcyZmczMmcz/mf8Amf8zmf9mmf+Zmf/Mmf//zAAAzAAzzABmzACZzADMzAD/
zDMAzDMzzDNmzDOZzDPMzDP/zGYAzGYzzGZmzGaZzGbMzGb/zJkAzJkzzJlmzJmZzJnMzJn/zMwA
zMwzzMxmzMyZzMzMzMz/zP8AzP8zzP9mzP+ZzP/MzP///wAA/wAz/wBm/wCZ/wDM/wD//zMA/zMz
/zNm/zOZ/zPM/zP//2YA/2Yz/2Zm/2aZ/2bM/2b//5kA/5kz/5lm/5mZ/5nM/5n//8wA/8wz/8xm
/8yZ/8zM/8z///8A//8z//9m//+Z///M////RGKwUAAAAKpJREFUKFNNzrEKAjEMBuCKBSlXzLsJ
t/VRQo9b9B18g3Pr1Ew39RG6dXRXHG4Sirmz1f6hFD6SEBFjxPL44wiGDDmsWmWDpRHsIONtGE6/
Htw/8OwdXGydwq7TKTmA8VD2sBgA71IrEiB5Z5oe5KY0jtN/KqCUUoNWnDkK3hPwStSbxDTnTZ6k
uHpz1DN8JdDrzlkJqMi6QaneWLIs7yXsqBYfLibbhg/4ACM/k1kU4ql2AAAAAElFTkSuQmCC
</icon>
<search>http://www.iciba.com/{query}</search>
</button>
</custombuttons>

就行了,如果要换图标去这里生成base64
http://www.motobit.com/util/base64-decoder-encoder.asp

替换<icon>就可以了

现在你在google bar搜索框写进去一个单词,比如gonna然后点击词霸的图标看看结果如何?

2008年9月21日星期日

CentOS 安装 Subversion

1. 下载collabnet subversion server rpm包
2. 安装rpm包rpm -iv client then server
3. 创建启动脚本 as /etc/init.d/svnserve

#!/bin/bash
#
# /etc/rc.d/init.d/svnserve
#
# Starts the Subversion Daemon
#
# chkconfig: 345 90 10
# description: Subversion Daemon

# processname: svnserve

source /etc/rc.d/init.d/functions

[ -x /usr/bin/svnserve ] || exit 1

# To pass additional options (for instace, -r root of directory to server) to
# the svnserve binary at startup, set OPTIONS here.
#
OPTIONS="-r /svn"
RETVAL=0
prog="svnserve"
desc="Subversion Daemon"

start() {
echo -n $"Starting $desc ($prog): "
daemon $prog -d $OPTIONS
RETVAL=$?
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog
echo
}

stop() {
echo -n $"Shutting down $desc ($prog): "
killproc $prog
RETVAL=$?
[ $RETVAL -eq 0 ] && success || failure
echo
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog
return $RETVAL
}

case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
start
RETVAL=$?
;;
condrestart)
[ -e /var/lock/subsys/$prog ] && restart
RETVAL=$?
;;
*)
echo $"Usage: $0 {start|stop|restart|condrestart}"
RETVAL=1
esac

exit $RETVAL

添加启动脚本到run level 3 和 5
[root@alpha init.d]# chkconfig --add svnserve
[root@alpha init.d]# chkconfig --level 35 svnserve on


现在启动客户端比如svn tortoise,browse repository就可以了

CentOS 安装 apache http server 2.2

1. download apache httpd source
2. unzip it to /opt/httpd/httpd-2.2.9
3. cd /opt/httpd-2.2.9, 执行 "./configure --prefix="/opt/httpd-2.2.9"
4. make
5. make install
6. bin/apachectl start,然后打开首页 http://192.168.0.100/, it should show "it works"
7. bin/apachectl stop

你也可以编译安装到其他路径,但是你不可以移动已经编译好的apache httpd

ref:
http://httpd.apache.org/docs/2.0/install.html#configure


8. 作为服务安装在runlevel 3

cd /etc/init.d
vi appachehttpd, 加入以下内容
#!/bin/bash
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
#
http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# Startup script for the Apache Web Server
#
# chkconfig: - 85 15
# description: Apache is a World Wide Web server. It is used to serve \
# HTML files and CGI.
# processname: httpd
# pidfile: /var/run/httpd.pid
# config: /etc/httpd/conf/httpd.conf
# Source function library.
. /etc/rc.d/init.d/functions
if [ -f /etc/sysconfig/httpd ]; then
. /etc/sysconfig/httpd
fi
# This will prevent initlog from swallowing up a pass-phrase prompt if
# mod_ssl needs a pass-phrase from the user.
INITLOG_ARGS=""
# Set HTTPD=/usr/sbin/httpd.worker in /etc/sysconfig/httpd to use a server
# with the thread-based "worker" MPM; BE WARNED that some modules may not
# work correctly with a thread-based MPM; notably PHP will refuse to start.
# Path to the apachectl script, server binary, and short-form for messages.
apachectl=/usr/local/apache/bin/apachectl
httpd=${HTTPD-/usr/local/apache/bin/httpd}
prog=httpd
RETVAL=0
# check for 1.3 configuration
check13 () {
CONFFILE=/usr/local/apache/conf/httpd.conf
GONE="(ServerType|BindAddress|Port|AddModule|ClearModuleList|"
GONE="${GONE}AgentLog|RefererLog|RefererIgnore|FancyIndexing|"
GONE="${GONE}AccessConfig|ResourceConfig)"
if grep -Eiq "^[[:space:]]*($GONE)" $CONFFILE; then
echo
echo 1>&2 " Apache 1.3 configuration directives found"
echo 1>&2 " please read @docdir@/migration.html"
failure "Apache 1.3 config directives test"
echo
exit 1
fi
}
# The semantics of these two functions differ from the way apachectl does
# things -- attempting to start while running is a failure, and shutdown
# when not running is also a failure. So we just do it the way init scripts
# are expected to behave here.
start() {
echo -n $"Starting $prog: "
check13 || exit 1
daemon $httpd $OPTIONS
RETVAL=$?
echo
[ $RETVAL = 0 ] && touch /var/lock/subsys/httpd
return $RETVAL
}
stop() {
echo -n $"Stopping $prog: "
killproc $httpd
RETVAL=$?
echo
[ $RETVAL = 0 ] && rm -f /var/lock/subsys/httpd /var/run/httpd.pid
}
reload() {
echo -n $"Reloading $prog: "
check13 || exit 1
killproc $httpd -HUP
RETVAL=$?
echo
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status $httpd
RETVAL=$?
;;
restart)
stop
start
;;
condrestart)
if [ -f /var/run/httpd.pid ] ; then
stop
start
fi
;;
reload)
reload
;;
graceful|help|configtest|fullstatus)
$apachectl $@
RETVAL=$?
;;
*)
echo $"Usage: $prog {start|stop|restart|condrestart|reload|status|fullstatus|graceful|help|configtest}"
exit 1
esac
exit $RETVAL

注意文件中有三处主要的地方需要修改下的:
apachectl=/usr/local/apache/bin/apachectl
httpd=${HTTPD-/usr/local/apache/bin/httpd}
CONFFILE=/usr/local/apache/conf/httpd.conf

然后
chkconfig --add apachehttpd
如果你要运行在多个runlevel,比如3和5
chkconfig --level 35 apachehttpd on

附各个run level:

* rc0.d - System Halted
* rc1.d - Single User Mode
* rc2.d - Single User Mode with Networking
* rc3.d - Multi-User Mode - boot up in text mode
* rc4.d - Not yet Defined
* rc5.d - Multi-User Mode - boot up in X Windows
* rc6.d - Shutdown & Reboot


确认安装:
cd /etc/rc.d/rc3.d
you should see S85apachehttpd
cat S85apachehttpd
should show you the script

reboot and you should see the web page

2008年9月19日星期五

资源管理器里添加DOS和Cygwin Bash命令行到右键上下文菜单


1. 添加DOS命令行到右键点击目录的上下文菜单
添加注册表
[HKEY_CLASSES_ROOT\Directory\shell\cmd]
默认值:Open Command Line
[HKEY_CLASSES_ROOT\Directory\shell\cmd\command]
默认值:cmd.exe /k "cd %L"

2. 添加Cygwin Bash命令行到右键点击目录的上下文菜单
add c:\Tools\cygwin\bin to %PATH%
然后 添加注册表
[HKEY_CLASSES_ROOT\Directory\shell\cygwin]
默认值:Open Cygwin Bash
[HKEY_CLASSES_ROOT\Directory\shell\cygwin\command]
默认值:c:\Tools\cygwin\bin\bash.exe --login -c "cd '%1' ; exec /bin/bash -rcfile ~/.bashrc"

对于配置cygwin bash上下文菜单更多内容请看http://www.mindview.net/Etc/Cygwin/BashHere

有了cygwin的一个主要好处是可以把windows dos 命令行和unix style的命令结合一起用,比如:
$ netstat -nao | grep 139
TCP 16.158.65.15:139 0.0.0.0:0 LISTENING 4
TCP 192.168.116.1:139 0.0.0.0:0 LISTENING 4
TCP 192.168.199.1:139 0.0.0.0:0 LISTENING 4

如果只用dos会很麻烦的事情,所以我现在在windows下也是用cgywin

2008年9月18日星期四

这个缺乏诚信的年代,还是不喝牛奶为妙

完了,我喝了不知道多少光明的牛奶的,估计在我国也没有办法索赔了
以后怎么办呢?还是不喝牛奶算了。不知道以后还能不能吃米饭,白菜?

液态奶检出三聚氰胺的批次表
公司 序号 生产企业 产品名称 规格型号 商标 生产日期/批次 三聚氰胺(mg/kg)
蒙牛 1 蒙牛(武汉)友芝友乳业有限公司 核桃牛奶 200ml/袋 友芝友 20080910 0.765
2 内蒙古蒙牛乳业(集团)股份有限公司 蒙牛高钙低脂牛奶 250ml/盒 蒙牛 2008.08.07 0.8
3 内蒙古蒙牛乳业(集团)股份有限公司 全脂灭菌纯牛乳 250ml/盒 蒙牛 2008.09.01 1.0
4 内蒙古蒙牛乳业(集团)股份有限公司 高钙低脂牛奶 250ml/盒 蒙牛 2008.08.01 1.5
5 内蒙古蒙牛乳业(集团)股份有限公司 早餐奶(麦香味) 250ml/包 蒙牛 20080814 1.9
6 内蒙古蒙牛乳业(集团)股份有限公司 蒙牛早餐奶 250ml/盒 蒙牛 2008.07.26/x 2.57
7 内蒙古蒙牛乳业(集团)股份有限公司 妙点 250ml/盒 蒙牛 20080728/W206 3.17
8 蒙牛乳业(北京)有限责任公司 木糖醇酸牛奶 2kg/瓶 蒙牛 20080806 3.52
9 内蒙古蒙牛乳业(集团)股份有限公司 高钙低脂牛奶 243ml(250g)/袋 蒙牛 20080908/C206/GAfb 4.2
10 蒙牛乳业(马鞍山)有限公司 蒙牛大粒果实酸牛奶 160克/盒 蒙牛 M20080903 6.8(A样)
11 蒙牛乳业(马鞍山)有限公司 蒙牛大粒果实酸牛奶 160克/盒 蒙牛 M20080903 7(B样)
伊利 1 济南伊利乳业有限责任公司 伊利芒果+黄桃酸牛奶 125g/盒 伊利 2008.09.07 0.69
2 内蒙古伊利实业集团股份有限公司 酸牛奶(木瓜+甜橙) 125g/瓶 伊利 20080903 1.02
3 内蒙古伊利实业集团股份有限公司 纯牛奶 220ml/袋 伊利 2008.09.13 2.2
4 内蒙古伊利实业集团股份有限公司 脱脂奶 250ml/盒 伊利 20080820 2.9
5 内蒙古伊利实业集团股份有限公司 纯牛奶 220ml/袋 伊利 20080905MIAC6 5.5
6 内蒙古伊利实业集团股份有限公司 纯牛奶 242ml/袋 伊利 20080906/LIA09 8
7 内蒙古伊利实业集团股份有限公司 高钙低脂奶 250ml/盒 伊利 20080819 8.4
光明 1 北京光明健能乳业有限公司 光明酸牛奶(原味) 180g/袋 光明 2008.09.12 0.6
2 武汉光明乳品有限公司 原味酸牛奶 180g/盒 光明 2008-09-13 3.41
3 北京光明健能乳业有限公司 原味酸牛奶 100克/杯 光明 20080910A 3.5
4 北京光明健能乳业有限公司 大颗果粒草莓酸奶 450克/盒 光明 20080902BC 4.8
5 光明乳业有限责任公司 益生菌·优乳酪(原味) 190g/罐 光明 B20080908C 5.65
6 北京光明健能乳业有限公司 优酪乳·酸牛奶(原味) 580克/瓶 光明 B20080909A 8.6

2008年9月17日星期三

SSL 单向双向认证配置实例 - HP OpenView Service Manager和Tomcat

0. 如果没有openssl预装,那么你需要自己下载并编译,如果java -version是gnu的,那么要下载并安装Sun的JRE,最好是JDK
1. 下载源代码http://www.openssl.org/source/
2. 我使用CentOS5.2其他Linux/unix操作系统应该类似,以root身份登录
3. 把source解压缩到安装路径,比如/opt/openssl
4. 进入目录编译
./config --prefix=/opt/openssl
5. 安装
make test
make install

配置openssl.cnf
找到openssl.cnf
[root@arsenal14 /]# find / -name openssl.cnf
/etc/pki/tls/openssl.cnf
然后加入你自己的country state等等:

生成自签名的CA根证书
首先生成CA的private key:
[root@arsenal14 tmp]# openssl genrsa -des3 -out ca.pem 2048
Generating RSA private key, 2048 bit long modulus
...............................................................................................................................+++
......................+++
e is 65537 (0x10001)
Enter pass phrase for ca.pem:
Verifying - Enter pass phrase for ca.pem:

然后用这个private key生成CA根证书
[root@arsenal14 tmp]# openssl req -new -key ca.pem -x509 -days 1095 -out ca.cer
Enter pass phrase for ca.pem:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [CN]:
State or Province Name (full name) [Shanghai]:
Locality Name (eg, city) [Shanghai]:
Organization Name (eg, company) [Hewlett Packard]:
Organizational Unit Name (eg, section) []:GDCC-SMCi
Common Name (eg, your name or your server's hostname) []:arsenal14.asiapacific.hpqcorp.net
Email Address []:daniel.woo@hp.com
注意机器名一定要是这台机器的FDQN

到此为止,我们看看当前目录都有了哪些东西:
[root@arsenal14 tmp]# ls
ca.cer CA根证书
ca.pem CA private key (重要!保密!)

下面是一个真是用例,比如我们要把一台tomcat和一台HP OpenView ServiceManager(下文简称SM)建立mutal authentication.
我们需要
1. 建立一个CA
2. 分别创建tomcat和SM的private/public key pair
3. CA签发tomcat和SM的证书
4. 导入CA证书到tomcat和SM的信任keystore


现在为了做mutual authentication我们还需要对SM和tomcat分别生成private key和证书,然后把证书通过CA签发。

生成SM的private/public key pair[root@arsenal14 tmp]# keytool -genkey -alias smserver -keystore sm.jks
Enter keystore password:
Re-enter new password:
What is your first and last name?
[Unknown]: tsmcivm8
What is the name of your organizational unit?
[Unknown]:
What is the name of your organization?
[Unknown]:
What is the name of your City or Locality?
[Unknown]:
What is the name of your State or Province?
[Unknown]:
What is the two-letter country code for this unit?
[Unknown]: CN
Is CN=tsmcivm8, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=CN correct?
[no]: y

Enter key password for
(RETURN if same as keystore password):



生成tomcat的private/public key pair

[root@arsenal14 tmp]# keytool -genkey -alias tomcat -keystore tomcat.jks
Enter keystore password:
Re-enter new password:
What is your first and last name?
[Unknown]: danielnc6400.asiapacific.hpqcorp.net
What is the name of your organizational unit?
[Unknown]:
What is the name of your organization?
[Unknown]:
What is the name of your City or Locality?
[Unknown]:
What is the name of your State or Province?
[Unknown]:
What is the two-letter country code for this unit?
[Unknown]: CN
Is CN=danielnc6400.asiapacific.hpqcorp.net, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=CN correct?
[no]: y

Enter key password for
(RETURN if same as keystore password):
Re-enter new password:


现在多了两个包含private/public key的keystore:
sm.jks 和 tomcat.jks

我们从中导出两个待签发的证书(只包含public key)
sm_req.crs 和 tomcat_req.crs

[root@arsenal14 tmp]# keytool -certreq -alias smserver -keystore sm.jks -file sm_req.crs
Enter key store password: password

[root@arsenal14 tmp]# keytool -certreq -alias tomcat -keystore tomcat.jks -file tomcat_req.crs
Enter key store password: password


然后我们要用CA根证书签发出一个SM的证书,发布给其他系统
[root@arsenal14 tmp]# openssl x509 -req -days 365 -in sm_req.crs -CA ca.cer -CAkey ca.pem -CAcreateserial -out sm.cer
Signature ok
subject=/CN=tsmcivm8/O=HP/OU=GDCC-SMCi/L=Shanghai/ST=Shanghai/C=CN
Getting CA Private Key
Enter pass phrase for ca.pem:

然后我们要用CA根证书签发出一个tomcat的证书,发布给其他系统
[root@arsenal14 tmp]# openssl x509 -req -days 365 -in tomcat_req.crs -CA ca.cer -CAkey ca.pem -CAcreateserial -out tomcat.cer
Signature ok
subject=/CN=danielnc6400.asiapacific.hpqcorp.net/O=HP/OU=GDCC-SMCi/L=Shanghai/ST=Shanghai/C=CN
Getting CA Private Key
Enter pass phrase for ca.pem:


现在又多了两个文件,两个sign好的证书sm.cer 和 tomcat.cer

我们有了这些文件:
ca.cer 根证书
ca.pem 根证书private key
ca.srl
sm.cer 已经签名的sm证书
sm.jks sm server keystore
sm.pem sm private key
sm_req.crs 没用了
tomcat.cer 已经签名的tomcat证书
tomcat.jks tomcat server keystore
tomcat.pem tomcat private key
tomcat_req.crs 没用了

现在把signed好的证书导回keystore
但是之前你必须把CA的根证书导入
[root@arsenal14 tmp]# keytool -cacert -file ca.cer -keystore sm.jks
[root@arsenal14 tmp]# keytool -cacert -file ca.cer -keystore tomcat.jks


然后才能导入CA签发好的证书
keytool -import -file sm.cer -keystore sm.jks -alias smserver
keytool -import -file tocmat.cer -keystore tomcat.jks -alias tomcat

先测试tomcat的单向服务器认证
把tomcat.jks复制到tomcat/conf下
打开tomcat/conf/server.xml,更改https的connector配置
先测试单向tomcat服务器端验证(不要求客户上传验证客户端证书)
minSpareThreads="2" maxSpareThreads="15"
enableLookups="true" disableUploadTimeout="true"
acceptCount="100" maxThreads="100"
scheme="https" secure="true" SSLEnabled="true"
keystoreFile="C:/Projects/Tomcat6SM/conf/tomcat.jks" keyAlias="tomcat" keystorePass="password"
clientAuth="false" sslProtocol="TLS"/>

启动tomcat, log应该有
2008-9-18 23:24:38 org.apache.coyote.http11.Http11Protocol start
信息: Starting Coyote HTTP/1.1 on http-443

从FireFox3这个时候访问https://danielnc6400.asiapacific.hpqcorp.net你应该看到这个内容



这是因为你不信任这个证书,下一步我们需要把根证书导入FireFox的trust keystore
Tools->Option->Advanced->Encryption->View Certificates->authorities->Import



导入之后你可以查看所有已经导入的证书



IE直接访问会弹出对话框问你是否信任
IE证书导入更简单,直接double click ca.cer安装根证书
然后再次打开这个URL你会发现IE不在有任何提示,因为IE信任这个根证,所以也信任根证书签发的tomcat的证书。

接下来配置SM的单向server认证
复制sm.jks 到SM安装路径的RUN/security目录下
修改sm.ini如下(sm -helpssl可以显示所有参数的意义)
# SSL configuration
ssl:0
ssl_reqClientAuth:0
sslConnector:1
httpsPort:13443

#
# Certificates
truststoreFile:security/sm.jks
truststorePass:password
keystoreFile:security/sm.jks
keystorePass:password

重新启动服务

然后到客户端打开Windows->Preferences->HP Service Manager->Security
把包含CA根证书的那个keystore全路径写在CA Certificate file里.这里是为了能让客户端验证服务器上的证书是否有效。不过奇怪的是这里没有输入访问keystore的密码的地方,eclipse client怎么打开keystore还是个迷。:-)

然后新建一个链接,hostname一定要填写域名,第二页要勾上Use SSL Encryption,现在你就可以连接上SM了

这个单向配置都没有问题了,我们来尝试把SM和tomcat做mutual authentication
由于双方trust keystore里面都信任ca.cer所以理论上讲现在是自动支持的。

打开SM做个实验,打开Script Library
创建个JS脚本
var url = "https://danielnc6400.asiapacific.hpqcorp.net/index.html";
var headers = new Array();
//headers.push("Authorization: Basic " + base64Encode("administrator:admblabla"));
var resp = doHTTPRequest( "GET", url, headers, null, 10, 10, 10 );
print(resp);

执行脚本输出是:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<body>
This is a test
</body>
</html>

结束

MySQL Master-Slave Replication

MySQL Master-Slave Replication

Offical reference
http://dev.mysql.com/doc/refman/5.0/en/replication.html

简化步骤:
1. download
mySQL for centOS5 (RHEL5)建议intel cc编译的这个版本,建议下载5.1版本,6.0的falcon性能并不比innoDB好
Linux (non RPM, Intel C/C++ compiled, glibc-2.3) downloads
配置默认网关:/etc/sysconfig/network
配置dhcp或者静态IP在/etc/network-scripts/ifcfg-eth0, 1, 2...
配置dns hostname在/etc/resolv.conf

2. 如果已经有了mysql那么先 rpm -ev mysql
然后关闭iptables防火墙

[root@arsenal14 mysqlM]# chkconfig --list iptables
iptables 0:off 1:off 2:on 3:on 4:on 5:on 6:off
[root@arsenal14 mysqlM]# service iptables save
Saving firewall rules to /etc/sysconfig/iptables: [ OK ]
[root@arsenal14 mysqlM]# service iptables stop
Flushing firewall rules: [ OK ]
Setting chains to policy ACCEPT: filter [ OK ]
Unloading iptables modules: [ OK ]
[root@arsenal14 mysqlM]# chkconfig iptables off
[root@arsenal14 mysqlM]# chkconfig --list iptables
iptables 0:off 1:off 2:off 3:off 4:off 5:off 6:off


3. 创建用户
groupadd mysql
useradd -g mysql mysql
passwd mysql

4. 解开压缩包然后复制到三份,master-slave-slave
cp -r mysql* /opt/mysqlM
cp -r mysql* /opt/mysqlS0
cp -r mysql* /opt/mysqlS1
到/opt下执行
chown -R mysql *
chgrp -R mysql *
到mysqlM,和另外两个数据库下执行
scripts/mysql_install_db --user=mysql
chown -R mysql data
chgrp -R mysql data

5. 启动主数据库
./bin/mysqld_safe --user=mysql &
然后测试数据库状态:
[root@arsenal14 mysqlM]# bin/mysqladmin version
bin/mysqladmin Ver 8.42 Distrib 5.1.26-rc, for redhat-linux-gnu on i686
Copyright (C) 2000-2006 MySQL AB
This software comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to modify and redistribute it under the GPL license

Server version 5.1.26-rc
Protocol version 10
Connection Localhost via UNIX socket
UNIX socket /tmp/mysql.sock
Uptime: 1 min 13 sec

Threads: 1 Questions: 1 Slow queries: 0 Opens: 15 Flush tables: 1 Open tables: 8 Queries per second avg: 0.13
关闭数据库 ./mysqladmin shutdown
创建配置文件,把support-files里的my-medium.conf复制到mysqlM/my.cnf
更改一些参数,比如
port=4306
socket=/tmp/mysqlM.sock
collation_server=utf8_unicode_ci
character_set_server=utf8
打开innoDB的配置等

然后用这个命令行启动mysql:
mysqld_safe --defaults-file=/opt/mysqlM/my.cnf --user=mysql

用ping看看数据库状态
[root@arsenal14 mysqlM]# bin/mysqladmin --defaults-file=/opt/mysqlM/my.cnf ping
mysqld is alive

如果你需要用root通过远程客户端登录,你需要enable mysql 的root remote login
[root@arsenal14 mysqlM]# bin/mysql --defaults-file=/opt/mysqlM/my.cnf
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 7
Server version: 5.1.26-rc-log MySQL Community Server (GPL)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'password' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

mysql> exit
Bye
然后你就可以用toad for mysql了

6. 配置两个slave数据库
[root@arsenal14 mysqlM]# cp my.cnf ../mysqlS0
[root@arsenal14 mysqlM]# cp my.cnf ../mysqlS1
然后只要改端口和sock文件不要重复,innoDB数据文件路径不重复,pid file 不重复就可以了
建议改成
port=5306
socket = /tmp/mysqlS.sock
innodb_data_home_dir = /opt/mysqlS0/data/
innodb_data_file_path = ibdata1:10M:autoextend
innodb_log_group_home_dir = /opt/mysqlS0/data/
pid-file=/opt/mysqlS0/pid


port = 6306
socket = /tmp/mysqlS1.sock
innodb_data_home_dir = /opt/mysqlS1/data/
innodb_data_file_path = ibdata1:10M:autoextend
innodb_log_group_home_dir = /opt/mysqlS1/data/
pid-file=/opt/mysqlS1/pid


检查是否可以启动,查看err日志
[root@arsenal14 mysqlS0]# bin/mysqld_safe --defaults-file=/opt/mysqlS0/my.cnf --user=mysql &

[root@arsenal14 mysqlS1]# bin/mysqld_safe --defaults-file=/opt/mysqlS1/my.cnf --user=mysql &


7. 准备知识
现在才开始真正配置master-slave,有些基础知识要知道
1. master-slave之间使用master的数据库日志来让slave知道master发生了什么
2. 除了master-slave模式,mySQL现在cluster也是免费的了
3. mySQL proxy 是一个代理组件,上面可以运行mysql load balancer
4. mysql load balancer的好处:
4.1 自动排除掉同步失败或者落后的slave
4.2 修复后同步正常的slave可以自动加入
4.3 把读取分布转发到集群内任何一台服务器
4.4 写不可以分发
4.5 保持各个数据库链接数平均

8. 配置主数据库
先关闭数据库 ./mysqladmin shutdown
在master上创建同步用的用户,我用了root偷懒

接下来要master打开日志,并标识一个ID
[mysqld]
log-bin=mysql-bin
server-id=1
innodb_flush_log_at_trx_commit=1
sync_binlog=1
binlog-do-db=test

9. 配置一个slave数据库

指定一个id
[mysqld]
server-id=2
master-host =arsenal14.asiapacific.hpqcorp.net
master-user =mysqlrep
master-password =password
master-port =4036
replicate-do-db=test

10.配置master-slave
如果master已经有了数据,那么你要停止master的数据操作,先dump到slave保持一致才能开始replication

mysql> FLUSH TABLES WITH READ LOCK;
mysql> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000010 | 106 | | |
+------------------+----------+--------------+------------------+
1 row in set (0.01 sec)


这里bin 日志是000006,偏移量是106,记下来这个数据

在命令行下导出master的数据
./bin/mysqldump --defaults-file=/opt/mysqlM/my.cnf --all-databases --lock-all-tables >dbdump.db
然后倒入到slave中。
./bin/mysql -h master <>最后在slave配置目标master
这里应该是
mysql> STOP SLAVE;
Query OK, 0 rows affected (0.01 sec)
mysql> change master to master_host='arsenal14.asiapacific.hpqcorp.net', master_port=4306, master_user='mysql', master_password='password', master_log_file='mysql-bin.000010', master_log_pos=106;
Query OK, 0 rows affected (0.02 sec)

好了,重新启动slave,然后对master新建一个表看看,不行的话要改配置需要先删除data/maseter.info,然后再执行上面的sql
然后你可以看到第三条数据是自动复制过来的,貌似create table没有正常复制过来,自己创建了table之后数据就复制过来了,钱两行因为没有表所以丢失了
mysql复制有两种,statement和record的,DDL比如创建表同步这个问题需要看一下
我又重新做了测试,这次DDL也可以同步了,数据也是正确的。刚才的问题一直没有再次重现,比较奇怪。

2008年1月14日星期一

远程桌面超出了最大允许连接数的解决办法

运行 mstsc /v:IP /console
就可以连接到远程主机的的0会话。
然后更改远程桌面配置:
Start->control pannel->administrative tools->terminal services configuration->RDP-Tcp
修改end a disconnected session的时间