RabbitMQ(一):入门
-
RabbitMQ是一个消息中间件,相当于一个中转站;用于接收、存储、转发消息数据
-
RabbitMQ的作用:
- 流量消峰:当服务器处理不了过多的请求时,在进入服务器之前先进入MQ,MQ会对请求做排序,防止服务器宕机
- 应用解耦:当系统的某个子系统出现故障无法正常工作时,该子系统要处理的内存会被缓存到消息队列中,当该子系统正常后再继续处理;提高用户体验,避免用户再次请求
- 异步处理:有些服务间的调用是异步的,当某个服务在工作时,其他服务不能调用该服务,可使用消息队列来进行监听管理调用
-
MQ的核心:
- 生产者:产生数据发送消息的模块
- 交换机:用于接收生产者的消息、将消息推送到队列中、处理消息
- 队列:相当于消息缓存区
- 消费者:用于接收消息的模块
- 一个应用程序既可以是生产者,又可以是消费者;比如一个通讯工具,既可以发送信息,也可以接收信息
-
CentOS安装RabbitMQ:
vi /etc/yum.repos.d/rabbitmq_erlang.repo # 配置 centos8 Erlang的下载源
yum install erlang # 安装Erlang
# 下载rabbitmq的rpm,传输centos
yum install socat # 安装socat依赖
rpm -ivh rabbitmq-server-3.8.1-1.el8.noarch.rpm # 安装rabbitmq
rabbitmq-plugins enable rabbitmq_management # 安装web插件
vi /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.1/ebin/rabbit.app # 设置可远程访问
firewall-cmd --add-port=15672/tcp --permanent # 开放端口
firewall-cmd --add-port=5672/tcp --permanent # 开放端口
firewall-cmd --reload # 使防火墙生效
rabbitmq-server -detached # 后台启动
http://ip:15672/ # 浏览器访问rabbitmq页面
sudo rabbitmq-server # 控制台启动
详情查看:https://www.jianshu.com/p/8aece9517533
RabbitMQ启动时报错:error: node with name "rabbit" already running on "bogon"
错误原因:后台进程被占用
解决方案:
ps -ef|grep rabbitmq # 查看进程
kill <进程id> # 杀死进程
# 再次启动
启动时报错:Warning: PID file not written; -detached was passed.
错误原因:警告信息,不影响
- 创建账号:
rabbitmqctl add_user <用户名> <密码>
rabbitmqctl set_user_tags <用户名> administrator # 为当前账号设置超级管理员角色权限
rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*" # 表示admin用户在"/"这个主机中具有配置、读、写权限
# 设置格式:set_permissions [-p <vhostpath>] <user> <conf> <write> <read> "/"表示主机路径,也可自定义其他路径
rabbitmqctl list_users # 查看所有账户和对应的角色
# 此时可在window系统的浏览器中访问Linux的rabbitmq,输入创建的账号
rabbitmqctl stop_app # 关闭
rabbitmqctl reset # 重启
rabbitmqctl start_app # 启动
- window10上RabbitMQ环境配置:
1. 安装erlang,配置环境变量
i. ERLANG_HOME = 安装路径
ii. path中添加:%ERLANG_HOME%\bin
iii. cmd输入erl查看版本验证是否安装成功
2. 安装RabbitMQ
i. 进入rabbitMQ安装路径的sbin文件夹,打开cmd
ii. rabbitmq-plugins enable rabbitmq_management # 安装可视化插件
iii. 浏览器输入:http://localhost:15672/ # 访问RabbitMQ页面,用户名、密码为guest
3. RabbitMQ常用命令:
i. net start RabbitMQ # 启动
ii. net stop RabbitMQ # 停止
iii. rabbitmqctl status # 查看状态
详情查看:https://blog.csdn.net/qq_39915083/article/details/107034747
-
消息传递入门案例:
- 导入所需依赖,新建一个类作为消息生产者,定义一个队列,创建连接工厂,创建连接,生成队列,发送消息
- 运行后发现massage发送到了rabbitmq,可在rabbitmq中查看
- 新建一个类作为消息消费者,创建连接工厂,创建连接,接收rabbitmq中的消息,可指定回调方法用于处理成功和失败的情况
-
工作队列是为了避免立即执行大量任务,生产者发送大量消息到rabbitmq的队列,队列将消息缓存、排序、分发到不同的消费者
-
轮训分发消息案例:
- 抽取连接的步骤抽取到一个工具类,新建生产者类和消费者类,消费者类启动多个线程
- 当生产者发送消息时,消息被多个线程(消费者类)平均接收
-
消息应答:消费者接收消息并处理完成后,会告诉rabbitmq,这时rabbitmq会把消息删除了
-
自动应答:建议不要使用,若消费者不能即使处理消息,会导致消息丢失
-
手动应答:自定义应答方法
Channel.basicAck:用于肯定确认,RabbitMQ 已知道该消息并且成功的处理消息,可以将其丢弃了
Channel.basicNack:用于否定确认
Channel.basicReject:用于否定确认,与 Channel.basicNack 相比少一个参数Multiple
Multiple为true表示批量应答,即消费者同时处理多个消息,其中一个消息处理完成后,则告诉rabbitmq全处理完了
Multiple为false表示不批量应答,即消费者同时处理多个消息,其中一个消息处理完成后,则告诉rabbitmq处理完的那个消息
-
消息自动重新入队,消息会保存在rabbitmq中,直到被消费者处理后、应答后才会删除,当消费者出现异常或者宕机时导致处理失败,rabbitmq会将消息分发给另一个消费者处理
-
手动应答 + 自动重新入队案例:
- 新建一个类作为消息生产者
- 新建一个类作为消息消费者:让该线程睡眠1秒后打印出接收到消息,再新建一个消息消费者类:让该线程睡眠30秒后打印出接收的消息,是为了模拟处理复杂的消息;在两个消费者类中设置为手动应答
- 测试:生产者发送消息,两个消费者轮训接收消息,当第二个消费者在接收消息时突然关闭服务,这时消息没处理完,又被自动分配到第一个消费者处理
-
队列持久化:
在生产者类中设置
消息应答机制确保了rabbitmq正常的情况下消息不会丢失
队列设置持久化后,rabbitmq即使宕机或重启后,该队列中的消息依然存在于rabbitmq
若将一个未持久化的队列设置成持久化后,重启会报错,这时由于该队列已经在rabbitmq中存在,在rabbitmq页面删除该队列,再重启线程,
这时该队列在rabbitmq显示的持久化
在rabbitmq控制台可查看设置了队列持久化的消息带有一个大写的D -
消息持久化:
rabbitmq中的队列持久化了,发送的消息也需设置持久化,否则rabbitmq重启后,队列存在,队列中的消息丢失了
在生产者类中发送消息时添加:MessageProperties.PERSISTENT_TEXT_PLAIN -
不公平分发:
即处理速度快的消费者多分配消息,确保消费者不会出现空闲状态
在消费者类添加:channel.basicQos(1) # 默认为0表示轮训,1表示不公平分发 -
预取值:
即指定消费者处理多少消息
在消费者类添加:channel.basicQos(5) # 这就表示分发5条,表示该消费者类会一直堆着5条,当该消费者类处理了1条,只剩4条时,又会接收1条消息 -
参考代码
// 连接rabbitmq工具类
public class RabbitMqUtils {
//得到一个连接的 channel
public static Channel getChannel() throws Exception{
//创建一个连接工厂
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("182.92.234.71"); // rabbitmq的ip和账户密码
factory.setUsername("admin");
factory.setPassword("123");
Connection connection = factory.newConnection(); // 创建连接
Channel channel = connection.createChannel();
return channel; // 返回信道
}
}
// 生产者
public class Task02 {
private static final String TASK_QUEUE_NAME = "ack_queue"; // 创建队列
public static void main(String[] argv) throws Exception {
try (Channel channel = RabbitMqUtils.getChannel()){ // 获取连接rabbitmq
/**
* 生成一个队列
* 1.队列名称
* 2.队列持久化
* 3.该队列是否只供一个消费者进行消费 是否进行共享 true 可以多个消费者消费
* 4.是否自动删除 最后一个消费者端开连接以后 该队列是否自动删除 true 自动删除
* 5.其他参数
*/
channel.queueDeclare(TASK_QUEUE_NAME, false, false, false, null);
Scanner sc = new Scanner(System.in);
System.out.println("请输入信息");
while (sc.hasNext()) {
String message = sc.nextLine(); // 从控制台获取消息
/**
* 发送一个消息
* 1.发送到那个交换机
* 2.路由的 key 是哪个
* 3.消息持久化
* 4.发送消息的消息体
*/
channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes("UTF-8")); // 发送消息到rabbitmq
System.out.println("生产者发出消息" + message);
}
}
}
}
// 消费者
public class Work03 {
private static final String ACK_QUEUE_NAME="ack_queue";
public static void main(String[] args) throws Exception{
Channel channel = RabbitMqUtils.getChannel();
System.out.println("C1 等待接收消息处理时间较短");
//消息消费的时候如何处理消息
DeliverCallback deliverCallback=(consumerTag,delivery)->{
String message= new String(delivery.getBody());
SleepUtils.sleep(1);
System.out.println("接收到消息:"+message);
/**
* 消息应答的具体处理:
* 1.消息标记 tag
* 2.是否批量应答未应答消息
*/
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
// 不公平分发
//channel.basicQos(1)
// 预取值
channel.basicQos(5)
//采用手动应答
boolean autoAck=false;
/**
* 1.队列名称
* 2.是否手动应答
* 3.接收成功的处理
* 4.接收消息失败的回调
*/
channel.basicConsume(ACK_QUEUE_NAME,autoAck,deliverCallback,(consumerTag)->{
System.out.println(consumerTag+"消费者取消消费接口回调逻辑");
});
}
}
// 模拟处理时间的睡眠工具类
public class SleepUtils {
public static void sleep(int second){
try {
Thread.sleep(1000*second);
} catch (InterruptedException _ignored){
Thread.currentThread().interrupt();
}
}
}