Inside Terracotta

2.  Terracotta Eclipse Plugin

    开发基于Terracotta的应用程序的最便捷的方法就是使用TerracottaEclipse Plugin。http://www.terracotta.org/web/display/docs/Eclipse+Plugin+Guide上有详细的安装说明。安装完毕后,通过Terracotta->Add Terracotta Nature可以给已有的工程增加Terracotta Nature。此外也可以通过File->New->Project->Java->Terracotta Projects->Terracotta DSO Project创建一个Terracotta工程。Terracotta工程创建完毕后,工程的根目录下会创建tc-config.xml、terracotta目录和一个Boot jar。tc-config.xml包含了所有Terracotta相关的配置信息,例如需要进行字节码加强的类、Lock相关的配置,共享对象的root、分布式方法的配置等。Terracotta目录用于保存Terracotta客户端的日志和统计信息等。Boot jar的作用会在稍后的章节里介绍。接下来通过两个例子介绍一下Terracotta Eclipse Plugin的使用。

2.1  wait/notify
    设想某个线程A调用了某个对象obj的wait方法后被阻塞,接下来另一个线程B调用了obj的notify方法从而唤醒了线程A。这在单个JVM中是司空见惯的场景了。但是有没有设想过B线程可以在一个不同于线程A所在的JVM中调用obj的notify方法从而唤醒线程A呢?这在Terracotta的世界里也是司空见惯的场景。
    首先建立一个Terracotta工程,然后创建以下两个普通的Java类:

Java代码
package tcinaction;  
 
public class A {  
      
    public static final Object OBJECT = new Object();  
      
    public static void main(String args[]) throws InterruptedException {  
          
        System.out.println("A is waiting on OBJECT...");  
        synchronized(OBJECT) {  
            OBJECT.wait();  
        }  
        System.out.println("A was woken up");  
    }  
}  
 
 
package tcinaction;  
 
public class B {  
      
    public static void main(String args[]) throws InterruptedException {  
          
        System.out.println("B is calling A.OBJECT.notify()...");  
        synchronized(A.OBJECT) {  
            A.OBJECT.notify();  
        }  
    }  


package tcinaction;

public class A {

public static final Object OBJECT = new Object();

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

System.out.println("A is waiting on OBJECT...");
synchronized(OBJECT) {
OBJECT.wait();
}
System.out.println("A was woken up");
}
}


package tcinaction;

public class B {

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

System.out.println("B is calling A.OBJECT.notify()...");
synchronized(A.OBJECT) {
A.OBJECT.notify();
}
}

    然后在Package Explorer中选中A,单击右键选中Terracotta->Module A.java->Instrumented,也就是令Terracotta对A类进行字节码加强。对B类也执行同样操作。接下来在A类的OBJECT静态成员变量上单击右键,选中Terracotta->Field OBJECT->Shared root,这样A类的OBJECT就成了在Terracotta中共享的对象。再接下来在A类的main方法上单击右键,选中Terracotta->Method main->Autolock,在弹出的Specify Autolock Attributes对话框中选中Write。对B类的main方法也执行相同的操作。经过了以上操作之后,tc-config.xml的内容如下:

Xml代码
<?xml version="1.0" encoding="UTF-8"?> 
<con:tc-config xmlns:con="http://www.terracotta.org/config"> 
  <servers> 
    <server host="%i" name="localhost"> 
      <dso-port>9510</dso-port> 
      <jmx-port>9520</jmx-port> 
      <data>terracotta/server-data</data> 
      <logs>terracotta/server-logs</logs> 
      <statistics>terracotta/cluster-statistics</statistics> 
    </server> 
  </servers> 
  <clients> 
    <logs>terracotta/client-logs</logs> 
    <statistics>terracotta/client-statistics/%D</statistics> 
  </clients> 
  <application> 
    <dso> 
      <instrumented-classes> 
        <include> 
          <class-expression>tcinaction.A</class-expression> 
        </include> 
        <include> 
          <class-expression>tcinaction.B</class-expression> 
        </include> 
      </instrumented-classes> 
      <roots> 
        <root> 
          <field-name>tcinaction.A.OBJECT</field-name> 
        </root> 
      </roots> 
      <locks> 
        <autolock auto-synchronized="false"> 
          <method-expression>void tcinaction.B.main(java.lang.String[])</method-expression> 
          <lock-level>write</lock-level> 
        </autolock> 
        <autolock auto-synchronized="false"> 
          <method-expression>void tcinaction.A.main(java.lang.String[])</method-expression> 
          <lock-level>write</lock-level> 
        </autolock> 
      </locks> 
    </dso> 
  </application> 
</con:tc-config> 

<?xml version="1.0" encoding="UTF-8"?>
<con:tc-config xmlns:con="http://www.terracotta.org/config">
  <servers>
    <server host="%i" name="localhost">
      <dso-port>9510</dso-port>
      <jmx-port>9520</jmx-port>
      <data>terracotta/server-data</data>
      <logs>terracotta/server-logs</logs>
      <statistics>terracotta/cluster-statistics</statistics>
    </server>
  </servers>
  <clients>
    <logs>terracotta/client-logs</logs>
    <statistics>terracotta/client-statistics/%D</statistics>
  </clients>
  <application>
    <dso>
      <instrumented-classes>
        <include>
          <class-expression>tcinaction.A</class-expression>
        </include>
        <include>
          <class-expression>tcinaction.B</class-expression>
        </include>
      </instrumented-classes>
      <roots>
        <root>
          <field-name>tcinaction.A.OBJECT</field-name>
        </root>
      </roots>
      <locks>
        <autolock auto-synchronized="false">
          <method-expression>void tcinaction.B.main(java.lang.String[])</method-expression>
          <lock-level>write</lock-level>
        </autolock>
        <autolock auto-synchronized="false">
          <method-expression>void tcinaction.A.main(java.lang.String[])</method-expression>
          <lock-level>write</lock-level>
        </autolock>
      </locks>
    </dso>
  </application>
</con:tc-config>

    最后我们来验证一下程序的运行结果。首先选中A类,单击右键Run As->Terracotta DSO Application。此时首先会启动Terracotta server,然后会启动另外一个JVM,运行A类的main方法。输出如下:
    A is waiting on OBJECT...
    接下来选中B类,单击右键Run As->Terracotta DSO Application。此时会再启动一个JVM,执行B类的main方法,相关控制台的输出如下:
    B is calling A.OBJECT.notify()...
    回到A的控制台,发现其主线程已被唤醒并执行完毕,输出如下:
    A is waiting on OBJECT...
    A was woken up

2.2  Simple Messaging
    在这个例子中介绍一个通过LinkedBlockingQueue在不同JVM中传递数据的方法。首先创建以下两个类:

Java代码
package tcinaction;  
 
import java.util.concurrent.LinkedBlockingQueue;  
 
public class MessageProducer {  
      
    public static final LinkedBlockingQueue<String> PIPE = new LinkedBlockingQueue<String>();  
      
    public static void main(String args[]) throws InterruptedException {  
        for(int i = 0; i < 100; i++) {  
            PIPE.offer("message-" + i);  
            Thread.sleep(1000);  
        }  
    }  
}  
 
 
package tcinaction;  
 
public class MessageConsumer {  
      
    public static void main(String args[]) throws InterruptedException {  
          
        while(true) {  
            String message = MessageProducer.PIPE.take();  
            System.out.println(message);  
        }  
    }  


package tcinaction;

import java.util.concurrent.LinkedBlockingQueue;

public class MessageProducer {

public static final LinkedBlockingQueue<String> PIPE = new LinkedBlockingQueue<String>();

public static void main(String args[]) throws InterruptedException {
for(int i = 0; i < 100; i++) {
PIPE.offer("message-" + i);
Thread.sleep(1000);
}
}
}


package tcinaction;

public class MessageConsumer {

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

while(true) {
String message = MessageProducer.PIPE.take();
System.out.println(message);
}
}

    然后将MessageProducer 和 MessageConsumer配置为instrumented;各自的main方法配置为Autolock(Write);MessageProducer的PIPE静态成员变量配置为Shared root。此时tc-config.xml的内容如下:

Xml代码
<?xml version="1.0" encoding="UTF-8"?> 
<con:tc-config xmlns:con="http://www.terracotta.org/config"> 
  <servers> 
    <server host="%i" name="localhost"> 
      <dso-port>9510</dso-port> 
      <jmx-port>9520</jmx-port> 
      <data>terracotta/server-data</data> 
      <logs>terracotta/server-logs</logs> 
      <statistics>terracotta/cluster-statistics</statistics> 
    </server> 
  </servers> 
  <clients> 
    <logs>terracotta/client-logs</logs> 
    <statistics>terracotta/client-statistics/%D</statistics> 
  </clients> 
  <application> 
    <dso> 
      <instrumented-classes> 
        <include> 
          <class-expression>tcinaction.MessageProducer</class-expression> 
        </include> 
        <include> 
          <class-expression>tcinaction.MessageConsumer</class-expression> 
        </include> 
      </instrumented-classes> 
      <locks> 
        <autolock auto-synchronized="false"> 
          <method-expression>void tcinaction.MessageConsumer.main(java.lang.String[])</method-expression> 
          <lock-level>write</lock-level> 
        </autolock> 
        <autolock auto-synchronized="false"> 
          <method-expression>void tcinaction.MessageProducer.main(java.lang.String[])</method-expression> 
          <lock-level>write</lock-level> 
        </autolock> 
      </locks> 
      <roots> 
        <root> 
          <field-name>tcinaction.MessageProducer.PIPE</field-name> 
        </root> 
      </roots> 
    </dso> 
  </application> 
</con:tc-config> 

<?xml version="1.0" encoding="UTF-8"?>
<con:tc-config xmlns:con="http://www.terracotta.org/config">
  <servers>
    <server host="%i" name="localhost">
      <dso-port>9510</dso-port>
      <jmx-port>9520</jmx-port>
      <data>terracotta/server-data</data>
      <logs>terracotta/server-logs</logs>
      <statistics>terracotta/cluster-statistics</statistics>
    </server>
  </servers>
  <clients>
    <logs>terracotta/client-logs</logs>
    <statistics>terracotta/client-statistics/%D</statistics>
  </clients>
  <application>
    <dso>
      <instrumented-classes>
        <include>
          <class-expression>tcinaction.MessageProducer</class-expression>
        </include>
        <include>
          <class-expression>tcinaction.MessageConsumer</class-expression>
        </include>
      </instrumented-classes>
      <locks>
        <autolock auto-synchronized="false">
          <method-expression>void tcinaction.MessageConsumer.main(java.lang.String[])</method-expression>
          <lock-level>write</lock-level>
        </autolock>
        <autolock auto-synchronized="false">
          <method-expression>void tcinaction.MessageProducer.main(java.lang.String[])</method-expression>
          <lock-level>write</lock-level>
        </autolock>
      </locks>
      <roots>
        <root>
          <field-name>tcinaction.MessageProducer.PIPE</field-name>
        </root>
      </roots>
    </dso>
  </application>
</con:tc-config>

    最后我们来验证一下程序的运行结果。首先启动MessageConsumer,单击右键Run As->Terracotta DSO Application。然后启动MessageProducer。MessageConsumer的控制台上会显示出接收到的消息:
    message-0
    message-1
    message-2
    …







3 Inside Terracotta
3.1 Core Terracotta Concepts
3.1.1 Root
    共享对象图中的顶层对象被称为root,它在Terracotta的配置文件中指定。所有经root引用可达的对象都会被Terracotta分配一个集群内唯一的object id,并在集群内共享直到被分布式垃圾收集器回收。需要注意的是,声明root对象的类也会被Terracotta隐含地标记为instrumented。
    当集群内的某个JVM第一次对root引用赋值的时候,Terracotta会在集群内创建root,并且所有接下来的对root引用的赋值操作均会被Terracotta忽略(除非root是Terracotta中的literal values)。无论被何种修饰符修饰,root对象的生命周期都超过了单个JVM的范畴。尽管Terracotta可以保证集群中的root引用只被赋值一次,但是root对象的内容是可以被修改的,这些修改会被同步到Terracotta server。此外也不能避免对其constructor的调用,例如:

Java代码
package tcinaction.sharedroot;  
 
public class A {  
    public static final A ROOT = new A();  
      
    public A() {  
        System.out.println("in A.A(), this: " + this);  
    }  
      
    public static void main(String args[]) throws InterruptedException {  
        System.out.println("root: " + ROOT);  
        Thread.sleep(Long.MAX_VALUE);  
    }  


package tcinaction.sharedroot;

public class A {
public static final A ROOT = new A();

public A() {
System.out.println("in A.A(), this: " + this);
}

public static void main(String args[]) throws InterruptedException {
System.out.println("root: " + ROOT);
Thread.sleep(Long.MAX_VALUE);
}
}    tc-config.xml的内容如下:

Xml代码
<?xml version="1.0" encoding="UTF-8"?> 
<con:tc-config xmlns:con="http://www.terracotta.org/config"> 
  <servers> 
    …  
  </servers> 
  <clients> 
    …  
  </clients> 
  <application> 
    <dso> 
      <locks> 
        <autolock auto-synchronized="false"> 
          <method-expression>void tcinaction.sharedroot.A.main(java.lang.String[])</method-expression> 
          <lock-level>write</lock-level> 
        </autolock> 
      </locks> 
      <instrumented-classes> 
        <include> 
          <class-expression>tcinaction.sharedroot.A</class-expression> 
        </include> 
      </instrumented-classes> 
      <roots> 
        <root> 
          <field-name>tcinaction.sharedroot.A.ROOT</field-name> 
        </root> 
      </roots> 
    </dso> 
  </application> 
</con:tc-config> 

<?xml version="1.0" encoding="UTF-8"?>
<con:tc-config xmlns:con="http://www.terracotta.org/config">
  <servers>
    …
  </servers>
  <clients>
    …
  </clients>
  <application>
    <dso>
      <locks>
        <autolock auto-synchronized="false">
          <method-expression>void tcinaction.sharedroot.A.main(java.lang.String[])</method-expression>
          <lock-level>write</lock-level>
        </autolock>
      </locks>
      <instrumented-classes>
        <include>
          <class-expression>tcinaction.sharedroot.A</class-expression>
        </include>
      </instrumented-classes>
      <roots>
        <root>
          <field-name>tcinaction.sharedroot.A.ROOT</field-name>
        </root>
      </roots>
    </dso>
  </application>
</con:tc-config>    以Terracotta DSO Application的方式运行A后,控制台的输出如下:
    in A.A(), this: tcinaction.sharedroot.A@1308c5c
    root: tcinaction.sharedroot.A@1308c5c
    接下来再次以Terracotta DSO Application的方式运行A,控制台的输出如下:
    in A.A(), this: tcinaction.sharedroot.A@6828d4
    root: tcinaction.sharedroot.A@83914b
    当第二次运行A的时候,A的构造函数会被调用,并打印出这个对象的堆地址是@6828d4,由于集群中的ROOT引用已经在第一运行A的时候被赋值过,Terracotta忽略了第二次对ROOT引用的赋值,所以在main方法中打印出的ROOT地址是@83914b。
    为什么第二次运行A时打印出来的ROOT地址(@83914b)跟第一次运行A时打印出的ROOT地址(@1308c5c)不同?Terracotta通过proxy保证了集群范围内的对象唯一性,这是逻辑上的唯一性,也就是说@1308c5c和@83914b是逻辑上的同一个ROOT实例。



3.1.2 Transactions
    Terracotta 事务是一系列对共享对象修改的原子集合,它的边界是集群锁(clustered lock)的获取和释放。当某个线程获取集群锁时事务开启,对共享对象的修改都被加入到该事务中。在释放集群锁之后,事务被提交。
    所有对共享对象的修改都必须在Terracotta 事务的上下文中。这意味着必须首先获得集群锁,然后才能对共享对象进行修改。如果某个线程试图在Terracotta 事务之外(更严格地说,还需要在经过字节码加强后代码中)修改共享对象,那么会导致运行时异常。在某个线程A获取集群锁之前,Terracotta会保证集群中由这把锁界定的事务中对共享对象的修改都会对线程A可见(这类似于Java语言规范的happens before语义和内存可见性的保证)。

3.1.3 Locks
    Locks在Terracotta中有两个职责:协调多线程的并发访问和定义Terracotta 事务边界。Terracotta要求: Java代码中所有对共享对象的写操作都必须被同步(可以使用synchronized关键字或者java.util.concurrent包中锁相关的工具类)。But why?Terracotta的理念是并发程序的设计是充满挑战的,需要尽可能地减少犯错的可能。有些并发问题在开发过程中无法发现,在测试过程中无法发现,却可能在你最重要的客户面前爆发。解决同样一个问题,时机的不同(比如是在开发阶段,或者是在production阶段)可能就决定了你老板的大拇指是向上还是向下。
    根据配置,Terracotta会对应用程序的字节码进行加强,以便增加Cluster locking相关的行为。配置文件中对集群锁的配置是在方法级别上。需要注意的是,如果声明这些方法的类没有被标记为instrumented,那么这些方法不会有任何Cluster locking相关的行为。也就是说如果在没有标记为instrumented的类中修改共享对象(这不会导致运行时异常,原因是Terracotta无法对uninstrumented的类进行检查),那么这些修改不会被同步到Terracotta server,也就不会对集群中的其它成员可见,从而导致共享对象在集群成员中处于不一致的状态。


3.1.3.1 Autolock
    Terracotta里最常见的集群锁由autolock配置。Terracotta会检查所有与autolock匹配的方法中的同步块,通过拦截MONITORENTER和MONITOREXIT字节码指令,以增加Cluster locking相关的行为。以下是个简单的例子:

Xml代码
<autolock auto-synchronized="false"> 
    <method-expression>* tcinaction.locks.A.*(..)</method-expression> 
    <lock-level>write</lock-level> 
</autolock> 

<autolock auto-synchronized="false">
<method-expression>* tcinaction.locks.A.*(..)</method-expression>
<lock-level>write</lock-level>
</autolock>    "method-expression" 指定了匹配的方法(通过AspectWerkz Pattern Selection Language)。以上例子中,表达式"* tcinaction.locks.A.*(..)" 匹配tcinaction.locks.A类中所有的方法:表达式开头的 "*" 匹配所有可能的返回值类型;表达式结尾的 "*" 匹配所有的方法名;"(..)" 匹配所有可能的方法参数(包括没有参数)。autolock 还有一个auto-synchronize属性,如果为true,那么等效于该方法被synchronized关键字修饰。该属性通常用于已有的程序中修改共享对象的代码没有被同步,却无法直接重构代码的情况下。

3.1.3.2 Named Lock
    除了autolock之外,Terracotta还支持named lock。跟autolock类似,"method-expression" 指定了匹配的方法;"lock-name"指定了命名锁的名字。当某个线程试图执行这些方法时,会从Terracotta server获得一个命名锁,这是一种非常粗粒度的集群锁,可能严重地影响应用性能,因此应该谨慎地使用。以下是个简单的例子:

Xml代码
<named-lock> 
   <lock-name>lockOne</lock-name> 
   <method-expression>* tcinaction.locks.A.*(..)</method-expression> 
   <lock-level>write</lock-level> 
</named-lock> 

<named-lock>
   <lock-name>lockOne</lock-name>
   <method-expression>* tcinaction.locks.A.*(..)</method-expression>
   <lock-level>write</lock-level>
</named-lock>
3.1.3.3 Lock Level
    所有与lock相关的配置还有一个属性lock level。Terracotta提供了以下四种可选级别:

Write。跟Java中的互斥锁类似,write locks保证任何时刻,集群中最多只有一个线程能够获得该锁。
Read。多个线程可以同时获得该锁,但是在持有该锁的时候不能修改共享对象(会导致运行时异常)。持有该锁的时候不能升级为Write locks,这也会导致运行时异常。当某个线程持有该锁的时候,在相同共享对象上试图获得Write locks的线程会被阻塞;当某个线程持有Write locks的时候,在相同共享对象上试图获得Read locks的线程也会被阻塞。在读取共享对象的时候,虽然Terracotta并不强制要求必须首先获得Read locks,但是在不持有锁的情况下试图读取共享对象可能会导致脏读,强烈建议不要这样做。
Synchronous-Write。该锁在Write locks的基础上进一步保证持有该锁的线程直到所有的修改已经被同步到Terracotta server,并得到Terracotta server的应答之后才会释放该锁。
Concurrent。该锁只是定义了事务边界。通常该锁只是被Terracotta libraries使用,并不建议在应用程序中使用。
3.1.4 Portability
    3.1.1节曾经介绍过,在不同的JVM中打印同一个root的堆地址是不同的,因此对于那些依赖于Object.hashCode的实现(即没有改写该方法)的共享对象来说,在不同的JVM中得到的hash code是不同的。假如集群中有个HashMap<Object, Object>类型的共享对象,在两个JVM中以同一个Object对象作为key向该HashMap中存放数据时,会不会因为hashCode不同而导致保存到两个不同的entry中呢?在回答这个问题之前,首先介绍一下Terracotta中portability相关的概念。
    可以被Terracotta共享的对象被称为"portable",与之对应的类必须被标记为instrumented。绝大多数被标记为instrumented的类的实例都是portable,但是有一小部分对象由于包含了特定平台或JVM相关的信息,因此不是portable(例如java.io.FileDescriptor、Thread和Runtime等)。同样,继承自non-portable类的实例也不是portable。
    对于绝大多数的对共享对象,Terracotta可以跟踪对其的修改,并将修改后的新值同步到Terracotta server,这些共享对象被称为Physically Managed Object。然而有一些对象不是通过这种方式共享的:当共享对象被修改时,Terracotta会记录修改共享对象时所调用的方法签名和方法参数,然后在集群中的其它JVM上再次调用该方法(这其实是分布式方法调用,会在稍后介绍),这些调用也被称为"logic action"。通过这种方式被共享的对象被称为是Logically Managed Objects。通常有两种原因导致对象被logically managed:

性能。
共享对象包含了特定JVM相关的信息(例如java.util.Hashtable、java.util.HashMap和java.util.HashSet都是logically managed)。
    至此,本节开头提出的问题的答案也应该明了了。
    尽管logically managed objects是portable,但是由于Terracotta实现细节的原因,logically managed classes有以下限制:如果某个类继承自logically managed classes,并且声明了额外的成员变量,假如符合以下条件,那么这个类就不是portable:

直接写入从父类中继承的成员变量。
改写了从父类继承的方法。
    当某个对象被集群共享的时候(例如被赋值到某个root引用),Terracotta首先会遍历通过该对象引用可达的整个对象图,同时检查对象图中的每个对象(可以配置成忽略transient对象)的portability,如果非portable则抛出运行时异常。此外Terracotta也会对logic action的方法参数进行portability检查。



3.1.5 Boot Jars
    Terracotta会在应用程序的字节码中织入集群相关的代码,通常的时机是在类载入的时候。可以配置成对所有载入的类都进行字节码加强,这是最简单和最安全的方法。随着你对Terracotta理解的深入,你会越来越清楚究竟那些类需要进行字节码加强,从而在配置文件中指定(通过AspectWerkz Class Selection Expression)需要进行字节码加强的类,这样会加快类载入的速度,以及提高运行时的性能。
    对于绝大多数类,Terracotta可以在载入时进行字节码加强,然而有些类(如rt.jar中的类)是通过boot classloader(是非Java实现的classloader)载入的,Terracotta无法对这些类在载入时进行字节码加强。因此Terracotta提供了Boot JAR Tool,它可以选择性地对这些类进行预处理,生成一个boot jar并保存到boot classpath的优先位置中。需要注意的是,由boot classloader载入的类无法被共享,除非它被包含在boot jar中;同样,继承自由boot classloader载入的类也无法被共享,除非它们的父类被包含在boot jar中。



3.1.6 Transience
    与Java序列化机制中的transient关键字类似,Terracotta提供了transience机制以避免共享对象中的部分成员变量被集群共享。此外Terracotta还提供了初始化transient成员变量的机制。以下是其配置的例子:

Xml代码
<transient-fields> 
    <field-name>tcinaction.sharedroot.A.logger</field-name> 
</transient-fields> 

<transient-fields>
<field-name>tcinaction.sharedroot.A.logger</field-name>
</transient-fields>    以上的例子中,A的logger成员变量被标记为transient。
    此外也可以使用Java的transient关键字修饰logger。在默认情况下,Terracotta在共享某个对象的时候不会忽略该对象中被transient修饰的成员变量。如果希望Terracotta对其进行忽略,那么需要在include节点内增加honor-transient,例如:

Xml代码
<instrumented-classes> 
    <include> 
        <honor-transient>true</honor-transient> 
        <class-expression>tcinaction.sharedroot.A</class-expression> 
    </include> 
</instrumented-classes> 

<instrumented-classes>
<include>
<honor-transient>true</honor-transient>
<class-expression>tcinaction.sharedroot.A</class-expression>
</include>
</instrumented-classes>    当包含transient成员变量的共享对象在集群中的某个JVM中被构造的时候,可以在配置文件中指定on-load,以便对transient成员变量进行初始化(如果无on-load配置,那么transient成员变量的值会是null或0)。on-load的配置有两种方式:

指定需要执行的方法名。
指定需要执行的BeanShell脚本。
3.1.7 Distributed Method Invocation
    在Terracotta配置文件中,共享对象的任何方法都可以表标记为distributed,这意味着在集群的某个JVM中对该方法的调用,都会在集群的其它JVM中以相同的参数调用。DMI通常被用来作为分布式listener的实现。笔者建议,不要在分布式方法内修改共享对象的非transient成员变量。以下是其配置的例子:

Xml代码
<distributed-methods> 
    <method-expression>void tcinaction.locks.A.onIdChanged()</method-expression> 
</distributed-methods> 

<distributed-methods>
<method-expression>void tcinaction.locks.A.onIdChanged()</method-expression>
</distributed-methods> 
3.1.8 Distributed Garbage Collection
    Terracotta virtual heap类似于操作系统的虚拟内存,它允许集群中的JVM访问超过其本地堆最大容量的共享对象。也就是说集群的各个JVM的本地堆中并不一定包含所有的共享对象。当某个JVM需要访问某个共享对象时,如果该对象并不存在于该JVM的本地堆中,那么会从Terracotta server进行lazy load。
    与之相反,Virtual Memory Manager(VMM)会将最少使用的共享对象的引用设置为null,从而在本地堆中清除。假如共享对象A包含了对共享对象B和C的引用,如果C并不经常被访问,那么VMM可能将C的引用设置为null,从而A被VMM部分清除(C被清除,B仍然被保留)。需要注意的是,对于大部分的Logically managed objects,VMM不能进行部分清除。目前VMM可以进行部分清除的Logically managed objects有ConcurrentHashMap、HashMap、 Hashtable、LinkedHashMap和Java arrays。
    Distributed Garbage Collector(DGC)由Terracotta server执行,它的职责是从共享对象中标记不再被使用的共享对象,以便从Terracotta server的内存和持久存储中安全地删除。当共享对象不被任何root经引用可达,并且不存在于任何客户端的本地堆中时(出于各种原因,Terracotta server知道加入到集群的各个JVM的本地堆中包含哪些共享对象),它就可以被DGC回收(root对象不会被DGC回收)。



3.2 Terracotta Cluster
    多个Terracotta server可以组成一个Terracotta server array。其中的每一个Terracotta server都履行以下两个职责:

集群内的并发管理和锁管理。Terracotta server跟踪各个JVM内哪些线程持有集群范围内的锁;哪些线程调用了共享对象的wait方法,以便在该共享对象的notify和notifyAll方法被调用后,可以正确地唤醒等待的线程。
共享对象的管理和持久化保存。Terracotta server跟踪各个Terracotta client中的共享对象,以及对其的修改,并根据配置以决定是否将修改进行持久化保存,同时通知其它需要访问这些共享对象的Terracotta client。
    如果某个Terracotta server无法正常工作,那么相应的后果由以下因素决定:

Terracotta server是否工作在持久化模式下。
Standby Terracotta server是否被配置,以及是否在等待接管active server。
该Terracotta server是否是Terracotta server array中的一员。
    如果该Terracotta server是Terracotta server array中的一员,那么集群可以继续正常工作。如果不是Terracotta server array中的一员,并且是唯一的active server,那么集群的行为如下:

Restarted Server's Mode Standby Server Existing Data Existing Clients Allowed Reconnect? New Clients Allowed Reconnect?
Non-persistent Not allowed Lost No No
Persistent None set up Saved Yes No
Persistent Yes - becomes active Saved Yes No



    如果集群中某个Terracotta client无法正常工作,那么Terracotta server会把它从集群中移除,回收该client持有的锁,并且拒绝该client的重连请求(因为该client可能出于某种中间状态)。然而可以为这个client配置一个重连窗口,以便处理网络连接短暂失效的情况。当重连窗口打开的时候,之前连接过的client被允许重新连接。当某个client没有和Terracotta server建立连接之前,该client内所有试图获得锁的操作均被阻塞。

你可能感兴趣的:(eclipse,jvm,多线程,xml,配置管理)