1. Rabbit的安装
1.将文件erlang-21.3-1.el7.x86_64.rpm,rabbitmq_delayed_message_exchange-3.8.0.ez, rabbitmq-server-3.8.8-1.el7.noarch.rpm三个文件使用xftp传入linux系统cenros77系统的/opt目录下。
2.打开/opt目录,顺序使用以下命令安装rabbitmq:
rpm -ivh erlang-21.3-1.el7.x86_64.rpm
yum install socat -y
rpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm
3.常用命令(按照以下顺序执行) 添加开机启动 RabbitMQ 服务(在此之前关闭linux防火墙)
chkconfig rabbitmq-server on
启动服务 /sbin/service rabbitmq-server start
查看服务状态 /sbin/service rabbitmq-server status
停止服务(选择执行) /sbin/service rabbitmq-server stop
关闭防火墙:
1.查看防火墙状态:systemctl status firewalld
2.关闭防火墙 systemctl stop firewalld
3.开机自动进制防火墙:systemctl disable firewalld
开启 web 管理插件 rabbitmq-plugins enable rabbitmq_management
用默认账号密码(guest)访问地址 http://192.168.5.130:15672/出现权限问题
其中192.168.75.130是linux本机ip地址,通过ifconfig命令查看。
4.添加一个新的用户
创建账号 rabbitmqctl add_user admin 123
设置用户角色 rabbitmqctl set_user_tags admin administrator
设置用户权限 set_permissions [-p ] rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
用户 user_admin 具有/vhost1 这个 virtual host 中所有资源的配置、写、读权限 当前用户和角色 rabbitmqctl list_users
——————————————————————————————————————————
RabbitMQ生产者与消费者
1.生产者代码:
package com.atguigu.rabbitmq.one;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.75.130");
factory.setUsername("admin");
factory.setPassword("123");
Connection connection= factory.newConnection();
Channel channel= connection.createChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
String message="hello world";
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("消息发送完毕");
}
}
2.创建一个连接工厂ConnectionFactory,将rabbitMQ的主机号,用户名,用户密码传入进去
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.75.130");
factory.setUsername("admin");
factory.setPassword("123");
3.建立一个连接connection,并创建一个信道channel。
Connection connection= factory.newConnection();
Channel channel= connection.createChannel();
4.并将数据传入channel生成一个队列:channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
而channel.queueDeclare里参数的各个意义如下:
/** * 生成一个队列
* 1.队列名称
* 2.队列里面的消息是否持久化 默认消息存储在内存中
* 3.该队列是否只供一个消费者进行消费 是否进行共享 true 可以多个消费者消费
* 4.是否自动删除 最后一个消费者端开连接以后 该队列是否自动删除 true 自动删除
* 5.其他参数
*/
5.发送一个消息
String message="hello world";
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
其中channel.basicPublish的各参数的意义如下:
/** * 发送一个消息
* 1.发送到那个交换机
* 2.路由的 key 是哪个
* 3.其他的参数信息
* 4.发送消息的消息体 */
———————————————————————————————————————————
RabbitMQ 抽取公共类并创建多进程消费者
1.由于每次创建消费者和生产者总是要创建通道channel,所以将创建channel的过程抽取出来做成一个工具类,每次创建channel调用工具类即可:
package com.atguigu.rabbitmq.utils;
import com.rabbitmq.client.*;
public class RabbitMqUtils {
public static Channel getChannel() throws Exception {
ConnectionFactory factory=new ConnectionFactory();
factory.setHost("192.168.75.130");
factory.setUsername("admin");
factory.setPassword("123");
Connection connection=factory.newConnection();
Channel channel=connection.createChannel();
return channel;
}
}
2.创建消费者worker01
package com.atguigu.rabbitmq.two;
import com.atguigu.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
public class Worker01 {
public static final String QUEUE_NAME="hello";
public static void main(String[] args) throws Exception {
Channel channel= RabbitMqUtils.getChannel();
DeliverCallback deliverCallback=(consumerTag,message)->{
System.out.println("接收到的信息:"+new String(message.getBody()));
};
CancelCallback cancelCallback=(consumerTag)->{
System.out.println("消息者取消消费接口回调逻辑");
};
System.out.println("C2等待接受消息.....");
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
}
}
其中deliverCallback表示的是推送的消息如何进行消费的接口回调
DeliverCallback deliverCallback=(consumerTag,message)->{
System.out.println("接收到的信息:"+new String(message.getBody()));
};
cancelCallback表示的是取消消费的一个回调接口 如在消费的时候队列被删除掉了
CancelCallback cancelCallback=(consumerTag)->{
System.out.println("消息者取消消费接口回调逻辑");
};
在多进程中,可以多次调用work01来多次接受生产者的消息
点击Allow multiple instance可以是work多次调用
3.创建消费者:
package com.atguigu.rabbitmq.two;
import com.atguigu.rabbitmq.utils.RabbitMqUtils;
import com.rabbitmq.client.Channel;
import java.util.Scanner;
public class Task01 {
public static final String QUEUE_NAME="hello";
public static void main(String[] args) throws Exception {
Channel channel = RabbitMqUtils.getChannel();
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
Scanner scanner=new Scanner(System.in);
while(scanner.hasNext()){
String message=scanner.next();
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("发送消息完成:"+message);
}
}
}
1.在生产者运行过程中,先在控制台接收消息
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
Scanner scanner=new Scanner(System.in);
2.然后在发送消息
while(scanner.hasNext()){
String message=scanner.next();
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
System.out.println("发送消息完成:"+message);
}
———————————————————————————————————————————
RabbitMQ 消息手动应答
消息应答::消费者在接 收到消息并且处理该消息之后,告诉 rabbitmq 它已经处理了,rabbitmq 可以把该消息删除了。
消息应答的方法:
A.Channel.basicAck(用于肯定确认) RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了 B.Channel.basicNack(用于否定确认) C.Channel.basicReject(用于否定确认) 与 Channel.basicNack 相比少一个参数 不处理该消息了直接拒绝,可以将其丢弃
1.消息自动应答
消息发送后立即被认为已经传送成功,这种模式需要在高吞吐量和数据传输安全性方面做权 衡,因为这种模式如果消息在接收到之前,消费者那边出现连接或者 channel 关闭,那么消息就丢 失了,当然另一方面这种模式消费者那边可以传递过载的消息,没有对传递的消息数量进行限制, 当然这样有可能使得消费者这边由于接收太多还来不及处理的消息,导致这些消息的积压,最终 使得内存耗尽,最终这些消费者线程被操作系统杀死,所以这种模式仅适用在消费者可以高效并 以某种速率能够处理这些消息的情况下使用
2.消息手动应答
手动应答的好处是可以批量应答并且减少网络拥堵
消息自动重新入列:消息在发出之后发生错误,连接失败等。此时消息会重新进入队列。并重新 发布。
Task02消费者代码:
public class Task02 {
public static final String TASK_QUEUE_NAME="ack_queue";
public static void main(String[] args) throws Exception {
Channel channel= RabbitMqUtils.getChannel();
//声明队列
channel.queueDeclare(TASK_QUEUE_NAME,false,false,false,null);
//从控制台输入信息
Scanner scanner=new Scanner(System.in);
while(scanner.hasNext()){
String message=scanner.next();
channel.basicPublish("",TASK_QUEUE_NAME,null,message.getBytes("UTF-8"));
System.out.println("生产者发出消息:"+message);
}
}
}
work03消费者代码
/**
* 消息再手动应答时是不丢失,放回队列中重新消费
*/
public class Work03 {
//队列名称
public static final String TASK_QUEUE_NAME="ack_queue";
//接收消息
public static void main(String[] args) throws Exception {
Channel channel= RabbitMqUtils.getChannel();
System.out.println("C1等待接收消息处理时间较短");
DeliverCallback deliverCallback=(consumerTag,message)->{
//沉睡一秒
RabbitMqUtils.sleep(1);
System.out.println("接受到的消息:"+new String(message.getBody(),"UTF-8"));
//手动应答
/**
* 1.消息的标记
* 2.是否批量应答 false:不批量应答信道中的消息 true:批量
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
boolean autoAck=false;
channel.basicConsume(TASK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag->{
System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
}));
}
}
work04消费者代码
/**
* 消息再手动应答时是不丢失,放回队列中重新消费
*/
public class Work04 {
//队列名称
public static final String TASK_QUEUE_NAME="ack_queue";
//接收消息
public static void main(String[] args) throws Exception {
Channel channel= RabbitMqUtils.getChannel();
System.out.println("C2等待接收消息处理时间较短");
DeliverCallback deliverCallback=(consumerTag,message)->{
//沉睡一秒
RabbitMqUtils.sleep(30);
System.out.println("接受到的消息:"+new String(message.getBody(),"UTF-8"));
//手动应答
/**
* 1.消息的标记
* 2.是否批量应答 false:不批量应答信道中的消息 true:批量
*/
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
boolean autoAck=false;
channel.basicConsume(TASK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag->{
System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
}));
}
}
运行结果
Task02 分别发送aa,bb,cc,dd
work03 在work04中断后,消息dd没有丢失,重新进入队列,发送到work03
work04 睡眠时间较长,在Task02发送dd时进行中断进程。导致work04没有接受
———————————————————————————————————————————
RabbitMQ 队列与消息持久化
在消息手动应答中,在任务丢失的情况下保障了消息不丢失。而在RabbitMQ停到服务后,使用持久化来保证数据不丢失
在生产者->队列->消费者
1.队列持久化
需要让Queue进行持久化
boolean durable=true;
然后将durable进入队列。保证持久化
2.消息持久化
在队列到消费者中,实现持久化。
———————————————————————————————————————————
RabbitMQ 不公平分发和预取值
1.不公平分发
在消费者work01中,睡眠时间较短,处理消息速度很快。相反,在消费者work02中睡眠时间较长,处理速度较慢。从而导致有的消费者一直在忙,而有的消费者一直处于空闲状态。这是一种不太好的处理方式
所以我们需要在消费者中设置参数 channel.basicQos(1)
来变成不公平分发
2.预取值
本身消息的发送就是异步发送的,所以在任何时候,channel 上肯定不止只有一个消息另外来自消费 者的手动确认本质上也是异步的。因此这里就存在一个未确认的消息缓冲区,因此希望开发人员能限制此 缓冲区的大小,以避免缓冲区里面无限制的未确认消息问题。这个时候就可以通过使用 basic.qos 方法设 置“预取计数”值来完成的。该值定义通道上允许的未确认消息的最大数量。一旦数量达到配置的数量, RabbitMQ 将停止在通道上传递更多消息,除非至少有一个未处理的消息被确认。
在消费者中设置代码、
work03
work04
———————————————————————————————————————————
RabbitMQ 发布确认
实现持久化需要三步:
1.队列持久化
2.消息持久化
3.发布确认
虽然进行了第1,2步。但是只有发布确认,才能真正知道消息真的写入磁盘,写入内存。真正实现持久化。
发布确认有三种方式:
1.单个确认
2.批量确认
3.异步i确认
1.单个确认
//单个确认
public static void publishMessageIndividually() throws Exception{
Channel channel= RabbitMqUtils.getChannel();
//队列的声明
String queueName= UUID.randomUUID().toString();
channel.queueDeclare(queueName,true,false,false,null);
//开启发布确认
channel.confirmSelect();
long begin=System.currentTimeMillis();
//批量发消息
for(int i=0;i<MESSAGE_COUNT;i++){
String message=i+"";
channel.basicPublish("",queueName,null,message.getBytes());
boolean flag=channel.waitForConfirms();
if(flag){
System.out.println("消息发送成功!");
}
}
long end=System.currentTimeMillis();
System.out.println("发布"+MESSAGE_COUNT+"个单独确认消息,耗时"+(end-begin)+"ms");
}
2.批量确认
//批量发布确认
public static void publishMessageBatch() throws Exception{
Channel channel= RabbitMqUtils.getChannel();
//队列的声明
String queueName= UUID.randomUUID().toString();
channel.queueDeclare(queueName,true,false,false,null);
//开启发布确认
channel.confirmSelect();
//开始时间
long begin=System.currentTimeMillis();
//批量确认消息大小
int batchSize=100;
//未确认消息的个数
//批量发送消息,批量发布确认
for(int i=0;i<MESSAGE_COUNT;i++){
String message=i+"";
channel.basicPublish("",queueName,null,message.getBytes());
//批量发布确认,每一百次批量确认一次
if (i%batchSize==0){
//发布确认
channel.waitForConfirms();
}
}
//结束时间
long end=System.currentTimeMillis();
System.out.println("发布"+MESSAGE_COUNT+"个批量确认消息,耗时"+(end-begin)+"ms");
}
3.异步发布
//异步发布确认
public static void publishMessageAsync()throws Exception{
Channel channel= RabbitMqUtils.getChannel();
//队列的声明
String queueName= UUID.randomUUID().toString();
channel.queueDeclare(queueName,true,false,false,null);
//开启发布确认
channel.confirmSelect();
//开始时间
long begin=System.currentTimeMillis();
/**
* 线程安全有序有一个哈希表,适用于高并发的情况下
* 1.轻松的将信号与消息进行关联
* 2.轻松批量删除条目 只要给到序号
* 3.支持高并发
*/
ConcurrentSkipListMap<Long,String> outstandingConfirms=
new ConcurrentSkipListMap<>();
//消息确认成功 回调函数
ConfirmCallback ackCallback=(deliveryTag,multiple)->{
if (multiple) {
//2.删除到已经确认的消息,剩下的就是未确认的消息
ConcurrentNavigableMap<Long, String> confirmed =
outstandingConfirms.headMap(deliveryTag);
confirmed.clear();
}else {
outstandingConfirms.remove(deliveryTag);
}
System.out.println("确认的消息:"+deliveryTag);
};
//消息确认失败 回调函数
/**
* 1,消息的标记
* 2.是否为批量确认
*/
ConfirmCallback nackCallback=(deliveryTag,multiple)->{
//3.打印下未确认的消息有哪些
String message=outstandingConfirms.get(deliveryTag);
System.out.println("未确认的消息是"+message+"::未确认的消息的Tag是:"+deliveryTag);
};
//准备消息的监听器,监听那些消息成功了,那些消息失败了
/**
* 1.监听那些消息成功了
* 2.监听那些消息失败了
*/
channel.addConfirmListener(ackCallback,nackCallback); //异步通知
//批量发送消息
for(int i=0;i<MESSAGE_COUNT;i++){
String message="消息"+i;
channel.basicPublish("",queueName,null,message.getBytes());
//1.此处记录下所有要发送的消息,消息的总和
outstandingConfirms.put(channel.getNextPublishSeqNo(), message);
}
//结束时间
long end=System.currentTimeMillis();
System.out.println("发布"+MESSAGE_COUNT+"个批量确认消息,耗时"+(end-begin)+"ms");
}
最后结果如下