此篇文章可参考教学视频:
https://www.bilibili.com/video/av95396959?p=5
https://www.bilibili.com/video/av95396959/?p=6
截止目前为止(3.8.3版本),Rabbit一共提供了其中模式以供开发者使用。
可参考官网: https://www.rabbitmq.com/getstarted.html
如下所示
此篇文章则先从最简单的第一种helloword模式开始讲起。
1.首先应该创建一个用户:
2.创建并绑定虚拟主机
以上均设置成功后就可以看到如下状况
剩下话不多说,直接上代码:
需要引入的jar包:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.2</version>
</dependency>
生产者:
package helloword;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @Author:
* @Despriction: helloword 生产消费模式
* @Package: helloword
* @Date:Created in 2020/3/19 11:14
* @Modify By:
*/
public class Provider {
@Test
public void sendMsg() throws IOException, TimeoutException {
//第一步:创建rabbitmq的连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//第二步:设置连接的rabbitmq服务器的连接ip
connectionFactory.setHost("192.168.4.6");
//第三步:连接端口号,默认的为5672
connectionFactory.setPort(5672);
//第四步:设置连接哪个虚拟主机
connectionFactory.setVirtualHost("/ems");
//第五步:设置连接虚拟主机的用户和密码
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
//第六步:获取连接对象
Connection connection = connectionFactory.newConnection();
//第七步:创建连接通道
Channel channel = connection.createChannel();
//第八步:通道绑定对应的消息队列
/**
* 参数1 queue:队列名称,如果队列不存在则自动创建
* 参数2 durable:用来定义队列是否需要持久化,true 需要持久化;false 不需要持久化
* 参数3 exclusive:定义是否独占队列,true 是;false 否
* 参数4 autodelete:消费完成后是否需要自动删除队列,true 是;false 否
* 参数5:自定义的额外附加参数
*/
channel.queueDeclare("hello",false,false,false,null);
//第九步:发布消息
/**
* 参数1:交换机名称
* 参数2:队列名称
* 参数3:传递消息的额外设置
* 参数4:消息的具体内容,必须二进制
*/
channel.basicPublish("","hello",null,"hello rabbit".getBytes());
//第十步:关闭通道和连接
channel.close();
connection.close();
}
}
执行上诉代码之后,可在图形化界面中看到:
消费者:
package helloword;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @Author:
* @Despriction:
* @Package: helloword
* @Date:Created in 2020/3/21 14:18
* @Modify By:
*/
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//第一步:创建rabbitmq的连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//第二步:设置连接的rabbitmq服务器的连接ip
connectionFactory.setHost("192.168.4.6");
//第三步:连接端口号,默认的为5672
connectionFactory.setPort(5672);
//第四步:设置连接哪个虚拟主机
connectionFactory.setVirtualHost("/ems");
//第五步:设置连接虚拟主机的用户和密码
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
//第六步:获取连接对象
Connection connection = connectionFactory.newConnection();
//第七步:创建连接通道
Channel channel = connection.createChannel();
//第八步:通道绑定对应的消息队列
/**
* 参数1 queue:队列名称,如果队列不存在则自动创建
* 参数2 durable:用来定义队列是否需要持久化,true 需要持久化;false 不需要持久化
* 参数3 exclusive:定义是否独占队列,true 是;false 否
* 参数4 autodelete:消费完成后是否需要自动删除队列,true 是;false 否
* 参数5:自定义的额外附加参数
*/
//尤其注意,此处队列的绑定的参数形式应该与生产者绑定的队列参数保持一致,以确保两者在同一队列中生产和消费
channel.queueDeclare("hello",false,false,false,null);
//第九步:消费消息
/**
* 参数1:队列名称,即将要消费哪一个队列的消息
* 参数2:开启消息的自动确认机制
* 参数3:消费时的回调接口,常用来处理业务逻辑,一般都是Consumer接口的实现类,此处用默认的DefaultConsumer
*/
channel.basicConsume("hello",true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费消息的内容--》" + new String(body));
}
});
//第十步:关闭通道和连接
//一般情况下消费端是不需要关闭通道和链接的,用以确保消费消费之后的回调接口能够顺利执行,一旦关闭了通道之后,
//回调接口里面的业务逻辑可能就无法执行,因此消费端使用的main方法,而不使用@Test注解,因为@Test注解不能异步操作
//channel.close();
//connection.close();
}
}
消费成功后可以见到控制台打印的信息
因为通道和连接并没有关闭,因此只要一有消息产生,就会被消费掉。而如果关闭了通道和连接词消费了消息但控制台可能就会无法打印出相关信息了,因为连接关闭,回调接口并没有被执行。
最后讲解一下RabbitMq API参数细节
/**
* 参数1 queue:队列名称,如果队列不存在则自动创建
* 参数2 durable:用来定义队列是否需要持久化,true 需要持久化;false 不需要持久化
* 参数3 exclusive:定义是否独占队列,true 是;false 否
* 参数4 autodelete:消费完成后是否需要自动删除队列,true 是;false 否
* 参数5:自定义的额外附加参数
*/
channel.queueDeclare("hello",false,false,false,null);
//第九步:发布消息
/**
* 参数1:交换机名称
* 参数2:队列名称
* 参数3:传递消息的额外设置
* 参数4:消息的具体内容,必须二进制
*/
channel.basicPublish("","hello",null,"hello rabbit".getBytes());
以上代码是通道与队列进行绑定的一个方法,各参数定义都在注释中说明,一下结合rabbit的图形化界面进行更详细的讲解。
1.当参数形式为:
channel.queueDeclare("hello",false,false,false,null);
界面为:
此时Features为空,什么都没有表示,Feature表示该队列的特征。
当第二个参数为true时表示该队列会被持久化到磁盘中,即当rabbit服务器重启或者关闭之后再开启,该队列依然存在,不会消失。代码如下:
channel.queueDeclare("hello",true,false,false,null);
图形化界面如下:
此时的Features特征多了一个D,用来表示durable持久化的。注意由于我们更改了代码,再重新执行生产者生产消失时可能会报错:
异常详细代码:
"C:\Program Files\Java\jdk1.8.0_05\bin\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:D:\idea\IntelliJ IDEA 2018.1.5\lib\idea_rt.jar=56245:D:\idea\IntelliJ IDEA 2018.1.5\bin" -Dfile.encoding=UTF-8 -classpath "D:\idea\IntelliJ IDEA 2018.1.5\lib\idea_rt.jar;D:\idea\IntelliJ IDEA 2018.1.5\plugins\junit\lib\junit-rt.jar;D:\idea\IntelliJ IDEA 2018.1.5\plugins\junit\lib\junit5-rt.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\rt.jar;E:\MyStudy\rabbitmq_study\target\classes;D:\maven\repository_elane\junit\junit\4.12\junit-4.12.jar;D:\maven\repository_elane\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\maven\repository_elane\com\rabbitmq\amqp-client\5.7.2\amqp-client-5.7.2.jar;D:\maven\repository_elane\org\slf4j\slf4j-api\1.7.26\slf4j-api-1.7.26.jar" com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit4 helloword.Provider,sendMsg
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
java.io.IOException
at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:129)
at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:125)
at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:147)
at com.rabbitmq.client.impl.ChannelN.queueDeclare(ChannelN.java:968)
at com.rabbitmq.client.impl.recovery.AutorecoveringChannel.queueDeclare(AutorecoveringChannel.java:333)
at helloword.Provider.sendMsg(Provider.java:46)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'hello' in vhost '/ems': received 'true' but current is 'false', class-id=50, method-id=10)
at com.rabbitmq.utility.ValueOrException.getValue(ValueOrException.java:66)
at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:36)
at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:502)
at com.rabbitmq.client.impl.AMQChannel.privateRpc(AMQChannel.java:293)
at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:141)
... 25 more
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'hello' in vhost '/ems': received 'true' but current is 'false', class-id=50, method-id=10)
at com.rabbitmq.client.impl.ChannelN.asyncShutdown(ChannelN.java:522)
at com.rabbitmq.client.impl.ChannelN.processAsync(ChannelN.java:346)
at com.rabbitmq.client.impl.AMQChannel.handleCompleteInboundCommand(AMQChannel.java:182)
at com.rabbitmq.client.impl.AMQChannel.handleFrame(AMQChannel.java:114)
at com.rabbitmq.client.impl.AMQConnection.readFrame(AMQConnection.java:672)
at com.rabbitmq.client.impl.AMQConnection.access$300(AMQConnection.java:48)
at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:599)
at java.lang.Thread.run(Thread.java:745)
Process finished with exit code -1
原因是更改了代码后我们并没有重启rabbit服务器,导致了rabbit中已经存在了一个名为/ems的队列,且队列参数与我们更改之后的队列参数不一致而抛出的异常,此时我们要么重启rabbit服务,要么就删除这个队列,再重新执行生产者代码即可。
3.第三个参数我们一般都设置为false,第三个参数表示是否独占队列,在实际项目中一般都是会有多个通道同时拥有同一个队列的情形,此处不再做过多解释
4.第四个参数表示消费完成后时候需要自动删除队列,此处的自动删除表示是当所有绑定的消费者的通道连接关闭之后才会自动删除改队列,否则就不会删除,关闭通道即 channel.close();和 connection.close();执行之后,或者消费者端的服务停掉之后。
代码如下:
channel.queueDeclare("hello",true,false,true,null);
界面视图如下,可以看到此时队列的Features多了一个 AD 的标识,即表示 autodelete 自动删除
5.做完以上事情之后,咱们会发现,一旦rabbit重启之后,虽然队列持久化到磁盘中了,不会丢失,但队列中的那些尚未被消费的消息却丢失了,这在项目中是不允许的,此时可以在消息发布的API方法中修改一个参数,即可保证未被消费的消息也能做到持久化。
原代码:
//第九步:发布消息
/**
* 参数1:交换机名称
* 参数2:队列名称
* 参数3:传递消息的额外设置
* 参数4:消息的具体内容,必须二进制
*/
channel.basicPublish("","hello",null,"hello rabbit".getBytes());
我们可以将第三个参数更改以使得未被消费的消息达到持久化,代码如下:
channel.basicPublish("","hello",MessageProperties.PERSISTENT_TEXT_PLAIN,"hello rabbit".getBytes());
6.一定要注意的一个事就是生产者与消费者两者的通道绑定队列的接口参数要严格保持一致,代码如下
生产者:
消费者:
如果不保持一致,则会抛出以下异常信息:
"C:\Program Files\Java\jdk1.8.0_05\bin\java.exe" "-javaagent:D:\idea\IntelliJ IDEA 2018.1.5\lib\idea_rt.jar=56873:D:\idea\IntelliJ IDEA 2018.1.5\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_05\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_05\jre\lib\rt.jar;E:\MyStudy\rabbitmq_study\target\classes;D:\maven\repository_elane\junit\junit\4.12\junit-4.12.jar;D:\maven\repository_elane\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\maven\repository_elane\com\rabbitmq\amqp-client\5.7.2\amqp-client-5.7.2.jar;D:\maven\repository_elane\org\slf4j\slf4j-api\1.7.26\slf4j-api-1.7.26.jar" helloword.Consumer
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Exception in thread "main" java.io.IOException
at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:129)
at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:125)
at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:147)
at com.rabbitmq.client.impl.ChannelN.queueDeclare(ChannelN.java:968)
at com.rabbitmq.client.impl.recovery.AutorecoveringChannel.queueDeclare(AutorecoveringChannel.java:333)
at helloword.Consumer.main(Consumer.java:43)
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'auto_delete' for queue 'hello' in vhost '/ems': received 'false' but current is 'true', class-id=50, method-id=10)
at com.rabbitmq.utility.ValueOrException.getValue(ValueOrException.java:66)
at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:36)
at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:502)
at com.rabbitmq.client.impl.AMQChannel.privateRpc(AMQChannel.java:293)
at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:141)
... 3 more
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg 'auto_delete' for queue 'hello' in vhost '/ems': received 'false' but current is 'true', class-id=50, method-id=10)
at com.rabbitmq.client.impl.ChannelN.asyncShutdown(ChannelN.java:522)
at com.rabbitmq.client.impl.ChannelN.processAsync(ChannelN.java:346)
at com.rabbitmq.client.impl.AMQChannel.handleCompleteInboundCommand(AMQChannel.java:182)
at com.rabbitmq.client.impl.AMQChannel.handleFrame(AMQChannel.java:114)
at com.rabbitmq.client.impl.AMQConnection.readFrame(AMQConnection.java:672)
at com.rabbitmq.client.impl.AMQConnection.access$300(AMQConnection.java:48)
at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:599)
at java.lang.Thread.run(Thread.java:745)