ข้ามไปยังเนื้อหาหลัก

ค้นหาใน CooLWJ

选择搜索范围,输入关键词后按回车。

ภาษา

ผู้บริโภคข้อความของ rabbitmq “หายไปโดยไม่มีสาเหตุ”

ที่มาของปัญหา:

ราว 17:12 น. วันที่ 1 เดือน 9 ได้รับข้อร้องเรียนจากเจ้าหน้าที่ติดตั้งระบบว่าอุปกรณ์บางส่วนอัปเกรดไม่ได้ตามปกติ ไม่ได้รับคำสั่งที่ส่งจากคอนโซล และมีปัญหาอื่น ๆ เพื่อนร่วมงานตรวจสอบบันทึกของโครงการ control(ต่อไปเรียกสั้น ๆ ว่า control)แล้วพบว่า control ไม่ได้รับข้อมูลเงาอุปกรณ์ที่อุปกรณ์รายงานมา จึงไม่ได้ส่งคำสั่งลงไป โครงการ control เชื่อมต่อกับอุปกรณ์โดยตรง และส่งคำสั่งกับข้อมูลการกำหนดค่าบางอย่างไปยังอุปกรณ์ตามข้อมูลที่อุปกรณ์รายงานมา ซึ่งรวมถึงการอัปเกรด การรายงานบันทึก เป็นต้น ก่อนนำ IoT ขึ้นใช้งาน control อาศัยรายงานฮาร์ตบีตเพื่อรับข้อมูลปัจจุบันของอุปกรณ์ หลังนำ IoT ขึ้นใช้งานแล้วจึงอาศัยข้อมูลเงาอุปกรณ์

เพื่อรับข้อมูลปัจจุบันของอุปกรณ์ โดย control จะสมัครรับข้อมูลเงาของอุปกรณ์ แต่ข้อมูลเงานั้นถูกส่งต่อมาโดยบริการเงา(เรียกสั้น ๆ ว่า IoT)มันไม่ได้เชื่อมต่อรับรายงานเงาจากอุปกรณ์โดยตรง สำหรับรายละเอียดการไหลของข้อมูล โปรดดูสองภาพด้านล่างนี้:

แอปพลิเคชัน(control)รับสถานะอุปกรณ์

แอปพลิเคชัน(control)ส่งคำสั่งไปยังอุปกรณ์

 

หลังรู้ว่า control รับข้อความเงาไม่ได้ ผมก็รีบไปดูในคอนโซลของ rabbitmq ว่ามีข้อความหรือไม่ เพื่อยืนยันสองเรื่อง:1.อุปกรณ์ได้ส่งข้อความรายงานขึ้นมาหรือไม่ 2.rabbitmq ทำงานปกติหรือไม่ ภาพ1、ภาพ2ด้านล่างเป็นภาพคอนโซล rabbitmq สองภาพที่บันทึกไว้ตอนนั้น จากภาพที่ 1ยืนยันได้ชัดเจนว่าอุปกรณ์มีการส่งข้อความรายงานขึ้นมา แต่มีข้อความจำนวนมากที่เป็น unacked(หมายความว่าส่งถึงคอนซูเมอร์แล้ว เพียงแต่คอนซูเมอร์ยังไม่ได้ส่ง ack เท่านั้น ตามทฤษฎีแล้วรอสักระยะก็น่าจะกลับเป็นปกติ) ส่วนคิวใดที่มีข้อความ unacked ค้างสะสมอยู่ โปรดดูภาพที่ 2,“spacebridgeiot-shadow”คือสิ่งที่เราใช้รับข้อมูลเงาอุปกรณ์ที่อุปกรณ์รายงานเข้ามาพอดี เมื่อข้อความค้างสะสมอยู่ในคิวทั้งหมด จึงสมเหตุสมผลที่ไม่ได้ส่งต่อไปยัง control หลังสังเกตอยู่ระยะหนึ่งพบว่าจำนวน unacked กลายเป็น 0 แต่จำนวนรวม total กลับไม่เปลี่ยนแปลงมากนัก ให้ความรู้สึกเหมือนข้อความ unacked กลับเข้าไปอยู่ในคิวข้อความเพื่อรอส่งอีกครั้ง และเมื่อผ่านไปไม่กี่นาทีก็พบข้อความ unacked จำนวนมากอีกจริง ๆ หลังจากนั้นไม่กี่นาที ข้อความ unacked ส่วนนี้ก็กลับเข้าไปในคิวอีก ทาง control ยังคงไม่ได้รับข้อความ เมื่อตรวจดูบันทึกทางฝั่ง IoT ในตอนนั้นกลับพบว่าไม่มีข้อความเงาอุปกรณ์เข้ามาเลย จึงดูในคอนโซลของ rabbitmq ที่ “spacebridgeiot-shadow”คิวนี้กลับไม่มีผู้บริโภคข้อความเหลืออยู่เลย เช่นภาพที่ 3ดังที่แสดงไว้ เมื่อตรวจดูบันทึกของ rabbitmq ในตอนนี้ ก็พบข้อความข้อผิดพลาดจริง เช่นภาพ4ดังที่แสดง rabbitmq เป็นฝ่ายปิดการเชื่อมต่อเอง。

ภาพที่ 1:ภาพรวม rabbitmq

 

 

ภาพที่ 2:แผนภูมิสถิติคิวของ rabbitmq

 

 

ภาพที่3:ภาพรวมของ spacebridgeiot-shadow

 

 

 ภาพ 4:ข้อความข้อผิดพลาดของ rabbitmq

 

วิธีแก้ไขชั่วคราว:

เนื่องจากตอนนั้นมีข้อร้องเรียนเข้ามาจำนวนมากแล้ว จึงเลือกใช้วิธีแก้ที่ค่อนข้างรุนแรงคือ “ลบข้อความที่ค้างสะสม” หลังลบแล้วก็กลับเป็นปกติจริง ๆ(หมายเหตุ:ปัญหาในระบบที่เปิดให้บริการต้องแก้ไขให้เร็วที่สุด เราไม่มีเวลามาวิเคราะห์ล็อกแล้วค่อยแก้อย่างเป็นขั้นเป็นตอน ต้องเร็ว)。

 

สัญลักษณ์ทางเทคโนโลยี RabbitMQ

ทำให้ปัญหาเกิดซ้ำในสภาพแวดล้อมนอกระบบจริง:

1.สะสมข้อความจำนวนมาก(อย่างน้อยหลักหมื่น)ไว้ในคิว “mirrorTestQueue” ของ rabbitmq ในสภาพแวดล้อม 10.200.41.166

2.หยุดตัวรับข้อความของ mirrorTestQueue รอจนข้อความสะสมเสร็จแล้วจึงเริ่มใหม่

3.เมื่อข้อความสะสมเสร็จแล้ว ให้เริ่มตัวรับข้อความใหม่

 

เป็นไปตามที่เราคาดไว้ ภายในไม่กี่วินาทีมีข้อความหลายพันข้อความถูกส่งให้ผู้บริโภคข้อความ หลังดำเนินต่อเนื่องหลายนาที rabbitmq ก็เป็นฝ่ายปิดการเชื่อมต่อกับผู้บริโภคข้อความ ตอนนั้นจะมองไม่เห็นผู้บริโภคข้อความของคิวในคอนโซล เนื่องจากเราตั้งค่าให้ผู้บริโภคข้อความกู้คืนอัตโนมัติ ผ่านไปสักพักจึงเชื่อมต่อกลับมาเอง,

แต่ไม่นานการเชื่อมต่อก็ถูกตัดอีก แทบจะเหมือนกับปัญหาที่เราพบในระบบจริง อะไรกันแน่ที่ทำให้เกิดปัญหานี้?พูดตามตรง ตอนนั้นยังไม่มีแนวทางอะไร ค้นหาบนอินเทอร์เน็ตอยู่พักหนึ่งก็ยังไม่พบคำตอบที่น่าพอใจเป็นพิเศษ(ตอนนั้นยังจับแก่นของปัญหาไม่ได้ คำค้นที่ใช้กว้างเกินไป),ต่อมา

เราคาดว่าอาจมีปัญหาอะไรบางอย่างในระดับ TCP จึงตัดสินใจลองดักจับแพ็กเก็ตดูว่าจะพบเบาะแสอะไรได้บ้าง และแล้วก็โชคดีจริง ๆ ไม่พูดมากแล้ว มาดูภาพกันเลย。

 

 

 ก่อน 13:06:25.643428 rabbitmq ยังคงส่งข้อความให้ผู้บริโภคข้อความอย่างต่อเนื่อง จนถึงเวลา 13:06:25.643428 จึงเริ่มเกิดภาวะที่หน้าต่าง tcp ของผู้บริโภคข้อความเต็ม หลังจากเป็นเช่นนี้ประมาณ 30 วินาที rabbitmq ก็เป็นฝ่ายตัดการเชื่อมต่อ(ส่งแพ็กเก็ต rst หนึ่งแพ็กเก็ต)จากนั้นผู้บริโภคข้อความเชื่อมต่อใหม่ แล้วหน้าต่างก็เต็มอีกครั้ง และหลังจากผ่านไปอีกประมาณ 30 วินาทีก็ถูกตัดการเชื่อมต่ออีก。

 

รู้สึกว่าค่อนข้างเป็นแบบแผน แต่ละครั้งกินเวลา 30s ดูเหมือนเป็นพารามิเตอร์ที่กำหนดค่าได้ สรุปคร่าว ๆ ก็คือ “tcp full window ทำให้เซิร์ฟเวอร์เป็นฝ่าย rst การเชื่อมต่อเอง และยังเกิดขึ้นอย่างเป็นแบบแผนด้วย”

ครั้งนี้ลองเปลี่ยนคำค้นแล้วก็พบคำตอบ,rabbitmq มีพารามิเตอร์ตัวหนึ่งชื่อว่าtcp_listen_options.send_timeout เป็นพารามิเตอร์ที่ใช้ควบคุมเวลาหมดอายุของการเขียน เมื่อการเขียนหมดเวลา จะกระตุ้น RST ของ tcp(https://github.com/rabbitmq/rabbitmq-java-client/issues/341),ลองปรับแก้ดูว่าผลจะเป็นอย่างไร:

1. เปลี่ยนระยะหมดเวลาการเขียนเป็น 10s

tcp_listen_options.send_timeout = 10000

 2.จับแพ็กเก็ตดูว่าได้ผลหรือไม่

 

 ตั้งแต่หน้าต่างรับข้อมูลเต็มจนถึงปิดการเชื่อมต่อใช้เวลาประมาณ 10 วินาที。

 

ทบทวนสิ่งที่เกิดขึ้น:

เนื่องจากฝั่งคอนซูเมอร์ของ rabbitmq ไม่ได้ตั้งค่า prefetch ทำให้ rabbitmq ส่งข้อความจำนวนมากเกินไปให้คอนซูเมอร์ในคราวเดียว จนหน้าต่าง tcp ของคอนซูเมอร์เต็ม และกระตุ้น tcp_listen_options.send_timeout ของ rabbitmq เมื่อการหมดเวลาเขียนนี้ถึงเกณฑ์ที่กำหนด ก็จะทำให้ rabbitmq ตัดการเชื่อมต่อ tcp ของคอนซูเมอร์。

 

วิธีแก้ปัญหาขั้นสุดท้าย:

        ก่อนหน้านี้การลบข้อความเป็นเพียงวิธีที่จำใจต้องใช้ แม้จะแก้ปัญหาได้แต่ก็รุนแรงเกินไป เราต้องหาวิธีที่ละเมียดละไมกว่านี้มารับมือ ในเมื่อสาเหตุคือการผลักข้อความให้คอนซูเมอร์มากเกินไปจนหน้าต่าง TCP เต็ม เราก็ควรปรับที่อัตราการรับ เพียงบอก rabbitmq ตอนเชื่อมต่อว่าอย่าส่งมาให้มากเกินไปก็พอ。(ข้อความต่อไปนี้คัดมาจากhttps://blog.csdn.net/james_searcher/article/details/70308565)rabbitmq มีพร็อพเพอร์ตีชื่อ prefetch ซึ่งหมายถึงจำนวนข้อความที่ยังไม่ได้รับการยืนยัน(unacked messages)สูงสุดที่ผู้บริโภคข้อความรายเดียวสามารถรับไปประมวลผลได้ จะเข้าใจอย่างไรดี?mq กำหนดบัฟเฟอร์ให้ consumer แต่ละราย โดยมีขนาดเท่ากับ prefetch ทุกครั้งที่ได้รับข้อความหนึ่งรายการ MQ จะส่งข้อความเข้าไปในบัฟเฟอร์ แล้วจึงส่งต่อให้ไคลเอนต์ เมื่อได้รับข้อความ ack หนึ่งรายการ(consumer ส่งคำสั่ง baseack) mq จะทำให้บัฟเฟอร์ว่างลงหนึ่งตำแหน่ง แล้วเพิ่มข้อความใหม่เข้าไป แต่หากบัฟเฟอร์เต็มในขณะนั้น MQ จะเข้าสู่สถานะบล็อก อธิบายให้ชัดขึ้น สมมติว่าตั้งค่า prefetch เป็น 10 และมี consumer สองราย นั่นหมายความว่า consumer แต่ละรายจะดึงข้อความล่วงหน้าจาก queue ครั้งละ 10 รายการมาเก็บในบัฟเฟอร์ภายในเพื่อรอประมวลผล ขณะเดียวกัน จำนวน unacked ของ channel นี้จะกลายเป็น 20 ส่วนลำดับการส่งของ Rabbit คือ ส่งให้ consumer1 จนครบ 10 ข้อความก่อน แล้วจึงส่งให้ consumer2 อีก 10 ข้อความ หากขณะนี้มีข้อความใหม่ที่ต้องส่ง ระบบจะตรวจสอบก่อนว่าจำนวน unacked ของ channel เท่ากับ 20 หรือไม่ หากใช่ ก็จะไม่ส่งข้อความให้ consumer และข้อความจะคงอยู่ใน queue ต่อไป จากนั้นเมื่อ consumer รายใดรายหนึ่งส่ง ack สำหรับข้อความหนึ่งรายการ จำนวน unacked จะเท่ากับ 19 Rabbit ก็จะตรวจสอบว่า consumer รายใดมีจำนวน unacked น้อยกว่า 10 แล้วส่งข้อความให้ consumer รายนั้น

 

เมื่อเขียนเป็นโค้ดก็คือ

 

 

จะประเมินค่านี้อย่างไร ทางการ rabbitmq มีบทความที่อธิบายไว้ได้ดีมาก จึงจะไม่ลงรายละเอียด ปัจจุบันระบบของเราตั้งค่าไว้ที่ 20

https://www.rabbitmq.com/blog/2012/05/11/some-queuing-theory-throughput-latency-and-bandwidth/

 

 

คำส่งท้าย:

เรายังอยู่ในขั้นเริ่มต้นในการใช้ rabbitmq ก่อนใช้งานจึงต้องศึกษา API ของ rabbitmq ให้มาก ทำความคุ้นเคยกับวิธีใช้ API ที่ใช้บ่อย และทดลองในสภาพแวดล้อมทดสอบให้มาก。


ความคิดเห็น

กำลังส่ง

บทความยอดนิยม

เลือกธีมที่ชอบ

ธีมที่เลือกจะใช้ทั่วทั้งเว็บไซต์