一、安装RabbitMQ
安装直接用docker安装,如果手动安装的话比较繁琐,还要安装erlang语言的环境。在安装有docker机器上执行官网提供的指令(https://www.rabbitmq.com/download.html):docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
安装好之后 浏览器访问15672端口,可以看到web管理端。默认的是用户名密码都是guest
二、AMQP协议(Advanced message queue protocol 高级消息队列协议)核心概念和RabbitMQ整体架构
RabbitMQ是通过Erlang语言基于AMQP协议开发的。erlang语言常用于交换机上,因为它的高效,自然rabbitMQ也很高效了。
AMQP模型描述了一套模块化的组件以及这些组件之间进行连接的标准规则,核心概念:
server:服务,又称broker,接受客户端连接,对外提供amqp实体服务
connection:连接,客户端与server的连接
channel:网络通道,几乎所有的操作都是在channel中进行的,是进行消息对象的通道,客户端可以建立多个通道,每一个channel表示一个会话任务
virtual host:虚拟主机,是一个逻辑上的概念,一个虚拟主机中可以包含多个exhange 和 message queue但是一个虚拟主机中不能有名称相同的exchange 和 message queue
exchange:接收发布应用程序发送的消息,并根据一定的规则将这些消息路由到消息队列
message queue:消息队列,存储消息,直到这些消息被消费者安全处理完为止
binding:定义了exchange和message queue之间的关联,提供路由规则,包含route_key
route_key:路由key ,作用是在交换机上通过route_key来把消息路由到某个队列上
RabbitMQ整体架构:
三、交换机详解
1.交换机属性:
name:交换机名字
type:类型:direct,topic,fanout,headers
durability:是否需要持久化
autodelete: 假如没有队列绑定到该交换机,那么该交换机会自动删除
Internal: 当前交换机是否用户rabbitmq内部使用不常用,默认为false
argurements: 扩展参数,用户扩展AMQP定制化协议
2.直连交换机:direct exchange 消息投递时的key与队列绑定routekey完全相同的队列上面
3.主题交换机:topic exchange 投递消息是可以进行key的匹配,#匹配多个单词,*匹配一个单词(用.隔开),比如队列A绑定的routekey:study.#。投递消息时设置的routekey.abc或者routekey.abc.def都可以投递到队列A上面;队列B设置的routekey:study.*,那么就只有routekey.abc可以投递到队列B
4.扇形交换机: fanout exchange 不会进行routekey的匹配,只要是该交换机的队列都会被投递。因为不会进行路由字符串的匹配,所以这种交换的消息投递是最快的
5.头部交换机:headers exchange 投递的消息头里面有一个特殊的key:“x-match”,有两个值:all(默认),发送消息的header里面的所有key都对应,才投递;any,任意一个对应就投递
四、不同交换机的demo
依赖:使用的spring-boot-starter-amqp,这个里面吧相应的原始的依赖的导入了的。还是使用最原始的方式来进行生产消费的,这样能更好的理解,和上面的核心内容中的概念,以及交换机绑定都可以联系起来。
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-amqpartifactId> <version>2.1.7.RELEASEversion> dependency>
设置连接:
package com.nijunyang.rabbitmq.util; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import java.io.IOException; import java.util.concurrent.TimeoutException; /** * Description: * Created by nijunyang on 2020/5/27 10:24 */ public abstract class RabbitMQUtils { private static ConnectionFactory connectionFactory = new ConnectionFactory(); //设置连接工厂信息 static { connectionFactory.setHost("*.*.*.*"); connectionFactory.setPort(5672); connectionFactory.setUsername("guest"); connectionFactory.setPassword("guest"); } public static Connection getConnection() throws IOException, TimeoutException { Connection connection = connectionFactory.newConnection(); return connection; } public static void close(AutoCloseable...closeables) throws Exception { for (AutoCloseable closeable : closeables) { closeable.close(); } } }
消费者获取连接,创建channel,声明交换机和队列,绑定交换机和队列,准备接收消息
package com.nijunyang.rabbitmq.consumer; import ch.qos.logback.core.net.SyslogOutputStream; import com.nijunyang.rabbitmq.util.RabbitMQUtils; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.Consumer; import com.rabbitmq.client.DefaultConsumer; import com.rabbitmq.client.Envelope; import java.io.IOException; /** * Description: * Created by nijunyang on 2020/5/27 10:42 */ public class RabbitMQConsumer { public static void main(String[] args) throws Exception { Connection connection = RabbitMQUtils.getConnection(); Channel channel = connection.createChannel(); /*------------------*/ //1:默认消息投递 消费 // String queueName = "hello.queue"; // //声明一个队列(如果没有这个队列,第一次是在消费者这边在声明的,所以要先启动消费者,生产者消息才有地方投递) // channel.queueDeclare(queueName,true,false,true,null); /*------------------*/ //2:直连交换机 // String exchangeName = "njy.directchange"; //// String exchangeType = "direct"; //// //// String routingKey = "directchange.key";//发送和消费的要一模一样 //// String queueName = "direct.queue"; //// //声明一个交换机和队列 //// channel.exchangeDeclare(exchangeName, exchangeType,true,false,null); //// channel.queueDeclare(queueName,true,false,false,null); //// //队列和交换机通过routingKey绑定 //// channel.queueBind(queueName, exchangeName, routingKey); //// /*------------------*/ //3:topic交换机 String exchangeName = "njy.topicchange"; String routingKey = "directchange.#"; //.分割,#匹配多个,*匹配一个 String exchangeType = "topic"; String queueName = "topic.queue"; //声明一个交换机和队列 channel.exchangeDeclare(exchangeName, exchangeType,true,false,null); channel.queueDeclare(queueName,true,false,false,null); //队列和交换机通过routingKey绑定 channel.queueBind(queueName, exchangeName, routingKey); /*------------------*/ //创建消费者 Consumer consumer = new DefaultConsumer(channel) { //回调 @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String message = new String(body, "UTF-8"); System.out.println(message); } }; channel.basicConsume(queueName,true, consumer); } }
生产者消费者获取连接,创建channel,发送消息
package com.nijunyang.rabbitmq.producer; import com.nijunyang.rabbitmq.util.RabbitMQUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import java.util.Random; /** * Description: * Created by nijunyang on 2020/5/27 10:18 */ public class RabbitMQProducer { public static void main(String[] args) throws Exception { Connection connection = RabbitMQUtils.getConnection(); Channel channel = connection.createChannel(); String message = "hello rabbitMQ." + new Random().nextInt(100); /*------------------*/ //1:默认消息投递 // //参数String exchange, String routingKey, AMQP.BasicProperties props, byte[] body // //不设置交换机会发送到默认上面 // channel.basicPublish("", "hello.queue", null, message.getBytes("utf-8")); /*------------------*/ //2:直连交换机 // String exchangeName = "njy.directchange"; // String routingKey = "directchange.key"; //发送和消费的要一模一样 /*------------------*/ //3.topic交换机 String exchangeName = "njy.topicchange"; String routingKey = "directchange.key"; //.分割,#匹配多个,*匹配一个 channel.basicPublish(exchangeName, routingKey, null, message.getBytes("utf-8")); /*------------------*/ RabbitMQUtils.close(channel, connection); } }
如果队列不存在,需要先启动消费者,才会先将生成队列,不然生产者发送的消息不知道路由到哪个队列,因为队列的声明,以及和交换机的绑定是在消费者这边做的。