微服务专题15-Spring Cloud Stream 实现

前言

前面的章节我们讲了Spring Cloud Bus

本节,继续微服务专题的内容分享,共计16小节,分别是:

本节内容重点为:

  • 实现 Spring Cloud Stream for RocketMQ

Spring Cloud Stream 以及 Binder 架构

在这里插入图片描述

参考 Spring Cloud Stream Binder 已有实现

这里我们参考 Spring Cloud Stream 文档:

Binder 实现步骤

A typical binder implementation consists of the following:

  • A class that implements the Binder interface;
    实现 Binder 接口

  • A Spring @Configuration class that creates a bean of type Binder along with the middleware connection infrastructure.
    Binder 实现类上标注 @Configuration 注解

  • A META-INF/spring.binders file found on the classpath containing one or more binder definitions, as shown in the following example:
    META-INF/spring.binders 配置 Binder 名称和 Binder 实现自动装配类映射

  kafka:\
  org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfiguration

思考:假设有多个 Binder 实现 jar 同时存在,那么采用哪个呢?

  • 配置默认 Binder 名称
spring.cloud.stream.defaultBinder = rabbit
  • 指定某种 Binder 实现
spring.cloud.stream.bindings.input.binder=kafka
spring.cloud.stream.bindings.output.binder=rabbit

参考 Spring Cloud Stream Binder RabbitMQ

Spring Cloud Stream Binder RabbitMQ

参考 Spring Cloud Stream Binder Kafka

Spring Cloud Stream Binder Kafka

实现 Spring Cloud Stream Binder RocketMQ

我们仿造 Spring Cloud Stream Binder RabbitMQ与Spring Cloud Stream Binder Kafka的实现过程,开始实现Spring Cloud Stream Binder RocketMQ,一共分为三步:

  • RocketMQ -> 发消息、收消息

  • bindProducer 发消息

  • bindConsumer 收消息

预备工作

  1. 下载 RocketMQ
    使用的是4.7.1版本,安装过程略

  2. 启动命名服务器
    即mqname

  3. 启动 MQ 代理
    在这里插入图片描述

  4. 启动mq控制台(可有可无,主要为了演示作用)

在这里插入图片描述

实现步骤

  • 创建 spring-cloud-stream-binder-rocketmq 工程
<groupId>com.test</groupId>
<artifactId>spring-cloud-stream-binder-rocketmq</artifactId>
<version>0.0.1-SNAPSHOT</version>
  • 增加 Spring Cloud Stream Binder 依赖
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-stream</artifactId>
</dependency>
  • 增加 RocketMQ Client 依赖
<dependency>
	<groupId>org.apache.rocketmq</groupId>
	<artifactId>rocketmq-client</artifactId>
	<version>4.3.0</version>
</dependency>
  • 编写生产者demo
public class RocketMQProducerDemo {

    public static void main(String[] args) throws Exception {
        //Instantiate with a producer group name.
        DefaultMQProducer producer = new
                DefaultMQProducer("test-group");
        //这里配置自己的mq地址即可
        producer.setNamesrvAddr("127.0.0.1:9876");
        //Launch the instance.
        producer.start();
        //CountDownLatch countDownLatch = new CountDownLatch(100);
        //countDownLatch.await(10, TimeUnit.SECONDS);
        for (int i = 0; i < 100; i++) {
            //Create a message instance, specifying topic, tag and message body.
            Message msg = new Message("TopicTest-1" /* Topic */,
                    "TagA" /* Tag */,
                    ("Hello RocketMQ " +
                            i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
            );
            //Call send message to deliver message to one of brokers.
            SendResult sendResult = producer.send(msg);
            System.out.printf("%s%n", sendResult);
        }
        //Shut down once the producer instance is not longer in use.
        producer.shutdown();
    }
}
  • 验证结果
    启动mq后,并运行主函数

在这里插入图片描述
循环100次,这里显然运行成功!

  • 编写消费者demo
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("test-group");
        consumer.setNamesrvAddr("192.168.200.111:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

//        //set to broadcast mode
//        consumer.setMessageModel(MessageModel.BROADCASTING);

        consumer.subscribe("TopicTest-1", "TagA");

        consumer.registerMessageListener(new MessageListenerConcurrently() {

            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
                System.out.printf(Thread.currentThread().getName() + " Receive New Messages: " + msgs + "%n");
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        consumer.start();
        System.out.printf("Broadcast Consumer Started.%n");
    }
}
  • 验证结果
    启动mq后,并运行主函数

在这里插入图片描述
循环100次,消费成功!

Spring Cloud Stream Binder For RocketMQ Starter

我们现在就编写一个功能通用的jar包:spring-cloud-stream-binder-rocketmq,专门用以rocketmq与SpringCloud做绑定的一个服务。

首先我们实现RocketMQ的发消息:

public class RocketMQMessageChannelBinder implements
        Binder<MessageChannel, ConsumerProperties, ProducerProperties> {

    private static final String GROUP = "test-group";

    private static final String TOPIC = "TEST_TOPIC";

    private static final String TAG = "TEST_TAG";

    private static final String NAME_ADDRESS = "192.168.200.111:9876";

    @Override
    public Binding<MessageChannel> bindConsumer(String name, String group,
                                                MessageChannel inputChannel, ConsumerProperties consumerProperties) {

        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(GROUP);
        consumer.setNamesrvAddr(NAME_ADDRESS);
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

        try {
            consumer.subscribe(TOPIC, TAG);

            consumer.registerMessageListener(new MessageListenerConcurrently() {

                @Override
                public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                                ConsumeConcurrentlyContext context) {
                    System.out.printf(Thread.currentThread().getName() + " Receive New Messages: " + msgs + "%n");

                    msgs.forEach(msg -> {
                        byte[] body = msg.getBody();
                        // 发送消息到 消息管道
                        inputChannel.send(new GenericMessage<Object>(body));
                    });

                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
            });

            consumer.start();

        } catch (Exception e) {
            e.getMessage();
        }

        return () -> {
            System.out.println("consumer shutdown...");
            consumer.shutdown();
        };
    }

    /**
     * 发送消息
     *
     * @param name
     * @param outputChannel
     * @param producerProperties
     * @return
     */
    @Override
    public Binding<MessageChannel> bindProducer(String name, MessageChannel outputChannel, ProducerProperties producerProperties) {

        //Instantiate with a producer group name.
        DefaultMQProducer producer = new DefaultMQProducer(GROUP);

        producer.setNamesrvAddr(NAME_ADDRESS);
        //Launch the instance.
        try {
            producer.start();


            SubscribableChannel subscribableChannel = (SubscribableChannel) outputChannel;
            // 消息订阅回调
            subscribableChannel.subscribe(message -> {
                // 消息主题
                Object messageBody = message.getPayload();
                Message mqMessage = new Message();
                mqMessage.setTopic(TOPIC);
                mqMessage.setTags(TAG);
                try {
                    mqMessage.setBody(serialize(messageBody));
                    SendResult sendResult = producer.send(mqMessage);
                    System.out.printf("消息发送 : %s%n", sendResult);
                } catch (Exception e) {
                    e.getMessage();
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }

        return () -> {
            System.out.println("producer shutdown...");
            producer.shutdown();
        };
    }

    private byte[] serialize(Object serializable) throws IOException {

        if (serializable instanceof byte[]) {
            return (byte[]) serializable;
        }

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        // 通过 Java 序列化 将 Object 写入字节流
        objectOutputStream.writeObject(serializable);
        // 返回字节数组
        return outputStream.toByteArray();
    }
}

RocketMQMessageChannelBinderConfiguration配置:

@Configuration
public class RocketMQMessageChannelBinderConfiguration {

    @Bean
    public RocketMQMessageChannelBinder rocketMQMessageChannelBinder() {
        return new RocketMQMessageChannelBinder();
    }

}

加入配置文件实现自动装配类映射:

rocketmq=\
com.test.micro.services.spring.cloud.stream.binder.rocketmq.RocketMQMessageChannelBinderConfiguration

我们现在编写的jar包初步完成,可以称之为:spring-cloud-stream-binder-rocketmq (V1.0 版本),接下来我们如何测试呢?

在前面的章节我们多次提到了 spring-cloud-client-application 与 spring-cloud-server-application 两个服务,在此,同样地使用这两个模块集成spring-cloud-stream-binder-rocketmq (V1.0 版本):

  1. 客户端设置开放的api
    @GetMapping("/stream/send/rocketmq")
    public boolean streamSendToRocketMQ(@RequestParam String message) {
        // 获取 MessageChannel
        MessageChannel messageChannel = simpleMessageService.testChannel();
        return messageChannel.send(new GenericMessage(message));
    }

service实现:

public interface SimpleMessageService {

    @Output("test007")
    MessageChannel testChannel(); //  destination = test007

  
}

  1. 客户端与服务端设置配置文件

spring.cloud.stream.defaultBinder = rabbit

## Spring Cloud Stream Binder - RocketMQ
### Channel  名字是 test007
spring.cloud.stream.bindings.test007.binder = rocketmq
spring.cloud.stream.bindings.test007.destination = test007
  1. 客户端pom引入spring-cloud-stream-binder-rocketmq 的jar包:
    <!--  RocketMQ -->
        <dependency>
            <groupId>com.test</groupId>
            <artifactId>spring-cloud-stream-binder-rocketmq</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
  1. 验证实现结果

首先启动zk与 spring-cloud-client-application 、 spring-cloud-server-application

在这里插入图片描述

然后访问地址:http://localhost:8888/stream/send/rocketmq?message=test
在这里插入图片描述

消息发送成功!
与此同时,服务端接收消息的情况是:
在这里插入图片描述
消息接收成功!

后记

本节代码地址:Spring Cloud Stream Binder For Rocketmq

更多架构知识,欢迎关注本套Java系列文章Java架构师成长之路

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值