1.测试环境:
broker :1master,1slave
producer: 1
consumer:3
2.架构图:
3.实现功能
Producer 投递到Broker 的消息,要实现顺序消费,而且Consumer 消费组C1,C2,C3要实现负载均衡消费
4.技术原理
Broker 的里面的Topic(消息逻辑存储单元)包含多个Queue(假定是4个),该Queue 的Nums 要> Consumer 实例数量,
将投递的所有消息根据ID做索引分配到指定的Queue ,每个Queue 又被指定的单个Consumer消费
不同的Queue 之间实现了并发消费,同一个Queue内部实现单线程顺序消费,思想类似于ConcurrentHashMap 的分段锁
5.示例代码
PayController.java
package net.xdclass.xdclassmq.controller;
import net.xdclass.xdclassmq.domain.ProductOrder;
import net.xdclass.xdclassmq.jms.JmsConfig;
import net.xdclass.xdclassmq.jms.PayProducer;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.exception.RemotingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
@RestController
public class PayController {
@Autowired
private PayProducer payProducer;
@RequestMapping("/api/v2/pay_cb")
public Object callback() throws Exception {
List list = ProductOrder.getOrderList();
for(int i=0; i< list.size(); i++){
ProductOrder order = list.get(i);
Message message = new Message(JmsConfig.ORDERLY_TOPIC,"",
order.getOrderId()+"",order.toString().getBytes());
SendResult sendResult = payProducer.getProducer().send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List mqs, Message msg, Object arg) {
//将消息进行索引处理,分配到不同的queue
Long id = (Long) arg;
long index = id % mqs.size();
return mqs.get((int)index);
}
},order.getOrderId());
System.out.printf("发送结果=%s, sendResult=%s ,orderid=%s, type=%s\n", sendResult.getSendStatus(), sendResult.toString(),order.getOrderId(),order.getType());
}
return new HashMap<>();
}
}
PayProduct
package net.xdclass.xdclassmq.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class ProductOrder implements Serializable {
//订单id
private long orderId;
//操作类型
private String type;
public long getOrderId() {
return orderId;
}
public void setOrderId(long orderId) {
this.orderId = orderId;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public ProductOrder(){}
public ProductOrder(long orderId ,String type){
this.orderId = orderId;
this.type = type;
}
public static List getOrderList(){
//模拟产生许多顺序的消息
List list = new ArrayList<>();
list.add(new ProductOrder(111L,"创建订单"));
list.add(new ProductOrder(222L,"创建订单"));
list.add(new ProductOrder(111L,"支付订单"));
list.add(new ProductOrder(222L,"支付订单"));
list.add(new ProductOrder(111L,"完成订单"));
list.add(new ProductOrder(333L,"创建订单"));
list.add(new ProductOrder(222L,"完成订单"));
list.add(new ProductOrder(333L,"支付订单"));
list.add(new ProductOrder(333L,"完成订单"));
return list;
}
@Override
public String toString() {
return "ProductOrder{" +
"orderId=" + orderId +
", type='" + type + '\'' +
'}';
}
}
JmsConfig.java
package net.xdclass.xdclassmq.jms;
public class JmsConfig {
public static final String NAME_SERVER = "192.168.33.129:9876;192.168.33.130:9876";
public static final String TOPIC = "xdclass_pay_test_topic_888";
public static final String ORDERLY_TOPIC = "xdclass_pay_test_topic_666";
}
PayOrderlyConsumer.java
package net.xdclass.xdclassmq.jms;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.*;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.common.protocol.heartbeat.MessageModel;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class PayOrderlyConsumer {
private DefaultMQPushConsumer consumer;
private String consumerGroup = "pay_orderly_consumer_group";
public PayOrderlyConsumer() throws MQClientException {
consumer = new DefaultMQPushConsumer(consumerGroup);
consumer.setNamesrvAddr(JmsConfig.NAME_SERVER);
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
//默认是集群方式,可以更改为广播,但是广播方式不支持重试
consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.subscribe(JmsConfig.ORDERLY_TOPIC, "*");
consumer.registerMessageListener( new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) {
MessageExt msg = msgs.get(0);
try {
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), new String(msgs.get(0).getBody()));
//做业务逻辑操作 TODO
return ConsumeOrderlyStatus.SUCCESS;
} catch (Exception e) {
e.printStackTrace();
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}
}
});
consumer.start();
System.out.println("consumer start ...");
}
}
PayProducer.java
package net.xdclass.xdclassmq.jms;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.springframework.stereotype.Component;
@Component
public class PayProducer {
private String producerGroup = "pay_producer_group";
private DefaultMQProducer producer;
public PayProducer(){
producer = new DefaultMQProducer(producerGroup);
//生产者投递消息重试次数
producer.setRetryTimesWhenSendFailed(3);
//指定NameServer地址,多个地址以 ; 隔开
producer.setNamesrvAddr(JmsConfig.NAME_SERVER);
start();
}
public DefaultMQProducer getProducer(){
return this.producer;
}
/**
* 对象在使用之前必须要调用一次,只能初始化一次
*/
public void start(){
try {
this.producer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
/**
* 一般在应用上下文,使用上下文监听器,进行关闭
*/
public void shutdown(){
this.producer.shutdown();
}
}
consumer 节点启动多个:
IDEA 模拟测试勾选去掉 ,根据配置不同的 application.yml 的端口,继续启动服务即启动多个consumer实例了