Tuesday, May 27, 2008

Pyro

ช่วงนี้มีความจำเป็นต้องใช้ remote call บน python, ค้น google ดูก็พบเจ้า Pyro
หลังจากอ่านหลักการแล้วก็อ๋อ เพราะทำงานแนวเดิียวกับ java RMI

หลักการทำงาน
  • Name Server
    เพื่อให้ client สามารถ lookup service ได้ง่ายๆ, Pyro ก็เลย provide Name Server มาให้ด้วย
    เจ้า name server ที่ pyro ให้มา มันแบ่งออกเป็น 2 ประเภทคือ
    • Regular, non-persistent
      ตัวนี้พอ name server process ตาย, ค่าต่างๆใน registry ก็หายไปด้วย
    • Persistent
      ค่าต่างๆใน registry จะเก็บไว้บน disk

    เพื่อเพิ่ม availability, เจ้าตัว name server นี้ ยังสามารถ start ในแบบ Paired mode ได้อีกด้วย โดยทั้งคู่จะทำ replicate registry ซึ่งกันและกันไว้

  • Client
    การที่ client จะติดต่อ server ได้นั้น ขั้นแรกก็ต้องหา Name Server ให้เจอเสียก่อน
    วิธีการหา Name Server ก็คือ
    locator = Pyro.naming.NameServerLocator()
    ns = locator.getNS()

    ขั้นตอนการทำงานภายในก็คือ มันจะทำการ broadcast message ออกไป
    เจ้า Name server (ซึ่ง implement broadcast listener) ก็จะตอบกลับมา

    Note: กรณีของผม ผมลองใช้ boardcast บน production server แล้ว, message มันหายต๋อมไป (ด้วยความขี้เกียจไปไล่หาสาเหตุ)
    ผมก็เลย config ให้ NameServerLocator มันต่อตรงไปที่ Name Server โดยตรงเลย แทนที่จะ broadcast
    การ config ให้ต่อตรงทำได้โดย
    Pyro.config.PYRO_NS_HOSTNAME='YOUR_HOSTNAME'

    หลังจากหา Name Server เจอแล้ว ขั้นถัดไปก็คือ ทำการถามหา URI ของ server ที่ต้องการ
    uri = ns.resolve('my_service')

    เมื่อได้ uri แล้ว สุดท้ายก็คือทำการสร้าง proxy object ซึ่งเจ้า Pyro แบ่งประเภท Proxy ออกเป็น 2 แบบคือ
    • Proxy ธรรมดา
    • Proxy ที่สามารถ access attribute ของ remote object ได้ด้วย

    วิธีการสร้าง proxy ก็คือ
    obj = uri.getProxy()
    # or
    obj = uri.getAttrProxy()

    ที่เหลือ ก็เป็นการเรียกใช้ method ต่างๆบน remote object ที่เราได้มา

  • Server
    การทำงานฝั่ง server เริ่มต้นด้วยการสร้าง daemon object
    (มี parameter เป็น name server object ซึ่ง lookup มาด้วยวิธีเดียวกันกับ client)
    daemon = Pyro.core.Daemon()
    daemon.useNameServer(ns)

    ข้อควรระวังก็คือ ต้องระวังเรื่อง reference ถึง object daemon ของเรา
    มิเช่นนั้น garbage collection อาจกวาด daemon เราทิ้งไปได้

    หลังจากได้ daemon มา ก็ทำการสร้าง Object instance ที่ต้องการให้ client ต่อใช้
    เนื่องจาก pyro ต้องหลอก object ของเราว่ามันทำงานเหมือนกับอยู่ในสภาพแวดล้อมแบบ stand-alone
    ดังนั้น Remote Object ของเราก็เลยต้องถูกห่อไว้ใน Pyro.core.ObjBase
    ซึ่งเราสามารถทำได้ 3 วิธีคือ
    • ให้ Remote class ของเรา extend Pyro.core.ObjBase ตรงๆเลย
    • delegate pattern
      บางครั้งเราอยากเขียน Remote Class โดยไม่อยากให้มี dependency ถึง Pyro เลย
      obj = Pyro.core.ObjBase()
      myservice = MyService()
      obj.delegateTo(myservice)

    • สร้าง class ใหม่ที่ extend ทั้ง Pyro.core.ObjBase และ Service class ของเรา
      class ServiceImpl(Pyro.core.ObjBase, MyService):
      def __init__(self):
      Pyro.core.ObjBase.__init__(self)
      MyService.__init__(self)

      ขั้นถัดไปก็คือทำการ binding
      โดยสั่ง connect daemon กับ​ Object instance
      ซึ่งเจ้า daemon จะจัดการไปคุยกับ Name Server ให้เราเอง
      daemon.connect(obj, 'my_service')


      สุดท้ายก็คือการสั่งให้ daemon วน loop รับ request จาก client
      daemon.requestLoop()




สิ่งที่ต้องระวังในการ implement remote object ของเราก็คือ
มันต้องเป็น thread safe เนื่องจาก daemon จะใช้วิธีแตก thread เมื่อมี incoming connection วิ่งเข้ามา
feature ที่น่าสนใจในประเด็นนี้ ก็คือ TLS (Thread Local Storage)
ซึ่งช่วยให้เราเก็บข้อมูลโดยการ bind local data เข้ากับ thread แทน

feature ที่น่าสนใจอีกอย่างก็คือ Mobile code
case นี้เป็นกรณีที่ client call ไปยัง remote object โดยส่ง argument เป็น object ที่ไม่มี code อยู่บน server มาด้วย
ซึ่งโดยปกติ มันจะเกิด error ประเภท NoModuleError
แต่ถ้าเรากำหนด configuration ให้ Pyro ใช้ feature Mobile code
เวลาที่ server เจอ code ที่ไม่รู้จัก มันก็จะ request code มายัง client
กรณี mobile code นี้ support 2-way
นั่นคือ support กรณี server return object ที่ไม่มี code บน client ด้วย

Related link from Roti

No comments: