RocketMQ简介请看这篇文章: RocketMQ入门笔记
RocketMQ安装请看这篇文章: 使用docker安装RocketMQ
org.apache.rocketmq
rocketmq-spring-boot-starter
2.1.0
org.springframework.boot
spring-boot-starter-data-jpa
mysql
mysql-connector-java
8.0.11
rocketmq:
#namesrv访问地址
name-server: 127.0.0.1:9876
producer:
#消息生产者组名
group: test-group
#发送消息的超时时间
send-message-timeout: 3000
#异步消息重试的次数
retry-times-when-send-async-failed: 2
#重试是否换一个server
retry-next-server: true
#同步消息重试的次数
retry-times-when-send-failed: 2
#消费消息认证用的ak
access-key: Ak
#消费消息认证用的sk
secret-key: SK
topic:
string: stringTopic
order: orderTopic
spring:
application:
name: springboot-rocketMq
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=GMT%2b8&characterEncoding=utf8&connectTimeout=10000&socketTimeout=3000&autoReconnect=true
username: root
password: 123456
jpa:
database: mysql
show-sql: true
hibernate:
#自动创建或修改表结构
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
注意事项
/**
* @author Dominick Li
* @CreateTime 2020/3/22 21:06
* @description 如果consumerGroup的名称不同, 则会产生重复消费的情况,例如 consumer1和consumer2
* 如果一个top下面有多个consumerGroup组,则消费者 消费消息会根据组的长度取模
**/
@Service
@RocketMQMessageListener(topic = "${topic.string}", consumerGroup = "${spring.application.name}-${topic.string}-consumer1")
public class StringConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String msg) {
System.out.println("StringConsumer1开始消费:"+ msg);
// if(true){
// //模拟出现异常,出现异常后这个任务还会被重试消费
// System.out.println(1/0);
// }
}
}
发送消息的三种方式
方法 | 描述 | 请求类型 |
---|---|---|
void send(topic,msg) | 底层调用的syncSend方法,但是不接受返回结果 | 异步 |
SendResult syncSend(topic,msg) | 发送消息,等待发送消息返回的结果 | 同步 |
void asyncSend(topic,msg,callback) | 发送消息,通过回调函数根据发送状态处理相对于逻辑 | 异步 |
参数 | 描述 |
---|---|
topic | 消息队列 |
msg | 消息类型 |
callback | 回调函数 |
SendResult | 发送消息是否成功,SendResult.OK为发送成功,其它为失败。 |
@Resource
private RocketMQTemplate rocketMQTemplate;
@Value("${topic.string}")
private String stringTopic;
@Test
void contextLoads() {
testBaseMethod();
}
public void testBaseMethod() {
//1.发送异步消息,但是不会确认消息有没有被接收,日志可以这么发,如果是对数据一致性要去比较高的,建议使用下面的方法
rocketMQTemplate.send(stringTopic, MessageBuilder.withPayload(String.format("test send method")).build());
//2. 发送同步消息,等待发送消息返回的结果
SendResult sendResult = rocketMQTemplate.syncSend(stringTopic, "test syncSend method");
if (sendResult.getSendStatus() == SendStatus.SEND_OK) {
System.out.println("发送成功...");
}
//3.发送异步消息,回调里处理发送成功或失败逻辑
for (int j = 1; j < 3; j++) {
rocketMQTemplate.asyncSend(stringTopic, "test asyncSend method", new SendCallback() {
@Override
public void onSuccess(SendResult sr) {
if (sr.getSendStatus() == SendStatus.SEND_OK) {
System.out.print("async onSucess ok");
} else {
System.out.print("async onSucess fail");
}
}
@Override
public void onException(Throwable var1) {
System.out.printf("async onException Throwable=%s %n", var1);
}
});
System.out.printf("发送第%d条消息\n", j);
}
}
rocketmq事务消息是发生在Producer和Broker之间,是二阶段提交。
第一阶段是:步骤1,2,3。
第二阶段是:步骤4,5。
和上面定义的消费者的除了泛型和消费队列不一样,其它基本是一致的。
@Service
@RocketMQMessageListener(topic = "${topic.order}", consumerGroup = "${spring.application.name}-${topic.order}-consumer1")
public class OrderConsumer implements RocketMQListener<Orders> {
@Override
public void onMessage(Orders orders) {
System.out.println("OrderConsumer1开始消费" + orders.getName() + "," + orders.getOrderId());
}
}
执行逻辑描述
发送端代码
/**
* 发送事务消息
*/
public void testTransactionMethod() {
Long orderId = System.currentTimeMillis();
Orders order = new Orders();
order.setOrderId(orderId);
order.setName("酒水订单");
order.setCreateDate(new Date());
rocketMQTemplate.sendMessageInTransaction(orderTopic,
MessageBuilder.withPayload(
order)
.setHeader(RocketMQHeaders.TRANSACTION_ID, orderId)
.build()
, order);
}
执行MQ事务代码
@RocketMQTransactionListener
public class TransactionListener implements RocketMQLocalTransactionListener {
@Autowired
OrdersService ordersService;
/**
* 执行本地事务
*/
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
String transId = (String) msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID);
System.out.printf("#### executeLocalTransaction transactionId=%s %n",
transId);
//执行本地事务,并记录日志
ordersService.save((Orders) arg);
//执行成功,可以提交事务
return RocketMQLocalTransactionState.COMMIT;
}
/**
* 检查本地事务是否成功
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
String transId = (String) msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID);
System.out.printf("#### checkLocalTransaction transactionId=%s %n", transId);
Optional<Orders> ordersOptional = ordersService.findById(Long.parseLong(transId));
if (ordersOptional.isPresent()) {
return RocketMQLocalTransactionState.COMMIT;
} else {
return RocketMQLocalTransactionState.ROLLBACK;
}
}
}
数据库操作相关代码
@Component
public class OrdersService {
@Autowired
OrdersRepository ordersRepository;
@Transactional
public Orders save(Orders orders) {
Orders save = ordersRepository.save(orders);
if (false) {
//测试抛出RuntimeException让mysql事务回滚
System.out.println(1 / 0);
}
return save;
}
public Optional<Orders> findById(Long id) {
return ordersRepository.findById(id);
}
}
@Entity
@Table(name = "orders")
public class Orders implements Serializable {
@Id
private Long orderId;
private String name;
private Date createDate;
//省略get,set方法
}
public interface OrdersRepository extends JpaRepository<Orders,Long> {
}
正常测试
此操作不模拟异常,正常发送事务消息,可以看到插入数据库的事务提交和MQ的事务提交都同步提交了。
回滚mysql事务测试
在执行数据库操作处把if(false)改成if(true),模拟数据库异常让事务回滚,然后发送事务消息。
执行后的结果如下,可以看到控制台并没有打印消费消息的信息
查看数据库也可以发现没有orderId为1628439222274的记录。
gitee代码地址
创作不易,要是觉得我写的对你有点帮助的话,麻烦在gitee上帮我点下 Star
【SpringBoot框架篇】其它文章如下,后续会继续更新。