消息发送者步骤
1.创建消费生产者producer,并制定生产者组名
2.制定nameserver地址
4.启动producer
5.创建消费对象,制定主题topic、Tag和消息体
6.发送消息
7.关闭生产者producer
发送同步消息
发送同步消息(消息发送方发送数据出去后,会在收到接收方发回响应才会发下一个数据包的通讯方法) 这种可靠性同步的发送方式使用的比较广泛,比如:重要的消息通知,短信通知。
public class Producter {
//发送同步消息 这种可靠性同步的发送方式使用的比较广泛,比如:重要的消息通知,短信通知。
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException, UnsupportedEncodingException {
// 1. 创建消费生产者producer,并制定生产者组名
DefaultMQProducer producer=new DefaultMQProducer("group1");
// 2. 制定nameserver地址
producer.setVipChannelEnabled(false);
producer.setNamesrvAddr("192.168.174.10:9876");
// 3. 启动producer
producer.start();
for (int i = 1; i <= 10; i++) {//模拟发送10次消息
// 4. 创建消费对象,制定主题topic、Tag和消息体
Message message=new Message("TopicTest","tag1", ("Hello RocketMQ").getBytes(RemotingHelper.DEFAULT_CHARSET));
// 5. 发送消息
SendResult send = producer.send(message);
System.out.println("%s%n"+ JSON.toJSONString(send));
}
// 6. 关闭生产者producer
producer.shutdown();
}
}
单向消息
单向消息(只负责发送,不等待响应结果)这种方式主要用在不特别关心发送结果的场景,例如日志发送
public class OneProducter {
//发送单向消息 这种方式主要用在不特别关心发送结果的场景,例如日志发送
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
// 1. 创建消费生产者producer,并制定生产者组名
DefaultMQProducer producer=new DefaultMQProducer("group1");
// 2. 制定nameserver地址
producer.setNamesrvAddr("192.168.174.10:9876");
// 3. 启动producer
producer.start();
for (int i = 1; i <= 10; i++) {//模拟发送10次消息
// 4. 创建消费对象,制定主题topic、Tag和消息体
Message message=new Message("TopicTest","tag2",("Hello word one"+i).getBytes(StandardCharsets.UTF_8));
// 5. 发送消息
Thread.sleep(3);
producer.sendOneway(message);
}
//让main线程睡眠6秒 不要提前关闭producer
// 6. 关闭生产者producer
producer.shutdown();
}
}
异步同步消息
异步消息(发送数据后,不等待接收方发回响应,接着发送下一个,发送方通过回调接收响应并行处理)通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应
public class SyncProducter {
//异步同步消息 异步消息通常用在对响应时间敏感的业务场景,即发送端不能容忍长时间地等待Broker的响应
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
// 1. 创建消费生产者producer,并制定生产者组名
DefaultMQProducer producer=new DefaultMQProducer("group1");
// 2. 制定nameserver地址
producer.setNamesrvAddr("192.168.174.10:9876");
// 3. 启动producer
producer.start();
for (int i = 1; i <= 10; i++) {//模拟发送10次消息
// 4. 创建消费对象,制定主题topic、Tag和消息体
Message message=new Message("TopicTest","tag2",("Hello word"+i).getBytes(StandardCharsets.UTF_8));
// 5. 发送消息
producer.send(message, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("发送成功"+ JSON.toJSONString(sendResult));
}
@Override
public void onException(Throwable throwable) {
throwable.printStackTrace();
System.out.println("发送异常"+throwable.getMessage());
}
});
}
//让main线程睡眠6秒 不要提前关闭producer
Thread.sleep(6);
// 6. 关闭生产者producer
producer.shutdown();
}
}
消费者
1)负载均衡模式(默认)
消费者采用负载均衡方式消费消息,多个消费者共同消费队列消息,每个消费者处理的消息不同
public class Consumer {
public static void main(String[] args) throws MQClientException {
// 1. 创建消费者consumer,制定消费者组名
DefaultMQPushConsumer consumer=new DefaultMQPushConsumer("group1");
// 2. 指定nameserver地址
consumer.setNamesrvAddr("192.168.174.10:9876");
// 3. 订阅主题Topic和Tag
consumer.subscribe("TopicTest","tag2"); //tag可以用*代替 消费所有tag
// 4. 设置回调函数,处理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
list.forEach(a-> System.out.println(new String(a.getBody())));
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 5. 启动消费者consumer
consumer.start();
}
}
2)广播模式
消费者采用广播的方式消费消息,每个消费者消费的消息都是相同的
public class Consumer {
public static void main(String[] args) throws MQClientException {
// 1. 创建消费者consumer,制定消费者组名
DefaultMQPushConsumer consumer=new DefaultMQPushConsumer("group1");
// 2. 指定nameserver地址
consumer.setNamesrvAddr("192.168.174.10:9876");
// 3. 订阅主题Topic和Tag
consumer.subscribe("TopicTest","*"); //tag可以用*代替 消费所有tag
//设置消费模式 默认MessageModel.CLUSTERING 负载均衡模式
consumer.setMessageModel(MessageModel.BROADCASTING);//广播模式
// 4. 设置回调函数,处理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
list.forEach(a-> System.out.println(new String(a.getBody())));
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 5. 启动消费者consumer
consumer.start();
}
}
顺序消息
消息有序指的是可以按照消息发送的顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或全局有序。
在默认情况下消费发送会采取Round Robin 轮询方式把消息发送到不同的queue(分区队列);而消费消息时候从多个queue拉取消息,这种情况发送和消费是不能保证顺序。
但是如果控制发送的顺序消息只能依次发送到同一个queue中,消费的时候只能一次从这个queue中拉取,则就保证了顺序。
小案例模拟订单发送,相同的订单发送到同一个消息队列
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer id;
private String desc;
@Override
public String toString() {
return "Order{" +
"id=" + id +
", desc='" + desc + '\'' +
'}';
}
public static List<Order> buildOrders(){
List<Order> orders = new ArrayList();
orders.add(new Order(1039,"创建"));
orders.add(new Order(1039,"付款"));
orders.add(new Order(1039,"推送"));
orders.add(new Order(1039,"完成"));
orders.add(new Order(1065,"创建"));
orders.add(new Order(1065,"付款"));
orders.add(new Order(7235,"创建"));
orders.add(new Order(7235,"付款"));
return orders;
}
}
订单生产者
public class SortProduct {
//消息有序指的是可以按照消息发送的顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或全局有序。
//在默认情况下消费发送会采取Round Robin 轮询方式把消息发送到不同的queue(分区队列);而消费消息时候从多个queue拉取消息,这种情况发送和消费是不能保证顺序。
// 但是如果控制发送的顺序消息只能依次发送到同一个queue中,消费的时候只能一次从这个queue中拉取,则就保证了顺序。
public static void main(String[] args) throws Exception {
// 1. 创建消费生产者producer,并制定生产者组名
DefaultMQProducer producer = new DefaultMQProducer("group1");
// 2. 制定nameserver地址
producer.setNamesrvAddr("192.168.174.10:9876");
// 3. 启动producer
producer.start();
//订单集合
List<Order> list = Order.buildOrders();
for (int i = 0; i < list.size(); i++) {
String body = list.get(i)+"";
//创建消息
Message message = new Message("orderTopic", "order","i"+i,
body.getBytes());
//发送消息
//参数一 消息对象
//参数二 消息对列选择器
//参数二 业务标识的参数(订单id)
SendResult sendResult = producer.send(message, new MessageQueueSelector() {
/**
*
* @param mqs MQ队列集合
* @param msg 消息对象
* @param arg 业务标识的参数
* @return
*/
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Integer orderId = (Integer) arg;
//用户ID取模选取队列,这样相同的id会取到同一个队列
// mqs.size()总共有几个队列,通过订单id取余相同id会分布在同一个队列上
Integer index = orderId % mqs.size();
System.out.println("队列Id"+index+"orderId"+orderId);
return mqs.get(index);
}
}, list.get(i).getId());//业务参数
//打印发送结果
System.out.println("发送结果"+sendResult);
}
}
}
订单消费者
public class SortConsumer {
public static void main(String[] args) throws MQClientException {
// 1. 创建消费者consumer,制定消费者组名
DefaultMQPushConsumer consumer=new DefaultMQPushConsumer("group1");
// 2. 指定nameserver地址
consumer.setNamesrvAddr("192.168.174.10:9876");
// 3. 订阅主题Topic和Tag
consumer.subscribe("orderTopic","*"); //tag可以用*代替 消费所有tag
//注册消息监听器
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
System.out.println("线程名称+【"+Thread.currentThread().getName()+"】"+new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
//启动
consumer.start();
System.out.println("消费者启动成功");
}
}
延时消息
现在rocketmq并不支持任意时间的延时,需要设置几个固定延时等级,从1s到2h分别对应着等级1到18
1s 2s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
比如电商里,提交一个订单就可以发送一个延时消息,1h后去检查这个订单状态,如果还是未付款就取消订单释放库存。
public static void main(String[] args) throws Exception {
// 1. 创建消费生产者producer,并制定生产者组名
DefaultMQProducer producer=new DefaultMQProducer("group1");
// 2. 制定nameserver地址
producer.setNamesrvAddr("192.168.174.10:9876");
// 3. 启动producer
producer.start();
for (int i = 1; i <= 10; i++) {//模拟发送10次消息
Message message = new Message("topicDelay","tag1",("Hello word"+i).getBytes(StandardCharsets.UTF_8));
//设置延时等级
message.setDelayTimeLevel(2);
// 5. 发送消息
SendResult send = producer.send(message);
System.out.println("%s%n"+ JSON.toJSONString(send));
}
// 6. 关闭生产者producer
producer.shutdown();
}
public static void main(String[] args) throws MQClientException {
// 1. 创建消费者consumer,制定消费者组名
DefaultMQPushConsumer consumer=new DefaultMQPushConsumer("group1");
// 2. 指定nameserver地址
consumer.setNamesrvAddr("192.168.174.10:9876");
// 3. 订阅主题Topic和Tag
consumer.subscribe("topicDelay","*"); //tag可以用*代替 消费所有tag
//注册消息监听器
// 4. 设置回调函数,处理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
list.forEach(a-> {
System.out.println(new String(a.getBody()) + "延迟时间:"+(System.currentTimeMillis()-a.getStoreTimestamp()));
});
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 5. 启动消费者consumer
System.out.println("启动消费者consumer");
consumer.start();
}
批量消息
如何使用批处理
如果您一次只发送不超过4MiB的消息,则可以轻松使用批处理:
String topic = "BatchTest";
List<Message> messages = new ArrayList<>();
messages.add(new Message(topic, "TagA", "OrderID001", "Hello world 0".getBytes()));
messages.add(new Message(topic, "TagA", "OrderID002", "Hello world 1".getBytes()));
messages.add(new Message(topic, "TagA", "OrderID003", "Hello world 2".getBytes()));
try {
producer.send(messages);
} catch (Exception e) {
e.printStackTrace();
//handle the error
}
超过了采用拆分
public class ListSplitter implements Iterator<List<Message>> {
private final int SIZE_LIMIT = 1000 * 1000;
private final List<Message> messages;
private int currIndex;
public ListSplitter(List<Message> messages) {
this.messages = messages;
}
@Override public boolean hasNext() {
return currIndex < messages.size();
}
@Override public List<Message> next() {
int nextIndex = currIndex;
int totalSize = 0;
for (; nextIndex < messages.size(); nextIndex++) {
Message message = messages.get(nextIndex);
int tmpSize = message.getTopic().length() + message.getBody().length;
Map<String, String> properties = message.getProperties();
for (Map.Entry<String, String> entry : properties.entrySet()) {
tmpSize += entry.getKey().length() + entry.getValue().length();
}
tmpSize = tmpSize + 20; //for log overhead
if (tmpSize > SIZE_LIMIT) {
//it is unexpected that single message exceeds the SIZE_LIMIT
//here just let it go, otherwise it will block the splitting process
if (nextIndex - currIndex == 0) {
//if the next sublist has no element, add this one and then break, otherwise just break
nextIndex++;
}
break;
}
if (tmpSize + totalSize > SIZE_LIMIT) {
break;
} else {
totalSize += tmpSize;
}
}
List<Message> subList = messages.subList(currIndex, nextIndex);
currIndex = nextIndex;
return subList;
}
}
//then you could split the large list into small ones:
ListSplitter splitter = new ListSplitter(messages);
while (splitter.hasNext()) {
try {
List<Message> listItem = splitter.next();
producer.send(listItem);
} catch (Exception e) {
e.printStackTrace();
//handle the error
}
}
过滤消息
tag过滤
过滤tag为TAGA 或TAGB或TAGC
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("CID_EXAMPLE");
consumer.subscribe("TOPIC", "TAGA || TAGB || TAGC");
sql过滤
SQL功能可以通过您在发送消息时放入的属性进行一些计算。在RocketMQ定义的语法下,您可以实现一些有趣的逻辑。
RocketMQ仅定义了一些基本语法来支持此功能。您也可以轻松扩展它。
数值比较,如>,>=,<,<=,BETWEEN,=;
字符比较,如=,<>,IN;
IS NULL或IS NOT NULL;
逻辑AND,OR,NOT,
常量类型为:
数字,例如123、3.1415;
字符(如“ abc”)必须用单引号引起来;
NULL,特殊常数;
布尔,TRUE或FALSE;
使用限制
只有推送使用者才能通过SQL92选择消息。该接口是:
DefaultMQProducer producer = new DefaultMQProducer("group1");
producer.setNamesrvAddr("192.168.174.10:9876");
producer.start();
Message msg = new Message("TopicTest","tag1",("Hello RocketMQ " + i).getBytes());
// 设置这个a用来sql过滤
msg.putUserProperty("a", String.valueOf(i));
SendResult sendResult = producer.send(msg);
producer.shutdown();
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("group1");
consumer.setNamesrvAddr("192.168.174.10:9876");
// only subsribe messages have property a, also a >=0 and a <= 3
consumer.subscribe("TopicTest", MessageSelector.bySql("a between 0 and 3");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
RockertMQ事务处理机制
流程图
1.rockertmq会生产者会向消费者发送半消息(半消息指的是收到但是不会消费的消息),当消费者收到后会反过来告诉生产者已经收到消息
2.生产者都到消费方发来的已经接收消息后再进行是否进行事务提交,回滚或者不作处理
3.如果消费方迟迟收不到生产者确定会进行事务回查
demo例子
创建三条消息,tag1提交,tag2回滚,tag3不作处理,当tag3不作处理后消费方会进行事务回查此时在进行提交。
生产者:
public static void main(String[] args) throws MQClientException, InterruptedException {
//事务
//TransactionListener transactionListener = new TransactionListenerImpl();
// 1. 创建消费生产者producer,并制定生产者组名
TransactionMQProducer producer = new TransactionMQProducer("transaction");
producer.setNamesrvAddr("192.168.174.10:9876");
producer.setTransactionListener(new TransactionListener() {
/**
* 该方法是mq消费者接受到半消息后,反过来询问生产方执行是消费还是回滚或者不作处理
*
* @param message
* @param o sendMessageInTransaction中的第二个参数保持一致
* @return
*/
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
System.out.println("Object"+o);
if(o.equals("test")){
if("tag1".equals(message.getTags())){
//第一条消息提交
return LocalTransactionState.COMMIT_MESSAGE;
}else if ("tag2".equals(message.getTags())){
//第二条消息回滚
return LocalTransactionState.ROLLBACK_MESSAGE;
}else if("tag3".equals(message.getTags())){
//第二条消息不作处理,接下会进行消息事务回查执行checkLocalTransaction方法
return LocalTransactionState.UNKNOW;
}
}
return LocalTransactionState.UNKNOW;
}
//当消费方接收到的半消息不作处理会执行回查方法
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
System.out.println("回查。。。。。"+messageExt.getTags());
//提交
return LocalTransactionState.COMMIT_MESSAGE;
}
});
producer.start();
//发送三条消息
for (int i = 1; i <= 3; i++) {
Message message = new Message("TransactionTop","tag"+i,("Hellow world"+i).getBytes());
//executeLocalTransaction中的第二个参数一致
SendResult sendResult = producer.sendMessageInTransaction(message, "test");
System.out.println("发送结果:"+sendResult);
TimeUnit.SECONDS.sleep(1);
}
}
消费者
public static void main(String[] args) throws MQClientException {
// 1. 创建消费者consumer,制定消费者组名
DefaultMQPushConsumer consumer=new DefaultMQPushConsumer("transaction");
// 2. 指定nameserver地址
consumer.setNamesrvAddr("192.168.174.10:9876");
// 3. 订阅主题Topic和Tag
consumer.subscribe("TransactionTop","*"); //tag可以用*代替 消费所有tag
// 4. 设置回调函数,处理消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
list.forEach(a-> System.out.println(new String(a.getBody())));
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
// 5. 启动消费者consumer
System.out.println("消费者启动。。。。。");
consumer.start();
}
结合springboot 5种发送方式(同步消息,单向消息。异步消息,顺序消息,事务消息)
<!--rocketmq-->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
#rocketmq配置 生产者
rocketmq:
name-server: 192.168.174.10:9876
producer:
group: my-producer-group
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MqProductApp.class)
public class Test {
@Autowired
private RocketMQTemplate rocketMQTemplate;
//同步消息
@org.junit.Test
public void test(){
//参数一:topic:tag
//参数二:消息体内容
SendResult sendResult = rocketMQTemplate.syncSend("myTopic:tag", "同步消息");
System.out.println(sendResult);
}
//异步步消息
@org.junit.Test
public void test2() throws InterruptedException {
rocketMQTemplate.asyncSend("myTopic1:tag1", "异步消息", new SendCallback() {
//消息成功方法
@Override
public void onSuccess(SendResult sendResult) {
System.out.println(sendResult);
}
//消息成功失败回调方法
@Override
public void onException(Throwable throwable) {
System.out.println("throwable"+throwable);
}
});
System.out.println("==============");
Thread.sleep(300000000);
}
//单向消息
@org.junit.Test
public void test3(){
rocketMQTemplate.sendOneWay("myTopic1:tag2", "单向消息");
}
//顺序消息(只需保证发送的消息在同一队列即可)
@org.junit.Test
public void test4(){
for (int i = 0; i < 10; i++) {
//xxx.Orderly就是顺序消息发送 第3个参数保证发送到哪一个队列
rocketMQTemplate.sendOneWayOrderly("myTopic1:tag2", "单向消息","xxx");
}
}
}
事务消息
生产者发送事务消息
@org.junit.Test
public void test4(){
TradeOrder order = new TradeOrder();
order.setGoodsId(1111L);
order.setGoodsNumber(99);
//发送半消息
rocketMQTemplate.sendMessageInTransaction("myTopic:tags"
, MessageBuilder.withPayload(order).setHeader("txId", "122").build(),
"arg");
}
rocketMQ发送消息后会监听来自消费者的消息,
- 如果发送的半消息已经到达了消费者 将会执行executeLocalTransaction(是消费还是回滚还是提交或者未知)
- 如果消费者一直得不到生产者的回复将会进行方法回查执行checkLocalTransaction
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.messaging.Message;
@RocketMQTransactionListener
public class RocketMQLocalTransactionListen implements RocketMQLocalTransactionListener {
//执行本地业务逻辑
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
try {
System.out.println(o + "============" + message);
System.out.println("=======执行本地业务逻辑·=====");
} catch (Exception e) {
//事务回滚
return RocketMQLocalTransactionState.ROLLBACK;
}
//事务提交
return RocketMQLocalTransactionState.COMMIT;
}
//回查方法
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
System.out.println("---------------" + message);
return RocketMQLocalTransactionState.ROLLBACK;
}
}
消费者
@Component
@RocketMQMessageListener(topic = "myTopic",// topic 必须跟发送者保持一致
consumerGroup = "consumer", //消费者组名 随便写
messageModel = MessageModel.CLUSTERING, //BROADCASTING 广播模式 CLUSTERING集群模式
consumeMode = ConsumeMode.CONCURRENTLY // ORDERLY 顺序消费 CONCURRENTLY 无序消费
)
public class SpringTxConsumer implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt s) {
System.out.println("接收到消息 -> " + s);
}
}