- Java 代码演示MQ的实现
文章目录
前言
博客直接参考RabbitMQ的官方文档案例,并且简化这个案例,将和RabbitMQ关联最为紧密的代码上带上注释并且删除了冗杂的代码,让案例更加通俗易懂。并且其中也会对MQ的一些工作方式进行讲解。
git clone https://github.com/YeZhiyue/Rabbitmq-SpringBootTuts.git
案例一:HelloWorld
依赖引入
主要是RabbitMQ依赖引入
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
基本配置
spring:
rabbitmq:
host: 59.110.213.92
port: 5672
username: guest
password: guest
listener:
simple:
# 并发消费者的初始化值
concurrency: 10
# 并发消费者的最大值,同时处理消息的消费者的最大数量
max-concurrency: 20
# 这个消费者每次监听时可拉取处理的消息数量。
prefetch: 5
# 不同的tut这里需要进行变更,表示我们配置生效的作用域
profiles:
active: tut1
logging:
level:
org: INFO
配置注意点
1.1 @EnableScheduling 需要使能我们的定时任务
我们这里的发送者是定时发送消息到MQ队列的,所以不要忘记这个的使能
@SpringBootApplication
@EnableScheduling
public class RabbitAmqpTutorialsApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitAmqpTutorialsApplication.class, args);
}
}
1.2 @Profile(“tut1”) 这里需要制定配置生效的作用域
由于这个案例工程中有多个例子,所以这里就配置了作用域防止配置冲突
@Profile("tut1")
@Configuration
public class Tut1Config {
@Bean // 创建我们的队列并且返回名称
public Queue hello() {
return new Queue("tut1");
}
@Bean // 注册我们的消费者,也就是初始化其中的参数
public Tut1Receiver receiver() {
return new Tut1Receiver();
}
@Bean // 注册我们的生产者,也就是初始化其中的部分参数
public Tut1Sender sender() {
return new Tut1Sender();
}
}
3个主要文件: RabbitMQ初始化配置 -> 生产者代码 -> 消费者代码
1.1 RabbitMQ初始化配置
@Profile("tut1")
@Configuration
public class Tut1Config {
@Bean // 创建我们的队列并且返回名称
public Queue hello() {
return new Queue("tut1");
}
@Bean // 注册我们的消费者,也就是初始化其中的参数
public Tut1Receiver receiver() {
return new Tut1Receiver();
}
@Bean // 注册我们的生产者,也就是初始化其中的部分参数
public Tut1Sender sender() {
return new Tut1Sender();
}
}
1.2 生产者代码
public class Tut1Sender {
@Autowired
private RabbitTemplate template;
@Autowired
private Queue queue;
@Scheduled(fixedDelay = 1000, initialDelay = 500)
public void send() {
this.template.convertAndSend(queue.getName(), "msg");
}
}
1.3 消费者代码
@RabbitListener(queues = "tut1")
public class Tut1Receiver {
@RabbitHandler
public void receive(String in) {
System.out.println(" [x] Received '" + in + "'");
}
}
结果展示
1.1 控制台接收消息
[x] Received 'msg'
[x] Received 'msg'
[x] Received 'msg'
[x] Received 'msg'
[x] Received 'msg'
1.2 MQ界面
1.2.1 队列情况
1.2.2 交换机情况
案例二:任务队列
说明
案例一中是一个生产者对应一个消费者,但是在实际的生产生活中通常会是一个生产者对应多个消费者,也就是在MQ服务器上的队列里面会有一堆消息等待处理,这个时候就需要有多个消费者一起同步处理这些消息效率会更加高。案例二中就是一个生产者对应多个消费者。并且一些基本配置和依赖引入就不做讲解。就是 application.yml 文件中注意修改配置作用域。
...
profiles:
active: tut2
3个主要文件: RabbitMQ初始化配置 -> 生产者代码 -> 消费者代码
1.1 RabbitMQ初始化配置
@Profile("tut2")
@Configuration
public class Tut2Config {
@Bean
public Queue hello() {
return new Queue("tut2");
}
// 注册你的工作者,分担任务压力
// 注意:这里可以注册很多工作者,其实可以理解为注册了多个线程同时工作
private static class ReceiverConfig {
@Bean
public Tut2Receiver receiver1() {
return new Tut2Receiver(1);
}
@Bean
public Tut2Receiver receiver2() {
return new Tut2Receiver(2);
}
}
@Bean
public Tut2Sender sender() {
return new Tut2Sender();
}
}
1.2 生产者代码
public class Tut2Sender {
@Autowired
private RabbitTemplate template;
@Autowired
private Queue queue;
AtomicInteger dots = new AtomicInteger(0);
AtomicInteger count = new AtomicInteger(0);
@Scheduled(fixedDelay = 1000, initialDelay = 500)
public void send() {
StringBuilder builder = new StringBuilder("Hello");
if (dots.getAndIncrement() == 4) {
dots.set(1);
}
for (int i = 0; i < dots.get(); i++) {
builder.append('.');
}
builder.append(count.incrementAndGet());
String message = builder.toString();
template.convertAndSend(queue.getName(), message);
System.out.println(" [x] Sent '" + message + "'");
}
}
1.3 消费者代码
@RabbitListener(queues = "tut2")
public class Tut2Receiver {
// 区分不同的工作者
private final int instance;
public Tut2Receiver(int i) {
this.instance = i;
}
@RabbitHandler
public void receive(String in) throws InterruptedException {
// 简单的定时器
StopWatch watch = new StopWatch();
watch.start();
System.out.println("instance " + this.instance + " [x] Received '" + in + "'");
doWork(in);
System.out.println("instance " + this.instance + " [x] Done in " + watch.getTotalTimeSeconds() + "s");
}
private void doWork(String in) throws InterruptedException {
for (char ch : in.toCharArray()) {
if (ch == '.') {
Thread.sleep(1000);
}
}
}
}
结果展示
1.1 控制台接收消息
下面 instance 1 代表消费者一号,instance 2 代表消费者二号。可以看到两个消费者同时处理MQ发送过来的消息,可见这个就是一个任务队列。
instance 1 [x] Done in 0.0s
instance 1 [x] Done in 0.0s
[x] Sent 'Hello...391'
instance 2 [x] Received 'Hello...391'
[x] Sent 'Hello....392'
instance 1 [x] Done in 0.0s
instance 1 [x] Done in 0.0s
instance 2 [x] Received 'Hello....392'
[x] Sent 'Hello.393'
instance 2 [x] Received 'Hello.393'
1.2 MQ界面
1.2.1 队列情况
1.2.2 交换机情况
案例三:FanoutExchange 基础交换机
说明
案例一和案例二中都是通过直接指定Queue的名称来和MQ进行连接,案例三中会通过Exchange交换机进行和队列的绑定。交换机就是消息的中转站,用于接收分发消息。其中有 fanout、direct、topic、headers 四种
...
profiles:
active: tut2
3个主要文件: RabbitMQ初始化配置 -> 生产者代码 -> 消费者代码
1.1 RabbitMQ初始化配置
/**
* 配置FanoutExchange
* 配置随机队列
* 配置队列和Exchange之间的绑定
*
* 注册接受者和发送者
*/
@Profile("tut3")
@Configuration
public class Tut3Config {
@Bean // 注册Exchange
public FanoutExchange fanout() {
return new FanoutExchange("tut3");
}
private static class ReceiverConfig {
@Bean // 表示匿名的,非耐用性,排他性,自动删除队列。
public Queue autoDeleteQueue1() {
return new AnonymousQueue();
}
@Bean
public Queue autoDeleteQueue2() {
return new AnonymousQueue();
}
@Bean // 直接通过上面注册的组件作为依赖注入方法
public Binding binding1(FanoutExchange fanout, Queue autoDeleteQueue1) {
return BindingBuilder.bind(autoDeleteQueue1).to(fanout);
}
@Bean
public Binding binding2(FanoutExchange fanout, Queue autoDeleteQueue2) {
return BindingBuilder.bind(autoDeleteQueue2).to(fanout);
}
@Bean
public Tut3Receiver receiver() {
return new Tut3Receiver();
}
}
@Bean
public Tut3Sender sender() {
return new Tut3Sender();
}
}
1.2 生产者代码
public class Tut3Sender {
@Autowired
private RabbitTemplate template;
@Autowired
private FanoutExchange fanout;
AtomicInteger dots = new AtomicInteger(0);
AtomicInteger count = new AtomicInteger(0);
@Scheduled(fixedDelay = 1000, initialDelay = 500)
public void send() {
StringBuilder builder = new StringBuilder("Hello");
if (dots.getAndIncrement() == 3) {
dots.set(1);
}
for (int i = 0; i < dots.get(); i++) {
builder.append('.');
}
builder.append(count.incrementAndGet());
String message = builder.toString();
// 通过Exchange来发送消息
template.convertAndSend(fanout.getName(), "", message);
System.out.println(" [x] Sent '" + message + "'");
}
}
1.3 消费者代码
public class Tut3Receiver {
// 通过 fanout 来随机绑定两个对象
@RabbitListener(queues = "#{autoDeleteQueue1.name}")
public void receive1(String in) throws InterruptedException {
receive(in, 1);
}
@RabbitListener(queues = "#{autoDeleteQueue2.name}")
public void receive2(String in) throws InterruptedException {
receive(in, 2);
}
public void receive(String in, int receiver) throws InterruptedException {
StopWatch watch = new StopWatch();
watch.start();
System.out.println("instance " + receiver + " [x] Received '" + in + "'");
doWork(in);
watch.stop();
System.out.println("instance " + receiver + " [x] Done in " + watch.getTotalTimeSeconds() + "s");
}
private void doWork(String in) throws InterruptedException {
for (char ch : in.toCharArray()) {
if (ch == '.') {
Thread.sleep(1000);
}
}
}
}
结果展示
1.1 控制台接收消息
下面 instance 1 代表消费者一号,instance 2 代表消费者二号。
instance 2 [x] Done in 2.002s
instance 1 [x] Done in 2.002s
instance 1 [x] Received 'Hello.3403'
instance 2 [x] Received 'Hello.3403'
[x] Sent 'Hello..3404'
instance 1 [x] Done in 1.0s
instance 2 [x] Done in 1.0s
1.2 MQ界面
1.2.1 队列情况
1.2.2 交换机情况
案例四:DirectExchange 直连交换机
说明
可以通过指定RoutKey去和多个消息队列进行匹配。而不是像FanoutExchange去使用默认的RoutingKey去连接队列,更加方便我们的管理。
3个主要文件: RabbitMQ初始化配置 -> 生产者代码 -> 消费者代码
1.1 RabbitMQ初始化配置
@Profile("tut4")
@Configuration
public class Tut4Config {
@Bean
public DirectExchange direct() {
return new DirectExchange("tut4");
}
private static class ReceiverConfig {
@Bean
public Queue autoDeleteQueue1() {
return new AnonymousQueue();
}
@Bean
public Queue autoDeleteQueue2() {
return new AnonymousQueue();
}
@Bean
public Binding binding1a(DirectExchange direct, Queue autoDeleteQueue1) {
return BindingBuilder.bind(autoDeleteQueue1).to(direct).with("orange");
}
@Bean
public Binding binding1b(DirectExchange direct, Queue autoDeleteQueue1) {
return BindingBuilder.bind(autoDeleteQueue1).to(direct).with("black");
}
@Bean
public Binding binding2a(DirectExchange direct, Queue autoDeleteQueue2) {
return BindingBuilder.bind(autoDeleteQueue2).to(direct).with("green");
}
@Bean
public Binding binding2b(DirectExchange direct, Queue autoDeleteQueue2) {
return BindingBuilder.bind(autoDeleteQueue2).to(direct).with("black");
}
@Bean
public Tut4Receiver receiver() {
return new Tut4Receiver();
}
}
@Bean
public Tut4Sender sender() {
return new Tut4Sender();
}
}
1.2 生产者代码
public class Tut4Sender {
@Autowired
private RabbitTemplate template;
@Autowired
private DirectExchange direct;
AtomicInteger index = new AtomicInteger(0);
AtomicInteger count = new AtomicInteger(0);
private final String[] keys = {"orange", "black", "green"};
@Scheduled(fixedDelay = 1000, initialDelay = 500)
public void send() {
StringBuilder builder = new StringBuilder("Hello to ");
if (this.index.incrementAndGet() == 3) {
this.index.set(0);
}
String key = keys[this.index.get()];
builder.append(key).append(' ');
builder.append(this.count.incrementAndGet());
String message = builder.toString();
template.convertAndSend(direct.getName(), key, message);
System.out.println(" [x] Sent '" + message + "'");
}
}
1.3 消费者代码
public class Tut4Receiver {
@RabbitListener(queues = "#{autoDeleteQueue1.name}")
public void receive1(String in) throws InterruptedException {
receive(in, 1);
}
@RabbitListener(queues = "#{autoDeleteQueue2.name}")
public void receive2(String in) throws InterruptedException {
receive(in, 2);
}
public void receive(String in, int receiver) throws InterruptedException {
StopWatch watch = new StopWatch();
watch.start();
System.out.println("instance " + receiver + " [x] Received '" + in + "'");
doWork(in);
watch.stop();
System.out.println("instance " + receiver + " [x] Done in " + watch.getTotalTimeSeconds() + "s");
}
private void doWork(String in) throws InterruptedException {
for (char ch : in.toCharArray()) {
if (ch == '.') {
Thread.sleep(1000);
}
}
}
}
结果展示
1.1 控制台接收消息
下面 instance 1 代表消费者一号,instance 2 代表消费者二号。
instance 1 [x] Done in 2.001s
[x] Sent 'Hello to lazy.brown.fox 171'
instance 1 [x] Done in 2.001s
instance 2 [x] Done in 2.001s
instance 2 [x] Received 'Hello to lazy.brown.fox 171'
[x] Sent 'Hello to lazy.pink.rabbit 172'
instance 1 [x] Done in 2.001s
1.2 MQ界面
1.2.1 队列情况
1.2.2 交换机情况
案例五:TopicExchange 可以使用通配符配置RoutingKey的交换机
说明
TopicExchange和前面的交换机相比更加复杂,就是可以使用通配符配置RoutingKey的交换机。其中 # 代表单个单词, * 代表多个单词。注意了,这里代表的是单词,而不是字母。如:red.apple.* -> 可以适配 red.apple.person.man、red.apple.person、red.apple.hello.peson.man , 但是 red.apple.# 只能适配 red.apple.person,其他的red.apple.hello.peson.man、red.apple.person.man不能匹配。
3个主要文件: RabbitMQ初始化配置 -> 生产者代码 -> 消费者代码
1.1 RabbitMQ初始化配置
@Profile("tut5")
@Configuration
public class Tut5Config {
@Bean
public TopicExchange topic() {
return new TopicExchange("tut5");
}
private static class ReceiverConfig {
@Bean
public Tut5Receiver receiver() {
return new Tut5Receiver();
}
@Bean
public Queue autoDeleteQueue1() {
return new AnonymousQueue();
}
@Bean
public Queue autoDeleteQueue2() {
return new AnonymousQueue();
}
@Bean
public Binding binding1a(TopicExchange topic, Queue autoDeleteQueue1) {
return BindingBuilder.bind(autoDeleteQueue1).to(topic).with("*.orange.*");
}
@Bean
public Binding binding1b(TopicExchange topic, Queue autoDeleteQueue1) {
return BindingBuilder.bind(autoDeleteQueue1).to(topic).with("*.*.rabbit");
}
@Bean
public Binding binding2a(TopicExchange topic, Queue autoDeleteQueue2) {
return BindingBuilder.bind(autoDeleteQueue2).to(topic).with("lazy.#");
}
}
@Bean
public Tut5Sender sender() {
return new Tut5Sender();
}
}
1.2 生产者代码
public class Tut5Sender {
@Autowired
private RabbitTemplate template;
@Autowired
private TopicExchange topic;
AtomicInteger index = new AtomicInteger(0);
AtomicInteger count = new AtomicInteger(0);
private final String[] keys = {"quick.orange.rabbit", "lazy.orange.elephant", "quick.orange.fox",
"lazy.brown.fox", "lazy.pink.rabbit", "quick.brown.fox"};
@Scheduled(fixedDelay = 1000, initialDelay = 500)
public void send() {
StringBuilder builder = new StringBuilder("Hello to ");
if (this.index.incrementAndGet() == keys.length) {
this.index.set(0);
}
String key = keys[this.index.get()];
builder.append(key).append(' ');
builder.append(this.count.incrementAndGet());
String message = builder.toString();
template.convertAndSend(topic.getName(), key, message);
System.out.println(" [x] Sent '" + message + "'");
}
}
1.3 消费者代码
public class Tut5Receiver {
@RabbitListener(queues = "#{autoDeleteQueue1.name}")
public void receive1(String in) throws InterruptedException {
receive(in, 1);
}
@RabbitListener(queues = "#{autoDeleteQueue2.name}")
public void receive2(String in) throws InterruptedException {
receive(in, 2);
}
public void receive(String in, int receiver) throws InterruptedException {
StopWatch watch = new StopWatch();
watch.start();
System.out.println("instance " + receiver + " [x] Received '" + in + "'");
doWork(in);
watch.stop();
System.out.println("instance " + receiver + " [x] Done in " + watch.getTotalTimeSeconds() + "s");
}
private void doWork(String in) throws InterruptedException {
for (char ch : in.toCharArray()) {
if (ch == '.') {
Thread.sleep(1000);
}
}
}
}
结果展示
1.1 控制台接收消息
下面 instance 1 代表消费者一号,instance 2 代表消费者二号。
instance 1 [x] Done in 2.001s
instance 2 [x] Received 'Hello to lazy.pink.rabbit 268'
instance 1 [x] Received 'Hello to lazy.pink.rabbit 268'
[x] Sent 'Hello to quick.brown.fox 269'
instance 2 [x] Done in 2.001s
[x] Sent 'Hello to quick.orange.rabbit 270'
instance 2 [x] Done in 2.001s
instance 1 [x] Done in 2.001s
1.2 MQ界面
1.2.1 队列情况
1.2.2 交换机情况
案例六:模拟RPC框架
说明
和前面几个案例不同,模拟RPC需要我们搭建两个服务。可以下载源码调试。
git clone https://github.com/YeZhiyue/RabbitMQ-RPC.git
Sender服务配置(生产者)
1.1 注意点
在生产者中需要配置我们的队列的名称,因为我们在另外的消费者服务中是不知道队列的信息的,所以需要在这里指定队列名称。方便我们消费者服务器接收消息。
1.2 配置发送者
@Configuration
public class SenderMQConfig {
@Bean // topic类型Exchange,可以使用通配符定义规则
public TopicExchange topic() {
return new TopicExchange("cooperate");
}
/// 建立队列
@Bean
public Queue autoDeleteQueue1() {
// 普通队列,方便RPC
return new Queue("topicQueue1");
}
@Bean
public Queue autoDeleteQueue2() {
return new Queue("topicQueue2");
}
@Bean
public Binding binding1a(TopicExchange topic, Queue autoDeleteQueue1) {
return BindingBuilder.bind(autoDeleteQueue1).to(topic).with("*.orange.*");
}
/// 配置绑定规则
@Bean
public Binding binding1b(TopicExchange topic, Queue autoDeleteQueue1) {
return BindingBuilder.bind(autoDeleteQueue1).to(topic).with("*.*.rabbit");
}
@Bean
public Binding binding2a(TopicExchange topic, Queue autoDeleteQueue2) {
return BindingBuilder.bind(autoDeleteQueue2).to(topic).with("lazy.#");
}
@Bean
public MQSender sender() {
return new MQSender();
}
}
1.3 发送者代码
public class MQSender {
@Autowired
private RabbitTemplate template;
@Autowired
private TopicExchange topic;
AtomicInteger index = new AtomicInteger(0);
AtomicInteger count = new AtomicInteger(0);
private final String[] keys = {"quick.orange.rabbit", "lazy.orange.elephant", "quick.orange.fox",
"lazy.brown.fox", "lazy.pink.rabbit", "quick.brown.fox"};
@Scheduled(fixedDelay = 1000, initialDelay = 500)
public void send() {
StringBuilder builder = new StringBuilder("Hello to ");
if (this.index.incrementAndGet() == keys.length) {
this.index.set(0);
}
String key = keys[this.index.get()];
builder.append(key).append(' ');
builder.append(this.count.incrementAndGet());
String message = builder.toString();
template.convertAndSend(topic.getName(), key, message);
System.out.println(" [x] Sent '" + message + "'");
}
}
Receiver服务配置(消费者)
1.1 配置接受者
@Configuration
public class RecieverConfig {
@Bean
public MQReceiver receiver() {
return new MQReceiver();
}
}
1.2 接受者代码
public class MQReceiver {
@RabbitListener(queues = "topicQueue1")
public void receive1(String in) throws InterruptedException {
receive(in, 1);
}
@RabbitListener(queues = "topicQueue2")
public void receive2(String in) throws InterruptedException {
receive(in, 2);
}
public void receive(String in, int receiver) throws InterruptedException {
StopWatch watch = new StopWatch();
watch.start();
System.out.println("instance " + receiver + " [x] Received '" + in + "'");
doWork(in);
watch.stop();
System.out.println("instance " + receiver + " [x] Done in " + watch.getTotalTimeSeconds() + "s");
}
private void doWork(String in) throws InterruptedException {
for (char ch : in.toCharArray()) {
if (ch == '.') {
Thread.sleep(1000);
}
}
}
}
运行结果
1.1 发送者
[x] Sent 'Hello to quick.orange.rabbit 372'
[x] Sent 'Hello to lazy.orange.elephant 373'
[x] Sent 'Hello to quick.orange.fox 374'
[x] Sent 'Hello to lazy.brown.fox 375'
[x] Sent 'Hello to lazy.pink.rabbit 376'
[x] Sent 'Hello to quick.brown.fox 377'
[x] Sent 'Hello to quick.orange.rabbit 378'
[x] Sent 'Hello to lazy.orange.elephant 379'
[x] Sent 'Hello to quick.orange.fox 380'
1.2 接收者
instance 2 [x] Done in 2.0001737s
instance 2 [x] Received 'Hello to lazy.orange.elephant 343'
instance 1 [x] Done in 2.0001565s
instance 1 [x] Received 'Hello to lazy.orange.elephant 1'
instance 2 [x] Done in 2.0008724s
instance 2 [x] Received 'Hello to lazy.brown.fox 345'
instance 1 [x] Done in 2.0007671s
instance 1 [x] Received 'Hello to quick.orange.fox 2'
instance 2 [x] Done in 2.0000875s