#进入rabbitmq的安装目录下的sbin目录下有提供的命令
rabbitmqctl add_user <用户名> <密码>
用户等级名称 | 登录控制台 | 查看所有信息 | 制定策略 | rabbitmq进行管理 |
---|---|---|---|---|
administrator | √ | √ | √ | √ |
monitoring | √ | √ | ||
policymaker | √ | √ | ||
managment | √ |
#进入rabbitmq的安装目录下的sbin目录下有提供的命令
rabbitmqctl set_user_tags <用户名> <用户等级名称>
当 通道channel 开启 事务模式 ,当抛出异常捕获异常 可以做 事务回滚txRollbac() 将发送的消息拿回来,只有与事务提交才可以将消息发送
try { //开启事务模式 channel.txSelect(); channel.basicPublish("", QUEUE_NAME, null, message.getBytes()); int result = 1 / 0; //事务提交 channel.txCommit(); } catch (Exception e) { e.printStackTrace(); //事务回滚 channel.txRollback(); }
2.1 简单模式
- publish一条消息后,等待服务器端confirm,如果服务端返回false或者超时时间内未返回,客户端进行消息重传,当发送多条信息,如果有一条返回false,就将这批消息全部重新发送
- 但是因为不是异步的,如果不返回结果,waitForConfirms就会等待造成阻塞
//开启confirm模式 channel.confirmSelect(); channel.basicPublish("", QUEUE_NAME, null, message.getBytes()); //等待返回 是否confirm接受成功 boolean b = channel.waitForConfirms(); System.out.println(b?"消息被接受":"消息未被接收");
//开启confirm模式 channel.confirmSelect(); for(int i=0;i<batchCount;i++){ channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes()); } if(!channel.waitForConfirms()){ //waitForConfirmsOrDie() 、waitForConfirms() 两个都可以 System.out.println("send message failed."); }
2.2 异步模式
- 与简单模式相比,它是异步,开启一个监听器线程,不会造成线程阻塞
- addConfirmListener 发送消息,交换机是否接收的监听器
- addReturnListener 发送消息,队列是否接收的监听器
– 普通的MAVEN项目
channel.basicPublish("", QUEUE_NAME, null, message.getBytes()); channel.addConfirmListener(new ConfirmListener() { //消息发送消息交换机接受成功调用 @Override public void handleAck(long l, boolean b) throws IOException { System.out.println("消息唯一标识 deliveryTag " + l); System.out.println("是否是多条消息确认 multiple " + b); System.out.println("消息发送已接收"); } //消息发送消息交换机接受失败调用 @Override public void handleNack(long l, boolean b) throws IOException { System.out.println("消息唯一标识 deliveryTag " + l); System.out.println("是否是多条消息确认 multiple " + b); System.out.println("消息发送未被接收"); } }); channel.addReturnListener(new ReturnListener() { //发送消息队列未接收,失败就会调用 @Override public void handleReturn(int i, String s, String s1, String s2, > >AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException { System.out.println("返回标识" + i); System.out.println("未接受原因" + s); System.out.println("发送消息交换机" + s1); System.out.println("接受消息的队列" + s2); System.out.println("消息内容" + new String(bytes)); System.out.println("消息的基础参数"+ basicProperties); System.out.println("消息发送队列已接收"); } });
– SpringBoot项目
- 配置文件开启配置项,创建类注入spring容器,通过注入rabbitmqtemplate模板对象中的returncallback,confirmcallback属性,通过实现ReturnCallback ,ConfirmCallback接口重写returnedMessage方法(监听队列是否接收到消息),重写confirm方法(监控交换机消息是否接收)
spring: rabbitmq: host: localhost port: 5672 username: lzj password: 123456 virtual-host: /vhost_lzj #开启RETURN模式 publisher-returns: true #开启CONFIRM模式 publisher-confirm-type: correlated
@Component public class MQReturn implements RabbitTemplate.ReturnCallback { @Autowired private RabbitTemplate rabbitTemplate; @PostConstruct public void init(){ this.rabbitTemplate.setReturnCallback(this); this.rabbitTemplate.setConfirmCallback(this); } //监听队列是否接收到消息 @Override public void returnedMessage(Message message, int i, String s, String s1, String s2) { System.out.println("返回标识" + i); System.out.println("未接受原因" + s); System.out.println("发送消息交换机" + s1); System.out.println("接受消息的队列" + s2); System.out.println("消息基础内容" + new String(message.getBody())); System.out.println("消息的基础参数对象 : " + message.getMessageProperties()); System.out.println("队列接受消息失败"); } //监控交换机消息是否接收 @Override public void confirm(CorrelationData correlationData, boolean b, String s) { System.out.println(correlationData.getId()); if(b){ System.out.println("交换机接收消息成功"); }else{ System.out.println("交换机接收消息失败,原因:"+ s); } } }
– 三个持久化可以通过rabbitmq后台创建,也可以通过代码方式创建(我后面也有介绍)
- 交换机持久化
- 队列持久化
- 消息持久化 (消息是在队列里,所以消息持久化要建立在队列持久化的基础上)
- 当关闭自动应答ack , RabbitMQ会等待消费者显式发回ack信号后才从内存(和磁盘,如果是持久化消息的话)中移去消息。如果自动应答ack是开启的,RabbitMQ会在队列中消息被消费后立即删除它
- 普通MAVEN项目
Consumer consumer = new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String msg = new String(body); System.out.println("接收到消息" + msg); System.out.println("处理逻辑"); //手动应答消息 channel.basicAck(envelope.getDeliveryTag(),false); } }; channel.basicConsume(QUEUE_NAME,AUTO_ACK,Consumer);
- SpringBoot项目
配置文件开启对应的配置项,在消费处使用 channel.basicAck 进行消费应答spring: rabbitmq: host: localhost port: 5672 username: lzj password: 123456 virtual-host: /vhost_lzj # 消息开启手动确认 listener: #包含四种工作模式(fanout, direct, topic, headers) direct: acknowledge-mode: manual # 包含两种工作模式(simple, work) simple: acknowledge-mode: manual
@RabbitHandler public void getMsgString(String msg, Channel channel, Message message) throws IOException { System.out.println("正在消费一条消息 : " + msg); System.out.println(message.getMessageProperties().getDeliveryTag()); Scanner scanner = new Scanner(System.in); System.out.println("请输入任意字符 消费该消息"); String s = scanner.nextLine(); channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); }
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
<version>5.6.0version>
dependency>
public class MQUtil {
public static Connection getConnection() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setVirtualHost("/vhost_lzj");
factory.setUsername("lzj");
factory.setPassword("123456");
return factory.newConnection();
}
}
//获取连接
Connection connection = MQUtil.getConnection();
//创建一个通道 相当于jdbc中的statement
Channel channel = connection.createChannel();
//rabbitmq的操作 ......
//关闭通道
channel.close();
//关闭连接
connection.close();
API | 作用 |
---|---|
queueDeclare | 创建声明队列 |
exchangeDeclare | 创建声明交换机 |
queueBind | 队列绑定 |
basicPublish | 消息推送 |
basicConsume/basicGet | 消息消费 |
basicAck | 消息应答 |
basicCancel | 取消消费者订阅 |
basicReject | 消息拒绝 |
basicRecover | 恢复消息到队列 |
basicQos | 设置服务端每次发送给消费者的消息数量 |
txSelect | 事务开启 |
txCommit | 事务提交 |
txRollback | 事务回滚 |
confirmSelect | confirm模式开启 |
addConfirmListener | 添加confirm监听器,监听交换机是否接收到消息 |
addReturnListener | 添加return监听器,监听队列是否接收到消息 |
- String – 队列的名字
- boolean – 消息持久化是否开启 (队列持久化就是就是即时重启rabbitmq服务,队列也存在,因为rabbitmq把队列存储在硬盘,非持久化就是存储在内存,重启宕机队列就会丢失)
- boolean – 是否排外 (当前队列是否为当前连接私有)
- boolean – 是否自动删除 (当最后一个消费者断开连接之后,就自动删除队列,不管队列里有没有消息)
- Map
– 属性参数设置 案例
参数 作用 Message TTL(x-message-ttl) 设置队列中的所有消息的生存周期(统一为整个队列的所有消息设置生命周期), 也可以在发布消息的时候单独为某个消息指定剩余生存时间,单位毫秒, 类似于redis中的ttl,生存时间到了,消息会被从队里中删除,注意是消息被删除,而不是队列被删除, 特性Features=TTL, 单独为某条消息设置过期时间 AMQP.BasicProperties.Builder properties = new AMQP.BasicProperties().builder().expiration(“6000”);channel.basicPublish(EXCHANGE_NAME, “”, properties.build(), message.getBytes(“UTF-8”));
Auto Expire(x-expires) 当队列在指定的时间没有被访问(consume, basicGet, queueDeclare…)就会被删除,Features=Exp Max Length(x-max-length) 限定队列的消息的最大值长度,超过指定长度将会把最早的几条删除掉, 类似于mongodb中的固定集合,例如保存最新的100条消息, Feature=LimMax Length Bytes(x-max-length-bytes) 限定队列最大占用的空间大小, 一般受限于内存、磁盘的大小, Features=Lim B Dead letter exchange(x-dead-letter-exchange) 当队列消息长度大于最大长度、或者过期的等,将从队列中删除的消息推送到指定的交换机中去而不是丢弃掉,Features=DLX Dead letter routing key(x-dead-letter-routing-key) 将删除的消息推送到指定交换机的指定路由键的队列中去, Feature=DLK Maximum priority(x-max-priority) 优先级队列,声明队列时先定义最大优先级值(定义最大值一般不要太大),在发布消息的时候指定该消息的优先级, 优先级更高(数值更大的)的消息先被消费, Lazy mode(x-queue-mode=lazy) Lazy Queues: 先将消息保存到磁盘上,不放在内存中,当消费者开始消费的时候才加载到内存中 Master locator(x-queue-master-locator)
- String – 交换机名称
- String – 交换机类型 【“fanout”-订阅模式,“direct”-路由模式,“topic”-主题模式】
- boolean – 交换机是否持久化(持久化就是即时重启宕机,交换机依然存在,因为不是存在内存而是存在硬盘中)
- boolean – 是否自删除 (当最后一个队列与该交换机解绑,该交换机自动删除)
- boolean – 是否是内置的交换器,如果是,生产者客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器这种方式。
- Map
– 属性参数设置 (不常用不做说明,一般为NULL)
- String – 队列名字
- String – 交换机名字
- String - 路由键值(routing key)
- String – 交换机名字 可以是空字符串
- String – 队列名或路由键值(如果交换机名字为空字符串,这个就是队列名,如果不为空字符串,就是路由键值)
- BasicProperties – 基础配置参数 (如果业务涉及要求消息携带以下参数,不要把参数写在消息内容,要设置在这,可以理解为JWT中的参数)
通过创建 BasicProperties 构造方法 ,将配置参数放入构造参数中
属性 作用 contentType 消息类型如(text/plain) contentEncoding 消息内容编码 headers 消息头部(可以填写任何K-V) deliveryMode 消息的投递方式(非持久【1】,持久【2】,就是消息存在硬盘还是内存中) priority 优先级(优先级越高消息,越先被消费) expiration 消息过期时间 timestamp 发送消息的时间戳 replyTo 发送该消息到的队列名称 correlationId 相关的标识 messageId 消息的标识 userId 用户标识 appId 应用标识 type 消息类型 clusterId 集群标识
- byte [] – 消息内容
方法重载 比上面多了一个 boolean形参
- boolean mandatory – 当交换器无法根据自动的类型和路由键找到一个符合条件的队列,如果mandatory = true ,则返回消息给生产者,mandatory = false,消息直接丢弃
- boolean immediate – true,如果交换器在消息路由到队列时发现没有任何消费者,那么这个消息将不会存和队列。rabbit3.0被丢弃了这个参数
消费队列的消息,只要队列还有消息就一直取
- String queue – 队列名字
- boolean autoAck – 自动应答,true,他就会自动ack回应。false,必须手动basicAck回应(只有生产者接受到ack才会从队列移除消息完成消费)
- String consumerTag – 消费者标签,用来区分多个消费者
- boolean onLocal – 设置为true,表示 不能将同一个Conenction中生产者发送的消息传递给这个Connection中 的消费者
- boolean exclusive – 是否排他
- Map
arguments – 消费者的参数 - DeliverCallback deliverCallback – 当一个消息发送过来后的回调接口(函数式接口)
- CancelCallback cancelCallback – 除了调用basicCancel的其他原因导致消息被取消时调用该接口。(函数式接口)
- ConsumerShutdownSignalCallback shutdownSignalCallback – 当Channel与Conenction关闭的时候会调用(函数式接口)
- Consumer consumer – 消费的回调对象
通过重写 DefaultConsumer 对象的方法 去实现回调
方法名 作用 handleDelivery 消息接收时被调用 handleConsumeOk 任意basicComsume调用导致消费者被注册时调用 handleRecoverOk basicRecover调用并被接收时调用 handleCancel 除了调用basicCancel的其他原因导致消息被取消时调用。 handleCancelOk basicCancel调用导致的订阅取消时被调用 handleShutdownSignal 当Channel与Conenction关闭的时候会调用
消费队列中的第一条消息
- String – 队列名字
- boolean autoAck – 是否自动应答
消息消费(basicConsume/basicGet)如果关闭了自动应答,则要使用该API来手动应答,如果关闭了自动应答,而在消费消息时不手动应答,当消费者消费消息时,队列会移除该消息,但是没有接收到消费者的应答前,该消息真正意义上是没被消费的,但消费者channel或connection关闭时,队列还没接收到应答就会返还该消息
- long – 服务器端向消费者推送消息,消息会携带一个deliveryTag参数,也可以成此参数为消息 * 的唯一标识,是一个递增的正整数
- boolean – true表示确认所有消息,包括消息唯一标识小于等于deliveryTag的消息,false只确认 * * deliveryTag指定的消息
取消消费者对队列的订阅关系,此消费者不再消费该队列消息
- String consumerTag – 服务器端生成的消费者标识
一次拒绝一个消息
- long – 服务器端向消费者推送消息,消息会携带一个deliveryTag参数,也可以成此参数为消息 * 的唯一标识,是一个递增的正整数
- boolean – true则重新入队列,否则丢弃或者进入死信队列。
批量拒绝消息
- long – 服务器端向消费者推送消息,消息会携带一个deliveryTag参数,也可以成此参数为消息 * 的唯一标识,是一个递增的正整数
- boolean – true表示确认所有消息,包括消息唯一标识小于等于deliveryTag的消息,false只确认 * * deliveryTag指定的消息
- boolean – 表示拒绝的消息,true是消息重新入队,false是消息丢弃
将未确认的消息重新恢复到队列,投递给其他消费者,而不是自己
将未确认的消息重新恢复到队列
boolean – true则重新入队列,并且尽可能的将之前recover的消息投递给其他消费者消费,而不是自己再次消费。false则消息会重新被投递给自己。
- int prefetchCount 服务端每次发送给消费者的消息数量
- boolean – 如果为true,则当前设置将会应用于整个Channel(频道)
- prefetchSize – 服务器传送最大内容量(以八位字节计算)
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
spring:
rabbitmq:
host: localhost
port: 5672
username: lzj
password: 123456'
virtual-host: /vhost_lzj
# 消息开启手动确认
listener:
#包含四种工作模式(fanout, direct, topic, headers)
direct:
acknowledge-mode: manual
# 包含两种工作模式(simple, work)
simple:
acknowledge-mode: manual
/*
* RabbitTemplate 是 AmpqTemplate 的实现类 所以两者没有很大区别
* */
@Autowired
private AmpqTemplate ampqTemplate;
@Autowired
private RabbitTemplate rabbitTemplate;
API | 作用 |
---|---|
一 | 队列,交换机的创建,以及交换机和队列绑定 |
convertAndSend / send | 消息的发送 |
三 | 消息接收 |
四 | confirm监听器,监听交换机是否接收到消息 |
五 | return监听器,监听队列是否接收到消息 |
六 | 消费者应答 |
七 | 消费者的质量保证 |
@Configuration public class MQConfiguration { /** * 交换机创建(FanoutExchange/DirectExchange/TopicExchange) */ @Bean public FanoutExchange lzjFanoutExchange() { FanoutExchange fanoutExchange = new FanoutExchange("lzj_fanout"); return fanoutExchange; } @Bean public DirectExchange lzjDirectExchange() { DirectExchange directExchange = new DirectExchange("lzj_direct"); return directExchange; } @Bean public TopicExchange lzjTopicExchange(){ TopicExchange topicExchange = new TopicExchange("lzj_topic"); return topicExchange; } /** * 队列创建 */ @Bean public Queue lzjQueue() { Queue queue = new Queue("lzj_queue_1"); return queue; } /** * 建立绑定关系 */ @Bean public Binding lzjFanoutQueueBinding(FanoutExchange lzjFanoutExchange, Queue lzjQueue){ Binding to = BindingBuilder.bind(lzjQueue).to(lzjFanoutExchange); return to; } }
- Message — 消息对象(里面包含消息内容字节类型,和消息参数)
//例子一: 给指定队列发送消息 rabbitTemplate.setDefaultReceiveQueue(QUEUE_NAME); rabbitTemplate.send(new Message(msg.getBytes(),new MessageProperties())); //例子二: 给指定交换机发送消息 rabbitTemplate.setExchange("ex_lzj_fanout"); rabbitTemplate.send(new Message(msg.getBytes(),new MessageProperties())); //例子三:给指定交换机,指定路由键发送消息 rabbitTemplate.setExchange("ex_lzj_direct"); rabbitTemplate.setRoutingKey("a"); rabbitTemplate.send(new Message(msg.getBytes(),new MessageProperties()));
- routingKey — 当没有设置setExchange设置发送指定交换机,routingKey 就是队列名,当设置了发送指定交换机,routingKey 就为路由键
- Message — 消息对象(里面包含消息内容字节类型,和消息参数[MessageProperties]: headers, properties)
//例子一: 没有设置setExchange设置发送指定交换机,routingKey 就是队列名 rabbitTemplate.send(QUEUE_NAME,new Message(msg.getBytes(),new MessageProperties())); //例子二: 当设置了发送指定交换机,routingKey 就为路由键,因为fanout是不用路由键,所以routingKey可以为空字符串 rabbitTemplate.setExchange("ex_lzj_fanout"); rabbitTemplate.send("",new Message(msg.getBytes(),new MessageProperties())); rabbitTemplate.setExchange("lzj_direct"); rabbitTemplate.send("a",new Message(msg.getBytes(),new MessageProperties()));
- exchange — 交换机名字
- routingKey — 路由键,可以为空字符串
- Message — 消息对象(里面包含消息内容字节类型,和消息参数[MessageProperties]: headers, properties)
待研究
发送消息,与send()方法用法相同,不同地方是他接收的参数是一个Object对象,该对象要实现Serializable接口。
- Object — 接收对象,Object 转换再传输
//例子一: 给指定队列发送消息 rabbitTemplate.setDefaultReceiveQueue(QUEUE_NAME); rabbitTemplate.convertAndSend(msg); //例子二: 给指定交换机发送消息 rabbitTemplate.setExchange("ex_lzj_fanout"); rabbitTemplate.send(msg); //例子三:给指定交换机,指定路由键发送消息 rabbitTemplate.setExchange("ex_lzj_direct"); rabbitTemplate.setRoutingKey("a"); rabbitTemplate.send(msg);
- Object — 接收对象,Object 转换再传输
- MessagePostProcessor — 在信息发送之前设置参数的接口对象,通过重写该接口的postProcessMessage去设置消息的餐数据【headers, properties】
MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { message.getMessageProperties().setHeader("K","V"); message.getMessageProperties().setMessageId("1"); return message; } }; //应为和rabbitTemplate.convertAndSend(String routingKey, Object object)重载方法参数重复,会造成编译匹配到两个重载方法,所以string要转object rabbitTemplate.convertAndSend((Object) msg,messagePostProcessor); ```
- routingKey — 当没有设置setExchange设置发送指定交换机,routingKey 就是队列名,当设置了发送指定交换机,routingKey 就为路由键
- Object — 接收对象,Object 转换再传输
//例子一: 没有设置setExchange设置发送指定交换机,routingKey 就是队列名 rabbitTemplate.convertAndSend(QUEUE_NAME,msg); //例子二: 当设置了发送指定交换机,routingKey 就为路由键,因为fanout是不用路由键,所以routingKey可以为空字符串 rabbitTemplate.setExchange("ex_lzj_fanout"); rabbitTemplate.convertAndSend("",msg); rabbitTemplate.setExchange("lzj_direct"); rabbitTemplate.convertAndSend("a",msg);
- routingKey — 当没有设置setExchange设置发送指定交换机,routingKey 就是队列名,当设置了发送指定交换机,routingKey 就为路由键
- Object — 接收对象,Object 转换再传输
- MessagePostProcessor — 在信息发送之前设置参数的接口对象,通过重写该接口的postProcessMessage去设置消息的餐数据【headers, properties】
- exchange — 交换机名字
- routingKey — 路由键,可以为空字符串
- Object — 接收对象,Object 转换再传输
- exchange — 交换机名字
- routingKey — 路由键,可以为空字符串
- Object — 接收对象,Object 转换再传输
- MessagePostProcessor — 在信息发送之前设置参数的接口对象,通过重写该接口的postProcessMessage去设置消息的餐数据【headers, properties】
创建一个类用于监听队列接受消息,标注@Component把此对象交给spring容器管理,标注@RabbitListener用于监听队列,再通过标注@RabbitHandler进行一个接收方法的声明。
- 注意:生产者发送的消息对象要序列化,所以消费者接收消息对象,要与生产者的消息对象一致(包括包名 ),@RabbitHandler标注的方法可以自动注入 Channel , Message对象。
@Component @RabbitListener(queues = "fanout_queue1") public class MyService { @RabbitHandler public void getMsgString(String msg, Channel channel, Message message) throws IOException { System.out.println("正在消费一条消息 : " + msg); System.out.println(message.getMessageProperties().getDeliveryTag()); Scanner scanner = new Scanner(System.in); System.out.println("请输入任意字符 消费该消息"); String s = scanner.nextLine(); channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); }
spring.rabbitmq.listener.simple.prefetch=100
消息被拒绝(Basic.Reject或Basic.Nack)并且设置 requeue 参数的值为 false
消息过期了
队列达到最大的长度
Netty框架做的聊天系统,可以用rabbitmq来作为会话类别管理
在大量请求的情况下,可以使用rabbitmq进行一个中间件的流量削峰,一个个请求进行处理。
例如登录发送邮件短信,邮件短信进行一个异步的处理发送。还有订单下单,
sleuth 微服务跟踪和熔断 + zipkin 一个开源项目 + rabbimq 进行一个削峰
filebeat 日志监控 + logtash 日志过滤 + rabbitmq 进行一个削峰 + elasticsearch 搜索引擎