先上两个rabbitmq图
安装
docker run -it --hostname rabbitmq --name mq1 -p 5672:5672 -p 15672:15672 rabbitmq:3-management
其他参数:
- –hostname 容器主机名
- –name 容器名
- –link 该容器联合其他容器使用 例如:–link rabbitmq:mq1
- -e 附加参数 例如:-e RABBITMQ_ERLANG_COOKIE=‘rabbitcookie’
- –net host 使用宿主机网络,此时设置映射端口无效,直接使用宿主机IP和端口
实操
-
yaml配置
rabbitmq: host: 192.168.38.128 port: 5672 username: guest password: guest virtual-host: / #确认消息已发送到交换机(Exchange) publisher-confirms: true #确认消息已发送到队列(Queue) publisher-returns: true listener: direct: #全局direct开启手动确认模式 acknowledge-mode: manual template: retry: enabled: true #重试3次 max-attempts: 3 # 间隔一秒 initial-interval: 2000
-
pom配置
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
-
生产者 配置类
package com.zq.lhkj.rabbitmq;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RabbitMQConfig {
/**正常交换机名称*/
public static final String EXCHANGE_NAME = "boot_topic_exchange";
/**
* 正常队列
*/
public static final String QUEUE_NAME = "boot_queue_zc";
/**
* 死信队列
*/
public static final String DLX_Q = "dlx_q";
/**
* 死信key
*/
public static String DXLKEY = "boot3.haha";
/**
* 死信的交换器
*/
public static String PROCESS_EXCHANGE = "dlx.exchange";
//1.交换机
@Bean("bootExchange")
public Exchange bootExchange() {
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
//2.Queue 队列
@Bean("bootQueue")
public Queue bootQueue() {
Map<String, Object> args = new HashMap<>(2);
// x-dead-letter-exchange 声明 死信队列Exchange
args.put("x-dead-letter-exchange", PROCESS_EXCHANGE);
// x-dead-letter-routing-key 声明 死信队列抛出异常重定向队列的routingKey(TKEY_R)
args.put("x-dead-letter-routing-key", DXLKEY);
args.put("x-message-ttl", 15000);
Queue queue = QueueBuilder.durable(QUEUE_NAME)
// DLX,dead letter发送到的exchange ,设置死信队列交换器到处理交换器
.withArguments(args)
.build();
return queue;
}
/**
* 配置处理交换器
*
* @return
*/
@Bean("processExchange")
public Exchange processExchange() {
return ExchangeBuilder.topicExchange(PROCESS_EXCHANGE).durable(true).build();
}
@Bean("bootQueue2")
public Queue bootQueue2() {
Queue queue = QueueBuilder.durable(DLX_Q).build();
return queue;
}
//3. 队列和交互机绑定关系 Binding
/*
1. 知道哪个队列
2. 知道哪个交换机
3. routing key
*/
@Bean
public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();
}
@Bean
public Binding bindQueueExchange2(@Qualifier("bootQueue2") Queue queue, @Qualifier("processExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("boot3.#").noargs();
}
/**
* 解决 同一template不能设置多个回调的问题
* @param connectionFactory
* @return
*/
@Bean
@Scope("prototype")
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMandatory(true);
template.setMessageConverter(new SerializerMessageConverter());
//2. 定义回调
template.setConfirmCallback(new Callback());
return template;
}
}
- 开启某个队列 手动确认
@Configuration
public class MessageListenerConfig {
@Autowired
private CachingConnectionFactory connectionFactory;
@Autowired
private RabbimtMQListener myAckReceiver;//消息接收处理类
@Bean
public SimpleMessageListenerContainer simpleMessageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // RabbitMQ默认是自动确认,这里改为手动确认消息
//设置一个队列
container.setQueueNames(RabbitMQConfig.QUEUE_NAME);
// 每次取多少个
container.setPrefetchCount(1);
//如果同时设置多个如下: 前提是队列都是必须已经创建存在的
// container.setQueueNames("TestDirectQueue","TestDirectQueue2","TestDirectQueue3");
//另一种设置队列的方法,如果使用这种情况,那么要设置多个,就使用addQueues
//container.setQueues(new Queue("TestDirectQueue",true));
//container.addQueues(new Queue("TestDirectQueue2",true));
//container.addQueues(new Queue("TestDirectQueue3",true));
container.setMessageListener(myAckReceiver);
return container;
}
}
ack回调
public class Callback implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("ConfirmCallback: " + "相关数据:" + correlationData);
System.out.println("ConfirmCallback: " + "确认情况:" + ack);
System.out.println("ConfirmCallback: " + "原因:" + cause);
if (ack) {
System.out.println("消息发送确认成功");
} else {
System.out.println("消息发送确认失败");
}
}
/**
* 只有失败的时候 才会回调
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
//失败的回调
try {
System.out.println("ReturnCallback: " + "消息:" + new String(message.getBody(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
System.out.println("ReturnCallback: " + "回应码:" + replyCode);
System.out.println("ReturnCallback: " + "回应信息:" + replyText);
System.out.println("ReturnCallback: " + "交换机:" + exchange);
System.out.println("ReturnCallback: " + "路由键:" + routingKey);
}
}
- 消费者 消息监听
@Component
public class RabbimtMQListener implements ChannelAwareMessageListener {
/*开启手动确认模式*/
// @Bean
// @ConditionalOnClass
// public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(CachingConnectionFactory connectionFactory) {
// SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
// factory.setConnectionFactory(connectionFactory);
// factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
// return factory;
// }
// @RabbitListener(queues = "test_q")
// public void ListenerQueue(Message message){
// //System.out.println(message);
// System.out.println(new String(message.getBody()));
//
// }
@Override
public void onMessage(Message message, Channel channel) throws Exception {
System.out.println(new String(message.getBody())+";;;;");
//不需要手动确认时 该参数为null
long deliveryTag = message.getMessageProperties().getDeliveryTag();
//第三个参数false时,不再放入队列,true时 会再次进入对接执行。
channel.basicNack(deliveryTag,false,false);
}
}
生产者发布消息
@RestController
@RequestMapping("/rabbitmq")
public class rabbitmqController {
@Autowired
private RabbitTemplate rabbitTemplate;
// 消息后处理对象,设置一些消息的参数信息
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//1.设置message的信息
message.getMessageProperties().setExpiration("10000");//消息的过期时间
//2.返回该消息
return message;
}
};
@GetMapping("/send")
public void login(String exchange,String topic, String msg) {
//3. 发送消息
rabbitTemplate.convertAndSend(exchange,topic,msg,messagePostProcessor);
//convertAndSend(String exchange, String routingKey, Object message, MessagePostProcessor messagePostProcessor, @Nullable CorrelationData correlationData)
//CorrelationData 里面可以放一个id 可以标识消息的唯一性,业务场景可能使用到
}
}
坑点与注意点:
-
当一个队列创建完后,无法再次修改参数,比如修改ttl等,此时可以使用policy策略实现。
-
进入死信队列的情况包括:
1.队列长度达到长度限制 x-max-length
2.手动确认时,消费者拒接消息时。basicNack或者basicReject并且不把消息重新放入对接时,第三个参数为false。
3.当消息设置过期时间,到期未消费者。 -
日志与监控
-
消息追踪–测试开发使用,因开启大大影响性能,只限调试使用
firehose

- rabbitmq-plugins list
- rabbitmq-plugins enable rabbitmq_tracing启用插件
-
官方也有个延迟队列的插件 rabbitmq_delayed_message_exchange
安装文档:https://www.cnblogs.com/cyleon/p/10450040.html -
路由key: *.hh.## *的含义是前面有1个单词, #是0或者多个单词
-
消息补偿
-
幂等(乐观锁)
集群(docker)
- 创建多个RabbitMQ容器
第一个mq
docker run -d --hostname rabbit1 --name myrabbit1 -p 15672:15672 -p 5672:5672 -e RABBITMQ_ERLANG_COOKIE=‘rabbitcookie’ rabbitmq:3.7.8-management
第二个
docker run -d --hostname rabbit2 --name myrabbit2 -p 5673:5672 -p 15673:15672 --link myrabbit1:rabbit1 -e RABBITMQ_ERLANG_COOKIE=‘rabbitcookie’ rabbitmq:3.7.8-management
注意点:–link 连接多个容器,使其可以互相访问 ,如果三台,要连接前面2个容器
RABBITMQ_ERLANG_COOKIE参数,多个mq容器 访问秘钥一定要一样
- 主节点
docker exec -it myrabbit1 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
exit
从节点:
docker exec -it myrabbit2 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbit1
rabbitmqctl start_app
exit
注意点:-ram 表示设置为内存节点,忽略该参数则默认为磁盘节点。
使用http://ip:15672 进行访问了,账号密码默认为guest&guest,可见2个节点了。
- 开启镜像同步
完成镜像集群搭建。
HA
- 安装 Ha
docker pull haproxy - 配置文件
global
maxconn 10000 #默认最大连接数
log 127.0.0.1 local0 #[err warning info debug]
chroot /usr/local/sbin #chroot运行的路径
daemon #以后台形式运行haproxy
pidfile /var/run/haproxy.pid #haproxy的pid存放路径,启动进程的用户必须有权限访问此文件
defaults
log 127.0.0.1 local3
mode http #所处理的类别 (#7层 http;4层tcp )
maxconn 10000 #最大连接数
option dontlognull #不记录健康检查的日志信息
option redispatch #serverId对应的服务器挂掉后,强制定向到其他健康的服务器
#stats refresh 30 #统计页面刷新间隔
retries 3 #3次连接失败就认为服务不可用,也可以通过后面设置
balance roundrobin #默认的负载均衡的方式,轮询方式
#balance source #默认的负载均衡的方式,类似nginx的ip_hash
#balance leastconn #默认的负载均衡的方式,最小连接
timeout connect 5000 #连接超时
timeout client 50000 #客户端超时
timeout server 50000 #服务器超时
timeout check 2000 #心跳检测超时
####################################################################
listen http_front
bind 0.0.0.0:5669 #监听端口
stats refresh 30s #统计页面自动刷新时间
stats uri /haproxy?stats #统计页面url
stats realm Haproxy Manager #统计页面密码框上提示文本
stats auth admin:admin #统计页面用户名和密码设置
#stats hide-version #隐藏统计页面上HAProxy的版本信息
#####################我把RabbitMQ的管理界面也放在HAProxy后面了###############################
listen rabbitmq_admin
bind 0.0.0.0:5671
listen rabbitmq_cluster
bind 0.0.0.0:5670
option tcplog
mode tcp
option tcplog
mode tcp
timeout client 3h
timeout server 3h
option clitcpka
balance roundrobin #负载均衡算法(#banlance roundrobin 轮询,balance source 保存session值,支持static-rr,leastconn,first,uri等参数)
#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
server rabbitmq2 192.168.38.128:5672 check inter 5s rise 2 fall 3 #check inter 2000 是检测心跳频率,rise 2是2次正确认为服务器可用,fall 3是3次失败认为服务器不可用
server rabbitmq1 192.168.38.128:5673 check inter 5s rise 2 fall 3
- HA状态管理
http://192.168.38.128:5669/haproxy?stats
- mq后台管理页面
http://192.168.38.128:5671/#/queues