php消费rabbitmq消息QoS,RabbitMQ多线程消费消息

背景

最近在改造一个IM系统后台,用到RabbitMQ。在处理消费线程的时候遇到点小麻烦,网上似乎也很少有提到如何多线程处理消息,所以记录一下。

业务场景

模块A向消息队列发送消息,模块B消费其中的消息,因为处理每条消息耗时略有不同,所以希望B能多线程处理。其中模块B建立20个Connection到MQ Broker,并且每个Connection创建一个Channel,用于消费消息。

实现方式

大概按照下面这样的方式,设置了一个40到160的线程池给ConnectionFactory。

ConnectionFactory factory = new ConnectionFactory();

factory.setSharedExecutor(someThreadPool); // someThreadPool为40到160的线程池

Connection mqConn = factory.newConnection(new ListAddressResolver(mqAddress));

someChannel = mqConn.createChannel();

someChannel.basicQos(1);

someChannel.queueDeclare(queueName, false, false, false, null);

String consumerTag = generateConsumerTag();

Consumer consumer = new SomeConsumer(someChannel;

someChannel.basicConsume(outputQueue, true, consumerTag, consumer);

实际效果

在QPS低的情况下,很正常,至少看起来是这样子的。 当QPS升级的时候,消息处理耗时增加,响应变慢。

原因分析

在QPS高的情况下,jstack出来一看,someThreadPool线程数量只有40个,而不是160个,进一步观察发现,就这40个之中,真正忙碌的只有20个,另外20个在睡大觉。 略一思索,联想到前面创建了20个Connection到MQ Broker,而忙碌的线程数刚好也是20,猜测同一channel在消费的时候,使用的单线程模式。网上也找到一点资料,印证了我的想法:

Rabbit MQ: A Connection represents a real TCP connection to the message broker, whereas a Channel is a virtual connection (AMPQ connection) inside it. This way you can use as many (virtual) connections as you want inside your application without overloading the broker with TCP connections. Channel instances are safe for use by multiple threads. Requests into a Channel are serialized, with only one thread being able to run a command on the Channel at a time. Even so, applications should prefer using a Channel per thread instead of sharing the same Channel across multiple threads.

注意上面加粗的这段,意思就是说,Channel的Consumer或者DeliverCallback在处理消息的时候,确实是单线程执行的。

解决方案

知道了原因,那么解决方案就很简单了,也就是Consumer或者DeliverCallback不处理消息,而是将消息转发给业务线程池处理。

channel.basicConsume(queueName, true, new DefaultConsumer(channel) {

@Override

public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,

byte[] body) throws IOException {

// channel.basicAck(envelope.getDeliveryTag(), false);

MqMessageDispatcher.doDispatch(new String(body, "UTF-8"));

}

});

MqMessageDispatcher示例如下:

public class MqMessageDispatcher {

public static ExecutorService msgHandleService = Executors.newFixedThreadPool(5);

static {

Runtime.getRuntime().addShutdownHook(new Thread() {

@Override

public void run() {

msgHandleService.shutdown();;

}

});

}

public static void doDispatch(String message) {

msgHandleService.execute(new MessageHandleTask(message));

}

private static class MessageHandleTask implements Runnable {

String message;

public MessageHandleTask(String message) {

this.message = message;

}

@Override

public void run() {

long start = System.currentTimeMillis();

String tName = Thread.currentThread().getName();

System.out.println(tName + " [x] Received '" + message + "'");

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

long end = System.currentTimeMillis();

System.out.println(tName + " cost " + (end - start));

}

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值