2007年12月30日 星期日

The C Programming Language 讀後整理(2) - Types, Operators, and Expres

Variable Names

以一般的 C 開發習慣來說,variable name 會使用小寫的英文字母來命名;而 symbolic constants 則會用大寫的英文字母來命名 。


另外變數的命名有幾點需要注意:
  1. 名稱的長度必須在 31 的字元之內
  2. 不可使用 key words (例如:if、else、int、float .... etc)


Data Types and Sizes

C 所提供的基本型態,在之前已經介紹過了。

另外需要注意的是,同樣的型態在不同的 OS 中,或是在不同的 CPU 中(32 bits 或 64 bits),所使用的長度可能不一樣,例如:int 有可能用 16 bits 或是 32 bits 來表示。

另外,有兩個與變數值為正或負相關的關鍵字為「signed」與「unsigned」。

一般若沒有使用的情況下,則會預設為 signed,表示變數是可以表示出正負數的;而 unsigned 則是指定正數(非負數)。

想當然爾,每個型態所使用的長度是相同的,但有些要表現負數,有些卻不用,然而這樣的差別,就影響了各型態所能表現值的範圍;舉例來說,每個 char 佔了 8 bits 的記憶體空間,若是在 signed 的情況下,可以表現 -128~127;但若是在 unsigned 的情況下,就可以表現 0~255。因此可以看出,關鍵字的使用會影響到變數值可表現的範圍。

以下用段程是碼來顯示各型態變數值可表現的範圍:(under Linux)
#include <stdio.h>
#include <limits.h>

main() {
//signed data type
printf(" signed char min : %d\n", SCHAR_MIN); //-128
printf(" signed char max : %d\n", SCHAR_MAX); // 127
printf(" signed short min : %d\n", SHRT_MIN); //-32768
printf(" signed short max : %d\n", SHRT_MAX); // 32767
printf(" signed int min : %d\n", INT_MIN); //-2147483648
printf(" signed int max : %d\n", INT_MAX); // 2147483647
printf(" signed long min : %ld\n", LONG_MIN); //-2147483648
printf(" signed long max : %ld\n", LONG_MAX); // 2147483647

//unsigned data type
printf("unsigned char max : %u\n", UCHAR_MAX); //255
printf("unsigned short max : %u\n", USHRT_MAX); //65535
printf("unsigned int max : %u\n", UINT_MAX); //4294967295
printf("unsigned long max : %lu\n", ULONG_MAX); //4294967295
} //end main


Constants

由於 C 支援了許多不同型態的變數,因此在表示的時候,為了辨識不同型態的變數值,會有不同的表示方式,以下一一來說明。

整數(interger)

以整數型態來說,若是 int 則是沒有任何問題,不需要加上特殊符號,因為 C 預設的 integer 型態即為 int;而若是 long,則必須要在變數值後方加上大寫或小寫 L,例如:123456789L;而若加上是 unsigned 的數值,又必須在變數值後方加上大寫或小寫 U,因此 unsigned long 的變數值,後方要加上「ul」或「UL」。

而整數一般常用的表示方式為 10 進位(octal)或是 16 進位(hexadecimal)。其中變數值以「0」為開頭的為 10 進位的變數值,例如:031(表示為 31);「0x」開頭的則是 16 進位的變數值,例如:0x1F(表示 31)。

綜合以上說明,舉例來說,若變數值為 0x1FUL,則表示此變數為 unsigned long 型態,其值為 31。

浮點數(floating)

再來是浮點數型態,由於 C 預設 float 型態為 double,因此 double 型態的變數值後方就不需多加任何符號;但若是 float,則需要在變數值後方加上大寫或小寫 F;另外一個比較特殊的,浮點數變數值加上大寫或小寫 L 則是表示 long double。

字元(character)

字元這個部份能表示的可就多了! 在一般的表示方法中,所表示的為 ASCII 的字元,每個字元有其對應的 int 值,例如:'0' 代表 48,因此字元可以用來進行比對。

此外,還有許多特殊符號或是特殊效果,必須使用跳脫字元來表示,看起來像多個字元,但其實對 C 來說,只是單一個字元! 以下列表說明:

跳脫字元
意義
跳脫字元意義
\a
alert(cell) character
\\
backslash
\b
backspace
\?
question mark
\f
form feed
\'
single quote
\n
new line
\"
double quote
\r
carriage return
\000
octal number
\t
horizontal tab
\xhh
hexadecimal number
\v
vertical tab



其中有一個較為特殊的是「\0」,其所代表的意義為 null,用來做為 string 的結尾表示之用,因此一個 string 儲存於記憶體時,除了 string所需要的記憶體長度外,還必須要多一個 bit 的空間來儲存這個代表 string 結尾的字元。

另外,針對 string 處理的部份,standard header 提供了「string.h」,其中包含了許多關於 string 的 function 可以使用,例如:計算 string 長度的 strlen(s)

列舉(enumeration)

最後 enumeration 是比較特殊的部份,而 enumeration 其實就是一群 int 變數值的集合,然後用比較容易瞭解的方式去表示。

在 enumeration 中會出現很多變數名稱,如果沒有指定,預設從第一個開始,其 int value 為 0,第二個則為 1,其他依此類推..........

舉例來說:
//第一個 NO 所代表的 int value 為 0
//第二個 YES 所代表的 int value 為 1
enum boolean { NO, YES };

//為每一個變數名稱指定值(每個跳脫字元都屬於 int value)
enum escapes { BELL = '\a', BACKSPACE = '\b', TAB = '\t'
NEWLINE = '\n', VTAB = '\v', RETURN='\r' };

//指定第一個的 int value 為 1,之後不指定就會從 1 遞增下去
//FEB = 2、MAR = 3、APR = 4 ..... etc
enum months { JAN=1, FEB, MAR, APR, MAY, JUN,
JUL, AUG, SEP, OCT, NOV, DEC};
PS.變數名稱多以大寫英文字母來表示。


Declarations

在這個部份,並沒有什麼特殊,只是記得變數使用前必須要宣告!

另外,可以在宣告變數時直接給定其值,不需多一行來指定。

其中比較需要注意的是,用「const」關鍵字所宣告的變數,其值是不能夠被改變的;若程式中去修改,compiler 會發出錯誤警告。

例如:
//用 const 宣告的變數,其值都不可被修改
const double e = 2.71283745345;
const char msg[] = "warning: ";
另外,使用 const 的宣告方式,也可以用在 function 的參數上,例如:string.h header 中的「int strlen(const char[])」,如此一來即表示,該傳入的參數在 function 中是不能被更改的!!


Arithmetic Operators

在算術相關的操作上,比較需要注意的是「%」(取餘數)符號無法使用在 float 或 double 型態上的變數。

另外,「/」與「%」針對負數的處理,可能會因為機器的不同而有所不同。


Relational and Logical Operators

在優先權的部份,arithmetic operators 是比 relational operators 來的稍微高一些,使用時必須注意一點。

以下直接用範例來說明:
//條件判斷的部份在 for loop 的中間一段,條件如下:
//(1)i 小於 lim - 1
//(2)由getchar()取得的字元不是代表new line(n)
//(3)由getchar()取得的字元不是代表結尾(EOF)
for(i = 0 ; i < lim - 1 && (c = getchar()) != 'n' && c != EOF ; i++)

//可以用括號的方式將條件敘述清楚,雖然多了幾個括號,不過不會有優先權搞錯的問題
//因此改寫如下:
for(i = 0 ; (i < (lim - 1)) && ((c = getchar()) != 'n') && (c != EOF) ; i++)

//以下兩種寫法,其實都是可以用的
//只是第一種看起來比較容易讓人理解,但實際使用上可能情況更為複雜
if(!valid)
if(valid == 0)
因此,若有很多條件判斷時,程式在撰寫上必須多注意,不然很容易產生很難處理的 bug。


Type Conversions

由於寫程式的時候會遇到的情況很多,因此對於變數進行型態上的轉換是很平常的事情,因此在型態轉換上有幾點必須注意的事情:

  1. 一般來說,若對變數進行操作,則會被轉換為共通的型態。

  2. 有些情況下 compiler 會進行自動轉換,不過規則是較小型態的變數會轉換為較大型態的變數。

  3. 若是將較大型態的變數值指定給較小型態的變數,compiler 會發出警告,但不會認為是錯誤的(但會喪失部份精確度)

因此舉例來說,char 型態的變數,本就是由一個 byte 的記憶體位置所組成,因此也可以當作 int 來表示或使用。

以下用個程式範例,來說明如何將數字 string 轉為 int 值:
int atoi(char s[]) {
int i, n;

n = 0;
//在 for loop 中,s[i] 會自動被轉為 int
for(i = 0 ; s[i] >= '0' && s[i] <= '9' ; i++)
n = 10 * n + (s[i] - '0'); //括號中的敘述會自動轉為 int
return n;
} //end atoi
另外一個程式,是將大寫字母轉換為小寫字母:
int lower(int c) {
if(c >= 'A' && c <= 'Z')
return c + 'a' - 'A';
else
return c;
} //end lower
以上的作法是因為那些 char 型態的變數都屬於 ASCII 的範圍內,因此可以順利轉換為 int。

不過,變數型態的轉換其實不需要這麼麻煩,因為 C 提供的 standard header 中就有名稱為「ctype.h」的 header 提供給程式設計師方便不同型態間的轉換。

若是透過 ctype.h 中所提供的 fcuntion,就不需要考慮在不同平台之間 character set 可能不一樣的問題了! 若是要字母轉小寫,可以使用 tolower(int ch),若是要檢查是不是數字,可以用 isdigit(int ch)

除了上述由 compiler 所自行進行的轉換外,也可以強制進行變數型態的轉換,轉換的語法如下:
(type-name)expression
在某些情況下,強制轉換是需要的,例如在 math.h 中提供的 sqrt(double num) function,其中參數部分就必須是 double,因此若有一個 integer variable 為 n,可以透過以下方式代入:
sqrt((double)n)
透過此方式,就可以在代入 sqrt function 計算之前將型態強制轉換為 double。

但是若是事先有定義出 function 的 prototype,compiler 就會自行協助轉換的工作,例如:
double sqrt(double); // function prototype

root2 = sqrt(2); // 2 將會自動轉為 double 型態代入


Bitwise Operators

C 提供了六種運算子作為 bit 的處理! 分別是:

運算元
說明
&
AND
|
OR
^
Exclusive OR
<<
位元左移
>>
位元右移
~
1's 補數

這些用在 bit 的操作,一般來說可能不會常使用,不過在特殊的用途上其實還蠻常會運用到的,例如:網路程式的開發。

AND 通常是作為 mask 之用。

OR 通常作為將特定 bit 開啟(0 -> 1)之用。

至於位元左右移,可以達成乘以 2 or 除以 2 的效果。

2007年12月27日 星期四

設定 GirdGain Wndows Service

環境說明

這個部分要說明如何將 GridGain 設定為 Windows Service,以下說明一下實驗的環境:

GridGain 版本:1.6.1

安裝路徑(%GRIDGAIN_HOME%):C:\gridgain-1.6.1

這個部分有很重要的地方一定要先作,就是在「環境變數 -> 系統變數」中設定 GRIDGAIN_HOME!!

由於安裝 GRIDGAIN 時,會在「環境變數 -> 使用者變數」中設定 GRIDGAIN_HOME,但僅有這個是沒辦法將 GridGain 設定為 Windows service 的,因此在進行下面設定時要將 %GRIDGAIN_HOME% 系統變數補上!


下載 Java Service Wrapper

首先要下載 Java Service Wrapper,可以到 http://wrapper.tanukisoftware.org 下載;由於是要用在 Windows 上,因此要選擇「wrapper-windows-x86-32-x.x.x.rar」。

【註】目前似乎沒有 Windows X86 64 bit 的版本可供下載........

下載完之後,將檔案解開,接著我們以 %WRAPPER_HOME% 作為檔案解開後存放的目錄(假設為 D:\wrapper-windows-x86-32),要進行以下動作:
  1. 複製檔案 %WRAPPER_HOME%\bin\wrapper.exe【D:\wrapper-windows-x86-32\bin\wrapper.exe】 到 %GRIDGAIN_HOME%\bin【C:\gridgain-1.6.1\bin】 目錄中。

  2. 複製目錄 %WRAPPER_HOME%\lib【D:\wrapper-windows-x86-32\lib】 中的檔案 wrapper.jar 與 wrapper.dll 到 %GRIDGAIN_HOME%\lib【C:\gridgain-1.6.1\lib】 目錄中


編輯設定檔

接著要編輯 Java Service Wrapper 設定檔,檔名為「wrapper.conf」(其實可以自己命名)

以下為設定檔內容:(要根據實際的情況進行修改喔!!)
# GridGain Daemon/Service Properties

# Java Application
wrapper.java.command=java

# Java Main class. See the Integration section
# of the documentation for details.
wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp

# Java Classpath (include wrapper.jar) Add class path elements as
# needed starting from 1
wrapper.java.classpath.1=C:\gridgain-1.6.1\libs\wrapper.jar
wrapper.java.classpath.2=C:\gridgain-1.6.1\libs\activation-1.0.2.jar
wrapper.java.classpath.3=C:\gridgain-1.6.1\libs\ant-1.6.5.jar
wrapper.java.classpath.4=C:\gridgain-1.6.1\libs\aspectjrt-1.5.3.jar
wrapper.java.classpath.5=C:\gridgain-1.6.1\libs\aspectjweaver-1.5.3.jar
wrapper.java.classpath.6=C:\gridgain-1.6.1\libs\commons-logging-1.0.3.jar
wrapper.java.classpath.7=C:\gridgain-1.6.1\libs\commons-net-1.4.1.jar
wrapper.java.classpath.8=C:\gridgain-1.6.1\libs\concurrent-1.3.jar
wrapper.java.classpath.9=C:\gridgain-1.6.1\libs\imap-1.4.jar
wrapper.java.classpath.10=C:\gridgain-1.6.1\libs\jakarta-oro-2.0.8.jar
wrapper.java.classpath.11=C:\gridgain-1.6.1\libs\javassist-4.0.4.jar
wrapper.java.classpath.12=C:\gridgain-1.6.1\libs\jboss-serialization-1.0.2.jar
wrapper.java.classpath.13=C:\gridgain-1.6.1\libs\jgroups-all-2.2.9.3.jar
wrapper.java.classpath.14=C:\gridgain-1.6.1\libs\jms-1.1.jar
wrapper.java.classpath.15=C:\gridgain-1.6.1\libs\jmxri-1.2.1.jar
wrapper.java.classpath.16=C:\gridgain-1.6.1\libs\jmxtools-1.2.1.jar
wrapper.java.classpath.17=C:\gridgain-1.6.1\libs\jnpserver-4.0.4.jar
wrapper.java.classpath.18=C:\gridgain-1.6.1\libs\log4j-1.2.15.jar
wrapper.java.classpath.19=C:\gridgain-1.6.1\libs\mailapi-1.4.jar
wrapper.java.classpath.20=C:\gridgain-1.6.1\libs\pop3-1.4.jar
wrapper.java.classpath.21=C:\gridgain-1.6.1\libs\smtp-1.4.jar
wrapper.java.classpath.22=C:\gridgain-1.6.1\libs\spring-2.0.jar
wrapper.java.classpath.23=C:\gridgain-1.6.1\libs\tidy-04aug2000-r7.jar
wrapper.java.classpath.24=C:\gridgain-1.6.1\libs\trove-1.0.2.jar
wrapper.java.classpath.25=C:\gridgain-1.6.1\gridgain-1.6.1.jar

# Java Library Path (location of Wrapper.DLL or libwrapper.so)
wrapper.java.library.path.1=C:\gridgain-1.6.1\libs

# Java Additional Parameters
wrapper.java.additional.1=-ea
wrapper.java.additional.2=-Dcom.sun.management.jmxremote
wrapper.java.additional.3=-Xms256m
wrapper.java.additional.4=-Xmx512m

# Application parameters. Add parameters as needed starting from 1
wrapper.app.parameter.1=org.gridgain.grid.loaders.cmdline.GridCommandLineLoader
wrapper.app.parameter.2=C:\gridgain-1.6.1\config\default-spring.xml

#********************************************************************
# Wrapper Windows Properties
#********************************************************************
# Title to use when running as a console
wrapper.console.title=GridGain-1.6.1

#********************************************************************
# Wrapper Windows NT/2000/XP Service Properties
#********************************************************************
# WARNING - Do not modify any of these properties when an application
# using this configuration file has been installed as a service.
# Please uninstall the service before modifying this section. The
# service can then be reinstalled.

# Name of the service
wrapper.ntservice.name=GridGain-1.6.1

# Display name of the service
wrapper.ntservice.displayname=GridGain-1.6.1

# Description of the service
wrapper.ntservice.description=GridGain Node

# Mode in which the service is installed. AUTO_START or DEMAND_START
wrapper.ntservice.starttype=AUTO_START

# Allow the service to interact with the desktop.
wrapper.ntservice.interactive=false

其中在 classpath 的部分,由於版本不同的關係,在官方網站上的 library 版本與實際安裝有出入,因此我有進行修改!


安裝、啟動、停止、移除 GridGain Service

以上步驟都完成後,就可以安裝 GridGain Service 囉! 選擇「開始 -> 執行」,輸入以下指令執行以下操作:

1、安裝 service
%GRIDGAIN_HOME%\bin\wrapper.exe -i %GRIDGAIN_HOME%\config\wrapper.conf
2、啟動 service
%GRIDGAIN_HOME%\bin\wrapper.exe -t %GRIDGAIN_HOME%\config\wrapper.conf
3、停止 service
%GRIDGAIN_HOME%\bin\wrapper.exe -p %GRIDGAIN_HOME%\config\wrapper.conf
4、移除 service
%GRIDGAIN_HOME%\bin\wrapper.exe -r %GRIDGAIN_HOME%\config\wrapper.conf

安裝完 GridGain service 之後,就可以在服務管理的頁面上進行管理囉!

2007年12月23日 星期日

Javadoc 撰寫方式

最近使用 eclipse,發現可以在程式馬上直接將 Javadoc 撰寫完成,不過必須用 Javadoc 的格式來撰寫,於是在網路上翻到了兩篇恆逸資訊所提供的教學文件:
  1. 量身訂作自己的 Javadoc - 上

  2. 量身訂作自己的 Javadoc - 下

2007年12月22日 星期六

Java 正規表示式(Regular Expression)

今天在研究 Java Regular Expression 的使用方式,跟 .NET 不太一樣,以下找到了幾篇不錯的介紹文章:
  1. Regular Expression in Java

  2. 正則運算式

  3. 正則運算式和Java編程語言

  4. JDK1.4之正規表示式

  5. Java 的正規表示式

2007年12月19日 星期三

GridGain 程式開發(9) - Gridify With Failover

在這個部分中,要介紹 GridGain 中 Failover 的機制,以及使用方式的說明,分成以下幾個部分:

Grid Job Failover 機制

GridGain 中提供了 Failover 的機制! 這個部分在分散式運算中是相當重要的,讓 job 在無法抗拒的原因下執行失敗後,可以轉移到其他的 node 上執行。

以下稍微說明一下 GridGain 中 Failover 機制的運作方式:
  1. 在 remote grid node 上執行的 Grid Job 發生錯誤或例外

  2. 透過實作 GridTask.result(GridJobResult, List<GridJobResult>) 的方式,決定發生錯誤後的處理方式

  3. 若 GridJobResultPolicy 為 FAILOVER,則 GridGain 會將此 Grid Job 轉派送到其他 node 執行

  4. 確定 Grid Job 有正確的執行完畢,若還是執行失敗,則會繼續 failover 該 job


AspectJ AOP 設定

這個部分在之前都已經提過,因此不再贅述。


範例程式

瞭解 Failover 機制的運作方式後,以下用範例程式來進行說明:

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

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

try {
sayIt("Hello this world!");
System.out.println("\n=========== local 執行完畢 ===========\n");
} finally {
GridFactory.stop(true);
}
} //end main

@Gridify(taskClass=GridifyHelloWorldFailoverTask.class, timeout=60000)
public static void sayIt(String arg) {
System.out.println("\n=========== remote 開始執行 ===========");
System.out.println(">>>> 印出 " + arg);
System.out.println("=========== remote 執行完畢 ===========\n");
} //end sayIt
}
GridifyHelloWorldFailoverTask.java
import org.gridgain.grid.*;
import org.gridgain.grid.gridify.*;
import org.gridgain.grid.resources.*;
import java.util.*;
import java.io.*;

//將 Grid Task 拆成 Grid Job 並送至 remote grid node
public class GridifyHelloWorldFailoverTask extends GridTaskSplitAdapter<GridifyArgument> {
private static final long serialVersionUID = 3675300294101581712L;

//注入(inject) GridTaskSessionResource
//不論是 task 或是分出去的 jobs 都可以看見此 session 與裡面的內容(attribute、checkpoint)
@GridTaskSessionResource
private GridTaskSession ses = null;

@Override
protected Collection<? extends GridJob> split(int gridSize, GridifyArgument arg) throws GridException {
//注入 fail = true 的 attribute 到 Task Session 中
ses.setAttribute("fail", true);

//取得要傳入的字串
String words = ((String)arg.getMethodParameters()[0]);


return Collections.singletonList(new GridJobAdapter<String>(words) {
private static final long serialVersionUID = 1456790456678955617L;

/**
* 執行工作:
* <ol>
* <li>預設 fail 為 false (此時工作可以正常執行)</li>
* <li>取得存在於 Task Session 中的 attribute</li>
* <li>如果 attribute fail 為 true(此時工作無法執行),將其設定為 false 並拋出 GridException 例外</li>
* <li>若拋出例外,Grid Job 停止執行並 failover 到其他 node 執行</li>
* <li>若未拋出例外,則印出傳入的字串</li>
* </ol>
*/

@Override
public Serializable execute() throws GridException {
//預設 fail 為 false (此時工作可以正常執行)
boolean fail = false;

try {
//取得存在於 Task Session 中的 attribute
fail = (Boolean)ses.waitForAttribute("fail");
} catch(InterruptedException e) {
throw new GridException("Got interrupted while waiting for attribute to be set.", e);
}

//如果 fail 為 true
if(fail) {
//將 fail attribute 設定為 false
ses.setAttribute("fail", false);

//拋出 GridException 例外,表示工作失敗
throw new GridException("Example job excepion");
}

//未拋出例外,印出傳入的字串
GridifyHelloWorldFailoverExample.sayIt(this.getArgument());

return null;
} //end execute
});
} //end split

/**
* 此處必須指定當 Grid Job 執行失敗時,所採取的動作<br />
* 設定為 GridJobResultPolicy.FAILOVER 表示執行失敗將會傳到其他 node 繼續執行
*/

@Override
public GridJobResultPolicy result(GridJobResult result, List<GridJobResult> received) throws GridException {
return result.getException() != null ? GridJobResultPolicy.FAILOVER : GridJobResultPolicy.WAIT;
} //end result

@Override
public Object reduce(List<GridJobResult> arg0) throws GridException {
return null;
} //end reduce
}
假設以上程式是運作在兩個 node 的環境上,結果會在 nodeA 產生錯誤訊息(就是程式中拋出的 GridException 例外),接著將 job 轉移到 nodeB 繼續執行,並正確的將字串印出。


而這個部分需要注意的是,程式實作了之前都沒實作過的 GridTask.result(GridJobResult, List<GridJobResult>) method;由於之前都假設 Grid Job 可以正確執行完畢,因此都沒有實作這個部分。

但在實際狀況中,分散式環境本來就較為嚴苛,考量也需要比較多,自然而然 Failover 也是不可缺少的一環!

但是只有 Failover 還是不夠,因為若是一個要執行五天的程式,執行了三天以後發生錯誤,只有 Failover 的話,雖然可以將 job 轉移到其他 node 繼續執行,但是卻得重新來過。

因此之後還要搭配 checkpoint 來達到從 job 中斷處繼續執行的目的,這個部分就留在之後繼續介紹了!

2007年12月18日 星期二

GridGain 程式開發(8) - Gridify With Session

Grid Task Session 運作方式與目的

此範例要介紹在 Grid Task 與 Grdi Job 之間,可以用來作為溝通媒介的「Grid Task Session」。

運作的機制如下:
  1. Grid Task Session 顧名思義,可以知道是屬於 Grid Task 而非 Grid Job

  2. 在 Grid Task Session 中可以加入 attribute 與 checkpoint 資訊,這些則是屬於 Grid Job 的資訊;而 Grid Job 加入 attribute 的用意通常作為與 Grid Task 溝通之用,而 checkpoint 則是記錄執行到何處,以便 job 執行失敗時進行 failover

  3. 每個 Grid Job 都可以檢視其他 job 所擁有的 attribute 與 checkpoint 相關資訊,因此可作為 Grid Job 之間溝通的橋樑

  4. 當然,Grid Task Session 也是可以被 Grid Task,以及所有該 task 拆出來的 Grid Job 所檢視


AspectJ AOP 設定

這個部分跟前面幾篇都是一樣的,因此就不贅述了!


範例程式

了解 Grid Task Session 的運作方式與目的之後,以下就使用程式進行說明:

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

public class GridifyHelloWorldSessionExample {

/**
* @param args
*/

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

try {
sayIt("Hello(1) this(2) world(3)");
System.out.println("\n=========== local 執行完畢 ===========\n");
} finally {
GridFactory.stop(true);
}
} //end main

/**
* 只有第一次呼叫此 method 才會被 GridGain 當作 GridTask 分成 GridJobs
* 之後僅會執行 function 內的動作(印出結果)
* */

@Gridify(taskClass=GridifyHelloWorldSessionTask.class, timeout=3000)
public static void sayIt(String arg) {
System.out.println("\n=========== remote 開始執行 ===========");
System.out.println(">>>> 印出 " + arg);
System.out.println("=========== remote 執行完畢 ===========\n");
} //end sayIt
}
GridifyHelloWorldSessionTask.java
import org.gridgain.grid.*;
import org.gridgain.grid.resources.*;
import org.gridgain.grid.gridify.*;
import java.util.*;
import java.io.*;

//處理 Grid Task 拆開成多個 Grid Jobs 的工作
public class GridifyHelloWorldSessionTask extends GridTaskSplitAdapter<GridifyArgument> {
//注入(inject) GridTaskSessionResource
//不論是 task 或是分出去的 jobs 都可以看見此 session 與裡面的內容(attribute、checkpoint)
@GridTaskSessionResource
private GridTaskSession ses = null;

@Override
protected Collection<? extends GridJob> split(int gridSize, GridifyArgument arg) throws GridException {
//取得傳入的字串,並以空白為間隔,拆為陣列
String[] words = ((String)arg.getMethodParameters()[0]).split(" ");

//產生 Grid Job collection
List<GridJobAdapter<String>> jobs = new ArrayList<GridJobAdapter<String>>(words.length);
for(String word : words) {
jobs.add(new GridJobAdapter<String>(word) {

//注入(inject) GridJobIdResource
//雖然乍看之下 jobID 被設定為 null,但經過注入 @GridJobResource 後
//就會被實際的 GridJobId 給取代(範例:92a5c837-5d64-413c-b254-1a79038f907d)
@GridJobIdResource
private UUID jobID = null;

/**
* 執行工作:
* <ol>
* <li>遠端執行所指定的工作(印出部分字串)</li>
* <li>將所印出的部分字串當作 attribute 加入 task session</li>
* <li>等待其他 Grid Job 也將其所取得的部分字串當作 attribute 加入 task session</li>
* <li>當所有 Grid Job 的 attribute 都設定完後,印出蒐集到的全部字串</li>
* </ol>
*/

@Override
public Serializable execute() throws GridException {
//印出一開始由 task 分配到的部分字串(GridGain 不會將其視為 Grid Task 處理)
GridifyHelloWorldSessionExample.sayIt(this.getArgument());

//將 jobID attribute 加入 GridTaskSession
//因此在 task 與所有 jobs 中都可以看見此 attribute 的內容
ses.setAttribute(jobID, this.getArgument());
try {
//等待所有屬於 task 中的 job 將 attribute 設定到 session 上
for(GridJobSibling sibling : ses.getJobSiblings()) {
//檢查 attribute 是否在 session 中 (Null 代表不存在)
if(ses.waitForAttribute(sibling.getJobId()) == null)
throw new GridException("Failed to get session attribute from job " + sibling.getJobId());
} //end loop
} catch(InterruptedException e) {
throw new GridException("Got interrupted while waiting for session attribute.", e);
}

StringBuilder msg = new StringBuilder();
for(Serializable arg : ses.getAttributes().values())
msg.append(arg).append(' ');

//印出 session 中所有字串的內容(GridGain 不會將其視為 Grid Task 處理)
GridifyHelloWorldSessionExample.sayIt(msg.toString());

return null;
}
});
} //end loop

return jobs;
} //end split

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


重要使用須知

上述的程式碼看起來沒什麼大問題,不過有一個相當重要的地方必須注意! 就是在 GridTaskSession.waitForAttribute(Serializable) 這個 method 上。

仔細注意此 method 所在的位置,位於「Grid Job Sibling 集合的 for loop 中」,加上參數所代入的是 GridJobSibling.getJobId();因此上面那段程式碼的意思為「確定每個 GridJobSibling 都將 JobID 這個 attribute 設定到 session 中」。

這樣說來似乎也覺得沒有什麼,不過重要的是:
若 waitForAttribute(Serializeable) method 中放的不是 GridJobId,而是其他的值呢? 此時該如何運作?
這樣有辦法進行嗎? 目前根據我的理解應該是不行,以下舉個範例:

假設 GridTaskA 拆成 GridJobA + GridJobB + GridJobC,假設在 remote gird node 在處理 GridJobA 的情況下,根據上面那一段 for loop 程式,會執行以下工作:
  1. 取得 GridJobSibling,分別是 GridJobB 與 GridJobC

  2. 指定 session 等待 GridJobB 將 JobID attribute 的加入,此時會等待其他 remote grid node 將 GridJobB 將其 JobID 設定進 session 中

  3. 確定 GridJobB 設定好 attribute

  4. 指定 session 等待 GridJobC 將 JobID attribute 的加入,此時會等待其他 remote grid node 將 GridJobC 將其 JobID 設定進 session 中

  5. Sibling 全部執行結束!

這樣看起來似乎還是沒什麼問題,但是重點來了! 在第二個與第四個流程,代入 GridTaskSession.waitForAttribute 的參數是??

沒錯! 就是 GridJobSibling.getJobId(),但這樣又有啥問題呢?

問題可大了! 這代表「使用者必須想辦法取得 Sibling 的 GridJobID」!!

因此問題慢慢釐清了,由於 GridJobSibling 提供了 getId() method,因此可以透過此 method 取得 GridJobID,所以沒有問題!

但是! 若是想要改用自行設定的值而非 JobID 呢?
恩,很抱歉! GridJobSibling 並沒有提供其他方法可以取得使用者所自訂的值!

因此,GridGain 在官方網站的文件上提到「可以透過 waitForAttribute 這種方式,讓多個 GridJob 在某種程度上達成有順序的執行」,其實還是有其限制在的;也就是說,若要判斷 session 中的 attribute 是由哪一個 GridJob 所設定的,那就必須使用 GridJobId 作為 attribute 的 key,因此在使用上還是需要多注意囉!

最後,看完上面的囉哩八嗦,應該可以了解 Grid Task Session 的使用方式以及注意事項了!

2007年12月17日 星期一

GridGain 程式開發(7) - Gridify With State

接著這個範例,將 grid-enabled method 從原本的程式中抽離出來,自成一個 class。

而這個 class 除了 grid-enabled method 以外,還包含了狀態(state)資訊,以及處理各狀態資訊所需要的 method。


AspectJ AOP 設定

然而,要使用這個功能,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


範例程式

為了簡單說明,state 資訊僅使用字串來表示,當然也可以是複雜的資料結構,端看使用者所需要的功能為何,以下就用範例程式來說明:

GridifyHelloWorld.java
import org.gridgain.grid.gridify.Gridify;

public class GridifyHelloWorld {
//設定 state 為 String
private String state = null;

// ========== 處理 state 相關 methods ========== (begin)
public String getState() {
return state;
} //end getState

public void setState(String state) {
this.state = state;
} //end setState
// ========== 處理 state 相關 methods ========== (end)

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

@Gridify(taskClass=GridifyHelloWorldStateTask.class, timeout=3000)
public void sayIt() {
System.out.println("\n=========== remote 開始執行 ===========");
System.out.println(">>>> 印出 " + this.state);
System.out.println("=========== remote 執行完畢 ===========\n");
} //end sayIt
}
GridifyHelloWorldStateTask.java
import java.io.*;
import java.util.*;
import org.gridgain.grid.*;
import org.gridgain.grid.gridify.*;

/**
* 繼承 GridTaskSplitAdapter,必須實作 split 與 reduce 兩個 method
*
* 【
GridifyArgument
* 為 grid-enabled method 所屬的 class(此範例為 GridifyHelloWorld) 的 instance 的反射(reflection)
* 透過 reflection< 的機制,可以取回 class instance 相關資訊
* */

public class GridifyHelloWorldStateTask extends GridTaskSplitAdapter<GridifyArgument> {

/**
* 將 task 拆成多個 jobs 並丟到 grid node 去執行
*/

@Override
protected Collection<? extends GridJob> split(int gridSize, GridifyArgument arg) throws GridException {
//取得 grid-enabled method 所屬的 class(GridifyHelloWorld) instance
GridifyHelloWorld hello = (GridifyHelloWorld)arg.getTarget();

String[] words = hello.getState().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 {
GridifyHelloWorld hello = new GridifyHelloWorld();
hello.setState(this.getArgument());
hello.sayIt();

return null;
} //end execute

});
} //end loop

return jobs;
} //end split

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

@Override
public Object reduce(List<GridJobResult> arg0) throws GridException {
return null;
}
}
GridifyHelloWorldStateExample.java
import org.gridgain.grid.*;
import org.gridgain.grid.gridify.*;

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

try {
GridifyHelloWorld hello = new GridifyHelloWorld();

hello.setState("Hello this World!");

hello.sayIt();

//此行僅會在本地端執行
System.out.println("\n=========== local 執行完畢 ===========\n");
} finally {
GridFactory.stop(true);
}
}
}
由於這幾個範例程式也是使用了 AOP 機制,因此在 sayIt() method 的呼叫也是容易產生混淆! 以下稍微說明一下程式執行流程:
  1. 產生 GridifyHelloWorld instance,並設定此 instance 的 state 資訊

  2. 呼叫 grid-enabled sayIt() method,但不會印出任何 state 資訊,而是由 aspectJ 呼叫 GridGain,並根據 GridifyHelloWorldStateTask 中的 split 實作來將 task 拆成多個 jobs(以空白為間隔拆開 state 資訊)

  3. 接著 GridGain 會將包含 grid-enabled method 的 class 序列化(serialize)後,傳到 remote grid node 準備執行

  4. remote gird node 將其反序列化(deserialize)之後,根據此 class 產生新的 instance,並根據接收到的字串重新設定 state 資訊

  5. 當 state 資訊設定完成,最後再呼叫 sayIt() method,此時 GridGain 不會再度將其視為 task 而只是將 state 資訊輸出

最後,透過這種方式,使用者就可以將 business logic 以另外開發成 module 的方式去寫了! 不需要通通嵌在原本包含控制 grid 的相關程式之中,開發起來也較為簡潔易懂!!


更簡單的開發方式

同樣的,以下將上面程式修改為較容易理解的版本,修改後的執行結果也是相同的!

GridifyHelloWorld.java
import org.gridgain.grid.gridify.Gridify;

public class GridifyHelloWorld {
private String state = null;

public void setState(String state) {
this.state = state;
} //end setState

public String getState() {
return this.state;
}

// ***************** 修改的部分 ***************** (Begin)
@Gridify(taskClass=GridifyHelloWorldStateTask.class, timeout=3000)
public void splitGridTask() {
//這裡只要加入足夠的 annotation 資訊即可
//不需要任何實作,只是告知 GridGain 作 split task 的動作
} //end splitGridTask

public void sayIt() {
System.out.println("\n=========== remote 開始執行 ===========");
System.out.println(">>>> 印出 " + this.state);
System.out.println("=========== remote 執行完畢 ===========\n");
} //end sayIt
// ***************** 修改的部分 ***************** (End)
}
GridifyHelloWorldStateTask.java
僅為 GridTask 拆開成為 GridJob 的部分,因此不需更改!
GridifyHelloWorldStateExample.java
import org.gridgain.grid.*;

public class GridifyHelloWorldStateExample {

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

try {
GridifyHelloWorld hello = new GridifyHelloWorld();

hello.setState("Hello this world!");

// ***************** 修改的部分 ***************** (Begin)
//將 GridTask 拆成多個 GridJob,並執行工作
hello.splitGridTask();
// ***************** 修改的部分 ***************** (End)

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

修改成以上這個版本之後,應該是會比較容易瞭解了!

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;
}
}

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