spring cloud alibaba开发笔记十一(基于SpringCloud Stream构建消息驱动微服务)

SpringBoot集成Kafka构建消息驱动微服务

下载、安装Kafka

◆下载Kafka : https://kafka.apache.org/quickstart

◆解压、启动ZK和Kafka Server即可(使用默认配置)

SpringCloud Stream消息驱动组件概览

◆负责与中间件交互的抽象绑定器: Binder

◆发送消息与接收消息的应用通信信道: Input、Output

创建相关的微服务e-commerce-stream-client

引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>e-commerce-springcloud</artifactId>
        <groupId>com.taluohui.ecommerce</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>e-commerce-stream-client</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <!-- 模块名及描述信息 -->
    <name>e-commerce-stream-client</name>
    <description>Stream Client</description>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- 创建工程需要的两个依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- web 工程 -->
        <dependency>
            <groupId>com.imooc.ecommerce</groupId>
            <artifactId>e-commerce-mvc-config</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- zipkin = spring-cloud-starter-sleuth + spring-cloud-sleuth-zipkin-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
            <version>2.5.0.RELEASE</version>
        </dependency>
        <!-- SpringCloud Stream-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream</artifactId>
        </dependency>
        <!-- SpringCloud Stream + Kafka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-kafka</artifactId>
        </dependency>
        <!-- SpringCloud Stream + RocketMQ -->
        <!--        <dependency>-->
        <!--            <groupId>com.alibaba.cloud</groupId>-->
        <!--            <artifactId>spring-cloud-starter-stream-rocketmq</artifactId>-->
        <!--        </dependency>-->
    </dependencies>

    <!--
        SpringBoot的Maven插件, 能够以Maven的方式为应用提供SpringBoot的支持,可以将
        SpringBoot应用打包为可执行的jar或war文件, 然后以通常的方式运行SpringBoot应用
     -->
    <build>
        <finalName>${artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

添加配置文件

server:
  port: 8006
  servlet:
    context-path: /ecommerce-stream-client

spring:
  application:
    name: e-commerce-stream-client
  cloud:
    nacos:
      # 服务注册发现
      discovery:
        enabled: true # 如果不想使用 Nacos 进行服务注册和发现, 设置为 false 即可
        server-addr: 127.0.0.1:8848
        namespace: 1bc13fd5-843b-4ac0-aa55-695c25bc0ac6
        metadata:
          management:
            context-path: ${server.servlet.context-path}/actuator
    # 消息驱动的配置
    stream:
      # SpringCloud Stream + Kafka
      kafka:
        binder:
          brokers: 127.0.0.1:9092
          auto-create-topics: true  # 如果设置为false, 就不会自动创建Topic, 你在使用之前需要手动创建好
      # SpringCloud Stream + RocketMQ
      #      rocketmq:
      #        binder:
      #          name-server: 127.0.0.1:9876
      # 开启 stream 分区支持
      instanceCount: 1  # 消费者的总数
      instanceIndex: 0  # 当前消费者的索引
      bindings:
        # 默认发送方
        output:      # 这里用 Stream 给我们提供的默认 output 信道
          destination: ecommerce-stream-client-default    # 消息发往的目的地, Kafka 中就是 Topic
          content-type: text/plain    # 消息发送的格式, 接收端不用指定格式, 但是发送端要
          # 消息分区
          producer:
            # partitionKeyExpression: payload.author  # 分区关键字, payload 指的是发送的对象, author 是对象中的属性
            partitionCount: 1   # 分区大小
            # 使用自定义的分区策略, 注释掉 partitionKeyExpression
            partitionKeyExtractorName: qinyiPartitionKeyExtractorStrategy
            partitionSelectorName: qinyiPartitionSelectorStrategy
        # 默认接收方
        input:      # 这里用 Stream 给我们提供的默认 input 信道
          destination: ecommerce-stream-client-default
          group: e-commerce-qinyi-default
          # 消费者开启分区支持
          consumer:
            partitioned: true

        # Qinyi 发送方
        qinyiOutput:
          destination: ecommerce-stream-client-qinyi
          content-type: text/plain
        # Qinyi 接收方
        qinyiInput:
          destination: ecommerce-stream-client-qinyi
          group: e-commerce-qinyi-qinyi

  # spring-kafka 的配置
  kafka:
    bootstrap-servers: 127.0.0.1:9092
    producer:
      retries: 3
    consumer:
      auto-offset-reset: latest
  sleuth:
    sampler:
      # ProbabilityBasedSampler 抽样策略
      probability: 1.0  # 采样比例, 1.0 表示 100%, 默认是 0.1
      # RateLimitingSampler 抽样策略
      rate: 100  # 每秒间隔接受的 trace 量
  zipkin:
    sender:
      type: kafka # 默认是 http
    base-url: http://localhost:9411/

# 暴露端点
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always

启动项

/**
 * <h1>基于 SpringCloud Stream 构建消息驱动微服务</h1>
 * */
@EnableDiscoveryClient
@SpringBootApplication
public class StreamClientApplication {
    public static void main(String[] args) {

        SpringApplication.run(StreamClientApplication.class, args);
    }
}

创建vo包,新建对象

/**
 * <h1>消息传递对象: SpringCloud Stream + Kafka/RocketMQ</h1>
 * */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class QinyiMessage {

    private Integer id;
    private String projectName;
    private String org;
    private String author;
    private String version;

    /**
     * <h2>返回一个默认的消息, 方便使用</h2>
     * */
    public static QinyiMessage defaultMessage() {

        return new QinyiMessage(
                1,
                "e-commerce-stream-client",
                "imooc.com",
                "Qinyi",
                "1.0"
        );
    }
}

新建stream包

使用默认信道发消息

发送方

/**
 * <h1>使用默认的通信信道实现消息的发送</h1>
 * */
@Slf4j
@EnableBinding(Source.class)
public class DefaultSendService {
    private final Source source;

    public DefaultSendService(Source source) {
        this.source = source;
    }

    /**
     * <h2>使用默认的输出信道发送消息</h2>
     * */
    public void sendMessage(QinyiMessage message) {

        String _message = JSON.toJSONString(message);
        log.info("in DefaultSendService send message: [{}]", _message);

        // Spring Messaging, 统一消息的编程模型, 是 Stream 组件的重要组成部分之一
        source.output().send(MessageBuilder.withPayload(_message).build());
    }
}

接收方

/**
 * <h1>使用默认的信道实现消息的接收</h1>
 * */
@Slf4j
@EnableBinding(Sink.class)
public class DefaultReceiveService {

    /**
     * <h2>使用默认的输入信道接收消息</h2>
     * */
    @StreamListener(Sink.INPUT)
    public void receiveMessage(Object payload) {

        log.info("in DefaultReceiveService consume message start");
        QinyiMessage qinyiMessage = JSON.parseObject(
                payload.toString(), QinyiMessage.class
        );
        // 消费消息
        log.info("in DefaultReceiveService consume message success: [{}]",
                JSON.toJSONString(qinyiMessage));
    }
}

使用自定义信道发消息

自定义输出信道

/**
 * <h1>自定义输出信道</h1>
 * */
public interface QinyiSource {

    String OUTPUT = "qinyiOutput";

    /** 输出信道的名称是 qinyiOutput, 需要使用 Stream 绑定器在 yml 文件中声明 */
    @Output(QinyiSource.OUTPUT)
    MessageChannel qinyiOutput();
}
使用自定义的输出信道发送消息
/**
 * <h1>使用自定义的通信信道 QinyiSource 实现消息的发送</h1>
 * */
@Slf4j
@EnableBinding(QinyiSource.class)
public class QinyiSendService {

    private final QinyiSource qinyiSource;

    public QinyiSendService(QinyiSource qinyiSource) {
        this.qinyiSource = qinyiSource;
    }

    /**
     * <h2>使用自定义的输出信道发送消息</h2>
     * */
    public void sendMessage(QinyiMessage message) {

        String _message = JSON.toJSONString(message);
        log.info("in QinyiSendService send message: [{}]", _message);
        qinyiSource.qinyiOutput().send(
                MessageBuilder.withPayload(_message).build()
        );
    }
}
自定义输入信道
/**
 * <h1>自定义输入信道</h1>
 * */
public interface QinyiSink {

    String INPUT = "qinyiInput";

    /** 输入信道的名称是 qinyiInput, 需要使用 Stream 绑定器在 yml 文件中配置*/
    @Input(QinyiSink.INPUT)
    SubscribableChannel qinyiInput();
}
使用自定义的输入信道实现消息的接收
/**
 * <h1>使用自定义的输入信道实现消息的接收</h1>
 * */
@Slf4j
@EnableBinding(QinyiSink.class)
public class QinyiReceiveService {

    /** 使用自定义的输入信道接收消息 */
    @StreamListener(QinyiSink.INPUT)
    public void receiveMessage(@Payload Object payload) {

        log.info("in QinyiReceiveService consume message start");
        QinyiMessage qinyiMessage = JSON.parseObject(payload.toString(), QinyiMessage.class);
        log.info("in QinyiReceiveService consume message success: [{}]",
                JSON.toJSONString(qinyiMessage));
    }
}

在配置文件中也要做相应的配置

        # Qinyi 发送方
        qinyiOutput:
          destination: ecommerce-stream-client-qinyi
          content-type: text/plain
        # Qinyi 接收方
        qinyiInput:
          destination: ecommerce-stream-client-qinyi
          group: e-commerce-qinyi-qinyi

SpringCloud Stream消息分组和消费分区的配置与说明

消息分组

◆应用的不同实例放在一个消费者组中 ,每一条消息只会被一个实例消费

◆消费者组的思想是通过多实例扩展服务吞吐量,且不会造成消息的重复消费

只需要在配置文件的接收方配置上group

        # Qinyi 接收方
        qinyiInput:
          destination: ecommerce-stream-client-qinyi
          group: e-commerce-qinyi-qinyi

消费分区

◆消费分区的作用就是为了确保具有共同特征标识的数据由同一个消费者实例进行处理

    # 消息驱动的配置
    stream:
      # 开启 stream 分区支持
      instanceCount: 1  # 消费者的总数
      instanceIndex: 0  # 当前消费者的索引
      bindings:
        # 默认发送方
        output:      # 这里用 Stream 给我们提供的默认 output 信道
          destination: ecommerce-stream-client-default    # 消息发往的目的地, Kafka 中就是 Topic
          content-type: text/plain    # 消息发送的格式, 接收端不用指定格式, 但是发送端要
          # 消息分区
          producer:
            # partitionKeyExpression: payload.author  # 分区关键字, payload 指的是发送的对象, author 是对象中的属性
            partitionCount: 1   # 分区大小
            # 使用自定义的分区策略, 注释掉 partitionKeyExpression
            partitionKeyExtractorName: qinyiPartitionKeyExtractorStrategy
            partitionSelectorName: qinyiPartitionSelectorStrategy
        # 默认接收方
        input:      # 这里用 Stream 给我们提供的默认 input 信道
          destination: ecommerce-stream-client-default
          group: e-commerce-qinyi-default
          # 消费者开启分区支持
          consumer:
            partitioned: true
/**
 * <h1>自定义从 Message 中提取 partition key 的策略</h1>
 * */
@Slf4j
@Component
public class QinyiPartitionKeyExtractorStrategy implements PartitionKeyExtractorStrategy {

    @Override
    public Object extractKey(Message<?> message) {
        QinyiMessage qinyiMessage = JSON.parseObject(
                message.getPayload().toString(), QinyiMessage.class
        );
        // 自定义提取 key
        String key = qinyiMessage.getProjectName();
        log.info("SpringCloud Stream Qinyi Partition Key: [{}]", key);
        return key;
    }
}
/**
 * <h1>决定 message 发送到哪个分区的策略</h1>
 * */
@Slf4j
@Component
public class QinyiPartitionSelectorStrategy implements PartitionSelectorStrategy {

    /**
     * <h2>选择分区的策略</h2>
     * */
    @Override
    public int selectPartition(Object key, int partitionCount) {

        int partition = key.toString().hashCode() % partitionCount;
        log.info("SpringCloud Stream Qinyi Selector info: [{}], [{}], [{}]",
                key.toString(), partitionCount, partition);

        return partition;
    }
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值