2007年12月17日 星期一

GridGain 程式開發(6) - Gridify With Custom Task

此範例是使用 @Gridify annotation 搭配 AOP 的方式(此處使用 AspectJ),指定要處理的物件(此處為 Hello World 字串)、以及負責處理物件的 GridTask class。

透過此方式,使用者可以自行決定要針對物件進行哪些處理工作。

而與之前範例不同的是,此處在 @Gridify annotation 中就指定了 taskClass,因此不需要透過 GridFactorygetGrid() method 取得 grid instance(實作了 Grid Interface),再執行 execute(String taskName, Object arg) method 來執行 grid task。


AspectJ AOP 設定

此處使用 AspectJ AOP,因此在 eclipse 上就必須要設定相關的啟動參數,因此在「Run Dialog」中設定:
  1. 在 Arguments -> VM arguments 中,設定以下內容:(GRIDGAIN_HOME 要自行取代掉!)
    -javaagent:[GRIDGAIN_HOME]/libs/aspectjweaver-1.5.3.jar

  2. 在 Classpath 中,透過 Advanced 按鈕加入以下 External Folder:
    [GRIDGAIN_HOME]/config/aop/aspectj


範例程式

以下為範例程式碼:

GridifyHelloWorldTaskExample.java
import org.gridgain.grid.*;
import org.gridgain.grid.gridify.*;;

public class GridifyHelloWorldTaskExample {

/**
* 執行 Grid 程式
* @param 命令列參數 此範例中並沒有使用上
* @throws GridException 錯誤發生時,會拋出的例外
*/

public static void main(String[] args) throws GridException {
GridFactory.start();

try {
sayIt("Hello this world!");
} finally {
GridFactory.stop(true);
}
}

/**
* 透過「@Gridify」annotation 將此 method 變為 grid-enabled
* 並透過 taskClass 關鍵字指定 class 作為將字串拆開並傳送到 grid node 處理的 GridTask
* @param msg 作為要作為遠端處理的字串
*/

@Gridify(taskClass=GridifyHelloWorldTask.class, timeout=3000)
public static void sayIt(String msg) {
System.out.println("\n=========== remote 開始執行 ===========");
System.out.println(">>>> 印出 " + msg);
System.out.println("=========== remote 執行完畢 ===========\n");
} //end sayIt
}

GridifyHelloWorldTask.java
import java.io.*;
import java.util.*;

import org.gridgain.grid.*;
import org.gridgain.grid.gridify.*;

/**
* 將 task 拆成多個 job,必須 extends GridTaskSplitAdapter 這個 abstract class<br />
* 其中有兩個 method 必須實作,分別是 split 及 reduce
* 而此 GridTaskSplitAdapter 已經將 job 與 node 的配對資訊隱藏,讓程式開發更為簡易
*/
public class GridifyHelloWorldTask extends GridTaskSplitAdapter<GridifyArgument> {

/**
* split 是透過參數列中的兩個參數將 task 分開成多個 job<br />
* 並指定每個 job 的處理工作
* @param gridSize
目前 Grid Node 的數量
* @param arg 要被拆開的字串<br />
* (其型態是根據 GridTaskSplitAdapter 所用的型態,此處為 GridifyArgument)
* GridifyArgument 所指的是加上 @Gridify annotation 的 method instance
*/
@Override
protected Collection<? extends GridJob> split(int gridSize, GridifyArgument arg) throws GridException {
//將傳入的字串以空格做區隔存成 List 中的各元素
//透過「GridifyArgument.getMethodParameters()[0]」 取得第一個參數(其實也只有一個參數)
//GridifyArgument instance 中包含了足夠的資訊讓 GridGain 可以透過 reflection 機制在 grid node 上執行工作
String[] words = ((String)arg.getMethodParameters()[0]).split(" ");

//產生存放 GridJob 的 List 容器
List<GridJobAdapter<String>> jobs = new ArrayList<GridJobAdapter<String>>(words.length);

//指定 job 所要處理的工作,並加入 List 容器中
for(String word: words) {
jobs.add(new GridJobAdapter<String>(word) {

/**
* 執行 grid-enabled method<br />
* 使用的參數為上面傳入的參數 word (透過 getArgument() 取得)
*/

@Override
public Serializable execute() throws GridException {
//此處只是使用 class 中的 static method 作為輸出字串之用
//透過 AOP 機制,就不會認為此處的呼叫為另一個 GridTask(crosscutting),因此只會正常執行
GridifyHelloWorldTaskExample.sayIt(this.getArgument());
return null;
} //end execute

});
} //end loop

return jobs;
}

/**
* reduce 的目的是將多個 job 所回傳的值匯集起來,並轉為單一物件輸出<br />
* 而這個物件會由 GridTaskFuture.get() 所取得<br />
* 此範例中不需回傳值,因此直接回傳 null
*/

@Override
public Object reduce(List<GridJobResult> arg0) throws GridException {
return null;
}
}

由於以上的範例,乍看之下並不是很容易明白,因此以下說明一下程式執行的流程:
  1. 首先在 main() 中呼叫了 sayIt() method

  2. 由於此 method 加上了 @Gridify annotation,此時並不會執行 print 的工作,而是 aspectJ 會呼叫 GridGain,根據 GridifyHelloWorldTask 中的 split 的實作將 task 拆成多個 jobs (IoC 發生,因此不會將結果印出,取而代之的是將 task 拆開)

  3. 接著會將 sayIt() method 以及其所在的 class 序列化(serialize)後傳到 remote grid node 準備來執行

  4. 在 remote grid node 又再次呼叫了 sayIt() method,此時 GridGain 不會再次進行 split 的動作(不會再有 IoC 的動作發生),而會讓程式以一般的方式執行,把結果印出


這個範例由於加上了 AOP 的機制,因此看似沒有上一個這麼直覺;主要的困難點就在於在 GridifyHelloWorldTaskExample class 中呼叫已經 grid-enabled 的 sayIt() method,卻在 GridifyHelloWorldTask class 中又再次呼叫了 sayIt() method,不免讓人有點混淆!

【註】
其實本來就可以直接呼叫的,因為 sayIt 是 static method,只是第二次呼叫不會觸發 IoC 的動作!

雖然呼叫了兩次,不過在 GridTask 裡面的呼叫,不會有 crosscutting(亦即 IoC) 發生, 因此只是一般的執行而已,不會再度將呼叫視為 GridTask 進行處理。

至於為何第二次呼叫不會發生 crosscutting,這個部分就由 GridGain 來處理了,使用者就不需要傷這個腦筋囉!


更簡單的開發方式

說明完 GridGain 程式運作的方式後,大家應該可以想到一個更簡單明瞭的開發方式,讓程式碼看起來更為容易閱讀,以下將修改原本的程式:

GridifyHelloWorldTaskExample.java
import org.gridgain.grid.*;
import org.gridgain.grid.gridify.*;

public class GridifyHelloWorldTaskExample {

public static void main(String[] args) throws GridException {
GridFactory.start();

try {
//呼叫 method 將 task 拆成多個 jobs
splitGridTask("Hello this world!");

System.out.println("\n=========== local 執行完畢 ===========\n");
} finally {
GridFactory.stop(true);
}
}

@Gridify(taskClass = GridifyHelloWorldTask.class, timeout = 3000)
public static void splitGridTask(String arg) {
//這裡只要加入足夠的 annotation 資訊即可
//不需要任何實作,只是告知 GridGain 作 split task 的動作
} //end splitGridTask
}
GridifyHelloWorldTask.java
import java.io.*;
import java.util.*;
import org.gridgain.grid.*;
import org.gridgain.grid.gridify.*;

public class GridifyHelloWorldTask extends GridTaskSplitAdapter<GridifyArgument> {

@Override
protected Collection<? extends GridJob> split(int gridSize, GridifyArgument arg) throws GridException {
String[] words = ((String)arg.getMethodParameters()[0]).split(" ");

List<GridJobAdapter<String>> jobs = new ArrayList<GridJobAdapter<String>>(words.length);

for(String word : words) {
jobs.add(new GridJobAdapter<String>(word) {

//也可以將原本 sayIt() 中的內容搬到這邊
@Override
public Serializable execute() throws GridException {
System.out.println("\n=========== remote 開始執行 ===========");
System.out.println(">>>> 印出 " + this.getArgument());
System.out.println("=========== remote 執行完畢 ===========\n");

return null;
}

});
} //end loop

return jobs;
}

@Override
public Object reduce(List<GridJobResult> arg0) throws GridException {
return null;
}
}

修改成上面這個版本之後,應該是會比原本的更容易了解!

沒有留言:

張貼留言