背景:
当消息发布方一直在推送消息,而消息的订阅方一直未订阅,且恰巧接收消息的队列未设置delete when unused。则消息会一直堆积在队列里面,长此以往难免会出现问题。那么有什么方法可以避免这种问题发生呢?
解决方案:
两种方法:
(1)在创建队列的时候,为队列进行“当没有消费者的时候,自动删除队列,即:auto-delete”。
(2)为存入队列的消息设置一个过期时间,即若消息超过一定的时间还没有被消费者进行消费,则自动清理掉该条消息。(该种方式其实存在一定的缺陷,那就是当消费者消费能力不足时,即未能及时的从队列里取走该条消息,会造成消息的丢失。)
这里我主要是介绍第二种方法,即给消息设置过期时间TTL。
什么是消息的TTL?
TTL全称Time To Live,即生存时间。消息的TTL也就是消息的生存时间。在RabbitMQ中设置TTL的手法有两种:
- 第一种是声明队列的时候,在队列的属性中设置TTL,这样该队列中的消息都会有相同的有效期。
- 第二种是发送消息时给消息设置属性,可以为每条消息都设置不同的TTL。
如果两者都进行了设置,则生存时间取两者中最小的那一个。
代码展示:
首先展示一下在声明队列的时候,设置消息TTL。其实是作为参数【"x-message-ttl"】传进去的。
(1)C++代码
//...
void AMQPQueue::Declare(int ttlTime)
{
//...
sendDeclareCommand();
}
void AMQPQueue::sendDeclareCommand() {
if (!name.size())
throw AMQPException("the queue must to have the name");
amqp_bytes_t queue_name = amqp_cstring_bytes(name.c_str());
/*
amqp_basic_properties_t props;
props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG;
props.content_type = amqp_cstring_bytes("text/plain");
*/
amqp_table_t args;
// 是否设置消息TTL
if (0 != ttlTime)
{
args.num_entries = 1;
amqp_table_entry_t_ pTable;
pTable.key = amqp_cstring_bytes("x-message-ttl");
pTable.value.kind = AMQP_FIELD_KIND_U16;
pTable.value.value.u16 = ttlTime;
args.entries = &pTable;
}
else
{
args.num_entries = 0;
args.entries = NULL;
}
amqp_boolean_t exclusive = (parms & AMQP_EXCLUSIVE) ? 1:0;
amqp_boolean_t passive = (parms & AMQP_PASSIVE) ? 1:0;
amqp_boolean_t autodelete = (parms & AMQP_AUTODELETE) ? 1:0;
amqp_boolean_t durable = (parms & AMQP_DURABLE) ? 1:0;
amqp_queue_declare_t s;
s.ticket = 0;
s.queue = queue_name;
s.passive = passive;
s.durable = durable;
s.exclusive = exclusive;
s.auto_delete = autodelete;
s.nowait = 0;
s.arguments = args;
amqp_method_number_t method_ok = AMQP_QUEUE_DECLARE_OK_METHOD;
amqp_rpc_reply_t res = amqp_simple_rpc(*cnn, channelNum, AMQP_QUEUE_DECLARE_METHOD, &method_ok , &s);
AMQPBase::checkReply(&res);
amqp_release_buffers(*cnn);
char error_message [256];
memset(error_message,0,256);
if (res.reply_type == AMQP_RESPONSE_NONE) {
throw AMQPException("error the QUEUE.DECLARE command, response none");
}
if (res.reply.id == AMQP_CHANNEL_CLOSE_METHOD) {
amqp_channel_close_t * err = (amqp_channel_close_t *) res.reply.decoded;
int c_id = (int) err->class_id;
sprintf( error_message, "server error %u, message '%s' class=%d method=%u ", err->reply_code, (char*)err->reply_text.bytes, c_id,err->method_id);
opened=0;
throw AMQPException(&res);
} else if (res.reply.id == AMQP_QUEUE_DECLARE_OK_METHOD) {
amqp_queue_declare_ok_t* data = (amqp_queue_declare_ok_t*) res.reply.decoded;
count = data->message_count;
} else {
sprintf( error_message, "error the Declare command receive method=%d", res.reply.id);
throw AMQPException(error_message);
}
}
(2)Go代码
// 在队列的属性中设置TTL,这样该队列中的所有消息都会有相同的有效期
var (
args = make(map[string]interface{})
)
args["x-message-ttl"] = 60000
_, err = channel.QueueDeclare(
que, // name
false, // durable
true, // delete when unused
false, // exclusive
false, // no-wait(是否阻塞处理)
args) // arguments
if err != nil {
errMsg := fmt.Sprintf("Failed to open declare Queue! err = %v", err)
return nil, nil, errors.New(errMsg)
}
其次展示一下在发送消息的时候,设置消息TTL。(下面仅展示Go代码,C++的实现可自行实现一下加深理解,原理都是相通的。)
if err := channel.Publish(
exchange, // exchange
routKey, // routing key
false, // mandatory
false,
amqp.Publishing{
Expiration: "60000" // 设置消息的TTL为60秒 <发送消息时给消息设置属性,可以为每条消息设置不同的TTL>
ContentType: "text/plain",
Body: []byte(body),
Timestamp: time.Now(),
},
); err != nil {
errMsg := fmt.Sprintf("Failed to msg Publish! err = %v", err)
return errors.New(errMsg)
}
return nil
检验:
在RabbitMQ网页中可以看Queue的Features字段若有“TTL”标识,则代表此队列设置TTL成功。
点进去后在Details面板下可以看到具体的TTL时间。
后言:
如果觉得此文章还算有所帮助的话,就三连支持一下吧(* ̄︶ ̄)。后面的分享更精彩喔~~