Interprocess Communication
ก่อนที่ process สอง process จะ "ร่วมมือ" กันได้ในระบบกระจาย ต้องมีกลไกส่งข้อมูลระหว่างกันก่อน — บทนี้ลงลึกตั้งแต่ socket ระดับล่างไปจนถึงการแปลงข้อมูลให้เป็นมาตรฐานกลาง (marshalling) และการส่งข้อความแบบกลุ่ม (multicast)
1Message Passing: พื้นฐานที่สุดของการสื่อสาร
send(destination, message) และ receive(source, message) — ฝั่งผู้รับมักมีคิว (queue) เก็บข้อความที่ส่งมาถึงแล้วรอให้โปรแกรมมาอ่าน| รูปแบบ | พฤติกรรม |
|---|---|
| Synchronous communication | ผู้ส่งหยุดรอ (block) จนกว่าข้อความจะถูกส่งมอบให้ผู้รับสำเร็จ (หรือบางนิยามคือรอจน receive ถูกเรียก) — เข้าใจง่าย แต่ผูก sender กับ receiver ไว้ด้วยกันตลอดเวลา |
| Asynchronous communication | ผู้ส่งส่งข้อความแล้วทำงานต่อทันที ไม่ต้องรอผู้รับมาอ่าน — มีกลไกแจ้งภายหลังเมื่อข้อความถูกอ่านแล้ว (เช่น callback หรือ future) |
2Socket, UDP, TCP — ทบทวนสั้น ๆ
รายละเอียดระดับโค้ด (bind/listen/accept, byte-stream ของ TCP, packet framing) อยู่ในวิชา Network Computing หัวข้อที่ 1 แล้ว บทนี้สรุปเฉพาะคุณสมบัติที่ต้องใช้ต่อในบทถัดไป
| โพรโทคอล | คุณสมบัติ | ผลต่อการออกแบบ middleware |
|---|---|---|
| UDP | Connectionless, ไม่มี acknowledgement, ไม่มี retry, ไม่มีการเรียงลำดับ; มี checksum ตรวจสอบความเสียหายแล้วทิ้งข้อความที่เสีย | เหมาะกับ multicast และงานที่ทนต่อข้อความหายได้ (เช่น video streaming, discovery protocol) |
| TCP | Connection-oriented, มี flow control, retransmit อัตโนมัติเมื่อ packet หาย, รับประกันลำดับไบต์ | เหมาะกับ request-reply ที่ต้องการความน่าเชื่อถือ — เป็นฐานของ RPC/RMI ในบทที่ 3 |
3External Data Representation และ Marshalling
แปลงข้อมูลในหน่วยความจำ (structure/object) ให้เป็นลำดับไบต์ที่พร้อมส่งผ่านเครือข่าย
แปลงลำดับไบต์ที่ได้รับกลับมาเป็นโครงสร้างข้อมูลที่ใช้งานต่อได้ในฝั่งผู้รับ
| แนวทาง | ลักษณะ |
|---|---|
| CORBA CDR (Common Data Representation) | ระบุ byte order ตามฝั่งผู้ส่ง ข้อมูลแต่ละตัวไม่มี type กำกับมาด้วย (client/server ต้องรู้ type ล่วงหน้าจาก IDL) |
| Java Serialization | แปลง object เป็น byte stream โดยอัตโนมัติผ่าน implements Serializable — มีข้อมูล class กำกับมาด้วย ทำให้ deserialize ฝั่งรับรู้ชนิดได้เอง |
| รูปแบบ | ลักษณะ |
|---|---|
| JSON | Text-based, มนุษย์อ่านได้ ไม่ต้องมี schema ตายตัว แลกกับขนาดใหญ่กว่าและ parse ช้ากว่า binary — มาตรฐานของ REST API (ดู Network Computing หัวข้อที่ 2) |
| Protocol Buffers (protobuf) | Binary, กำหนด schema ล่วงหน้าด้วย .proto file คล้าย IDL ของ CORBA แต่กระชับกว่ามาก ขนาดข้อมูลเล็กและเร็วกว่า JSON — ใช้เป็นมาตรฐานของ gRPC (ดูบทที่ 3) |
| Apache Thrift / Avro | แนวทางคล้าย protobuf จาก Facebook/Apache — schema-based binary serialization สำหรับ RPC ข้ามภาษา |
4Request–Reply Protocol
รูปแบบข้อความประกอบด้วย: ชนิดข้อความ (Request/Reply), หมายเลขข้อความ (message ID) เรียงตามลำดับที่ส่ง, remote reference ของ object/service ที่เรียก, หมายเลข method ที่เรียก, และ argument ที่ส่งไป
5Group Communication และ Multicast
multicast(group, message)ส่งอัปเดตไปยังกลุ่ม replica เพื่อให้ทุกสำเนามีค่าตรงกัน (เชื่อมกับบทที่ 9)
ถามหากลุ่มเครื่องว่าใครมี object หรือ service ที่ต้องการอยู่บ้าง
อ่านข้อมูลจาก replica ที่ใกล้/ว่างที่สุดได้ เพราะทุก replica ได้รับข้อความอัปเดตเดียวกัน
แจ้งกลุ่ม process ที่สนใจเหตุการณ์เดียวกัน — บรรพบุรุษของ Pub-Sub
ปัญหาของ IP Multicast บน UDP
เพราะข้อจำกัดเหล่านี้กระทบการใช้งานจริงโดยตรง (กลุ่ม replica ต้องได้ค่าตรงกันทุกตัว ไม่ใช่บางตัว) จึงต้องมี multicast ระดับที่แข็งแรงกว่า
Reliable Multicast และ Ordered Multicast
| ประเภท | การรับประกัน |
|---|---|
| Reliable multicast | ถ้า process หนึ่งในกลุ่มได้รับข้อความ ทุก process ที่ยังทำงานอยู่ในกลุ่มต้องได้รับข้อความเดียวกันนั้นด้วย (integrity + validity + agreement) |
| FIFO ordering | ข้อความจากผู้ส่งคนเดียวกันถึงผู้รับทุกคนตามลำดับที่ส่งจริง |
| Causal ordering | ถ้าการส่งข้อความ A "เป็นเหตุ" ให้เกิดการส่งข้อความ B ทุกคนต้องได้รับ A ก่อน B เสมอ |
| Total ordering | ทุก process ในกลุ่มได้รับข้อความทั้งหมดตามลำดับเดียวกัน ไม่ว่าจะเป็นลำดับใดก็ตาม (arbitrary but consistent) |
6ทันสมัย: จาก Multicast สู่ Message Broker
ระบบยุคปัจจุบันไม่ค่อยใช้ IP multicast ตรง ๆ อีกต่อไป (เพราะ ISP และ cloud ส่วนใหญ่ไม่ route multicast traffic ข้าม subnet) แต่ปัญหาเดิม — ส่งข้อความเดียวกันให้กลุ่มผู้รับพร้อมกันอย่างเชื่อถือได้และมีลำดับ — ยังต้องแก้อยู่ จึงย้ายไปแก้ที่ระดับ application ผ่าน message broker
| เทคโนโลยี | แก้ปัญหาเดิมอย่างไร |
|---|---|
| Apache Kafka | Topic แบบ partition เก็บ log เรียงลำดับต่อ partition (คล้าย FIFO ordering) ผู้ subscribe หลายคนอ่าน log เดียวกันได้ (คล้าย reliable multicast แต่ทำงานผ่าน broker กลางแทนเครือข่าย) |
| RabbitMQ / Message Queue | Pub-Sub และ Competing Consumers (ดู Network Computing หัวข้อที่ 4) แก้ปัญหาการกระจายงานและแจ้งเหตุการณ์แทน multicast ระดับเครือข่าย |
- อธิบายความแตกต่างระหว่าง synchronous กับ asynchronous communication และเชื่อมโยงกับ Request-Response/Async Messaging ในวิชา Network Computing
- ทำไมข้อมูลที่ส่งข้ามเครือข่ายจึงต้อง marshal ก่อนเสมอ ยกตัวอย่างปัญหาที่เกิดถ้าไม่ทำ
- เปรียบเทียบ CORBA CDR กับ Protocol Buffers ว่าแก้ปัญหาเดียวกันอย่างไรต่างกัน
- อธิบายว่า timeout และการตรวจ duplicate request ใน Request-Reply Protocol เกี่ยวข้องกับ idempotency อย่างไร
- อธิบายว่าทำไม IP multicast บน UDP จึงไม่เพียงพอสำหรับกลุ่ม replica ที่ต้องการความสอดคล้องของข้อมูล และยกตัวอย่างเทคโนโลยีสมัยใหม่ที่แก้ปัญหานี้แทน
7Message ไม่ใช่เพียงข้อมูล แต่เป็นข้อตกลง
เมื่อเริ่มเขียนโปรแกรมเครือข่าย เรามักมอง message เป็นข้อความหรือ byte ที่ต้องส่งให้ถึงปลายทาง แต่ในระบบกระจาย message มีอย่างน้อยสี่ส่วนซ้อนกันอยู่: รูปแบบข้อมูล ความหมายของคำสั่ง identity ของคำขอ และกติกาเมื่อเกิด failure ถ้าตกลงเพียง JSON field แต่ไม่ตกลงว่าคำสั่งส่งซ้ำได้หรือไม่ protocol ยังไม่สมบูรณ์
| ส่วนของสัญญา | คำถามที่ต้องตอบ | ตัวอย่าง |
|---|---|---|
| Syntax | field, type, encoding และ framing คืออะไร | Protobuf message มี order_id เป็น string |
| Semantics | คำสั่งหมายถึงอะไร state เปลี่ยนเมื่อใด | CreateOrder สำเร็จเมื่อรับเรื่องหรือเมื่อตัดเงินแล้ว |
| Identity | ข้อความนี้สัมพันธ์กับ request/event ใด | request_id, correlation_id, event_id |
| Failure | timeout, retry, duplicate และ error แปลว่าอะไร | deadline exceeded ไม่ยืนยันว่า operation ไม่เกิด |
| Evolution | เพิ่ม/เลิก field โดยไม่ทำให้ของเดิมพังอย่างไร | optional field และ version compatibility |
Command, Event และ Document ต่างกันที่เจตนา
Command ขอให้อีกฝ่ายทำบางอย่างและอาจถูกปฏิเสธ เช่น ReserveStock ส่วน Event บอกว่าเหตุการณ์เกิดขึ้นแล้ว เช่น StockReserved ผู้รับไม่ควร “ปฏิเสธอดีต” แต่เลือกว่าจะตอบสนองอย่างไร ขณะที่ Document/message บางชนิดเพียงส่งข้อมูลโดยไม่เน้นคำสั่งหรือเหตุการณ์
การตั้งชื่อผิดทำให้ flow สับสน ถ้าส่ง event ชื่อ CreateOrder แต่จริง ๆ ต้องการให้ consumer ตัดสินว่าจะสร้างหรือไม่ เรากำลังซ่อน command ในเสื้อ event และจะตอบ error ยาก ในทางกลับกัน event ควรใช้ past tense เพื่อสื่อว่าข้อเท็จจริงเกิดแล้ว ไม่ใช่คำขอที่ยังรออนุมัติ
8Transport property กับ Application guarantee เป็นคนละชั้น
TCP รับประกัน byte stream ที่เรียงลำดับและส่งซ้ำ packet ที่หายใน connection เดียว แต่ไม่ได้รับประกันว่า business operation เกิดครั้งเดียว ถ้า server ทำงานเสร็จแล้ว connection ขาดก่อน client ได้ response client อาจเปิด connection ใหม่และส่ง request ซ้ำ TCP ทั้งสอง connection ทำงานถูกต้อง แต่ application สร้าง order ซ้ำได้
| สิ่งที่ต้องการ | Transport ช่วยอะไร | Application ยังต้องทำอะไร |
|---|---|---|
| ข้อมูลไม่เสียระหว่าง connection | TCP checksum, sequence, retransmission | validation และ integrity ที่ข้าม storage/processing |
| ข้อความหนึ่งฉบับ | TCP ไม่มี message boundary | delimiter, length prefix หรือ protocol framing |
| ทำงานไม่ซ้ำ | TCP ไม่รู้ business operation | idempotency key, deduplication, unique constraint |
| ส่งถึงในที่สุด | connection มีขอบเขตอายุ | durable queue, retry policy, poison-message handling |
| ลำดับข้ามหลาย producer | TCP เรียง byte ต่อ connection | partition key, sequence/causal metadata หรือ consensus |
UDP กลับกัน มันรักษาขอบเขต datagram แต่ไม่รับประกันการมาถึง ลำดับ หรือการไม่ซ้ำ จึงเหมาะเมื่อ application ยอมเสียข้อมูลบางส่วนได้ ต้องการ latency ต่ำ หรือสร้าง reliability ที่เฉพาะกับงานเอง เช่น realtime media สนใจ packet ปัจจุบันมากกว่า packet เก่าที่ retransmit มาช้า
Backpressure: เมื่อผู้รับช้ากว่าผู้ส่ง
การสื่อสารไม่ได้มีเพียงส่งสำเร็จ/ล้มเหลว ยังมีกรณีส่งได้เร็วเกินกว่าผู้รับจะประมวลผล ถ้าไม่มี backpressure ข้อมูลสะสมใน memory, socket buffer หรือ queue จน latency สูงและระบบล้มในที่สุด การเพิ่ม buffer เพียงเลื่อนเวลาล้ม ไม่ได้แก้สมการอัตราเข้าเกินอัตราออก
แนวทางมีทั้ง block ผู้ส่ง, จำกัดจำนวน in-flight request, ใช้ bounded queue, ส่ง credit จาก consumer, ลดคุณภาพ/ทิ้งข้อมูลบางประเภท หรือปฏิเสธด้วย status ที่ชัดเจน การเลือกขึ้นกับ semantics: transaction การเงินทิ้งไม่ได้ แต่ telemetry บางชนิดอาจ sample ได้ และ video realtime อาจทิ้ง frame เก่าดีกว่าเล่นตามหลังไปเรื่อย ๆ
9Marshalling: ความยากไม่ได้จบเมื่อ encode/decode ได้
External Data Representation แก้ความต่างเรื่อง byte order, primitive type และโครงสร้างข้อมูลระหว่างเครื่อง แต่ระบบที่มีอายุยาวต้องรับมือ schema evolution ด้วย producer และ consumer ไม่ได้ upgrade พร้อมกันเสมอ ช่วง deployment จะมีทั้ง version เก่าและใหม่ทำงานพร้อมกัน
| การเปลี่ยน schema | ความเสี่ยง | แนวทาง |
|---|---|---|
| เพิ่ม optional field | consumer เก่าไม่รู้จัก | กำหนดให้ข้าม unknown field และมี default ที่ปลอดภัย |
| ลบ field | producer/consumer เก่ายังใช้ | เลิกใช้เป็นช่วงก่อนลบจริง ไม่ reuse field number ใน Protobuf |
| เปลี่ยน type | parse ได้แต่ความหมายผิดหรือ overflow | เพิ่ม field ใหม่และ migrate แทนการเปลี่ยนเงียบ ๆ |
| เปลี่ยนความหมาย | syntax compatible แต่ semantic incompatible | ตั้งชื่อ/version ใหม่และประกาศ contract ชัด |
| เปลี่ยนหน่วย | ตัวเลขดูถูกแต่ต่างหน่วย | ใส่หน่วยในชื่อ/schema และใช้ type ที่บังคับได้ |
timeout: 5 parse ได้ทุกภาษา แต่ 5 หมายถึงวินาที มิลลิวินาที จำนวนครั้ง หรือเวลาสิ้นสุด? Serialization ทำให้รูปแบบเดินทางได้ ไม่ได้ทำให้ความหมายตรงกัน Contract ต้องระบุ semantics ที่เครื่องมือ encode ไม่สามารถเดาแทนเราText กับ Binary ไม่ได้มีคำตอบเดียว
JSON อ่านและตรวจสอบด้วยคนง่าย เชื่อมกับ browser/tooling กว้าง และเหมาะกับ public API แต่มีขนาดมากกว่า type หลวมกว่า และตัวเลข/วันที่ต้องกำหนด convention เพิ่ม Binary format อย่าง Protobuf กระชับ เร็ว มี schema และ generate code ได้ แต่ debug ด้วยตาเปล่ายากและต้องจัดการ schema registry/version ให้ดี
อย่าเลือกด้วยคำว่าเร็วกว่าเพียงอย่างเดียว ถ้า payload เล็กและ latency ส่วนใหญ่อยู่ที่ database การเปลี่ยน JSON เป็น binary อาจแทบไม่ช่วย แต่เพิ่ม operational complexity ในทางกลับกัน stream ปริมาณสูงหรือ mobile link อาจคุ้มมาก ต้องวัด end-to-end ไม่ใช่ benchmark serialization แยกจากระบบ
Canonical representation และความปลอดภัย
ข้อมูลเดียวกันอาจ serialize ได้หลายแบบ เช่นลำดับ field หรือรูปแบบเลขต่างกัน ถ้าจะ hash/sign message ต้องกำหนด canonical representation ไม่เช่นนั้นสองฝ่ายเห็น semantic เดียวกันแต่ได้ byte ต่างกันและตรวจลายเซ็นไม่ผ่าน นอกจากนี้ decoder ต้องจำกัดขนาด ความลึก และจำนวน element เพราะ input จากเครือข่ายไม่ควรได้รับความเชื่อใจ
10Request–Reply เป็น Protocol เล็ก ๆ ที่มี State
ภาพพื้นฐานคือ client ส่ง request แล้ว server ส่ง reply แต่ implementation จริงต้องตอบเรื่อง request ID, duplicate, timeout, retry, cache reply และการจับคู่ response กับ request เมื่อมีหลายคำขอพร้อมกัน ถ้าใช้ connection เดียวแบบ multiplex ต้องมี correlation ID เพราะลำดับ response อาจไม่ตรงกับลำดับ request
Request { request_id, method, deadline, metadata, body }
Reply { request_id, status, metadata, body | error }
request_id ใช้จับคู่และ deduplicate deadline บอกเวลาที่ผลยังมีประโยชน์ status แยก application rejection ออกจาก transport failure และ metadata ส่ง authentication, trace context หรือ version โดยไม่ปน business body
Invocation semantics จากมุม request–reply
| Semantics | พฤติกรรม | เหมาะกับ | สิ่งที่ยังไม่ได้รับประกัน |
|---|---|---|---|
| Maybe | ส่งครั้งเดียว ไม่ retry | telemetry ที่เสียบางจุดได้ | ไม่รู้ว่าทำหรือไม่ |
| At-least-once | retry จนได้ reply/หมดนโยบาย | idempotent operation | อาจทำซ้ำ |
| At-most-once | server ตรวจ request ID และคืนผลเดิม | งานที่ duplicate อันตราย | อาจไม่ทำเลยถ้า retry ไม่ถึง |
| Exactly-once effect | ผูก dedupe กับ state change แบบ atomic | business invariant สำคัญ | ไม่ได้เกิดจาก network guarantee ตัวเดียว |
คำว่า exactly-once มักใช้เกินจริง ในระบบทั่วไปเราสร้างผลที่ดูเหมือนครั้งเดียวด้วย unique key, transaction, idempotent consumer และการเก็บผลลัพธ์ ไม่ได้ทำให้ packet หรือ handler ถูกเรียกเพียงครั้งเดียวทางกายภาพ ความต่างนี้สำคัญเวลาวิเคราะห์ failure ตรงช่วงระหว่าง “ทำงานแล้ว” กับ “บันทึกว่าเคยทำแล้ว”
Deadline propagation
ถ้า Client ให้เวลา Gateway 2 วินาที และ Gateway เรียก A→B→C แต่ทุก hop ตั้ง timeout 2 วินาทีแยกกัน ระบบอาจทำงานต่อเป็นหลายวินาทีหลัง client เลิกรอ Deadline ควรเป็น budget ที่ลดลงตามสาย ผู้รับตรวจเวลาที่เหลือก่อนเริ่มงาน และยกเลิกงานที่ไม่มีผู้ใช้รอถ้างานนั้นยกเลิกได้อย่างปลอดภัย
อย่างไรก็ตาม cancellation เป็น request ไม่ใช่เครื่องย้อนเวลา งานอาจผ่านจุด commit ไปแล้ว การกดยกเลิกหน้าจอไม่ได้รับประกันว่าธุรกรรมไม่เกิด API ต้องแยก cancel-before-commit, cancel-requested และ compensate-after-commit ให้ชัด
11Group Communication: หนึ่งข้อความ หลายมุมมอง
การส่งให้กลุ่มดูเหมือนเพิ่มผู้รับจากหนึ่งเป็นหลายคน แต่ปัญหาใหม่คือ membership, ordering และ delivery semantics สมาชิกคนใดถือว่าอยู่ในกลุ่มขณะส่ง ถ้าสมาชิก join/leave ระหว่างทางควรได้รับหรือไม่ และผู้รับทุกคนต้องเห็นข้อความลำดับเดียวกันหรือเพียงลำดับของผู้ส่งแต่ละคน
| Ordering | รับประกัน | กรณีใช้ |
|---|---|---|
| Unordered | ไม่กำหนดลำดับ | metric อิสระที่รวมภายหลังได้ |
| FIFO per sender | ข้อความจาก producer เดียวไม่สลับ | event stream ต่อ device/user |
| Causal | ถ้า B เกิดเพราะ A ทุกคนเห็น A ก่อน B | collaboration/comment-reply |
| Total order | สมาชิกทุกคนเห็นลำดับเดียวกัน | replicated state machine |
Guarantee ที่เข้มขึ้นต้องจ่าย coordination และ latency มากขึ้น ถ้างานไม่ต้องการ total order การบังคับทุก event ผ่าน sequencer เดียวสร้างคอขวดโดยไม่เพิ่ม correctness ที่มีความหมาย ตัวอย่างเช่นยอด like จากผู้ใช้ต่างคนอาจรวมแบบ commutative ได้ แต่การเปลี่ยนสถานะ order อาจต้องควบคุมลำดับมากกว่า
Reliable multicast ไม่ได้หมายถึงทุกคนได้พร้อมกัน
Reliable อาจหมายถึงสมาชิก non-faulty ได้รับในที่สุด แต่ไม่ได้รับประกันเวลาเดียวกัน ถ้าต้องให้สมาชิกเปลี่ยน view ของกลุ่มอย่างสอดคล้อง เราต้องมี membership protocol และนิยาม virtual synchrony หรือแนวคิดใกล้เคียง การใช้คำว่า broadcast โดยไม่ระบุสมาชิกและช่วงเวลาจึงคลุมเครือ
12จาก Network Multicast สู่ Broker และ Event Streaming
IP multicast ส่ง packet ไปยังกลุ่มได้มีประสิทธิภาพในเครือข่ายที่รองรับ แต่ application สมัยใหม่จำนวนมากใช้ broker เพราะต้องการ persistence, consumer state, filtering, retry และการเชื่อมระบบข้าม administrative domain Broker ไม่ได้แทน multicast ทุกกรณี แต่มันยก semantics หลายอย่างขึ้นมาไว้ระดับ application
| รูปแบบ | ผู้รับ | การเก็บข้อความ | การกระจายงาน |
|---|---|---|---|
| Queue | โดยทั่วไป consumer หนึ่งคนต่อ message | เก็บจน ack/หมดอายุ | competing consumers แบ่งงาน |
| Publish–Subscribe | ทุก subscription ที่ตรงเงื่อนไข | ขึ้นกับ broker | กระจาย event ไปหลายความสนใจ |
| Log/Stream | consumer group อ่านตาม offset ของตน | เก็บตาม retention | อ่านซ้ำและสร้าง state ใหม่ได้ |
| IP multicast | สมาชิก multicast group บน network | โดยตัวมันไม่เก็บ | ส่ง packet มีประสิทธิภาพ |
Acknowledgment ย้ายความรับผิดชอบ ไม่ได้ลบ failure
ถ้า consumer ประมวลผลแล้ว crash ก่อน ack broker จะส่งซ้ำ ถ้า ack ก่อนประมวลผลแล้ว crash งานอาจหาย ดังนั้นรูปแบบทั่วไปคือประมวลผลและบันทึกผลให้ idempotent แล้วค่อย ack แต่ช่วงระหว่าง commit กับ ack ยังทำให้ส่งซ้ำได้อยู่
Dead Letter Queue ช่วยแยก message ที่ล้มซ้ำไม่ให้ block งานทั้งหมด แต่ DLQ ไม่ใช่ถังขยะที่ปล่อยไว้ตลอด ต้องมี owner, alert, วิธีตรวจข้อมูลส่วนตัว และขั้นตอน replay หลังแก้สาเหตุ Poison message อาจเกิดจาก schema incompatibility ซึ่ง retry ร้อยครั้งก็ไม่ดีขึ้น
Event schema และ consumer ที่เราไม่รู้จัก
Public event อาจมี consumer หลายทีมที่ producer ไม่รู้ การเปลี่ยนความหมายเงียบ ๆ จึงอันตรายกว่า API ที่รู้คู่สัญญา Event ควรมี schema governance, compatibility test, owner และเอกสารความหมาย ไม่ควรใส่ database row ทั้งแถวแล้วเรียกว่า domain event เพราะทำให้ consumer ผูกกับ schema ภายใน
13Workshop: ออกแบบ Protocol ที่ตั้งใจให้ Failure เกิด
สร้างระบบ Producer → Broker → Consumer สำหรับคำสั่งส่งอีเมลหรือสร้าง thumbnail โดยใช้เครื่องมือใดก็ได้ เป้าหมายไม่ใช่เพียงส่งได้ แต่ต้องแสดงพฤติกรรมเมื่อเกิด failure ตามขั้นตอนต่อไปนี้
- กำหนด message schema มี message_id, correlation_id, occurred_at, type, schema_version และ payload
- รัน happy path แล้วบันทึก latency และลำดับ log ทั้งสามส่วน
- ทำ consumer crash หลังทำงานสำเร็จแต่ก่อน ack พิสูจน์ว่า message ถูกส่งซ้ำ
- เพิ่ม deduplication ที่ผูกกับ side effect ไม่ใช่เก็บ ID ใน memory อย่างเดียว
- ส่ง payload ผิด schema และกำหนด retry limit/DLQ
- ทำ consumer ช้ากว่า producer วัด queue depth และ latency เพื่อเห็น backpressure
- เปลี่ยน schema เพิ่ม optional field โดยให้ consumer เก่ายังทำงาน
- เขียน timeline ว่าแต่ละฝ่ายรู้ข้อมูลอะไร ณ จุดที่ crash
| หลักฐาน | สิ่งที่ต้องอธิบาย |
|---|---|
| Producer log | ส่ง message ใด สำเร็จระดับไหน ได้ broker acknowledgment หรือยัง |
| Broker state | queue depth, redelivery count, offset หรือ consumer lag |
| Consumer log | เริ่ม/จบ side effect, dedupe decision, ack และ error |
| Business state | ผลเกิดกี่ครั้ง ตรงกับ message delivery กี่ครั้งหรือไม่ |
14ข้อผิดพลาดในการออกแบบการสื่อสารที่พบเสมอ
- คิดว่า TCP รักษาขอบเขต message: อ่าน
recv()ครั้งเดียวแล้ว parse ทันที - retry ทุก error เหมือนกัน: retry validation error หรือ authentication failure ซึ่งไม่มีทางดีขึ้นจากเวลา
- ไม่มีขนาดสูงสุด: message ใหญ่หรือ nested ลึกทำให้ memory/CPU ถูกใช้จนหมด
- ใช้ timestamp เป็น ID: ชนกันได้และเปิดเผยข้อมูลเวลาโดยไม่จำเป็น
- ไม่มี deadline: งานค้างยึด connection/thread จน failure ลาม
- เชื่อคำว่า exactly-once โดยไม่ถามขอบเขต: broker อาจรับประกันใน log แต่ side effect ภายนอกยังซ้ำ
- ส่ง event เป็นสำเนาตาราง: consumer ผูกกับ schema ภายในและไม่รู้ความหมายทางธุรกิจ
- ใช้ queue เพื่อซ่อน overload: queue ยาวขึ้นเรื่อย ๆ ทำให้ข้อมูลมาถึงช้าเกินมีประโยชน์
15สรุปและขั้นตอนถัดไป
Interprocess Communication ไม่ได้จบที่เลือก TCP, UDP หรือ broker เพราะ transport รู้เพียง byte/datagram ส่วน application ต้องกำหนด message boundary, schema, meaning, identity, deadline และพฤติกรรมเมื่อส่งซ้ำหรือคำตอบหาย Serialization ทำให้ข้อมูลอ่านได้ข้ามภาษา แต่ไม่รับประกันว่าความหมายตรงกัน และ queue ทำให้ผู้ส่งกับผู้รับไม่ต้องพร้อมกัน แต่เพิ่มคำถามเรื่อง duplicate, ordering และ consumer lag
หลักคิดที่ควรติดไปคือแยก delivery ออกจาก effect ข้อความอาจส่งหลายครั้งแต่เราสร้างผลครั้งเดียวได้ด้วย idempotency หรือข้อความอาจส่งครั้งเดียวแต่ผลไม่เกิดเพราะ consumer crash การออกแบบต้องระบุให้ชัดว่ารับประกันตรงขอบเขตใด
ขั้นตอนถัดไปคือ Distributed Objects & Remote Invocation เราจะดูความพยายามยกระดับ request–reply ให้ดูเหมือนเรียก method ธรรมดา ความสะดวกนั้นสร้าง productivity สูง แต่ก็ซ่อน latency, partial failure และ invocation semantics จนกลายเป็นบทเรียนสำคัญของ API และ microservices ในปัจจุบัน