Spring cloud stream 其他特性总结使用

目录

 

背景

自定义接口

生产者

定义接口

加注解

加配置

发消息

消费者

定义接口

改注解

加配置

消费消息

消息筛选(Tag、自定义消息筛选)

生产者

加接口

加配置

发送消息

消费者

加接口

加配置

消费消息

输出结果

消息筛选(sql92 方式)

生产者

加接口

加配置

发送消息

消费者

加接口

加配置

消费消息

 输出结果

事务消息发送

生产者

加接口

加配置

发送消息

事务消息监听类

分区消息

生产者

加接口

加配置

发送消息

消费者

加接口

加配置

消费消息

 异常处理

全局异常处理编码

总结


背景

上一篇博文Spring cloud Stream 入门 讲述了spring cloud stream 的背景,组成,以及基于rocketMq 搭建简单的demo 。完成消息的发送和消费。但是在实际工作过程中,我们要用到的远远不局限于简单的demo .

比如自定义接口、事务消息、消息的筛选,异常处理,消息的分区等;下面一一来列举出这些基于spring cloud stream 该如何实现呢?

自定义接口

在上一篇博文demo中,我们发送消息使用的是Source.class 中的output 方法,在配置文件中为output 指定了destination,因为一个channel 接口只能绑定一个destination ,所以在业务系统各种场景中,一个output 往往不能满足我们对消息的发送需求。这时候我们可以根据自定义接口,实现指定outputChannel ;

生产者

定义接口

/**
 * @Author corn
 * @Date 2021/3/21 15:44
 */
public interface OutPutSource {

    String MY_CUSTOM_OUTPUT = "my-custom-output";

    @Output(MY_CUSTOM_OUTPUT)
    MessageChannel customOutPut();
}

加注解

在Application 启动类的@EnableBinding({OutputSource.class,Source.class}),目的通过spring ioc ,创建出对应的实例;

加配置

spring:
    cloud:
        stream:
          rocketmq:
            binder:
              name-server: localhost:9876
          bindings:
            output:
              # 用例指定topic
              destination: stream-test-topic
            my-custom-output:
              destination: custom-output-send

发消息


/**
 * @Author corn
 * @Date 2021/3/21 15:47
 */
@Slf4j
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
@RestController("/testStream")
public class StreamMqTest {

    private final OutPutSource outPutSource;

    /**
     *@描述 实现自定义接口发送消息
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/3/21
     */
    @GetMapping("/customOutput")
    public Boolean SendCustomMessage(){
        boolean result = this.outPutSource.customOutPut().send(MessageBuilder.withPayload("测试发送自定义接口消息").build());
        return result;
    }
}

消费者

定义接口

/**
 * @Author corn
 * @Date 2021/3/21 15:52
 */
public interface InputSink {

    String CUSTOM_INPUT = "custom-input";

    @Input(CUSTOM_INPUT)
    SubscribableChannel customInput();
}

改注解

在Application 启动类的@EnableBinding({InputSink.class,Sink.class}),目的通过spring ioc ,创建出对应的实例;

加配置

spring:
    cloud:
        stream:
            rocketmq:
                binder:
                  name-server: 127.0.0.1:9876
             bindings:
                 input:
                  destination: stream-test-topic
                      # 如果使用的是RocketMq,group 一定要填写,如果使用的是非RocketMq ,group 可以不填写
                  group: binder-group
                custom-input:
                  destination: custom-output-send
                  group: custom-group1

消费消息

/**
 * @Author corn
 * @Date 2021/3/21 15:53
 */
@Service
@Slf4j
public class StreamMqTest {

    /**
     *@描述 自定义接口消息消费
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/3/21
     */
    @StreamListener(InputSink.CUSTOM_INPUT)
    public void messageConsumer(String messageBody){
        log.info("获取消息消费内容为:{}",messageBody);
    }
}

消息筛选(Tag、自定义消息筛选)

RocketMq 支持消息按照tag ,或者sql92 的方式进行筛选消费,下面我们就基于spring cloud stream 实现rocket mq 的消息筛选功能。我们知道rocketMq 消息的筛选是在consumer 实现的,所以对于消息生产者么有什么改变,重点在于consumer 端的修改;

生产者

加接口

/**
 * @Author corn
 * @Date 2021/3/21 15:44
 */
public interface OutPutSource {

    String MY_CUSTOM_OUTPUT_TAG = "my-custom-output-tag";

    

    @Output(MY_CUSTOM_OUTPUT_TAG)
    MessageChannel customOutPutTag();
}

加配置

spring:
    cloud:
        stream:
          rocketmq:
            binder:
              name-server: localhost:9876
          bindings:
            my-custom-output-tag:
              destination: custom-output-tag

发送消息



/**
 * @Author corn
 * @Date 2021/3/21 15:47
 */
@Slf4j
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
@RestController("/testStream")
public class StreamMqTest {

    private final OutPutSource outPutSource;



    /**
     *@描述 自定义发送header 消息头消息。注意: header 中的内容在传输过程中都会被转换为String 字符串;
     * 及如果想在header 中携带对象,需要先将对象序列化成json 串,然后传输;
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/3/21
     */
    @GetMapping("/customOutPutTag")
    public Boolean SendCustomTagMessage(){
        // 分别发送tag 为tag1 和tag2 的消息,并且tag1 消息中指定version 分别为1 和2 。用来做消费端消息的自定义筛选
        this.outPutSource.customOutPutTag().send(MessageBuilder.withPayload("发送带有header 的消息体,供消费端tag1筛选")
                .setHeader(RocketMQHeaders.TAGS,"tag1")
                .setHeader("version","1")
                .build());

        this.outPutSource.customOutPutTag().send(MessageBuilder.withPayload("发送带有header 的消息体,供消费端tag1筛选")
                .setHeader(RocketMQHeaders.TAGS,"tag1")
                .setHeader("version","2")
                .build());

        this.outPutSource.customOutPutTag().send(MessageBuilder.withPayload("发送带有header 的消息体,供消费者tag2筛选")
        .setHeader(RocketMQHeaders.TAGS,"tag2")
        .build());
        return true;
    }
}

 

消费者

加接口

/**
 * @Author corn
 * @Date 2021/3/21 15:52
 */
public interface InputSink {

    String CUSTOM_INPUT_TAG = "custom-input-tag";


    @Input(CUSTOM_INPUT_TAG)
    SubscribableChannel customInputTag();
}

加配置

spring:
    cloud:
        stream:
            rocketmq:
                binder:
                  name-server: 127.0.0.1:9876
                # 指定bindings 消费者匹配规则
                bindings:
                    custom-input-tag:
                        consumer:
                          # 表示筛选custom-input-tag channel 下tag1 的消息,多个可以用||来表示,比如tags: tag1||tag2 表示根据tag1 或者tag2 进行消费
                          tags: tag1
             bindings:
                custom-input-tag:
                  destination: custom-output-send-tag
                  group: custom-group2

消费消息

  /**
     *@描述 condition 用作筛选,该方法表示只消费InputSink.custom_input_tag下 headers 中version == 1 的消息
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/3/21
     */
    @StreamListener(value = InputSink.CUSTOM_INPUT_TAG,condition = "headers['version']=='1'")
    public void messageConsumer1(String messageBody){
        log.info("获取tag 和自定义header 筛选后的消息内容为:{}",messageBody);
    }

输出结果

消息筛选(sql92 方式)

注意:sql92 是rocketMq 特有的消息筛选方式,sql92 支持> 、<、=、or、and 等方式;

Sql92 方式的消息筛选,可以更加灵活的对消息内容进行筛选,比如通过sql92 的方式筛选某个区间的消息等等;sql92 支持使用

生产者

加接口

/**
 * @Author corn
 * @Date 2021/3/21 15:44
 */
public interface OutPutSource {

    String MY_CUSTOM_SQL_92 = "my-custom-sql92";


    @Output(MY_CUSTOM_SQL_92)
    MessageChannel customSql();
}

加配置

spring:
    cloud:
        stream:
          rocketmq:
            binder:
              name-server: localhost:9876
          bindings:
            my-custom-sql92:
              destination:  custom-output-sql

发送消息

 /**
     *@描述  发送消息
     *@参数 
     *@返回值 
     *@创建人  zj
     *@创建时间  2021/3/21
     */
    @GetMapping("/sendSql")
    public Boolean sendsql92Message(){
        this.outPutSource.customSql().send(
                MessageBuilder.withPayload("携带sql92的消息体1")
                        .setHeader("num",1)
                        .build()
        );

        this.outPutSource.customSql().send(
                MessageBuilder.withPayload("携带sql92的消息体2")
                        .setHeader("num",2)
                        .build()
        );

        this.outPutSource.customSql().send(
                MessageBuilder.withPayload("携带sql92的消息体3")
                        .setHeader("num",3)
                        .build()
        );
        return true;
    }

消费者

加接口

/**
 * @Author corn
 * @Date 2021/3/21 15:52
 */
public interface InputSink {

    
    String CUSTOM_INPUT_SQL = "custom-input-sql";


    
    @Input(CUSTOM_INPUT_SQL)
    SubscribableChannel customInputSql();
}

加配置

spring:
    cloud:
        stream:
            rocketmq:
                binder:
                  name-server: 127.0.0.1:9876
                # 指定bindings 消费者匹配规则
                bindings:
                    custom-input-sql:
                        consumer:
                          # 表示筛选custom-input-sql channel 下的header 中携带的num 大于2 的消息
                          sql: 'num>2'
             bindings:
                custom-input-sql:
                  destination: custom-output-sql
                  group: custom-group3

消费消息

  /**
     *@描述 根据sql92 方式筛选消息
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/3/21
     */
    @StreamListener(InputSink.CUSTOM_INPUT_SQL)
    public void MessageConsumer2(String messageBody){
        log.info("获取sql92 筛选后的消息内容为:{}",messageBody);
    }

 输出结果

事务消息发送

rocketMq 完美支持了事务消息,但是当我们使用springcloud stream 时,是如何支持事务消息的呢?

事务消息的发送,主要体现在produce 端 对消息的处理;

生产者

加接口


/**
 * @Author corn
 * @Date 2021/3/21 15:44
 */
public interface OutPutSource {

  
    String MY_CUSTOM_TRANSACTION = "my-custom-transaction";


    @Output(MY_CUSTOM_TRANSACTION)
    MessageChannel customTransaction();
}

加配置

spring:
    cloud:
        stream:
          rocketmq:
            binder:
              name-server: localhost:9876
            bindings:
              # 对my-custom-transaction channel 开启事务消息
              my-custom-transaction:
                producer:
                  transactional: true
              # 注意,这里的group 要和事务listener 中的txProducerGroup 值保持一致,否则listener 监听到事务消息
                  group: tx-test-transcation-message
          bindings:
            my-custom-transaction:
              destination: custom-transaction

发送消息

   /**
     *@描述 发送事务消息
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/3/21
     */
    @GetMapping("/sendTranscationMessage")
    public boolean sendTranscationMessage(){

        this.outPutSource.customTransaction().send(
                // 消息内容,setHeader 主要传递参数,供事务回调使用
                MessageBuilder.withPayload("事务消息对象")
                        .setHeader(RocketMQHeaders.TRANSACTION_ID,1)
                        .setHeader("share_id",3)
                        .setHeader("user", "需要携带对象的json串")
                        .build()
        );
        return true;
    }

事务消息监听类

package com.springcloud.alibaba.controller;

import com.alibaba.fastjson.JSONObject;
import com.springcloud.alibaba.model.CanalUser;
import com.springcloud.alibaba.rocketmq.OutPutSource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;

/**
 * @Author corn
 * @Date 2021/3/21 18:31
 */
@RocketMQTransactionListener(txProducerGroup = "tx-test-transcation-message")
@RequiredArgsConstructor(onConstructor = @_(@Autowired))
@Slf4j
public class TranscationListener implements RocketMQLocalTransactionListener {




    /**
     *@描述 执行本地事务
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/3/21
     */
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
        MessageHeaders headers = message.getHeaders();
        String transactionId = (String)headers.get(RocketMQHeaders.TRANSACTION_ID);
        try {
            log.info("事务消息内容为:{}",message);
            log.info("获取到事务的transactionId 为{}",transactionId);

            // 调用本地方法事务逻辑,如果执行成功,则提交事务,反之回滚事务
            Thread.sleep(500);

            // 提交事务
            return RocketMQLocalTransactionState.COMMIT;
        }catch (Exception e){
            log.error("异常:{}",e.getMessage());
            // 事务回滚
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    /**
     *@描述  事务回调方法,发生在生产者没有告诉broker事务消息的执行状态,broker会调用回查方法查询本地事务执行状态;
     *       需要注意的是,broker 并非无休止的轮查,默认回查15次,如果还未查到消息的状态,那么就会回滚该条事务消息;
     *       注意: RocketMq 3.1.2之前的有事务消息回查功能的版本。消息回查功能基于文件系统,回查后得到的结果以及正常的处理结果Commit/Rollback都会修改CommitLog里PREPARED消息的状态
     *       ,这会导致内存中脏页过多,有隐患。在之后的版本移除了基于文件系统的状态修改机制,对事务消息的处理流程进行重做,移除了消息回查功能。
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/3/16
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
        return null;
    }
}

分区消息

在一些业务场景下,集群环境下,我们希望同一类型的消息,在消费组中能够在同一个实例内进行处理。比如同一个订单号在同一个实例内进行处理,这样可以在一定程度上保证消息的有序消费;下面来具体实现消息的分区处理;

生产者

加接口

/**
 * @Author corn
 * @Date 2021/3/21 15:44
 */
public interface OutPutSource {

    
    String MY_CUSTOM_PARTITION = "my-custom-partition";


    @Output(MY_CUSTOM_PARTITION)
    MessageChannel customPartition();
}

加配置

spring:
    cloud:
        stream:
          rocketmq:
            binder:
              name-server: localhost:9876
            
          bindings:
            my-custom-partition:
              destination: custom-partition
                  producer:
                    # 根据什么字段进行分区,payload 是消息体中的对象。这样就能够保证同一个对象落到同一个实例 中。当然我们也可以根据payload 中的属性进行分区,比如根据订单号payload.orderId。前提需要保证消息体汇总含有orderId 属性字段
                    partitionKeyExpression: payload
                    # 分区个数,这里分了2个区
                    partitionCount: 2

发送消息

/**
     *@描述 发送分区消息
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/3/21
     */
    @GetMapping("/sendPartitionMessage")
    public boolean sendPartitionMessage(){
        CanalUser build = CanalUser.builder().name("张三").sex("男").age(2).build();
        boolean result = this.outPutSource.customPartition().send(MessageBuilder.withPayload(build).build());
        CanalUser build1 = CanalUser.builder().name("张三").sex("男").age(1).build();
        boolean result1 = this.outPutSource.customPartition().send(MessageBuilder.withPayload(build1).build());
        log.info("消息发送:{}",result);
        return true;
    }

消费者

加接口

/**
 * @Author corn
 * @Date 2021/3/21 15:52
 */
public interface InputSink {

    
    String CUSTOM_INPUT_PARTITION = "custom-input-partition";



    @Input(CUSTOM_INPUT_PARTITION)
    SubscribableChannel customInputPartition();
}

加配置

spring:
    cloud:
        stream:
            rocketmq:
                binder:
                  name-server: 127.0.0.1:9876
               
             bindings:
                custom-input-partition:
                  destination: custom-partition
                  group: custom-group4
           # 表示分区的个数
          instance-count: 2
          # 表示当前的分区是什么,从0 开始,设置的当前分区的最大分区不得超过分区个数,否则设置当前分区无效
          instance-index: 1

消费消息

  /**
     *@描述 分区消息消费
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/3/21
     */
    @StreamListener(InputSink.CUSTOM_INPUT_PARTITION)
    public void MessageConsumer3(String messageBody){
        log.info("获取分区消息消费内容:{}",messageBody);
    }

 异常处理

Spring Cloud Stream 异常处理有4类,分别是

自动重试

自定义错误处理逻辑

加入死信队列

从新加入队列消费

这里优先实现消息全局自定义逻辑处理,更多的异常处理方式可以参考大目老师的博文spring cloud stream 错误详解

全局异常处理编码

  /**
     *@描述 全局消息消费异常处理
     *@参数
     *@返回值
     *@创建人  corn
     *@创建时间  2021/3/19
     */
    @StreamListener("errorChannel")
    public void error(Message<?> message) {
        ErrorMessage errorMessage = (ErrorMessage) message;
       log.error("消息消费失败啦,异常消息内容为:{}",errorMessage);
    }

总结

基于上一篇简单的实现spring cloud stream 基础后,本文详细从编码层实现了sprign cloud stream 的其他属性。在日常的工作中,这些属性内容足以完成需求实现,更细致的在后期工作中遇到在进行记录;

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值