<?xml version="1.0" encoding="gb2312"?>

<!-- RSS generated by oioj.net on 4/16/2004 ; 感谢LeXRus提供 RSS 2.0 文档; 此文件可自由使用，但请保留此行信息 --> 
<!-- Source download URL: http://blogger.org.cn/blog/rss2.asp       -->
<rss version="2.0">

<channel>
<title>toyboysli的博客</title>
<link>http://blogger.org.cn/blog/blog.asp?name=toyboysli</link>
<description>李小白的博客</description>
<copyright>blogger.org.cn</copyright>
<generator>W3CHINA Blog</generator>
<webMaster>webmaster@blogger.org.cn</webMaster>
<item>
<title><![CDATA[synchronized和wait()/notify()]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=47452</link>
<author>toyboysli</author>
<pubDate>2009/11/7 21:32:05</pubDate>
<description><![CDATA[方法控制对类成员变量的访问：每个类实例对应一把锁，每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行，否则所属线程阻塞，方法一旦执行，就独占该锁，直到从该方法返回时才将锁释放，此后被阻塞的线程方能获得该锁，重新进入可执行状态。
wait()/notify()：调用任意对象的 wait() 方法导致线程阻塞，并且该对象上的锁被释放。而调用 任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞（但要等到获得锁后才真正可执行）。

synchronized和wait()、notify()的关系:

1.有synchronized的地方不一定有wait,notify

2.有wait,notify的地方必有synchronized.这是因为wait和notify不是属于线程类，而是每一个对象都具有的方法，而且，这两个方法都和对象锁有关，有锁的地方，必有synchronized。

另外，请注意一点：如果要把notify和wait方法放在一起用的话，必须先调用notify后调用wait，因为如果调用完wait，该线程就已经不是current thread了。

注：调用wait()方法前的判断最好用while，而不用if；while可以实现被wakeup后thread再次作条件判断；而if则只能判断一次；



线程的四种状态

　　1. 新状态：线程已被创建但尚未执行（start() 尚未被调用）。

　　2. 可执行状态：线程可以执行，虽然不一定正在执行。CPU 时间随时可能被分配给该线程，从而使得它执行。

　　3. 死亡状态：正常情况下 run() 返回使得线程死亡。调用 stop()或 destroy() 亦有同样效果，但是不被推荐，前者会产生异常，后者

是强制终止，不会释放锁。

　　4. 阻塞状态：线程不会被分配 CPU 时间，无法执行。


首先，前面叙述的所有方法都隶属于 Thread 类，但是这一对 (wait()/notify()) 却直接隶属于 Object 类，也就是说，所有对象都拥有这一

对方法。初看起来这十分不可思议，但是实际上却是很自然的，因为这一对方法阻塞时要释放占用的锁，而锁是任何对象都具有的，调用任意

对象的 wait() 方法导致线程阻塞，并且该对象上的锁被释放。

　　而调用 任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞（但要等到获得锁后才真正

可执行）。

　　其次，前面叙述的所有方法都可在任何位置调用，但是这一对方法却必须在 synchronized 方法或块中调用，理由也很简单，只有在

synchronized 方法或块中当前线程才占有锁，才有锁可以释放。

　　同样的道理，调用这一对方法的对象上的锁必须为当前线程所拥有，这样才有锁可以释放。因此，这一对方法调用必须放置在这样的

synchronized 方法或块中，该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件，则程序虽然仍能编译，但在运行时会出现

IllegalMonitorStateException 异常。

　　wait() 和 notify() 方法的上述特性决定了它们经常和synchronized 方法或块一起使用，将它们和操作系统的进程间通信机制作一个比

较就会发现它们的相似性：synchronized方法或块提供了类似于操作系统原语的功能，它们的执行不会受到多线程机制的干扰，而这一对方法

则相当于 block 和wakeup 原语（这一对方法均声明为 synchronized）。

它们的结合使得我们可以实现操作系统上一系列精妙的进程间通信的算法（如信号量算法），并用于解决各种复杂的线程间通信问题。关于

wait() 和 notify() 方法最后再说明两点：

　　第一：调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的，我们无法预料哪一个线程

将会被选择，所以编程时要特别小心，避免因这种不确定性而产生问题。

　　第二：除了 notify()，还有一个方法 notifyAll() 也可起到类似作用，唯一的区别在于，调用 notifyAll() 方法将把因调用该对象的

wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然，只有获得锁的那一个线程才能进入可执行状态。

　　谈到阻塞，就不能不谈一谈死锁，略一分析就能发现，suspend() 方法和不指定超时期限的 wait() 方法的调用都可能产生死锁。遗憾的

是，Java 并不在语言级别上支持死锁的避免，我们在编程中必须小心地避免死锁。

　　以上我们对 Java 中实现线程阻塞的各种方法作了一番分析，我们重点分析了 wait() 和 notify()方法，因为它们的功能最强大，使用也

最灵活，但是这也导致了它们的效率较低，较容易出错。实际使用中我们应该灵活使用各种方法，以便更好地达到我们的目的。

　　七、守护线程

　　守护线程是一类特殊的线程，它和普通线程的区别在于它并不是应用程序的核心部分，当一个应用程序的所有非守护线程终止运行时，即

使仍然有守护线程在运行，应用程序也将终止，反之，只要有一个非守护线程在运行，应用程序就不会终止。守护线程一般被用于在后台为其

它线程提供服务。

可以通过调用方法 isDaemon() 来判断一个线程是否是守护线程，也可以调用方法 setDaemon() 来将一个线程设为守护线程。 


用synchronized关键字修饰方法后,程序将根据调用此方法的对象的锁来判断是否能调用此方法。
 
对一个类的instance method,则当此方法被一个线程调用时,其他线程不能再通过同一个对象调用此方法（可以通过这个类的另一个对象来调用这个方法）。
 
对一个类的static method，则当一个线程通过类对象调用此方法时，其他线程不能再通过类对象调用此方法。由于类对象在类加载时由虚拟机创建，只有一个，所以同一时刻此方法只能被一个线程调用。
 
在servlet程序中，容器只实例化一个servlet对象，多个用户访问的是同一个servlet对象，因此对servlet的方法加同步修饰，可以防止多个用户同时调用一个方法，避免共享冲突。
 
创建多线程程序时，在子线程中通过一个对象调用一个类的instance方法时，应该在主线程创建这个对象，将对象的引用通过子线程的构造函数或其他接口方法传入子线程，供子线程使用。
]]></description>
</item><item>
<title><![CDATA[Java内部类（Inner Class）详解[转]]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=47381</link>
<author>toyboysli</author>
<pubDate>2009/10/30 13:24:20</pubDate>
<description><![CDATA[关键字: java inner class nested
简单的说，内部（inner）类指那些类定义代码被置于其它类定义中的类；而对于一般的、类定义代码不嵌套在其它类定义中的类，称为顶层（top-level）类。对于一个内部类，包含其定义代码的类称为它的外部（outer）类。
1          Static member class（静态成员类）
类声明中包含“static”关键字的内部类。如以下示例代码，
Inner1/Inner2/Inner3/Inner4就是Outer的四个静态成员类。静态成员类的使用方式与一般顶层类的使用方式基本相同。
java 代码
 
public class  Outer{  
    //just like static method, static member class has public/private/default access privilege levels  
      
    //access privilege level: public   
    public static class Inner1 {  
        public Inner1() {  
            //Static member inner class can access static method of outer class  
            staticMethod();      
            //Compile error: static member inner class can not access instance method of outer class  
            //instanceMethod();    
        }  
    }  
      
    //access privilege level: default   
    static class Inner2   
      
    //access privilege level: private   
    private static class Inner3 {  
        //define a nested inner class in another inner class   
        public static class Inner4   
    }  
  
    private static void staticMethod() {  
        //cannot define an inner class in a method  
        /*public static class Inner4() */  
    }  
      
    private void instanceMethod() {  
        //private static member class can be accessed only in its outer class definition scope  
        Inner3 inner3 = new Inner3();  
        //how to use nested inner class  
        Inner3.Inner4 inner4 = new Inner3.Inner4();  
    }  
}  
  
class Test {  
    Outer.Inner1 inner1 = new Outer.Inner1();  
    //Test and Outer are in the same package, so Inner2 can be accessed here  
    Outer.Inner2 inner2 = new Outer.Inner2();   
    //Compile error: Inner3 cannot be accessed here  
    //Outer.Inner3 inner3 = new Outer.Inner3();   
}  

1.1      静态成员类特性
静态成员类可访问外部类的任一静态字段或静态方法
像静态方法或静态字段一样，静态成员类有public/private/default权限修饰符
1.2      静态成员类约束
静态成员类不能与外部类重名
像外部类的静态方法一样，不能直接访问外部类的实例字段和实例方法
静态成员类只能定义于外部类的顶层代码或外部类其它静态成员类的顶层代码中（嵌套定义）；不能定义于外部类的某个函数中。
1.3      新增语法
    如示例代码所示，可以以“OuterClass.InnerClass”的方式来引用某个内部类。
1.4      什么时候使用静态成员类
B为A的辅助类，且只为A所用时，可将B定义为A的静态成员类。例如JDK中的LinkedList类就有Entry静态成员类：
java 代码
 
public class LinkedList&lt;e&gt;&lt;/e&gt;&lt;E&gt;&lt;e&gt; &lt;/e&gt;extends AbstractSequentialList&lt;e&gt;&lt;/e&gt;&lt;E&gt;&lt;e&gt; 
&lt;/e&gt;
   …;  
   private static class Entry&lt;e&gt;&lt;/e&gt;&lt;E&gt;&lt;e&gt; {  &lt;/e&gt;
    E element;  
    Entry&lt;E&gt;&lt;e&gt; next;  &lt;/e&gt;
    Entry&lt;E&gt;&lt;e&gt; previous;  &lt;/e&gt;
  
    Entry(E element, Entry&lt;E&gt;&lt;e&gt; next, Entry&lt;e&gt;&lt;/e&gt;&lt;/e&gt;&lt;E&gt;&lt;e&gt;&lt;e&gt; previous) {  &lt;/e&gt;&lt;/e&gt;
        this.element = element;  
        this.next = next;  
        this.previous = previous;  
    }  
    }  
    …;  
}  

   显然，Entry用来表示LinkedList中的一个结点，只被LinkedList自身使用。

2          Member class（成员类）
一个静态成员类，若去掉“static”关键字，就成为成员类。如下示例代码，Inner1/Inner2/Inner3/Inner4就是Outer的四个成员类
 

java 代码
 
public class Outer {  
    //just like instance method, member class has public/private/default access privilege levels  
    private int data;  
      
    //access privilege level: public   
    public class Inner1 {  
        private int data;  
        private int data1;  
        public Inner1() {  
            //member class can access its outer class' instance field directly  
            data1 = 1;  
            //itself data field  
            data = 1;  
            //its outer class instance field  
            Outer.this.data = 1;  
        }  
    }  
      
    //access privilege level: default  
    class Inner2 {  
        //can not define static filed, method, class in member class  
        //static int j = 1;  
          
        //but, &quot;static final&quot; compound is allowed   
        static final int CONSTANT = 1;  
    }  
      
    //access privilege level: private   
    private class Inner3 {  
        public class Inner4   
    }  
      
    //in fact, Inner5 is not a member class but a static member class  
    interface Inner5   
      
    private static void staticMethod() {  
        //can not create a member class instance directly in outer class' static method  
        //Inner1 inner1 = new Inner1();  
    }  
      
    private void instanceMethod() {  
        //can create a member class instance in outer class' instance method  
        Inner1 inner1 = new Inner1();  
    }  
}  
  
class Test {  
    public Test() {  
        //cannot create member class instance directly in class other than outer class  
        //Outer.Inner2 inner2 = new Outer.Inner2();  
          
        //create a member class instance outside it's outer class  
        Outer outer = new Outer();  
        Outer.Inner1 inner1 = outer.new Inner1();  
    }  
}  
 
2.1      成员类特性
&#183;        类似于外部类的实例函数，成员类有public/private/default权限修饰符
&#183;        一个成员类实例必然所属一个外部类实例，成员类可访问外部类的任一个实例字段和实例函数。
2.2      成员类约束
成员类不能与外部类重名
不能在成员类中定义static字段、方法和类（static final形式的常量定义除外）。因为一个成员类实例必然与一个外部类实例关联，这个static定义完全可以移到其外部类中去
成员类不能是接口（interface）。因为成员类必须能被某个外部类实例实例化，而接口是不能实例化的。事实上，如示例代码所示，如果你以成员 类的形式定义一个接口，该接口实际上是一个静态成员类，static关键字对inner interface是内含（implicit）的。
2.3      新增语法
    一个成员类实例必然所属于其外部类的一个实例，那么如何在成员类内部获得其所属外部类实例呢？如示例代码所示，采用“OuterClass.this”的形式。
2.4      指定内部类实例所属的外部类实例
内部类实例可在其外部类的实例方法中创建，此新创建内部类实例所属的外
部类实例自然就是创建它的外部类实例方法对应的外部类实例。
          另外，如示例代码所示，对于给定的一个外部类实例outerClass，可以直接创建其内部类实例，语法形式为：
OuterClass.InnerClass innerClass = outerClass.new InnerClass();

2.5      什么时候使用成员类
     成员类的显著特性就是成员类能访问它的外部类实例的任意字段与方法。方便一个类对外提供一个公共接口的实现是成员类的典型应用。
       以JDK Collection类库为例，每种Collection类必须提供一个与其对应的Iterator实现以便客户端能以统一的方式遍历任一 Collection实例。每种Collection类的Iterator实现就被定义为该Collection类的成员类。例如JDK中 AbstractList类的代码片断：
java 代码
 
public abstract class AbstractList&lt;E&gt;&lt;e&gt; &lt;/e&gt;extends AbstractCollection&lt;E&gt;&lt;e&gt; &lt;/e&gt;implements List&lt;e&gt;&lt;/e&gt;&lt;E&gt;&lt;e&gt;{  &lt;/e&gt;
    private class Itr implements Iterator&lt;e&gt;&lt;/e&gt;&lt;E&gt;&lt;e&gt; {  &lt;/e&gt;
         ………;  
    }  
  
     public Iterator&lt;E&gt;&lt;e&gt; iterator() {  &lt;/e&gt;
        return new Itr();  
     }  
}  
  
    因为定义在AbstractList中的Itr可访问AbstractList中的任意字段和方法，所以很方便实现Iterator，无需AbstractList对外暴露更多的接口。
    试想，如果没有成员类机制，只有在AbastractList源码之外定义一个实现Iterator的类Itr，该类有一个AbstractList实例 成员list，为了Itr能获取list的内部信息以便实现遍历，AbstractList必然要向Itr开放额外的访问接口。]]></description>
</item><item>
<title><![CDATA[Hibernate乐观锁的实现原理剖析与使用乐观锁时的注意点]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=47323</link>
<author>toyboysli</author>
<pubDate>2009/10/24 10:49:26</pubDate>
<description><![CDATA[Hibernate支持乐观锁。当多个事务同时对数据库表中的同一条数据操作时，如果没有加锁机制的话，就会产生脏数据（duty data）。Hibernate有2种机制可以解决这个问题：乐观锁和悲观锁。这里我们只讨论乐观锁。
        Hibernate乐观锁，能自动检测多个事务对同一条数据进行的操作，并根据先胜原则，提交第一个事务，其他的事务提交时则抛出org.hibernate.StaleObjectStateException异常。
    Hibernate乐观锁是怎么做到的呢？
    我们先从Hibernate乐观锁的实现说起。要实现Hibenate乐观锁，我们首先要在数据库表里增加一个版本控制字段，字段名随意，比如就叫version，对应hibernate类型只能为 long,integer,short,timestamp,calendar，也就是只能为数字或timestamp类型。然后在hibernate mapping里作如下类似定义：
    &lt;version name=&quot;version&quot;
        column=&quot;VERSION&quot;
        type=&quot;integer&quot;
    /&gt;
告诉Hibernate version作为版本控制用，交由它管理。
当然在entity class里也需要给version加上定义，定义的方法跟其他字段完全一样。
private Integer version;
…
// setVersion() &amp;&amp; getVersion(Integer)

Hibernate乐观锁的的使用：
Session session1 = sessionFactory.openSession();
Session session2 = sessionFactory.openSession();
MyEntity et1 = session1.load(MyEntity.class, id);
MyEntity et2 = session2.load(MyEntity.class, id);
//这里 et1, et2为同一条数据
Transaction tx1 = session1.beginTransaction();
//事务1开始
et1.setName(“Entity1”);
//事务1中对该数据修改
tx1.commit();
session1.close();
//事务1提交
Transaction tx2 = session2.beginTransaction();
//事务2开始
et2.setName(“Entity2”);
//事务2中对该数据修改
tx2.commit();
session2.close();
//事务2提交
在事务2提交时，因为它提交的数据比事务1提交后的数据旧，所以hibernate会抛出一个org.hibernate.StaleObjectStateException异常。
回到前面的问题，Hibernate怎么知道事务2提交的数据比事务1提交后的数据旧呢？
因为MyEntity有个version版本控制字段。
回头看看上面的源代码中的：
MyEntity et1 = session1.load(MyEntity.class, id);
MyEntity et2 = session2.load(MyEntity.class, id);
这里，et1.version==et2.version，比如此时version=1，
当事务1提交后，该数据的版本控制字段version=version+1=2，而事务2提交时version=1&lt;2所以Hibernate认为事务2提交的数据为过时数据，抛出异常。
这就是Hibernate乐观锁的原理机制。
我们已经知道了Hibernate乐观锁是根据version的值来判断数据是否过时，也就是说，在向数据库update某数据时，必须保证该entity 里的version字段被正确地设置为update之前的值，否则hibernate乐观锁机制将无法根据version作出正确的判断。
在我们的WEB应用中，尤其应该注意这个问题。]]></description>
</item><item>
<title><![CDATA[转：图解JVM在内存中申请对象及垃圾回收流程]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=47176</link>
<author>toyboysli</author>
<pubDate>2009/10/6 21:28:20</pubDate>
<description><![CDATA[关键字: jvm 内存 gc
先看一下JVM的内存模型：
 


 
从大的方面来讲，JVM的内存模型分为两大块：
 
永久区内存（ Permanent space ）和堆内存（heap space）。
 
栈内存（stack space）一般都不归在JVM内存模型中，因为栈内存属于线程级别。
每个线程都有个独立的栈内存空间。
 
Permanent space里存放加载的Class类级对象如class本身，method，field等等。
heap space主要存放对象实例和数组。
heap space由Old Generation和New Generation组成，Old Generation存放生命周期长久的实例对象，而新的对象实例一般放在New Generation。
New Generation还可以再分为Eden区(圣经中的伊甸园)、和Survivor区，新的对象实例总是首先放在Eden区，Survivor区作为Eden区和Old区的缓冲，可以向Old区转移活动的对象实例。
 
下图是JVM在内存空间（堆空间）中申请新对象过程的活动图（点击看大图）： 

没错，我们常见的OOM（out of memory）内存溢出异常，就是堆内存空间不足以存放新对象实例时导致。
 
永久区内存溢出相对少见，一般是由于需要加载海量的Class数据，超过了非堆内存的容量导致。通常出现在Web应用刚刚启动时，因此Web应用推荐使用预加载机制，方便在部署时就发现并解决该问题。
 
栈内存也会溢出，但是更加少见。
 
堆内存优化：
调整JVM启动参数-Xms  -Xmx   -XX:newSize -XX:MaxNewSize，如调整初始堆内存和最大对内存 -Xms256M -Xmx512M。 或者调整初始New Generation的初始内存和最大内存 -XX:newSize=128M -XX:MaxNewSize=128M。
 
永久区内存优化：
调整PermSize参数   如  -XX:PermSize=256M -XX:MaxPermSize=512M。
 
栈内存优化：
调整每个线程的栈内存容量  如  -Xss2048K
 
 
最终，一个运行中的JVM所占的内存= 堆内存  +  永久区内存  +  所有线程所占的栈内存总和 。]]></description>
</item><item>
<title><![CDATA[转：Spring事务的传播行为和隔离级别]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=47175</link>
<author>toyboysli</author>
<pubDate>2009/10/6 21:26:32</pubDate>
<description><![CDATA[关键字: 数据库
事务的传播行为和隔离级别[transaction behavior and isolated level]
Spring中事务的定义：
一、Propagation ：
　　key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用：
PROPAGATION_REQUIRED--支持当前事务，如果当前没有事务，就新建一个事务。这是最常见的选择。 
PROPAGATION_SUPPORTS--支持当前事务，如果当前没有事务，就以非事务方式执行。 
PROPAGATION_MANDATORY--支持当前事务，如果当前没有事务，就抛出异常。 
PROPAGATION_REQUIRES_NEW--新建事务，如果当前存在事务，把当前事务挂起。 
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作，如果当前存在事务，就把当前事务挂起。 
PROPAGATION_NEVER--以非事务方式执行，如果当前存在事务，则抛出异常。 

很多人看到事务的传播行为属性都不甚了解，我昨晚看了j2ee without ejb的时候，看到这里也不了解，甚至重新翻起数据库系统的教材书，但是也没有找到对这个的分析。今天搜索，找到一篇极好的分析文章，虽然这篇文章是重点分析PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRED_NESTED的
解惑 spring 嵌套事务
/** 
* @author 王政 
* @date 2006-11-24 
* @note 转载自http://www.javaeye.com/topic/35907?page=1
*/
********TransactionDefinition 接口定义*******************
/**   
      * Support a current transaction, create a new one if none exists.   
      * Analogous to EJB transaction attribute of the same name.   
      *
This is typically the default setting of a transaction definition.   
      */   
     int PROPAGATION_REQUIRED = 0;   
  
     /**   
      * Support a current transaction, execute non-transactionally if none exists.   
      * Analogous to EJB transaction attribute of the same name.   
      *
Note: For transaction managers with transaction synchronization,   
      * PROPAGATION_SUPPORTS is slightly different from no transaction at all,   
      * as it defines a transaction scopp that synchronization will apply for.   
      * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)   
      * will be shared for the entire specified scope. Note that this depends on   
      * the actual synchronization configuration of the transaction manager.   
      * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization   
      */   
     int PROPAGATION_SUPPORTS = 1;   
  
     /**   
      * Support a current transaction, throw an exception if none exists.   
      * Analogous to EJB transaction attribute of the same name.   
      */   
     int PROPAGATION_MANDATORY = 2;   
  
     /**   
      * Create a new transaction, suspend the current transaction if one exists.   
      * Analogous to EJB transaction attribute of the same name.   
      *
Note: Actual transaction suspension will not work on out-of-the-box   
      * on all transaction managers. This in particular applies to JtaTransactionManager,   
      * which requires the javax.transaction.TransactionManager to be   
      * made available it to it (which is server-specific in standard J2EE).   
      * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager   
      */   
     int PROPAGATION_REQUIRES_NEW = 3;   
  
     /**   
      * Execute non-transactionally, suspend the current transaction if one exists.   
      * Analogous to EJB transaction attribute of the same name.   
      *
Note: Actual transaction suspension will not work on out-of-the-box   
      * on all transaction managers. This in particular applies to JtaTransactionManager,   
      * which requires the javax.transaction.TransactionManager to be   
      * made available it to it (which is server-specific in standard J2EE).   
      * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager   
      */   
     int PROPAGATION_NOT_SUPPORTED = 4;   
  
     /**   
      * Execute non-transactionally, throw an exception if a transaction exists.   
      * Analogous to EJB transaction attribute of the same name.   
      */   
     int PROPAGATION_NEVER = 5;   
  
     /**   
      * Execute within a nested transaction if a current transaction exists,   
      * behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.   
      *
Note: Actual creation of a nested transaction will only work on specific   
      * transaction managers. Out of the box, this only applies to the JDBC   
      * DataSourceTransactionManager when working on a JDBC 3.0 driver.   
      * Some JTA providers might support nested transactions as well.   
      * @see org.springframework.jdbc.datasource.DataSourceTransactionManager   
      */   
     int PROPAGATION_NESTED = 6;   
*************************************************************************
在这篇文章里，他用两个嵌套的例子辅助分析，我这里直接引用了。
********************sample***********************
ServiceA {   
       
     /**  
      * 事务属性配置为 PROPAGATION_REQUIRED  
      */  
     void methodA() {   
         ServiceB.methodB();   
     }   
  
}   
  
ServiceB {   
       
     /**  
      * 事务属性配置为 PROPAGATION_REQUIRED  
      */    
     void methodB() {   
     }   
       
}      
*************************************************
我们这里一个个分析吧
1： PROPAGATION_REQUIRED 
加入当前正要执行的事务不在另外一个事务里，那么就起一个新的事务
比如说，ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候，
         ServiceA.methodA已经起了事务，这时调用ServiceB.methodB，ServiceB.methodB看到自己已经运行在ServiceA.methodA
的事务内部，就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中，他就会为自己分配一个事务。
这样，在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常，事务都会被回滚。即使ServiceB.methodB的事务已经被
提交，但是ServiceA.methodA在接下来fail要回滚，ServiceB.methodB也要回滚
2：   PROPAGATION_SUPPORTS
如果当前在事务中，即以事务的形式运行，如果当前不再一个事务中，那么就以非事务的形式运行
这就跟平常用的普通非事务的代码只有一点点区别了。不理这个，因为我也没有觉得有什么区别
3：   PROPAGATION_MANDATORY
必须在一个事务中运行。也就是说，他只能被一个父事务调用。否则，他就要抛出异常。
4：   PROPAGATION_REQUIRES_NEW
这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED，ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW，
那么当执行到ServiceB.methodB的时候，ServiceA.methodA所在的事务就会挂起，ServiceB.methodB会起一个新的事务，等待ServiceB.methodB的事务完成以后，
他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务，那么就是存在
两个不同的事务。如果ServiceB.methodB已经提交，那么ServiceA.methodA失败回滚，ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚，
如果他抛出的异常被ServiceA.methodA捕获，ServiceA.methodA事务仍然可能提交。
5：   PROPAGATION_NOT_SUPPORTED 
当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ，而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ，
那么当执行到ServiceB.methodB时，ServiceA.methodA的事务挂起，而他以非事务的状态运行完，再继续ServiceA.methodA的事务。
6：   PROPAGATION_NEVER 
不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED， 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ，
那么ServiceB.methodB就要抛出异常了。
7：   PROPAGATION_NESTED 
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是，PROPAGATION_REQUIRES_NEW另起一个事务，将会与他的父事务相互独立，
而Nested的事务和他的父事务是相依的，他的提交是要等和他的父事务一块提交的。也就是说，如果父事务最后回滚，他也要回滚的。
而Nested事务的好处是他有一个savepoint。
*****************************************
ServiceA {   
       
     /**  
      * 事务属性配置为 PROPAGATION_REQUIRED  
      */  
     void methodA() {   
         try {
      //savepoint   
             ServiceB.methodB();    //PROPAGATION_NESTED 级别
         } catch (SomeException) {   
             // 执行其他业务, 如 ServiceC.methodC();   
         }   
     }   
  
}   
********************************************
也就是说ServiceB.methodB失败回滚，那么ServiceA.methodA也会回滚到savepoint点上，ServiceA.methodA可以选择另外一个分支，比如
ServiceC.methodC，继续执行，来尝试完成自己的事务。
但是这个事务并没有在EJB标准中定义。

二、Isolation Level(事务隔离等级):
1、Serializable：最严格的级别，事务串行执行，资源消耗最大；

2、REPEATABLE READ：保证了一个事务不会修改已经由另一个事务读取但未提交（回滚）的数据。避免了“脏读取”和“不可重复读取”的情况，但是带来了更多的性能损失。

3、READ COMMITTED:大多数主流数据库的默认事务等级，保证了一个事务不会读到另一个并行事务已修改但未提交的数据，避免了“脏读取”。该级别适用于大多数系统。

4、Read Uncommitted：保证了读取过程中不会读取到非法数据。
隔离级别在于处理多事务的并发问题。
我们知道并行可以提高数据库的吞吐量和效率，但是并不是所有的并发事务都可以并发运行，这需要查看数据库教材的可串行化条件判断了。
这里就不阐述。
我们首先说并发中可能发生的3中不讨人喜欢的事情
1：   Dirty reads--读脏数据。也就是说，比如事务A的未提交（还依然缓存）的数据被事务B读走，如果事务A失败回滚，会导致事务B所读取的的数据是错误的。

2：   non-repeatable reads--数据不可重复读。比如事务A中两处读取数据-total-的值。在第一读的时候，total是100，然后事务B就把total的数据改成200，事务A再读一次，结果就发现，total竟然就变成200了，造成事务A数据混乱。

3：   phantom reads--幻象读数据，这个和non-repeatable reads相似，也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据集被改变了（比如total的数据），但是phantom reads所要读的数据的不一致却不是他所要读的数据集改变，而是他的条件数据集改变。比如Select account.id where account.name=&quot;ppgogo*&quot;,第一次读去了6个符合条件的id，第二次读取的时候，由于事务b把一个帐号的名字由&quot;dd&quot;改成&quot;ppgogo1&quot;，结果取出来了7个数据。
                          Dirty reads          non-repeatable reads            phantom reads
Serializable                     不会                   不会                           不会
REPEATABLE READ           不会                   不会                            会
READ COMMITTED            不会                    会                             会
Read Uncommitted            会                     会                             会

三、readOnly
事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。这是一个最优化提示。在一些情况下，一些事务策略能够起到显著的最优化效果，例如在使用Object/Relational映射工具（如：Hibernate或TopLink）时避免dirty checking（试图“刷新”）。
四、Timeout
       在事务属性中还有定义“timeout”值的选项，指定事务超时为几秒。在JTA中，这将被简单地传递到J2EE服务器的事务协调程序，并据此得到相应的解释。]]></description>
</item><item>
<title><![CDATA[Nginx 0.7.x + PHP 5.2.10（FastCGI）搭建胜过Apache十倍的Web服务器（第5版）]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=47005</link>
<author>toyboysli</author>
<pubDate>2009/9/5 12:12:23</pubDate>
<description><![CDATA[[文章作者：张宴 本文版本：v5.4 最后修改：2009.06.26 转载请注明原文链接：http://blog.s135.com/nginx_php_v5/]

　　前言：本文是我撰写的关于搭建“Nginx + PHP（FastCGI）”Web服务器的第5篇文章。本系列文章作为国内最早详细介绍 Nginx + PHP 安装、配置、使用的资料之一，为推动 Nginx 在国内的发展产生了积极的作用。这是一篇关于Nginx 0.7.x系列版本的文章，安装、配置方式与第4篇文章相差不大，但增加了MySQL安装配置的信息、PHP 5.2.10 的 php-fpm 补丁。Nginx 0.7.x系列版本虽然为开发版，但在很多大型网站的生产环境中已经使用。

　　链接：《2007年9月的第1版》、《2007年12月的第2版》、《2008年6月的第3版》、《2008年8月的第4版》

　　

　　Nginx (&quot;engine x&quot;) 是一个高性能的 HTTP 和反向代理服务器，也是一个 IMAP/POP3/SMTP 代理服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的，它已经在该站点运行超过两年半了。Igor 将源代码以类BSD许可证的形式发布。

　　Nginx 超越 Apache 的高性能和稳定性，使得国内使用 Nginx 作为 Web 服务器的网站也越来越多，其中包括新浪博客、新浪播客、网易新闻等门户网站频道，六间房、56.com等视频分享网站，Discuz!官方论坛、水木社区等知名论坛，豆瓣、YUPOO相册、海内SNS、迅雷在线等新兴Web 2.0网站。


　　Nginx 的官方中文维基：http://wiki.nginx.org/NginxChs


　　在高并发连接的情况下，Nginx是Apache服务器不错的替代品。Nginx同时也可以作为7层负载均衡服务器来使用。根据我的测试结果，Nginx 0.7.61 + PHP 5.2.10 (FastCGI) 可以承受3万以上的并发连接数，相当于同等环境下Apache的10倍。

　　根据我的经验，4GB内存的服务器+Apache（prefork模式）一般只能处理3000个并发连接，因为它们将占用3GB以上的内存，还得为系统预留1GB的内存。我曾经就有两台Apache服务器，因为在配置文件中设置的MaxClients为4000，当Apache并发连接数达到3800时，导致服务器内存和Swap空间用满而崩溃。

　　而这台 Nginx 0.7.61 + PHP 5.2.10 (FastCGI) 服务器在3万并发连接下，开启的10个Nginx进程消耗150M内存（15M*10=150M），开启的64个php-cgi进程消耗1280M内存（20M*64=1280M），加上系统自身消耗的内存，总共消耗不到2GB内存。如果服务器内存较小，完全可以只开启25个php-cgi进程，这样php-cgi消耗的总内存数才500M。

　　在3万并发连接下，访问Nginx 0.7.61 + PHP 5.2.10 (FastCGI) 服务器的PHP程序，仍然速度飞快。下图为Nginx的状态监控页面，显示的活动连接数为28457（关于Nginx的监控页配置，会在本文接下来所给出的Nginx配置文件中写明）：

　　

　　我生产环境下的两台Nginx + PHP5（FastCGI）服务器，跑多个一般复杂的纯PHP动态程序，单台Nginx + PHP5（FastCGI）服务器跑PHP动态程序的处理能力已经超过“700次请求/秒”，相当于每天可以承受6000万（700*60*60*24=60480000）的访问量（更多信息见此），而服务器的系统负载也不高：

　　


　　下面是用100个并发连接分别去压生产环境中同一负载均衡器VIP下、提供相同服务的两台服务器，一台为Nginx，另一台为Apache，Nginx每秒处理的请求数是Apache的两倍多，Nginx服务器的系统负载、CPU使用率远低于Apache：

　　你可以将连接数开到10000～30000，去压Nginx和Apache上的phpinfo.php，这是用浏览器访问Nginx上的phpinfo.php一切正常，而访问Apache服务器的phpinfo.php，则是该页无法显示。4G内存的服务器，即使再优化，Apache也很难在“webbench -c 30000 -t 60 http://xxx.xxx.xxx.xxx/phpinfo.php”的压力情况下正常访问，而调整参数优化后的Nginx可以。

　　webbench 下载地址：http://blog.s135.com/post/288/

　　注意：webbench 做压力测试时，该软件自身也会消耗CPU和内存资源，为了测试准确，请将 webbench 安装在别的服务器上。

　　测试结果：##### Nginx + PHP #####
引用
[root@localhost webbench-1.5]# webbench -c 100 -t 30 http://192.168.1.21/phpinfo.php
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Benchmarking: GET http://192.168.1.21/phpinfo.php
100 clients, running 30 sec.

Speed=102450 pages/min, 16490596 bytes/sec.
Requests: 51225 susceed, 0 failed.

top - 14:06:13 up 27 days,  2:25,  2 users,  load average: 14.57, 9.89, 6.51
Tasks: 287 total,   4 running, 283 sleeping,   0 stopped,   0 zombie
Cpu(s): 49.9% us,  6.7% sy,  0.0% ni, 41.4% id,  1.1% wa,  0.1% hi,  0.8% si
Mem:   6230016k total,  2959468k used,  3270548k free,   635992k buffers
Swap:  2031608k total,     3696k used,  2027912k free,  1231444k cached


　　测试结果：#####  Apache + PHP #####
引用
[root@localhost webbench-1.5]# webbench -c 100 -t 30 http://192.168.1.27/phpinfo.php
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Benchmarking: GET http://192.168.1.27/phpinfo.php
100 clients, running 30 sec.

Speed=42184 pages/min, 31512914 bytes/sec.
Requests: 21092 susceed, 0 failed.

top - 14:06:20 up 27 days,  2:13,  2 users,  load average: 62.15, 26.36, 13.42
Tasks: 318 total,   7 running, 310 sleeping,   0 stopped,   1 zombie
Cpu(s): 80.4% us, 10.6% sy,  0.0% ni,  7.9% id,  0.1% wa,  0.1% hi,  0.9% si
Mem:   6230016k total,  3075948k used,  3154068k free,   379896k buffers
Swap:  2031608k total,    12592k used,  2019016k free,  1117868k cached



　　为什么Nginx的性能要比Apache高得多？这得益于Nginx使用了最新的epoll（Linux 2.6内核）和kqueue（freebsd）网络I/O模型，而Apache则使用的是传统的select模型。目前Linux下能够承受高并发访问的Squid、Memcached都采用的是epoll网络I/O模型。

　　处理大量的连接的读写，Apache所采用的select网络I/O模型非常低效。下面用一个比喻来解析Apache采用的select模型和Nginx采用的epoll模型进行之间的区别：

　　假设你在大学读书，住的宿舍楼有很多间房间，你的朋友要来找你。select版宿管大妈就会带着你的朋友挨个房间去找，直到找到你为止。而epoll版宿管大妈会先记下每位同学的房间号，你的朋友来时，只需告诉你的朋友你住在哪个房间即可，不用亲自带着你的朋友满大楼找人。如果来了10000个人，都要找自己住这栋楼的同学时，select版和epoll版宿管大妈，谁的效率更高，不言自明。同理，在高并发服务器中，轮询I/O是最耗时间的操作之一，select和epoll的性能谁的性能更高，同样十分明了。


　　安装步骤：
　　（系统要求：Linux 2.6+ 内核，本文中的Linux操作系统为CentOS 5.3，另在RedHat AS4上也安装成功）

　　一、获取相关开源程序：
　　1、【适用CentOS操作系统】利用CentOS Linux系统自带的yum命令安装、升级所需的程序库（RedHat等其他Linux发行版可从安装光盘中找到这些程序库的RPM包，进行安装）：
sudo -s
LANG=C
yum -y install gcc gcc-c++ autoconf libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 libxml2-devel zlib zlib-devel glibc glibc-devel glib2 glib2-devel bzip2 bzip2-devel ncurses ncurses-devel curl curl-devel e2fsprogs e2fsprogs-devel krb5 krb5-devel libidn libidn-devel openssl openssl-devel openldap openldap-devel nss_ldap openldap-clients openldap-servers


　　2、【适用RedHat操作系统】RedHat等其他Linux发行版可从安装光盘中找到这些程序库的RPM包（事先可通过类似“rpm -qa | grep libjpeg”的命令查看所需的RPM包是否存在，通常是“xxx-devel”不存在，需要安装）。RedHat可以直接利用CentOS的RPM包安装，以下是RPM包下载网址：
　　①、RedHat AS4 &amp; CentOS 4
　　http://mirrors.163.com/centos/4/os/i386/CentOS/RPMS/
　　http://mirrors.163.com/centos/4/os/x86_64/CentOS/RPMS/

　　②、RedHat AS5 &amp; CentOS 5
　　http://mirrors.163.com/centos/5/os/i386/CentOS/
　　http://mirrors.163.com/centos/5/os/x86_64/CentOS/

　　③、RPM包搜索网站
　　http://rpm.pbone.net/
　　http://www.rpmfind.net/

　　④、RedHat AS4 系统环境，通常情况下缺少的支持包安装：
　　Ⅰ、i386 系统
wget http://blog.s135.com/soft/linux/nginx_php/rpm/i386/libjpeg-devel-6b-33.i386.rpm
rpm -ivh libjpeg-devel-6b-33.i386.rpm
wget http://blog.s135.com/soft/linux/nginx_php/rpm/i386/freetype-devel-2.1.9-1.i386.rpm
rpm -ivh freetype-devel-2.1.9-1.i386.rpm
wget http://blog.s135.com/soft/linux/nginx_php/rpm/i386/libpng-devel-1.2.7-1.i386.rpm
rpm -ivh libpng-devel-1.2.7-1.i386.rpm

　　Ⅱ、x86_64 系统
wget http://blog.s135.com/soft/linux/nginx_php/rpm/x86_64/libjpeg-devel-6b-33.x86_64.rpm
rpm -ivh libjpeg-devel-6b-33.x86_64.rpm
wget http://blog.s135.com/soft/linux/nginx_php/rpm/x86_64/freetype-devel-2.1.9-1.x86_64.rpm
rpm -ivh freetype-devel-2.1.9-1.x86_64.rpm
wget http://blog.s135.com/soft/linux/nginx_php/rpm/x86_64/libpng-devel-1.2.7-1.x86_64.rpm
rpm -ivh libpng-devel-1.2.7-1.x86_64.rpm


　　3、【适用CentOS、RedHat及其它Linux操作系统】下载程序源码包：
　　本文中提到的所有开源软件为截止到2009年06月26日的最新稳定版。
　　①、从软件的官方网站下载：
mkdir -p /data0/software
cd /data0/software
wget http://sysoev.ru/nginx/nginx-0.7.61.tar.gz
wget http://www.php.net/get/php-5.2.10.tar.gz/from/this/mirror
wget http://blog.s135.com/soft/linux/nginx_php/phpfpm/php-5.2.10-fpm-0.5.11.diff.gz
wget http://dev.mysql.com/get/Downloads/MySQL-5.1/mysql-5.1.35.tar.gz/from/http://mysql.he.net/
wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.13.tar.gz
wget &quot;http://downloads.sourceforge.net/mcrypt/libmcrypt-2.5.8.tar.gz?modtime=1171868460&amp;big_mirror=0&quot;
wget &quot;http://downloads.sourceforge.net/mcrypt/mcrypt-2.6.8.tar.gz?modtime=1194463373&amp;big_mirror=0&quot;
wget http://pecl.php.net/get/memcache-2.2.5.tgz
wget &quot;http://downloads.sourceforge.net/mhash/mhash-0.9.9.9.tar.gz?modtime=1175740843&amp;big_mirror=0&quot;
wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-7.9.tar.gz
wget http://bart.eaccelerator.net/source/0.9.5.3/eaccelerator-0.9.5.3.tar.bz2
wget http://pecl.php.net/get/PDO_MYSQL-1.0.2.tgz
wget http://blog.s135.com/soft/linux/nginx_php/imagick/ImageMagick.tar.gz
wget http://pecl.php.net/get/imagick-2.2.2.tgz

　　②、从blog.s135.com下载（比较稳定，只允许在本站，或者在Linux/Unix下通过Wget、Curl等命令下载以下软件）：
mkdir -p /data0/software
cd /data0/software
wget http://blog.s135.com/soft/linux/nginx_php/nginx/nginx-0.7.61.tar.gz
wget http://blog.s135.com/soft/linux/nginx_php/php/php-5.2.10.tar.gz
wget http://blog.s135.com/soft/linux/nginx_php/phpfpm/php-5.2.10-fpm-0.5.11.diff.gz
wget http://blog.s135.com/soft/linux/nginx_php/mysql/mysql-5.1.35.tar.gz
wget http://blog.s135.com/soft/linux/nginx_php/libiconv/libiconv-1.13.tar.gz
wget http://blog.s135.com/soft/linux/nginx_php/mcrypt/libmcrypt-2.5.8.tar.gz
wget http://blog.s135.com/soft/linux/nginx_php/mcrypt/mcrypt-2.6.8.tar.gz
wget http://blog.s135.com/soft/linux/nginx_php/memcache/memcache-2.2.5.tgz
wget http://blog.s135.com/soft/linux/nginx_php/mhash/mhash-0.9.9.9.tar.gz
wget http://blog.s135.com/soft/linux/nginx_php/pcre/pcre-7.9.tar.gz
wget http://blog.s135.com/soft/linux/nginx_php/eaccelerator/eaccelerator-0.9.5.3.tar.bz2
wget http://blog.s135.com/soft/linux/nginx_php/pdo/PDO_MYSQL-1.0.2.tgz
wget http://blog.s135.com/soft/linux/nginx_php/imagick/ImageMagick.tar.gz
wget http://blog.s135.com/soft/linux/nginx_php/imagick/imagick-2.2.2.tgz


　　二、安装PHP 5.2.10（FastCGI模式）
　　1、编译安装PHP 5.2.10所需的支持库：
tar zxvf libiconv-1.13.tar.gz
cd libiconv-1.13/
./configure --prefix=/usr/local
make
make install
cd ../

tar zxvf libmcrypt-2.5.8.tar.gz 
cd libmcrypt-2.5.8/
./configure
make
make install
/sbin/ldconfig
cd libltdl/
./configure --enable-ltdl-install
make
make install
cd ../../

tar zxvf mhash-0.9.9.9.tar.gz
cd mhash-0.9.9.9/
./configure
make
make install
cd ../

ln -s /usr/local/lib/libmcrypt.la /usr/lib/libmcrypt.la
ln -s /usr/local/lib/libmcrypt.so /usr/lib/libmcrypt.so
ln -s /usr/local/lib/libmcrypt.so.4 /usr/lib/libmcrypt.so.4
ln -s /usr/local/lib/libmcrypt.so.4.4.8 /usr/lib/libmcrypt.so.4.4.8
ln -s /usr/local/lib/libmhash.a /usr/lib/libmhash.a
ln -s /usr/local/lib/libmhash.la /usr/lib/libmhash.la
ln -s /usr/local/lib/libmhash.so /usr/lib/libmhash.so
ln -s /usr/local/lib/libmhash.so.2 /usr/lib/libmhash.so.2
ln -s /usr/local/lib/libmhash.so.2.0.1 /usr/lib/libmhash.so.2.0.1

tar zxvf mcrypt-2.6.8.tar.gz
cd mcrypt-2.6.8/
/sbin/ldconfig
./configure
make
make install
cd ../


　　2、编译安装MySQL 5.1.35
/usr/sbin/groupadd mysql
/usr/sbin/useradd -g mysql mysql
tar zxvf mysql-5.1.35.tar.gz
cd mysql-5.1.35/
./configure --prefix=/usr/local/webserver/mysql/ --enable-assembler --with-extra-charsets=complex --enable-thread-safe-client --with-big-tables --with-readline --with-ssl --with-embedded-server --enable-local-infile --with-plugins=innobase
make &amp;&amp; make install
chmod +w /usr/local/webserver/mysql
chown -R mysql:mysql /usr/local/webserver/mysql
cd ../


　　附：以下为附加步骤，如果你想在这台服务器上运行MySQL数据库，则执行以下两步。如果你只是希望让PHP支持MySQL扩展库，能够连接其他服务器上的MySQL数据库，那么，以下两步无需执行。

　　①、创建MySQL数据库存放目录
mkdir -p /data0/mysql/3306/data/
chown -R mysql:mysql /data0/mysql/


　　②、以mysql用户帐号的身份建立数据表：
/usr/local/webserver/mysql/bin/mysql_install_db --basedir=/usr/local/webserver/mysql --datadir=/data0/mysql/3306/data --user=mysql


　　③、创建my.cnf配置文件：
vi /data0/mysql/3306/my.cnf

　　输入以下内容：
引用
[client]
default-character-set = utf8
port    = 3306
socket  = /tmp/mysql.sock

[mysql]
prompt=&quot;(\u:blog.s135.com:)[\d]&gt; &quot;
no-auto-rehash

[mysqld]
#default-character-set = utf8
user    = mysql
port    = 3306
socket  = /tmp/mysql.sock
basedir = /usr/local/webserver/mysql
datadir = /data0/mysql/3306/data
open_files_limit    = 10240
back_log = 600
max_connections = 3000
max_connect_errors = 6000
table_cache = 614
external-locking = FALSE
max_allowed_packet = 32M
sort_buffer_size = 2M
join_buffer_size = 2M
thread_cache_size = 300
thread_concurrency = 8
query_cache_size = 32M
query_cache_limit = 2M
query_cache_min_res_unit = 2k
default-storage-engine = MyISAM
default_table_type = MyISAM
thread_stack = 192K
transaction_isolation = READ-COMMITTED
tmp_table_size = 246M
max_heap_table_size = 246M
long_query_time = 1
log_long_format
log-bin = /data0/mysql/3306/binlog
binlog_cache_size = 4M
binlog_format = MIXED
max_binlog_cache_size = 8M
max_binlog_size = 512M
expire_logs_days = 7
key_buffer_size = 256M
read_buffer_size = 1M
read_rnd_buffer_size = 16M
bulk_insert_buffer_size = 64M
myisam_sort_buffer_size = 128M
myisam_max_sort_file_size = 10G
myisam_max_extra_sort_file_size = 10G
myisam_repair_threads = 1
myisam_recover

skip-name-resolve
master-connect-retry = 10
slave-skip-errors = 1032,1062,126,1114,1146,1048,1396

server-id = 1

innodb_additional_mem_pool_size = 16M
innodb_buffer_pool_size = 2048M
innodb_data_file_path = ibdata1:1024M:autoextend
innodb_file_io_threads = 4
innodb_thread_concurrency = 8
innodb_flush_log_at_trx_commit = 2
innodb_log_buffer_size = 16M
innodb_log_file_size = 128M
innodb_log_files_in_group = 3
innodb_max_dirty_pages_pct = 90
innodb_lock_wait_timeout = 120
innodb_file_per_table = 0
[mysqldump]
quick
max_allowed_packet = 32M


　　④、创建管理MySQL数据库的shell脚本：
vi /data0/mysql/3306/mysql

　　输入以下内容（这里的用户名admin和密码12345678接下来的步骤会创建）：
view plainprint?
#!/bin/sh  
  
mysql_port=3306  
mysql_username=&quot;admin&quot;  
mysql_password=&quot;12345678&quot;  
  
function_start_mysql()  
{  
    printf &quot;Starting MySQL...\n&quot;  
    /bin/sh /usr/local/webserver/mysql/bin/mysqld_safe --defaults-file=/data0/mysql/${mysql_port}/my.cnf 2&gt;&amp;1 &gt; /dev/null &amp;  
}  
  
function_stop_mysql()  
{  
    printf &quot;Stoping MySQL...\n&quot;  
    /usr/local/webserver/mysql/bin/mysqladmin -u ${mysql_username} -p${mysql_password} -S /tmp/mysql.sock shutdown  
}  
  
function_restart_mysql()  
{  
    printf &quot;Restarting MySQL...\n&quot;  
    function_stop_mysql  
    sleep 5  
    function_start_mysql  
}  
  
function_kill_mysql()  
{  
    kill -9 $(ps -ef | grep 'bin/mysqld_safe' | grep ${mysql_port} | awk '{printf $2}')  
    kill -9 $(ps -ef | grep 'libexec/mysqld' | grep ${mysql_port} | awk '{printf $2}')  
}  
  
if [ &quot;$1&quot; = &quot;start&quot; ]; then  
    function_start_mysql  
elif [ &quot;$1&quot; = &quot;stop&quot; ]; then  
    function_stop_mysql  
elif [ &quot;$1&quot; = &quot;restart&quot; ]; then  
function_restart_mysql  
elif [ &quot;$1&quot; = &quot;kill&quot; ]; then  
function_kill_mysql  
else  
    printf &quot;Usage: /data0/mysql/${mysql_port}/mysql {start|stop|restart|kill}\n&quot;  
fi  

　　⑤、赋予shell脚本可执行权限：
chmod +x /data0/mysql/3306/mysql


　　⑥、启动MySQL：
/data0/mysql/3306/mysql start


　　⑦、通过命令行登录管理MySQL服务器（提示输入密码时直接回车）：
/usr/local/webserver/mysql/bin/mysql -u root -p -S /tmp/mysql.sock


　　⑧、输入以下SQL语句，创建一个具有root权限的用户（admin）和密码（12345678）：
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'localhost' IDENTIFIED BY '12345678';
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'127.0.0.1' IDENTIFIED BY '12345678';


　　⑨、（可选）停止MySQL：
/data0/mysql/3306/mysql stop


　　3、编译安装PHP（FastCGI模式）
tar zxvf php-5.2.10.tar.gz
gzip -cd php-5.2.10-fpm-0.5.11.diff.gz | patch -d php-5.2.10 -p1
cd php-5.2.10/
./configure --prefix=/usr/local/webserver/php --with-config-file-path=/usr/local/webserver/php/etc --with-mysql=/usr/local/webserver/mysql --with-mysqli=/usr/local/webserver/mysql/bin/mysql_config --with-iconv-dir=/usr/local --with-freetype-dir --with-jpeg-dir --with-png-dir --with-zlib --with-libxml-dir=/usr --enable-xml --disable-rpath --enable-discard-path --enable-safe-mode --enable-bcmath --enable-shmop --enable-sysvsem --enable-inline-optimization --with-curl --with-curlwrappers --enable-mbregex --enable-fastcgi --enable-fpm --enable-force-cgi-redirect --enable-mbstring --with-mcrypt --with-gd --enable-gd-native-ttf --with-openssl --with-mhash --enable-pcntl --enable-sockets --with-ldap --with-ldap-sasl --with-xmlrpc --enable-zip --enable-soap --without-pear
make ZEND_EXTRA_LIBS='-liconv'
make install
cp php.ini-dist /usr/local/webserver/php/etc/php.ini
cd ../


　　4、编译安装PHP5扩展模块
tar zxvf memcache-2.2.5.tgz
cd memcache-2.2.5/
/usr/local/webserver/php/bin/phpize
./configure --with-php-config=/usr/local/webserver/php/bin/php-config
make
make install
cd ../

tar jxvf eaccelerator-0.9.5.3.tar.bz2
cd eaccelerator-0.9.5.3/
/usr/local/webserver/php/bin/phpize
./configure --enable-eaccelerator=shared --with-php-config=/usr/local/webserver/php/bin/php-config
make
make install
cd ../

tar zxvf PDO_MYSQL-1.0.2.tgz
cd PDO_MYSQL-1.0.2/
/usr/local/webserver/php/bin/phpize
./configure --with-php-config=/usr/local/webserver/php/bin/php-config --with-pdo-mysql=/usr/local/webserver/mysql
make
make install
cd ../

tar zxvf ImageMagick.tar.gz
cd ImageMagick-6.5.1-2/
./configure
make
make install
cd ../

tar zxvf imagick-2.2.2.tgz
cd imagick-2.2.2/
/usr/local/webserver/php/bin/phpize
./configure --with-php-config=/usr/local/webserver/php/bin/php-config
make
make install
cd ../


　　5、修改php.ini文件
　　手工修改：查找/usr/local/webserver/php/etc/php.ini中的extension_dir = &quot;./&quot;
　　修改为extension_dir = &quot;/usr/local/webserver/php/lib/php/extensions/no-debug-non-zts-20060613/&quot;
　　并在此行后增加以下几行，然后保存：
　　extension = &quot;memcache.so&quot;
　　extension = &quot;pdo_mysql.so&quot;
　　extension = &quot;imagick.so&quot;

　　再查找output_buffering = Off
　　修改为output_buffering = On

　　自动修改：若嫌手工修改麻烦，可执行以下shell命令，自动完成对php.ini文件的修改：
sed -i 's#extension_dir = &quot;./&quot;#extension_dir = &quot;/usr/local/webserver/php/lib/php/extensions/no-debug-non-zts-20060613/&quot;\nextension = &quot;memcache.so&quot;\nextension = &quot;pdo_mysql.so&quot;\nextension = &quot;imagick.so&quot;\n#' /usr/local/webserver/php/etc/php.ini
sed -i 's#output_buffering = Off#output_buffering = On#' /usr/local/webserver/php/etc/php.ini
sed -i &quot;s#; always_populate_raw_post_data = On#always_populate_raw_post_data = On#g&quot; /usr/local/webserver/php/etc/php.ini


　　6、配置eAccelerator加速PHP：
mkdir -p /usr/local/webserver/eaccelerator_cache
vi /usr/local/webserver/php/etc/php.ini

　　按shift+g键跳到配置文件的最末尾，加上以下配置信息：
引用
[eaccelerator]
zend_extension=&quot;/usr/local/webserver/php/lib/php/extensions/no-debug-non-zts-20060613/eaccelerator.so&quot;
eaccelerator.shm_size=&quot;64&quot;
eaccelerator.cache_dir=&quot;/usr/local/webserver/eaccelerator_cache&quot;
eaccelerator.enable=&quot;1&quot;
eaccelerator.optimizer=&quot;1&quot;
eaccelerator.check_mtime=&quot;1&quot;
eaccelerator.debug=&quot;0&quot;
eaccelerator.filter=&quot;&quot;
eaccelerator.shm_max=&quot;0&quot;
eaccelerator.shm_ttl=&quot;3600&quot;
eaccelerator.shm_prune_period=&quot;3600&quot;
eaccelerator.shm_only=&quot;0&quot;
eaccelerator.compress=&quot;1&quot;
eaccelerator.compress_level=&quot;9&quot;



　　7、创建www用户和组，以及供blog.s135.com和www.s135.com两个虚拟主机使用的目录：
/usr/sbin/groupadd www
/usr/sbin/useradd -g www www
mkdir -p /data0/htdocs/blog
chmod +w /data0/htdocs/blog
chown -R www:www /data0/htdocs/blog
mkdir -p /data0/htdocs/www
chmod +w /data0/htdocs/www
chown -R www:www /data0/htdocs/www


　　8、创建php-fpm配置文件（php-fpm是为PHP打的一个FastCGI管理补丁，可以平滑变更php.ini配置而无需重启php-cgi）：
　　在/usr/local/webserver/php/etc/目录中创建php-fpm.conf文件：
rm -f /usr/local/webserver/php/etc/php-fpm.conf
vi /usr/local/webserver/php/etc/php-fpm.conf

　　输入以下内容（如果您安装 Nginx + PHP 用于程序调试，请将以下的&lt;value name=&quot;display_errors&quot;&gt;0&lt;/value&gt;改为&lt;value name=&quot;display_errors&quot;&gt;1&lt;/value&gt;，以便显示PHP错误信息，否则，Nginx 会报状态为500的空白错误页）：
view plainprint?
&lt;?xml version=&quot;1.0&quot; ?&gt;  
&lt;configuration&gt;  
  
  All relative paths in this config are relative to php's install prefix  
  
  &lt;section name=&quot;global_options&quot;&gt;  
  
    Pid file  
    &lt;value name=&quot;pid_file&quot;&gt;/usr/local/webserver/php/logs/php-fpm.pid&lt;/value&gt;  
  
    Error log file  
    &lt;value name=&quot;error_log&quot;&gt;/usr/local/webserver/php/logs/php-fpm.log&lt;/value&gt;  
  
    Log level  
    &lt;value name=&quot;log_level&quot;&gt;notice&lt;/value&gt;  
  
    When this amount of php processes exited with SIGSEGV or SIGBUS ...  
    &lt;value name=&quot;emergency_restart_threshold&quot;&gt;10&lt;/value&gt;  
  
    ... in a less than this interval of time, a graceful restart will be initiated.  
    Useful to work around accidental curruptions in accelerator's shared memory.  
    &lt;value name=&quot;emergency_restart_interval&quot;&gt;1m&lt;/value&gt;  
  
    Time limit on waiting child's reaction on signals from master  
    &lt;value name=&quot;process_control_timeout&quot;&gt;5s&lt;/value&gt;  
  
    Set to 'no' to debug fpm  
    &lt;value name=&quot;daemonize&quot;&gt;yes&lt;/value&gt;  
  
  &lt;/section&gt;  
  
  &lt;workers&gt;  
  
    &lt;section name=&quot;pool&quot;&gt;  
  
      Name of pool. Used in logs and stats.  
      &lt;value name=&quot;name&quot;&gt;default&lt;/value&gt;  
  
      Address to accept fastcgi requests on.  
      Valid syntax is 'ip.ad.re.ss:port' or just 'port' or '/path/to/unix/socket'  
      &lt;value name=&quot;listen_address&quot;&gt;127.0.0.1:9000&lt;/value&gt;  
  
      &lt;value name=&quot;listen_options&quot;&gt;  
  
        Set listen(2) backlog  
        &lt;value name=&quot;backlog&quot;&gt;-1&lt;/value&gt;  
  
        Set permissions for unix socket, if one used.  
        In Linux read/write permissions must be set in order to allow connections from web server.  
        Many BSD-derrived systems allow connections regardless of permissions.  
        &lt;value name=&quot;owner&quot;&gt;&lt;/value&gt;  
        &lt;value name=&quot;group&quot;&gt;&lt;/value&gt;  
        &lt;value name=&quot;mode&quot;&gt;0666&lt;/value&gt;  
      &lt;/value&gt;  
  
      Additional php.ini defines, specific to this pool of workers.  
      &lt;value name=&quot;php_defines&quot;&gt;  
        &lt;value name=&quot;sendmail_path&quot;&gt;/usr/sbin/sendmail -t -i&lt;/value&gt;  
        &lt;value name=&quot;display_errors&quot;&gt;1&lt;/value&gt;  
      &lt;/value&gt;  
  
      Unix user of processes  
        &lt;value name=&quot;user&quot;&gt;www&lt;/value&gt;  
  
      Unix group of processes  
        &lt;value name=&quot;group&quot;&gt;www&lt;/value&gt;  
  
      Process manager settings  
      &lt;value name=&quot;pm&quot;&gt;  
  
        Sets style of controling worker process count.  
        Valid values are 'static' and 'apache-like'  
        &lt;value name=&quot;style&quot;&gt;static&lt;/value&gt;  
  
        Sets the limit on the number of simultaneous requests that will be served.  
        Equivalent to Apache MaxClients directive.  
        Equivalent to PHP_FCGI_CHILDREN environment in original php.fcgi  
        Used with any pm_style.  
        &lt;value name=&quot;max_children&quot;&gt;128&lt;/value&gt;  
  
        Settings group for 'apache-like' pm style  
        &lt;value name=&quot;apache_like&quot;&gt;  
  
          Sets the number of server processes created on startup.  
          Used only when 'apache-like' pm_style is selected  
          &lt;value name=&quot;StartServers&quot;&gt;20&lt;/value&gt;  
  
          Sets the desired minimum number of idle server processes.  
          Used only when 'apache-like' pm_style is selected  
          &lt;value name=&quot;MinSpareServers&quot;&gt;5&lt;/value&gt;  
  
          Sets the desired maximum number of idle server processes.  
          Used only when 'apache-like' pm_style is selected  
          &lt;value name=&quot;MaxSpareServers&quot;&gt;35&lt;/value&gt;  
  
        &lt;/value&gt;  
  
      &lt;/value&gt;  
  
      The timeout (in seconds) for serving a single request after which the worker process will be terminated  
      Should be used when 'max_execution_time' ini option does not stop script execution for some reason  
      '0s' means 'off'  
      &lt;value name=&quot;request_terminate_timeout&quot;&gt;0s&lt;/value&gt;  
  
      The timeout (in seconds) for serving of single request after which a php backtrace will be dumped to slow.log file  
      '0s' means 'off'  
      &lt;value name=&quot;request_slowlog_timeout&quot;&gt;0s&lt;/value&gt;  
  
      The log file for slow requests  
      &lt;value name=&quot;slowlog&quot;&gt;logs/slow.log&lt;/value&gt;  
  
      Set open file desc rlimit  
      &lt;value name=&quot;rlimit_files&quot;&gt;51200&lt;/value&gt;  
  
      Set max core size rlimit  
      &lt;value name=&quot;rlimit_core&quot;&gt;0&lt;/value&gt;  
  
      Chroot to this directory at the start, absolute path  
      &lt;value name=&quot;chroot&quot;&gt;&lt;/value&gt;  
  
      Chdir to this directory at the start, absolute path  
      &lt;value name=&quot;chdir&quot;&gt;&lt;/value&gt;  
  
      Redirect workers' stdout and stderr into main error log.  
      If not set, they will be redirected to /dev/null, according to FastCGI specs  
      &lt;value name=&quot;catch_workers_output&quot;&gt;yes&lt;/value&gt;  
  
      How much requests each process should execute before respawn.  
      Useful to work around memory leaks in 3rd party libraries.  
      For endless request processing please specify 0  
      Equivalent to PHP_FCGI_MAX_REQUESTS  
      &lt;value name=&quot;max_requests&quot;&gt;102400&lt;/value&gt;  
  
      Comma separated list of ipv4 addresses of FastCGI clients that allowed to connect.  
      Equivalent to FCGI_WEB_SERVER_ADDRS environment in original php.fcgi (5.2.2+)  
      Makes sense only with AF_INET listening socket.  
      &lt;value name=&quot;allowed_clients&quot;&gt;127.0.0.1&lt;/value&gt;  
  
      Pass environment variables like LD_LIBRARY_PATH  
      All $VARIABLEs are taken from current environment  
      &lt;value name=&quot;environment&quot;&gt;  
        &lt;value name=&quot;HOSTNAME&quot;&gt;$HOSTNAME&lt;/value&gt;  
        &lt;value name=&quot;PATH&quot;&gt;/usr/local/bin:/usr/bin:/bin&lt;/value&gt;  
        &lt;value name=&quot;TMP&quot;&gt;/tmp&lt;/value&gt;  
        &lt;value name=&quot;TMPDIR&quot;&gt;/tmp&lt;/value&gt;  
        &lt;value name=&quot;TEMP&quot;&gt;/tmp&lt;/value&gt;  
        &lt;value name=&quot;OSTYPE&quot;&gt;$OSTYPE&lt;/value&gt;  
        &lt;value name=&quot;MACHTYPE&quot;&gt;$MACHTYPE&lt;/value&gt;  
        &lt;value name=&quot;MALLOC_CHECK_&quot;&gt;2&lt;/value&gt;  
      &lt;/value&gt;  
  
    &lt;/section&gt;  
  
  &lt;/workers&gt;  
  
&lt;/configuration&gt;  
　　9、启动php-cgi进程，监听127.0.0.1的9000端口，进程数为200（如果服务器内存小于3GB，可以只开启64个进程），用户为www：
ulimit -SHn 51200
/usr/local/webserver/php/sbin/php-fpm start

　　注：/usr/local/webserver/php/sbin/php-fpm还有其他参数，包括：start|stop|quit|restart|reload|logrotate，修改php.ini后不重启php-cgi，重新加载配置文件使用reload。


　　三、安装Nginx 0.7.61
　　1、安装Nginx所需的pcre库：
tar zxvf pcre-7.9.tar.gz
cd pcre-7.9/
./configure
make &amp;&amp; make install
cd ../


　　2、安装Nginx
tar zxvf nginx-0.7.61.tar.gz
cd nginx-0.7.61/
./configure --user=www --group=www --prefix=/usr/local/webserver/nginx --with-http_stub_status_module --with-http_ssl_module
make &amp;&amp; make install
cd ../


　　3、创建Nginx日志目录
mkdir -p /data1/logs
chmod +w /data1/logs
chown -R www:www /data1/logs


　　4、创建Nginx配置文件
　　①、在/usr/local/webserver/nginx/conf/目录中创建nginx.conf文件：
rm -f /usr/local/webserver/nginx/conf/nginx.conf
vi /usr/local/webserver/nginx/conf/nginx.conf

　　输入以下内容：
引用
user  www www;

worker_processes 8;

error_log  /data1/logs/nginx_error.log  crit;

pid        /usr/local/webserver/nginx/nginx.pid;

#Specifies the value for maximum file descriptors that can be opened by this process. 
worker_rlimit_nofile 51200;

events 
{
  use epoll;
  worker_connections 51200;
}

http 
{
  include       mime.types;
  default_type  application/octet-stream;

  #charset  gb2312;
      
  server_names_hash_bucket_size 128;
  client_header_buffer_size 32k;
  large_client_header_buffers 4 32k;
  client_max_body_size 8m;
      
  sendfile on;
  tcp_nopush     on;

  keepalive_timeout 60;

  tcp_nodelay on;

  fastcgi_connect_timeout 300;
  fastcgi_send_timeout 300;
  fastcgi_read_timeout 300;
  fastcgi_buffer_size 64k;
  fastcgi_buffers 4 64k;
  fastcgi_busy_buffers_size 128k;
  fastcgi_temp_file_write_size 128k;

  gzip on;
  gzip_min_length  1k;
  gzip_buffers     4 16k;
  gzip_http_version 1.0;
  gzip_comp_level 2;
  gzip_types       text/plain application/x-javascript text/css application/xml;
  gzip_vary on;

  #limit_zone  crawler  $binary_remote_addr  10m;

  server
  {
    listen       80;
    server_name  blog.s135.com;
    index index.html index.htm index.php;
    root  /data0/htdocs/blog;

    #limit_conn   crawler  20;    
                             
    location ~ .*\.(php|php5)?$
    {      
      #fastcgi_pass  unix:/tmp/php-cgi.sock;
      fastcgi_pass  127.0.0.1:9000;
      fastcgi_index index.php;
      include fcgi.conf;
    }
    
    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
    {
      expires      30d;
    }

    location ~ .*\.(js|css)?$
    {
      expires      1h;
    }    

    log_format  access  '$remote_addr - $remote_user [$time_local] &quot;$request&quot; '
              '$status $body_bytes_sent &quot;$http_referer&quot; '
              '&quot;$http_user_agent&quot; $http_x_forwarded_for';
    access_log  /data1/logs/access.log  access;
      }

  server
  {
    listen       80;
    server_name  www.s135.com;
    index index.html index.htm index.php;
    root  /data0/htdocs/www;

    location ~ .*\.(php|php5)?$
    {      
      #fastcgi_pass  unix:/tmp/php-cgi.sock;
      fastcgi_pass  127.0.0.1:9000;
      fastcgi_index index.php;
      include fcgi.conf;
    }

    log_format  wwwlogs  '$remote_addr - $remote_user [$time_local] &quot;$request&quot; '
               '$status $body_bytes_sent &quot;$http_referer&quot; '
               '&quot;$http_user_agent&quot; $http_x_forwarded_for';
    access_log  /data1/logs/wwwlogs.log  wwwlogs;
  }

  server
  {
    listen  80;
    server_name  status.blog.s135.com;

    location / {
    stub_status on;
    access_log   off;
    }
  }
}


　　②、在/usr/local/webserver/nginx/conf/目录中创建fcgi.conf文件：
vi /usr/local/webserver/nginx/conf/fcgi.conf

　　输入以下内容：
引用
fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx;

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;


　　5、启动Nginx
ulimit -SHn 51200
/usr/local/webserver/nginx/sbin/nginx


　　四、配置开机自动启动Nginx + PHP
vi /etc/rc.local

　　在末尾增加以下内容：
引用
ulimit -SHn 51200
/usr/local/webserver/php/sbin/php-fpm start
/usr/local/webserver/nginx/sbin/nginx


　　五、优化Linux内核参数
vi /etc/sysctl.conf

　　在末尾增加以下内容：
引用
# Add
net.ipv4.tcp_max_syn_backlog = 65536
net.core.netdev_max_backlog =  32768
net.core.somaxconn = 32768

net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2

net.ipv4.tcp_tw_recycle = 1
#net.ipv4.tcp_tw_len = 1
net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_mem = 94500000 915000000 927000000
net.ipv4.tcp_max_orphans = 3276800

#net.ipv4.tcp_fin_timeout = 30
#net.ipv4.tcp_keepalive_time = 120
net.ipv4.ip_local_port_range = 1024  65535


　　使配置立即生效：
/sbin/sysctl -p


　　六、在不停止Nginx服务的情况下平滑变更Nginx配置
　　1、修改/usr/local/webserver/nginx/conf/nginx.conf配置文件后，请执行以下命令检查配置文件是否正确：
/usr/local/webserver/nginx/sbin/nginx -t

　　如果屏幕显示以下两行信息，说明配置文件正确：
　　the configuration file /usr/local/webserver/nginx/conf/nginx.conf syntax is ok
　　the configuration file /usr/local/webserver/nginx/conf/nginx.conf was tested successfully

　　2、这时，输入以下命令查看Nginx主进程号：
ps -ef | grep &quot;nginx: master process&quot; | grep -v &quot;grep&quot; | awk -F ' ' '{print $2}'

　　屏幕显示的即为Nginx主进程号，例如：
　　6302
　　这时，执行以下命令即可使修改过的Nginx配置文件生效：
kill -HUP 6302

　　或者无需这么麻烦，找到Nginx的Pid文件：
kill -HUP `cat /usr/local/webserver/nginx/nginx.pid`


　　七、编写每天定时切割Nginx日志的脚本
　　1、创建脚本/usr/local/webserver/nginx/sbin/cut_nginx_log.sh
vi /usr/local/webserver/nginx/sbin/cut_nginx_log.sh

　　输入以下内容：
引用
#!/bin/bash
# This script run at 00:00

# The Nginx logs path
logs_path=&quot;/usr/local/webserver/nginx/logs/&quot;

mkdir -p ${logs_path}$(date -d &quot;yesterday&quot; +&quot;%Y&quot;)/$(date -d &quot;yesterday&quot; +&quot;%m&quot;)/
mv ${logs_path}access.log ${logs_path}$(date -d &quot;yesterday&quot; +&quot;%Y&quot;)/$(date -d &quot;yesterday&quot; +&quot;%m&quot;)/access_$(date -d &quot;yesterday&quot; +&quot;%Y%m%d&quot;).log
kill -USR1 `cat /usr/local/webserver/nginx/nginx.pid`


　　2、设置crontab，每天凌晨00:00切割nginx访问日志
crontab -e

　　输入以下内容：
引用
00 00 * * * /bin/bash  /usr/local/webserver/nginx/sbin/cut_nginx_log.sh



　　本文若有小的修改，会第一时间在以下网址发布：
　　http://blog.s135.com/nginx_php_v5/


　　附：文章修改历史

　　● [2009年05月06日] [Version 5.0] 在4.14版本的基础上重新撰写本文，支持PHP 5.2.9，增加MySQL配置过程

　　● [2009年05月10日] [Version 5.1] 增加压力测试方法。

　　● [2009年05月20日] [Version 5.2] Nginx升级到0.7.58版本；PHP编译选项增加：--with-xmlrpc --enable-zip。

　　● [2009年06月10日] [Version 5.3] Nginx升级到0.7.59版本；MySQL升级到5.1.35版本。

　　● [2009年06月26日] [Version 5.4] Nginx升级到0.7.61版本；PHP升级到5.2.10版本；PCRE升级到7.9版本；PHP增加soap扩展；关闭了PHP的PEAR；优化sysctl配置。

　　（全文完）]]></description>
</item><item>
<title><![CDATA[Linux必学的安装登录命令]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=47004</link>
<author>toyboysli</author>
<pubDate>2009/9/5 11:08:41</pubDate>
<description><![CDATA[不同Linux发行版的命令数量不一样，但Linux发行版本最少的命令也有200多个。这里笔者把
比较重要和使用频率最多的命令，按照它们在系统中的作用分成下面六个部分一一介绍。
◆ 安装和登录命令：login、shutdown、halt、reboot、install、mount、umount、chsh
、exit、last；
◆ 文件处理命令：file、mkdir、grep、dd、find、mv、ls、diff、cat、ln；
◆ 系统管理相关命令：df、top、free、quota、at、lp、adduser、groupadd、kill、
crontab；
◆ 网络操作命令：ifconfig、ip、ping、netstat、telnet、ftp、route、rlogin、rcp、
finger、mail、 nslookup；
◆ 系统安全相关命令：passwd、su、umask、chgrp、chmod、chown、chattr、sudo ps、
who；
◆ 其它命令：tar、unzip、gunzip、unarj、mtools、man、unendcode、uudecode。
本文以Mandrake Linux 9.1(Kenrel 2.4.21)为例，介绍Linux下的安装和登录命令。
login
1.作用
login的作用是登录系统，它的使用权限是所有用户。
2.格式
login [name][－p ][－h 主机名称]
3.主要参数
－p:通知login保持现在的环境参数。
－h:用来向远程登录的之间传输用户名。
如果选择用命令行模式登录Linux的话，那么看到的第一个Linux命令就是login：。
一般界面是这样的：
Manddrake Linux release 9.1(Bamboo) for i586
renrel 2.4.21－0.13mdk on i686 / tty1
localhost login:root
password:
上面代码中，第一行是Linux发行版本号，第二行是内核版本号和登录的虚拟控制台，我们
在第三行输入登录名，按“Enter”键在Password后输入账户密码，即可登录系统。出于
安全考虑，输入账户密码时字符不会在屏幕上回显，光标也不移动。
登录后会看到下面这个界面（以超级用户为例）：
[root@localhost root]#
last login:Tue ,Nov 18 10:00:55 on vc/1
上面显示的是登录星期、月、日、时间和使用的虚拟控制台。
4.应用技巧
Linux是一个真正的多用户操作系统，可以同时接受多个用户登录，还允许一个用户进行多
次登录。这是因为Linux和许多版本的Unix一样，提供了虚拟控制台的访问方式，允许用
户在同一时间从控制台（系统的控制台是与系统直接相连的监视器和键盘）进行多次登录。
每个虚拟控制台可以看作是一个独立的工作站，工作台之间可以切换。虚拟控制台的切换可
以通过按下Alt键和一个功能键来实现，通常使用F1-F6 。
例如，用户登录后，按一下“Alt+F2”键，用户就可以看到上面出现的“login:”提示符，
说明用户看到了第二个虚拟控制台。然后只需按“Alt+F1”键，就可以回到第一个虚拟控制
台。 一个新安装的Linux系统允许用户使用“Alt+F1”到“Alt+F6”键来访问前六个虚拟
控制台。虚拟控制台最有用的是，当一个程序出错造成系统死锁时，可以切换到其它虚拟控
制台工作，关闭这个程序。
shutdown
1.作用
shutdown命令的作用是关闭计算机，它的使用权限是超级用户。
2.格式
shutdown [－h][－i][－k][－m][－t]
3.重要参数
－t：在改变到其它运行级别之前，告诉init程序多久以后关机。
－k：并不真正关机，只是送警告信号给每位登录者。
－h：关机后关闭电源。
－c：cancel current process取消目前正在执行的关机程序。所以这个选项当然没有
时间参数，但是可以输入一个用来解释的讯息，而这信息将会送到每位使用者。
－F：在重启计算机时强迫fsck。
－time：设定关机前的时间。
－m: 将系统改为单用户模式。
－i：关机时显示系统信息。
4.命令说明
shutdown命令可以安全地将系统关机。有些用户会使用直接断掉电源的方式来关闭Linux
系统，这是十分危险的。因为Linux与Windows不同，其后台运行着许多进程，所以强制
关机可能会导致进程的数据丢失，使系统处于不稳定的状态，甚至在有的系统中会损坏硬件
设备（硬盘）。在系统关机前使用shutdown命令，系统管理员会通知所有登录的用户系统
将要关闭，并且login指令会被冻结，即新的用户不能再登录。
halt
1.作用
halt命令的作用是关闭系统，它的使用权限是超级用户。
2.格式
halt [－n] [－w] [－d] [－f] [－i] [－p]
3.主要参数说明
－n：防止sync系统调用，它用在用fsck修补根分区之后，以阻止内核用老版本的超级块
覆盖修补过的超级块。
－w：并不是真正的重启或关机,只是写wtmp（/var/log/wtmp）纪录。
－f：没有调用shutdown，而强制关机或重启。
－i：关机（或重启）前，关掉所有的网络接口。
－f：强迫关机，不呼叫shutdown这个指令。
－p: 当关机的时候顺便做关闭电源的动作。
－d：关闭系统，但不留下纪录。　
4.命令说明
halt就是调用shutdown －h。halt执行时，杀死应用进程，执行sync(将存于buffer
中的资料强制写入硬盘中)系统调用，文件系统写操作完成后就会停止内核。若系统的运行级
别为0或6，则关闭系统；否则以shutdown指令（加上－h参数）来取代。　
reboot
1.作用
reboot命令的作用是重新启动计算机，它的使用权限是系统管理者。
2.格式
reboot [－n] [－w] [－d] [－f] [－i]
3.主要参数
－n: 在重开机前不做将记忆体资料写回硬盘的动作。
－w: 并不会真的重开机，只是把记录写到/var/log/wtmp文件里。
－d: 不把记录写到/var/log/wtmp文件里（－n这个参数包含了－d）。
－i: 在重开机之前先把所有与网络相关的装置停止。
install
1.作用
install命令的作用是安装或升级软件或备份数据，它的使用权限是所有用户。
2.格式
(1)install [选项]... 来源 目的地
(2)install [选项]... 来源... 目录
(3)install －d [选项]... 目录...
在前两种格式中，会将&lt;来源&gt;复制至&lt;目的地&gt;或将多个&lt;来源&gt;文件复制至已存在的&lt;目录&gt;，
同时设定权限模式及所有者/所属组。在第三种格式中，会创建所有指定的目录及它们的主目
录。长选项必须用的参数在使用短选项时也是必须的。
3.主要参数
－－backup[=CONTROL]：为每个已存在的目的地文件进行备份。
－b：类似 －－backup，但不接受任何参数。
－c：(此选项不作处理)。
－d，－－directory：所有参数都作为目录处理，而且会创建指定目录的所有主目录。
－D：创建&lt;目的地&gt;前的所有主目录，然后将&lt;来源&gt;复制至 &lt;目的地&gt;；在第一种使用格式中
有用。
－g，－－group=组：自行设定所属组，而不是进程目前的所属组。
－m，－－mode=模式：自行设定权限模式 (像chmod)，而不是rwxr－xr－x。
－o，－－owner=所有者：自行设定所有者 (只适用于超级用户)。
－p，－－preserve－timestamps：以&lt;来源&gt;文件的访问/修改时间作为相应的目的地文
件的时间属性。
－s，－－strip：用strip命令删除symbol table，只适用于第一及第二种使用格式。
－S，－－suffix=后缀：自行指定备份文件的&lt;后缀&gt;。
－v，－－verbose：处理每个文件/目录时印出名称。
－－help：显示此帮助信息并离开。
－－version：显示版本信息并离开。
mount
1.作用
mount命令的作用是加载文件系统，它的用权限是超级用户或/etc/fstab中允许的使用者。
2.格式
mount －a [－fv] [－t vfstype] [－n] [－rw] [－F] device dir
3.主要参数
－h：显示辅助信息。
－v：显示信息，通常和－f用来除错。
－a：将/etc/fstab中定义的所有文件系统挂上。
－F：这个命令通常和－a一起使用，它会为每一个mount的动作产生一个行程负责执行。
在系统需要挂上大量NFS文件系统时可以加快加载的速度。
－f：通常用于除错。它会使mount不执行实际挂上的动作，而是模拟整个挂上的过程，通常会和－v一起使用。
－t vfstype：显示被加载文件系统的类型。
－n：一般而言，mount挂上后会在/etc/mtab中写入一笔资料，在系统中没有可写入文件
系统的情况下，可以用这个选项取消这个动作。
4.应用技巧
在Linux和Unix系统上，所有文件都是作为一个大型树（以/为根）的一部分访问的。要
访问CD-ROM上的文件，需要将CD-ROM设备挂装在文件树中的某个挂装点。如果发行版安
装了自动挂装包，那么这个步骤可自动进行。在Linux中，如果要使用硬盘、光驱等储存设
备 ，就得先将它加载，当储存设备挂上了之后，就可以把它当成一个目录来访问。挂上一个
设备使用mount命令。 在使用mount这个指令时，至少要先知道下列三种信息：要加载对
象的文件系统类型、要加载对象的设备名称及要将设备加载到哪个目录下。
（1）Linux可以识别的文件系统
◆ Windows 95/98常用的FAT 32文件系统：vfat ；
◆ Win NT/2000 的文件系统：ntfs ；
◆ OS/2用的文件系统：hpfs；
◆ Linux用的文件系统：ext2、ext3；
◆ CD-ROM光盘用的文件系统：iso9660。
虽然vfat是指FAT 32系统，但事实上它也兼容FAT 16的文件系统类型。
（2）确定设备的名称
在Linux中，设备名称通常都存在/dev里。这些设备名称的命名都是有规则的，可以用“
推理”的方式把设备名称找出来。例如，/dev/hda1这个IDE设备，hd是Hard Disk(硬
盘)的，sd是SCSI Device，fd是Floppy Device(或是Floppy Disk?)。a代表第一
个设备，通常IDE接口可以接上4个IDE设备(比如4块硬盘)。所以要识别IDE硬盘的方
法分别就是hda、hdb、hdc、hdd。hda1 中的“1”代表hda 的第一个硬盘分区
(partition)，hda2代表hda的第二主分区，第一个逻辑分区从hda5开始，依此类推。
此外，可以直接检查/var/log/messages文件，在该文件中可以找到计算机开机后系统已
辨认出来的设备代号。
（3）查找挂接点
在决定将设备挂接之前，先要查看一下计算机是不是有个/mnt的空目录，该目录就是专门用来当作挂载点(Mount Point) 的目录。建议在/mnt 里建几个/mnt/cdrom
、/mnt/floppy、/mnt/mo等目录，当作目录的专用挂载点。举例而言，如要挂载下列5
个设备，其执行指令可能如下 (假设都是Linux的ext2系统，如果是Windows XX请将
ext2改成vfat)：
软盘 ===&gt;mount －t ext2 /dev/fd0 /mnt/floppy
cdrom ===&gt;mount －t iso9660 /dev/hdc /mnt/cdrom
SCSI cdrom ===&gt;mount －t iso9660 /dev/sdb /mnt/scdrom
SCSI cdr ===&gt;mount －t iso9660 /dev/sdc /mnt/scdr
不过目前大多数较新的Linux 发行版本（包括红旗 Linux、中软Linux、Mandrake
Linux等）都可以自动挂装文件系统。
umount
1.作用
umount命令的作用是卸载一个文件系统，它的使用权限是超级用户或/etc/fstab中允许
的使用者。
2.格式
unmount －a [－fFnrsvw] [－t vfstype] [－n] [－rw] [－F] device dir
3.使用说明
umount命令是mount命令的逆操作，它的参数和使用方法和mount命令是一样的。Linux
挂装CD-ROM后，会锁定CD—ROM，这样就不能用CD-ROM面板上的Eject按钮弹出它。但
是，当不再需要光盘时，如果已将/cdrom作为符号链接，请使用umount/cdrom来卸装它。
仅当无用户正在使用光盘时，该命令才会成功。该命令包括了将带有当前工作目录当作该光
盘中的目录的终端窗口。
chsh
1.作用
chsh命令的作用是更改使用者shell设定，它的使用权限是所有使用者。
2.格式
chsh [ －s ] [ －list] [ －－help ] [ －v ] [ username ]
3.主要参数－l：显示系统所有Shell类型。
－v：显示Shell版本号。
4.应用技巧
前面介绍了Linux下有多种Shell，一般缺省的是Bash，如果想更换Shell类型可以使用
chsh命令。先输入账户密码，然后输入新Shell类型，如果操作正确系统会显示“Shell
change”。其界面一般如下：
Changing fihanging shell for cao
Password:
New shell [/bin/bash]: /bin/tcsh
上面代码中，[ ]内是目前使用的Shell。普通用户只能修改自己的Shell，超级用户可以
修改全体用户的Shell。要想查询系统提供哪些Shell，可以使用chsh -l 命令。
exit
1.作用
exit命令的作用是退出系统，它的使用权限是所有用户。
2.格式
exit
3.参数
exit命令没有参数，运行后退出系统进入登录界面。
last
1.作用
last命令的作用是显示近期用户或终端的登录情况，它的使用权限是所有用户。通过last
命令查看该程序的log，管理员可以获知谁曾经或企图连接系统。
2.格式
1ast[—n][－f file][－t tty] [—h 节点][－I —IP][—1][－y][1D]
3.主要参数
－n：指定输出记录的条数。
－f file：指定用文件file作为查询用的log文件。
－t tty：只显示指定的虚拟控制台上登录情况。
－h 节点：只显示指定的节点上的登录情况。
－i IP：只显示指定的IP上登录的情况。
－1：用IP来显示远端地址。
－y：显示记录的年、月、日。
－ID：知道查询的用户名。
－x:显示系统关闭、用户登录和退出的历史。
动手练习
上面介绍了Linux安装和登录命令，下面介绍几个实例，动手练习一下刚才讲过的命令。
1.一次运行多个命令
在一个命令行中可以执行多个命令，用分号将各个命令隔开即可，例如：
＃last －x；halt
上面代码表示在显示系统关闭、用户登录和退出的历史后关闭计算机。
2.利用mount挂装文件系统访问Windows系统
许多Linux发行版本现在都可以自动加载Vfat分区来访问Windows系统，而Red Hat各个版本
都没有自动加载Vfat分区，因此还需要进行手工操作。
mount可以将Windows分区作为Linux的一个“文件”挂接到Linux的一个空文件夹下，从而将
Windows的分区和/mnt这个目录联系起来。因此，只要访问这个文件夹就相当于访问该分区了。首先
要在/mnt下建立winc文件夹，在命令提示符下输入下面命令：
＃mount -t vfat /dev/hda1 /mnt/winc
即表示将Windows的C分区挂到Liunx的/mnt/winc目录下。这时，在/mnt/winc目录下就可
以看到Windows中C盘的内容了。使用类似的方法可以访问Windows系统的D、E盘。在Linux系统
显示Windows的分区一般顺序这样的：hda1为C盘、hda5为D盘、hda6为E盘……以此类推。上述
方法可以查看Windows系统有一个很大的问题，就是Windows中的所有中文文件名或文件夹名全部显
示为问号“？”，而英文却可以正常显示。我们可以通过加入一些参数让它显示中文。还以上面的操作
为例，此时输入命令：
＃mount -t vfat -o iocharset=cp936 /dev/hda1 /mnt/winc
现在它就可以正常显示中文了。
3.使用mount加挂闪盘上的文件系统
在Linux下使用闪盘非常简单。Linux对USB设备有很好的支持，当插入闪盘后，闪盘被识别为一
个SCSI盘，通常输入以下命令：
＃ mount /dev/sda1 /usb
就能够加挂闪盘上的文件系统。]]></description>
</item><item>
<title><![CDATA[转:mysql存储过程语法]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=46924</link>
<author>toyboysli</author>
<pubDate>2009/8/24 16:15:17</pubDate>
<description><![CDATA[mysql 5.0存储过程学习总结

一.创建存储过程
1.基本语法：
create procedure sp_name()
begin
………
end
2.参数传递
二.调用存储过程
1.基本语法：call sp_name()
注意：存储过程名称后面必须加括号，哪怕该存储过程没有参数传递
三.删除存储过程
1.基本语法：
drop procedure sp_name//
2.注意事项
(1)不能在一个存储过程中删除另一个存储过程，只能调用另一个存储过程
四.区块，条件，循环
1.区块定义，常用
begin
……
end;
也可以给区块起别名，如：
lable:begin
………..
end lable;
可以用leave lable;跳出区块，执行区块以后的代码
2.条件语句
if  条件  then 
 statement
 else 
 statement
 end   if ;
3.循环语句
(1).while循环
[ label: ]   WHILE  expression DO

 statements

 END   WHILE   [ label ]  ;

(2).loop循环
[ label: ]  LOOP

 statements

 END  LOOP  [ label ] ;
(3).repeat until循环
[ label: ]  REPEAT

 statements

 UNTIL expression

 END  REPEAT  [ label ]  ;
五.其他常用命令
1.show procedure status
显示数据库中所有存储的存储过程基本信息，包括所属数据库，存储过程名称，创建时间等
2.show create procedure sp_name
显示某一个存储过程的详细信息
 
mysql存储过程中要用到的运算符
mysql存储过程学习总结－操作符

算术运算符
+     加   SET var1=2+2;       4
-     减   SET var2=3-2;       1
*     乘   SET var3=3*2;       6
/     除   SET var4=10/3;      3.3333
DIV   整除 SET var5=10 DIV 3;  3
%     取模 SET var6=10%3 ;     1
比较运算符
&gt;            大于 1&gt;2 False
&lt;            小于 2&lt;1 False
&lt;=           小于等于 2&lt;=2 True
&gt;=           大于等于 3&gt;=2 True
BETWEEN      在两值之间 5 BETWEEN 1 AND 10 True
NOT BETWEEN  不在两值之间 5 NOT BETWEEN 1 AND 10 False
IN           在集合中 5 IN (1,2,3,4) False
NOT IN       不在集合中 5 NOT IN (1,2,3,4) True
=            等于 2=3 False
&lt;&gt;, !=       不等于 2&lt;&gt;3 False
&lt;=&gt;          严格比较两个NULL值是否相等 NULL&lt;=&gt;NULL True
LIKE         简单模式匹配 &quot;Guy Harrison&quot; LIKE &quot;Guy%&quot; True
REGEXP       正则式匹配 &quot;Guy Harrison&quot; REGEXP &quot;[Gg]reg&quot; False
IS NULL      为空 0 IS NULL False
IS NOT NULL  不为空 0 IS NOT NULL True
逻辑运算符
与 (AND)





&lt;!-- @page { size: 8.5in 11in; margin: 0.79in } TD P { margin-bottom: 0in } TH P { margin-bottom: 0in; font-style: italic } P { margin-bottom: 0.08in } --&gt;
 
AND
TRUE
FALSE
NULL
TRUE
TRUE
FALSE
NULL
FALSE
FALSE
FALSE
NULL
NULL
NULL
NULL
NULL
或(OR)
&lt;!-- @page { size: 8.5in 11in; margin: 0.79in } TD P { margin-bottom: 0in } TH P { margin-bottom: 0in; font-style: italic } P { margin-bottom: 0.08in } --&gt;
 
OR
TRUE
FALSE
NULL
TRUE
TRUE
TRUE
TRUE
FALSE
TRUE
FALSE
NULL
NULL
TRUE
NULL
NULL
异或(XOR)
&lt;!-- @page { size: 8.5in 11in; margin: 0.79in } TD P { margin-bottom: 0in } TH P { margin-bottom: 0in; font-style: italic } P { margin-bottom: 0.08in } --&gt;
 
XOR
TRUE
FALSE
NULL
TRUE
FALSE
TRUE
NULL
FALSE
TRUE
FALSE
NULL
NULL
NULL
NULL
NULL
位运算符
|   位或
&amp;   位与
&lt;&lt;  左移位
&gt;&gt;  右移位
~   位非(单目运算，按位取反)
 
mysq存储过程中常用的函数，字符串类型操作，数学类，日期时间类。
mysql存储过程基本函数

一.字符串类  
CHARSET(str) //返回字串字符集
CONCAT (string2  [,... ]) //连接字串
INSTR (string ,substring ) //返回substring首次在string中出现的位置,不存在返回0
LCASE (string2 ) //转换成小写
LEFT (string2 ,length ) //从string2中的左边起取length个字符
LENGTH (string ) //string长度
LOAD_FILE (file_name ) //从文件读取内容
LOCATE (substring , string  [,start_position ] ) 同INSTR,但可指定开始位置
LPAD (string2 ,length ,pad ) //重复用pad加在string开头,直到字串长度为length
LTRIM (string2 ) //去除前端空格
REPEAT (string2 ,count ) //重复count次
REPLACE (str ,search_str ,replace_str ) //在str中用replace_str替换search_str
RPAD (string2 ,length ,pad) //在str后用pad补充,直到长度为length
RTRIM (string2 ) //去除后端空格
STRCMP (string1 ,string2 ) //逐字符比较两字串大小,
SUBSTRING (str , position  [,length ]) //从str的position开始,取length个字符,
注：mysql中处理字符串时，默认第一个字符下标为1 ，即参数position必须大于等于1
mysql&gt; select substring(’abcd’,0,2);
+———————–+
| substring(’abcd’,0,2) |
+———————–+
|                       |
+———————–+
1 row in set (0.00 sec)
mysql&gt; select substring(’abcd’,1,2);
+———————–+
| substring(’abcd’,1,2) |
+———————–+
| ab                    |
+———————–+
1 row in set (0.02 sec)
TRIM([[BOTH|LEADING|TRAILING] [padding] FROM]string2) //去除指定位置的指定字符
UCASE (string2 ) //转换成大写
RIGHT(string2,length) //取string2最后length个字符
SPACE(count) //生成count个空格 
二.数学类
ABS (number2 ) //绝对值
BIN (decimal_number ) //十进制转二进制
CEILING (number2 ) //向上取整
CONV(number2,from_base,to_base) //进制转换
FLOOR (number2 ) //向下取整
FORMAT (number,decimal_places ) //保留小数位数
HEX (DecimalNumber ) //转十六进制
注：HEX()中可传入字符串，则返回其ASC-11码，如HEX(’DEF’)返回4142143
也可以传入十进制整数，返回其十六进制编码，如HEX(25)返回19
LEAST (number , number2  [,..]) //求最小值
MOD (numerator ,denominator ) //求余
POWER (number ,power ) //求指数
RAND([seed]) //随机数
ROUND (number  [,decimals ]) //四舍五入,decimals为小数位数]
注：返回类型并非均为整数，如：
(1)默认变为整形值
mysql&gt; select round(1.23);
+————-+
| round(1.23) |
+————-+
|           1 |
+————-+
1 row in set (0.00 sec)
mysql&gt; select round(1.56);
+————-+
| round(1.56) |
+————-+
|           2 |
+————-+
1 row in set (0.00 sec)
(2)可以设定小数位数，返回浮点型数据 
mysql&gt; select round(1.567,2);
+—————-+
| round(1.567,2) |
+—————-+
|           1.57 |
+—————-+
1 row in set (0.00 sec)
SIGN (number2 ) //返回符号,正负或0
SQRT(number2) //开平方
 
三.日期时间类
ADDTIME (date2 ,time_interval ) //将time_interval加到date2
CONVERT_TZ (datetime2 ,fromTZ ,toTZ ) //转换时区
CURRENT_DATE (  ) //当前日期
CURRENT_TIME (  ) //当前时间
CURRENT_TIMESTAMP (  ) //当前时间戳
DATE (datetime ) //返回datetime的日期部分
DATE_ADD (date2 , INTERVAL d_value d_type ) //在date2中加上日期或时间
DATE_FORMAT (datetime ,FormatCodes ) //使用formatcodes格式显示datetime
DATE_SUB (date2 , INTERVAL d_value d_type ) //在date2上减去一个时间
DATEDIFF (date1 ,date2 ) //两个日期差
DAY (date ) //返回日期的天
DAYNAME (date ) //英文星期
DAYOFWEEK (date ) //星期(1-7) ,1为星期天
DAYOFYEAR (date ) //一年中的第几天
EXTRACT (interval_name  FROM date ) //从date中提取日期的指定部分
MAKEDATE (year ,day ) //给出年及年中的第几天,生成日期串
MAKETIME (hour ,minute ,second ) //生成时间串
MONTHNAME (date ) //英文月份名
NOW (  ) //当前时间
SEC_TO_TIME (seconds ) //秒数转成时间
STR_TO_DATE (string ,format ) //字串转成时间,以format格式显示
TIMEDIFF (datetime1 ,datetime2 ) //两个时间差
TIME_TO_SEC (time ) //时间转秒数]
WEEK (date_time [,start_of_week ]) //第几周
YEAR (datetime ) //年份
DAYOFMONTH(datetime) //月的第几天
HOUR(datetime) //小时
LAST_DAY(date) //date的月的最后日期
MICROSECOND(datetime) //微秒
MONTH(datetime) //月
MINUTE(datetime) //分
 
附:可用在INTERVAL中的类型
DAY ,DAY_HOUR ,DAY_MINUTE ,DAY_SECOND ,HOUR ,HOUR_MINUTE ,HOUR_SECOND ,MINUTE ,MINUTE_SECOND,MONTH ,SECOND ,YEAR 


　与Oracle或者微软的相关数据库不一样，MySQL和IBM的DB2能够遵循存储程序的SQL:2003语法。在理论上这意味着，如果数据库结构相同，存储程序可以在不同数据库中使用。
　　可支持的SQL声明

　　虽然MySQL不能支持存储程序，但它却可以完成很多任务，如表A 所示。除此之外，MySQL的stored procedure documentation(存储过程文档)描述了可用于Oracle的PL/SQL和SQL Server的 T-SQL的很多兼容特性。我对存储过程支持的印象是，它执行比较缓慢，目的是避免任何影响大型软件 开发工程的步骤。
　　表A 
　　声明
　　描述
　　CREATE PROCEDURE
　　建立一个存放在MySQL数据库的表格的存储过程。
　　CREATE FUNCTION
　　建立一个用户自定义的函数，尤其是返回数据的存储过程。
　　ALTER PROCEDURE
　　更改用CREATE PROCEDURE 建立的预先指定的存储过程，其不会影响相关存储过程或存储功能。.
　　ALTER FUNCTION
　　更改用CREATE FUNCTION 建立的预先指定的存储过程，其不会影响相关存储过程或存储功能。.
　　DROP PROCEDURE
　　从MySQL的表格中删除一个或多个存储过程。
　　DROP FUNCTION
　　从MySQL的表格中删除一个或多个存储函数。
　　SHOW CREATE PROCEDURE
　　返回使用CREATE PROCEDURE 建立的预先指定的存储过程的文本。这一声明是SQL:2003规范的一个MySQL扩展。
　　SHOW CREATE FUNCTION
　　返回使用CREATE  FUNCTION建立的预先指定的存储过程的文本。这一声明是SQL:2003规范的一个MySQL扩展。
　　SHOW PROCEDURE STATUS
　　返回一个预先指定的存储过程的特性，包括名称、类型、建立者、建立日期、以及更改日期。这一声明是SQL:2003规范的一个MySQL扩展。
　　SHOW FUNCTION STATUS
　　返回一个预先指定的存储函数的特性，包括名称、类型、建立者、建立日期、以及更改日期。这一声明是SQL:2003规范的一个MySQL扩展。
　　CALL
　　调用一个使用CREATE PROCEDURE建立的预先指定的存储过程。
　　BEGIN ... END
　　包含一组执行的多声明。
　　DECLARE
　　用于指定当地变量、环境、处理器，以及指针。
　　SET
　　用于更改当地和全局服务器变量的值。
　　SELECT ... INTO
　　用于存储显示变量的纵列。
　　OPEN
　　用于打开一个指针。
　　FETCH
　　使用特定指针来获得下一列。
　　CLOSE
　　用于关闭和打开指针。
　　IF
　　一个An if-then-else-end if 声明。
　　CASE ... WHEN
　　一个 case声明的结构
　　LOOP
　　一个简单的循环结构；可以使用LEAVE 语句来退出。
　　LEAVE
　　用于退出IF，CASE，LOOP，REPEAT以及WHILE 语句。
　　ITERATE
　　用于重新开始循环。
　　REPEAT
　　在结束时测试的循环。
　　WHILE
　　在开始时测试的循环。
　　RETURNS
　　返回一个存储过程的值。
　　[/size][/b]
　　MySQL 5.0支持存储过程语句。]]></description>
</item><item>
<title><![CDATA[转：Apache Mina使用手记（四）]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=46904</link>
<author>toyboysli</author>
<pubDate>2009/8/21 16:18:57</pubDate>
<description><![CDATA[上一篇中，我们介绍了如何在mina中编写自己的日志过滤器，这一篇我们自己实现一个编解器。

实际应用当，很多应用系统应用的都不是标准的web service或XML等，比如象中国移动/联通/电信的短信网关程序，都有自己不同的协议实现，并且都是基于TCP/IP的字节流。Mina自带的编解码器实现了TextLineEncoder和TextLineDecoder，可以进行按行的字符串处理，对于象短信网关程序，就要自己实现编解码过滤器了。

我们定义一个简单的基于TCP/IP字节流的协议，实现在客户端和服务端之间的数据包传输。数据包MyProtocalPack有消息头和消息体组成，消息头包括：length（消息包的总长度，数据类型int），flag（消息包标志位，数据类型byte），消息体content是一个字符串，实际实现的时候按byte流处理。源代码如下：

view plaincopy to clipboardprint?
package com.gftech.mytool.mina;  
import com.gftech.util.GFCommon;  
public class MyProtocalPack {  
    private int length;  
    private byte flag;  
    private String content;  
      
    public MyProtocalPack(){  
          
    }  
      
    public MyProtocalPack(byte flag,String content){  
        this.flag=flag;  
        this.content=content;  
        int len1=content==null?0:content.getBytes().length;  
        this.length=5+len1;  
    }  
      
    public MyProtocalPack(byte[] bs){  
        if(bs!=null &amp;&amp; bs.length&gt;=5){  
            length=GFCommon.bytes2int(GFCommon.bytesCopy(bs, 0, 4));  
            flag=bs[4];  
            content=new String(GFCommon.bytesCopy(bs, 5, length-5));  
        }  
    }  
      
    public int getLength() {  
        return length;  
    }  
    public void setLength(int length) {  
        this.length = length;  
    }  
    public byte getFlag() {  
        return flag;  
    }  
    public void setFlag(byte flag) {  
        this.flag = flag;  
    }  
    public String getContent() {  
        return content;  
    }  
    public void setContent(String content) {  
        this.content = content;  
    }  
      
    public String toString(){  
        StringBuffer sb=new StringBuffer();  
        sb.append(&quot; Len:&quot;).append(length);  
        sb.append(&quot; flag:&quot;).append(flag);  
        sb.append(&quot; content:&quot;).append(content);  
        return sb.toString();  
    }  
}  
回过头来，我们先看一下在MinaTimeServer中，如何使用一个文本的编解码过滤器，它是在过滤器链中添加了一个叫ProtocalCodecFilter的类，其中它调用 了一个工厂方法TextLineCodecFactory的工厂类，创建具休的TextLineEncoder和TextLineDecoder编码和解 码器。我们看一下具体的源代码：
view plaincopy to clipboardprint?
acceptor.getFilterChain().addLast(&quot;codec&quot;, new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName(&quot;GBK&quot;))));  

view plaincopy to clipboardprint?
package org.apache.mina.filter.codec.textline;  
import java.nio.charset.Charset;  
import org.apache.mina.core.buffer.BufferDataException;  
import org.apache.mina.core.session.IoSession;  
import org.apache.mina.filter.codec.ProtocolCodecFactory;  
import org.apache.mina.filter.codec.ProtocolDecoder;  
import org.apache.mina.filter.codec.ProtocolEncoder;  
/** 
 * A {@link ProtocolCodecFactory} that performs encoding and decoding between 
 * a text line data and a Java string object.  This codec is useful especially 
 * when you work with a text-based protocols such as SMTP and IMAP. 
 * 
 * @author The Apache MINA Project (dev@mina.apache.org) 
 * @version $Rev$, $Date$ 
 */  
public class TextLineCodecFactory implements ProtocolCodecFactory {  
    private final TextLineEncoder encoder;  
    private final TextLineDecoder decoder;  
    /** 
     * Creates a new instance with the current default {@link Charset}. 
     */  
    public TextLineCodecFactory() {  
        this(Charset.defaultCharset());  
    }  
    /** 
     * Creates a new instance with the specified {@link Charset}.  The 
     * encoder uses a UNIX {@link LineDelimiter} and the decoder uses 
     * the AUTO {@link LineDelimiter}. 
     * 
     * @param charset 
     *  The charset to use in the encoding and decoding 
     */  
    public TextLineCodecFactory(Charset charset) {  
        encoder = new TextLineEncoder(charset, LineDelimiter.UNIX);  
        decoder = new TextLineDecoder(charset, LineDelimiter.AUTO);  
    }  
    /** 
     * Creates a new instance of TextLineCodecFactory.  This constructor 
     * provides more flexibility for the developer. 
     * 
     * @param charset 
     *  The charset to use in the encoding and decoding 
     * @param encodingDelimiter 
     *  The line delimeter for the encoder 
     * @param decodingDelimiter 
     *  The line delimeter for the decoder 
     */  
    public TextLineCodecFactory(Charset charset,  
            String encodingDelimiter, String decodingDelimiter) {  
        encoder = new TextLineEncoder(charset, encodingDelimiter);  
        decoder = new TextLineDecoder(charset, decodingDelimiter);  
    }  
    /** 
     * Creates a new instance of TextLineCodecFactory.  This constructor 
     * provides more flexibility for the developer. 
     * 
     * @param charset 
     *  The charset to use in the encoding and decoding 
     * @param encodingDelimiter 
     *  The line delimeter for the encoder 
     * @param decodingDelimiter 
     *  The line delimeter for the decoder 
     */  
    public TextLineCodecFactory(Charset charset,  
            LineDelimiter encodingDelimiter, LineDelimiter decodingDelimiter) {  
        encoder = new TextLineEncoder(charset, encodingDelimiter);  
        decoder = new TextLineDecoder(charset, decodingDelimiter);  
    }  
    public ProtocolEncoder getEncoder(IoSession session) {  
        return encoder;  
    }  
    public ProtocolDecoder getDecoder(IoSession session) {  
        return decoder;  
    }  
       /** 
     * Returns the allowed maximum size of the encoded line. 
     * If the size of the encoded line exceeds this value, the encoder 
     * will throw a {@link IllegalArgumentException}.  The default value 
     * is {@link Integer#MAX_VALUE}. 
     * &lt;p&gt; 
     * This method does the same job with {@link TextLineEncoder#getMaxLineLength()}. 
     */  
    public int getEncoderMaxLineLength() {  
        return encoder.getMaxLineLength();  
    }  
    /** 
     * Sets the allowed maximum size of the encoded line. 
     * If the size of the encoded line exceeds this value, the encoder 
     * will throw a {@link IllegalArgumentException}.  The default value 
     * is {@link Integer#MAX_VALUE}. 
     * &lt;p&gt; 
     * This method does the same job with {@link TextLineEncoder#setMaxLineLength(int)}. 
     */  
    public void setEncoderMaxLineLength(int maxLineLength) {  
        encoder.setMaxLineLength(maxLineLength);  
    }  
    /** 
     * Returns the allowed maximum size of the line to be decoded. 
     * If the size of the line to be decoded exceeds this value, the 
     * decoder will throw a {@link BufferDataException}.  The default 
     * value is &lt;tt&gt;1024&lt;/tt&gt; (1KB). 
     * &lt;p&gt; 
     * This method does the same job with {@link TextLineDecoder#getMaxLineLength()}. 
     */  
    public int getDecoderMaxLineLength() {  
        return decoder.getMaxLineLength();  
    }  
    /** 
     * Sets the allowed maximum size of the line to be decoded. 
     * If the size of the line to be decoded exceeds this value, the 
     * decoder will throw a {@link BufferDataException}.  The default 
     * value is &lt;tt&gt;1024&lt;/tt&gt; (1KB). 
     * &lt;p&gt; 
     * This method does the same job with {@link TextLineDecoder#setMaxLineLength(int)}. 
     */  
    public void setDecoderMaxLineLength(int maxLineLength) {  
        decoder.setMaxLineLength(maxLineLength);  
    }  
}  

TextLineFactory实现了ProtocalCodecFactory接口，该接口主要有一个编码的方法getEncoder()和一个解码的方法getDecoder()：

view plaincopy to clipboardprint?
package org.apache.mina.filter.codec;  
import org.apache.mina.core.session.IoSession;  
/** 
 * Provides {@link ProtocolEncoder} and {@link ProtocolDecoder} which translates 
 * binary or protocol specific data into message object and vice versa. 
 * &lt;p&gt; 
 * Please refer to 
 * &lt;a href=&quot;../../../../../xref-examples/org/apache/mina/examples/reverser/ReverseProtocolProvider.html&quot; mce_href=&quot;xref-examples/org/apache/mina/examples/reverser/ReverseProtocolProvider.html&quot;&gt;&lt;code&gt;ReverserProtocolProvider&lt;/code&gt;&lt;/a&gt; 
 * example. 
 * 
 * @author The Apache MINA Project (dev@mina.apache.org) 
 * @version $Rev$, $Date$ 
 */  
public interface ProtocolCodecFactory {  
    /** 
     * Returns a new (or reusable) instance of {@link ProtocolEncoder} which 
     * encodes message objects into binary or protocol-specific data. 
     */  
    ProtocolEncoder getEncoder(IoSession session) throws Exception;  
    /** 
     * Returns a new (or reusable) instance of {@link ProtocolDecoder} which 
     * decodes binary or protocol-specific data into message objects. 
     */  
    ProtocolDecoder getDecoder(IoSession session) throws Exception;  
}  

我们主要是仿照TextLineEncoder实现其中的encode（）方法，仿照TextLineDecoder实现其中的decode（）即可，它们分别实现了ProtocalEncoder和ProtocalDecoder接口。我们要编写三个类分别是：MyProtocalCodecFactory，MyProtocalEncoder，MyProtocalDecoder对应TextLineCodecFactory，TextLineEncoder，TextLineDecoder。

view plaincopy to clipboardprint?
package com.gftech.mytool.mina;  
import java.nio.charset.Charset;  
import org.apache.mina.core.session.IoSession;  
import org.apache.mina.filter.codec.ProtocolCodecFactory;  
import org.apache.mina.filter.codec.ProtocolDecoder;  
import org.apache.mina.filter.codec.ProtocolEncoder;  
public class MyProtocalCodecFactory   implements ProtocolCodecFactory {  
        private final MyProtocalEncoder encoder;  
        private final MyProtocalDecoder decoder;  
          
        public MyProtocalCodecFactory(Charset charset) {  
            encoder=new MyProtocalEncoder(charset);  
            decoder=new MyProtocalDecoder(charset);  
        }  
           
        public ProtocolEncoder getEncoder(IoSession session) {  
            return encoder;  
        }  
        public ProtocolDecoder getDecoder(IoSession session) {  
            return decoder;  
        }  
          
}  

view plaincopy to clipboardprint?
package com.gftech.mytool.mina;  
import java.nio.charset.Charset;  
import org.apache.mina.core.buffer.IoBuffer;  
import org.apache.mina.core.session.IoSession;  
import org.apache.mina.filter.codec.ProtocolEncoderAdapter;  
import org.apache.mina.filter.codec.ProtocolEncoderOutput;  
public class MyProtocalEncoder extends ProtocolEncoderAdapter {  
    private final Charset charset;  
    public MyProtocalEncoder(Charset charset) {  
        this.charset = charset;  
    }  
    //在此处实现对MyProtocalPack包的编码工作，并把它写入输出流中  
    public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception {  
        MyProtocalPack value = (MyProtocalPack) message;  
        IoBuffer buf = IoBuffer.allocate(value.getLength());  
        buf.setAutoExpand(true);  
        buf.putInt(value.getLength());  
        buf.put(value.getFlag());  
        if (value.getContent() != null)  
            buf.put(value.getContent().getBytes());  
        buf.flip();  
        out.write(buf);  
    }  
    public void dispose() throws Exception {  
    }  
}  

view plaincopy to clipboardprint?
package com.gftech.mytool.mina;  
import java.nio.charset.Charset;  
import java.nio.charset.CharsetDecoder;  
import org.apache.mina.core.buffer.IoBuffer;  
import org.apache.mina.core.session.AttributeKey;  
import org.apache.mina.core.session.IoSession;  
import org.apache.mina.filter.codec.ProtocolDecoder;  
import org.apache.mina.filter.codec.ProtocolDecoderOutput;  
public class MyProtocalDecoder implements ProtocolDecoder {  
    private final AttributeKey CONTEXT = new AttributeKey(getClass(), &quot;context&quot;);  
    private final Charset charset;  
    private int maxPackLength = 100;  
    public MyProtocalDecoder() {  
        this(Charset.defaultCharset());  
    }  
    public MyProtocalDecoder(Charset charset) {  
        this.charset = charset;  
    }  
    public int getMaxLineLength() {  
        return maxPackLength;  
    }  
    public void setMaxLineLength(int maxLineLength) {  
        if (maxLineLength &lt;= 0) {  
            throw new IllegalArgumentException(&quot;maxLineLength: &quot; + maxLineLength);  
        }  
        this.maxPackLength = maxLineLength;  
    }  
    private Context getContext(IoSession session) {  
        Context ctx;  
        ctx = (Context) session.getAttribute(CONTEXT);  
        if (ctx == null) {  
            ctx = new Context();  
            session.setAttribute(CONTEXT, ctx);   
        }   
        return ctx;  
    }  
    public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {  
        final int packHeadLength = 5;  
        //先获取上次的处理上下文，其中可能有未处理完的数据  
        Context ctx = getContext(session);  
        // 先把当前buffer中的数据追加到Context的buffer当中   
        ctx.append(in);   
        //把position指向0位置，把limit指向原来的position位置  
        IoBuffer buf = ctx.getBuffer();  
        buf.flip();   
        // 然后按数据包的协议进行读取  
        while (buf.remaining() &gt;= packHeadLength) {  
            buf.mark();  
            // 读取消息头部分  
            int length = buf.getInt();  
            byte flag = buf.get();  
            //检查读取的包头是否正常，不正常的话清空buffer  
            if (length&lt;0 ||length &gt; maxPackLength) {  
                buf.clear();   
                break;  
            }   
            //读取正常的消息包，并写入输出流中，以便IoHandler进行处理  
            else if (length &gt;= packHeadLength &amp;&amp; length - packHeadLength &lt;= buf.remaining()) {  
                int oldLimit2 = buf.limit();  
                buf.limit(buf.position() + length - packHeadLength);  
                String content = buf.getString(ctx.getDecoder());  
                buf.limit(oldLimit2);  
                MyProtocalPack pack = new MyProtocalPack(flag, content);  
                out.write(pack);  
            } else {  
                // 如果消息包不完整  
                // 将指针重新移动消息头的起始位置   
                buf.reset();   
                break;  
            }  
        }  
        if (buf.hasRemaining()) {  
            // 将数据移到buffer的最前面   
                IoBuffer temp = IoBuffer.allocate(maxPackLength).setAutoExpand(true);  
                temp.put(buf);  
                temp.flip();  
                buf.clear();  
                buf.put(temp);  
                   
        } else {// 如果数据已经处理完毕，进行清空  
            buf.clear();   
        }  
          
          
    }  
    public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception {  
    }  
    public void dispose(IoSession session) throws Exception {   
        Context ctx = (Context) session.getAttribute(CONTEXT);  
        if (ctx != null) {  
            session.removeAttribute(CONTEXT);  
        }  
    }  
    //记录上下文，因为数据触发没有规模，很可能只收到数据包的一半  
    //所以，需要上下文拼起来才能完整的处理  
    private class Context {  
        private final CharsetDecoder decoder;  
        private IoBuffer buf;  
        private int matchCount = 0;  
        private int overflowPosition = 0;  
        private Context() {  
            decoder = charset.newDecoder();  
            buf = IoBuffer.allocate(80).setAutoExpand(true);  
        }  
        public CharsetDecoder getDecoder() {  
            return decoder;  
        }  
        public IoBuffer getBuffer() {  
            return buf;  
        }  
        public int getOverflowPosition() {  
            return overflowPosition;  
        }  
        public int getMatchCount() {  
            return matchCount;  
        }  
        public void setMatchCount(int matchCount) {  
            this.matchCount = matchCount;  
        }  
        public void reset() {  
            overflowPosition = 0;  
            matchCount = 0;  
            decoder.reset();  
        }  
        public void append(IoBuffer in) {   
            getBuffer().put(in);  
            
        }  
   
    }  
}  

在MyProtocalServer中，添加自己实现的Log4jFilter和编解码过滤器：

view plaincopy to clipboardprint?
package com.gftech.mytool.mina;  
import java.io.IOException;  
import java.net.InetSocketAddress;  
import java.nio.charset.Charset;  
import org.apache.log4j.Logger;  
import org.apache.log4j.PropertyConfigurator;  
import org.apache.mina.core.service.IoAcceptor;  
import org.apache.mina.core.service.IoHandlerAdapter;  
import org.apache.mina.core.session.IdleStatus;  
import org.apache.mina.core.session.IoSession;  
import org.apache.mina.filter.codec.ProtocolCodecFilter;  
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;  
public class MyProtocalServer {  
    private static final int PORT = 2500;  
    static Logger logger = Logger.getLogger(MyProtocalServer.class);  
    public static void main(String[] args) throws IOException {  
        PropertyConfigurator.configure(&quot;conf\\log4j.properties&quot;);  
        IoAcceptor acceptor = new NioSocketAcceptor();  
        Log4jFilter lf = new Log4jFilter(logger);  
        acceptor.getFilterChain().addLast(&quot;logger&quot;, lf);  
      
        acceptor.getFilterChain().addLast(&quot;codec&quot;, new ProtocolCodecFilter(new MyProtocalCodecFactory(Charset.forName(&quot;GBK&quot;))));  
        acceptor.getSessionConfig().setReadBufferSize(1024);  
        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);  
        acceptor.setHandler(new MyHandler());  
        acceptor.bind(new InetSocketAddress(PORT));  
        System.out.println(&quot;start server ...&quot;);  
    }  
}  
class MyHandler extends IoHandlerAdapter {  
    static Logger logger = Logger.getLogger(MyHandler.class);  
    @Override  
    public void exceptionCaught(IoSession session, Throwable cause) throws Exception {  
        cause.printStackTrace();  
    }  
    @Override  
    public void messageReceived(IoSession session, Object message) throws Exception {  
        MyProtocalPack pack=(MyProtocalPack)message;  
        logger.debug(&quot;Rec:&quot; + pack);  
    }  
    @Override  
    public void sessionIdle(IoSession session, IdleStatus status) throws Exception {  
        logger.debug(&quot;IDLE &quot; + session.getIdleCount(status));  
    }  
}  

编写一个客户端程序进行测试：

view plaincopy to clipboardprint?
package com.gftech.mytool.mina;  
import java.io.DataOutputStream;  
import java.net.Socket;  
public class MyProtocalClient {  
      
    public static void main(String[] args) {  
        try {  
            Socket socket = new Socket(&quot;127.0.0.1&quot;, 2500);  
            DataOutputStream out =  new DataOutputStream( socket.getOutputStream() ) ;  
            for (int i = 0; i &lt; 1000; i++) {  
                MyProtocalPack pack=new MyProtocalPack((byte)i,i+&quot;测试MyProtocalaaaaaaaaaaaaaa&quot;);  
                out.writeInt(pack.getLength());  
                out.write(pack.getFlag());  
                out.write(pack.getContent().getBytes());  
                out.flush();  
                System.out.println(i + &quot; sended&quot;);  
            }  
            Thread.sleep(1000 );  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}  
也可以用IoConnector实现自己的客户端：

view plaincopy to clipboardprint?
package com.gftech.mytool.mina;  
import java.io.IOException;  
import java.net.InetSocketAddress;  
import java.nio.charset.Charset;  
import org.apache.mina.core.future.ConnectFuture;  
import org.apache.mina.core.future.IoFutureListener;  
import org.apache.mina.core.service.IoConnector;  
import org.apache.mina.core.service.IoHandlerAdapter;  
import org.apache.mina.core.session.IdleStatus;  
import org.apache.mina.core.session.IoSession;  
import org.apache.mina.filter.codec.ProtocolCodecFilter;  
import org.apache.mina.transport.socket.nio.NioSocketConnector;  
public class MyProtocalClient2 {  
    private static final String HOST = &quot;192.168.10.8&quot;;  
    private static final int PORT = 2500;  
    static long counter = 0;  
    final static int FC1 = 100;  
    static long start = 0;  
    /** 
     * 使用Mina的框架结构进行测试 
     *  
     * @param args 
     */  
    public static void main(String[] args) throws IOException {  
        start = System.currentTimeMillis();  
        IoConnector connector = new NioSocketConnector();  
        connector.getFilterChain().addLast(&quot;codec&quot;, new ProtocolCodecFilter(new MyProtocalCodecFactory(Charset.forName(&quot;GBK&quot;))));  
        connector.setHandler(new TimeClientHandler2());  
        connector.getSessionConfig().setReadBufferSize(100);  
        connector.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);   
        ConnectFuture connFuture = connector.connect(new InetSocketAddress(HOST, PORT));  
        connFuture.addListener(new IoFutureListener&lt;ConnectFuture&gt;() {  
            public void operationComplete(ConnectFuture future) {  
                try {  
                    if (future.isConnected()) {  
                        IoSession session = future.getSession();   
                        sendData(session);    
                    } else {  
                        System.out.println(&quot;连接不存在 &quot;);  
                    }  
                } catch (Exception e) {  
                    e.printStackTrace();  
                }  
            }  
        });  
        System.out.println(&quot;start client ...&quot;);  
    }  
    public static void sendData(IoSession session) throws IOException {  
        for (int i = 0; i &lt; FC1; i++) {  
            String content = &quot;afdjkdafk张新波测试&quot; + i;  
            MyProtocalPack pack = new MyProtocalPack((byte) i, content);  
            session.write(pack);  
            System.out.println(&quot;send data:&quot; + pack);  
        }  
    }  
}  
class TimeClientHandler2 extends IoHandlerAdapter {  
    @Override  
    public void sessionOpened(IoSession session) {  
        // Set reader idle time to 10 seconds.  
        // sessionIdle(...) method will be invoked when no data is read  
        // for 10 seconds.  
        session.getConfig().setIdleTime(IdleStatus.READER_IDLE, 60);  
    }  
    @Override  
    public void sessionClosed(IoSession session) {  
        // Print out total number of bytes read from the remote peer.  
        System.err.println(&quot;Total &quot; + session.getReadBytes() + &quot; byte(s)&quot;);  
    }  
    @Override  
    public void sessionIdle(IoSession session, IdleStatus status) {  
        // Close the connection if reader is idle.  
        if (status == IdleStatus.READER_IDLE) {  
            session.close(true);  
        }  
    }  
    @Override  
    public void messageReceived(IoSession session, Object message) {  
        MyProtocalPack pack = (MyProtocalPack) message;  
        System.out.println(&quot;rec:&quot; + pack);  
    }  
}  
]]></description>
</item><item>
<title><![CDATA[转：Apache Mina使用手记（三）]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=46903</link>
<author>toyboysli</author>
<pubDate>2009/8/21 15:55:58</pubDate>
<description><![CDATA[在上一篇中，通过一个简单的例子，得以管中窥豹，了解了Mina的基本编写方法。在MinaTimeServer演示程序中，我们添加了两个过滤器，一个是日志过滤器LoggingFilter，一个是文本编解码过滤器。前者实现日志信息的自动处理，后者实现对按行读写的文本数据的编码和解码。

其中LoggingFilter默认的是slf4j，它是一个日志Facade，实际并不实现真正的日志处理功能，它在程序运行时自动判断classpath中加载的日志组件，比如：log4j/Logback/JUL等，确定之后调用真正的日志组件实现真正的日志处理操作。这一点对于组件式的程序，很明显是非常灵活的，因为你并不知道用户的实际环境中使用的是log4j还是JUL，或者是Logback等，但是为了实现自动识别，slf4j默认了log配置文件的加载位置，让我觉得十分不便。

我自己的所有系统中，都使用是log4j（他的替代产品Logback已经出来了，据说性能更强），我一般喜欢把所有配置文件包括log4j.properties都放在conf目录下，以便管理。因此，对于slf4j中需要把log4j.properties放在src文件夹下非常不习惯，对于以后管理也容易引起混乱。所以，我希望能直接实现自己的log4j过滤器，按以前的方式使用。我们观察一下LoggingFilter过滤器的源代码，如下所示：

view plaincopy to clipboardprint?
public class LoggingFilter extends IoFilterAdapter{...}  

LoggingFilter继承自IoFilterAdapter，它是java模型中的适配器模式，是对接口IoFilter的包装。我们可以模仿LoggingFilter，实现自己的Log4jFilter类：

view plaincopy to clipboardprint?
import org.apache.log4j.Level;  
import org.apache.log4j.Logger;  
import org.apache.mina.core.filterchain.IoFilterAdapter;  
import org.apache.mina.core.session.IdleStatus;  
import org.apache.mina.core.session.IoSession;  
import org.apache.mina.core.write.WriteRequest;  
import org.apache.mina.filter.logging.LogLevel;  
import org.slf4j.helpers.MessageFormatter;  
public class Log4jFilter extends IoFilterAdapter {  
    /** The logger name */  
    private final String name;  
      
    /** The logger */  
    private final Logger logger;  
      
    /** The log level for the exceptionCaught event. Default to WARN. */  
    private LogLevel exceptionCaughtLevel = LogLevel.WARN;  
      
    /** The log level for the messageSent event. Default to INFO. */  
    private LogLevel messageSentLevel = LogLevel.INFO;  
      
    /** The log level for the messageReceived event. Default to INFO. */  
    private LogLevel messageReceivedLevel = LogLevel.INFO;  
      
    /** The log level for the sessionCreated event. Default to INFO. */  
    private LogLevel sessionCreatedLevel = LogLevel.INFO;  
      
    /** The log level for the sessionOpened event. Default to INFO. */  
    private LogLevel sessionOpenedLevel = LogLevel.INFO;  
      
    /** The log level for the sessionIdle event. Default to INFO. */  
    private LogLevel sessionIdleLevel = LogLevel.INFO;  
      
    /** The log level for the sessionClosed event. Default to INFO. */  
    private LogLevel sessionClosedLevel = LogLevel.INFO;  
      
    /** 
     * Default Constructor. 
     */  
    public Log4jFilter(Logger logger) {  
        this.logger=logger;  
        this.name=logger.getName();  
    }  
      
       
       
    /** 
     * @return The logger's name 
     */  
    public String getName() {  
        return name;  
    }  
      
    /** 
     * Log if the logger and the current event log level are compatible. We log 
     * a message and an exception. 
     * 
     * @param eventLevel the event log level as requested by the user 
     * @param message the message to log 
     * @param cause the exception cause to log 
     */  
    private void log(LogLevel eventLevel, String message, Throwable cause) {  
        if (eventLevel == LogLevel.TRACE) {  
            logger.trace(message, cause);  
        } else if (eventLevel.getLevel() &gt; LogLevel.INFO.getLevel()) {  
            logger.info(message, cause);  
        } else if (eventLevel.getLevel() &gt; LogLevel.WARN.getLevel()) {  
            logger.warn(message, cause);  
        } else if (eventLevel.getLevel() &gt; LogLevel.ERROR.getLevel()) {  
            logger.error(message, cause);  
        }  
    }  
    /** 
     * Log if the logger and the current event log level are compatible. We log 
     * a formated message and its parameters. 
     * 
     * @param eventLevel the event log level as requested by the user 
     * @param message the formated message to log 
     * @param param the parameter injected into the message 
     */  
    private void log(LogLevel eventLevel, String message, Object param) {  
        String msgStr = MessageFormatter.format(message, param);  
        if (eventLevel == LogLevel.TRACE) {  
            logger.log(name, Level.TRACE, msgStr, null);  
              
        } else if (eventLevel.getLevel() &gt; LogLevel.INFO.getLevel()) {  
            logger.log(name, Level.INFO, msgStr, null);  
        } else if (eventLevel.getLevel() &gt; LogLevel.WARN.getLevel()) {  
            logger.log(name, Level.WARN, msgStr, null);  
        } else if (eventLevel.getLevel() &gt; LogLevel.ERROR.getLevel()) {  
            logger.log(name, Level.ERROR, msgStr, null);  
        }  
    }  
    /** 
     * Log if the logger and the current event log level are compatible. We log 
     * a simple message. 
     * 
     * @param eventLevel the event log level as requested by the user 
     * @param message the message to log 
     */  
    private void log(LogLevel eventLevel, String message) {  
        if (eventLevel == LogLevel.TRACE) {  
            logger.trace(message);  
        } else if (eventLevel.getLevel() &gt; LogLevel.INFO.getLevel()) {  
            logger.info(message);  
        } else if (eventLevel.getLevel() &gt; LogLevel.WARN.getLevel()) {  
            logger.warn(message);  
        } else if (eventLevel.getLevel() &gt; LogLevel.ERROR.getLevel()) {  
            logger.error(message);  
        }  
    }  
    @Override  
    public void exceptionCaught(NextFilter nextFilter, IoSession session,  
            Throwable cause) throws Exception {  
        log(exceptionCaughtLevel, &quot;EXCEPTION :&quot;, cause);  
        nextFilter.exceptionCaught(session, cause);  
    }  
    @Override  
    public void messageReceived(NextFilter nextFilter, IoSession session,  
            Object message) throws Exception {  
        log(messageReceivedLevel, &quot;RECEIVED: {}&quot;, message );  
        nextFilter.messageReceived(session, message);  
    }  
    @Override  
    public void messageSent(NextFilter nextFilter, IoSession session,  
            WriteRequest writeRequest) throws Exception {  
        log(messageSentLevel, &quot;SENT: {}&quot;, writeRequest.getMessage() );  
        nextFilter.messageSent(session, writeRequest);  
    }  
    @Override  
    public void sessionCreated(NextFilter nextFilter, IoSession session)  
            throws Exception {  
        log(sessionCreatedLevel, &quot;CREATED&quot;);  
        nextFilter.sessionCreated(session);  
    }  
    @Override  
    public void sessionOpened(NextFilter nextFilter, IoSession session)  
    throws Exception {  
        log(sessionOpenedLevel, &quot;OPENED&quot;);  
        nextFilter.sessionOpened(session);  
    }  
    @Override  
    public void sessionIdle(NextFilter nextFilter, IoSession session,  
            IdleStatus status) throws Exception {  
        log(sessionIdleLevel, &quot;IDLE&quot;);  
        nextFilter.sessionIdle(session, status);  
    }  
    @Override  
    public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception {  
        log(sessionClosedLevel, &quot;CLOSED&quot;);  
        nextFilter.sessionClosed(session);  
    }  
      
    /** 
     * Set the LogLevel for the ExceptionCaught event. 
     * 
     * @param level The LogLevel to set 
     */  
    public void setExceptionCaughtLoglevel(LogLevel level) {  
        exceptionCaughtLevel = level;  
    }  
      
    /** 
     * Get the LogLevel for the ExceptionCaught event. 
     * 
     * @return The LogLevel for the ExceptionCaught eventType 
     */  
    public LogLevel getExceptionCaughtLoglevel() {  
        return exceptionCaughtLevel;  
    }  
      
    /** 
     * Set the LogLevel for the MessageReceived event. 
     * 
     * @param level The LogLevel to set 
     */  
    public void setMessageReceivedLoglevel(LogLevel level) {  
        messageReceivedLevel = level;  
    }  
      
    /** 
     * Get the LogLevel for the MessageReceived event. 
     * 
     * @return The LogLevel for the MessageReceived eventType 
     */  
    public LogLevel getMessageReceivedLoglevel() {  
        return messageReceivedLevel;  
    }  
      
    /** 
     * Set the LogLevel for the MessageSent event. 
     * 
     * @param level The LogLevel to set 
     */  
    public void setMessageSentLoglevel(LogLevel level) {  
        messageSentLevel = level;  
    }  
      
    /** 
     * Get the LogLevel for the MessageSent event. 
     * 
     * @return The LogLevel for the MessageSent eventType 
     */  
    public LogLevel getMessageSentLoglevel() {  
        return messageSentLevel;  
    }  
      
    /** 
     * Set the LogLevel for the SessionCreated event. 
     * 
     * @param level The LogLevel to set 
     */  
    public void setSessionCreatedLoglevel(LogLevel level) {  
        sessionCreatedLevel = level;  
    }  
      
    /** 
     * Get the LogLevel for the SessionCreated event. 
     * 
     * @return The LogLevel for the SessionCreated eventType 
     */  
    public LogLevel getSessionCreatedLoglevel() {  
        return sessionCreatedLevel;  
    }  
      
    /** 
     * Set the LogLevel for the SessionOpened event. 
     * 
     * @param level The LogLevel to set 
     */  
    public void setSessionOpenedLoglevel(LogLevel level) {  
        sessionOpenedLevel = level;  
    }  
      
    /** 
     * Get the LogLevel for the SessionOpened event. 
     * 
     * @return The LogLevel for the SessionOpened eventType 
     */  
    public LogLevel getSessionOpenedLoglevel() {  
        return sessionOpenedLevel;  
    }  
      
    /** 
     * Set the LogLevel for the SessionIdle event. 
     * 
     * @param level The LogLevel to set 
     */  
    public void setSessionIdleLoglevel(LogLevel level) {  
        sessionIdleLevel = level;  
    }  
      
    /** 
     * Get the LogLevel for the SessionIdle event. 
     * 
     * @return The LogLevel for the SessionIdle eventType 
     */  
    public LogLevel getSessionIdleLoglevel() {  
        return sessionIdleLevel;  
    }  
      
    /** 
     * Set the LogLevel for the SessionClosed event. 
     * 
     * @param level The LogLevel to set 
     */  
    public void setSessionClosedLoglevel(LogLevel level) {  
        sessionClosedLevel = level;  
    }  
    /** 
     * Get the LogLevel for the SessionClosed event. 
     * 
     * @return The LogLevel for the SessionClosed eventType 
     */  
    public LogLevel getSessionClosedLoglevel() {  
        return sessionClosedLevel;  
    }  
}  

其实代码很类似，在MinaTimeServer中使用Log4jFilter使使看：

view plaincopy to clipboardprint?
package com.gftech.mytool.mina;  
import java.io.IOException;  
import java.net.InetSocketAddress;  
import java.nio.charset.Charset;  
import java.util.Date;  
import org.apache.log4j.Logger;  
import org.apache.log4j.PropertyConfigurator;  
import org.apache.mina.core.service.IoAcceptor;  
import org.apache.mina.core.service.IoHandlerAdapter;  
import org.apache.mina.core.session.IdleStatus;  
import org.apache.mina.core.session.IoSession;  
import org.apache.mina.filter.codec.ProtocolCodecFilter;  
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;  
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;  
public class MinaTimeServer {  
    private static final int PORT = 2500;  
    // static Logger logger = LoggerFactory.getLogger(MinaTimeServer.class);  
    static Logger logger = Logger.getLogger(MinaTimeServer.class);  
    public static void main(String[] args) throws IOException {  
        PropertyConfigurator.configure(&quot;conf\\log4j.properties&quot;);  
        IoAcceptor acceptor = new NioSocketAcceptor();  
        // LoggingFilter lf = new LoggingFilter(&quot;testLog&quot;);  
        Log4jFilter lf = new Log4jFilter(logger);  
        acceptor.getFilterChain().addLast(&quot;logger&quot;, lf);  
        acceptor.getFilterChain().addLast(&quot;codec&quot;, new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName(&quot;GBK&quot;))));  
        acceptor.setHandler(new TimeServerHandler());  
        acceptor.getSessionConfig().setReadBufferSize(10);  
        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);  
        acceptor.bind(new InetSocketAddress(PORT));  
        System.out.println(&quot;start server ...&quot;);  
    }  
}  
class TimeServerHandler extends IoHandlerAdapter {  
    // static Logger logger = LoggerFactory.getLogger(TimeServerHandler.class);  
    static Logger logger = Logger.getLogger(TimeServerHandler.class);  
    @Override  
    public void exceptionCaught(IoSession session, Throwable cause) throws Exception {  
        cause.printStackTrace();  
    }  
    @Override  
    public void messageReceived(IoSession session, Object message) throws Exception {  
        String str = message.toString();  
        if (str.trim().equalsIgnoreCase(&quot;quit&quot;)) {  
            session.close(true);  
            return;  
        }  
        logger.debug(&quot;Rec:&quot; + str);  
        Date date = new Date();  
        session.write(date.toString());  
        logger.debug(&quot;Message written...&quot;);  
    }  
    @Override  
    public void sessionIdle(IoSession session, IdleStatus status) throws Exception {  
        logger.debug(&quot;IDLE &quot; + session.getIdleCount(status));  
    }  
}  

把配置文件按照自己的习惯放到conf目录下，就可以正常使用了。

需要注意的是，增加了自己的Log4jFilter之后，只能在您自己的代码中实现logger.debug()之类的调用时产生的日志记录才是正常的，即按配置文件中的要求老老实实进行日志记录。对于Mina代码中用到的logger调用，因为还是用的默认的slf4j，找不到src目录下的log4j.properties，所以产生的日志将无法正确显示和记录。除非您自己的将mina源代码中的logger全部改成log4j的logger，这是个小小的遗憾，鱼和熊掌不能兼得呀。

下一篇中，我们将介绍，如何编写自己的编解码过滤器。]]></description>
</item><item>
<title><![CDATA[转：Apache Mina使用手记（二）]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=46902</link>
<author>toyboysli</author>
<pubDate>2009/8/21 15:31:46</pubDate>
<description><![CDATA[Mina主要是作为服务器端底层框架来实现数据处理，它的实现很简单，如下例所示：

view plaincopy to clipboardprint?
package com.gftech.mytool.mina;  
import java.io.IOException;  
import java.net.InetSocketAddress;  
import java.nio.charset.Charset;  
import java.util.Date;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.apache.mina.core.service.IoAcceptor;  
import org.apache.mina.core.service.IoHandlerAdapter;  
import org.apache.mina.core.session.IdleStatus;  
import org.apache.mina.core.session.IoSession;  
import org.apache.mina.filter.codec.ProtocolCodecFilter;  
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;  
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;  
public class MinaTimeServer {  
    private static final int PORT = 2500;  
    //调用工厂方法，得到一个日志记录器，我用的是自己最熟悉的Log4j  
    //slf4j本身是一个Facade，或者说象集群服务中的分发器，它本身没有Logger的记录功能  
    //它会自动根据Classpath中的具体Logger类库来实现具体调用，在LoggerFactory实例化Logger  
    static Logger logger = LoggerFactory.getLogger(MinaTimeServer.class);   
    public static void main(String[] args) throws IOException {  
        //在服务器端创建一个接收器  
        IoAcceptor acceptor = new NioSocketAcceptor();  
        //创建一个日志过滤器进行日志处理，并添加到过滤器链的第一个位置  
        //过滤器的位置很重要，在这里因为放到了第一个位置，它会记录原始字节码数据  
        LoggingFilter lf = new LoggingFilter("testLog");   
        acceptor.getFilterChain().addLast("logger", lf);  
        //增加一个按行进行处理文本的编解码过滤器，并且指定按GBK的方法进行编解码  
        acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("GBK"))));  
        //进行配置信息的设置  
        acceptor.getSessionConfig().setReadBufferSize(10);  
        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);  
        //添加一个数据处理器，对接收或发送的数据进行处理  
        acceptor.setHandler(new TimeServerHandler());  
        //把IoAccepter绑定到指定的2500端口          
        acceptor.bind(new InetSocketAddress(PORT));  
        System.out.println("start server ...");  
    }  
}  
class TimeServerHandler extends IoHandlerAdapter {  
     static Logger logger = LoggerFactory.getLogger(TimeServerHandler.class);  
    //static Logger logger = Logger.getLogger(TimeServerHandler.class);  
    //异常处理  
    public void exceptionCaught(IoSession session, Throwable cause) throws Exception {  
        cause.printStackTrace();  
    }  
    //对接收到的数据进行业务处理，在这里我们不管收到什么信息都返回一个当前的日期  
    public void messageReceived(IoSession session, Object message) throws Exception {  
        String str = message.toString();  
        if (str.trim().equalsIgnoreCase("quit")) {  
            session.close(true);  
            return;  
        }  
        logger.debug("Rec:" + str);  
        Date date = new Date();  
        session.write(date.toString());  
        logger.debug("Message written...");  
    }  
    //当连接空闲时的处理  
    public void sessionIdle(IoSession session, IdleStatus status) throws Exception {  
        logger.debug("IDLE " + session.getIdleCount(status));  
    }  
}  

在上面的代码中，我用了mina默认的SLF4J日志处理器。因为slf4j本身没有真正的日志处理功能，它最终调用的是log4j，所以我们可以编写一下log4j的配置文件来指定具体的输出方式。需要注意的一点是配置文件必须放在src文件夹下面，否则slf4j无法找到，示例如下：

view plaincopy to clipboardprint?
log4j.rootLogger =DEBUG, A1,A2  
#输出到控制台  
log4j.appender.A1 = org.apache.log4j.ConsoleAppender  
log4j.appender.A1.layout = org.apache.log4j.PatternLayout  
log4j.appender.A1.layout.ConversionPattern =[%d] [%t] %-5p - %m %n  
#输出到固定大小的日志文件  
log4j.appender.A2 = org.apache.log4j.RollingFileAppender  
log4j.appender.A2.File = logs\\test1.log  
log4j.appender.A2.MaxFileSize = 1MB  
log4j.appender.A2.MaxBackupIndex = 3  
log4j.appender.A2.layout = org.apache.log4j.PatternLayout  
log4j.appender.A2.layout.ConversionPattern =[%d] %-4r [%t] %-5p %c %x - %m %n  
#定义A3输出到数据库  
log4j.appender.A3 = org.apache.log4j.jdbc.JDBCAppender  
log4j.appender.A3.BufferSize = 40  
log4j.appender.A3.Driver = sun.jdbc.odbc.JdbcOdbcDriver  
log4j.appender.A3.URL = jdbc:ODBC:driver={Microsoft Access Driver (*.mdb)};DBQ=MobileDB.mdb  
log4j.appender.A3.User =  
log4j.appender.A3.Password =  
log4j.appender.A3.layout = org.apache.log4j.PatternLayout  
log4j.appender.A3.layout.ConversionPattern = INSERT INTO log4j(createDate, thread, priority, category, message) values('%d', '%t', '%-5p', '%c', '%m')  
#输出到HTML文件当中,并按日期自动分割  
log4j.appender.A4 = org.apache.log4j.DailyRollingFileAppender  
log4j.appender.A4.File = logs\\log.html  
log4j.appender.A4.DatePattern='.'yyyy-MM-dd-HH'.html'  
log4j.appender.A4.layout = org.apache.log4j.HTMLLayout   
#A5 send log info to remote mysql database  
log4j.appender.A5 = com.gftech.log4j.JDBCExtAppender  
log4j.appender.A5.Driver = com.mysql.jdbc.Driver  
log4j.appender.A5.URL = jdbc:mysql://192.168.10.1:3306/log  
log4j.appender.A5.User = root  
log4j.appender.A5.Password = plus  
log4j.appender.A5.layout = org.apache.log4j.PatternLayout  
log4j.appender.A5.sql = INSERT INTO app_log(machine,occur_date,thread_name,cat,level,info) values('DP','%d{yyyy-MM-dd HH:mm:ss}','%t','%c','%p','%m')  
#A6 send log info(ERROR or Fatal) by Email  
log4j.appender.A6 = com.gftech.log4j.SMTPExtAppender  
log4j.appender.A6.Threshold=FATAL  
log4j.appender.A6.SMTPHost=smtp.126.com  
log4j.appender.A6.to=sinboy@126.com  
log4j.appender.A6.from=sinboy@126.com  
log4j.appender.A6.SMTPAuth=true  
log4j.appender.A6.SMTPUsername=sinboy  
log4j.appender.A6.SMTPPassword=123456  
log4j.appender.A6.Subject=Log4J Message  
log4j.appender.A6.layout=org.apache.log4j.PatternLayout  
log4j.appender.A6.layout.ConversionPattern= [%d{HH:mm:ss}] [%t] %c - %-5p - %m%n  

在命令行终端输入：telnet 127.0.0.1 2500可以进行测试，当然也可以自己模拟多个客户端进行并发访问测试mina的真实性能。我在P4/512M的机器上测出的结果是每秒可以处理1700~2000个数据，,根据并发客户端的多少会有不同。]]></description>
</item><item>
<title><![CDATA[转：Apache Mina使用手记（一）]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=46894</link>
<author>toyboysli</author>
<pubDate>2009/8/20 17:59:59</pubDate>
<description><![CDATA[1.Apache Mina是一个高性能的基础网络构架平台，构建在java NIO的基础上

2.Mina使用了SLF4J做为日志记录器，全称Simple Logging Facade for Java，它是一个日志门面，只负责为客户端提供应用接口，实际的日志记录由Log4j/JUL等日志记录器实现。如下图所示：



3.Mina主要有IoConnector，IoAccepter，IoSession，IoSessionConfig，IoHandler，IoFilter，IoFuture，EventListener等对象组成

4.IoConnector实现客户端的连接功能，IoAccepter实现服务端的接收功能，它们都继承自IoService

5.IoSession为客户端和服务端的一个会话，每一个会话都包括会话的建立，打开，注销等功能

6.IoSession中还包括发送或接收到的数据，以及会话上触发相应事件的侦听器

7.而IoSession的相关配置由IoSessionConfig实现

8.IoHandler为数据处理器，在此对象中可以对接收到的数据进行具体的业务处理，也可以决定发送数据成功后是否进行其他的操作。很类似VB当中的事件处理。

9.IoFilter决定着在IoAccepter接收到原始数据之后，IoHandler进行业务处理之前，或反过来的对数据的中间处理过程

10.第一个IoFilter可以实现一个子功能，比如LoggingFilter可以实现对接收或发送数据的进行日志处理，其中LoggingFilter根据加载的具体类库动态判断具体的日志操作

11.在IoFilter中，也可以进行协议或编码解码处理，可以支持byte/txt/http/ftp/xml等各种方法

12.在实际的实现 中，多个过滤器由IoFilterChain进行管理，类似一个管道，原始数据从管道的一端进入，过滤层层过滤处理，最终得到需要的数据，交给IoHandler进行业务处理

13.IoFuture指定IO操作包括connect/read/write等的未来状态

14.EventListener事件侦听器，一般会加载到IoSession上面，进行事件监听。比如对接收数据的事件触发后，会在侦听器中调用IoHandler的MessageReceived（）方法来处理。]]></description>
</item><item>
<title><![CDATA[java.nio.Buffer分析]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=46893</link>
<author>toyboysli</author>
<pubDate>2009/8/20 17:55:18</pubDate>
<description><![CDATA[在研究Apache Mina源代码时，在IoFilter中使用IoBuffer做为数据缓冲对象，而IoBuffer的实现来自于java.nio.Buffer。Buffer中的flip()、clear()、reset()、mark()等概念让我有点糊涂，仔细看了jdk的文档说明，才对Buffer对象的概念了然于胸。

在Buffer类当中有以下四个重要的属性：

mark：标记位，用于reset（）时把position恢复到原来的位置，调用mark（）方法使position的值赋与mark

position：表示Buffer中第一个可以被读取或写入的数据的位置，每次调用put（）方法时,把写入的数据放到position位置，然后position的值就会加1

limit：表示buffer中第一个不可被读取或写入的数据的位置，也即停止位，数据操作到此为止

capacity：初始化时调用allocate(int size)为buffer分配的空间大小，不可变

演示代码如下：

view plaincopy to clipboardprint?
import java.nio.ByteBuffer;  
public class ByteBufferTest {  
   
    public static void main(String[] args) {   
        ByteBuffer bb=ByteBuffer.allocate(10);  
        for(int i=1;i&lt;9;i++){  
            bb.put((byte)i);  
        }  
          
        System.out.println(&quot;pos:&quot;+bb.position());  
        System.out.println(&quot;limit:&quot;+bb.limit());  
        System.out.println(&quot;cap:&quot;+bb.capacity());  
          
        bb.flip();  
        System.out.println(&quot;\nafter flip&quot;);   
        System.out.println(&quot;pos:&quot;+bb.position());  
        System.out.println(&quot;limit:&quot;+bb.limit());  
        System.out.println(&quot;cap:&quot;+bb.capacity());  
          
            
        bb.mark();   
        System.out.println(&quot;\nafter mark&quot;);   
        System.out.println(&quot;pos:&quot;+bb.position());  
        System.out.println(&quot;limit:&quot;+bb.limit());  
        System.out.println(&quot;cap:&quot;+bb.capacity());  
          
          
        bb.reset();  
        System.out.println(&quot;\nafter reset&quot;);   
        System.out.println(&quot;pos:&quot;+bb.position());  
        System.out.println(&quot;limit:&quot;+bb.limit());  
        System.out.println(&quot;cap:&quot;+bb.capacity());  
          
        bb.clear();  
        System.out.println(&quot;\nafter clear&quot;);   
        System.out.println(&quot;pos:&quot;+bb.position());  
        System.out.println(&quot;limit:&quot;+bb.limit());  
        System.out.println(&quot;cap:&quot;+bb.capacity());   
          
        bb.limit(1);   
        bb.put((byte)9);  
        bb.put((byte)10);//超出limit范围，抛出java.nio.BufferOverflowException异常  
        bb.put((byte)11);  
    }  
}  

输出结果：

view plaincopy to clipboardprint?
pos:8  
limit:10  
cap:10  
after flip  
pos:0  
limit:8  
cap:10  
after mark  
pos:0  
limit:8  
cap:10  
after reset  
pos:0  
limit:8  
cap:10  
after clear  
pos:0  
limit:10  
cap:10  
Exception in thread &quot;main&quot; java.nio.BufferOverflowException  
    at java.nio.Buffer.nextPutIndex(Buffer.java:419)  
    at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:145)  
    at com.gftech.mytool.mina.ByteBufferTest.main(ByteBufferTest.java:45)  
]]></description>
</item><item>
<title><![CDATA[转：REST架构实质]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=46580</link>
<author>toyboysli</author>
<pubDate>2009/7/17 14:55:59</pubDate>
<description><![CDATA[作者：<A href="http://www.jdon.com/jivejdon/profile.jsp?user=banq"><B>banq</B></A> 发表时间：2009年07月07日 11:28 <A href="http://www.jdon.com/jivejdon/message/messageReplyAction.shtml?parentMessage.messageId=23123409&amp;forum.forumId=16"><IMG border=0 alt=回复此消息 src="http://www.jdon.com/jivejdon/images/reply.gif" width=17 height=17>回复</A> <BR>原贴网址：<A href="http://www.jdon.com/jivejdon/thread/36506.html"> http://www.jdon.com:8080/jivejdon/thread/36506.html</A> <!-- advert -->A name=36506&gt;</A> 
<P class=article>REST(Representational State Transfer) 曾经被误解为只是CRUD（增删改查），从这个层面上，好像REST只是和RPC一个层面的东西，没有什么了不起，其实这些都是对REST误读。<BR><BR>理解REST需要从系统集成整合以及架构的伸缩性方面入手，这方面有一篇很重要的REST博文： I finally get REST. Wow.<BR><A href="http://www.pluralsight.com/community/blogs/tewald/archive/2007/04/26/46984.aspx">http://www.pluralsight.com/community/blogs/tewald/archive/2007/04/26/46984.aspx</A><BR><BR>作者认为：每个通讯协议都有一个状态机，当你使用RPC时，你要做些方法来改变通讯的状态，但是这些状态是封装在服务器端或客户端的专门通讯模块中，比如通过Hessian/SOAP等Proxy技术进行RPC调用，虽然可以在客户端很方便地象调用本地服务一样，缺点总是伴随优点到来，由于Proxy封装了客户端和服务器的通讯，就很容易让客户端和服务器紧耦合。<BR><BR>这其实是C/S架构的一个本质问题，而B/S架构通过简单的浏览器则解耦了客户端和服务器的耦合，别小看丑丑的浏览器，特别是当初还没有AJAX辅助时，浏览器被很多传统C/S讥笑和不屑，但是他们不知道B/S架构后面蕴含的深刻朴实的设计道理，是真正大道至简。所以，当我们重新回归RIA富客户端代表的C/S架构时，必须吸取Web架构的本质优点，这样才是螺旋式上升。<BR><BR>回归话题，由于传统架构隐藏了通讯模块进而发生了紧耦合，而REST最大作用则是将通讯状态以URI显式表现出来，也就是将通讯状态的透明性，这样做最大的好处就可以在通讯环节引入伸缩性。<BR><BR>REST核心思想就是：以URI形式将状态机表现为动态的节点图，我们就能引入动态负载平衡dynamic load-balancing, 数据重导向data-directed-routing, versioning 等其他正常的Web底层设计架构Web infrastructure，从而使客户端和服务器之间服务器之间享受Web架构的好处。<BR><BR>谈到系统集成，SOAP/Web服务/SOA被厂商吹嘘了很多年，很多应用也在使用SOAP，SOAP和REST无疑是相互竞争的，SOA其实也是一种的RPC，是一种基于XML的RPC，因此，同样存在通讯状态隐藏的致命问题。<BR><BR>上述博客作者写了另外一篇文章来比较(http://www.pluralsight.com/community/blogs/tewald/archive/2007/04/27/47031.aspx)<BR>比如，两个城市之间航班看成一个协议，这个协议有下面几个状态：<BR>&lt;ready&gt;<BR>- searched (查询)<BR>- retrieved details (获得细节)<BR>- reserved (预订)<BR><BR>通过URI表现出来是：<BR>&lt;none&gt;<BR>- http://quuxTravel.com/searched<BR>- ??? depends on previous state<BR>- ??? depends on previous state<BR><BR>客户端通过get方式http://quuxTravel.com/searched?src=London&amp;dest=NYC进行查询，得到结果如下：<BR>&lt;itineraries&gt;<BR>&lt;itinerary src=“London“ dest=“NYC“ price=“400.03“&gt;<BR>&lt;getDetails uri=“http://quuxTravel.com/details?itinerary=402“ /&gt;<BR>&lt;reserve uri=“http://reservations.bookingsunlimited.com/quuxTravel?itinerary=402“ /&gt;<BR>&lt;/itinerary&gt;<BR>&lt;itinerary src=“London“ dest=“NYC“ price=“109.88“&gt;<BR>&lt;getDetails uri=“http://quuxTravel.com/details?itinerary=219“ /&gt;<BR>&lt;reserve uri=“http://reservations.bookingsunlimited.com/quuxTravel?itinerary=219“ /&gt;<BR>&lt;/itinerary&gt;<BR>&lt;/itineraries&gt;<BR>客户端目前处于“查询”状态，通过遍历上述itineraries旅游线路结果集合，寻找出最便宜的一家，如果用户想查询一个不在上述结果的信息比如飞行总体时间，他能通过上述结果属性getDetails/@uri中保存的URI信息再次GET获得，这样就会切换到 retrieved details状态。然后又会回到searched状态。<BR><BR>当用户选择一条路线后，通过保存在reserve属性中的URI值就进入 reserved状态，客户端得到一些reserved方面的信息，至此，整个业务算完成了。<BR><BR>如果使用RPC如何来完成呢？我们必须创建一个接口如下：<BR>interface IFlightSystem<BR>{<BR>Itineraries Search(string src, string dest);<BR>Details GetDetails(int itineraryId);<BR>Confirmation Reserve(itineraryId);<BR>}<BR>客户端可以通过调用这个接口的几个方面来完成上述一些业务.<BR><BR>在这里，接口表达了和前面REST同样的协议，所不同的是, RPC客户端依赖服务器端这个接口，也就是两者通过接口耦合了，更致命的是：searched (查询) retrieved details (获得细节) reserved三个状态耦合到同一个服务器中，如果我们想将这三个状态分离分散到多台服务器上，除非重写服务器端代码，否则无能为力。<BR><BR>而在前面REST调用中，每个状态都是通过URI重新定位，这样，我们可以在这三个状态中引入伸缩性，比如searched状态的URI在A服务器，而retrieved details的URI则是B服务器的网址，而reserved的URI则是c服务器，看看我们的业务不再是铁板一块了。<BR><BR>这就是REST本质魅力，REST和RPC/SOAP本质区别是透明性。REST透明性可以让我们以更细粒度引入伸缩性，这样以REST为主要形式可以组建一个分布式的大型架构，而这个目的恰恰是重量解决方案SOA提出的目标，现在我们有了另外一个轻量的选择。</P>
<P class=article>&nbsp;</P>
<P class=article><SPAN class=tpc_content>A terabyte <A id=id_http://www.jdon.com/jivejdon/key/cache class="hotkeys ajax_query=cache" href="http://www.jdon.com/jivejdon/key/cache"><B>cache</B></A> with the RESTful Ehcache Server<BR>使用RESTful EhCache服务器实现TB级级别<A id=id_http://www.jdon.com/jivejdon/query/taggedThreadList.shtml?tagID=313 class="hotkeys ajax_query=缓存" href="http://www.jdon.com/jivejdon/query/taggedThreadList.shtml?tagID=313"><B>缓存</B></A><BR><A href="http://gregluck.com/blog/archives/2008/08/_the_restful_eh.html">http://gregluck.com/blog/archives/2008/08/_the_restful_eh.html</A><BR><BR>使用RESTful接口实现数据分区(也可以称为通常的数据库分离)，一个最大的ehcache可以有20GB内存，而最大的磁盘存储是100Gb. 使用数据分区将这些节点组合在一起，可以得到更大的内存，50个节点X 20GB可以得到1TB。<BR><BR>非冗余的一个设计：<BR><IMG border=0 src="http://docs.google.com/File?id=agbdcfj3mkdt_23ftq7nfgj_b"><BR><BR>带客户端Hash的设计，代码如下：<BR>String[] <A id=id_http://www.jdon.com/jivejdon/key/cache class="hotkeys ajax_query=cache" href="http://www.jdon.com/jivejdon/key/cache"><B>cache</B></A>servers = new String[]{"cacheserver0.company.com", <BR>"cacheserver1.company.com",<BR>"cacheserver2.company.com",<BR>"cacheserver3.company.com",<BR>"cacheserver4.company.com",<BR>"cacheserver5.company.com"};<BR>Object key = "123231";<BR>int hash =Math.abs(key.hashCode());<BR>int <A id=id_http://www.jdon.com/jivejdon/key/cache class="hotkeys ajax_query=cache" href="http://www.jdon.com/jivejdon/key/cache"><B>cache</B></A>serverIndex = hash % <A id=id_http://www.jdon.com/jivejdon/key/cache class="hotkeys ajax_query=cache" href="http://www.jdon.com/jivejdon/key/cache"><B>cache</B></A>servers.length;<BR>String <A id=id_http://www.jdon.com/jivejdon/key/cache class="hotkeys ajax_query=cache" href="http://www.jdon.com/jivejdon/key/cache"><B>cache</B></A>server = <A id=id_http://www.jdon.com/jivejdon/key/cache class="hotkeys ajax_query=cache" href="http://www.jdon.com/jivejdon/key/cache"><B>cache</B></A>servers[cacheserverIndex]<BR>图示：<BR><IMG border=0 src="http://docs.google.com/File?id=agbdcfj3mkdt_21fmncv7g5_b"><BR><BR><BR>有趣的是，一些内容交换的负载均衡器可以使用某种形式的正则表达式可以实现URI路由，如下代码所示，所以，你可以不选择上面客户端Hash散列实现分割的方式来实现负载平衡器。 <BR>/ehcache/rest/sampleCache1/a1 =&gt;cluster1<BR>/ehcache/rest/sampleCache1/a2 =&gt;cluster2<BR><BR>当然，更复杂的是使用F5负载平衡器，还可以使用TCL创建iRules，但是比正则表达式要复杂。F5的URI hashing iRule见:<BR><A href="http://devcentral.f5.com/Default.aspx?tabid=63&amp;PageID=153&amp;ArticleID=135&amp;articleType=ArticleView">http://devcentral.f5.com/Default.aspx?tabid=63&amp;PageID=153&amp;ArticleID=135&amp;articleType=ArticleView</A><BR><BR>个人建议：图中Cluster组可以使用Terracotta，可自动整合Ehcache，对程序无侵入性，更新性能好于memcached，比JMS JGroups要高级多。<BR><BR>见配置：<A href="http://www.terracotta.org/web/display/docs/About+Terracotta+Configuration+Files">http://www.terracotta.org/web/display/docs/About+Terracotta+Configuration+Files</A><BR></SPAN><BR></P>]]></description>
</item><item>
<title><![CDATA[异步调用时，异常rethrow的编程模式]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=46231</link>
<author>toyboysli</author>
<pubDate>2009/6/25 17:57:10</pubDate>
<description><![CDATA[<SPAN style="FONT-SIZE: 13px; FONT-FAMILY: tahoma"> 
<TABLE id=forum_main cellSpacing=1>
<THEAD>
<TR>
<TD class=first_col>作者</TD>
<TD class=last_col></TD></TR></THEAD>
<TBODY id=posts>
<TR id=p_1066263>
<TD class=postauthor>
<P class=name>Qieqie </P></TD></TR></TBODY></TABLE>
<P><A href="http://www.javaeye.com/topic/414366">http://www.javaeye.com/topic/414366</A></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN>今天看到的一个问题，顺便总结如下：</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px">&nbsp;</P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN lang=EN-US>Java</SPAN><SPAN>编程中，程序异常处理是一个跑不掉的东西。</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px">&nbsp;</P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN>对于同步编程：</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN>调用某个可能抛出</SPAN><SPAN lang=EN-US>checked</SPAN><SPAN>或</SPAN><SPAN lang=EN-US>unchecked</SPAN><SPAN>异常的方法时，可以直接在方法中声明</SPAN><SPAN lang=EN-US>throws</SPAN><SPAN>继续往上处理，也可以在本方法中进行</SPAN><SPAN lang=EN-US>try catch</SPAN><SPAN>处理</SPAN><SPAN lang=EN-US>(</SPAN><SPAN>比如进行日志登记</SPAN><SPAN lang=EN-US>)</SPAN><SPAN>。</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN lang=EN-US>catch</SPAN><SPAN>处理后，是否进行</SPAN><SPAN lang=EN-US>throw e</SPAN><SPAN>或</SPAN><SPAN lang=EN-US>throw new XxxException(e)</SPAN><SPAN>视具体情况而定。这些处理，从技术上都是正确的，我们也处理地很好。问题不在这。</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px">&nbsp;</P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><STRONG><SPAN>对于异步编程</SPAN></STRONG><STRONG><SPAN lang=EN-US>(</SPAN></STRONG><STRONG><SPAN>重点</SPAN></STRONG><STRONG><SPAN lang=EN-US>)</SPAN></STRONG><STRONG><SPAN>：</SPAN></STRONG><STRONG></STRONG></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN>在线程</SPAN><SPAN lang=EN-US>A</SPAN><SPAN>中，从底层的</SPAN><SPAN lang=EN-US>Thread.run</SPAN><SPAN>一直运行调用到某个方法</SPAN><SPAN lang=EN-US>xxx()</SPAN><SPAN>，</SPAN><SPAN lang=EN-US>xxx()</SPAN><SPAN>设置线程之间的共享对象并</SPAN><SPAN lang=EN-US>notify</SPAN><SPAN>通知另外一个线程</SPAN><SPAN lang=EN-US>B</SPAN><SPAN>处理输入的任务，</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN>如果任务失败时</SPAN>&nbsp;<SPAN lang=EN-US>(</SPAN><SPAN>比如验证错误、程序空指针错误、超时错误等等</SPAN><SPAN lang=EN-US>)</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN>这样情况下，通常按照以下判断程序的好坏：</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN lang=EN-US><SPAN>1、<SPAN>&nbsp;</SPAN></SPAN></SPAN><SPAN>差的做法：</SPAN><SPAN lang=EN-US>A</SPAN><SPAN>线程无法收到失败通知，也就是</SPAN><SPAN lang=EN-US>B</SPAN><SPAN>线程产生的异常对象无法通知到</SPAN><SPAN lang=EN-US>A</SPAN><SPAN>，使</SPAN><SPAN lang=EN-US>A</SPAN><SPAN>无法接收该异常；</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN lang=EN-US><SPAN>2、<SPAN>&nbsp;</SPAN></SPAN></SPAN><SPAN>好的做法：</SPAN><SPAN lang=EN-US>B</SPAN><SPAN>的异常能够被</SPAN><SPAN lang=EN-US>A</SPAN><SPAN>接收到！技术上是</SPAN><SPAN lang=EN-US>B</SPAN><SPAN>发生异常时，要把异常记录到某共享对象，然后</SPAN><SPAN lang=EN-US>B</SPAN><SPAN>线程通知</SPAN><SPAN lang=EN-US>A</SPAN><SPAN>线程取回该异常。</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px">&nbsp;</P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN>但，</SPAN><SPAN lang=EN-US>xxx()</SPAN><SPAN>获取到</SPAN><SPAN lang=EN-US>B</SPAN><SPAN>的异常后，如何处理呢？分为</SPAN><SPAN lang=EN-US>3</SPAN><SPAN>种情况：</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN lang=EN-US><SPAN>1、<SPAN>&nbsp;</SPAN></SPAN></SPAN><SPAN lang=EN-US>xxx()&nbsp;</SPAN><SPAN>自己</SPAN><SPAN lang=EN-US>try catch</SPAN><SPAN>处理掉</SPAN><SPAN lang=EN-US>&nbsp;—&nbsp;</SPAN><SPAN>没问题，如果真的应该由它处理的话</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN lang=EN-US><SPAN>2、</SPAN></SPAN><SPAN lang=EN-US>xxx()&nbsp;</SPAN><SPAN>需要把这个异常</SPAN><SPAN lang=EN-US>rethrow</SPAN><SPAN>出去给上级调用者，</SPAN><SPAN lang=EN-US><BR></SPAN><STRONG><SPAN>此时要注意了</SPAN></STRONG><SPAN>，<STRONG>请不要简单这样写</STRONG></SPAN><STRONG><SPAN lang=EN-US>throw exceptionFromThreadB</SPAN></STRONG><STRONG><SPAN lang=EN-US>;&nbsp;</SPAN></STRONG><STRONG><SPAN>而应该写成这样：</SPAN></STRONG><STRONG><SPAN lang=EN-US>throw new XxxException(exceptionFromThreadB);</SPAN></STRONG><STRONG></STRONG></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN lang=EN-US><SPAN>3、<SPAN>&nbsp;</SPAN></SPAN></SPAN><SPAN>如果第</SPAN><SPAN lang=EN-US>1</SPAN><SPAN>步的</SPAN><SPAN lang=EN-US>try catch</SPAN><SPAN>中需要做</SPAN><SPAN lang=EN-US>logger.error</SPAN><SPAN>记录，也请这样记录</SPAN><STRONG><SPAN lang=EN-US>logger.error(“”, new XxxException(exceptionFromThreadB));</SPAN></STRONG><STRONG><SPAN>，</SPAN></STRONG><SPAN>不写成</SPAN><SPAN lang=EN-US>logger.error(“”, exceptionFromThreadB);</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px">&nbsp;</P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><STRONG><SPAN>为什么？</SPAN></STRONG></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px">&nbsp;</P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN>一个异常对象，意味着</SPAN><SPAN lang=EN-US>JVM dump</SPAN><SPAN>出的当时的堆栈调用情况。</SPAN><SPAN lang=EN-US>throw&nbsp;</SPAN><SPAN>或</SPAN><SPAN lang=EN-US>rethrow</SPAN><SPAN>出去的意味着，让上级了解当时这个堆栈。</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN>如果只是</SPAN><SPAN lang=EN-US>throw/log.error exceptionFromThreadB</SPAN><SPAN>，那么从后台的日志文件中，并<STRONG>不能</STRONG>看出原来是</SPAN><SPAN lang=EN-US>xxxx()</SPAN><SPAN>这个方法进行异步调用发生的异常，更不知道是</SPAN><SPAN lang=EN-US>xxx()</SPAN><SPAN>里面的具体哪一个步骤、哪一个代码调用的异步调用。</SPAN></P>
<P style="PADDING-RIGHT: 0px; DISPLAY: block; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px; PADDING-TOP: 0px"><SPAN>这会耽误程序的调试，问题的解决。</SPAN></P></SPAN>]]></description>
</item><item>
<title><![CDATA[数据库水平切分的实现原理解析]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=46224</link>
<author>toyboysli</author>
<pubDate>2009/6/25 17:19:29</pubDate>
<description><![CDATA[<P>作者 lishuaibt&nbsp; 发表时间：2009-06-16 关键字: <STRONG>分库</STRONG></P>
<P><A href="http://www.javaeye.com/topic/409294">http://www.javaeye.com/topic/409294</A></P>
<P>&nbsp;</P>
<DIV id=related_topics style="POSITION: relative" _madePositioned="true">
<DIV style="TEXT-ALIGN: center">第1章&nbsp; 引言</DIV><BR>随着互联网应用的广泛普及，海量数据的存储和访问成为了系统设计的瓶颈问题。对于一个大型的互联网应用，每天几十亿的PV无疑对数据库造成了相当高的负载。对于系统的稳定性和扩展性造成了极大的问题。通过数据切分来提高网站性能，横向扩展数据层已经成为架构研发人员首选的方式。水平切分数据库，可以降低单台机器的负载，同时最大限度的降低了了宕机造成的损失。通过负载均衡策略，有效的降低了单台机器的访问负载，降低了宕机的可能性；通过集群方案，解决了数据库宕机带来的单点数据库不能访问的问题；通过读写分离策略更是最大限度了提高了应用中读取（Read）数据的速度和并发量。目前国内的大型互联网应用中，大量的采用了这样的数据切分方案，Taobao,Alibaba,Tencent，它们大都实现了自己的分布式数据访问层（DDAL）。以实现方式和实现的层次来划分，大概分为两个层次（Java应用为例）：JDBC层的封装，ORM框架层的实现。就JDBC层的直接封装而言，现在国内发展较好的一个项目是被称作“变形虫”(Amoeba)的项目，由阿里集团的研究院开发，现在仍然处于测试阶段（beta版），其运行效率和生产时效性有待考究。就ORM框架层的实现而言，比如Taobao的基于ibatis和Spring的的分布式数据访问层，已有多年的应用，运行效率和生产实效性得到了开发人员和用户的肯定。本文就是以ORM框架层为基础而实现的分布式数据访问层。本课题的难点在于分库后，路由规则的制定和选择以及后期的扩展性，比如：如何做到用最少的数据迁移量，达到扩充数据库容量（增加机器节点）的目的。核心问题将围绕数据库分库分表的路由规则和负载均衡策略展开。 <BR><BR><BR><BR>
<DIV style="TEXT-ALIGN: center">第2章 基本原理和概念</DIV><BR><BR>2.1基本原理： <BR><BR>人类认知问题的过程总是这样的：what（什么）-&#61664;why(为什么)-&#61664;how(怎么 <BR>做)，接下来，本文将就这三个问题展开讨论和研究： <BR><BR>2.1.1什么是数据切分 <BR><BR>"Shard" 这个词英文的意思是"碎片"，而作为数据库相关的技术用语，似乎最早见于大型多人在线角色扮演游戏中。"Sharding" 姑且称之为"分片"。Sharding 不是一门新技术，而是一个相对简朴的软件理念。众所周知，MySQL 5 之后才有了数据表分区功能，那么在此之前，很多 MySQL 的潜在用户都对 MySQL 的扩展性有所顾虑，而是否具备分区功能就成了衡量一个数据库可扩展性与否的一个关键指标(当然不是唯一指标)。数据库扩展性是一个永恒的话题，MySQL 的推广者经常会被问到：如在单一数据库上处理应用数据捉襟见肘而需要进行分区化之类的处理，是如何办到的呢? 答案是：Sharding。&nbsp; Sharding 不是一个某个特定数据库软件附属的功能，而是在具体技术细节之上的抽象处理，是水平扩展(Scale Out，亦或横向扩展、向外扩展)的解决方案，其主要目的是为突破单节点数据库服务器的 I/O 能力限制，解决数据库扩展性问题。 <BR>通过一系列的切分规则将数据水平分布到不同的DB或table中，在通过相应的DB路由或者table路由规则找到需要查询的具体的DB或者table，以进行Query操作。这里所说的“sharding”通常是指“水平切分”，这也是本文讨论的重点。具体将有什么样的切分方式呢和路由方式呢？行文至此，读者难免有所疑问，接下来举个简单的例子：我们针对一个Blog应用中的日志来说明， 比如日志文章（article）表有如下字段： <BR><BR><IMG src="http://www.javaeye.com/upload/attachment/115688/63a2c616-03ea-3311-8c7c-b97b807f162c.png"> <BR><BR>面对这样的一个表，我们怎样切分呢？怎样将这样的数据分布到不同的数据库中的表中去呢？其实分析blog的应用，我们不难得出这样的结论：blog的应用中，用户分为两种：浏览者和blog的主人。浏览者浏览某个blog，实际上是在一个特定的用户的blog下进行浏览的，而blog的主人管理自己的blog，也同样是在特定的用户blog下进行操作的（在自己的空间下）。所谓的特定的用户，用数据库的字段表示就是“user_id”。就是这个“user_id”，它就是我们需要的分库的依据和规则的基础。我们可以这样做，将user_id为1～10000的所有的文章信息放入DB1中的article表中，将user_id为10001～20000的所有文章信息放入DB2中的article表中，以此类推，一直到DBn。这样一来，文章数据就很自然的被分到了各个数据库中，达到了数据切分的目的。接下来要解决的问题就是怎样找到具体的数据库呢？其实问题也是简单明显的，既然分库的时候我们用到了区分字段user_id，那么很自然，数据库路由的过程当然还是少不了user_id的。考虑一下我们刚才呈现的blog应用，不管是访问别人的blog还是管理自己的blog，总之我都要知道这个blog的用户是谁吧，也就是我们知道了这个blog的user_id，就利用这个user_id，利用分库时候的规则，反过来定位具体的数据库，比如user_id是234，利用该才的规则，就应该定位到DB1，假如user_id是12343，利用该才的规则，就应该定位到DB2。以此类推，利用分库的规则，反向的路由到具体的DB，这个过程我们称之为“DB路由”。 <BR>当然考虑到数据切分的DB设计必然是非常规，不正统的DB设计。那么什么样的DB设计是正统的DB设计呢？ <BR>我们平常规规矩矩用的基本都是。平常我们会自觉的按照范式来设计我们的数据库，负载高点可能考虑使用相关的Replication机制来提高读写的吞吐和性能，这可能已经可以满足很多需求，但这套机制自身的缺陷还是比较显而易见的（下文会提及）。上面提到的“自觉的按照范式设计”。考虑到数据切分的DB设计，将违背这个通常的规矩和约束，为了切分，我们不得不在数据库的表中出现冗余字段，用作区分字段或者叫做分库的标记字段，比如上面的article的例子中的user_id这样的字段（当然，刚才的例子并没有很好的体现出user_id的冗余性，因为user_id这个字段即使就是不分库，也是要出现的，算是我们捡了便宜吧）。当然冗余字段的出现并不只是在分库的场景下才出现的，在很多大型应用中，冗余也是必须的，这个涉及到高效DB的设计，本文不再赘述。 <BR><BR>2.1.2为什么要数据切分 <BR><BR>上面对什么是数据切分做了个概要的描述和解释，读者可能会疑问，为什么需要数据切分呢？像Oracle这样成熟稳定的数据库，足以支撑海量数据的存储与查询了？为什么还需要数据切片呢？的确，Oracle的DB确实很成熟很稳定，但是高昂的使用费用和高端的硬件支撑不是每一个公司能支付的起的。试想一下一年几千万的使用费用和动辄上千万元的小型机作为硬件支撑，这是一般公司能支付的起的吗？即使就是能支付的起，假如有更好的方案，有更廉价且水平扩展性能更好的方案，我们为什么不选择呢？ <BR>但是，事情总是不尽人意。平常我们会自觉的按照范式来设计我们的数据库，负载高点可能考虑使用相关的Replication机制来提高读写的吞吐和性能，这可能已经可以满足很多需求，但这套机制自身的缺陷还是比较显而易见的。首先它的有效很依赖于读操作的比例，Master往往会成为瓶颈所在，写操作需要顺序排队来执行，过载的话Master首先扛不住，Slaves的数据同步的延迟也可能比较大，而且会大大耗费CPU的计算能力，因为write操作在Master上执行以后还是需要在每台slave机器上都跑一次。这时候 Sharding可能会成为鸡肋了。 Replication搞不定，那么为什么Sharding可以工作呢？道理很简单，因为它可以很好的扩展。我们知道每台机器无论配置多么好它都有自身的物理上限，所以当我们应用已经能触及或远远超出单台机器的某个上限的时候，我们惟有寻找别的机器的帮助或者继续升级的我们的硬件，但常见的方案还是横向扩展, 通过添加更多的机器来共同承担压力。我们还得考虑当我们的业务逻辑不断增长，我们的机器能不能通过线性增长就能满足需求？Sharding可以轻松的将计算，存储，I/O并行分发到多台机器上，这样可以充分利用多台机器各种处理能力，同时可以避免单点失败，提供系统的可用性，进行很好的错误隔离。 <BR>综合以上因素，数据切分是很有必要的，且我们在此讨论的数据切分也是将MySql作为背景的。基于成本的考虑，很多公司也选择了Free且Open的MySql。对MySql有所了解的开发人员可能会知道，MySQL 5 之后才有了数据表分区功能，那么在此之前，很多 MySQL 的潜在用户都对 MySQL 的扩展性有所顾虑，而是否具备分区功能就成了衡量一个数据库可扩展性与否的一个关键指标(当然不是唯一指标)。数据库扩展性是一个永恒的话题，MySQL 的推广者经常会被问到：如在单一数据库上处理应用数据捉襟见肘而需要进行分区化之类的处理，是如何办到的呢? 答案也是Sharding，也就是我们所说的数据切分方案。 <BR>&nbsp;&nbsp;&nbsp; 我们用免费的MySQL和廉价的Server甚至是PC做集群，达到小型机+大型商业DB的效果，减少大量的资金投入，降低运营成本，何乐而不为呢？所以，我们选择Sharding，拥抱Sharding。 <BR><BR>2.1.3怎么做到数据切分 <BR><BR>说到数据切分，再次我们讲对数据切分的方法和形式进行比较详细的阐述和说明。 <BR>数据切分可以是物理上的，对数据通过一系列的切分规则将数据分布到不同的DB服务器上，通过路由规则路由访问特定的数据库，这样一来每次访问面对的就不是单台服务器了，而是N台服务器，这样就可以降低单台机器的负载压力。 <BR>数据切分也可以是数据库内的，对数据通过一系列的切分规则，将数据分布到一个数据库的不同表中，比如将article分为article_001,article_002等子表，若干个子表水平拼合有组成了逻辑上一个完整的article表，这样做的目的其实也是很简单的。举个例子说明，比如article表中现在有5000w条数据，此时我们需要在这个表中增加（insert）一条新的数据，insert完毕后，数据库会针对这张表重新建立索引，5000w行数据建立索引的系统开销还是不容忽视的。但是反过来，假如我们将这个表分成100个table呢，从article_001一直到article_100，5000w行数据平均下来，每个子表里边就只有50万行数据，这时候我们向一张只有50w行数据的table中insert数据后建立索引的时间就会呈数量级的下降，极大了提高了DB的运行时效率，提高了DB的并发量。当然分表的好处还不知这些，还有诸如写操作的锁操作等，都会带来很多显然的好处。 <BR>综上，分库降低了单点机器的负载；分表，提高了数据操作的效率，尤其是Write操作的效率。行文至此我们依然没有涉及到如何切分的问题。接下来，我们将对切分规则进行详尽的阐述和说明。 <BR>上文中提到，要想做到数据的水平切分，在每一个表中都要有相冗余字符作为切分依据和标记字段，通常的应用中我们选用user_id作为区分字段，基于此就有如下三种分库的方式和规则：（当然还可以有其他的方式） <BR>按号段分： <BR>(1) user_id为区分，1～1000的对应DB1，1001～2000的对应DB2，以此类推； <BR>优点：可部分迁移 <BR>缺点：数据分布不均 <BR><BR>(2)hash取模分： <BR>对user_id进行hash（或者如果user_id是数值型的话直接使用user_id的值也可），然后用一个特定的数字，比如应用中需要将一个数据库切分成4个数据库的话，我们就用4这个数字对user_id的hash值进行取模运算，也就是user_id%4,这样的话每次运算就有四种可能：结果为1的时候对应DB1；结果为2的时候对应DB2；结果为3的时候对应DB3；结果为0的时候对应DB4，这样一来就非常均匀的将数据分配到4个DB中。 <BR>优点：数据分布均匀 <BR>缺点：数据迁移的时候麻烦，不能按照机器性能分摊数据 <BR>(3)在认证库中保存数据库配置 <BR>就是建立一个DB，这个DB单独保存user_id到DB的映射关系，每次访问数据库的时候都要先查询一次这个数据库，以得到具体的DB信息，然后才能进行我们需要的查询操作。 <BR>优点：灵活性强，一对一关系 <BR>缺点：每次查询之前都要多一次查询，性能大打折扣 <BR>以上就是通常的开发中我们选择的三种方式，有些复杂的项目中可能会混合使用这三种方式。通过上面的描述，我们对分库的规则也有了简单的认识和了解。当然还会有更好更完善的分库方式，还需要我们不断的探索和发现。 <BR><BR><BR>
<DIV style="TEXT-ALIGN: center">第3章 本课题研究的基本轮廓</DIV><BR><BR>上面的文字，我们按照人类认知事物的规律，what&#61664;why&#61664;how这样的方式阐述了数据库切分的一些概念和意义以及对一些常规的切分规则做了概要的介绍。本课题所讨论的分布数据层并不仅仅如此，它是一个完整的数据层解决方案，它到底是什么样的呢？接下来的文字，我将详细阐述本研究课题的完整思想和实现方式。 <BR>分布式数据方案提供功能如下： <BR>（1）提供分库规则和路由规则（RouteRule简称RR），将上面的说明中提到的三中切分规则直接内嵌入本系统，具体的嵌入方式在接下来的内容中进行详细的说明和论述； <BR>（2）引入集群（Group）的概念，保证数据的高可用性； <BR>（3）引入负载均衡策略（LoadBalancePolicy简称LB）； <BR>（4）引入集群节点可用性探测机制，对单点机器的可用性进行定时的侦测，以保证LB策略的正确实施，以确保系统的高度稳定性； <BR>（5）引入读/写分离，提高数据的查询速度； <BR>仅仅是分库分表的数据层设计也是不够完善的，当某个节点上的DB服务器出现了宕机的情况的时候，会是什么样的呢？是的，我们采用了数据库切分方案，也就是说有N太机器组成了一个完整的DB，如果有一台机器宕机的话，也仅仅是一个DB的N分之一的数据不能访问而已，这是我们能接受的，起码比切分之前的情况好很多了，总不至于整个DB都不能访问。一般的应用中，这样的机器故障导致的数据无法访问是可以接受的，假设我们的系统是一个高并发的电子商务网站呢？单节点机器宕机带来的经济损失是非常严重的。也就是说，现在我们这样的方案还是存在问题的，容错性能是经不起考验的。当然了，问题总是有解决方案的。我们引入集群的概念，在此我称之为Group，也就是每一个分库的节点我们引入多台机器，每台机器保存的数据是一样的，一般情况下这多台机器分摊负载，当出现宕机情况，负载均衡器将分配负载给这台宕机的机器。这样一来， <BR>就解决了容错性的问题。所以我们引入了集群的概念，并将其内嵌入我们的框架中，成为框架的一部分。 <BR><IMG src="http://www.javaeye.com/upload/attachment/115686/8b42ab9e-8170-362e-b8e0-19141412fc54.png"> <BR><BR><BR>如上图所示，整个数据层有Group1，Group2，Group3三个集群组成，这三个集群就是数据水平切分的结果，当然这三个集群也就组成了一个包含完整数据的DB。每一个Group包括1个Master（当然Master也可以是多个）和N个Slave，这些Master和Slave的数据是一致的。 比如Group1中的一个slave发生了宕机现象，那么还有两个slave是可以用的，这样的模型总是不会造成某部分数据不能访问的问题，除非整个Group里的机器全部宕掉，但是考虑到这样的事情发生的概率非常小（除非是断电了，否则不易发生吧）。 <BR>在没有引入集群以前，我们的一次查询的过程大致如下：请求数据层，并传递必要的分库区分字段（通常情况下是user_id）&#61664;数据层根据区分字段Route到具体的DB&#61664;在这个确定的DB内进行数据操作。这是没有引入集群的情况，当时引入集群会是什么样子的呢？看图一即可得知，我们的路由器上规则和策略其实只能路由到具体的Group，也就是只能路由到一个虚拟的Group，这个Group并不是某个特定的物理服务器。接下来需要做的工作就是找到具体的物理的DB服务器，以进行具体的数据操作。基于这个环节的需求，我们引入了负载均衡器的概念（LB）。负载均衡器的职责就是定位到一台具体的DB服务器。具体的规则如下：负载均衡器会分析当前sql的读写特性，如果是写操作或者是要求实时性很强的操作的话，直接将查询负载分到Master，如果是读操作则通过负载均衡策略分配一个Slave。我们的负载均衡器的主要研究放向也就是负载分发策略，通常情况下负载均衡包括随机负载均衡和加权负载均衡。随机负载均衡很好理解，就是从N个Slave中随机选取一个Slave。这样的随机负载均衡是不考虑机器性能的，它默认为每台机器的性能是一样的。假如真实的情况是这样的，这样做也是无可厚非的。假如实际情况并非如此呢？每个Slave的机器物理性能和配置不一样的情况，再使用随机的不考虑性能的负载均衡，是非常不科学的，这样一来会给机器性能差的机器带来不必要的高负载，甚至带来宕机的危险，同时高性能的数据库服务器也不能充分发挥其物理性能。基于此考虑从，我们引入了加权负载均衡，也就是在我们的系统内部通过一定的接口，可以给每台DB服务器分配一个权值，然后再运行时LB根据权值在集群中的比重，分配一定比例的负载给该DB服务器。当然这样的概念的引入，无疑增大了系统的复杂性和可维护性。有得必有失，我们也没有办法逃过的。 <BR>有了分库，有了集群，有了负载均衡器，是不是就万事大吉了呢？事情远没有我们想象的那么简单。虽然有了这些东西，基本上能保证我们的数据层可以承受很大的压力，但是这样的设计并不能完全规避数据库宕机的危害。假如Group1中的slave2宕机了，那么系统的LB并不能得知，这样的话其实是很危险的，因为LB不知道，它还会以为slave2为可用状态，所以还是会给slave2分配负载。这样一来，问题就出来了，客户端很自然的就会发生数据操作失败的错误或者异常。这样是非常不友好的！怎样解决这样的问题呢？我们引入集群节点的可用性探测机制，或者是可用性的数据推送机制。这两种机制有什么不同呢？首先说探测机制吧，顾名思义，探测即使，就是我的数据层客户端，不定时对集群中各个数据库进行可用性的尝试，实现原理就是尝试性链接，或者数据库端口的尝试性访问，都可以做到，当然也可以用JDBC尝试性链接，利用Java的Exception机制进行可用性的判断，具体的会在后面的文字中提到。那数据推送机制又是什么呢？其实这个就要放在现实的应用场景中来讨论这个问题了，一般情况下应用的DB数据库宕机的话我相信DBA肯定是知道的，这个时候DBA手动的将数据库的当前状态通过程序的方式推送到客户端，也就是分布式数据层的应用端，这个时候在更新一个本地的DB状态的列表。并告知LB，这个数据库节点不能使用，请不要给它分配负载。一个是主动的监听机制，一个是被动的被告知的机制。两者各有所长。但是都可以达到同样的效果。这样一来刚才假设的问题就不会发生了，即使就是发生了，那么发生的概率也会降到最低。 <BR>上面的文字中提到的Master和Slave，我们并没有做太多深入的讲解。如图一所示，一个Group由1个Master和N个Slave组成。为什么这么做呢？其中Master负责写操作的负载，也就是说一切写的操作都在Master上进行，而读的操作则分摊到Slave上进行。这样一来的可以大大提高读取的效率。在一般的互联网应用中，经过一些数据调查得出结论，读/写的比例大概在10：1左右，也就是说大量的数据操作是集中在读的操作，这也就是为什么我们会有多个Slave的原因。但是为什么要分离读和写呢？熟悉DB的研发人员都知道，写操作涉及到锁的问题，不管是行锁还是表锁还是块锁，都是比较降低系统执行效率的事情。我们这样的分离是把写操作集中在一个节点上，而读操作其其他的N个节点上进行，从另一个方面有效的提高了读的效率，保证了系统的高可用性。读写分离也会引入新的问题，比如我的Master上的数据怎样和集群中其他的Slave机器保持数据的同步和一致呢?这个是我们不需要过多的关注的问题，MySql的Proxy机制可以帮助我们做到这点，由于Proxy机制与本课题相关性不是太强， <BR>在这里不做详细介绍。 <BR>综上所述，本课题中所研究的分布式数据层的大体功能就是如此。以上是对基本原理的一些讨论和阐述。接下来就系统设计层面，进行深入的剖析和研究。 <BR><BR><BR>
<DIV style="TEXT-ALIGN: center">第4章 系统设计</DIV><BR><BR>4.1系统实现层面的选择 <BR><BR>在引言部分中提到，该系统的实现层面有两种选择，一种是基于JDBC层面上的选择，一种是基于现有数据持久层框架层面上的选择，比如Hibernate，ibatis。两种层面各有长处，也各有不足之处。基于JDBC层面上的系统实现，系统开发难度和后期的使用难度都将大大提高。大大增加了系统的开发费用和维护费用。本课题的定位是在成型的ibatis持久层框架的基础上进行上层的封装，而不是对ibatis源码的直接修改，这样一来使本系统不会对现有框架有太多的侵入性，从而也增加了使用的灵活性。之所以选择ibatis，原因如下： <BR>（1）ibatis的学习成本非常低，熟练的Java Programmer可在非常的短时间内熟练使用ibatis； <BR>（2）ibatis是轻量级的ORM，只是简单的完成了RO，OR的映射，其查询语句也是通过配置文件sql-map.xml文件在原生sql的层面进行简单的配置，也就是说我们没有引入诸如Hibernate那样的HQL的概念，从而增强了sql的可控性，优秀的DBA可以很好的从sql的层面对sql进行优化，使数据层的应用有很强的可控性。Hibernate虽然很强大，但是由于Hibernate是OR的一个重型封装，且引入HQL的概念，不便于DBA团队对sql语句的控制和性能的调优。 <BR>基于以上两点理由，本课题在ORM的产品的选择上选择了易学易用且轻量级的持久层框架ibatis。下面的讨论也都是特定于ibatis的基础上的讨论。 <BR><BR><BR>4.2其他开源框架的选择 <BR><BR>在一些大型的Java应用中，我们通常会采用Spring这样的开源框架，尤其是IoC（DI）这部分，有效的帮助开发人员管理对象的依赖关系和层次，降低系统各层次之间的实体耦合。Spring的优点和用处我相信这是开发人员众所周知的，在此不再赘述。本课题的数据层也将采用Spring做为IoC（DI）的框架。 <BR>4.3系统开发技术和工具介绍 <BR>开发语言：Java JDK1.5 <BR>集成开发环境：Eclipse 3.3.4 <BR>Web环境下测试服务器：JBoss 4.2 <BR>构建工具：淘宝自行研发的构建工具Antx（类似于Maven），当然也可以用Maven <BR>依赖的开源Jar：Spring2.0，ibaits，commons-configuration(读取配置文件)，log4j，junit等 <BR>第5章 系统分析（待续。。） <BR></DIV>]]></description>
</item><item>
<title><![CDATA[javanio:java nio本质与精要]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=46221</link>
<author>toyboysli</author>
<pubDate>2009/6/25 16:19:58</pubDate>
<description><![CDATA[NIO之关键: <BR><B>1.数据缓冲(Buffer)处理</B> <BR>数据缓冲是IO操作的基本元素.从本质上来说,无论是磁盘还是网络IO,应用程序所作的所有事情就是把数据放到相应的数据缓冲当中去(写操作),或者从相应的数据缓冲中提取数据(读操作).至于数据缓冲中的数据IO设备之间的交互,则是操作系统和硬件驱动程序所关心的事情了.因此,数据缓冲在IO操作中具有重要的作用,是操作系统与应用之间的IO桥梁. <BR>在java.nio中, "Direct ByteBuffer"是一个值得关注的Buffer类型.在创建ByteBuffer的时候可以使用ByteBuffer.allocateDirect()来创建一块直接(Direct)的ByteBuffer.这一块数据缓冲和一般的缓冲不一样.<FONT face="" color=#ff0000 size=3>第一，它是一块连续的空间.第二,它的实现不是纯java的代码,而是本地(native)代码.它内存的分配不在Java的堆栈中,不受java内存回收的影响.</FONT><FONT face="" color=#ff0000 size=3>这种直接的ByteBuffer是NIO用来保证性能的重要手段.刚才提到,数据缓冲是操作系统和应用程序之间的IO桥梁.应用程序需要写出去的数据放到数据缓冲中,操作系统从这块缓冲中获得数据执行写操作.当IO设备数据传进来时,操作系统会将数据放到相应的数据缓冲中,应用程序从缓冲中读进数据进行处理.一般的java对象很难胜任这个直接的数据缓冲工作.而操作系统需要的是一片连续不变动的地址空间,才能完成IO操作.在原来的java版本中需要java虚拟机的介入,将数据进行转换,拷贝才能被操作系统所使用.而通过"Direct ByteBuffer", 应用程序能够直接与操作系统进行交流,大大减少了系统调用的次数而提高了执行效率. </FONT><BR>数据缓冲的另外一个重要特点是可以在一个数据缓冲中上再建立一个或多个视图(View)缓冲.这个概念有些类似于数据库视图的概念.同样,在一个数据缓冲之上也可以建立多个逻辑的视图缓冲.视图缓冲的用处很多,例如可以将Byte类型的缓冲当作Int类型的视图,来进行类型转换.视图缓冲也可以将一个大的缓冲看成是很多小的缓冲视图.这对提高性能很有帮助,因为创建物理的数据缓冲(特别是直接的数据缓冲)是非常耗时的操作,而创建视图却非常快.在如Grizzly等框架中就有这方面的考虑. <BR><BR><B>2.异步通道(Channel)</B> <BR>Channel(又称频道)是NIO的另外一个重要的特性.Channel并不是对原有java类的扩充和完善,而是完全崭新的实现.通过Channel, java应用程序能够更好的与操作系统的IO服务结合起来,充分的利用上文提到的ByteBuffer,完成高性能的IO操作.Channel实现也不是纯java的,而是和操作系统结合紧密的本地代码. <BR>Channel的一个重要特点是在网络套接字频道(SocketChannel)中，可以将其设置为异步非阻塞方式. <BR><BR><B>3.有条件的阻塞(Readiness Selection)</B> <BR>类似UNIX的select()或poll().在现在大多数主流OS上,都支持有条件地选择已经准备好的IO通道,这就使得只需要一个线程就能同时有效地管理多个IO通道. <BR>NIO通过几个关键的类来实现这种有条件选择的功能: <BR><FONT face="" color=#0000ff size=3>1) Selector</FONT> <BR>Selector维护了多个注册的Channel以及它们的状态.Channel需要向Selector注册,Selector负责维护和更新Channel的状态,以表明哪些Channel是准备好的. <BR><FONT face="" color=#0000ff size=3>2) SelectableChannel</FONT> <BR>SelectableChannel是可以被Selector所管理的Channel. FileChannel不属于SelectableChannel,而SocketChannel则属于.因此在NIO中,只有网络IO操作才可能被有条件选择. <BR><FONT face="" color=#0000ff size=3>3) SelectionKey</FONT> <BR>SelectionKey用于维护Selector和SelectableChannel之间的映射关系.当一个Channel向Selector注册后,就回返回一个SelectionKey作为注册的凭证]]></description>
</item><item>
<title><![CDATA[转：HttpClient容易忽视的细节——连接关闭]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=46059</link>
<author>toyboysli</author>
<pubDate>2009/6/17 10:50:17</pubDate>
<description><![CDATA[<A href="http://www.javaeye.com/topic/234759Java代码 ">　原著地址：http://www.javaeye.com/topic/234759
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>&nbsp;</DIV>
<DIV class=tools>Java代码 </A><A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://www.javaeye.com/topic/234759#"><IMG alt=复制代码 src="http://www.javaeye.com/images/icon_copy.gif"></A><A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>HttpClient&nbsp;client&nbsp;=&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>new</FONT></STRONG></SPAN><SPAN>&nbsp;HttpClient(); &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>HttpMethod&nbsp;method&nbsp;=&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>new</FONT></STRONG></SPAN><SPAN>&nbsp;GetMethod(</SPAN><SPAN class=string><FONT color=#0000ff>"http://www.apache.org"</FONT></SPAN><SPAN>); &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN></SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>try</FONT></STRONG></SPAN><SPAN>&nbsp;{ &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;client.executeMethod(method); &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>byte</FONT></STRONG></SPAN><SPAN>[]&nbsp;responseBody&nbsp;=&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>null</FONT></STRONG></SPAN><SPAN>; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;responseBody&nbsp;=&nbsp;method.getResponseBody(); &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>}&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>catch</FONT></STRONG></SPAN><SPAN>&nbsp;(HttpException&nbsp;e)&nbsp;{ &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;</SPAN><SPAN class=comment><FONT color=#008200>//&nbsp;TODO&nbsp;Auto-generated&nbsp;catch&nbsp;block </FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;e.printStackTrace(); &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>}&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>catch</FONT></STRONG></SPAN><SPAN>&nbsp;(IOException&nbsp;e)&nbsp;{ &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;</SPAN><SPAN class=comment><FONT color=#008200>//&nbsp;TODO&nbsp;Auto-generated&nbsp;catch&nbsp;block </FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;e.printStackTrace(); &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>}</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>finally</FONT></STRONG></SPAN><SPAN>{ &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;method.releaseConnection(); &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>}&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">HttpClient client = new HttpClient();
HttpMethod method = new GetMethod("http://www.apache.org");
try {
  client.executeMethod(method);
  byte[] responseBody = null;
  
  responseBody = method.getResponseBody();
  
} catch (HttpException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
} catch (IOException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}finally{
  method.releaseConnection();
  
}
</PRE><BR>大部分人使用HttpClient都是使用类似上面的事例代码，包括Apache官方的例子也是如此。最近我在使用HttpClient是发现一次循环发送大量请求到服务器会导致APACHE服务器的链接被占满，后续的请求便排队等待。 <BR>我服务器端APACHE的配置 <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 </A><A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://www.javaeye.com/topic/234759#"><IMG alt=复制代码 src="http://www.javaeye.com/images/icon_copy.gif"></A><A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>Timeout&nbsp;</SPAN><SPAN class=number><FONT color=#c00000>30</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>KeepAlive&nbsp;On&nbsp;&nbsp;&nbsp;#表示服务器端不会主动关闭链接 &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>MaxKeepAliveRequests&nbsp;</SPAN><SPAN class=number><FONT color=#c00000>100</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>KeepAliveTimeout&nbsp;</SPAN><SPAN class=number><FONT color=#c00000>180</FONT></SPAN><SPAN>&nbsp;&nbsp;&nbsp;</SPAN></SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">Timeout 30
KeepAlive On   #表示服务器端不会主动关闭链接
MaxKeepAliveRequests 100
KeepAliveTimeout 180 
</PRE><BR>因此这样的配置就会导致每个链接至少要过180S才会被释放，这样在大量请求访问时就必然会造成链接被占满，请求等待的情况。 <BR>在通过DEBUH后发现HttpClient在method.releaseConnection()后并没有把链接关闭，这个方法只是将链接返回给connection manager。如果使用HttpClient client = new HttpClient()实例化一个HttpClient connection manager默认实现是使用SimpleHttpConnectionManager。SimpleHttpConnectionManager有个构造函数如下 <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 </A><A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://www.javaeye.com/topic/234759#"><IMG alt=复制代码 src="http://www.javaeye.com/images/icon_copy.gif"></A><A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN class=comment><FONT color=#008200>/** </FONT></SPAN>&nbsp;</SPAN></LI>
<LI><SPAN><SPAN class=comment><FONT color=#008200>&nbsp;*&nbsp;The&nbsp;connection&nbsp;manager&nbsp;created&nbsp;with&nbsp;this&nbsp;constructor&nbsp;will&nbsp;try&nbsp;to&nbsp;keep&nbsp;the&nbsp; </FONT></SPAN>&nbsp;</SPAN></LI>
<LI><SPAN><SPAN class=comment><FONT color=#008200>&nbsp;*&nbsp;connection&nbsp;open&nbsp;(alive)&nbsp;between&nbsp;consecutive&nbsp;requests&nbsp;if&nbsp;the&nbsp;alwaysClose&nbsp; </FONT></SPAN>&nbsp;</SPAN></LI>
<LI><SPAN><SPAN class=comment><FONT color=#008200>&nbsp;*&nbsp;parameter&nbsp;is&nbsp;set&nbsp;to&nbsp;&lt;tt&gt;false&lt;/tt&gt;.&nbsp;Otherwise&nbsp;the&nbsp;connection&nbsp;manager&nbsp;will&nbsp; </FONT></SPAN>&nbsp;</SPAN></LI>
<LI><SPAN><SPAN class=comment><FONT color=#008200>&nbsp;*&nbsp;always&nbsp;close&nbsp;connections&nbsp;upon&nbsp;release. </FONT></SPAN>&nbsp;</SPAN></LI>
<LI><SPAN><SPAN class=comment><FONT color=#008200>&nbsp;*&nbsp; </FONT></SPAN>&nbsp;</SPAN></LI>
<LI><SPAN><SPAN class=comment><FONT color=#008200>&nbsp;*&nbsp;@param&nbsp;alwaysClose&nbsp;if&nbsp;set&nbsp;&lt;tt&gt;true&lt;/tt&gt;,&nbsp;the&nbsp;connection&nbsp;manager&nbsp;will&nbsp;always </FONT></SPAN>&nbsp;</SPAN></LI>
<LI><SPAN><SPAN class=comment><FONT color=#008200>&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;close&nbsp;connections&nbsp;upon&nbsp;release. </FONT></SPAN>&nbsp;</SPAN></LI>
<LI><SPAN><SPAN class=comment><FONT color=#008200>&nbsp;*/</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN></SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>public</FONT></STRONG></SPAN><SPAN>&nbsp;SimpleHttpConnectionManager(</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>boolean</FONT></STRONG></SPAN><SPAN>&nbsp;alwaysClose)&nbsp;{ &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>super</FONT></STRONG></SPAN><SPAN>(); &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>this</FONT></STRONG></SPAN><SPAN>.alwaysClose&nbsp;=&nbsp;alwaysClose; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>}&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">/**
 * The connection manager created with this constructor will try to keep the 
 * connection open (alive) between consecutive requests if the alwaysClose 
 * parameter is set to &lt;tt&gt;false&lt;/tt&gt;. Otherwise the connection manager will 
 * always close connections upon release.
 * 
 * @param alwaysClose if set &lt;tt&gt;true&lt;/tt&gt;, the connection manager will always
 *    close connections upon release.
 */
public SimpleHttpConnectionManager(boolean alwaysClose) {
    super();
    this.alwaysClose = alwaysClose;
}
</PRE><BR>看方法注释我们就可以看到如果alwaysClose设为true在链接释放之后connection manager 就会关闭链。在我们HttpClient client = new HttpClient()这样实例化一个client时connection manager是这样被实例化的 <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 </A><A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://www.javaeye.com/topic/234759#"><IMG alt=复制代码 src="http://www.javaeye.com/images/icon_copy.gif"></A><A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>this</FONT></STRONG></SPAN><SPAN>.httpConnectionManager&nbsp;=&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>new</FONT></STRONG></SPAN><SPAN>&nbsp;SimpleHttpConnectionManager();&nbsp;&nbsp;</SPAN></SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">this.httpConnectionManager = new SimpleHttpConnectionManager();
</PRE><BR>因此alwaysClose默认是false,connection是不会被主动关闭的，因此我们就有了一个客户端关闭链接的方法。 <BR><STRONG>方法一：</STRONG> <BR>把事例代码中的第一行实例化代码改为如下即可，在method.releaseConnection();之后connection manager会关闭connection 。 <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 </A><A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://www.javaeye.com/topic/234759#"><IMG alt=复制代码 src="http://www.javaeye.com/images/icon_copy.gif"></A><A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>HttpClient&nbsp;client&nbsp;=&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>new</FONT></STRONG></SPAN><SPAN>&nbsp;HttpClient(</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>new</FONT></STRONG></SPAN><SPAN>&nbsp;HttpClientParams(),</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>new</FONT></STRONG></SPAN><SPAN>&nbsp;SimpleHttpConnectionManager(</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>true</FONT></STRONG></SPAN><SPAN>)&nbsp;);&nbsp;&nbsp;</SPAN></SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">HttpClient client = new HttpClient(new HttpClientParams(),new SimpleHttpConnectionManager(true) );
</PRE><BR><STRONG>方法二：</STRONG> <BR>实例化代码使用：HttpClient client = new HttpClient(); <BR>在method.releaseConnection();之后加上 <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 </A><A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://www.javaeye.com/topic/234759#"><IMG alt=复制代码 src="http://www.javaeye.com/images/icon_copy.gif"></A><A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>((SimpleHttpConnectionManager)client.getHttpConnectionManager()).shutdown();&nbsp;&nbsp;</SPAN></SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">((SimpleHttpConnectionManager)client.getHttpConnectionManager()).shutdown();</PRE><BR>shutdown源代码很简单，看了一目了然 <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 </A><A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://www.javaeye.com/topic/234759#"><IMG alt=复制代码 src="http://www.javaeye.com/images/icon_copy.gif"></A><A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>public</FONT></STRONG></SPAN><SPAN>&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>void</FONT></STRONG></SPAN><SPAN>&nbsp;shutdown()&nbsp;{ &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;httpConnection.close(); &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>}&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">public void shutdown() {
    httpConnection.close();
}
</PRE><BR><STRONG>方法三：</STRONG> <BR>实例化代码使用：HttpClient client = new HttpClient(); <BR>在method.releaseConnection();之后加上 <BR>client.getHttpConnectionManager().closeIdleConnections(0);此方法源码代码如下： <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 </A><A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://www.javaeye.com/topic/234759#"><IMG alt=复制代码 src="http://www.javaeye.com/images/icon_copy.gif"></A><A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>public</FONT></STRONG></SPAN><SPAN>&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>void</FONT></STRONG></SPAN><SPAN>&nbsp;closeIdleConnections(</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>long</FONT></STRONG></SPAN><SPAN>&nbsp;idleTimeout)&nbsp;{ &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>long</FONT></STRONG></SPAN><SPAN>&nbsp;maxIdleTime&nbsp;=&nbsp;System.currentTimeMillis()&nbsp;-&nbsp;idleTimeout; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>if</FONT></STRONG></SPAN><SPAN>&nbsp;(idleStartTime&nbsp;&lt;=&nbsp;maxIdleTime)&nbsp;{ &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;httpConnection.close(); &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>}&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">public void closeIdleConnections(long idleTimeout) {
    long maxIdleTime = System.currentTimeMillis() - idleTimeout;
    if (idleStartTime &lt;= maxIdleTime) {
        httpConnection.close();
    }
}
</PRE><BR>将idleTimeout设为0可以确保链接被关闭。 <BR>以上这三种方法都是有客户端主动关闭TCP链接的方法。下面再介绍由服务器端自动关闭链接的方法。 <BR><STRONG>方法四：</STRONG> <BR>代码实现很简单，所有代码就和最上面的事例代码一样。只需要在HttpMethod method = new GetMethod("http://www.apache.org");加上一行HTTP头的设置即可 <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 </A><A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://www.javaeye.com/topic/234759#"><IMG alt=复制代码 src="http://www.javaeye.com/images/icon_copy.gif"></A><A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>method.setRequestHeader(</SPAN><SPAN class=string><FONT color=#0000ff>"Connection"</FONT></SPAN><SPAN>,&nbsp;</SPAN><SPAN class=string><FONT color=#0000ff>"close"</FONT></SPAN><SPAN>);&nbsp;&nbsp;</SPAN></SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">method.setRequestHeader("Connection", "close");
</PRE><BR>看一下HTTP协议中关于这个属性的定义： <BR>HTTP/1.1 defines the "close" connection option for the sender to signal that the connection will be closed after completion of the response. For example, <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Connection: close <BR>现在再说一下客户端关闭链接和服务器端关闭链接的区别。如果采用客户端关闭链接的方法，在客户端的机器上使用netstat –an命令会看到很多TIME_WAIT的TCP链接。如果服务器端主动关闭链接这中情况就出现在服务器端。 <BR>参考WIKI上的说明http://wiki.apache.org/HttpComponents/FrequentlyAskedConnectionManagementQuestions <BR>The TIME_WAIT state is a protection mechanism in TCP. The side that closes a socket connection orderly will keep the connection in state TIME_WAIT for some time, typically between 1 and 4 minutes. <BR>TIME_WAIT的状态会出现在主动关闭链接的这一端。TCP协议中TIME_WAIT状态主要是为了保证数据的完整传输。具体可以参考此文档： <BR>http://www.softlab.ntua.gr/facilities/documentation/unix/unix-socket-faq/unix-socket-faq-2.html#ss2.7 <BR><STRONG>另外强调一下使用上面这些方法关闭链接是在我们的应用中明确知道不需要重用链接时可以主动关闭链接来释放资源。如果你的应用是需要重用链接的话就没必要这么做，使用原有的链接还可以提供性能。</STRONG> </A>]]></description>
</item><item>
<title><![CDATA[spring 事务管理探讨]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=45813</link>
<author>toyboysli</author>
<pubDate>2009/6/5 9:56:40</pubDate>
<description><![CDATA[声明式的事务管理（Declarative transaction management）： <BR>&lt;1&gt;事务配置方式： <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 <A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://log-cd.javaeye.com/blog/212780#"><IMG alt=复制代码 src="http://log-cd.javaeye.com/images/icon_copy.gif"></A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>&lt;!--&nbsp;dataSource&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>for</FONT></STRONG></SPAN><SPAN>&nbsp;MySQL&nbsp;--&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;bean&nbsp;id=</SPAN><SPAN class=string><FONT color=#0000ff>"dataSource"</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>class</FONT></STRONG></SPAN><SPAN>=</SPAN><SPAN class=string><FONT color=#0000ff>"org.apache.commons.dbcp.BasicDataSource"</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;destroy-method=</SPAN><SPAN class=string><FONT color=#0000ff>"close"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"driverClassName"</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value=</SPAN><SPAN class=string><FONT color=#0000ff>"com.mysql.jdbc.Driver"</FONT></SPAN><SPAN>&nbsp;/&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"url"</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value=</SPAN><SPAN class=string><FONT color=#0000ff>"jdbc:mysql://localhost:3306/springapp"</FONT></SPAN><SPAN>&nbsp;/&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"username"</FONT></SPAN><SPAN>&nbsp;value=</SPAN><SPAN class=string><FONT color=#0000ff>"root"</FONT></SPAN><SPAN>&nbsp;/&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"password"</FONT></SPAN><SPAN>&nbsp;value=</SPAN><SPAN class=string><FONT color=#0000ff>"root"</FONT></SPAN><SPAN>&nbsp;/&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;/bean&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">&lt;!-- dataSource for MySQL --&gt;
&lt;bean id="dataSource"
		class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close"&gt;
		&lt;property name="driverClassName"
			value="com.mysql.jdbc.Driver" /&gt;
		&lt;property name="url"
			value="jdbc:mysql://localhost:3306/springapp" /&gt;
		&lt;property name="username" value="root" /&gt;
		&lt;property name="password" value="root" /&gt;
&lt;/bean&gt;	</PRE><BR><BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 <A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://log-cd.javaeye.com/blog/212780#"><IMG alt=复制代码 src="http://log-cd.javaeye.com/images/icon_copy.gif"></A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>&lt;!--&nbsp;Hibernate&nbsp;SessionFactory&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>for</FONT></STRONG></SPAN><SPAN>&nbsp;MySQL&nbsp;--&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;bean&nbsp;id=</SPAN><SPAN class=string><FONT color=#0000ff>"sessionFactory"</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>class</FONT></STRONG></SPAN><SPAN>=</SPAN><SPAN class=string><FONT color=#0000ff>"org.springframework.orm.hibernate3.LocalSessionFactoryBean"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"dataSource"</FONT></SPAN><SPAN>&nbsp;ref=</SPAN><SPAN class=string><FONT color=#0000ff>"dataSource"</FONT></SPAN><SPAN>&nbsp;/&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"mappingDirectoryLocations"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;list&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;value&gt;classpath:/&lt;/value&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/list&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"hibernateProperties"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;props&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"hibernate.dialect"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;org.hibernate.dialect.MySQLDialect &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/prop&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"hibernate.show_sql"</FONT></SPAN><SPAN>&gt;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>true</FONT></STRONG></SPAN><SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"hibernate.jdbc.fetch_size"</FONT></SPAN><SPAN>&gt;</SPAN><SPAN class=number><FONT color=#c00000>10</FONT></SPAN><SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"hibernate.jdbc.batch_size"</FONT></SPAN><SPAN>&gt;</SPAN><SPAN class=number><FONT color=#c00000>50</FONT></SPAN><SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/props&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/bean&gt;&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">&lt;!-- Hibernate SessionFactory for MySQL --&gt;
&lt;bean id="sessionFactory"
	class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"&gt;
		&lt;property name="dataSource" ref="dataSource" /&gt;
		&lt;property name="mappingDirectoryLocations"&gt;
			&lt;list&gt;
				&lt;value&gt;classpath:/&lt;/value&gt;
			&lt;/list&gt;
		&lt;/property&gt;
		&lt;property name="hibernateProperties"&gt;
			&lt;props&gt;
				&lt;prop key="hibernate.dialect"&gt;
					org.hibernate.dialect.MySQLDialect
				&lt;/prop&gt;
				&lt;prop key="hibernate.show_sql"&gt;true&lt;/prop&gt;
				&lt;prop key="hibernate.jdbc.fetch_size"&gt;10&lt;/prop&gt;
				&lt;prop key="hibernate.jdbc.batch_size"&gt;50&lt;/prop&gt;
			&lt;/props&gt;
		&lt;/property&gt;
&lt;/bean&gt;</PRE><BR><BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 <A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://log-cd.javaeye.com/blog/212780#"><IMG alt=复制代码 src="http://log-cd.javaeye.com/images/icon_copy.gif"></A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>&lt;!—define&nbsp;transactionManager&nbsp;--&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;bean&nbsp;id=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionManager"</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>class</FONT></STRONG></SPAN><SPAN>=</SPAN><SPAN class=string><FONT color=#0000ff>"org.springframework.orm.hibernate3.HibernateTransactionManager"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"sessionFactory"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&lt;ref&nbsp;local=</SPAN><SPAN class=string><FONT color=#0000ff>"sessionFactory"</FONT></SPAN><SPAN>&nbsp;/&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/bean&gt;&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">&lt;!—define transactionManager --&gt;
&lt;bean id="transactionManager"
  class="org.springframework.orm.hibernate3.HibernateTransactionManager"&gt;
  &lt;property name="sessionFactory"&gt;
   &lt;ref local="sessionFactory" /&gt;
  &lt;/property&gt;
&lt;/bean&gt;</PRE><BR><BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 <A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://log-cd.javaeye.com/blog/212780#"><IMG alt=复制代码 src="http://log-cd.javaeye.com/images/icon_copy.gif"></A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>&lt;!—business&nbsp;layer&nbsp;--&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;bean&nbsp;id=</SPAN><SPAN class=string><FONT color=#0000ff>"userManageService"</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>class</FONT></STRONG></SPAN><SPAN>=</SPAN><SPAN class=string><FONT color=#0000ff>"com.test.service.impl.userManageServiceImpl"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"userLoginDao"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&lt;ref&nbsp;bean=</SPAN><SPAN class=string><FONT color=#0000ff>"userLoginDao"</FONT></SPAN><SPAN>&nbsp;/&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>… &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/bean&gt;&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">&lt;!—business layer --&gt;
&lt;bean id="userManageService"
  class="com.test.service.impl.userManageServiceImpl"&gt;
  &lt;property name="userLoginDao"&gt;
   &lt;ref bean="userLoginDao" /&gt;
  &lt;/property&gt;
…
&lt;/bean&gt;</PRE><BR><BR>第一种：使用TransactionProxyFactoryBean，配置声明式事务的方法如下。 <BR>(1)表比较少的情况: <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 <A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://log-cd.javaeye.com/blog/212780#"><IMG alt=复制代码 src="http://log-cd.javaeye.com/images/icon_copy.gif"></A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>&lt;bean&nbsp;id=</SPAN><SPAN class=string><FONT color=#0000ff>"userManagerServiceProxy"</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>class</FONT></STRONG></SPAN><SPAN>=</SPAN><SPAN class=string><FONT color=#0000ff>"org.springframework.transaction.interceptor.TransactionProxyFactoryBean"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;!--&nbsp;配置事务管理器&nbsp;--&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionManager"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&lt;ref&nbsp;bean=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionManager"</FONT></SPAN><SPAN>&nbsp;/&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;!--&nbsp;此属性指定目标类本身是否是代理的对象，如果目标类没有实现任何类，就设为</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>true</FONT></STRONG></SPAN><SPAN>代表自己&nbsp;--&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"proxyTargetClass"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&lt;value&gt;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>false</FONT></STRONG></SPAN><SPAN>&lt;/value&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"proxyInterfaces"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&lt;value&gt;&nbsp;com.test.service.userManageService&lt;/value&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;!--&nbsp;目标bean&nbsp;--&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"target"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&lt;ref&nbsp;bean=</SPAN><SPAN class=string><FONT color=#0000ff>"userManageService"</FONT></SPAN><SPAN>/&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;!--&nbsp;配置事务属性&nbsp;--&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionAttributes"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&lt;props&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"delete*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"add*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"update*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"save*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"find*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,readOnly&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;/props&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/bean&gt;&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">&lt;bean id="userManagerServiceProxy"
  class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"&gt;
  &lt;!-- 配置事务管理器 --&gt;
  &lt;property name="transactionManager"&gt;
   &lt;ref bean="transactionManager" /&gt;
  &lt;/property&gt;
  &lt;!-- 此属性指定目标类本身是否是代理的对象，如果目标类没有实现任何类，就设为true代表自己 --&gt;
  &lt;property name="proxyTargetClass"&gt;
   &lt;value&gt;false&lt;/value&gt;
  &lt;/property&gt;
  &lt;property name="proxyInterfaces"&gt;
   &lt;value&gt; com.test.service.userManageService&lt;/value&gt;
  &lt;/property&gt;
  &lt;!-- 目标bean --&gt;
  &lt;property name="target"&gt;
   &lt;ref bean="userManageService"/&gt;
  &lt;/property&gt;
  &lt;!-- 配置事务属性 --&gt;
&lt;property name="transactionAttributes"&gt;
   &lt;props&gt;
&lt;prop key="delete*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt;
&lt;prop key="add*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt;
&lt;prop key="update*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt;
&lt;prop key="save*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt;
&lt;prop key="find*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,readOnly&lt;/prop&gt;
&lt;/props&gt;
&lt;/property&gt;
&lt;/bean&gt;</PRE><BR>(2)利用继承的思想简化配置,适合相对比较多的模块时使用。 <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 <A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://log-cd.javaeye.com/blog/212780#"><IMG alt=复制代码 src="http://log-cd.javaeye.com/images/icon_copy.gif"></A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>&nbsp;&lt;bean&nbsp;id=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionBase"</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>class</FONT></STRONG></SPAN><SPAN>=</SPAN><SPAN class=string><FONT color=#0000ff>"org.springframework.transaction.interceptor.TransactionProxyFactoryBean"</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;lazy-init=</SPAN><SPAN class=string><FONT color=#0000ff>"true"</FONT></SPAN><SPAN>&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>abstract</FONT></STRONG></SPAN><SPAN>=</SPAN><SPAN class=string><FONT color=#0000ff>"true"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;!--&nbsp;配置事务管理器&nbsp;--&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionManager"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&lt;ref&nbsp;bean=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionManager"</FONT></SPAN><SPAN>&nbsp;/&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;!--&nbsp;配置事务属性&nbsp;--&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionAttributes"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&lt;props&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"delete*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"add*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"update*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"save*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"find*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,readOnly&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;/props&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/bean&gt;&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code"> &lt;bean id="transactionBase"
  class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
  lazy-init="true" abstract="true"&gt;
  &lt;!-- 配置事务管理器 --&gt;
  &lt;property name="transactionManager"&gt;
   &lt;ref bean="transactionManager" /&gt;
  &lt;/property&gt;
  &lt;!-- 配置事务属性 --&gt;
  &lt;property name="transactionAttributes"&gt;
   &lt;props&gt;
&lt;prop key="delete*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt;
&lt;prop key="add*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt;
&lt;prop key="update*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt;
&lt;prop key="save*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt;
&lt;prop key="find*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,readOnly&lt;/prop&gt;
&lt;/props&gt;
&lt;/property&gt;
&lt;/bean&gt;</PRE><BR>而具体的模块可以简单的这样配置。只要指明它的parent（父类）就可以了。父类一般把abstract="true"，因为在容器加载的时候不需要初始化，等到用的时候再有它的子类调用的时候，再去初始化。 <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 <A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://log-cd.javaeye.com/blog/212780#"><IMG alt=复制代码 src="http://log-cd.javaeye.com/images/icon_copy.gif"></A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>&lt;bean&nbsp;id=</SPAN><SPAN class=string><FONT color=#0000ff>"userManageServiceProxy"</FONT></SPAN><SPAN>&nbsp;parent=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionBase"</FONT></SPAN><SPAN>&nbsp;&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"target"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;ref&nbsp;bean=</SPAN><SPAN class=string><FONT color=#0000ff>"userManageService"</FONT></SPAN><SPAN>/&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/bean&gt;&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">&lt;bean id="userManageServiceProxy" parent="transactionBase" &gt;
  &lt;property name="target"&gt;
  &lt;ref bean="userManageService"/&gt;
  &lt;/property&gt;
&lt;/bean&gt;</PRE><BR>第二种：自动创建事务代理的方式。主要利用BeanNameAutoProxyCreator自动创建事务代理 <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 <A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://log-cd.javaeye.com/blog/212780#"><IMG alt=复制代码 src="http://log-cd.javaeye.com/images/icon_copy.gif"></A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>&nbsp;&nbsp;&lt;!--利用了拦截器的原理。--&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;bean&nbsp;id=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionInterceptor"</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>class</FONT></STRONG></SPAN><SPAN>=</SPAN><SPAN class=string><FONT color=#0000ff>"org.springframework.transaction.interceptor.TransactionInterceptor"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionManager"</FONT></SPAN><SPAN>&gt;&nbsp; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;ref&nbsp;bean=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionManager"</FONT></SPAN><SPAN>&nbsp;/&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;!--&nbsp;配置事务属性&nbsp;--&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionAttributes"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&lt;props&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"delete*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"add*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"update*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"save*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"find*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,readOnly&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;/props&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/bean&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;bean&nbsp;id=</SPAN><SPAN class=string><FONT color=#0000ff>"serviceProxy&nbsp;"</FONT></SPAN><SPAN>&nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;</SPAN><SPAN class=keyword><STRONG><FONT color=#7f0055>class</FONT></STRONG></SPAN><SPAN>=</SPAN><SPAN class=string><FONT color=#0000ff>"org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"beanNames"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;list&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;value&gt;userManageService&lt;/value&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/list&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;!--或者直接用&nbsp;&lt;value&gt;*Service&lt;/value&gt;--&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"interceptorNames"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&lt;list&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;value&gt;transactionInterceptor&lt;/value&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/list&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/property&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/bean&gt;&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">  &lt;!--利用了拦截器的原理。--&gt;
  &lt;bean id="transactionInterceptor"
  class="org.springframework.transaction.interceptor.TransactionInterceptor"&gt;
  &lt;property name="transactionManager"&gt; 
  &lt;ref bean="transactionManager" /&gt;
  &lt;/property&gt;
  &lt;!-- 配置事务属性 --&gt;
  &lt;property name="transactionAttributes"&gt;
   &lt;props&gt;
&lt;prop key="delete*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt;
&lt;prop key="add*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt;
&lt;prop key="update*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt;
&lt;prop key="save*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>&lt;/prop&gt;
&lt;prop key="find*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,readOnly&lt;/prop&gt;
&lt;/props&gt;
&lt;/property&gt;
&lt;/bean&gt;
&lt;bean id="serviceProxy "
  class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"&gt;
 &lt;property name="beanNames"&gt;
&lt;list&gt;
&lt;value&gt;userManageService&lt;/value&gt;
&lt;/list&gt;
&lt;!--或者直接用 &lt;value&gt;*Service&lt;/value&gt;--&gt;
&lt;/property&gt;
  &lt;property name="interceptorNames"&gt;
   &lt;list&gt;
&lt;value&gt;transactionInterceptor&lt;/value&gt;
&lt;/list&gt;
&lt;/property&gt;
&lt;/bean&gt;</PRE><BR>&lt;2&gt;事务中异常 <BR>
<DIV class=dp-highlighter>
<DIV class=bar>
<DIV class=tools>Java代码 <A title=复制代码 onclick="dp.sh.Toolbar.CopyToClipboard(this);return false;" href="http://log-cd.javaeye.com/blog/212780#"><IMG alt=复制代码 src="http://log-cd.javaeye.com/images/icon_copy.gif"></A></DIV></DIV>
<OL class=dp-j>
<LI><SPAN><SPAN>&lt;!--&nbsp;配置事务属性&nbsp;--&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;property&nbsp;name=</SPAN><SPAN class=string><FONT color=#0000ff>"transactionAttributes"</FONT></SPAN><SPAN>&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&nbsp;&nbsp;&nbsp;&lt;props&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;!--指定了&nbsp;</SPAN><SPAN class=string><FONT color=#0000ff>"</FONT><SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN><FONT color=#0000ff>"</FONT></SPAN><SPAN>，表示在当前的事务中执行操作，如果事务不存在就建立一个新的--&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"delete*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,-ProgramException&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"add*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,-ProgramException&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"update*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,&nbsp;-ProgramException&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"save*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,&nbsp;-ProgramException&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;prop&nbsp;key=</SPAN><SPAN class=string><FONT color=#0000ff>"find*"</FONT></SPAN><SPAN>&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,readOnly&lt;/prop&gt; &nbsp;&nbsp;</SPAN></SPAN></LI>
<LI><SPAN>&lt;/props&gt; &nbsp;&nbsp;</SPAN></LI>
<LI><SPAN>&lt;/property&gt;&nbsp;&nbsp;</SPAN></LI></OL></DIV><PRE class=java style="DISPLAY: none" name="code">&lt;!-- 配置事务属性 --&gt;
&lt;property name="transactionAttributes"&gt;
   &lt;props&gt;
&lt;!--指定了 "<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>"，表示在当前的事务中执行操作，如果事务不存在就建立一个新的--&gt;
&lt;prop key="delete*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,-ProgramException&lt;/prop&gt;
&lt;prop key="add*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,-ProgramException&lt;/prop&gt;
&lt;prop key="update*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>, -ProgramException&lt;/prop&gt;
&lt;prop key="save*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>, -ProgramException&lt;/prop&gt;
&lt;prop key="find*"&gt;<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>,readOnly&lt;/prop&gt;
&lt;/props&gt;
&lt;/property&gt;</PRE><BR><BR>Spring中对异常的回滚，默认是在抛出运行时异常(RuntimeException)时才回滚，对非运行时异常不回滚。如果使用-Exception,意思是对所有的异常异常都回滚。Exception前面加上 "-" 时，表示发生指定异常时撤消操作(rollback)，如果前面加上 "+"，表示发生异常时立即提交（commit）。 <BR>要想用Spring的事务管理机制，就需要把数据库的连接交给Spring来管理，（JDBC,SESSION道理一样），如果使用Hibernate框架，要把Session交给Spring管理。在整个Service方法调用中，虽然Sevice调用了多个Dao，但是整个过程中Session只有一个。也就是说你对数据库的DML操作，都会先保存在这个Session中，包括update,insert,delete。当发生异常（这个异常可以是数据库的，也可以是程序的），Spring会把这个Session中对应的DML操作回滚。 <BR>&lt;3&gt;事务的属性 <BR>(1) 传播行为 <BR>
<UL>
<LI>PROPAGATION_MANDATORY: 方法必须在一个现存的事务中进行，否则丢出异常 
<LI>PROPAGATION_NESTED: 在一个嵌入的事务中进行，如果不是，则同<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN> 
<LI>PROPAGATION_NEVER: 指出不应在事务中进行，如果有就丢出异常 
<LI>PROPAGATION_NOT_SUPPORTED: 指出不应在事务中进行，如果有就暂停现存的事务 
<LI><SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>: 在当前的事务中进行，如果没有就建立一个新的事务 
<LI>PROPAGATION_REQUIRES_NEW: 建立一个新的事务，如果现存一个事务就暂停它 
<LI>PROPAGATION_SUPPORTS: 支持现在的事务，如果没有就以非事务的方式执行 </LI></UL><BR>(2) 隔离层级 <BR>
<UL>
<LI>ISOLATION_DEFAULT: 使用底层数据库预设的隔离层级 
<LI>ISOLATION_READ_COMMITTED: 允许事务读取其他并行的事务已经送出（Commit）的数据字段，可以防止Dirty read问题 
<LI>ISOLATION_READ_UNCOMMITTED: 允许事务读取其他并行的事务还没送出的数据，会发生Dirty、Nonrepeatable、Phantom read等问题 
<LI>ISOLATION_REPEATABLE_READ: 要求多次读取的数据必须相同，除非事务本身更新数据，可防止Dirty、Nonrepeatable read问题 
<LI>ISOLATION_SERIALIZABLE: 完整的隔离层级，可防止Dirty、Nonrepeatable、Phantom read等问题，会锁定对应的数据表格，因而有效率问题 </LI></UL><BR>(3) 只读提示（Read-only hints） <BR>如果事务只进行读取的动作，则可以利用底层数据库在只读操作时发生的一些最佳化动作，由于这个动作利用到数据库在只读的事务操作最佳化，因而必须在事务中才有效，也就是说要搭配传播行为<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED来设置。 <BR>(4)事务超时期间（The transaction timeout period） <BR>有的事务操作可能延续很长一段的时间，事务本身可能关联到数据表的锁定，因而长时间的事务操作会有效率上的问题，对于过长的事务操作，考虑Roll back事务并要求重新操作，而不是无限时的等待事务完成。 <BR>可以设置事务超时期间，计时是从事务开始时，所以这个设置必须搭配传播行为<SPAN class=hilite1><FONT style="BACKGROUND-COLOR: #ffff00">PROPAGATION_REQUIRED</FONT></SPAN>、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED来设置。 <BR>事务的超时属性以timeout_为前缀和一个整型数字定义，例如： &lt;prop key="query*"&gt;PROPAGATION_REGUIRED,timeout_5,readOnly&lt;/prop&gt; <BR>]]></description>
</item><item>
<title><![CDATA[不要依赖hibernate的二级缓存]]></title>
<link>http://blogger.org.cn/blog/more.asp?name=toyboysli&amp;id=45064</link>
<author>toyboysli</author>
<pubDate>2009/4/26 22:28:07</pubDate>
<description><![CDATA[<STRONG>关键字: 不要依赖hibernate的二级缓存</STRONG> 
<DIV class=blog_content>
<DIV class=blog_content>一、hibernate的二级缓存 <BR>如果开启了二级缓存，hibernate在执行任何一次查询的之后，都会把得到的结果集放到缓存中，缓存结构可以看作是一个hash table，key是数据库记录的id，value是id对应的pojo对象。当用户根据id查询对象的时候（load、iterator方法），会首先在缓存中查找，如果没有找到再发起数据库查询。但是如果使用hql发起查询（find, query方法）则不会利用二级缓存，而是直接从数据库获得数据，但是它会把得到的数据放到二级缓存备用。也就是说，基于hql的查询，对二级缓存是只写不读的。 <BR>针对二级缓存的工作原理，采用iterator取代list来提高二级缓存命中率的想法是不可行的。Iterator的工作方式是根据检索条件从数据库中选取所有目标数据的id，然后用这些id一个一个的到二级缓存里面做检索，如果找到就直接加载，找不到就向数据库做查询。因此假如 iterator检索100条数据的话，最好情况是100%全部命中，最坏情况是0%命中，执行101条sql把所有数据选出来。而list虽然不利用缓存，但是它只会发起1条sql取得所有数据。在合理利用分页查询的情况下，list整体效率高于iterator。 <BR>二级缓存的失效机制由hibernate控制，当某条数据被修改之后，hibernate会根据它的id去做缓存失效操作。基于此机制，如果数据表不是被hibernate独占（比如同时使用JDBC或者ado等），那么二级缓存无法得到有效控制。 <BR>由于hibernate的缓存接口很灵活，cache provider可以方便的切换，因此支持cluster环境不是大问题，通过使用swarmcache、jboss cache等支持分布式的缓存方案，可以实现。但是问题在于: <BR>1、 分布式缓存本身成本偏高（比如使用同步复制模式的jboss cache） <BR>2、 分布式环境通常对事务控制有较高要求，而目前的开源缓存方案对事务缓存（transaction cache）支持得不够好。当jta事务发生会滚，缓存的最后更新结果很难预料。这一点会带来很大的部署成本，甚至得不偿失。 <BR>结论：不应把hibernate二级缓存作为优化的主要手段，一般情况下建议不要使用。 <BR>原因如下： <BR>1、 由于hibernate批量操作的性能不如sql，而且为了兼容1.0的dao类，所以项目中有保留了sql操作。哪些数据表是单纯被hibernate独占无法统计，而且随着将来业务的发展可能会有很大变数。因此不宜采用二级缓存。 <BR>2、 针对系统业务来说，基于id检索的二级缓存命中率极为有限，hql被大量采用，二级缓存对性能的提升很有限。 <BR>3、 hibernate 3.0在做批量修改、批量更新的时候，是不会同步更新二级缓存的，该问题在hibernate 3.2中是否仍然存在尚不确定。 <BR>二、hibernate的查询缓存 <BR>查询缓存的实现机制与二级缓存基本一致，最大的差异在于放入缓存中的key是查询的语句，value是查询之后得到的结果集的id列表。表面看来这样的方案似乎能解决hql利用缓存的问题，但是需要注意的是，构成key的是：hql生成的sql、sql的参数、排序、分页信息等。也就是说如果你的 hql有小小的差异，比如第一条hql取1-50条数据，第二条hql取20-60条数据，那么hibernate会认为这是两个完全不同的key，无法重复利用缓存。因此利用率也不高。 <BR>另外一个需要注意的问题是，查询缓存和二级缓存是有关联关系的，他们不是完全独立的两套东西。假如一个查询条件hql_1，第一次被执行的时候，它会从数据库取得数据，然后把查询条件作为key，把返回数据的所有id列表作为value（请注意仅仅是id）放到查询缓存中，同时整个结果集放到 class缓存（也就是二级缓存），key是id，value是pojo对象。当你再次执行hql_1，它会从缓存中得到id列表，然后根据这些列表一个一个的到class缓存里面去找pojo对象，如果找不到就向数据库发起查询。也就是说，如果二级缓存配置了超时时间（或者发呆时间），就有可能出现查询缓存命中了，获得了id列表，但是class里面相应的pojo已经因为超时(或发呆)被失效，hibernate就会根据id清单，一个一个的去向数据库查询，有多少个id，就执行多少个sql。该情况将导致性能下降严重。 <BR>查询缓存的失效机制也由hibernate控制，数据进入缓存时会有一个timestamp，它和数据表的timestamp对应。当 hibernate环境内发生save、update等操作时，会更新被操作数据表的timestamp。用户在获取缓存的时候，一旦命中就会检查它的 timestamp是否和数据表的timestamp匹配，如果不，缓存会被失效。因此查询缓存的失效控制是以数据表为粒度的，只要数据表中任何一条记录发生一点修改，整个表相关的所有查询缓存就都无效了。因此查询缓存的命中率可能会很低。 <BR>结论：不应把hibernate二级缓存作为优化的主要手段，一般情况下建议不要使用。 <BR>原因如下： <BR>1、 项目上层业务中检索条件都比较复杂，尤其是涉及多表操作的地方。很少出现重复执行一个排序、分页、参数一致的查询，因此命中率很难提高。 <BR>2、 查询缓存必须配合二级缓存一起使用，否则极易出现1+N的情况，否则性能不升反降 <BR>3、 使用查询缓存必须在执行查询之前显示调用Query.setCacheable(true)才能激活缓存，这势必会对已有的hibernate封装类带来问题。 <BR>总结 <BR>详细分析hibernate的二级缓存和查询缓存之后，在底层使用通用缓存方案的想法基本上是不可取的。比较好的做法是在高层次中（业务逻辑层面），针对具体的业务逻辑状况手动使用数据缓存，不仅可以完全控制缓存的生命周期，还可以针对业务具体调整缓存方案提交命中率。Cluster中的缓存同步可以完全交给缓存本身的同步机制来完成。比如开源缓存swarmcache采用invalidate的机制，可以根据用户指定的策略，在需要的时候向网络中的其他swarmcache节点发送失效消息，建议采用。 </DIV></DIV>]]></description>
</item>
</channel>
</rss>