Skip to content
消息收发模式

消息收发模式

对应 RabbitMQ 官方六大教程的完整 Go 实现:Hello World、Work Queue(工作队列)、Pub/Sub(Fanout 广播)、Routing(Direct 路由)、Topics(主题通配符)与 RPC(请求/响应)。


Hello World — 最简单的生产者和消费者

send.go(生产者):

package main

import (
    "context"
    "log"
    rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
)

func main() {
    ctx := context.Background()

    // 1. 创建 Environment
    env := rmq.NewEnvironment("amqp://guest:guest@localhost:5672/", nil)
    defer env.CloseConnections(context.Background())

    // 2. 建立连接
    conn, err := env.NewConnection(ctx)
    if err != nil {
        log.Panicf("连接失败: %v", err)
    }

    // 3. 声明队列
    _, err = conn.Management().DeclareQueue(ctx,
        &rmq.QuorumQueueSpecification{Name: "hello"})
    if err != nil {
        log.Panicf("声明队列失败: %v", err)
    }

    // 4. 创建发布者
    publisher, err := conn.NewPublisher(ctx,
        &rmq.QueueAddress{Queue: "hello"}, nil)
    if err != nil {
        log.Panicf("创建发布者失败: %v", err)
    }
    defer publisher.Close(context.Background())

    // 5. 发布消息
    result, err := publisher.Publish(ctx,
        rmq.NewMessage([]byte("Hello, RabbitMQ AMQP 1.0!")))
    if err != nil {
        log.Panicf("发布失败: %v", err)
    }

    // 6. 验证发送结果
    switch result.Outcome.(type) {
    case *rmq.StateAccepted:
        log.Println("✓ 消息发送成功")
    default:
        log.Printf("✗ 意外结果: %v", result.Outcome)
    }
}

receive.go(消费者):

package main

import (
    "context"
    "errors"
    "log"
    rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
)

func main() {
    ctx := context.Background()

    // 1. 创建 Environment
    env := rmq.NewEnvironment("amqp://guest:guest@localhost:5672/", nil)
    defer env.CloseConnections(context.Background())

    // 2. 建立连接
    conn, err := env.NewConnection(ctx)
    if err != nil {
        log.Panicf("连接失败: %v", err)
    }

    // 3. 声明队列(幂等操作,确保队列存在)
    _, err = conn.Management().DeclareQueue(ctx,
        &rmq.QuorumQueueSpecification{Name: "hello"})
    if err != nil {
        log.Panicf("声明队列失败: %v", err)
    }

    // 4. 创建消费者
    consumer, err := conn.NewConsumer(ctx, "hello", nil)
    if err != nil {
        log.Panicf("创建消费者失败: %v", err)
    }
    defer consumer.Close(context.Background())

    log.Println("[*] 等待消息...按 Ctrl+C 退出")

    // 5. 循环接收消息
    for {
        delivery, err := consumer.Receive(ctx)
        if err != nil {
            if errors.Is(err, context.Canceled) {
                log.Println("消费者正常退出")
                return
            }
            log.Panicf("接收消息失败: %v", err)
        }

        // 6. 处理消息
        msg := delivery.Message()
        body := string(msg.Data()[0])
        log.Printf("[x] 收到: %s", body)

        // 7. 确认消息
        err = delivery.Accept(ctx)
        if err != nil {
            log.Printf("确认消息失败: %v", err)
        }
    }
}

Work Queue(工作队列)— 任务分发

new_task.go(任务生产者):

package main

import (
    "context"
    "fmt"
    "log"
    "os"
    "strings"
    rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
)

func main() {
    ctx := context.Background()
    env := rmq.NewEnvironment("amqp://guest:guest@localhost:5672/", nil)
    defer env.CloseConnections(context.Background())

    conn, _ := env.NewConnection(ctx)

    // 声明持久化队列
    _, err := conn.Management().DeclareQueue(ctx,
        &rmq.QuorumQueueSpecification{Name: "task_queue"})
    if err != nil {
        log.Panicf("声明队列失败: %v", err)
    }

    publisher, _ := conn.NewPublisher(ctx,
        &rmq.QueueAddress{Queue: "task_queue"}, nil)
    defer publisher.Close(context.Background())

    // 从命令行参数获取消息
    body := strings.Join(os.Args[1:], " ")
    if body == "" {
        body = "Hello World!"
    }

    msg := rmq.NewMessage([]byte(body))
    msg.SetContentType("text/plain")
    // 设置消息持久化
    msg.SetMessageAnnotation("x-opt-durable", true)

    result, err := publisher.Publish(ctx, msg)
    if err != nil {
        log.Panicf("发布失败: %v", err)
    }

    switch result.Outcome.(type) {
    case *rmq.StateAccepted:
        fmt.Printf("[x] 已发送: %s\n", body)
    }
}

worker.go(工作者):

package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "strings"
    "time"
    rmq "github.com/rabbitmq/rabbitmq-amqp-go-client/pkg/rabbitmqamqp"
)

func main() {
    ctx := context.Background()
    env := rmq.NewEnvironment("amqp://guest:guest@localhost:5672/", nil)
    defer env.CloseConnections(context.Background())

    conn, _ := env.NewConnection(ctx)

    _, err := conn.Management().DeclareQueue(ctx,
        &rmq.QuorumQueueSpecification{Name: "task_queue"})
    if err != nil {
        log.Panicf("声明队列失败: %v", err)
    }

    // InitialCredits: 1 实现公平分发(一次只取一个任务)
    consumer, _ := conn.NewConsumer(ctx, "task_queue",
        &rmq.ConsumerOptions{InitialCredits: 1})
    defer consumer.Close(context.Background())

    fmt.Println("[*] 等待任务...")

    for {
        delivery, err := consumer.Receive(ctx)
        if err != nil {
            if errors.Is(err, context.Canceled) {
                return
            }
            log.Panicf("接收失败: %v", err)
        }

        msg := delivery.Message()
        body := string(msg.Data()[0])

        // 模拟耗时任务
        fmt.Printf("[x] 收到: %s\n", body)
        dots := strings.Count(body, ".")
        time.Sleep(time.Duration(dots) * time.Second)
        fmt.Printf("[x] 完成\n")

        delivery.Accept(ctx)
    }
}

发布/订阅模式(Pub/Sub)

使用 Fanout 交换机

// ─── 发布者 ───
func publishLogs(ctx context.Context, conn *rmq.Connection) error {
    // 声明 fanout 交换机
    err := conn.Management().DeclareExchange(ctx,
        &rmq.FanoutExchangeSpecification{
            Name: "logs",
        })
    if err != nil {
        return err
    }

    publisher, err := conn.NewPublisher(ctx,
        &rmq.ExchangeAddress{Exchange: "logs", Key: ""}, nil)
    if err != nil {
        return err
    }
    defer publisher.Close(context.Background())

    // 发布消息
    msg := rmq.NewMessage([]byte("系统启动完成"))
    _, err = publisher.Publish(ctx, msg)
    return err
}

// ─── 订阅者 ───
func subscribeLogs(ctx context.Context, conn *rmq.Connection) error {
    // 声明 fanout 交换机
    err := conn.Management().DeclareExchange(ctx,
        &rmq.FanoutExchangeSpecification{
            Name: "logs",
        })
    if err != nil {
        return err
    }

    // 创建临时独占队列
    queueInfo, err := conn.Management().DeclareQueue(ctx,
        &rmq.AutoGeneratedQueueSpecification{
            IsExclusive:  true,
            IsAutoDelete: true,
        })
    if err != nil {
        return err
    }

    // 绑定队列到交换机
    err = conn.Management().BindQueue(ctx, &rmq.BindingSpecification{
        SourceExchange:   "logs",
        DestinationQueue: queueInfo.Name(),
        BindingKey:       "", // fanout 忽略 routing key
    })
    if err != nil {
        return err
    }

    // 消费消息
    consumer, err := conn.NewConsumer(ctx, queueInfo.Name(), nil)
    if err != nil {
        return err
    }
    defer consumer.Close(context.Background())

    fmt.Printf("[*] 等待日志消息(队列: %s)\n", queueInfo.Name())

    for {
        delivery, err := consumer.Receive(ctx)
        if err != nil {
            return err
        }
        body := string(delivery.Message().Data()[0])
        fmt.Printf("[x] 收到日志: %s\n", body)
        delivery.Accept(ctx)
    }
}

Routing 路由模式

使用 Direct 交换机

// ─── 发布者(按严重级别发布)───
func publishWithSeverity(ctx context.Context, conn *rmq.Connection,
    severity, message string) error {

    err := conn.Management().DeclareExchange(ctx,
        &rmq.DirectExchangeSpecification{
            Name:      "direct_logs",
            IsDurable: true,
        })
    if err != nil {
        return err
    }

    publisher, err := conn.NewPublisher(ctx,
        &rmq.ExchangeAddress{
            Exchange: "direct_logs",
            Key:      severity, // "info" / "warning" / "error"
        }, nil)
    if err != nil {
        return err
    }
    defer publisher.Close(context.Background())

    msg := rmq.NewMessage([]byte(message))
    msg.SetContentType("text/plain")

    _, err = publisher.Publish(ctx, msg)
    return err
}

// ─── 消费者(按严重级别订阅)───
func consumeSeverity(ctx context.Context, conn *rmq.Connection,
    severities []string) error {

    err := conn.Management().DeclareExchange(ctx,
        &rmq.DirectExchangeSpecification{
            Name: "direct_logs",
        })
    if err != nil {
        return err
    }

    // 创建临时队列
    queueInfo, err := conn.Management().DeclareQueue(ctx,
        &rmq.AutoGeneratedQueueSpecification{
            IsExclusive:  true,
            IsAutoDelete: true,
        })
    if err != nil {
        return err
    }

    // 为每个严重级别绑定
    for _, severity := range severities {
        err = conn.Management().BindQueue(ctx, &rmq.BindingSpecification{
            SourceExchange:   "direct_logs",
            DestinationQueue: queueInfo.Name(),
            BindingKey:       severity,
        })
        if err != nil {
            return err
        }
    }

    consumer, err := conn.NewConsumer(ctx, queueInfo.Name(), nil)
    if err != nil {
        return err
    }
    defer consumer.Close(context.Background())

    fmt.Printf("[*] 等待 %v 级别的日志...\n", severities)

    for {
        delivery, err := consumer.Receive(ctx)
        if err != nil {
            return err
        }
        body := string(delivery.Message().Data()[0])
        subject := delivery.Message().Subject()
        fmt.Printf("[x] [%s] %s\n", subject, body)
        delivery.Accept(ctx)
    }
}

Topics 主题模式

使用 Topic 交换机

// ─── 发布者 ───
func publishTopic(ctx context.Context, conn *rmq.Connection,
    routingKey, message string) error {

    err := conn.Management().DeclareExchange(ctx,
        &rmq.TopicExchangeSpecification{
            Name:      "topic_logs",
            IsDurable: true,
        })
    if err != nil {
        return err
    }

    publisher, err := conn.NewPublisher(ctx,
        &rmq.ExchangeAddress{
            Exchange: "topic_logs",
            Key:      routingKey, // 如 "kern.critical", "cron.warning"
        }, nil)
    if err != nil {
        return err
    }
    defer publisher.Close(context.Background())

    msg := rmq.NewMessage([]byte(message))
    _, err = publisher.Publish(ctx, msg)
    return err
}

// ─── 订阅者(使用通配符)───
// 通配符规则:
//   * (星号) 匹配恰好一个单词
//   # (井号) 匹配零个或多个单词
//
// 示例:
//   "*.critical" → 匹配 "kern.critical", "app.critical"
//   "kern.#"     → 匹配所有以 "kern" 开头的
//   "#"          → 匹配所有消息

func subscribeTopic(ctx context.Context, conn *rmq.Connection,
    bindingPattern string) error {

    err := conn.Management().DeclareExchange(ctx,
        &rmq.TopicExchangeSpecification{
            Name: "topic_logs",
        })
    if err != nil {
        return err
    }

    queueInfo, err := conn.Management().DeclareQueue(ctx,
        &rmq.AutoGeneratedQueueSpecification{
            IsExclusive:  true,
            IsAutoDelete: true,
        })
    if err != nil {
        return err
    }

    err = conn.Management().BindQueue(ctx, &rmq.BindingSpecification{
        SourceExchange:   "topic_logs",
        DestinationQueue: queueInfo.Name(),
        BindingKey:       bindingPattern, // 如 "kern.*", "*.critical", "#"
    })
    if err != nil {
        return err
    }

    consumer, err := conn.NewConsumer(ctx, queueInfo.Name(), nil)
    if err != nil {
        return err
    }
    defer consumer.Close(context.Background())

    fmt.Printf("[*] 等待匹配 '%s' 的消息...\n", bindingPattern)

    for {
        delivery, err := consumer.Receive(ctx)
        if err != nil {
            return err
        }
        body := string(delivery.Message().Data()[0])
        fmt.Printf("[x] 收到: %s\n", body)
        delivery.Accept(ctx)
    }
}

RPC 请求/响应模式

RPC 服务端

func runRPCServer(ctx context.Context, conn *rmq.Connection) error {
    // 声明 RPC 队列
    _, err := conn.Management().DeclareQueue(ctx,
        &rmq.QuorumQueueSpecification{Name: "rpc_queue"})
    if err != nil {
        return err
    }

    // 创建消费者,每次只取一条消息
    consumer, err := conn.NewConsumer(ctx, "rpc_queue",
        &rmq.ConsumerOptions{InitialCredits: 1})
    if err != nil {
        return err
    }
    defer consumer.Close(context.Background())

    fmt.Println("RPC 服务端就绪,等待请求...")

    for {
        delivery, err := consumer.Receive(ctx)
        if err != nil {
            return err
        }

        msg := delivery.Message()
        request := string(msg.Data()[0])

        // 获取回复地址
        replyTo := msg.ReplyTo()
        correlationID := msg.CorrelationID()

        // 处理请求
        fmt.Printf("收到请求: %s (correlationID: %s)\n", request, correlationID)
        response := processRPCRequest(request)

        // 发送响应
        publisher, err := conn.NewPublisher(ctx,
            &rmq.QueueAddress{Queue: replyTo}, nil)
        if err != nil {
            delivery.Requeue(ctx)
            continue
        }

        respMsg := rmq.NewMessage([]byte(response))
        respMsg.SetCorrelationID(correlationID)

        _, err = publisher.Publish(ctx, respMsg)
        publisher.Close(context.Background())

        if err != nil {
            delivery.Requeue(ctx)
            continue
        }

        delivery.Accept(ctx)
    }
}

func processRPCRequest(req string) string {
    // 实际业务逻辑
    return "处理结果: " + req
}

RPC 客户端

func callRPC(ctx context.Context, conn *rmq.Connection,
    request string) (string, error) {

    // 创建临时回复队列
    replyQueueInfo, err := conn.Management().DeclareQueue(ctx,
        &rmq.AutoGeneratedQueueSpecification{
            IsExclusive:  true,
            IsAutoDelete: true,
        })
    if err != nil {
        return "", err
    }
    replyQueueName := replyQueueInfo.Name()

    correlationID := fmt.Sprintf("rpc-%d", time.Now().UnixNano())

    // 发布请求
    publisher, err := conn.NewPublisher(ctx,
        &rmq.QueueAddress{Queue: "rpc_queue"}, nil)
    if err != nil {
        return "", err
    }
    defer publisher.Close(context.Background())

    msg := rmq.NewMessage([]byte(request))
    msg.SetReplyTo(replyQueueName)
    msg.SetCorrelationID(correlationID)

    _, err = publisher.Publish(ctx, msg)
    if err != nil {
        return "", err
    }

    // 等待响应
    consumer, err := conn.NewConsumer(ctx, replyQueueName,
        &rmq.ConsumerOptions{InitialCredits: 1})
    if err != nil {
        return "", err
    }
    defer consumer.Close(context.Background())

    // 带超时的等待
    timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
    defer cancel()

    for {
        delivery, err := consumer.Receive(timeoutCtx)
        if err != nil {
            return "", fmt.Errorf("等待响应超时: %w", err)
        }

        respMsg := delivery.Message()
        if respMsg.CorrelationID() == correlationID {
            body := string(respMsg.Data()[0])
            delivery.Accept(ctx)
            return body, nil
        }
        // 不是我们的响应,继续等待
        delivery.Accept(ctx)
    }
}