基本命令行操作
关于服务的操作:
-
服务启动:
rabbitmqctl start_app
/rabbitmq-server start &
-
服务停止:
rabbitmqctl stop_app
/rabbitmq-server stop
-
服务重启:
service rabbitmq-server restart
-
节点状态:
rabbitmqctl status
关于用户的操作:
- 添加用户:
rabbitmqctl add_user username password
- 列出所有用户:
rabbitmqctl list_users
- 删除用户:
rabbitmqctl delete_user username
- 清除用户权限:
rabbitmqctl clear_permissions -p vhostpath username
- 列出用户权限:
rabbitmqctl list_user_permissions username
- 修改密码:
rabbitmqctl change_password username newpassword
- 设置用户权限:
rabbitmqctl set_permissions -p vhostpath username ".*" ".*" ".*"
关于虚拟主机的操作:
- 创建虚拟主机:
rabbitmqctl add_vhost vhostpath
- 列出所有虚拟主机:
rabbitmqctl list_vhost
- 列出虚拟主机上所有权限:
rabbitmqctl list_permissions -p vhostpath
- 删除虚拟主机:
rabbitmqctl delete_vhost vhostpath
关于消息队列的操作:
- 查看所有队列信息:
rabbitmqctl list_queues
- 清除队列里的消息:
rabbitmqctl -p vhostpath purge_queue blue
高级命令
rabbitmqctl reset
:移除所有数据,要在rabbitmqctl stop_app
之后使用- 组成集群命令:
rabbitmqctl join_cluster [--ram]
(ram内存级别存储,disc磁盘) - 查看集群状态:
rabbitmqctl cluster_status
- 修改集群节点的存储形式:
rabbitmqctl change_cluster_node_type disc | ram
- 忘记(摘除)节点:
rabbitmqctl forget_cluster_node [--offline]
(offline服务不启动的情况下) - 修改节点名称:
rabbitmqctl rename_cluster_node oldnode1 newnode1 [oldnode2] [newnode2 ...]
集群配置失败,故障转移等情况下可以将启动失败的节点给移除掉。它可以在不启动的情况下对节点的摘除
入门使用
引入RabbitMQ依赖:
com.rabbitmq amqp-client 3.6.5创建一个生产者
public class Producer {
private static final String QUEUE_NAME = “test01”;
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接工厂并配置
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(“192.168.58.129”);
connectionFactory.setPort(5672);
// 设置虚拟机
connectionFactory.setVirtualHost(“/test”);
// 2. 通过连接工厂建立连接
Connection connection = connectionFactory.newConnection();
// 3. 通过connection创建Channel
Channel channel = connection.createChannel();
// 4. 通过Channel发送数据 (exchange, routingKey, props, body)
// 不指定Exchange时, 交换机默认是AMQP default, 此时就看RoutingKey
// RoutingKey要等于队列名才能被路由, 否则消息会被删除
for (int i = 0; i < 5; i++) {
String msg = “Learn For RabbitMQ-” + i;
channel.basicPublish(“”, QUEUE_NAME, null, msg.getBytes());
System.out.println("Send message : " + msg);
}
// 5.关闭连接
channel.close();
connection.close();
}
}
创建一个消费者
public class Consumer {
private static final String QUEUE_NAME = “test01”;
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 创建连接工厂并配置
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(“192.168.58.129”);
connectionFactory.setPort(5672);
// 设置虚拟机
connectionFactory.setVirtualHost(“/test”);
// 2. 通过连接工厂建立连接
Connection connection = connectionFactory.newConnection();
// 3. 通过connection创建Channel
Channel channel = connection.createChannel();
// 4. 声明队列 (queue, durable, exclusive, autoDelete, args)
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
// 5. 创建消费者
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
/**
- 获取消息 (监听到有消息时调用)
- @param consumerTag 消费者标签, 在监听队列时可以设置autoAck为false,即手动确认(避免消息的丢失), 消息唯一性处理
- @param envelope 信封
- @param properties 消息的属性
- @param body 消息的内容
- @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body, “utf-8”);
System.out.println("Received message : " + msg);
}
};
// 6. 设置Channel, 监听队列(String queue, boolean autoAck,Consumer callback)
channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
}
}
参数:
queue
:队列名称durable
:持久化,true 即使服务重启也不会被删除exclusive
:独占,true 队列只能使用一个连接,连接断开队列删除autoDelete
:自动删除,true 脱离了Exchange(连接断开),即队列没有Exchange关联时,自动删除arguments
:扩展参数autoAck
:是否自动签收(回执)
不指定Exchange时,交换机默认是AMQP default,此时就看RoutingKey
,RoutingKey要等于队列名才能被路由,否则消息会被删除
交换机属性
Name
:交换机名称
Type
:交换机类型—— direct、topic、fanout、header
Durability
:是否需要持久化,true为持久化
Auto Delete
:当最后一个绑定到Exchange上的队列删除后,即Exchange上没有队列绑定,自动删除该Exhcange
Internal
:当前Exchange是否用于RabbitMQ内部使用,大多数使用默认False
Arguments
:扩展参数,用于扩展AMQP协议定制化使用
Direct Exchange:
// Consumer
// 声明交换机:
// (String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object) arguments)
channel.exchangeDeclare(“exchangeName”, BuiltinExchangeType.DIRECT, true, false, false, null);
// 声明队列 (String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object) args)
channel.queueDeclare(“queueName”, true, false, false, null);
// 建立绑定关系:
channel.queueBind(“queueName”, “exchangeName”, “routingKey”);
// ===================================================================
// Producer
// 发送消息 (String exchange, String routingKey, BasicProperties props, Bytes[] body)
channel.basicPublish(“exchangeName”, “routingKey”, null, “msg”.getBytes());
Topic Exchange:
// Consumer
// 声明交换机:
// (String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object) arguments)
channel.exchangeDeclare(“exchangeName”, BuiltinExchangeType.TOPIC, true, false, false, null);
// 声明队列 (String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object) args)
channel.queueDeclare(“queueName”, true, false, false, null);
// 建立绑定关系:
channel.queueBind(“queueName”, “exchangeName”, “routingKey.#”);
// ===================================================================
// Producer
// 发送消息 (String exchange, String routingKey, BasicProperties props, Bytes[] body)
channel.basicPublish(“exchangeName”, “routingKey.hi”, null, “msg”.getBytes());
channel.basicPublish(“exchangeName”, “routingKey.save”, null, “msg”.getBytes());
channel.basicPublish(“exchangeName”, “routingKey.save.hi”, null, “msg”.getBytes());
因为使用了模糊匹配的"#
",可以匹配到发送的三条消息。因此可以收到三条消息
Fanout Exchange:
// Consumer
// 声明交换机:
// (String exchange, String type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object) arguments)
channel.exchangeDeclare(“exchangeName”, BuiltinExchangeType.FANOUT, true, false, false, null);
// 声明队列 (String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object) args)
channel.queueDeclare(“queueName”, true, false, false, null);
// 建立绑定关系:
//(不设置routingKey, 这里为空)
channel.queueBind(“queueName”, “exchangeName”, “”);
// ===================================================================
// Producer
// 发送消息 (String exchange, String routingKey, BasicProperties props, Bytes[] body)
// 同样routingKey为空 (也可以是任意字符串, 因为fanout并不依据routingKey)
channel.basicPublish(“exchangeName”, “”, null, “msg”.getBytes());
高级特性
可靠性投递
什么是生产端的可靠性投递
- 保障消息的成功发出
- 保障MQ节点成功接收
- 发送端收到MQ节点(Broker)确认应答(已收到)
- 完善消息进行补偿机制
可靠性投递的方案一
消息落库(持久化至数据库),对消息状态进行打标,如若消息未响应,进行轮询操作
1.把业务消息落库,再生成一条消息落库到消息DB用来记录(譬如消息刚创建,正在发送中 status: 0)
缺点:对数据库进行两次持久化
2.生产端发送消息。
3.Broker端收到后,应答至生产端。Confirm Listener
异步监听Broker的应答。
4.应答表明消息投递成功后,去消息DB中抓取到指定的消息记录,更新状态,如status: 1
5.如在3中出现网络不稳定等情况,导致Listener未收到消息成功确认的应答。
那么消息数据库中的status就还是0,而Broker可能是接收到消息的状态。
因此设定一个规则(定时任务),例如消息在落库5分钟后(超时)还是0的状态,就把该条记录抽取出来。
6.重新投递
7.限制一个重试的次数,譬如3次,如果大于3次,即为投递失败,更新status的值
用人工补偿机制去查询消息失败的原因
高并发场景消息的延迟投递,做二次确认,回调检查
Upstream service:生产端
Downstream service:消费端
1:业务消息落库后,发送消息至Broker。
2:紧接着发送第二条延迟(设置延迟时间)检查的消息。
3:消费端监听指定的队列接收到消息进行处理
4:处理完后,生成一条响应消息发送到Broker。
5:由Callback服务去监听该响应消息,收到该响应消息后持久化至消息DB(记录成功状态)。
6:到了延迟时间,延迟发送的消息也被Callback服务的监听器监听到后,去检查消息DB。如果未查询到成功的状态,Callback服务需要做补偿,发起RPC通讯,让生产端重新发送。生产端通过接收到的命令中所带的id去数据库查询该业务消息,再重新发送,即跳转到1。
该方案减少了对数据库的存储,保证了性能
消费端幂等性
避免消息的重复消费
消费端实现幂等性,接收到多条相同的消息,但不会重复消费,即收到多条一样的消息。
方案:
1.唯一ID + 指纹码机制
- 唯一ID + 指纹码(业务规则、时间戳等拼接)机制,利用数据库主键去重
SELECT COUNT(1) FROM T_ORDER WHERE ID = 唯一ID + 指纹码
未查询到就insert
,如有说明已处理过该消息,返回失败- 优点:实现简单
- 缺点:高并发下有数据库写入的性能瓶颈
- 解决方案:根据ID进行分库分表、算法路由
2.利用Redis的原子性
需要考虑的问题:
- 是否要落库数据库,如落库,数据库和缓存如何做到数据的一致性
- 不落库,数据存储在缓存中,如何设置定时同步的策略(可靠性保障)
Confirm确认消息
生产者投递消息后,如果Broker收到消息,则会给生产者一个应答。
生产者进行接收应答,用来确认这条消息是否正常发送到Broker是消息可靠性投递的核心保障。
确认机制的流程图
发送消息与监听应答的消息是异步操作。
确认消息的实现
- 在channel开启确认模式:
channel.confirmSelect();
- 在channel添加监听:
channel.addConfirmListener(ConfirmListener listener);
返回监听成功和失败的结果,对具体结果进行相应的处理(重新发送、记录日志等待后续处理等)
具体代码:
public class ConfirmProducer {
private static final String EXCHANGE_NAME = “confirm_exchange”;
private static final String ROUTING_KEY = “confirm.key”;
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(“192.168.58.129”);
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost(“/test”);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
// 指定消息的投递模式: 确认模式
channel.confirmSelect();
// 发送消息
String msg = “Send message of confirm demo”;
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, msg.getBytes());
// 添加确认监听
channel.addConfirmListener(new ConfirmListener() {
// 成功
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
System.out.println(“========= Ack ========”);
}
// 失败
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
System.out.println(“========= Nack ========”);
}
});
}
}
Return消息机制
用于处理一些不可路由的消息。
基础API
有一个关键配置项:Mandatory
:true,则监听器会接收到路由不可达的消息,然后进行处理;false,Broker会自动删除该消息
默认为false,当我们使用Return 消息机制的时候,我们需要将它设置为true
消息的生产者通过制定Exchange和RoutingKey
,把消息投递到某一个队列中,消费者监听队列,进行消费。
但在一些情况下,发送消息时,Exchange不存在或RoutingKey
路由不到,Return Listener就会监听这种不可达的消息,然后进行处理。
Return Listener代码
public class ReturnProducer {
private static final String EXCHANGE_NAME = “return_exchange”;
private static final String ROUTING_KEY = “return.key”;
private static final String ROUTING_KEY_ERROR = “wrong.key”;
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(“192.168.58.129”);
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost(“/test”);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
// 消息
String msg = “Send message of return demo”;
// 添加并设置Return监听器
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.err.println(“============ handleReturn ============”);
System.err.println("replyCode —— " + replyCode);
System.err.println("replyText —— " + replyText);
System.err.println("exchange —— " + exchange);
System.err.println("routingKey —— " + routingKey);
System.err.println("properties —— " + properties);
System.err.println("body —— " + new String(body));
}
});
// 设置Mandatory为true, 可以进行后续处理, 不会删除消息。
// channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, true,null, msg.getBytes());
// 发送消息
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY_ERROR, true, null, msg.getBytes());
}
}
消费端自定义监听
自定义监听的原因:
- 我们一般就是在代码中编写while循环,进行
consumer.nextDelivery
方法进行获取下一条消息,然后进行消费处理! - 但是我们使用自定义的Consumer更加的方便,解耦性更加的强,也是在实际工作中最常用的使用方式!
- 非常简单,消费者只需要继承DefaultConsumer类,然后重写handleDelivery方法即可;
继承DefaultConsumer的此类被写出后,需要进行绑定。(在交换机绑定时绑定自定义的Consumer);
public class ReturnConsumer {
private static final String EXCHANGE_NAME = “return_exchange”;
private static final String ROUTING_KEY = “return.#”;
private static final String QUEUE_NAME = “return_queue”;
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(“192.168.58.129”);
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost(“/test”);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
// 绑定交换机与队列, 指定路由键
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC, true);
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("Receive Message —— " + new String(body));
}
};
channel.basicConsume(QUEUE_NAME, true, defaultConsumer);
}
}
消费端限流
当巨量消息瞬间全部推送时,单个客户端无法同时处理这些数据,服务器容易故障。因此要进行消费端限流
RabbitMQ提供了一种Qos(服务质量保证)功能,即在非自动确认前提下,如果一定数目的消息未被确认前(通过consume或者channel设置Qos值),不进行消费新消息。
void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException;
prefetchSize:消息限制大小,一般为0,不做限制。
prefetchCount:一次处理消息的个数,一般设置为1
global:一般为false。true,在channel级别做限制;false,在consumer级别做限制
public class QosConsumer {
private static final String EXCHANGE_NAME = “qos_exchange”;
private static final String ROUTING_KEY = “qos.#”;
private static final String QUEUE_NAME = “qos_queue”;
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost(“192.168.58.129”);
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost(“/test”);
connectionFactory.setUsername(“orcas”);
connectionFactory.setPassword(“1224”);
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
// 绑定交换机与队列, 指定路由键
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC, true);
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("Receive Message —— " + new String(body));
// 手动ack签收
channel.basicAck(envelope.getDeliveryTag(), false); // 不批量签收
}
};
/**
- prefetchSize: 0 不限制消息大小
- prefetchCount: 一次处理消息的个数, ack后继续推送
- global: false 应用在consumer级别
*/
channel.basicQos(0, 1, false);
//限流:autoAck需设置为false, 关闭自动签收
channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
}
}
限流需要设置channel.basicQos(0, 1, false);
关闭autoAck,且需要手动签收。
在重写的handleDelivery方法中,如果没有进行手动签收channel.basicAck()
,那么消费端在接收消息时,因为prefetchCount设置为1,只会接收1条消息,剩下的消息的等待中,并不会被推送,直到手动ack后。
队列
消费端ACK与重回队列机制
消费端的手工ACK和NACK:
消费端进行消费时,可能由于业务异常,会调用NACK拒绝确认,而到了一定次数,就直接ACK,将异常消息进行日志的记录,然后进行补偿。
由于服务器宕机等严重问题,消费端没消费成功,重发消息后,需要手工ACK保障消费端消费成功。
消费端的重回队列:
将没有处理成功的消息重新回递给Broker。
一般在实际应用中,会关闭重回队列。
TTL队列
TTL:Time To Live,生存时间。
可以指定消息的过期时间。
可以指定队列的过期时间,从消息入队列开始计算,超过了队列的超时时间设置,那么消息会自动清除
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.deliveryMode(2)
.expiration(“10000”)
.build();
死信队列
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
一次偶然,从朋友那里得到一份“java高分面试指南”,里面涵盖了25个分类的面试题以及详细的解析:JavaOOP、Java集合/泛型、Java中的IO与NIO、Java反射、Java序列化、Java注解、多线程&并发、JVM、Mysql、Redis、Memcached、MongoDB、Spring、Spring Boot、Spring Cloud、RabbitMQ、Dubbo 、MyBatis 、ZooKeeper 、数据结构、算法、Elasticsearch 、Kafka 、微服务、Linux。
这不,马上就要到招聘季了,很多朋友又开始准备“金三银四”的春招啦,那我想这份“java高分面试指南”应该起到不小的作用,所以今天想给大家分享一下。
请注意:关于这份“java高分面试指南”,每一个方向专题(25个)的题目这里几乎都会列举,在不看答案的情况下,大家可以自行测试一下水平 且由于篇幅原因,这边无法展示所有完整的答案解析
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
style=“zoom: 33%;” />
最后
一次偶然,从朋友那里得到一份“java高分面试指南”,里面涵盖了25个分类的面试题以及详细的解析:JavaOOP、Java集合/泛型、Java中的IO与NIO、Java反射、Java序列化、Java注解、多线程&并发、JVM、Mysql、Redis、Memcached、MongoDB、Spring、Spring Boot、Spring Cloud、RabbitMQ、Dubbo 、MyBatis 、ZooKeeper 、数据结构、算法、Elasticsearch 、Kafka 、微服务、Linux。
这不,马上就要到招聘季了,很多朋友又开始准备“金三银四”的春招啦,那我想这份“java高分面试指南”应该起到不小的作用,所以今天想给大家分享一下。
[外链图片转存中…(img-4XCmiAm0-1713511660036)]
请注意:关于这份“java高分面试指南”,每一个方向专题(25个)的题目这里几乎都会列举,在不看答案的情况下,大家可以自行测试一下水平 且由于篇幅原因,这边无法展示所有完整的答案解析
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!