คิวเหตุการณ์และตัวกลางรับส่งข้อความ
คิว Redis, RabbitMQ และ Kafka สำหรับการสื่อสารระหว่างเอเจนต์แบบแยกส่วน
คิวเหตุการณ์และตัวกลางรับส่งข้อความ เป็นบทเรียน AI Agents ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Agents และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใดจึงใช้คิวข้อความกับเอเจนต์
คิวข้อความช่วยแยกตัวกระตุ้น (ผู้ผลิตเหตุการณ์) ออกจากเอเจนต์ (ผู้บริโภคเหตุการณ์) ผู้ผลิตสามารถส่งเหตุการณ์ด้วยอัตราใดก็ได้ ส่วนเอเจนต์จะประมวลผลตามความเร็วของตนเอง วิธีนี้ช่วยป้องกันการทำงานเกินกำลังและทำให้ลองดำเนินการซ้ำได้
รายการ Redis ในฐานะคิว
รายการ Redis สามารถทำงานเป็นคิวอย่างง่ายได้ โดย LPUSH เพิ่มข้อมูลที่ด้านหน้า (เข้าคิว) ส่วน BRPOP นำข้อมูลออกจากด้านหลังและหยุดรอหากคิวว่าง นี่คือคิวแบบเข้าก่อนออกก่อนที่เชื่อถือได้
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
QUEUE_NAME = 'agent:tasks'
# Producer: add a task to the queue
def enqueue_task(task: dict):
import json
r.lpush(QUEUE_NAME, json.dumps(task))
print(f'Enqueued task: {task["id"]}')
# Consumer: blocking pop (waits up to 5 seconds for a message)
def dequeue_task(timeout: int = 5):
import json
result = r.brpop(QUEUE_NAME, timeout=timeout)
if result:
queue_name, raw_data = result
return json.loads(raw_data)
return None # Timed out - no tasks
# Producer side
enqueue_task({'id': 'task-1', 'type': 'email', 'email_id': 'email-abc'})
enqueue_task({'id': 'task-2', 'type': 'email', 'email_id': 'email-def'})
# Check queue length
print(f'Queue length: {r.llen(QUEUE_NAME)}')ลูปผู้ปฏิบัติงานเอเจนต์ด้วย Redis
ผู้ปฏิบัติงานของเอเจนต์จะตรวจสอบคิวเพื่อหางานใหม่อย่างต่อเนื่อง ประมวลผลงานแต่ละรายการ แล้ววนซ้ำ หากไม่มีงานเข้ามาภายในเวลาที่กำหนด ก็จะวนซ้ำอีกครั้งโดยไม่ใช้ทรัพยากร
import redis
import json
import time
import logging
logger = logging.getLogger('worker')
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
def process_task(task: dict) -> bool:
task_type = task.get('type')
if task_type == 'email':
logger.info(f'Processing email: {task.get("email_id")}')
# Call email agent here
return True
elif task_type == 'file':
logger.info(f'Processing file: {task.get("filepath")}')
return True
else:
logger.warning(f'Unknown task type: {task_type}')
return False
def run_worker(queue_name: str = 'agent:tasks'):
print(f'Worker started, watching queue: {queue_name}')
while True:
try:
task = dequeue_task(timeout=5)
if task:
success = process_task(task)
if not success:
# Re-queue failed tasks for retry
r.lpush('agent:failed', json.dumps(task))
else:
logger.debug('No tasks, waiting...')
except KeyboardInterrupt:
print('Worker stopped')
break
except Exception as e:
logger.error(f'Worker error: {e}')
time.sleep(1) # Brief pause on unexpected error
print('Worker function defined')Redis Pub/Sub สำหรับเหตุการณ์
Redis Pub/Sub แตกต่างจากรายการ เพราะจะกระจายข้อความไปยังผู้สมัครรับข้อมูลปัจจุบันทั้งหมด ใช้ Pub/Sub สำหรับการแจ้งเตือนแบบกระจายหลายปลายทาง เมื่อเอเจนต์หลายตัวต้องตอบสนองต่อเหตุการณ์เดียวกัน
import redis
import json
import threading
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# Publisher: send event to all subscribers
def publish_event(channel: str, event: dict):
r.publish(channel, json.dumps(event))
print(f'Published to {channel}: {event}')
# Subscriber: listen for events in a background thread
def subscribe_and_handle(channel: str, handler_fn):
pubsub = r.pubsub()
pubsub.subscribe(channel)
def listener():
for message in pubsub.listen():
if message['type'] == 'message':
event = json.loads(message['data'])
handler_fn(event)
thread = threading.Thread(target=listener, daemon=True)
thread.start()
return thread
def handle_agent_event(event):
print(f'Agent received event: {event}')
# Start subscriber
subscribe_and_handle('agent:events', handle_agent_event)
# Publish an event
publish_event('agent:events', {'type': 'user_action', 'action': 'login', 'user_id': 42})
import time
time.sleep(0.1) # Give subscriber time to receiveพื้นฐาน RabbitMQ ด้วย pika
RabbitMQ เป็นตัวกลางข้อความที่มีความสามารถครบถ้วน พร้อมการกำหนดเส้นทาง การยืนยันการรับ และคิวสำหรับข้อความที่ส่งไม่สำเร็จ ไลบรารี pika ใช้เชื่อมต่อ Python กับ RabbitMQ
import pika
import json
# Connect to RabbitMQ
connection = pika.BlockingConnection(
pika.ConnectionParameters(
host='localhost',
port=5672,
credentials=pika.PlainCredentials('guest', 'guest')
)
)
channel = connection.channel()
# Declare a durable queue (survives broker restart)
channel.queue_declare(
queue='agent_tasks',
durable=True # Queue survives RabbitMQ restart
)
# Publish a message
def publish_task(task: dict):
channel.basic_publish(
exchange='',
routing_key='agent_tasks',
body=json.dumps(task),
properties=pika.BasicProperties(
delivery_mode=2, # Make message persistent
content_type='application/json'
)
)
print(f'Published task: {task["id"]}')
publish_task({'id': 'task-1', 'type': 'process_email', 'email_id': 'email-xyz'})
connection.close()ผู้บริโภค RabbitMQ พร้อมการยืนยันการรับ
ผู้บริโภคต้องยืนยันการรับข้อความหลังจากประมวลผลแล้ว หากเอเจนต์หยุดทำงานก่อนยืนยันการรับ RabbitMQ จะนำข้อความกลับเข้าคิวเพื่อให้ผู้บริโภครายอื่นประมวลผล วิธีนี้ช่วยให้มั่นใจได้ว่าจะไม่มีข้อความสูญหาย
import pika
import json
def create_consumer():
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost')
)
channel = connection.channel()
channel.queue_declare(queue='agent_tasks', durable=True)
# Process one message at a time (fair dispatch)
channel.basic_qos(prefetch_count=1)
def process_message(ch, method, properties, body):
task = json.loads(body)
print(f'Processing: {task["id"]}')
try:
# Do agent work here
print(f'Completed task: {task["id"]}')
# Acknowledge: message is removed from queue
ch.basic_ack(delivery_tag=method.delivery_tag)
except Exception as e:
print(f'Failed task {task["id"]}: {e}')
# Negative acknowledge + requeue=True: puts message back
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
channel.basic_consume(queue='agent_tasks', on_message_callback=process_message)
print('Consumer ready. Waiting for messages...')
channel.start_consuming()
print('RabbitMQ consumer defined')คิวข้อความที่ส่งไม่สำเร็จ
ข้อความที่ประมวลผลไม่สำเร็จหลายครั้งเกินไปควรถูกส่งไปยังคิวข้อความที่ส่งไม่สำเร็จ (DLQ) เพื่อให้ตรวจสอบด้วยตนเอง แทนที่จะลองซ้ำไปเรื่อย ๆ ตั้งค่า RabbitMQ ให้กำหนดเส้นทางข้อความที่ล้มเหลวไปยัง DLQ โดยอัตโนมัติ
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# Create the dead letter queue first
channel.queue_declare(queue='agent_tasks_dlq', durable=True)
# Create main queue with dead-letter exchange config
channel.queue_declare(
queue='agent_tasks',
durable=True,
arguments={
'x-dead-letter-exchange': '',
'x-dead-letter-routing-key': 'agent_tasks_dlq',
'x-message-ttl': 3600000, # Messages expire after 1 hour
'x-max-delivery-count': 3 # Max 3 delivery attempts
}
)
# Consumer: nack without requeue sends to DLQ
def careful_consumer(ch, method, properties, body):
import json
task = json.loads(body)
try:
# Process task
ch.basic_ack(delivery_tag=method.delivery_tag)
except Exception:
# Do NOT requeue - send to DLQ
ch.basic_nack(delivery_tag=method.delivery_tag, requeue=False)
print('Dead letter queue configured')
connection.close()รูปแบบคิวงาน
รูปแบบคิวงานช่วยแยกตัวกระตุ้นออกจากการดำเนินการ ผู้ผลิตที่ทำงานเพียงเล็กน้อยจะส่งคำอธิบายงานไปยังคิว ส่วนผู้ปฏิบัติงานจะดึงงานออกมาแล้วดำเนินการ ผู้ปฏิบัติงานหลายรายสามารถประมวลผลงานแบบขนานได้
import redis
import json
from datetime import datetime
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# Task descriptor - what needs to be done
def create_agent_task(task_type: str, params: dict, priority: int = 5) -> dict:
return {
'id': f'{task_type}-{int(datetime.utcnow().timestamp() * 1000)}',
'type': task_type,
'params': params,
'priority': priority,
'created_at': datetime.utcnow().isoformat(),
'retry_count': 0,
'max_retries': 3
}
# Enqueue with priority (multiple queues by priority)
def enqueue_with_priority(task: dict):
priority = task.get('priority', 5)
queue = f'agent:tasks:p{priority}'
r.lpush(queue, json.dumps(task))
print(f'Enqueued {task["id"]} to priority-{priority} queue')
# Worker dequeues from high-priority queue first
def dequeue_priority(timeout: int = 5):
for p in [1, 2, 3, 4, 5]: # Check priority 1 first
result = r.brpop(f'agent:tasks:p{p}', timeout=0.1)
if result:
return json.loads(result[1])
return None
enqueue_with_priority(create_agent_task('email_analysis', {'email_id': 'abc'}, priority=2))
enqueue_with_priority(create_agent_task('file_process', {'path': '/tmp/file.txt'}, priority=5))Celery: คิวงานระดับสูง
Celery เป็นคิวงาน Python ที่ได้รับความนิยมสูงสุด รองรับ Redis และ RabbitMQ ในฐานะตัวกลาง รองรับการลองงานซ้ำโดยอัตโนมัติ การกำหนดเส้นทางงาน และการติดตามด้วย Flower ใช้ Celery สำหรับภาระงานของเอเจนต์ในระบบจริง
from celery import Celery
import os
# Create Celery app with Redis broker
app = Celery(
'agent_tasks',
broker=os.environ.get('REDIS_URL', 'redis://localhost:6379/0'),
backend=os.environ.get('REDIS_URL', 'redis://localhost:6379/0')
)
# Configure retry behavior
app.conf.update(
task_acks_late=True,
task_reject_on_worker_lost=True,
task_serializer='json',
result_expires=3600
)
@app.task(bind=True, max_retries=3, default_retry_delay=60)
def run_email_agent(self, email_id: str):
try:
# Agent logic here
print(f'Processing email: {email_id}')
return {'status': 'success', 'email_id': email_id}
except Exception as exc:
raise self.retry(exc=exc)
# Trigger a task (from any Python code)
# run_email_agent.delay('email-abc') # Fire and forget
# result = run_email_agent.apply_async(args=['email-abc'], countdown=60) # Delayed
print('Celery task defined')การตรวจสอบสถานะคิว
ตรวจสอบความลึกของคิวเพื่อค้นหางานค้างสะสม หากความลึกของคิวเพิ่มขึ้น คุณจำเป็นต้องเพิ่มผู้ปฏิบัติงาน หรือเอเจนต์ทำงานช้าเกินไป ตั้งค่าการแจ้งเตือนเมื่อความลึกเกินเกณฑ์ที่กำหนด
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
def check_queue_health(queue_name: str, max_depth: int = 100) -> dict:
depth = r.llen(queue_name)
oldest_raw = r.lindex(queue_name, -1) # Get last item (oldest)
oldest_age_seconds = None
if oldest_raw:
import json
from datetime import datetime
oldest = json.loads(oldest_raw)
if 'created_at' in oldest:
created = datetime.fromisoformat(oldest['created_at'])
oldest_age_seconds = (datetime.utcnow() - created).total_seconds()
health = {
'queue': queue_name,
'depth': depth,
'max_depth': max_depth,
'overloaded': depth > max_depth,
'oldest_item_age_seconds': oldest_age_seconds
}
if health['overloaded']:
print(f'ALERT: Queue {queue_name} depth={depth} exceeds max={max_depth}')
return health
print('Queue health monitor defined')
print('Usage: check_queue_health("agent:tasks")')การเลือกระหว่าง Redis กับ RabbitMQ
ใช้ Redis เมื่อต้องการความเรียบง่าย ใช้ Redis อยู่แล้ว หรืองานมีอายุสั้นและยอมรับการสูญเสียข้อความบางส่วนเมื่อระบบหยุดทำงานได้ ใช้ RabbitMQ เมื่อต้องการการส่งมอบที่รับประกัน การกำหนดเส้นทางที่ซับซ้อน คิวข้อความที่ส่งไม่สำเร็จ หรือผู้บริโภคหลายรายที่มีการสมัครรับข้อมูลแตกต่างกัน
# Decision guide as code comments
# Use Redis lists when:
# - Simple FIFO queue is enough
# - Redis already in stack
# - Can tolerate rare message loss on Redis crash
# - Low operational overhead matters
# Use RabbitMQ when:
# - Message acknowledgment is critical (no data loss)
# - Need complex routing (topic exchanges, fanout)
# - Need dead-letter queues for failed messages
# - Multiple consumer types need different message subsets
# - Need message TTL and expiry
# Use Celery (on top of either) when:
# - Need task scheduling and delays
# - Need automatic retries with exponential backoff
# - Need task result storage
# - Need monitoring dashboard (Flower)
print('Redis: simple, fast, ok with rare loss')
print('RabbitMQ: guaranteed delivery, complex routing')
print('Celery: high-level abstraction over both')แบบทดสอบความเข้าใจ: คิวข้อความ
ทดสอบความเข้าใจของคุณเกี่ยวกับคิวข้อความและตัวกลางสำหรับเอเจนต์
สรุปคิวข้อความ
คิวข้อความช่วยแยกตัวกระตุ้นของเอเจนต์ออกจากการดำเนินการ รายการ Redis ให้คิวแบบเข้าก่อนออกก่อนอย่างง่ายด้วย LPUSH/BRPOP, Redis Pub/Sub ช่วยให้กระจายเหตุการณ์, RabbitMQ ให้การส่งมอบที่รับประกันด้วยการยืนยันการรับและคิวข้อความที่ส่งไม่สำเร็จ และ Celery เพิ่มการจัดตาราง การลองซ้ำ และการติดตามระดับสูง การตรวจสอบความลึกของคิวช่วยแจ้งเตือนงานค้างสะสมก่อนจะกลายเป็นเหตุขัดข้องของระบบ
เรียนรู้ AI Agents ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 60
- บทเรียน
- 239
คำถามที่พบบ่อย
บทเรียน “คิวเหตุการณ์และตัวกลางรับส่งข้อความ” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “คิวเหตุการณ์และตัวกลางรับส่งข้อความ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Agents ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Agents มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “คิวเหตุการณ์และตัวกลางรับส่งข้อความ”
คิว Redis, RabbitMQ และ Kafka สำหรับการสื่อสารระหว่างเอเจนต์แบบแยกส่วน คุณปฏิบัติ AI Agents ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Agents หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Agents บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “คิวเหตุการณ์และตัวกลางรับส่งข้อความ” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน AI Agents นี้ได้ไหม
ได้ บทเรียน AI Agents ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- Python แบบอะซิงโครนัสสำหรับนักพัฒนาเอเจนต์
- คิวเหตุการณ์และตัวกลางรับส่งข้อความ
- การทำงานของเครื่องมือแบบขนานโดยไม่บล็อก
- เฟรมเวิร์กเอเจนต์แบบอะซิงโครนัส: LangChain และอื่น ๆ