顺序消息:是指消息的消费顺序与消息的产生顺序相同;顺序消息分为全局顺序消息和局部顺序消息,全局顺序消息是指:在某个topic下的所有消息都要保证消费顺序与产生顺序相同;部分顺序消息是指:只要保证每一组消息被顺序消费即可。在RocketMQ中,若要实现全局顺序消息,首先把topic的读写队列设置为一,然后把生产者producer和消费者consumer都设置成单线程即可。但这样一来,就需要牺牲高并发和高吞吐量了。一般情况下,根据业务的需要,我们只需要实现局部顺序消息即可。
在高并发情况下,RocketMQ实现局部顺序消息是通过消息的生产者和消息的消费者协同完成的。发送端需要做的事情:把同一个小组内的消息发送到指定的队列Message Queue中;消费端需要做的事情:仅用一个线程处理这个队列中的消息。
默认情况下,消息的生产端实现负载均衡的做法是:轮流向各个消息队列Message Queue中发送消息。消息的消费端实现负载均衡的做法是:把消息队列的总数简单的除以消费者的个数,每个消费者负责一些消息队列(注意:消费者的数量不要超过消息队列的个数,否则多余的消费者接收不到消息)。在我们人为不干涉的情况下,把一条消息投递到哪个队列以及被哪个消费者下的线程消费都是未知的。
为了实现局部顺序消息的消费,发送端通过使用MessageQueueSelector类来控制把消息发往哪个消息队列Message Queue中,其代码如下:
SendResult result = null; try { result = producer.send(message, new MessageQueueSelector() { public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) { Integer queueNumber = (Integer)arg; return mqs.get(queueNumber); } }, 2); } catch (MQClientException e) { e.printStackTrace(); } catch (RemotingException e) { e.printStackTrace(); } catch (MQBrokerException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(result);
在我们初始化消费者时,需要指定监听器的类型:
MessageListenerOrderly:在消息需要按局部顺序消费时使用;
MessageListenerConcurrently:在消息不需要按局部顺序消费时使用。
在MessageListenerOrderly的实现中,为每个Consumer Queue加个锁,消费每个消息前,需要先获得这个消息所在的Consumer Queue所对应的的锁,这样就可以保证在同一时间、同一个Consumer Queue的消息不被并发消费,但不同的Consumer Queue的消息可以并发处理。
为了实现局部顺序消息的消费,消息的消费端需要指定监听器类型为:MessageListenerOrderly,代码如下:
this.consumer.setMessageListener(new MessageListenerOrderly() { public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) { try { //处理业务逻辑 return ConsumeOrderlyStatus.SUCCESS; } catch (Exception e) { e.printStackTrace(); //当消费消息的过程中,若是出现了异常,则稍后再重新消费 return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; } } });