RocketMQ
一.RocketMQ 简介
1. 概述
消息队列作为高并发系统的核心组件之一,能够帮助业务系统解构提升开发效率和系统稳定性。主要具有以下优势:
- 削峰填谷: 主要解决瞬时写压力大于应用服务能力而导致消息丢失、系统奔溃等的问题;
- 系统解耦: 解决不同重要程度、不同能力级别的系统之间的依赖导致一死全死;
- 提升性能: 当存在一对多调用时,可以发一条消息给消息系统,让消息系统通知相关系统;
- 蓄流压测: 线上有些链路不好压测,可以通过堆积一定量的消息再放开来压测.
2. RocketMQ
Apache Alibaba RocketMQ 是一个消息中间件。消息中间件中有两个角色:消息生产者和消息消费者。RocketMQ 里同样有这两个概念,消息生产者负责创建消息并发消息送到 RocketMQ 服务器,RocketMQ 服务器会将消息持久化到磁盘,消息消费者从 RocketMQ 服务器拉取消息并提交给应用消费。
3. RocketMQ 特点
RocketMQ 是一款分布式、队列模型的消息中间件,具有以下特点:
- 支持严格的消息顺序;
- 支持 Topic 与 Queue 两种模式;
- 亿级消息堆积能力;
- 比较友好的分布式特性;
- 同时支持 Push 与 Pull 方式消费消息;
- 历经多次天猫双十一海量消息考验.
4. RocketMQ 优势
目前主流的 MQ 主要是 RocketMQ、kafka、RabbitMQ,其主要优势有:
- 支持事务型消息(消息发送和 DB 操作保持两方的最终一致性,RabbitMQ 和 Kafka 不支持)
- 支持结合 RocketMQ 的多个系统之间数据最终一致性(多方事务,二方事务是前提)
- 支持 18 个级别的延迟消息(RabbitMQ 和 Kafka 不支持)
- 支持指定次数和时间间隔的失败消息重发(Kafka 不支持,RabbitMQ 需要手动确认)
- 支持 Consumer 端 Tag 过滤,减少不必要的网络传输(RabbitMQ 和 Kafka 不支持)
- 支持重复消费(RabbitMQ 不支持,Kafka 支持)
5. 消息队列对比参照表

二.RocketMQ 生产者
1. 概述
RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。
由于本教程整个案例基于 Spring Cloud,故我们采用 Spring Cloud Stream 完成一次发布和订阅
官方教程
2. Spring Cloud Stream
Spring Cloud Stream 是一个用于构建基于消息的微服务应用框架。它基于 Spring Boot 来创建具有生产级别的单机 Spring 应用,并且使用 Spring Integration
与 Broker 进行连接。
Spring Cloud Stream 提供了消息中间件配置的统一抽象,推出了 publish-subscribe
、consumer groups
、partition
这些统一的概念。
Spring Cloud Stream 内部有两个概念:
- Binder: 跟外部消息中间件集成的组件,用来创建 Binding,各消息中间件都有自己的 Binder 实现。
- Binding: 包括 Input Binding 和 Output Binding。
Binding 在消息中间件与应用程序提供的 Provider 和 Consumer 之间提供了一个桥梁,实现了开发者只需使用应用程序的 Provider 或 Consumer 生产或消费数据即可,屏蔽了开发者与底层消息中间件的接触。
3. 解决连接超时问题
在之前的 基于 Docker 安装 RocketMQ 章节中,我们采用 Docker 部署了 RocketMQ 服务,此时 RocketMQ Broker 暴露的地址和端口(10909,10911)是基于容器的,会导致我们开发机无法连接,从而引发 org.apache.rocketmq.remoting.exception.RemotingTooMuchRequestException: sendDefaultImpl call timeout
异常
注意下图中的 IP 地址,这个是容器的 IP,开发机与容器不在一个局域网所以无法连接。
解决方案是在 broker.conf
配置文件中增加 brokerIP1=宿主机IP
即可
4. POM
主要增加了 org.springframework.cloud:spring-cloud-starter-stream-rocketmq
依赖
<?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>
<parent>
<groupId>com.funtl</groupId>
<artifactId>hello-spring-cloud-alibaba-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../hello-spring-cloud-alibaba-dependencies/pom.xml</relativePath>
</parent>
<artifactId>hello-spring-cloud-alibaba-rocketmq-provider</artifactId>
<packaging>jar</packaging>
<name>hello-spring-cloud-alibaba-rocketmq-provider</name>
<url>http://www.funtl.com</url>
<inceptionYear>2018-Now</inceptionYear>
<dependencies>
<!-- Spring Boot Begin -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot End -->
<!-- Spring Cloud Begin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
<!-- Spring Cloud End -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.funtl.hello.spring.cloud.alibaba.rocketmq.provider.RocketMQProviderApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
5. 消息生产者服务
package com.funtl.hello.spring.cloud.alibaba.rocketmq.provider.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
@Service
public class ProviderService {
@Autowired
private MessageChannel output;
public void send(String message) {
output.send(MessageBuilder.withPayload(message).build());
}
}
6. Application
配置 Output(Source.class
) 的 Binding 信息并配合 @EnableBinding
注解使其生效
package com.funtl.hello.spring.cloud.alibaba.rocketmq.provider;
import com.funtl.hello.spring.cloud.alibaba.rocketmq.provider.service.ProviderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Source;
@SpringBootApplication
@EnableBinding({Source.class})
public class RocketMQProviderApplication implements CommandLineRunner {
@Autowired
private ProviderService providerService;
public static void main(String[] args) {
SpringApplication.run(RocketMQProviderApplication.class, args);
}
/**
* 实现了 CommandLineRunner 接口,只是为了 Spring Boot 启动时执行任务,不必特别在意
* @param args
* @throws Exception
*/
@Override
public void run(String... args) throws Exception {
providerService.send("Hello RocketMQ");
}
}
7. application.yml
spring:
application:
name: rocketmq-provider
cloud:
stream:
rocketmq:
binder:
# RocketMQ 服务器地址
namesrv-addr: 192.168.10.149:9876
bindings:
# 这里是个 Map 类型参数,{} 为 YAML 中 Map 的行内写法
output: {destination: test-topic, content-type: application/json}
server:
port: 9093
management:
endpoints:
web:
exposure:
include: '*'
运行成功后即可在 RocketMQ 控制台的 消息
列表中选择 test-topic
主题即可看到发送的消息
三.RocketMQ 消费者
1. POM
主要增加了 org.springframework.cloud:spring-cloud-starter-stream-rocketmq
依赖
<?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>
<parent>
<groupId>com.funtl</groupId>
<artifactId>hello-spring-cloud-alibaba-dependencies</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../hello-spring-cloud-alibaba-dependencies/pom.xml</relativePath>
</parent>
<artifactId>hello-spring-cloud-alibaba-rocketmq-consumer</artifactId>
<packaging>jar</packaging>
<name>hello-spring-cloud-alibaba-rocketmq-consumer</name>
<url>http://www.funtl.com</url>
<inceptionYear>2018-Now</inceptionYear>
<dependencies>
<!-- Spring Boot Begin -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot End -->
<!-- Spring Cloud Begin -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
<!-- Spring Cloud End -->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.funtl.hello.spring.cloud.alibaba.rocketmq.consumer.RocketMQConsumerApplication</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
2. 消息消费者服务
主要使用 @StreamListener("input")
注解来订阅从名为 input
的 Binding 中接收的消息
package com.funtl.hello.spring.cloud.alibaba.rocketmq.consumer.receive;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.stereotype.Service;
@Service
public class ConsumerReceive {
@StreamListener("input")
public void receiveInput(String message) {
System.out.println("Receive input: " + message);
}
}
3. Application
配置 Input(Sink.class
) 的 Binding 信息并配合 @EnableBinding
注解使其生效
package com.funtl.hello.spring.cloud.alibaba.rocketmq.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.messaging.Sink;
@SpringBootApplication
@EnableBinding({Sink.class})
public class RocketMQConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(RocketMQConsumerApplication.class, args);
}
}
4. application.yml
spring:
application:
name: rocketmq-consumer
cloud:
stream:
rocketmq:
binder:
namesrv-addr: 192.168.10.149:9876
bindings:
input: {consumer.orderly: true}
bindings:
input: {destination: test-topic, content-type: text/plain, group: test-group, consumer.maxAttempts: 1}
server:
port: 9094
management:
endpoints:
web:
exposure:
include: '*'
运行成功后即可在控制台接收到消息:Receive input: Hello RocketMQ
四.RocketMQ 自定义 Binding
1. 概述
在实际生产中,我们需要发布和订阅的消息可能不止一种 Topic ,故此时就需要使用自定义 Binding 来帮我们实现多 Topic 的发布和订阅功能
2. 生产者
自定义 Output 接口,代码如下:
public interface MySource {
@Output("output1")
MessageChannel output1();
@Output("output2")
MessageChannel output2();
}
发布消息的案例代码如下:
@Autowired
private MySource source;
public void send(String msg) throws Exception {
source.output1().send(MessageBuilder.withPayload(msg).build());
}
3. 消费者
自定义 Input 接口,代码如下:
public interface MySink {
@Input("input1")
SubscribableChannel input1();
@Input("input2")
SubscribableChannel input2();
@Input("input3")
SubscribableChannel input3();
@Input("input4")
SubscribableChannel input4();
}
接收消息的案例代码如下:
@StreamListener("input1")
public void receiveInput1(String receiveMsg) {
System.out.println("input1 receive: " + receiveMsg);
}
4. Application
配置 Input 和 Output 的 Binding 信息并配合 @EnableBinding
注解使其生效,代码如下:
@SpringBootApplication
@EnableBinding({ MySource.class, MySink.class })
public class RocketMQApplication {
public static void main(String[] args) {
SpringApplication.run(RocketMQApplication.class, args);
}
}
5. application.yml
5.1 生产者
spring:
application:
name: rocketmq-provider
cloud:
stream:
rocketmq:
binder:
namesrv-addr: 192.168.10.149:9876
bindings:
output1: {destination: test-topic1, content-type: application/json}
output2: {destination: test-topic2, content-type: application/json}
5.2 消费者
spring:
application:
name: rocketmq-consumer
cloud:
stream:
rocketmq:
binder:
namesrv-addr: 192.168.10.149:9876
bindings:
input: {consumer.orderly: true}
bindings:
input1: {destination: test-topic1, content-type: text/plain, group: test-group, consumer.maxAttempts: 1}
input2: {destination: test-topic1, content-type: text/plain, group: test-group, consumer.maxAttempts: 1}
input3: {destination: test-topic2, content-type: text/plain, group: test-group, consumer.maxAttempts: 1}
input4: {destination: test-topic2, content-type: text/plain, group: test-group, consumer.maxAttempts: 1}