尚硅谷学习笔记-8 RabbitMQ高级特性
8.1 消息的可靠投递
在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。
· confirm 确认模式
· return 退回模式
rabbitmq 整个消息投递的路径为:
producer—>rabbitmq broker—>exchange—>queue—>consumer
l消息从 producer 到 exchange 则会返回一个 confirmCallback 。
l消息从 exchange–>queue 投递失败则会返回一个 returnCallback 。
我们将利用这两个 callback 控制消息的可靠性投递
8.1.1 提供者代码实现(这部分代码是在生产者端实现的)
在rabbit-mq模块中创建MqAckConfig
@Component
public class MqAckConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
@Resource
RabbitTemplate rabbitTemplate;
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
@Override
public void confirm(@Nullable CorrelationData correlationData, boolean ack, @Nullable String error) {
System.out.println("消息的发送情况");
}
@Override
public void returnedMessage(Message message, int code, String codeMessage, String exchange, String routing) {
System.out.println("消息的投递情况");
}
}
在rabbit-test测试服务中添加配置
application.properties
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.publisher-confirm-type=simple
8.1.2 消息的可靠投递小结
· 设置 ConnectionFactory
的publisher-confirms="true"
开启 确认模式。
· 使用 rabbitTemplate.setConfirmCallback
设置回调函数。当消息发送到 exchange
后回调 confirm
方法。在方法中判断 ack
,如果为true
,则发送成功,如果为false
,则发送失败,需要处理。
· 设置 ConnectionFactory
的 publisher-returns="true"
开启 退回模式。
· 使用 rabbitTemplate.setReturnCallback
设置退回函数,当消息从exchange
路由到 queue
失败后,如果设置了 rabbitTemplate.setMandatory(true)
参数,则会将消息退回给 producer
并执行回调函数returnedMessage
8.1.3 Consumer Ack(消息事务)
ack指Acknowledge,确认。 表示消费端收到消息后的确认方式。
有二种确认方式:
· 自动确认:acknowledge=“none” 默认
· 手动确认:acknowledge=“manual”
其中自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message 从 RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失。
如果设置了手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息。
8.1.3.1 自动确认
application.properties
spring.rabbitmq.listener.simple.acknowledge-mode=auto
spring.rabbitmq.listener.direct.acknowledge-mode=auto
8.1.3.2 手动确认
application.properties
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.direct.acknowledge-mode=manual
相关代码
public void a(Channel channel , Message message, String messageStr){
System.out.println(messageStr);
String s = new String(message.getBody());
// 消费完成后,确认commit
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
Consumer Ack 小结
commit提交确认:
如果在消费端没有出现异常,则调用channel.basicAck(deliveryTag,true);方法确认签收消息,如果出现异常
Rollback回滚消息:
则在catch中调用 basicNack,拒绝消息,让MQ重新发送消息,
8.2 消费端限流
application.properties
spring.rabbitmq.listener.simple.prefetch=1
spring.rabbitmq.listener.direct.prefetch=1
小结:
prefetch = 1,表示消费端每次从mq拉去一条消息来消费,直到手动确认消费完毕后,才会继续拉取下一条消息
8.3 TTL
TTL 全称 Time To Live(存活时间/过期时间)。
当消息到达存活时间后,还没有被消费,会被自动清除。
RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。
8.3.1 控制后台演示消息过期
① 修改管理后台界面,增加队列
参数:表示过期时间,单位毫秒 ,10000表示10秒
② 增加交换机
③ 绑定队列
④ 发送消息
Delivery mode:2-Persistent表示需要进行持久化
⑤ 查看消息,可以看到消息,但十秒之后,消息自动消失,因为我们设置了十秒消息过期
8.3.2 代码实现
/**
* TTL:过期时间
* 1. 队列统一过期
* 2. 消息单独过期
* 如果设置了消息的过期时间,也设置了队列的过期时间,它以时间短的为准。
*/
@Test
public void testMessageTtl() {
// 消息后处理对象,设置一些消息的参数信息
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//1.设置message的信息
// 第二个方法:消息的过期时间 ,5秒之后过期
message.getMessageProperties().setExpiration("5000");
//2.返回该消息
return message;
}
};
//消息单独过期
rabbitTemplate.convertAndSend("test_exchange_ttl","ttl.hehe","message ttl....",messagePostProcessor);
}
运行程序,查看后台管理系统
8.4 死信队列
死信队列,英文缩写:DLX 。DeadLetter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。
什么是死信队列
先从概念解释上搞清楚这个定义,死信,顾名思义就是无法被消费的消息,字面意思可以这样理解,一般来说,producer将消息投递到broker或者直接到queue里了,consumer从queue取出消息进行消费,但某些时候由于特定的原因导致queue中的某些消息无法被消费,这样的消息如果没有后续的处理,就变成了死信,有死信,自然就有了死信队列;
消息成为死信的三种情况:
-
队列消息数量到达限制;比如队列最大只能存储10条消息,而发了11条消息,根据先进先出,最先发的消息会进入死信队列。
-
消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
-
原队列存在消息过期设置,消息到达超时时间未被消费;
死信的处理方式
死信的产生既然不可避免,那么就需要从实际的业务角度和场景出发,对这些死信进行后续的处理,常见的处理方式大致有下面几种,
① 丢弃,如果不是很重要,可以选择丢弃
② 记录死信入库,然后做后续的业务分析或处理
③ 通过死信队列,由负责监听死信的应用程序进行处理
综合来看,更常用的做法是第三种,即通过死信队列,将产生的死信通过程序的配置路由到指定的死信队列,然后应用监听死信队列,对接收到的死信做后续的处理,
队列绑定死信交换机:
给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key
8.4.1 代码实现
新建配置文件(类),DeadLetterMqConfig
@Configuration
public class DeadLetterMqConfig {
public static final String exchange_dead = "exchange.dead";
public static final String routing_dead_1 = "routing.dead.1";
public static final String routing_dead_2 = "routing.dead.2";
public static final String queue_dead_1 = "queue.dead.1";
public static final String queue_dead_2 = "queue.dead.2";
/**
* 其他队列可以在RabbitListener上面做绑定
*
* @return
*/
@Bean
public DirectExchange exchange() {
return new DirectExchange(exchange_dead, true, false, null);
}
@Bean
public Queue queue1() {
Map<String, Object> arguments = new HashMap<>();
arguments.put("x-dead-letter-exchange", exchange_dead);
arguments.put("x-dead-letter-routing-key", routing_dead_2);
return new Queue(queue_dead_1, true, false, false, arguments);
}
@Bean
public Binding binding() {
return BindingBuilder.bind(queue1()).to(exchange()).with(routing_dead_1);
}
@Bean
public Queue queue2() {
return new Queue(queue_dead_2, true, false, false, null);
}
@Bean
public Binding deadBinding() {
return BindingBuilder.bind(queue2()).to(exchange()).with(routing_dead_2);
}
}
发送死信息队列,并设置ttl时间,MqServiceImpl类中增加方法
@Override
public void sendDeadMsg(String exchange_dead, String routing_dead_1, String test) {
System.out.println("发送死信消息");
rabbitTemplate.convertAndSend(exchange_dead,routing_dead_1,test,p->{
// 死信消息的设置,设置ttl(time to live 存活时间)
p.getMessageProperties().setExpiration("3000");//3秒的ttl
return p;
});
}
调用死信方法
@Override
public void sendDeadMsg() {
mqService.sendDeadMsg(DeadLetterMqConfig.exchange_dead,"1111","test");
}
死信队列小结
-
死信交换机和死信队列和普通的没有区别
-
当消息成为死信后,如果该队列绑定了死信交换机,则消息会被死信交换机重新路由到死信队列
-
消息成为死信的三种情况:
- 队列消息长度(数量)到达限制;
- 消费者拒接消费消息,并且不重回队列;
- 原队列存在消息过期设置,消息到达超时时间未被消费;
8.5 延迟队列
延迟队列存储的对象肯定是对应的延时消息,所谓”延时消息”是指当消息被发送以后,并不想让消费者立即拿到消息,而是等待指定时间后,消费者才拿到这个消息进行消费。
场景:在订单系统中,一个用户下单之后通常有30分钟的时间进行支付,如果30分钟之内没有支付成功,那么这个订单将进行取消处理。这时就可以使用延时队列将订单信息发送到延时队列。
需求:
-
下单后,30分钟未支付,取消订单,回滚库存。
-
新用户注册成功30分钟后,发送短信问候。
实现方式:
- 延迟队列
很可惜,在RabbitMQ中并未提供延迟队列功能。
但是可以使用:TTL+死信队列 组合实现延迟队列的效果。
8.5.1 代码实现
配置文件
@Configuration
public class DelayedMqConfig {
public static final String exchange_delay = "exchange.delay";
public static final String routing_delay = "routing.delay";
public static final String queue_delay_1 = "queue.delay.1";
/**
* 队列不要在RabbitListener上面做绑定,否则不会成功,如队列2,必须在此绑定
*
* @return
*/
@Bean
public Queue delayQeue1() {
// 第一个参数是创建的queue的名字,第二个参数是是否支持持久化
return new Queue(queue_delay_1, true);
}
@Bean
public CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-delayed-type", "direct");
return new CustomExchange(exchange_delay, "x-delayed-message", true, false, args);
}
@Bean
public Binding delayBbinding1() {
return BindingBuilder.bind(delayQeue1()).to(delayExchange()).with(routing_delay).noargs();
}
}
8.5.1.1 生产者
生产者
@Override
public void sendDelayMessage(String exchange, String routingKey, String message, int time, TimeUnit seconds) {
rabbitTemplate.convertAndSend(exchange,routingKey,message,processer->{
// 设置消息的延迟时间
processer.getMessageProperties().setDelay(1*1000*time);// ttl时间,默认毫秒
return processer;
});
}
8.5.1.2 消费者
消费者
@SneakyThrows
@RabbitListener(queues = "queue.delay.1")
public void d(Channel channel, Message message, String c) throws IOException {
System.out.println("延迟队列的监听");
long deliveryTag = message.getMessageProperties().getDeliveryTag();
channel.basicAck(deliveryTag, false);
}
9 消息百分百成功投递
谈到消息的可靠性投递,无法避免的,在实际的工作中会经常碰到,比如一些核心业务需要保障消息不丢失,接下来我们看一个可靠性投递的流程图,说明可靠性投递的概念:
Step 1: 首先把消息信息(业务数据)存储到数据库中,紧接着,我们再把这个消息记录也存储到一张消息记录表里(或者另外一个同源数据库的消息记录表)
Step 2:发送消息到MQ Broker节点(采用confirm方式发送,会有异步的返回结果)
Step 3、4:生产者端接受MQ Broker节点返回的Confirm确认消息结果,然后进行更新消息记录表里的消息状态。比如默认Status = 0 当收到消息确认成功后,更新为1即可!
Step 5:但是在消息确认这个过程中可能由于网络闪断、MQ Broker端异常等原因导致 回送消息失败或者异常。这个时候就需要发送方(生产者)对消息进行可靠性投递了,保障消息不丢失,100%的投递成功!(有一种极限情况是闪断,Broker返回的成功确认消息,但是生产端由于网络闪断没收到,这个时候重新投递可能会造成消息重复,需要消费端去做幂等处理)所以我们需要有一个定时任务,(比如每5分钟拉取一下处于中间状态的消息,当然这个消息可以设置一个超时时间,比如超过1分钟 Status = 0 ,也就说明了1分钟这个时间窗口内,我们的消息没有被确认,那么会被定时任务拉取出来)
Step 6:接下来我们把中间状态的消息进行重新投递 retry send,继续发送消息到MQ ,当然也可能有多种原因导致发送失败
Step 7:我们可以采用设置最大努力尝试次数,比如投递了3次,还是失败,那么我们可以将最终状态设置为Status = 2 ,最后 交由人工解决处理此类问题(或者把消息转储到失败表中)。
9.1 数据库文件
-- ----------------------------
-- Table structure for broker_message_log
-- ----------------------------
DROP TABLE IF EXISTS `broker_message_log`;
CREATE TABLE `broker_message_log` (
`message_id` varchar(255) NOT NULL COMMENT '消息唯一ID',
`message` varchar(4000) NOT NULL COMMENT '消息内容',
`try_count` int(4) DEFAULT '0' COMMENT '重试次数',
`status` varchar(10) DEFAULT '' COMMENT '消息投递状态 0投递中,1投递成功,2投递失败',
`next_retry` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP COMMENT '下一次重试时间',
`create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
`update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for t_order
-- ----------------------------
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`message_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2018091102 DEFAULT CHARSET=utf8;
10 消息幂等性保障
幂等性指一次和多次请求某一个资源,对于资源本身应该具有同样的结果。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
在MQ中指,消费多条相同的消息,得到与消费该消息一次相同的结果。
幂等性
概念
用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常, 此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额发现多扣钱了,流水记录也变成了两条。在以前的单应用系统中,我们只需要把数据操作放入事务中即可,发生错误立即回滚,但是再响应客户端的时候也有可能出现网络中断或者异常等等
消息重复消费
消费者在消费 MQ 中的消息时,MQ 已把消息发送给消费者,消费者在给MQ 返回 ack 时网络中断, 故 MQ 未收到确认信息,该条消息会重新发给其他的消费者,或者在网络重连后再次发送给该消费者,但实际上该消费者已成功消费了该条消息,造成消费者消费了重复的消息。
解决思路
MQ 消费者的幂等性的解决一般使用全局 ID 或者写个唯一标识比如时间戳 或者 UUID 或者订单消费者消费 MQ 中的消息也可利用 MQ 的该 id 来判断,或者可按自己的规则生成一个全局唯一 id,每次消费消息时用该 id 先判断该消息是否已消费过。
消费端的幂等性保障
在海量订单生成的业务高峰期,生产端有可能就会重复发生了消息,这时候消费端就要实现幂等性, 这就意味着我们的消息永远不会被消费多次,即使我们收到了一样的消息。业界主流的幂等性有两种操作:a. 唯一 ID+指纹码机制,利用数据库主键去重, b.利用 redis 的原子性去实现
唯一ID+指纹码机制
指纹码:我们的一些规则或者时间戳加别的服务给到的唯一信息码,它并不一定是我们系统生成的,基本都是由我们的业务规则拼接而来,但是一定要保证唯一性,然后就利用查询语句进行判断这个 id 是否存在数据库中,优势就是实现简单就一个拼接,然后查询判断是否重复;劣势就是在高并发时,如果是单个数据库就会有写入性能瓶颈当然也可以采用分库分表提升性能,但也不是我们最推荐的方式。
Redis 原子性
利用 redis 执行 setnx 命令,天然具有幂等性。从而实现不重复消费
优先级队列
使用场景
在我们系统中有一个订单催付的场景,我们的客户在天猫下的订单,淘宝会及时将订单推送给我们,如果在用户设定的时间内未付款那么就会给用户推送一条短信提醒,很简单的一个功能对吧,但是,tmall 商家对我们来说,肯定是要分大客户和小客户的对吧,比如像苹果,小米这样大商家一年起码能给我们创造很大的利润,所以理应当然,他们的订单必须得到优先处理,而曾经我们的后端系统是使用 redis 来存
放的定时轮询,大家都知道 redis 只能用 List 做一个简简单单的消息队列,并不能实现一个优先级的场景,
所以订单量大了后采用 RabbitMQ 进行改造和优化,如果发现是大客户的订单给一个相对比较高的优先级, 否则就是默认优先级。
消息幂等性保障 乐观锁机制
生产者发送消息:
id=1,money=500,version=1
消费者接收消息
id=1,money=500,version=1
id=1,money=500,version=1
消费者需要保证幂等性:第一次执行SQL语句
第一次执行:version=1
update account set money = money - 500 , version = version + 1
where id = 1 and version = 1
消费者需要保证幂等性:第二次执行SQL语句
第二次执行:version=2
update account set money = money - 500 , version = version + 1
where id = 1 and version = 1
11 RabbitMQ集群搭建
摘要:实际生产应用中都会采用消息队列的集群方案,如果选择RabbitMQ那么有必要了解下它的集群方案原理
一般来说,如果只是为了学习RabbitMQ或者验证业务工程的正确性那么在本地环境或者测试环境上使用其单实例部署就可以了,但是出于MQ中间件本身的可靠性、并发性、吞吐量和消息堆积能力等问题的考虑,在生产环境上一般都会考虑使用RabbitMQ的集群方案。
11.1 集群方案的原理
RabbitMQ这款消息队列中间件产品本身是基于Erlang编写,Erlang语言天生具备分布式特性(通过同步Erlang集群各节点的magic cookie来实现)。因此,RabbitMQ天然支持Clustering。这使得RabbitMQ本身不需要像ActiveMQ、Kafka那样通过ZooKeeper分别来实现HA方案和保存集群的元数据。集群是保证可靠性的一种方式,同时可以通过水平扩展以达到增加消息吞吐量能力的目的。
11.2 单机多实例部署
由于某些因素的限制,有时候你不得不在一台机器上去搭建一个rabbitmq集群,这个有点类似zookeeper的单机版。真实生成环境还是要配成多机集群的。有关怎么配置多机集群的可以参考其他的资料,这里主要论述如何在单机中配置多个rabbitmq实例。
主要参考官方文档:https://www.rabbitmq.com/clustering.html
首先确保RabbitMQ运行没有问题
[root@atguigu ~]# systemctl start rabbitmq-server.service
[root@atguigu ~]# systemctl status rabbitmq-server.service
停止rabbitmq服务
[root@atguigu ~]# systemctl stop rabbitmq-server.service
启动三个节点做集群演示:
由于web管理插件端口占用,所以还要指定其web插件占用的端口号。
[root@atguigu ~]# RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit1 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15672}]" rabbitmq-server -detached
[root@atguigu ~]# RABBITMQ_NODE_PORT=5673 RABBITMQ_NODENAME=rabbit2 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15673}]" rabbitmq-server -detached
[root@atguigu ~]# RABBITMQ_NODE_PORT=5674 RABBITMQ_NODENAME=rabbit3 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15674}]" rabbitmq-server -detached
启动三个节点后,分别访问三个节点,后台管理页面,看看是否OK.
停止服务命令:
rabbitmqctl -n rabbit1 stop
rabbitmqctl -n rabbit2 stop
rabbitmqctl -n rabbit3 stop
rabbit1操作作为主节点:
[root@atguigu ~]# rabbitmqctl -n rabbit1 stop_app
Stopping node rabbit1@localhost ...
[root@atguigu ~]# rabbitmqctl -n rabbit1 reset
Resetting node rabbit1@localhost ...
[root@atguigu ~]# rabbitmqctl -n rabbit1 start_app
Starting node rabbit1@localhost ...
rabbit2操作为从节点:
[root@atguigu ~]# rabbitmqctl -n rabbit2 stop_app
Stopping node rabbit2@localhost ...
[root@atguigu ~]# rabbitmqctl -n rabbit2 reset
Resetting node rabbit2@localhost ...
[root@atguigu ~]# rabbitmqctl -n rabbit2 join_cluster rabbit1
Clustering node rabbit2@localhost with rabbit1 ...
[root@atguigu ~]# rabbitmqctl -n rabbit2 start_app
Starting node rabbit2@localhost ...
rabbit3操作为从节点:
[root@atguigu ~]# rabbitmqctl -n rabbit3 stop_app
Stopping node rabbit3@localhost ...
[root@atguigu ~]# rabbitmqctl -n rabbit3 reset
Resetting node rabbit3@localhost ...
[root@atguigu ~]# rabbitmqctl -n rabbit3 join_cluster rabbit1
Clustering node rabbit3@localhost with rabbit1@super ...
[root@atguigu ~]# rabbitmqctl -n rabbit3 start_app
Starting node rabbit3@localhost ...
查看集群状态:
[root@atguigu ~]# rabbitmqctl -n rabbit1 cluster_status
Cluster status of node rabbit1@localhost ...
Basics
Cluster name: rabbit1@localhost
Disk Nodes
rabbit1@localhost
rabbit2@localhost
rabbit3@localhost
Running Nodes
rabbit1@localhost
rabbit2@localhost
rabbit3@localhost
Versions
rabbit1@localhost: RabbitMQ 3.8.1 on Erlang 21.3.8.9
rabbit2@localhost: RabbitMQ 3.8.1 on Erlang 21.3.8.9
rabbit3@localhost: RabbitMQ 3.8.1 on Erlang 21.3.8.9
Alarms
(none)
Network Partitions
(none)
Listeners
Node: rabbit1@localhost, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit1@localhost, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
Node: rabbit1@localhost, interface: [::], port: 15672, protocol: http, purpose: HTTP API
Node: rabbit2@localhost, interface: [::], port: 25673, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit2@localhost, interface: [::], port: 5673, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
Node: rabbit2@localhost, interface: [::], port: 15673, protocol: http, purpose: HTTP API
Node: rabbit3@localhost, interface: [::], port: 25674, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit3@localhost, interface: [::], port: 5674, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
Node: rabbit3@localhost, interface: [::], port: 15674, protocol: http, purpose: HTTP API
Feature flags
Flag: drop_unroutable_metric, state: enabled
Flag: empty_basic_get_metric, state: enabled
Flag: implicit_default_bindings, state: enabled
Flag: quorum_queue, state: enabled
Flag: virtual_host_metadata, state: enabled
web监控:
rabbitmqctl -n rabbit1 add_user admin admin
rabbitmqctl -n rabbit1 set_user_tags admin administrator
rabbitmqctl -n rabbit1 change_password admin 123456
11.3 集群管理(独立命令,了解即可)
rabbitmqctl join_cluster {cluster_node} [–ram]
将节点加入指定集群中。在这个命令执行前需要停止RabbitMQ应用并重置节点。
rabbitmqctl cluster_status
显示集群的状态。
rabbitmqctl change_cluster_node_type {disc|ram}
修改集群节点的类型。在这个命令执行前需要停止RabbitMQ应用。
rabbitmqctl forget_cluster_node [–offline]
将节点从集群中删除,允许离线执行。
rabbitmqctl update_cluster_nodes {clusternode}
在集群中的节点应用启动前咨询clusternode节点的最新信息,并更新相应的集群信息。这个和join_cluster不同,它不加入集群。考虑这样一种情况,节点A和节点B都在集群中,当节点A离线了,节点C又和节点B组成了一个集群,然后节点B又离开了集群,当A醒来的时候,它会尝试联系节点B,但是这样会失败,因为节点B已经不在集群中了。rabbitmqctl update_cluster_nodes -n A C可以解决这种场景。
rabbitmqctl cancel_sync_queue [-p vhost] {queue}
取消队列queue同步镜像的操作。
rabbitmqctl set_cluster_name {name}
设置集群名称。集群名称在客户端连接时会通报给客户端。Federation和Shovel插件也会有用到集群名称的地方。集群名称默认是集群中第一个节点的名称,通过这个命令可以重新设置。
11.4 RabbitMQ镜像集群配置
上面已经完成RabbitMQ默认集群模式,但并不保证队列的高可用性,尽管交换机、绑定这些可以复制到集群里的任何一个节点,但是队列内容不会复制。虽然该模式解决一项目组节点压力,但队列节点宕机直接导致该队列无法应用,只能等待重启,所以要想在队列节点宕机或故障也能正常应用,就要复制队列内容到集群里的每个节点,必须要创建镜像队列。
镜像队列是基于普通的集群模式的,然后再添加一些策略,所以你还是得先配置普通集群,然后才能设置镜像队列,我们就以上面的集群接着做。
设置的镜像队列可以通过开启的网页的管理端Admin->Policies,也可以通过命令。
· Name:策略名称
· Pattern:匹配的规则,如果是匹配所有的队列,是^.
· Definition:使用ha-mode模式中的all,也就是同步所有匹配的队列。问号链接帮助文档。
11.5 负载均衡-HAProxy
HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费、快速并且可靠的一种解决方案,包括Twitter,Reddit,StackOverflow,GitHub在内的多家知名互联网公司在使用。HAProxy实现了一种事件驱动、单一进程模型,此模型支持非常大的并发连接数。
https://www.haproxy.org/
11.5.1 安装HAProxy
//下载依赖包
yum install gcc vim wget
//上传haproxy源码包; -C解压到指定的目录
tar -zxvf haproxy-2.3.14.tar.gz -C /usr/local
//进入目录、进行编译、安装
cd /usr/local/haproxy-2.3.14
// make 表示编译;TARGET=linux31 表示CentOS7系统;
PREFIX=/usr/local/haproxy指定安装路径
// TARGET=linux310,内核版本,使用uname -r查看内核,如:3.10.0-514.el7,此时该参数就为linux310;
make TARGET=linux310 PREFIX=/usr/local/haproxy
make install PREFIX=/usr/local/haproxy
mkdir /etc/haproxy
//添加用户组:-r 创建一个系统组;-g 组ID
groupadd -r -g 149 haproxy
//添加用户:-g 新账户组的名称;-r 创建一个系统用户;-s 新用户的登录shell; -u 新账户的用户ID
useradd -g haproxy -r -s /sbin/nologin -u 149 haproxy
//创建haproxy配置文件
vim /etc/haproxy/haproxy.cfg
11.5.2 配置HAProxy
配置文件路径:/etc/haproxy/haproxy.cfg
#全局配置
global
#日志输出配置,所有日志都记录在本机,通过local0输出
log 127.0.0.1 local0 info
#最大连接数
maxconn 5120
#改变当前的工作目录
chroot /usr/local/haproxy
#以指定的UID运行haproxy进程
uid 99
#以指定的GID运行haproxy进程
gid 99
#以守护进程方式运行haproxy
daemon
quiet
nbproc 20
#当前进程PID文件
pidfile /var/run/haproxy.pid
#默认配置
defaults
#应用全局的日志配置
log global
#默认的模式mode{tcp|http|health}
mode tcp
#日志类别
option tcplog
#不记录检查检查日志信息
option dontlognull
#3次失败则认为服务不可用
retries 3
option redispatch
#每个进程可用的最大连接数
maxconn 2000
#绑定配置
listen rabbitmq_cluster
bind *:5677
#配置TCP模式
mode tcp
#balance url_param userid
#balance url_param session_id check_post 64
#balance hdr(User-Agent)
#balance hdr(host)
#balance hdr(Host) use_domain_only
#balance rdp-cookie
#balance leastconn
#balance source //ip
#简单的轮询
balance roundrobin
#server rabbit1 定义服务内部标识,
#127.0.0.1:5672 服务连接IP和端口,
#check inter 5000 定义每隔多少毫秒检查服务是否可用,
#rise 2 服务故障后需要多少次检查才能被再次确认可用,
#fall 2 经历多次失败的检查检查后,haproxy才会停止使用此服务
#weight 1 定义服务权重
server rabbit1 192.168.137.118:5672 check inter 5000 rise 2 fall 2 weight 1
server rabbit2 192.168.137.118:5673 check inter 5000 rise 2 fall 2 weight 1
server rabbit3 192.168.137.118:5674 check inter 5000 rise 2 fall 2 weight 1
#haproxy监控页面地址
listen stats
bind 192.168.137.118:8100
mode http
option httplog
stats enable
stats uri /rabbitmq-stats
stats refresh 5s
启动HAproxy负载
/usr/local/haproxy/sbin/haproxy -f /etc/haproxy/haproxy.cfg
//查看haproxy进程状态
ps -ef | grep haproxy
访问如下地址对mq节点进行监控
http://192.168.137.118:8100/rabbitmq-stats
springboot yml文件中访问mq集群地址:
spring:
rabbitmq:
host: 192.168.137.118
port: 5677
username: admin
password: 123456
virtual-host: /
#addresses: 192.168.137.118:5672,192.168.137.118:5673,192.168.137.118:5674