AI Agents · บทเรียน

คิวเหตุการณ์และตัวกลางรับส่งข้อความ

คิว Redis, RabbitMQ และ Kafka สำหรับการสื่อสารระหว่างเอเจนต์แบบแยกส่วน

บทเรียน 2 จาก 413 ขั้นตอน

คิวเหตุการณ์และตัวกลางรับส่งข้อความ เป็นบทเรียน 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. Python แบบอะซิงโครนัสสำหรับนักพัฒนาเอเจนต์
  2. คิวเหตุการณ์และตัวกลางรับส่งข้อความ
  3. การทำงานของเครื่องมือแบบขนานโดยไม่บล็อก
  4. เฟรมเวิร์กเอเจนต์แบบอะซิงโครนัส: LangChain และอื่น ๆ
← กลับไปที่ AI Agents