Powered by Drupal, an open source content management system

โฆษณาโดย Google

erlang

เรียกใช้งาน gen_server ที่อยู่กันคนละ node

ปกติแล้วเวลาเราจะเรียกใช้ handle_call กับ handle_cast ของ gen_server เราจะเรียกใช้โดย gen_server:call สำหรับ synchronous call และเรียกใช้ gen_server:cast สำหรับ asynchronous call ตัวอย่างเช่น

Response = gen_server:call(?SERVER,{request}).

gen_server:cast(?SERVER,{request}).

แต่ทั้งสองฟังก์ชันนี้จะเรียกได้เฉพาะ gen_server ที่รันอยู่บนเออแลงโนดเดียวกันเท่านั้น ถ้าเราต้องการให้เรียกใช้งาน gen_server ที่รันอยู่โนดอื่นๆสามารถใช้ได้โดยเรียก gen_server:multi_call กับ gen_server:abcast โดยค่าแรกที่ส่งให้ฟังก์ชันเป็นลิสต์ของโนดที่ตัว gen_server เปิดใช้งานอยู่

{ResponseList,_} = gen_server:multi_call(['pgsql_node@192.168.1.123'],?SERVER,{request}).

gen_server:abcast(['pgsql_node@192.168.1.123'],?SERVER,{request}).

แก้ปัญหาโปรเซสของ comet ใน nitrogen ไม่ตาย

เจอบั๊กในงานที่ทำอยู่ตอนนี้ ที่ใช้ Nitrogen Web framework for Erlang เป็น framework สำหรับทำเว็บ ซึ่งตัวนี้มันทำ comet ได้ ปัญหาคร่าวๆคือเมื่อเราสร้าง comet เราต้องกำหนดฟังก์ชันให้กับมันเมื่อเราเข้าใช้งานหน้าเว็บหน้านั้น ตัว nitrogen จะทำการแบ่งโปรเซสออกไปสำหรับฟังก์ชันนั้นเพื่อให้เป็นตัวรับ message ระหว่างหน้าเพจกับตัว nitrogen ทีนี้ตัว nitrogen มันดันสร้าง process ใหม่สำหรับ comet ตลอดเมื่อมีการ refresh หรือ หรือเข้าหน้านั้นอีก tab หนึ่ง เมื่อเราปิดหน้านั้นหรือปิดเว็บบราวเซอร์ไปเลย ตัวโปรเซสนี้มันดันไม่ยอมตาย พอมีการส่งข้อความหา comet ชื่อที่กำหนดไว้ตอนแรก มันก็จะส่งข้อความหาทุกๆโปรเซสที่เคยสร้างไว้ แล้วโค้ดใน ฟังก์ชันของ comet ที่ทำเอาไว้คือต้องการให้มัน query ข้อมูลจาก database ผลก็คือยิ่งใช้งานไปเรื่อยๆโปรเซสก็เพิ่มขึ้นไม่หยุด การ query ก็เยอะขึ้นๆทำให้ตัว nitrogen เองเดี้ยงไปเลย

วิธีแก้ที่หาได้ก็คือ ตอนสร้าง comet มันจะรีเทิร์นค่า PID ของ process ออกมาให้เราใช้ฟังก์ชัน timer:kill_after(Time, PID) ช่วยกำหนดเวลาว่าจะ kill process นี้หลังจากผ่านเวลาไปแล้วเท่าไหร่

ตัวอย่างโค้ดที่ใช้งาน

{ok, PID } = wf:comet_global(
    fun() ->
        alarm_loop(wf:q("id"))
    end,
    device_status),

timer:kill_after(330000, PID),

แปลงโค้ด erlang ใน string ให้เป็นตัวข้อมูลใน erlang

ถ้าเราต้องการแปลง string ที่อยู่ในรูปโค้ดของ erlang ให้กลับมาเป็นตัวข้อมูลของ erlang เอง เช่น

มี "[1,2,3,4,5]." ต้องการแปลงกลับมาให้เป็นข้อมูลลิสต์ [1,2,3,4,5]

สามารถทำได้ด้วยวิธีการนี้

  {ok, Toks, _Line} = erl_scan:string("[1,2,3,4,5].",1).
  {ok,List} = erl_parse:parse_term(Toks).

คือให้ใช้ erl_scan:string โดยส่งค่าที่จะแปลงใน argument แรก และให้ใส่ 1 ที่ argument ที่ 2 เสร็จแล้วรับค่า Toks ซึ่งจะเป็นค่าที่ 2 ใน tuple ของข้อมูลที่ส่งกลับมาจากฟังก์ชัน erl_scan:string แล้วไปเรียกอีกฟังก์ชันคือ erl_parse:parse_term(Toks). โดยส่งค่า Toks ไป เมื่อทำงานเสร็จจะได้ tuple กลับออกมาโดยที่ค่าแรกเป็น ok เมื่อทำงานสำเร็จและค่าที่สอง เป็นผลลัพธ์ที่ได้

ใช้งาน MongoDB ผ่าน Erlang โดยใช้ emongo driver

MongoDB เป็นฐานข้อมูลในแบบที่สมัยนี้เขาเรียกกันว่า NoSQL อยากรู้ละเอียดๆเพิ่มเติมลองเข้าไปอ่านได้ที่เว็บ กลุ่มผู้ใช้ MongoDB แห่งสยามประเทศ :) ที่นี้การเอาตัว MongoDB ไปใช้งานร่วมกับการเขียนโปรแกรมภาษาใดๆนั้น ต้องมีตัว driver เป็น API ช่วยในการติดต่อกับตัว service ของ MongoDB ในที่ผมจะเขียน blog ไว้นี้ผมใช้ตัว emongo

Introduction to Erlang at Blognone Tech Day 2.0

ค้น google ไปมาไปเจอ Video บรรยาย Introduction ของ erlang ตั้งแต่ 4 ปีที่แล้วโดยพี่ป๊อก @pphetra ในงาน Blognone Tech Day 2.0

เอามาลงไว้หน่อยเผื่อใครสนใจอยากรู้จัก Erlang

ลอง Distributed Programming ด้วย Erlang

อ่าน Concurrent Programming in Erlang เรื่อง Distributed Programming แล้วหลักๆฟังก์ชันหลักๆที่ใช้ก็คือ

- spawn(Node, Mod, Func, Args)
ไว้ไปสร้าง process ที่ Node อื่น

- spawn_link(Node, Mod, Func, Args)
สร้าง process ที่โหนดอื่นและสร้าง links เชื่อมด้วย

- monitor_node(Node, Flag)
ไว้เช็คว่าเชื่อมต่อ Node ได้หรือไม่

- node()
ชื่อของโหนด

- nodes()
หาลิสต์ของโหนดอื่นที่เชื่อมต่ออยู่

- node(Item)
หาชื่อของโหนด ที่เป็นเจ้าของ Item โดย Item เป็นได้ทั้ง PID , reference หรือ Port
reference or a port.

- disconnect_node(Nodename)
ตัดการเชื่อมต่อโหนด

เวลาจะเริ่มการทำงานของ erlang ในแต่ละโหนด ตอนเรียกคำสั่ง erl เราสามารถกำหนดชื่อโหนดได้โดยใช้ออบชั่น -name เช่น
erl -name iporsut@12.34.56.78

นอกจากนั้น ถ้าต้องการให้ node แต่ละโหนดติดต่อกันได้ เราต้องแก้ค่าในไฟล์ .erlang.cookie ที่อยู่ใน home directory ของแต่ละโหนด ให้มีค่าเดียวกันด้วย

สรุปฟังก์ชัน ของ Erlang จำเป็นๆที่ใช้ในงานวิจัย

ตอนนี้กำลังทดลองเขียน erlang ให้ค้นหาตำแหน่งของ sub string โดยเทียบตอนที่ใช้ process เดียวกับตอน spawn ไปหลายๆโปรเซส โดยแบ่ง string ออกไปให้แต่ละโปรเซสค้นหา มีฟังก์ชันในโมดูลที่สำคัญๆ ที่เอามาใช้ ช่วยให้ทำงานได้ง่ายขึ้นเยอะเลย เลยเอามาสรุปไว้หน่อย

file:read_file(filename)

ฟังก์ชันนี้ใช้อ่านไฟล์ ผลลัพธ์ที่ได้อยู่ในรูปแบบ {ok,binary_data} ถ้าอ่านได้ถูกต้องจะได้ข้อมูลในรูปแบบ binary อยู่ในส่วนที่ 2 ของ tuple

binary:part(Binary,Pos,Length)

ฟังก์ชัน part ในโมดูล binary ใช้ดึงข้อมูลบางส่วนจากข้อมูล binary ออกมาโดยกำหนดตำแหน่งเริ่มต้น Pos และจำนวนที่จะดึงออกมา Length ใช้คลาย substring ในภาษาอื่น

binary:matches(Binary,Pattern)

ฟังก์ชัน matches ใช้หาตำแหน่งของ Pattern ว่ามีอยู่ตรงในบ้างในข้อมูล Binary ตรงส่วน Pattern เป็นได้ทั้งข้อมูล binary หรือว่าเป็น list ของ binary หลายๆตัวก็ได้ ผลลัพธ์ที่ได้จะได้มาเป็น list ของ {Pos,Length} คือ Pos เป็นตำแหน่ง และ Length คือความยาวของ Pattern ที่ใช้ค้นหา

lists:umerge(List1,List2)

ฟังก์ชัน umerge คือฟังก์ชันที่จะเอาสมาชิกใน List1 กับ List2 มารวมกัน ถ้ามีสมาชิกใน List1 กับ List2 ซ้ำกัน จะเอามาแค่ตัวเดียวไม่เอามาซ้ำ

lists:map(Fun,List)

map นี้เหมือนกับในภาษาแนว functional ตัวอื่นเลย คือรับค่า Function กับ List ที่จะ apply Function นี้เข้าไป ผลลัพธ์ที่ได้ก็เป็น list ของการทำงานของฟังก์ชั้นที่ทำกับสมาชิกแต่ละตัวใน list

Erlang binary pattern matching

ในโปรแกรมที่ผมกำลังทำอยู่ตอนนี้ จำเป็นต้องอ่านเท็กไฟล์ขนาดใหญ่เข้ามา แล้วเอามาแบ่งออกเป็นส่วนย่อยๆก็คือเอามา split นะแหละครับ แล้วทีนี้เวลาอ่านข้อมูลเอามาจากไฟล์ใน erlang จะใช้ฟังก์ชัน read_file จาก module file โดยผลลัพธ์ที่ได้จะอยู่ในรูปแบบประมาณนี้

{ok, <<"attaccggttaaccttgg">>}

คือได้ tuple ตัวแรกเป็น ok คืออ่านได้ปกติ ตัวหลังคือข้อมูลที่เป็นแบบ binary ใน erlang ข้อมูลแบบ binary จะอยู่ในสัญลักษณ์ <<>>

ตอนแรกทำโปรแกรมให้อ่านค่าจากไฟล์แล้วให้โปรแกรมส่งแค่ผลลัพธ์ข้างใน binary ออกมาเป็น string แล้วเอา string ไปใช้งานต่อ ผลที่ได้คือตอนทำการแบ่งสตริงออกเป็นลิสต์ของสตริงย่อยๆ ทำงานช้ามาก ขนาดที่ทดลองคือ ล้านกว่าตัวอักษร

ก็ลองค้นหาดูว่า มีวิธียังไงบ้างให้สามารถอ่านข้อมูลขนาดใหญ่เข้ามาจัดการได้อย่างรวดเร็ว ก็ไปเจอว่าส่วนใหญ่แล้วเขาจะไม่แปลงข้อมูลจาก binary ไปเป็น string แต่จะจัดการกับ binary เลย เช่นตรงนี้ก็คือ แบ่งตัว binary ที่ได้จากการอ่านไฟล์เลย โดยอาศัยการใช้ pattern matching กับข้อมูลแบบ binary เพื่อทำการ bind ค่าจาก binary ออกมาเป็น binary ย่อยๆตามจำนวนที่ต้องการ

ข้อมูลแบบ binary จะอยู่ใน pattern แบบนี้

<<"abc">>

ตัวอย่างคือมีข้อมูลอยู่ 3 bytes (24 bits แต่ละตัวอักษรใช้ 1 byte ใน 1 byte มี 8 bits)

ถ้าเราต้องการ binding ค่าของ a ออกมาจะทำได้แบบนี้

<<A,Rest/binary>> = <<"abc">>.

คือตรงส่วน pattern เราก็ทำให้อยู่ในฟอร์มของ binary มี <<>> ครอบเหมือนกัน ผลที่ได้คือ A จะถูก bound ค่าของ 8 bits แรกใน binary <<"abc">> ถ้าเราสั่ง

A.
97

จะเห็นว่าจะได้ค่า 97 ก็คือค่ารหัส ASCII ของอักษร a นั่นเอง ส่วน Rest/binary เป็นการบอกว่า Rest คือส่วนที่เหลือทั้งหมด และส่วนที่เหลือทั้งหมดนี้เป็น binary ทั้งก้อน ผลที่ได้คือ Rest จะเท่ากับ <<"bc">> นั่นเอง

Rest.
<<"bc">>

ถ้าเราต้องการนำ A กลับไปเป็น binary อีกครับก็สามารถทำได้โดยใช้ <<>> ครอบ A

<<A>>.
<<"a">>

นอกจากนี้ เราสามารถกำหนดได้ว่าเราจะทำการ matching ค่าจาก binary เป็นจำนวนกี่บิต จะเห็นว่าปกติเป็น 8 บิต ตัวอย่างเช่น เราต้องการดึงค่าจาก <<"abc">> ออกมา 2 ตัวหน้า คือ "ab" จะเห็นว่าตัวอักษรมีทั้งหมด 2 ตัวแสดงว่าใช้ทั้งหมด 16 bits เราสามารถ match ได้ด้วยวิธีแบบนี้

<<A:16, Rest/binary>> = <<"abc">>.

ผลที่ได้คือ Rest จะเหลือ

<<"c">>

ส่วน A จะได้ค่าเป็น

24930

คือเป็นค่าที่เกิดจาก bit ของ $a และ $b มาต่อกัน
ถ้าเราต้องการเอา A ไปสร้างเป็น binary ที่มี "ab" ทำได้โดยเอา A ไปครอบด้วย <<>> เหมือนเดิม แต่เราต้องใส่รายละเอียดจำนวนบิตเหมือนเดิมกับตอนที่เอา binding ค่าออกมาด้วยเช่น

<<A:16>>.
<<"ab">>

ตัวอย่างที่ผมเอาไปใช้ใน my_split ฟังก์ชันที่แบ่ง binary แต่ผมจะแบ่งแบบคาบเกี่ยวกัน เช่น แบ่ง 2000 แต่หักออก 1000 แล้วแบ่งอีก 2000

-module(my_binary).
-export([my_split/2]).

my_split(BinList,N) -> my_split(BinList,N,[]).

my_split(<<>>,_,R) -> R;
my_split(BinList,N,R) -> Length = N*8,
                         BinListLength = bit_size(BinList),
                         CheckLength = Length =< BinListLength,

                         if CheckLength == true ->
                                        <<Take:Length,_/binary>> = BinList,
                                        DropLength = Length div 2,
                                        <<_:DropLength,DropRest/binary>> = BinList,
                                        my_split(DropRest,N,[<<Take:Length>>|R]);
                                   true ->
                                        <<Take:BinListLength,Rest/binary>> = BinList,
                                        my_split(Rest,N,[<<Take:BinListLength>>|R])
                         end.