ได้ฤกษ์ทดลอง
Terracotta แล้ว หลังจากที่ดองเรื่องไว้นานแสนนาน
สำหรับคนที่ไม่รู้จัก Terracotta
Terracotta คือ clustering Techonology อันหนึ่ง
ที่ใช้หลักการ AOP เข้าไปเปลี่ยนแปลง java byte code ของเรา
ทำให้ POJO Object ของเราประพฤติตนในลักษณะ cluster ได้
AOP library ที่ Terracotta เลือกใช้ก็คือ
AspectWerkzส่วน byte code manipulation library เขาใช้
ASMสิบปากว่าไม่เท่าตาเห็น
ทดสอบเขียน application ง่ายๆ ในเรื่องการประมูล
เริ่มด้วย class Bid แทน item ที่กำลังถูกประมูลอยู่
ภายในมี information แค่ว่า ใครประมูลล่าสุด ด้วยราคาเท่าไร
package bid;
import java.math.BigDecimal;
public class Bid {
private String name = "";
private BigDecimal currentPrice = BigDecimal.ZERO;
public BigDecimal getCurrentPrice() {
return currentPrice;
}
public void setCurrentPrice(BigDecimal currentPrice) {
this.currentPrice = currentPrice;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
จากนั้นก็ทดลองเขียน class Bidding ที่เป็น console application ที่ใช้ในการประมูล
โดยวิธีใช้งานมันจะหน้าตาประมาณนี้
Command (b-bid, n-new, s-show, q-quit) : n
id ? 1
Command (b-bid, n-new, s-show, q-quit) : s
id : 1, -> name = , currentPrice = 0
Command (b-bid, n-new, s-show, q-quit) : b
id ? 1
name :pok
price :200
Command (b-bid, n-new, s-show, q-quit) : s
id : 1, -> name = pok, currentPrice = 200
Command (b-bid, n-new, s-show, q-quit) : q
ตั้งโครง class Bidding ก่อน โดยในการประมูลครั้งหนึ่งๆ เรามี item ที่จะประมูลได้หลายอันพร้อมๆกัน
ดังนั้นเราจึงใช้ HashMap ในการเก็บ instance Bid ที่กำลังประมูลอยู่
public class Bidding {
ConsoleReader console;
HashMap<String,Bid> bids = new HashMap<String,Bid>();
public static void main(String[] args) throws IOException {
new Bidding().run();
}
}
method run ก็คือ method ที่ใช้ loop เพื่อรับ input จากหน้าจอ console ของ user
public void run() throws IOException {
console = new ConsoleReader();
while (true) {
String tmp = console.readLine("Command (b-bid, n-new, s-show, q-quit) : ");
switch (tmp.charAt(0)) {
case ('q'):
return;
case ('n'):
newBid();
break;
case ('s'):
show();
break;
case ('b'):
bid();
break;
}
}
}
method newBid คือ method ที่ใช้สร้าง bid item อันใหม่
public void newBid() throws IOException {
String id = console.readLine("id ? ");
Bid bid = new Bid();
bids.put(id, bid);
}
method show ทำหน้าที่ print bid item ทั้งหมด
public void show() throws IOException {
for (String key : bids.keySet()) {
Bid bid = bids.get(key);
console.printString("id : " + key + ", -> ");
console.printString("name = " + bid.getName() + ", currentPrice = " + bid.getCurrentPrice());
console.printNewline();
}
}
method bid ก็คือการเสนอราคาประมูล
public void bid() throws IOException {
String id = console.readLine("id ? ");
Bid bid = bids.get(id);
String bidder = console.readLine("name :");
String price = console.readLine("price :");
BigDecimal p = new BigDecimal(price);
if (p.compareTo(bid.getCurrentPrice()) > 0) {
bid.setCurrentPrice(p);
bid.setName(bidder);
}
}
จะเห็นว่าเราเขียนโดยไม่มีแนวคิดเรื่อง cluster หรือ share thread อยู่ในนี้เลย
ทีนี้ก็มาถึงคำถามว่า ถ้าเราต้องการจะให้มันเป็น cluster ขึ้นมาหล่ะ
เราจะทำอย่างไร
เริ่มด้วยลักษณะ cluster ที่เราต้องการก่อน
เราจะ start program bidding หลายๆตัว บน virtual machine คนละตัวกัน
แต่เราต้องการให้ bidding มองเห็น instance variable bids (HashMap) ของทุกตัวเป็นตัวเดียวกัน
จะได้แย่งกันประมูลได้
เริ่มแรกสุด terracotta มี architecture เป็นแบบ hub and spoke
ไม่ได้เป็น peer-to-peer ดังนั้นเราต้อง start terracotta server ขึ้นมาก่อน
โดยใช้คำสั่ง
start-tc-server.sh
จากนั้นเราก็ต้องกำหนด configuration เพื่อให้ terracotta รู้ว่า
ควรจะตัดต่อ code เราตรงไหนบ้าง
โดยสิ่งแรกที่ต้องกำหนดก็คือ root object
root object คือ object ที่เราอยากให้ ทุกๆ virtual machine ที่ join เข้าวง cluster มองเห็นเป็นตัวเดียวกัน
ในกรณีของเรา root object ก็คือ
HashMap<String,Bid> bids = new HashMap<String,Bid>();
ซึ่งใน config ของ terracotta เราจะกำหนดโดยใช้ xml ดังนี้
<roots>
<root>
<field-name>bid.Bidding.bids</field-name>
</root>
</roots>
เมื่อกำหนด root object แล้ว
เราก็ต้องบอกให้ terracotta จัดการ modify access method ทุกๆอันของเรา
ที่มีการเรียกใช้ root object
โดยเราสามารถเลือก lock ว่าจะเป็น read lock, write lock, และ concurrent
เริ่มด้วย method show ก่อน
อันนี้เลือกได้ว่าจะเป็น read หรือ concurrent หรือไม่กำหนดเลยก็ได้
ถ้าเป็น read ถ้ามันเจอใครกำลัง write อยู่ มันจะ wait รอ
ส่วน concurrent เป็นแบบที่ไม่สนว่าใครกำลังทำอะไร จะเขียนหรืออ่านก็ได้
ลองกำหนดเป็นแบบ read จะได้ไม่เจอ case แปลกๆเช่น ชื่อเป็นชื่อของคนคนหนึ่งและราคาเป็นของอีกคนๆหนึ่ง
<named-lock>
<lock-name>show</lock-name>
<method-expression>* bid.Bidding.show(..)</method-expression>
<lock-level>read</lock-level>
</named-lock>
สังเกตุว่ามันมี type เป็น named-lock
ซึ่งมีความหมายว่า method ที่เราร้อย AOP นี้ไม่ได้เขียนแบบคำนึงถึง multi-thread มาก่อน (ไม่ได้ใช้ keyword พวก synchronized)
โดยเราต้องกำหนด name ของ lock ให้มันด้วย
จากนั้นก็ว่าด้วย method newBid โดย
เรากำหนดให้เป็น write lock
<named-lock>
<lock-name>newBid</lock-name>
<method-expression>* bid.Bidding.newBid(..)</method-expression>
<lock-level>write</lock-level>
</named-lock>
method สุดท้ายก็คือ bid
method นี้พิเศษหน่อยตรงมันจะมี process การอ่าน console ก่อน จากนั้นจึงค่อย เขียนลงไป
เราเลยลองใช้ synchronized ครอบลงไป เพื่อไม่ให้เจอ user ใส่ข้อมูลค้างไว้
แล้วลุกไปกินกาแฟ ทำให้ระบบเราหยุดเพราะเจอ write lock เปิดทิ้งไว้
แก้ code ดังนี้
synchronized(bids) {
if (p.compareTo(bid.getCurrentPrice()) > 0) {
bid.setCurrentPrice(p);
bid.setName(bidder);
}
}
จากนั้นก็กำหนด configuration ของ lock
แต่คราวนี้จะสังเกตว่าเราเปลี่ยนไปใช้ autolock type แทน
<autolock>
<method-expression>* bid.Bidding.bid(..)</method-expression>
<lock-level>write</lock-level>
</autolock>
เสร็จ แค่นี้เราก็ทดลอง run มันจาก หลายๆ virtual machine เข้าไปประมูล
บน bids ชุดเดียวกันได้แล้ว
เวลา run ก็ใช้คำสั่งประมาณนี้
โดย tc-config.xml ก็คือ configuration file ของเรา
~/dev/terracotta-2.2/dso/bin/dso-java.sh -cp bin:jline-0.9.91.jar -Dtc.config=tc-config.xml bid.Bidding
ง่ายจนน่าตกใจ