Ngiêm cấm sử đụng PROXY và VPS vào mục đích trái pháp luật bạn sẽ phải chịu toàn bộ trách nhiệm trước pháp luật khi sử dụng dịch vụ của chúng tôi tks!
RabbitMQ publisher confirms cho crawl: không mất task

RabbitMQ publisher confirms cho crawl: không mất task khi lỗi

Chuyên viên Kỹ thuật ProxyVN 03-01-2026 Đang tính... phút đọc

RabbitMQ publisher confirms cho crawl là cách để bên đẩy URL biết broker đã nhận task hay chưa, rồi kết hợp với consumer ack để task không rơi giữa đường. Hai cơ chế này giải hai nửa khác nhau của bài toán. ProxyVN chạy thư viện pika thật trên một broker mô phỏng tự viết, cho broker và worker chết ở từng thời điểm, rồi ghi lại timeline.

RabbitMQ publisher confirms cho crawl: mục tiêu và phạm vi không lặp

Phần này chốt bài toán, đầu ra và giới hạn của lab.

Bài toán cần giải

Một crawler đưa URL vào RabbitMQ, nhiều worker lấy ra và tải. Sau một lần khởi động lại broker, số trang tải được ít hơn số URL đã đẩy. Lần khác, cùng một trang lại nằm hai lần trong kho. Nhóm cần biết task mất ở đâu, trùng ở đâu, và phải sửa chỗ nào.

Đầu ra nghiệm thu

Đầu ra là timeline publish, confirm, deliver, ack cho từng ca lỗi. Kèm theo là một bảng cho biết chết ở thời điểm nào thì mất task, thời điểm nào thì trùng task. Phần code gồm outbox phía publisher và bảng done phía worker.

Ranh giới với bài Celery

Bài Celery nói về API của framework task, ví dụ gọi delay và cấu hình worker. Bài này đi xuống tầng giao thức, nói chuyện giao nhận giữa client và broker. Anh em dùng Celery thì khái niệm vẫn đúng, nhưng tên cấu hình sẽ khác và bài không bàn tới.

Giới hạn của lab

Đây là điểm phải nói trước. Lab không chạy RabbitMQ thật. Máy lab không tải được máy chủ từ bên ngoài, nên ProxyVN viết một broker AMQP 0-9-1 rất nhỏ bằng Python, dùng bộ mã frame của pika. Broker này chỉ có handshake, channel, confirm, khai báo queue, publish, consume, ack và nack.

Thứ gì thật, thứ gì giả

Client pika 1.4.4 là thật, nói giao thức thật qua socket thật. Worker là tiến trình riêng, bị giết thật bằng os._exit. Còn quy tắc giữ và trả lại thông điệp là do ProxyVN viết theo tài liệu RabbitMQ. Broker mô phỏng không có đĩa, không có cluster, không có quorum queue.

Vậy kết quả có giá trị tới đâu

Lab chứng minh mã client của anh em phản ứng đúng với từng tín hiệu: có confirm, không có confirm, nack, return, redelivered. Nó không chứng minh RabbitMQ thật ghi đĩa nhanh chậm ra sao. Mọi con số về độ bền và độ trễ, anh em phải đo trên broker thật của mình.

Ai nên đọc bài này

Người đang vận hành crawler phân tán dùng RabbitMQ. Người thấy số task đẩy vào không khớp số trang tải được. Người định dùng auto_ack cho nhanh mà chưa biết cái giá của nó.

Chống mất task crawl bằng RabbitMQ publisher confirms cho crawl và consumer ack

Phần này tóm tắt tài liệu chính thức, vì nhầm hai khái niệm là nguồn gốc của hầu hết lỗi.

Hai cơ chế, hai nửa đường

Hai nửa đường

Hai nửa đường

Trang Consumer Acknowledgements and Publisher Confirms viết rõ publisher confirm chỉ nói về liên lạc giữa publisher và broker, không biết gì về consumer. Consumer ack thì không biết gì về publisher. Hai cơ chế độc lập, nên có cái này không thay được cái kia.

Bảng: Hai cơ chế, hai nửa đường

Cơ chế

Ai nói với ai

Trả lời câu hỏi

Publisher confirm

Broker báo publisher

Broker đã nhận trách nhiệm giữ task chưa

Consumer ack

Consumer báo broker

Task đã được xử lý xong, xóa được chưa

Mandatory và return

Broker trả lại publisher

Task có vào được queue nào không

Prefetch

Consumer đặt giới hạn

Được giữ bao nhiêu task chưa ack

Vì sao thiếu confirm là mất âm thầm

Publish trong AMQP không chờ trả lời. Hàm basic_publish chạy xong chỉ nói rằng byte đã ghi ra socket. Broker chết ngay sau đó thì publisher không biết gì. Tài liệu nói muốn bảo đảm giữ được thông điệp thì client nên dùng confirm.

Confirm được gửi khi nào

Tài liệu nêu với thông điệp persistent đi vào durable queue, broker gửi basic.ack sau khi đã ghi xuống đĩa. RabbitMQ ghi theo lô, cách nhau vài trăm mili giây, để giảm số lần fsync. Vậy nên publisher chờ confirm từng thông điệp một sẽ chậm, và tài liệu khuyên xử lý theo lô.

Điều kiện đủ để giữ được

Confirm chỉ có ý nghĩa khi cả hai vế đúng: queue là durable và thông điệp có delivery_mode bằng 2. Tài liệu cũng cảnh báo node có thể mất thông điệp persistent nếu lỗi trước khi ghi xong, trong khi client chưa dùng confirm. Quên một vế, task vẫn bay hơi khi broker khởi động lại.

Consumer ack hoạt động thế nào

Với ack thủ công, broker giữ thông điệp cho tới khi nhận basic.ack. Tài liệu nêu thông điệp chưa ack sẽ tự động được đưa lại queue khi channel hoặc connection đóng, gồm cả mất TCP và tiến trình consumer chết. Việc phát hiện client mất cũng tốn một khoảng thời gian.

Cờ redelivered

Tài liệu nêu thông điệp giao lại có cờ redelivered bằng true, lần đầu là false. Cờ này chỉ là gợi ý. Nó không nói lần trước đã làm tới đâu. Thông điệp cũng có thể đã từng giao cho một consumer khác, nên worker không thể dựa vào bộ nhớ riêng của mình.

Câu quan trọng nhất trong tài liệu

Tài liệu kết luận consumer phải chuẩn bị cho việc giao lại và phải được thiết kế theo hướng idempotent. Nói cách khác, RabbitMQ giao ít nhất một lần. Không có cấu hình nào của broker thay được việc worker tự chịu trùng.

Delivery tag có phạm vi channel

Tài liệu nêu delivery tag tăng dần và gắn với channel. Ack bằng tag trên channel khác sẽ gặp lỗi giao thức unknown delivery tag. Với crawler đa luồng, đừng giữ tag rồi ack từ một channel khác. Tag cũng không phải mã task, vì lần giao lại có thể mang tag khác.

Prefetch và công bằng giữa worker

Prefetch giới hạn số thông điệp chưa ack trên một channel. Không đặt, một worker có thể ôm rất nhiều task rồi chết, kéo theo tất cả phải giao lại. Tải trang chậm và thời gian dao động lớn, nên prefetch nhỏ là hợp lý. Lab dùng prefetch 1 để timeline dễ đọc.

Bật RabbitMQ publisher confirms cho crawl và lưu task ID trước publish

Phần này là mã publisher, chỗ giải quyết nửa đầu của bài toán.

Bật confirm trong pika

Với BlockingConnection, chỉ một dòng confirm_delivery. Sau đó basic_publish sẽ chờ phản hồi và ném ngoại lệ nếu không thành công. Anh em dùng mandatory để bắt trường hợp không có queue nào nhận.

import json, pika
from pika import exceptions as X
 
c = pika.BlockingConnection(pika.ConnectionParameters("127.0.0.1", 9475))
ch = c.channel()
ch.confirm_delivery()
ch.queue_declare("crawl", durable=True)
 
ch.basic_publish(
    "", "crawl",
    json.dumps({"task_id": "t1", "url": "http://s/t1"}),
    pika.BasicProperties(delivery_mode=2, message_id="t1"),
    mandatory=True,
)

Đoạn trên chạy thật với pika 1.4.4 trên broker mô phỏng. Cổng 9475 là của lab, RabbitMQ mặc định dùng 5672.

Ca không bật confirm

Lab gửi vào một queue chưa tồn tại, không confirm, không mandatory. Hàm basic_publish chạy xong không báo gì, queue trống, phía broker mô phỏng ghi nhận bỏ qua. Ở phía publisher, ca này nhìn y như thành công. Đây là kiểu mất task khó tìm nhất, vì không có dòng lỗi nào.

Ca confirm cộng mandatory

Cũng queue đó, có confirm và mandatory, pika ném UnroutableError với thông báo một thông điệp bị trả về. Đây là tín hiệu đúng. Anh em bắt ngoại lệ, giữ task lại, báo cấu hình sai tên queue.

Vì sao cần outbox

Confirm cho biết kết quả của từng lần gửi. Nó không giúp nếu chính publisher chết giữa chừng. Muốn không mất URL, phải ghi task xuống chỗ bền trước khi publish, rồi chỉ đánh dấu xong khi có confirm. Đó là mẫu outbox.

out.execute("""create table outbox(
  task_id text primary key, url text,
  state text default 'new', tries int default 0)""")
 
def pub_all(q):
    c = conn()
    ch = c.channel()
    ch.confirm_delivery()
    ch.queue_declare(q, durable=True)
    rows = out.execute("select task_id,url from outbox where state='new'").fetchall()
    for tid, url in rows:
        out.execute("update outbox set tries=tries+1 where task_id=?", (tid,))
        try:
            ch.basic_publish("", q, json.dumps({"task_id": tid, "url": url}),
                pika.BasicProperties(delivery_mode=2, message_id=tid), mandatory=True)
            out.execute("update outbox set state='confirmed' where task_id=?", (tid,))
        except (X.NackError, X.UnroutableError, X.AMQPError):
            return

Hàm này là mã lab, rút gọn phần đếm và đóng kết nối. Mã task sinh ra trước khi gửi và đi theo thông điệp suốt đường, nên về sau đối soát được.

Mã task lấy từ đâu

Mã task nên suy ra từ nội dung, ví dụ băm URL đã chuẩn hóa cộng lượt crawl. Sinh mã ngẫu nhiên mỗi lần publish sẽ làm hai bản trùng có hai mã khác nhau, và phía worker không khử trùng được. Quy tắc này quyết định mọi thứ ở phần sau.

Ca broker chết trước khi lưu

Broker mô phỏng đóng kết nối ngay khi nhận thông điệp, chưa lưu. pika ném StreamLostError. Outbox giữ t1 ở trạng thái new, tries bằng 1. Lần chạy thứ hai gửi lại t1 và t2, cả hai được confirm. Task không mất.

Bảng: Ca broker chết trước khi lưu

Lượt

Confirmed

Trong queue

Trạng thái outbox

Lần 1

0

0

t1 new, tries 1; t2 new, tries 0

Lần 2

2

2

t1 confirmed, tries 2; t2 confirmed, tries 1

Ca broker lưu xong rồi chết trước confirm

Không rõ đã lưu hay chưa

Không rõ đã lưu hay chưa

Ca này đau hơn. Broker mô phỏng lưu task, rồi đóng kết nối trước khi gửi confirm. Publisher nhận StreamLostError, không biết task đã vào hay chưa, nên gửi lại. Queue có hai bản t1. Không có cách nào phân biệt ca này với ca trước từ phía publisher.

Kết luận cho phía publisher

Phía publisher chỉ có hai lựa chọn: không gửi lại và có thể mất, hoặc gửi lại và có thể trùng. Với crawl, chọn gửi lại. Trùng thì xử lý được, mất thì không biết mà xử lý. Vì thế confirm luôn đi kèm khử trùng ở worker.

Ca broker nack

Tài liệu nêu broker có thể gửi basic.nack khi không nhận được trách nhiệm giữ thông điệp. Lab cho broker mô phỏng trả nack, pika ném NackError, outbox giữ new. Anh em đối xử với nack như một lỗi tạm: chờ một lúc rồi gửi lại, kèm trần số lần thử.

Gửi theo lô

Chờ confirm từng thông điệp có độ trễ cộng dồn. Tài liệu gợi ý publish theo lô rồi chờ các confirm còn treo. BlockingConnection chờ từng cái nên dễ hiểu nhưng chậm. Khi cần thông lượng cao, chuyển sang SelectConnection với callback theo delivery tag. Lab không đo phần này.

Phân biệt broker confirm với consumer ack trong RabbitMQ publisher confirms cho crawl

Phần này đi vào worker và đọc timeline của ca bình thường.

Worker tối thiểu

Worker bật ack thủ công, đặt prefetch, và chỉ ack sau khi đã lưu kết quả. Thứ tự này là cả câu chuyện.

ch.basic_qos(prefetch_count=1)
 
def cb(ch, m, p, b):
    t = json.loads(b)
    effect(t)                     # tai trang, luu ket qua
    ch.basic_ack(m.delivery_tag)  # chi ack sau khi da luu
 
ch.basic_consume("crawl", cb, auto_ack=False)
ch.start_consuming()

Hàm effect trong lab ghi một dòng vào SQLite thay cho việc tải trang, để dễ đếm số lần chạy.

Timeline ca bình thường

Ba task đi trọn đường. Mỗi task có bốn mốc, và mỗi mốc do một bên khác nhau xác nhận.

Bảng: Timeline ca bình thường

Mốc

Bên phát sinh

Ý nghĩa

luu t1

Broker

Task đã nằm trong queue

publisher_confirm 1

Broker tới publisher

Publisher được đánh dấu xong

deliver tag 1, redelivered False

Broker tới worker

Worker bắt đầu làm

consumer_ack 1

Worker tới broker

Broker xóa task

Kết quả: t1, t2, t3 mỗi task chạy một lần, queue về 0.

Nhầm lẫn hay gặp

Publisher thấy confirm rồi báo trang đã tải xong. Sai. Confirm chỉ nói task đã vào queue. Muốn biết trang đã tải, phải nhìn kho kết quả. Dashboard nên có hai số riêng: số task confirmed và số task done.

Hai trạng thái trong outbox

Outbox nên dừng ở confirmed. Trạng thái done nằm ở phía worker. Trộn hai thứ vào một cột sẽ tạo ra phụ thuộc vòng: worker phải quay về sửa bảng của publisher. Đối soát cuối ngày chỉ cần so hai tập mã task.

Tại sao không dùng auto_ack

Với auto_ack, broker coi thông điệp xong ngay khi gửi đi. Tài liệu gọi đây là chế độ fire and forget, đánh đổi độ an toàn để lấy thông lượng. Lab cho worker auto_ack chết ngay khi nhận t1: queue về 0, kết quả rỗng, task mất hẳn. Với crawl, tải trang chậm hơn giao thông điệp rất nhiều, nên thông lượng thêm đó không đáng.

Nack và requeue

Khi tải lỗi tạm thời, worker có thể gọi basic_nack với requeue true. Tài liệu cảnh báo thông điệp requeue có thể quay lại ngay, tạo vòng lặp giao lại tốn tài nguyên. Một URL hỏng sẽ quay mãi. Anh em nên đếm số lần thử trong header hoặc kho riêng, rồi đưa sang queue lỗi.

Hàng đợi lỗi cho URL hỏng

Với URL lỗi vĩnh viễn như 404, không nên requeue. Gọi nack với requeue false và cấu hình dead letter exchange để thông điệp sang queue lỗi. Lab không có dead letter vì broker mô phỏng không làm phần này, nên bài chỉ nêu hướng, không có kết quả chạy.

Timeout khi giữ thông điệp quá lâu

Tài liệu có nhắc RabbitMQ áp thời hạn ack cho consumer. Task tải dài hơn thời hạn đó thì kênh có thể bị đóng và thông điệp trả lại queue. Crawler có trang rất chậm nên kiểm tra cấu hình này trên broker thật. Lab không mô phỏng thời hạn, nên bài không nêu con số.

Cho worker dừng trước và sau ack trong RabbitMQ publisher confirms cho crawl

Phần này là nơi lab có giá trị nhất, vì nó cho thấy thứ tự thao tác quyết định kết quả.

Cách giết worker

Mỗi worker là một tiến trình Python riêng. Lab gọi os._exit(9) ở đúng điểm cần kiểm tra, nên không có khối finally nào chạy, không có đóng kết nối tử tế. Với broker mô phỏng, socket mất, và thông điệp chưa ack được đưa lại đầu queue.

Ca worker chết sau khi lưu, trước khi ack

Ack sau khi lưu

Ack sau khi lưu

Worker nhận t1, lưu kết quả, rồi chết. Broker đưa t1 lại queue. Worker thứ hai nhận t1 với redelivered bằng True, lưu lần nữa, rồi ack. Bảng kết quả có t1 hai lần.

worker die_before_ack exit=9: nhan task=t1 tag=1 redelivered=False
queue 1, ket qua [('t1', 1)]
worker normal exit=0: nhan task=t1 tag=1 redelivered=True
ket qua [('t1', 2)]
timeline: deliver 1 False, requeue 1, deliver 1 True, consumer_ack 1

Đây là output của lab. Tag ở hai lần đều là 1 vì mỗi worker có channel mới, đúng như việc tag gắn với channel.

Ca worker chết sau khi ack, trước khi lưu

Worker ack trước cho nhanh, rồi chết. Broker đã xóa t1. Queue chỉ còn t2. Worker kế tiếp làm t2, và t1 biến mất khỏi hệ thống mà không để lại dấu vết nào.

worker early_ack_die exit=9: nhan task=t1 tag=1 redelivered=False
queue 1, ket qua []
worker normal exit=0: nhan task=t2 tag=1 redelivered=True
ket qua [('t2', 1)]

Để ý t2 cũng có redelivered True dù chưa ai xử lý nó. Trong lab, worker đầu đã nhận trước t2 rồi chết trước khi đụng tới. Cờ này không có nghĩa là task đã được làm một phần.

Giữ thông điệp nhỏ

Thông điệp chỉ nên chứa mã task, URL và vài tham số. Đừng nhét HTML vào queue. Trang đã tải nên nằm ở kho tệp, còn thông điệp mang đường dẫn tới tệp đó. Thông điệp nhỏ thì ghi đĩa nhanh, confirm về nhanh, và queue không ngốn bộ nhớ khi worker chậm.

Task dài và heartbeat

Với BlockingConnection, callback chạy lâu sẽ chặn vòng lặp của pika, nên client không trả lời được heartbeat. Trang chậm vài phút có thể làm broker cắt kết nối, kéo theo task bị giao lại dù worker vẫn đang làm. Lab đặt heartbeat bằng 0 để timeline gọn, nên không thử ca này. Khi triển khai, anh em đưa phần tải sang luồng riêng và ack trở lại bằng add_callback_threadsafe, hoặc đặt timeout tải ngắn hơn heartbeat.

Dừng worker tử tế khi triển khai

Mỗi lần cập nhật mã, worker bị dừng giữa lúc đang có task. Nếu chỉ kill, task đó được giao lại và làm từ đầu. Cách dừng gọn là bắt tín hiệu, gọi stop_consuming, làm nốt task hiện tại, ack rồi mới thoát. Prefetch nhỏ giúp bước này kết thúc nhanh, vì worker không ôm nhiều task chưa làm.

Bảng chết ở đâu thì ra sao

Bảng này gom các ca đã chạy. Kết quả phản ánh quy tắc trong tài liệu, được mô phỏng lại, nên anh em cần thử lại trên broker thật.

Bảng: Bảng chết ở đâu thì ra sao

Thời điểm chết

Cấu hình

Kết quả

Hướng xử lý

Broker chết trước khi lưu

Confirm + outbox

Gửi lại, không mất

Giữ nguyên

Broker chết sau lưu, trước confirm

Confirm + outbox

Trùng 1 bản

Khử trùng ở worker

Worker chết sau lưu, trước ack

Ack thủ công

Trùng 1 lần xử lý

Khử trùng ở worker

Worker chết sau ack, trước lưu

Ack sớm

Mất task

Ack sau khi lưu

Worker chết khi vừa nhận

auto_ack

Mất task

Bỏ auto_ack

Quy tắc ngắn gọn

Ack đặt sau side effect thì lỗi biến thành trùng. Ack đặt trước side effect thì lỗi biến thành mất. Chọn đặt sau, rồi làm side effect chịu được trùng. Hai dòng này đáng dán lên đầu tệp worker.

Đối soát redelivery và side effect trùng với RabbitMQ publisher confirms cho crawl

Phần này giải nốt nửa còn lại: làm sao chịu được trùng.

Bảng done

Cách đơn giản nhất là bảng có khóa chính task_id. Worker thử chèn trước, chèn được mới làm, không chèn được thì chỉ ack.

def cb(ch, m, p, b):
    t = json.loads(b)
    cur = db.execute("insert or ignore into done(task_id) values(?)", (t["task_id"],))
    if cur.rowcount:
        effect(t)
    ch.basic_ack(m.delivery_tag)

Với ca broker gửi hai bản t1, worker in ra dòng đã xử lý trước đó, chỉ ack, và kết quả là t1 một lần. Đây là output thật của lab.

Lỗ hổng của cách trên

Đoạn trên chèn done trước khi effect chạy. Worker chết giữa hai dòng thì done có t1 nhưng kết quả không có, lần giao lại sẽ bỏ qua và task mất. Lỗ hổng này nguy hiểm vì nó xóa đi đúng lợi ích của ack sau.

Cách đúng hơn

Đưa done và kết quả vào cùng một giao dịch, hoặc viết kết quả theo kiểu ghi đè bằng khóa task_id. Với kho dữ liệu hỗ trợ upsert, chỉ cần một câu ghi có khóa ổn định, không cần bảng done riêng. Lần thứ hai ghi đè giá trị giống hệt, không có gì sai.

INSERT INTO result(task_id, url, body_hash, fetched_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(task_id) DO UPDATE SET
  body_hash = excluded.body_hash,
  fetched_at = excluded.fetched_at;

Câu này là ví dụ triển khai cho SQLite, ProxyVN không chạy nó trong lab. Điểm chính là một câu lệnh, một khóa.

Side effect không gom được vào giao dịch

Tải trang là side effect ra bên ngoài, không thể hoàn tác. Hai lần tải cùng URL là hai request tới website người ta. Với crawl thông thường, tải trùng hiếm gặp vẫn chấp nhận được. Nhưng nếu task là gửi biểu mẫu hay gọi API có tính phí, phải có khóa idempotency ở phía dịch vụ, và đó là việc nằm ngoài phạm vi broker.

Dùng cờ redelivered thế nào cho đúng

Cờ false chưa chắc là lần đầu: bản trùng do publisher gửi lại cũng có cờ false, như ca hai bản t1 trong lab. Vì thế đừng dùng cờ này để quyết định có khử trùng hay không. Hãy luôn kiểm tra theo task_id. Cờ chỉ nên dùng để ghi số liệu theo dõi.

Đối soát cuối ngày

Đối soát

Đối soát

Mỗi ngày so ba tập mã: task trong outbox, task trong done, và task trong queue lỗi. Mã có trong outbox mà không có ở hai tập kia, sau một khoảng đủ lâu, là task mất. Lab dùng đúng phép so này để tìm ra t1 trong ca ack sớm.

SELECT o.task_id FROM outbox o
LEFT JOIN done d ON d.task_id = o.task_id
WHERE o.state = 'confirmed' AND d.task_id IS NULL;

Câu lệnh này giả định hai bảng cùng cơ sở dữ liệu. Nếu tách riêng, anh em xuất mã ra rồi so bằng script.

Số liệu nên theo dõi

Theo dõi số task confirmed, số done, số lần redelivered, độ sâu queue, và tuổi task cũ nhất. Tỷ lệ redelivered tăng đột ngột thường là dấu hiệu worker chết hoặc task dài quá thời hạn. Tuổi task cũ nhất cho biết hàng đợi có tắc không, chính xác hơn độ sâu queue.

Đầu ra nghiệm thu của RabbitMQ publisher confirms cho crawl

Phần này gom tiêu chí để biết đã làm xong.

Danh sách kiểm tra

Hệ thống đạt khi mọi dòng dưới đây đúng.

  • Queue là durable, thông điệp có delivery_mode 2.
  • Publisher bật confirm và mandatory, không gửi task nào chưa có trong outbox.
  • Mã task ổn định, suy ra từ nội dung.
  • Worker dùng ack thủ công, ack sau khi đã lưu kết quả.
  • Lưu kết quả là thao tác ghi đè được theo mã task.
  • Prefetch có giá trị cụ thể, không để mặc định không giới hạn.
  • Có queue lỗi và trần số lần thử.
  • Có đối soát outbox với kết quả.

Thứ tự triển khai từng bước

Anh em đang có hệ thống chạy thì sửa theo thứ tự này, mỗi bước kiểm tra xong mới sang bước sau.

  • Chuyển queue sang durable và thêm delivery_mode 2 cho thông điệp.
  • Sinh mã task ổn định, đưa vào message_id và phần thân thông điệp.
  • Đổi lưu kết quả thành ghi đè theo mã task.
  • Tắt auto_ack, đặt prefetch, chuyển ack xuống sau bước lưu.
  • Bật confirm và mandatory ở publisher, thêm outbox.
  • Thêm queue lỗi, trần số lần thử, và đối soát hằng ngày.

Bước ghi đè phải đi trước bước đổi ack. Làm ngược lại, số bản trùng sẽ tăng ngay khi worker bắt đầu ack muộn.

Thử nghiệm phá hỏng nên chạy trên môi trường thật

Trên broker thật, anh em chạy lại bốn ca: tắt broker giữa lúc đẩy, tắt mạng giữa lúc confirm, kill worker trước ack, kill worker sau ack. Lần nào cũng đếm số task vào và số task ra. Dùng cùng script publisher và worker, chỉ đổi cổng sang 5672.

Những gì lab chưa chạy

Chưa chạy RabbitMQ thật. Chưa có đĩa, chưa có quorum queue, chưa có cluster, chưa có dead letter exchange, chưa có thời hạn ack, chưa đo độ trễ confirm. Bài cũng chưa thử chế độ bất đồng bộ của pika. Tất cả cần kiểm trên hệ thống của anh em.

Quorum queue hay classic queue

Tài liệu độ tin cậy của RabbitMQ bàn về nhân bản dữ liệu giữa nhiều node. Crawl một node thì durable queue là đủ để bắt đầu. Khi mất task thật sự gây thiệt hại, cần xem xét quorum queue. Lab không kiểm chứng phần này nên bài không khuyến nghị cụ thể.

Kết nối lại khi mất mạng

BlockingConnection không tự kết nối lại. Worker nên bọc vòng lặp ngoài cùng, bắt lỗi AMQPConnectionError, chờ rồi tạo kết nối mới. Lab thấy lỗi StreamLostError nằm trong nhóm lỗi AMQP nên một khối except là đủ. Thời gian chờ nên tăng dần, tránh nhiều worker cùng nối lại một lúc.

Proxy nằm ở đâu trong sơ đồ

Proxy không đụng tới RabbitMQ. Nó thuộc về bước tải trang bên trong effect. Với website được phép thu thập, có lúc cần IP tại Việt Nam để nhận đúng nội dung trong nước. Khi đó anh em có thể mua proxy tại trang chủ ProxyVN. Proxy không giữ được task và không sửa lỗi ack. Bối cảnh dùng hàng đợi cho thu thập dữ liệu lớn có ở bài Proxy training AI, vai trò proxy khi cào dữ liệu ở bài Web Scraping proxy là gì.

Khi proxy lỗi thì ack hay không

Lỗi proxy là lỗi tạm. Worker nên nack hoặc đưa vào queue thử lại có độ trễ, đừng ack rồi coi như hỏng. Hãy tách ba nhóm lỗi: proxy hoặc mạng thì thử lại, website trả 4xx thì đưa sang lỗi, dữ liệu sai thì giữ để xem. Thông tin proxy nên nằm trong cấu hình worker, đừng nhét vào thông điệp, cách viết chuỗi cấu hình có ở bài định dạng proxy.

RabbitMQ publisher confirms cho crawl: câu hỏi thường gặp

Các câu dưới đây gom những điểm anh em hay hỏi.

Publisher confirm có nghĩa là trang đã được tải chưa?

Không. Confirm chỉ nói broker đã nhận trách nhiệm giữ task. Muốn biết đã tải, phải xem kho kết quả hoặc bảng done của worker.

Bật confirm rồi có cần consumer ack không?

Có. Tài liệu nói rõ hai cơ chế độc lập. Lab cho thấy có confirm mà worker dùng auto_ack thì task vẫn mất khi worker chết.

Task có thể bị xử lý hai lần dù cấu hình đúng không?

Có. Lab tái lập hai đường: publisher gửi lại khi mất confirm, và worker chết trước khi ack. Tài liệu cũng yêu cầu consumer được thiết kế idempotent.

Nên ack trước hay sau khi lưu kết quả?

Sau. Ack trước khiến lỗi trở thành mất task. Ack sau khiến lỗi trở thành trùng task, mà trùng thì khử được bằng mã task ổn định.

Cờ redelivered có đủ để khử trùng không?

Không đủ. Trong lab, bản trùng do publisher gửi lại có cờ false, và một task chưa ai xử lý vẫn có cờ true. Hãy khử trùng theo mã task.

Kết quả trong bài có đúng với RabbitMQ thật không?

Quy tắc lấy từ tài liệu chính thức, nhưng broker trong lab là mô phỏng. Client pika và phần giết tiến trình là thật. Anh em nên chạy lại trên broker của mình trước khi dựa vào.

RabbitMQ publisher confirms cho crawl và những điều cần nhớ

RabbitMQ publisher confirms cho crawl chỉ giải nửa đường từ publisher tới broker, nửa còn lại là consumer ack đặt sau khi lưu kết quả. Lab với pika cho thấy lỗi biến thành trùng, nên mã task ổn định và thao tác ghi đè là bắt buộc. Proxy.vn - Nhà cung cấp dịch vụ proxy chất lượng hàng đầu Việt Nam khuyên anh em kiểm lại RabbitMQ publisher confirms cho crawl trên broker thật.

TIN TỨC LIÊN QUAN