练习Spring Cloud Alibaba 5 —— RocketMQ

消息驱动微服务——RocketMQ

RocketMQ:是一款分布式、队列模型的开源消息中间件

1.RocketMQ的安装

以RocketMQ 4.5.1为例
下载地址:http://rocketmq.apache.org/release_notes/release-notes-4.5.1/ ,下载Binary 文件即可。

练习Spring Cloud Alibaba 5 —— RocketMQ_第1张图片

配置环境变量

变量值以实际存放目录为主
练习Spring Cloud Alibaba 5 —— RocketMQ_第2张图片

启动NAMESERVER

CMD命令框执行进入文件bin目录,然后执行‘start mqnamesrv.cmd’,启动NAMESERVER。成功后会弹出如下的提示框,此框勿关闭。
练习Spring Cloud Alibaba 5 —— RocketMQ_第3张图片

启动BROKER
CMD命令框进入至文件bin目录下,然后执行‘start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true’,启动BROKER。成功后会弹出提示框,此框勿关闭。 

练习Spring Cloud Alibaba 5 —— RocketMQ_第4张图片
注意: 假如弹出提示框提示错误: 找不到或无法加载主类 xxxxxx。打开runbroker.cmd,然后将‘%CLASSPATH%’加上英文双引号。
练习Spring Cloud Alibaba 5 —— RocketMQ_第5张图片

2.RocketMQ插件部署

下载地址:https://github.com/apache/rocketmq-externals.git

下载完成之后,进入‘rocketmq-externals\rocketmq-console\src\main\resources’文件夹,打开‘application.properties’修改配置。
练习Spring Cloud Alibaba 5 —— RocketMQ_第6张图片

编译启动

进入‘\rocketmq-externals\rocketmq-console’文件夹,执行‘mvn clean package -Dmaven.test.skip=true’,进行编译。编译成功之后,Cmd进入‘target’文件夹,执行‘java -jar rocketmq-console-ng-1.0.0.jar’,启动‘rocketmq-console-ng-1.0.0.jar’。

练习Spring Cloud Alibaba 5 —— RocketMQ_第7张图片
练习Spring Cloud Alibaba 5 —— RocketMQ_第8张图片

登录测试

启动完毕后,在页面上输入地址:127.0.0.1:17890 登录查看
练习Spring Cloud Alibaba 5 —— RocketMQ_第9张图片

3.编写生产者代码

pom文件添加依赖

注意: 这里指定了使用2.0.3版本,为了适配RocketMQ4.5.1

<dependency>
    <groupId>org.apache.rocketmqgroupId>
    <artifactId>rocketmq-spring-boot-starterartifactId>
    <version>2.0.3version>
dependency>
yml文件写配置

注意: group 必须指定,否者启动项目会报 无法初始化RocketMQ 的错误

rocketmq:
  name-server: 127.0.0.1:9876
  producer:
    #必须指定group
    group: test-group
编写生产者发送消息代码
controller:
@RestController
@RequestMapping("/admin/shares")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ShareAdminController {
     
    private final ShareService shareService;
    @PutMapping("/audit/{id}")
    public Share auditById(@PathVariable Integer id,@RequestBody ShareAuditDTO shareAuditDTO){
     
        //TODO 认证授
        return this.shareService.auditById(id,shareAuditDTO);
    }
}
service:
@Transactional(rollbackFor = Exception.class)
    public Share auditById(Integer id, ShareAuditDTO auditDTO) {
     
        //1.查询share是否存在,如果不存在或者当前的audit_status != NOT_YET,那么抛异常
        Share share = this.shareMapper.selectByPrimaryKey(id);
        if(share == null){
     
            throw new IllegalArgumentException("参数非法!该分享不存在!");
        }
        if(!Objects.equals("NOT_YET", share.getAuditStatus())){
     
            throw new IllegalArgumentException("该分享不是待审核状态!");
        }
        //2.审核资源,将状态设为PSAA或REJECT
        share.setAuditStatus(auditDTO.getAuditStatusEnum().toString());
        this.shareMapper.updateByPrimaryKey(share);
        //3.如果PASS给用户添加积分,发生消息给rocketmq,让用户中心去消费
        UserAddBonusMsgDTO userAddBonusMsgDTO = new UserAddBonusMsgDTO();
        userAddBonusMsgDTO.setUserId(share.getUserId());
        userAddBonusMsgDTO.setBonus(50);
        rocketMQTemplate.convertAndSend("add-bonus", userAddBonusMsgDTO);

        return share;
    }
启动项目,发送请求,可以在消息里找到一条生产者发送的消息

练习Spring Cloud Alibaba 5 —— RocketMQ_第10张图片

练习Spring Cloud Alibaba 5 —— RocketMQ_第11张图片

4.总结各种消息中间件的模型使用

练习Spring Cloud Alibaba 5 —— RocketMQ_第12张图片

4.编写消费者代码

1.新建一个类AddBonusListener实现RocketMQListener,并在onMessage()方法里编写业务
@Service
@RocketMQMessageListener(consumerGroup = "consume-group", topic = "add-bonus")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AddBonusListener implements RocketMQListener<UserAddBonusMsgDTO> {
     

    private final UserMapper userMapper;
    private final BonusEventLogMapper bonusEventLogMapper;

    @Override
    public void onMessage(UserAddBonusMsgDTO message) {
     
        //编写业务逻辑
        //1.为用户添加积分
        Integer userId = message.getUserId();
        User user = userMapper.selectByPrimaryKey(userId);
        user.setBonus(user.getBonus() + message.getBonus());
        this.userMapper.updateByPrimaryKeySelective(user);
        //2.记录日志到bonus_event_log表中
        BonusEventLog bonusEventLog = new BonusEventLog();
        bonusEventLog.setUserId(userId);
        bonusEventLog.setValue(user.getBonus());
        bonusEventLog.setDescription("投稿通过添加用户积分");
        bonusEventLog.setEvent("CONTRIBUTE");
        bonusEventLog.setCreateTime(new Date());
        this.bonusEventLogMapper.insert(bonusEventLog);
    }
}
2.启动项目可以看到消费者接接收了生产者发送的信息,成功执行了业务

练习Spring Cloud Alibaba 5 —— RocketMQ_第13张图片

5.RocketMQ分布式事务

1.分布式事务流程图

练习Spring Cloud Alibaba 5 —— RocketMQ_第14张图片

2.分布式事务的消息三态
  • Commit :提交事务信息,消费者可以消费此信息
  • RollBack :回滚事务信息,broker会删除该信息,消费者不能消费该信息
  • UNKNOWN :broker需要回查该信息的状态
3.改写代码

重构ShareService里的auditById方法

public Share auditById(Integer id, ShareAuditDTO auditDTO) {
     
        //1.查询share是否存在,如果不存在或者当前的audit_status != NOT_YET,那么抛异常
        Share share = this.shareMapper.selectByPrimaryKey(id);
        if(share == null){
     
            throw new IllegalArgumentException("参数非法!该分享不存在!");
        }
        if(!Objects.equals("NOT_YET", share.getAuditStatus())){
     
            throw new IllegalArgumentException("该分享不是待审核状态!");
        }

        //3.如果PASS给用户添加积分,发生消息给rocketmq,让用户中心去消费
        if(AuditStatusEnum.PASS.equals(auditDTO.getAuditStatusEnum())){
     
            UserAddBonusMsgDTO userAddBonusMsgDTO = new UserAddBonusMsgDTO();
            userAddBonusMsgDTO.setUserId(share.getUserId());
            userAddBonusMsgDTO.setBonus(50);
            this.rocketMQTemplate.sendMessageInTransaction(
                    "tx-add-bonus-group",
                    "add-bonus",
                    MessageBuilder.withPayload(userAddBonusMsgDTO)
                            .setHeader(RocketMQHeaders.TRANSACTION_ID, UUID.randomUUID().toString())
                            .setHeader("share_id",id)
                            .build(),
                    auditDTO
            );

        }else{
     
            this.auditByIdInDB(id,auditDTO);
        }

        return share;
    }

ShareService里添加auditByIdInDBauditByIdInWithRocketMqLog两个方法

	@Transactional(rollbackFor = Exception.class)
    public void auditByIdInDB(Integer id,ShareAuditDTO auditDTO){
     
        Share share = new Share();
        share.setId(id);
        share.setAuditStatus(auditDTO.getAuditStatusEnum().toString());
        share.setReason(auditDTO.getReason());
        this.shareMapper.updateByPrimaryKeySelective(share);
    }

    @Transactional(rollbackFor = Exception.class)
    public void auditByIdInWithRocketMqLog(Integer id,ShareAuditDTO auditDTO,String transactionId){
     
        this.auditByIdInDB(id,auditDTO);

        RocketmqTransactionLog rocketmqTransactionLog = new RocketmqTransactionLog();
        rocketmqTransactionLog.setTransactionId(transactionId);
        rocketmqTransactionLog.setLog("审核分享");
        this.rocketmqTransactionLogMapper.insert(rocketmqTransactionLog);
    }

添加AddBonusTransactionListener 方法并实现RocketMQLocalTransactionListener

@RocketMQTransactionListener(txProducerGroup = "tx-add-bonus-group")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class AddBonusTransactionListener implements RocketMQLocalTransactionListener {
     
    private final ShareService shareService;
    private final RocketmqTransactionLogMapper rocketmqTransactionLogMapper;

    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object obj) {
     
        MessageHeaders headers = message.getHeaders();

        String transactionId = (String) headers.get(RocketMQHeaders.TRANSACTION_ID);
        Integer shareId = Integer.valueOf((String) headers.get("share_id"));
        try {
     
            this.shareService.auditByIdInWithRocketMqLog(shareId, (ShareAuditDTO) obj, transactionId);
            return RocketMQLocalTransactionState.COMMIT;
        }catch (Exception e){
     
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
     
        MessageHeaders headers = message.getHeaders();
        String transactionId = (String) headers.get(RocketMQHeaders.TRANSACTION_ID);
        RocketmqTransactionLog rocketmqTransactionLog = new RocketmqTransactionLog();
        rocketmqTransactionLog.setTransactionId(transactionId);
        RocketmqTransactionLog rocketmqTransactionLog1 = this.rocketmqTransactionLogMapper.selectOne(rocketmqTransactionLog);
        if(rocketmqTransactionLog1 != null){
     
            return RocketMQLocalTransactionState.COMMIT;
        }
        return RocketMQLocalTransactionState.ROLLBACK;
    }
}

6.Spring Cloud Stream

1.简介

Spring Cloud Stream是一个框架,用于构建与共享消息传递系统连接的高度可扩展的事件驱动型微服务。

2.使用Spring Cloud Stream编写生产者

pom文件添加依赖
<dependency>
   <groupId>org.springframework.cloudgroupId>
   <artifactId>spring-cloud-starter-stream-rocketmqartifactId>
dependency>
启动类上添加注解
@EnableBinding(Source.class)
yml写配置
spring:
  cloud:
    stream:
      rocketmq:
        binder:
          name-server: 127.0.0.1:9876
      bindings:
        output:
          # 用来指定topic
          destination: add-bonus
测试代码
@Autowired
    private Source source;

    @GetMapping("/test-stream")
    public String testStream(){
     
        this.source.output()
                .send(
                	MessageBuilder.withPayload("消息体").build()
                );
                return "success";
    }
启动发起测试,可以看到RocketMQ控制台里收到了信息

练习Spring Cloud Alibaba 5 —— RocketMQ_第15张图片

3.使用Spring Cloud Stream编写消费者

pom文件添加依赖
<dependency>
   <groupId>org.springframework.cloudgroupId>
   <artifactId>spring-cloud-starter-stream-rocketmqartifactId>
dependency>
启动类上添加注解
@EnableBinding(Sink.class)
yml写配置
spring:
  cloud:
    stream:
      rocketmq:
        binder:
          name-server: 127.0.0.1:9876
      bindings:
        input:
          # 用来指定topic
          destination: add-bonus
          # 这里的group必须配置
          group: binder-group
测试代码
@Service
@Slf4j
public class TestStreamConsumer {
     

    @StreamListener(Sink.INPUT)
    public void receive(String messageBody){
     
        log.info("通过Stream收到了消息:messageBody = {}", messageBody);
    }

}
测试结果在这里插入图片描述

4.自定义接口——发送消息

添加自定义接口MySource
public interface MySource {
     
    String MY_OUTPUT = "my-output";

    @Output(MY_OUTPUT)
    MessageChannel output();
}
修改启动类的注解
@EnableBinding(MySource.class)
修改yml的配置
spring:
  cloud:
    stream:
      rocketmq:
        binder:
          name-server: 127.0.0.1:9876
      bindings:
        output:
          # 用来指定topic
          destination: add-bonus
        my-output:
          # 用来指定topic
          destination: stream-my-topic
测试代码
	@Autowired
    private MySource mySource;

    @GetMapping("/test-stream-mysource")
    public String testStreamMysource(){
     
        this.mySource.output()
                .send(
                        MessageBuilder.withPayload("mySource消息体").build()
                );
        return "success";
    }
启动测试结果

练习Spring Cloud Alibaba 5 —— RocketMQ_第16张图片

5.自定义接口——消费消息

添加自定义接口MySink
public interface MySink {
     

    String MY_INPUT = "my-input";

    @Input(MY_INPUT)
    SubscribableChannel input();
}
修改启动类的注解
@EnableBinding({
     Sink.class, MySink.class})
修改yml的配置
spring:
  cloud:
    stream:
      rocketmq:
        binder:
          name-server: 127.0.0.1:9876
      bindings:
        input:
          # 用来指定topic
          destination: add-bonus
          # 这里的group必须配置
          group: binder-group
        my-input:
          destination: stream-my-topic
          group: my-group
测试代码
@Service
@Slf4j
public class MyTestStreamConsumer {
     
    @StreamListener(MySink.MY_INPUT)
    public void receive(String messageBody){
     
        log.info("MySink通过Stream收到了消息:messageBody = {}", messageBody);
    }
}
启动测试结果

在这里插入图片描述

未完待续!!!

上一篇: 练习Spring Cloud Alibaba —— 4.
下一篇: 练习Spring Cloud Alibaba —— 6.

你可能感兴趣的:(java)