顺序消费如何实现:
消息队列
宏观上是一个topic
底层实际是有多个队列组成的 也就是queue
默认是四个
producer在发送消息的时候,可以只指定topic,也可以同时指定topic和底层的queue
所以只有当同一个topic,同一个queue(topic底层的),且发消息的producer是同一个线程发的消息,才能保证严格顺序。
来个demo:
private DefaultMQProducer producer = null;
@PostConstruct
public void initMQProducer() {
producer = new DefaultMQProducer("defaultGroup");
//producer.setNamesrvAddr(IpConfig.localhost+":9876");
producer.setNamesrvAddr("localhost:9876");
producer.setRetryTimesWhenSendFailed(1);
try {
producer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
System.out.println("mqinit成功");
}
/**
* 只发一条
*
* @param topic
* @param tags
* @param content
* @return
*/
public SendResult send(String topic,
String tags,
String content,
Object obj,
// 代表当前topic下的queue下标
final Integer which) {
Message msg = new Message(topic, tags, "1", content.getBytes());
try {
// 第一种方式
SendResult sendResult = producer.send(
// 原始的消息
msg,
// 消息队列选择器
new MessageQueueSelector() {
@Override
public MessageQueue select(
// 当前topic里包含的所有queue
List<MessageQueue> mqs,
// 要发送的消息 就是外面的消息
Message msg,
// 附带信息 就是外面那个obj
Object arg) {
// 在这里做选择
return mqs.get(which);
}
},
// 附带的信息 任意格式
obj,
// 超时时长
1000L
);
return sendResult;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
来个demo2:
@Override
public SendResult send(String topic,
String tags,
String content,
Object obj) {
Message msg = new Message(topic, tags, content.getBytes());
SelectMessageQueueByHash smqb = new SelectMessageQueueByHash();
try {
producer.send(
// 发送的消息
msg,
// 消息选择器 根据object来做hash
// 此处可使用object代表业务含义
// 比如object是订单号
smqb,
1000L
);
} catch (MQClientException e) {
e.printStackTrace();
} catch (RemotingException e) {
e.printStackTrace();
} catch (MQBrokerException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
// 注意这不是我写的 rocketmq的
// 但是可以自定义 实现MessageQueueSelector就可以了
public class SelectMessageQueueByHash implements MessageQueueSelector {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
int value = arg.hashCode();
if (value < 0) {
value = Math.abs(value);
}
value = value % mqs.size();
return mqs.get(value);
}
}
注:如上两种情况,单线程发送消息,在broker里,顺序是严格保持的。
消费侧:
并发消息监听:
private DefaultMQPushConsumer consumer = null;
@PostConstruct
public void initMQConsumer() {
consumer = new DefaultMQPushConsumer("defaultGroup");
consumer.setNamesrvAddr(IpConfig.localhost + ":9876");
try {
// 指定topic和tag
// MessageListenerConcurrently 并发消息监听
// 意味着底层接受消息是多线程来做的
consumer.subscribe("topic_test", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("Message Received: " + new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("consumer init 成功");
} catch (MQClientException e) {
e.printStackTrace();
}
}
顺序消费消息(单线程):
对一个queue开一个线程 对多个queue开多个线程
@PostConstruct
public void initMQConsumer2() {
consumer2 = new DefaultMQPushConsumer("defaultGroup");
consumer2.setNamesrvAddr(IpConfig.localhost + ":9876");
try {
// 指定topic和tag
// MessageListenerOrderly 顺序消费消息
// 一个queue对应一个线程
consumer2.subscribe("topic_test", "*");
// 设置最大线程数
consumer2.setConsumeThreadMax(2);
// 设置最小线程数
consumer2.setConsumeThreadMin(1);
consumer2.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("Message Received: " + new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer2.start();
System.out.println("consumer2 init 成功");
} catch (MQClientException e) {
e.printStackTrace();
}
}
总结:
要保证顺序消费:
- 同一个topic
- 同一个queue
- 单个线程发送
- 单个线程接收 (一个线程接收一个queue,同一个topic里的queue与queue不保证顺序)