1.使用java连接RabbitMq,需要配置链接virtaulhost、端口port、用户名userName、密码password、地址host
先安装好rabbitmq,登陆用户名密码默认为guest,创建一个用户,创建一个测试virtualhost,给用户分配virtualhost
pom导入amqp-client包
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.3</version>
</dependency>
public static Connection getConnection(){
ConnectionFactory factory = new ConnectionFactory();
factory.setVirtualHost("/test");
factory.setHost("127.0.0.1");
factory.setUsername("test");
factory.setPassword("test");
factory.setPort(5672);
Connection connection = null;
try {
factory.newConnection();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return connection;
}
@Test
public void getConnection() throws IOException {
Connection connection = ConnectMq.getConnection();
connection.close();
}
执行方法后可以看到界面多了一个连接
2.exchanges有七种类型,官网上指明了每种类型支持的开发语言
刚才创建的virtualhost后也默认创建了七种方式
3.helloworld模式,一个生产者,一个默认交化机,一个queue队列,一个消费者
(1)生产者
public class Publisher {
@Test
public void publish() throws Exception {
//1.获取到链接
Connection connection = ConnectMq.getConnection();
//2.创建信道channel
Channel channel = connection.createChannel();
//3.发布消息,exchange不会把消息实例化到本地,queue才会实例化消息
String mes = "Hello World !";
//参数1:指定exchange,使用默认的“”,
//参数2:指定路由的规则,使用queue名称
//参数3:指定传递的消息所携带的properties,使用null
//参数4:指定发布的具体消息,这是使用byte[]类型
channel.basicPublish("","HelloWorld",null,mes.getBytes());
System.out.println("生产者发布消息成功");
//4.释放资源
channel.close();
connection.close();
}
}
(2)消费者
public class Consumer {
@Test
public void consume() throws Exception {
//1.获取到连接对象
Connection connection = ConnectMq.getConnection();
//2.创建channel
Channel channel = connection.createChannel();
//3.声明队列
//参数1:queue--指明队列的名称
//参数2:durable--当前队列是否需要持久化
//参数3:exclusive--是否排外(conne.close(),当前队列会被自动删除,当前队列只能被一个消费者消费)
//参数4:autoDelete--如果这个队列没有消费者在消费,直接删除
//参数5:arguments --指定当前队列的其它信息
channel.queueDeclare("HelloWorld",true,false,false,null);
//4.开启监听QUEUE
DefaultConsumer consume = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息:"+new String(body,"UTF-8"));
}
};
//参数1:queue--指定消费哪个队列
//参数2:autoAck--指定是否自动ACK(true,接收到消息,会立即告诉Rabbitmq)
//参数3:Consumer--指定消费回调
channel.basicConsume("HelloWorld",true,consume);
System.out.println("消费者开始监听消息!");
System.in.read();
//5.释放资源
channel.close();
connection.close();
}
}
启动控制台生产者打印消息
消费者打打印消息
空控制台的queue队列有波峰
4.work queue模式,一个生产者,一个默认交化机,一个queue队列,两个消费者
(1)生产者
public class Publisher {
@Test
public void publish() throws Exception {
//1.获取到链接
Connection connection = ConnectMq.getConnection();
//2.创建信道channel
Channel channel = connection.createChannel();
//3.发布消息,exchange不会把消息实例化到本地,queue才会实例化消息
for (int i = 0; i < 10;i++){
String mes = "Hello World !"+i;
channel.basicPublish("","work",null,mes.getBytes());
}
System.out.println("生产者发布消息成功");
//4.释放资源
channel.close();
connection.close();
}
}
(2)消费者
public class Consumer1 {
@Test
public void consume() throws Exception {
//1.获取到连接对象
Connection connection = ConnectMq.getConnection();
//2.创建channel
Channel channel = connection.createChannel();
//3.声明队列
//参数1:queue--指明队列的名称
//参数2:durable--当前队列是否需要持久化
//参数3:exclusive--是否排外(conne.close(),当前队列会被自动删除,当前队列只能被一个消费者消费)
//参数4:autoDelete--如果这个队列没有消费者在消费,直接删除
//参数5:arguments --指定当前队列的其它信息
channel.queueDeclare("work",true,false,false,null);
//4.开启监听QUEUE
DefaultConsumer consume = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1接收到消息:"+new String(body,"UTF-8"));
}
};
//参数1:queue--指定消费哪个队列
//参数2:autoAck--指定是否自动ACK(true,接收到消息,会立即告诉Rabbitmq)
//参数3:Consumer--指定消费回调
channel.basicConsume("work",true,consume);
System.out.println("消费者1开始监听消息!");
System.in.read();
//5.释放资源
channel.close();
connection.close();
}
}
public class Consumer2 {
@Test
public void consume() throws Exception {
//1.获取到连接对象
Connection connection = ConnectMq.getConnection();
//2.创建channel
Channel channel = connection.createChannel();
//3.声明队列
//参数1:queue--指明队列的名称
//参数2:durable--当前队列是否需要持久化
//参数3:exclusive--是否排外(conne.close(),当前队列会被自动删除,当前队列只能被一个消费者消费)
//参数4:autoDelete--如果这个队列没有消费者在消费,直接删除
//参数5:arguments --指定当前队列的其它信息
channel.queueDeclare("work",true,false,false,null);
//4.开启监听QUEUE
DefaultConsumer consume = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2接收到消息:"+new String(body,"UTF-8"));
}
};
//参数1:queue--指定消费哪个队列
//参数2:autoAck--指定是否自动ACK(true,接收到消息,会立即告诉Rabbitmq)
//参数3:Consumer--指定消费回调
channel.basicConsume("work",true,consume);
System.out.println("消费者2开始监听消息!");
System.in.read();
//5.释放资源
channel.close();
connection.close();
}
}
控制台消息:两个消费者均匀的消费信息
(3)修改:给每个消费者指定每次消费的能力qos和手动ack,根据自己的能力去消费消息,而不是默认情况由RabbitMq评价分配了
public class Consumer1 {
@Test
public void consume() throws Exception {
//1.获取到连接对象
Connection connection = ConnectMq.getConnection();
//2.创建channel
Channel channel = connection.createChannel();
//3.声明队列
//参数1:queue--指明队列的名称
//参数2:durable--当前队列是否需要持久化
//参数3:exclusive--是否排外(conne.close(),当前队列会被自动删除,当前队列只能被一个消费者消费)
//参数4:autoDelete--如果这个队列没有消费者在消费,直接删除
//参数5:arguments --指定当前队列的其它信息
channel.queueDeclare("work",true,false,false,null);
//指定当前消费者一次消费多少个消息
channel.basicQos(1);
//4.开启监听QUEUE
DefaultConsumer consume = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者1接收到消息:"+new String(body,"UTF-8"));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//参数1:queue--指定消费哪个队列
//参数2:autoAck--指定是否自动ACK(true,接收到消息,会立即告诉Rabbitmq)
//参数3:Consumer--指定消费回调
channel.basicConsume("work",false,consume);
System.out.println("消费者1开始监听消息!");
System.in.read();
//5.释放资源
channel.close();
connection.close();
}
}
public class Consumer2 {
@Test
public void consume() throws Exception {
//1.获取到连接对象
Connection connection = ConnectMq.getConnection();
//2.创建channel
Channel channel = connection.createChannel();
//3.声明队列
//参数1:queue--指明队列的名称
//参数2:durable--当前队列是否需要持久化
//参数3:exclusive--是否排外(conne.close(),当前队列会被自动删除,当前队列只能被一个消费者消费)
//参数4:autoDelete--如果这个队列没有消费者在消费,直接删除
//参数5:arguments --指定当前队列的其它信息
channel.queueDeclare("work",true,false,false,null);
//指定当前消费者一次消费多少个消息
channel.basicQos(1);
//4.开启监听QUEUE
DefaultConsumer consume = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者2接收到消息:"+new String(body,"UTF-8"));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//参数1:queue--指定消费哪个队列
//参数2:autoAck--指定是否自动ACK(true,接收到消息,会立即告诉Rabbitmq)
//参数3:Consumer--指定消费回调
channel.basicConsume("work",false,consume);
System.out.println("消费者2开始监听消息!");
System.in.read();
//5.释放资源
channel.close();
connection.close();
}
}
消费结果
5.publish/Subscribe模式,一个生产者,一个交化机,两个queue队列,两个消费者
声明一个FANOUT类型的exchange,并且将exchange和queue绑定在一起,绑定的方式就是直接绑定
(1)生产者:创建一个exchange,和一个或多个queue绑定在一起
public class Publisher {
public void publish() throws Exception {
//1.获取连接
Connection connection = ConnectionClient.getConnection();
//2.创建chnnel信道
Channel channel = connection.createChannel();
//3.创建exchange --绑定队列,声明一个FONOUT类型的exchange,并且将exchange和queue绑定在一起
//参数1:exchange名称
//参数2:exchange类型 FANOUT --pubsub DIRECT --Routing TOPIC --Topics
channel.exchangeDeclare("exchange-pubsub", BuiltinExchangeType.FANOUT);
channel.queueBind("pubsub-queue1","exchange-pubsub","");
channel.queueBind("pubsub-queue2","exchange-pubsub","");
//4.发布消息到exchange
for (int i = 0;i < 10;i++) {
String mes = "Hello World !" +i;
channel.basicPublish("exchange-pubsub","Work",null,mes.getBytes());
}
//5.释放资源
channel.close();
connection.close();
}
}
(2)消费者:监听某一个队列
public class Consumer1 {
@Test
public void Consume () throws Exception {
//1.获取到链接
Connection connection = ConnectionClient.getConnection();
//2.创建channel
Channel channel = connection.createChannel();
//3.声明队列
channel.queueDeclare("pubsub-queue1",true,false,false,null);
//4.指定当前消费者--指定一次消费多少个
channel.basicQos(1);
//5.开启监听Queue
DefaultConsumer consume = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者1接收到的消息:"+new String(body,"UTF-8"));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//参数1:queue --指定消费哪个队列
//参数2:autoAck -- 指定是否自动ack(true,接收到消息会立即告诉rabbitMq)
//参数3:Consumer -- 消费回调
channel.basicConsume("pubsub-queue1",false,consume);
System.out.println("消费者1开始监听消息");
System.in.read();
//6.释放资源
channel.close();
connection.close();
}
}
public class Consumer2 {
@Test
public void Consume () throws Exception {
//1.获取到链接
Connection connection = ConnectionClient.getConnection();
//2.创建channel
Channel channel = connection.createChannel();
//3.声明队列
channel.queueDeclare("pubsub-queue2",true,false,false,null);
//4.指定当前消费者--指定一次消费多少个
channel.basicQos(1);
//5.开启监听Queue
DefaultConsumer consume = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者1接收到的消息:"+new String(body,"UTF-8"));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//参数1:queue --指定消费哪个队列
//参数2:autoAck -- 指定是否自动ack(true,接收到消息会立即告诉rabbitMq)
//参数3:Consumer -- 消费回调
channel.basicConsume("pubsub-queue2",false,consume);
System.out.println("消费者2开始监听消息");
System.in.read();
//6.释放资源
channel.close();
connection.close();
}
}
消费结果
6.Routing方式:一个生产者,一个exchange,两个queue,两个消费者
创建DIRECT类型的exchange,并且根据RoutingKey绑定指定的队列
(1)生产者:创建DIRECT类型的exchange,绑定对应的队列,在发送消息时,指定消息的具体RoutingKey
public class Publisher {
@Test
public void publish() throws Exception {
//1.获取连接
Connection connection = ConnectionClient.getConnection();
//2.创建chnnel信道
Channel channel = connection.createChannel();
//3.创建exchange --绑定队列,声明一个FONOUT类型的exchange,并且将exchange和queue绑定在一起
//参数1:exchange名称
//参数2:exchange类型 FANOUT --pubsub DIRECT --Routing TOPIC --Topics
channel.exchangeDeclare("exchange-routing", BuiltinExchangeType.DIRECT);
channel.queueBind("routing-queue-error","exchange-routing","ERROR");
channel.queueBind("routing-queue-info","exchange-routing","INFO");
//4.发布消息到exchange
channel.basicPublish("exchange-routing","ERROR",null,"ERROR".getBytes());
channel.basicPublish("exchange-routing","INFO",null,"INFO1".getBytes());
channel.basicPublish("exchange-routing","INFO",null,"INFO2".getBytes());
channel.basicPublish("exchange-routing","INFO",null,"INFO3".getBytes());
System.out.println("生产者发布消息成功");
//5.释放资源
channel.close();
connection.close();
}
}
(2)消费者:监听自己的队列
public class Consumer1 {
@Test
public void Consume () throws Exception {
//1.获取到链接
Connection connection = ConnectionClient.getConnection();
//2.创建channel
Channel channel = connection.createChannel();
//3.声明队列
channel.queueDeclare("routing-queue-error",true,false,false,null);
//4.指定当前消费者--指定一次消费多少个
channel.basicQos(1);
//5.开启监听Queue
DefaultConsumer consume = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者ERROR接收到的消息:"+new String(body,"UTF-8"));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//参数1:queue --指定消费哪个队列
//参数2:autoAck -- 指定是否自动ack(true,接收到消息会立即告诉rabbitMq)
//参数3:Consumer -- 消费回调
channel.basicConsume("routing-queue-error",false,consume);
System.out.println("消费者1开始监听消息");
System.in.read();
//6.释放资源
channel.close();
connection.close();
}
}
public class Consumer2 {
@Test
public void Consume () throws Exception {
//1.获取到链接
Connection connection = ConnectionClient.getConnection();
//2.创建channel
Channel channel = connection.createChannel();
//3.声明队列
channel.queueDeclare("routing-queue-info",true,false,false,null);
//4.指定当前消费者--指定一次消费多少个
channel.basicQos(1);
//5.开启监听Queue
DefaultConsumer consume = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者INFO接收到的消息:"+new String(body,"UTF-8"));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//参数1:queue --指定消费哪个队列
//参数2:autoAck -- 指定是否自动ack(true,接收到消息会立即告诉rabbitMq)
//参数3:Consumer -- 消费回调
channel.basicConsume("routing-queue-info",false,consume);
System.out.println("消费者2开始监听消息");
System.in.read();
//6.释放资源
channel.close();
connection.close();
}
}
输出结果
7.Topic:一个生产者,一个exchange,两个queue,两个消费者
(1)生产者:创建topic的exchange方式,并且绑定到队列中,绑定可以通过*和#关键字,对指定的RoutingKey内容,编写时注意格式xxx.xxx.xxx,*代表一个xxx,#代表多个xxx,指定具体的RoutingKey内容
public class Publisher {
@Test
public void publish() throws Exception {
//1.获取连接
Connection connection = ConnectionClient.getConnection();
//2.创建chnnel信道
Channel channel = connection.createChannel();
//3.创建exchange --绑定队列,声明一个FONOUT类型的exchange,并且将exchange和queue绑定在一起
//参数1:exchange名称
//参数2:exchange类型 FANOUT --pubsub DIRECT --Routing TOPIC --Topics
channel.exchangeDeclare("exchange-topic", BuiltinExchangeType.TOPIC);
//绑定队列,指明绑定方式
channel.queueBind("topic-queue-1","exchange-topic","*.red.*");
channel.queueBind("topic-queue-2","exchange-topic","fast.#");
channel.queueBind("topic-queue-2","exchange-topic","*.*.rabbit");
//4.发布消息到exchange
channel.basicPublish("exchange-topic","fast.red.cat",null,"快红猫".getBytes());
channel.basicPublish("exchange-topic","slow.black.dog",null,"慢黑构".getBytes());
channel.basicPublish("exchange-topic","fast.white.rabbit",null,"快白兔".getBytes());
System.out.println("生产者发布消息成功");
//5.释放资源
channel.close();
connection.close();
}
}
(2)消费者:监听队列
public class Consumer1 {
@Test
public void Consume () throws Exception {
//1.获取到链接
Connection connection = ConnectionClient.getConnection();
//2.创建channel
Channel channel = connection.createChannel();
//3.声明队列
channel.queueDeclare("topic-queue-1",true,false,false,null);
//4.指定当前消费者--指定一次消费多少个
channel.basicQos(1);
//5.开启监听Queue
DefaultConsumer consume = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("对红色感兴趣接收到的消息:"+new String(body,"UTF-8"));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//参数1:queue --指定消费哪个队列
//参数2:autoAck -- 指定是否自动ack(true,接收到消息会立即告诉rabbitMq)
//参数3:Consumer -- 消费回调
channel.basicConsume("topic-queue-1",false,consume);
System.out.println("消费者1开始监听消息");
System.in.read();
//6.释放资源
channel.close();
connection.close();
}
}
public class Consumer2 {
@Test
public void Consume () throws Exception {
//1.获取到链接
Connection connection = ConnectionClient.getConnection();
//2.创建channel
Channel channel = connection.createChannel();
//3.声明队列
channel.queueDeclare("topic-queue-2",true,false,false,null);
//4.指定当前消费者--指定一次消费多少个
channel.basicQos(1);
//5.开启监听Queue
DefaultConsumer consume = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("对快和兔子感兴趣接收到的消息:"+new String(body,"UTF-8"));
//手动ack
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//参数1:queue --指定消费哪个队列
//参数2:autoAck -- 指定是否自动ack(true,接收到消息会立即告诉rabbitMq)
//参数3:Consumer -- 消费回调
channel.basicConsume("topic-queue-2",false,consume);
System.out.println("消费者2开始监听消息");
System.in.read();
//6.释放资源
channel.close();
connection.close();
}
}
输出结果
8.springboot整合rabbitmq
(1)创建完springboot项目后,在pom.xml文件中添加依赖,application.yml中添加链接信息
<!--spring-boot结合rabbitmq的jar-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: test
password: test
virtual-host: /test
(2)创建一个TopicExchange、Queue,并把他们绑定到一起,此类使用@Configuration修饰,加载到spring容器中,绑定的方法直接从注册bean中获取交换机和队列
@Configuration
public class RabbitMqConfig {
//1.创建exchange topic方式
@Bean
public TopicExchange getTopicExchange(){
return new TopicExchange("boot-topic-change",true,false);
}
//2.创建Queue
@Bean
public Queue getQueue(){
return new Queue("boot-queue",true,false,false,null);
}
//3.绑定在一起
@Bean
public Binding getBinding(TopicExchange topicExchange,Queue queue){
return BindingBuilder.bind(queue).to(topicExchange).with("*.red.*");
}
}
(3)创建一个消费者,使用@Component注解修饰,让spring能够扫描到,使用@RabbitListener监听对应的queue
@Component
public class Consumer {
//使用RabbitListener监听,使用queues配置监听的queue
@RabbitListener(queues = "boot-queue")
public void getMessage(Object object){
System.out.println("监听到队列boot-queue收到的消息"+object);
}
}
(4)发布消息,指明发布到的交互机,以及路由方式,消息信息,使用RabbitTemplate来发送
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
//发送消息:指定发送到哪个交换机下,消息的routingkey,消息内容
rabbitTemplate.convertAndSend("boot-topic-change","slow.red.cat","跑得快的猫");
}
(5)启动springboot项目进行监听,运行发布消息
9.手动ack(告诉rabbitMq消息已经正常消费完了)
(1)application.yml配置文件中添加配置,手动ack,模式是自动ack
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
(2)接收消息的时候,使用channel信道手动ack
//使用RabbitListener监听,使用queues配置监听的queue
@RabbitListener(queues = "boot-queue")
public void getMessage(String msg, Channel channel, Message message) throws IOException {
System.out.println("监听到队列boot-queue收到的消息"+msg);
//使用信道手动ack
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
10.保证消息的可靠性--confirm机制:保证生产者可以将消息发送到exchange
(1)使用事务:事务可以保证消息100%发送,可以通过事务的回滚去记录日志,后面定时再次发送此消息,但是效率太低
(2)使用confirm确认机制,效率比事务要高
①普通confirm方式
//开启confirm
channel.confirmSelect();
//发送消息
String mes = "Hello World !" ;
channel.basicPublish("exchange-pubsub","Work",null,mes.getBytes());
//判断消息是否发送成功
if(channel.waitForConfirms()){
System.out.println("消息发送成功");
}else{
System.out.println("消息发送失败");
}
②批量confirm方式
//开启confirm
channel.confirmSelect();
//批量发送消息
for (int i = 0;i < 10;i++) {
String mes = "Hello World !" +i;
channel.basicPublish("exchange-pubsub","Work",null,mes.getBytes());
}
//判断消息是否发送成功
channel.waitForConfirmsOrDie();//当发送的消息有一个失败,则全部失败,抛出IoException异常
③异步confirm方式
//开启confirm
channel.confirmSelect();
//批量发送消息
for (int i = 0;i < 1000;i++) {
String mes = "Hello World !" +i;
channel.basicPublish("exchange-pubsub","Work",null,mes.getBytes());
}
//异步的方式监听消息发送
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long l, boolean b) throws IOException {
System.out.println("消息发送成功,标识:"+l+",是否批量:"+b);
}
@Override
public void handleNack(long l, boolean b) throws IOException {
System.out.println("消息发送失败,标识:"+l+",是否批量:"+b);
}
});
11.保证消息的可靠性--return机制:保证消息从exchange发送到queue,发送消息时mandatory设置为true
//开启return机制
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
//消息从exchange中发送到queue中失败时,才执行此方法
System.out.println(new String(bytes,"UTF-8")+"消息从exchange到queue发送失败");
}
});
public void basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body) throws IOException {
this.delegate.basicPublish(exchange, routingKey, mandatory, props, body);
}
12.springboot项目实现confirm和return机制
(1)配置文件中进行设置
publisher-confirm-type: simple
publisher-returns: true
(2)写一个confirm和return的配置类,实现RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback接口
/**
* 生产者的确认和返回机制配置类
*/
@Component
public class PublisherConfirmAndReturnConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback{
@Autowired
private RabbitTemplate rabbitTemplate;
//@PostConstruct修饰为构建此类时执行
@PostConstruct
public void initMethed(){
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
}
//confirm确认机制
@Override
public void confirm(CorrelationData correlationData, boolean ack, String s) {
if(ack){
System.out.println("消息成功送到exchange");
}else{
System.out.println("消息没有送到exchange");
}
}
//消息没有送达时执行
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("消息没有从exchange送达到queue"+message);
}
}
13.消息重复消费
消息重复消费会对非幂等性操作造成影响;造成重复消费的原因,消费者没有给rabbitmq一个ack。
为了解决重复消费的问题,可以使用Redis,在消费者消费消息之前,将消息的id放到redis中,id-0:正在执行业务,id-1:消息执行完成
如果ack失败,rabbitmq将消息交个其它消费者时,先执行setnx(如果key存在,什么事情都不做,如果key不存在,正常的set方法);若果key已经存在,获取到它的值,如果是0,当前消费者什么都不做,如果是1,其它消费者已经消费过此消息,直接ack。
极端情况:当消费者在执行业务时,出现了死锁,在setnx的基础上,再给key设置一个生存时间
(1)生产者:传递一个uuid
//发送消息
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.deliveryMode(1) //是否需要持久化消息到队列, 1--需要 2——不需要
.messageId(UUID.randomUUID().toString())
.build();
String mes = "Hello World !";
channel.basicPublish("exchange-pubsub","pubsub-queue4",true,properties,mes.getBytes());
(2)引入redis的包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
(3)消费者:使用redis记录消息的消费情况
//开启监听Queue
DefaultConsumer consume = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//拿到消息id
String messageId = properties.getMessageId();
Jedis jedis = new Jedis("127.0.0.1",6379);
String result = jedis.set(messageId,"0","NX","EX",10);
//1.setnx到redis,value默认0
if(null != result && "OK".equalsIgnoreCase(result)){
System.out.println("消费者1接收到的消息:"+new String(body,"UTF-8"));
//2.消费成功,设置value为1
jedis.set(messageId,"1");
channel.basicAck(envelope.getDeliveryTag(),false);
}else{
//3.若是setnx失败,获取到value值,若值等于0,return什么都不做,若值等于1,直接ack
String s = jedis.get(messageId);
if("1".equals(s)){
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
}
};
14.避免消息重复消费:springboot方式
(1)引入redis的相关jar包
<!--spring-boot结合redis方式-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
(2)编写配置文件
spring:
redis:
host: 127.0.0.1
port: 6379
(3)生产者添加唯一主键参数
@SpringBootTest
class QingyunApplicationTests {
@Autowired
RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
CorrelationData messageId= new CorrelationData(UUID.randomUUID().toString());
//发送消息:指定发送到哪个交换机下,消息的routingkey,消息内容
rabbitTemplate.convertAndSend("boot-topic-change","slow.red.cat","跑得快的猫",messageId);
}
}
(4)消费者结合redis根据唯一主键进行相关操作
@Component
public class Consumer {
//注入redis
@Autowired
private StringRedisTemplate redisTemplate;
//使用RabbitListener监听,使用queues配置监听的queue
@RabbitListener(queues = "boot-queue")
public void getMessage(String msg, Channel channel, Message message) throws IOException {
//1.获取messageid
String messageId = message.getMessageProperties().getHeader("spring_returned_message_correlation");
//2.使用redis设置value默认为0
if(redisTemplate.opsForValue().setIfAbsent(messageId,"0",10, TimeUnit.SECONDS)){
//3.消费消息
System.out.println("监听到队列boot-queue收到的消息"+msg);
//4.设置value为1
redisTemplate.opsForValue().set(messageId,"1");
//5.手动ack
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} else{
//若是redis设置值失败,获取值,根据值判断,为1则手动ack,为其它情况不做任何处理
if("1".equalsIgnoreCase(redisTemplate.opsForValue().get(messageId))){
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
}
//使用信道手动ack
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
}
15.channel信道
无论是生产者还是消费者,都需要和 RabbitMQ Broker 建立连接,这个连接就是一条 TCP 连接,也就是 Connection;一旦 TCP 连接建立起来,客户端紧接着可以创建一个 AMQP 信道(Channel),每个信道都会被指派一个唯一的 ID;信道是建立在 Connection 之上的虚拟连接,RabbitMQ 处理的每条 AMQP 指令都是通过信道完成的。
试想这样一个场景,一个应用程序中有很多个线程需要从 RabbitMQ 中消费消息,或者生产消息,那么必然需要建立很多个 Connection,也就是多个 TCP 连接。
然而对于操作系统而言,建立和销毁 TCP 连接是非常昂贵的开销,如果遇到使用高峰,性能瓶颈也随之显现。
RabbitMQ 采用类似 NIO(Non-blocking I/O)的做法,选择 TCP 连接复用,不仅可以减少性能开销,同时也便于管理。
每个线程把持一个信道,所以信道复用了 Connection 的 TCP 连接。同时 RabbitMQ 可以确保每个线程的私密性,就像拥有独立的连接一样。当每个信道的流量不是很大时,复用单一的 Connection 可以在产生性能瓶颈的情况下有效地节省 TCP 连接资源。但是信道本身的流量很大时,这时候多个信道复用一个 Connection 就会产生性能瓶颈,进而使整体的流量被限制了。此时就需要开辟多个 Connection,将这些信道均摊到这些 Connection 中,至于这些相关的调优策略需要根据业务自身的实际情况进行调节。
16.RabbitMQ 的4种集群架构
(1)主备模式
也称为 Warren (兔子窝) 模式。实现 rabbitMQ 的高可用集群,一般在并发和数据量不高的情况下,这种模式非常的好用且简单。
也就是一个主/备方案,主节点提供读写,备用节点不提供读写。如果主节点挂了,就切换到备用节点,原来的备用节点升级为主节点提供读写服务,当原来的主节点恢复运行后,原来的主节点就变成备用节点,和 activeMQ 利用 zookeeper 做主/备一样,也可以一主多备。
(2)远程模式
远程模式可以实现双活的一种模式,简称 shovel 模式,所谓的 shovel 就是把消息进行不同数据中心的复制工作,可以跨地域的让两个 MQ 集群互联,远距离通信和复制。
有两个异地的 MQ 集群(可以是更多的集群),当用户在地区 1 这里下单了,系统发消息到 1 区的 MQ 服务器,发现 MQ 服务已超过设定的阈值,负载过高,这条消息就会被转到 地区 2 的 MQ 服务器上,由 2 区的去执行后面的业务逻辑,相当于分摊我们的服务压力。
(3)镜像模式
非常经典的 mirror 镜像模式,保证 100% 数据不丢失。
用 KeepAlived 做了 HA-Proxy 的高可用,然后有 3 个节点的 MQ 服务,消息发送到主节点上,主节点通过 mirror 队列把数据同步到其他的 MQ 节点,这样来实现其高可靠。
(4)多活模式
也是实现异地数据复制的主流模式,因为 shovel 模式配置比较复杂,所以一般来说,实现异地集群的都是采用这种双活 或者 多活模型来实现的。这种模式需要依赖 rabbitMQ 的 federation 插件,可以实现持续的,可靠的 AMQP 数据通信,多活模式在实际配置与应用非常的简单。
17.rabbitmq如何保证消费的顺序
(1)发送消息有序:自己确保是有序的,集群化部署,可以通过分布式锁确保消息有序,例如根据订单id加锁;相同类型的数据投递到同一个队列中。
存储有序:队列中的数据是FIFO,先进先出,只要保证了投递的顺序,存储是有序的。
消费有序:一个队列对应一个消费者,并且一个消费者一个Channel,使用同一个线程消费,不能并发消费。
(2)Rabbitmq解决方案:x-single-active-consumer(true或false):单活模式(队列中配置),表示是否最多只允许一个消费者消费,如果有多个消费者同时绑定,则只会激活第一个,除非第一个消费者被取消或者死亡,才会自动转到下一个消费者。
18.mq如何解决消息积压
消息消费的速度,小于消息生产的速度时,会产出消息积压。
解决方案:
(1)检查消费端是否正常消费消息
(2)增加消费端数量
(3)增加消费端并行度,使用多线程、线程池并发处理消息
(4)消费端设置合理的超时时间,并在超时后对消息进行重新处理后者补偿操作
(5)扩容消息队列,增加消息队列节点数
(6)调整消息处理的优先级,优先处理重要紧急的消息
(7)消息发送端流控
19.rabbitmq我们项目中具体的运用
(1)创建项目时,根据选中指标生成决策树
(2)编辑指标值时,重新生成组合解释信息
(3)登陆系统日志信息记录
(4)增删改日志记录
crms项目-生产者模块
①pom.xml中引入rabbitmq
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
②application.properties配置信息
#Rabbitmq相关配置
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=test
spring.rabbitmq.password=test
spring.rabbitmq.virtual-host=/test
③声明交换机、队列并绑定的配置类
@Configuration
public class RabbitMqConfig {
@Bean
public TopicExchange topicExchange() {
return new TopicExchange("crms-logging-exchange",true,false);
}
@Bean
public Queue queue() {
return new Queue("crms-logging-queue");
}
@Bean
public Binding bind(Queue queue,TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("crms.logging.*");
}
}
④业务代码发送消息
@Autowired
private RabbitTemplate rabbitTemplate;
//使用rabbitmq发送消息到交换机
FwRightRole fwRightRole = new FwRightRole();
fwRightRole.setId(this.getUUID());
fwRightRole.setRemark("这是删除日志呀");
fwRightRole.setCreateTime(new Date());
fwRightRole.setCreateUserCode(this.getUserCode());
rabbitTemplate.convertAndSend("crms-logging-exchange", "crms.logging.delete", JSONObject.toJSONString(fwRightRole));
ctms项目-消费者
①pom.xml中引入rabbitmq
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
②application.properties配置信息
#Rabbitmq相关配置
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=test
spring.rabbitmq.password=test
spring.rabbitmq.virtual-host=/test
spring.rabbitmq.listener.simple.acknowledge-mode=manual
③声明交换机、队列并绑定的配置类
@Configuration
public class RabbitMqConfig {
@Bean
public TopicExchange topicExchange() {
return new TopicExchange("crms-logging-exchange",true,false);
}
@Bean
public Queue queue() {
return new Queue("crms-logging-queue");
}
@Bean
public Binding bind(Queue queue,TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("crms.logging.*");
}
}
④业务代码消费消息
@Component
public class CrmsLoggingListener {
@RabbitListener(queues="crms-logging-queue")
public void getMessage(String msg,Channel channel,Message message) throws IOException {
//1.获取routingkey
String receivedRoutingKey = message.getMessageProperties().getReceivedRoutingKey();
//2.使用switch判读是否为删除,
switch(receivedRoutingKey) {
case "crms.logging.delete" :
//3.插入日志记录里面
System.out.println(msg);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
break;
}
}
}