文章目录
1.相关文章
RocketMQ简介请看这篇文章: RocketMQ入门笔记
RocketMQ安装请看这篇文章: 使用docker安装RocketMQ
2…使用的依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!---发送事务消息用到了下面2个依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
3.配置文件
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
4.发送正常的消息
4.1.消费者(订阅者)
注意事项
- 消息发布者设置topic 要和消费者的RocketMQMessageListener注解里的topic 一致。
- RocketMQListener泛型类型要和生产者发送的消息的类型一致,如果生产者发布的类型是User,此处的String要改成User.
/**
* @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);
// }
}
}
4.2.发布者(生产者)
发送消息的三种方式
方法 | 描述 | 请求类型 |
---|---|---|
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);
}
}
4.3.测试
执行发布消息的函数,然后控制台输出如下
5.发布事务消息结合@Transactional一起使用
rocketmq事务消息是发生在Producer和Broker之间,是二阶段提交。
第一阶段是:步骤1,2,3。
第二阶段是:步骤4,5。
5.1.消费者(订阅者)
和上面定义的消费者的除了泛型和消费队列不一样,其它基本是一致的。
@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());
}
}
5.2.生产者 (发布者)
执行逻辑描述
- 1.发送事务消息后TransactionListener 会监听到事务消息,并执行MQ本地事务executeLocalTransaction方法
- 2.在executeLocalTransaction方法中执行添加数据到数据库的操作,被执行的方法需要添加**@Transactional注解**让数据库事务可以回滚。
- 3.在checkLocalTransaction方法中会检查本地事务是否提交成功,根据消息生产者传递的事务Id(订单Id)去查询数据库里面是否存在这条记录,如果记录存在提交MQ事务,如果不存在则表示数据库事务没有正常提交,MQ事务也进行回滚。
发送端代码
/**
* 发送事务消息
*/
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> {
}
5.3.测试
正常测试
此操作不模拟异常,正常发送事务消息,可以看到插入数据库的事务提交和MQ的事务提交都同步提交了。
回滚mysql事务测试
在执行数据库操作处把if(false)改成if(true),模拟数据库异常让事务回滚,然后发送事务消息。
执行后的结果如下,可以看到控制台并没有打印消费消息的信息
查看数据库也可以发现没有orderId为1628439222274的记录。
6.项目配套代码
创作不易,要是觉得我写的对你有点帮助的话,麻烦在gitee上帮我点下 Star
【SpringBoot框架篇】其它文章如下,后续会继续更新。
- 1.搭建第一个springboot项目
- 2.Thymeleaf模板引擎实战
- 3.优化代码,让代码更简洁高效
- 4.集成jta-atomikos实现分布式事务
- 5.分布式锁的实现方式
- 6.docker部署,并挂载配置文件到宿主机上面
- 7.项目发布到生产环境
- 8.搭建自己的spring-boot-starter
- 9.dubbo入门实战
- 10.API接口限流实战
- 11.Spring Data Jpa实战
- 12.使用druid的monitor工具查看sql执行性能
- 13.使用springboot admin对springboot应用进行监控
- 14.mybatis-plus实战
- 15.使用shiro对web应用进行权限认证
- 16.security整合jwt实现对前后端分离的项目进行权限认证
- 17.使用swagger2生成RESTful风格的接口文档
- 18.使用Netty加websocket实现在线聊天功能
- 19.使用spring-session加redis来实现session共享
- 20.自定义@Configuration配置类启用开关
- 21.对springboot框架编译后的jar文件瘦身
- 22.集成RocketMQ实现消息发布和订阅
- 23.集成smart-doc插件零侵入自动生成RESTful格式API文档
- 24.集成FastDFS实现文件的分布式存储
- 25.集成Minio实现文件的私有化对象存储
- 26.集成spring-boot-starter-validation对接口参数校验
- 27.集成mail实现邮件推送带网页样式的消息
- 28.使用JdbcTemplate操作数据库
- 29.Jpa+vue实现单模型的低代码平台
- 30.使用sharding-jdbc实现读写分离和分库分表
- 31.基于分布式锁或xxx-job实现分布式任务调度
- 32.基于注解+redis实现表单防重复提交
- 33.优雅集成i18n实现国际化信息返回
- 34.使用Spring Retry完成任务的重试