SpringCloud Stream

微服务之SpringCloud Stream

Spring Cloud Stream
官方的说法是一个构建消息驱动微服务的框架。我们可以这么理解,这个Spring Cloud Stream封装了mq的玩法,统一了模型,然后屏蔽各个mq产品中间件不同,降低了我们的学习成本,不过目前只支持kafka与rabbitmq。
类似mybatis集成mysql,oracal数据库一样,不再需要关注底层jdbc。只需要配置Mapper就行了。

第一步需要安装消息队列服务器,类似安装mysql数据库一样。然后才是写代码配置参数,连接服务器。
与mysql不同的是,消息队列的服务端分为消息生成者和消息消费者,一般都是分成两个微服务。

第二步创建消息生成者微服务(producer-service)
maven 父项目依赖

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.book</groupId>
    <artifactId>shop-parent</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>producer-service</module>
        <module>stream-consumer</module>
		<module>stream-consumer2</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

producer-service项目依赖

<?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>shop-parent</artifactId>
        <groupId>cn.book</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>producer-service</artifactId>

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
        </dependency>
    </dependencies>
</project>

application.yml 配置

server:
  port: 6001
spring:
  application:
    name: producer-service
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      bindings:
        myoutput:
          destination: my-book-default
      binders:
        defaultRabbit:
          type: rabbit

启动类 ProducerApplication.java

package cn.book.stream;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ProducerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProducerApplication.class);
    }
}

自定义消息通道类 MyProcessor.java
MessageChannel对应配置文件bindings

package cn.book.stream.channel;

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;

/**
 * 自定义的消息通道
 */
public interface MyProcessor {

    /**
     * 消息生产者的配置
     */
    String MYOUTPUT = "myoutput";

    @Output(MYOUTPUT)
    MessageChannel myoutput();

    /**
     * 消息消费者的配置
     */
    String MYINPUT = "myinput";

    @Input(MYINPUT)
    SubscribableChannel myinput();
}

消息发送service类 MessageSender.java
package cn.book.stream.producer;

import cn.book.stream.channel.MyProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;

/**
 * 负责向中间件发送数据
 */
@Component
@EnableBinding(MyProcessor.class)
public class MessageSender {

    @Autowired
    private MessageChannel myoutput;

    //发送消息
    public void send(Object obj) {
        myoutput.send(MessageBuilder.withPayload(obj).build());
    }
}

测试发送消息类 ProducerTest.java

package cn.book.stream;

import cn.book.stream.producer.MessageSender;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class ProducerTest {
    @Autowired
    private MessageSender messageSender;

    @Test
    public void testSend() {
        for (int i = 1; i < 11; i++) {
            messageSender.send("msg" + i);
        }
    }
}

第三步创建消息消费者微服务(stream-consumer)
父依赖与消息生成者微服务相同
maven依赖

<?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>shop-parent</artifactId>
        <groupId>cn.book</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>stream-consumer</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
        </dependency>
    </dependencies>
</project>

application.yml配置文件

server:
  port: 6002
spring:
  application:
    name: stream-consumer
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      bindings:
        myinput:
          destination: my-book-default
      binders:
        defaultRabbit:
          type: rabbit
		  

启动类 ConsumerApplication.java

package cn.book.stream;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class);
    }
}

自定义消息通道类 MyProcessor.java
SubscribableChannel对应配置文件bindings

package cn.book.stream.channel;

import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;

/**
 * 自定义的消息通道
 */
public interface MyProcessor {

    /**
     * 消息生产者的配置
     */
    String MYOUTPUT = "myoutput";

    @Output(MYOUTPUT)
    MessageChannel myoutput();

    /**
     * 消息消费者的配置
     */
    String MYINPUT = "myinput";

    @Input(MYINPUT)
    SubscribableChannel myinput();
}

消息监听类 MessageReceiver.java
@EnableBinding 注解类
@StreamListener 注解方法

package cn.book.stream.consumer;

import cn.book.stream.channel.MyProcessor;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Component;

@Component
@EnableBinding(MyProcessor.class)
public class MessageReceiver {

    //监听binding中的消息
    @StreamListener(MyProcessor.MYINPUT)
    public void input(String message) {
        System.out.println("获取到消息: " + message);
    }
}

运行测试类后,再运行消息消费微服务。

如果消息微服务有多个实例,多个实例轮询获取同一队列的消息。可以给多实例分组,只要分到一个组里面的实例就会轮询获取消息。

改动如下
1.消息生成者微服务不需要改动。、
2.改动消息消费者的配置文件

server:
  port: 6003
spring:
  application:
    name: stream-consumer
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      bindings:
        myinput:
          destination: book-default-group
          group: mygroup
      binders:
        defaultRabbit:
          type: rabbit

如果想指定某种类型的消息给指定的消费者消费,则需要给消费者实例分区。
改动如下
1.修改消息生成者微服务
增加消息主体类

package cn.book.stream.producer;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * 消息对象
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Message implements Serializable {
    //消息编号
    private int id;
    //消息主体
    private String body;
}

修改application.yml

server:
  port: 6001
spring:
  application:
    name: stream-producer
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      bindings:
        myoutput:
          destination: book-default-group
          producer:
            partition-key-expression: payload.id  	//指定了由消息主体类的id属性值分类。
            partition-count: 2						//设置了分区数。
      binders:
        defaultRabbit:
          type: rabbit
		  

修改测试类 ProducerTest.java

package cn.book.stream;

import cn.book.stream.producer.Message;
import cn.book.stream.producer.MessageSender;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class ProducerTest {
    @Autowired
    private MessageSender messageSender;

    @Test
    public void testSend() {
        for (int i = 1; i < 11; i++) {
            Message message1 = new Message(1,"msg"+i);
            messageSender.send(message1);

            Message message2 = new Message(2,"msg"+i);
            messageSender.send(message2);
        }
    }
}

2.修改消息消费者微服务
修改第一个实例的application.yml

server:
  port: 6002
spring:
  application:
    name: stream-consumer
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      instanceCount: 2  		//设置消费者实例数
      instanceIndex: 0			//设置当前实例索引值
      bindings:
        input:
          destination: book-default-group
          group: mygroup
          consumer:
            partitioned: true	//设置开启分区功能
      binders:
        defaultRabbit:
          type: rabbit

修改第二个实例的application.yml

server:
  port: 6003
spring:
  application:
    name: stream-consumer
  rabbitmq:
    addresses: 127.0.0.1
    username: guest
    password: guest
  cloud:
    stream:
      instanceCount: 2  		//设置消费者实例数
      instanceIndex: 1			//设置当前实例索引值
      bindings:
        input:
          destination: book-default-group
          group: mygroup
          consumer:
            partitioned: true	//设置开启分区功能
      binders:
        defaultRabbit:
          type: rabbit

运行测试类后,再运行消息消费微服务。
结果id为1的被6003消费了,
结果id为2的被6002消费了。
规则如下

id.hashCode()%instanceCount  的结果取匹配instanceIndex的值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芊芸爸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值