Posts Tagged ‘rmi’

สปริงรีโมทติ้งด้วยอาร์เอ็มไอ (Spring Remoting with RMI)

August 12th, 2009

ความต้องการพื้นฐานของระบบพรีเซนเตชั่นเทียร์กับแอพพลิเคชั่นเทียร์ต้องแยกกันโดยเด็ดขาดหรืออยู่คนละเครื่องเท่านั้น ทำไงดี?
คำตอบที่ง่ายที่สุดตอนนี้คือใช้สปริงรีโมทติ้งนั่นเอง(ถ้าเอาแบบสุดต้องโอเอสจีไอ) สำหรับสปริงรีโมทติ้งนั้นเราสามารถเลือกใช้งานได้ไหลายแบบด้วยกันดังนี้ Remote Method Invocation (RMI).Spring’s HTTP invoker.Hessian.Burlap. JAX-RPC. JAX-WS. JMS. แล้วแบบไหนดีล่ะที่ดูแล้วซิมเปิ้ลแต่ทรงพลังที่สุดแน่นอนคำตอบคืออาร์เอ็มไอของเราเหมือนเดิม ดังนั้นเราจะลองใช้บริการอาร์เอ็มไอกัน

springrmi1

จากรูปเราจะแบ่งแอพพลิเคชั่นออกเป็นสองส่วนคือ “helloWorldService” ฝั่งเซิร์ฟเวอร์โดยที่ผั่งเซิร์ฟเวอร์ในจังหวะที่เราต้องการประกาศบีนนั้นต้องทำการเปลี่ยนแปลงการเขียนคอนฟิกกูเรชั่นเล็กน้อยโดยต้องบอกว่าเราจะให้เซอร์วิสนี้ถูกเรียกใช้งานได้ผ่าน อาร์เอ็มไอ แต่ก่อนอื่นเรามาดูเซอร์วิสอินเทอร์เฟสและอิมพลีเมนท์คลาสก่อน

public interface HelloWorld {
    public String getMessage();
}

โดยที่อินเทอร์เฟสคลาสนี้จะต้องถูกใส่ไว้ทั้งที่ฝั่งไคล์แอนท์และเซิร์ฟเวอร์ ต่อไปเรามาดูอิมพลีเมนท์เตชั่นคลาสกัน

public class SimpleHelloWorld implements HelloWorld {
    public String getMessage() {
        return "Hello World";
    }
}

เมื่อเรามีเซอร์วิสแล้วเราต้องทำการสร้างคอนฟิกกูเรชั่นโดยเราจะตั้งชื่อว่า helloWorld.xml ดดยมีรายละเอียดดังนี้

    <bean id="helloWorldService" class="com.spring66.remoting.rmi.SimpleHelloWorld"/>

    <bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
        <property name="serviceName" value="HelloWorld" />
        <property name="service" ref="helloWorldService" />
        <property name="serviceInterface" value="com.spring66.remoting.rmi.HelloWorld" />
        <property name="registryPort" value="9000" />
        <property name="servicePort" value="9001" />
    </bean>

คอนฟิกกูเรชั่นไฟล์นี้จะบอกสปริงว่าเซอร์วิสที่ชื่อ “helloWorldService” นั้นจะถูกเอ็กซ์พอร์ทไปเป็นรีโมททิ่งเซอร์วิสผ่านอาร์เอ็มไอเซอร์วิสเอ็กพอร์ทเทอร์ โดยชื่อที่เราจะใช้อ้างถึงคือ “HelloWorld” ส่วนเซอร์วิสจริงนั้นจะเป็นบีนชื่อ “helloWorldService” และทั้งสองฝั่งจะใช้อินเทอร์เฟสร่วมกัน “com.spring66.remoting.rmi.HelloWorld” และพอร์ทที่เราจะใช้สื่อสารคือพอร์ทหมายเลย 9000
หลังจากได้ผั่งเซิร์ฟเวอร์แล้วเราจะมาเขียนฝั่งไคล์แอนท์กัน
โดยที่ฝั่งไคล์แอนท์นั้นเราก็คิดเสมอนว่าเรามีบีนอยู่แล้วเพียงแต่สิ่งที่เรามีนั้นมีแต่อินเทอร์เฟสคลาสเท่านั้น เพราะตัวจริงเราจะใช้พลังดูดดาวดึงมาจากที่อื่นผ่านอาร์เอ็มไอแทนดังนั้นสิ่งที่เปลี่ยนไปคือคอนฟิกกูเรชั่นไฟล์เท่านั้นเองโดยเรามาดูกันเลย

    <bean id="helloWorldService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
        <property name="serviceUrl" value="rmi://localhost:9000/HelloWorld" />
        <property name="serviceInterface" value="com.spring66.remoting.rmi.HelloWorld"/>
    </bean>

    <bean id="helloWorldClient" class="com.spring66.remoting.rmi.HelloWorldClient">
        <property name="helloWorldService" ref="helloWorldService" />
    </bean>

ส่วนโค้ดก็เหมือนเดิมไม่มีอะไรเปลี่ยนแปลงทุกอย่างเดมือนเคย จะเห็นได้ว่าวิธีนี้เป็นทางออกที่ดีมากสำหรับการแก้ปัญหาเรื่องของการแยก เทียร์ ของการแสดงผลออกจากการคำนวณแบบจริงๆตามข้อกำหนดของบางบริษัท

การทำ อาร์เอ็มไอ ดิสทรืบิวเท็ต แคช (RMI Distributed Cache) ด้วย อีเอชแคช

August 5th, 2009

จริงๆไม่ได้เกี่ยวกับสปริงตรงๆครับแต่เกี่ยวทางอ้อมๆ เพราะในกรณีที่เรานำแอพพลิเคชั่นของเราไปวางไว้บนคลัสเตอร์แล้วเราต้องการให้ข้อมูลที่อยู่ในโลคอลแคชของเราเหมือนกันทุกเราจะทำยังไง? แน่นอนปัญหาพวกนี้ไม่ได้เพิ่งเิกิดขึ้นกับเราคนแรก มันมักจะมีคนอื่นแก้ไว้ให้แล้วเสมอ ดังนั้นอย่าเทพ ไปแก้ปัญหาเหล่านี้ด้วยการเขียนเองทั้งหมด
หลังจากไล่อ่านเอกสารของอีเฮชแคชไปได้สามรอบผลคือรู้ว่ามันทำได้ห้าวิธีคือ อาร์เอ็มไอ(RMI),เจกรุ๊ป(JGroups),เจเอ็มเอส(JMS),เทอรราคอตต้า(Terracotta)และแคชเซิร์ฟเวอร์(Cache Server)
แต่เท่าที่ไล่ดูจากเอกสารแล้วที่พอจะเอาได้ไล่ออกจากเอกสารแย่ๆของอีเฮชแคชนั้น สรุปได้ว่าอาร์เอ็มไอ ดูเข้าท่าที่สุดเพราะเนื่อหาดูท่าจะอ่านรู้เรื่องที่สุดแต่หลังจากที่อ่านไปอีกสามรอบก็ ห่านนนนนนน อ่านไม่รู้เรื่องแต่โชคดีที่มีลิงค์เล็กที่ท้ายระบุว่าถ้าอยากชมการทำงานเรื่องของการเรพพลิเขตข้อมูลให้ไปดูที่ยูนิตเทสท์ ==”แต่ต้องยอมรับว่ายูนิตเทสท์เค้าเทพมากๆ ถึงแม้ว่าอ่านรอบแรกจะไม่รู้เรื่องเลยก็ตาม แต่หลังจากรื้อๆค้นแก้ไปแก้มาก็เห็นภาพชัดเจนโดยสามารถสรุปได้ดังนี้
สิ่งที่ต้องมีเพิ่มในคอนฟิกกูเรชั่นไฟล์
1.เพียร์โพรไวด์เดอร์ (PeerProvider)
2.แคชเมเนเจอร์เพียร์ลิสท์ซึนเนอร์ (CacheManagerPeerListener)
โดยเราสามารถเพิ่มลงไปได้ดังนี้

    <cacheManagerPeerProviderFactory
         class="net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory"
         properties="hostName=,
         peerDiscovery=automatic,
         multicastGroupAddress=230.0.0.1,
         multicastGroupPort=4446,
         timeToLive=64"/>

ส่วนของเพียร์โพรไวด์เดอร์มีหน้าที่ระบุว่าแต่ละแคชนั้นเป็นเพียร์ต่อกันไม่มีตัวใดตัวหนึ่งเป็นมาสเตอร์ และแต่ละเพียร์จะสื่อสารกันผ่านมัลติคาสท์กรุ๊ปแอดเดรส(MulticastGroupAddress)ด้วยการค้นหาแบบอัตโนมัติซึ่งเป็นวิธีที่ง่ายและสะดวกที่สุด ดังนั้นในกรณีของเราเราสามารถใส่ค่านี้ลงไปในคอนฟิกกูเรชั่นไฟล์ได้ทั้งสองเครื่องเหมือนๆกัน ส่วนต่อไปคือแคชเมเนเจอร์เพียร์ลิสท์ซึนเนอร์

    <cacheManagerPeerListenerFactory
         class="net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory"
         properties="hostName=,
         port=40001,
         remoteObjectPort=47000,
         socketTimeoutMillis="/>

แคชเมเนเจอร์เพียร์ลิสท์ซึนเนอร์ทำหน้าที่คอยฟังสัญญาณจากเพียร์ต่างๆและส่งต่อไปให้แคชเมเนเจอร์ตัวปัจจุบัน
ส่วนต่อไปคือเราต้องกำหนดการทำเรพพลิเคเตอร์ที่แคชเมเนเจอร์ของเราว่าต้องการให้ทำงานในโหมดไหน ในที่นี้เราจทำเป็นแบบซิงค์โครนัส และยอมรับการเปลี่ยนแปลงทุกชนืดที่เกิดขึ้นในแคชไม่ว่าจะเป็นการ แก้ไข ลบ หรือเพิ่ม ข้อมูล

    <cache name="getUserById"
           maxElementsInMemory="10"
           eternal="true"
           overflowToDisk="true">
        <cacheEventListenerFactory
           class="net.sf.ehcache.distribution.RMICacheReplicatorFactory"
           properties="replicateAsynchronously=false,
           replicatePuts=true,
           replicateUpdates=true,
           replicateUpdatesViaCopy=true,
           replicateRemovals=true "/>
        <bootstrapCacheLoaderFactory
           class="net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory"/>
    </cache>

ดังนั้นหลังจากที่เราทำการกำหนดค่าต่างๆไว้เรียบร้อยแล้วเราจะสามารถทดสอบการทำเรพพลิเขตข้อมูลข้ามแคชได้ด้วยการเขียนยูนิตเทสท์ดังนี้

       @Before
    public void setUp() throws Exception {

        MulticastKeepaliveHeartbeatSender.setHeartBeatInterval(1000);

        CountingCacheEventListener.resetCounters();
        manager1 = new CacheManager(AbstractCacheTest.TEST_CONFIG_DIR + "distribution/ehcache-distributed1.xml");
        manager2 = new CacheManager(AbstractCacheTest.TEST_CONFIG_DIR + "distribution/ehcache-distributed2.xml");

        //allow cluster to be established
        Thread.sleep(1020);

        cache1 = manager1.getCache(cacheName);
        cache1.removeAll();

        cache2 = manager2.getCache(cacheName);
        cache2.removeAll();

        //enable distributed removeAlls to finish
        waitForPropagate();

    }
    @Test
    public void testBigPutsProgagatesSynchronous() throws CacheException, InterruptedException {

        //Give everything a chance to startup
        StopWatch stopWatch = new StopWatch();
        Integer index;
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 1000; j++) {
                index = new Integer(((1000 * i) + j));
                manager2.getCache("sampleCache3").put(new Element(index,
                        "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));
            }

        }
        long elapsed = stopWatch.getElapsedTime();
        long putTime = ((elapsed / 1000));
        LOG.log(Level.INFO, "Put and Propagate Synchronously Elapsed time: " + putTime + " seconds");

        assertEquals(2000, manager1.getCache("getUserById").getSize());
        waitForPropagate();
        assertEquals(2000, manager2.getCache("getUserById").getSize());
    }