Rabbitmq的工作模式
简单模式
一个生产者,一个消费者,生产者发送消息,消费者接收消息,服务器不是持久的保存消息,消费者处理完,消息就会消除
连接rabbitmq的依赖
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.4.3</version>
</dependency>
</dependencies>
package m1;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* rabbitmq 简单模式
* 15672 管理控制台端口
* 5672 访问端口
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
f.setPort(5672);
f.setUsername("guest");
f.setPassword("guest");
Connection connection = f.newConnection();
Channel c = connection.createChannel();
//创建队列helloworld
c.queueDeclare("helloworld",false,false,false,null);
//向helloword队列发送消息
c.basicPublish("","helloworld",null ,"Hello world!".getBytes());
//断开连接
c.close();
}
}
运行后,访问http://192.168.64.140:15672/#/
在运行consumer
package m1;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
f.setPort(5672);
f.setUsername("guest");
f.setPassword("guest");
Connection connection = f.newConnection();
Channel c = connection.createChannel();
//创建helloworld队列,已经存在不会创建
c.queueDeclare("helloworld",false,false,false,null);
//创建回调对象
DeliverCallback deliverCallback = (consumerTag,message) ->{
byte[] a = message.getBody();
String s = new String(a);
System.out.println("收到的消息"+s);
};
CancelCallback cancelCallback = consumerTag ->{
};
//接收消息,消息会传递到一个回调对象进行处理,
/*
true, Auto Acknowledgement
autoAck = true 自动确认
autoAck = false 手动确认
*/
c.basicConsume("helloworld",true,deliverCallback,cancelCallback);
}
}
ready变成了0
工作模式
生产者发送5条消息,
消费者会同时接收消息
消费者1接收1 ,
消费者2接收2 ,
消费者3 接收3 ,
然后消费者1 在接收 4
消费者2在接收 5
- 生产者发送消息代码
package m2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
/**
* 生产者
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
f.setPort(5672);
f.setUsername("guest");
f.setPassword("guest");
Connection connection = f.newConnection();
Channel c = connection.createChannel();
//创建helloworld队列,已经存在不会创建
c.queueDeclare("helloworld",false,false,false,null);
while(true){
System.out.println("输入消息");
String s = new Scanner(System.in).nextLine();
c.basicPublish("","helloworld",null ,s.getBytes());
}
}
}
-
运行截图
-
消费者接收消息,其中使用了"."阻塞消息发送,先看最简单的接收
package m2;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
f.setPort(5672);
f.setUsername("guest");
f.setPassword("guest");
Connection connection = f.newConnection();
Channel c = connection.createChannel();
//创建helloworld队列,已经存在不会创建
c.queueDeclare("helloworld", false, false, false, null);
//创建回调对象
DeliverCallback deliverCallback = (consumerTag, message) -> {
String s = new String(message.getBody());
System.out.println("收到:" + s); //"sdf.ew....dad..// "
//处理收到消息的对象,遍历字符串找'.',每找到一个暂停1秒,模拟耗时消息
for (int i = 0; i < s.length(); i++) {
if ('.' == s.charAt(i)) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
System.out.println("消息处理完成...............");
};
CancelCallback cancelCallback = consumerTag -> {
};
//接收消息,消息会传递到一个回调对象进行处理,
c.basicConsume("helloworld", true, deliverCallback, cancelCallback);
}
}
每个消费者接受到生产者消息的数量都是一致的,但是也有新的问题,就是,如果其中一条消息堵塞还会不会,和上面的结果一样?结果其实是一样的,还是接收同等数量的消息,可以去利用上面的Demo进行尝试。
然后,就会产生新的问题,就是消费者1接受到的第一条消息被堵塞就会影响,接下来的消息接收,但是,剩下的两个消费者都是空闲状态,但是,消息都被堵塞到了消费者1中,极大的影响了消息的传递的效率。这是因为服务器不知到这件那个消费者处理完和没处理完,所以就没办法合理发送。
解决办法:
可以在消费者处改成autoAck=false
手动确认模式,然后在设置qos=1
,每次只处理一条消息,处理完之前不会再处理新的消息
/*
这个在消息处理完成后添加
*/
System.out.println("消息处理完成...............");
//发送回执
// c.basicAck(回执,是否同时确认之前未确认的额消息);
c.basicAck(message.getEnvelope().getDeliveryTag(),false);
// 在c.basicConsume添加
c.basicQos(1);
//接收消息,消息会传递到一个回调对象进行处理,
c.basicConsume("helloworld", false, deliverCallback, cancelCallback);
改完之后就不会再试轮循的方式了.
新的问题又出现了,然后就是如果每条消息,都加上很多的.
,那么所有的消费者都在等待,第一条和第二条消息在等待,其他的消息在就绪(在内存中保存),如果服务器崩溃的话,这些在内存中的消息就会丢失.所以要解决的问题就是要将这些消息持久化存储到磁盘.
-
队列的持久化,消息的持久化
将 生产者和消费者
c.queueDeclare("helloworld",false,false,false,null);
更改为
c.queueDeclare("taskqueue", true, false, false, null);
还有这个
c.basicConsume("taskqueue", false, deliverCallback, cancelCallback); //生产者 c.basicPublish("","taskqueue", MessageProperties.PERSISTENT_BASIC,s.getBytes());
群发模式
中间需要交换机的使用,生产者发消息到交换机,然后交换机会与消费者连接,每个消费者都有自己的队列,在进行特定的分发
交换机:
- direct
- fanout(扇形交换机,像电风扇,做群发,每个消费者都能收到这个消息)
- topic
- headers
首先生产者创建交换机
package m3;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;
/**
* 生产者
*/
public class Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
f.setPort(5672);
f.setUsername("guest");
f.setPassword("guest");
Connection connection = f.newConnection();
Channel c = connection.createChannel();
//创建fanout交换机:logs
//如果存在才不会重建
c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
//向交换机发送消息
while (true){
System.out.print("输入消息:");
String s = new Scanner(System.in).nextLine();
c.basicPublish("logs","",null,s.getBytes());
}
}
}
消费者部分代码,用UUID设置队列名称
package m3;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
f.setPort(5672);
f.setUsername("guest");
f.setPassword("guest");
Connection connection = f.newConnection();
Channel c = connection.createChannel();//通信的通道
//创建队列,创建交换机,绑定
//创建随机命名的队列
String queue = UUID.randomUUID().toString();
c.queueDeclare(queue,false,true,true,null);
//创建fanout交换机:Logs
c.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
//绑定,第三个参数对fanout交换机无效
c.queueBind(queue,"logs","");
DeliverCallback deliverCallback = (consumerTag,message) ->{
String s = new String(message.getBody());
System.out.println("收到:"+s);
};
CancelCallback cancelCallback = consumerTag->{
};
c.basicConsume(queue,true,deliverCallback,cancelCallback);
}
}
路由模式
用消息上的关键词与绑定的关键词匹配,就可以找到绑定的关键词,在根据路由键(info,error,warning)对应的消息队列,对应的消费者才能接收消息.
使用的交换机是direct
-
生产者的代码
package m4; import com.rabbitmq.client.BuiltinExchangeType; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.Scanner; import java.util.concurrent.TimeoutException; public class Producer { public static void main(String[] args) throws IOException, TimeoutException { //连接 ConnectionFactory f = new ConnectionFactory(); f.setHost("192.168.64.140"); f.setPort(5672); f.setUsername("guest"); f.setPassword("guest"); Connection connection = f.newConnection(); Channel c = connection.createChannel(); //创建fanout交换机:logs //如果存在才不会重建 c.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT); //向交换机发送消息 while (true){ System.out.print("输入消息:"); String s = new Scanner(System.in).nextLine(); System.out.print("输入路由键:"); String k = new Scanner(System.in).nextLine(); //第二个参数是路由键 //前两种模式使用默认交换机,默认交互机使用队列名作为路由键 c.basicPublish("direct_logs",k,null,s.getBytes()); } } }
对应的关键词,消费者才能接收到消息
- 消费者部分代码
package m4;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.TimeoutException;
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
f.setPort(5672);
f.setUsername("guest");
f.setPassword("guest");
Connection connection = f.newConnection();
Channel c = connection.createChannel();//通信的通道
//创建队列,创建交换机,绑定
//创建随机命名的队列
String queue = UUID.randomUUID().toString();
c.queueDeclare(queue,false,true,true,null);
//创建direct交换机:direct_logs
c.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT);
System.out.println("输入绑定键,用空格隔开:");
String s1 = new Scanner(System.in).nextLine();//"aa bb cc dd"
String[] a = s1.split("\\s+"); // \s空白字符,是一到多个
for (String k :a){
c.queueBind(queue,"direct_logs",k);
}
//接收消息
DeliverCallback deliverCallback = (consumerTag,message) ->{
String s = new String(message.getBody());
System.out.println("收到:"+s);
};
CancelCallback cancelCallback = consumerTag->{
};
c.basicConsume(queue,true,deliverCallback,cancelCallback);
}
}
主题模式
绑定键有一个特别的格式,比如aa.bb.cc
,或者用*.*.cc.dd
或者aa.#
,和路由模式非常的相近,使用的交换机是tocpic,只不过绑定键的格式发生了变化
然后代码和上面路由模式的代码没啥变化,交换机部分更改,有direct
到topic
举例(只修改交换机其他都一样)
c.exchangeDeclare("topic_logs", BuiltinExchangeType.TOPIC);
RPC模式
-
对于RPC请求,客户端发送一条带有两个属性的消息:replyTo,设置为仅为请求创建的匿名独占队列,和correlationId,设置为每个请求的惟一id值。
-
请求被发送到rpcqueue队列。
-
RPC工作进程(即:服务器)在队列上等待请求。当一个请求出现时,它执行任务,并使用replyTo字段中的队列将结果发回客户机。
-
客户机在回应消息队列上等待数据。当消息出现时,它检查correlationId属性。如果匹配请求中的值,则向程序返回该响应数据。
-
客户端
package m6;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;
public class RpcClient {
static BlockingQueue<String> q =
new ArrayBlockingQueue<>(10);
public static void main(String[] args) throws IOException, InterruptedException, TimeoutException {
while (true) {
System.out.print("求第几个斐波那契数:");
int n = new Scanner(System.in).nextInt();
long r = f(n);
System.out.println("结果: "+r);
}
}
private static long f(int n) throws IOException, TimeoutException, InterruptedException {
//连接
ConnectionFactory f = new ConnectionFactory();
f.setHost("192.168.64.140");
f.setPort(5672);
f.setUsername("guest");
f.setPassword("guest");
Connection con = f.newConnection();
Channel c = con.createChannel(); //通信的通道
//创建调用队列和返回队列
c.queueDeclare("rpcQueue",false,false,false,null);
String replyTo = c.queueDeclare().getQueue();//由rabbitmq服务器自动命名,false,true, true
//随机产生一个关联id
String cid = UUID.randomUUID().toString();
//发送调用,携带返回队列名和关联id
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
.replyTo(replyTo).correlationId(cid).build();
c.basicPublish("","rpcQueue",props,String.valueOf(n).getBytes());
//不用等待结果,可以继续执行其他运算
//直到需要结果时,再从rabbitmq接收结果,在新的线程中接收消息放入阻塞队列
DeliverCallback deliverCallback = (consumerTag, message) -> {
q.add(new String(message.getBody()));
String cid2 = message.getProperties().getCorrelationId();
System.out.println(cid2);
};
CancelCallback cancelCallback = consumerTag -> {};
c.basicConsume(replyTo,true, deliverCallback, cancelCallback);
//主线程从阻塞队列获取结果数据
String r = q.take();
return Long.valueOf(r);
}
}
-
服务端
package m6; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; public class RpcServer { public static void main(String[] args)throws IOException, TimeoutException { //连接 ConnectionFactory f = new ConnectionFactory(); f.setHost("192.168.64.140"); f.setPort(5672); f.setUsername("guest"); f.setPassword("guest"); Connection con = f.newConnection(); Channel c = con.createChannel(); //通信的通道 //接收调用数据: 1.n 2.返回队列名 3.关联id //计算第n个斐波那契数 //向客户端发送结果 c.queueDeclare("rpcQueue",false,false,false,null); DeliverCallback deliverCallback = (consumerTag, message) -> { Integer n = Integer.valueOf(new String(message.getBody())); // n AMQP.BasicProperties props = message.getProperties(); String replyTo = props.getReplyTo(); // 返回队列名 String cid = props.getCorrelationId(); // 关联id //求第n个斐波那契数 long r = fbnq(n); //发回结果 //属性对象中添加关联id参数 props = new AMQP.BasicProperties.Builder() .correlationId(cid).build(); c.basicPublish("",replyTo,props,String.valueOf(r).getBytes()); }; CancelCallback cancelCallback = consumerTag -> {}; c.basicConsume("rpcQueue",true,deliverCallback,cancelCallback); } /* 求第n个斐波那契数 1 1 2 3 5 8 13 21 34 55 89 144 */ static long fbnq(int n) { if (n < 1) { return 0; } if (n == 1 || n == 2) { return 1; } //递归求斐波那契数是低效算法,应该使用循环计算 return fbnq(n-1) + fbnq(n-2); } }