RocketMq相关基本概念与安装
docker-compose 安装RocketMq
分布式事务相关说明
RocketMq 可以发送事务消息
RocketMq 事务消息的基本原理是
**注意:使用RocketMq 发送事务消息,这里主要解决的是生产者执行事务后确保消息有发布到Mq 并可以被消费。消费者按照正常消费就好,但是要确保消息多次消费的结果是要一致相等的(幂等性,不确保只消费一次),多次消费失败就会进入死信队列 **
图解步骤
alibb-rocketmq-producer 工程
gradle 一些主要的包引用和配置
plugins {
id 'org.springframework.boot' version '2.3.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
}
ext {
set('springCloudAlibabaVersion', "2.2.3.RELEASE")
set('springCloudVersion', "Hoxton.SR8")
}
//版本冲突可能导致启动异常 官网推荐整合的各个资源的版本
// https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.alibaba.cloud:spring-cloud-starter-stream-rocketmq'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
mavenBom "com.alibaba.cloud:spring-cloud-alibaba-dependencies:${springCloudAlibabaVersion}"
}
}
application.properties
#配置rocketmq的nameserver地址
spring.cloud.stream.rocketmq.binder.name-server=ip:port
spring.application.name=aibb-rocketmq-producer
server.port=8090
#自定义消息
#MsgSource中的normalMsg
spring.cloud.stream.bindings.normalMsg.destination=normal-topic
#消息的默认类型 the default type of the msg
spring.cloud.stream.bindings.normalMsg.content-type=application/json
spring.cloud.stream.bindings.normalMsg.group=normal-group
#MsgSource中的 orderlyMsg 顺序发送
spring.cloud.stream.bindings.orderlyMsg.destination=orderly-topic
spring.cloud.stream.bindings.orderlyMsg.content-type=application/json
spring.cloud.stream.rocketmq.bindings.orderlyMsg.producer.group=orderly-group
#消息同步发送
spring.cloud.stream.rocketmq.bindings.orderlyMsg.producer.sync=true
#MsgSource中的 orderlyMsg 顺序发送
spring.cloud.stream.bindings.transactionMsg.destination=transaction-topic
spring.cloud.stream.bindings.transactionMsg.content-type=application/json
spring.cloud.stream.rocketmq.bindings.transactionMsg.producer.group=msgRoducerGroup
#事务消息
spring.cloud.stream.rocketmq.bindings.transactionMsg.producer.transactional=true
/**
* @Auther: jmx
* @Date: 2021/01/12
* @Description: MsgConfig 自定义Mq的相关配置
* @Version 1.0.0
*/
@Configuration
@EnableBinding(MsgSource.class)
public class MsgConfig {
}
package com.study.alibbrocketmqproducer.msgproducer.listener;
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(
txProducerGroup = "msgRoducerGroup",
corePoolSize = 2,
maximumPoolSize = 5
)
public class MsgMQLocalTransactionListenerImpl implements RocketMQLocalTransactionListener {
// 发送half消息成功之后,mq返回成功,回调执行本地事务操作,并返回执行事务的结果给MQ,
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
String type = msg.getHeaders().get("status").toString();
System.out.println("executeLocalTransaction: msg-"+ msg + "-arg:" + arg +"-status:"+type);
switch (type) {
case "1":
System.out.println("事务执行状态未知");
return RocketMQLocalTransactionState.UNKNOWN;
case "2":
System.out.println("事务执行状态成功");
return RocketMQLocalTransactionState.COMMIT;
case "3":
System.out.println("事务执行状态失败");
return RocketMQLocalTransactionState.ROLLBACK;
}
return RocketMQLocalTransactionState.ROLLBACK ;
}
//当Mq 没有收到我们返回的事务状态信息 或者 返回的事务状态为RocketMQLocalTransactionState.UNKNOWN,会
// 再次发送消息过来确定消息的状态
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
System.out.println("checkLocalTransaction:"+msg);
return RocketMQLocalTransactionState.COMMIT;
}
}
package com.study.alibbrocketmqproducer.msgproducer.model;
/**
* @Auther: jmx
* @Date: 2021/01/12
* @Description: MsgModel
* @Version 1.0.0
*/
public class MsgModel {
private String content;
private long time;
public MsgModel() {
}
public MsgModel(String content,long time) {
this.content = content;
this.time = time ;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
@Override
public String toString() {
return "MsgModel{" +
"content='" + content + '\'' +
", time=" + time +
'}';
}
}
package com.study.alibbrocketmqproducer.msgproducer.service;
import com.study.alibbrocketmqproducer.msgproducer.model.MsgModel;
import com.study.alibbrocketmqproducer.msgproducer.source.MsgSource;
import org.apache.rocketmq.common.message.MessageConst;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.MimeTypeUtils;
/**
* @Auther: jmx
* @Date: 2021/01/12
* @Description: MsgSendService
* @Version 1.0.0
*/
@Service
public class MsgSendService {
@Autowired
private MsgSource msgSource;
//普通发送
public void sendNormal(String content) {
Message<String> msg = MessageBuilder.withPayload(content).build();
msgSource.normalMsg().send(msg);
}
//顺序发送
public void sendOrderly(String content,String tag) {
MsgModel msgModel = new MsgModel(content,System.currentTimeMillis());
Message<MsgModel> msg = MessageBuilder.withPayload(msgModel)
.setHeader(MessageConst.PROPERTY_TAGS,tag)
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
.build();
msgSource.orderlyMsg().send(msg);
}
//事务消息 half 消息,这条消息去到MQ 在本地事务没有执行成功之前,对于消费者来说是不可见,无法消费的,这里的事务操作是指确保
// 本地事务执行成功之后,消息发送到MQ并可以被消费
// 对于消费者来说,只要消费MQ的MQ信息就好,但是要确保,事务消息的消费结果是幂等性的,即多次消费结果都一样,MQ不确保消息被消费一次
public void sendTransaction(String content,int status){
MsgModel msgModel = new MsgModel(content,System.currentTimeMillis());
Message<MsgModel> msg = MessageBuilder.withPayload(msgModel)
.setHeader("status",status)
.setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON)
.build();
msgSource.transactionMsg().send(msg);
}
}
package com.study.alibbrocketmqproducer.msgproducer;
import com.study.alibbrocketmqproducer.msgproducer.service.MsgSendService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @Auther: jmx
* @Date: 2021/01/12
* @Description: MsgController
* @Version 1.0.0
*/
@RestController
@RequestMapping("/msg")
public class MsgController {
@Autowired
private MsgSendService msgSendService;
@RequestMapping("/normal/{content}")
public ResponseEntity<String> normal(@PathVariable("content") String content){
msgSendService.sendNormal(content);
return ResponseEntity.ok().body("normal ok");
}
@RequestMapping("/orderly")
public String orderly(@RequestParam(name = "c") String content){
msgSendService.sendOrderly(content,"orderly");
return "orderly ok";
}
@RequestMapping("/transaction")
public String transaction(String content,int status){
msgSendService.sendTransaction(content,status);
return "transaction ok";
}
}
alibb-rocketmq-consumer 工程
gradle 一些主要的包引用和配置
plugins {
id 'org.springframework.boot' version '2.3.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
}
ext {
set('springCloudAlibabaVersion', "2.2.3.RELEASE")
set('springCloudVersion', "Hoxton.SR8")
}
//版本冲突可能导致启动异常 官网推荐整合的各个资源的版本
// https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.alibaba.cloud:spring-cloud-starter-stream-rocketmq'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
mavenBom "com.alibaba.cloud:spring-cloud-alibaba-dependencies:${springCloudAlibabaVersion}"
}
}
application.properties
spring.application.name=aibb-rocketmq-consumer
server.port=8091
spring.cloud.stream.rocketmq.binder.name-server=ip:port
#自定义消息
#MsgSink中的normalMsg
spring.cloud.stream.bindings.normalMsg.destination=normal-topic
#消息的默认类型 the default type of the msg
spring.cloud.stream.bindings.normalMsg.content-type=application/json
spring.cloud.stream.bindings.normalMsg.group=normal-group
#MsgSink 中的 orderlyMsg 顺序发送
spring.cloud.stream.bindings.orderlyMsg.destination=orderly-topic
spring.cloud.stream.bindings.orderlyMsg.content-type=application/json
#spring.cloud.stream.rocketmq.bindings.orderlyMsg.consumer.group=orderly-group
spring.cloud.stream.bindings.orderlyMsg.group=orderly-group
#消息同步发送
spring.cloud.stream.rocketmq.bindings.orderlyMsg.consumer.orderly=true
#MsgSink中的 transactionMsg
spring.cloud.stream.bindings.transactionMsg.destination=transaction-topic
spring.cloud.stream.bindings.transactionMsg.content-type=application/json
spring.cloud.stream.bindings.transactionMsg.group=msgRoducerGroup
spring.cloud.stream.bindings.transactionMsg.consumer.concurrency=5
package com.study.alibbrocketmqconsumer.msgconsumer.config;
import com.study.alibbrocketmqconsumer.msgconsumer.sink.MsgSink;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableBinding(MsgSink.class)
public class MsgConfig {
}
package com.study.alibbrocketmqconsumer.msgconsumer.model;
/**
* @Auther: jmx
* @Date: 2021/01/12
* @Description: MsgModel
* @Version 1.0.0
*/
public class MsgModel {
private String content;
private long time;
public MsgModel() {
}
public MsgModel(String content,long time) {
this.content = content;
this.time = time ;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
@Override
public String toString() {
return "MsgModel{" +
"content='" + content + '\'' +
", time=" + time +
'}';
}
}
package com.study.alibbrocketmqconsumer.msgconsumer.sink;
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.messaging.SubscribableChannel;
/**
* @Auther: jmx
* @Date: 2021/01/13
* @Description: MsgSink
* @Version 1.0.0
*/
public interface MsgSink {
@Input("normalMsg")
SubscribableChannel normalMsg();
//顺序组消息
@Input("orderlyMsg")
SubscribableChannel orderlyMsg();
//事务消息
@Input("transactionMsg")
SubscribableChannel transactionMsg();
}
package com.study.alibbrocketmqconsumer.msgconsumer;
import com.study.alibbrocketmqconsumer.msgconsumer.model.MsgModel;
import com.study.alibbrocketmqconsumer.msgconsumer.sink.MsgSink;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.cloud.stream.annotation.StreamRetryTemplate;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Service;
@Service
public class ReceivceService {
@Autowired
private MsgSink msgSink;
@StreamListener("normalMsg")
public void normalMsg(String msg){
System.out.println("normalMsg:"+msg);
}
@StreamListener("orderlyMsg")
public void orderlyMsg(@Payload MsgModel msg){
System.out.println("orderlyMsg:"+msg);
}
@StreamListener("transactionMsg")
public void transactionMsg(MsgModel msg){
System.out.println("transactionMsg:"+msg);
}
}
如果要测试死信队列(集群消费的无序消息才会出现,顺序消息在测试中一直重试)可以将
部署的broker下broker.conf 中添加
messageDelayLevel=5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s 5s(测试使用)
默认配置为:messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h