RabbitMQ
RabbitMQ
简介
-
实现了AMQP(高级队列协议)
-
Erlang是面向并发的编程语言,充分利用了CPU的性能,延迟特别低
-
支持多种语言通讯:Java,Python...都有相应的API
-
支持海量的插件
安装
- vi docker-compose.yml
- 把rabbitmq的配置写入文件
- vi docker-compose up -d
- curl localhost:5672
图形化页面启动
- sbin目录下执行 ./rabbitmq-plugins enable rabbitmq_management
- 访问15672端口:默认用户名密码 guest
架构:
- Publisher
- Consumer
- virtual host ,默认为“/”
- Exchange
- Queue
Publisher与virtual host建立连接,Publisher创建Channel,
通过Channel发送到virtual host的某一个Exchange上.
Exchange通过路由规则发送到某一个或多个Queue中.
Consumer一样,与virtual host建立连接,创建Channel,从队列中消费消息
通讯方式
RabbitMQ提供的7种通讯方式
- "Hello World!" 为了入门操作
- Work Queues 一个消息被多个消费者消费
- Publish/Subscribe 手动创建交换机 (FANOUT)
- Routing 手动创建交换机 (DIRECT)
- Topics 手动创建交换机 (TOPIC)
- RPC RPC方式
- Publisher Confirms 保证消息可靠性
构建Connection工具类
-
导入依赖:amqp-client
-
构建工具类
import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; public class RabbitMQConnectionUtil { public final static String HOST = "192.168.2.107"; public final static int PORT= 5672; public final static String VITUAL_HOST = "test"; public final static String USERNAME = "lubanrabbitmq"; public final static String PASSWORD = "lubanrabbitmq123456"; public static Connection getConnection() throws Exception { ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost(HOST); connectionFactory.setPort(PORT); connectionFactory.setVirtualHost(VITUAL_HOST); connectionFactory.setUsername(USERNAME); connectionFactory.setPassword(PASSWORD); return connectionFactory.newConnection(); } }
1.Hello World
public class Producer {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("zyl",false,false,false,null);
channel.basicPublish("", "zyl", null, "hello".getBytes(StandardCharsets.UTF_8));
System.out.println("消息发布成功");
System.in.read();
}
}
public class Consumer {
public static void main(String[] args) throws Exception {
Connection connection = RabbitMQConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("zyl",false,false,false,null);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("收到消息");
}
};
channel.basicConsume("zyl",true,defaultConsumer);
System.out.println("开始监听");
System.in.read();
}
}
2.Work Queues
一个队列中的消息,只会被一个消费者成功的消费
默认情况下,RabbitMQ的队列会将消息以轮询的方式交给不同的消费者
消费者拿到消息后,需要给生产者一个ack,RabbitMQ认为消费者已经拿到消息了
设置消息的流控,消费者可以尽可能的多消费消息
public class WorkQueueConsumer {
@Test
public void test1() throws Exception {
String queueName = "work";
Connection connection = RabbitMQConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(queueName,false,false,false,null);
channel.basicQos(1);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("Consumer1 收到消息");
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(queueName,true,defaultConsumer);
System.out.println("Consumer1 开始监听");
System.in.read();
}
@Test
public void test2() throws Exception {
String queueName = "work";
Connection connection = RabbitMQConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(queueName,false,false,false,null);
channel.basicQos(1);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Consumer2 收到消息");
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(queueName,false,defaultConsumer);
System.out.println("Consumer2 开始监听");
System.in.read();
}
}
3.Publish/Subcribe 发布订阅模式
Exchange:FANOUT
如何构建一个自定义的交换机,并指定类型是FANOUT,让交换机和多个Queue绑定在一起
public class PubProducer {
@Test
public void publish() throws Exception {
String queueName = "pub1";
String queueName2 = "pub2";
String exchange = "pubExchange";
Connection connection = RabbitMQConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange, BuiltinExchangeType.FANOUT.getType());
channel.queueDeclare(queueName,false,false,false,null);
channel.queueDeclare(queueName2,false,false,false,null);
channel.queueBind(queueName, exchange, "");
channel.queueBind(queueName2, exchange, "");
for (int i = 0; i < 10; i++) {
channel.basicPublish(exchange,"" , null, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
}
System.out.println("消息发布成功");
System.in.read();
}
}
4.Routing
public class RoutingProducer {
@Test
public void publish() throws Exception {
String queueName = "rou1";
String queueName2 = "rou2";
String exchange = "rouExchange";
Connection connection = RabbitMQConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange, BuiltinExchangeType.DIRECT.getType());
channel.queueDeclare(queueName,false,false,false,null);
channel.queueDeclare(queueName2,false,false,false,null);
channel.queueBind(queueName, exchange, "rou1");
channel.queueBind(queueName2, exchange, "rou2");
for (int i = 0; i < 10; i++) {
channel.basicPublish(exchange,"rou1" , null, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
}
for (int i = 0; i < 10; i++) {
channel.basicPublish(exchange,"rou2" , null, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
}
System.out.println("消息发布成功");
System.in.read();
}
}
5.Topic
public class TopicProducer {
@Test
public void publish() throws Exception {
String queueName = "topic1";
String queueName2 = "topic2";
String exchange = "topicExchange";
Connection connection = RabbitMQConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange, BuiltinExchangeType.TOPIC.getType());
channel.queueDeclare(queueName,false,false,false,null);
channel.queueDeclare(queueName2,false,false,false,null);
channel.queueBind(queueName, exchange, "*.A.*");
channel.queueBind(queueName2, exchange, "*.B.*");
channel.queueBind(queueName2, exchange, "C.#");
for (int i = 0; i < 10; i++) {
channel.basicPublish(exchange,"AAA.A.AAA" , null, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
}
for (int i = 0; i < 10; i++) {
channel.basicPublish(exchange,"BBB.B.BBB" , null, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
}
for (int i = 0; i < 10; i++) {
channel.basicPublish(exchange,"C.CC.CCCC.CC.CCC" , null, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
}
System.out.println("消息发布成功");
System.in.read();
}
}
6.RPC(了解)
SpringBoot操作RabbitMQ
声明消息
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置信息
spring.rabbitmq.host=192.168.2.107
spring.rabbitmq.port=5672
spring.rabbitmq.username=lubanrabbitmq
spring.rabbitmq.password=lubanrabbitmq123456
spring.rabbitmq.virtual-host=test
##设置消费者不自动ack
spring.rabbitmq.listener.direct.acknowledge-mode=manual
##每次消费的数量
spring.rabbitmq.listener.direct.prefetch=10
声明交换机,队列
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MQConfig {
private final static String EXCHANGE = "bootExchange";
private final static String QUEUE = "bootQueue";
private final static String ROUTINGKEY = "bootRoutingkey";
@Bean
public Exchange exchange() {
return ExchangeBuilder.topicExchange(EXCHANGE).build();
}
@Bean
public Queue queue() {
return QueueBuilder.durable(QUEUE).build();
}
@Bean
public Binding bind(Exchange exchange,Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with(ROUTINGKEY).noargs();
}
}
生产消息
import com.example.rabbitmq.config.MQConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ProducerTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void publish(){
rabbitTemplate.send(MQConfig.EXCHANGE,"1.zyl",new Message("hello".getBytes()));
}
@Test
public void publishWithProperties(){
rabbitTemplate.convertAndSend(MQConfig.EXCHANGE, "1.zyl", new Message("hello".getBytes()), new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setCorrelationId("234");
return message;
}
});
}
}
消费消息
@Component
public class ConsumerTest {
@RabbitListener(queues = MQConfig.QUEUE )
public void publishWithProperties(String msg, Message message, Channel channel) throws IOException {
System.out.println(msg);
String correlationId = message.getMessageProperties().getCorrelationId();
System.out.println("correlationId "+correlationId);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
}
消息的可靠性保证
1.保证消息一定送达到Exchange
Confirm机制
2.保证消息可以路由到Queue
Return机制
保证Exchange的消息一定到Queue
在发送消息时 将basicPublish方法参数中的mandatory设置为true,当消息没有路由到队列中时,就会执行return回调
3.保证Queue可以持久化消息
DeliveryMode设置消息持久化,为2代表持久化,为1代表不持久化
4.保证消费者可以正常消费消息
手动ack
import com.example.rabbitmq.util.RabbitMQConnectionUtil;
import com.rabbitmq.client.*;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.jms.JmsProperties;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class ConfirmProducer {
@Test
public void publish() throws Exception {
String queueName = "topic1";
String queueName2 = "topic2";
String exchange = "topicExchange";
Connection connection = RabbitMQConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(exchange, BuiltinExchangeType.TOPIC.getType());
channel.queueDeclare(queueName,true,false,false,null);
channel.queueDeclare(queueName2,false,false,false,null);
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long l, boolean b) throws IOException {
System.out.println("消息已经到交换机");
}
@Override
public void handleNack(long l, boolean b) throws IOException {
System.out.println("消息没有到交换机");
}
});
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
System.out.println("消息没有到Queue,可能Queue名不对或其他原因");
}
});
channel.queueBind(queueName, exchange, "*.A.*");
channel.queueBind(queueName2, exchange, "*.B.*");
channel.queueBind(queueName2, exchange, "C.#");
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties();
//设置持久化
AMQP.BasicProperties prop = basicProperties.builder().deliveryMode(JmsProperties.DeliveryMode.PERSISTENT.getValue()).build();
for (int i = 0; i < 10; i++) {
channel.basicPublish(exchange,"AAA.A.AAA" ,true, prop, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
}
for (int i = 0; i < 10; i++) {
channel.basicPublish(exchange,"LL" ,true, prop, (" WorkQueue "+i).getBytes(StandardCharsets.UTF_8));
}
System.out.println("消息发布成功");
System.in.read();
}
}
5.SpringBoot保证消息可靠性
Confirm
-
配置文件开启confirm机制
spring.rabbitmq.publisher-confirm-type=correlated
-
在发送消息时配置rabbitTemplate
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if(ack){
System.out.println("消息已成功发到交换机");
}else{
System.out.println("没有发送到交换机 可以尝试重试!!!");
}
}
});
Return
- 配置文件开启return机制
spring.rabbitmq.publisher-returns=true
在发送消息时配置rabbitTemplate
rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returned) {
String s = new String(returned.getMessage().getBody());
System.out.println("消息"+s+"路由到队列失败,请开启补救措施");
}
});
消息持久化
rabbitTemplate.convertAndSend(MQConfig.EXCHANGE, "1.zyl", new Message("hello".getBytes()), new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);//开启持久化
return message;
}
});
RabbitMQ死信队列&延迟交换机
什么是死信队列?
出现的场景
- 消息被拒绝(nack或reject),并且requeue设置为false(不放回原来的队列了)
- 设置了最长存活时间,在这个最长存活时间到期未被消费
- message设置ttl
- queue设置ttl,整个队列的消息都有ttl
- 超过了队列的最大允许的长度
死信队列的应用
- 在队列已满的情况下,消息也不会丢失
- 实现延迟消费的效果
实现死信队列
准备Exchange&Queue
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DeadLetterConfig {
public static final String NORMAL_EXCHANGE = "normal-exchange";
public static final String NORMAL_QUEUE = "normal-queue";
public static final String NORMAL_ROUTING_KEY = "normal.#";
public static final String DEAD_EXCHANGE = "dead-exchange";
public static final String DEAD_QUEUE = "dead-queue";
public static final String DEAD_ROUTING_KEY = "dead.#";
@Bean
public Exchange normalExchange() {
return ExchangeBuilder.topicExchange(NORMAL_EXCHANGE).build();
}
@Bean
public Queue normalQueue() {
return QueueBuilder.durable(NORMAL_QUEUE).deadLetterExchange(DEAD_EXCHANGE).deadLetterRoutingKey("dead.abc").build();
}
@Bean
public Binding normalBind(Exchange normalExchange, Queue normalQueue) {
return BindingBuilder.bind(normalQueue).to(normalExchange).with(NORMAL_ROUTING_KEY).noargs();
}
@Bean
public Exchange deadExchange() {
return ExchangeBuilder.topicExchange(DEAD_EXCHANGE).build();
}
@Bean
public Queue deadQueue() {
return QueueBuilder.durable(DEAD_QUEUE).build();
}
@Bean
public Binding deadBind(Exchange deadExchange, Queue deadQueue) {
return BindingBuilder.bind(deadQueue).to(deadExchange).with(DEAD_ROUTING_KEY).noargs();
}
}
实现死信队列
消费者拒绝消息
@Test
public void publishLetter() throws IOException {
System.out.println("消息发送成功");
rabbitTemplate.convertAndSend(DeadLetterConfig.NORMAL_EXCHANGE, "normal.11", new Message("hello".getBytes()));
System.in.read();
}
@RabbitListener(queuesToDeclare = @Queue(DeadLetterConfig.NORMAL_QUEUE) )
public void consume(String msg, Message message, Channel channel) throws IOException {
System.out.println("DeadLetterConfig"+ msg);
String correlationId = message.getMessageProperties().getCorrelationId();
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
}
消息的生存时间
- 给消息设置TTL
@Test
public void publishLetterTTl() throws IOException {
rabbitTemplate.convertAndSend(DeadLetterConfig.NORMAL_EXCHANGE, "normal.11", new Message("hello".getBytes()), new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setExpiration("20000");
return message;
}
});
System.in.read();
}
-
给队列设置TTL
@Bean public Queue normalQueue() { return QueueBuilder.durable(NORMAL_QUEUE) .deadLetterExchange(DEAD_EXCHANGE) .deadLetterRoutingKey("dead.abc") .ttl(10000) .build(); }
队列MaxLength
@Bean
public Queue normalQueue() {
return QueueBuilder.durable(NORMAL_QUEUE)
.deadLetterExchange(DEAD_EXCHANGE)
.deadLetterRoutingKey("dead.abc")
.ttl(10000)
.maxLength(2) //多了就近死信队列
.build();
}
延迟交换机
死信队列实现延迟消费时,如果延迟时间比较复杂,比较多.直接使用死信队列需要创建大量的队列设定不同的时间.可以使用延迟交换机来解决这个问题
- 构建延迟交换机
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
@Configuration
public class DelayedConfig {
public static final String DELAY_EXCHANGE = "delay-exchange";
public static final String DELAY_QUEUE = "delay-queue";
public static final String DELAY_ROUTING_KEY = "delay.#";
@Bean
public Exchange delayExchange() {
HashMap<String, Object> map = new HashMap<>();
map.put("x-delayed-type","topic");
CustomExchange customExchange = new CustomExchange(DELAY_EXCHANGE, "x-delayed-message",true,false,map);
return customExchange;
}
@Bean
public Queue delayQueue() {
return QueueBuilder.durable(DELAY_QUEUE).build();
}
@Bean
public Binding normalBind(Exchange delayExchange, Queue delayQueue) {
return BindingBuilder.bind(delayQueue).to(delayExchange).with(DELAY_ROUTING_KEY).noargs();
}
}
-
设置延迟时间
@Test public void delayPublish() throws IOException { rabbitTemplate.convertAndSend(DelayedConfig.DELAY_EXCHANGE, "delay.11", new Message("hello".getBytes()), new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) throws AmqpException { message.getMessageProperties().setDelay(10000);//*** return message; } }); System.in.read(); }
RabbitMQ的集群
- 高可用(避免单点故障问题)
- 提升MQ的效率
搭建RabbitMQ集群
-
准备两台虚拟机(克隆)
-
准备RabbitMQ的yml文件
-
让RabbitMQ服务实现join操作
- rabbitmqctl stop_app
- rabbitmqctl reset
- rabbitmqctl join_cluster rabbit@rabbitmq1
- rabbitmqctl start_app
-
设置镜像模式
在指定的rabbitmq中设置好镜像策略即可
RabbitMQ其他内容
Headers类型Exchange
基于key-value方式,让Exchange和Queue绑定在一起,
想比topic更加灵活
x-match = all
x-match = any
@Configuration
public class HeaderConfig {
public static final String HEADERS_EXCHANGE = "headers-exchange";
public static final String HEADERS_QUEUE = "headers-queue";
@Bean
public Exchange headersExchange() {
return ExchangeBuilder.headersExchange(HEADERS_EXCHANGE).build();
}
@Bean
public Queue headersQueue() {
return QueueBuilder.durable(HEADERS_QUEUE).build();
}
@Bean
public Binding normalBind(Exchange headersExchange, Queue headersQueue) {
HashMap<String, Object> map = new HashMap<>();
map.put("x-match", "all");
map.put("name", "jack");
map.put("age", "12");
return BindingBuilder.bind(headersQueue).to(headersExchange).with("").and(map);
}
}
@Test
public void headersPublish() throws IOException {
MessageProperties messageProperties = new MessageProperties();
messageProperties.setHeader("name", "zyl");
messageProperties.setHeader("age", "13");
rabbitTemplate.convertAndSend(HeaderConfig.HEADERS_EXCHANGE, "delay.11", new Message("hello".getBytes(),messageProperties));
System.in.read();
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战