2007年12月16日 星期日

GridGain 程式開發(5) - Basic Grid Task

GridGain 程式開發入門

第一個 GridGain 的程式其實很容易撰寫,假設要在 Grid node 輸出 Hello World,只要使用以下寫法即可:
import org.gridgain.grid.*;
import org.gridgain.grid.gridify.*;

public class GridHelloWorldExample {
public static void main(String[] args) throws GridException {
//啟動 Grid Factory
GridFactory.start();

try {
//丟出 Grid Task 到 remote client 執行
sayIt("Hello World!");
} finally {
//停止 Grid Factory
GridFactory.stop(true);
}
}

@Gridify //設定 Gridify annotation
public static void sayIt(String msg) {
System.out.println(msg);
}
}
從上面範例中,可以看出,要在遠端的 Grid node 輸出 Hello World,只要在 method 前面加上「Gridify」這個 annotation 即可!


Basic Grid Task

這個部分是將 Hello World 字串拆開(以空白作間隔)後,分別傳到不同的 grid node 做輸出

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

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

public static void main(String[] args) throws GridException {
//啟動 Grid Factory
GridFactory.start();

try {
//取得 grid instance,用來處理 grid task
Grid grid = GridFactory.getGrid();

//指定處理 Grid Task 的 class,並丟出所需要的參數
grid.execute(GridHelloWorldTask.class.getName(), "Hello this world").get();

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

GridHelloWorldTask.java
import org.gridgain.grid.*;
import java.util.*;
import java.io.*;

/**
* 將 task 拆成多個 job,必須 extends GridTaskSplitAdapter 這個 abstract class<br />
* 其中有兩個 method 必須實作,分別是 split 及 reduce
* 而此 GridTaskSplitAdapter 已經將 job 與 node 的配對資訊隱藏,讓程式開發更為簡易
* */
public class GridHelloWorldTask extends GridTaskSplitAdapter<String> {
/**
* split 是透過參數列中的兩個參數將 task 分開成多個 job<br />
* 並指定每個 job 的處理工作
* @param gridSize 目前 Grid Node 的數量
* @param arg 要被拆開的字串<br />
* (其型態是根據 GridTaskSplitAdapter 所用的型態,此處為 String)
*/

@Override
public Collection<? extends GridJob> split(int gridSize, String arg) throws GridException {

//將傳入的字串以空格做區隔存成 List 中的各元素
List<String> words = Arrays.asList(arg.split(" "));

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

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

//遠端 grid node 接收到 job 後所要進行的工作
public Serializable execute() {
System.out.println("\n=========== remote 開始執行 ===========");
//getArgument() 為 GridJobAdapter 的 method,會回傳上面的 word 參數
System.out.println(">>>> 印出 " + this.getArgument());
System.out.println("=========== remote 執行完畢 ===========\n");

//必須回傳實作 Serializable 介面的 class instance
//此處不需回傳任何東西,因此就回傳 null
return null;
}

});
} //end loop

return jobs;
} //end split

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

@Override
public Object reduce(List<GridJobResult> arg0) throws GridException {
return null;
} //end resuce
}
假設額外啟動的 GridGain service 有兩個,加上原本 eclipse 所跑的一個,一共會被視為 3 個 grid node,因此上面的字串拆成三份後,每一份就會傳到 node 上去印出。

然而比較需要注意的是,在「GridHelloWorldExample.java」程式中印出 ">>>程式執行完畢...." 的字眼,只會出現在 eclipse 的 console output 上,原因是因為其他的 grid node 上僅會執行 GridHelloWorldTask.java 中 execute() method 中的內容。

GridGain 程式開發(4) - 開發指引

在開始進行 GridGain 程式開發之前,有一些程式設計上的重點是必須先瞭解的,一共有三個部分:

Serialization & Deserialization

在分散式的環境中,要讓 job 可以從某個 node 發出到 remote node 執行,必須使用稱為序列化(serialize)的機制。

而 Serialize 的作用是將 job 與 code serialize 成 byte stream 後,在透過網路傳送到 remote node 上,由 remote node 將其 deserialize 成為原本的 job 和 code 再開始執行!

當然其中有很多控制資訊與機制是包含在裡面的,加上了這些部分後,想當然爾實作複雜度會提高不少! 然而這些部分有許多 GridGain 都幫我們處理好了,因此就不再繼續探討了!

只是為了要讓程式可以正確傳送到 remote grid node 執行,以下幾個條件必須滿足:
  1. 每個 class 都必須實作 java.io.Serializable interface

  2. class 中 non-static 的 field 或是 method 都必須有實作 java.io.Serializable interface

  3. 傳入 method 的參數也是必須要實作 java.io.Serializable interface


Inner & Anonymous Classes

在 GridGain 中,通常 Grid Job 執行的實作,都是以 inner class 的方式進行實作的,而 inner class 有時會使用到 outer class 的參數! (inner class 是可以隨意存取 outer class 中的 field 的)

這個部分在之後的範例就可以很詳細的看到。


Overriding Methods with Gridify Annotation

這是在開發程式使用繼承時必須注意到的地方,千萬注意,「不要繼承加入 Annotation 的 method 的 class」,否則程式執行的結果往往不會是你預期的.....

以下用一段範例程式碼來說明:
public class A {
@Gridify
protected void methodA() {
...
}
};

public class B extends A {
@Override
protected void methodA() {
...
super.methodA();
...
}
}
由於 GridGain 使用 aspectJ 的關係,若是呼叫 B.methodA() 的話,實際上 B.methodA 的執行會變成兩次。

以下是 GridGain 運作的方式:
  1. 建立一個 class B 的物件

  2. 透過 B 物件呼叫 methodA(),由於此 method 沒有加上 annotation,因此 aspectJ 不會有動作

  3. methodA() 執行到一半,呼叫了 A.methodA()

  4. 由於 A.methodA() 有加上 annotation,因此 aspectJ 會呼叫 GridGain 並將呼叫此 A.methodA() 所屬的 method(此處為 B.methodA) 與 class(此處為 B) 序列化後傳到 remote grid node 上

  5. 因此不論是在 local node 或是 remote node 都會再次執行 B.methodA() => 因為 B.methodA() 是包含 Gridify A.methodA() 的 method

  6. 由於 B.methoda() 再度執行,因此又呼叫了 A.methodA()

  7. 此時 GridGain 偵測到此情況,就不會再將工作派送到 remote grid node 執行,而僅在 local 端執行完畢

因此,假設除了 local 外還有一個 remote grid node(共兩個 node);原本應該是 B.methodA() 執行一次,A.methodA() 執行兩次;由於錯誤的繼承方式,變成了 B.methodA() 執行兩次,反倒是 A.methodA() 僅執行一次。

GridGain 程式開發(3) - Grid Tasks & Grid Jobs

了解 Grid Task 與 Grid Jobs 之間資訊(session、attribute、checkpoint)的交換方式與種類介紹後,接下來這個部分也是 GridGain 程式運作的基礎:GridTask & GridJob。

以下就分成幾個部分來說明這兩個概念:

GridTask & GridJob Interfaces

由於分散式運算所需注意的地方很多,因此 GridGain 將應該要有的功能清楚的定義在 GridTask 與 GridJob 兩個 interface 中,讓使用者可以知道如何建立一個 Grid Task,然後對其進行後續的操作。

在第一篇提到,Grid Task 即是使用者送出的工作,而 GridGain 在 GridTask interface 中定義了幾個功能,分別是:
  1. 將使用者派送的工作,根據自訂的規則拆成(split)多個 Grid Job

  2. 接收 remote grid node 處理 Grid Job 的結果

  3. 將所有的處理結果進行彙整(reduce)

而若要建立 Grid Task,必須實作 GridTask interface,其中當 GridTask.map(List, T) method 被呼叫時,就可以取得由該 Grid Task 所拆開的 Grid Jobs 的資訊(會被派送到那個 grid node 去執行的相關訊息),再由 GridGain 將 Grid Job 序列化(serialize)傳送至 remote grid node 執行。


Grid Task Execution Sequence

這個部分要說明 Grid Task 執行的步驟細節:
  1. 接收到 grid task 的執行要求,並根據使用者給定的名稱,準備開始將 grid task 部署到 Grid 上。

  2. Grid 將會產生與此 grid task 相對應的 session 資訊。

  3. Grid 會根據使用者在程式中所加入的 annotation,注入(inject)所需要的 resource 到 grid task instance 上。

  4. 接著 Grid 會呼叫 GridTask.map(List, T) method,將 Grid Task 拆成多個 Grid Jobs 之後,再取得 Grid Job 與 remote grid node 對應的相關執行時的資訊。

  5. 有了 Grid Job 與 remote grid node 的對應資訊後,就可以開始將 job 丟到 remote grid node 上去執行。

  6. 當 job 傳到 node 準備執行前,會根據使用者在 GridCollisionSpi SPI 中所設定的方式進行處理,有可能會有以下三項:
    • WAIT:job 會進入 waiting list,等待有新 job 到達或是先前 job 已經處理完後,才會被移到 active list 中開始執行(此時 Collision SPI 會被呼叫,job 才會有所動作)。
    • EXECUTE:job 會直接被移到 active list 中執行。
    • REJECT:如果目前 active list 中有 job 正在執行,則此 job 會被拒絕執行;此時會將包含有 GridExecutionRejectedException 例外的 GridJobResult instance 傳入 GridTask.result(GridJobResult, List<GridJobResult>) 作為參數。

  7. 對於位於 node active list 中的 job,Grid 會將使用者指定的所有 annotation resource 注入(inject)到 Grid Job instance 中。

  8. remote grid node 會執行使用者在 GridJob.execute() 中所實作的工作。

  9. 當 job 在遠端執行完成後,會將結果回傳至 Grid Task,並傳入 GridTask.result(GridJobResult, List<GridJobResult>) 作為參數;若執行期間有例外產生,在可檢查到的例外發生情況下,可透過 GridJobResult.getException() 取得例外資訊;若無法檢查到的例外,會被統一包裝為 GridUserUndeclaredException 例外。

  10. 每個 job 執行完都會呼叫一次 GridTask.result(GridJobResult, List<GridJobResult>) method,此 method 會回傳 GridJobResultPolicy instance,而 Grid 會根據所回傳的 policy instance 來決定後續的 job 的處理方式,可以有以下三種作法:
    • GridJobResultPolicy.WAIT:Grid Task 會持續等待其他工作的完成,直到最後一個工作完成後,會呼叫 GridTask.reduce(List) method 將結果彙整。
    • GridJobResultPolicy.REDUCE:當收到的 policy 為 REDUCE,則會馬上呼叫 GridTask.reduce(List) method 進行結果的彙整,而其他未完成的 job 則會收到 cancel 的命令。
    • GridJobResultPolicy.FAILOVER:job 在執行中若是發生錯誤時,就會收到 FAILOVER policy;此時會根據使用者在 GridFailoverSpi 中的實作來決定要將 job 傳到何處繼續執行(若使用的是 Grid Task Adapter,則在發生 node crash 或是 job rejection 時,就會自動將 job 派送到其他 node 繼續執行)。

  11. 當所有 job 都處理完,也都收到回傳結果後,就會呼叫 GridTask.reduce(List) method 將結果彙整。

  12. 最後 Grid 會將相關的 session resource 全部清除,包含 attribute、checkpoint .... 等等,此時表示 Grid Task 正式執行完畢。

還有一點需要注意的! 當 job 在 remote grid node 執行到一半時收到中斷執行(cancel)的命令時,GridJob.cancel() method 就會被呼叫;使用者可以在此 method 進行實作,可以決定讓 job 忽略中斷繼續執行、直接中斷,或是進行其他不同的工作 .... 等等。

而通常 job 會再執行到一半被中斷的原因,有可能會有以下幾種:
  • Collision SPI 中斷了正在執行的 job。

  • Grid Task 已經執行完畢,而不需要再繼續等待其他 job 執行回傳的結果。

  • 使用者呼叫了 GridTaskFuture.cancel() method 停止整個 Grid Task 繼續執行。


Resource Injection

在 GridTask 與 GridJob 之間訊息的傳遞,或是控制資訊的交換,在 GridGain 中通稱為 Grid Resource。

而 GridGain 使用 IoC(Inverse of Control) 的方式,讓使用者可以透過 annotation 的方式將 resource 注入(inject),而 annotation 可以用在 field 上,也可以用在 method 上喔!!

以下介紹幾個常用的 Grid Resource:

Grid Resource
描述
GridTaskSessionResource 此即為上一篇所提到的 Distributed Grid Task Session(可以根據需求自訂 resource 為 primitive type 或是 object)
GridInstanceResource
正在執行的 task 所屬的 Grid instance
GridLoggerResource
GridLogger instance,而此 Logger instance 將會被 Grid instance 所使用
GridHomeResource
GridGain 安裝目錄的資訊
GridExecutorServiceResource
java.util.concurrent.ExecutorService instance (供 Grid 所使用)
GridLocalNodeIdResource
本地端 Grid Node ID,型態為 java.util.UUID
GridMBeanServerResource
javax.management.MBeanServer instance (供 Grid 所使用)
GridJobIdResource
代表 Grid Job 的唯一 ID 值,型態為 java.util.UUID

在 GridGain 中定義的 Grid Resource 一共就是以上這八種,詳細的使用方式可以參考官方網站的說明。


Convenience Adapters

之前介紹的 Grid Task、Grid Job、Grid Resource .... 等等不同的部分,要瞭解這些部分才能順利的開發程式,其實有蠻多細節是必須要注意的! 實際學習上也不是這麼容易!

因此 GrinGain 中提供了幾個 adapter,可以讓使用者在程式開發上更容易上手喔!!

以下來介紹這些 adapter:

Adapter
描述
GridTaskAdapter
GridTaskAdapter 提供了預設的 GridTask.result(GridJobResult, List<GridJobResult>) 實作,當 remote grid node 在執行工作時發生 node crash(拋出 GridTopologyException 例外) 或是 job 被拒絕執行(拋出 GridExecutionRejectException 例外)時,會自動處理 Job Failover 的工作。
GridTaskSplitAdapter
GridTaskAdapter 提供了讓使用者透過自行實作 GridTaskSplitAdapter.split(int, T) method 的方式,讓使用者可以根據需求決定要如何將 Grid Task 拆成 Grid Jobs,隱藏了 job 與 node 的配對相關資訊,讓整個工作更為簡化。
GridJobAdapter
GridJobAdapter 提供了預設沒有實作內容的 GridJob.cancel(),因此若無此需求,使用者可以不用再實作此 method;並允許使用者根據需求取得或設定 job 的參數。

GridGain 提供的 Adapter 不只以上這三種,若想要更瞭解 Adapter 的相關細節,可以參考官方網站的文件。


Distributed Session Attributes and Checkpoints

這個部分其實在上一篇都有說明到了! 重點再說明一次:

在 Session Attributes 的部分,藉由設定 session attributes 的方式可以讓 Grid Task 與 Grid Jobs 之間,或是 Grid Sibling Jobs 之間作溝通。
有了這個機制之後,工作較快執行結束的 node,可以根據 session attributes 的值,等待其他 node 將工作處理完畢,因此可以達到一定程度的工作執行流程控制。

另外在 Checkpoints 的部分,則是持續不斷的儲存 job 的執行期間的相關資訊,以便錯誤發生時,可以將此 job failover 到其他的 node 繼續執行,而不需要全部從頭開始。
而這個功能,對於長時間執行的工作來說,是特別的重要的!

2007年12月13日 星期四

GridGain 程式開發(2) - Distributed Grid Task Session

要在分散式的環境下開發程式,各個 node 之間,各 job 之間的資訊交換與傳遞,通常是相當複雜且不容易處理的好的部分!

而在 GridGain 中,針對控制資訊交換的部分進行了完善的處理,並開放許多 SPI 可供使用者針對自身需求自行開發程客製化。

因此為了開發程式上可以更容易進入狀況,有一些觀念是必須事先瞭解的喔! 以下就慢慢來說明.........


Description

每當有 grid task 被執行時,就會產生相對應的 distributed task session 資訊,而這些資訊不僅在 grid task 中可以存取,連屬於此 grid task 的 grid jobs 也都可以存取喔!

反之,在 grid job 上的資訊,在 grid task 中可以看見,GridGain 會自動在 task 與 jobs 之間進行資訊的傳遞與交換。藉由這種機制,賦予使用者在開發程式上有相當大的彈性空間。

而 Grid Task Session 包含了兩個部分:attribute & checkpoint

這兩個部分可以從 task 進行存取,也可以從 jobs 中進行存取;當然 GridGain 中就提供了 GridTaskSession Interface,主要負責進行 Grid Task Session相關資訊的存取控制之用。

關於 attribute 與 checkpoint,有幾個地方是需要特別注意的:
  1. task session 中的 attribute 與 checkpoint 可以在 jobs 執行發生錯誤時,自動的 failover 到其他的 grid node 上去以方便從中斷的地方繼續執行。

  2. 當 task 執行完畢後,儲存於 session 中,scope 屬於 GridCheckPointScope.SESSION_SCOPE 的 checkpoint 資訊都會被移除掉。

  3. 承上,若 scope 屬於 GridCheckpointScope.GLOBAL_SCOPE 的 checkpoint 資訊,則會繼續保留,而其他的 task 還是可以檢視此資訊。

  4. 在 session 中資訊的排序,對任何的 job 都是相同的。例如不會出現 A job 看到 attribute A 在 attribute B 之前,B job 看到的卻相反,GridGain 保證在 session 中資訊的排序對 task 或是 jobs 都是相同的。


Connected Tasks


在分散式處理的環境中,通常分散出去的工作,執行完畢的順序都是不一定的,影響因素很多,有可能是 grid node 的繁忙程度、硬體效能、網路頻寬 .... 等等。

在這種情況下,若是有工作必須確定其順序來執行的需求,通常會需要加上許多額外的資訊來控制工作的完成順序,才有可能達成此目的。

而在 GridGain 卻替使用者解決了這個問題! 如何解決呢? 有兩種方式:

一種是透過設定 waitForAttribute(...) method 的方式,確定相關的資訊全部蒐集完成後再繼續進行工作!

另外一種則是設定 GridTaskSessionAttributeListener 的方式,針對 attribute 資訊的改變額外進行處理,以確保工作按照順序進行。


Session Injection

GridGain 提供了上述的這麼強大的功能,那要如何將 task session 加入到程式中使用呢? 答案是:Injection(注入) !

由於 GridGain 支援了 IoC(Inverse of Control),因此只要設定好 library、classpath 等部分,就可以透過「@GridTaskSessionResource」annotation 的方式加入 task session 囉!

而 annotation 不僅可以使用在 field 上,也可以使用在 method 上喔!! 以下用簡單範例說明:
//field
@GridTaskSessionResource
private GridTaskSession ses = null;

//method
@GridTaskSessionResource
void setSession(GridTaskSession ses) {
this.ses = ses;
} //end setSession

GridGain 程式開發(1) - 入門簡介

重要名詞說明

要在分散式環境開發程式,有一些事情是必須住役的,而在 GridGain 中對於這些部分,為其定義了專有名詞。以下進行說明:

Grid Intance
Grid Instance 為單一 grid runtime,而 GridGain 強大之處就是可以在同一個 VM 中產生多個 Grid Instance。
Grid Node
Grid Node 與 Grid Instance 的概念幾乎是相同的,也是單一 grid runtime,唯一差別之處是每個 Grid Node 都有一個唯一的 ID 植可供辨識之用。
Grid Task
Grid Task 即為使用者所發佈必須執行的工作,這是 GridGain 中工作處理最重要的部分。
Grid Job
將 Grid Task 拆成可傳遞到遠端各個 Grid Node 去執行的工作,這些工作稱為 Grid Job;這些 Grid Job 會在遠端的 Grid Node 執行完後,再將結果回傳給派送工作的 Grid Node 作彙整。
Grid Task Session
在整個 Grid Task 執行的過程中,GridGain 提供了一個機制可以讓 Grid Task Instance 以及 Grid Job Instance 可以共同存取的狀態資訊,稱為 Grid Task Session。而 Grid Task Session 可使用 annotation 的方式注入(inject);而 GridGain 還提供了許多 API 可以用來存取這些狀態資訊。
【備註】Grid Task Session 是可以自訂的,可以是 primitive type、複雜的資料結構、甚至是物件
Grid Topology
Grid Topology 表示 Grid 所在的環境,例如:同一個 VM 中、在 LAN 中、在 WAN 中。GridGain 提供了 SPI 可作為 topology 的管理,而在不同的 topology 中管理機制相對也不同,在 LAN 中可以做精準的控管,但在 WAN 中的控管自然就不會這麼的嚴密。
Grid Job Sibling
由同一個 Grid Task 所拆出來的多個 job,都屬於 Grid Job Sibling。
Grid Job Collision
若 Grid Job 傳送到某個 Grid Node 去執行,但該 Node 正在執行其他的 Job,此時就發生 Collision 的狀況;GridGain 提供了 Collision SPI,讓使用者可以自行開發程式,來決定這一類的問題應該要如何處理。
Grid Job Failover
當 Grid Job 在 Node 上執行失敗,必須將執行到一半的工作移轉到其他 node 繼續執行,稱為 failover。而 GridGain 也提供了 Failover SPI 可讓使用者自訂 failover 發生時的處理方式。
Grid Job Preemption
當 Grid Job 在某個 node 執行到一半,其執行優先權很高,有異常狀況發生需要轉移到其他 node 執行,就可以透過類似插隊的方式,到其他 Grid Node 上優先執行,稱為 Grid Job Preemption。通常會發生在需要長時間執行的 Grid Node 上。
Grid Job Checkpoint
與 Task Session 不同的是,Grid Job Checkpoint 所儲存的為 Job 的狀態資訊,同樣的,狀態資訊也可以由使用者自訂,通常會用在 failover 或是 preemption 的狀況,目的是讓 Grid Job 可以從上次執行停止的地方繼續執行,不需每次都從頭開始。
Split & Aggregate
這概念很容易,就是將 Grid Task 拆成多個 Grid Job 並傳送到 remote Grid Node 執行,最後再將各 remote Grid Node 回傳的結果進行彙整。而這功能是分散式運算最基本的概念,所有相關的 framework 都應該會提供。
Grid Enabling
透過 Gridify annotation 即可讓指定的 method 在 Grid 環境上執行,這種動作即稱為 Grid Enabling。
Grid Event
在 Grid Task 拆成多個 Grid Job 並傳送到 remote Grid Node 執行的過程中,會產生許多 Grid Event,而這些 event 資訊都會存在發生該 event 的 node 上,因此這些資訊都是散亂在每個 node 上,因此 GridGain 就提供了 API 可以來針對這些 event 資訊進行查詢。


Grid Interface

從上一個部分可得知,在分散式運算的環境中,有許多重點是必須要備考量的。當然 GridGain 也提供了許多 API 可供使用者使用,而根據 polymorphism 的特性,GridGain 將許多功能定義在 Grid Interface 中,因此可透過 Grid Interface 針對 Grid Instance 進行各種 method 的呼叫。

而 Grid Interface 所提供的功能,大概有以下幾類:
  • Grid Task 的管理
    這是 Grid Interface 所提供的主要功能,使用者可以根據需求以同步或非同步的方式執行 Grid Task,差別在於非同步的執行,需要指定 GridTaskListener。詳細的情形可參考 Grid Interface 所提供了 execute() method。
  • Topology 管理
    此部分的功能可用來管理 Topology 的 listener,或是存取不同的 Grid Node。
  • 訊息交換
    訊息的交換必須靠使用者自行實作 Communication SPI,透過這種方式除了可讓使用者對 Grid Node 發送訊息外,還可以對 listener 進行訊息訂閱,讓 listener 傳遞所需要的訊息回來。
  • System Event 管理
    在 Grid Node 所進行的工作,都會產生各種不同的 event,而 Gird Interface 則提供了這些 event 的管理,除了可以查詢 event 相關資訊以外,還可以根據需求設定 event 的 listener。

一般來說,在程式中的使用方式通常是宣告 Grid Interface 後,並將其指向用 GridFactory.getGrid() 或是 GridFactory.getGrid(String) 所取得 Grid Instance,再根據呼叫 GridGain 所提供的 API。

2007年12月11日 星期二

使用 Command Line(cmd.exe) 重新啟動 Windows service

今天找了一下如何透過 command line(cmd.exe) 啟動 Windows service 的方式,只要透過「NET」這個指令即可!!

NET 的使用方式,可以參考此篇文章(PC NTMMTA: 如何從命令列啟動服務)

而 cmd.exe 的參數有很多,詳細的參數說明可以參考這篇文章

其中我們用到的是 /C(命令執行完後關閉視窗) 以及 /K(命令執行完後不關閉視窗)

以下用個範例來說明:

假設在 XP 底下,要重新啟動(restart)名稱為 "Alchemi Executor Service" 的 service,選擇「開始 -> 執行」,輸入以下內容:
# 透過「&&」符號可以將多個指令串接起來!

# Windows XP

C:\WINDOWS\system32\cmd.exe /C NET STOP "Alchemi Executor Service" && NET START "Alchemi Executor Service"

# Windows 2000
C:\WINNT\system32\cmd.exe /C NET STOP "Alchemi Executor Service" && NET START "Alchemi Executor Service"
或是進入命令提示字元,改輸入以下內容:
NET STOP "Alchemi Executor Service"
NET START "Alchemi Executor Service"
都可以達到相同目的喔!! (重新啟動 service)

2007年12月5日 星期三

Java 平台上的 Grid Framework - GridGain

簡介

之前使用了在 .NET 平台上的 Grid Framework - Alchemi

使用了一陣子的結論是.........真的不是很穩定,三不五時 Grid Manager service 就會掛掉.....

於是想找另外一套來試試看,於是找到一個 Java based 的,叫做 GridGain

GridGain 有什麼特色呢? 以下是官方網站的描述:
Grid
GridGain is focused on doing one thing and doing it better than any other product – providing the best grid computing platform for Java that is fun, simple and productive to use.

Java
GridGain is developed in Java and for Java developers and it is a natural extension of the latest Java development methodologies including annotations, integration of Spring and AOP-based grid-enabling.

Open Source & Professionally Supported
GridGain is dual-licensed under LGPL and Apache 2.0 licenses and is built on open source software foundation. GridGain Systems provides full range of services for GridGain project including support, training and consulting.

以下是官方網站的圖表說明:(看起來的確架構很簡單)



接著是簡單的程式與執行方式的圖表說明:

原本的程式:
class BizLogic {
public static Result process(String param) {
//執行程式的地方
........
}
}

class Caller {
public static void Main(String[] args) {
//開始執行
BizLogic.process(args[0]);
}
}
執行方式為:

將其改寫成可以在 GridGain 上面運作:
class BizLogic {
@Gridify(…) //使用「Gridify」annotation 來註明這是要到 Grid node 去跑的!
public static Result process(String param) {
......
}
}

class Caller {
public static void Main(String[] args) {
//Grid 程式啟動
GridFactory.start();

try {
//原本的程式還是照樣執行
BizLogic.process(args[0]);
}
finally {
//停止 Grid 程式
GridFactory.stop();
}
}
}
透過加入 annotation 後,執行方式就改變為:
大概修改方式就是這樣,其實還蠻容易的!

總而言之,由於 GridGain 是 based on Java,因此不論是裝在 Windows or Linux,都是可以使用的!

再來,主要針對的是 computing 的部分,沒有包含其他複雜的部分(例如:Grid storage),簡單容易使用。

最後,這是免錢的!!

考慮了許久,還是決定把這一套也研究一下,畢竟系統還是穩定、安全為最佳考量!

總不能在論文程式 demo 時,程式突然不能跑了.....那可慘了.... Orz

另外,官方網站上還提供了幾段教學影片,包含了安裝以及簡單的程式開發教學,雖然是講英文且沒字幕,不過應該也不難看懂或聽懂!


行前準備

GridGain 的安裝是相當容易的! 只要從官方網站下載回來後就可以直接安裝了!

但安裝了以後,若要啟動 GridGain service,必須要正確的設定「GRIDGAIN_HOME」與「JAVA_HOME」兩個環境變數。

當 GridGain 安裝好以後,除了包含了啟動 GridGain service 需要的執行檔以外,還有開發程式所需要的 library,說明如下:
  1. 在 $GRIDGAIN_HOME 目錄中所包含的 gridgain-x.x.x.jar 檔案
  2. 在 $GRIDGAIN_HOME/lib 目錄中所包含的所有 jar 檔按

開發程式時必須把以上兩種 library 都加入,程式才可以正常運作!

另外,在之後有兩種 Grid 範例程式開發的說明,分別是:
  1. Hello World
    這個部分是以簡單的字串處理工作來進行說明
  2. Monte-Carlo
    這個是比較複雜的範例,內容大概是投資相關的風險模擬計算,比較貼近一般所要開發程式的方式

在官方網站中,都有針對上面兩種範例,以不同的實作方式進行說明。

2007年12月1日 星期六

解決 MySQL 與 .NET 間 DateTime 資料格式衝突的問題

如果有用 MySQL Connector for .NET platform 的人可能在將 DateTime 格式的資料寫入資料庫時,會出現以下錯誤訊息:(我用的是 5.1 版 for .NET 2.0)
Unable to convert MySQL date/time value to System.DateTime
此例外很明顯的說明要將 DateTime 的格式轉換為 MySQL 可以接受的 DateTime 格式!

有兩種方式:

1、SQL 語法為自己組合而成的字串

此時只要知道 .NET DateTime 之間與 MySQL DateTime 之間格式有何不同即可。

.NET DateTime:2007/01/01 12:00:00

MySQL DateTime:2007-01-01 12:00:00

只要將年月日中間的「/」符號換成「-」即可囉!


2、由程式所自動產生的 SQL 語法

若是用了 MySqlDataAdapter、MySqlCommandBuilder、DataTable 所搭配起來的組合,若是 DateTime 的欄位,就必須要傳入 MySqlDateTime 格式的資料!

注意喔! 不是 DateTime 而是 MySqlDateTime 喔!

而 MySqlDataTime 的 class 在 MySql.Data.Type namespace 中,必須在程式前面先 using 才行,這是必須要注意的!

轉換範例如下:
//這是會發生錯誤的寫法,因為 MySQL 不接受 .NET DateTime 格式的資料
myDataTable.Rows[0]["myDateTime"] = DateTime.Parse("2007/01/01 12:00:00");

//必須初始化一個 MySqlDateTime 的物件傳入才行
myDataTable.Rows[0]["myDateTime"] = new MySqlDateTime(2007, 1, 1, 12, 0, 0);
當然,MySqlDateTime 的 constructor 有很多種,甚至可以傳入 .NET 的 DateTime 做為參數,詳細資料可以參考 MySQL Connector for .NET 的檔案中的文件檔,再以自己方便的方式傳入即可。

C# 產生亂數(Random Number)的方式

昨天因為有需求要產生很多個亂數,想說 C# 應該很容易....

後來發現應該要用 Random 這個 Class 去產生.........(原本以為 Math 裡面會有 static method 可以用....)

不過 Random Class 使用起來會有問題,產生的亂數重複性極高,簡單來說,很明顯看的出有問題,後來經過修正後已經解決,以下用一段程式碼說明修改前後的差距:
class Program
{
static void Main(string[] args)
{
Random outerRnd = new Random(); //Random class 要放在 loop 外才可以!
for (int i = 0; i < 10; i++)
{
Random innerRnd = new Random(); //放在 loop 內會有問題
Console.WriteLine("inner:" + innerRnd.Next(10, 20).ToString() + " outer:" + outerRnd.Next(10, 21).ToString() + "t");
/* 顯示結果如下:
inner:13 outer:13
inner:16 outer:12
inner:16 outer:12
inner:16 outer:13
inner:16 outer:18
inner:16 outer:20
inner:16 outer:15
inner:16 outer:19
inner:16 outer:11
inner:19 outer:17
*/

}
Console.ReadLine();
}
}

PHP中網址傳遞中文參數的處理

原本以為要 GET 後用 iconv 相關的 function 去處理應該可以解決問題,結果發現搞錯方向....

原來只要使用 urlencode 搭配 urldecode 就可以解決中文參數的問題了!

2007年11月26日 星期一

修改 Ubuntu runlevel 設定

今天想要把原本 run XWindow 的 Ubuntu 改為 run console 就好了,這樣系統資源的耗費可以省一點。

結果發現,在 Ubuntu 中竟然沒有 /etc/inittab 這個檔案........(忘記從哪一版開始沒有,不過我用 6.10 就已經沒有了!)

因此 google 的一下修改方式,找到以下這邊討論串:

由於解說蠻簡單的,也就不翻譯了,直接節錄修改方式:
1./etc/inittab.
You're right. It's gone. It left as of Edgy Eft, as Ubuntu now manages start-up services with "upstart."
The file does exist in pre-Edgy Ubuntu's, and other flavors of Linux, so you were not crazy to look there .

2. default runlevel in Ubuntu

In redhat/suse, the default run level is 5. I came from RH, and like you, expected the same in Ubuntu. After the "upstart" transition, the notion of runlevels is supposedly gone, but if forced to choose, the default run level is actually 2 in Ubuntu, not 5.

2. You don't want GNOME to start at boot, how?

So yes, in other flavors of Linux, you'd probably just change run levels, because on run level would have X, and another wouldn't. But in Ubuntu, *all* run levels start GNOME, so here's an easier way:

In /etc/rc2.d, you will see a file S13gdm. This starts the GNOME system.
Simply change that file to K13gdm.

# sudo mv S13gdm K13gdm

(It's possible the number between the "S" and the "gdm" will be slightly different than 13. That's OK.)

This leaves the package installed, but just not started at boot.

To start X later, run:

# sudo /etc/init.d/gdm start.
看了以上的修改方式,就是不讓 GDM 啟動就對了!

不過應該還有更好的修改方式,之後有看到再說吧!!

2007年11月25日 星期日

The C Programming Language 讀後整理(1) - A Tutorial Introduction

變數型態(type)介紹

在 C 中,所有的變數在使用前都必須宣告其型態(type),以下是 C 提供的基本型態
  1. char:character,為單一 byte
  2. short:short integer
  3. int:integer
  4. long:long integer
  5. float:floating point
  6. double:doule-precision floating point

而每種變數型態都有其大小的範圍:(以下用個簡單範例來說明)
#include <stdio.h>
#include <limits.h> //要取得變數型態的大小範圍值,必須 include 此 header file

main()
{
//short(short integer) 的範圍 => 16 bits
printf("%d\n", SHRT_MIN); //-32768
printf("%d\n", SHRT_MAX); // 32767

//int(interget) 的範圍
printf("%d\n", INT_MIN); //-2147483648
printf("%d\n", INT_MAX); // 2147483647
}
以上是在 Linux 的環境上的執行結果,每個 int 的長度為 32 bits;而各種變數型態在不同的 OS 平台上可能會有不同的長度,這可能需要注意一下。


使用不同型態的變數,撰寫方式也必須不同

以下用一個華氏(fahrenheit)轉攝氏(celsius)範例來說明:
#include <stdio.h>

main()
{
float fahr, celsius;
int lower, upper, step;

lower = 0;
upper = 300;
step = 20;

//以 while 的方式撰寫
fahr = lower;
while(fahr <= upper) {
//由於指定變數型態為 float,前面相除的兩個變數必須以 float 方式去撰寫
//若沒有,會被 compiler 視為 int 而遭截斷為 0
celsius = (5.0 / 9.0) * (fahr - 32.0);
//以比較漂亮的方式印出結果
printf("%3.0f\t%6.1f\n", fahr, celsius);
fahr = fahr + step;
} //end while
//以 while 的方式撰寫 =============== End

//以 for 的方式撰寫
for(fahr = 0 ; fahr <= upper ; fahr = fahr + step)
printf("%3.0f\t%6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32.0));
//以 for 的方式撰寫 =============== End
}


Symbolic Constant 的使用

在程式中,如果直接使用某個數字,而未註明其用途,可能會讓其他看程式碼的人不知道原作者的用意為何。因此最好每個值都可以賦與其意義,讓使用上不會太過於突兀,其中一個方法就是使用「Symbolic Constant」。

而使用的語法如下:
#define [name] [replacement text]
以下用個簡單範例來說明:
#include <stdio.h>

//Symbolic Constant 的定義 (名稱通常以「大寫」表示)
#define LOWER 0
#define UPPER 300
#define STEP 20

main()
{
int fahr;
for(fahr = LOWER ; fahr <= UPPER ; fahr = fahr + STEP)
printf("%3d %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32));
}
如此一來,每個值都知道其代表意義為何了!!


Character Input and Output

在程式中,字元(字串)資料的處理是相當普遍的。

在 C 中,資料的 input 與 output 的功能是由 library 所提供,因此不太需要須考慮其實作的細節。

接著,文字資料的輸入或輸出方面,不論來源是鍵盤,或是檔案,都會被視為由許多字元所組合而成的 stream;而 text stream 則是一行一行分開,且有順序的字元,每一行都包含 0 或以上的字元,且在結尾帶有一個「n」(newline)字元。

File Copying

C standard library 中提供了許多存取單一字元的 function,其中最常用的就是 putchar() 以及 getchar() 兩個 function,以下有個簡單範例將來源資料一個一個字元取出並印出來:
#include <stdio.h>

main()
{
//為了確保變數可以存入所有字元,因此宣告型態為 int
int c;

//取得一個字元
while((c = getchar()) != EOF) { //未到檔案結尾(End of File),繼續執行
putchar(c); //輸出一個字元
printf("\n%s\n", "-----------------------");
} //end while
}
PS. 其實 EOF 的值為 -1

Character & Word & Line Counting

首先說明計算的規則:
  • Character:非 EOF 字元
  • Word:非空白、n、t .... 等字元
  • Line:n 字元

以下用個簡單範例說明:
#include <stdio.h>

#define IN 1 //在 word 內
#define OUT 0 //在 word 外

main()
{
//number of characters
//number of words
//number of lines
long nc, nw, nl;
nc = nw = nl = 0; //初始化變數

//保留輸入字元之用
int c;

//檢查是否在 word 中
int state;

while((c = getchar()) != EOF) {
++nc; //character count + 1

//檢查是否為 new line
if(c == '\n')
++nl;

//檢查字元是否為空白、new line 或是 tab
if(c == ' ' || c == '\n' || c == '\t')
state = OUT; //設定在 word 之外
else if(state == OUT) { //字元不是空白,也不是 new line,也不是 tab,但狀態正在 word 之外
//表示進入新的 word 中
state = IN; //設定為進入 word 中
++nw;
}
} //end while

printf("%ld %ld %ld\n", nc, nl, nw);
}
【註】輸入 Ctrl + D 即為丟出 EOF 訊息!


Arrays

看過上面範例後,如果要計算 0~9 各出現了幾次,要怎麼做呢? 有兩種作法:
  1. 宣告 10 個變數,分別儲存 0~9 所出現的次數
  2. 宣告 1 個長度為 10 的 array,每個 element 分別儲存 0~9 出現的次數
顯而易見的,使用 array 是比較好的選擇,以下用個範例程式來說明:
#include <stdio.h>

main()
{
int c, i, nwhite, nother;
int ndigit[10]; //宣告長度為 10 的 int array

nwhite = nother = 0;
for(i = 0 ; i < 10 ; i++)
ndigit[i] = 0;

while((c = getchar()) != EOF) {
if(c >= '0' && c <= '9') //檢查是否為數字
ndigit[c - '0']++; //由於 c 是 char 型態,因此透過此方式轉為 int
else if(c == ' ' || c == '\n' || c == '\t')
nwhite++;
else
nother++;
} //end while

printf("digits = ");
for(i = 0 ; i < 10 ; i++)
printf(" %d", ndigit[i]);
printf(", white space = %d, other = %d\n", nwhite, nother);
}


Function

function 的目的在於將程式封裝,讓使用 function 的人不需要知道 function 是如何達成其所宣告可達成的功能,只要拿來使用即可。

這個概念在 OO 設計中是相當重要的一部分,當然 C 也提供了這個功能,以下用個範例程式來說明:
#include <stdio.h>

//function 要在前面先進行宣告
//此處定義的參數名稱不會與以下的程式中的變數名稱有所衝突(僅宣告而已)
//甚至可以省略為 int power(int, int)
int power(int m, int n);

main()
{
int i;

for(i = 0 ; i < 10 ; i++)
printf("%6d %6d %6d\n", i, power(2, i), power(-3, i));
}


//回傳型態:int
//function 名稱:power
//參數 1:base
//參數 2:n
int power(int base, int n) //計算次方的 function
{
int i, p;

p = 1;
for(i = 1 ; i <=n ; i++)
p = p * base;
return p;
}


Arguments - Call by Value

在 C 中,所有 function 的參數都是 Call by Value,也就是呼叫 function 時,程式會將要傳入的變數複製一份再傳入,而不會用原本的變數,因此在 function 中對變數的任何操作,並不會影響到原本的變數。

對於 function 來說,參數值可以視為 funtion 開始就初始化並已經指定好值的變數。

PS. 其實參數名稱所代表的是 memory address,也就是之後會提到的 pointer


Character Array

在一般程式語言中很常用的 string 型態,在 C 中是沒有的;而在 C 中若是要表示 string,就必須要使用 char array。

除此之外,為了方便辨識 string 的結尾,C 會在 char array 最後方加入「n」與「0」 兩個字元,如下所示:

範例字串:this is a dog!
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
t
h
i
s

i
s

a

d
o
g
!
\n
\0

表示上面那個 string,最少需要長度為 16 的 char 陣列,其中 index 為 14、15 個兩個字元,是 C 用來做 string 結尾表示之用,因此在處理 string 的時候,必須要注意這個地方。

以下用個範例來說明 char array 的使用:
#include <stdio.h>

#define MAXLINE 1000

//宣告 function
int getline(char[], int maxline);
void copy(char[], char[]);

main()
{
int len; //目前資料行的長度
int max; //目前最大的資料行長度
char line[MAXLINE]; //目前讀取的資料行
char longest[MAXLINE]; //最長資料行的儲存陣列

max = 0;
while((len = getline(line, MAXLINE)) > 0) { //取得每一行資料,並檢查長度是否大於0
if(len > max) { //資料長度比目前最長的還要更長
max = len; //修改最長資料長度變數
copy(longest, line); //最長資料陣列的資料也隨著更換
}

//印出最長的資料行
if(max > 0)
printf("%s", longest);
} //end while
}

//讀取每行的資料進入 s 陣列,並回傳陣列長度
int getline(char s[], int lim)
{
int c, i;

//資料行檢查條件:
//(1)尚未到達資料行最大行數(通常這不會到達)
//(2)未遇到 EOF 字元 (按下 Enter 或是 Ctrl+D 會送出 EOF 字元)
//(3)未遇到 new line 字元
for(i = 0 ; (i < lim - 1 && (c = getchar()) != EOF && c != '\n') ; i++)
s[i] = c;
if(c == '\n') {
s[i] = c;
++i;
}
s[i] = '\0'; //「n」與「0」為 string 結束的最後兩個字元
return i;
}

//將 from 陣列的內容複製到 to 陣列 (假設陣列夠大)
void copy(char to[], char from[])
{
int i;

i = 0;
while((to[i] = from[i]) != '\0') //檢查是否到了 string 結束字元
i++;
}
由上面的程式可知道,要印出 string 型態的變數,必須使用「%s」這個符號,而 C 也會自動將表示 string 結尾的兩個字元給忽略不印。


External Variables and Scope

這個部份要談到外部變數(external variables)以及其有效範圍。

在一般的 function 中,變數的有效範圍僅限於該 function 內,每次呼叫相同的 function ,裡面的變數值也不一定會相同;且 function 之間的變數是毫無相關的。

但若是有共同變數的需求呢? 此時就要借用 external variable 的方式來達成了!

其中,最重要的關鍵字即為「extern」的使用,以下用個範例來說明:
#include <stdio.h>

#define MAXLINE 1000

int max;
char line[MAXLINE];
char longest[MAXLINE];

//修改為使用 external variable 的版本,因此不需要參數了
int getline(void); //void 表示沒有參數(可省略)
int copy(void);

main() {
int len;
//extern int max; //使用 extern 關鍵字,就知道使用的是 external variable
//extern char longest[];

max = 0;
while((len = getline()) > 0) {
if(len > max) {
max = len;
copy();
}
} //end while

if(max > 0)
printf("%s\n", longest);
} //end main


//取得一行資料的內容
int getline(void) {
int c, i;
extern char line[]; //使用 extern 關鍵字,就知道使用的是 external variable

for(i = 0 ; i < MAXLINE - 1 && (c = getchar()) != EOF && c != '\n' ; i++)
line[i] = c;
if(c = '\n') {
line[i] = c;
i++;
}
line[i] = '\0';
return i;
} //end getline


//複製陣列
int copy(void) {
int i;
extern char line[], longest[]; //使用 extern 關鍵字,就知道使用的是 external variable

i = 0;
while((longest[i] = line[i]) != '\0')
i++;
} //end copy
外部變數的使用,有時候固然方便,不過也會因為不小心誤改了變數值而造成其他 function 執行上的錯誤。

就軟體工程的觀念來說,此種方式會讓 function 不夠模組化,因為必須依賴 external variable 才能正常運作,對於 reuse 來說並不會有什麼幫助,因此使用上還是要注意一下。

PS. 其實不使用 extern 關鍵字也是可以通過編譯的! (extern 整行拿掉)

一般來說,若有一群 external variable 需要定義被使用,通常會使用額外的檔案來進行變數的定義,然後在程式中把該檔案引用進來;而通常這一類的檔案都稱為 header,副檔名為「.h」,在檔案一開始就會被 include 進來。

就像每個程式一開始的「#include <stdio.h>」就是標準的用法,將常用的 I/O function 給引用進來。


參考資料
  1. The GNU C Library - A.5.2 Range of an Integer Type