เริ่มเกริ่นจากเรื่องของ microservices ที่มีการแยก services ออกเป็นหลาย ๆ ส่วน สำหรับการติดต่อและจัดการกับข้อมูลจะมีวิธียอดนิยมที่ถูกที่เรียกว่า Saga
Saga ก็คือการจัดการกับ transaction (การทำงานหนึ่ง ๆ) ที่มีการติดต่อข้ามกันหลาย ๆ services; ซึ่งจะมีวิธีย่อยอยู่สองวิธีก็คือ Orchestration (มีตัวกลาง) กับ Choreography (ไม่มีตัวกลาง) และในการใช้ Saga นี้ก็จะต้องอาศัยเครื่องมืออย่าง Message Queue เข้ามาช่วยจัดการกับ transactions เหล่านี้
ให้ลองนึกดูว่าถ้าเราจะอยากจะส่งข้อมูลออกทางไหนสักทางนึง แต่ไม่ได้สนใจว่าผู้รับปลายทางจะเป็นใคร แค่ส่งพ่นออกไปแล้วให้ใครก็ได้ที่สนใจข้อมูลเหล่านั้นเป็นคนเลือกไปอ่านเอง โดยเราจะทำให้ง่ายกว่านั้น คือต้องระบุหัวข้อของข้อมูลด้วยว่าเป็นเรื่องเกี่ยวกับอะไร แล้วคนรับปลายทางก็จะได้ดูว่าใช่เรื่องที่ตัวเองอยากได้มั้ย; และในความเป็นจริง ผู้ส่งไม่ได้มีคนเดียว ผู้รับเองก็จะมีหลายคนได้ด้วย ทุกคนคอยรับและส่งข้อมูลในเส้นทางเดียว; Message queue นั้นจะทำงานแบบนี้ เราเรียกคนส่งว่า Producer, คนรับเรียกว่า Consumer และช่องทางที่ส่งไปว่า Channel (หรืออาจจะเรียกว่าท่อข้อมูล) หัวข้อของข้อมูลเราเรียกมันว่า Topic และการเลือกหัวข้อที่สนใจเรียกว่า Subscribe
ในการใช้งาน (ไม่ได้ทำแบบนี้เสมอไป แต่ยกตัวอย่างมาแบบนึง) สมมติว่าเรามี services อยู่สองตัวคือ Customer กับ Order โดยให้ Order เป็น producer และอีกตัวเป็น consumer และทำการสร้างแชนแนลตัวกลางสำหรับไว้ติดต่อกัน; เริ่มจากฝั่ง Order service รับข้อมูลมาจากลูกค้าว่าต้องการให้สร้างออร์เดอร์ใหม่ขึ้นมา แต่ก่อนจะสร้างออร์เดอร์จำเป็นต้องเช็คเงินที่ลูกค้ามีอยู่ก่อนและข้อมูลเงินนั้นถูกเก็บไว้ที่ Customer service; ดังนั้นระหว่างนี้เราจะให้สถานะออร์เดอร์เป็น pending ไปก่อน; Order service จะส่งข้อมูลไปที่แชนแนลโดยกำหนดหัวข้อไว้ว่าเป็นเรื่องเกี่ยวกับการขอเช็คเงินลูกค้า; Customer ที่กำลังอ่านแชนแนล เมื่อเห็นว่ามีข้อมูลส่งมาว่าต้องการขอเช็คเงินก็จะไปเช็คฐานข้อมูลของตัวเองว่าเงินของลูกค้านั้นมีพอหรือไม่ ซึ่ง Customer service ก็จะส่งข้อมูลตอบกลับไป โดยอาจจะบอกว่าพอหรือไม่พอ; Order service ก็จะอ่านข้อมูลที่ส่งมาในแชนแนลเหมือนกัน หัวข้อที่ Order service สนใจก็อาจจะเป็นมีเงินพอกับมีเงินไม่พอ เมื่อได้รับข้อมูลมาแล้วก็จะไปแก้สถานะของออร์เดอร์ว่า approved หรือ rejected
นี่เป็นวิธีการทำงานโดยทั่วไปของ Message Queue ทีนี้ลองมาโฟกัสดูที่การทำงานของ Kafka กัน
จากตัวอย่างที่พูดถึงข้างบน ในมุมมองการทำงานบน production นั้น services ที่ทำการอ่านข้อมูลใน channel นั่นจะไม่ได้มีตัวเดียว อาจจะมีสองหรือสามหรือมากกว่านั้น (ให้คิดถึง replicas ของ pod ใน Kubernetes); สมมติว่ามี customer services สองตัว เวลาที่ข้อมูลส่งมานั้นจะแบ่งงานยังไง ใครเป็นคนทำ หรือจะมีการทำงานซ้ำเกิดขึ้นมั้ย
Kafka เป็นหนึ่งใน tool ประเภท Message Queue ซึ่งจะมีคีย์เวิร์ดที่เพิ่มมานั่นก็คือ Partition ที่ถูกสร้างมาเพื่อให้ Kafka ทำงานบน Distributed System ได้ และเมื่อมี Partition แล้ว Consumer Group ก็เป็นส่วนที่เข้ามาเกี่ยวข้องกับเรื่องนี้ด้วย
จะขอใช้ techinical words ตรงนี้มากหน่อย... เวลาที่มี consumer ตัวนึงเข้ามา subscribe topics จะต้องบอกด้วยว่าตัวเองนั้นอยู่ consumer group ไหน; Kafka จะให้ข้อมูลตรงนี้เพื่อระบุว่า consumer ตัวนี้ควรจะทำงานยังไง; เวลาที่ producer ส่งข้อมูลที่ระบุ topics เข้ามาในแชนแนลก็ทำการส่งแชนแนลย่อย ๆ ช่องนึงซึ่งเรียกว่า Partition; Partition นั้นจะมีได้หลายช่อง จำนวนของ Partition นั้นจะสามารถปรับได้ตามใจ และ consumer ที่อ่านข้อมูลนั้นจริง ๆ แล้วจะไม่ได้อ่านเป็นแชนแนลทั้งหมด แต่จะอ่านจาก partition ที่ถูกแบ่งออกมา; Topic จะถูกส่งไปที่ partition ไหนนั้นกำหนดได้ไม่ว่าจะเป็นการสุ่ม เลือกช่องเอง หรือใช้คีย์ไป hash ล็อคช่องเอาก็ยังได้ (Topic ที่ใช้ค่าคีย์เหมือนกันก็จะถูกส่งไป partition ช่องเดียวกัน)
ยกตัวอย่างว่าถ้าเกิดว่ามี 3 partitions และส่งข้อมูล 1, 2, 3, 4 และ 5 มาแบบสุ่ม partition ก็อาจจะได้หน้าตาประมาณนี้
Partition 1: 1 3
Partition 2: 4
Partition 3: 2 5
เวลาที่ consumer เข้ามาอ่านข้อมูลที่แชนแนลโดยระบุ consumer group ของตัวเอง; แล้ว Kafka กำหนดให้อ่าน Partition 1 กับ Partition 2 (consumer นึงอ่านได้มากกว่า 1 partition) consumer ตัวนั้นก็จะไม่ได้รับข้อมูลจาก Partition 2 ซึ่งมีเลข 4 อยู่ไป
ลองมาดูข้อมูลเกี่ยวกับ consumer group เพิ่มกัน
Kafka นั่นจะมีกฎในการจัดงานให้กับ consumers ที่เข้ามา subscribe ในแชนแนลอยู่
ถ้าอยากอ่านข้อมูลในแต่ละ partitions อีกครั้งให้ใช้วิธีกำหนด consumer group ใหม่เป็นค่าที่ยังไม่เคยถูกใช้
ใน Kafka มีระบบ rebalance ที่ทำงานได้อัตโนมัติ คือเวลาที่มี consumer เพิ่มเข้ามาใน consumer group ใด ๆ Kafka จะทำการกำหนด partitions ที่แต่ละ consumer ต้องอ่านให้ใหม่; (ทำให้รองรับ Distributed System) และก็ทำงานในตอนที่มี consumer ใน group ถอนตัวออกไปด้วย (เช่นเวลาโดน shutdown) Kafka จะจำว่า consumer ที่อยู่ใน consumer group นั้นอ่านแต่ละ partition ไปถึงไหนแล้ว (offset) จะได้อ่านต่อจากเดิมได้; และระบบนี้ทำให้ kafka รองรับ Distributed System มากขึ้น
เวลาสร้าง consumer group ใหม่จะสามารถเลือกได้ว่าจะให้อ่านแต่ละ partition จากเริ่มแรกเลย (earliest) หรือจะไม่สนใจรออ่านแต่ข้อมูลใหม่เท่านั้น (latest); โดยที่ทั้งสองแบบเมื่อ rebalance ก็จะอ่านต่อจากเดิมได้เหมือนกัน จะเลือกอ่านแบบไหนนั้นก็ขึ้นอยู่กับว่า consumer group นั้นสร้างมาทำอะไร
จบ ถ้าหากว่ามีไอเดียอะไรเพิ่มขึ้นจะมาเติมใส่ทีหลัง