如果RabbitMQConsumer没有收到响应,如何超时

时间:2019-11-27 09:55:52

标签: go rabbitmq timeout channel

我有一些代码使用了RabbitMQ队列中的消息。如果一段时间没有收到消息,它将失败。

c1 := make(chan error, 1)
    go func() {
        for d := range msgs {
            if corrID == d.CorrelationId {
                err = json.Unmarshal([]byte(uncompress(d.Body)), &v)
                if err != nil {
                    c1 <- err
                }
                ch.Ack(d.DeliveryTag, false)
                c1 <- nil
            }
        }
    }()

    select {
    case <-time.After(defaultTimeout * time.Second):
        log.Printf("Failed to receive response for action %+v\n Payload: %+v\nError: %+v\n", action, body, err)
        return errors.New("Failed to receive response in time for action")
    case err := <-c1:
        failOnError(err, "Failed to process response")
    }
    return err

我从RabbitMQ手册中获取了消费代码,并尝试了一些有关实现超时的建议。我知道如何用Java做到这一点,但不能在Golang中重复。

谢谢。

更新

已将选择更改为此:

{{1}}

现在它可以正常工作-如果它没有收到带有正确corellationId的消息,它将因超时而失败。谢谢大家的帮助。

2 个答案:

答案 0 :(得分:1)

您的循环有一个select,有2种情况:超时和default分支。进入循环后,超时不会触发,因此将执行default分支。

default分支在for range通道上包含一个msgs,该通道一直从该通道接收直到关闭为止(并且已经从该通道接收到所有值)。通常,这种情况不应该发生,因此不会重新讨论超时情况(仅当发生某些错误并且msgs关闭时)。

在循环内部使用select处理2种情况,一种超时,另一种仅从msgs接收单个值。如果收到消息,请重新启动超时。对于可重启计时器,请使用time.Timer

timeout := time.Second
timer := time.NewTimer(timeout)
for {
    select {
    case <-timer.C:
        fmt.Println("timeout, returning")
        return
    case msg := <-msgs:
        fmt.Println("received message:", msg)
        // Reset timer: it must be stopped first
        // (and drain its channel if it reports false)
        if !timer.Stop() {
            <-timer.C
        }
        timer.Reset(timeout)
    }
}

请查看此Go Playground示例以了解其运行情况。

请注意,如果在收到消息后不需要重置计时器,请注释掉重置代码。另外,如果不需要重置,则time.After()更简单:

timeout := time.After(time.Second)
for {
    select {
    case <-timeout:
        fmt.Println("timeout, returning")
        return
    case msg := <-msgs:
        fmt.Println("received message:", msg, time.Now())
    }
}

Go Playground上尝试这个。

最后一点:如果您在发生超时之前退出循环,则不会立即释放后台计时器(仅在发生超时时)。如果您经常需要执行此操作,则可以使用context.WithTimeout()获得一个context.Context和一个cancel函数,您可以在返回之前立即调用该函数以释放计时器资源(最好是延迟)。

它是这样的:

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
for {
    select {
    case <-ctx.Done():
        fmt.Println("timeout, returning")
        return
    case msg := <-msgs:
        fmt.Println("received message:", msg, time.Now())
    }
}

Go Playground上尝试这个。

答案 1 :(得分:0)

已将选择更改为此:

c1 := make(chan error, 1)
    go func() {
        for d := range msgs {
            if corrID == d.CorrelationId {
                err = json.Unmarshal([]byte(uncompress(d.Body)), &v)
                if err != nil {
                    c1 <- err
                }
                ch.Ack(d.DeliveryTag, false)
                c1 <- nil
            }
        }
    }()

    select {
    case <-time.After(defaultTimeout * time.Second):
        log.Printf("Failed to receive response for action %+v\n Payload: %+v\nError: %+v\n", action, body, err)
        return errors.New("Failed to receive response in time for action")
    case err := <-c1:
        failOnError(err, "Failed to process response")
    }
    return err