Chuyển đến nội dung chính

Tìm trên CooLWJ

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

Ngôn ngữ

Consumer rabbitmq “biến mất vô cớ”

Bối cảnh vấn đề:

Khoảng 17:12 ngày 1 tháng 9, chúng tôi nhận được phản ánh từ nhân viên triển khai rằng một số thiết bị không thể nâng cấp bình thường, không nhận được lệnh gửi từ bảng điều khiển và các vấn đề khác. Đồng nghiệp kiểm tra nhật ký bên dự án control (sau đây gọi tắt là control), phát hiện control không nhận được thông tin bóng thiết bị do thiết bị báo lên, nên không gửi lệnh xuống. Dự án control kết nối trực tiếp với thiết bị, dựa trên thông tin thiết bị báo lên để gửi một số lệnh và thông tin cấu hình xuống thiết bị, bao gồm nâng cấp, báo cáo nhật ký, v.v. Trước khi IoT đi vào hoạt động, control dựa vào báo cáo heartbeat để lấy thông tin hiện tại của thiết bị; sau khi IoT đi vào hoạt động thì dựa vào thông tin bóng thiết bị

để lấy thông tin hiện tại của thiết bị, control sẽ đăng ký nhận thông tin bản sao trạng thái của thiết bị, nhưng thông tin này được dịch vụ bản sao trạng thái (gọi tắt là IoT) chuyển tiếp tới, chứ control không trực tiếp tiếp nhận báo cáo bản sao trạng thái từ thiết bị. Chi tiết luồng xử lý được trình bày trong hai hình dưới đây:

Ứng dụng (control) lấy trạng thái thiết bị

Ứng dụng(control)gửi lệnh xuống thiết bị

 

Sau khi biết control không nhận được thông điệp shadow, tôi lập tức vào bảng điều khiển rabbitmq để kiểm tra xem có thông điệp hay không, nhằm xác định hai việc:1.Thiết bị có gửi thông điệp lên hay không 2.rabbitmq có hoạt động bình thường hay không, hình 1、hình 2 bên dưới là hai ảnh chụp bảng điều khiển rabbitmq lúc đó, từ Hình 1 Có thể xác định rất rõ rằng thiết bị có gửi thông điệp lên, nhưng nhiều thông điệp đang ở trạng thái unacked(chưa được xác nhận Cho thấy thông điệp đã được chuyển đến bên tiêu thụ, chỉ là bên tiêu thụ chưa ack; về lý thuyết, chờ một thời gian là có thể trở lại bình thường), để biết cụ thể hàng đợi nào đang tồn đọng thông điệp unacked, vui lòng xem Hình 2,“spacebridgeiot-shadow” chính là thứ chúng tôi dùng để nhận thông tin bản sao trạng thái do thiết bị gửi lên, các thông điệp đều dồn vào hàng đợi nên việc không được chuyển tiếp đến control cũng hợp lý, sau khi quan sát một thời gian thì thấy số lượng unacked giảm xuống 0, nhưng tổng số total lại không thay đổi nhiều, tạo cảm giác như các thông điệp unacked đã quay lại hàng đợi để chờ được chuyển giao, quả nhiên vài phút sau lại xuất hiện rất nhiều thông điệp unacked, vài phút tiếp theo số thông điệp unacked này lại quay về hàng đợi, phía control vẫn không nhận được thông điệp, lúc này kiểm tra nhật ký phía IoT thì phát hiện không hề có thông điệp bản sao trạng thái nào đi vào, trong bảng điều khiển rabbitmq xem mục “spacebridgeiot-shadow” Hàng đợi này vậy mà không còn bên tiêu thụ nào, như Hình 3 như minh họa. Lúc này kiểm tra nhật ký của rabbitmq thì quả thực có thông tin lỗi, như Hình 4 như minh họa, rabbitmq đã chủ động đóng kết nối.

Hình 1:Sơ đồ tổng quan rabbitmq

 

 

Hình 2:Biểu đồ thống kê hàng đợi rabbitmq

 

 

Hình3:Tổng quan về spacebridgeiot-shadow

 

 

 Hình 4:Thông báo lỗi rabbitmq

 

Giải pháp tạm thời:

Vì lúc đó đã có rất nhiều khiếu nại gửi đến, nên chúng tôi áp dụng một biện pháp khá mạnh tay là “xóa các tin nhắn tồn đọng”, xóa xong quả nhiên hoạt động bình thường trở lại(Ghi chú:Sự cố trên môi trường vận hành phải được giải quyết sớm nhất có thể, không có thời gian để chúng tôi phân tích nhật ký rồi giải quyết một cách tuần tự, nhất định phải nhanh)。

 

Biểu trưng kỹ thuật RabbitMQ

Tái hiện vấn đề trong môi trường ngoài hệ thống vận hành thực tế:

1.Dồn một lượng lớn thông điệp(ít nhất hàng chục nghìn)vào hàng đợi “mirrorTestQueue” của rabbitmq trong môi trường 10.200.41.166

2.Dừng bên tiêu thụ của mirrorTestQueue, đợi thông điệp tích đọng xong rồi khởi động lại

3.Tích lũy xong, khởi động lại consumer

 

Đúng như chúng tôi dự tính, trong vài giây đã có hàng nghìn thông điệp được đẩy đến bên tiêu thụ. Sau khi tiếp diễn vài phút, RabbitMQ chủ động đóng kết nối với bên tiêu thụ, lúc này trên bảng điều khiển không còn thấy bên tiêu thụ của hàng đợi. Vì bên tiêu thụ của chúng tôi đã được thiết lập tự động khôi phục nên một lúc sau sẽ tự kết nối lại,

Nhưng rất nhanh lại bị ngắt kết nối, về cơ bản giống hệt vấn đề chúng tôi gặp trên môi trường production. Rốt cuộc điều gì đã gây ra vấn đề này? Thật lòng mà nói, lúc ấy tôi chẳng có hướng nào, tìm một vòng trên mạng cũng không thấy câu trả lời nào thật sự thỏa đáng(Lúc đó chưa nắm được bản chất vấn đề, từ khóa tìm kiếm quá chung chung),về sau

Chúng tôi đoán có thể đã xảy ra vấn đề ở tầng TCP, nên quyết định thử bắt gói tin để xem có tìm được manh mối nào không. Quả nhiên, may mắn đã đến, không nói nhiều nữa, xem hình luôn.

 

 

 Trước 13:06:25.643428, rabbitmq vẫn liên tục đẩy thông điệp cho bên tiêu thụ, đến thời điểm 13:06:25.643428 mới bắt đầu xuất hiện tình trạng cửa sổ tcp của bên tiêu thụ bị lấp đầy, kéo dài khoảng 30 giây thì rabbitmq chủ động ngắt kết nối(gửi một gói rst), sau đó bên tiêu thụ kết nối lại, rồi cửa sổ lại tiếp tục bị lấp đầy, kéo dài thêm khoảng 30 giây rồi lại bị ngắt kết nối.

 

Cảm thấy khá có quy luật, mỗi lần kéo dài 30s, có vẻ là một tham số có thể cấu hình, tóm tắt đại khái là “tcp full window khiến phía máy chủ chủ động rst kết nối, hơn nữa còn có quy luật”

Lần này tôi đổi từ khóa tìm kiếm và tìm được Đáp án,rabbitmq có một tham số tên là tcp_listen_options.send_timeout là một tham số dùng để kiểm soát thời gian chờ ghi, khi ghi quá thời gian chờ sẽ kích hoạt RST của tcp(https://github.com/rabbitmq/rabbitmq-java-client/issues/341), sửa lại thử xem hiệu quả thế nào:

1. Đổi thời gian chờ ghi thành 10s

tcp_listen_options.send_timeout = 10000

 2.Bắt gói tin để xem có tác dụng hay không

 

 Từ lúc cửa sổ đầy đến khi ngắt kết nối kéo dài khoảng 10s.

 

Nhìn lại hiện tượng:

Do phía tiêu thụ của rabbitmq không thiết lập prefetch nên rabbitmq đã gửi quá nhiều thông điệp đến phía tiêu thụ trong một lần, khiến cửa sổ tcp của phía tiêu thụ bị chiếm đầy, từ đó kích hoạt tcp_listen_options.send_timeout của rabbitmq, khi thời gian chờ ghi này đạt một ngưỡng nhất định sẽ khiến rabbitmq ngắt kết nối tcp của bên tiêu thụ.

 

Giải pháp cuối cùng:

        Việc xóa thông điệp trước đó chỉ là giải pháp bất đắc dĩ; tuy giải quyết được vấn đề nhưng quá mạnh tay, nên chúng ta cần tìm một giải pháp thanh nhã hơn. Vì quá nhiều thông điệp được đẩy tới bên tiêu thụ khiến cửa sổ tcp bị lấp đầy, chúng ta nên tập trung vào tốc độ nhận, chỉ cần báo cho rabbitmq khi kết nối rằng đừng gửi quá nhiều thông điệp cho mình là được.(Đoạn văn phía sau được trích từ https://blog.csdn.net/james_searcher/article/details/70308565)rabbitmq có một thuộc tính tên là prefetch, chỉ số lượng thông điệp chưa được xác nhận(unacked messages)tối đa mà một bên tiêu thụ có thể tiêu thụ. Hiểu thế nào? mq thiết lập một vùng đệm cho mỗi consumer, với kích thước chính là prefetch. Mỗi khi nhận một thông điệp, MQ đẩy thông điệp vào vùng đệm, sau đó đẩy tiếp đến máy khách. Khi nhận được một thông điệp ack(consumer gửi lệnh baseack), mq giải phóng một vị trí trong vùng đệm, rồi thêm thông điệp mới. Nhưng nếu lúc này vùng đệm đầy, MQ sẽ chuyển sang trạng thái bị chặn. Cụ thể hơn, giả sử giá trị prefetch được đặt là 10, có tổng cộng hai consumer. Nghĩa là mỗi consumer sẽ lấy trước 10 thông điệp từ queue vào bộ nhớ đệm cục bộ mỗi lần để chờ tiêu thụ. Đồng thời số unacked của channel này trở thành 20. Thứ tự Rabbit chuyển giao là chuyển đủ 10 thông điệp cho consumer1 trước, rồi chuyển 10 thông điệp cho consumer2. Nếu lúc này có thông điệp mới cần chuyển giao, trước tiên sẽ kiểm tra xem số unacked của channel có bằng 20 hay không; nếu có thì sẽ không chuyển thông điệp đến consumer, thông điệp tiếp tục nằm trong queue. Sau đó, một consumer xác nhận ack cho một thông điệp, lúc này unacked bằng 19, Rabbit sẽ kiểm tra consumer nào có số unacked ít hơn 10 rồi chuyển giao cho consumer đó.

 

Cụ thể trong mã thì là

 

 

Đánh giá giá trị này như thế nào, RabbitMQ có một bài viết chính thức giải thích rất hay nên tôi không nói chi tiết nữa, hiện hệ thống của chúng tôi đặt là 20.

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

 

 

Lời kết:

Chúng ta hiện vẫn ở giai đoạn mới học sử dụng rabbitmq, nên trước khi dùng nhất định phải đọc kỹ API của rabbitmq, làm quen với cách dùng các API thường gặp và thử nghiệm nhiều trong môi trường cục bộ.


Khu vực bình luận

Đang gửi

Chọn giao diện yêu thích

Giao diện đã chọn áp dụng cho toàn trang.