发送不同类型的消息
普通消息
RocketMQ提供三种方式来发送普通消息:可靠同步发送、可靠异步发送和单向发送。
可靠同步发送
同步发送是指消息发送方发出数据后,会在收到接收方发回响应之后才发下一个数据包的通讯方式。
此种方式应用场景非常广泛,例如重要通知邮件、报名短信通知、营销短信系统等
可靠异步发送
异步发送是指发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。发送方通过
回调接口接收服务器响应,并对响应结果进行处理。
异步发送一般用于链路耗时较长,对 RT 响应时间较为敏感的业务场景,例如用户视频上传后通知
启动转码服务,转码完成后通知推送转码结果等。
单向发送
单向发送是指发送方只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不
等待应答。
适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。
测试代码:
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
package com.wxit.test;
import com.wxit.OrderApplication;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @Author wj
**/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = OrderApplication.class)
public class MessageTypeTest {
@Autowired
private RocketMQTemplate rocketMQTemplate;
//同步消息
@Test
public void testSyncSend(){
//参数一:topic:tag
//参数二:消息体
//参数三:超时时间
SendResult result = rocketMQTemplate.syncSend("test-topic-1:tag", "这是一条同步消息", 10000);
System.out.println(result);
}
//异步消息
@Test
public void testAsyncSend() throws InterruptedException {
rocketMQTemplate.asyncSend("test-topic-1", "这是一条异步消息", new SendCallback() {
//成功响应的回调
@Override
public void onSuccess(SendResult result) {
System.out.println(result);
}
//异常响应的回调
@Override
public void onException(Throwable throwable) {
System.out.println(throwable);
}
});
System.out.println("=============");
Thread.sleep(3000000);
}
@Test
//单向消息
public void testOneWay(){
for (int i = 0; i < 10; i++) {
rocketMQTemplate.sendOneWay("test-topic-1","这是一条单向消息");
}
}
}
顺序消息
顺序消息是消息队列提供的一种严格按照顺序来发布和消费的消息类型。
@Test
public void testOneWayOrderly(){
//
for (int i = 0; i < 10; i++) {
rocketMQTemplate.sendOneWayOrderly("test-topic-1","这是一条单向消息","xx");
}
}
事务消息
RocketMQ提供了事务消息,通过事务消息就能达到分布式事务的最终一致
事务消息交互流程
两个概念
半事务消息:暂不能投递的消息,发送方已经成功地将消息发送到了RocketMQ服务端,但是服务
端未收到生产者对该消息的二次确认,此时该消息被标记成“暂不能投递”状态,处于该种状态下的
消息即半事务消息。
消息回查:由于网络闪断、生产者应用重启等原因,导致某条事务消息的二次确认丢失,
RocketMQ服务端通过扫描发现某条消息长期处于“半事务消息”时,需要主动向消息生产者询问该
消息的最终状态(Commit 或是 Rollback),该询问过程即消息回查。
事务消息发送步骤
\1. 发送方将半事务消息发送至RocketMQ服务端。
\2. RocketMQ服务端将消息持久化之后,向发送方返回Ack确认消息已经发送成功,此时消息为半事
务消息。
\3. 发送方开始执行本地事务逻辑。
\4. 发送方根据本地事务执行结果向服务端提交二次确认(Commit 或是 Rollback),服务端收到
Commit 状态则将半事务消息标记为可投递,订阅方最终将收到该消息;服务端收到 Rollback 状
态则删除半事务消息,订阅方将不会接受该消息。
事务消息回查步骤
\1. 在断网或者是应用重启的特殊情况下,上述步骤4提交的二次确认最终未到达服务端,经过固定时
间后服务端将对该消息发起消息回查。
\2. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
\3. 发送方根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息
进行操作
代码示例
package com.wxit.domain;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Date;
//消息事物状态记录
@Entity(name = "shop_txlog")
@Data
public class TxLog {
@Id
private String txId;
private Date date;
}
package com.wxit.service.impl;
import com.wxit.dao.OrderDao;
import com.wxit.dao.TxLogDao;
import com.wxit.domain.Order;
import com.wxit.domain.TxLog;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.UUID;
/**
* @Author wj
**/
@Service
public class OrderServiceImpl4 {
@Autowired
private OrderDao orderDao;
@Autowired
private TxLogDao txLogDao;
@Autowired
private RocketMQTemplate rocketMQTemplate;
//发送半事务消息
public void createOrderBefore(Order order){
String txId = UUID.randomUUID().toString();
rocketMQTemplate.sendMessageInTransaction(
"tx_producer_group",
"tx_topic",
MessageBuilder.withPayload(order).setHeader("txId",txId).build(),
order
);
}
@Transactional
public void createOrder(String txId,Order order){
orderDao.save(order);
TxLog txLog = new TxLog();
txLog.setTxId(txId);
txLog.setDate(new Date());
txLogDao.save(txLog);
}
}
package com.wxit.service.impl;
import com.wxit.dao.TxLogDao;
import com.wxit.domain.Order;
import com.wxit.domain.TxLog;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;
/**
* @Author wj
**/
@Service
@RocketMQTransactionListener(txProducerGroup = "tx_producer_group")
public class OrderServiceImpl4Listener implements RocketMQLocalTransactionListener {
@Autowired
private OrderServiceImpl4 orderServiceImpl4;
@Autowired
private TxLogDao txLogDao;
//执行本地事务
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
String txId = (String) msg.getHeaders().get("txId");
try {
//本地事务
Order order = (Order) arg;
orderServiceImpl4.createOrder(txId,order);
return RocketMQLocalTransactionState.COMMIT;
} catch (Exception e){
return RocketMQLocalTransactionState.ROLLBACK;
}
}
//消息回查
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
String txId = (String) msg.getHeaders().get("txId");
TxLog txLog = txLogDao.findById(txId).get();
if (txLog != null){
//本地事务订单成功
return RocketMQLocalTransactionState.COMMIT;
} else {
return RocketMQLocalTransactionState.ROLLBACK;
}
}
}
消息消费要注意的细节
@Slf4j
@Service
@RocketMQMessageListener(
consumerGroup = "shop-user",//消费者分组
topic = "order-topic",//要消费的主题
consumeMode = ConsumeMode.CONCURRENTLY, //消费模式:无序和有序
messageModel = MessageModel.CLUSTERING//消息模式:广播和集群,默认是集群
)
public class SmsService implements RocketMQListener<Order> {
@Override
public void onMessage(Order message) {
log.info("接收到了一个订单信息{},接下来就可以发送短信通知了", message);
}
}
RocketMQ支持两种消息模式:
广播消费: 每个消费者实例都会收到消息,也就是一条消息可以被每个消费者实例处理;
集群消费: 一条消息只能被一个消费者实例消费